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