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