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