OpenSolaris_b135/cmd/backup/restore/interactive.c

Compare this file to the similar file:
Show the results in this format:

/*
 * Copyright 1998,2001-2003 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
/*	  All Rights Reserved	*/

/*
 * Copyright (c) 1985 Regents of the University of California.
 * All rights reserved.  The Berkeley software License Agreement
 * specifies the terms and conditions for redistribution.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include <setjmp.h>
#include <euc.h>
#include <widec.h>
#include "restore.h"
#include <ctype.h>
#include <limits.h>
#include <sys/wait.h>

extern eucwidth_t wp;

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

/*
 * Things to handle interruptions.
 */
static jmp_buf reset;
static int reset_OK;
static char *nextarg = NULL;

static int dontexpand;	/* co-routine state set in getnext, used in expandarg */

#ifdef __STDC__
static void getcmd(char *, char *, size_t, char *, size_t, struct arglist *);
static void expandarg(char *, struct arglist *);
static void printlist(char *, ino_t, char *, int);
static void formatf(struct arglist *);
static char *copynext(char *, char *, size_t);
static int fcmp(struct afile *, struct afile *);
static char *fmtentry(struct afile *);
static void setpagercmd(void);
static uint_t setpagerargs(char **);
#else
static void getcmd();
static void expandarg();
static void printlist();
static void formatf();
static char *copynext();
static int fcmp();
static char *fmtentry();
static void setpagercmd();
static uint_t setpagerargs();
#endif

/*
 * Read and execute commands from the terminal.
 */
void
#ifdef __STDC__
runcmdshell(void)
#else
runcmdshell()
#endif
{
	struct entry *np;
	ino_t ino;
	static struct arglist alist = { 0, 0, 0, 0, 0 };
	char curdir[MAXCOMPLEXLEN];
	char name[MAXCOMPLEXLEN];
	char cmd[BUFSIZ];

#ifdef	lint
	curdir[0] = '\0';
#endif	/* lint */

	canon("/", curdir, sizeof (curdir));
loop:
	if (setjmp(reset) != 0) {
		for (; alist.head < alist.last; alist.head++)
			freename(alist.head->fname);
		nextarg = NULL;
		volno = 0;
		goto loop;	/* make sure jmpbuf is up-to-date */
	}
	reset_OK = 1;
	getcmd(curdir, cmd, sizeof (cmd), name, sizeof (name), &alist);

	/*
	 * Using strncmp() to catch unique prefixes.
	 */
	switch (cmd[0]) {
	/*
	 * Add elements to the extraction list.
	 */
	case 'a':
		if (strncmp(cmd, "add", strlen(cmd)) != 0)
			goto bad;
		if (name[0] == '\0')
			break;
		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;
		if (name[0] == '\0')
			break;
		ino = dirlookup(name);
		if (ino == 0)
			break;
		if (inodetype(ino) == LEAF) {
			(void) fprintf(stderr,
				gettext("%s: not a directory\n"), name);
			break;
		}

		/* No need to canon(name), getcmd() did it for us */
		(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;
		if (name[0] == '\0')
			break;
		np = lookupname(name);
		if (np == NIL || (np->e_flags & NEW) == 0) {
			(void) fprintf(stderr,
				gettext("%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;
		attrscan(0, addfile);
		createfiles();
		createlinks();
		setdirmodes();
		if (dflag)
			checkrestore();
		volno = 0;
		break;
	/*
	 * List available commands.
	 */
	case 'h':
		if (strncmp(cmd, "help", strlen(cmd)) != 0)
			goto bad;
		/*FALLTHROUGH*/
	case '?':
		/* ANSI string catenation, to shut cstyle up */
		(void) fprintf(stderr, "%s",
			gettext("Available commands are:\n"
"\tls [arg] - list directory\n"
"\tmarked [arg] - list items marked for extraction from 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"
"\tpaginate - toggle pagination flag (affects ``ls'' and ``marked'')\n"
"\tsetpager - set pagination command and arguments\n"
"\thelp or `?' - print this list\n"
"If no `arg' is supplied, the current directory is used\n"));
		break;
	/*
	 * List a directory.
	 */
	case 'l':
	case 'm':
		if ((strncmp(cmd, "ls", strlen(cmd)) != 0) &&
		    (strncmp(cmd, "marked", strlen(cmd)) != 0))
			goto bad;
		if (name[0] == '\0')
			break;
		ino = dirlookup(name);
		if (ino == 0)
			break;
		printlist(name, ino, curdir, *cmd == 'm');
		break;
	/*
	 * Print current directory or enable pagination.
	 */
	case 'p':
		if (strlen(cmd) < 2)
			goto ambiguous;
		if (strncmp(cmd, "pwd", strlen(cmd)) == 0) {
			if (curdir[1] == '\0') {
				(void) fprintf(stderr, "/\n");
			} else {
				(void) fprintf(stderr, "%s\n", &curdir[1]);
			}
		} else if (strncmp(cmd, "paginate", strlen(cmd)) == 0) {
			if (paginating) {
				(void) fprintf(stderr,
				    gettext("paging disabled\n"));
				paginating = 0;
				break;
			}
			if (vflag) {
				(void) fprintf(stderr,
				    gettext("paging enabled (%s)\n"),
				    pager_catenated);
			} else {
				(void) fprintf(stderr,
				    gettext("paging enabled\n"));
			}
			if (dflag) {
				int index = 0;

				while (index < pager_len) {
					(void) fprintf(stderr,
					    ">>>pager_vector[%d] = `%s'\n",
					    index,
					    pager_vector[index] ?
						pager_vector[index] : "(null)");
					index += 1;
				}
			}
			paginating = 1;
		} else {
			goto bad;
		}
		break;
	/*
	 * Quit.
	 */
	case 'q':
		if (strncmp(cmd, "quit", strlen(cmd)) != 0)
			goto bad;
		reset_OK = 0;
		return;
	case 'x':
		if (strncmp(cmd, "xit", strlen(cmd)) != 0)
			goto bad;
		reset_OK = 0;
		return;
	/*
	 * Toggle verbose mode.
	 */
	case 'v':
		if (strncmp(cmd, "verbose", strlen(cmd)) != 0)
			goto bad;
		if (vflag) {
			(void) fprintf(stderr, gettext("verbose mode off\n"));
			vflag = 0;
			break;
		}
		(void) fprintf(stderr, gettext("verbose mode on\n"));
		vflag = 1;
		break;
	/*
	 * Just restore requested directory modes, or set pagination command.
	 */
	case 's':
		if (strlen(cmd) < 4)
			goto ambiguous;
		if (strncmp(cmd, "setmodes", strlen(cmd)) == 0) {
			setdirmodes();
		} else if (strncmp(cmd, "setpager", strlen(cmd)) == 0) {
			setpagercmd();
		} else {
			goto bad;
		}
		break;
	/*
	 * Print out dump header information.
	 */
	case 'w':
		if (strncmp(cmd, "what", strlen(cmd)) != 0)
			goto bad;
		printdumpinfo();
		break;
	/*
	 * Turn on debugging.
	 */
	case 'D':
		if (strncmp(cmd, "Debug", strlen(cmd)) != 0)
			goto bad;
		if (dflag) {
			(void) fprintf(stderr, gettext("debugging mode off\n"));
			dflag = 0;
			break;
		}
		(void) fprintf(stderr, gettext("debugging mode on\n"));
		dflag++;
		break;
	/*
	 * Unknown command.
	 */
	default:
	bad:
		(void) fprintf(stderr,
			gettext("%s: unknown command; type ? for help\n"), cmd);
		break;
	ambiguous:
		(void) fprintf(stderr,
		    gettext("%s: ambiguous command; type ? for help\n"), cmd);
		break;
	}
	goto loop;
}

static char input[MAXCOMPLEXLEN]; /* shared by getcmd() and setpagercmd() */
#define	rawname input	/* save space by reusing input buffer */

/*
 * 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.
 */
/* ARGSUSED */
static void
getcmd(curdir, cmd, cmdsiz, name, namesiz, ap)
	char *curdir, *cmd, *name;
	size_t cmdsiz, namesiz;
	struct arglist *ap;
{
	char *cp;
	char output[MAXCOMPLEXLEN];

	/*
	 * Check to see if still processing arguments.
	 */
	if (ap->head != ap->last) {
		(void) strncpy(name, ap->head->fname, namesiz);
		name[namesiz - 1] = '\0';
		/* double null terminate string */
		if ((strlen(name) + 2) > namesiz) {
			fprintf(stderr, gettext("name is too long, ignoring"));
			memset(name, 0, namesiz);
		} else {
			name[strlen(name) + 1] = '\0';
		}
		freename(ap->head->fname);
		ap->head++;
		return;
	}
	if (nextarg != NULL)
		goto getnext;
	/*
	 * Read a command line and trim off trailing white space.
	 */
readagain:
	do {
		(void) fprintf(stderr, "%s > ", progname);
		(void) fflush(stderr);
		(void) fgets(input, sizeof (input), terminal);
	} while (!feof(terminal) && input[0] == '\n');
	if (feof(terminal)) {
		(void) strncpy(cmd, "quit", cmdsiz);
		return;
	}
	/* trim off trailing white space and newline */
	for (cp = &input[strlen(input) - 2];
	    cp >= &input[0] && isspace((uchar_t)*cp);
	    cp--) {
		continue;
		/*LINTED [empty loop body]*/
	}
	*++cp = '\0';
	if ((strlen(input) + 2) > MAXCOMPLEXLEN) {
		fprintf(stderr, gettext("command is too long\n"));
		goto readagain;
	} else {
		/* double null terminate string */
		*(cp + 1) = '\0';
	}

	if (cp == &input[0])
		goto readagain;

	/*
	 * Copy the command into "cmd".
	 */
	cp = copynext(input, cmd, cmdsiz);
	ap->cmd = cmd;
	/*
	 * If no argument, use curdir as the default.
	 */
	if (*cp == '\0') {
		(void) strncpy(name, curdir, namesiz);
		name[namesiz - 1] = '\0';
		/* double null terminate string */
		if ((strlen(name) + 2) > namesiz) {
			fprintf(stderr, gettext("name is too long, ignoring"));
			memset(name, 0, namesiz);
		} else {
			name[strlen(name) + 1] = '\0';
		}
		return;
	}
	nextarg = cp;
	/*
	 * Find the next argument.
	 */
getnext:
	cp = copynext(nextarg, rawname, sizeof (rawname));
	if (*cp == '\0')
		nextarg = NULL;
	else
		nextarg = cp;
	/*
	 * If it an absolute pathname, canonicalize it and return it.
	 */
	if (rawname[0] == '/') {
		canon(rawname, name, namesiz);
	} else {
		/*
		 * For relative pathnames, prepend the current directory to
		 * it then canonicalize and return it.
		 */
		(void) snprintf(output, sizeof (output), "%s/%s",
		    curdir, rawname);
		canon(output, name, namesiz);
	}
	expandarg(name, ap);
	/*
	 * ap->head->fname guaranteed to be double null-terminated and
	 * no more than MAXCOMPLEXLEN characters long.
	 */
	assert(namesiz >= (MAXCOMPLEXLEN));
	(void) strcpy(name, ap->head->fname);
	/* double null terminate string */
	name[strlen(name) + 1] = '\0';
	freename(ap->head->fname);
	ap->head++;
#undef	rawname
}

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

	dontexpand = 0;
	/* skip to argument */
	for (cp = input; *cp != '\0' && isspace((uchar_t)*cp); cp++) {
		continue;
		/*LINTED [empty loop body]*/
	}
	bp = output;
	limit = output + outsize - 1; /* -1 for the trailing \0 */
	while (!isspace((uchar_t)*cp) && *cp != '\0' && bp < limit) {
		/*
		 * Handle back slashes.
		 */
		if (*cp == '\\') {
			if (*++cp == '\0') {
				(void) fprintf(stderr, gettext(
				    "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++;
		dontexpand = 1;
		while (*cp != quote && *cp != '\0' && bp < limit)
			*bp++ = *cp++;
		if (*cp++ == '\0') {
			(void) fprintf(stderr,
			    gettext("missing %c\n"), (uchar_t)quote);
			cp--;
			continue;
		}
	}
	*bp = '\0';
	if ((strlen(output) + 2) > outsize) {
		fprintf(stderr, gettext(
		    "name is too long, ignoring"));
		memset(output, 0, outsize);
	} else {
		/* double null terminate string */
		*(bp + 1) = '\0';
	}
	return (cp);
}

/*
 * Canonicalize file names to always start with ``./'' and
 * remove any imbedded "." and ".." components.
 *
 * The pathname "canonname" is returned double null terminated.
 */
void
canon(rawname, canonname, limit)
	char *rawname, *canonname;
	size_t limit;
{
	char *cp, *np, *prefix;
	uint_t len;

	assert(limit > 3);
	if (strcmp(rawname, ".") == 0 || strncmp(rawname, "./", 2) == 0)
		prefix = "";
	else if (rawname[0] == '/')
		prefix = ".";
	else
		prefix = "./";
	(void) snprintf(canonname, limit, "%s%s", prefix, rawname);
	/*
	 * Eliminate multiple and trailing '/'s
	 */
	for (cp = np = canonname; *np != '\0'; cp++) {
		*cp = *np++;
		while (*cp == '/' && *np == '/')
			np++;
	}
	*cp = '\0';
	if ((strlen(canonname) + 2) > limit) {
		fprintf(stderr,
		    gettext("canonical name is too long, ignoring name\n"));
		memset(canonname, 0, limit);
	} else {
		/* double null terminate string */
		*(cp + 1) = '\0';
	}

	if (*--cp == '/')
		*cp = '\0';
	/*
	 * Eliminate extraneous "." and ".." from pathnames.  Uses
	 * memmove(), as strcpy() might do the wrong thing for these
	 * small overlaps.
	 */
	np = canonname;
	while (*np != '\0') {
		np++;
		cp = np;
		while (*np != '/' && *np != '\0')
			np++;
		if (np - cp == 1 && *cp == '.') {
			cp--;
			len = strlen(np);
			(void) memmove(cp, np, len);
			*(cp + len) = '\0';
			/* double null terminate string */
			*(cp + len + 1) = '\0';
			np = cp;
		}
		if (np - cp == 2 && strncmp(cp, "..", 2) == 0) {
			cp--;
			/* find beginning of name */
			while (cp > &canonname[1] && *--cp != '/') {
				continue;
				/*LINTED [empty loop body]*/
			}
			len = strlen(np);
			(void) memmove(cp, np, len);
			*(cp + len) = '\0';
			/* double null terminate string */
			*(cp + len + 1) = '\0';
			np = cp;
		}
	}
}

/*
 * globals (file name generation)
 *
 * "*" in params matches r.e ".*"
 * "?" in params matches r.e. "."
 * "[...]" in params matches character class
 * "[...a-z...]" in params matches a through z.
 */
static void
expandarg(arg, ap)
	char *arg;
	struct arglist *ap;
{
	static struct afile single;
	int size;

	ap->head = ap->last = (struct afile *)0;
	if (dontexpand)
		size = 0;
	else
		size = expand(arg, 0, ap);
	if (size == 0) {
		struct entry *ep;

		ep = lookupname(arg);
		single.fnum = ep ? ep->e_ino : 0;
		single.fname = savename(arg);
		ap->head = &single;
		ap->last = ap->head + 1;
		return;
	}
	if ((ap->last - ap->head) > ULONG_MAX) {
		(void) fprintf(stderr,
		    gettext("Argument expansion too large to sort\n"));
	} else {
		/* LINTED pointer arith just range-checked */
		qsort((char *)ap->head, (size_t)(ap->last - ap->head),
		    sizeof (*ap->head),
		    (int (*)(const void *, const void *)) fcmp);
	}
}

/*
 * Do an "ls" style listing of a directory
 */
static void
printlist(name, ino, basename, marked_only)
	char *name;
	ino_t ino;
	char *basename;
	int marked_only;
{
	struct afile *fp;
	struct direct *dp;
	static struct arglist alist = { 0, 0, 0, 0, "ls" };
	struct afile single;
	struct entry *np;
	RST_DIR *dirp;
	int list_entry;

	if ((dirp = rst_opendir(name)) == NULL) {
		single.fnum = ino;
		if (strncmp(name, basename, strlen(basename)) == 0)
			single.fname = savename(name + strlen(basename) + 1);
		else
			single.fname = savename(name);
		alist.head = &single;
		alist.last = alist.head + 1;
		if (alist.base != NULL) {
			free(alist.base);
			alist.base = NULL;
		}
	} else {
		alist.head = (struct afile *)0;
		(void) fprintf(stderr, "%s:\n", name);
		while (dp = rst_readdir(dirp)) {
			if (dp == NULL || dp->d_ino == 0) {
				rst_closedir(dirp);
				dirp = NULL;
				break;
			}
			if (!dflag && BIT(dp->d_ino, dumpmap) == 0)
				continue;
			if (vflag == 0 &&
			    (strcmp(dp->d_name, ".") == 0 ||
			    strcmp(dp->d_name, "..") == 0))
				continue;
			list_entry = 1;
			if (marked_only) {
				np = lookupino(dp->d_ino);
				if ((np == NIL) || ((np->e_flags & NEW) == 0))
					list_entry = 0;
			}
			if (list_entry) {
				if (!mkentry(dp->d_name, dp->d_ino, &alist)) {
					rst_closedir(dirp);
					return;
				}
			}
		}
	}
	if (alist.head != 0) {
		if ((alist.last - alist.head) > ULONG_MAX) {
			(void) fprintf(stderr,
			    gettext("Directory too large to sort\n"));
		} else {
			qsort((char *)alist.head,
			    /* LINTED range-checked */
			    (size_t)(alist.last - alist.head),
			    sizeof (*alist.head),
			    (int (*)(const void *, const void *)) fcmp);
		}
		formatf(&alist);
		for (fp = alist.head; fp < alist.last; fp++)
			freename(fp->fname);
		alist.head = NULL;
		/*
		 * Don't free alist.base, as we'll probably be called
		 * again, and might as well re-use what we've got.
		 */
	}
	if (dirp != NULL) {
		(void) fprintf(stderr, "\n");
		rst_closedir(dirp);
	}
}

/*
 * Print out a pretty listing of a directory
 */
static void
formatf(ap)
	struct arglist *ap;
{
	struct afile *fp;
	struct entry *np;
	/* LINTED: result fits into an int */
	int nentry = (int)(ap->last - ap->head);
	int i, j;
	uint_t len, w, width = 0, columns, lines;
	char *cp;
	FILE *output = stderr;

	if (ap->head == ap->last)
		return;

	if (paginating) {
		int fds[2];

		if (pipe(fds) < 0) {
			perror(gettext("could not create pipe"));
			goto no_page;
		}

		switch (fork()) {
		case -1:
			perror(gettext("could not fork"));
			goto no_page;
		case 0:
			/*
			 * Make sure final output still ends up in
			 * the same place.
			 */
			(void) dup2(fileno(stderr), fileno(stdout));
			(void) close(fds[0]);
			(void) dup2(fds[1], fileno(stdin));
			execvp(pager_vector[0], pager_vector);
			perror(gettext("execvp of pager failed"));
			exit(1);
			/*NOTREACHED*/
		default:
			(void) close(fds[1]);
			output = fdopen(fds[0], "w");
			if (output != (FILE *)NULL) {
				break;
			}
			perror(gettext("could not open pipe to pager"));
			output = stderr;
		no_page:
			(void) fprintf(stderr,
			    gettext("pagination disabled\n"));
			paginating = 0;
		}
	}

	for (fp = ap->head; fp < ap->last; fp++) {
		fp->ftype = inodetype(fp->fnum);
		np = lookupino(fp->fnum);
		if (np != NIL)
			fp->fflags = np->e_flags;
		else
			fp->fflags = 0;
		len = strlen(fmtentry(fp));
		if (len > width)
			width = len;
	}
	width += 2;
	columns = 80 / width;
	if (columns == 0)
		columns = 1;
	lines = (nentry + columns - 1) / columns;
	for (i = 0; i < lines && !ferror(output); i++) {
		for (j = 0; j < columns && !ferror(output); j++) {
			fp = ap->head + j * lines + i;
			cp = fmtentry(fp);
			(void) fprintf(output, "%s", cp);
			if (fp + lines >= ap->last) {
				(void) fprintf(output, "\n");
				break;
			}
			w = strlen(cp);
			while (w < width) {
				w++;
				if (fprintf(output, " ") < 0)
					break;
			}
		}
	}

	if (paginating) {
		(void) fclose(output);
		(void) wait((int *)NULL);
	}
}

/*
 * Comparison routine for qsort.
 */
static int
fcmp(f1, f2)
	struct afile *f1, *f2;
{

	return (strcoll(f1->fname, f2->fname));
}

/*
 * Format a directory entry.
 */
static char *
fmtentry(fp)
	struct afile *fp;
{
	static char fmtres[MAXCOMPLEXLEN];
	static int precision = 0;
	ino_t i;
	char *cp, *dp, *limit;

	if (!vflag) {
		/* MAXCOMPLEXLEN assumed to be >= 1 */
		fmtres[0] = '\0';
	} else {
		if (precision == 0) {
			for (i = maxino; i != 0; i /= 10)
				precision++;
			if (sizeof (fmtres) < (unsigned)(precision + 2)) {
				(void) fprintf(stderr, gettext(
"\nInternal check failed, minimum width %d exceeds available size %d\n"),
				    (precision + 2), sizeof (fmtres));
				done(1);
			}
		}
		(void) snprintf(fmtres, sizeof (fmtres), "%*ld ",
		    precision, fp->fnum);
	}
	dp = &fmtres[strlen(fmtres)];
	limit = fmtres + sizeof (fmtres) - 1;
	if (dflag && BIT(fp->fnum, dumpmap) == 0)
		*dp++ = '^';
	else if ((fp->fflags & NEW) != 0)
		*dp++ = '*';
	else
		*dp++ = ' ';
	for (cp = fp->fname; *cp && dp < limit; cp++)
		/* LINTED: precedence ok, can't fix system macro */
		if (!vflag && (!ISPRINT(*cp, wp)))
			*dp++ = '?';
		else
			*dp++ = *cp;
	if (fp->ftype == NODE && dp < limit)
		*dp++ = '/';
	*dp++ = 0;
	return (fmtres);
}

/*
 * respond to interrupts
 */
/* ARGSUSED */
void
onintr(sig)
	int	sig;
{
	char	buf[300];

	if (command == 'i' && reset_OK)
		longjmp(reset, 1);

	(void) snprintf(buf, sizeof (buf),
	    gettext("%s interrupted, continue"), progname);
	if (reply(buf) == FAIL)
		done(1);
}
/*
 * Set up pager_catenated and pager_vector.
 */
void
#ifdef __STDC__
initpagercmd(void)
#else
initpagercmd()
#endif
{
	char *cp;

	cp = getenv("PAGER");
	if (cp != NULL)
		pager_catenated = strdup(cp);
	if ((pager_catenated == NULL) || (*pager_catenated == '\0')) {
		if (pager_catenated != NULL)
			free(pager_catenated);
		pager_catenated = strdup(DEF_PAGER);
	}
	if (pager_catenated == NULL) {
		(void) fprintf(stderr, gettext("out of memory\n"));
		done(1);
	}

	pager_vector = (char **)malloc(sizeof (char *));
	if (pager_vector == NULL) {
		(void) fprintf(stderr, gettext("out of memory\n"));
		done(1);
	}

	pager_len = 1;
	cp = pager_catenated;
	(void) setpagerargs(&cp);
}


/*
 * Resets pager_catenated and pager_vector from user input.
 */
void
#ifdef __STDC__
setpagercmd(void)
#else
setpagercmd()
#endif
{
	uint_t catenate_length;
	int index;

	/*
	 * We'll get called immediately after setting a pager, due to
	 * our interaction with getcmd()'s internal state.  Don't do
	 * anything when that happens.
	 */
	if (*input == '\0')
		return;

	if (pager_len > 0) {
		for (index = 0; pager_vector[index] != (char *)NULL; index += 1)
			free(pager_vector[index]);
		free(pager_vector);
		free(pager_catenated);
	}

	pager_vector = (char **)malloc(2 * sizeof (char *));
	if (pager_vector == NULL) {
		(void) fprintf(stderr, gettext("out of memory\n"));
		done(1);
	}

	pager_len = 2;
	pager_vector[0] = strdup(input);
	if (pager_vector[0] == NULL) {
		(void) fprintf(stderr, gettext("out of memory\n"));
		done(1);
	}
	if (dflag)
		(void) fprintf(stderr, gettext("got command `%s'\n"), input);
	catenate_length = setpagerargs(&nextarg) + strlen(pager_vector[0]) + 1;
	pager_catenated = (char *)malloc(catenate_length *
		(size_t)sizeof (char));
	if (pager_catenated == (char *)NULL) {
		(void) fprintf(stderr, gettext("out of memory\n"));
		done(1);
	}
	for (index = 0; pager_vector[index] != (char *)NULL; index += 1) {
		if (index > 0)
			(void) strcat(pager_catenated, " ");
		(void) strcat(pager_catenated, pager_vector[index]);
	}
}


/*
 * Extract arguments for the pager command from getcmd()'s input buffer.
 */
static uint_t
setpagerargs(source)
	char	**source;
{
	char	word[MAXCOMPLEXLEN];
	char	*cp = *source;
	uint_t	length = 0;

	while ((cp != (char *)NULL) && (*cp != '\0')) {
		cp = copynext(cp, word, sizeof (word));
		if (dflag)
			fprintf(stderr, gettext("got word `%s'\n"), word);
		pager_vector = (char **)realloc(pager_vector,
			(size_t)sizeof (char *) * (pager_len + 1));
		if (pager_vector == (char **)NULL) {
			(void) fprintf(stderr, gettext("out of memory\n"));
			done(1);
		}
		pager_vector[pager_len - 1] = strdup(word);
		if (pager_vector[pager_len - 1] == (char *)NULL) {
			(void) fprintf(stderr, gettext("out of memory\n"));
			done(1);
		}
		length += strlen(word) + 1;
		pager_len += 1;
	}
	pager_vector[pager_len - 1] = (char *)NULL;
	*source = cp;
	return (length);
}