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