crash/cmdline.c

2644 lines
62 KiB
C

/* 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 <readline.h>
#include <rldefs.h>
#include <history.h>
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\\: <SPACE>, <ENTER> 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, " <segmentation violation%s>\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);
}
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);
}