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