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