/* cmdline.c - core analysis suite * * Copyright (C) 1999, 2000, 2001, 2002 Mission Critical Linux, Inc. * Copyright (C) 2002-2015,2019 David Anderson * Copyright (C) 2002-2015,2019 Red Hat, Inc. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include "defs.h" static void restore_sanity(void); static void restore_ifile_sanity(void); static int pseudo_command(char *); static void check_special_handling(char *); static int is_executable_in_PATH(char *); static int is_shell_script(char *); static void list_aliases(char *); static int allocate_alias(int); static int alias_exists(char *); static void resolve_aliases(void); static int setup_redirect(int); int multiple_pipes(char **); static int output_command_to_pids(void); static void set_my_tty(void); static char *signame(int); static int setup_stdpipe(void); static void wait_for_children(ulong); #define ZOMBIES_ONLY (1) #define ALL_CHILDREN (2) int shell_command(char *); static void modify_orig_line(char *, struct args_input_file *); static void modify_expression_arg(char *, char **, struct args_input_file *); static int verify_args_input_file(char *); static char *crash_readline_completion_generator(const char *, int); static char **crash_readline_completer(const char *, int, int); #define READLINE_LIBRARY #include #include #include static void readline_init(void); static struct alias_data alias_head = { 0 }; void process_command_line(void) { /* * Restore normal environment, clearing out any excess baggage * piled up by the previous command. */ restore_sanity(); fp = stdout; BZERO(pc->command_line, BUFSIZE); if (!(pc->flags & (READLINE|SILENT|CMDLINE_IFILE|RCHOME_IFILE|RCLOCAL_IFILE))) fprintf(fp, "%s", pc->prompt); fflush(fp); /* * Input can come from five possible sources: * * 1. an .rc file located in the user's HOME directory. * 2. an .rc file located in the current directory. * 3. an input file that was designated by the -i flag at * program invocation. * 4. from a terminal. * 5. from a pipe, if stdin is a pipe rather than a terminal. * * But first, handle the interruption of an input file caused * by a FATAL error in one of its commands. * */ if (pc->ifile_in_progress) { switch (pc->ifile_in_progress) { case RCHOME_IFILE: pc->flags |= INIT_IFILE|RCHOME_IFILE; sprintf(pc->command_line, "< %s/.%src", pc->home, pc->program_name); break; case RCLOCAL_IFILE: sprintf(pc->command_line, "< .%src", pc->program_name); pc->flags |= INIT_IFILE|RCLOCAL_IFILE; break; case CMDLINE_IFILE: sprintf(pc->command_line, "< %s", pc->input_file); pc->flags |= INIT_IFILE|CMDLINE_IFILE; break; case RUNTIME_IFILE: sprintf(pc->command_line, "%s", pc->runtime_ifile_cmd); pc->flags |= IFILE_ERROR; break; default: error(FATAL, "invalid input file\n"); } } else if (pc->flags & RCHOME_IFILE) { sprintf(pc->command_line, "< %s/.%src", pc->home, pc->program_name); pc->flags |= INIT_IFILE; } else if (pc->flags & RCLOCAL_IFILE) { sprintf(pc->command_line, "< .%src", pc->program_name); pc->flags |= INIT_IFILE; } else if (pc->flags & CMDLINE_IFILE) { sprintf(pc->command_line, "< %s", pc->input_file); pc->flags |= INIT_IFILE; } else if (pc->flags & TTY) { if (!(pc->readline = readline(pc->prompt))) { args[0] = NULL; fprintf(fp, "\n"); return; } if (strlen(pc->readline) >= BUFSIZE) error(FATAL, "input line exceeds maximum of 1500 bytes\n"); else strcpy(pc->command_line, pc->readline); free(pc->readline); clean_line(pc->command_line); pseudo_command(pc->command_line); strcpy(pc->orig_line, pc->command_line); if (strlen(pc->command_line) && !iscntrl(pc->command_line[0])) add_history(pc->command_line); check_special_handling(pc->command_line); } else { if (fgets(pc->command_line, BUFSIZE-1, stdin) == NULL) clean_exit(1); clean_line(pc->command_line); strcpy(pc->orig_line, pc->command_line); } /* * First clean out all linefeeds and leading/trailing spaces. * Then substitute aliases for the real thing they represent. */ clean_line(pc->command_line); resolve_aliases(); /* * Setup output redirection based upon the command line itself or * based upon the default scrolling behavior, if any. */ switch (setup_redirect(FROM_COMMAND_LINE)) { case REDIRECT_NOT_DONE: case REDIRECT_TO_STDPIPE: case REDIRECT_TO_PIPE: case REDIRECT_TO_FILE: break; case REDIRECT_SHELL_ESCAPE: case REDIRECT_SHELL_COMMAND: case REDIRECT_FAILURE: RESTART(); break; } /* * Setup the global argcnt and args[] array for use by everybody * during the life of this command. */ argcnt = parse_line(pc->command_line, args); } /* * Allow input file redirection without having to put a space between * the < and the filename. Allow the "pointer-to" asterisk to "touch" * the structure/union name. */ static void check_special_handling(char *s) { char local[BUFSIZE]; strcpy(local, s); if ((local[0] == '*') && (!whitespace(local[1]))) { sprintf(s, "* %s", &local[1]); return; } if ((local[0] == '<') && (!whitespace(local[1]))) { sprintf(s, "< %s", &local[1]); return; } } static int is_executable_in_PATH(char *filename) { char *buf1, *buf2; char *tok, *path; int retval; if ((path = getenv("PATH"))) { buf1 = GETBUF(strlen(path)+1); buf2 = GETBUF(strlen(path)+1); strcpy(buf2, path); } else return FALSE; retval = FALSE; tok = strtok(buf2, ":"); while (tok) { sprintf(buf1, "%s/%s", tok, filename); if (file_exists(buf1, NULL) && (access(buf1, X_OK) == 0)) { retval = TRUE; break; } tok = strtok(NULL, ":"); } FREEBUF(buf1); FREEBUF(buf2); return retval; } /* * At this point the only pseudo commands are the "r" (repeat) and * the "h" (history) command: * * 1. an "r" alone, or "!!" along, just means repeat the last command. * 2. an "r" followed by a number, means repeat that command from the * history table. * 3. an "!" followed by a number that is not the name of a command * in the user's PATH, means repeat that command from the history table. * 4. an "r" followed by one or more non-decimal characters means to * seek back until a line-beginning match is found. * 5. an "h" alone, or a string beginning with "hi", means history. */ static int pseudo_command(char *input) { int i; HIST_ENTRY *entry; int idx, found; char *p; clean_line(input); /* * Just dump all commands that have been entered to date. */ if (STREQ(input, "h") || STRNEQ(input, "hi")) { dump_history(); pc->command_line[0] = NULLCHAR; return TRUE; } if (STREQ(input, "r") || STREQ(input, "!!")) { if (!history_offset) error(FATAL, "no commands entered!\n"); entry = history_get(history_offset); strcpy(input, entry->line); fprintf(fp, "%s%s\n", pc->prompt, input); return TRUE; } if ((input[0] == 'r') && decimal(&input[1], 0)) { if (!history_offset) error(FATAL, "no commands entered!\n"); p = &input[1]; goto rerun; } if ((input[0] == '!') && decimal(&input[1], 0) && !is_executable_in_PATH(first_nonspace(&input[1]))) { p = first_nonspace(&input[1]); goto rerun; } if (STRNEQ(input, "r ")) { if (!history_offset) error(FATAL, "no commands entered!\n"); p = first_nonspace(&input[1]); rerun: if (decimal(p, 0)) { idx = atoi(p); if (idx == 0) goto invalid_repeat_request; if (idx > history_offset) error(FATAL, "command %d not entered yet!\n", idx); entry = history_get(idx); strcpy(input, entry->line); fprintf(fp, "%s%s\n", pc->prompt, input); return TRUE; } idx = -1; found = FALSE; for (i = history_offset; i > 0; i--) { entry = history_get(i); if (STRNEQ(entry->line, p)) { found = TRUE; break; } } if (found) { strcpy(input, entry->line); fprintf(fp, "%s%s\n", pc->prompt, input); return TRUE; } invalid_repeat_request: fprintf(fp, "invalid repeat request: %s\n", input); strcpy(input, ""); return TRUE; } return FALSE; } /* * Dump the history table in first-to-last chronological order. */ void dump_history(void) { int i; HIST_ENTRY **the_history; HIST_ENTRY *entry; if (!history_offset) error(FATAL, "no commands entered!\n"); the_history = history_list(); for (i = 0; i < history_offset; i++) { entry = the_history[i]; fprintf(fp, "[%d] %s\n", i+1, entry->line); } } /* * Pager arguments. */ static char *less_argv[5] = { "/usr/bin/less", "-E", "-X", "-Ps -- MORE -- forward\\: , or j backward\\: b or k quit\\: q", NULL }; static char *more_argv[2] = { "/bin/more", NULL }; static char **CRASHPAGER_argv = NULL; int CRASHPAGER_valid(void) { int i, c; char *env, *CRASHPAGER_buf; char *arglist[MAXARGS]; if (CRASHPAGER_argv) return TRUE; if (!(env = getenv("CRASHPAGER"))) return FALSE; if (strstr(env, "|") || strstr(env, "<") || strstr(env, ">")) { error(INFO, "CRASHPAGER ignored: contains invalid character: \"%s\"\n", env); return FALSE; } if ((CRASHPAGER_buf = (char *)malloc(strlen(env)+1)) == NULL) return FALSE; strcpy(CRASHPAGER_buf, env); if (!(c = parse_line(CRASHPAGER_buf, arglist)) || !file_exists(arglist[0], NULL) || access(arglist[0], X_OK) || !(CRASHPAGER_argv = (char **)malloc(sizeof(char *) * (c+1)))) { free(CRASHPAGER_buf); if (strlen(env)) error(INFO, "CRASHPAGER ignored: \"%s\"\n", env); return FALSE; } for (i = 0; i < c; i++) CRASHPAGER_argv[i] = arglist[i]; CRASHPAGER_argv[i] = NULL; return TRUE; } /* * Set up a command string buffer for error/help output. */ char * setup_scroll_command(void) { char *buf; long i, len; if (!(pc->flags & SCROLL)) return NULL; switch (pc->scroll_command) { case SCROLL_LESS: buf = GETBUF(strlen(less_argv[0])+1); strcpy(buf, less_argv[0]); break; case SCROLL_MORE: buf = GETBUF(strlen(more_argv[0])+1); strcpy(buf, more_argv[0]); break; case SCROLL_CRASHPAGER: for (i = len = 0; CRASHPAGER_argv[i]; i++) len += strlen(CRASHPAGER_argv[i])+1; buf = GETBUF(len); for (i = 0; CRASHPAGER_argv[i]; i++) { sprintf(&buf[strlen(buf)], "%s%s", i ? " " : "", CRASHPAGER_argv[i]); } break; default: return NULL; } return buf; } /* * Parse the command line for pipe or redirect characters: * * 1. if a "|" character is found, popen() what comes after it, and * modify the contents of the global "fp" FILE pointer. * 2. if one or two ">" characters are found, fopen() the filename that * follows, and modify the contents of the global "fp" FILE pointer. * * Care is taken to segregate: * * 1. expressions encompassed by parentheses, or * 2. strings encompassed by single or double quotation marks * * When either of the above are in affect, no redirection is done. * * Lastly, if no redirection is requested by the user on the command line, * output is passed to the default scrolling command, which is popen()'d * and again, the contents of the global "fp" FILE pointer is modified. * This default behavior is not performed if the command is coming from * an input file, nor if scrolling has been turned off. */ static int setup_redirect(int origin) { char *p, which; int append; int expression; int string; int ret ATTRIBUTE_UNUSED; FILE *pipe; FILE *ofile; pc->redirect = origin; pc->eoc_index = 0; p = pc->command_line; if (STREQ(p, "|") || STREQ(p, "!")) { ret = system("/bin/sh"); pc->redirect |= REDIRECT_SHELL_ESCAPE; return REDIRECT_SHELL_ESCAPE; } if (FIRSTCHAR(p) == '|' || FIRSTCHAR(p) == '!') pc->redirect |= REDIRECT_SHELL_COMMAND; expression = 0; string = FALSE; while (*p) { if (*p == '(') expression++; if (*p == ')') expression--; if ((*p == '"') || (*p == '\'')) string = !string; if (!(expression || string) && ((*p == '|') || (*p == '!'))) { which = *p; *p = NULLCHAR; pc->eoc_index = p - pc->command_line; p++; p = strip_beginning_whitespace(p); if (!strlen(p)) { error(INFO, "no shell command after '%c'\n", which); pc->redirect |= REDIRECT_FAILURE; return REDIRECT_FAILURE; } if (LASTCHAR(p) == '|') error(FATAL_RESTART, "pipe to nowhere?\n"); if (pc->redirect & REDIRECT_SHELL_COMMAND) return shell_command(p); if ((pipe = popen(p, "w")) == NULL) { error(INFO, "cannot open pipe\n"); pc->redirect |= REDIRECT_FAILURE; return REDIRECT_FAILURE; } setbuf(pipe, NULL); switch (origin) { case FROM_COMMAND_LINE: fp = pc->pipe = pipe; break; case FROM_INPUT_FILE: fp = pc->ifile_pipe = pipe; break; } if (multiple_pipes(&p)) pc->redirect |= REDIRECT_MULTI_PIPE; strcpy(pc->pipe_command, p); null_first_space(pc->pipe_command); pc->redirect |= REDIRECT_TO_PIPE; if (!(pc->redirect & REDIRECT_SHELL_COMMAND)) { if ((pc->pipe_pid = output_command_to_pids())) pc->redirect |= REDIRECT_PID_KNOWN; else error(FATAL_RESTART, "pipe operation failed\n"); } return REDIRECT_TO_PIPE; } if (!(expression || string) && (*p == '>') && !((p > pc->command_line) && (*(p-1) == '-'))) { append = FALSE; *p = NULLCHAR; pc->eoc_index = p - pc->command_line; if (*(p+1) == '>') { append = TRUE; *p = NULLCHAR; p++; } p++; p = strip_beginning_whitespace(p); if (!strlen(p)) { error(INFO, "no file name after %s\n", append ? ">>" : ">"); pc->redirect |= REDIRECT_FAILURE; return REDIRECT_FAILURE; } if (pc->flags & IFILE_ERROR) append = TRUE; if ((ofile = fopen(p, append ? "a+" : "w+")) == NULL) { error(INFO, "unable to open %s\n", p); pc->redirect = REDIRECT_FAILURE; return REDIRECT_FAILURE; } setbuf(ofile, NULL); switch (origin) { case FROM_COMMAND_LINE: fp = pc->ofile = ofile; break; case FROM_INPUT_FILE: fp = pc->ifile_ofile = ofile; break; } pc->redirect |= REDIRECT_TO_FILE; return REDIRECT_TO_FILE; } p++; } if ((origin == FROM_COMMAND_LINE) && (pc->flags & TTY) && (pc->flags & SCROLL) && pc->scroll_command) { if (!strlen(pc->command_line) || STREQ(pc->command_line, "q") || STREQ(pc->command_line, "Q") || STREQ(pc->command_line, "exit") || STRNEQ(pc->command_line, "<")) { pc->redirect |= REDIRECT_NOT_DONE; return REDIRECT_NOT_DONE; } if (!setup_stdpipe()) { error(INFO, "cannot open pipe\n"); pc->redirect |= REDIRECT_FAILURE; return REDIRECT_FAILURE; } fp = pc->stdpipe; pc->redirect |= REDIRECT_TO_STDPIPE; switch (pc->scroll_command) { case SCROLL_LESS: strcpy(pc->pipe_command, less_argv[0]); break; case SCROLL_MORE: strcpy(pc->pipe_command, more_argv[0]); break; case SCROLL_CRASHPAGER: strcpy(pc->pipe_command, CRASHPAGER_argv[0]); break; } return REDIRECT_TO_STDPIPE; } pc->redirect |= REDIRECT_NOT_DONE; return REDIRECT_NOT_DONE; } /* * Find the last command in an input line that possibly contains * multiple pipes. */ int multiple_pipes(char **input) { char *p, *found; int quote; found = NULL; quote = FALSE; for (p = *input; *p; p++) { if ((*p == '\'') || (*p == '"')) { quote = !quote; continue; } else if (quote) continue; if (*p == '|') { if (STRNEQ(p, "||")) break; found = first_nonspace(p+1); } } if (found) { *input = found; return TRUE; } else return FALSE; } void debug_redirect(char *s) { int others; int alive; others = 0; console("%s: (", s); if (pc->redirect & FROM_COMMAND_LINE) console("%sFROM_COMMAND_LINE", others++ ? "|" : ""); if (pc->redirect & FROM_INPUT_FILE) console("%sFROM_INPUT_FILE", others++ ? "|" : ""); if (pc->redirect & REDIRECT_NOT_DONE) console("%sREDIRECT_NOT_DONE", others++ ? "|" : ""); if (pc->redirect & REDIRECT_TO_PIPE) console("%sREDIRECT_TO_PIPE", others++ ? "|" : ""); if (pc->redirect & REDIRECT_TO_STDPIPE) console("%sREDIRECT_TO_STDPIPE", others++ ? "|" : ""); if (pc->redirect & REDIRECT_TO_FILE) console("%sREDIRECT_TO_FILE", others++ ? "|" : ""); if (pc->redirect & REDIRECT_FAILURE) console("%sREDIRECT_FAILURE", others++ ? "|" : ""); if (pc->redirect & REDIRECT_SHELL_ESCAPE) console("%sREDIRECT_SHELL_ESCAPE", others++ ? "|" : ""); if (pc->redirect & REDIRECT_SHELL_COMMAND) console("%sREDIRECT_SHELL_COMMAND", others++ ? "|" : ""); if (pc->redirect & REDIRECT_PID_KNOWN) console("%sREDIRECT_PID_KNOWN", others++ ? "|" : ""); if (pc->redirect & REDIRECT_MULTI_PIPE) console("%sREDIRECT_MULTI_PIPE", others++ ? "|" : ""); console(")\n"); if (pc->pipe_pid || strlen(pc->pipe_command)) { if (pc->pipe_pid && PID_ALIVE(pc->pipe_pid)) alive = TRUE; else alive = FALSE; console("pipe_pid: %d (%s) pipe_command: %s\n", pc->pipe_pid, alive ? "alive" : "dead", pc->pipe_command); } } /* * Determine whether the pid receiving the current piped output is still * alive. * * NOTE: This routine returns TRUE by default, and only returns FALSE if * the pipe_pid exists *and* it's known to have died. Therefore the * caller must be cognizant of pc->pipe_pid or pc->stdpipe_pid. */ int output_open(void) { int waitstatus, waitret; if (!(pc->flags & TTY)) return TRUE; switch (pc->redirect & PIPE_OPTIONS) { case (REDIRECT_TO_STDPIPE|FROM_COMMAND_LINE): waitret = waitpid(pc->stdpipe_pid, &waitstatus, WNOHANG); if ((waitret == pc->stdpipe_pid) || (waitret == -1)) return FALSE; break; case (REDIRECT_TO_PIPE|FROM_INPUT_FILE): if (pc->curcmd_flags & REPEAT) break; /* FALLTHROUGH */ case (REDIRECT_TO_PIPE|FROM_COMMAND_LINE): switch (pc->redirect & (REDIRECT_MULTI_PIPE)) { case REDIRECT_MULTI_PIPE: if (!PID_ALIVE(pc->pipe_pid)) return FALSE; break; default: waitret = waitpid(pc->pipe_pid, &waitstatus, WNOHANG); if (waitret == pc->pipe_pid) return FALSE; if (waitret == -1) { /* intervening sh */ if (!PID_ALIVE(pc->pipe_pid)) return FALSE; } break; } break; default: break; } return TRUE; } /* * Determine the pids of the current popen'd shell and output command. * This is all done using /proc; the ps kludge at the bottom of this * routine is legacy, and should only get executed if /proc doesn't exist. */ static int output_command_to_pids(void) { DIR *dirp; struct dirent *dp; FILE *stp; char buf1[BUFSIZE]; char buf2[BUFSIZE]; char lookfor[BUFSIZE+2]; char *pid, *name, *status, *p_pid, *pgrp, *comm; char *arglist[MAXARGS]; int argc; FILE *pipe; int retries, shell_has_exited; retries = 0; shell_has_exited = FALSE; pc->pipe_pid = pc->pipe_shell_pid = 0; comm = strrchr(pc->pipe_command, '/'); sprintf(lookfor, "(%s)", comm ? ++comm : pc->pipe_command); stall(1000); retry: if (is_directory("/proc") && (dirp = opendir("/proc"))) { for (dp = readdir(dirp); dp && !pc->pipe_pid; dp = readdir(dirp)) { if (!decimal(dp->d_name, 0)) continue; sprintf(buf1, "/proc/%s/stat", dp->d_name); if (file_exists(buf1, NULL) && (stp = fopen(buf1, "r"))) { if (fgets(buf2, BUFSIZE, stp)) { pid = strtok(buf2, " "); name = strtok(NULL, " "); status = strtok(NULL, " "); p_pid = strtok(NULL, " "); pgrp = strtok(NULL, " "); if (STREQ(name, "(sh)") && (atoi(p_pid) == getpid())) { pc->pipe_shell_pid = atoi(pid); if (STREQ(status, "Z")) shell_has_exited = TRUE; } if (STREQ(name, lookfor) && ((atoi(p_pid) == getpid()) || (atoi(p_pid) == pc->pipe_shell_pid) || (atoi(pgrp) == getpid()))) { pc->pipe_pid = atoi(pid); console( "FOUND[%d] (%d->%d->%d) %s %s p_pid: %s pgrp: %s\n", retries, getpid(), pc->pipe_shell_pid, pc->pipe_pid, name, status, p_pid, pgrp); } } fclose(stp); } } closedir(dirp); } if (!pc->pipe_pid && !shell_has_exited && ((retries++ < 10) || pc->pipe_shell_pid)) { stall(1000); goto retry; } console("getpid: %d pipe_shell_pid: %d pipe_pid: %d\n", getpid(), pc->pipe_shell_pid, pc->pipe_pid); if (pc->pipe_pid) return pc->pipe_pid; sprintf(buf1, "ps -ft %s", pc->my_tty); console("%s: ", buf1); if ((pipe = popen(buf1, "r")) == NULL) { error(INFO, "cannot determine output pid\n"); return 0; } while (fgets(buf1, BUFSIZE, pipe)) { argc = parse_line(buf1, arglist); if ((argc >= 8) && STREQ(arglist[7], pc->pipe_command) && STRNEQ(pc->my_tty, arglist[5])) { pc->pipe_pid = atoi(arglist[1]); break; } } pclose(pipe); console("%d\n", pc->pipe_pid); return pc->pipe_pid; } /* * Close straggling, piped-to, output commands. */ void close_output(void) { if ((pc->flags & TTY) && (pc->pipe_pid || strlen(pc->pipe_command)) && output_open()) kill(pc->pipe_pid, 9); } /* * Initialize what's needed for the command line: * * 1. termios structures for raw and cooked terminal mode. * 2. set up SIGINT and SIGPIPE handlers for aborted commands. * 3. set up the command history table. * 4. create the prompt string. */ void cmdline_init(void) { int fd = 0; /* * Stash a copy of the original termios setup. * Build a raw version for quick use for each command entry. */ if (isatty(fileno(stdin)) && ((fd = open("/dev/tty", O_RDONLY)) >= 0)) { if (tcgetattr(fd, &pc->termios_orig) == -1) error(FATAL, "tcgetattr /dev/tty: %s\n", strerror(errno)); if (tcgetattr(fd, &pc->termios_raw) == -1) error(FATAL, "tcgetattr /dev/tty: %s\n", strerror(errno)); close(fd); pc->termios_raw.c_lflag &= ~ECHO & ~ICANON; pc->termios_raw.c_cc[VMIN] = (char)1; pc->termios_raw.c_cc[VTIME] = (char)0; restore_sanity(); pc->flags |= TTY; set_my_tty(); SIGACTION(SIGINT, restart, &pc->sigaction, NULL); readline_init(); } else { if (fd < 0) error(INFO, "/dev/tty: %s\n", strerror(errno)); if (!(pc->flags & SILENT)) fprintf(fp, "NOTE: stdin: not a tty\n\n"); fflush(fp); pc->flags &= ~TTY; } SIGACTION(SIGPIPE, SIG_IGN, &pc->sigaction, NULL); set_command_prompt(NULL); } /* * Create and stash the original prompt, but allow changes during runtime. */ void set_command_prompt(char *new_prompt) { static char *orig_prompt = NULL; if (!orig_prompt) { if (!(orig_prompt = (char *)malloc(strlen(pc->program_name)+3))) error(FATAL, "cannot malloc prompt string\n"); sprintf(orig_prompt, "%s> ", pc->program_name); } if (new_prompt) pc->prompt = new_prompt; else pc->prompt = orig_prompt; } /* * SIGINT, SIGPIPE, and SIGSEGV handler. * Signal number 0 is sent for a generic restart. */ #define MAX_RECURSIVE_SIGNALS (10) #define MAX_SIGINTS_ACCEPTED (1) void restart(int sig) { static int in_restart = 0; console("restart (%s) %s\n", signame(sig), pc->flags & IN_GDB ? "(in gdb)" : "(in crash)"); if (sig == SIGUSR2) clean_exit(1); if (pc->flags & IN_RESTART) { fprintf(stderr, "\nembedded signal received (%s): recursive restart call\n", signame(sig)); if (++in_restart < MAX_RECURSIVE_SIGNALS) return; fprintf(stderr, "bailing out...\n"); clean_exit(1); } else { pc->flags |= IN_RESTART; in_restart = 0; } switch (sig) { case SIGSEGV: fflush(fp); fprintf(stderr, " \n", pc->flags & IN_GDB ? " in gdb" : ""); case 0: case SIGPIPE: restore_sanity(); break; case SIGINT: SIGACTION(SIGINT, restart, &pc->sigaction, NULL); pc->flags |= _SIGINT_; pc->sigint_cnt++; pc->flags &= ~IN_RESTART; if (pc->sigint_cnt == MAX_SIGINTS_ACCEPTED) { restore_sanity(); if (pc->ifile_in_progress) { pc->ifile_in_progress = 0; pc->ifile_offset = 0; } break; } return; default: fprintf(stderr, "unexpected signal received: %s\n", signame(sig)); restore_sanity(); close_output(); break; } fprintf(stderr, "\n"); pc->flags &= ~(IN_FOREACH|IN_GDB|IN_RESTART); longjmp(pc->main_loop_env, 1); } /* * Return a signal name string, or a number if the signal is not listed. */ static char * signame(int sig) { static char sigbuf[20]; switch (sig) { case SIGINT: sprintf(sigbuf, "SIGINT-%d", pc->sigint_cnt+1); return sigbuf; case SIGPIPE: return "SIGPIPE"; case SIGSEGV: return "SIGSEGV"; default: sprintf(sigbuf, "%d", sig); return sigbuf; } } /* * Restore the program environment to the state it was in before the * last command was executed: * * 1. close all temporarily opened pipes and output files. * 2. set the terminal back to normal cooked mode. * 3. free all temporary buffers. * 4. restore the last known output radix. */ static void restore_sanity(void) { int fd, waitstatus; struct extension_table *ext; struct command_table_entry *cp; if (pc->stdpipe) { close(fileno(pc->stdpipe)); pc->stdpipe = NULL; if (pc->stdpipe_pid && PID_ALIVE(pc->stdpipe_pid)) { while (!waitpid(pc->stdpipe_pid, &waitstatus, WNOHANG)) stall(1000); } pc->stdpipe_pid = 0; } if (pc->pipe) { close(fileno(pc->pipe)); pc->pipe = NULL; console("wait for redirect %d->%d to finish...\n", pc->pipe_shell_pid, pc->pipe_pid); if (pc->pipe_pid) while (PID_ALIVE(pc->pipe_pid)) { waitpid(pc->pipe_pid, &waitstatus, WNOHANG); stall(1000); } if (pc->pipe_shell_pid) while (PID_ALIVE(pc->pipe_shell_pid)) { waitpid(pc->pipe_shell_pid, &waitstatus, WNOHANG); stall(1000); } pc->pipe_pid = 0; } if (pc->ifile_pipe) { fflush(pc->ifile_pipe); close(fileno(pc->ifile_pipe)); pc->ifile_pipe = NULL; if (pc->pipe_pid && ((pc->redirect & (PIPE_OPTIONS|REDIRECT_PID_KNOWN)) == (FROM_INPUT_FILE|REDIRECT_TO_PIPE|REDIRECT_PID_KNOWN))) { console("wait for redirect %d->%d to finish...\n", pc->pipe_shell_pid, pc->pipe_pid); while (PID_ALIVE(pc->pipe_pid)) { waitpid(pc->pipe_pid, &waitstatus, WNOHANG); stall(1000); } if (pc->pipe_shell_pid) while (PID_ALIVE(pc->pipe_shell_pid)) { waitpid(pc->pipe_shell_pid, &waitstatus, WNOHANG); stall(1000); } if (pc->redirect & (REDIRECT_MULTI_PIPE)) wait_for_children(ALL_CHILDREN); } } if (pc->ofile) { fclose(pc->ofile); pc->ofile = NULL; } if (pc->ifile_ofile) { fclose(pc->ifile_ofile); pc->ifile_ofile = NULL; } if (pc->ifile) { fclose(pc->ifile); pc->ifile = NULL; } if (pc->args_ifile) { fclose(pc->args_ifile); pc->args_ifile = NULL; } if (pc->tmpfile) close_tmpfile(); if (pc->tmpfile2) close_tmpfile2(); if (pc->cmd_cleanup) pc->cmd_cleanup(pc->cmd_cleanup_arg); if (pc->flags & TTY) { if ((fd = open("/dev/tty", O_RDONLY)) < 0) { console("/dev/tty: %s\n", strerror(errno)); clean_exit(1); } if (tcsetattr(fd, TCSANOW, &pc->termios_orig) == -1) error(FATAL, "tcsetattr /dev/tty: %s\n", strerror(errno)); close(fd); } wait_for_children(ZOMBIES_ONLY); pc->flags &= ~(INIT_IFILE|RUNTIME_IFILE|IFILE_ERROR|_SIGINT_|PLEASE_WAIT); pc->sigint_cnt = 0; pc->redirect = 0; pc->pipe_command[0] = NULLCHAR; pc->pipe_pid = 0; pc->pipe_shell_pid = 0; pc->sbrk = sbrk(0); if ((pc->curcmd_flags & (UD2A_INSTRUCTION|BAD_INSTRUCTION)) == (UD2A_INSTRUCTION|BAD_INSTRUCTION)) error(WARNING, "A (bad) instruction was noted in last disassembly.\n" " Use \"dis -b [number]\" to set/restore the number of\n" " encoded bytes to skip after a ud2a (BUG) instruction.\n"); pc->curcmd_flags = 0; pc->curcmd_private = 0; restore_gdb_sanity(); free_all_bufs(); /* * Clear the structure cache references -- no-ops if DUMPFILE(). */ clear_task_cache(); clear_machdep_cache(); clear_swap_info_cache(); clear_file_cache(); clear_dentry_cache(); clear_inode_cache(); clear_vma_cache(); clear_active_set(); if (kt->ikconfig_flags & IKCONFIG_LOADED) read_in_kernel_config(IKCFG_FREE); /* * Call the cleanup() function of any extension. */ for (ext = extension_table; ext; ext = ext->next) { for (cp = ext->command_table; cp->name; cp++) { if (cp->flags & CLEANUP) (*cp->func)(); } } if (CRASHDEBUG(5)) { dump_filesys_table(0); dump_vma_cache(0); dump_text_value_cache(0); } if (REMOTE()) remote_clear_pipeline(); hq_close(); } /* * Similar to above, but only called in between each command that is * read from an input file. */ static void restore_ifile_sanity(void) { int fd; pc->flags &= ~IFILE_ERROR; if (pc->ifile_pipe) { close(fileno(pc->ifile_pipe)); pc->ifile_pipe = NULL; } if (pc->ifile_ofile) { fclose(pc->ifile_ofile); pc->ifile_ofile = NULL; } if (pc->flags & TTY) { if ((fd = open("/dev/tty", O_RDONLY)) < 0) { console("/dev/tty: %s\n", strerror(errno)); clean_exit(1); } if (tcsetattr(fd, TCSANOW, &pc->termios_orig) == -1) error(FATAL, "tcsetattr /dev/tty: %s\n", strerror(errno)); close(fd); } if (pc->tmpfile2) { close_tmpfile2(); } restore_gdb_sanity(); free_all_bufs(); hq_close(); } /* * Check whether a SIGINT was received during the execution of a command, * clearing the flag if it was set. This allows individual commands or * entities to do whatever is appropriate to handle CTRL-C. */ int received_SIGINT(void) { if (pc->flags & _SIGINT_) { pc->flags &= ~_SIGINT_; pc->sigint_cnt = 0; if (pc->ifile_in_progress) { pc->ifile_in_progress = 0; pc->ifile_offset = 0; } return TRUE; } else return FALSE; } /* * Look for an executable file that begins with #! */ static int is_shell_script(char *s) { int fd; char interp[2]; struct stat sbuf; if ((fd = open(s, O_RDONLY)) < 0) return FALSE; if (isatty(fd)) { close(fd); return FALSE; } if (read(fd, interp, 2) != 2) { close(fd); return FALSE; } if (!STRNEQ(interp, "#!")) { close(fd); return FALSE; } close(fd); if (stat(s, &sbuf) == -1) return FALSE; if (!(sbuf.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH))) return FALSE; return TRUE; } /* * After verifying the user's input file, loop through each line, executing * one command at a time. This command pretty much does the same as * get_command_line(), but also kicks off the command execution as well. * It's kept self-contained, as indicated by the RUNTIME_IFILE flag, and * keeps its own internal sanity by calling restore_ifile_sanity() between * each line. */ void exec_input_file(void) { char *file; FILE *incoming_fp; char buf[BUFSIZE]; ulong this; /* * Do start-up .rc or input files in the proper order. */ if (pc->flags & RCHOME_IFILE) { this = RCHOME_IFILE; pc->flags &= ~RCHOME_IFILE; } else if (pc->flags & RCLOCAL_IFILE) { this = RCLOCAL_IFILE; pc->flags &= ~RCLOCAL_IFILE; } else if (pc->flags & CMDLINE_IFILE) { this = CMDLINE_IFILE; pc->flags &= ~CMDLINE_IFILE; } else this = 0; if (pc->flags & RUNTIME_IFILE) { error(INFO, "embedded input files not allowed!\n"); return; } if (argcnt < 2) { error(INFO, "no input file entered!\n"); return; } else file = args[1]; if (!file_exists(file, NULL)) { error(INFO, "%s: %s\n", file, strerror(ENOENT)); return; } if (is_elf_file(file)) { error(INFO, "input from executable files not supported yet!\n"); return; } if (is_shell_script(file)) { error(INFO, "input from shell scripts not supported yet!\n"); return; } if ((pc->ifile = fopen(file, "r")) == NULL) { error(INFO, "%s: %s\n", file, strerror(errno)); return; } pc->flags |= RUNTIME_IFILE; incoming_fp = fp; /* * Handle runtime commands that use input files. */ if ((pc->ifile_in_progress = this) == 0) { if (!pc->runtime_ifile_cmd) { if (!(pc->runtime_ifile_cmd = (char *)malloc(BUFSIZE))) { error(INFO, "cannot malloc input file command line buffer\n"); return; } BZERO(pc->runtime_ifile_cmd, BUFSIZE); } if (!strlen(pc->runtime_ifile_cmd)) strcpy(pc->runtime_ifile_cmd, pc->orig_line); pc->ifile_in_progress = RUNTIME_IFILE; } /* * If there's an offset, then there was a FATAL error caused * by the last command executed from the input file. */ if (pc->ifile_offset) fseek(pc->ifile, (long)pc->ifile_offset, SEEK_SET); while (fgets(buf, BUFSIZE-1, pc->ifile)) { /* * Restore normal environment. */ fp = incoming_fp; restore_ifile_sanity(); BZERO(pc->command_line, BUFSIZE); BZERO(pc->orig_line, BUFSIZE); if (this & (RCHOME_IFILE|RCLOCAL_IFILE)) pc->curcmd_flags |= FROM_RCFILE; pc->ifile_offset = ftell(pc->ifile); if (STRNEQ(buf, "#") || STREQ(buf, "\n")) continue; check_special_handling(buf); strcpy(pc->command_line, buf); clean_line(pc->command_line); strcpy(pc->orig_line, pc->command_line); strip_linefeeds(pc->orig_line); resolve_aliases(); switch (setup_redirect(FROM_INPUT_FILE)) { case REDIRECT_NOT_DONE: case REDIRECT_TO_PIPE: case REDIRECT_TO_FILE: break; case REDIRECT_SHELL_ESCAPE: case REDIRECT_SHELL_COMMAND: continue; case REDIRECT_FAILURE: goto done_input; } if (CRASHDEBUG(1)) console(buf); if (!(argcnt = parse_line(pc->command_line, args))) continue; if (!(pc->flags & SILENT)) { fprintf(fp, "%s%s", pc->prompt, buf); fflush(fp); } exec_command(); if (received_SIGINT()) goto done_input; } done_input: fclose(pc->ifile); pc->ifile = NULL; pc->flags &= ~RUNTIME_IFILE; pc->ifile_offset = 0; if (pc->runtime_ifile_cmd) BZERO(pc->runtime_ifile_cmd, BUFSIZE); pc->ifile_in_progress = 0; } /* * Prime the alias list with a few built-in's. */ void alias_init(char *inbuf) { char buf[BUFSIZE]; if (inbuf) { strcpy(buf, inbuf); argcnt = parse_line(buf, args); allocate_alias(ALIAS_BUILTIN); return; } strcpy(buf, "alias man help"); argcnt = parse_line(buf, args); allocate_alias(ALIAS_BUILTIN); strcpy(buf, "alias ? help"); argcnt = parse_line(buf, args); allocate_alias(ALIAS_BUILTIN); strcpy(buf, "alias quit q"); argcnt = parse_line(buf, args); allocate_alias(ALIAS_BUILTIN); strcpy(buf, "alias sf set scroll off"); argcnt = parse_line(buf, args); allocate_alias(ALIAS_BUILTIN); strcpy(buf, "alias sn set scroll on"); argcnt = parse_line(buf, args); allocate_alias(ALIAS_BUILTIN); strcpy(buf, "alias hex set radix 16"); argcnt = parse_line(buf, args); allocate_alias(ALIAS_BUILTIN); strcpy(buf, "alias dec set radix 10"); argcnt = parse_line(buf, args); allocate_alias(ALIAS_BUILTIN); strcpy(buf, "alias g gdb"); argcnt = parse_line(buf, args); allocate_alias(ALIAS_BUILTIN); strcpy(buf, "alias px p -x"); argcnt = parse_line(buf, args); allocate_alias(ALIAS_BUILTIN); strcpy(buf, "alias pd p -d"); argcnt = parse_line(buf, args); allocate_alias(ALIAS_BUILTIN); strcpy(buf, "alias for foreach"); argcnt = parse_line(buf, args); allocate_alias(ALIAS_BUILTIN); strcpy(buf, "alias size *"); argcnt = parse_line(buf, args); allocate_alias(ALIAS_BUILTIN); strcpy(buf, "alias dmesg log"); argcnt = parse_line(buf, args); allocate_alias(ALIAS_BUILTIN); strcpy(buf, "alias lsmod mod"); argcnt = parse_line(buf, args); allocate_alias(ALIAS_BUILTIN); } /* * Before the command line is parsed, take a snapshot and parse the snapshot. * If args[0] is an known alias, recreate the pc->command_line string with * the alias substitution. */ static void resolve_aliases(void) { int i; struct alias_data *ad; int found; char *p1, *remainder; char buf1[BUFSIZE]; char buf2[BUFSIZE]; if (!strlen(pc->command_line)) return; strcpy(buf1, pc->command_line); argcnt = parse_line(buf1, args); if (argcnt > 1) { strcpy(buf2, &pc->command_line[args[1] - buf1]); remainder = buf2; } else remainder = NULL; found = FALSE; for (ad = alias_head.next; ad; ad = ad->next) { if (STREQ(ad->alias, args[0])) { for (i = 0; i < ad->argcnt; i++) args[i] = ad->args[i]; found = TRUE; break; } } if (!found) return; BZERO(pc->command_line, BUFSIZE); p1 = pc->command_line; for (i = 0; i < ad->argcnt; i++) { snprintf(p1, BUFSIZE - (p1-pc->command_line), "%s ", args[i]); while (*p1) p1++; if ((p1 - pc->command_line) >= BUFSIZE) break; } if (remainder) { if ((strlen(remainder)+strlen(pc->command_line)) < BUFSIZE) strcat(pc->command_line, remainder); else error(INFO, "command line overflow.\n"); } else if (strlen(pc->command_line) >= (BUFSIZE-1)) error(INFO, "command line overflow.\n"); clean_line(pc->command_line); } /* * If input string is an alias, return a pointer to the alias_data struct. */ struct alias_data * is_alias(char *s) { struct alias_data *ad; for (ad = alias_head.next; ad; ad = ad->next) { if (STREQ(ad->alias, s)) return(ad); } return NULL; } /* * .rc file commands that are "set" commands may be performed prior * to initialization, so pass them to cmd_set() for consideration. * All other commands are flagged for execution by exec_input_file() * after session initialization is complete. */ void resolve_rc_cmd(char *s, int origin) { clean_line(s); if (*s == '#') return; if ((argcnt = parse_line(s, args)) == 0) return; if (STREQ(args[0], "set")) { optind = 0; cmd_set(); } switch (origin) { case ALIAS_RCHOME: pc->flags |= RCHOME_IFILE; break; case ALIAS_RCLOCAL: pc->flags |= RCLOCAL_IFILE; break; } return; } /* * The "alias" command. With no arguments, list all aliases. With one * argument -- which must be an alias -- display the string it's aliased to. * With two or more arguments, setup a new alias, where the first argument * is the alias, and the remaining arguments make up the alias string. * If the second arg is the NULL string "", delete the alias. */ void cmd_alias(void) { if (argerrs) cmd_usage(pc->curcmd, SYNOPSIS); switch (argcnt) { case 1: list_aliases(NULL); break; case 2: list_aliases(args[1]); break; default: if (allocate_alias(ALIAS_RUNTIME)) list_aliases(args[1]); break; } } /* * Dump the current set of aliases. */ static void list_aliases(char *s) { int i; struct alias_data *ad; int found, precision; char buf[BUFSIZE]; if (!alias_head.next) { error(INFO, "alias list is empty\n"); return; } BZERO(buf, BUFSIZE); found = FALSE; precision = 7; for (ad = alias_head.next; ad; ad = ad->next) { switch (ad->origin) { case ALIAS_RCLOCAL: sprintf(buf, ".%src", pc->program_name); if (strlen(buf) > precision) precision = strlen(buf); break; case ALIAS_RCHOME: sprintf(buf, "$HOME/.%src", pc->program_name); if (strlen(buf) > precision) precision = strlen(buf); break; } } fprintf(fp, "ORIGIN"); pad_line(fp, precision-6, ' '); BZERO(buf, BUFSIZE); fprintf(fp, " ALIAS COMMAND\n"); for (ad = alias_head.next; ad; ad = ad->next) { if (s && !STREQ(s, ad->alias)) continue; found = TRUE; switch (ad->origin) { case ALIAS_RUNTIME: sprintf(buf, "runtime"); break; case ALIAS_RCLOCAL: sprintf(buf, ".%src", pc->program_name); break; case ALIAS_RCHOME: sprintf(buf, "$HOME/.%src", pc->program_name); break; case ALIAS_BUILTIN: sprintf(buf, "builtin"); break; } fprintf(fp, "%s ", buf); pad_line(fp, precision-strlen(buf), ' '); fprintf(fp, "%-7s ", ad->alias); for (i = 0; i < ad->argcnt; i++) { fprintf(fp, "%s ", ad->args[i]); } fprintf(fp, "\n"); } if (s && !found) fprintf(fp, "alias does not exist: %s\n", s); } /* * Verify the alias request set up in the args[] array: * * 1. make sure that the alias string starts with a legitimate command. * 2. if the already exists, deallocate its current version. * * Then malloc space for the alias string, and link it in to the alias list. */ static int allocate_alias(int origin) { int i; int size; struct alias_data *ad; struct alias_data *newad; char *p1, *enclosed_string; int found; if ((enclosed_string = strstr(args[2], " "))) *enclosed_string = NULLCHAR; found = FALSE; if (get_command_table_entry(args[1])) { error(INFO, "cannot alias existing command name: %s\n", args[1]); return FALSE; } if (get_command_table_entry(args[2])) found = TRUE; if (!found) { if (!strlen(args[2])) { if (alias_exists(args[1])) { deallocate_alias(args[1]); fprintf(fp, "alias deleted: %s\n", args[1]); } } else { error(INFO, "invalid alias attempt on non-existent command: %s\n", args[2]); } return FALSE; } if (alias_exists(args[1])) deallocate_alias(args[1]); if (enclosed_string) *enclosed_string = ' '; size = sizeof(struct alias_data) + argcnt; for (i = 0; i < argcnt; i++) size += strlen(args[i]); if ((newad = (struct alias_data *)malloc(size+1)) == NULL) { error(INFO, "alias_data malloc: %s\n", strerror(errno)); return FALSE; } BZERO(newad, size); newad->next = NULL; newad->size = size; newad->origin = origin; p1 = newad->argbuf; for (i = 1; i < argcnt; i++) { sprintf(p1, "%s ", args[i]); while (*p1) p1++; } p1 = strstr(newad->argbuf, " "); *p1 = NULLCHAR; newad->alias = newad->argbuf; newad->argcnt = parse_line(p1+1, newad->args); for (ad = &alias_head; ad->next; ad = ad->next) ; ad->next = newad; return TRUE; } /* * Check whether the passed-in string is a currently-existing alias. */ static int alias_exists(char *s) { struct alias_data *ad; if (!alias_head.next) return FALSE; for (ad = alias_head.next; ad; ad = ad->next) if (STREQ(ad->alias, s)) return TRUE; return FALSE; } /* * If the passed-in string is an alias, delink it and free its memory. */ void deallocate_alias(char *s) { struct alias_data *ad, *lastad; for (ad = alias_head.next, lastad = &alias_head; ad; ad = ad->next) { if (!STREQ(ad->alias, s)) { lastad = ad; continue; } lastad->next = ad->next; free(ad); break; } } /* * "help -a" output */ void dump_alias_data(void) { int i; struct alias_data *ad; fprintf(fp, "alias_head.next: %lx\n\n", (ulong)alias_head.next); for (ad = alias_head.next; ad; ad = ad->next) { fprintf(fp, " next: %lx\n", (ulong)ad->next); fprintf(fp, " alias: %s\n", ad->alias); fprintf(fp, " size: %d\n", ad->size); fprintf(fp, " origin: "); switch (ad->origin) { case ALIAS_RUNTIME: fprintf(fp, "runtime setting \n"); break; case ALIAS_RCLOCAL: fprintf(fp, ".%src \n", pc->program_name); break; case ALIAS_RCHOME: fprintf(fp, "$HOME/.%src \n", pc->program_name); break; case ALIAS_BUILTIN: fprintf(fp, "builtin\n"); break; } fprintf(fp, " argcnt: %d\n", ad->argcnt); for (i = 0; i < ad->argcnt; i++) fprintf(fp, " args[%d]: %lx: %s\n", i, (ulong)ad->args[i], ad->args[i]); fprintf(fp, "\n"); } } /* * Repeat a command on a live system. */ void cmd_repeat(void) { ulong delay; char buf[BUFSIZE]; char bufsave[BUFSIZE]; FILE *incoming_fp; if (argcnt == 1) cmd_usage(pc->curcmd, SYNOPSIS); delay = 0; if (args[1][0] == '-') { switch (args[1][1]) { default: case NULLCHAR: cmd_usage(pc->curcmd, SYNOPSIS); case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '0': delay = dtol(&args[1][1], FAULT_ON_ERROR, NULL); concat_args(buf, 2, FALSE); break; } } else concat_args(buf, 1, FALSE); check_special_handling(buf); strcpy(pc->command_line, buf); resolve_aliases(); if (!argcnt) return; strcpy(buf, pc->command_line); strcpy(bufsave, buf); argcnt = parse_line(buf, args); if (!argcnt) return; if (STREQ(args[0], "<") && (pc->flags & TTY) && (pc->flags & SCROLL) && pc->scroll_command) error(FATAL, "scrolling must be turned off when repeating an input file\n"); pc->curcmd_flags |= REPEAT; incoming_fp = fp; while (TRUE) { optind = 0; fp = incoming_fp; exec_command(); free_all_bufs(); wait_for_children(ZOMBIES_ONLY); if (received_SIGINT() || !output_open()) break; if ((pc->flags & TTY) && !is_a_tty("/dev/tty")) break; if (!(pc->curcmd_flags & REPEAT)) break; if (delay) sleep(delay); strcpy(buf, bufsave); argcnt = parse_line(buf, args); } } /* * Initialize readline, set the editing mode, and then perform any * crash-specific bindings, etc. */ static void readline_init(void) { rl_initialize(); if (STREQ(pc->editing_mode, "vi")) { rl_editing_mode = vi_mode; rl_bind_key(CTRL('N'), rl_get_next_history); rl_bind_key(CTRL('P'), rl_get_previous_history); rl_bind_key_in_map(CTRL('P'), rl_get_previous_history, vi_insertion_keymap); rl_bind_key_in_map(CTRL('N'), rl_get_next_history, vi_insertion_keymap); rl_bind_key_in_map(CTRL('l'), rl_clear_screen, vi_insertion_keymap); rl_generic_bind(ISFUNC, "[A", (char *)rl_get_previous_history, vi_movement_keymap); rl_generic_bind(ISFUNC, "[B", (char *)rl_get_next_history, vi_movement_keymap); } if (STREQ(pc->editing_mode, "emacs")) { rl_editing_mode = emacs_mode; } rl_attempted_completion_function = crash_readline_completer; rl_attempted_completion_over = 1; } /* * Find and set the tty string of this session as seen in "ps -ef" output. */ static void set_my_tty(void) { char buf[BUFSIZE]; char *arglist[MAXARGS]; int argc; FILE *pipe; strcpy(pc->my_tty, "?"); if (file_exists("/usr/bin/tty", NULL)) { sprintf(buf, "/usr/bin/tty"); if ((pipe = popen(buf, "r")) == NULL) return; while (fgets(buf, BUFSIZE, pipe)) { if (STRNEQ(buf, "/dev/")) { strcpy(pc->my_tty, strip_line_end(&buf[strlen("/dev/")])); break; } } pclose(pipe); return; } sprintf(buf, "ps -ef | grep ' %d '", getpid()); if (CRASHDEBUG(1)) fprintf(fp, "popen(%s)\n", buf); if ((pipe = popen(buf, "r")) == NULL) return; while (fgets(buf, BUFSIZE, pipe)) { argc = parse_line(buf, arglist); if ((argc >= 8) && (atoi(arglist[1]) == getpid())) { if (strlen(arglist[5]) < 9) strcpy(pc->my_tty, arglist[5]); else strncpy(pc->my_tty, arglist[5], 9); } } pclose(pipe); } /* * Check whether SIGINT's are allowed before shipping a request off to gdb. */ int interruptible(void) { if (!(pc->flags & RUNTIME)) return FALSE; if (!(pc->flags & TTY)) return FALSE; if ((pc->redirect & (FROM_INPUT_FILE|REDIRECT_NOT_DONE)) == (FROM_INPUT_FILE|REDIRECT_NOT_DONE)) return TRUE; if (strlen(pc->pipe_command)) return FALSE; return TRUE; } /* * Set up the standard output pipe using whichever was selected during init. */ static int setup_stdpipe(void) { char *path; if (pipe(pc->pipefd) < 0) { error(INFO, "pipe system call failed: %s", strerror(errno)); return FALSE; } if ((pc->stdpipe_pid = fork()) < 0) { error(INFO, "fork system call failed: %s", strerror(errno)); return FALSE; } path = NULL; if (pc->stdpipe_pid > 0) { pc->redirect |= REDIRECT_PID_KNOWN; close(pc->pipefd[0]); /* parent closes read end */ if ((pc->stdpipe = fdopen(pc->pipefd[1], "w")) == NULL) { error(INFO, "fdopen system call failed: %s", strerror(errno)); return FALSE; } setbuf(pc->stdpipe, NULL); switch (pc->scroll_command) { case SCROLL_LESS: strcpy(pc->pipe_command, less_argv[0]); break; case SCROLL_MORE: strcpy(pc->pipe_command, more_argv[0]); break; case SCROLL_CRASHPAGER: strcpy(pc->pipe_command, CRASHPAGER_argv[0]); break; } if (CRASHDEBUG(2)) console("pipe: %lx\n", pc->stdpipe); return TRUE;; } else { close(pc->pipefd[1]); /* child closes write end */ if (dup2(pc->pipefd[0], 0) != 0) { perror("child dup2 failed"); clean_exit(1); } if (CRASHDEBUG(2)) console("execv: %d\n", getpid()); switch (pc->scroll_command) { case SCROLL_LESS: path = less_argv[0]; execv(path, less_argv); break; case SCROLL_MORE: path = more_argv[0]; execv(path, more_argv); break; case SCROLL_CRASHPAGER: path = CRASHPAGER_argv[0]; execv(path, CRASHPAGER_argv); break; } perror(path); fprintf(stderr, "execv of scroll command failed\n"); exit(1); } } static void wait_for_children(ulong waitflag) { int status, pid; while (TRUE) { switch (pid = waitpid(-1, &status, WNOHANG)) { case 0: if (CRASHDEBUG(2)) console("wait_for_children: child running...\n"); if (waitflag == ZOMBIES_ONLY) return; break; case -1: if (CRASHDEBUG(2)) console("wait_for_children: no children alive\n"); return; default: console("wait_for_children(%d): reaped %d\n", waitflag, pid); if (CRASHDEBUG(2)) fprintf(fp, "wait_for_children: reaped %d\n", pid); break; } stall(1000); } } /* * Run an escaped shell command, redirecting the output to * the current output file. */ int shell_command(char *cmd) { FILE *pipe; char buf[BUFSIZE]; if ((pipe = popen(cmd, "r")) == NULL) { error(INFO, "cannot open pipe: %s\n", cmd); pc->redirect &= ~REDIRECT_SHELL_COMMAND; pc->redirect |= REDIRECT_FAILURE; return REDIRECT_FAILURE; } while (fgets(buf, BUFSIZE, pipe)) fputs(buf, fp); pclose(pipe); return REDIRECT_SHELL_COMMAND; } static int verify_args_input_file(char *fileptr) { struct stat stat; if (!file_exists(fileptr, &stat)) { if (CRASHDEBUG(1)) error(INFO, "%s: no such file\n", fileptr); } else if (!S_ISREG(stat.st_mode)) { if (CRASHDEBUG(1)) error(INFO, "%s: not a regular file\n", fileptr); } else if (!stat.st_size) { if (CRASHDEBUG(1)) error(INFO, "%s: file is empty\n", fileptr); } else if (!file_readable(fileptr)) { if (CRASHDEBUG(1)) error(INFO, "%s: permission denied\n", fileptr); } else return TRUE; return FALSE; } /* * Verify a command line argument input file. */ #define NON_FILENAME_CHARS "*?!|\'\"{}<>;,^()$~" int is_args_input_file(struct command_table_entry *ct, struct args_input_file *aif) { int c, start, whites, args_used; char *p1, *p2, *curptr, *fileptr; char buf[BUFSIZE]; int retval; if (pc->curcmd_flags & NO_MODIFY) return FALSE; if (STREQ(ct->name, "repeat")) return FALSE; BZERO(aif, sizeof(struct args_input_file)); retval = FALSE; if (STREQ(ct->name, "gdb")) { curptr = pc->orig_line; next_gdb: if ((p1 = strstr(curptr, "<"))) { while (STRNEQ(p1, "<<")) { p2 = p1+2; if (!(p1 = strstr(p2, "<"))) return retval; } } if (!p1) return retval; start = p1 - curptr; p2 = p1+1; for (whites = 0; whitespace(*p2); whites++) p2++; if (*p2 == NULLCHAR) return retval; strcpy(buf, p2); p2 = buf; if (*p2) { fileptr = p2; while (*p2 && !whitespace(*p2) && (strpbrk(p2, NON_FILENAME_CHARS) != p2)) p2++; *p2 = NULLCHAR; if (verify_args_input_file(fileptr)) { if (retval == TRUE) { error(INFO, "ignoring multiple argument input files: " "%s and %s\n", aif->fileptr, fileptr); return FALSE; } aif->start = start; aif->resume = start + (p2-buf) + whites + 1; aif->fileptr = GETBUF(strlen(fileptr)+1); strcpy(aif->fileptr, fileptr); aif->is_gdb_cmd = TRUE; retval = TRUE; } } curptr = p1+1; goto next_gdb; } for (c = 0; c < argcnt; c++) { if (STRNEQ(args[c], "<") && !STRNEQ(args[c], "<<")) { if (strlen(args[c]) > 1) { fileptr = &args[c][1]; args_used = 1; } else { if ((c+1) == argcnt) error(FATAL, "< requires a file argument\n"); fileptr = args[c+1]; args_used = 2; } if (!verify_args_input_file(fileptr)) continue; if (retval == TRUE) error(FATAL, "multiple input files are not supported\n"); aif->index = c; aif->fileptr = GETBUF(strlen(fileptr)+1); strcpy(aif->fileptr, fileptr); aif->args_used = args_used; retval = TRUE; continue; } if (STRNEQ(args[c], "(")) { curptr = args[c]; next_expr: if ((p1 = strstr(curptr, "<"))) { while (STRNEQ(p1, "<<")) { p2 = p1+2; if (!(p1 = strstr(p2, "<"))) continue; } } if (!p1) continue; start = p1 - curptr; p2 = p1+1; for (whites = 0; whitespace(*p2); whites++) p2++; if (*p2 == NULLCHAR) continue; strcpy(buf, p2); p2 = buf; if (*p2) { fileptr = p2; while (*p2 && !whitespace(*p2) && (strpbrk(p2, NON_FILENAME_CHARS) != p2)) p2++; *p2 = NULLCHAR; if (!verify_args_input_file(fileptr)) continue; if (retval == TRUE) { error(INFO, "ignoring multiple argument input files: " "%s and %s\n", aif->fileptr, fileptr); return FALSE; } retval = TRUE; aif->in_expression = TRUE; aif->args_used = 1; aif->index = c; aif->start = start; aif->resume = start + (p2-buf) + whites + 1; aif->fileptr = GETBUF(strlen(fileptr)+1); strcpy(aif->fileptr, fileptr); } curptr = p1+1; goto next_expr; } } return retval; } static void modify_orig_line(char *inbuf, struct args_input_file *aif) { char buf[BUFSIZE]; strcpy(buf, pc->orig_line); strcpy(&buf[aif->start], inbuf); strcat(buf, &pc->orig_line[aif->resume]); strcpy(pc->orig_line, buf); } static void modify_expression_arg(char *inbuf, char **aif_args, struct args_input_file *aif) { char *old, *new; old = aif_args[aif->index]; new = GETBUF(strlen(aif_args[aif->index]) + strlen(inbuf)); strcpy(new, old); strcpy(&new[aif->start], inbuf); strcat(new, &old[aif->resume]); aif_args[aif->index] = new; } /* * Sequence through an args input file, and for each line, * reinitialize the global args[] and argcnt, and issue the command. */ void exec_args_input_file(struct command_table_entry *ct, struct args_input_file *aif) { char buf[BUFSIZE]; int i, c, aif_cnt; int orig_argcnt; char *aif_args[MAXARGS]; char *new_args[MAXARGS]; char *orig_args[MAXARGS]; char orig_line[BUFSIZE]; char *save_args[MAXARGS]; char save_line[BUFSIZE]; if ((pc->args_ifile = fopen(aif->fileptr, "r")) == NULL) error(FATAL, "%s: %s\n", aif->fileptr, strerror(errno)); if (aif->is_gdb_cmd) strcpy(orig_line, pc->orig_line); BCOPY(args, orig_args, sizeof(args)); orig_argcnt = argcnt; /* * Commands cannot be trusted to leave the arguments intact. * Stash them here and restore them each time through the loop. */ save_args[0] = save_line; for (i = 0; i < orig_argcnt; i++) { strcpy(save_args[i], orig_args[i]); save_args[i+1] = save_args[i] + strlen(save_args[i]) + 2; } while (fgets(buf, BUFSIZE-1, pc->args_ifile)) { clean_line(buf); if ((strlen(buf) == 0) || (buf[0] == '#')) continue; for (i = 1; i < orig_argcnt; i++) strcpy(orig_args[i], save_args[i]); if (aif->is_gdb_cmd) { console("(gdb) before: [%s]\n", orig_line); strcpy(pc->orig_line, orig_line); modify_orig_line(buf, aif); console("(gdb) after: [%s]\n", pc->orig_line); } else if (aif->in_expression) { console("expr before: [%s]\n", orig_args[aif->index]); BCOPY(orig_args, aif_args, sizeof(aif_args)); modify_expression_arg(buf, aif_args, aif); BCOPY(aif_args, args, sizeof(aif_args)); console("expr after: [%s]\n", args[aif->index]); } else { if (!(aif_cnt = parse_line(buf, aif_args))) continue; for (i = 0; i < orig_argcnt; i++) console("%s[%d]:%s %s", (i == 0) ? "before: " : "", i, orig_args[i], (i+1) == orig_argcnt ? "\n" : ""); for (i = 0; i < aif->index; i++) new_args[i] = orig_args[i]; for (i = aif->index, c = 0; c < aif_cnt; c++, i++) new_args[i] = aif_args[c]; for (i = aif->index + aif_cnt, c = aif->index + aif->args_used; c < orig_argcnt; c++, i++) new_args[i] = orig_args[c]; argcnt = orig_argcnt - aif->args_used + aif_cnt; new_args[argcnt] = NULL; BCOPY(new_args, args, sizeof(args)); for (i = 0; i < argcnt; i++) console("%s[%d]:%s %s", (i == 0) ? " after: " : "", i, args[i], (i+1) == argcnt ? "\n" : ""); } optind = argerrs = 0; pc->cmdgencur++; if (setjmp(pc->foreach_loop_env)) pc->flags &= ~IN_FOREACH; else { pc->flags |= IN_FOREACH; (*ct->func)(); pc->flags &= ~IN_FOREACH; } if (pc->cmd_cleanup) pc->cmd_cleanup(pc->cmd_cleanup_arg); free_all_bufs(); if (received_SIGINT()) break; } fclose(pc->args_ifile); pc->args_ifile = NULL; } static char * crash_readline_completion_generator(const char *match, int state) { static struct syment *sp_match; if (state == 0) sp_match = NULL; sp_match = symbol_complete_match(match, sp_match); if (sp_match) return(strdup(sp_match->name)); else return NULL; } static char ** crash_readline_completer(const char *match, int start, int end) { rl_attempted_completion_over = 1; return rl_completion_matches(match, crash_readline_completion_generator); }