Expand PMF_FN_* macros.
[netbsd-mini2440.git] / distrib / utils / sysinst / run.c
blob56cd061c6aa3783efa07dbbbb8e39617040c632d
1 /* $NetBSD: run.c,v 1.64 2007/10/09 18:43:26 martin Exp $ */
3 /*
4 * Copyright 1997 Piermont Information Systems Inc.
5 * All rights reserved.
7 * Written by Philip A. Nelson for Piermont Information Systems Inc.
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. All advertising materials mentioning features or use of this software
18 * must display the following acknowledgement:
19 * This product includes software developed for the NetBSD Project by
20 * Piermont Information Systems Inc.
21 * 4. The name of Piermont Information Systems Inc. may not be used to endorse
22 * or promote products derived from this software without specific prior
23 * written permission.
25 * THIS SOFTWARE IS PROVIDED BY PIERMONT INFORMATION SYSTEMS INC. ``AS IS''
26 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
27 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
28 * ARE DISCLAIMED. IN NO EVENT SHALL PIERMONT INFORMATION SYSTEMS INC. BE
29 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
30 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
31 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
32 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
33 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
34 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
35 * THE POSSIBILITY OF SUCH DAMAGE.
39 /* run.c -- routines to interact with other programs. */
41 /* XXX write return codes ignored. XXX */
43 #include <errno.h>
44 #include <stdio.h>
45 #include <stdarg.h>
46 #include <stdlib.h>
47 #include <unistd.h>
48 #include <fcntl.h>
49 #include <curses.h>
50 #include <termios.h>
51 #include <dirent.h>
52 #include <util.h>
53 #include <signal.h>
54 #include <err.h>
55 #include <sys/ioctl.h>
56 #include <sys/types.h>
57 #include <sys/wait.h>
58 #include <sys/stat.h>
59 #include "defs.h"
61 #include "menu_defs.h"
62 #include "msg_defs.h"
64 #define MAXBUF 256
66 #ifdef DEBUG
67 #define Xsystem(y) printf ("%s\n", y), 0
68 #else
69 #define Xsystem(y) system(y)
70 #endif
73 * local prototypes
75 int log_flip (menudesc *, void *);
76 static int script_flip (menudesc *, void *);
78 #define BUFSIZE 4096
80 menu_ent logmenu [2] = {
81 { NULL, OPT_NOMENU, 0, log_flip},
82 { NULL, OPT_NOMENU, 0, script_flip} };
84 static void
85 log_menu_label(menudesc *m, int opt, void *arg)
87 wprintw(m->mw, "%s: %s",
88 msg_string(opt ? MSG_Scripting : MSG_Logging),
89 msg_string((opt ? scripting : logging) ? MSG_On : MSG_Off));
92 void
93 do_logging(void)
95 int menu_no;
97 menu_no = new_menu(MSG_Logging_functions, logmenu, 2, -1, 12,
98 0, 20, MC_SCROLL, NULL, log_menu_label, NULL,
99 MSG_Pick_an_option, NULL);
101 if (menu_no < 0) {
102 (void)fprintf(stderr, "Dynamic menu creation failed.\n");
103 if (logging)
104 (void)fprintf(logfp, "Dynamic menu creation failed.\n");
105 exit(EXIT_FAILURE);
107 process_menu(menu_no, NULL);
108 free_menu(menu_no);
112 /*ARGSUSED*/
113 log_flip(menudesc *m, void *arg)
115 time_t tloc;
117 (void)time(&tloc);
118 if (logging == 1) {
119 logging = 0;
120 fprintf(logfp, "Log ended at: %s\n", asctime(localtime(&tloc)));
121 fflush(logfp);
122 fclose(logfp);
123 } else {
124 logfp = fopen("/tmp/sysinst.log", "a");
125 if (logfp != NULL) {
126 logging = 1;
127 fprintf(logfp,
128 "Log started at: %s\n", asctime(localtime(&tloc)));
129 fflush(logfp);
130 } else {
131 msg_display(MSG_openfail, "log file", strerror(errno));
134 return(0);
137 static int
138 /*ARGSUSED*/
139 script_flip(menudesc *m, void *arg)
141 time_t tloc;
143 (void)time(&tloc);
144 if (scripting == 1) {
145 scripting_fprintf(NULL, "# Script ended at: %s\n", asctime(localtime(&tloc)));
146 scripting = 0;
147 fflush(script);
148 fclose(script);
149 } else {
150 script = fopen("/tmp/sysinst.sh", "w");
151 if (script != NULL) {
152 scripting = 1;
153 scripting_fprintf(NULL, "#!/bin/sh\n");
154 scripting_fprintf(NULL, "# Script started at: %s\n",
155 asctime(localtime(&tloc)));
156 fflush(script);
157 } else {
158 msg_display(MSG_openfail, "script file", strerror(errno));
161 return(0);
165 collect(int kind, char **buffer, const char *name, ...)
167 size_t nbytes; /* Number of bytes in buffer. */
168 size_t fbytes; /* Number of bytes in file. */
169 struct stat st; /* stat information. */
170 int ch;
171 FILE *f;
172 char fileorcmd[STRSIZE];
173 va_list ap;
174 char *cp;
176 va_start(ap, name);
177 vsnprintf(fileorcmd, sizeof fileorcmd, name, ap);
178 va_end(ap);
180 if (kind == T_FILE) {
181 /* Get the file information. */
182 if (stat(fileorcmd, &st)) {
183 *buffer = NULL;
184 return -1;
186 fbytes = (size_t)st.st_size;
188 /* Open the file. */
189 f = fopen(fileorcmd, "r");
190 if (f == NULL) {
191 *buffer = NULL;
192 return -1;
194 } else {
195 /* Open the program. */
196 f = popen(fileorcmd, "r");
197 if (f == NULL) {
198 *buffer = NULL;
199 return -1;
201 fbytes = BUFSIZE;
204 if (fbytes == 0)
205 fbytes = BUFSIZE;
207 /* Allocate the buffer size. */
208 *buffer = cp = malloc(fbytes + 1);
209 if (!cp)
210 nbytes = -1;
211 else {
212 /* Read the buffer. */
213 nbytes = 0;
214 while (nbytes < fbytes && (ch = fgetc(f)) != EOF)
215 cp[nbytes++] = ch;
216 cp[nbytes] = 0;
219 if (kind == T_FILE)
220 fclose(f);
221 else
222 pclose(f);
224 return nbytes;
229 * system(3), but with a debug wrapper.
230 * use only for curses sub-applications.
233 do_system(const char *execstr)
235 register int ret;
238 * The following may be more than one function call. Can't just
239 * "return Xsystem (command);"
242 ret = Xsystem(execstr);
243 return (ret);
247 static char **
248 make_argv(const char *cmd)
250 char **argv = 0;
251 int argc = 0;
252 const char *cp;
253 char *dp, *fn;
254 DIR *dir;
255 struct dirent *dirent;
256 int l;
258 for (; *cmd != 0; cmd = cp + strspn(cp, " "), argc++) {
259 if (*cmd == '\'')
260 cp = strchr(++cmd, '\'');
261 else
262 cp = strchr(cmd, ' ');
263 if (cp == NULL)
264 cp = strchr(cmd, 0);
265 argv = realloc(argv, (argc + 2) * sizeof *argv);
266 if (argv == NULL)
267 err(1, "realloc(argv) for %s", cmd);
268 asprintf(argv + argc, "%.*s", (int)(cp - cmd), cmd);
269 /* Hack to remove %xx encoded ftp password */
270 dp = strstr(cmd, ":%");
271 if (dp != NULL && dp < cp) {
272 for (fn = dp + 4; *fn == '%'; fn += 3)
273 continue;
274 if (*fn == '@')
275 memset(dp + 1, '*', fn - dp - 1);
277 if (*cp == '\'')
278 cp++;
279 if (cp[-1] != '*')
280 continue;
281 /* do limited filename globbing */
282 dp = argv[argc];
283 fn = strrchr(dp, '/');
284 if (fn != NULL)
285 *fn = 0;
286 dir = opendir(dp);
287 if (fn != NULL)
288 *fn++ = '/';
289 else
290 fn = dp;
291 if (dir == NULL)
292 continue;
293 l = strlen(fn) - 1;
294 while ((dirent = readdir(dir))) {
295 if (dirent->d_name[0] == '.')
296 continue;
297 if (strncmp(dirent->d_name, fn, l) != 0)
298 continue;
299 if (dp != argv[argc])
300 argc++;
301 argv = realloc(argv, (argc + 2) * sizeof *argv);
302 if (argv == NULL)
303 err(1, "realloc(argv) for %s", cmd);
304 asprintf(argv + argc, "%.*s%s", (int)(fn - dp), dp,
305 dirent->d_name);
307 if (dp != argv[argc])
308 free(dp);
309 closedir(dir);
311 argv[argc] = NULL;
312 return argv;
315 static void
316 free_argv(char **argv)
318 char **n, *a;
320 for (n = argv; (a = *n++);)
321 free(a);
322 free(argv);
325 static WINDOW *
326 show_cmd(const char *scmd, struct winsize *win)
328 int n, m;
329 WINDOW *actionwin;
330 int nrow;
332 wclear(stdscr);
333 clearok(stdscr, 1);
334 touchwin(stdscr);
335 refresh();
337 mvaddstr(0, 4, msg_string(MSG_Status));
338 standout();
339 addstr(msg_string(MSG_Running));
340 standend();
341 mvaddstr(1, 4, msg_string(MSG_Command));
342 standout();
343 printw("%s", scmd);
344 standend();
345 addstr("\n\n");
346 for (n = win->ws_col; (m = min(n, 30)) > 0; n -= m)
347 addstr( "------------------------------" + 30 - m);
348 refresh();
350 nrow = getcury(stdscr) + 1;
352 actionwin = subwin(stdscr, win->ws_row - nrow, win->ws_col, nrow, 0);
353 if (actionwin == NULL) {
354 fprintf(stderr, "sysinst: failed to allocate output window.\n");
355 exit(1);
357 scrollok(actionwin, TRUE);
358 if (has_colors()) {
359 wbkgd(actionwin, getbkgd(stdscr));
360 wattrset(actionwin, getattrs(stdscr));
363 wmove(actionwin, 0, 0);
364 wrefresh(actionwin);
366 return actionwin;
370 * launch a program inside a subwindow, and report its return status when done
372 static int
373 launch_subwin(WINDOW **actionwin, char **args, struct winsize *win, int flags,
374 const char *scmd, const char **errstr)
376 int n, i;
377 int selectfailed;
378 int status, master, slave;
379 fd_set active_fd_set, read_fd_set;
380 pid_t child, pid;
381 char ibuf[MAXBUF];
382 char pktdata;
383 char *cp, *ncp;
384 struct termios rtt;
385 struct termios tt;
386 struct timeval tmo;
387 static int do_tioccons = 2;
390 (void)tcgetattr(STDIN_FILENO, &tt);
391 if (openpty(&master, &slave, NULL, &tt, win) == -1) {
392 *errstr = "openpty() failed";
393 return -1;
396 rtt = tt;
398 /* ignore tty signals until we're done with subprocess setup */
399 ttysig_ignore = 1;
400 ioctl(master, TIOCPKT, &ttysig_ignore);
402 /* Try to get console output into our pipe */
403 if (do_tioccons) {
404 if (ioctl(slave, TIOCCONS, &do_tioccons) == 0
405 && do_tioccons == 2) {
406 /* test our output - we don't want it grabbed */
407 write(1, " \b", 2);
408 ioctl(master, FIONREAD, &do_tioccons);
409 if (do_tioccons != 0) {
410 do_tioccons = 0;
411 ioctl(slave, TIOCCONS, &do_tioccons);
412 } else
413 do_tioccons = 1;
417 if (logging)
418 fflush(logfp);
419 if (scripting)
420 fflush(script);
422 child = fork();
423 switch (child) {
424 case -1:
425 ttysig_ignore = 0;
426 refresh();
427 *errstr = "fork() failed";
428 return -1;
429 case 0: /* child */
430 (void)close(STDIN_FILENO);
431 /* silently stop curses */
432 (void)close(STDOUT_FILENO);
433 (void)open("/dev/null", O_RDWR, 0);
434 dup2(STDIN_FILENO, STDOUT_FILENO);
435 endwin();
436 (void)close(master);
437 rtt = tt;
438 rtt.c_lflag |= (ICANON|ECHO);
439 (void)tcsetattr(slave, TCSANOW, &rtt);
440 login_tty(slave);
441 if (logging) {
442 fprintf(logfp, "executing: %s\n", scmd);
443 fclose(logfp);
445 if (scripting) {
446 fprintf(script, "%s\n", scmd);
447 fclose(script);
449 if (strcmp(args[0], "cd") == 0 && strcmp(args[2], "&&") == 0) {
450 target_chdir_or_die(args[1]);
451 args += 3;
453 if (flags & RUN_XFER_DIR)
454 target_chdir_or_die(xfer_dir);
456 * If target_prefix == "", the chroot will fail, but
457 * that's ok, since we don't need it then.
459 if (flags & RUN_CHROOT && *target_prefix()
460 && chroot(target_prefix()) != 0)
461 warn("chroot(%s) for %s", target_prefix(), *args);
462 else {
463 execvp(*args, args);
464 warn("execvp %s", *args);
466 _exit(EXIT_FAILURE);
467 // break; /* end of child */
468 default:
470 * parent: we've set up the subprocess.
471 * forward tty signals to its process group.
473 ttysig_forward = child;
474 ttysig_ignore = 0;
475 break;
479 * Now loop transferring program output to screen, and keyboard
480 * input to the program.
483 FD_ZERO(&active_fd_set);
484 FD_SET(master, &active_fd_set);
485 FD_SET(STDIN_FILENO, &active_fd_set);
487 for (selectfailed = 0;;) {
488 if (selectfailed) {
489 const char *mmsg = "select(2) failed but no child died?";
490 if (logging)
491 (void)fprintf(logfp, mmsg);
492 errx(1, mmsg);
494 read_fd_set = active_fd_set;
495 tmo.tv_sec = 2;
496 tmo.tv_usec = 0;
497 i = select(FD_SETSIZE, &read_fd_set, NULL, NULL, &tmo);
498 if (i == 0 && *actionwin == NULL)
499 *actionwin = show_cmd(scmd, win);
500 if (i < 0) {
501 if (errno != EINTR) {
502 warn("select");
503 if (logging)
504 (void)fprintf(logfp,
505 "select failure: %s\n",
506 strerror(errno));
507 selectfailed = 1;
509 } else for (i = 0; i < FD_SETSIZE; ++i) {
510 if (!FD_ISSET(i, &read_fd_set))
511 continue;
512 n = read(i, ibuf, sizeof ibuf - 1);
513 if (n <= 0) {
514 if (n < 0)
515 warn("read");
516 continue;
518 ibuf[n] = 0;
519 cp = ibuf;
520 if (i == STDIN_FILENO) {
521 (void)write(master, ibuf, (size_t)n);
522 if (!(rtt.c_lflag & ECHO))
523 continue;
524 } else {
525 pktdata = ibuf[0];
526 if (pktdata != 0) {
527 if (pktdata & TIOCPKT_IOCTL)
528 memcpy(&rtt, ibuf, sizeof(rtt));
529 continue;
531 cp += 1;
533 if (*cp == 0 || flags & RUN_SILENT)
534 continue;
535 if (logging) {
536 fprintf(logfp, "%s", cp);
537 fflush(logfp);
539 if (*actionwin == NULL)
540 *actionwin = show_cmd(scmd, win);
541 /* posix curses is braindead wrt \r\n so... */
542 for (ncp = cp; (ncp = strstr(ncp, "\r\n")); ncp += 2) {
543 ncp[0] = '\n';
544 ncp[1] = '\r';
546 waddstr(*actionwin, cp);
547 wrefresh(*actionwin);
549 pid = wait4(child, &status, WNOHANG, 0);
550 if (pid == child && (WIFEXITED(status) || WIFSIGNALED(status)))
551 break;
553 close(master);
554 close(slave);
555 if (logging)
556 fflush(logfp);
558 /* from here on out, we take tty signals ourselves */
559 ttysig_forward = 0;
561 reset_prog_mode();
563 if (WIFEXITED(status)) {
564 *errstr = msg_string(MSG_Command_failed);
565 return WEXITSTATUS(status);
567 if (WIFSIGNALED(status)) {
568 *errstr = msg_string(MSG_Command_ended_on_signal);
569 return WTERMSIG(status);
571 return 0;
575 * generic program runner.
576 * flags:
577 * RUN_DISPLAY display command name and output
578 * RUN_FATAL program errors are fatal
579 * RUN_CHROOT chroot to target before the exec
580 * RUN_FULLSCREEN display output only
581 * RUN_SILENT do not display program output
582 * RUN_ERROR_OK don't wait for key if program fails
583 * RUN_PROGRESS don't wait for key if program has output
584 * If both RUN_DISPLAY and RUN_SILENT are clear then the program name will
585 * be displayed as soon as it generates output.
586 * Steps are taken to collect console messages, they will be interleaved
587 * into the program output - but not upset curses.
591 run_program(int flags, const char *cmd, ...)
593 va_list ap;
594 struct winsize win;
595 int ret;
596 WINDOW *actionwin = NULL;
597 char *scmd;
598 char **args;
599 const char *errstr = NULL;
601 va_start(ap, cmd);
602 vasprintf(&scmd, cmd, ap);
603 va_end(ap);
604 if (scmd == NULL)
605 err(1, "vasprintf(&scmd, \"%s\", ...)", cmd);
607 args = make_argv(scmd);
609 /* Make curses save tty settings */
610 def_prog_mode();
612 (void)ioctl(STDIN_FILENO, TIOCGWINSZ, &win);
613 /* Apparently, we sometimes get 0x0 back, and that's not useful */
614 if (win.ws_row == 0)
615 win.ws_row = 24;
616 if (win.ws_col == 0)
617 win.ws_col = 80;
619 if ((flags & RUN_DISPLAY) != 0) {
620 if (flags & RUN_FULLSCREEN) {
621 wclear(stdscr);
622 clearok(stdscr, 1);
623 touchwin(stdscr);
624 refresh();
625 actionwin = stdscr;
626 } else
627 actionwin = show_cmd(scmd, &win);
628 } else
629 win.ws_row -= 4;
631 ret = launch_subwin(&actionwin, args, &win, flags, scmd, &errstr);
632 fpurge(stdin);
634 /* If the command failed, show command name */
635 if (actionwin == NULL && ret != 0 && !(flags & RUN_ERROR_OK))
636 actionwin = show_cmd(scmd, &win);
638 if (actionwin != NULL) {
639 int y, x;
640 getyx(actionwin, y, x);
641 if (actionwin != stdscr)
642 mvaddstr(0, 4, msg_string(MSG_Status));
643 if (ret != 0) {
644 if (actionwin == stdscr && x != 0)
645 addstr("\n");
646 x = 1; /* force newline below */
647 standout();
648 addstr(errstr);
649 standend();
650 } else {
651 if (actionwin != stdscr) {
652 standout();
653 addstr(msg_string(MSG_Finished));
654 standend();
657 clrtoeol();
658 refresh();
659 if ((ret != 0 && !(flags & RUN_ERROR_OK)) ||
660 (y + x != 0 && !(flags & RUN_PROGRESS))) {
661 if (actionwin != stdscr)
662 move(getbegy(actionwin) - 2, 5);
663 else if (x != 0)
664 addstr("\n");
665 addstr(msg_string(MSG_Hit_enter_to_continue));
666 refresh();
667 getchar();
668 } else {
669 if (y + x != 0) {
670 /* give user 1 second to see messages */
671 refresh();
672 sleep(1);
677 /* restore tty setting we saved earlier */
678 reset_prog_mode();
680 /* clean things up */
681 if (actionwin != NULL) {
682 if (actionwin != stdscr)
683 delwin(actionwin);
684 if (err == 0 || !(flags & RUN_NO_CLEAR)) {
685 wclear(stdscr);
686 touchwin(stdscr);
687 clearok(stdscr, 1);
688 refresh();
692 free(scmd);
693 free_argv(args);
695 if (ret != 0 && flags & RUN_FATAL)
696 exit(ret);
697 return ret;