xref: /illumos-gate/usr/src/cmd/svr4pkg/pkginfo/pkginfo.c (revision c57b775050136774ef88b195e5833b3f9b86561b)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 
22 /*
23  * Copyright (c) 2017 Peter Tribble.
24  */
25 
26 /*
27  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
28  * Use is subject to license terms.
29  */
30 
31 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
32 /* All Rights Reserved */
33 
34 
35 #define	__EXTENTIONS__
36 
37 #include <stdio.h>
38 #include <limits.h>
39 #include <unistd.h>
40 #include <stdlib.h>
41 #include <locale.h>
42 #include <libintl.h>
43 #include <strings.h>
44 #include <string.h>
45 #include <dirent.h>
46 #include <sys/param.h>
47 #include <sys/stat.h>
48 #include <pkginfo.h>
49 #include <fcntl.h>
50 #include <sys/types.h>
51 #include <sys/stat.h>
52 #include <sys/param.h>
53 #include <sys/mman.h>
54 #include <pkgstrct.h>
55 #include <pkglocs.h>
56 #include <errno.h>
57 #include <ctype.h>
58 
59 #include <pkglib.h>
60 #include <instzones_api.h>
61 #include <libadm.h>
62 #include <libinst.h>
63 
64 extern char	*pkgdir;
65 extern int	pkginfofind(char *path, char *pkg_dir, char *pkginst);
66 
67 #define	ERR_USAGE	"usage:\n" \
68 			"%s [-q] [-pi] [-x|l] [options] [pkg ...]\n" \
69 			"%s -d device [-q] [-x|l] [options] [pkg ...]\n" \
70 			"where\n" \
71 			"  -q #quiet mode\n" \
72 			"  -p #select partially installed packages\n" \
73 			"  -i #select completely installed packages\n" \
74 			"  -x #extracted listing\n" \
75 			"  -l #long listing\n" \
76 			"  -r #relocation base \n" \
77 			"and options may include:\n" \
78 			"  -c category, [category...]\n" \
79 			"  -a architecture\n" \
80 			"  -v version\n"
81 
82 #define	ERR_INCOMP0	"-L and -l/-x/-r flags are incompatible"
83 #define	ERR_INCOMP1	"-l and -x/-r flags are not compatible"
84 #define	ERR_INCOMP2	"-x and -l/-r flags are not compatible"
85 #define	ERR_INCOMP3	"-r and -x/-x flags are not compatible"
86 #define	ERR_NOINFO	"ERROR: information for \"%s\" was not found"
87 #define	ERR_NOPINFO	"ERROR: No partial information for \"%s\" was found"
88 #define	ERR_BADINFO	"pkginfo file is corrupt or missing"
89 #define	ERR_ROOT_SET	"Could not set install root from the environment."
90 #define	ERR_ROOT_CMD	"Command line install root contends with environment."
91 
92 /* Format for dumping package attributes in dumpinfo() */
93 #define	FMT	"%10s:  %s\n"
94 #define	SFMT	"%-11.11s %-*.*s %s\n"
95 #define	CFMT	"%*.*s  "
96 #define	XFMT	"%-*.*s  %s\n"
97 
98 #define	nblock(size)	((size + (DEV_BSIZE - 1)) / DEV_BSIZE)
99 #define	MAXCATG	64
100 
101 static char	*device = NULL;
102 static char	*parmlst[] = {
103 	"DESC", "PSTAMP", "INSTDATE", "VSTOCK", "SERIALNUM", "HOTLINE",
104 	"EMAIL", NULL
105 };
106 
107 static int	errflg = 0;
108 static int	qflag = 0;
109 static int	iflag = -1;
110 static int	pflag = -1;
111 static int	lflag = 0;
112 static int	Lflag = 0;
113 static int	Nflag = 0;
114 static int	xflag = 0;
115 static int	rflag = 0; 		/* bug # 1081606 */
116 static struct cfent	entry;
117 static char	**pkg = NULL;
118 static int	pkgcnt = 0;
119 static char	*ckcatg[MAXCATG] = {NULL};
120 static int	ncatg = 0;
121 static char	*ckvers = NULL;
122 static char	*ckarch = NULL;
123 
124 static struct cfstat {
125 	char	pkginst[32];
126 	short	exec;
127 	short	dirs;
128 	short	link;
129 	short	partial;
130 	long	spooled;
131 	long	installed;
132 	short	info;
133 	short	shared;
134 	short	setuid;
135 	long	tblks;
136 	struct cfstat *next;
137 } *data;
138 static struct pkginfo info;
139 
140 static struct	cfstat *fpkg(char *pkginst);
141 static int	iscatg(char *list);
142 static int	selectp(char *p);
143 static void	usage(void), look_for_installed(void),
144 		report(void), rdcontents(void);
145 static void	pkgusage(struct cfstat *dp, struct cfent *pentry);
146 static void	getinfo(struct cfstat *dp);
147 static void	dumpinfo(struct cfstat *dp, int pkgLngth);
148 
149 int
main(int argc,char ** argv)150 main(int argc, char **argv)
151 {
152 	int	c;
153 
154 	pkgdir = NULL;
155 	setErrstr(NULL);
156 
157 	/* initialize locale mechanism */
158 
159 	(void) setlocale(LC_ALL, "");
160 
161 #if !defined(TEXT_DOMAIN)	/* Should be defined by cc -D */
162 #define	TEXT_DOMAIN "SYS_TEST"
163 #endif
164 	(void) textdomain(TEXT_DOMAIN);
165 
166 	/* determine program name */
167 
168 	(void) set_prog_name(argv[0]);
169 
170 	/* tell spmi zones interface how to access package output functions */
171 
172 	z_set_output_functions(echo, echoDebug, progerr);
173 
174 	/* establish installation root directory */
175 
176 	if (!set_inst_root(getenv("PKG_INSTALL_ROOT"))) {
177 		progerr(gettext(ERR_ROOT_SET));
178 		exit(1);
179 	}
180 
181 	while ((c = getopt(argc, argv, "LNR:xv:a:d:qrpilc:?")) != EOF) {
182 		switch (c) {
183 		case 'v':
184 			ckvers = optarg;
185 			break;
186 
187 		case 'a':
188 			ckarch = optarg;
189 			break;
190 
191 		case 'd':
192 			/* -d could specify stream or mountable device */
193 			device = flex_device(optarg, 1);
194 			break;
195 
196 		case 'q':
197 			qflag++;
198 			break;
199 
200 		case 'i':
201 			iflag = 1;
202 			if (pflag > 0)
203 				usage();
204 			pflag = 0;
205 			break;
206 
207 		case 'p':
208 			pflag = 1;
209 			if (iflag > 0)
210 				usage();
211 			iflag = 0;
212 			break;
213 
214 		case 'N':
215 			Nflag++;
216 			break;
217 
218 		case 'L':
219 			if (xflag || lflag || rflag) {
220 				progerr(gettext(ERR_INCOMP0));
221 				usage();
222 			}
223 			Lflag++;
224 			break;
225 
226 		case 'l':
227 			if (xflag || rflag) {
228 				progerr(gettext(ERR_INCOMP1));
229 				usage();
230 			}
231 			lflag++;
232 			break;
233 
234 		case 'x':
235 			/* bug # 1081606 */
236 			if (lflag || rflag) {
237 				progerr(gettext(ERR_INCOMP2));
238 				usage();
239 			}
240 			xflag++;
241 			break;
242 
243 		case 'r':
244 			if (lflag || xflag || Lflag) {
245 				progerr(gettext(ERR_INCOMP0));
246 				usage();
247 			}
248 			rflag++;
249 			break;
250 
251 		case 'c':
252 			ckcatg[ncatg++] = strtok(optarg, " \t\n, ");
253 			while (ckcatg[ncatg] = strtok(NULL, " \t\n, "))
254 				ncatg++;
255 			break;
256 
257 		/* added for newroot functions */
258 		case 'R':
259 			if (!set_inst_root(optarg)) {
260 				progerr(gettext(ERR_ROOT_CMD));
261 				exit(1);
262 			}
263 			break;
264 
265 		default:
266 			usage();
267 		}
268 	}
269 
270 	/*
271 	 * implement the newroot option
272 	 */
273 	set_PKGpaths(get_inst_root());	/* set up /var... directories */
274 
275 	/*
276 	 * Open the install DB, if one exists.
277 	 */
278 
279 	pkg = &argv[optind];
280 	pkgcnt = (argc - optind);
281 
282 	if (pkg[0] && strcmp(pkg[0], "all") == 0) {
283 		pkgcnt = 0;
284 		pkg[0] = NULL;
285 	}
286 
287 	if (pkgdir == NULL)
288 		pkgdir = get_PKGLOC(); 	/* we need this later */
289 
290 	/* convert device appropriately */
291 	if (pkghead(device))
292 		exit(1);
293 
294 	/*
295 	 * If we are to inspect a spooled package we are only interested in
296 	 * the pkginfo file in the spooled pkg.  We have a spooled pkg if
297 	 * device is not NULL.
298 	 */
299 
300 	look_for_installed();
301 
302 	if (lflag && strcmp(pkgdir, get_PKGLOC()) == 0) {
303 		/* look at contents file */
304 		rdcontents();
305 
306 	}
307 
308 	/*
309 	 * If we are to inspect a spooled package we are only interested in
310 	 * the pkginfo file in the spooled pkg so we skip any Reg 4 DB
311 	 * lookups and use the old algorithm. We have a spooled pkg if
312 	 * device is not NULL.
313 	 */
314 
315 	report();
316 
317 	(void) pkghead(NULL);
318 
319 	return (errflg ? 1 : 0);
320 }
321 
322 static void
report(void)323 report(void)
324 {
325 	struct cfstat *dp, *choice;
326 	int	i;
327 	int	pkgLgth = 0;
328 	int	longestPkg = 0;
329 	boolean_t output = B_FALSE;
330 
331 	for (;;) {
332 		choice = (struct cfstat *)0;
333 		for (dp = data; dp; dp = dp->next) {
334 			pkgLgth = strlen(dp->pkginst);
335 			if (pkgLgth > longestPkg)
336 				longestPkg = pkgLgth;
337 		}
338 		for (dp = data; dp; dp = dp->next) {
339 			/* get information about this package */
340 			if (dp->installed < 0)
341 				continue; /* already used */
342 			if (Lflag && pkgcnt) {
343 				choice = dp;
344 				break;
345 			} else if (!choice ||
346 			    (strcmp(choice->pkginst, dp->pkginst) > 0))
347 				choice = dp;
348 		}
349 		if (!choice)
350 			break; /* no more packages */
351 
352 		if (pkginfo(&info, choice->pkginst, ckarch, ckvers)) {
353 			choice->installed = (-1);
354 			continue;
355 		}
356 
357 		/*
358 		 * Confirm that the pkginfo file contains the
359 		 * required information.
360 		 */
361 		if (info.name == NULL || *(info.name) == '\0' ||
362 		    info.arch == NULL || *(info.arch) == '\0' ||
363 		    info.version == NULL || *(info.version) == '\0' ||
364 		    info.catg == NULL || *(info.catg) == '\0') {
365 			progerr(gettext(ERR_BADINFO));
366 			errflg++;
367 			return;
368 		}
369 
370 		/* is it in an appropriate catgory? */
371 		if (iscatg(info.catg)) {
372 			choice->installed = (-1);
373 			continue;
374 		}
375 
376 		if (!pflag &&
377 		    (choice->partial || (info.status == PI_PARTIAL) ||
378 		    (info.status == PI_UNKNOWN))) {
379 			/* don't include partially installed packages */
380 			choice->installed = (-1);
381 			continue;
382 		}
383 
384 		if (!iflag && (info.status == PI_INSTALLED)) {
385 			/* don't include completely installed packages */
386 			choice->installed = (-1);
387 			continue;
388 		}
389 
390 		output = B_TRUE;
391 		dumpinfo(choice, longestPkg);
392 		choice->installed = (-1);
393 		if (pkgcnt) {
394 			i = selectp(choice->pkginst);
395 			if (i >= 0)
396 				pkg[i] = NULL;
397 			else {
398 				if (qflag) {
399 					errflg++;
400 					return;
401 				}
402 			}
403 		}
404 	}
405 
406 	/* If no package matched and no output produced set error flag */
407 	if (!output)
408 		errflg++;
409 
410 	/* verify that each package listed on command line was output */
411 	for (i = 0; i < pkgcnt; ++i) {
412 		if (pkg[i]) {
413 			errflg++;
414 			if (!qflag) {
415 				if (pflag == 1)
416 					logerr(gettext(ERR_NOPINFO), pkg[i]);
417 				else
418 					logerr(gettext(ERR_NOINFO), pkg[i]);
419 			} else
420 				return;
421 		}
422 	}
423 	(void) pkginfo(&info, NULL); /* free up all memory and open fds */
424 }
425 
426 static void
dumpinfo(struct cfstat * dp,int pkgLngth)427 dumpinfo(struct cfstat *dp, int pkgLngth)
428 {
429 	register int i;
430 	char	*pt;
431 	char	category[128];
432 
433 	if (qflag) {
434 		return; /* print nothing */
435 	}
436 
437 	if (rflag) {
438 		(void) puts((info.basedir) ? info.basedir : "none");
439 		return;
440 	}
441 
442 	if (Lflag) {
443 		(void) puts(info.pkginst);
444 		return;
445 	} else if (xflag) {
446 		(void) printf(XFMT, pkgLngth, pkgLngth, info.pkginst,
447 		    info.name);
448 
449 		if (info.arch || info.version) {
450 			(void) printf(CFMT, pkgLngth, pkgLngth, "");
451 			if (info.arch)
452 				(void) printf("(%s) ", info.arch);
453 			if (info.version)
454 				(void) printf("%s", info.version);
455 			(void) printf("\n");
456 		}
457 		return;
458 	} else if (!lflag) {
459 		if (info.catg) {
460 			(void) sscanf(info.catg, "%[^, \t\n]", category);
461 		} else {
462 			(void) strcpy(category, "(unknown)");
463 		}
464 		(void) printf(SFMT, category, pkgLngth, pkgLngth, info.pkginst,
465 		    info.name);
466 		return;
467 	}
468 	if (info.pkginst)
469 		(void) printf(FMT, "PKGINST", info.pkginst);
470 	if (info.name)
471 		(void) printf(FMT, "NAME", info.name);
472 	if (lflag && info.catg)
473 		(void) printf(FMT, "CATEGORY", info.catg);
474 	if (lflag && info.arch)
475 		(void) printf(FMT, "ARCH", info.arch);
476 	if (info.version)
477 		(void) printf(FMT, "VERSION", info.version);
478 	if (info.basedir)
479 		(void) printf(FMT, "BASEDIR", info.basedir);
480 	if (info.vendor)
481 		(void) printf(FMT, "VENDOR", info.vendor);
482 
483 	for (i = 0; parmlst[i]; ++i) {
484 		if ((pt = pkgparam(info.pkginst, parmlst[i])) != NULL && *pt)
485 			(void) printf(FMT, parmlst[i], pt);
486 	}
487 	if (info.status == PI_SPOOLED)
488 		(void) printf(FMT, "STATUS", gettext("spooled"));
489 	else if (info.status == PI_PARTIAL)
490 		(void) printf(FMT, "STATUS",
491 		    gettext("partially installed"));
492 	else if (info.status == PI_INSTALLED)
493 		(void) printf(FMT, "STATUS",
494 		    gettext("completely installed"));
495 	else
496 		(void) printf(FMT, "STATUS", gettext("(unknown)"));
497 
498 	(void) pkgparam(NULL, NULL);
499 
500 	if (!lflag) {
501 		(void) putchar('\n');
502 		return;
503 	}
504 
505 	if (strcmp(pkgdir, get_PKGLOC()))
506 		getinfo(dp);
507 
508 	if (dp->spooled)
509 		(void) printf(gettext("%10s:  %7ld spooled pathnames\n"),
510 		    "FILES", dp->spooled);
511 	if (dp->installed)
512 		(void) printf(gettext("%10s:  %7ld installed pathnames\n"),
513 		    "FILES", dp->installed);
514 	if (dp->partial)
515 		(void) printf(gettext("%20d partially installed pathnames\n"),
516 		    dp->partial);
517 	if (dp->shared)
518 		(void) printf(gettext("%20d shared pathnames\n"), dp->shared);
519 	if (dp->link)
520 		(void) printf(gettext("%20d linked files\n"), dp->link);
521 	if (dp->dirs)
522 		(void) printf(gettext("%20d directories\n"), dp->dirs);
523 	if (dp->exec)
524 		(void) printf(gettext("%20d executables\n"), dp->exec);
525 	if (dp->setuid)
526 		(void) printf(gettext("%20d setuid/setgid executables\n"),
527 		    dp->setuid);
528 	if (dp->info)
529 		(void) printf(gettext("%20d package information files\n"),
530 		    dp->info+1); /* pkgmap counts! */
531 
532 	if (dp->tblks)
533 		(void) printf(gettext("%20ld blocks used (approx)\n"),
534 		    dp->tblks);
535 
536 	(void) putchar('\n');
537 }
538 
539 static struct cfstat *
fpkg(char * pkginst)540 fpkg(char *pkginst)
541 {
542 	struct cfstat *dp, *last;
543 
544 	dp = data;
545 	last = NULL;
546 	while (dp) {
547 		if (strcmp(dp->pkginst, pkginst) == 0)
548 			return (dp);
549 		last = dp;
550 		dp = dp->next;
551 	}
552 	dp = (struct cfstat *)calloc(1, sizeof (struct cfstat));
553 	if (!dp) {
554 		progerr(gettext("no memory, malloc() failed"));
555 		exit(1);
556 	}
557 	if (!last)
558 		data = dp;
559 	else
560 		last->next = dp; /* link list */
561 	(void) strcpy(dp->pkginst, pkginst);
562 	return (dp);
563 }
564 
565 #define	SEPAR	','
566 
567 static int
iscatg(char * list)568 iscatg(char *list)
569 {
570 	register int i;
571 	register char *pt;
572 	int	match;
573 
574 	if (!ckcatg[0])
575 		return (0); /* no specification implies all packages */
576 
577 	if (!list)
578 		return (1); /* no category specified in pkginfo is a bug */
579 
580 	match = 0;
581 	do {
582 		if (pt = strchr(list, ','))
583 			*pt = '\0';
584 
585 		for (i = 0; ckcatg[i]; /* void */) {
586 			/* bug id 1081607 */
587 			if (!strcasecmp(list, ckcatg[i++])) {
588 				match++;
589 				break;
590 			}
591 		}
592 
593 		if (pt)
594 			*pt++ = ',';
595 		if (match)
596 			return (0);
597 		list = pt; /* points to next one */
598 	} while (pt);
599 	return (1);
600 }
601 
602 static void
look_for_installed(void)603 look_for_installed(void)
604 {
605 	struct dirent *drp;
606 	DIR	*dirfp;
607 	char	path[PATH_MAX];
608 
609 	if ((dirfp = opendir(pkgdir)) == NULL)
610 		return;
611 
612 	while (drp = readdir(dirfp)) {
613 		if (drp->d_name[0] == '.')
614 			continue;
615 
616 		if (pkgcnt && (selectp(drp->d_name) < 0))
617 			continue;
618 
619 		if (!pkginfofind(path, pkgdir, drp->d_name))
620 			continue; /* doesn't appear to be a package */
621 
622 		(void) fpkg(drp->d_name);
623 	}
624 	(void) closedir(dirfp);
625 }
626 
627 static int
selectp(char * p)628 selectp(char *p)
629 {
630 	register int i;
631 
632 	for (i = 0; i < pkgcnt; ++i) {
633 		if (pkg[i] && pkgnmchk(p, pkg[i], 1) == 0)
634 			return (i);
635 	}
636 	return (-1);
637 }
638 
639 static void
rdcontents(void)640 rdcontents(void)
641 {
642 	struct cfstat	*dp;
643 	struct pinfo	*pinfo;
644 	int		n;
645 	PKGserver	server;
646 
647 	if (!socfile(&server, B_TRUE) ||
648 	    pkgopenfilter(server, pkgcnt == 1 ? pkg[0] :  NULL) != 0)
649 		exit(1);
650 
651 	/* check the contents file to look for referenced packages */
652 	while ((n = srchcfile(&entry, "*", server)) > 0) {
653 		for (pinfo = entry.pinfo; pinfo; pinfo = pinfo->next) {
654 			/* see if entry is used by indicated packaged */
655 			if (pkgcnt && (selectp(pinfo->pkg) < 0))
656 				continue;
657 
658 			dp = fpkg(pinfo->pkg);
659 			pkgusage(dp, &entry);
660 
661 			if (entry.npkgs > 1)
662 				dp->shared++;
663 
664 			/*
665 			 * Only objects specifically tagged with '!' event
666 			 * character are considered "partial", everything
667 			 * else is considered "installed" (even server
668 			 * objects).
669 			 */
670 			switch (pinfo->status) {
671 			case '!' :
672 				dp->partial++;
673 				break;
674 			default :
675 				dp->installed++;
676 				break;
677 			}
678 		}
679 	}
680 	if (n < 0) {
681 		char	*errstr = getErrstr();
682 		progerr(gettext("bad entry read in contents file"));
683 		logerr(gettext("pathname: %s"),
684 		    (entry.path && *entry.path) ? entry.path : "Unknown");
685 		logerr(gettext("problem: %s"),
686 		    (errstr && *errstr) ? errstr : "Unknown");
687 		exit(1);
688 	}
689 	pkgcloseserver(server);
690 }
691 
692 static void
getinfo(struct cfstat * dp)693 getinfo(struct cfstat *dp)
694 {
695 	int		n;
696 	char		pkgmap[MAXPATHLEN];
697 	VFP_T		*vfp;
698 
699 	(void) snprintf(pkgmap, sizeof (pkgmap),
700 	    "%s/%s/pkgmap", pkgdir, dp->pkginst);
701 
702 	if (vfpOpen(&vfp, pkgmap, "r", VFP_NEEDNOW) != 0) {
703 		progerr(gettext("unable open \"%s\" for reading"), pkgmap);
704 		exit(1);
705 	}
706 
707 	dp->spooled = 1; /* pkgmap counts! */
708 
709 	while ((n = gpkgmapvfp(&entry, vfp)) > 0) {
710 		dp->spooled++;
711 		pkgusage(dp, &entry);
712 	}
713 
714 	if (n < 0) {
715 		char	*errstr = getErrstr();
716 		progerr(gettext("bad entry read in pkgmap file"));
717 		logerr(gettext("pathname: %s"),
718 		    (entry.path && *entry.path) ? entry.path : "Unknown");
719 		logerr(gettext("problem: %s"),
720 		    (errstr && *errstr) ? errstr : "Unknown");
721 		exit(1);
722 	}
723 
724 	(void) vfpClose(&vfp);
725 }
726 
727 static void
pkgusage(struct cfstat * dp,struct cfent * pentry)728 pkgusage(struct cfstat *dp, struct cfent *pentry)
729 {
730 	if (pentry->ftype == 'i') {
731 		dp->info++;
732 		return;
733 	} else if (pentry->ftype == 'l') {
734 		dp->link++;
735 	} else {
736 		if ((pentry->ftype == 'd') || (pentry->ftype == 'x'))
737 			dp->dirs++;
738 
739 		/* Only collect mode stats if they would be meaningful. */
740 		if (pentry->ainfo.mode != BADMODE) {
741 			if (pentry->ainfo.mode & 06000)
742 				dp->setuid++;
743 			if (!strchr("dxcbp", pentry->ftype) &&
744 			    (pentry->ainfo.mode & 0111))
745 				dp->exec++;
746 		}
747 	}
748 
749 	if (strchr("ifve", pentry->ftype))
750 		dp->tblks += nblock(pentry->cinfo.size);
751 }
752 
753 static void
usage(void)754 usage(void)
755 {
756 	char *prog = get_prog_name();
757 
758 	/* bug # 1081606 */
759 	(void) fprintf(stderr, gettext(ERR_USAGE), prog, prog);
760 
761 	exit(1);
762 }
763 
764 void
quit(int retval)765 quit(int retval)
766 {
767 	exit(retval);
768 }
769