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