NetBSD/distrib/utils/sysinst/run.c

660 lines
15 KiB
C

/* $NetBSD: run.c,v 1.45 2003/07/18 09:34:42 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 <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 launch_subwin (WINDOW *actionwin, char **args, struct winsize *win,
int display, const char **errstr);
int log_flip (menudesc *, menu_ent *, void *);
int script_flip (menudesc *, menu_ent *, void *);
#define BUFSIZE 4096
char log_text[2][30] = {"Logging: Off", "Scripting: Off"};
menu_ent logmenu [2] = {
{ log_text[0], OPT_NOMENU, 0, log_flip},
{ log_text[1], OPT_NOMENU, 0, script_flip} };
void
do_logging(void)
{
int menu_no;
menu_no = new_menu ("Logging Functions", logmenu, 2, -1, 12,
0, 20, MC_SCROLL, NULL, NULL, NULL,
"Pick an option to turn on or off.\n", 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);
}
int
/*ARGSUSED*/
log_flip(menudesc *m, menu_ent *opt, void *arg)
{
time_t tloc;
(void)time(&tloc);
if (logging == 1) {
sprintf(log_text[0], "Logging: Off");
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) {
sprintf(log_text[0], "Logging: On");
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);
}
int
/*ARGSUSED*/
script_flip(menudesc *m, menu_ent *opt, void *arg)
{
time_t tloc;
(void)time(&tloc);
if (scripting == 1) {
sprintf(log_text[1], "Scripting: Off");
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) {
sprintf(log_text[1], "Scripting: On");
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++) {
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[-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);
}
/*
* 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 **errstr)
{
int xcor,ycor;
int n, i, j;
int selectfailed;
int status, master, slave;
fd_set active_fd_set, read_fd_set;
int dataflow[2];
pid_t child, subchild, pid;
char ibuf[MAXBUF], obuf[MAXBUF];
char pktdata;
struct termios rtt;
struct termios tt;
struct timeval tmo;
if (pipe(dataflow) < 0) {
*errstr = "pipe() failed";
return (1);
}
(void)tcgetattr(STDIN_FILENO, &tt);
if (openpty(&master, &slave, NULL, &tt, win) == -1) {
*errstr = "openpty() failed";
return(1);
}
#if 0
rtt = tt;
rtt.c_lflag |= (ICANON|ECHO);
(void)tcsetattr(STDIN_FILENO, TCSAFLUSH, &rtt);
#endif
rtt = tt;
/* ignore tty signals until we're done with subprocess setup */
ttysig_ignore = 1;
ioctl(master, TIOCPKT, &ttysig_ignore);
child = fork();
switch (child) {
case -1:
ttysig_ignore = 0;
refresh();
*errstr = "fork() failed";
return -1;
case 0: /* child */
(void)close(STDIN_FILENO);
subchild = fork();
if (subchild == 0) {
close(dataflow[0]);
for (;;) {
n = read(master, obuf, sizeof(obuf));
if (n <= 0)
break;
write(dataflow[1], obuf, (size_t)n);
} /* while spinning */
_exit(EXIT_SUCCESS);
} /* subchild, child forks */
(void)close(master);
close(dataflow[1]);
close(dataflow[0]);
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;
}
close(dataflow[1]);
FD_ZERO(&active_fd_set);
FD_SET(dataflow[0], &active_fd_set);
FD_SET(STDIN_FILENO, &active_fd_set);
for (selectfailed = 0;;) {
if (selectfailed) {
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)) {
n = read(i, ibuf, MAXBUF);
if (n <= 0) {
if (n < 0)
warn("read");
continue;
}
if (i == STDIN_FILENO) {
(void)write(master, ibuf, (size_t)n);
if ((rtt.c_lflag & ECHO) == 0)
goto enddisp;
}
pktdata = ibuf[0];
if (pktdata != 0 && i != STDIN_FILENO) {
if (pktdata & TIOCPKT_IOCTL)
memcpy(&rtt, ibuf, sizeof(rtt));
goto enddisp;
}
for (j = 1; j < n; j++) {
if ((flags & RUN_DISPLAY) != 0) {
switch (ibuf[j]) {
case '\n':
getyx(actionwin, ycor, xcor);
if (ycor + 1 >= getmaxy(actionwin)) {
scroll(actionwin);
wmove(actionwin, getmaxy(actionwin) - 1, 0);
} else
wmove(actionwin, ycor + 1, 0);
break;
case '\r':
getyx(actionwin, ycor, xcor);
wmove(actionwin, ycor, 0);
break;
case '\b':
getyx(actionwin, ycor, xcor);
if (xcor > 0)
wmove(actionwin, ycor, xcor - 1);
break;
default:
waddch(actionwin, ibuf[j]);
break;
}
if (logging)
putc(ibuf[j], logfp);
}
}
enddisp:
if ((flags & RUN_DISPLAY) != 0)
wrefresh(actionwin);
if (logging)
fflush(logfp);
}
}
loop:
pid = wait4(child, &status, WNOHANG, 0);
if (pid == child && (WIFEXITED(status) || WIFSIGNALED(status)))
break;
}
close(dataflow[0]); /* clean up our leaks */
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 = "command failed";
return(WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
*errstr = "command ended on signal";
return(WTERMSIG(status));
} else
return(0);
}
/*
* generic program runner. fatal and display can be set to
* 1 if you wish the output to be displayed, or an error to be
* fatal.
*/
int
run_prog(int flags, msg errmsg, const char *cmd, ...)
{
va_list ap;
struct winsize win;
int ret;
WINDOW *actionwin, *statuswin, *boxwin;
char *scmd;
char **args;
const char *errstr;
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_SYSTEM) != 0) {
if ((flags & RUN_CHROOT) != 0)
chroot(target_prefix());
ret = system(scmd);
} else if ((flags & RUN_DISPLAY) != 0) {
wclear(stdscr);
clearok(stdscr, 1);
touchwin(stdscr);
refresh();
if ((flags & RUN_FULLSCREEN) != 0) {
ret = launch_subwin(stdscr, args, &win, flags, &errstr);
if (ret != 0) {
waddstr(stdscr, "Press any key to continue");
wrefresh(stdscr);
getchar();
}
goto done;
}
statuswin = subwin(stdscr, 3, win.ws_col, 0, 0);
if (statuswin == NULL) {
fprintf(stderr, "sysinst: failed to allocate"
" status window.\n");
exit(1);
}
boxwin = subwin(stdscr, 1, win.ws_col, 3, 0);
if (boxwin == NULL) {
fprintf(stderr, "sysinst: failed to allocate"
" status box.\n");
exit(1);
}
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);
win.ws_row -= 4;
wmove(statuswin, 0, 5);
waddstr(statuswin, "Status: ");
wstandout(statuswin);
waddstr(statuswin, "Running");
wstandend(statuswin);
wmove(statuswin, 1, 4);
waddstr(statuswin, "Command: ");
wstandout(statuswin);
waddstr(statuswin, scmd);
wstandend(statuswin);
wrefresh(statuswin);
wmove(boxwin, 0, 0);
{
int n, m;
for (n = win.ws_col; (m = min(n, 30)) > 0; n -= m)
waddstr(boxwin,
"------------------------------" + 30 - m);
}
wrefresh(boxwin);
wrefresh(actionwin);
ret = launch_subwin(actionwin, args, &win, flags, &errstr);
wmove(statuswin, 0, 13);
wstandout(statuswin);
if (ret) {
waddstr(statuswin, "Failed: ");
waddstr(statuswin, errstr);
} else
waddstr(statuswin, "Finished");
wstandend(statuswin);
waddstr(statuswin, " ");
wmove(statuswin, 2, 5);
if (ret != 0)
waddstr(statuswin, "Press any key to continue");
wrefresh(statuswin);
if (ret != 0)
(void)getchar();
/* clean things up */
delwin(actionwin);
delwin(boxwin);
delwin(statuswin);
done:
wclear(stdscr);
touchwin(stdscr);
clearok(stdscr, 1);
refresh();
} else { /* display */
ret = launch_subwin(NULL, args, &win, flags, &errstr);
}
va_end(ap);
/* restore tty setting we saved earlier */
reset_prog_mode();
if ((flags & RUN_FATAL) != 0 && ret != 0)
exit(ret);
if (ret && errmsg != NULL) {
msg_display(errmsg, scmd);
process_menu(MENU_ok, NULL);
}
free(scmd);
free_argv(args);
return (ret);
}