/*
 * Copyright (c) 2004 by David I. Bell
 * Permission is granted to use, distribute, or modify this source,
 * provided that this copyright notice remains intact.
 *
 * Stand-alone shell for system maintainance for Linux.
 * This program should NOT be built using shared libraries.
 */

#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <errno.h>

#include "sash.h"


static const char * const	version = "3.7-fb";


/*
 * The special maximum argument value which means that there is
 * no limit to the number of arguments on the command line.
 */
#define	INFINITE_ARGS	0x7fffffff


/*
 * One entry of the command table.
 */
typedef struct
{
	const char *	name;
	void		(*func)(int argc, const char ** argv);
	int		minArgs;
	int		maxArgs;
	const char *	description;
	const char *	usage;
} CommandEntry;


/*
 * The table of built-in commands.
 * This is terminated wih an entry containing NULL values.
 */
static const CommandEntry	commandEntryTable[] =
{
	{
		"alias",	do_alias,	1,	INFINITE_ARGS,
		"Define a command alias",
		"[name [command]]"
	},

	{
		"aliasall",	do_aliasall,	1,	1,
		"Define aliases for all of the build-in commands",
		""
	},

	{
		"-ar",		do_ar,		3,	INFINITE_ARGS,
		"Extract or list files from an AR file",
		"[txp]v arFileName fileName ..."
	},

	{
		"cd",		do_cd,		1,	2,
		"Change current directory",
		"[dirName]"
	},

#if	HAVE_LINUX_ATTR
	{
		"-chattr",	do_chattr,	3,	INFINITE_ARGS,
		"Change ext2 file attributes",
		"[+i] [-i] [+a] [-a] fileName ..."
	},
#endif

	{
		"-chgrp",	do_chgrp,	3,	INFINITE_ARGS,
		"Change the group id of some files",
		"gid fileName ..."
	},

	{
		"-chmod",	do_chmod,	3,	INFINITE_ARGS,
		"Change the protection of some files",
		"mode fileName ..."
	},

	{
		"-chown",	do_chown,	3,	INFINITE_ARGS,
		"Change the owner id of some files",
		"uid fileName ..."
	},

	{
		"-cmp",		do_cmp,		3,	3,
		"Compare two files for equality",
		"fileName1 fileName2"
	},

	{
		"-cp",		do_cp,		3,	INFINITE_ARGS,
		"Copy files",
		"srcName ... destName"
	},

#ifdef	HAVE_LINUX_CHROOT
	{
		"-chroot",	do_chroot,	2,	2,
		"change root file system",
		"new_root_dir"
	},
#endif

	{
		"-dd",		do_dd,		3,	INFINITE_ARGS,
		"Copy data between two files",
		"if=name of=name [bs=n] [count=n] [skip=n] [seek=n]"
	},

	{
		"-echo",	do_echo,	1,	INFINITE_ARGS,
		"Echo the arguments",
		"[args] ..."
	},

	{
		"-ed",		do_ed,		1,	2,
		"Edit a fileName using simple line mode commands",
		"[fileName]"
	},

	{
		"exec",		do_exec,	2,	INFINITE_ARGS,
		"Execute another program in place of this sash process",
		"fileName [args]"
	},

	{
		"exit",		do_exit,	1,	2,
		"Exit from sash",
		"[exit value]"
	},

	{
		"-file",	do_file,	1,	INFINITE_ARGS,
		"Describe information about files",
		"fileName ..."
	},

	{
		"-find",	do_find,	2,	INFINITE_ARGS,
		"Find files in a directory tree meeting some conditions",
		"dirName [-xdev] [-type chars] [-name pattern] [-size minSize]"
	},

	{
		"-grep",	do_grep,	3,	INFINITE_ARGS,
		"Look for lines containing a word in some files",
		"[-in] word fileName ..."
	},

#if	HAVE_GZIP
	{
		"-gunzip",	do_gunzip,	2,	INFINITE_ARGS,
		"Uncompress files which were saved in GZIP or compress format",
		"fileName ... [-o outputPath]"
	},

	{
		"-gzip",	do_gzip,	2,	INFINITE_ARGS,
		"Compress files into GZIP format",
		"fileName ... [-o outputPath]"
	},
#endif

	{
		"help",		do_help,	1,	2,
		"Print help about a command",
		"[word]"
	},

	{
		"-kill",	do_kill,	2,	INFINITE_ARGS,
		"Send a signal to the specified process",
		"[-sig] pid ..."
	},

#ifdef	HAVE_LINUX_LOSETUP
	{
		"-losetup",	do_losetup,	3,	3,
		"Associate a loopback device with a file",
		"[-d] device\n       -losetup device filename"
	},
#endif

	{
		"-ln",		do_ln,		3,	INFINITE_ARGS,
		"Link one fileName to another",
		"[-s] srcName ... destName"
	},

	{
		"-ls",		do_ls,		1,	INFINITE_ARGS,
		"List information about files or directories",
		"[-lidFC] fileName ..."
	},

#if	HAVE_LINUX_ATTR
	{
		"-lsattr",	do_lsattr,	2,	INFINITE_ARGS,
		"List ext2 file attributes",
		"fileName ..."
	},
#endif

	{
		"-mkdir",	do_mkdir,	2,	INFINITE_ARGS,
		"Create a directory",
		"dirName ..."
	},

	{
		"-mknod",	do_mknod,	5,	5,
		"Create a special type of file",
		"fileName type major minor"
	},

	{
		"-more",	do_more,	2,	INFINITE_ARGS,
		"Type file contents page by page",
		"fileName ..."
	},

	{
		"-mount",	do_mount,	3,	INFINITE_ARGS,
		"Mount or remount a filesystem on a directory",
#if	HAVE_LINUX_MOUNT
		"[-t type] [-r] [-s] [-e] [-m] devName dirName"
#elif	HAVE_BSD_MOUNT
		"[-t type] [-r] [-s] [-e] devName dirName"
#else
		"[-t type] devName dirName"
#endif
	},

	{
		"-mv",		do_mv,		3,	INFINITE_ARGS,
		"Move or rename files",
		"srcName ... destName"
	},

#ifdef	HAVE_LINUX_PIVOT
	{
		"-pivot_root",	do_pivot_root,	3,	3,
		"pivot the root file system",
		"new_dir old_dir"
	},
#endif

	{
		"-printenv",	do_printenv,	1,	2,
		"Print environment variables",
		"[name]"
	},

	{
		"prompt",	do_prompt,	2,	INFINITE_ARGS,
		"Set the prompt string for sash",
		"string"
	},

	{
		"-pwd",		do_pwd,		1,	1,
		"Print the current working directory",
		""
	},

	{
		"quit",		do_exit,	1,	1,
		"Exit from sash",
		""
	},

	{
		"-rm",		do_rm,		2,	INFINITE_ARGS,
		"Remove the specified files",
		"fileName ..."
	},

	{
		"-rmdir",	do_rmdir,	2,	INFINITE_ARGS,
		"Remove the specified empty directories",
		"dirName ..."
	},

	{
		"setenv",	do_setenv,	3,	3,
		"Set an environment variable value",
		"name value"
	},

	{
		"source",	do_source,	2,	2,
		"Read commands from the specified file",
		"fileName"
	},

	{
		"-sum",		do_sum,		2,	INFINITE_ARGS,
		"Calculate checksums of the specified files",
		"fileName ..."
	},

	{
		"-sync",	do_sync,	1,	1,
		"Sync the disks to force cached data to them",
		""
	},

	{
		"-tar",		do_tar,		2,	INFINITE_ARGS,
		"Create, extract, or list files from a TAR file",
		"[cxtv]f tarFileName fileName ..."
	},

	{
		"-touch",	do_touch,	2,	INFINITE_ARGS,
		"Update times or create the specified files",
		"fileName ..."
	},

	{
		"umask",	do_umask,	1,	2,
		"Set the umask value for file protections",
		"[mask]"
	},

	{
#if	HAVE_BSD_MOUNT
		"-umount",	do_umount,	2,	3,
		"Unmount a filesystem",
		"[-f] fileName"
#else
		"-umount",	do_umount,	2,	2,
		"Unmount a filesystem",
		"fileName"
#endif
	},

	{
		"unalias",	do_unalias,	2,	2,
		"Remove a command alias",
		"name"
	},

	{
		"-where",	do_where,	2,	2,
		"Type the location of a program",
		"program"
	},

	{
		NULL,		0,		0,	0,
		NULL,
		NULL
	}
};


/*
 * The definition of an command alias.
 */
typedef struct
{
	char *	name;
	char *	value;
} Alias;


/*
 * Local data.
 */
static	Alias *	aliasTable;
static	int	aliasCount;

static	FILE *	sourcefiles[MAX_SOURCE];
static	int	sourceCount;

static	BOOL	intCrlf = TRUE;
static	char *	prompt;


/*
 * Local procedures.
 */
static	void	catchInt(int);
static	void	catchQuit(int);
static	int	readFile(const char * name);
static	int	command(const char * cmd);
static	BOOL	tryBuiltIn(const char * cmd);
static	int	runCmd(const char * cmd);
static	void	childProcess(const char * cmd);
static	void	showPrompt(void);
static	void	usage(void);
static	Alias *	findAlias(const char * name);
static	void	expandVariable(char * name);


/*
 * Global interrupt flag.
 */
BOOL	intFlag;


int
main(int argc, const char ** argv)
{
	const char *	cp;
	const char *	singleCommand;
	const char *	commandFile;
	BOOL		quietFlag;
	BOOL		aliasFlag;
        BOOL		interactiveFlag;
	char		buf[PATH_LEN];

	singleCommand = NULL;
	commandFile = NULL;
	quietFlag = FALSE;
	aliasFlag = FALSE;
        interactiveFlag = FALSE;

	/*
	 * Look for options.
	 */
	argv++;
	argc--;

	while ((argc > 0) && (**argv == '-'))
	{
		cp = *argv++ + 1;
		argc--;

		while (*cp) switch (*cp++)
		{
                        case '-':
                                /*
                                 * Ignore.  This is so that we can be
                                 * run from login.
                                 */
                                break;
			case 'c':
				/*
				 * Execute specified command.
				 */
				if ((argc != 1) || singleCommand || interactiveFlag)
					usage();

				singleCommand = *argv++;
				argc--;

				break;

			case 'f':
				/*
				 * Execute commands from file.
				 * This is used for sash script files.
				 * The quiet flag is also set.
				 */
				if ((argc != 1) || commandFile)
					usage();

				quietFlag = TRUE;
				commandFile = *argv++;
				argc--;

				break;

			case 'i':
				/*
				 * Be an interactive shell
				 * ..is a no-op, but some contexts require this
				 * ..interactiveFlag is to avoid -ic as a legacy
				 */
				 if (singleCommand)
				 	usage();
				 
				 interactiveFlag = TRUE;
				 break;
				 
			case 'p':
				/*
				 * Set the prompt string.
				 */
				if ((argc <= 0) || (**argv == '-'))
					usage();

				if (prompt)
					free(prompt);

				prompt = strdup(*argv++);
				argc--;

				break;

			case 'q':
				quietFlag = TRUE;
				break;

			case 'a':
				aliasFlag = TRUE;
				break;

			case 'h':
			case '?':
				usage();
				break;

			default:
				fprintf(stderr, "Unknown option -%c\n", cp[-1]);

				return 1;
		}
	}

	/*
	 * No more arguments are allowed.
	 */
	if (argc > 0)
		usage();

	/*
	 * Default our path if it is not set.
	 */
	if (getenv("PATH") == NULL)
		putenv("PATH=/bin:/usr/bin:/sbin:/usr/sbin:/etc");

	/*
	 * If the alias flag is set then define all aliases.
	 */
	if (aliasFlag)
		do_aliasall(0, NULL);

	/*
	 * If we are to execute a single command, then do so and exit.
	 */
	if (singleCommand)
	{
		return command(singleCommand);
	}

	/*
	 * Print a hello message unless we are told to be silent.
	 */
	if (!quietFlag && isatty(STDIN))
	{
		printf("Stand-alone shell (version %s)\n", version);

		if (aliasFlag)
			printf("Built-in commands are aliased to standard commands\n");
	}

	signal(SIGINT, catchInt);
	signal(SIGQUIT, catchQuit);

	/*
	 * Execute the user's alias file if present.
	 */
	cp = getenv("HOME");

	if (cp)
	{
		strcpy(buf, cp);
		strcat(buf, "/");
		strcat(buf, ".aliasrc");

		if ((access(buf, 0) == 0) || (errno != ENOENT))
			readFile(buf);
	}

	/*
	 * Read commands from stdin or from a command file.
	 */
	return readFile(commandFile);

}


/*
 * Read commands from the specified file.
 * A null name pointer indicates to read from stdin.
 */
static int
readFile(const char * name)
{
	FILE *	fp;
	int	cc;
	BOOL	ttyFlag;
	char	buf[CMD_LEN];
	int	r = 0;

	if (sourceCount >= MAX_SOURCE)
	{
		fprintf(stderr, "Too many source files\n");

		return 1;
	}

	fp = stdin;

	if (name)
	{
		fp = fopen(name, "r");

		if (fp == NULL)
		{
			perror(name);

			return 1;
		}
	}

	sourcefiles[sourceCount++] = fp;

	ttyFlag = isatty(fileno(fp));

	while (TRUE)
	{
		if (ttyFlag)
			showPrompt();

		if (intFlag && !ttyFlag && (fp != stdin))
		{
			fclose(fp);
			sourceCount--;

			return 1;
		}
	
		if (fgets(buf, CMD_LEN - 1, fp) == NULL)
		{
			if (ferror(fp) && (errno == EINTR))
			{
				clearerr(fp);

				continue;
			}

			break;
		}

		cc = strlen(buf);

		if (buf[cc - 1] == '\n')
			cc--;

		while ((cc > 0) && isBlank(buf[cc - 1]))
			cc--;

		buf[cc] = '\0';

		r = command(buf);
	}

	if (ferror(fp))
	{
		perror("Reading command line");

		if (fp == stdin)
			exit(1);
	}

	clearerr(fp);

	if (fp != stdin)
		fclose(fp);

	sourceCount--;
	return r;
}


/*
 * Parse and execute one null-terminated command line string.
 * This breaks the command line up into words, checks to see if the
 * command is an alias, and expands wildcards.
 */
static int
command(const char * cmd)
{
	const char *	endCmd;
	const Alias *	alias;
	char		newCommand[CMD_LEN];
	char		cmdName[CMD_LEN];

	/*
	 * Rest the interrupt flag and free any memory chunks that
	 * were allocated by the previous command.
	 */
	intFlag = FALSE;

	freeChunks();

	/*
	 * Skip leading blanks.
	 */
	while (isBlank(*cmd))
		cmd++;

	/*
	 * If the command is empty or is a comment then ignore it.
	 */
	if ((*cmd == '\0') || (*cmd == '#'))
		return 0;

	/*
	 * Look for the end of the command name and then copy the
	 * command name to a buffer so we can null terminate it.
	 */
	endCmd = cmd;

	while (*endCmd && !isBlank(*endCmd))
		endCmd++;

	memcpy(cmdName, cmd, endCmd - cmd);

	cmdName[endCmd - cmd] = '\0';

	/*
	 * Search for the command name in the alias table.
	 * If it is found, then replace the command name with
	 * the alias value, and append the current command
	 * line arguments to that.
	 */
	alias = findAlias(cmdName);

	if (alias)
	{
		strcpy(newCommand, alias->value);
		strcat(newCommand, endCmd);

		cmd = newCommand;
	}

	/*
	 * Expand simple environment variables
	 */
	while (strstr(cmd, "$(")) expandVariable((char *)cmd);

	/*
	 * Now look for the command in the builtin table, and execute
	 * the command if found.
	 */
	if (tryBuiltIn(cmd))
		return 0; /* This is a blatant lie */

	/*
	 * The command is not a built-in, so run the program along
	 * the PATH list.
	 */
	return runCmd(cmd);
}


/*
 * Try to execute a built-in command.
 * Returns TRUE if the command is a built in, whether or not the
 * command succeeds.  Returns FALSE if this is not a built-in command.
 */
static BOOL
tryBuiltIn(const char * cmd)
{
	const char *		endCmd;
	const CommandEntry *	entry;
	int			argc;
	const char **		argv;
	char			cmdName[CMD_LEN];

	/*
	 * Look for the end of the command name and then copy the
	 * command name to a buffer so we can null terminate it.
	 */
	endCmd = cmd;

	while (*endCmd && !isBlank(*endCmd))
		endCmd++;

	memcpy(cmdName, cmd, endCmd - cmd);

	cmdName[endCmd - cmd] = '\0';

	/*
	 * Search the command table looking for the command name.
	 */
	for (entry = commandEntryTable; entry->name != NULL; entry++)
	{
		if (strcmp(entry->name, cmdName) == 0)
			break;
	}

	/*
	 * If the command is not a built-in, return indicating that.
	 */
	if (entry->name == NULL)
		return FALSE;

	/*
	 * The command is a built-in.
	 * Break the command up into arguments and expand wildcards.
	 */
	if (!makeArgs(cmd, &argc, &argv))
		return TRUE;

	/*
	 * Give a usage string if the number of arguments is too large
	 * or too small.
	 */
	if ((argc < entry->minArgs) || (argc > entry->maxArgs))
	{
		fprintf(stderr, "usage: %s %s\n", entry->name, entry->usage);

		return TRUE;
	}

	/*
	 * Call the built-in function with the argument list.
	 */
	entry->func(argc, argv);

	return TRUE;
}


/*
 * Execute the specified command either by forking and executing
 * the program ourself, or else by using the shell.
 */
static int
runCmd(const char * cmd)
{
	const char *	cp;
	BOOL		magic;
	pid_t		pid;
	int		status;

	/*
	 * Check the command for any magic shell characters
	 * except for quoting.
	 */
	magic = FALSE;

	for (cp = cmd; *cp; cp++)
	{
		if ((*cp >= 'a') && (*cp <= 'z'))
			continue;

		if ((*cp >= 'A') && (*cp <= 'Z'))
			continue;

		if (isDecimal(*cp))
			continue;

		if (isBlank(*cp))
			continue;

		if ((*cp == '.') || (*cp == '/') || (*cp == '-') ||
			(*cp == '+') || (*cp == '=') || (*cp == '_') ||
			(*cp == ':') || (*cp == ',') || (*cp == '\'') ||
			(*cp == '"'))
		{
			continue;
		}

		magic = TRUE;
	}

	/*
	 * If there were any magic characters used then run the
	 * command using the shell.
	 */
	if (magic)
	{
		return system(cmd);
	}

	/*
	 * No magic characters were in the command, so we can do the fork
	 * and exec ourself.
	 */
	pid = fork();

	if (pid < 0)
	{
		perror("fork failed");

		return -1;
	}

	/*
	 * If we are the child process, then go execute the program.
	 */
	if (pid == 0)
		childProcess(cmd);

	/*
	 * We are the parent process.
	 * Wait for the child to complete.
	 */
	status = 0;
	intCrlf = FALSE;

	while (((pid = waitpid(pid, &status, 0)) < 0) && (errno == EINTR))
		;

	intCrlf = TRUE;

	if (pid < 0)
	{
		fprintf(stderr, "Error from waitpid: %s", strerror(errno));

		return -1;
	}

	if (WIFSIGNALED(status))
	{
		fprintf(stderr, "pid %ld: killed by signal %d\n",
			(long) pid, WTERMSIG(status));
	}
	return WEXITSTATUS(status);
}


/*
 * Here as the child process to try to execute the command.
 * This is only called if there are no meta-characters in the command.
 * This procedure never returns.
 */
static void
childProcess(const char * cmd)
{
	const char **	argv;
	int		argc;

	/*
	 * Close any extra file descriptors we have opened.
	 */	
	while (--sourceCount >= 0)
	{
		if (sourcefiles[sourceCount] != stdin)
			fclose(sourcefiles[sourceCount]);
	}

	/*
	 * Break the command line up into individual arguments.
	 * If this fails, then run the shell to execute the command.
	 */
	if (!makeArgs(cmd, &argc, &argv))
	{
		system(cmd);
		exit(0);
	}

	/*
	 * Try to execute the program directly.
	 */
	execvp(argv[0], (char **) argv);

	/*
	 * The exec failed, so try to run the command using the shell
	 * in case it is a shell script.
	 */
	if (errno == ENOEXEC)
	{
		system(cmd);
		exit(0);
	}

	/*
	 * There was something else wrong, complain and exit.
	 */
	perror(argv[0]);
	exit(1);
}


void
do_help(int argc, const char ** argv)
{
	const CommandEntry *	entry;
	const char *		str;

	str = NULL;

	if (argc == 2)
		str = argv[1];

	/*
	 * Check for an exact match, in which case describe the program.
	 */
	if (str)
	{
		for (entry = commandEntryTable; entry->name; entry++)
		{
			if (strcmp(str, entry->name) == 0)
			{
				printf("%s\n", entry->description);

				printf("usage: %s %s\n", entry->name,
					entry->usage);

				return;
			}
		}
	}

	/*
	 * Print short information about commands which contain the
	 * specified word.
	 */
	for (entry = commandEntryTable; entry->name; entry++)
	{
		if ((str == NULL) || (strstr(entry->name, str) != NULL) ||
			(strstr(entry->usage, str) != NULL))
		{
			printf("%-10s %s\n", entry->name, entry->usage);
		}
	}
}


void
do_alias(int argc, const char ** argv)
{
	const char *	name;
	char *		value;
	Alias *		alias;
	int		count;
	char		buf[CMD_LEN];

	if (argc < 2)
	{
		count = aliasCount;

		for (alias = aliasTable; count-- > 0; alias++)
			printf("%s\t%s\n", alias->name, alias->value);

		return;
	}

	name = argv[1];

	if (argc == 2)
	{
		alias = findAlias(name);

		if (alias)
			printf("%s\n", alias->value);
		else
			fprintf(stderr, "Alias \"%s\" is not defined\n", name);

		return;	
	}

	if (strcmp(name, "alias") == 0)
	{
		fprintf(stderr, "Cannot alias \"alias\"\n");

		return;
	}

	if (!makeString(argc - 2, argv + 2, buf, CMD_LEN))
		return;

	value = malloc(strlen(buf) + 1);

	if (value == NULL)
	{
		fprintf(stderr, "No memory for alias value\n");

		return;
	}

	strcpy(value, buf);

	alias = findAlias(name);

	if (alias)
	{
		free(alias->value);
		alias->value = value;

		return;
	}

	if ((aliasCount % ALIAS_ALLOC) == 0)
	{
		count = aliasCount + ALIAS_ALLOC;

		if (aliasTable)
		{
			alias = (Alias *) realloc(aliasTable,
				sizeof(Alias) * count);
		}
		else
			alias = (Alias *) malloc(sizeof(Alias) * count);

		if (alias == NULL)
		{
			free(value);
			fprintf(stderr, "No memory for alias table\n");

			return;
		}

		aliasTable = alias;
	}

	alias = &aliasTable[aliasCount];

	alias->name = malloc(strlen(name) + 1);

	if (alias->name == NULL)
	{
		free(value);
		fprintf(stderr, "No memory for alias name\n");

		return;
	}

	strcpy(alias->name, name);
	alias->value = value;
	aliasCount++;
}


/*
 * Build aliases for all of the built-in commands which start with a dash,
 * using the names without the dash.
 */
void
do_aliasall(int argc, const char **argv)
{
	const CommandEntry *	entry;
	const char *		name;
	const char *		newArgv[4];

	for (entry = commandEntryTable; entry->name; entry++)
	{
		name = entry->name;

		if (*name != '-')
			continue;

		newArgv[0] = "alias";
		newArgv[1] = name + 1;
		newArgv[2] = name;
		newArgv[3] = NULL;

		do_alias(3, newArgv);
	}
}


/*
 * Look up an alias name, and return a pointer to it.
 * Returns NULL if the name does not exist.
 */
static Alias *
findAlias(const char * name)
{
	Alias *	alias;
	int	count;

	count = aliasCount;

	for (alias = aliasTable; count-- > 0; alias++)
	{
		if (strcmp(name, alias->name) == 0)
			return alias;
	}

	return NULL;
}


void
do_source(int argc, const char ** argv)
{
	readFile(argv[1]);
}


void
do_exec(int argc, const char ** argv)
{
	const char *	name;

	name = argv[1];

	while (--sourceCount >= 0)
	{
		if (sourcefiles[sourceCount] != stdin)
			fclose(sourcefiles[sourceCount]);
	}

	argv[argc] = NULL;

	execvp(name, (char **) argv + 1);
	perror(name);
}


void
do_prompt(int argc, const char ** argv)
{
	char *	cp;
	char	buf[CMD_LEN];

	if (!makeString(argc - 1, argv + 1, buf, CMD_LEN))
		return;

	cp = malloc(strlen(buf) + 2);

	if (cp == NULL)
	{
		fprintf(stderr, "No memory for prompt\n");

		return;
	}

	strcpy(cp, buf);
	strcat(cp, " ");

	if (prompt)
		free(prompt);

	prompt = cp;
}


void
do_unalias(int argc, const char ** argv)
{
	Alias *	alias;

	while (--argc > 0)
	{
		alias = findAlias(*++argv);

		if (alias == NULL)
			continue;

		free(alias->name);
		free(alias->value);
		aliasCount--;
		alias->name = aliasTable[aliasCount].name;
		alias->value = aliasTable[aliasCount].value;	
	}
}


/*
 * Display the prompt string.
 */
static void
showPrompt(void)
{
	const char *	cp;

	cp = "> ";

	if (prompt)
		cp = prompt;

	write(STDOUT, cp, strlen(cp));
}	


static void
catchInt(int val)
{
	signal(SIGINT, catchInt);

	intFlag = TRUE;

	if (intCrlf)
		write(STDOUT, "\n", 1);
}


static void
catchQuit(int val)
{
	signal(SIGQUIT, catchQuit);

	intFlag = TRUE;

	if (intCrlf)
		write(STDOUT, "\n", 1);
}


/*
 * Print the usage information and quit.
 */
static void
usage(void)
{
	fprintf(stderr, "Stand-alone shell (version %s)\n", version);
	fprintf(stderr, "\n");
	fprintf(stderr, "Usage: sash [-a] [-q] [-f fileName] [-c command] [-p prompt] [-i]\n");

	exit(1);
}

/*
 * Expand one environment variable: Syntax $(VAR)
 */
static void
expandVariable(char * cmd)
{
	char	tmp[CMD_LEN];
	char 	*cp;
	char	*ep;

	strcpy(tmp, cmd);
	cp = strstr(tmp, "$(");
	if (cp) {
		*cp++ = '\0';
		strcpy(cmd, tmp);
		ep = ++cp;
		while (*ep && (*ep != ')')) ep++;
		if (*ep == ')') *ep++ = '\0';
		cp = getenv(cp);
		if (cp) strcat(cmd, cp);
		strcat(cmd, ep);
	}
	return;
}

/* END CODE */
