1275988Sngie/* Copyright (c) 2010 The NetBSD Foundation, Inc.
2240116Smarcel * All rights reserved.
3240116Smarcel *
4240116Smarcel * Redistribution and use in source and binary forms, with or without
5240116Smarcel * modification, are permitted provided that the following conditions
6240116Smarcel * are met:
7240116Smarcel * 1. Redistributions of source code must retain the above copyright
8240116Smarcel *    notice, this list of conditions and the following disclaimer.
9240116Smarcel * 2. Redistributions in binary form must reproduce the above copyright
10240116Smarcel *    notice, this list of conditions and the following disclaimer in the
11240116Smarcel *    documentation and/or other materials provided with the distribution.
12240116Smarcel *
13240116Smarcel * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
14240116Smarcel * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
15240116Smarcel * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
16240116Smarcel * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
17240116Smarcel * IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
18240116Smarcel * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19240116Smarcel * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
20240116Smarcel * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21240116Smarcel * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
22240116Smarcel * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
23240116Smarcel * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
24275988Sngie * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  */
25240116Smarcel
26260029Sjmmv#include "atf-c/utils.h"
27260029Sjmmv
28260029Sjmmv#include <sys/stat.h>
29260029Sjmmv#include <sys/wait.h>
30260029Sjmmv
31260029Sjmmv#include <err.h>
32260029Sjmmv#include <errno.h>
33260029Sjmmv#include <fcntl.h>
34260029Sjmmv#include <regex.h>
35260029Sjmmv#include <stdio.h>
36240116Smarcel#include <stdlib.h>
37260029Sjmmv#include <string.h>
38260029Sjmmv#include <unistd.h>
39240116Smarcel
40260029Sjmmv#include <atf-c.h>
41240116Smarcel
42275988Sngie#include "atf-c/detail/dynstr.h"
43260029Sjmmv
44275988Sngie/** Allocate a filename to be used by atf_utils_{fork,wait}.
45275988Sngie *
46275988Sngie * In case of a failure, marks the calling test as failed when in_parent is
47275988Sngie * true, else terminates execution.
48275988Sngie *
49275988Sngie * \param [out] name String to contain the generated file.
50275988Sngie * \param pid PID of the process that will write to the file.
51275988Sngie * \param suffix Either "out" or "err".
52275988Sngie * \param in_parent If true, fail with atf_tc_fail; else use err(3). */
53275988Sngiestatic void
54275988Sngieinit_out_filename(atf_dynstr_t *name, const pid_t pid, const char *suffix,
55275988Sngie                  const bool in_parent)
56275988Sngie{
57275988Sngie    atf_error_t error;
58275988Sngie
59275988Sngie    error = atf_dynstr_init_fmt(name, "atf_utils_fork_%d_%s.txt",
60275988Sngie                                (int)pid, suffix);
61275988Sngie    if (atf_is_error(error)) {
62275988Sngie        char buffer[1024];
63275988Sngie        atf_error_format(error, buffer, sizeof(buffer));
64275988Sngie        if (in_parent) {
65275988Sngie            atf_tc_fail("Failed to create output file: %s", buffer);
66275988Sngie        } else {
67275988Sngie            err(EXIT_FAILURE, "Failed to create output file: %s", buffer);
68275988Sngie        }
69275988Sngie    }
70275988Sngie}
71275988Sngie
72260029Sjmmv/** Searches for a regexp in a string.
73260029Sjmmv *
74260029Sjmmv * \param regex The regexp to look for.
75260029Sjmmv * \param str The string in which to look for the expression.
76260029Sjmmv *
77260029Sjmmv * \return True if there is a match; false otherwise. */
78260029Sjmmvstatic
79260029Sjmmvbool
80260029Sjmmvgrep_string(const char *regex, const char *str)
81260029Sjmmv{
82260029Sjmmv    int res;
83260029Sjmmv    regex_t preg;
84260029Sjmmv
85260029Sjmmv    printf("Looking for '%s' in '%s'\n", regex, str);
86260029Sjmmv    ATF_REQUIRE(regcomp(&preg, regex, REG_EXTENDED) == 0);
87260029Sjmmv
88260029Sjmmv    res = regexec(&preg, str, 0, NULL, 0);
89260029Sjmmv    ATF_REQUIRE(res == 0 || res == REG_NOMATCH);
90260029Sjmmv
91260029Sjmmv    regfree(&preg);
92260029Sjmmv
93260029Sjmmv    return res == 0;
94260029Sjmmv}
95260029Sjmmv
96260029Sjmmv/** Prints the contents of a file to stdout.
97260029Sjmmv *
98260029Sjmmv * \param name The name of the file to be printed.
99260029Sjmmv * \param prefix An string to be prepended to every line of the printed
100260029Sjmmv *     file. */
101240116Smarcelvoid
102260029Sjmmvatf_utils_cat_file(const char *name, const char *prefix)
103260029Sjmmv{
104260029Sjmmv    const int fd = open(name, O_RDONLY);
105260029Sjmmv    ATF_REQUIRE_MSG(fd != -1, "Cannot open %s", name);
106260029Sjmmv
107260029Sjmmv    char buffer[1024];
108260029Sjmmv    ssize_t count;
109260029Sjmmv    bool continued = false;
110260029Sjmmv    while ((count = read(fd, buffer, sizeof(buffer) - 1)) > 0) {
111260029Sjmmv        buffer[count] = '\0';
112260029Sjmmv
113260029Sjmmv        if (!continued)
114260029Sjmmv            printf("%s", prefix);
115260029Sjmmv
116260029Sjmmv        char *iter = buffer;
117260029Sjmmv        char *end;
118260029Sjmmv        while ((end = strchr(iter, '\n')) != NULL) {
119260029Sjmmv            *end = '\0';
120260029Sjmmv            printf("%s\n", iter);
121260029Sjmmv
122260029Sjmmv            iter = end + 1;
123260029Sjmmv            if (iter != buffer + count)
124260029Sjmmv                printf("%s", prefix);
125260029Sjmmv            else
126260029Sjmmv                continued = false;
127260029Sjmmv        }
128260029Sjmmv        if (iter < buffer + count) {
129260029Sjmmv            printf("%s", iter);
130260029Sjmmv            continued = true;
131260029Sjmmv        }
132260029Sjmmv    }
133260029Sjmmv    ATF_REQUIRE(count == 0);
134260029Sjmmv}
135260029Sjmmv
136260029Sjmmv/** Compares a file against the given golden contents.
137260029Sjmmv *
138260029Sjmmv * \param name Name of the file to be compared.
139260029Sjmmv * \param contents Expected contents of the file.
140260029Sjmmv *
141260029Sjmmv * \return True if the file matches the contents; false otherwise. */
142260029Sjmmvbool
143260029Sjmmvatf_utils_compare_file(const char *name, const char *contents)
144260029Sjmmv{
145260029Sjmmv    const int fd = open(name, O_RDONLY);
146260029Sjmmv    ATF_REQUIRE_MSG(fd != -1, "Cannot open %s", name);
147260029Sjmmv
148260029Sjmmv    const char *pos = contents;
149260029Sjmmv    ssize_t remaining = strlen(contents);
150260029Sjmmv
151260029Sjmmv    char buffer[1024];
152260029Sjmmv    ssize_t count;
153260029Sjmmv    while ((count = read(fd, buffer, sizeof(buffer))) > 0 &&
154260029Sjmmv           count <= remaining) {
155260029Sjmmv        if (memcmp(pos, buffer, count) != 0) {
156260029Sjmmv            close(fd);
157260029Sjmmv            return false;
158260029Sjmmv        }
159260029Sjmmv        remaining -= count;
160260029Sjmmv        pos += count;
161260029Sjmmv    }
162260029Sjmmv    close(fd);
163260029Sjmmv    return count == 0 && remaining == 0;
164260029Sjmmv}
165260029Sjmmv
166260029Sjmmv/** Copies a file.
167260029Sjmmv *
168260029Sjmmv * \param source Path to the source file.
169260029Sjmmv * \param destination Path to the destination file. */
170260029Sjmmvvoid
171260029Sjmmvatf_utils_copy_file(const char *source, const char *destination)
172260029Sjmmv{
173260029Sjmmv    const int input = open(source, O_RDONLY);
174260029Sjmmv    ATF_REQUIRE_MSG(input != -1, "Failed to open source file during "
175260029Sjmmv                    "copy (%s)", source);
176260029Sjmmv
177260029Sjmmv    const int output = open(destination, O_WRONLY | O_CREAT | O_TRUNC, 0777);
178260029Sjmmv    ATF_REQUIRE_MSG(output != -1, "Failed to open destination file during "
179260029Sjmmv                    "copy (%s)", destination);
180260029Sjmmv
181260029Sjmmv    char buffer[1024];
182260029Sjmmv    ssize_t length;
183260029Sjmmv    while ((length = read(input, buffer, sizeof(buffer))) > 0)
184260029Sjmmv        ATF_REQUIRE_MSG(write(output, buffer, length) == length,
185260029Sjmmv                        "Failed to write to %s during copy", destination);
186260029Sjmmv    ATF_REQUIRE_MSG(length != -1, "Failed to read from %s during copy", source);
187260029Sjmmv
188260029Sjmmv    struct stat sb;
189260029Sjmmv    ATF_REQUIRE_MSG(fstat(input, &sb) != -1,
190260029Sjmmv                    "Failed to stat source file %s during copy", source);
191260029Sjmmv    ATF_REQUIRE_MSG(fchmod(output, sb.st_mode) != -1,
192260029Sjmmv                    "Failed to chmod destination file %s during copy",
193260029Sjmmv                    destination);
194260029Sjmmv
195260029Sjmmv    close(output);
196260029Sjmmv    close(input);
197260029Sjmmv}
198260029Sjmmv
199260029Sjmmv/** Creates a file.
200260029Sjmmv *
201260029Sjmmv * \param name Name of the file to create.
202260029Sjmmv * \param contents Text to write into the created file.
203260029Sjmmv * \param ... Positional parameters to the contents. */
204260029Sjmmvvoid
205260029Sjmmvatf_utils_create_file(const char *name, const char *contents, ...)
206260029Sjmmv{
207260029Sjmmv    va_list ap;
208260029Sjmmv    atf_dynstr_t formatted;
209260029Sjmmv    atf_error_t error;
210260029Sjmmv
211260029Sjmmv    va_start(ap, contents);
212260029Sjmmv    error = atf_dynstr_init_ap(&formatted, contents, ap);
213260029Sjmmv    va_end(ap);
214260029Sjmmv    ATF_REQUIRE(!atf_is_error(error));
215260029Sjmmv
216260029Sjmmv    const int fd = open(name, O_WRONLY | O_CREAT | O_TRUNC, 0644);
217260029Sjmmv    ATF_REQUIRE_MSG(fd != -1, "Cannot create file %s", name);
218260029Sjmmv    ATF_REQUIRE(write(fd, atf_dynstr_cstring(&formatted),
219260029Sjmmv                      atf_dynstr_length(&formatted)) != -1);
220260029Sjmmv    close(fd);
221260029Sjmmv
222260029Sjmmv    atf_dynstr_fini(&formatted);
223260029Sjmmv}
224260029Sjmmv
225260029Sjmmv/** Checks if a file exists.
226260029Sjmmv *
227260029Sjmmv * \param path Location of the file to check for.
228260029Sjmmv *
229260029Sjmmv * \return True if the file exists, false otherwise. */
230260029Sjmmvbool
231260029Sjmmvatf_utils_file_exists(const char *path)
232260029Sjmmv{
233260029Sjmmv    const int ret = access(path, F_OK);
234260029Sjmmv    if (ret == -1) {
235260029Sjmmv        if (errno != ENOENT)
236260029Sjmmv            atf_tc_fail("Failed to check the existence of %s: %s", path,
237260029Sjmmv                        strerror(errno));
238260029Sjmmv        else
239260029Sjmmv            return false;
240260029Sjmmv    } else
241260029Sjmmv        return true;
242260029Sjmmv}
243260029Sjmmv
244260029Sjmmv/** Spawns a subprocess and redirects its output to files.
245260029Sjmmv *
246260029Sjmmv * Use the atf_utils_wait() function to wait for the completion of the spawned
247260029Sjmmv * subprocess and validate its exit conditions.
248260029Sjmmv *
249260029Sjmmv * \return 0 in the new child; the PID of the new child in the parent.  Does
250260029Sjmmv * not return in error conditions. */
251260029Sjmmvpid_t
252260029Sjmmvatf_utils_fork(void)
253260029Sjmmv{
254260029Sjmmv    const pid_t pid = fork();
255260029Sjmmv    if (pid == -1)
256260029Sjmmv        atf_tc_fail("fork failed");
257260029Sjmmv
258260029Sjmmv    if (pid == 0) {
259275988Sngie        atf_dynstr_t out_name;
260275988Sngie        init_out_filename(&out_name, getpid(), "out", false);
261275988Sngie
262275988Sngie        atf_dynstr_t err_name;
263275988Sngie        init_out_filename(&err_name, getpid(), "err", false);
264275988Sngie
265275988Sngie        atf_utils_redirect(STDOUT_FILENO, atf_dynstr_cstring(&out_name));
266275988Sngie        atf_utils_redirect(STDERR_FILENO, atf_dynstr_cstring(&err_name));
267275988Sngie
268275988Sngie        atf_dynstr_fini(&err_name);
269275988Sngie        atf_dynstr_fini(&out_name);
270260029Sjmmv    }
271260029Sjmmv    return pid;
272260029Sjmmv}
273260029Sjmmv
274260029Sjmmv/** Frees an dynamically-allocated "argv" array.
275260029Sjmmv *
276260029Sjmmv * \param argv A dynamically-allocated array of dynamically-allocated
277260029Sjmmv *     strings. */
278260029Sjmmvvoid
279240116Smarcelatf_utils_free_charpp(char **argv)
280240116Smarcel{
281240116Smarcel    char **ptr;
282240116Smarcel
283240116Smarcel    for (ptr = argv; *ptr != NULL; ptr++)
284240116Smarcel        free(*ptr);
285240116Smarcel
286240116Smarcel    free(argv);
287240116Smarcel}
288260029Sjmmv
289260029Sjmmv/** Searches for a regexp in a file.
290260029Sjmmv *
291260029Sjmmv * \param regex The regexp to look for.
292260029Sjmmv * \param file The file in which to look for the expression.
293260029Sjmmv * \param ... Positional parameters to the regex.
294260029Sjmmv *
295260029Sjmmv * \return True if there is a match; false otherwise. */
296260029Sjmmvbool
297260029Sjmmvatf_utils_grep_file(const char *regex, const char *file, ...)
298260029Sjmmv{
299260029Sjmmv    int fd;
300260029Sjmmv    va_list ap;
301260029Sjmmv    atf_dynstr_t formatted;
302260029Sjmmv    atf_error_t error;
303260029Sjmmv
304260029Sjmmv    va_start(ap, file);
305260029Sjmmv    error = atf_dynstr_init_ap(&formatted, regex, ap);
306260029Sjmmv    va_end(ap);
307260029Sjmmv    ATF_REQUIRE(!atf_is_error(error));
308260029Sjmmv
309260029Sjmmv    ATF_REQUIRE((fd = open(file, O_RDONLY)) != -1);
310260029Sjmmv    bool found = false;
311260029Sjmmv    char *line = NULL;
312260029Sjmmv    while (!found && (line = atf_utils_readline(fd)) != NULL) {
313260029Sjmmv        found = grep_string(atf_dynstr_cstring(&formatted), line);
314260029Sjmmv        free(line);
315260029Sjmmv    }
316260029Sjmmv    close(fd);
317260029Sjmmv
318260029Sjmmv    atf_dynstr_fini(&formatted);
319260029Sjmmv
320260029Sjmmv    return found;
321260029Sjmmv}
322260029Sjmmv
323260029Sjmmv/** Searches for a regexp in a string.
324260029Sjmmv *
325260029Sjmmv * \param regex The regexp to look for.
326260029Sjmmv * \param str The string in which to look for the expression.
327260029Sjmmv * \param ... Positional parameters to the regex.
328260029Sjmmv *
329260029Sjmmv * \return True if there is a match; false otherwise. */
330260029Sjmmvbool
331260029Sjmmvatf_utils_grep_string(const char *regex, const char *str, ...)
332260029Sjmmv{
333260029Sjmmv    bool res;
334260029Sjmmv    va_list ap;
335260029Sjmmv    atf_dynstr_t formatted;
336260029Sjmmv    atf_error_t error;
337260029Sjmmv
338260029Sjmmv    va_start(ap, str);
339260029Sjmmv    error = atf_dynstr_init_ap(&formatted, regex, ap);
340260029Sjmmv    va_end(ap);
341260029Sjmmv    ATF_REQUIRE(!atf_is_error(error));
342260029Sjmmv
343260029Sjmmv    res = grep_string(atf_dynstr_cstring(&formatted), str);
344260029Sjmmv
345260029Sjmmv    atf_dynstr_fini(&formatted);
346260029Sjmmv
347260029Sjmmv    return res;
348260029Sjmmv}
349260029Sjmmv
350260029Sjmmv/** Reads a line of arbitrary length.
351260029Sjmmv *
352260029Sjmmv * \param fd The descriptor from which to read the line.
353260029Sjmmv *
354260029Sjmmv * \return A pointer to the read line, which must be released with free(), or
355260029Sjmmv * NULL if there was nothing to read from the file. */
356260029Sjmmvchar *
357260029Sjmmvatf_utils_readline(const int fd)
358260029Sjmmv{
359260029Sjmmv    char ch;
360260029Sjmmv    ssize_t cnt;
361260029Sjmmv    atf_dynstr_t temp;
362260029Sjmmv    atf_error_t error;
363260029Sjmmv
364260029Sjmmv    error = atf_dynstr_init(&temp);
365260029Sjmmv    ATF_REQUIRE(!atf_is_error(error));
366260029Sjmmv
367260029Sjmmv    while ((cnt = read(fd, &ch, sizeof(ch))) == sizeof(ch) &&
368260029Sjmmv           ch != '\n') {
369260029Sjmmv        error = atf_dynstr_append_fmt(&temp, "%c", ch);
370260029Sjmmv        ATF_REQUIRE(!atf_is_error(error));
371260029Sjmmv    }
372260029Sjmmv    ATF_REQUIRE(cnt != -1);
373260029Sjmmv
374260029Sjmmv    if (cnt == 0 && atf_dynstr_length(&temp) == 0) {
375260029Sjmmv        atf_dynstr_fini(&temp);
376260029Sjmmv        return NULL;
377260029Sjmmv    } else
378260029Sjmmv        return atf_dynstr_fini_disown(&temp);
379260029Sjmmv}
380260029Sjmmv
381260029Sjmmv/** Redirects a file descriptor to a file.
382260029Sjmmv *
383260029Sjmmv * \param target_fd The file descriptor to be replaced.
384260029Sjmmv * \param name The name of the file to direct the descriptor to.
385260029Sjmmv *
386260029Sjmmv * \pre Should only be called from the process spawned by fork_for_testing
387260029Sjmmv * because this exits uncontrolledly.
388260029Sjmmv * \post Terminates execution if the redirection fails. */
389260029Sjmmvvoid
390260029Sjmmvatf_utils_redirect(const int target_fd, const char *name)
391260029Sjmmv{
392260029Sjmmv    if (target_fd == STDOUT_FILENO)
393260029Sjmmv        fflush(stdout);
394260029Sjmmv    else if (target_fd == STDERR_FILENO)
395260029Sjmmv        fflush(stderr);
396260029Sjmmv
397260029Sjmmv    const int new_fd = open(name, O_WRONLY | O_CREAT | O_TRUNC, 0644);
398260029Sjmmv    if (new_fd == -1)
399260029Sjmmv        err(EXIT_FAILURE, "Cannot create %s", name);
400260029Sjmmv    if (new_fd != target_fd) {
401260029Sjmmv        if (dup2(new_fd, target_fd) == -1)
402260029Sjmmv            err(EXIT_FAILURE, "Cannot redirect to fd %d", target_fd);
403260029Sjmmv    }
404260029Sjmmv    close(new_fd);
405260029Sjmmv}
406260029Sjmmv
407260029Sjmmv/** Waits for a subprocess and validates its exit condition.
408260029Sjmmv *
409260029Sjmmv * \param pid The process to be waited for.  Must have been started by
410260029Sjmmv *     testutils_fork().
411260029Sjmmv * \param exitstatus Expected exit status.
412260029Sjmmv * \param expout Expected contents of stdout.
413260029Sjmmv * \param experr Expected contents of stderr. */
414260029Sjmmvvoid
415260029Sjmmvatf_utils_wait(const pid_t pid, const int exitstatus, const char *expout,
416260029Sjmmv               const char *experr)
417260029Sjmmv{
418260029Sjmmv    int status;
419260029Sjmmv    ATF_REQUIRE(waitpid(pid, &status, 0) != -1);
420260029Sjmmv
421275988Sngie    atf_dynstr_t out_name;
422275988Sngie    init_out_filename(&out_name, pid, "out", true);
423260029Sjmmv
424275988Sngie    atf_dynstr_t err_name;
425275988Sngie    init_out_filename(&err_name, pid, "err", true);
426275988Sngie
427275988Sngie    atf_utils_cat_file(atf_dynstr_cstring(&out_name), "subprocess stdout: ");
428275988Sngie    atf_utils_cat_file(atf_dynstr_cstring(&err_name), "subprocess stderr: ");
429275988Sngie
430260029Sjmmv    ATF_REQUIRE(WIFEXITED(status));
431260029Sjmmv    ATF_REQUIRE_EQ(exitstatus, WEXITSTATUS(status));
432260029Sjmmv
433260029Sjmmv    const char *save_prefix = "save:";
434260029Sjmmv    const size_t save_prefix_length = strlen(save_prefix);
435260029Sjmmv
436260029Sjmmv    if (strlen(expout) > save_prefix_length &&
437260029Sjmmv        strncmp(expout, save_prefix, save_prefix_length) == 0) {
438275988Sngie        atf_utils_copy_file(atf_dynstr_cstring(&out_name),
439260029Sjmmv                            expout + save_prefix_length);
440260029Sjmmv    } else {
441275988Sngie        ATF_REQUIRE(atf_utils_compare_file(atf_dynstr_cstring(&out_name),
442275988Sngie                                           expout));
443260029Sjmmv    }
444260029Sjmmv
445260029Sjmmv    if (strlen(experr) > save_prefix_length &&
446260029Sjmmv        strncmp(experr, save_prefix, save_prefix_length) == 0) {
447275988Sngie        atf_utils_copy_file(atf_dynstr_cstring(&err_name),
448260029Sjmmv                            experr + save_prefix_length);
449260029Sjmmv    } else {
450275988Sngie        ATF_REQUIRE(atf_utils_compare_file(atf_dynstr_cstring(&err_name),
451275988Sngie                                           experr));
452260029Sjmmv    }
453260029Sjmmv
454275988Sngie    ATF_REQUIRE(unlink(atf_dynstr_cstring(&out_name)) != -1);
455275988Sngie    ATF_REQUIRE(unlink(atf_dynstr_cstring(&err_name)) != -1);
456260029Sjmmv}
457