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