2 * buildrun.c: wait to run one command until an instance of another
3 * completes successfully
11 #include <sys/types.h>
18 * Implementation strategy
19 * -----------------------
21 * The basic plan for making buildrun -r wait for buildrun -w is that
22 * buildrun -w creates a named pipe, opens it for writing, and does
23 * not actually write to it; then buildrun -r opens the same named
24 * pipe for reading. When buildrun -w terminates, anything which was
25 * trying to read from the pipe receives EOF, so buildrun -r can wake
26 * up and look to see if the run was successful.
28 * buildrun -w's report of success or failure is done by the very
29 * simple approach of deleting the pipe file before terminating, if
30 * the run was successful. So after buildrun -r receives EOF on the
31 * pipe, it immediately tries to open it again - and if it gets
32 * ENOENT, that's the success condition. Otherwise it reopens the pipe
33 * and blocks again until another buildrun -w succeeds.
35 * If the run was unsuccessful, buildrun -r must block immediately and
36 * stay blocked until buildrun -w next runs. This is achieved
37 * automatically by the above strategy, since attempting to open a
38 * pipe for reading when nothing is trying to write it will cause the
39 * open(2) call to block until something opens the pipe for writing.
41 * Likewise, if the last buildrun -w was unsuccessful and no -w run is
42 * active at the moment buildrun -r starts in the first place,
43 * buildrun -r must block until one starts up - and again, this is
44 * done automatically by the attempt to open the pipe for reading.
46 * Of course, the blocking open behaviour of named pipes works both
47 * ways: if buildrun -w tried to open the pipe for writing when no
48 * buildrun -r is trying to read it, _it_ would block. For that
49 * reason, buildrun -w actually opens with O_RDWR, so it can't block.
51 * Just in case the user tries to run two buildrun -w instances on the
52 * same control file, we also flock(LOCK_EX) the named pipe once we've
53 * opened it, so that the second -w instance can cleanly report the
56 * Finally, there is a small race condition in the mechanism I've just
57 * described. Suppose one buildrun -w instance finishes and deletes
58 * its pipe file - but before buildrun -r manages to unblock and
59 * notice the file's absence, another buildrun -w starts up and
60 * recreates it. This is a more or less theoretical risk in the
61 * interactive environments where I expect this tool to be used, but
62 * even so, I like to solve these problems where I can; what I do is
63 * to create not just a named pipe, but a _subdirectory_ containing a
64 * named pipe. So a successful buildrun -w deletes the pipe and
65 * removes the containing directory; a subsequent buildrun -w will
66 * create a fresh directory and pipe. Then buildrun -r starts by
67 * setting its _cwd_ to the directory containing the pipe - which
68 * means that if one buildrun -w finishes and another one starts,
69 * buildrun -r will still have its cwd pointing at the orphaned
70 * directory from the first, and won't accidentally find the pipe file
71 * created by the second in its own directory.
74 #define PIPEFILE "pipe"
76 char *ctldir
, *pipepath
, **command
;
83 if (mkdir(ctldir
, 0700) < 0 && errno
!= EEXIST
) {
84 fprintf(stderr
, "buildrun: %s: mkdir: %s\n", ctldir
, strerror(errno
));
87 if (mknod(pipepath
, S_IFIFO
| 0600, 0) < 0 && errno
!= EEXIST
) {
88 fprintf(stderr
, "buildrun: %s: mknod: %s\n", pipepath
,strerror(errno
));
91 fd
= open(pipepath
, O_RDWR
);
93 fprintf(stderr
, "buildrun: %s: open: %s\n", pipepath
, strerror(errno
));
96 if (flock(fd
, LOCK_EX
| LOCK_NB
) < 0) {
97 if (errno
== EAGAIN
) {
98 fprintf(stderr
, "buildrun: another write process "
99 "is already active\n");
101 fprintf(stderr
, "buildrun: %s: flock: %s\n",
102 pipepath
, strerror(errno
));
109 fprintf(stderr
, "buildrun: fork: %s\n", strerror(errno
));
111 } else if (pid
== 0) {
113 execvp(command
[0], command
);
114 fprintf(stderr
, "buildrun: %s: exec: %s\n",
115 command
[0], strerror(errno
));
120 if (waitpid(pid
, &status
, 0) < 0) {
121 fprintf(stderr
, "buildrun: wait: %s\n", strerror(errno
));
124 if (WIFEXITED(status
)) {
125 status
= WEXITSTATUS(status
);
127 } else if (WIFSIGNALED(status
)) {
128 status
= 128 | WTERMSIG(status
);
134 if (remove(pipepath
) < 0) {
135 fprintf(stderr
, "buildrun: %s: remove: %s\n", pipepath
,
139 if (rmdir(ctldir
) < 0) {
140 fprintf(stderr
, "buildrun: %s: rmdir: %s\n", ctldir
,
156 * Our strategy against race conditions relies on changing our cwd
157 * to the control dir, but that will confuse the user command if
158 * we exec one afterwards. So we do most of our work in a
159 * subprocess, and don't pollute our main process context.
163 fprintf(stderr
, "buildrun: fork: %s\n", strerror(errno
));
165 } else if (pid
== 0) {
169 if (chdir(ctldir
) < 0) {
170 if (errno
== ENOENT
) {
172 * If the control directory doesn't exist at all, it
173 * must be because the last -w run completed
174 * successfully, so we immediately return success.
178 fprintf(stderr
, "buildrun: %s: chdir: %s\n",
179 ctldir
, strerror(errno
));
183 fd
= open(PIPEFILE
, O_RDONLY
);
186 return 0; /* success! */
188 fprintf(stderr
, "buildrun: %s: open: %s\n",
189 pipepath
, strerror(errno
));
193 ret
= read(fd
, buf
, 1);
195 fprintf(stderr
, "buildrun: %s: read: %s\n",
196 pipepath
, strerror(errno
));
198 } else if (ret
> 0) {
199 fprintf(stderr
, "buildrun: %s: read: unexpectedly received "
200 "some data!\n", pipepath
, strerror(errno
));
208 if (waitpid(pid
, &status
, 0) < 0) {
209 fprintf(stderr
, "buildrun: wait: %s\n", strerror(errno
));
212 if (WIFEXITED(status
)) {
213 status
= WEXITSTATUS(status
);
215 } else if (WIFSIGNALED(status
)) {
216 status
= 128 | WTERMSIG(status
);
221 if (status
!= 0) /* something went wrong in child */
225 * In -r mode, running a command is optional; in the absence of
226 * one, we simply return true, so the user can invoke as 'buildrun
227 * -r file && complex shell command'.
232 execvp(command
[0], command
);
233 fprintf(stderr
, "buildrun: %s: exec: %s\n", command
[0], strerror(errno
));
237 const char usagemsg
[] =
238 "usage: buildrun -w <control_dir> <command> [<args>...]\n"
239 " or: buildrun -r <control_dir> [<command> [<args>...]]\n"
240 "where: -w write mode: run a build command\n"
241 " -r read mode: wait for a build command to succeed\n"
242 " <control_dir> directory to use for communication; suggest in /tmp\n"
243 " also: buildrun --version report version number\n"
244 " buildrun --help display this help text\n"
245 " buildrun --licence display the (MIT) licence text\n"
249 fputs(usagemsg
, stdout
);
252 const char licencemsg
[] =
253 "buildrun is copyright 2012 Simon Tatham.\n"
255 "Permission is hereby granted, free of charge, to any person\n"
256 "obtaining a copy of this software and associated documentation files\n"
257 "(the \"Software\"), to deal in the Software without restriction,\n"
258 "including without limitation the rights to use, copy, modify, merge,\n"
259 "publish, distribute, sublicense, and/or sell copies of the Software,\n"
260 "and to permit persons to whom the Software is furnished to do so,\n"
261 "subject to the following conditions:\n"
263 "The above copyright notice and this permission notice shall be\n"
264 "included in all copies or substantial portions of the Software.\n"
266 "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n"
267 "EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n"
268 "MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n"
269 "NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n"
270 "BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n"
271 "ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n"
272 "CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n"
277 fputs(licencemsg
, stdout
);
281 #define SVN_REV "$Revision$"
282 char rev
[sizeof(SVN_REV
)];
285 strcpy(rev
, SVN_REV
);
287 for (p
= rev
; *p
&& *p
!= ':'; p
++);
290 while (*p
&& isspace(*p
)) p
++;
291 for (q
= p
; *q
&& *q
!= '$'; q
++);
293 printf("buildrun revision %s\n", p
);
295 printf("buildrun: unknown version\n");
299 int main(int argc
, char **argv
)
301 enum { UNSPEC
, READ
, WRITE
} mode
= UNSPEC
;
309 if (doing_opts
&& *p
== '-') {
310 if (!strcmp(p
, "-w")) {
312 } else if (!strcmp(p
, "-r")) {
314 } else if (!strcmp(p
, "--")) {
316 } else if (!strcmp(p
, "--version")) {
319 } else if (!strcmp(p
, "--help")) {
322 } else if (!strcmp(p
, "--licence") || !strcmp(p
, "--license")) {
326 fprintf(stderr
, "buildrun: unrecognised option '%s'\n", p
);
339 if (mode
== UNSPEC
) {
340 fprintf(stderr
, "buildrun: expected -w or -r\n");
345 fprintf(stderr
, "buildrun: expected a control directory name\n");
349 pipepath
= malloc(strlen(ctldir
) + strlen(PIPEFILE
) + 2);
351 fprintf(stderr
, "buildrun: out of memory\n");
354 sprintf(pipepath
, "%s/%s", ctldir
, PIPEFILE
);
358 } else if (mode
== READ
) {