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 <stdint.h>
51#include <stdio.h>
52#include <stdlib.h>
53#include <string.h>
54
55#include "restore.h"
56#include "extern.h"
57
58#define round(a, b) (((a) + (b) - 1) / (b) * (b))
59
60/*
61 * Things to handle interruptions.
62 */
63static int runshell;
64static jmp_buf reset;
65static char *nextarg = NULL;
66
67/*
68 * Structure and routines associated with listing directories.
69 */
70struct afile {
71	ino_t	fnum;		/* inode number of file */
72	char	*fname;		/* file name */
73	short	len;		/* name length */
74	char	prefix;		/* prefix character */
75	char	postfix;	/* postfix character */
76};
77struct arglist {
78	int	freeglob;	/* glob structure needs to be freed */
79	int	argcnt;		/* next globbed argument to return */
80	glob_t	glob;		/* globbing information */
81	char	*cmd;		/* the current command */
82};
83
84static char	*copynext(char *, char *);
85static int	 fcmp(const void *, const void *);
86static void	 formatf(struct afile *, int);
87static void	 getcmd(char *, char *, char *, size_t, struct arglist *);
88struct dirent	*glob_readdir(void *);
89static int	 glob_stat(const char *, struct stat *);
90static void	 mkentry(char *, struct direct *, struct afile *);
91static void	 printlist(char *, char *);
92
93/*
94 * Read and execute commands from the terminal.
95 */
96void
97runcmdshell(void)
98{
99	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 = rst_opendir;
110	arglist.glob.gl_readdir = glob_readdir;
111	arglist.glob.gl_closedir = rst_closedir;
112	arglist.glob.gl_lstat = glob_stat;
113	arglist.glob.gl_stat = glob_stat;
114	canon("/", curdir, sizeof(curdir));
115loop:
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, sizeof(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 */
304static void
305getcmd(char *curdir, char *cmd, char *name, size_t size, struct arglist *ap)
306{
307	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		if (fgets(input, BUFSIZ, terminal) == NULL) {
326			strcpy(cmd, "quit");
327			return;
328		}
329	} while (input[0] == '\n');
330	for (cp = &input[strlen(input) - 2]; *cp == ' ' || *cp == '\t'; cp--)
331		/* trim off trailing white space and newline */;
332	*++cp = '\0';
333	/*
334	 * Copy the command into "cmd".
335	 */
336	cp = copynext(input, cmd);
337	ap->cmd = cmd;
338	/*
339	 * If no argument, use curdir as the default.
340	 */
341	if (*cp == '\0') {
342		(void) strncpy(name, curdir, size);
343		name[size - 1] = '\0';
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, size);
361	} else {
362		/*
363		 * For relative pathnames, prepend the current directory to
364		 * it then canonicalize and return it.
365		 */
366		snprintf(output, sizeof(output), "%s/%s", curdir, rawname);
367		canon(output, name, size);
368	}
369	switch (glob(name, GLOB_ALTDIRFUNC, NULL, &ap->glob)) {
370	case GLOB_NOSPACE:
371		fprintf(stderr, "%s: out of memory\n", ap->cmd);
372		break;
373	case GLOB_NOMATCH:
374		fprintf(stderr, "%s %s: no such file or directory\n", ap->cmd, name);
375		break;
376	}
377	if (ap->glob.gl_pathc == 0)
378		return;
379	ap->freeglob = 1;
380	ap->argcnt = ap->glob.gl_pathc;
381
382retnext:
383	strncpy(name, ap->glob.gl_pathv[ap->glob.gl_pathc - ap->argcnt], size);
384	name[size - 1] = '\0';
385	if (--ap->argcnt == 0) {
386		ap->freeglob = 0;
387		globfree(&ap->glob);
388	}
389#	undef rawname
390}
391
392/*
393 * Strip off the next token of the input.
394 */
395static char *
396copynext(char *input, char *output)
397{
398	char *cp, *bp;
399	char quote;
400
401	for (cp = input; *cp == ' ' || *cp == '\t'; cp++)
402		/* skip to argument */;
403	bp = output;
404	while (*cp != ' ' && *cp != '\t' && *cp != '\0') {
405		/*
406		 * Handle back slashes.
407		 */
408		if (*cp == '\\') {
409			if (*++cp == '\0') {
410				fprintf(stderr,
411					"command lines cannot be continued\n");
412				continue;
413			}
414			*bp++ = *cp++;
415			continue;
416		}
417		/*
418		 * The usual unquoted case.
419		 */
420		if (*cp != '\'' && *cp != '"') {
421			*bp++ = *cp++;
422			continue;
423		}
424		/*
425		 * Handle single and double quotes.
426		 */
427		quote = *cp++;
428		while (*cp != quote && *cp != '\0')
429			*bp++ = *cp++;
430		if (*cp++ == '\0') {
431			fprintf(stderr, "missing %c\n", quote);
432			cp--;
433			continue;
434		}
435	}
436	*bp = '\0';
437	return (cp);
438}
439
440/*
441 * Canonicalize file names to always start with ``./'' and
442 * remove any embedded "." and ".." components.
443 */
444void
445canon(char *rawname, char *canonname, size_t len)
446{
447	char *cp, *np;
448
449	if (strcmp(rawname, ".") == 0 || strncmp(rawname, "./", 2) == 0)
450		(void) strcpy(canonname, "");
451	else if (rawname[0] == '/')
452		(void) strcpy(canonname, ".");
453	else
454		(void) strcpy(canonname, "./");
455	if (strlen(canonname) + strlen(rawname) >= len) {
456		fprintf(stderr, "canonname: not enough buffer space\n");
457		done(1);
458	}
459
460	(void) strcat(canonname, rawname);
461	/*
462	 * Eliminate multiple and trailing '/'s
463	 */
464	for (cp = np = canonname; *np != '\0'; cp++) {
465		*cp = *np++;
466		while (*cp == '/' && *np == '/')
467			np++;
468	}
469	*cp = '\0';
470	if (*--cp == '/')
471		*cp = '\0';
472	/*
473	 * Eliminate extraneous "." and ".." from pathnames.
474	 */
475	for (np = canonname; *np != '\0'; ) {
476		np++;
477		cp = np;
478		while (*np != '/' && *np != '\0')
479			np++;
480		if (np - cp == 1 && *cp == '.') {
481			cp--;
482			(void) strcpy(cp, np);
483			np = cp;
484		}
485		if (np - cp == 2 && strncmp(cp, "..", 2) == 0) {
486			cp--;
487			while (cp > &canonname[1] && *--cp != '/')
488				/* find beginning of name */;
489			(void) strcpy(cp, np);
490			np = cp;
491		}
492	}
493}
494
495/*
496 * Do an "ls" style listing of a directory
497 */
498static void
499printlist(char *name, char *basename)
500{
501	struct afile *fp, *list, *listp;
502	struct direct *dp;
503	struct afile single;
504	RST_DIR *dirp;
505	int entries, len, namelen;
506	char locname[MAXPATHLEN];
507
508	dp = pathsearch(name);
509	if (dp == NULL || (!dflag && TSTINO(dp->d_ino, dumpmap) == 0) ||
510	    (!vflag && dp->d_ino == WINO))
511		return;
512	if ((dirp = rst_opendir(name)) == NULL) {
513		entries = 1;
514		list = &single;
515		mkentry(name, dp, list);
516		len = strlen(basename) + 1;
517		if (strlen(name) - len > single.len) {
518			freename(single.fname);
519			single.fname = savename(&name[len]);
520			single.len = strlen(single.fname);
521		}
522	} else {
523		entries = 0;
524		while ((dp = rst_readdir(dirp)))
525			entries++;
526		rst_closedir(dirp);
527		list = (struct afile *)malloc(entries * sizeof(struct afile));
528		if (list == NULL) {
529			fprintf(stderr, "ls: out of memory\n");
530			return;
531		}
532		if ((dirp = rst_opendir(name)) == NULL)
533			panic("directory reopen failed\n");
534		fprintf(stderr, "%s:\n", name);
535		entries = 0;
536		listp = list;
537		(void)strlcpy(locname, name, MAXPATHLEN);
538		(void)strlcat(locname, "/", MAXPATHLEN);
539		namelen = strlen(locname);
540		while ((dp = rst_readdir(dirp))) {
541			if (dp == NULL)
542				break;
543			if (!dflag && TSTINO(dp->d_ino, dumpmap) == 0)
544				continue;
545			if (!vflag && (dp->d_ino == WINO ||
546			     strcmp(dp->d_name, ".") == 0 ||
547			     strcmp(dp->d_name, "..") == 0))
548				continue;
549			locname[namelen] = '\0';
550			if (namelen + dp->d_namlen >= MAXPATHLEN) {
551				fprintf(stderr, "%s%s: name exceeds %d char\n",
552					locname, dp->d_name, MAXPATHLEN);
553			} else {
554				(void)strlcat(locname, dp->d_name, MAXPATHLEN);
555				mkentry(locname, dp, listp++);
556				entries++;
557			}
558		}
559		rst_closedir(dirp);
560		if (entries == 0) {
561			fprintf(stderr, "\n");
562			free(list);
563			return;
564		}
565		qsort((char *)list, entries, sizeof(struct afile), fcmp);
566	}
567	formatf(list, entries);
568	if (dirp != NULL) {
569		for (fp = listp - 1; fp >= list; fp--)
570			freename(fp->fname);
571		fprintf(stderr, "\n");
572		free(list);
573	}
574}
575
576/*
577 * Read the contents of a directory.
578 */
579static void
580mkentry(char *name, struct direct *dp, struct afile *fp)
581{
582	char *cp;
583	struct entry *np;
584
585	fp->fnum = dp->d_ino;
586	fp->fname = savename(dp->d_name);
587	for (cp = fp->fname; *cp; cp++)
588		if (!vflag && !isprint((unsigned char)*cp))
589			*cp = '?';
590	fp->len = cp - fp->fname;
591	if (dflag && TSTINO(fp->fnum, dumpmap) == 0)
592		fp->prefix = '^';
593	else if ((np = lookupname(name)) != NULL && (np->e_flags & NEW))
594		fp->prefix = '*';
595	else
596		fp->prefix = ' ';
597	switch(dp->d_type) {
598
599	default:
600		fprintf(stderr, "Warning: undefined file type %d\n",
601		    dp->d_type);
602		/* FALLTHROUGH */
603	case DT_REG:
604		fp->postfix = ' ';
605		break;
606
607	case DT_LNK:
608		fp->postfix = '@';
609		break;
610
611	case DT_FIFO:
612	case DT_SOCK:
613		fp->postfix = '=';
614		break;
615
616	case DT_CHR:
617	case DT_BLK:
618		fp->postfix = '#';
619		break;
620
621	case DT_WHT:
622		fp->postfix = '%';
623		break;
624
625	case DT_UNKNOWN:
626	case DT_DIR:
627		if (inodetype(dp->d_ino) == NODE)
628			fp->postfix = '/';
629		else
630			fp->postfix = ' ';
631		break;
632	}
633	return;
634}
635
636/*
637 * Print out a pretty listing of a directory
638 */
639static void
640formatf(struct afile *list, int nentry)
641{
642	struct afile *fp, *endlist;
643	int width, bigino, haveprefix, havepostfix;
644	int i, j, w, precision, columns, lines;
645
646	width = 0;
647	haveprefix = 0;
648	havepostfix = 0;
649	bigino = ROOTINO;
650	endlist = &list[nentry];
651	for (fp = &list[0]; fp < endlist; fp++) {
652		if (bigino < fp->fnum)
653			bigino = fp->fnum;
654		if (width < fp->len)
655			width = fp->len;
656		if (fp->prefix != ' ')
657			haveprefix = 1;
658		if (fp->postfix != ' ')
659			havepostfix = 1;
660	}
661	if (haveprefix)
662		width++;
663	if (havepostfix)
664		width++;
665	if (vflag) {
666		for (precision = 0, i = bigino; i > 0; i /= 10)
667			precision++;
668		width += precision + 1;
669	}
670	width++;
671	columns = 81 / width;
672	if (columns == 0)
673		columns = 1;
674	lines = (nentry + columns - 1) / columns;
675	for (i = 0; i < lines; i++) {
676		for (j = 0; j < columns; j++) {
677			fp = &list[j * lines + i];
678			if (vflag) {
679				fprintf(stderr, "%*ju ",
680				    precision, (uintmax_t)fp->fnum);
681				fp->len += precision + 1;
682			}
683			if (haveprefix) {
684				putc(fp->prefix, stderr);
685				fp->len++;
686			}
687			fprintf(stderr, "%s", fp->fname);
688			if (havepostfix) {
689				putc(fp->postfix, stderr);
690				fp->len++;
691			}
692			if (fp + lines >= endlist) {
693				fprintf(stderr, "\n");
694				break;
695			}
696			for (w = fp->len; w < width; w++)
697				putc(' ', stderr);
698		}
699	}
700}
701
702/*
703 * Skip over directory entries that are not on the tape
704 *
705 * First have to get definition of a dirent.
706 */
707#undef DIRBLKSIZ
708#include <dirent.h>
709#undef d_ino
710
711struct dirent *
712glob_readdir(void *dirp)
713{
714	struct direct *dp;
715	static struct dirent adirent;
716
717	while ((dp = rst_readdir(dirp)) != NULL) {
718		if (!vflag && dp->d_ino == WINO)
719			continue;
720		if (dflag || TSTINO(dp->d_ino, dumpmap))
721			break;
722	}
723	if (dp == NULL)
724		return (NULL);
725	adirent.d_fileno = dp->d_ino;
726	adirent.d_namlen = dp->d_namlen;
727	memmove(adirent.d_name, dp->d_name, dp->d_namlen + 1);
728	return (&adirent);
729}
730
731/*
732 * Return st_mode information in response to stat or lstat calls
733 */
734static int
735glob_stat(const char *name, struct stat *stp)
736{
737	struct direct *dp;
738
739	dp = pathsearch(name);
740	if (dp == NULL || (!dflag && TSTINO(dp->d_ino, dumpmap) == 0) ||
741	    (!vflag && dp->d_ino == WINO))
742		return (-1);
743	if (inodetype(dp->d_ino) == NODE)
744		stp->st_mode = IFDIR;
745	else
746		stp->st_mode = IFREG;
747	return (0);
748}
749
750/*
751 * Comparison routine for qsort.
752 */
753static int
754fcmp(const void *f1, const void *f2)
755{
756	return (strcoll(((struct afile *)f1)->fname,
757	    ((struct afile *)f2)->fname));
758}
759
760/*
761 * respond to interrupts
762 */
763void
764onintr(int signo __unused)
765{
766	if (command == 'i' && runshell)
767		longjmp(reset, 1);
768	if (reply("restore interrupted, continue") == FAIL)
769		done(1);
770}
771