xref: /netbsd-src/tests/lib/libcurses/director/director.c (revision a3e154cbe09e13b1fd7ce40b5ce15068a84be3b3)
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