1275988Sngie/* Copyright (c) 2010 The NetBSD Foundation, Inc. 2240116Smarcel * All rights reserved. 3240116Smarcel * 4240116Smarcel * Redistribution and use in source and binary forms, with or without 5240116Smarcel * modification, are permitted provided that the following conditions 6240116Smarcel * are met: 7240116Smarcel * 1. Redistributions of source code must retain the above copyright 8240116Smarcel * notice, this list of conditions and the following disclaimer. 9240116Smarcel * 2. Redistributions in binary form must reproduce the above copyright 10240116Smarcel * notice, this list of conditions and the following disclaimer in the 11240116Smarcel * documentation and/or other materials provided with the distribution. 12240116Smarcel * 13240116Smarcel * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND 14240116Smarcel * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 15240116Smarcel * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 16240116Smarcel * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 17240116Smarcel * IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY 18240116Smarcel * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19240116Smarcel * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 20240116Smarcel * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21240116Smarcel * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER 22240116Smarcel * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 23240116Smarcel * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN 24275988Sngie * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ 25240116Smarcel 26260029Sjmmv#include "atf-c/utils.h" 27260029Sjmmv 28260029Sjmmv#include <sys/stat.h> 29260029Sjmmv#include <sys/wait.h> 30260029Sjmmv 31260029Sjmmv#include <err.h> 32260029Sjmmv#include <errno.h> 33260029Sjmmv#include <fcntl.h> 34260029Sjmmv#include <regex.h> 35260029Sjmmv#include <stdio.h> 36240116Smarcel#include <stdlib.h> 37260029Sjmmv#include <string.h> 38260029Sjmmv#include <unistd.h> 39240116Smarcel 40260029Sjmmv#include <atf-c.h> 41240116Smarcel 42275988Sngie#include "atf-c/detail/dynstr.h" 43260029Sjmmv 44275988Sngie/** Allocate a filename to be used by atf_utils_{fork,wait}. 45275988Sngie * 46275988Sngie * In case of a failure, marks the calling test as failed when in_parent is 47275988Sngie * true, else terminates execution. 48275988Sngie * 49275988Sngie * \param [out] name String to contain the generated file. 50275988Sngie * \param pid PID of the process that will write to the file. 51275988Sngie * \param suffix Either "out" or "err". 52275988Sngie * \param in_parent If true, fail with atf_tc_fail; else use err(3). */ 53275988Sngiestatic void 54275988Sngieinit_out_filename(atf_dynstr_t *name, const pid_t pid, const char *suffix, 55275988Sngie const bool in_parent) 56275988Sngie{ 57275988Sngie atf_error_t error; 58275988Sngie 59275988Sngie error = atf_dynstr_init_fmt(name, "atf_utils_fork_%d_%s.txt", 60275988Sngie (int)pid, suffix); 61275988Sngie if (atf_is_error(error)) { 62275988Sngie char buffer[1024]; 63275988Sngie atf_error_format(error, buffer, sizeof(buffer)); 64275988Sngie if (in_parent) { 65275988Sngie atf_tc_fail("Failed to create output file: %s", buffer); 66275988Sngie } else { 67275988Sngie err(EXIT_FAILURE, "Failed to create output file: %s", buffer); 68275988Sngie } 69275988Sngie } 70275988Sngie} 71275988Sngie 72260029Sjmmv/** Searches for a regexp in a string. 73260029Sjmmv * 74260029Sjmmv * \param regex The regexp to look for. 75260029Sjmmv * \param str The string in which to look for the expression. 76260029Sjmmv * 77260029Sjmmv * \return True if there is a match; false otherwise. */ 78260029Sjmmvstatic 79260029Sjmmvbool 80260029Sjmmvgrep_string(const char *regex, const char *str) 81260029Sjmmv{ 82260029Sjmmv int res; 83260029Sjmmv regex_t preg; 84260029Sjmmv 85260029Sjmmv printf("Looking for '%s' in '%s'\n", regex, str); 86260029Sjmmv ATF_REQUIRE(regcomp(&preg, regex, REG_EXTENDED) == 0); 87260029Sjmmv 88260029Sjmmv res = regexec(&preg, str, 0, NULL, 0); 89260029Sjmmv ATF_REQUIRE(res == 0 || res == REG_NOMATCH); 90260029Sjmmv 91260029Sjmmv regfree(&preg); 92260029Sjmmv 93260029Sjmmv return res == 0; 94260029Sjmmv} 95260029Sjmmv 96260029Sjmmv/** Prints the contents of a file to stdout. 97260029Sjmmv * 98260029Sjmmv * \param name The name of the file to be printed. 99260029Sjmmv * \param prefix An string to be prepended to every line of the printed 100260029Sjmmv * file. */ 101240116Smarcelvoid 102260029Sjmmvatf_utils_cat_file(const char *name, const char *prefix) 103260029Sjmmv{ 104260029Sjmmv const int fd = open(name, O_RDONLY); 105260029Sjmmv ATF_REQUIRE_MSG(fd != -1, "Cannot open %s", name); 106260029Sjmmv 107260029Sjmmv char buffer[1024]; 108260029Sjmmv ssize_t count; 109260029Sjmmv bool continued = false; 110260029Sjmmv while ((count = read(fd, buffer, sizeof(buffer) - 1)) > 0) { 111260029Sjmmv buffer[count] = '\0'; 112260029Sjmmv 113260029Sjmmv if (!continued) 114260029Sjmmv printf("%s", prefix); 115260029Sjmmv 116260029Sjmmv char *iter = buffer; 117260029Sjmmv char *end; 118260029Sjmmv while ((end = strchr(iter, '\n')) != NULL) { 119260029Sjmmv *end = '\0'; 120260029Sjmmv printf("%s\n", iter); 121260029Sjmmv 122260029Sjmmv iter = end + 1; 123260029Sjmmv if (iter != buffer + count) 124260029Sjmmv printf("%s", prefix); 125260029Sjmmv else 126260029Sjmmv continued = false; 127260029Sjmmv } 128260029Sjmmv if (iter < buffer + count) { 129260029Sjmmv printf("%s", iter); 130260029Sjmmv continued = true; 131260029Sjmmv } 132260029Sjmmv } 133260029Sjmmv ATF_REQUIRE(count == 0); 134260029Sjmmv} 135260029Sjmmv 136260029Sjmmv/** Compares a file against the given golden contents. 137260029Sjmmv * 138260029Sjmmv * \param name Name of the file to be compared. 139260029Sjmmv * \param contents Expected contents of the file. 140260029Sjmmv * 141260029Sjmmv * \return True if the file matches the contents; false otherwise. */ 142260029Sjmmvbool 143260029Sjmmvatf_utils_compare_file(const char *name, const char *contents) 144260029Sjmmv{ 145260029Sjmmv const int fd = open(name, O_RDONLY); 146260029Sjmmv ATF_REQUIRE_MSG(fd != -1, "Cannot open %s", name); 147260029Sjmmv 148260029Sjmmv const char *pos = contents; 149260029Sjmmv ssize_t remaining = strlen(contents); 150260029Sjmmv 151260029Sjmmv char buffer[1024]; 152260029Sjmmv ssize_t count; 153260029Sjmmv while ((count = read(fd, buffer, sizeof(buffer))) > 0 && 154260029Sjmmv count <= remaining) { 155260029Sjmmv if (memcmp(pos, buffer, count) != 0) { 156260029Sjmmv close(fd); 157260029Sjmmv return false; 158260029Sjmmv } 159260029Sjmmv remaining -= count; 160260029Sjmmv pos += count; 161260029Sjmmv } 162260029Sjmmv close(fd); 163260029Sjmmv return count == 0 && remaining == 0; 164260029Sjmmv} 165260029Sjmmv 166260029Sjmmv/** Copies a file. 167260029Sjmmv * 168260029Sjmmv * \param source Path to the source file. 169260029Sjmmv * \param destination Path to the destination file. */ 170260029Sjmmvvoid 171260029Sjmmvatf_utils_copy_file(const char *source, const char *destination) 172260029Sjmmv{ 173260029Sjmmv const int input = open(source, O_RDONLY); 174260029Sjmmv ATF_REQUIRE_MSG(input != -1, "Failed to open source file during " 175260029Sjmmv "copy (%s)", source); 176260029Sjmmv 177260029Sjmmv const int output = open(destination, O_WRONLY | O_CREAT | O_TRUNC, 0777); 178260029Sjmmv ATF_REQUIRE_MSG(output != -1, "Failed to open destination file during " 179260029Sjmmv "copy (%s)", destination); 180260029Sjmmv 181260029Sjmmv char buffer[1024]; 182260029Sjmmv ssize_t length; 183260029Sjmmv while ((length = read(input, buffer, sizeof(buffer))) > 0) 184260029Sjmmv ATF_REQUIRE_MSG(write(output, buffer, length) == length, 185260029Sjmmv "Failed to write to %s during copy", destination); 186260029Sjmmv ATF_REQUIRE_MSG(length != -1, "Failed to read from %s during copy", source); 187260029Sjmmv 188260029Sjmmv struct stat sb; 189260029Sjmmv ATF_REQUIRE_MSG(fstat(input, &sb) != -1, 190260029Sjmmv "Failed to stat source file %s during copy", source); 191260029Sjmmv ATF_REQUIRE_MSG(fchmod(output, sb.st_mode) != -1, 192260029Sjmmv "Failed to chmod destination file %s during copy", 193260029Sjmmv destination); 194260029Sjmmv 195260029Sjmmv close(output); 196260029Sjmmv close(input); 197260029Sjmmv} 198260029Sjmmv 199260029Sjmmv/** Creates a file. 200260029Sjmmv * 201260029Sjmmv * \param name Name of the file to create. 202260029Sjmmv * \param contents Text to write into the created file. 203260029Sjmmv * \param ... Positional parameters to the contents. */ 204260029Sjmmvvoid 205260029Sjmmvatf_utils_create_file(const char *name, const char *contents, ...) 206260029Sjmmv{ 207260029Sjmmv va_list ap; 208260029Sjmmv atf_dynstr_t formatted; 209260029Sjmmv atf_error_t error; 210260029Sjmmv 211260029Sjmmv va_start(ap, contents); 212260029Sjmmv error = atf_dynstr_init_ap(&formatted, contents, ap); 213260029Sjmmv va_end(ap); 214260029Sjmmv ATF_REQUIRE(!atf_is_error(error)); 215260029Sjmmv 216260029Sjmmv const int fd = open(name, O_WRONLY | O_CREAT | O_TRUNC, 0644); 217260029Sjmmv ATF_REQUIRE_MSG(fd != -1, "Cannot create file %s", name); 218260029Sjmmv ATF_REQUIRE(write(fd, atf_dynstr_cstring(&formatted), 219260029Sjmmv atf_dynstr_length(&formatted)) != -1); 220260029Sjmmv close(fd); 221260029Sjmmv 222260029Sjmmv atf_dynstr_fini(&formatted); 223260029Sjmmv} 224260029Sjmmv 225260029Sjmmv/** Checks if a file exists. 226260029Sjmmv * 227260029Sjmmv * \param path Location of the file to check for. 228260029Sjmmv * 229260029Sjmmv * \return True if the file exists, false otherwise. */ 230260029Sjmmvbool 231260029Sjmmvatf_utils_file_exists(const char *path) 232260029Sjmmv{ 233260029Sjmmv const int ret = access(path, F_OK); 234260029Sjmmv if (ret == -1) { 235260029Sjmmv if (errno != ENOENT) 236260029Sjmmv atf_tc_fail("Failed to check the existence of %s: %s", path, 237260029Sjmmv strerror(errno)); 238260029Sjmmv else 239260029Sjmmv return false; 240260029Sjmmv } else 241260029Sjmmv return true; 242260029Sjmmv} 243260029Sjmmv 244260029Sjmmv/** Spawns a subprocess and redirects its output to files. 245260029Sjmmv * 246260029Sjmmv * Use the atf_utils_wait() function to wait for the completion of the spawned 247260029Sjmmv * subprocess and validate its exit conditions. 248260029Sjmmv * 249260029Sjmmv * \return 0 in the new child; the PID of the new child in the parent. Does 250260029Sjmmv * not return in error conditions. */ 251260029Sjmmvpid_t 252260029Sjmmvatf_utils_fork(void) 253260029Sjmmv{ 254260029Sjmmv const pid_t pid = fork(); 255260029Sjmmv if (pid == -1) 256260029Sjmmv atf_tc_fail("fork failed"); 257260029Sjmmv 258260029Sjmmv if (pid == 0) { 259275988Sngie atf_dynstr_t out_name; 260275988Sngie init_out_filename(&out_name, getpid(), "out", false); 261275988Sngie 262275988Sngie atf_dynstr_t err_name; 263275988Sngie init_out_filename(&err_name, getpid(), "err", false); 264275988Sngie 265275988Sngie atf_utils_redirect(STDOUT_FILENO, atf_dynstr_cstring(&out_name)); 266275988Sngie atf_utils_redirect(STDERR_FILENO, atf_dynstr_cstring(&err_name)); 267275988Sngie 268275988Sngie atf_dynstr_fini(&err_name); 269275988Sngie atf_dynstr_fini(&out_name); 270260029Sjmmv } 271260029Sjmmv return pid; 272260029Sjmmv} 273260029Sjmmv 274260029Sjmmv/** Frees an dynamically-allocated "argv" array. 275260029Sjmmv * 276260029Sjmmv * \param argv A dynamically-allocated array of dynamically-allocated 277260029Sjmmv * strings. */ 278260029Sjmmvvoid 279240116Smarcelatf_utils_free_charpp(char **argv) 280240116Smarcel{ 281240116Smarcel char **ptr; 282240116Smarcel 283240116Smarcel for (ptr = argv; *ptr != NULL; ptr++) 284240116Smarcel free(*ptr); 285240116Smarcel 286240116Smarcel free(argv); 287240116Smarcel} 288260029Sjmmv 289260029Sjmmv/** Searches for a regexp in a file. 290260029Sjmmv * 291260029Sjmmv * \param regex The regexp to look for. 292260029Sjmmv * \param file The file in which to look for the expression. 293260029Sjmmv * \param ... Positional parameters to the regex. 294260029Sjmmv * 295260029Sjmmv * \return True if there is a match; false otherwise. */ 296260029Sjmmvbool 297260029Sjmmvatf_utils_grep_file(const char *regex, const char *file, ...) 298260029Sjmmv{ 299260029Sjmmv int fd; 300260029Sjmmv va_list ap; 301260029Sjmmv atf_dynstr_t formatted; 302260029Sjmmv atf_error_t error; 303260029Sjmmv 304260029Sjmmv va_start(ap, file); 305260029Sjmmv error = atf_dynstr_init_ap(&formatted, regex, ap); 306260029Sjmmv va_end(ap); 307260029Sjmmv ATF_REQUIRE(!atf_is_error(error)); 308260029Sjmmv 309260029Sjmmv ATF_REQUIRE((fd = open(file, O_RDONLY)) != -1); 310260029Sjmmv bool found = false; 311260029Sjmmv char *line = NULL; 312260029Sjmmv while (!found && (line = atf_utils_readline(fd)) != NULL) { 313260029Sjmmv found = grep_string(atf_dynstr_cstring(&formatted), line); 314260029Sjmmv free(line); 315260029Sjmmv } 316260029Sjmmv close(fd); 317260029Sjmmv 318260029Sjmmv atf_dynstr_fini(&formatted); 319260029Sjmmv 320260029Sjmmv return found; 321260029Sjmmv} 322260029Sjmmv 323260029Sjmmv/** Searches for a regexp in a string. 324260029Sjmmv * 325260029Sjmmv * \param regex The regexp to look for. 326260029Sjmmv * \param str The string in which to look for the expression. 327260029Sjmmv * \param ... Positional parameters to the regex. 328260029Sjmmv * 329260029Sjmmv * \return True if there is a match; false otherwise. */ 330260029Sjmmvbool 331260029Sjmmvatf_utils_grep_string(const char *regex, const char *str, ...) 332260029Sjmmv{ 333260029Sjmmv bool res; 334260029Sjmmv va_list ap; 335260029Sjmmv atf_dynstr_t formatted; 336260029Sjmmv atf_error_t error; 337260029Sjmmv 338260029Sjmmv va_start(ap, str); 339260029Sjmmv error = atf_dynstr_init_ap(&formatted, regex, ap); 340260029Sjmmv va_end(ap); 341260029Sjmmv ATF_REQUIRE(!atf_is_error(error)); 342260029Sjmmv 343260029Sjmmv res = grep_string(atf_dynstr_cstring(&formatted), str); 344260029Sjmmv 345260029Sjmmv atf_dynstr_fini(&formatted); 346260029Sjmmv 347260029Sjmmv return res; 348260029Sjmmv} 349260029Sjmmv 350260029Sjmmv/** Reads a line of arbitrary length. 351260029Sjmmv * 352260029Sjmmv * \param fd The descriptor from which to read the line. 353260029Sjmmv * 354260029Sjmmv * \return A pointer to the read line, which must be released with free(), or 355260029Sjmmv * NULL if there was nothing to read from the file. */ 356260029Sjmmvchar * 357260029Sjmmvatf_utils_readline(const int fd) 358260029Sjmmv{ 359260029Sjmmv char ch; 360260029Sjmmv ssize_t cnt; 361260029Sjmmv atf_dynstr_t temp; 362260029Sjmmv atf_error_t error; 363260029Sjmmv 364260029Sjmmv error = atf_dynstr_init(&temp); 365260029Sjmmv ATF_REQUIRE(!atf_is_error(error)); 366260029Sjmmv 367260029Sjmmv while ((cnt = read(fd, &ch, sizeof(ch))) == sizeof(ch) && 368260029Sjmmv ch != '\n') { 369260029Sjmmv error = atf_dynstr_append_fmt(&temp, "%c", ch); 370260029Sjmmv ATF_REQUIRE(!atf_is_error(error)); 371260029Sjmmv } 372260029Sjmmv ATF_REQUIRE(cnt != -1); 373260029Sjmmv 374260029Sjmmv if (cnt == 0 && atf_dynstr_length(&temp) == 0) { 375260029Sjmmv atf_dynstr_fini(&temp); 376260029Sjmmv return NULL; 377260029Sjmmv } else 378260029Sjmmv return atf_dynstr_fini_disown(&temp); 379260029Sjmmv} 380260029Sjmmv 381260029Sjmmv/** Redirects a file descriptor to a file. 382260029Sjmmv * 383260029Sjmmv * \param target_fd The file descriptor to be replaced. 384260029Sjmmv * \param name The name of the file to direct the descriptor to. 385260029Sjmmv * 386260029Sjmmv * \pre Should only be called from the process spawned by fork_for_testing 387260029Sjmmv * because this exits uncontrolledly. 388260029Sjmmv * \post Terminates execution if the redirection fails. */ 389260029Sjmmvvoid 390260029Sjmmvatf_utils_redirect(const int target_fd, const char *name) 391260029Sjmmv{ 392260029Sjmmv if (target_fd == STDOUT_FILENO) 393260029Sjmmv fflush(stdout); 394260029Sjmmv else if (target_fd == STDERR_FILENO) 395260029Sjmmv fflush(stderr); 396260029Sjmmv 397260029Sjmmv const int new_fd = open(name, O_WRONLY | O_CREAT | O_TRUNC, 0644); 398260029Sjmmv if (new_fd == -1) 399260029Sjmmv err(EXIT_FAILURE, "Cannot create %s", name); 400260029Sjmmv if (new_fd != target_fd) { 401260029Sjmmv if (dup2(new_fd, target_fd) == -1) 402260029Sjmmv err(EXIT_FAILURE, "Cannot redirect to fd %d", target_fd); 403260029Sjmmv } 404260029Sjmmv close(new_fd); 405260029Sjmmv} 406260029Sjmmv 407260029Sjmmv/** Waits for a subprocess and validates its exit condition. 408260029Sjmmv * 409260029Sjmmv * \param pid The process to be waited for. Must have been started by 410260029Sjmmv * testutils_fork(). 411260029Sjmmv * \param exitstatus Expected exit status. 412260029Sjmmv * \param expout Expected contents of stdout. 413260029Sjmmv * \param experr Expected contents of stderr. */ 414260029Sjmmvvoid 415260029Sjmmvatf_utils_wait(const pid_t pid, const int exitstatus, const char *expout, 416260029Sjmmv const char *experr) 417260029Sjmmv{ 418260029Sjmmv int status; 419260029Sjmmv ATF_REQUIRE(waitpid(pid, &status, 0) != -1); 420260029Sjmmv 421275988Sngie atf_dynstr_t out_name; 422275988Sngie init_out_filename(&out_name, pid, "out", true); 423260029Sjmmv 424275988Sngie atf_dynstr_t err_name; 425275988Sngie init_out_filename(&err_name, pid, "err", true); 426275988Sngie 427275988Sngie atf_utils_cat_file(atf_dynstr_cstring(&out_name), "subprocess stdout: "); 428275988Sngie atf_utils_cat_file(atf_dynstr_cstring(&err_name), "subprocess stderr: "); 429275988Sngie 430260029Sjmmv ATF_REQUIRE(WIFEXITED(status)); 431260029Sjmmv ATF_REQUIRE_EQ(exitstatus, WEXITSTATUS(status)); 432260029Sjmmv 433260029Sjmmv const char *save_prefix = "save:"; 434260029Sjmmv const size_t save_prefix_length = strlen(save_prefix); 435260029Sjmmv 436260029Sjmmv if (strlen(expout) > save_prefix_length && 437260029Sjmmv strncmp(expout, save_prefix, save_prefix_length) == 0) { 438275988Sngie atf_utils_copy_file(atf_dynstr_cstring(&out_name), 439260029Sjmmv expout + save_prefix_length); 440260029Sjmmv } else { 441275988Sngie ATF_REQUIRE(atf_utils_compare_file(atf_dynstr_cstring(&out_name), 442275988Sngie expout)); 443260029Sjmmv } 444260029Sjmmv 445260029Sjmmv if (strlen(experr) > save_prefix_length && 446260029Sjmmv strncmp(experr, save_prefix, save_prefix_length) == 0) { 447275988Sngie atf_utils_copy_file(atf_dynstr_cstring(&err_name), 448260029Sjmmv experr + save_prefix_length); 449260029Sjmmv } else { 450275988Sngie ATF_REQUIRE(atf_utils_compare_file(atf_dynstr_cstring(&err_name), 451275988Sngie experr)); 452260029Sjmmv } 453260029Sjmmv 454275988Sngie ATF_REQUIRE(unlink(atf_dynstr_cstring(&out_name)) != -1); 455275988Sngie ATF_REQUIRE(unlink(atf_dynstr_cstring(&err_name)) != -1); 456260029Sjmmv} 457