Add copyright
[unix-history] / usr / src / sbin / restore / interactive.c
CommitLineData
8c5eec2f
DF
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 */
a15cd7fc
KM
6
7#ifndef lint
8c5eec2f 8static char sccsid[] = "@(#)interactive.c 5.1 (Berkeley) %G%";
a15cd7fc
KM
9#endif not lint
10
11#include "restore.h"
12#include <dumprestor.h>
13#include <setjmp.h>
14
5bb9ce2d
KM
15#define round(a, b) (((a) + (b) - 1) / (b) * (b))
16
a15cd7fc
KM
17/*
18 * Things to handle interruptions.
19 */
20static jmp_buf reset;
21static char *nextarg = NULL;
22
23/*
24 * Structure and routines associated with listing directories.
25 */
26struct 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};
87276fab
KM
32struct 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};
a15cd7fc
KM
39extern int fcmp();
40extern char *fmtentry();
41char *copynext();
42
43/*
44 * Read and execute commands from the terminal.
45 */
46runcmdshell()
47{
48 register struct entry *np;
49 ino_t ino;
87276fab 50 static struct arglist alist = { 0, 0, 0, 0, 0 };
a15cd7fc
KM
51 char curdir[MAXPATHLEN];
52 char name[MAXPATHLEN];
53 char cmd[BUFSIZ];
54
55 canon("/", curdir);
56loop:
57 if (setjmp(reset) != 0) {
87276fab
KM
58 for (; alist.head < alist.last; alist.head++)
59 freename(alist.head->fname);
a15cd7fc
KM
60 nextarg = NULL;
61 volno = 0;
62 }
87276fab 63 getcmd(curdir, cmd, name, &alist);
a15cd7fc
KM
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 '?':
31b99bb7 116 fprintf(stderr, "%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s",
a15cd7fc
KM
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",
31b99bb7 126 "\tsetmodes - set modes of requested directories\n",
a15cd7fc
KM
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 */
31b99bb7 173 case 's':
a15cd7fc
KM
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 */
87276fab 209getcmd(curdir, cmd, name, ap)
a15cd7fc 210 char *curdir, *cmd, *name;
87276fab 211 struct arglist *ap;
a15cd7fc
KM
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 */
87276fab
KM
221 if (ap->head != ap->last) {
222 strcpy(name, ap->head->fname);
223 freename(ap->head->fname);
224 ap->head++;
225 return;
226 }
a15cd7fc
KM
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);
87276fab 248 ap->cmd = cmd;
a15cd7fc
KM
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 */
260getnext:
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 }
87276fab
KM
281 expandarg(name, ap);
282 strcpy(name, ap->head->fname);
283 freename(ap->head->fname);
284 ap->head++;
a15cd7fc
KM
285# undef rawname
286}
287
288/*
289 * Strip off the next token of the input.
290 */
291char *
292copynext(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
87276fab 339 * remove any imbedded "." and ".." components.
a15cd7fc
KM
340 */
341canon(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);
a15cd7fc 354 /*
87276fab
KM
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.
a15cd7fc
KM
367 */
368 for (np = canonname; *np != '\0'; ) {
369 np++;
370 cp = np;
371 while (*np != '/' && *np != '\0')
372 np++;
87276fab
KM
373 if (np - cp == 1 && *cp == '.') {
374 cp--;
375 (void) strcpy(cp, np);
376 np = cp;
377 }
a15cd7fc
KM
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
5bb9ce2d
KM
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 */
87276fab 396expandarg(arg, ap)
5bb9ce2d 397 char *arg;
87276fab 398 register struct arglist *ap;
5bb9ce2d 399{
5a4684bf 400 static struct afile single;
5bb9ce2d 401 int size;
87276fab
KM
402
403 ap->head = ap->last = (struct afile *)0;
404 size = expand(arg, 0, ap);
5bb9ce2d 405 if (size == 0) {
87276fab
KM
406 single.fnum = lookupname(arg)->e_ino;
407 single.fname = savename(arg);
408 ap->head = &single;
409 ap->last = ap->head + 1;
410 return;
5bb9ce2d 411 }
87276fab 412 qsort((char *)ap->head, ap->last - ap->head, sizeof *ap->head, fcmp);
5bb9ce2d
KM
413}
414
415/*
416 * Expand a file name
417 */
87276fab 418expand(as, rflg, ap)
5bb9ce2d
KM
419 char *as;
420 int rflg;
87276fab 421 register struct arglist *ap;
5bb9ce2d
KM
422{
423 int count, size;
424 char dir = 0;
425 char *rescan = 0;
426 DIR *dirp;
427 register char *s, *cs;
87276fab 428 int sindex, rindex, lindex;
5bb9ce2d
KM
429 struct direct *dp;
430 register char slash;
431 register char *rs;
5bb9ce2d
KM
432 register char c;
433
434 /*
435 * check for meta chars
436 */
437 s = cs = as;
438 slash = 0;
439 while (*cs != '*' && *cs != '?' && *cs != '[') {
87276fab 440 if (*cs++ == 0) {
5bb9ce2d
KM
441 if (rflg && slash)
442 break;
443 else
444 return (0) ;
87276fab 445 } else if (*cs == '/') {
5bb9ce2d
KM
446 slash++;
447 }
448 }
449 for (;;) {
450 if (cs == s) {
87276fab 451 s = "";
5bb9ce2d
KM
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)
87276fab 464 *cs++ = 0200;
5bb9ce2d
KM
465 if (dir) {
466 /*
467 * check for rescan
468 */
469 rs = cs;
470 do {
471 if (*rs == '/') {
472 rescan = rs;
473 *rs = 0;
5bb9ce2d
KM
474 }
475 } while (*rs++);
87276fab 476 sindex = ap->last - ap->head;
5bb9ce2d
KM
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)) {
87276fab 483 if (addg(dp, s, rescan, ap) < 0)
5bb9ce2d
KM
484 return (-1);
485 count++;
486 }
487 }
488 if (rescan) {
87276fab
KM
489 rindex = sindex;
490 lindex = ap->last - ap->head;
5bb9ce2d
KM
491 if (count) {
492 count = 0;
87276fab
KM
493 while (rindex < lindex) {
494 size = expand(ap->head[rindex].fname,
495 1, ap);
5bb9ce2d
KM
496 if (size < 0)
497 return (size);
498 count += size;
87276fab 499 rindex++;
5bb9ce2d
KM
500 }
501 }
87276fab
KM
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;
5bb9ce2d
KM
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 */
518gmatch(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++) {
87276fab 535 if (c == ']') {
5bb9ce2d
KM
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 */
87276fab
KM
573addg(dp, as1, as3, ap)
574 struct direct *dp;
575 char *as1, *as3;
576 struct arglist *ap;
5bb9ce2d
KM
577{
578 register char *s1, *s2;
579 register int c;
87276fab 580 char buf[BUFSIZ];
5bb9ce2d 581
87276fab 582 s2 = buf;
5bb9ce2d
KM
583 s1 = as1;
584 while (c = *s1++) {
585 if ((c &= 0177) == 0) {
87276fab 586 *s2++ = '/';
5bb9ce2d
KM
587 break;
588 }
589 *s2++ = c;
590 }
87276fab 591 s1 = dp->d_name;
5bb9ce2d
KM
592 while (*s2 = *s1++)
593 s2++;
594 if (s1 = as3) {
595 *s2++ = '/';
596 while (*s2++ = *++s1)
597 /* void */;
598 }
87276fab
KM
599 if (mkentry(buf, dp->d_ino, ap) == FAIL)
600 return (-1);
5bb9ce2d
KM
601}
602
a15cd7fc
KM
603/*
604 * Do an "ls" style listing of a directory
605 */
606printlist(name, ino, basename)
607 char *name;
608 ino_t ino;
609 char *basename;
610{
611 register struct afile *fp;
87276fab
KM
612 register struct direct *dp;
613 static struct arglist alist = { 0, 0, 0, 0, "ls" };
a15cd7fc
KM
614 struct afile single;
615 DIR *dirp;
616
617 if ((dirp = rst_opendir(name)) == NULL) {
618 single.fnum = ino;
87276fab
KM
619 single.fname = savename(name + strlen(basename) + 1);
620 alist.head = &single;
621 alist.last = alist.head + 1;
a15cd7fc 622 } else {
87276fab
KM
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);
a15cd7fc 644 }
87276fab
KM
645 if (dirp != NULL)
646 fprintf(stderr, "\n");
a15cd7fc
KM
647}
648
649/*
650 * Read the contents of a directory.
651 */
87276fab
KM
652mkentry(name, ino, ap)
653 char *name;
654 ino_t ino;
655 register struct arglist *ap;
a15cd7fc
KM
656{
657 register struct afile *fp;
a15cd7fc 658
87276fab
KM
659 if (ap->base == NULL) {
660 ap->nent = 20;
661 ap->base = (struct afile *)calloc((unsigned)ap->nent,
a15cd7fc 662 sizeof (struct afile));
87276fab
KM
663 if (ap->base == NULL) {
664 fprintf(stderr, "%s: out of memory\n", ap->cmd);
a15cd7fc
KM
665 return (FAIL);
666 }
667 }
87276fab
KM
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);
a15cd7fc 680 }
87276fab
KM
681 ap->head = ap->base;
682 fp = ap->head + ap->nent;
683 ap->nent *= 2;
a15cd7fc 684 }
87276fab 685 ap->last = fp;
a15cd7fc
KM
686 return (GOOD);
687}
688
689/*
690 * Print out a pretty listing of a directory
691 */
87276fab
KM
692formatf(ap)
693 register struct arglist *ap;
a15cd7fc
KM
694{
695 register struct afile *fp;
696 struct entry *np;
87276fab 697 int width = 0, w, nentry = ap->last - ap->head;
a15cd7fc
KM
698 int i, j, len, columns, lines;
699 char *cp;
700
87276fab 701 if (ap->head == ap->last)
a15cd7fc 702 return;
87276fab 703 for (fp = ap->head; fp < ap->last; fp++) {
a15cd7fc
KM
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++) {
87276fab 721 fp = ap->head + j * lines + i;
a15cd7fc
KM
722 cp = fmtentry(fp);
723 fprintf(stderr, "%s", cp);
87276fab 724 if (fp + lines >= ap->last) {
a15cd7fc
KM
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 */
740fcmp(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 */
750char *
751fmtentry(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 */
782onintr()
783{
784 if (command == 'i')
785 longjmp(reset, 1);
786 if (reply("restore interrupted, continue") == FAIL)
787 done(1);
788}