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