xref: /illumos-gate/usr/src/cmd/backup/restore/interactive.c (revision 8a2b682e57a046b828f37bcde1776f131ef4629f)
1 /*
2  * Copyright 1998,2001-2003 Sun Microsystems, Inc.  All rights reserved.
3  * Use is subject to license terms.
4  */
5 
6 /*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
7 /*	  All Rights Reserved	*/
8 
9 /*
10  * Copyright (c) 1985 Regents of the University of California.
11  * All rights reserved.  The Berkeley software License Agreement
12  * specifies the terms and conditions for redistribution.
13  */
14 
15 #include <setjmp.h>
16 #include <euc.h>
17 #include <widec.h>
18 #include "restore.h"
19 #include <ctype.h>
20 #include <limits.h>
21 #include <sys/wait.h>
22 
23 extern eucwidth_t wp;
24 
25 #define	round(a, b) ((((a) + (b) - 1) / (b)) * (b))
26 
27 /*
28  * Things to handle interruptions.
29  */
30 static jmp_buf reset;
31 static int reset_OK;
32 static char *nextarg = NULL;
33 
34 static int dontexpand;	/* co-routine state set in getnext, used in expandarg */
35 
36 #ifdef __STDC__
37 static void getcmd(char *, char *, size_t, char *, size_t, struct arglist *);
38 static void expandarg(char *, struct arglist *);
39 static void printlist(char *, ino_t, char *, int);
40 static void formatf(struct arglist *);
41 static char *copynext(char *, char *, size_t);
42 static int fcmp(struct afile *, struct afile *);
43 static char *fmtentry(struct afile *);
44 static void setpagercmd(void);
45 static uint_t setpagerargs(char **);
46 #else
47 static void getcmd();
48 static void expandarg();
49 static void printlist();
50 static void formatf();
51 static char *copynext();
52 static int fcmp();
53 static char *fmtentry();
54 static void setpagercmd();
55 static uint_t setpagerargs();
56 #endif
57 
58 /*
59  * Read and execute commands from the terminal.
60  */
61 void
62 #ifdef __STDC__
63 runcmdshell(void)
64 #else
65 runcmdshell()
66 #endif
67 {
68 	struct entry *np;
69 	ino_t ino;
70 	static struct arglist alist = { 0, 0, 0, 0, 0 };
71 	char curdir[MAXCOMPLEXLEN];
72 	char name[MAXCOMPLEXLEN];
73 	char cmd[BUFSIZ];
74 
75 #ifdef	lint
76 	curdir[0] = '\0';
77 #endif	/* lint */
78 
79 	canon("/", curdir, sizeof (curdir));
80 loop:
81 	if (setjmp(reset) != 0) {
82 		for (; alist.head < alist.last; alist.head++)
83 			freename(alist.head->fname);
84 		nextarg = NULL;
85 		volno = 0;
86 		goto loop;	/* make sure jmpbuf is up-to-date */
87 	}
88 	reset_OK = 1;
89 	getcmd(curdir, cmd, sizeof (cmd), name, sizeof (name), &alist);
90 
91 	/*
92 	 * Using strncmp() to catch unique prefixes.
93 	 */
94 	switch (cmd[0]) {
95 	/*
96 	 * Add elements to the extraction list.
97 	 */
98 	case 'a':
99 		if (strncmp(cmd, "add", strlen(cmd)) != 0)
100 			goto bad;
101 		if (name[0] == '\0')
102 			break;
103 		ino = dirlookup(name);
104 		if (ino == 0)
105 			break;
106 		if (mflag)
107 			pathcheck(name);
108 		treescan(name, ino, addfile);
109 		break;
110 	/*
111 	 * Change working directory.
112 	 */
113 	case 'c':
114 		if (strncmp(cmd, "cd", strlen(cmd)) != 0)
115 			goto bad;
116 		if (name[0] == '\0')
117 			break;
118 		ino = dirlookup(name);
119 		if (ino == 0)
120 			break;
121 		if (inodetype(ino) == LEAF) {
122 			(void) fprintf(stderr,
123 				gettext("%s: not a directory\n"), name);
124 			break;
125 		}
126 
127 		/* No need to canon(name), getcmd() did it for us */
128 		(void) strncpy(curdir, name, sizeof (curdir));
129 		curdir[sizeof (curdir) - 1] = '\0';
130 		break;
131 	/*
132 	 * Delete elements from the extraction list.
133 	 */
134 	case 'd':
135 		if (strncmp(cmd, "delete", strlen(cmd)) != 0)
136 			goto bad;
137 		if (name[0] == '\0')
138 			break;
139 		np = lookupname(name);
140 		if (np == NIL || (np->e_flags & NEW) == 0) {
141 			(void) fprintf(stderr,
142 				gettext("%s: not on extraction list\n"), name);
143 			break;
144 		}
145 		treescan(name, np->e_ino, deletefile);
146 		break;
147 	/*
148 	 * Extract the requested list.
149 	 */
150 	case 'e':
151 		if (strncmp(cmd, "extract", strlen(cmd)) != 0)
152 			goto bad;
153 		attrscan(0, addfile);
154 		createfiles();
155 		createlinks();
156 		setdirmodes();
157 		if (dflag)
158 			checkrestore();
159 		volno = 0;
160 		break;
161 	/*
162 	 * List available commands.
163 	 */
164 	case 'h':
165 		if (strncmp(cmd, "help", strlen(cmd)) != 0)
166 			goto bad;
167 		/*FALLTHROUGH*/
168 	case '?':
169 		/* ANSI string catenation, to shut cstyle up */
170 		(void) fprintf(stderr, "%s",
171 			gettext("Available commands are:\n"
172 "\tls [arg] - list directory\n"
173 "\tmarked [arg] - list items marked for extraction from directory\n"
174 "\tcd arg - change directory\n"
175 "\tpwd - print current directory\n"
176 "\tadd [arg] - add `arg' to list of files to be extracted\n"
177 "\tdelete [arg] - delete `arg' from list of files to be extracted\n"
178 "\textract - extract requested files\n"
179 "\tsetmodes - set modes of requested directories\n"
180 "\tquit - immediately exit program\n"
181 "\twhat - list dump header information\n"
182 "\tverbose - toggle verbose flag (useful with ``ls'')\n"
183 "\tpaginate - toggle pagination flag (affects ``ls'' and ``marked'')\n"
184 "\tsetpager - set pagination command and arguments\n"
185 "\thelp or `?' - print this list\n"
186 "If no `arg' is supplied, the current directory is used\n"));
187 		break;
188 	/*
189 	 * List a directory.
190 	 */
191 	case 'l':
192 	case 'm':
193 		if ((strncmp(cmd, "ls", strlen(cmd)) != 0) &&
194 		    (strncmp(cmd, "marked", strlen(cmd)) != 0))
195 			goto bad;
196 		if (name[0] == '\0')
197 			break;
198 		ino = dirlookup(name);
199 		if (ino == 0)
200 			break;
201 		printlist(name, ino, curdir, *cmd == 'm');
202 		break;
203 	/*
204 	 * Print current directory or enable pagination.
205 	 */
206 	case 'p':
207 		if (strlen(cmd) < 2)
208 			goto ambiguous;
209 		if (strncmp(cmd, "pwd", strlen(cmd)) == 0) {
210 			if (curdir[1] == '\0') {
211 				(void) fprintf(stderr, "/\n");
212 			} else {
213 				(void) fprintf(stderr, "%s\n", &curdir[1]);
214 			}
215 		} else if (strncmp(cmd, "paginate", strlen(cmd)) == 0) {
216 			if (paginating) {
217 				(void) fprintf(stderr,
218 				    gettext("paging disabled\n"));
219 				paginating = 0;
220 				break;
221 			}
222 			if (vflag) {
223 				(void) fprintf(stderr,
224 				    gettext("paging enabled (%s)\n"),
225 				    pager_catenated);
226 			} else {
227 				(void) fprintf(stderr,
228 				    gettext("paging enabled\n"));
229 			}
230 			if (dflag) {
231 				int index = 0;
232 
233 				while (index < pager_len) {
234 					(void) fprintf(stderr,
235 					    ">>>pager_vector[%d] = `%s'\n",
236 					    index,
237 					    pager_vector[index] ?
238 						pager_vector[index] : "(null)");
239 					index += 1;
240 				}
241 			}
242 			paginating = 1;
243 		} else {
244 			goto bad;
245 		}
246 		break;
247 	/*
248 	 * Quit.
249 	 */
250 	case 'q':
251 		if (strncmp(cmd, "quit", strlen(cmd)) != 0)
252 			goto bad;
253 		reset_OK = 0;
254 		return;
255 	case 'x':
256 		if (strncmp(cmd, "xit", strlen(cmd)) != 0)
257 			goto bad;
258 		reset_OK = 0;
259 		return;
260 	/*
261 	 * Toggle verbose mode.
262 	 */
263 	case 'v':
264 		if (strncmp(cmd, "verbose", strlen(cmd)) != 0)
265 			goto bad;
266 		if (vflag) {
267 			(void) fprintf(stderr, gettext("verbose mode off\n"));
268 			vflag = 0;
269 			break;
270 		}
271 		(void) fprintf(stderr, gettext("verbose mode on\n"));
272 		vflag = 1;
273 		break;
274 	/*
275 	 * Just restore requested directory modes, or set pagination command.
276 	 */
277 	case 's':
278 		if (strlen(cmd) < 4)
279 			goto ambiguous;
280 		if (strncmp(cmd, "setmodes", strlen(cmd)) == 0) {
281 			setdirmodes();
282 		} else if (strncmp(cmd, "setpager", strlen(cmd)) == 0) {
283 			setpagercmd();
284 		} else {
285 			goto bad;
286 		}
287 		break;
288 	/*
289 	 * Print out dump header information.
290 	 */
291 	case 'w':
292 		if (strncmp(cmd, "what", strlen(cmd)) != 0)
293 			goto bad;
294 		printdumpinfo();
295 		break;
296 	/*
297 	 * Turn on debugging.
298 	 */
299 	case 'D':
300 		if (strncmp(cmd, "Debug", strlen(cmd)) != 0)
301 			goto bad;
302 		if (dflag) {
303 			(void) fprintf(stderr, gettext("debugging mode off\n"));
304 			dflag = 0;
305 			break;
306 		}
307 		(void) fprintf(stderr, gettext("debugging mode on\n"));
308 		dflag++;
309 		break;
310 	/*
311 	 * Unknown command.
312 	 */
313 	default:
314 	bad:
315 		(void) fprintf(stderr,
316 			gettext("%s: unknown command; type ? for help\n"), cmd);
317 		break;
318 	ambiguous:
319 		(void) fprintf(stderr,
320 		    gettext("%s: ambiguous command; type ? for help\n"), cmd);
321 		break;
322 	}
323 	goto loop;
324 }
325 
326 static char input[MAXCOMPLEXLEN]; /* shared by getcmd() and setpagercmd() */
327 #define	rawname input	/* save space by reusing input buffer */
328 
329 /*
330  * Read and parse an interactive command.
331  * The first word on the line is assigned to "cmd". If
332  * there are no arguments on the command line, then "curdir"
333  * is returned as the argument. If there are arguments
334  * on the line they are returned one at a time on each
335  * successive call to getcmd. Each argument is first assigned
336  * to "name". If it does not start with "/" the pathname in
337  * "curdir" is prepended to it. Finally "canon" is called to
338  * eliminate any embedded ".." components.
339  */
340 /* ARGSUSED */
341 static void
342 getcmd(curdir, cmd, cmdsiz, name, namesiz, ap)
343 	char *curdir, *cmd, *name;
344 	size_t cmdsiz, namesiz;
345 	struct arglist *ap;
346 {
347 	char *cp;
348 	char output[MAXCOMPLEXLEN];
349 
350 	/*
351 	 * Check to see if still processing arguments.
352 	 */
353 	if (ap->head != ap->last) {
354 		(void) strncpy(name, ap->head->fname, namesiz);
355 		name[namesiz - 1] = '\0';
356 		/* double null terminate string */
357 		if ((strlen(name) + 2) > namesiz) {
358 			fprintf(stderr, gettext("name is too long, ignoring"));
359 			memset(name, 0, namesiz);
360 		} else {
361 			name[strlen(name) + 1] = '\0';
362 		}
363 		freename(ap->head->fname);
364 		ap->head++;
365 		return;
366 	}
367 	if (nextarg != NULL)
368 		goto getnext;
369 	/*
370 	 * Read a command line and trim off trailing white space.
371 	 */
372 readagain:
373 	do {
374 		(void) fprintf(stderr, "%s > ", progname);
375 		(void) fflush(stderr);
376 		(void) fgets(input, sizeof (input), terminal);
377 	} while (!feof(terminal) && input[0] == '\n');
378 	if (feof(terminal)) {
379 		(void) strncpy(cmd, "quit", cmdsiz);
380 		return;
381 	}
382 	/* trim off trailing white space and newline */
383 	for (cp = &input[strlen(input) - 2];
384 	    cp >= &input[0] && isspace((uchar_t)*cp);
385 	    cp--) {
386 		continue;
387 		/*LINTED [empty loop body]*/
388 	}
389 	*++cp = '\0';
390 	if ((strlen(input) + 2) > MAXCOMPLEXLEN) {
391 		fprintf(stderr, gettext("command is too long\n"));
392 		goto readagain;
393 	} else {
394 		/* double null terminate string */
395 		*(cp + 1) = '\0';
396 	}
397 
398 	if (cp == &input[0])
399 		goto readagain;
400 
401 	/*
402 	 * Copy the command into "cmd".
403 	 */
404 	cp = copynext(input, cmd, cmdsiz);
405 	ap->cmd = cmd;
406 	/*
407 	 * If no argument, use curdir as the default.
408 	 */
409 	if (*cp == '\0') {
410 		(void) strncpy(name, curdir, namesiz);
411 		name[namesiz - 1] = '\0';
412 		/* double null terminate string */
413 		if ((strlen(name) + 2) > namesiz) {
414 			fprintf(stderr, gettext("name is too long, ignoring"));
415 			memset(name, 0, namesiz);
416 		} else {
417 			name[strlen(name) + 1] = '\0';
418 		}
419 		return;
420 	}
421 	nextarg = cp;
422 	/*
423 	 * Find the next argument.
424 	 */
425 getnext:
426 	cp = copynext(nextarg, rawname, sizeof (rawname));
427 	if (*cp == '\0')
428 		nextarg = NULL;
429 	else
430 		nextarg = cp;
431 	/*
432 	 * If it an absolute pathname, canonicalize it and return it.
433 	 */
434 	if (rawname[0] == '/') {
435 		canon(rawname, name, namesiz);
436 	} else {
437 		/*
438 		 * For relative pathnames, prepend the current directory to
439 		 * it then canonicalize and return it.
440 		 */
441 		(void) snprintf(output, sizeof (output), "%s/%s",
442 		    curdir, rawname);
443 		canon(output, name, namesiz);
444 	}
445 	expandarg(name, ap);
446 	/*
447 	 * ap->head->fname guaranteed to be double null-terminated and
448 	 * no more than MAXCOMPLEXLEN characters long.
449 	 */
450 	assert(namesiz >= (MAXCOMPLEXLEN));
451 	(void) strcpy(name, ap->head->fname);
452 	/* double null terminate string */
453 	name[strlen(name) + 1] = '\0';
454 	freename(ap->head->fname);
455 	ap->head++;
456 #undef	rawname
457 }
458 
459 /*
460  * Strip off the next token of the input.
461  */
462 static char *
463 copynext(input, output, outsize)
464 	char *input, *output;
465 	size_t outsize;
466 {
467 	char *cp, *bp, *limit;
468 	char quote;
469 
470 	dontexpand = 0;
471 	/* skip to argument */
472 	for (cp = input; *cp != '\0' && isspace((uchar_t)*cp); cp++) {
473 		continue;
474 		/*LINTED [empty loop body]*/
475 	}
476 	bp = output;
477 	limit = output + outsize - 1; /* -1 for the trailing \0 */
478 	while (!isspace((uchar_t)*cp) && *cp != '\0' && bp < limit) {
479 		/*
480 		 * Handle back slashes.
481 		 */
482 		if (*cp == '\\') {
483 			if (*++cp == '\0') {
484 				(void) fprintf(stderr, gettext(
485 				    "command lines cannot be continued\n"));
486 				continue;
487 			}
488 			*bp++ = *cp++;
489 			continue;
490 		}
491 		/*
492 		 * The usual unquoted case.
493 		 */
494 		if (*cp != '\'' && *cp != '"') {
495 			*bp++ = *cp++;
496 			continue;
497 		}
498 		/*
499 		 * Handle single and double quotes.
500 		 */
501 		quote = *cp++;
502 		dontexpand = 1;
503 		while (*cp != quote && *cp != '\0' && bp < limit)
504 			*bp++ = *cp++;
505 		if (*cp++ == '\0') {
506 			(void) fprintf(stderr,
507 			    gettext("missing %c\n"), (uchar_t)quote);
508 			cp--;
509 			continue;
510 		}
511 	}
512 	*bp = '\0';
513 	if ((strlen(output) + 2) > outsize) {
514 		fprintf(stderr, gettext(
515 		    "name is too long, ignoring"));
516 		memset(output, 0, outsize);
517 	} else {
518 		/* double null terminate string */
519 		*(bp + 1) = '\0';
520 	}
521 	return (cp);
522 }
523 
524 /*
525  * Canonicalize file names to always start with ``./'' and
526  * remove any imbedded "." and ".." components.
527  *
528  * The pathname "canonname" is returned double null terminated.
529  */
530 void
531 canon(rawname, canonname, limit)
532 	char *rawname, *canonname;
533 	size_t limit;
534 {
535 	char *cp, *np, *prefix;
536 	uint_t len;
537 
538 	assert(limit > 3);
539 	if (strcmp(rawname, ".") == 0 || strncmp(rawname, "./", 2) == 0)
540 		prefix = "";
541 	else if (rawname[0] == '/')
542 		prefix = ".";
543 	else
544 		prefix = "./";
545 	(void) snprintf(canonname, limit, "%s%s", prefix, rawname);
546 	/*
547 	 * Eliminate multiple and trailing '/'s
548 	 */
549 	for (cp = np = canonname; *np != '\0'; cp++) {
550 		*cp = *np++;
551 		while (*cp == '/' && *np == '/')
552 			np++;
553 	}
554 	*cp = '\0';
555 	if ((strlen(canonname) + 2) > limit) {
556 		fprintf(stderr,
557 		    gettext("canonical name is too long, ignoring name\n"));
558 		memset(canonname, 0, limit);
559 	} else {
560 		/* double null terminate string */
561 		*(cp + 1) = '\0';
562 	}
563 
564 	if (*--cp == '/')
565 		*cp = '\0';
566 	/*
567 	 * Eliminate extraneous "." and ".." from pathnames.  Uses
568 	 * memmove(), as strcpy() might do the wrong thing for these
569 	 * small overlaps.
570 	 */
571 	np = canonname;
572 	while (*np != '\0') {
573 		np++;
574 		cp = np;
575 		while (*np != '/' && *np != '\0')
576 			np++;
577 		if (np - cp == 1 && *cp == '.') {
578 			cp--;
579 			len = strlen(np);
580 			(void) memmove(cp, np, len);
581 			*(cp + len) = '\0';
582 			/* double null terminate string */
583 			*(cp + len + 1) = '\0';
584 			np = cp;
585 		}
586 		if (np - cp == 2 && strncmp(cp, "..", 2) == 0) {
587 			cp--;
588 			/* find beginning of name */
589 			while (cp > &canonname[1] && *--cp != '/') {
590 				continue;
591 				/*LINTED [empty loop body]*/
592 			}
593 			len = strlen(np);
594 			(void) memmove(cp, np, len);
595 			*(cp + len) = '\0';
596 			/* double null terminate string */
597 			*(cp + len + 1) = '\0';
598 			np = cp;
599 		}
600 	}
601 }
602 
603 /*
604  * globals (file name generation)
605  *
606  * "*" in params matches r.e ".*"
607  * "?" in params matches r.e. "."
608  * "[...]" in params matches character class
609  * "[...a-z...]" in params matches a through z.
610  */
611 static void
612 expandarg(arg, ap)
613 	char *arg;
614 	struct arglist *ap;
615 {
616 	static struct afile single;
617 	int size;
618 
619 	ap->head = ap->last = (struct afile *)0;
620 	if (dontexpand)
621 		size = 0;
622 	else
623 		size = expand(arg, 0, ap);
624 	if (size == 0) {
625 		struct entry *ep;
626 
627 		ep = lookupname(arg);
628 		single.fnum = ep ? ep->e_ino : 0;
629 		single.fname = savename(arg);
630 		ap->head = &single;
631 		ap->last = ap->head + 1;
632 		return;
633 	}
634 	if ((ap->last - ap->head) > ULONG_MAX) {
635 		(void) fprintf(stderr,
636 		    gettext("Argument expansion too large to sort\n"));
637 	} else {
638 		/* LINTED pointer arith just range-checked */
639 		qsort((char *)ap->head, (size_t)(ap->last - ap->head),
640 		    sizeof (*ap->head),
641 		    (int (*)(const void *, const void *)) fcmp);
642 	}
643 }
644 
645 /*
646  * Do an "ls" style listing of a directory
647  */
648 static void
649 printlist(name, ino, basename, marked_only)
650 	char *name;
651 	ino_t ino;
652 	char *basename;
653 	int marked_only;
654 {
655 	struct afile *fp;
656 	struct direct *dp;
657 	static struct arglist alist = { 0, 0, 0, 0, "ls" };
658 	struct afile single;
659 	struct entry *np;
660 	RST_DIR *dirp;
661 	int list_entry;
662 
663 	if ((dirp = rst_opendir(name)) == NULL) {
664 		single.fnum = ino;
665 		if (strncmp(name, basename, strlen(basename)) == 0)
666 			single.fname = savename(name + strlen(basename) + 1);
667 		else
668 			single.fname = savename(name);
669 		alist.head = &single;
670 		alist.last = alist.head + 1;
671 		if (alist.base != NULL) {
672 			free(alist.base);
673 			alist.base = NULL;
674 		}
675 	} else {
676 		alist.head = (struct afile *)0;
677 		(void) fprintf(stderr, "%s:\n", name);
678 		while (dp = rst_readdir(dirp)) {
679 			if (dp == NULL || dp->d_ino == 0) {
680 				rst_closedir(dirp);
681 				dirp = NULL;
682 				break;
683 			}
684 			if (!dflag && BIT(dp->d_ino, dumpmap) == 0)
685 				continue;
686 			if (vflag == 0 &&
687 			    (strcmp(dp->d_name, ".") == 0 ||
688 			    strcmp(dp->d_name, "..") == 0))
689 				continue;
690 			list_entry = 1;
691 			if (marked_only) {
692 				np = lookupino(dp->d_ino);
693 				if ((np == NIL) || ((np->e_flags & NEW) == 0))
694 					list_entry = 0;
695 			}
696 			if (list_entry) {
697 				if (!mkentry(dp->d_name, dp->d_ino, &alist)) {
698 					rst_closedir(dirp);
699 					return;
700 				}
701 			}
702 		}
703 	}
704 	if (alist.head != 0) {
705 		if ((alist.last - alist.head) > ULONG_MAX) {
706 			(void) fprintf(stderr,
707 			    gettext("Directory too large to sort\n"));
708 		} else {
709 			qsort((char *)alist.head,
710 			    /* LINTED range-checked */
711 			    (size_t)(alist.last - alist.head),
712 			    sizeof (*alist.head),
713 			    (int (*)(const void *, const void *)) fcmp);
714 		}
715 		formatf(&alist);
716 		for (fp = alist.head; fp < alist.last; fp++)
717 			freename(fp->fname);
718 		alist.head = NULL;
719 		/*
720 		 * Don't free alist.base, as we'll probably be called
721 		 * again, and might as well re-use what we've got.
722 		 */
723 	}
724 	if (dirp != NULL) {
725 		(void) fprintf(stderr, "\n");
726 		rst_closedir(dirp);
727 	}
728 }
729 
730 /*
731  * Print out a pretty listing of a directory
732  */
733 static void
734 formatf(ap)
735 	struct arglist *ap;
736 {
737 	struct afile *fp;
738 	struct entry *np;
739 	/* LINTED: result fits into an int */
740 	int nentry = (int)(ap->last - ap->head);
741 	int i, j;
742 	uint_t len, w, width = 0, columns, lines;
743 	char *cp;
744 	FILE *output = stderr;
745 
746 	if (ap->head == ap->last)
747 		return;
748 
749 	if (paginating) {
750 		int fds[2];
751 
752 		if (pipe(fds) < 0) {
753 			perror(gettext("could not create pipe"));
754 			goto no_page;
755 		}
756 
757 		switch (fork()) {
758 		case -1:
759 			perror(gettext("could not fork"));
760 			goto no_page;
761 		case 0:
762 			/*
763 			 * Make sure final output still ends up in
764 			 * the same place.
765 			 */
766 			(void) dup2(fileno(stderr), fileno(stdout));
767 			(void) close(fds[0]);
768 			(void) dup2(fds[1], fileno(stdin));
769 			execvp(pager_vector[0], pager_vector);
770 			perror(gettext("execvp of pager failed"));
771 			exit(1);
772 			/*NOTREACHED*/
773 		default:
774 			(void) close(fds[1]);
775 			output = fdopen(fds[0], "w");
776 			if (output != (FILE *)NULL) {
777 				break;
778 			}
779 			perror(gettext("could not open pipe to pager"));
780 			output = stderr;
781 		no_page:
782 			(void) fprintf(stderr,
783 			    gettext("pagination disabled\n"));
784 			paginating = 0;
785 		}
786 	}
787 
788 	for (fp = ap->head; fp < ap->last; fp++) {
789 		fp->ftype = inodetype(fp->fnum);
790 		np = lookupino(fp->fnum);
791 		if (np != NIL)
792 			fp->fflags = np->e_flags;
793 		else
794 			fp->fflags = 0;
795 		len = strlen(fmtentry(fp));
796 		if (len > width)
797 			width = len;
798 	}
799 	width += 2;
800 	columns = 80 / width;
801 	if (columns == 0)
802 		columns = 1;
803 	lines = (nentry + columns - 1) / columns;
804 	for (i = 0; i < lines && !ferror(output); i++) {
805 		for (j = 0; j < columns && !ferror(output); j++) {
806 			fp = ap->head + j * lines + i;
807 			cp = fmtentry(fp);
808 			(void) fprintf(output, "%s", cp);
809 			if (fp + lines >= ap->last) {
810 				(void) fprintf(output, "\n");
811 				break;
812 			}
813 			w = strlen(cp);
814 			while (w < width) {
815 				w++;
816 				if (fprintf(output, " ") < 0)
817 					break;
818 			}
819 		}
820 	}
821 
822 	if (paginating) {
823 		(void) fclose(output);
824 		(void) wait((int *)NULL);
825 	}
826 }
827 
828 /*
829  * Comparison routine for qsort.
830  */
831 static int
832 fcmp(f1, f2)
833 	struct afile *f1, *f2;
834 {
835 
836 	return (strcoll(f1->fname, f2->fname));
837 }
838 
839 /*
840  * Format a directory entry.
841  */
842 static char *
843 fmtentry(fp)
844 	struct afile *fp;
845 {
846 	static char fmtres[MAXCOMPLEXLEN];
847 	static int precision = 0;
848 	ino_t i;
849 	char *cp, *dp, *limit;
850 
851 	if (!vflag) {
852 		/* MAXCOMPLEXLEN assumed to be >= 1 */
853 		fmtres[0] = '\0';
854 	} else {
855 		if (precision == 0) {
856 			for (i = maxino; i != 0; i /= 10)
857 				precision++;
858 			if (sizeof (fmtres) < (unsigned)(precision + 2)) {
859 				(void) fprintf(stderr, gettext(
860 "\nInternal check failed, minimum width %d exceeds available size %d\n"),
861 				    (precision + 2), sizeof (fmtres));
862 				done(1);
863 			}
864 		}
865 		(void) snprintf(fmtres, sizeof (fmtres), "%*ld ",
866 		    precision, fp->fnum);
867 	}
868 	dp = &fmtres[strlen(fmtres)];
869 	limit = fmtres + sizeof (fmtres) - 1;
870 	if (dflag && BIT(fp->fnum, dumpmap) == 0)
871 		*dp++ = '^';
872 	else if ((fp->fflags & NEW) != 0)
873 		*dp++ = '*';
874 	else
875 		*dp++ = ' ';
876 	for (cp = fp->fname; *cp && dp < limit; cp++)
877 		/* LINTED: precedence ok, can't fix system macro */
878 		if (!vflag && (!ISPRINT(*cp, wp)))
879 			*dp++ = '?';
880 		else
881 			*dp++ = *cp;
882 	if (fp->ftype == NODE && dp < limit)
883 		*dp++ = '/';
884 	*dp++ = 0;
885 	return (fmtres);
886 }
887 
888 /*
889  * respond to interrupts
890  */
891 /* ARGSUSED */
892 void
893 onintr(sig)
894 	int	sig;
895 {
896 	char	buf[300];
897 
898 	if (command == 'i' && reset_OK)
899 		longjmp(reset, 1);
900 
901 	(void) snprintf(buf, sizeof (buf),
902 	    gettext("%s interrupted, continue"), progname);
903 	if (reply(buf) == FAIL)
904 		done(1);
905 }
906 /*
907  * Set up pager_catenated and pager_vector.
908  */
909 void
910 #ifdef __STDC__
911 initpagercmd(void)
912 #else
913 initpagercmd()
914 #endif
915 {
916 	char *cp;
917 
918 	cp = getenv("PAGER");
919 	if (cp != NULL)
920 		pager_catenated = strdup(cp);
921 	if ((pager_catenated == NULL) || (*pager_catenated == '\0')) {
922 		if (pager_catenated != NULL)
923 			free(pager_catenated);
924 		pager_catenated = strdup(DEF_PAGER);
925 	}
926 	if (pager_catenated == NULL) {
927 		(void) fprintf(stderr, gettext("out of memory\n"));
928 		done(1);
929 	}
930 
931 	pager_vector = (char **)malloc(sizeof (char *));
932 	if (pager_vector == NULL) {
933 		(void) fprintf(stderr, gettext("out of memory\n"));
934 		done(1);
935 	}
936 
937 	pager_len = 1;
938 	cp = pager_catenated;
939 	(void) setpagerargs(&cp);
940 }
941 
942 
943 /*
944  * Resets pager_catenated and pager_vector from user input.
945  */
946 void
947 #ifdef __STDC__
948 setpagercmd(void)
949 #else
950 setpagercmd()
951 #endif
952 {
953 	uint_t catenate_length;
954 	int index;
955 
956 	/*
957 	 * We'll get called immediately after setting a pager, due to
958 	 * our interaction with getcmd()'s internal state.  Don't do
959 	 * anything when that happens.
960 	 */
961 	if (*input == '\0')
962 		return;
963 
964 	if (pager_len > 0) {
965 		for (index = 0; pager_vector[index] != (char *)NULL; index += 1)
966 			free(pager_vector[index]);
967 		free(pager_vector);
968 		free(pager_catenated);
969 	}
970 
971 	pager_vector = (char **)malloc(2 * sizeof (char *));
972 	if (pager_vector == NULL) {
973 		(void) fprintf(stderr, gettext("out of memory\n"));
974 		done(1);
975 	}
976 
977 	pager_len = 2;
978 	pager_vector[0] = strdup(input);
979 	if (pager_vector[0] == NULL) {
980 		(void) fprintf(stderr, gettext("out of memory\n"));
981 		done(1);
982 	}
983 	if (dflag)
984 		(void) fprintf(stderr, gettext("got command `%s'\n"), input);
985 	catenate_length = setpagerargs(&nextarg) + strlen(pager_vector[0]) + 1;
986 	pager_catenated = (char *)malloc(catenate_length *
987 		(size_t)sizeof (char));
988 	if (pager_catenated == (char *)NULL) {
989 		(void) fprintf(stderr, gettext("out of memory\n"));
990 		done(1);
991 	}
992 	for (index = 0; pager_vector[index] != (char *)NULL; index += 1) {
993 		if (index > 0)
994 			(void) strcat(pager_catenated, " ");
995 		(void) strcat(pager_catenated, pager_vector[index]);
996 	}
997 }
998 
999 
1000 /*
1001  * Extract arguments for the pager command from getcmd()'s input buffer.
1002  */
1003 static uint_t
1004 setpagerargs(source)
1005 	char	**source;
1006 {
1007 	char	word[MAXCOMPLEXLEN];
1008 	char	*cp = *source;
1009 	uint_t	length = 0;
1010 
1011 	while ((cp != (char *)NULL) && (*cp != '\0')) {
1012 		cp = copynext(cp, word, sizeof (word));
1013 		if (dflag)
1014 			fprintf(stderr, gettext("got word `%s'\n"), word);
1015 		pager_vector = (char **)realloc(pager_vector,
1016 			(size_t)sizeof (char *) * (pager_len + 1));
1017 		if (pager_vector == (char **)NULL) {
1018 			(void) fprintf(stderr, gettext("out of memory\n"));
1019 			done(1);
1020 		}
1021 		pager_vector[pager_len - 1] = strdup(word);
1022 		if (pager_vector[pager_len - 1] == (char *)NULL) {
1023 			(void) fprintf(stderr, gettext("out of memory\n"));
1024 			done(1);
1025 		}
1026 		length += strlen(word) + 1;
1027 		pager_len += 1;
1028 	}
1029 	pager_vector[pager_len - 1] = (char *)NULL;
1030 	*source = cp;
1031 	return (length);
1032 }
1033