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