xref: /freebsd/contrib/atf/atf-c/utils.c (revision 7d8f797b725e3efc0a4256554654780df83c456c)
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). */
53 static void
54 init_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. */
78 static
79 bool
80 grep_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. */
101 void
102 atf_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. */
142 bool
143 atf_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. */
170 void
171 atf_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. */
204 void
205 atf_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. */
230 bool
231 atf_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. */
251 pid_t
252 atf_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. */
278 void
279 atf_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. */
296 bool
297 atf_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. */
330 bool
331 atf_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. */
356 char *
357 atf_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. */
389 void
390 atf_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. */
414 void
415 atf_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