Blob Blame History Raw
/* 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);
		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);
}