Add missing Revision keyword expansions, so that --version tells the truth.
[sgt/utils] / after / after.c
CommitLineData
26165a70 1/*
2 * after: wait for an unrelated process to terminate, returning
3 * its exit code if possible.
4 */
5
6/*
7 * Possible future extensions:
8 *
ad3af167 9 * - improvements to existing methods
10 * * the ptrace method on MacOS has the really unpleasant
11 * property that if after is interrupted or killed then the
12 * traced process dies too. We could mitigate that by
13 * trapping some common fatal signals (certainly including
14 * SIGINT and SIGTERM) and have them PT_DETACH before
15 * self-reraising. Alternatively, we could fork off a
16 * subprocess to do the tracing, and a pipe communicating
17 * with the parent; then if the subprocess detected its pipe
18 * end being closed it could PT_DETACH. Then even kill -9
19 * against the primary `after' process would allow the
20 * subprocess to clean up.
21 *
26165a70 22 * - alternatives to ptrace on systems supporting different
23 * process-debugging mechanisms
24 *
25 * - possibly an approach based on invoking ps?
26 * * with a longish interval, to prevent system overload
27 * * and we have to deal with the profusion of ps options. I
28 * suspect going by SUS/POSIX is the best thing here: set
29 * POSIXLY_CORRECT and expect ps to support the options
30 * given in SUS.
31 * * need to try to avoid race condition if the process ends
32 * and another one of the same name starts up. Printing the
33 * start time seems like the sensible thing, except that SUS
34 * doesn't give a standard option to do that; we can only
35 * print elapsed time since the process began, which means
36 * we have to be alert for off-by-1s errors if we wait 30s
37 * and the time only changes by 29 for some irritating
38 * reason.
39 */
40
41#include <stdio.h>
42#include <stdlib.h>
43#include <string.h>
44#include <ctype.h>
45#include <errno.h>
46
47#include <unistd.h>
48#include <sys/types.h>
49#include <sys/wait.h>
50
51char errbuf[4096];
52int errlen = 0;
53char *pname;
54
55#ifndef NO_PTRACE
56
57/*
58 * ptrace-based waiting method, which gets the process's exit
59 * code.
60 */
61
62#include <sys/ptrace.h>
63
ad3af167 64#if !defined PTRACE_FLAVOUR_LINUX && !defined PTRACE_FLAVOUR_BSD
65/*
66 * Attempt to autodetect ptrace flavour.
67 */
68#if defined PTRACE_ATTACH
69
70/*
71 * Linux flavour.
72 */
73#define ATTACH_TO(pid) ptrace(PTRACE_ATTACH, pid, NULL, 0)
74#define ATTACH_STR "ptrace(PTRACE_ATTACH, %d)"
75#define CONTINUE(pid) ptrace(PTRACE_CONT, pid, NULL, 0)
76#define CONT_STR "ptrace(PTRACE_CONT, %d)"
77
78#elif defined PT_ATTACH
79
80/*
81 * BSD/MacOS flavour.
82 */
83#define ATTACH_TO(pid) ptrace(PT_ATTACH, pid, NULL, 0)
84#define ATTACH_STR "ptrace(PT_ATTACH, %d)"
85#define CONTINUE(pid) ptrace(PT_CONTINUE, pid, (caddr_t)1, 0)
86#define CONT_STR "ptrace(PT_CONTINUE, %d)"
87
88#else
89#error Unable to autodetect ptrace flavour.
90#endif /* ptrace flavour detection */
91
92#endif /* outer ifdef containing flavour detection */
93
26165a70 94int after_ptrace(int pid)
95{
ad3af167 96 if (
97#ifdef PTRACE_FLAVOUR_LINUX
98 ptrace(PTRACE_ATTACH, pid, NULL, 0)
99#else
100 ptrace(PT_ATTACH, pid, NULL, 0)
101#endif
102 < 0) {
26165a70 103 /*
104 * If we can't attach to the process, that's not a fatal
105 * error; it just means we fall back to the next available
106 * method.
107 */
108 errlen += sprintf(errbuf+errlen,
ad3af167 109 "%s: "ATTACH_STR": %.256s\n",
26165a70 110 pname, pid, strerror(errno));
111 return -1;
112 }
113
114 /*
115 * Having successfully attached to the process, however, any
116 * subsequent error is fatal.
117 */
118
119 while (1) {
120 int wpid, wstatus;
121 wpid = waitpid(pid, &wstatus, 0);
122 if (wpid < 0) {
123 perror("wait");
124 return 127;
125 } else if (WIFEXITED(wstatus)) {
126 return WEXITSTATUS(wstatus);
127 } else {
ad3af167 128 if (CONTINUE(pid) < 0) {
129 fprintf(stderr, "%s: "CONT_STR": %.256s\n",
130 pname, pid, strerror(errno));
26165a70 131 return 127;
132 }
133 }
134 }
135}
136
137#endif
138
139#ifndef NO_PROC
140
141/*
142 * /proc-based waiting method.
143 */
144
145int after_proc(int pid)
146{
147 char buf[128];
148
149 sprintf(buf, "/proc/%d", pid);
150
151 if (chdir(buf) < 0) {
152 /*
153 * Fallback error.
154 */
155 errlen += sprintf(errbuf+errlen,
156 "%s: chdir(\"%.32s\"): %.256s\n",
157 pname, buf, strerror(errno));
158 return -1;
159 }
160
161 do {
162 sleep(1);
163 } while (getcwd(buf, sizeof(buf)));
164
165 return 0;
166}
167
168#endif
169
170#ifdef NO_PTRACE
171#define NO_EXITCODE
172#endif
173
174const char usagemsg[] =
175 "usage: after"
176#ifndef NO_EXITCODE
177 " [ -x | -z ]"
178#endif
179 " <pid>\n"
180#ifndef NO_EXITCODE
181 "where: -x return an error if the process's exit code is unavailable\n"
182 " -z return 0 instead of the process's exit code\n"
183#endif
184 " also: after --version report version number\n"
185 " after --help display this help text\n"
186 " after --licence display the (MIT) licence text\n"
187 ;
188
189void usage(void) {
190 fputs(usagemsg, stdout);
191}
192
193const char licencemsg[] =
194 "after is copyright 2008 Simon Tatham.\n"
195 "\n"
196 "Permission is hereby granted, free of charge, to any person\n"
197 "obtaining a copy of this software and associated documentation files\n"
198 "(the \"Software\"), to deal in the Software without restriction,\n"
199 "including without limitation the rights to use, copy, modify, merge,\n"
200 "publish, distribute, sublicense, and/or sell copies of the Software,\n"
201 "and to permit persons to whom the Software is furnished to do so,\n"
202 "subject to the following conditions:\n"
203 "\n"
204 "The above copyright notice and this permission notice shall be\n"
205 "included in all copies or substantial portions of the Software.\n"
206 "\n"
207 "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n"
208 "EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n"
209 "MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n"
210 "NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n"
211 "BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n"
212 "ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n"
213 "CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n"
214 "SOFTWARE.\n"
215 ;
216
217void licence(void) {
218 fputs(licencemsg, stdout);
219}
220
221void version(void) {
6f739499 222#define SVN_REV "$Revision$"
26165a70 223 char rev[sizeof(SVN_REV)];
224 char *p, *q;
225
226 strcpy(rev, SVN_REV);
227
228 for (p = rev; *p && *p != ':'; p++);
229 if (*p) {
230 p++;
231 while (*p && isspace((unsigned char)*p)) p++;
232 for (q = p; *q && !isspace((unsigned char)*q) && *q != '$'; q++);
233 if (*q) *q = '\0';
234 printf("after revision %s", p);
235 } else {
236 printf("after: unknown version");
237 }
238 putchar('\n');
239}
240
241int main(int argc, char **argv)
242{
243 int pid = -1;
244 enum { UNWANTED, OPTIONAL, MANDATORY } exitcode = OPTIONAL;
245 int ret;
246
247 pname = argv[0];
248
249 /* parse the command line arguments */
250 while (--argc) {
251 char *p = *++argv;
252
253#ifndef NO_EXITCODE
254 if (!strcmp(p, "-x")) {
255 exitcode = MANDATORY;
256 } else if (!strcmp(p, "-z")) {
257 exitcode = UNWANTED;
258 } else
259#endif
260 if (!strcmp(p, "--help")) {
261 usage();
262 return 0;
263 } else if (!strcmp(p, "--version")) {
264 version();
265 return 0;
266 } else if (!strcmp(p, "--licence") || !strcmp(p, "--license")) {
267 licence();
268 return 0;
269 } else if (*p=='-') {
270 fprintf(stderr, "%s: unrecognised option `%s'\n", pname, p);
271 return 1;
272 } else {
273 if (pid >= 0) {
274 fprintf(stderr, "%s: parameter `%s' unexpected\n", pname, p);
275 return 1;
276 } else {
277 pid = atoi(p);
278 }
279 }
280 }
281
282 if (pid < 0) {
283 usage();
284 return 0;
285 }
286
287#ifndef NO_PTRACE
288 ret = after_ptrace(pid);
289 if (ret >= 0)
290 return (exitcode == UNWANTED ? 0 : ret);
291#endif
292
293 if (exitcode == MANDATORY) {
294 /*
295 * If we reach here, the user has demanded the process's
296 * return code, and we haven't been able to get it. Print
297 * the error messages we accrued while trying, and abandon
298 * ship.
299 */
300 fputs(errbuf, stderr);
301 return 127;
302 }
303
304#ifndef NO_PROC
305 ret = after_proc(pid);
306 if (ret >= 0)
307 return ret;
308#endif
309
310 /*
311 * If we reach here, we have run out of all our options. Print
312 * all our error messages, and return total failure.
313 */
314 fputs(errbuf, stderr);
315 return 127;
316}