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