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