utils.c revision 275988
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/** Allocate a filename to be used by atf_utils_{fork,wait}.
45 *
46 * In case of a failure, marks the calling test as failed when in_parent is
47 * true, else terminates execution.
48 *
49 * \param [out] name String to contain the generated file.
50 * \param pid PID of the process that will write to the file.
51 * \param suffix Either "out" or "err".
52 * \param in_parent If true, fail with atf_tc_fail; else use err(3). */
53static void
54init_out_filename(atf_dynstr_t *name, const pid_t pid, const char *suffix,
55                  const bool in_parent)
56{
57    atf_error_t error;
58
59    error = atf_dynstr_init_fmt(name, "atf_utils_fork_%d_%s.txt",
60                                (int)pid, suffix);
61    if (atf_is_error(error)) {
62        char buffer[1024];
63        atf_error_format(error, buffer, sizeof(buffer));
64        if (in_parent) {
65            atf_tc_fail("Failed to create output file: %s", buffer);
66        } else {
67            err(EXIT_FAILURE, "Failed to create output file: %s", buffer);
68        }
69    }
70}
71
72/** Searches for a regexp in a string.
73 *
74 * \param regex The regexp to look for.
75 * \param str The string in which to look for the expression.
76 *
77 * \return True if there is a match; false otherwise. */
78static
79bool
80grep_string(const char *regex, const char *str)
81{
82    int res;
83    regex_t preg;
84
85    printf("Looking for '%s' in '%s'\n", regex, str);
86    ATF_REQUIRE(regcomp(&preg, regex, REG_EXTENDED) == 0);
87
88    res = regexec(&preg, str, 0, NULL, 0);
89    ATF_REQUIRE(res == 0 || res == REG_NOMATCH);
90
91    regfree(&preg);
92
93    return res == 0;
94}
95
96/** Prints the contents of a file to stdout.
97 *
98 * \param name The name of the file to be printed.
99 * \param prefix An string to be prepended to every line of the printed
100 *     file. */
101void
102atf_utils_cat_file(const char *name, const char *prefix)
103{
104    const int fd = open(name, O_RDONLY);
105    ATF_REQUIRE_MSG(fd != -1, "Cannot open %s", name);
106
107    char buffer[1024];
108    ssize_t count;
109    bool continued = false;
110    while ((count = read(fd, buffer, sizeof(buffer) - 1)) > 0) {
111        buffer[count] = '\0';
112
113        if (!continued)
114            printf("%s", prefix);
115
116        char *iter = buffer;
117        char *end;
118        while ((end = strchr(iter, '\n')) != NULL) {
119            *end = '\0';
120            printf("%s\n", iter);
121
122            iter = end + 1;
123            if (iter != buffer + count)
124                printf("%s", prefix);
125            else
126                continued = false;
127        }
128        if (iter < buffer + count) {
129            printf("%s", iter);
130            continued = true;
131        }
132    }
133    ATF_REQUIRE(count == 0);
134}
135
136/** Compares a file against the given golden contents.
137 *
138 * \param name Name of the file to be compared.
139 * \param contents Expected contents of the file.
140 *
141 * \return True if the file matches the contents; false otherwise. */
142bool
143atf_utils_compare_file(const char *name, const char *contents)
144{
145    const int fd = open(name, O_RDONLY);
146    ATF_REQUIRE_MSG(fd != -1, "Cannot open %s", name);
147
148    const char *pos = contents;
149    ssize_t remaining = strlen(contents);
150
151    char buffer[1024];
152    ssize_t count;
153    while ((count = read(fd, buffer, sizeof(buffer))) > 0 &&
154           count <= remaining) {
155        if (memcmp(pos, buffer, count) != 0) {
156            close(fd);
157            return false;
158        }
159        remaining -= count;
160        pos += count;
161    }
162    close(fd);
163    return count == 0 && remaining == 0;
164}
165
166/** Copies a file.
167 *
168 * \param source Path to the source file.
169 * \param destination Path to the destination file. */
170void
171atf_utils_copy_file(const char *source, const char *destination)
172{
173    const int input = open(source, O_RDONLY);
174    ATF_REQUIRE_MSG(input != -1, "Failed to open source file during "
175                    "copy (%s)", source);
176
177    const int output = open(destination, O_WRONLY | O_CREAT | O_TRUNC, 0777);
178    ATF_REQUIRE_MSG(output != -1, "Failed to open destination file during "
179                    "copy (%s)", destination);
180
181    char buffer[1024];
182    ssize_t length;
183    while ((length = read(input, buffer, sizeof(buffer))) > 0)
184        ATF_REQUIRE_MSG(write(output, buffer, length) == length,
185                        "Failed to write to %s during copy", destination);
186    ATF_REQUIRE_MSG(length != -1, "Failed to read from %s during copy", source);
187
188    struct stat sb;
189    ATF_REQUIRE_MSG(fstat(input, &sb) != -1,
190                    "Failed to stat source file %s during copy", source);
191    ATF_REQUIRE_MSG(fchmod(output, sb.st_mode) != -1,
192                    "Failed to chmod destination file %s during copy",
193                    destination);
194
195    close(output);
196    close(input);
197}
198
199/** Creates a file.
200 *
201 * \param name Name of the file to create.
202 * \param contents Text to write into the created file.
203 * \param ... Positional parameters to the contents. */
204void
205atf_utils_create_file(const char *name, const char *contents, ...)
206{
207    va_list ap;
208    atf_dynstr_t formatted;
209    atf_error_t error;
210
211    va_start(ap, contents);
212    error = atf_dynstr_init_ap(&formatted, contents, ap);
213    va_end(ap);
214    ATF_REQUIRE(!atf_is_error(error));
215
216    const int fd = open(name, O_WRONLY | O_CREAT | O_TRUNC, 0644);
217    ATF_REQUIRE_MSG(fd != -1, "Cannot create file %s", name);
218    ATF_REQUIRE(write(fd, atf_dynstr_cstring(&formatted),
219                      atf_dynstr_length(&formatted)) != -1);
220    close(fd);
221
222    atf_dynstr_fini(&formatted);
223}
224
225/** Checks if a file exists.
226 *
227 * \param path Location of the file to check for.
228 *
229 * \return True if the file exists, false otherwise. */
230bool
231atf_utils_file_exists(const char *path)
232{
233    const int ret = access(path, F_OK);
234    if (ret == -1) {
235        if (errno != ENOENT)
236            atf_tc_fail("Failed to check the existence of %s: %s", path,
237                        strerror(errno));
238        else
239            return false;
240    } else
241        return true;
242}
243
244/** Spawns a subprocess and redirects its output to files.
245 *
246 * Use the atf_utils_wait() function to wait for the completion of the spawned
247 * subprocess and validate its exit conditions.
248 *
249 * \return 0 in the new child; the PID of the new child in the parent.  Does
250 * not return in error conditions. */
251pid_t
252atf_utils_fork(void)
253{
254    const pid_t pid = fork();
255    if (pid == -1)
256        atf_tc_fail("fork failed");
257
258    if (pid == 0) {
259        atf_dynstr_t out_name;
260        init_out_filename(&out_name, getpid(), "out", false);
261
262        atf_dynstr_t err_name;
263        init_out_filename(&err_name, getpid(), "err", false);
264
265        atf_utils_redirect(STDOUT_FILENO, atf_dynstr_cstring(&out_name));
266        atf_utils_redirect(STDERR_FILENO, atf_dynstr_cstring(&err_name));
267
268        atf_dynstr_fini(&err_name);
269        atf_dynstr_fini(&out_name);
270    }
271    return pid;
272}
273
274/** Frees an dynamically-allocated "argv" array.
275 *
276 * \param argv A dynamically-allocated array of dynamically-allocated
277 *     strings. */
278void
279atf_utils_free_charpp(char **argv)
280{
281    char **ptr;
282
283    for (ptr = argv; *ptr != NULL; ptr++)
284        free(*ptr);
285
286    free(argv);
287}
288
289/** Searches for a regexp in a file.
290 *
291 * \param regex The regexp to look for.
292 * \param file The file in which to look for the expression.
293 * \param ... Positional parameters to the regex.
294 *
295 * \return True if there is a match; false otherwise. */
296bool
297atf_utils_grep_file(const char *regex, const char *file, ...)
298{
299    int fd;
300    va_list ap;
301    atf_dynstr_t formatted;
302    atf_error_t error;
303
304    va_start(ap, file);
305    error = atf_dynstr_init_ap(&formatted, regex, ap);
306    va_end(ap);
307    ATF_REQUIRE(!atf_is_error(error));
308
309    ATF_REQUIRE((fd = open(file, O_RDONLY)) != -1);
310    bool found = false;
311    char *line = NULL;
312    while (!found && (line = atf_utils_readline(fd)) != NULL) {
313        found = grep_string(atf_dynstr_cstring(&formatted), line);
314        free(line);
315    }
316    close(fd);
317
318    atf_dynstr_fini(&formatted);
319
320    return found;
321}
322
323/** Searches for a regexp in a string.
324 *
325 * \param regex The regexp to look for.
326 * \param str The string in which to look for the expression.
327 * \param ... Positional parameters to the regex.
328 *
329 * \return True if there is a match; false otherwise. */
330bool
331atf_utils_grep_string(const char *regex, const char *str, ...)
332{
333    bool res;
334    va_list ap;
335    atf_dynstr_t formatted;
336    atf_error_t error;
337
338    va_start(ap, str);
339    error = atf_dynstr_init_ap(&formatted, regex, ap);
340    va_end(ap);
341    ATF_REQUIRE(!atf_is_error(error));
342
343    res = grep_string(atf_dynstr_cstring(&formatted), str);
344
345    atf_dynstr_fini(&formatted);
346
347    return res;
348}
349
350/** Reads a line of arbitrary length.
351 *
352 * \param fd The descriptor from which to read the line.
353 *
354 * \return A pointer to the read line, which must be released with free(), or
355 * NULL if there was nothing to read from the file. */
356char *
357atf_utils_readline(const int fd)
358{
359    char ch;
360    ssize_t cnt;
361    atf_dynstr_t temp;
362    atf_error_t error;
363
364    error = atf_dynstr_init(&temp);
365    ATF_REQUIRE(!atf_is_error(error));
366
367    while ((cnt = read(fd, &ch, sizeof(ch))) == sizeof(ch) &&
368           ch != '\n') {
369        error = atf_dynstr_append_fmt(&temp, "%c", ch);
370        ATF_REQUIRE(!atf_is_error(error));
371    }
372    ATF_REQUIRE(cnt != -1);
373
374    if (cnt == 0 && atf_dynstr_length(&temp) == 0) {
375        atf_dynstr_fini(&temp);
376        return NULL;
377    } else
378        return atf_dynstr_fini_disown(&temp);
379}
380
381/** Redirects a file descriptor to a file.
382 *
383 * \param target_fd The file descriptor to be replaced.
384 * \param name The name of the file to direct the descriptor to.
385 *
386 * \pre Should only be called from the process spawned by fork_for_testing
387 * because this exits uncontrolledly.
388 * \post Terminates execution if the redirection fails. */
389void
390atf_utils_redirect(const int target_fd, const char *name)
391{
392    if (target_fd == STDOUT_FILENO)
393        fflush(stdout);
394    else if (target_fd == STDERR_FILENO)
395        fflush(stderr);
396
397    const int new_fd = open(name, O_WRONLY | O_CREAT | O_TRUNC, 0644);
398    if (new_fd == -1)
399        err(EXIT_FAILURE, "Cannot create %s", name);
400    if (new_fd != target_fd) {
401        if (dup2(new_fd, target_fd) == -1)
402            err(EXIT_FAILURE, "Cannot redirect to fd %d", target_fd);
403    }
404    close(new_fd);
405}
406
407/** Waits for a subprocess and validates its exit condition.
408 *
409 * \param pid The process to be waited for.  Must have been started by
410 *     testutils_fork().
411 * \param exitstatus Expected exit status.
412 * \param expout Expected contents of stdout.
413 * \param experr Expected contents of stderr. */
414void
415atf_utils_wait(const pid_t pid, const int exitstatus, const char *expout,
416               const char *experr)
417{
418    int status;
419    ATF_REQUIRE(waitpid(pid, &status, 0) != -1);
420
421    atf_dynstr_t out_name;
422    init_out_filename(&out_name, pid, "out", true);
423
424    atf_dynstr_t err_name;
425    init_out_filename(&err_name, pid, "err", true);
426
427    atf_utils_cat_file(atf_dynstr_cstring(&out_name), "subprocess stdout: ");
428    atf_utils_cat_file(atf_dynstr_cstring(&err_name), "subprocess stderr: ");
429
430    ATF_REQUIRE(WIFEXITED(status));
431    ATF_REQUIRE_EQ(exitstatus, WEXITSTATUS(status));
432
433    const char *save_prefix = "save:";
434    const size_t save_prefix_length = strlen(save_prefix);
435
436    if (strlen(expout) > save_prefix_length &&
437        strncmp(expout, save_prefix, save_prefix_length) == 0) {
438        atf_utils_copy_file(atf_dynstr_cstring(&out_name),
439                            expout + save_prefix_length);
440    } else {
441        ATF_REQUIRE(atf_utils_compare_file(atf_dynstr_cstring(&out_name),
442                                           expout));
443    }
444
445    if (strlen(experr) > save_prefix_length &&
446        strncmp(experr, save_prefix, save_prefix_length) == 0) {
447        atf_utils_copy_file(atf_dynstr_cstring(&err_name),
448                            experr + save_prefix_length);
449    } else {
450        ATF_REQUIRE(atf_utils_compare_file(atf_dynstr_cstring(&err_name),
451                                           experr));
452    }
453
454    ATF_REQUIRE(unlink(atf_dynstr_cstring(&out_name)) != -1);
455    ATF_REQUIRE(unlink(atf_dynstr_cstring(&err_name)) != -1);
456}
457