7ef7a3f227
- rework run_prog() so that the program name (etc) is usually displayed if the program generates any output, or terminates with an error. Allow arguments to included in single quotes. Try to collect console output so it doesn't interfere with curses. - Add a '*' to the cylinder count if non-integral number of cylinders (on disklabel editor) - Only show partition type for unused partitions. - Show size including unused space on '+' partition, remove a..z since the don't relate to partition IDs (netbsd partition sizes) - Fix deleting of 'user' partitions - killed size of next ptn. - Don't default a swap partition is the disk already has one. - Fix deleting of extended MBR partitions - changed size of ptn 2. - Show error message if user tries something illegal in mbr editor. - Default to old diskname (actually disk type - dunno why!) - Use MI enable_rc_conf() to set RC_CONFIGURED=YES, use a single sed command instead of a raft of code playing with files etc. - Float some menus to just below header text, saves counting and lets language variants have different height headers (use y=-1) - Track whether anything is mounted on /mnt2 better. - Put more texts into message file. - Change english prose texts to be more correct. - Stop french and polish versions core dumping if ptn start/size changed. - Fix processing logic for saving /etc (action is still borked) - Do tail-end setup if any sets (eg X) can't be found (but not if you give in (yet)).
661 lines
15 KiB
C
661 lines
15 KiB
C
/* $NetBSD: run.c,v 1.53 2003/10/19 20:17:32 dsl Exp $ */
|
|
|
|
/*
|
|
* Copyright 1997 Piermont Information Systems Inc.
|
|
* All rights reserved.
|
|
*
|
|
* Written by Philip A. Nelson for Piermont Information Systems Inc.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* 3. All advertising materials mentioning features or use of this software
|
|
* must display the following acknowledgement:
|
|
* This product includes software developed for the NetBSD Project by
|
|
* Piermont Information Systems Inc.
|
|
* 4. The name of Piermont Information Systems Inc. may not be used to endorse
|
|
* or promote products derived from this software without specific prior
|
|
* written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY PIERMONT INFORMATION SYSTEMS INC. ``AS IS''
|
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL PIERMONT INFORMATION SYSTEMS INC. BE
|
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
|
* THE POSSIBILITY OF SUCH DAMAGE.
|
|
*
|
|
*/
|
|
|
|
/* run.c -- routines to interact with other programs. */
|
|
|
|
/* XXX write return codes ignored. XXX */
|
|
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <stdarg.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <curses.h>
|
|
#include <termios.h>
|
|
#include <dirent.h>
|
|
#include <util.h>
|
|
#include <signal.h>
|
|
#include <err.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/types.h>
|
|
#include <sys/wait.h>
|
|
#include <sys/stat.h>
|
|
#include "defs.h"
|
|
|
|
#include "menu_defs.h"
|
|
#include "msg_defs.h"
|
|
|
|
#define MAXBUF 256
|
|
|
|
#ifdef DEBUG
|
|
#define Xsystem(y) printf ("%s\n", y), 0
|
|
#else
|
|
#define Xsystem(y) system(y)
|
|
#endif
|
|
|
|
/*
|
|
* local prototypes
|
|
*/
|
|
static int log_flip (menudesc *, void *);
|
|
static int script_flip (menudesc *, void *);
|
|
|
|
#define BUFSIZE 4096
|
|
|
|
menu_ent logmenu [2] = {
|
|
{ NULL, OPT_NOMENU, 0, log_flip},
|
|
{ NULL, OPT_NOMENU, 0, script_flip} };
|
|
|
|
static void
|
|
log_menu_label(menudesc *m, int opt, void *arg)
|
|
{
|
|
wprintw(m->mw, "%s: %s",
|
|
msg_string(opt ? MSG_Scripting : MSG_Logging),
|
|
msg_string((opt ? scripting : logging) ? MSG_On : MSG_Off));
|
|
}
|
|
|
|
void
|
|
do_logging(void)
|
|
{
|
|
int menu_no;
|
|
|
|
menu_no = new_menu(MSG_Logging_functions, logmenu, 2, -1, 12,
|
|
0, 20, MC_SCROLL, NULL, log_menu_label, NULL,
|
|
MSG_Pick_an_option, NULL);
|
|
|
|
if (menu_no < 0) {
|
|
(void)fprintf(stderr, "Dynamic menu creation failed.\n");
|
|
if (logging)
|
|
(void)fprintf(logfp, "Dynamic menu creation failed.\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
process_menu(menu_no, NULL);
|
|
free_menu(menu_no);
|
|
}
|
|
|
|
static int
|
|
/*ARGSUSED*/
|
|
log_flip(menudesc *m, void *arg)
|
|
{
|
|
time_t tloc;
|
|
|
|
(void)time(&tloc);
|
|
if (logging == 1) {
|
|
logging = 0;
|
|
fprintf(logfp, "Log ended at: %s\n", asctime(localtime(&tloc)));
|
|
fflush(logfp);
|
|
fclose(logfp);
|
|
} else {
|
|
logfp = fopen("sysinst.log", "a");
|
|
if (logfp != NULL) {
|
|
logging = 1;
|
|
fprintf(logfp,
|
|
"Log started at: %s\n", asctime(localtime(&tloc)));
|
|
fflush(logfp);
|
|
} else {
|
|
msg_display(MSG_openfail, "log file", strerror(errno));
|
|
}
|
|
}
|
|
return(0);
|
|
}
|
|
|
|
static int
|
|
/*ARGSUSED*/
|
|
script_flip(menudesc *m, void *arg)
|
|
{
|
|
time_t tloc;
|
|
|
|
(void)time(&tloc);
|
|
if (scripting == 1) {
|
|
scripting_fprintf(NULL, "# Script ended at: %s\n", asctime(localtime(&tloc)));
|
|
scripting = 0;
|
|
fflush(script);
|
|
fclose(script);
|
|
} else {
|
|
script = fopen("sysinst.sh", "w");
|
|
if (script != NULL) {
|
|
scripting = 1;
|
|
scripting_fprintf(NULL, "#!/bin/sh\n");
|
|
scripting_fprintf(NULL, "# Script started at: %s\n",
|
|
asctime(localtime(&tloc)));
|
|
fflush(script);
|
|
} else {
|
|
msg_display(MSG_openfail, "script file", strerror(errno));
|
|
}
|
|
}
|
|
return(0);
|
|
}
|
|
|
|
int
|
|
collect(int kind, char **buffer, const char *name, ...)
|
|
{
|
|
size_t nbytes; /* Number of bytes in buffer. */
|
|
size_t fbytes; /* Number of bytes in file. */
|
|
struct stat st; /* stat information. */
|
|
int ch;
|
|
FILE *f;
|
|
char fileorcmd [STRSIZE];
|
|
va_list ap;
|
|
|
|
va_start(ap, name);
|
|
vsnprintf(fileorcmd, STRSIZE, name, ap);
|
|
va_end(ap);
|
|
|
|
if (kind == T_FILE) {
|
|
/* Get the file information. */
|
|
if (stat(fileorcmd, &st)) {
|
|
*buffer = NULL;
|
|
return -1;
|
|
}
|
|
fbytes = (size_t)st.st_size;
|
|
|
|
/* Open the file. */
|
|
f = fopen(fileorcmd, "r");
|
|
if (f == NULL) {
|
|
*buffer = NULL;
|
|
return -1;
|
|
}
|
|
} else {
|
|
/* Open the program. */
|
|
f = popen(fileorcmd, "r");
|
|
if (f == NULL) {
|
|
*buffer = NULL;
|
|
return -1;
|
|
}
|
|
fbytes = BUFSIZE;
|
|
}
|
|
|
|
if (fbytes == 0)
|
|
fbytes = BUFSIZE;
|
|
|
|
/* Allocate the buffer size. */
|
|
*buffer = (char *)malloc(fbytes + 1);
|
|
if (!*buffer)
|
|
return -1;
|
|
|
|
/* Read the buffer. */
|
|
nbytes = 0;
|
|
while (nbytes < fbytes && (ch = fgetc(f)) != EOF)
|
|
(*buffer)[nbytes++] = ch;
|
|
|
|
(*buffer)[nbytes] = 0;
|
|
|
|
if (kind == T_FILE)
|
|
fclose(f);
|
|
else
|
|
pclose(f);
|
|
|
|
return nbytes;
|
|
}
|
|
|
|
|
|
/*
|
|
* system(3), but with a debug wrapper.
|
|
* use only for curses sub-applications.
|
|
*/
|
|
int
|
|
do_system(const char *execstr)
|
|
{
|
|
register int ret;
|
|
|
|
/*
|
|
* The following may be more than one function call. Can't just
|
|
* "return Xsystem (command);"
|
|
*/
|
|
|
|
ret = Xsystem(execstr);
|
|
return (ret);
|
|
|
|
}
|
|
|
|
static char **
|
|
make_argv(const char *cmd)
|
|
{
|
|
char **argv = 0;
|
|
int argc = 0;
|
|
const char *cp;
|
|
char *dp, *fn;
|
|
DIR *dir;
|
|
struct dirent *dirent;
|
|
int l;
|
|
|
|
for (; *cmd != 0; cmd = cp + strspn(cp, " "), argc++) {
|
|
if (*cmd == '\'')
|
|
cp = strchr(++cmd, '\'');
|
|
else
|
|
cp = strchr(cmd, ' ');
|
|
if (cp == NULL)
|
|
cp = strchr(cmd, 0);
|
|
argv = realloc(argv, (argc + 2) * sizeof *argv);
|
|
if (argv == NULL)
|
|
err(1, "realloc(argv) for %s", cmd);
|
|
asprintf(argv + argc, "%.*s", (int)(cp - cmd), cmd);
|
|
if (*cp == '\'')
|
|
cp++;
|
|
if (cp[-1] != '*')
|
|
continue;
|
|
/* do limited filename globbing */
|
|
dp = argv[argc];
|
|
fn = strrchr(dp, '/');
|
|
if (fn != NULL)
|
|
*fn = 0;
|
|
dir = opendir(dp);
|
|
if (fn != NULL)
|
|
*fn++ = '/';
|
|
else
|
|
fn = dp;
|
|
if (dir == NULL)
|
|
continue;
|
|
l = strlen(fn) - 1;
|
|
while ((dirent = readdir(dir))) {
|
|
if (dirent->d_name[0] == '.')
|
|
continue;
|
|
if (strncmp(dirent->d_name, fn, l) != 0)
|
|
continue;
|
|
if (dp != argv[argc])
|
|
argc++;
|
|
argv = realloc(argv, (argc + 2) * sizeof *argv);
|
|
if (argv == NULL)
|
|
err(1, "realloc(argv) for %s", cmd);
|
|
asprintf(argv + argc, "%.*s%s", (int)(fn - dp), dp,
|
|
dirent->d_name);
|
|
}
|
|
if (dp != argv[argc])
|
|
free(dp);
|
|
closedir(dir);
|
|
}
|
|
argv[argc] = NULL;
|
|
return argv;
|
|
}
|
|
|
|
static void
|
|
free_argv(char **argv)
|
|
{
|
|
char **n, *a;
|
|
|
|
for (n = argv; (a = *n++);)
|
|
free(a);
|
|
free(argv);
|
|
}
|
|
|
|
static WINDOW *
|
|
show_cmd(const char *scmd, struct winsize *win)
|
|
{
|
|
int n, m;
|
|
WINDOW *actionwin;
|
|
|
|
wclear(stdscr);
|
|
clearok(stdscr, 1);
|
|
touchwin(stdscr);
|
|
refresh();
|
|
|
|
actionwin = subwin(stdscr, win->ws_row - 4, win->ws_col, 4, 0);
|
|
if (actionwin == NULL) {
|
|
fprintf(stderr, "sysinst: failed to allocate"
|
|
" output window.\n");
|
|
exit(1);
|
|
}
|
|
scrollok(actionwin, TRUE);
|
|
if (has_colors()) {
|
|
wbkgd(actionwin, getbkgd(stdscr));
|
|
wattrset(actionwin, getattrs(stdscr));
|
|
}
|
|
|
|
mvaddstr(0, 3, msg_string(MSG_Status));
|
|
standout();
|
|
addstr(msg_string(MSG_Running));
|
|
standend();
|
|
mvaddstr(1, 4, msg_string(MSG_Command));
|
|
standout();
|
|
addstr(scmd);
|
|
standend();
|
|
|
|
move(3, 0);
|
|
for (n = win->ws_col; (m = min(n, 30)) > 0; n -= m)
|
|
addstr( "------------------------------" + 30 - m);
|
|
refresh();
|
|
|
|
wmove(actionwin, 0, 0);
|
|
wrefresh(actionwin);
|
|
|
|
return actionwin;
|
|
}
|
|
|
|
/*
|
|
* launch a program inside a subwindow, and report it's return status when done
|
|
*/
|
|
static int
|
|
launch_subwin(WINDOW **actionwin, char **args, struct winsize *win, int flags,
|
|
const char *scmd, const char **errstr)
|
|
{
|
|
int n, i;
|
|
int selectfailed;
|
|
int status, master, slave;
|
|
fd_set active_fd_set, read_fd_set;
|
|
pid_t child, pid;
|
|
char ibuf[MAXBUF];
|
|
char pktdata;
|
|
char *cp, *ncp;
|
|
struct termios rtt;
|
|
struct termios tt;
|
|
struct timeval tmo;
|
|
static int do_tioccons = 2;
|
|
|
|
|
|
(void)tcgetattr(STDIN_FILENO, &tt);
|
|
if (openpty(&master, &slave, NULL, &tt, win) == -1) {
|
|
*errstr = "openpty() failed";
|
|
return -1;
|
|
}
|
|
|
|
rtt = tt;
|
|
|
|
/* ignore tty signals until we're done with subprocess setup */
|
|
ttysig_ignore = 1;
|
|
ioctl(master, TIOCPKT, &ttysig_ignore);
|
|
|
|
/* Try to get console output into our pipe */
|
|
if (do_tioccons) {
|
|
if (ioctl(slave, TIOCCONS, &do_tioccons) == 0
|
|
&& do_tioccons == 2) {
|
|
/* test our output - we don't want it grabbed */
|
|
write(1, " \b", 2);
|
|
ioctl(master, FIONREAD, &do_tioccons);
|
|
if (do_tioccons != 0) {
|
|
do_tioccons = 0;
|
|
ioctl(slave, TIOCCONS, &do_tioccons);
|
|
} else
|
|
do_tioccons = 1;
|
|
}
|
|
}
|
|
|
|
child = fork();
|
|
switch (child) {
|
|
case -1:
|
|
ttysig_ignore = 0;
|
|
refresh();
|
|
*errstr = "fork() failed";
|
|
return -1;
|
|
case 0: /* child */
|
|
(void)close(STDIN_FILENO);
|
|
/* silently stop curses */
|
|
(void)close(STDOUT_FILENO);
|
|
(void)open("/dev/null", O_RDWR, 0);
|
|
dup2(STDIN_FILENO, STDOUT_FILENO);
|
|
endwin();
|
|
(void)close(master);
|
|
rtt = tt;
|
|
rtt.c_lflag |= (ICANON|ECHO);
|
|
(void)tcsetattr(slave, TCSANOW, &rtt);
|
|
login_tty(slave);
|
|
if (logging) {
|
|
fprintf(logfp, "executing:");
|
|
for (i = 0; args[i]; i++)
|
|
fprintf(logfp, " %s", args[i]);
|
|
fprintf(logfp, "\n");
|
|
fclose(logfp);
|
|
}
|
|
if (scripting) {
|
|
for (i = 0; args[i]; i++)
|
|
fprintf(script, "%s ", args[i]);
|
|
fprintf(script, "\n");
|
|
fclose(script);
|
|
}
|
|
/*
|
|
* If target_prefix == "", the chroot will fail, but
|
|
* that's ok, since we don't need it then.
|
|
*/
|
|
if ((flags & RUN_CHROOT) != 0)
|
|
chroot(target_prefix());
|
|
execvp(*args, args);
|
|
/* The parent will see this as the output from the child */
|
|
warn("execvp %s", *args);
|
|
_exit(EXIT_FAILURE);
|
|
break; /* end of child */
|
|
default:
|
|
/*
|
|
* parent: we've set up the subprocess.
|
|
* forward tty signals to its process group.
|
|
*/
|
|
ttysig_forward = child;
|
|
ttysig_ignore = 0;
|
|
break;
|
|
}
|
|
FD_ZERO(&active_fd_set);
|
|
FD_SET(master, &active_fd_set);
|
|
FD_SET(STDIN_FILENO, &active_fd_set);
|
|
|
|
for (selectfailed = 0;;) {
|
|
if (selectfailed) {
|
|
const char *mmsg = "select(2) failed but no child died?";
|
|
if (logging)
|
|
(void)fprintf(logfp, mmsg);
|
|
errx(1, mmsg);
|
|
}
|
|
read_fd_set = active_fd_set;
|
|
tmo.tv_sec = 1;
|
|
tmo.tv_usec = 0;
|
|
if (select(FD_SETSIZE, &read_fd_set, NULL, NULL, &tmo) < 0) {
|
|
if (errno == EINTR)
|
|
goto loop;
|
|
warn("select");
|
|
if (logging)
|
|
(void)fprintf(logfp,
|
|
"select failure: %s\n", strerror(errno));
|
|
++selectfailed;
|
|
} else for (i = 0; i < FD_SETSIZE; ++i) {
|
|
if (!FD_ISSET(i, &read_fd_set))
|
|
continue;
|
|
n = read(i, ibuf, sizeof ibuf - 1);
|
|
if (n <= 0) {
|
|
if (n < 0)
|
|
warn("read");
|
|
continue;
|
|
}
|
|
ibuf[n] = 0;
|
|
cp = ibuf;
|
|
if (i == STDIN_FILENO) {
|
|
(void)write(master, ibuf, (size_t)n);
|
|
if (!(rtt.c_lflag & ECHO))
|
|
continue;
|
|
} else {
|
|
pktdata = ibuf[0];
|
|
if (pktdata != 0) {
|
|
if (pktdata & TIOCPKT_IOCTL)
|
|
memcpy(&rtt, ibuf, sizeof(rtt));
|
|
continue;
|
|
}
|
|
cp += 1;
|
|
}
|
|
if (*cp == 0 || flags & RUN_SILENT)
|
|
continue;
|
|
if (logging) {
|
|
fprintf(logfp, "%s", cp);
|
|
fflush(logfp);
|
|
}
|
|
if (*actionwin == NULL)
|
|
*actionwin = show_cmd(scmd, win);
|
|
/* posix curses is braindead wrt \r\n so... */
|
|
for (ncp = cp; (ncp = strstr(ncp, "\r\n")); ncp += 2) {
|
|
ncp[0] = '\n';
|
|
ncp[1] = '\r';
|
|
}
|
|
waddstr(*actionwin, cp);
|
|
wrefresh(*actionwin);
|
|
}
|
|
loop:
|
|
pid = wait4(child, &status, WNOHANG, 0);
|
|
if (pid == child && (WIFEXITED(status) || WIFSIGNALED(status)))
|
|
break;
|
|
}
|
|
close(master);
|
|
close(slave);
|
|
if (logging)
|
|
fflush(logfp);
|
|
|
|
/* from here on out, we take tty signals ourselves */
|
|
ttysig_forward = 0;
|
|
|
|
reset_prog_mode();
|
|
|
|
if (WIFEXITED(status)) {
|
|
*errstr = msg_string(MSG_Command_failed);
|
|
return WEXITSTATUS(status);
|
|
}
|
|
if (WIFSIGNALED(status)) {
|
|
*errstr = msg_string(MSG_Command_ended_on_signal);
|
|
return WTERMSIG(status);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* generic program runner.
|
|
* flags:
|
|
* RUN_DISPLAY display command name and output
|
|
* RUN_FATAL program errors are fatal
|
|
* RUN_CHROOT chroot to target before the exec
|
|
* RUN_FULLSCREEN display output only
|
|
* RUN_SILENT do not display program output
|
|
* RUN_DISPLAY_ERR display status if program fails
|
|
* RUN_ERROR_OK don't wait for key if program fails
|
|
* RUN_PROGRESS don't wait for key if program has output
|
|
* If both RUN_DISPLAY and RUN_SILENT are clear then the program name will
|
|
* be displayed as soon as it generates output.
|
|
* Steps are taken to collect console messages, they will be interleaved
|
|
* into the program output - but not upset curses.
|
|
*/
|
|
|
|
int
|
|
run_prog(int flags, msg errmsg, const char *cmd, ...)
|
|
{
|
|
va_list ap;
|
|
struct winsize win;
|
|
int ret;
|
|
WINDOW *actionwin = NULL;
|
|
char *scmd;
|
|
char **args;
|
|
const char *errstr = NULL;
|
|
|
|
va_start(ap, cmd);
|
|
vasprintf(&scmd, cmd, ap);
|
|
if (scmd == NULL)
|
|
err(1, "vasprintf(&scmd, \"%s\", ...)", cmd);
|
|
|
|
args = make_argv(scmd);
|
|
|
|
/* Make curses save tty settings */
|
|
def_prog_mode();
|
|
|
|
(void)ioctl(STDIN_FILENO, TIOCGWINSZ, &win);
|
|
/* Apparently, we sometimes get 0x0 back, and that's not useful */
|
|
if (win.ws_row == 0)
|
|
win.ws_row = 24;
|
|
if (win.ws_col == 0)
|
|
win.ws_col = 80;
|
|
|
|
if ((flags & RUN_DISPLAY) != 0) {
|
|
if (flags & RUN_FULLSCREEN) {
|
|
wclear(stdscr);
|
|
clearok(stdscr, 1);
|
|
touchwin(stdscr);
|
|
refresh();
|
|
actionwin = stdscr;
|
|
} else
|
|
actionwin = show_cmd(scmd, &win);
|
|
} else
|
|
win.ws_row -= 4;
|
|
|
|
ret = launch_subwin(&actionwin, args, &win, flags, scmd, &errstr);
|
|
|
|
if (ret != 0 && actionwin == NULL && flags & RUN_DISPLAY_ERR)
|
|
actionwin = show_cmd(scmd, &win);
|
|
|
|
if (actionwin != NULL) {
|
|
int y, x;
|
|
getyx(actionwin, y, x);
|
|
standout();
|
|
if (ret != 0) {
|
|
if (actionwin != stdscr)
|
|
move(0, 13);
|
|
else if (x != 0)
|
|
addstr("\n");
|
|
addstr(errstr);
|
|
x = 1; /* force newline below */
|
|
} else
|
|
if (actionwin != stdscr)
|
|
mvaddstr(0, 13, msg_string(MSG_Finished));
|
|
standend();
|
|
refresh();
|
|
if (ret != 0 || (y + x != 0 && !(flags & RUN_PROGRESS))) {
|
|
if (actionwin != stdscr)
|
|
move(2, 5);
|
|
else if (x != 0)
|
|
addstr("\n");
|
|
addstr(msg_string(MSG_Hit_enter_to_continue));
|
|
refresh();
|
|
getchar();
|
|
}
|
|
}
|
|
|
|
/* clean things up */
|
|
if (actionwin != NULL) {
|
|
if (actionwin != stdscr)
|
|
delwin(actionwin);
|
|
wclear(stdscr);
|
|
touchwin(stdscr);
|
|
clearok(stdscr, 1);
|
|
refresh();
|
|
}
|
|
|
|
va_end(ap);
|
|
/* restore tty setting we saved earlier */
|
|
reset_prog_mode();
|
|
if (ret != 0) {
|
|
if (errmsg != NULL) {
|
|
msg_display(errmsg, scmd);
|
|
process_menu(MENU_ok, NULL);
|
|
}
|
|
if (flags & RUN_FATAL)
|
|
exit(ret);
|
|
}
|
|
free(scmd);
|
|
free_argv(args);
|
|
return ret;
|
|
}
|