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