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