]> eyrie.org Git - kerberos/krb5-strength.git/blob - tests/tap/process.c
New upstream version 3.3
[kerberos/krb5-strength.git] / tests / tap / process.c
1 /*
2  * Utility functions for tests that use subprocesses.
3  *
4  * Provides utility functions for subprocess manipulation.  Specifically,
5  * provides a function, run_setup, which runs a command and bails if it fails,
6  * using its error message as the bail output, and is_function_output, which
7  * runs a function in a subprocess and checks its output and exit status
8  * against expected values.
9  *
10  * Requires an Autoconf probe for sys/select.h and a replacement for a missing
11  * mkstemp.
12  *
13  * The canonical version of this file is maintained in the rra-c-util package,
14  * which can be found at <https://www.eyrie.org/~eagle/software/rra-c-util/>.
15  *
16  * Written by Russ Allbery <eagle@eyrie.org>
17  * Copyright 2002, 2004-2005, 2013, 2016-2017, 2022
18  *     Russ Allbery <eagle@eyrie.org>
19  * Copyright 2009-2011, 2013-2014
20  *     The Board of Trustees of the Leland Stanford Junior University
21  *
22  * Permission is hereby granted, free of charge, to any person obtaining a
23  * copy of this software and associated documentation files (the "Software"),
24  * to deal in the Software without restriction, including without limitation
25  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
26  * and/or sell copies of the Software, and to permit persons to whom the
27  * Software is furnished to do so, subject to the following conditions:
28  *
29  * The above copyright notice and this permission notice shall be included in
30  * all copies or substantial portions of the Software.
31  *
32  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
33  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
34  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
35  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
36  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
37  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
38  * DEALINGS IN THE SOFTWARE.
39  *
40  * SPDX-License-Identifier: MIT
41  */
42
43 #include <config.h>
44 #include <portable/system.h>
45
46 #include <errno.h>
47 #include <fcntl.h>
48 #include <signal.h>
49 #ifdef HAVE_SYS_SELECT_H
50 #    include <sys/select.h>
51 #endif
52 #include <sys/stat.h>
53 #ifdef HAVE_SYS_TIME_H
54 #    include <sys/time.h>
55 #endif
56 #include <sys/wait.h>
57 #include <time.h>
58
59 #include <tests/tap/basic.h>
60 #include <tests/tap/process.h>
61 #include <tests/tap/string.h>
62
63 /* May be defined by the build system. */
64 #ifndef PATH_FAKEROOT
65 #    define PATH_FAKEROOT ""
66 #endif
67
68 /* How long to wait for the process to start in seconds. */
69 #define PROCESS_WAIT 10
70
71 /*
72  * Used to store information about a background process.  This contains
73  * everything required to stop the process and clean up after it.
74  */
75 struct process {
76     pid_t pid;            /* PID of child process */
77     char *pidfile;        /* PID file to delete on process stop */
78     char *tmpdir;         /* Temporary directory for log file */
79     char *logfile;        /* Log file of process output */
80     bool is_child;        /* Whether we can waitpid for process */
81     struct process *next; /* Next process in global list */
82 };
83
84 /*
85  * Global list of started processes, which will be cleaned up automatically on
86  * program exit if they haven't been explicitly stopped with process_stop
87  * prior to that point.
88  */
89 static struct process *processes = NULL;
90
91
92 /*
93  * Given a function, an expected exit status, and expected output, runs that
94  * function in a subprocess, capturing stdout and stderr via a pipe, and
95  * returns the function output in newly allocated memory.  Also captures the
96  * process exit status.
97  */
98 static void
99 run_child_function(test_function_type function, void *data, int *status,
100                    char **output)
101 {
102     int fds[2];
103     pid_t child;
104     char *buf;
105     ssize_t count, ret, buflen;
106     int rval;
107
108     /* Flush stdout before we start to avoid odd forking issues. */
109     fflush(stdout);
110
111     /* Set up the pipe and call the function, collecting its output. */
112     if (pipe(fds) == -1)
113         sysbail("can't create pipe");
114     child = fork();
115     if (child == (pid_t) -1) {
116         sysbail("can't fork");
117     } else if (child == 0) {
118         /* In child.  Set up our stdout and stderr. */
119         close(fds[0]);
120         if (dup2(fds[1], 1) == -1)
121             _exit(255);
122         if (dup2(fds[1], 2) == -1)
123             _exit(255);
124
125         /* Now, run the function and exit successfully if it returns. */
126         (*function)(data);
127         fflush(stdout);
128         _exit(0);
129     } else {
130         /*
131          * In the parent; close the extra file descriptor, read the output if
132          * any, and then collect the exit status.
133          */
134         close(fds[1]);
135         buflen = BUFSIZ;
136         buf = bmalloc(buflen);
137         count = 0;
138         do {
139             ret = read(fds[0], buf + count, buflen - count - 1);
140             if (SSIZE_MAX - count <= ret)
141                 bail("maximum output size exceeded in run_child_function");
142             if (ret > 0)
143                 count += ret;
144             if (count >= buflen - 1) {
145                 buflen += BUFSIZ;
146                 buf = brealloc(buf, buflen);
147             }
148         } while (ret > 0);
149         buf[count] = '\0';
150         if (waitpid(child, &rval, 0) == (pid_t) -1)
151             sysbail("waitpid failed");
152         close(fds[0]);
153     }
154
155     /* Store the output and return. */
156     *status = rval;
157     *output = buf;
158 }
159
160
161 /*
162  * Given a function, data to pass to that function, an expected exit status,
163  * and expected output, runs that function in a subprocess, capturing stdout
164  * and stderr via a pipe, and compare the combination of stdout and stderr
165  * with the expected output and the exit status with the expected status.
166  * Expects the function to always exit (not die from a signal).
167  */
168 void
169 is_function_output(test_function_type function, void *data, int status,
170                    const char *output, const char *format, ...)
171 {
172     char *buf, *msg;
173     int rval;
174     va_list args;
175
176     run_child_function(function, data, &rval, &buf);
177
178     /* Now, check the results against what we expected. */
179     va_start(args, format);
180     bvasprintf(&msg, format, args);
181     va_end(args);
182     ok(WIFEXITED(rval), "%s (exited)", msg);
183     is_int(status, WEXITSTATUS(rval), "%s (status)", msg);
184     is_string(output, buf, "%s (output)", msg);
185     free(buf);
186     free(msg);
187 }
188
189
190 /*
191  * A helper function for run_setup.  This is a function to run an external
192  * command, suitable for passing into run_child_function.  The expected
193  * argument must be an argv array, with argv[0] being the command to run.
194  */
195 static void
196 exec_command(void *data)
197 {
198     char *const *argv = data;
199
200     execvp(argv[0], argv);
201 }
202
203
204 /*
205  * Given a command expressed as an argv struct, with argv[0] the name or path
206  * to the command, run that command.  If it exits with a non-zero status, use
207  * the part of its output up to the first newline as the error message when
208  * calling bail.
209  */
210 void
211 run_setup(const char *const argv[])
212 {
213     char *output, *p;
214     int status;
215
216     run_child_function(exec_command, (void *) argv, &status, &output);
217     if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
218         p = strchr(output, '\n');
219         if (p != NULL)
220             *p = '\0';
221         if (output[0] != '\0')
222             bail("%s", output);
223         else
224             bail("setup command failed with no output");
225     }
226     free(output);
227 }
228
229
230 /*
231  * Free the resources associated with tracking a process, without doing
232  * anything to the process.  This is kept separate so that we can free
233  * resources during shutdown in a non-primary process.
234  */
235 static void
236 process_free(struct process *process)
237 {
238     struct process **prev;
239
240     /* Do nothing if called with a NULL argument. */
241     if (process == NULL)
242         return;
243
244     /* Remove the process from the global list. */
245     prev = &processes;
246     while (*prev != NULL && *prev != process)
247         prev = &(*prev)->next;
248     if (*prev == process)
249         *prev = process->next;
250
251     /* Free resources. */
252     free(process->pidfile);
253     free(process->logfile);
254     test_tmpdir_free(process->tmpdir);
255     free(process);
256 }
257
258
259 /*
260  * Kill a process and wait for it to exit.  Returns the status of the process.
261  * Calls bail on a system failure or a failure of the process to exit.
262  *
263  * We are quite aggressive with error reporting here because child processes
264  * that don't exit or that don't exist often indicate some form of test
265  * failure.
266  */
267 static int
268 process_kill(struct process *process)
269 {
270     int result, i;
271     int status = -1;
272     struct timeval tv;
273     unsigned long pid = process->pid;
274
275     /* If the process is not a child, just kill it and hope. */
276     if (!process->is_child) {
277         if (kill(process->pid, SIGTERM) < 0 && errno != ESRCH)
278             sysbail("cannot send SIGTERM to process %lu", pid);
279         return 0;
280     }
281
282     /* Check if the process has already exited. */
283     result = waitpid(process->pid, &status, WNOHANG);
284     if (result < 0)
285         sysbail("cannot wait for child process %lu", pid);
286     else if (result > 0)
287         return status;
288
289     /*
290      * Kill the process and wait for it to exit.  I don't want to go to the
291      * work of setting up a SIGCHLD handler or a full event loop here, so we
292      * effectively poll every tenth of a second for process exit (and
293      * hopefully faster when it does since the SIGCHLD may interrupt our
294      * select, although we're racing with it.
295      */
296     if (kill(process->pid, SIGTERM) < 0 && errno != ESRCH)
297         sysbail("cannot send SIGTERM to child process %lu", pid);
298     for (i = 0; i < PROCESS_WAIT * 10; i++) {
299         tv.tv_sec = 0;
300         tv.tv_usec = 100000;
301         select(0, NULL, NULL, NULL, &tv);
302         result = waitpid(process->pid, &status, WNOHANG);
303         if (result < 0)
304             sysbail("cannot wait for child process %lu", pid);
305         else if (result > 0)
306             return status;
307     }
308
309     /* The process still hasn't exited.  Bail. */
310     bail("child process %lu did not exit on SIGTERM", pid);
311
312     /* Not reached, but some compilers may get confused. */
313     return status;
314 }
315
316
317 /*
318  * Stop a particular process given its process struct.  This kills the
319  * process, waits for it to exit if possible (giving it at most five seconds),
320  * and then removes it from the global processes struct so that it isn't
321  * stopped again during global shutdown.
322  */
323 void
324 process_stop(struct process *process)
325 {
326     int status;
327     unsigned long pid = process->pid;
328
329     /* Stop the process. */
330     status = process_kill(process);
331
332     /* Call diag to flush logs as well as provide exit status. */
333     if (process->is_child)
334         diag("stopped process %lu (exit status %d)", pid, status);
335     else
336         diag("stopped process %lu", pid);
337
338     /* Remove the log and PID file. */
339     diag_file_remove(process->logfile);
340     unlink(process->pidfile);
341     unlink(process->logfile);
342
343     /* Free resources. */
344     process_free(process);
345 }
346
347
348 /*
349  * Stop all running processes.  This is called as a cleanup handler during
350  * process shutdown.  The first argument, which says whether the test was
351  * successful, is ignored, since the same actions should be performed
352  * regardless.  The second argument says whether this is the primary process,
353  * in which case we do the full shutdown.  Otherwise, we only free resources
354  * but don't stop the process.
355  */
356 static void
357 process_stop_all(int success UNUSED, int primary)
358 {
359     while (processes != NULL) {
360         if (primary)
361             process_stop(processes);
362         else
363             process_free(processes);
364     }
365 }
366
367
368 /*
369  * Read the PID of a process from a file.  This is necessary when running
370  * under fakeroot to get the actual PID of the remctld process.
371  */
372 static pid_t
373 read_pidfile(const char *path)
374 {
375     FILE *file;
376     char buffer[BUFSIZ];
377     long pid;
378
379     file = fopen(path, "r");
380     if (file == NULL)
381         sysbail("cannot open %s", path);
382     if (fgets(buffer, sizeof(buffer), file) == NULL)
383         sysbail("cannot read from %s", path);
384     fclose(file);
385     pid = strtol(buffer, NULL, 10);
386     if (pid <= 0)
387         bail("cannot read PID from %s", path);
388     return (pid_t) pid;
389 }
390
391
392 /*
393  * Start a process and return its status information.  The status information
394  * is also stored in the global processes linked list so that it can be
395  * stopped automatically on program exit.
396  *
397  * The boolean argument says whether to start the process under fakeroot.  If
398  * true, PATH_FAKEROOT must be defined, generally by Autoconf.  If it's not
399  * found, call skip_all.
400  *
401  * This is a helper function for process_start and process_start_fakeroot.
402  */
403 static struct process *
404 process_start_internal(const char *const argv[], const char *pidfile,
405                        bool fakeroot)
406 {
407     size_t i;
408     int log_fd;
409     const char *name;
410     struct timeval tv;
411     struct process *process;
412     const char **fakeroot_argv = NULL;
413     const char *path_fakeroot = PATH_FAKEROOT;
414
415     /* Check prerequisites. */
416     if (argv == NULL)
417         bail("argv for process_start is NULL");
418     if (fakeroot && path_fakeroot[0] == '\0')
419         skip_all("fakeroot not found");
420
421     /* Create the process struct and log file. */
422     process = bcalloc(1, sizeof(struct process));
423     process->pidfile = bstrdup(pidfile);
424     process->tmpdir = test_tmpdir();
425     name = strrchr(argv[0], '/');
426     if (name != NULL)
427         name++;
428     else
429         name = argv[0];
430     basprintf(&process->logfile, "%s/%s.log.XXXXXX", process->tmpdir, name);
431     log_fd = mkstemp(process->logfile);
432     if (log_fd < 0)
433         sysbail("cannot create log file for %s", argv[0]);
434
435     /* If using fakeroot, rewrite argv accordingly. */
436     if (fakeroot) {
437         for (i = 0; argv[i] != NULL; i++)
438             ;
439         fakeroot_argv = bcalloc(2 + i + 1, sizeof(const char *));
440         fakeroot_argv[0] = path_fakeroot;
441         fakeroot_argv[1] = "--";
442         for (i = 0; argv[i] != NULL; i++)
443             fakeroot_argv[i + 2] = argv[i];
444         fakeroot_argv[i + 2] = NULL;
445         argv = fakeroot_argv;
446     }
447
448     /*
449      * Fork off the child process, redirect its standard output and standard
450      * error to the log file, and then exec the program.
451      */
452     process->pid = fork();
453     if (process->pid < 0)
454         sysbail("fork failed");
455     else if (process->pid == 0) {
456         if (dup2(log_fd, STDOUT_FILENO) < 0)
457             sysbail("cannot redirect standard output");
458         if (dup2(log_fd, STDERR_FILENO) < 0)
459             sysbail("cannot redirect standard error");
460         close(log_fd);
461         if (execv(argv[0], (char *const *) argv) < 0)
462             sysbail("exec of %s failed", argv[0]);
463     }
464     close(log_fd);
465     free(fakeroot_argv);
466
467     /*
468      * In the parent.  Wait for the child to start by watching for the PID
469      * file to appear in 100ms intervals.
470      */
471     for (i = 0; i < PROCESS_WAIT * 10 && access(pidfile, F_OK) != 0; i++) {
472         tv.tv_sec = 0;
473         tv.tv_usec = 100000;
474         select(0, NULL, NULL, NULL, &tv);
475     }
476
477     /*
478      * If the PID file still hasn't appeared after ten seconds, attempt to
479      * kill the process and then bail.
480      */
481     if (access(pidfile, F_OK) != 0) {
482         kill(process->pid, SIGTERM);
483         alarm(5);
484         waitpid(process->pid, NULL, 0);
485         alarm(0);
486         bail("cannot start %s", argv[0]);
487     }
488
489     /*
490      * Read the PID back from the PID file.  This usually isn't necessary for
491      * non-forking daemons, but always doing this makes this function general,
492      * and it's required when running under fakeroot.
493      */
494     if (fakeroot)
495         process->pid = read_pidfile(pidfile);
496     process->is_child = !fakeroot;
497
498     /* Register the log file as a source of diag messages. */
499     diag_file_add(process->logfile);
500
501     /*
502      * Add the process to our global list and set our cleanup handler if this
503      * is the first process we started.
504      */
505     if (processes == NULL)
506         test_cleanup_register(process_stop_all);
507     process->next = processes;
508     processes = process;
509
510     /* All done. */
511     return process;
512 }
513
514
515 /*
516  * Start a process and return the opaque process struct.  The process must
517  * create pidfile with its PID when startup is complete.
518  */
519 struct process *
520 process_start(const char *const argv[], const char *pidfile)
521 {
522     return process_start_internal(argv, pidfile, false);
523 }
524
525
526 /*
527  * Start a process under fakeroot and return the opaque process struct.  If
528  * fakeroot is not available, calls skip_all.  The process must create pidfile
529  * with its PID when startup is complete.
530  */
531 struct process *
532 process_start_fakeroot(const char *const argv[], const char *pidfile)
533 {
534     return process_start_internal(argv, pidfile, true);
535 }