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