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