Home | History | Annotate | Line # | Download | only in restore
interactive.c revision 1.18
      1 /*	$NetBSD: interactive.c,v 1.18 2001/02/19 22:56:22 cgd Exp $	*/
      2 
      3 /*
      4  * Copyright (c) 1985, 1993
      5  *	The Regents of the University of California.  All rights reserved.
      6  *
      7  * Redistribution and use in source and binary forms, with or without
      8  * modification, are permitted provided that the following conditions
      9  * are met:
     10  * 1. Redistributions of source code must retain the above copyright
     11  *    notice, this list of conditions and the following disclaimer.
     12  * 2. Redistributions in binary form must reproduce the above copyright
     13  *    notice, this list of conditions and the following disclaimer in the
     14  *    documentation and/or other materials provided with the distribution.
     15  * 3. All advertising materials mentioning features or use of this software
     16  *    must display the following acknowledgement:
     17  *	This product includes software developed by the University of
     18  *	California, Berkeley and its contributors.
     19  * 4. Neither the name of the University nor the names of its contributors
     20  *    may be used to endorse or promote products derived from this software
     21  *    without specific prior written permission.
     22  *
     23  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
     24  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     25  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     26  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
     27  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     28  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
     29  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     30  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     31  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     32  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     33  * SUCH DAMAGE.
     34  */
     35 
     36 #include <sys/cdefs.h>
     37 #ifndef lint
     38 #if 0
     39 static char sccsid[] = "@(#)interactive.c	8.5 (Berkeley) 5/1/95";
     40 #else
     41 __RCSID("$NetBSD: interactive.c,v 1.18 2001/02/19 22:56:22 cgd Exp $");
     42 #endif
     43 #endif /* not lint */
     44 
     45 #include <sys/param.h>
     46 #include <sys/time.h>
     47 #include <sys/stat.h>
     48 
     49 #include <ufs/ufs/dinode.h>
     50 #include <ufs/ufs/dir.h>
     51 #include <ufs/ffs/fs.h>
     52 #include <protocols/dumprestore.h>
     53 
     54 #include <setjmp.h>
     55 #include <glob.h>
     56 #include <stdio.h>
     57 #include <stdlib.h>
     58 #include <string.h>
     59 
     60 #include "restore.h"
     61 #include "extern.h"
     62 
     63 #define round(a, b) (((a) + (b) - 1) / (b) * (b))
     64 
     65 /*
     66  * Things to handle interruptions.
     67  */
     68 static int runshell;
     69 static jmp_buf reset;
     70 static char *nextarg = NULL;
     71 
     72 /*
     73  * Structure and routines associated with listing directories.
     74  */
     75 struct afile {
     76 	ino_t	fnum;		/* inode number of file */
     77 	char	*fname;		/* file name */
     78 	short	len;		/* name length */
     79 	char	prefix;		/* prefix character */
     80 	char	postfix;	/* postfix character */
     81 };
     82 struct arglist {
     83 	int	freeglob;	/* glob structure needs to be freed */
     84 	int	argcnt;		/* next globbed argument to return */
     85 	glob_t	glob;		/* globbing information */
     86 	char	*cmd;		/* the current command */
     87 };
     88 
     89 static char	*copynext __P((char *, char *));
     90 static int	 fcmp __P((const void *, const void *));
     91 static void	 formatf __P((struct afile *, int));
     92 static void	 getcmd __P((char *, char *, char *, struct arglist *));
     93 struct dirent	*glob_readdir __P((RST_DIR *dirp));
     94 static int	 glob_stat __P((const char *, struct stat *));
     95 static void	 mkentry __P((char *, struct direct *, struct afile *));
     96 static void	 printlist __P((char *, char *));
     97 
     98 /*
     99  * Read and execute commands from the terminal.
    100  */
    101 void
    102 runcmdshell()
    103 {
    104 	struct entry *np;
    105 	ino_t ino;
    106 	struct arglist arglist;
    107 	char curdir[MAXPATHLEN];
    108 	char name[MAXPATHLEN];
    109 	char cmd[BUFSIZ];
    110 
    111 	arglist.freeglob = 0;
    112 	arglist.argcnt = 0;
    113 	arglist.glob.gl_flags = GLOB_ALTDIRFUNC;
    114 	arglist.glob.gl_opendir = (void *)rst_opendir;
    115 	arglist.glob.gl_readdir = (void *)glob_readdir;
    116 	arglist.glob.gl_closedir = (void *)rst_closedir;
    117 	arglist.glob.gl_lstat = glob_stat;
    118 	arglist.glob.gl_stat = glob_stat;
    119 	canon("/", curdir);
    120 loop:
    121 	if (setjmp(reset) != 0) {
    122 		if (arglist.freeglob != 0) {
    123 			arglist.freeglob = 0;
    124 			arglist.argcnt = 0;
    125 			globfree(&arglist.glob);
    126 		}
    127 		nextarg = NULL;
    128 		volno = 0;
    129 	}
    130 	runshell = 1;
    131 	getcmd(curdir, cmd, name, &arglist);
    132 	switch (cmd[0]) {
    133 	/*
    134 	 * Add elements to the extraction list.
    135 	 */
    136 	case 'a':
    137 		if (strncmp(cmd, "add", strlen(cmd)) != 0)
    138 			goto bad;
    139 		ino = dirlookup(name);
    140 		if (ino == 0)
    141 			break;
    142 		if (mflag)
    143 			pathcheck(name);
    144 		treescan(name, ino, addfile);
    145 		break;
    146 	/*
    147 	 * Change working directory.
    148 	 */
    149 	case 'c':
    150 		if (strncmp(cmd, "cd", strlen(cmd)) != 0)
    151 			goto bad;
    152 		ino = dirlookup(name);
    153 		if (ino == 0)
    154 			break;
    155 		if (inodetype(ino) == LEAF) {
    156 			fprintf(stderr, "%s: not a directory\n", name);
    157 			break;
    158 		}
    159 		(void) strcpy(curdir, name);
    160 		break;
    161 	/*
    162 	 * Delete elements from the extraction list.
    163 	 */
    164 	case 'd':
    165 		if (strncmp(cmd, "delete", strlen(cmd)) != 0)
    166 			goto bad;
    167 		np = lookupname(name);
    168 		if (np == NULL || (np->e_flags & NEW) == 0) {
    169 			fprintf(stderr, "%s: not on extraction list\n", name);
    170 			break;
    171 		}
    172 		treescan(name, np->e_ino, deletefile);
    173 		break;
    174 	/*
    175 	 * Extract the requested list.
    176 	 */
    177 	case 'e':
    178 		if (strncmp(cmd, "extract", strlen(cmd)) != 0)
    179 			goto bad;
    180 		createfiles();
    181 		createlinks();
    182 		setdirmodes(0);
    183 		if (dflag)
    184 			checkrestore();
    185 		volno = 0;
    186 		break;
    187 	/*
    188 	 * List available commands.
    189 	 */
    190 	case 'h':
    191 		if (strncmp(cmd, "help", strlen(cmd)) != 0)
    192 			goto bad;
    193 	case '?':
    194 		fprintf(stderr, "%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s",
    195 			"Available commands are:\n",
    196 			"\tls [arg] - list directory\n",
    197 			"\tcd arg - change directory\n",
    198 			"\tpwd - print current directory\n",
    199 			"\tadd [arg] - add `arg' to list of",
    200 			" files to be extracted\n",
    201 			"\tdelete [arg] - delete `arg' from",
    202 			" list of files to be extracted\n",
    203 			"\textract - extract requested files\n",
    204 			"\tsetmodes - set modes of requested directories\n",
    205 			"\tquit or xit - immediately exit program\n",
    206 			"\twhat - list dump header information\n",
    207 			"\tverbose - toggle verbose flag",
    208 			" (useful with ``ls'')\n",
    209 			"\thelp or `?' - print this list\n",
    210 			"\tDebug - turn on debugging\n",
    211 			"If no `arg' is supplied, the current",
    212 			" directory is used\n");
    213 		break;
    214 	/*
    215 	 * List a directory.
    216 	 */
    217 	case 'l':
    218 		if (strncmp(cmd, "ls", strlen(cmd)) != 0)
    219 			goto bad;
    220 		printlist(name, curdir);
    221 		break;
    222 	/*
    223 	 * Print current directory.
    224 	 */
    225 	case 'p':
    226 		if (strncmp(cmd, "pwd", strlen(cmd)) != 0)
    227 			goto bad;
    228 		if (curdir[1] == '\0')
    229 			fprintf(stderr, "/\n");
    230 		else
    231 			fprintf(stderr, "%s\n", &curdir[1]);
    232 		break;
    233 	/*
    234 	 * Quit.
    235 	 */
    236 	case 'q':
    237 		if (strncmp(cmd, "quit", strlen(cmd)) != 0)
    238 			goto bad;
    239 		return;
    240 	case 'x':
    241 		if (strncmp(cmd, "xit", strlen(cmd)) != 0)
    242 			goto bad;
    243 		return;
    244 	/*
    245 	 * Toggle verbose mode.
    246 	 */
    247 	case 'v':
    248 		if (strncmp(cmd, "verbose", strlen(cmd)) != 0)
    249 			goto bad;
    250 		if (vflag) {
    251 			fprintf(stderr, "verbose mode off\n");
    252 			vflag = 0;
    253 			break;
    254 		}
    255 		fprintf(stderr, "verbose mode on\n");
    256 		vflag++;
    257 		break;
    258 	/*
    259 	 * Just restore requested directory modes.
    260 	 */
    261 	case 's':
    262 		if (strncmp(cmd, "setmodes", strlen(cmd)) != 0)
    263 			goto bad;
    264 		setdirmodes(FORCE);
    265 		break;
    266 	/*
    267 	 * Print out dump header information.
    268 	 */
    269 	case 'w':
    270 		if (strncmp(cmd, "what", strlen(cmd)) != 0)
    271 			goto bad;
    272 		printdumpinfo();
    273 		break;
    274 	/*
    275 	 * Turn on debugging.
    276 	 */
    277 	case 'D':
    278 		if (strncmp(cmd, "Debug", strlen(cmd)) != 0)
    279 			goto bad;
    280 		if (dflag) {
    281 			fprintf(stderr, "debugging mode off\n");
    282 			dflag = 0;
    283 			break;
    284 		}
    285 		fprintf(stderr, "debugging mode on\n");
    286 		dflag++;
    287 		break;
    288 	/*
    289 	 * Unknown command.
    290 	 */
    291 	default:
    292 	bad:
    293 		fprintf(stderr, "%s: unknown command; type ? for help\n", cmd);
    294 		break;
    295 	}
    296 	goto loop;
    297 }
    298 
    299 /*
    300  * Read and parse an interactive command.
    301  * The first word on the line is assigned to "cmd". If
    302  * there are no arguments on the command line, then "curdir"
    303  * is returned as the argument. If there are arguments
    304  * on the line they are returned one at a time on each
    305  * successive call to getcmd. Each argument is first assigned
    306  * to "name". If it does not start with "/" the pathname in
    307  * "curdir" is prepended to it. Finally "canon" is called to
    308  * eliminate any embedded ".." components.
    309  */
    310 static void
    311 getcmd(curdir, cmd, name, ap)
    312 	char *curdir, *cmd, *name;
    313 	struct arglist *ap;
    314 {
    315 	char *cp;
    316 	static char input[BUFSIZ];
    317 	char output[BUFSIZ];
    318 	int globretval;
    319 #	define rawname input	/* save space by reusing input buffer */
    320 
    321 	/*
    322 	 * Check to see if still processing arguments.
    323 	 */
    324 	if (ap->argcnt > 0)
    325 		goto retnext;
    326 	if (nextarg != NULL)
    327 		goto getnext;
    328 	/*
    329 	 * Read a command line and trim off trailing white space.
    330 	 */
    331 	do	{
    332 		fprintf(stderr, "%s > ", getprogname());
    333 		(void) fflush(stderr);
    334 		(void) fgets(input, BUFSIZ, terminal);
    335 	} while (!feof(terminal) && input[0] == '\n');
    336 	if (feof(terminal)) {
    337 		(void) strcpy(cmd, "quit");
    338 		return;
    339 	}
    340 	for (cp = &input[strlen(input) - 2]; *cp == ' ' || *cp == '\t'; cp--)
    341 		/* trim off trailing white space and newline */;
    342 	*++cp = '\0';
    343 	/*
    344 	 * Copy the command into "cmd".
    345 	 */
    346 	cp = copynext(input, cmd);
    347 	ap->cmd = cmd;
    348 	/*
    349 	 * If no argument, use curdir as the default.
    350 	 */
    351 	if (*cp == '\0') {
    352 		(void) strcpy(name, curdir);
    353 		return;
    354 	}
    355 	nextarg = cp;
    356 	/*
    357 	 * Find the next argument.
    358 	 */
    359 getnext:
    360 	cp = copynext(nextarg, rawname);
    361 	if (*cp == '\0')
    362 		nextarg = NULL;
    363 	else
    364 		nextarg = cp;
    365 	/*
    366 	 * If it is an absolute pathname, canonicalize it and return it.
    367 	 */
    368 	if (rawname[0] == '/') {
    369 		canon(rawname, name);
    370 	} else {
    371 		/*
    372 		 * For relative pathnames, prepend the current directory to
    373 		 * it then canonicalize and return it.
    374 		 */
    375 		(void) strcpy(output, curdir);
    376 		(void) strcat(output, "/");
    377 		(void) strcat(output, rawname);
    378 		canon(output, name);
    379 	}
    380 	if ((globretval = glob(name, GLOB_ALTDIRFUNC, NULL, &ap->glob)) < 0) {
    381 		fprintf(stderr, "%s: %s: ", ap->cmd, name);
    382 		switch (globretval) {
    383 		case GLOB_NOSPACE:
    384 			fprintf(stderr, "out of memory\n");
    385 			break;
    386 		case GLOB_NOMATCH:
    387 			fprintf(stderr, "no filename match.\n");
    388 			break;
    389 		case GLOB_ABORTED:
    390 			fprintf(stderr, "glob() aborted.\n");
    391 			break;
    392 		default:
    393 			fprintf(stderr, "unknown error!\n");
    394 			break;
    395 		}
    396 	}
    397 	if (ap->glob.gl_pathc == 0)
    398 		return;
    399 	ap->freeglob = 1;
    400 	ap->argcnt = ap->glob.gl_pathc;
    401 
    402 retnext:
    403 	strcpy(name, ap->glob.gl_pathv[ap->glob.gl_pathc - ap->argcnt]);
    404 	if (--ap->argcnt == 0) {
    405 		ap->freeglob = 0;
    406 		globfree(&ap->glob);
    407 	}
    408 #	undef rawname
    409 }
    410 
    411 /*
    412  * Strip off the next token of the input.
    413  */
    414 static char *
    415 copynext(input, output)
    416 	char *input, *output;
    417 {
    418 	char *cp, *bp;
    419 	char quote;
    420 
    421 	for (cp = input; *cp == ' ' || *cp == '\t'; cp++)
    422 		/* skip to argument */;
    423 	bp = output;
    424 	while (*cp != ' ' && *cp != '\t' && *cp != '\0') {
    425 		/*
    426 		 * Handle back slashes.
    427 		 */
    428 		if (*cp == '\\') {
    429 			if (*++cp == '\0') {
    430 				fprintf(stderr,
    431 					"command lines cannot be continued\n");
    432 				continue;
    433 			}
    434 			*bp++ = *cp++;
    435 			continue;
    436 		}
    437 		/*
    438 		 * The usual unquoted case.
    439 		 */
    440 		if (*cp != '\'' && *cp != '"') {
    441 			*bp++ = *cp++;
    442 			continue;
    443 		}
    444 		/*
    445 		 * Handle single and double quotes.
    446 		 */
    447 		quote = *cp++;
    448 		while (*cp != quote && *cp != '\0')
    449 			*bp++ = *cp++;
    450 		if (*cp++ == '\0') {
    451 			fprintf(stderr, "missing %c\n", quote);
    452 			cp--;
    453 			continue;
    454 		}
    455 	}
    456 	*bp = '\0';
    457 	return (cp);
    458 }
    459 
    460 /*
    461  * Canonicalize file names to always start with ``./'' and
    462  * remove any imbedded "." and ".." components.
    463  */
    464 void
    465 canon(rawname, canonname)
    466 	char *rawname, *canonname;
    467 {
    468 	char *cp, *np;
    469 
    470 	if (strcmp(rawname, ".") == 0 || strncmp(rawname, "./", 2) == 0)
    471 		(void) strcpy(canonname, "");
    472 	else if (rawname[0] == '/')
    473 		(void) strcpy(canonname, ".");
    474 	else
    475 		(void) strcpy(canonname, "./");
    476 	(void) strcat(canonname, rawname);
    477 	/*
    478 	 * Eliminate multiple and trailing '/'s
    479 	 */
    480 	for (cp = np = canonname; *np != '\0'; cp++) {
    481 		*cp = *np++;
    482 		while (*cp == '/' && *np == '/')
    483 			np++;
    484 	}
    485 	*cp = '\0';
    486 	if (*--cp == '/')
    487 		*cp = '\0';
    488 	/*
    489 	 * Eliminate extraneous "." and ".." from pathnames.
    490 	 */
    491 	for (np = canonname; *np != '\0'; ) {
    492 		np++;
    493 		cp = np;
    494 		while (*np != '/' && *np != '\0')
    495 			np++;
    496 		if (np - cp == 1 && *cp == '.') {
    497 			cp--;
    498 			(void) strcpy(cp, np);
    499 			np = cp;
    500 		}
    501 		if (np - cp == 2 && strncmp(cp, "..", 2) == 0) {
    502 			cp--;
    503 			while (cp > &canonname[1] && *--cp != '/')
    504 				/* find beginning of name */;
    505 			(void) strcpy(cp, np);
    506 			np = cp;
    507 		}
    508 	}
    509 }
    510 
    511 /*
    512  * Do an "ls" style listing of a directory
    513  */
    514 static void
    515 printlist(name, basename)
    516 	char *name;
    517 	char *basename;
    518 {
    519 	struct afile *fp, *list, *listp;
    520 	struct direct *dp;
    521 	struct afile single;
    522 	RST_DIR *dirp;
    523 	int entries, len, namelen;
    524 	char locname[MAXPATHLEN + 1];
    525 
    526 	dp = pathsearch(name);
    527 	listp = NULL;
    528 	if (dp == NULL || (!dflag && TSTINO(dp->d_ino, dumpmap) == 0) ||
    529 	    (!vflag && dp->d_ino == WINO))
    530 		return;
    531 	if ((dirp = rst_opendir(name)) == NULL) {
    532 		entries = 1;
    533 		list = &single;
    534 		mkentry(name, dp, list);
    535 		len = strlen(basename) + 1;
    536 		if (strlen(name) - len > single.len) {
    537 			freename(single.fname);
    538 			single.fname = savename(&name[len]);
    539 			single.len = strlen(single.fname);
    540 		}
    541 	} else {
    542 		entries = 0;
    543 		while ((dp = rst_readdir(dirp)) != NULL)
    544 			entries++;
    545 		rst_closedir(dirp);
    546 		list = (struct afile *)malloc(entries * sizeof(struct afile));
    547 		if (list == NULL) {
    548 			fprintf(stderr, "ls: out of memory\n");
    549 			return;
    550 		}
    551 		if ((dirp = rst_opendir(name)) == NULL)
    552 			panic("directory reopen failed\n");
    553 		fprintf(stderr, "%s:\n", name);
    554 		entries = 0;
    555 		listp = list;
    556 		(void) strncpy(locname, name, MAXPATHLEN);
    557 		(void) strncat(locname, "/", MAXPATHLEN);
    558 		namelen = strlen(locname);
    559 		while ((dp = rst_readdir(dirp)) != NULL) {
    560 			if (!dflag && TSTINO(dp->d_ino, dumpmap) == 0)
    561 				continue;
    562 			if (!vflag && (dp->d_ino == WINO ||
    563 			     strcmp(dp->d_name, ".") == 0 ||
    564 			     strcmp(dp->d_name, "..") == 0))
    565 				continue;
    566 			locname[namelen] = '\0';
    567 			if (namelen + dp->d_namlen >= MAXPATHLEN) {
    568 				fprintf(stderr, "%s%s: name exceeds %d char\n",
    569 					locname, dp->d_name, MAXPATHLEN);
    570 			} else {
    571 				(void) strncat(locname, dp->d_name,
    572 				    (int)dp->d_namlen);
    573 				mkentry(locname, dp, listp++);
    574 				entries++;
    575 			}
    576 		}
    577 		rst_closedir(dirp);
    578 		if (entries == 0) {
    579 			fprintf(stderr, "\n");
    580 			free(list);
    581 			return;
    582 		}
    583 		qsort((char *)list, entries, sizeof(struct afile), fcmp);
    584 	}
    585 	formatf(list, entries);
    586 	if (dirp != NULL) {
    587 		for (fp = listp - 1; fp >= list; fp--)
    588 			freename(fp->fname);
    589 		fprintf(stderr, "\n");
    590 		free(list);
    591 	}
    592 }
    593 
    594 /*
    595  * Read the contents of a directory.
    596  */
    597 static void
    598 mkentry(name, dp, fp)
    599 	char *name;
    600 	struct direct *dp;
    601 	struct afile *fp;
    602 {
    603 	char *cp;
    604 	struct entry *np;
    605 
    606 	fp->fnum = dp->d_ino;
    607 	fp->fname = savename(dp->d_name);
    608 	for (cp = fp->fname; *cp; cp++)
    609 		if (!vflag && (*cp < ' ' || *cp >= 0177))
    610 			*cp = '?';
    611 	fp->len = cp - fp->fname;
    612 	if (dflag && TSTINO(fp->fnum, dumpmap) == 0)
    613 		fp->prefix = '^';
    614 	else if ((np = lookupname(name)) != NULL && (np->e_flags & NEW))
    615 		fp->prefix = '*';
    616 	else
    617 		fp->prefix = ' ';
    618 	switch(dp->d_type) {
    619 
    620 	default:
    621 		fprintf(stderr, "Warning: undefined file type %d\n",
    622 		    dp->d_type);
    623 		/* fall through */
    624 	case DT_REG:
    625 		fp->postfix = ' ';
    626 		break;
    627 
    628 	case DT_LNK:
    629 		fp->postfix = '@';
    630 		break;
    631 
    632 	case DT_FIFO:
    633 	case DT_SOCK:
    634 		fp->postfix = '=';
    635 		break;
    636 
    637 	case DT_CHR:
    638 	case DT_BLK:
    639 		fp->postfix = '#';
    640 		break;
    641 
    642 	case DT_WHT:
    643 		fp->postfix = '%';
    644 		break;
    645 
    646 	case DT_UNKNOWN:
    647 	case DT_DIR:
    648 		if (inodetype(dp->d_ino) == NODE)
    649 			fp->postfix = '/';
    650 		else
    651 			fp->postfix = ' ';
    652 		break;
    653 	}
    654 	return;
    655 }
    656 
    657 /*
    658  * Print out a pretty listing of a directory
    659  */
    660 static void
    661 formatf(list, nentry)
    662 	struct afile *list;
    663 	int nentry;
    664 {
    665 	struct afile *fp, *endlist;
    666 	int width, bigino, haveprefix, havepostfix;
    667 	int i, j, w, precision, columns, lines;
    668 
    669 	width = 0;
    670 	haveprefix = 0;
    671 	havepostfix = 0;
    672 	precision = 0;
    673 	bigino = ROOTINO;
    674 	endlist = &list[nentry];
    675 	for (fp = &list[0]; fp < endlist; fp++) {
    676 		if (bigino < fp->fnum)
    677 			bigino = fp->fnum;
    678 		if (width < fp->len)
    679 			width = fp->len;
    680 		if (fp->prefix != ' ')
    681 			haveprefix = 1;
    682 		if (fp->postfix != ' ')
    683 			havepostfix = 1;
    684 	}
    685 	if (haveprefix)
    686 		width++;
    687 	if (havepostfix)
    688 		width++;
    689 	if (vflag) {
    690 		for (precision = 0, i = bigino; i > 0; i /= 10)
    691 			precision++;
    692 		width += precision + 1;
    693 	}
    694 	width++;
    695 	columns = 81 / width;
    696 	if (columns == 0)
    697 		columns = 1;
    698 	lines = (nentry + columns - 1) / columns;
    699 	for (i = 0; i < lines; i++) {
    700 		for (j = 0; j < columns; j++) {
    701 			fp = &list[j * lines + i];
    702 			if (vflag) {
    703 				fprintf(stderr, "%*d ", precision, fp->fnum);
    704 				fp->len += precision + 1;
    705 			}
    706 			if (haveprefix) {
    707 				putc(fp->prefix, stderr);
    708 				fp->len++;
    709 			}
    710 			fprintf(stderr, "%s", fp->fname);
    711 			if (havepostfix) {
    712 				putc(fp->postfix, stderr);
    713 				fp->len++;
    714 			}
    715 			if (fp + lines >= endlist) {
    716 				fprintf(stderr, "\n");
    717 				break;
    718 			}
    719 			for (w = fp->len; w < width; w++)
    720 				putc(' ', stderr);
    721 		}
    722 	}
    723 }
    724 
    725 /*
    726  * Skip over directory entries that are not on the tape
    727  *
    728  * First have to get definition of a dirent.
    729  */
    730 #undef DIRBLKSIZ
    731 #include <dirent.h>
    732 #undef d_ino
    733 
    734 struct dirent *
    735 glob_readdir(dirp)
    736 	RST_DIR *dirp;
    737 {
    738 	struct direct *dp;
    739 	static struct dirent adirent;
    740 
    741 	while ((dp = rst_readdir(dirp)) != NULL) {
    742 		if (!vflag && dp->d_ino == WINO)
    743 			continue;
    744 		if (dflag || TSTINO(dp->d_ino, dumpmap))
    745 			break;
    746 	}
    747 	if (dp == NULL)
    748 		return (NULL);
    749 	adirent.d_fileno = dp->d_ino;
    750 	adirent.d_namlen = dp->d_namlen;
    751 	memmove(adirent.d_name, dp->d_name, dp->d_namlen + 1);
    752 	return (&adirent);
    753 }
    754 
    755 /*
    756  * Return st_mode information in response to stat or lstat calls
    757  */
    758 static int
    759 glob_stat(name, stp)
    760 	const char *name;
    761 	struct stat *stp;
    762 {
    763 	struct direct *dp;
    764 
    765 	dp = pathsearch(name);
    766 	if (dp == NULL || (!dflag && TSTINO(dp->d_ino, dumpmap) == 0) ||
    767 	    (!vflag && dp->d_ino == WINO))
    768 		return (-1);
    769 	if (inodetype(dp->d_ino) == NODE)
    770 		stp->st_mode = S_IFDIR;
    771 	else
    772 		stp->st_mode = S_IFREG;
    773 	return (0);
    774 }
    775 
    776 /*
    777  * Comparison routine for qsort.
    778  */
    779 static int
    780 fcmp(f1, f2)
    781 	const void *f1, *f2;
    782 {
    783 	return (strcmp(((struct afile *)f1)->fname,
    784 	    ((struct afile *)f2)->fname));
    785 }
    786 
    787 /*
    788  * respond to interrupts
    789  */
    790 void
    791 onintr(signo)
    792 	int signo;
    793 {
    794 	if (command == 'i' && runshell)
    795 		longjmp(reset, 1);
    796 	if (reply("restore interrupted, continue") == FAIL)
    797 		exit(1);
    798 }
    799