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