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