Home | History | Annotate | Line # | Download | only in sysinst
run.c revision 1.5
      1 /*	$NetBSD: run.c,v 1.5 2014/12/30 10:10:22 martin Exp $	*/
      2 
      3 /*
      4  * Copyright 1997 Piermont Information Systems Inc.
      5  * All rights reserved.
      6  *
      7  * Written by Philip A. Nelson for Piermont Information Systems Inc.
      8  *
      9  * Redistribution and use in source and binary forms, with or without
     10  * modification, are permitted provided that the following conditions
     11  * are met:
     12  * 1. Redistributions of source code must retain the above copyright
     13  *    notice, this list of conditions and the following disclaimer.
     14  * 2. Redistributions in binary form must reproduce the above copyright
     15  *    notice, this list of conditions and the following disclaimer in the
     16  *    documentation and/or other materials provided with the distribution.
     17  * 3. The name of Piermont Information Systems Inc. may not be used to endorse
     18  *    or promote products derived from this software without specific prior
     19  *    written permission.
     20  *
     21  * THIS SOFTWARE IS PROVIDED BY PIERMONT INFORMATION SYSTEMS INC. ``AS IS''
     22  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     24  * ARE DISCLAIMED. IN NO EVENT SHALL PIERMONT INFORMATION SYSTEMS INC. BE
     25  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     26  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     27  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     28  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     29  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     30  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
     31  * THE POSSIBILITY OF SUCH DAMAGE.
     32  *
     33  */
     34 
     35 /* run.c -- routines to interact with other programs. */
     36 
     37 /* XXX write return codes ignored. XXX */
     38 
     39 #include <errno.h>
     40 #include <stdio.h>
     41 #include <stdarg.h>
     42 #include <stdlib.h>
     43 #include <unistd.h>
     44 #include <fcntl.h>
     45 #include <curses.h>
     46 #include <termios.h>
     47 #include <dirent.h>
     48 #include <util.h>
     49 #include <signal.h>
     50 #include <err.h>
     51 #include <sys/ioctl.h>
     52 #include <sys/types.h>
     53 #include <sys/wait.h>
     54 #include <sys/stat.h>
     55 #include "defs.h"
     56 
     57 #include "menu_defs.h"
     58 #include "msg_defs.h"
     59 
     60 #define MAXBUF 256
     61 
     62 #ifdef DEBUG
     63 #define Xsystem(y) printf ("%s\n", y), 0
     64 #else
     65 #define Xsystem(y) system(y)
     66 #endif
     67 
     68 /*
     69  * local prototypes
     70  */
     71 int log_flip (menudesc *, void *);
     72 static int script_flip (menudesc *, void *);
     73 
     74 #define BUFSIZE 4096
     75 
     76 menu_ent logmenu [2] = {
     77 	{ NULL, OPT_NOMENU, 0, log_flip},
     78 	{ NULL, OPT_NOMENU, 0, script_flip} };
     79 
     80 static void
     81 log_menu_label(menudesc *m, int opt, void *arg)
     82 {
     83 	wprintw(m->mw, "%s: %s",
     84 		msg_string(opt ? MSG_Scripting : MSG_Logging),
     85 		msg_string((opt ? script != NULL : logfp != NULL) ?
     86 		    MSG_On : MSG_Off));
     87 }
     88 
     89 void
     90 do_logging(void)
     91 {
     92 	int menu_no;
     93 
     94 	menu_no = new_menu(MSG_Logging_functions, logmenu, 2, -1, 12,
     95 		0, 20, MC_SCROLL, NULL, log_menu_label, NULL,
     96 		MSG_Pick_an_option, NULL);
     97 
     98 	if (menu_no < 0) {
     99 		(void)fprintf(stderr, "Dynamic menu creation failed.\n");
    100 		if (logfp)
    101 			(void)fprintf(logfp, "Dynamic menu creation failed.\n");
    102 		exit(EXIT_FAILURE);
    103 	}
    104 	process_menu(menu_no, NULL);
    105 	free_menu(menu_no);
    106 }
    107 
    108 int
    109 /*ARGSUSED*/
    110 log_flip(menudesc *m, void *arg)
    111 {
    112 	time_t tloc;
    113 
    114 	(void)time(&tloc);
    115 	if (logfp) {
    116 		fprintf(logfp, "Log ended at: %s\n", safectime(&tloc));
    117 		fflush(logfp);
    118 		fclose(logfp);
    119 		logfp = NULL;
    120 	} else {
    121 		logfp = fopen("/tmp/sysinst.log", "a");
    122 		if (logfp != NULL) {
    123 			fprintf(logfp,
    124 			    "Log started at: %s\n", safectime(&tloc));
    125 			fflush(logfp);
    126 		} else {
    127 			if (mainwin) {
    128 				msg_display(MSG_openfail, "log file",
    129 				    strerror(errno));
    130 			} else {
    131 				fprintf(stderr, "could not open /tmp/sysinst.log: %s\n",
    132 				    strerror(errno));
    133 				exit(1);
    134 			}
    135 		}
    136 	}
    137 	return(0);
    138 }
    139 
    140 static int
    141 /*ARGSUSED*/
    142 script_flip(menudesc *m, void *arg)
    143 {
    144 	time_t tloc;
    145 
    146 	(void)time(&tloc);
    147 	if (script) {
    148 		scripting_fprintf(NULL, "# Script ended at: %s\n",
    149 		    safectime(&tloc));
    150 		fflush(script);
    151 		fclose(script);
    152 		script = NULL;
    153 	} else {
    154 		script = fopen("/tmp/sysinst.sh", "w");
    155 		if (script != NULL) {
    156 			scripting_fprintf(NULL, "#!/bin/sh\n");
    157 			scripting_fprintf(NULL, "# Script started at: %s\n",
    158 			    safectime(&tloc));
    159 			fflush(script);
    160 		} else {
    161 			msg_display(MSG_openfail, "script file",
    162 			    strerror(errno));
    163 		}
    164 	}
    165 	return(0);
    166 }
    167 
    168 int
    169 collect(int kind, char **buffer, const char *name, ...)
    170 {
    171 	size_t nbytes;		/* Number of bytes in buffer. */
    172 	size_t fbytes;		/* Number of bytes in file. */
    173 	struct stat st;		/* stat information. */
    174 	int ch;
    175 	FILE *f;
    176 	char fileorcmd[STRSIZE];
    177 	va_list ap;
    178 	char *cp;
    179 
    180 	va_start(ap, name);
    181 	vsnprintf(fileorcmd, sizeof fileorcmd, name, ap);
    182 	va_end(ap);
    183 
    184 	if (kind == T_FILE) {
    185 		/* Get the file information. */
    186 		if (stat(fileorcmd, &st)) {
    187 			*buffer = NULL;
    188 			return -1;
    189 		}
    190 		fbytes = (size_t)st.st_size;
    191 
    192 		/* Open the file. */
    193 		f = fopen(fileorcmd, "r");
    194 		if (f == NULL) {
    195 			*buffer = NULL;
    196 			return -1;
    197 		}
    198 	} else {
    199 		/* Open the program. */
    200 		f = popen(fileorcmd, "r");
    201 		if (f == NULL) {
    202 			*buffer = NULL;
    203 			return -1;
    204 		}
    205 		fbytes = BUFSIZE;
    206 	}
    207 
    208 	if (fbytes == 0)
    209 		fbytes = BUFSIZE;
    210 
    211 	/* Allocate the buffer size. */
    212 	*buffer = cp = malloc(fbytes + 1);
    213 	if (!cp)
    214 		nbytes =  -1;
    215 	else {
    216 		/* Read the buffer. */
    217 		nbytes = 0;
    218 		while (nbytes < fbytes && (ch = fgetc(f)) != EOF)
    219 			cp[nbytes++] = ch;
    220 		cp[nbytes] = 0;
    221 	}
    222 
    223 	if (kind == T_FILE)
    224 		fclose(f);
    225 	else
    226 		pclose(f);
    227 
    228 	return nbytes;
    229 }
    230 
    231 
    232 /*
    233  * system(3), but with a debug wrapper.
    234  * use only for curses sub-applications.
    235  */
    236 int
    237 do_system(const char *execstr)
    238 {
    239 	register int ret;
    240 
    241 	/*
    242 	 * The following may be more than one function call.  Can't just
    243 	 * "return Xsystem (command);"
    244 	 */
    245 
    246 	ret = Xsystem(execstr);
    247 	return (ret);
    248 
    249 }
    250 
    251 static char **
    252 make_argv(const char *cmd)
    253 {
    254 	char **argv = 0;
    255 	int argc = 0;
    256 	const char *cp;
    257 	char *dp, *fn;
    258 	DIR *dir;
    259 	struct dirent *dirent;
    260 	int l;
    261 
    262 	for (; *cmd != 0; cmd = cp + strspn(cp, " "), argc++) {
    263 		if (*cmd == '\'')
    264 			cp = strchr(++cmd, '\'');
    265 		else
    266 			cp = strchr(cmd, ' ');
    267 		if (cp == NULL)
    268 			cp = strchr(cmd, 0);
    269 		argv = realloc(argv, (argc + 2) * sizeof *argv);
    270 		if (argv == NULL)
    271 			err(1, "realloc(argv) for %s", cmd);
    272 		asprintf(argv + argc, "%.*s", (int)(cp - cmd), cmd);
    273 		/* Hack to remove %xx encoded ftp password */
    274 		dp = strstr(cmd, ":%");
    275 		if (dp != NULL && dp < cp) {
    276 			for (fn = dp + 4; *fn == '%'; fn += 3)
    277 				continue;
    278 			if (*fn == '@')
    279 				memset(dp + 1, '*', fn - dp - 1);
    280 		}
    281 		if (*cp == '\'')
    282 			cp++;
    283 		if (cp[-1] != '*')
    284 			continue;
    285 		/* do limited filename globbing */
    286 		dp = argv[argc];
    287 		fn = strrchr(dp, '/');
    288 		if (fn != NULL)
    289 			*fn = 0;
    290 		dir = opendir(dp);
    291 		if (fn != NULL)
    292 			*fn++ = '/';
    293 		else
    294 			fn = dp;
    295 		if (dir == NULL)
    296 			continue;
    297 		l = strlen(fn) - 1;
    298 		while ((dirent = readdir(dir))) {
    299 			if (dirent->d_name[0] == '.')
    300 				continue;
    301 			if (strncmp(dirent->d_name, fn, l) != 0)
    302 				continue;
    303 			if (dp != argv[argc])
    304 				argc++;
    305 			argv = realloc(argv, (argc + 2) * sizeof *argv);
    306 			if (argv == NULL)
    307 				err(1, "realloc(argv) for %s", cmd);
    308 			asprintf(argv + argc, "%.*s%s", (int)(fn - dp), dp,
    309 				dirent->d_name);
    310 		}
    311 		if (dp != argv[argc])
    312 			free(dp);
    313 		closedir(dir);
    314 	}
    315 	argv[argc] = NULL;
    316 	return argv;
    317 }
    318 
    319 static void
    320 free_argv(char **argv)
    321 {
    322 	char **n, *a;
    323 
    324 	for (n = argv; (a = *n++);)
    325 		free(a);
    326 	free(argv);
    327 }
    328 
    329 static WINDOW *
    330 show_cmd(const char *scmd, struct winsize *win)
    331 {
    332 	int n, m;
    333 	WINDOW *actionwin;
    334 	int nrow;
    335 
    336 	wclear(stdscr);
    337 	clearok(stdscr, 1);
    338 	touchwin(stdscr);
    339 	refresh();
    340 
    341 	mvaddstr(0, 4, msg_string(MSG_Status));
    342 	standout();
    343 	addstr(msg_string(MSG_Running));
    344 	standend();
    345 	mvaddstr(1, 4, msg_string(MSG_Command));
    346 	standout();
    347 	printw("%s", scmd);
    348 	standend();
    349 	addstr("\n\n");
    350 	for (n = win->ws_col; (m = min(n, 30)) > 0; n -= m)
    351 		addstr( "------------------------------" + 30 - m);
    352 	refresh();
    353 
    354 	nrow = getcury(stdscr) + 1;
    355 
    356 	actionwin = subwin(stdscr, win->ws_row - nrow, win->ws_col, nrow, 0);
    357 	if (actionwin == NULL) {
    358 		fprintf(stderr, "sysinst: failed to allocate output window.\n");
    359 		exit(1);
    360 	}
    361 	scrollok(actionwin, TRUE);
    362 	if (has_colors()) {
    363 		wbkgd(actionwin, getbkgd(stdscr));
    364 		wattrset(actionwin, getattrs(stdscr));
    365 	}
    366 
    367 	wmove(actionwin, 0, 0);
    368 	wrefresh(actionwin);
    369 
    370 	return actionwin;
    371 }
    372 
    373 /*
    374  * launch a program inside a subwindow, and report its return status when done
    375  */
    376 static int
    377 launch_subwin(WINDOW **actionwin, char **args, struct winsize *win, int flags,
    378     const char *scmd, const char **errstr)
    379 {
    380 	int n, i;
    381 	int selectfailed;
    382 	int status, master, slave;
    383 	fd_set active_fd_set, read_fd_set;
    384 	pid_t child, pid;
    385 	char ibuf[MAXBUF];
    386 	char pktdata;
    387 	char *cp, *ncp;
    388 	struct termios rtt, tt;
    389 	struct timeval tmo;
    390 	static int do_tioccons = 2;
    391 
    392 	(void)tcgetattr(STDIN_FILENO, &tt);
    393 	if (openpty(&master, &slave, NULL, &tt, win) == -1) {
    394 		*errstr = "openpty() failed";
    395 		return -1;
    396 	}
    397 
    398 	rtt = tt;
    399 
    400 	/* ignore tty signals until we're done with subprocess setup */
    401 	ttysig_ignore = 1;
    402 	ioctl(master, TIOCPKT, &ttysig_ignore);
    403 
    404 	/* Try to get console output into our pipe */
    405 	if (do_tioccons) {
    406 		if (ioctl(slave, TIOCCONS, &do_tioccons) == 0
    407 		    && do_tioccons == 2) {
    408 			/* test our output - we don't want it grabbed */
    409 			write(1, " \b", 2);
    410 			ioctl(master, FIONREAD, &do_tioccons);
    411 			if (do_tioccons != 0) {
    412 				do_tioccons = 0;
    413 				ioctl(slave, TIOCCONS, &do_tioccons);
    414 			} else
    415 				do_tioccons = 1;
    416 		}
    417 	}
    418 
    419 	if (logfp)
    420 		fflush(logfp);
    421 	if (script)
    422 		fflush(script);
    423 
    424 	child = fork();
    425 	switch (child) {
    426 	case -1:
    427 		ttysig_ignore = 0;
    428 		refresh();
    429 		*errstr = "fork() failed";
    430 		return -1;
    431 	case 0:	/* child */
    432 		(void)close(STDIN_FILENO);
    433 		/* silently stop curses */
    434 		(void)close(STDOUT_FILENO);
    435 		(void)open("/dev/null", O_RDWR, 0);
    436 		dup2(STDIN_FILENO, STDOUT_FILENO);
    437 		endwin();
    438 		(void)close(master);
    439 		rtt = tt;
    440 		rtt.c_lflag |= (ICANON|ECHO);
    441 		(void)tcsetattr(slave, TCSANOW, &rtt);
    442 		login_tty(slave);
    443 		if (logfp) {
    444 			fprintf(logfp, "executing: %s\n", scmd);
    445 			fclose(logfp);
    446 			logfp = NULL;
    447 		}
    448 		if (script) {
    449 			fprintf(script, "%s\n", scmd);
    450 			fclose(script);
    451 			script = NULL;
    452 		}
    453 		if (strcmp(args[0], "cd") == 0 && strcmp(args[2], "&&") == 0) {
    454 			target_chdir_or_die(args[1]);
    455 			args += 3;
    456 		}
    457 		if (flags & RUN_XFER_DIR)
    458 			target_chdir_or_die(xfer_dir);
    459 		/*
    460 		 * If target_prefix == "", the chroot will fail, but
    461 		 * that's ok, since we don't need it then.
    462 		 */
    463 		if (flags & RUN_CHROOT && *target_prefix()
    464 		    && chroot(target_prefix()) != 0)
    465 			warn("chroot(%s) for %s", target_prefix(), *args);
    466 		else {
    467 			execvp(*args, args);
    468 			warn("execvp %s", *args);
    469 		}
    470 		_exit(EXIT_FAILURE);
    471 		// break; /* end of child */
    472 	default:
    473 		/*
    474 		 * parent: we've set up the subprocess.
    475 		 * forward tty signals to its process group.
    476 		 */
    477 		ttysig_forward = child;
    478 		ttysig_ignore = 0;
    479 		break;
    480 	}
    481 
    482 	/*
    483 	 * Now loop transferring program output to screen, and keyboard
    484 	 * input to the program.
    485 	 */
    486 
    487 	FD_ZERO(&active_fd_set);
    488 	FD_SET(master, &active_fd_set);
    489 	FD_SET(STDIN_FILENO, &active_fd_set);
    490 
    491 	for (selectfailed = 0;;) {
    492 		if (selectfailed) {
    493 			const char mmsg[] =
    494 			    "select(2) failed but no child died?";
    495 			if (logfp)
    496 				(void)fprintf(logfp, mmsg);
    497 			errx(1, mmsg);
    498 		}
    499 		read_fd_set = active_fd_set;
    500 		tmo.tv_sec = flags & RUN_SILENT ? 20 : 2;
    501 		tmo.tv_usec = 0;
    502 		i = select(FD_SETSIZE, &read_fd_set, NULL, NULL, &tmo);
    503 		if (i == 0 && *actionwin == NULL && (flags & RUN_SILENT) == 0)
    504 			*actionwin = show_cmd(scmd, win);
    505 		if (i < 0) {
    506 			if (errno != EINTR) {
    507 				warn("select");
    508 				if (logfp)
    509 					(void)fprintf(logfp,
    510 					    "select failure: %s\n",
    511 					    strerror(errno));
    512 				selectfailed = 1;
    513 			}
    514 		} else for (i = 0; i < FD_SETSIZE; ++i) {
    515 			if (!FD_ISSET(i, &read_fd_set))
    516 				continue;
    517 			n = read(i, ibuf, sizeof ibuf - 1);
    518 			if (n <= 0) {
    519 				if (n < 0)
    520 					warn("read");
    521 				continue;
    522 			}
    523 			ibuf[n] = 0;
    524 			cp = ibuf;
    525 			if (i == STDIN_FILENO) {
    526 				(void)write(master, ibuf, (size_t)n);
    527 				if (!(rtt.c_lflag & ECHO))
    528 					continue;
    529 			} else {
    530 				pktdata = ibuf[0];
    531 				if (pktdata != 0) {
    532 					if (pktdata & TIOCPKT_IOCTL)
    533 						memcpy(&rtt, ibuf, sizeof(rtt));
    534 					continue;
    535 				}
    536 				cp += 1;
    537 			}
    538 			if (*cp == 0 || flags & RUN_SILENT)
    539 				continue;
    540 			if (logfp) {
    541 				fprintf(logfp, "%s", cp);
    542 				fflush(logfp);
    543 			}
    544 			if (*actionwin == NULL)
    545 				*actionwin = show_cmd(scmd, win);
    546 			/* posix curses is braindead wrt \r\n so... */
    547 			for (ncp = cp; (ncp = strstr(ncp, "\r\n")); ncp += 2) {
    548 				ncp[0] = '\n';
    549 				ncp[1] = '\r';
    550 			}
    551 			waddstr(*actionwin, cp);
    552 			wrefresh(*actionwin);
    553 		}
    554 		pid = wait4(child, &status, WNOHANG, 0);
    555  		if (pid == child && (WIFEXITED(status) || WIFSIGNALED(status)))
    556 			break;
    557 	}
    558 	close(master);
    559 	close(slave);
    560 	if (logfp)
    561 		fflush(logfp);
    562 
    563 	/* from here on out, we take tty signals ourselves */
    564 	ttysig_forward = 0;
    565 
    566 	reset_prog_mode();
    567 
    568 	if (WIFEXITED(status)) {
    569 		*errstr = msg_string(MSG_Command_failed);
    570 		return WEXITSTATUS(status);
    571 	}
    572 	if (WIFSIGNALED(status)) {
    573 		*errstr = msg_string(MSG_Command_ended_on_signal);
    574 		return WTERMSIG(status);
    575 	}
    576 	return 0;
    577 }
    578 
    579 /*
    580  * generic program runner.
    581  * flags:
    582  *	RUN_DISPLAY	display command name and output
    583  *	RUN_FATAL	program errors are fatal
    584  *	RUN_CHROOT	chroot to target before the exec
    585  *	RUN_FULLSCREEN	display output only
    586  *	RUN_SILENT	do not display program output
    587  *	RUN_ERROR_OK	don't wait for key if program fails
    588  *	RUN_PROGRESS	don't wait for key if program has output
    589  * If both RUN_DISPLAY and RUN_SILENT are clear then the program name will
    590  * be displayed as soon as it generates output.
    591  * Steps are taken to collect console messages, they will be interleaved
    592  * into the program output - but not upset curses.
    593  */
    594 
    595 int
    596 run_program(int flags, const char *cmd, ...)
    597 {
    598 	va_list ap;
    599 	struct winsize win;
    600 	int ret;
    601 	WINDOW *actionwin = NULL;
    602 	char *scmd;
    603 	char **args;
    604 	const char *errstr = NULL;
    605 
    606 	va_start(ap, cmd);
    607 	vasprintf(&scmd, cmd, ap);
    608 	va_end(ap);
    609 	if (scmd == NULL)
    610 		err(1, "vasprintf(&scmd, \"%s\", ...)", cmd);
    611 
    612 	args = make_argv(scmd);
    613 
    614 	/* Make curses save tty settings */
    615 	def_prog_mode();
    616 
    617 	(void)ioctl(STDIN_FILENO, TIOCGWINSZ, &win);
    618 	/* Apparently, we sometimes get 0x0 back, and that's not useful */
    619 	if (win.ws_row == 0)
    620 		win.ws_row = 24;
    621 	if (win.ws_col == 0)
    622 		win.ws_col = 80;
    623 
    624 	if ((flags & RUN_DISPLAY) != 0) {
    625 		if (flags & RUN_FULLSCREEN) {
    626 			wclear(stdscr);
    627 			clearok(stdscr, 1);
    628 			touchwin(stdscr);
    629 			refresh();
    630 			actionwin = stdscr;
    631 		} else
    632 			actionwin = show_cmd(scmd, &win);
    633 	} else
    634 		win.ws_row -= 4;
    635 
    636 	ret = launch_subwin(&actionwin, args, &win, flags, scmd, &errstr);
    637 	fpurge(stdin);
    638 
    639 	/* If the command failed, show command name */
    640 	if (actionwin == NULL && ret != 0 && !(flags & RUN_ERROR_OK))
    641 		actionwin = show_cmd(scmd, &win);
    642 
    643 	if (actionwin != NULL) {
    644 		int y, x;
    645 		getyx(actionwin, y, x);
    646 		if (actionwin != stdscr)
    647 			mvaddstr(0, 4, msg_string(MSG_Status));
    648 		if (ret != 0) {
    649 			if (actionwin == stdscr && x != 0)
    650 				addstr("\n");
    651 			x = 1;	/* force newline below */
    652 			standout();
    653 			addstr(errstr);
    654 			standend();
    655 		} else {
    656 			if (actionwin != stdscr) {
    657 				standout();
    658 				addstr(msg_string(MSG_Finished));
    659 				standend();
    660 			}
    661 		}
    662 		clrtoeol();
    663 		refresh();
    664 		if ((ret != 0 && !(flags & RUN_ERROR_OK)) ||
    665 		    (y + x != 0 && !(flags & RUN_PROGRESS))) {
    666 			if (actionwin != stdscr)
    667 				move(getbegy(actionwin) - 2, 5);
    668 			else if (x != 0)
    669 				addstr("\n");
    670 			addstr(msg_string(MSG_Hit_enter_to_continue));
    671 			refresh();
    672 			getchar();
    673 		} else {
    674 			if (y + x != 0) {
    675 				/* give user 1 second to see messages */
    676 				refresh();
    677 				sleep(1);
    678 			}
    679 		}
    680 	}
    681 
    682 	/* restore tty setting we saved earlier */
    683 	reset_prog_mode();
    684 
    685 	/* clean things up */
    686 	if (actionwin != NULL) {
    687 		if (actionwin != stdscr)
    688 			delwin(actionwin);
    689 		if (errstr == 0 || !(flags & RUN_NO_CLEAR)) {
    690 			wclear(stdscr);
    691 			touchwin(stdscr);
    692 			clearok(stdscr, 1);
    693 			refresh();
    694 		}
    695 	}
    696 
    697 	free(scmd);
    698 	free_argv(args);
    699 
    700 	if (ret != 0 && flags & RUN_FATAL)
    701 		exit(ret);
    702 	return ret;
    703 }
    704