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 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 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 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 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 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 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 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 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 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 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 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 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 * 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 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 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