/*
 *	Ported to Linux's Second Extended File System as part of the
 *	dump and restore backup suit
 *	Remy Card <card@Linux.EU.Org>, 1994-1997
 *	Stelian Pop <stelian@popies.net>, 1999-2000
 *	Stelian Pop <stelian@popies.net> - Alcôve <www.alcove.com>, 2000-2002
 */

/*
 * Copyright (c) 1985, 1993
 *	The Regents of the University of California.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#ifndef lint
static const char rcsid[] =
	"$Id: interactive.c,v 1.27 2003/10/26 16:05:48 stelian Exp $";
#endif /* not lint */

#include <config.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/stat.h>

#ifdef	__linux__
#ifdef HAVE_EXT2FS_EXT2_FS_H
#include <ext2fs/ext2_fs.h>
#else
#include <linux/ext2_fs.h>
#endif
#include <bsdcompat.h>
#else	/* __linux__ */
#ifdef sunos
#include <sys/fcntl.h>
#include <bsdcompat.h>
#else
#include <ufs/ufs/dinode.h>
#include <ufs/ufs/dir.h>
#endif
#endif	/* __linux__ */
#include <protocols/dumprestore.h>

#include <setjmp.h>
#include <compaterr.h>
#include <errno.h>
#include <compatglob.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#ifdef	__linux__
#include <ext2fs/ext2fs.h>
#endif

#if defined(__linux__) || defined(sunos)
extern char * __progname;
#endif

#include "restore.h"
#include "extern.h"

#if HAVE_READLINE
#include <readline/readline.h>
#include <readline/history.h>

static char *rl_gets (char *prompt);
static void initialize_readline(void);
static char **restore_completion (const char *text, int start, int end);
static char *command_generator(const char *text, int state);
static char *filename_generator(const char *text, int state);
#endif

#define round(a, b) (((a) + (b) - 1) / (b) * (b))

/*
 * Things to handle interruptions.
 */
static int runshell;
static jmp_buf reset;
static char *nextarg = NULL;
static int pflag = 0;		/* prompt mode */
/*
 * Structure and routines associated with listing directories.
 */
struct afile {
	dump_ino_t fnum;	/* inode number of file */
	char	*fname;		/* file name */
	short	len;		/* name length */
	char	prefix;		/* prefix character */
	char	postfix;	/* postfix character */
};
struct arglist {
	int	freeglob;	/* glob structure needs to be freed */
	int	argcnt;		/* next globbed argument to return */
	glob_t	glob;		/* globbing information */
	char	*cmd;		/* the current command */
};

static char	*copynext __P((char *, char *));
static int	 fcmp __P((const void *, const void *));
static void	 formatf __P((struct afile *, int));
static void	 getcmd __P((char *, char *, char *, int, struct arglist *));
struct dirent	*glob_readdir __P((RST_DIR *dirp));
static int	 glob_stat __P((const char *, struct stat *));
static void	 mkentry __P((char *, struct direct *, struct afile *));
static void	 printlist __P((char *, char *));

/*
 * Read and execute commands from the terminal.
 */
void
runcmdshell(void)
{
	struct entry *np;
	dump_ino_t ino;
	struct arglist arglist;
	char curdir[MAXPATHLEN];
	char name[MAXPATHLEN];
	char cmd[BUFSIZ];

#if HAVE_READLINE
	initialize_readline();
#endif
	arglist.freeglob = 0;
	arglist.argcnt = 0;
	arglist.glob.gl_flags = GLOB_ALTDIRFUNC;
	arglist.glob.gl_opendir = (void *)rst_opendir;
	arglist.glob.gl_readdir = (void *)glob_readdir;
	arglist.glob.gl_closedir = (void *)rst_closedir;
	arglist.glob.gl_lstat = (int (*)(const char *, void *))glob_stat;
	arglist.glob.gl_stat = (int (*)(const char *, void *))glob_stat;
	canon("/", curdir, sizeof(curdir));
loop:
	if (setjmp(reset) != 0) {
		if (arglist.freeglob != 0) {
			arglist.freeglob = 0;
			arglist.argcnt = 0;
			globfree(&arglist.glob);
		}
		nextarg = NULL;
		volno = 0;
	}
	runshell = 1;
	getcmd(curdir, cmd, name, sizeof(name), &arglist);
	switch (cmd[0]) {
	/*
	 * Add elements to the extraction list.
	 */
	case 'a':
		if (strncmp(cmd, "add", strlen(cmd)) != 0)
			goto bad;
		ino = dirlookup(name);
		if (ino == 0)
			break;
		if (mflag)
			pathcheck(name);
		treescan(name, ino, addfile);
		break;
	/*
	 * Change working directory.
	 */
	case 'c':
		if (strncmp(cmd, "cd", strlen(cmd)) != 0)
			goto bad;
		ino = dirlookup(name);
		if (ino == 0)
			break;
		if (inodetype(ino) == LEAF) {
			fprintf(stderr, "%s: not a directory\n", name);
			break;
		}
		(void) strncpy(curdir, name, sizeof(curdir));
		curdir[sizeof(curdir) - 1] = '\0';
		break;
	/*
	 * Delete elements from the extraction list.
	 */
	case 'd':
		if (strncmp(cmd, "delete", strlen(cmd)) != 0)
			goto bad;
		np = lookupname(name);
		if (np == NULL || (np->e_flags & NEW) == 0) {
			fprintf(stderr, "%s: not on extraction list\n", name);
			break;
		}
		treescan(name, np->e_ino, deletefile);
		break;
	/*
	 * Extract the requested list.
	 */
	case 'e':
		if (strncmp(cmd, "extract", strlen(cmd)) != 0)
			goto bad;
		createfiles();
		createlinks();
		setdirmodes(oflag ? FORCE : 0);
		if (dflag)
			checkrestore();
		volno = 0;
		break;
	/*
	 * List available commands.
	 */
	case 'h':
		if (strncmp(cmd, "help", strlen(cmd)) != 0)
			goto bad;
	case '?':
		fprintf(stderr, "%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s",
			"Available commands are:\n",
			"\tls [arg] - list directory\n",
			"\tcd arg - change directory\n",
			"\tpwd - print current directory\n",
			"\tadd [arg] - add `arg' to list of",
			" files to be extracted\n",
			"\tdelete [arg] - delete `arg' from",
			" list of files to be extracted\n",
			"\textract - extract requested files\n",
			"\tsetmodes - set modes of requested directories\n",
			"\tquit - immediately exit program\n",
			"\twhat - list dump header information\n",
			"\tverbose - toggle verbose flag",
			" (useful with ``ls'')\n",
			"\tprompt - toggle the prompt display\n",
			"\thelp or `?' - print this list\n",
			"If no `arg' is supplied, the current",
			" directory is used\n");
		break;
	/*
	 * List a directory.
	 */
	case 'l':
		if (strncmp(cmd, "ls", strlen(cmd)) != 0)
			goto bad;
		printlist(name, curdir);
		break;
	/*
	 * Print current directory.
	 */
	case 'p':
		if (strncmp(cmd, "pwd", strlen(cmd)) == 0) {
			if (curdir[1] == '\0')
				fprintf(stderr, "/\n");
			else
				fprintf(stderr, "%s\n", &curdir[1]);
		}
	/*
	 * Toggle prompt mode.
	 */
		else if (strncmp(cmd, "prompt", strlen(cmd)) == 0) {
			if (pflag) {
				fprintf(stderr, "prompt mode off\n");
				pflag = 0;
				break;
			}
			fprintf(stderr, "prompt mode on\n");
			pflag++;
			break;
		}
		else goto bad;
		break;
	/*
	 * Quit.
	 */
	case 'q':
		if (strncmp(cmd, "quit", strlen(cmd)) != 0)
			goto bad;
		return;
	case 'x':
		if (strncmp(cmd, "xit", strlen(cmd)) != 0)
			goto bad;
		return;
	/*
	 * Toggle verbose mode.
	 */
	case 'v':
		if (strncmp(cmd, "verbose", strlen(cmd)) != 0)
			goto bad;
		if (vflag) {
			fprintf(stderr, "verbose mode off\n");
			vflag = 0;
			break;
		}
		fprintf(stderr, "verbose mode on\n");
		vflag++;
		break;
	/*
	 * Just restore requested directory modes.
	 */
	case 's':
		if (strncmp(cmd, "setmodes", strlen(cmd)) != 0)
			goto bad;
		setdirmodes(FORCE);
		break;
	/*
	 * Print out dump header information.
	 */
	case 'w':
		if (strncmp(cmd, "what", strlen(cmd)) != 0)
			goto bad;
		printdumpinfo();
		printvolinfo();
		break;
	/*
	 * Turn on debugging.
	 */
	case 'D':
		if (strncmp(cmd, "Debug", strlen(cmd)) != 0)
			goto bad;
		if (dflag) {
			fprintf(stderr, "debugging mode off\n");
			dflag = 0;
			break;
		}
		fprintf(stderr, "debugging mode on\n");
		dflag++;
		break;
	/*
	 * Unknown command.
	 */
	default:
	bad:
		fprintf(stderr, "%s: unknown command; type ? for help\n", cmd);
		break;
	}
	goto loop;
}

/*
 * Read and parse an interactive command.
 * The first word on the line is assigned to "cmd". If
 * there are no arguments on the command line, then "curdir"
 * is returned as the argument. If there are arguments
 * on the line they are returned one at a time on each
 * successive call to getcmd. Each argument is first assigned
 * to "name". If it does not start with "/" the pathname in
 * "curdir" is prepended to it. Finally "canon" is called to
 * eliminate any embedded ".." components.
 */
static void
getcmd(char *curdir, char *cmd, char *name, int size, struct arglist *ap)
{
	char *cp;
	static char input[BUFSIZ];
	char output[BUFSIZ];
#	define rawname input	/* save space by reusing input buffer */

	/*
	 * Check to see if still processing arguments.
	 */
	if (ap->argcnt > 0)
		goto retnext;
	if (nextarg != NULL)
		goto getnext;
	/*
	 * Read a command line and trim off trailing white space.
	 */
#if HAVE_READLINE
	snprintf(input, BUFSIZ, "%s\n", rl_gets(curdir));
#else
	do	{
		if (pflag)
			fprintf(stderr, "%s:%s:%s > ", 
				__progname,
				spcl.c_filesys, 
				curdir[1] ? &curdir[1] : "/");
		else
			fprintf(stderr, "%s > ", __progname);
		(void) fflush(stderr);
		(void) fgets(input, BUFSIZ, terminal);
	} while (!feof(terminal) && input[0] == '\n');
	if (feof(terminal)) {
		(void) strcpy(cmd, "quit");
		return;
	}
#endif
	for (cp = &input[strlen(input) - 2]; *cp == ' ' || *cp == '\t'; cp--)
		/* trim off trailing white space and newline */;
	*++cp = '\0';
	/*
	 * Copy the command into "cmd".
	 */
	cp = copynext(input, cmd);
	ap->cmd = cmd;
	/*
	 * If no argument, use curdir as the default.
	 */
	if (*cp == '\0') {
		(void) strncpy(name, curdir, size);
		name[size - 1] = '\0';
		return;
	}
	nextarg = cp;
	/*
	 * Find the next argument.
	 */
getnext:
	cp = copynext(nextarg, rawname);
	if (*cp == '\0')
		nextarg = NULL;
	else
		nextarg = cp;
	/*
	 * If it is an absolute pathname, canonicalize it and return it.
	 */
	if (rawname[0] == '/') {
		canon(rawname, name, size);
	} else {
		/*
		 * For relative pathnames, prepend the current directory to
		 * it then canonicalize and return it.
		 */
		snprintf(output, sizeof(output), "%s/%s", curdir, rawname);
		canon(output, name, size);
	}
	if (glob(name, GLOB_ALTDIRFUNC, NULL, &ap->glob) < 0)
		fprintf(stderr, "%s: out of memory\n", ap->cmd);
	if (ap->glob.gl_pathc == 0)
		return;
	ap->freeglob = 1;
	ap->argcnt = ap->glob.gl_pathc;

retnext:
	strncpy(name, ap->glob.gl_pathv[ap->glob.gl_pathc - ap->argcnt], size);
	name[size - 1] = '\0';
	if (--ap->argcnt == 0) {
		ap->freeglob = 0;
		globfree(&ap->glob);
	}
#	undef rawname
}

/*
 * Strip off the next token of the input.
 */
static char *
copynext(char *input, char *output)
{
	char *cp, *bp;
	char quote;

	for (cp = input; *cp == ' ' || *cp == '\t'; cp++)
		/* skip to argument */;
	bp = output;
	while (*cp != ' ' && *cp != '\t' && *cp != '\0') {
		/*
		 * Handle back slashes.
		 */
		if (*cp == '\\') {
			if (*++cp == '\0') {
				fprintf(stderr,
					"command lines cannot be continued\n");
				continue;
			}
			*bp++ = *cp++;
			continue;
		}
		/*
		 * The usual unquoted case.
		 */
		if (*cp != '\'' && *cp != '"') {
			*bp++ = *cp++;
			continue;
		}
		/*
		 * Handle single and double quotes.
		 */
		quote = *cp++;
		while (*cp != quote && *cp != '\0')
			*bp++ = *cp++;
		if (*cp++ == '\0') {
			fprintf(stderr, "missing %c\n", quote);
			cp--;
			continue;
		}
	}
	*bp = '\0';
	return (cp);
}

/*
 * Canonicalize file names to always start with ``./'' and
 * remove any embedded "." and ".." components.
 */
void
canon(char *rawname, char *canonname, int len)
{
	char *cp, *np;

	if (strcmp(rawname, ".") == 0 || strncmp(rawname, "./", 2) == 0)
		(void) strcpy(canonname, "");
	else if (rawname[0] == '/')
		(void) strcpy(canonname, ".");
	else
		(void) strcpy(canonname, "./");
	if (strlen(canonname) + strlen(rawname) >= (unsigned)len)
		errx(1, "canonname: not enough buffer space");
		
	(void) strcat(canonname, rawname);
	/*
	 * Eliminate multiple and trailing '/'s
	 */
	for (cp = np = canonname; *np != '\0'; cp++) {
		*cp = *np++;
		while (*cp == '/' && *np == '/')
			np++;
	}
	*cp = '\0';
	if (*--cp == '/')
		*cp = '\0';
	/*
	 * Eliminate extraneous "." and ".." from pathnames.
	 */
	for (np = canonname; *np != '\0'; ) {
		np++;
		cp = np;
		while (*np != '/' && *np != '\0')
			np++;
		if (np - cp == 1 && *cp == '.') {
			cp--;
			(void) strcpy(cp, np);
			np = cp;
		}
		if (np - cp == 2 && strncmp(cp, "..", 2) == 0) {
			cp--;
			while (cp > &canonname[1] && *--cp != '/')
				/* find beginning of name */;
			(void) strcpy(cp, np);
			np = cp;
		}
	}
}

/*
 * Do an "ls" style listing of a directory
 */
static void
printlist(char *name, char *basename)
{
	struct afile *fp, *list, *listp = NULL;
	struct direct *dp;
	struct afile single;
	RST_DIR *dirp;
	int entries, len, namelen;
	char locname[MAXPATHLEN + 1];

	dp = pathsearch(name);
	if (dp == NULL || (!dflag && TSTINO(dp->d_ino, dumpmap) == 0) ||
	    (!vflag && dp->d_ino == WINO))
		return;
	if ((dirp = rst_opendir(name)) == NULL) {
		entries = 1;
		list = &single;
		mkentry(name, dp, list);
		len = strlen(basename) + 1;
		if (strlen(name) - len > (unsigned)single.len) {
			freename(single.fname);
			single.fname = savename(&name[len]);
			single.len = strlen(single.fname);
		}
	} else {
		entries = 0;
		while ((dp = rst_readdir(dirp)))
			entries++;
		rst_closedir(dirp);
		list = (struct afile *)malloc(entries * sizeof(struct afile));
		if (list == NULL) {
			fprintf(stderr, "ls: out of memory\n");
			return;
		}
		if ((dirp = rst_opendir(name)) == NULL)
			panic("directory reopen failed\n");
		fprintf(stderr, "%s:\n", name);
		entries = 0;
		listp = list;
		namelen = snprintf(locname, sizeof(locname), "%s/", name);
		if (namelen >= (int)sizeof(locname))
			namelen = sizeof(locname) - 1;
		while ((dp = rst_readdir(dirp))) {
			if (dp == NULL)
				break;
			if (!dflag && TSTINO(dp->d_ino, dumpmap) == 0)
				continue;
			if (!vflag && (dp->d_ino == WINO ||
			     strcmp(dp->d_name, ".") == 0 ||
			     strcmp(dp->d_name, "..") == 0))
				continue;
			locname[namelen] = '\0';
			if (namelen + strlen(dp->d_name) >= MAXPATHLEN) {
				fprintf(stderr, "%s%s: name exceeds %d char\n",
					locname, dp->d_name, MAXPATHLEN);
			} else {
				(void) strncat(locname, dp->d_name,
				    (int)strlen(dp->d_name));
				mkentry(locname, dp, listp++);
				entries++;
			}
		}
		rst_closedir(dirp);
		if (entries == 0) {
			fprintf(stderr, "\n");
			free(list);
			return;
		}
		qsort((char *)list, entries, sizeof(struct afile), fcmp);
	}
	formatf(list, entries);
	if (dirp != NULL) {
		for (fp = listp - 1; fp >= list; fp--)
			freename(fp->fname);
		fprintf(stderr, "\n");
		free(list);
	}
}

/*
 * Read the contents of a directory.
 */
static void
mkentry(char *name, struct direct *dp, struct afile *fp)
{
	char *cp;
	struct entry *np;

	fp->fnum = dp->d_ino;
	fp->fname = savename(dp->d_name);
	for (cp = fp->fname; *cp; cp++)
		if (!vflag && (*cp < ' ' || *cp >= 0177))
			*cp = '?';
	fp->len = cp - fp->fname;
	if (dflag && TSTINO(fp->fnum, dumpmap) == 0)
		fp->prefix = '^';
	else if ((np = lookupname(name)) != NULL && (np->e_flags & NEW))
		fp->prefix = '*';
	else
		fp->prefix = ' ';
	switch(dp->d_type) {

	default:
		fprintf(stderr, "Warning: undefined file type %d\n",
		    dp->d_type);
		/* fall through */
	case DT_REG:
		fp->postfix = ' ';
		break;

	case DT_LNK:
		fp->postfix = '@';
		break;

	case DT_FIFO:
	case DT_SOCK:
		fp->postfix = '=';
		break;

	case DT_CHR:
	case DT_BLK:
		fp->postfix = '#';
		break;

#ifndef __linux__
	/* no need for this */
	case DT_WHT:
		fp->postfix = '%';
		break;
#endif

	case DT_UNKNOWN:
	case DT_DIR:
		if (inodetype(dp->d_ino) == NODE)
			fp->postfix = '/';
		else
			fp->postfix = ' ';
		break;
	}
	return;
}

/*
 * Print out a pretty listing of a directory
 */
static void
formatf(struct afile *list, int nentry)
{
	struct afile *fp, *endlist;
	int width, bigino, haveprefix, havepostfix;
	int i, j, w, precision = 0, columns, lines;

	width = 0;
	haveprefix = 0;
	havepostfix = 0;
	bigino = ROOTINO;
	endlist = &list[nentry];
	for (fp = &list[0]; fp < endlist; fp++) {
		if (bigino < (int)fp->fnum)
			bigino = fp->fnum;
		if (width < fp->len)
			width = fp->len;
		if (fp->prefix != ' ')
			haveprefix = 1;
		if (fp->postfix != ' ')
			havepostfix = 1;
	}
	if (haveprefix)
		width++;
	if (havepostfix)
		width++;
	if (vflag) {
		for (precision = 0, i = bigino; i > 0; i /= 10)
			precision++;
		width += precision + 1;
	}
	width++;
	columns = 81 / width;
	if (columns == 0)
		columns = 1;
	lines = (nentry + columns - 1) / columns;
	for (i = 0; i < lines; i++) {
		for (j = 0; j < columns; j++) {
			fp = &list[j * lines + i];
			if (vflag) {
				fprintf(stderr, "%*ld ", precision, (long)fp->fnum);
				fp->len += precision + 1;
			}
			if (haveprefix) {
				putc(fp->prefix, stderr);
				fp->len++;
			}
			fprintf(stderr, "%s", fp->fname);
			if (havepostfix) {
				putc(fp->postfix, stderr);
				fp->len++;
			}
			if (fp + lines >= endlist) {
				fprintf(stderr, "\n");
				break;
			}
			for (w = fp->len; w < width; w++)
				putc(' ', stderr);
		}
	}
}

/*
 * Skip over directory entries that are not on the tape
 *
 * First have to get definition of a dirent.
 *
 * For Linux the dirent struct is now included from bsdcompat.h
 */
#ifndef	__linux__
#undef DIRBLKSIZ
#include <dirent.h>
#undef d_ino
#endif	/* ! __linux__ */

struct dirent *
glob_readdir(RST_DIR *dirp)
{
	struct direct *dp;
	static struct dirent adirent; 

	while ((dp = rst_readdir(dirp)) != NULL) {
		if (!vflag && dp->d_ino == WINO)
			continue;
		if (dflag || TSTINO(dp->d_ino, dumpmap))
			break;
	}
	if (dp == NULL)
		return (NULL);
	adirent.d_fileno = dp->d_ino;
	memmove(adirent.d_name, dp->d_name, dp->d_namlen + 1);
	return (&adirent);
}

/*
 * Return st_mode information in response to stat or lstat calls
 */
static int
glob_stat(const char *name, struct stat *stp)
{
	struct direct *dp;
	dp = pathsearch(name);
	if (dp == NULL || (!dflag && TSTINO(dp->d_ino, dumpmap) == 0) ||
	    (!vflag && dp->d_ino == WINO))
		return (-1);
	if (inodetype(dp->d_ino) == NODE)
		stp->st_mode = IFDIR;
	else
		stp->st_mode = IFREG;
	return (0);
}

/*
 * Comparison routine for qsort.
 */
static int
fcmp(const void *f1, const void *f2)
{
	return (strcmp(((struct afile *)f1)->fname,
	    ((struct afile *)f2)->fname));
}

/*
 * respond to interrupts
 */
void
onintr(UNUSED(int signo))
{
	int save_errno = errno;

	if (command == 'i' && runshell)
		longjmp(reset, 1);
	if (reply("restore interrupted, continue") == FAIL)
		exit(1);
	errno = save_errno;
}


#if HAVE_READLINE

#if !HAVE_READLINE_RLCM
#define rl_completion_matches completion_matches
#endif

/* A static variable for holding the line. */
static char *line_read = NULL;

static char completion_curdir[MAXPATHLEN];

static char *commands[] = { 
	"add ", "cd ", "delete ", "extract ", "help ", 
	"? ", "ls ", "pwd ", "prompt ", "quit ", "xit ", 
	"verbose ", "setmodes ", "what ", "Debug ",
	NULL };

static char *files = NULL;

static char *
rl_gets (char *dir)
{
	char *prompt;
	int sz;

	snprintf(completion_curdir, MAXPATHLEN, "%s", dir);
	completion_curdir[MAXPATHLEN - 1] = '\0';

	if (pflag) {
		sz = 6 + strlen(__progname) + strlen(spcl.c_filesys) + strlen((completion_curdir + 1 ? completion_curdir + 1 : "/"));
		prompt = (char *)malloc(sz);
		if (!prompt)
			return NULL;
		snprintf(prompt, sz, "%s:%s:%s > ", 
			__progname,
			spcl.c_filesys, 
			(completion_curdir + 1 ? completion_curdir + 1 : "/"));
	}
	else {
		sz = 4 + strlen(__progname);
		prompt = (char *)malloc(sz);
		if (!prompt)
			return NULL;
		snprintf(prompt, sz, "%s > ", __progname);
	}
	prompt[sz - 1] = '\0';

	if (line_read) {
		free (line_read);
		line_read = (char *)NULL;
	}

	do {
		line_read = readline (prompt);
	} while (line_read && !*line_read);

	free(prompt);

	if (!line_read) {
		printf("\n");
		return strdup("quit");
	}

	add_history (line_read);

	return (line_read);
}

static char *
command_generator(const char *text, int state)
{
	static int list_index, len;
	char *name;

	if (!state) {
		list_index = 0;
		len = strlen(text);
	}

	while ( (name = commands[list_index]) != NULL) {

		list_index ++;

		if (strncmp(name, text, len) == 0)
			return strdup(name);
	}

	return NULL;
}

static char *
filename_generator(const char *text, int state)
{
	static int list_index;
	char *name;
	RST_DIR *dirp;
	struct direct *dp;
	static int entries;
	char pname[MAXPATHLEN];
	char fname[MAXPATHLEN];
	char *slash;
	char ppname[MAXPATHLEN];

	if (!state) {
		list_index = 0;

		if (files != NULL) {
			free(files);
			entries = 0;
			files = NULL;
		}
		if ((slash = strrchr(text, '/')) != NULL) {
			int idx = slash - text;
			if (idx > MAXPATHLEN - 2)
				idx = MAXPATHLEN - 2;
			strncpy(ppname, text, MAXPATHLEN);
			ppname[MAXPATHLEN - 1] = '\0';
			ppname[idx] = '\0';
			if (text[0] == '/')
				snprintf(pname, MAXPATHLEN, ".%s", ppname);
			else
				snprintf(pname, MAXPATHLEN, "%s/%s", completion_curdir, ppname);
			strncpy(fname, ppname + idx + 1, MAXPATHLEN);
			ppname[idx] = '/';
			ppname[idx + 1] = '\0';
		}
		else {
			strncpy(pname, completion_curdir, MAXPATHLEN);
			strncpy(fname, text, MAXPATHLEN);
			ppname[0] = '\0';
		}
		pname[MAXPATHLEN - 1] = '\0';
		fname[MAXPATHLEN - 1] = '\0';
		if ((dirp = rst_opendir(pname)) == NULL)
			return NULL;
		entries = 0;
		while ((dp = rst_readdir(dirp)))
			entries++;
		rst_closedir(dirp);
		files = (char *)malloc(entries * MAXPATHLEN);
		if (files == NULL) {
			fprintf(stderr, "Out of memory\n");
			entries = 0;
			return NULL;
		}
		if ((dirp = rst_opendir(pname)) == NULL)
			panic("directory reopen failed\n");
		entries = 0;
		while ((dp = rst_readdir(dirp))) {
                        if (TSTINO(dp->d_ino, dumpmap) == 0)
                                continue;
			if (strcmp(dp->d_name, ".") == 0 ||
			    strcmp(dp->d_name, "..") == 0)
			    	continue;
			if (strncmp(dp->d_name, fname, strlen(fname)) == 0) {
				if (inodetype(dp->d_ino) == NODE)
					snprintf(files + entries * MAXPATHLEN, MAXPATHLEN, "%s%s/", ppname, dp->d_name);
				else
					snprintf(files + entries * MAXPATHLEN, MAXPATHLEN, "%s%s ", ppname, dp->d_name);
				*(files + (entries + 1) * MAXPATHLEN - 1) = '\0';
				++entries;
			}
                 }
                 rst_closedir(dirp);
	}

	if (list_index >= entries)
		return NULL;

	name = strdup(files + list_index * MAXPATHLEN);
	list_index ++;

	return name;
}

static char **
restore_completion (const char *text, int start, UNUSED(int end))
{
	char **matches;

	if (start == 0)
		matches = rl_completion_matches (text, command_generator);
	else
		matches = rl_completion_matches (text, filename_generator);

	return (matches);
}

static void 
initialize_readline(void) 
{
	rl_readline_name = "dump";
	rl_attempted_completion_function = restore_completion;
	rl_completion_entry_function = NULL;
#if HAVE_READLINE_CAC	/* compile with readline 2.0 */
	rl_completion_append_character = '\0';
#endif
	rl_instream = terminal;
}

#endif /* HAVE_READLINE */
