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