1/* Copyright (c) 2010 The NetBSD Foundation, Inc.
2 * All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
14 * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
15 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
16 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
17 * IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
18 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
20 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
22 * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
23 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
24 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  */
25
26#include "atf-c/utils.h"
27
28#include <sys/stat.h>
29#include <sys/wait.h>
30
31#include <err.h>
32#include <errno.h>
33#include <fcntl.h>
34#include <regex.h>
35#include <stdio.h>
36#include <stdlib.h>
37#include <string.h>
38#include <unistd.h>
39
40#include <atf-c.h>
41
42#include "atf-c/detail/dynstr.h"
43
44/* No prototype in header for this one, it's a little sketchy (internal). */
45void atf_tc_set_resultsfile(const char *);
46
47/** Allocate a filename to be used by atf_utils_{fork,wait}.
48 *
49 * In case of a failure, marks the calling test as failed when in_parent is
50 * true, else terminates execution.
51 *
52 * \param [out] name String to contain the generated file.
53 * \param pid PID of the process that will write to the file.
54 * \param suffix Either "out" or "err".
55 * \param in_parent If true, fail with atf_tc_fail; else use err(3). */
56static void
57init_out_filename(atf_dynstr_t *name, const pid_t pid, const char *suffix,
58                  const bool in_parent)
59{
60    atf_error_t error;
61
62    error = atf_dynstr_init_fmt(name, "atf_utils_fork_%d_%s.txt",
63                                (int)pid, suffix);
64    if (atf_is_error(error)) {
65        char buffer[1024];
66        atf_error_format(error, buffer, sizeof(buffer));
67        if (in_parent) {
68            atf_tc_fail("Failed to create output file: %s", buffer);
69        } else {
70            err(EXIT_FAILURE, "Failed to create output file: %s", buffer);
71        }
72    }
73}
74
75/** Searches for a regexp in a string.
76 *
77 * \param regex The regexp to look for.
78 * \param str The string in which to look for the expression.
79 *
80 * \return True if there is a match; false otherwise. */
81static
82bool
83grep_string(const char *regex, const char *str)
84{
85    int res;
86    regex_t preg;
87
88    printf("Looking for '%s' in '%s'\n", regex, str);
89    ATF_REQUIRE(regcomp(&preg, regex, REG_EXTENDED) == 0);
90
91    res = regexec(&preg, str, 0, NULL, 0);
92    ATF_REQUIRE(res == 0 || res == REG_NOMATCH);
93
94    regfree(&preg);
95
96    return res == 0;
97}
98
99/** Prints the contents of a file to stdout.
100 *
101 * \param name The name of the file to be printed.
102 * \param prefix An string to be prepended to every line of the printed
103 *     file. */
104void
105atf_utils_cat_file(const char *name, const char *prefix)
106{
107    const int fd = open(name, O_RDONLY);
108    ATF_REQUIRE_MSG(fd != -1, "Cannot open %s", name);
109
110    char buffer[1024];
111    ssize_t count;
112    bool continued = false;
113    while ((count = read(fd, buffer, sizeof(buffer) - 1)) > 0) {
114        buffer[count] = '\0';
115
116        if (!continued)
117            printf("%s", prefix);
118
119        char *iter = buffer;
120        char *end;
121        while ((end = strchr(iter, '\n')) != NULL) {
122            *end = '\0';
123            printf("%s\n", iter);
124
125            iter = end + 1;
126            if (iter != buffer + count)
127                printf("%s", prefix);
128            else
129                continued = false;
130        }
131        if (iter < buffer + count) {
132            printf("%s", iter);
133            continued = true;
134        }
135    }
136    ATF_REQUIRE(count == 0);
137}
138
139/** Compares a file against the given golden contents.
140 *
141 * \param name Name of the file to be compared.
142 * \param contents Expected contents of the file.
143 *
144 * \return True if the file matches the contents; false otherwise. */
145bool
146atf_utils_compare_file(const char *name, const char *contents)
147{
148    const int fd = open(name, O_RDONLY);
149    ATF_REQUIRE_MSG(fd != -1, "Cannot open %s", name);
150
151    const char *pos = contents;
152    ssize_t remaining = strlen(contents);
153
154    char buffer[1024];
155    ssize_t count;
156    while ((count = read(fd, buffer, sizeof(buffer))) > 0 &&
157           count <= remaining) {
158        if (memcmp(pos, buffer, count) != 0) {
159            close(fd);
160            return false;
161        }
162        remaining -= count;
163        pos += count;
164    }
165    close(fd);
166    return count == 0 && remaining == 0;
167}
168
169/** Copies a file.
170 *
171 * \param source Path to the source file.
172 * \param destination Path to the destination file. */
173void
174atf_utils_copy_file(const char *source, const char *destination)
175{
176    const int input = open(source, O_RDONLY);
177    ATF_REQUIRE_MSG(input != -1, "Failed to open source file during "
178                    "copy (%s)", source);
179
180    const int output = open(destination, O_WRONLY | O_CREAT | O_TRUNC, 0777);
181    ATF_REQUIRE_MSG(output != -1, "Failed to open destination file during "
182                    "copy (%s)", destination);
183
184    char buffer[1024];
185    ssize_t length;
186    while ((length = read(input, buffer, sizeof(buffer))) > 0)
187        ATF_REQUIRE_MSG(write(output, buffer, length) == length,
188                        "Failed to write to %s during copy", destination);
189    ATF_REQUIRE_MSG(length != -1, "Failed to read from %s during copy", source);
190
191    struct stat sb;
192    ATF_REQUIRE_MSG(fstat(input, &sb) != -1,
193                    "Failed to stat source file %s during copy", source);
194    ATF_REQUIRE_MSG(fchmod(output, sb.st_mode) != -1,
195                    "Failed to chmod destination file %s during copy",
196                    destination);
197
198    close(output);
199    close(input);
200}
201
202/** Creates a file.
203 *
204 * \param name Name of the file to create.
205 * \param contents Text to write into the created file.
206 * \param ... Positional parameters to the contents. */
207void
208atf_utils_create_file(const char *name, const char *contents, ...)
209{
210    va_list ap;
211    atf_dynstr_t formatted;
212    atf_error_t error;
213
214    va_start(ap, contents);
215    error = atf_dynstr_init_ap(&formatted, contents, ap);
216    va_end(ap);
217    ATF_REQUIRE(!atf_is_error(error));
218
219    const int fd = open(name, O_WRONLY | O_CREAT | O_TRUNC, 0644);
220    ATF_REQUIRE_MSG(fd != -1, "Cannot create file %s", name);
221    ATF_REQUIRE(write(fd, atf_dynstr_cstring(&formatted),
222                      atf_dynstr_length(&formatted)) != -1);
223    close(fd);
224
225    atf_dynstr_fini(&formatted);
226}
227
228/** Checks if a file exists.
229 *
230 * \param path Location of the file to check for.
231 *
232 * \return True if the file exists, false otherwise. */
233bool
234atf_utils_file_exists(const char *path)
235{
236    const int ret = access(path, F_OK);
237    if (ret == -1) {
238        if (errno != ENOENT)
239            atf_tc_fail("Failed to check the existence of %s: %s", path,
240                        strerror(errno));
241        else
242            return false;
243    } else
244        return true;
245}
246
247/** Spawns a subprocess and redirects its output to files.
248 *
249 * Use the atf_utils_wait() function to wait for the completion of the spawned
250 * subprocess and validate its exit conditions.
251 *
252 * \return 0 in the new child; the PID of the new child in the parent.  Does
253 * not return in error conditions. */
254pid_t
255atf_utils_fork(void)
256{
257    const pid_t pid = fork();
258    if (pid == -1)
259        atf_tc_fail("fork failed");
260
261    if (pid == 0) {
262        atf_dynstr_t out_name;
263        init_out_filename(&out_name, getpid(), "out", false);
264
265        atf_dynstr_t err_name;
266        init_out_filename(&err_name, getpid(), "err", false);
267
268        atf_utils_redirect(STDOUT_FILENO, atf_dynstr_cstring(&out_name));
269        atf_utils_redirect(STDERR_FILENO, atf_dynstr_cstring(&err_name));
270
271        atf_dynstr_fini(&err_name);
272        atf_dynstr_fini(&out_name);
273    }
274    return pid;
275}
276
277void
278atf_utils_reset_resultsfile(void)
279{
280
281    atf_tc_set_resultsfile("/dev/null");
282}
283
284/** Frees an dynamically-allocated "argv" array.
285 *
286 * \param argv A dynamically-allocated array of dynamically-allocated
287 *     strings. */
288void
289atf_utils_free_charpp(char **argv)
290{
291    char **ptr;
292
293    for (ptr = argv; *ptr != NULL; ptr++)
294        free(*ptr);
295
296    free(argv);
297}
298
299/** Searches for a regexp in a file.
300 *
301 * \param regex The regexp to look for.
302 * \param file The file in which to look for the expression.
303 * \param ... Positional parameters to the regex.
304 *
305 * \return True if there is a match; false otherwise. */
306bool
307atf_utils_grep_file(const char *regex, const char *file, ...)
308{
309    int fd;
310    va_list ap;
311    atf_dynstr_t formatted;
312    atf_error_t error;
313
314    va_start(ap, file);
315    error = atf_dynstr_init_ap(&formatted, regex, ap);
316    va_end(ap);
317    ATF_REQUIRE(!atf_is_error(error));
318
319    ATF_REQUIRE((fd = open(file, O_RDONLY)) != -1);
320    bool found = false;
321    char *line = NULL;
322    while (!found && (line = atf_utils_readline(fd)) != NULL) {
323        found = grep_string(atf_dynstr_cstring(&formatted), line);
324        free(line);
325    }
326    close(fd);
327
328    atf_dynstr_fini(&formatted);
329
330    return found;
331}
332
333/** Searches for a regexp in a string.
334 *
335 * \param regex The regexp to look for.
336 * \param str The string in which to look for the expression.
337 * \param ... Positional parameters to the regex.
338 *
339 * \return True if there is a match; false otherwise. */
340bool
341atf_utils_grep_string(const char *regex, const char *str, ...)
342{
343    bool res;
344    va_list ap;
345    atf_dynstr_t formatted;
346    atf_error_t error;
347
348    va_start(ap, str);
349    error = atf_dynstr_init_ap(&formatted, regex, ap);
350    va_end(ap);
351    ATF_REQUIRE(!atf_is_error(error));
352
353    res = grep_string(atf_dynstr_cstring(&formatted), str);
354
355    atf_dynstr_fini(&formatted);
356
357    return res;
358}
359
360/** Reads a line of arbitrary length.
361 *
362 * \param fd The descriptor from which to read the line.
363 *
364 * \return A pointer to the read line, which must be released with free(), or
365 * NULL if there was nothing to read from the file. */
366char *
367atf_utils_readline(const int fd)
368{
369    char ch;
370    ssize_t cnt;
371    atf_dynstr_t temp;
372    atf_error_t error;
373
374    error = atf_dynstr_init(&temp);
375    ATF_REQUIRE(!atf_is_error(error));
376
377    while ((cnt = read(fd, &ch, sizeof(ch))) == sizeof(ch) &&
378           ch != '\n') {
379        error = atf_dynstr_append_fmt(&temp, "%c", ch);
380        ATF_REQUIRE(!atf_is_error(error));
381    }
382    ATF_REQUIRE(cnt != -1);
383
384    if (cnt == 0 && atf_dynstr_length(&temp) == 0) {
385        atf_dynstr_fini(&temp);
386        return NULL;
387    } else
388        return atf_dynstr_fini_disown(&temp);
389}
390
391/** Redirects a file descriptor to a file.
392 *
393 * \param target_fd The file descriptor to be replaced.
394 * \param name The name of the file to direct the descriptor to.
395 *
396 * \pre Should only be called from the process spawned by fork_for_testing
397 * because this exits uncontrolledly.
398 * \post Terminates execution if the redirection fails. */
399void
400atf_utils_redirect(const int target_fd, const char *name)
401{
402    if (target_fd == STDOUT_FILENO)
403        fflush(stdout);
404    else if (target_fd == STDERR_FILENO)
405        fflush(stderr);
406
407    const int new_fd = open(name, O_WRONLY | O_CREAT | O_TRUNC, 0644);
408    if (new_fd == -1)
409        err(EXIT_FAILURE, "Cannot create %s", name);
410    if (new_fd != target_fd) {
411        if (dup2(new_fd, target_fd) == -1)
412            err(EXIT_FAILURE, "Cannot redirect to fd %d", target_fd);
413    }
414    close(new_fd);
415}
416
417/** Waits for a subprocess and validates its exit condition.
418 *
419 * \param pid The process to be waited for.  Must have been started by
420 *     testutils_fork().
421 * \param exitstatus Expected exit status.
422 * \param expout Expected contents of stdout.
423 * \param experr Expected contents of stderr. */
424void
425atf_utils_wait(const pid_t pid, const int exitstatus, const char *expout,
426               const char *experr)
427{
428    int status;
429    ATF_REQUIRE(waitpid(pid, &status, 0) != -1);
430
431    atf_dynstr_t out_name;
432    init_out_filename(&out_name, pid, "out", true);
433
434    atf_dynstr_t err_name;
435    init_out_filename(&err_name, pid, "err", true);
436
437    atf_utils_cat_file(atf_dynstr_cstring(&out_name), "subprocess stdout: ");
438    atf_utils_cat_file(atf_dynstr_cstring(&err_name), "subprocess stderr: ");
439
440    ATF_REQUIRE(WIFEXITED(status));
441    ATF_REQUIRE_EQ(exitstatus, WEXITSTATUS(status));
442
443    const char *save_prefix = "save:";
444    const size_t save_prefix_length = strlen(save_prefix);
445
446    if (strlen(expout) > save_prefix_length &&
447        strncmp(expout, save_prefix, save_prefix_length) == 0) {
448        atf_utils_copy_file(atf_dynstr_cstring(&out_name),
449                            expout + save_prefix_length);
450    } else {
451        ATF_REQUIRE(atf_utils_compare_file(atf_dynstr_cstring(&out_name),
452                                           expout));
453    }
454
455    if (strlen(experr) > save_prefix_length &&
456        strncmp(experr, save_prefix, save_prefix_length) == 0) {
457        atf_utils_copy_file(atf_dynstr_cstring(&err_name),
458                            experr + save_prefix_length);
459    } else {
460        ATF_REQUIRE(atf_utils_compare_file(atf_dynstr_cstring(&err_name),
461                                           experr));
462    }
463
464    ATF_REQUIRE(unlink(atf_dynstr_cstring(&out_name)) != -1);
465    ATF_REQUIRE(unlink(atf_dynstr_cstring(&err_name)) != -1);
466}
467