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