root/usr.sbin/sysinst/run.c
/*      $NetBSD: run.c,v 1.17 2026/06/28 11:03:14 gson 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. 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

#if defined(DEBUG) && defined(DEBUG_SYSTEM)
static inline int
Xsystem(const char *y)
{
        printf ("%s\n", y);
        return 0;
}
#else
#define Xsystem(y) system(y)
#endif

/*
 * local prototypes
 */
int log_flip (menudesc *, void *);
static int script_flip (menudesc *, void *);

#define BUFSIZE 4096

menu_ent logmenu [2] = {
        { .opt_action=log_flip},
        { .opt_action=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 ? script != NULL : logfp != NULL) ?
                    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, MSG_exit_menu_generic);

        if (menu_no < 0) {
                (void)fprintf(stderr, "Dynamic menu creation failed.\n");
                if (logfp)
                        (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, void *arg)
{
        time_t tloc;

        (void)time(&tloc);
        if (logfp) {
                fprintf(logfp, "Log ended at: %s\n", safectime(&tloc));
                fflush(logfp);
                fclose(logfp);
                logfp = NULL;
        } else {
                logfp = fopen("/tmp/sysinst.log", "a");
                if (logfp != NULL) {
                        fprintf(logfp,
                            "Log started at: %s\n", safectime(&tloc));
                        fflush(logfp);
                } else {
                        if (mainwin) {
                                msg_fmt_display(MSG_openfail, "%s%s",
                                    "log file", strerror(errno));
                        } else {
                                fprintf(stderr, "could not open /tmp/sysinst.log: %s\n",
                                    strerror(errno));
                                exit(1);
                        }
                }
        }
        return(0);
}

static int
/*ARGSUSED*/
script_flip(menudesc *m, void *arg)
{
        time_t tloc;

        (void)time(&tloc);
        if (script) {
                scripting_fprintf(NULL, "# Script ended at: %s\n",
                    safectime(&tloc));
                fflush(script);
                fclose(script);
                script = NULL;
        } else {
                script = fopen("/tmp/sysinst.sh", "w");
                if (script != NULL) {
                        scripting_fprintf(NULL, "#!/bin/sh\n");
                        scripting_fprintf(NULL, "# Script started at: %s\n",
                            safectime(&tloc));
                        fflush(script);
                } else {
                        msg_fmt_display(MSG_openfail, "%s%s", "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. */
        size_t abytes;          /* allocated size of buffer */
        struct stat st;         /* stat information. */
        int ch;
        FILE *f;
        char fileorcmd[STRSIZE];
        va_list ap;
        char *cp;

        va_start(ap, name);
        vsnprintf(fileorcmd, sizeof fileorcmd, 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) {
                        if (logfp)
                                fprintf(logfp, "%s: failed to open %s\n",
                                    __func__, fileorcmd);
                        *buffer = NULL;
                        return -1;
                }
        } else {
                /* Open the program. */
                f = popen(fileorcmd, "r");
                if (f == NULL) {
                        if (logfp)
                                fprintf(logfp, "%s: failed to open %s\n",
                                    __func__, fileorcmd);
                        *buffer = NULL;
                        return -1;
                }
                fbytes = 0;
        }

        if (fbytes == 0)
                abytes = BUFSIZE;
        else
                abytes = fbytes+1;

        /* Allocate the buffer size. */
        *buffer = cp = malloc(abytes);
        if (!cp)
                nbytes =  -1;
        else {
                /* Read the buffer. */
                nbytes = 0;
                while ((ch = fgetc(f)) != EOF) {
                        if (nbytes >= abytes-1) {
                                if (fbytes > 0 || abytes >= 512*BUFSIZE) {
                                        free(cp);
                                        *buffer = cp = NULL;
                                        nbytes = -1;
                                        break;
                                }
                                abytes *= 2;
                                *buffer = cp = realloc(cp, abytes);
                                if (!cp) {
                                        nbytes =  -1;
                                        break;
                                }

                        }
                        cp[nbytes++] = ch;
                }
                if (cp)
                        cp[nbytes] = 0;
        }

        if (kind == T_FILE)
                fclose(f);
        else
                pclose(f);

        if (nbytes <= 0 && logfp)
                fprintf(logfp, "%s: failed for %s\n", __func__, fileorcmd);

        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(char *cmd)
{
        char **argv = 0;
        int argc = 0;
        char *cp, *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);
                /* Hack to remove %xx encoded ftp password */
                dp = strstr(cmd, ":%");
                if (dp != NULL && dp < cp) {
                        for (fn = dp + 4; *fn == '%'; fn += 3)
                                continue;
                        if (*fn == '@')
                                memset(dp + 1, '*', fn - dp - 1);
                }
                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)
{
        WINDOW *actionwin;
        int nrow;

        wclear(stdscr);
        clearok(stdscr, 1);
        touchwin(stdscr);
        refresh();

        mvaddstr(0, 4, msg_string(MSG_Status));
        standout();
        addstr(msg_string(MSG_Running));
        standend();
        mvaddstr(1, 4, msg_string(MSG_Command));
        standout();
        printw("%s", scmd);
        standend();
        addstr("\n\n");
        hline(0, win->ws_col);
        refresh();

        nrow = getcury(stdscr) + 1;

        actionwin = subwin(stdscr, win->ws_row - nrow, win->ws_col, nrow, 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));
        }

        wmove(actionwin, 0, 0);
        wrefresh(actionwin);

        return actionwin;
}

/*
 * launch a program inside a subwindow, and report its 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, tt;
        struct timeval tmo;

        (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);

        redirect_console(master, slave);

        if (logfp)
                fflush(logfp);
        if (script)
                fflush(script);

        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 (logfp) {
                        fprintf(logfp, "executing: %s\n", scmd);
                        fclose(logfp);
                        logfp = NULL;
                }
                if (script) {
                        fprintf(script, "%s\n", scmd);
                        fclose(script);
                        script = NULL;
                }
                if (strcmp(args[0], "cd") == 0 && strcmp(args[2], "&&") == 0) {
                        target_chdir_or_die(args[1]);
                        args += 3;
                }
                if (flags & RUN_XFER_DIR)
                        target_chdir_or_die(xfer_dir);
                /*
                 * If target_prefix == "", the chroot will fail, but
                 * that's ok, since we don't need it then.
                 */
                if (flags & RUN_CHROOT && *target_prefix()
                    && chroot(target_prefix()) != 0)
                        warn("chroot(%s) for %s", target_prefix(), *args);
                else {
                        execvp(*args, args);
                        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;
        }

        /*
         * Now loop transferring program output to screen, and keyboard
         * input to the program.
         */

        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 (logfp)
                                (void)fprintf(logfp, mmsg);
                        errx(1, mmsg);
                }
                read_fd_set = active_fd_set;
                tmo.tv_sec = flags & RUN_SILENT ? 20 : 2;
                tmo.tv_usec = 0;
                i = select(FD_SETSIZE, &read_fd_set, NULL, NULL, &tmo);
                if (i == 0 && *actionwin == NULL && (flags & RUN_SILENT) == 0)
                        *actionwin = show_cmd(scmd, win);
                if (i < 0) {
                        if (errno != EINTR) {
                                warn("select");
                                if (logfp)
                                        (void)fprintf(logfp,
                                            "select failure: %s\n",
                                            strerror(errno));
                                selectfailed = 1;
                        }
                } 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 (logfp) {
                                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);
                }
                pid = wait4(child, &status, WNOHANG, 0);
                if (pid == child && (WIFEXITED(status) || WIFSIGNALED(status)))
                        break;
        }
        close(master);
        close(slave);
        discard_console_output();
        if (logfp)
                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_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_program(int flags, 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);
        va_end(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_STDSCR) {
                        actionwin = stdscr;
                        wmove(stdscr, msg_row()+2, 0);
                        wrefresh(stdscr);
                } else 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);
        fpurge(stdin);

        /* If the command failed, show command name */
        if (actionwin == NULL && ret != 0 && !(flags & RUN_ERROR_OK))
                actionwin = show_cmd(scmd, &win);

        if (actionwin != NULL) {
                int y, x;
                getyx(actionwin, y, x);
                if (actionwin != stdscr)
                        mvaddstr(0, 4, msg_string(MSG_Status));
                if (ret != 0) {
                        if (actionwin == stdscr && x != 0)
                                addstr("\n");
                        x = 1;  /* force newline below */
                        standout();
                        addstr(errstr);
                        standend();
                } else {
                        if (actionwin != stdscr) {
                                standout();
                                addstr(msg_string(MSG_Finished));
                                standend();
                        }
                }
                clrtoeol();
                refresh();
                if ((ret != 0 && !(flags & RUN_ERROR_OK)) ||
                    (y + x != 0 && !(flags & RUN_PROGRESS))) {
                        if (actionwin != stdscr)
                                move(getbegy(actionwin) - 2, 5);
                        else if (x != 0)
                                addstr("\n");
                        addstr(msg_string(MSG_Hit_enter_to_continue));
                        refresh();
                        getchar();
                } else {
                        if (y + x != 0) {
                                /* give user 1 second to see messages */
                                refresh();
                                sleep(1);
                        }
                }
        }

        /* restore tty setting we saved earlier */
        reset_prog_mode();

        /* clean things up */
        if (actionwin != NULL) {
                if (actionwin != stdscr)
                        delwin(actionwin);
                if (errstr == 0 || !(flags & RUN_NO_CLEAR)) {
                        wclear(stdscr);
                        touchwin(stdscr);
                        clearok(stdscr, 1);
                        refresh();
                }
        }

        free(scmd);
        free_argv(args);

        if (ret != 0 && flags & RUN_FATAL)
                exit(ret);
        return ret;
}