1 /* $Id: run_with_lock.c 625755 2021-02-18 19:34:22Z ivanov $
2 * ===========================================================================
3 *
4 * PUBLIC DOMAIN NOTICE
5 * National Center for Biotechnology Information
6 *
7 * This software/database is a "United States Government Work" under the
8 * terms of the United States Copyright Act. It was written as part of
9 * the author's official duties as a United States Government employee and
10 * thus cannot be copyrighted. This software/database is freely available
11 * to the public for use. The National Library of Medicine and the U.S.
12 * Government have not placed any restriction on its use or reproduction.
13 *
14 * Although all reasonable efforts have been taken to ensure the accuracy
15 * and reliability of the software and data, the NLM and the U.S.
16 * Government do not and cannot warrant the performance or results that
17 * may be obtained by using this software or data. The NLM and the U.S.
18 * Government disclaim all warranties, express or implied, including
19 * warranties of performance, merchantability or fitness for any particular
20 * purpose.
21 *
22 * Please cite the author in any work or product based on this material.
23 *
24 * ===========================================================================
25 *
26 * Author: Aaron Ucko
27 *
28 * File Description:
29 * Run a portion of the build under a filesystem-based lock,
30 * optionally logging any resulting output (both stdout and stderr).
31 *
32 * ===========================================================================
33 */
34
35
36 #ifndef _GNU_SOURCE
37 # define _GNU_SOURCE 1
38 #endif
39
40 #include <errno.h>
41 #include <fcntl.h>
42 #include <signal.h>
43 #include <stdio.h>
44 #include <stdlib.h>
45 #include <string.h>
46 #include <sys/select.h>
47 #include <sys/wait.h>
48 #include <termios.h>
49 #include <unistd.h>
50
51 #include <assert.h>
52
53 #ifndef PATH_MAX
54 # define PATH_MAX 4096
55 #endif
56
57 #ifndef W_EXITCODE
58 # define W_EXITCODE(x, y) (((x) << 8) | (y))
59 #endif
60
61 static const char* s_AppName;
62 static char s_LockName[PATH_MAX];
63 static int s_MainChildDone;
64 static int s_FinishingInBackground;
65 static int s_CaughtSignal;
66 static sig_t s_OrigHandlers[NSIG + 1];
67
68
69 typedef struct {
70 const char* base;
71 const char* getter;
72 const char* logfile;
73 const char* map;
74 const char* reviewer;
75 int error_status;
76 } SOptions;
77
78 typedef enum {
79 eFS_off,
80 eFS_on,
81 eFS_esc,
82 eFS_csi,
83 } EFilterState;
84
85
86 static
s_ApplyMap(SOptions * options)87 void s_ApplyMap(SOptions* options)
88 {
89 char line[1024];
90 FILE* map_file;
91
92 assert(options->map != NULL);
93 map_file = fopen(options->map, "r");
94 if (map_file == NULL) {
95 if (errno != ENOENT) {
96 fprintf(stderr,
97 "%s: Unable to open lock map %s: %s; leaving base as %s.\n",
98 s_AppName, options->map, strerror(errno), options->base);
99 }
100 return;
101 }
102 while (fgets(line, sizeof(line), map_file) != NULL) {
103 char key[1024], value[1024];
104 if (sscanf(line, " %s %s", key, value) == 2
105 && strcmp(key, options->base + 5) == 0) {
106 char *new_base = malloc(strlen(value) + 6);
107 sprintf(new_base, "make_%s", value);
108 fprintf(stderr, "%s: Adjusting base from %s to %s per %s.\n",
109 s_AppName, options->base, new_base, options->map);
110 options->base = new_base;
111 break;
112 }
113 }
114
115 fclose(map_file);
116 }
117
118
119 static
s_ParseOptions(SOptions * options,int * argc,const char * const ** argv)120 void s_ParseOptions(SOptions* options, int* argc, const char* const** argv)
121 {
122 options->base = NULL;
123 options->getter = NULL;
124 options->logfile = NULL;
125 options->map = NULL;
126 options->reviewer = NULL;
127 options->error_status = 1;
128
129 while (*argc > 1) {
130 if (strcmp((*argv)[1], "-base") == 0 && *argc > 2) {
131 options->base = (*argv)[2];
132 *argv += 2;
133 *argc -= 2;
134 } else if (strcmp((*argv)[1], "-getter") == 0 && *argc > 2) {
135 options->getter = (*argv)[2];
136 *argv += 2;
137 *argc -= 2;
138 } else if (strcmp((*argv)[1], "-log") == 0 && *argc > 2) {
139 options->logfile = (*argv)[2];
140 *argv += 2;
141 *argc -= 2;
142 } else if (strcmp((*argv)[1], "-map") == 0 && *argc > 2) {
143 options->map = (*argv)[2];
144 *argv += 2;
145 *argc -= 2;
146 } else if (strcmp((*argv)[1], "-reviewer") == 0 && *argc > 2) {
147 options->reviewer = (*argv)[2];
148 *argv += 2;
149 *argc -= 2;
150 } else if ((*argv)[1][0] == '-') {
151 fprintf(stderr, "%s: Unsupported option %s.\n",
152 s_AppName, (*argv)[1]);
153 exit(options->error_status);
154 } else if (strcmp((*argv)[1], "!") == 0) {
155 options->error_status = 0;
156 ++*argv;
157 --*argc;
158 } else {
159 break;
160 }
161 }
162
163 if (options->base == NULL && *argc > 1) {
164 const char* p = strrchr((*argv)[1], '/');
165 if (p != NULL) {
166 options->base = p + 1;
167 } else {
168 options->base = (*argv)[1];
169 }
170 }
171 }
172
173
174 static
s_Tee(int in,FILE * out1,FILE * out2,EFilterState * state)175 int s_Tee(int in, FILE* out1, FILE* out2, EFilterState* state)
176 {
177 char buffer[1024];
178 ssize_t n;
179 while ((n = read(in, buffer, sizeof(buffer))) > 0) {
180 char* p = buffer;
181 if (fwrite(buffer, 1, n, out1) < n) {
182 fprintf(stderr, "%s: Error propagating process output: %s.\n",
183 s_AppName, strerror(errno));
184 }
185 if (*state == eFS_esc) {
186 if (*p == '[') {
187 ++p;
188 /* --n; */ /* overadjusts when the final m turns up */
189 *state = eFS_csi;
190 } else {
191 fputc('\033', out2);
192 *state = eFS_on;
193 }
194 }
195 if (*state == eFS_csi) {
196 p = memchr(buffer, 'm', n);
197 if (p == NULL) {
198 continue;
199 } else {
200 ++p;
201 n -= (p - buffer);
202 *state = eFS_on;
203 }
204 }
205 if (*state == eFS_on) {
206 const char* start = p;
207 const char* src = p;
208 char* dest = p;
209 while (src - p < n
210 && (start = memchr(src, '\033', n - (src - p))) != NULL
211 && (start == p + n - 1 || start[1] == '[')) {
212 if (src > dest && start > src) {
213 memmove(dest, src, start - src);
214 }
215 dest += start - src;
216 if (start == p + n - 1) {
217 *state = eFS_esc;
218 n = dest - p;
219 break;
220 }
221 const char* end = memchr(start + 2, 'm', n - 2 - (start - p));
222 if (end == NULL) {
223 *state = eFS_csi;
224 n = dest - p;
225 break;
226 } else {
227 src = end + 1;
228 }
229 }
230 if (*state == eFS_on) {
231 memmove(dest, src, n - (src - p));
232 n -= src - dest;
233 }
234 }
235 if (fwrite(p, 1, n, out2) < n) {
236 fprintf(stderr, "%s: Error logging process output: %s.\n",
237 s_AppName, strerror(errno));
238 }
239 }
240 if (n < 0 && errno != EAGAIN && errno != EWOULDBLOCK) {
241 if (*state == eFS_off || errno != EIO) {
242 fprintf(stderr, "%s: Error reading from process: %s.\n",
243 s_AppName, strerror(errno));
244 }
245 return 1;
246 }
247 return n == 0;
248 }
249
250
251 static
s_OnSIGCHLD(int n)252 void s_OnSIGCHLD(int n)
253 {
254 s_MainChildDone = 1;
255 }
256
257
258 static
s_OpenPipeOrPty(int fd_to_mimic,const char * label,int fds[2],EFilterState * state)259 int s_OpenPipeOrPty(int fd_to_mimic, const char* label, int fds[2],
260 EFilterState* state)
261 {
262 const char* term = getenv("TERM");
263 if (term != NULL && strcmp(term, "dumb") && isatty(fd_to_mimic)) {
264 struct termios attr;
265 const char* name;
266 *state = eFS_on;
267 if ((fds[0] = posix_openpt(O_RDONLY | O_NOCTTY)) < 0) {
268 fprintf(stderr, "%s: Error allocating pty master for %s: %s.\n",
269 s_AppName, label, strerror(errno));
270 return -1;
271 } else if (grantpt(fds[0]) < 0 || unlockpt(fds[0]) < 0
272 || (name = ptsname(fds[0])) == NULL
273 || (fds[1] = open(name, O_WRONLY)) < 0) {
274 fprintf(stderr, "%s: Error opening pty slave for %s: %s.\n",
275 s_AppName, label, strerror(errno));
276 close(fds[0]);
277 return -1;
278 }
279 if (tcgetattr(fds[1], &attr) < 0) {
280 fprintf(stderr,
281 "%s: Warning: unable to get attributes for %s: %s.\n",
282 s_AppName, label, strerror(errno));
283 } else {
284 attr.c_oflag |= ONLRET;
285 #ifdef ONLCR
286 attr.c_oflag &= ~ONLCR;
287 #endif
288 /* XXX -- propagate anything from fd_to_mimic? */
289 if (tcsetattr(fds[1], TCSADRAIN, &attr) < 0) {
290 fprintf(stderr,
291 "%s: Warning: unable to set attributes for %s: %s.\n",
292 s_AppName, label, strerror(errno));
293 }
294 }
295 } else {
296 if (pipe(fds) < 0) {
297 fprintf(stderr, "%s: Error creating pipe for %s: %s.n",
298 s_AppName, label, strerror(errno));
299 return -1;
300 }
301 }
302
303 return 0;
304 }
305
306
307 static
s_Run(const char * const * args,FILE * log)308 int s_Run(const char* const* args, FILE* log)
309 {
310 int stdout_fds[2], stderr_fds[2];
311 int status = W_EXITCODE(126, 0);
312 pid_t pid;
313 EFilterState stdout_state = eFS_off, stderr_state = eFS_off;
314 if (log != NULL) {
315 if (s_OpenPipeOrPty(STDOUT_FILENO, "stdout", stdout_fds, &stdout_state)
316 < 0) {
317 return status;
318 }
319 if (s_OpenPipeOrPty(STDERR_FILENO, "stderr", stderr_fds, &stderr_state)
320 < 0) {
321 close(stdout_fds[0]);
322 close(stdout_fds[1]);
323 return status;
324 }
325 s_MainChildDone = 0;
326 signal(SIGCHLD, s_OnSIGCHLD);
327 }
328
329 pid = fork();
330 if (pid < 0) { /* error */
331 fprintf(stderr, "%s: Fork failed: %s.\n", s_AppName, strerror(errno));
332 } else if (pid == 0) { /* child */
333 int n;
334 if (log != NULL) {
335 close(stdout_fds[0]);
336 close(stderr_fds[0]);
337 dup2(stdout_fds[1], STDOUT_FILENO);
338 dup2(stderr_fds[1], STDERR_FILENO);
339 }
340 for (n = 1; n <= NSIG; ++n) {
341 if (n != SIGKILL && n != SIGSTOP) {
342 signal(n, s_OrigHandlers[n]);
343 }
344 }
345 execvp(args[0], (char* const*) args);
346 fprintf(stderr, "%s: Unable to exec %s: %s.\n",
347 s_AppName, args[0], strerror(errno));
348 _exit(127);
349 } else { /* parent */
350 if (log != NULL) {
351 int stdout_done = 0, stderr_done = 0;
352 close(stdout_fds[1]);
353 close(stderr_fds[1]);
354 fcntl(stdout_fds[0], F_SETFL,
355 fcntl(stdout_fds[0], F_GETFL) | O_NONBLOCK);
356 fcntl(stderr_fds[0], F_SETFL,
357 fcntl(stderr_fds[0], F_GETFL) | O_NONBLOCK);
358 while ( !stdout_done || !stderr_done ) {
359 fd_set rfds, efds;
360 unsigned int nfds = 0;
361 FD_ZERO(&rfds);
362 FD_ZERO(&efds);
363 if ( !stderr_done ) {
364 FD_SET(stderr_fds[0], &rfds);
365 FD_SET(stderr_fds[0], &efds);
366 nfds = stderr_fds[0] + 1;
367 }
368 if ( !stdout_done ) {
369 FD_SET(stdout_fds[0], &rfds);
370 FD_SET(stdout_fds[0], &efds);
371 if (stdout_fds[0] >= nfds) {
372 nfds = stdout_fds[0] + 1;
373 }
374 }
375 if (select(nfds, &rfds, NULL, &efds, NULL) < 0
376 && errno != EINTR && errno != EAGAIN) {
377 fprintf(stderr,
378 "%s: Error checking for output to log: %s.\n",
379 s_AppName, strerror(errno));
380 break;
381 }
382 if (FD_ISSET(stdout_fds[0], &rfds)
383 || FD_ISSET(stdout_fds[0], &efds)) {
384 stdout_done = s_Tee(stdout_fds[0], stdout, log,
385 &stdout_state);
386 }
387 if (FD_ISSET(stderr_fds[0], &rfds)
388 || FD_ISSET(stderr_fds[0], &efds)) {
389 stderr_done = s_Tee(stderr_fds[0], stderr, log,
390 &stderr_state);
391 }
392 if (s_MainChildDone && ( !stdout_done || !stderr_done )
393 && waitpid(pid, &status, WNOHANG) != 0) {
394 s_MainChildDone = 0;
395 fflush(stdout);
396 fflush(stderr);
397 fflush(log);
398 if (fork() > 0) {
399 s_FinishingInBackground = 1;
400 break;
401 }
402 }
403 }
404 signal(SIGCHLD, s_OrigHandlers[SIGCHLD]);
405 }
406 if (log == NULL || !s_FinishingInBackground ) {
407 waitpid(pid, &status, 0);
408 }
409 }
410
411 if (log != NULL) {
412 close(stdout_fds[0]);
413 close(stderr_fds[0]);
414 }
415 return status;
416 }
417
418
419 static
s_CleanUp(void)420 void s_CleanUp(void)
421 {
422 const char* args[] = { "/bin/rm", "-rf", s_LockName, NULL };
423 if ( !s_FinishingInBackground ) {
424 s_Run(args, NULL);
425 }
426 }
427
428
429 static
s_OnSignal(int n)430 void s_OnSignal(int n)
431 {
432 /* Propagate to child? The shell version doesn't. */
433 fprintf(stderr, "%s: Caught signal %d\n", s_AppName, n);
434 /*
435 s_CleanUp();
436 _exit(n | 0x80);
437 */
438 s_CaughtSignal = n;
439 signal(n, s_OnSignal);
440 }
441
442
443 typedef enum {
444 eOldLogMissing,
445 eNewLogBoring,
446 eNewLogInteresting
447 } ELogStatus;
448
449
main(int argc,const char * const * argv)450 int main(int argc, const char* const* argv)
451 {
452 SOptions options;
453 char pid_str[sizeof(pid_t) * 3 + 1], new_log[PATH_MAX];
454 const char* getter_args[] = { "get_lock", "BASE", pid_str, NULL };
455 FILE* log = NULL;
456 int status = 0, n;
457
458 s_AppName = argv[0];
459 s_ParseOptions(&options, &argc, &argv);
460
461 if (options.map != NULL && strncmp(options.base, "make_", 5) == 0) {
462 s_ApplyMap(&options);
463 }
464
465 if (options.getter != NULL) {
466 getter_args[0] = options.getter;
467 }
468 getter_args[1] = options.base;
469 sprintf(pid_str, "%ld", (long)getpid());
470 sprintf(s_LockName, "%s.lock", options.base);
471 for (n = 1; n <= NSIG; ++n) {
472 switch (n) {
473 /* Blacklist some signals for various reasons. */
474 case SIGQUIT: case SIGILL: case SIGABRT: case SIGFPE: case SIGSEGV:
475 case SIGBUS: case SIGSYS: case SIGTRAP: case SIGXCPU: case SIGXFSZ:
476 /* Don't touch anything severe enough to cause a core dump. */
477 case SIGPIPE:
478 /* Should react immediately here too. */
479 case SIGKILL: case SIGSTOP:
480 /* Uncatchable. */
481 case SIGTSTP: case SIGTTIN: case SIGTTOU:
482 /* Harmless temporary suspension; don't overreact. */
483 case SIGCHLD: case SIGCONT: case SIGURG: case SIGWINCH:
484 /* Totally harmless; don't overreact. */
485 break;
486 default:
487 s_OrigHandlers[n] = signal(n, s_OnSignal);
488 }
489 }
490 s_Run(getter_args, NULL);
491 if (s_CaughtSignal) {
492 goto cleanup;
493 }
494
495 if (options.logfile != NULL) {
496 sprintf(new_log, "%s.new", options.logfile);
497 log = fopen(new_log, "w");
498 if (log == NULL) {
499 fprintf(stderr, "%s: Couldn't open log file %s: %s.\n",
500 s_AppName, options.logfile, strerror(errno));
501 return options.error_status;
502 }
503 }
504
505 if ( !s_CaughtSignal ) {
506 status = s_Run(argv + 1, log);
507 }
508
509 if (log != NULL && !s_CaughtSignal && access(new_log, F_OK) == 0) {
510 ELogStatus log_status = eNewLogInteresting;
511 fclose(log);
512 if (access(options.logfile, F_OK) != 0) {
513 log_status = eOldLogMissing;
514 } else if (options.reviewer != NULL) {
515 const char* reviewer_args[] = { options.reviewer, new_log, NULL };
516 if (s_Run(reviewer_args, NULL) != 0) {
517 log_status = eNewLogBoring;
518 }
519 }
520 if (log_status != eNewLogBoring) {
521 if (rename(new_log, options.logfile) < 0) {
522 fprintf(stderr, "%s: Unable to rename log file %s: %s.\n",
523 s_AppName, new_log, strerror(errno));
524 }
525 }
526 }
527
528 cleanup:
529 s_CleanUp();
530
531 if (s_CaughtSignal) {
532 status = s_CaughtSignal | 0x80;
533 } else if (WIFSIGNALED(status)) {
534 status = WTERMSIG(status) | 0x80;
535 } else {
536 status = WEXITSTATUS(status);
537 }
538 if (options.error_status == 0) {
539 status = !status;
540 }
541
542 return status;
543 }
544