/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
/* All Rights Reserved */


#define	__EXTENTIONS__

#include <stdio.h>
#include <limits.h>
#include <unistd.h>
#include <stdlib.h>
#include <locale.h>
#include <libintl.h>
#include <strings.h>
#include <string.h>
#include <dirent.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <pkginfo.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <sys/mman.h>
#include <pkgstrct.h>
#include <pkglocs.h>
#include <errno.h>
#include <ctype.h>

#include <pkglib.h>
#include <instzones_api.h>
#include <libadm.h>
#include <libinst.h>

extern char	*pkgdir;
extern int	pkginfofind(char *path, char *pkg_dir, char *pkginst);

#define	ERR_USAGE	"usage:\n" \
			"%s [-q] [-pi] [-x|l] [options] [pkg ...]\n" \
			"%s -d device [-q] [-x|l] [options] [pkg ...]\n" \
			"where\n" \
			"  -q #quiet mode\n" \
			"  -p #select partially installed packages\n" \
			"  -i #select completely installed packages\n" \
			"  -x #extracted listing\n" \
			"  -l #long listing\n" \
			"  -r #relocation base \n" \
			"and options may include:\n" \
			"  -c category, [category...]\n" \
			"  -a architecture\n" \
			"  -v version\n"

#define	ERR_INCOMP0	"-L and -l/-x/-r flags are incompatible"
#define	ERR_INCOMP1	"-l and -x/-r flags are not compatible"
#define	ERR_INCOMP2	"-x and -l/-r flags are not compatible"
#define	ERR_INCOMP3	"-r and -x/-x flags are not compatible"
#define	ERR_NOINFO	"ERROR: information for \"%s\" was not found"
#define	ERR_NOPINFO	"ERROR: No partial information for \"%s\" was found"
#define	ERR_BADINFO	"pkginfo file is corrupt or missing"
#define	ERR_ROOT_SET	"Could not set install root from the environment."
#define	ERR_ROOT_CMD	"Command line install root contends with environment."

/* Format for dumping package attributes in dumpinfo() */
#define	FMT	"%10s:  %s\n"
#define	SFMT	"%-11.11s %-*.*s %s\n"
#define	CFMT	"%*.*s  "
#define	XFMT	"%-*.*s  %s\n"

#define	nblock(size)	((size + (DEV_BSIZE - 1)) / DEV_BSIZE)
#define	MAXCATG	64

static char	*device = NULL;
static char	*parmlst[] = {
	"DESC", "PSTAMP", "INSTDATE", "VSTOCK", "SERIALNUM", "HOTLINE",
	"EMAIL", NULL
};

static int	errflg = 0;
static int	qflag = 0;
static int	iflag = -1;
static int	pflag = -1;
static int	lflag = 0;
static int	Lflag = 0;
static int	Nflag = 0;
static int	xflag = 0;
static int	rflag = 0; 		/* bug # 1081606 */
static struct cfent	entry;
static char	**pkg = NULL;
static int	pkgcnt = 0;
static char	*ckcatg[MAXCATG] = {NULL};
static int	ncatg = 0;
static char	*ckvers = NULL;
static char	*ckarch = NULL;

static struct cfstat {
	char	pkginst[32];
	short	exec;
	short	dirs;
	short	link;
	short	partial;
	long	spooled;
	long	installed;
	short	info;
	short	shared;
	short	setuid;
	long	tblks;
	struct cfstat *next;
} *data;
static struct pkginfo info;

static struct	cfstat *fpkg(char *pkginst);
static int	iscatg(char *list);
static int	selectp(char *p);
static void	usage(void), look_for_installed(void),
		report(void), rdcontents(void);
static void	pkgusage(struct cfstat *dp, struct cfent *pentry);
static void	getinfo(struct cfstat *dp);
static void	dumpinfo(struct cfstat *dp, int pkgLngth);

int
main(int argc, char **argv)
{
	int	c;

	pkgdir = NULL;
	setErrstr(NULL);

	/* initialize locale mechanism */

	(void) setlocale(LC_ALL, "");

#if !defined(TEXT_DOMAIN)	/* Should be defined by cc -D */
#define	TEXT_DOMAIN "SYS_TEST"
#endif
	(void) textdomain(TEXT_DOMAIN);

	/* determine program name */

	(void) set_prog_name(argv[0]);

	/* tell spmi zones interface how to access package output functions */

	z_set_output_functions(echo, echoDebug, progerr);

	/* establish installation root directory */

	if (!set_inst_root(getenv("PKG_INSTALL_ROOT"))) {
		progerr(gettext(ERR_ROOT_SET));
		exit(1);
	}

	while ((c = getopt(argc, argv, "LNR:xv:a:d:qrpilc:?")) != EOF) {
		switch (c) {
		    case 'v':
			ckvers = optarg;
			break;

		    case 'a':
			ckarch = optarg;
			break;

		    case 'd':
			/* -d could specify stream or mountable device */
			device = flex_device(optarg, 1);
			break;

		    case 'q':
			qflag++;
			break;

		    case 'i':
			iflag = 1;
			if (pflag > 0)
				usage();
			pflag = 0;
			break;

		    case 'p':
			pflag = 1;
			if (iflag > 0)
				usage();
			iflag = 0;
			break;

		    case 'N':
			Nflag++;
			break;

		    case 'L':
			if (xflag || lflag || rflag) {
				progerr(gettext(ERR_INCOMP0));
				usage();
			}
			Lflag++;
			break;

		    case 'l':
			if (xflag || rflag) {
				progerr(gettext(ERR_INCOMP1));
				usage();
			}
			lflag++;
			break;

		    case 'x':
			/* bug # 1081606 */
			if (lflag || rflag) {
				progerr(gettext(ERR_INCOMP2));
				usage();
			}
			xflag++;
			break;

		    case 'r':
			if (lflag || xflag || Lflag) {
				progerr(gettext(ERR_INCOMP0));
				usage();
			}
			rflag++;
			break;

		    case 'c':
			ckcatg[ncatg++] = strtok(optarg, " \t\n, ");
			while (ckcatg[ncatg] = strtok(NULL, " \t\n, "))
				ncatg++;
			break;

		/* added for newroot functions */
		    case 'R':
			if (!set_inst_root(optarg)) {
				progerr(gettext(ERR_ROOT_CMD));
				exit(1);
			}
			break;

		    default:
			usage();
		}
	}

	/*
	 * implement the newroot option
	 */
	set_PKGpaths(get_inst_root());	/* set up /var... directories */

	/*
	 * Open the install DB, if one exists.
	 */

	pkg = &argv[optind];
	pkgcnt = (argc - optind);

	if (pkg[0] && strcmp(pkg[0], "all") == NULL) {
		pkgcnt = 0;
		pkg[0] = NULL;
	}

	if (pkgdir == NULL)
		pkgdir = get_PKGLOC(); 	/* we need this later */

	/* convert device appropriately */
	if (pkghead(device))
		exit(1);

	/*
	 * If we are to inspect a spooled package we are only interested in
	 * the pkginfo file in the spooled pkg.  We have a spooled pkg if
	 * device is not NULL.
	 */

	look_for_installed();

	if (lflag && strcmp(pkgdir, get_PKGLOC()) == 0) {
		/* look at contents file */
		rdcontents();

	}

	/*
	 * If we are to inspect a spooled package we are only interested in
	 * the pkginfo file in the spooled pkg so we skip any Reg 4 DB
	 * lookups and use the old algorithm. We have a spooled pkg if
	 * device is not NULL.
	 */

	report();

	(void) pkghead(NULL);

	return (errflg ? 1 : 0);
}

static void
report(void)
{
	struct cfstat *dp, *choice;
	int	i;
	int	pkgLgth = 0;
	int	longestPkg = 0;
	boolean_t output = B_FALSE;

	for (;;) {
		choice = (struct cfstat *)0;
		for (dp = data; dp; dp = dp->next) {
			pkgLgth = strlen(dp->pkginst);
			if (pkgLgth > longestPkg)
				longestPkg = pkgLgth;
		}
		for (dp = data; dp; dp = dp->next) {
			/* get information about this package */
			if (dp->installed < 0)
				continue; /* already used */
			if (Lflag && pkgcnt) {
				choice = dp;
				break;
			} else if (!choice ||
			    (strcmp(choice->pkginst, dp->pkginst) > 0))
				choice = dp;
		}
		if (!choice)
			break; /* no more packages */

		if (pkginfo(&info, choice->pkginst, ckarch, ckvers)) {
			choice->installed = (-1);
			continue;
		}

		/*
		 * Confirm that the pkginfo file contains the
		 * required information.
		 */
		if (info.name == NULL || *(info.name) == NULL ||
		    info.arch == NULL || *(info.arch) == NULL ||
		    info.version == NULL || *(info.version) == NULL ||
		    info.catg == NULL || *(info.catg) == NULL) {
			progerr(gettext(ERR_BADINFO));
			errflg++;
			return;
		}

		/* is it in an appropriate catgory? */
		if (iscatg(info.catg)) {
			choice->installed = (-1);
			continue;
		}

		if (!pflag &&
			/* don't include partially installed packages */
			(choice->partial || (info.status == PI_PARTIAL) ||
				(info.status == PI_UNKNOWN))) {
			choice->installed = (-1);
			continue;
		}

		if (!iflag && (info.status == PI_INSTALLED)) {
			/* don't include completely installed packages */
			choice->installed = (-1);
			continue;
		}

		output = B_TRUE;
		dumpinfo(choice, longestPkg);
		choice->installed = (-1);
		if (pkgcnt) {
			i = selectp(choice->pkginst);
			if (i >= 0)
				pkg[i] = NULL;
			else {
				if (qflag) {
					errflg++;
					return;
				}
			}
		}
	}

	/* If no package matched and no output produced set error flag */
	if (!output)
		errflg++;

	/* verify that each package listed on command line was output */
	for (i = 0; i < pkgcnt; ++i) {
		if (pkg[i]) {
			errflg++;
			if (!qflag) {
				if (pflag == 1)
					logerr(gettext(ERR_NOPINFO), pkg[i]);
				else
					logerr(gettext(ERR_NOINFO), pkg[i]);
			} else
				return;
		}
	}
	(void) pkginfo(&info, NULL); /* free up all memory and open fds */
}

static void
dumpinfo(struct cfstat *dp, int pkgLngth)
{
	register int i;
	char	*pt;
	char	category[128];

	if (qflag) {
		return; /* print nothing */
	}

	if (rflag) {
		(void) puts((info.basedir) ? info.basedir : "none");
		return;
	}

	if (Lflag) {
		(void) puts(info.pkginst);
		return;
	} else if (xflag) {
		(void) printf(XFMT, pkgLngth, pkgLngth, info.pkginst,
		    info.name);

		if (info.arch || info.version) {
			(void) printf(CFMT, pkgLngth, pkgLngth, "");
			if (info.arch)
				(void) printf("(%s) ", info.arch);
			if (info.version)
				(void) printf("%s", info.version);
			(void) printf("\n");
		}
		return;
	} else if (!lflag) {
		if (info.catg) {
			(void) sscanf(info.catg, "%[^, \t\n]", category);
		} else {
			(void) strcpy(category, "(unknown)");
		}
		(void) printf(SFMT, category, pkgLngth, pkgLngth, info.pkginst,
		    info.name);
		return;
	}
	if (info.pkginst)
		(void) printf(FMT, "PKGINST", info.pkginst);
	if (info.name)
		(void) printf(FMT, "NAME", info.name);
	if (lflag && info.catg)
		(void) printf(FMT, "CATEGORY", info.catg);
	if (lflag && info.arch)
		(void) printf(FMT, "ARCH", info.arch);
	if (info.version)
		(void) printf(FMT, "VERSION", info.version);
	if (info.basedir)
		(void) printf(FMT, "BASEDIR", info.basedir);
	if (info.vendor)
		(void) printf(FMT, "VENDOR", info.vendor);

	for (i = 0; parmlst[i]; ++i) {
		if ((pt = pkgparam(info.pkginst, parmlst[i])) != NULL && *pt)
			(void) printf(FMT, parmlst[i], pt);
	}
	if (info.status == PI_SPOOLED)
		(void) printf(FMT, "STATUS", gettext("spooled"));
	else if (info.status == PI_PARTIAL)
		(void) printf(FMT, "STATUS",
		    gettext("partially installed"));
	else if (info.status == PI_INSTALLED)
		(void) printf(FMT, "STATUS",
		    gettext("completely installed"));
	else
		(void) printf(FMT, "STATUS", gettext("(unknown)"));

	(void) pkgparam(NULL, NULL);

	if (!lflag) {
		(void) putchar('\n');
		return;
	}

	if (strcmp(pkgdir, get_PKGLOC()))
		getinfo(dp);

	if (dp->spooled)
		(void) printf(gettext("%10s:  %7ld spooled pathnames\n"),
		    "FILES", dp->spooled);
	if (dp->installed)
		(void) printf(gettext("%10s:  %7ld installed pathnames\n"),
		    "FILES", dp->installed);
	if (dp->partial)
		(void) printf(gettext("%20d partially installed pathnames\n"),
		    dp->partial);
	if (dp->shared)
		(void) printf(gettext("%20d shared pathnames\n"), dp->shared);
	if (dp->link)
		(void) printf(gettext("%20d linked files\n"), dp->link);
	if (dp->dirs)
		(void) printf(gettext("%20d directories\n"), dp->dirs);
	if (dp->exec)
		(void) printf(gettext("%20d executables\n"), dp->exec);
	if (dp->setuid)
		(void) printf(gettext("%20d setuid/setgid executables\n"),
		    dp->setuid);
	if (dp->info)
		(void) printf(gettext("%20d package information files\n"),
		    dp->info+1); /* pkgmap counts! */

	if (dp->tblks)
		(void) printf(gettext("%20ld blocks used (approx)\n"),
		    dp->tblks);

	(void) putchar('\n');
}

static struct cfstat *
fpkg(char *pkginst)
{
	struct cfstat *dp, *last;

	dp = data;
	last = (struct cfstat *)0;
	while (dp) {
		if (strcmp(dp->pkginst, pkginst) == NULL)
			return (dp);
		last = dp;
		dp = dp->next;
	}
	dp = (struct cfstat *)calloc(1, sizeof (struct cfstat));
	if (!dp) {
		progerr(gettext("no memory, malloc() failed"));
		exit(1);
	}
	if (!last)
		data = dp;
	else
		last->next = dp; /* link list */
	(void) strcpy(dp->pkginst, pkginst);
	return (dp);
}

#define	SEPAR	','

static int
iscatg(char *list)
{
	register int i;
	register char *pt;
	int	match;

	if (!ckcatg[0])
		return (0); /* no specification implies all packages */

	if (!list)
		return (1); /* no category specified in pkginfo is a bug */

	match = 0;
	do {
		if (pt = strchr(list, ','))
			*pt = '\0';

		for (i = 0; ckcatg[i]; /* void */) {
			/* bug id 1081607 */
			if (!strcasecmp(list, ckcatg[i++])) {
				match++;
				break;
			}
		}

		if (pt)
			*pt++ = ',';
		if (match)
			return (0);
		list = pt; /* points to next one */
	} while (pt);
	return (1);
}

static void
look_for_installed(void)
{
	struct dirent *drp;
	struct stat	status;
	DIR	*dirfp;
	char	path[PATH_MAX];

	if ((dirfp = opendir(pkgdir)) == NULL)
		return;

	while (drp = readdir(dirfp)) {
		if (drp->d_name[0] == '.')
			continue;

		if (pkgcnt && (selectp(drp->d_name) < 0))
			continue;

		if (!pkginfofind(path, pkgdir, drp->d_name))
			continue; /* doesn't appear to be a package */

		(void) fpkg(drp->d_name);
	}
	(void) closedir(dirfp);
}

static int
selectp(char *p)
{
	register int i;

	for (i = 0; i < pkgcnt; ++i) {
		if (pkg[i] && pkgnmchk(p, pkg[i], 1) == 0)
			return (i);
	}
	return (-1);
}

static void
rdcontents(void)
{
	struct cfstat	*dp;
	struct pinfo	*pinfo;
	int		n;
	PKGserver	server;

	if (!socfile(&server, B_TRUE) ||
	    pkgopenfilter(server, pkgcnt == 1 ? pkg[0] :  NULL) != 0)
		exit(1);

	/* check the contents file to look for referenced packages */
	while ((n = srchcfile(&entry, "*", server)) > 0) {
		for (pinfo = entry.pinfo; pinfo; pinfo = pinfo->next) {
			/* see if entry is used by indicated packaged */
			if (pkgcnt && (selectp(pinfo->pkg) < 0))
				continue;

			dp = fpkg(pinfo->pkg);
			pkgusage(dp, &entry);

			if (entry.npkgs > 1)
				dp->shared++;

			/*
			 * Only objects specifically tagged with '!' event
			 * character are considered "partial", everything
			 * else is considered "installed" (even server
			 * objects).
			 */
			switch (pinfo->status) {
			case '!' :
				dp->partial++;
				break;
			default :
				dp->installed++;
				break;
			}
		}
	}
	if (n < 0) {
		char	*errstr = getErrstr();
		progerr(gettext("bad entry read in contents file"));
		logerr(gettext("pathname: %s"),
		    (entry.path && *entry.path) ? entry.path : "Unknown");
		logerr(gettext("problem: %s"),
		    (errstr && *errstr) ? errstr : "Unknown");
		exit(1);
	}
	pkgcloseserver(server);
}

static void
getinfo(struct cfstat *dp)
{
	int		n;
	char		pkgmap[MAXPATHLEN];
	VFP_T		*vfp;

	(void) snprintf(pkgmap, sizeof (pkgmap),
			"%s/%s/pkgmap", pkgdir, dp->pkginst);

	if (vfpOpen(&vfp, pkgmap, "r", VFP_NEEDNOW) != 0) {
		progerr(gettext("unable open \"%s\" for reading"), pkgmap);
		exit(1);
	}

	dp->spooled = 1; /* pkgmap counts! */

	while ((n = gpkgmapvfp(&entry, vfp)) > 0) {
		dp->spooled++;
		pkgusage(dp, &entry);
	}

	if (n < 0) {
		char	*errstr = getErrstr();
		progerr(gettext("bad entry read in pkgmap file"));
		logerr(gettext("pathname: %s"),
		    (entry.path && *entry.path) ? entry.path : "Unknown");
		logerr(gettext("problem: %s"),
		    (errstr && *errstr) ? errstr : "Unknown");
		exit(1);
	}

	(void) vfpClose(&vfp);
}

static void
pkgusage(struct cfstat *dp, struct cfent *pentry)
{
	if (pentry->ftype == 'i') {
		dp->info++;
		return;
	} else if (pentry->ftype == 'l') {
		dp->link++;
	} else {
		if ((pentry->ftype == 'd') || (pentry->ftype == 'x'))
			dp->dirs++;

		/* Only collect mode stats if they would be meaningful. */
		if (pentry->ainfo.mode != BADMODE) {
			if (pentry->ainfo.mode & 06000)
				dp->setuid++;
			if (!strchr("dxcbp", pentry->ftype) &&
			(pentry->ainfo.mode & 0111))
				dp->exec++;
		}
	}

	if (strchr("ifve", pentry->ftype))
		dp->tblks += nblock(pentry->cinfo.size);
}

static void
usage(void)
{
	char *prog = get_prog_name();

	/* bug # 1081606 */
	(void) fprintf(stderr, gettext(ERR_USAGE), prog, prog);

	exit(1);
}

void
quit(int retval)
{
	exit(retval);
}