xref: /freebsd/contrib/atf/atf-c/utils.c (revision c203bd70b5957f85616424b6fa374479372d06e3)
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). */
45 void 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). */
56 static void
init_out_filename(atf_dynstr_t * name,const pid_t pid,const char * suffix,const bool in_parent)57 init_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. */
81 static
82 bool
grep_string(const char * regex,const char * str)83 grep_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. */
104 void
atf_utils_cat_file(const char * name,const char * prefix)105 atf_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. */
145 bool
atf_utils_compare_file(const char * name,const char * contents)146 atf_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. */
173 void
atf_utils_copy_file(const char * source,const char * destination)174 atf_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. */
207 void
atf_utils_create_file(const char * name,const char * contents,...)208 atf_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. */
233 bool
atf_utils_file_exists(const char * path)234 atf_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. */
254 pid_t
atf_utils_fork(void)255 atf_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 
277 void
atf_utils_reset_resultsfile(void)278 atf_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. */
288 void
atf_utils_free_charpp(char ** argv)289 atf_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. */
306 bool
atf_utils_grep_file(const char * regex,const char * file,...)307 atf_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. */
340 bool
atf_utils_grep_string(const char * regex,const char * str,...)341 atf_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. */
366 char *
atf_utils_readline(const int fd)367 atf_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. */
399 void
atf_utils_redirect(const int target_fd,const char * name)400 atf_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. */
424 void
atf_utils_wait(const pid_t pid,const int exitstatus,const char * expout,const char * experr)425 atf_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