1*a3e154cbSblymn /* $NetBSD: director.c,v 1.30 2024/07/18 22:10:51 blymn Exp $ */ 2bdd37afaSblymn 3bdd37afaSblymn /*- 4bdd37afaSblymn * Copyright 2009 Brett Lymn <blymn@NetBSD.org> 5cf012ad4Srillig * Copyright 2021 Roland Illig <rillig@NetBSD.org> 6bdd37afaSblymn * 7bdd37afaSblymn * All rights reserved. 8bdd37afaSblymn * 9bdd37afaSblymn * This code has been donated to The NetBSD Foundation by the Author. 10bdd37afaSblymn * 11bdd37afaSblymn * Redistribution and use in source and binary forms, with or without 12bdd37afaSblymn * modification, are permitted provided that the following conditions 13bdd37afaSblymn * are met: 14bdd37afaSblymn * 1. Redistributions of source code must retain the above copyright 15bdd37afaSblymn * notice, this list of conditions and the following disclaimer. 16bdd37afaSblymn * 2. The name of the author may not be used to endorse or promote products 1709f966d1Srillig * derived from this software without specific prior written permission 18bdd37afaSblymn * 19bdd37afaSblymn * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 20bdd37afaSblymn * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 21bdd37afaSblymn * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 22bdd37afaSblymn * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 23bdd37afaSblymn * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 24bdd37afaSblymn * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25bdd37afaSblymn * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26bdd37afaSblymn * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27bdd37afaSblymn * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 28bdd37afaSblymn * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29bdd37afaSblymn */ 30bdd37afaSblymn 3123bea509Schristos #include <sys/param.h> 3223bea509Schristos #include <sys/stat.h> 3323bea509Schristos #include <sys/mman.h> 342eec5692Srillig #include <sys/wait.h> 35bdd37afaSblymn #include <fcntl.h> 36bdd37afaSblymn #include <unistd.h> 376551109cSchristos #include <ctype.h> 38bdd37afaSblymn #include <termios.h> 39bdd37afaSblymn #include <signal.h> 40bdd37afaSblymn #include <stdio.h> 41bdd37afaSblymn #include <stdlib.h> 42bdd37afaSblymn #include <string.h> 436551109cSchristos #include <util.h> 446551109cSchristos #include <err.h> 45bdd37afaSblymn #include "returns.h" 46ce321bb0Sblymn #include "director.h" 47bdd37afaSblymn 486551109cSchristos void yyparse(void); 49bdd37afaSblymn #define DEF_TERMPATH "." 50bdd37afaSblymn #define DEF_TERM "atf" 51bdd37afaSblymn #define DEF_SLAVE "./slave" 52bdd37afaSblymn 532a18cea9Schristos const char *def_check_path = "./"; /* default check path */ 54bdd37afaSblymn 55bdd37afaSblymn extern size_t nvars; /* In testlang_conf.y */ 56bdd37afaSblymn saved_data_t saved_output; /* In testlang_conf.y */ 571b4c2377Srillig int to_slave; 581b4c2377Srillig int from_slave; 59bdd37afaSblymn int master; /* pty to the slave */ 60*a3e154cbSblymn int nofail; /* don't exit on check file fail */ 61bdd37afaSblymn int verbose; /* control verbosity of tests */ 62c8713e3dSrillig int check_file_flag; /* control check-file generation */ 632a18cea9Schristos const char *check_path; /* path to prepend to check files for output 64bdd37afaSblymn validation */ 65bdd37afaSblymn char *cur_file; /* name of file currently being read */ 66bdd37afaSblymn 67bdd37afaSblymn void init_parse_variables(int); /* in testlang_parse.y */ 68bdd37afaSblymn 69bdd37afaSblymn /* 70bdd37afaSblymn * Handle the slave exiting unexpectedly, try to recover the exit message 71bdd37afaSblymn * and print it out. 72c1b0da33Srillig * 73c1b0da33Srillig * FIXME: Must not use stdio in a signal handler. This leads to incomplete 74c1b0da33Srillig * output in verbose mode, truncating the useful part of the error message. 75bdd37afaSblymn */ 762a18cea9Schristos static void 772eec5692Srillig slave_died(int signo) 78bdd37afaSblymn { 79bdd37afaSblymn char last_words[256]; 802a18cea9Schristos size_t count; 81bdd37afaSblymn 82bdd37afaSblymn fprintf(stderr, "ERROR: Slave has exited\n"); 83bdd37afaSblymn if (saved_output.count > 0) { 84bdd37afaSblymn fprintf(stderr, "output from slave: "); 85bdd37afaSblymn for (count = 0; count < saved_output.count; count ++) { 86dc127031Srillig unsigned char b = saved_output.data[count]; 87dc127031Srillig if (isprint(b)) 88dc127031Srillig fprintf(stderr, "%c", b); 89dc127031Srillig else 90dc127031Srillig fprintf(stderr, "\\x%02x", b); 91bdd37afaSblymn } 92bdd37afaSblymn fprintf(stderr, "\n"); 93bdd37afaSblymn } 94bdd37afaSblymn 95bdd37afaSblymn if ((count = read(master, &last_words, 255)) > 0) { 96bdd37afaSblymn last_words[count] = '\0'; 97bdd37afaSblymn fprintf(stderr, "slave exited with message \"%s\"\n", 98bdd37afaSblymn last_words); 99bdd37afaSblymn } 100bdd37afaSblymn 101bdd37afaSblymn exit(2); 102bdd37afaSblymn } 103bdd37afaSblymn 104bdd37afaSblymn 105bdd37afaSblymn static void 10623bea509Schristos usage(void) 107bdd37afaSblymn { 108ce321bb0Sblymn fprintf(stderr, "Usage: %s [-vgf] [-I include-path] [-C check-path] " 10923bea509Schristos "[-T terminfo-file] [-s pathtoslave] [-t term] " 11023bea509Schristos "commandfile\n", getprogname()); 111bdd37afaSblymn fprintf(stderr, " where:\n"); 112bdd37afaSblymn fprintf(stderr, " -v enables verbose test output\n"); 113c8713e3dSrillig fprintf(stderr, " -g generates check-files if they do not exist\n"); 114c8713e3dSrillig fprintf(stderr, " -f overwrites check-files with the actual data\n"); 115fc67b0cdSjoerg fprintf(stderr, " -T is a directory containing the terminfo.cdb " 116fb153a60Srillig "file, or a file holding the terminfo description\n"); 11723bea509Schristos fprintf(stderr, " -s is the path to the slave executable\n"); 11823bea509Schristos fprintf(stderr, " -t is value to set TERM to for the test\n"); 119c8713e3dSrillig fprintf(stderr, " -C is the directory for check-files\n"); 120bdd37afaSblymn fprintf(stderr, " commandfile is a file of test directives\n"); 12123bea509Schristos exit(1); 122bdd37afaSblymn } 123bdd37afaSblymn 124bdd37afaSblymn 125bdd37afaSblymn int 126bdd37afaSblymn main(int argc, char *argv[]) 127bdd37afaSblymn { 128bdd37afaSblymn extern char *optarg; 129bdd37afaSblymn extern int optind; 13023bea509Schristos const char *termpath, *term, *slave; 131433a1297Sjoerg int ch; 132bdd37afaSblymn pid_t slave_pid; 133bdd37afaSblymn extern FILE *yyin; 13426ce28a5Srillig char *arg1, *arg2; 135bdd37afaSblymn struct termios term_attr; 13623bea509Schristos struct stat st; 1371b4c2377Srillig int pipe_to_slave[2], pipe_from_slave[2]; 138bdd37afaSblymn 139bdd37afaSblymn termpath = term = slave = NULL; 140*a3e154cbSblymn nofail = 0; 141bdd37afaSblymn verbose = 0; 142ce321bb0Sblymn check_file_flag = 0; 143bdd37afaSblymn 144*a3e154cbSblymn while ((ch = getopt(argc, argv, "nvgfC:s:t:T:")) != -1) { 145bdd37afaSblymn switch (ch) { 14623bea509Schristos case 'C': 14723bea509Schristos check_path = optarg; 14823bea509Schristos break; 14923bea509Schristos case 'T': 15023bea509Schristos termpath = optarg; 15123bea509Schristos break; 152*a3e154cbSblymn case 'n': 153*a3e154cbSblymn nofail = 1; 154*a3e154cbSblymn break; 155bdd37afaSblymn case 's': 15623bea509Schristos slave = optarg; 157bdd37afaSblymn break; 158bdd37afaSblymn case 't': 15923bea509Schristos term = optarg; 160bdd37afaSblymn break; 161bdd37afaSblymn case 'v': 162bdd37afaSblymn verbose = 1; 163bdd37afaSblymn break; 164ce321bb0Sblymn case 'g': 165ce321bb0Sblymn check_file_flag |= GEN_CHECK_FILE; 166ce321bb0Sblymn break; 167ce321bb0Sblymn case 'f': 168ce321bb0Sblymn check_file_flag |= FORCE_GEN; 169ce321bb0Sblymn break; 170bdd37afaSblymn case '?': 171bdd37afaSblymn default: 17223bea509Schristos usage(); 173bdd37afaSblymn break; 174bdd37afaSblymn } 175bdd37afaSblymn } 176bdd37afaSblymn 17723bea509Schristos argc -= optind; 17823bea509Schristos argv += optind; 179aaac17ccSrillig if (argc != 1) 18023bea509Schristos usage(); 18123bea509Schristos 182bdd37afaSblymn if (termpath == NULL) 18323bea509Schristos termpath = DEF_TERMPATH; 184bdd37afaSblymn 185bdd37afaSblymn if (slave == NULL) 18623bea509Schristos slave = DEF_SLAVE; 187bdd37afaSblymn 188bdd37afaSblymn if (term == NULL) 18923bea509Schristos term = DEF_TERM; 190bdd37afaSblymn 19123bea509Schristos if (check_path == NULL) 19223bea509Schristos check_path = getenv("CHECK_PATH"); 19323bea509Schristos if ((check_path == NULL) || (check_path[0] == '\0')) { 1942f4dbca7Srillig warnx("$CHECK_PATH not set, defaulting to %s", def_check_path); 19523bea509Schristos check_path = def_check_path; 19623bea509Schristos } 19723bea509Schristos 198bdd37afaSblymn signal(SIGCHLD, slave_died); 199bdd37afaSblymn 200bdd37afaSblymn if (setenv("TERM", term, 1) != 0) 201bdd37afaSblymn err(2, "Failed to set TERM variable"); 202bdd37afaSblymn 203013e6195Smcf if (unsetenv("ESCDELAY") != 0) 204013e6195Smcf err(2, "Failed to unset ESCDELAY variable"); 205013e6195Smcf 20623bea509Schristos if (stat(termpath, &st) == -1) 20723bea509Schristos err(1, "Cannot stat %s", termpath); 208bdd37afaSblymn 20923bea509Schristos if (S_ISDIR(st.st_mode)) { 21023bea509Schristos char tinfo[MAXPATHLEN]; 211355eb06bSchristos int l = snprintf(tinfo, sizeof(tinfo), "%s/%s", termpath, 212fc67b0cdSjoerg "terminfo.cdb"); 21323bea509Schristos if (stat(tinfo, &st) == -1) 214355eb06bSchristos err(1, "Cannot stat `%s'", tinfo); 215fc67b0cdSjoerg if (l >= 4) 216fc67b0cdSjoerg tinfo[l - 4] = '\0'; 217355eb06bSchristos if (setenv("TERMINFO", tinfo, 1) != 0) 218355eb06bSchristos err(1, "Failed to set TERMINFO variable"); 21923bea509Schristos } else { 22023bea509Schristos int fd; 22123bea509Schristos char *tinfo; 22223bea509Schristos if ((fd = open(termpath, O_RDONLY)) == -1) 22323bea509Schristos err(1, "Cannot open `%s'", termpath); 22423bea509Schristos if ((tinfo = mmap(NULL, (size_t)st.st_size, PROT_READ, MAP_FILE, 22523bea509Schristos fd, 0)) == MAP_FAILED) 22623bea509Schristos err(1, "Cannot map `%s'", termpath); 22723bea509Schristos if (setenv("TERMINFO", tinfo, 1) != 0) 228355eb06bSchristos err(1, "Failed to set TERMINFO variable"); 22923bea509Schristos close(fd); 23023bea509Schristos munmap(tinfo, (size_t)st.st_size); 231bdd37afaSblymn } 232bdd37afaSblymn 2331b4c2377Srillig if (pipe(pipe_to_slave) < 0) 2342a18cea9Schristos err(1, "Command pipe creation failed"); 2351b4c2377Srillig to_slave = pipe_to_slave[1]; 236bdd37afaSblymn 2371b4c2377Srillig if (pipe(pipe_from_slave) < 0) 2382a18cea9Schristos err(1, "Slave pipe creation failed"); 2391b4c2377Srillig from_slave = pipe_from_slave[0]; 240bdd37afaSblymn 241fd21e1d8Smartin /* 242fd21e1d8Smartin * Create default termios settings for later use 243fd21e1d8Smartin */ 244fd21e1d8Smartin memset(&term_attr, 0, sizeof(term_attr)); 245fd21e1d8Smartin term_attr.c_iflag = TTYDEF_IFLAG; 246fd21e1d8Smartin term_attr.c_oflag = TTYDEF_OFLAG; 247fd21e1d8Smartin term_attr.c_cflag = TTYDEF_CFLAG; 248fd21e1d8Smartin term_attr.c_lflag = TTYDEF_LFLAG; 249fd21e1d8Smartin cfsetspeed(&term_attr, TTYDEF_SPEED); 2506f8e29adSblymn term_attr.c_cc[VERASE] = '\b'; 2516f8e29adSblymn term_attr.c_cc[VKILL] = '\025'; /* ^U */ 252bdd37afaSblymn 2532a18cea9Schristos if ((slave_pid = forkpty(&master, NULL, &term_attr, NULL)) < 0) 2542a18cea9Schristos err(1, "Fork of pty for slave failed\n"); 255bdd37afaSblymn 256bdd37afaSblymn if (slave_pid == 0) { 257bdd37afaSblymn /* slave side, just exec the slave process */ 2581b4c2377Srillig if (asprintf(&arg1, "%d", pipe_to_slave[0]) < 0) 259bdd37afaSblymn err(1, "arg1 conversion failed"); 2601b4c2377Srillig close(pipe_to_slave[1]); 261bdd37afaSblymn 2621b4c2377Srillig close(pipe_from_slave[0]); 2631b4c2377Srillig if (asprintf(&arg2, "%d", pipe_from_slave[1]) < 0) 264bdd37afaSblymn err(1, "arg2 conversion failed"); 265bdd37afaSblymn 26626ce28a5Srillig if (execl(slave, slave, arg1, arg2, (char *)0) < 0) 2672a18cea9Schristos err(1, "Exec of slave %s failed", slave); 268bdd37afaSblymn 269bdd37afaSblymn /* NOT REACHED */ 270bdd37afaSblymn } 271bdd37afaSblymn 2721b4c2377Srillig (void)close(pipe_to_slave[0]); 2731b4c2377Srillig (void)close(pipe_from_slave[1]); 2741b4c2377Srillig 275bdd37afaSblymn fcntl(master, F_SETFL, O_NONBLOCK); 276bdd37afaSblymn 2772a18cea9Schristos if ((yyin = fopen(argv[0], "r")) == NULL) 2782a18cea9Schristos err(1, "Cannot open command file %s", argv[0]); 279bdd37afaSblymn 28026b851dfSjoerg if ((cur_file = strdup(argv[0])) == NULL) 281bdd37afaSblymn err(2, "Failed to alloc memory for test file name"); 282bdd37afaSblymn 283bdd37afaSblymn init_parse_variables(1); 284bdd37afaSblymn 285bdd37afaSblymn yyparse(); 286bdd37afaSblymn fclose(yyin); 287bdd37afaSblymn 2882eec5692Srillig signal(SIGCHLD, SIG_DFL); 2892eec5692Srillig (void)close(to_slave); 2902eec5692Srillig (void)close(from_slave); 2912eec5692Srillig 2922eec5692Srillig int status; 2932eec5692Srillig (void)waitpid(slave_pid, &status, 0); 2942eec5692Srillig 295bdd37afaSblymn exit(0); 296bdd37afaSblymn } 297