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