root/usr/src/cmd/svr4pkg/pkginfo/pkginfo.c
/*
 * 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 (c) 2017 Peter Tribble.
 */

/*
 * 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") == 0) {
                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) == '\0' ||
                    info.arch == NULL || *(info.arch) == '\0' ||
                    info.version == NULL || *(info.version) == '\0' ||
                    info.catg == NULL || *(info.catg) == '\0') {
                        progerr(gettext(ERR_BADINFO));
                        errflg++;
                        return;
                }

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

                if (!pflag &&
                    (choice->partial || (info.status == PI_PARTIAL) ||
                    (info.status == PI_UNKNOWN))) {
                        /* don't include partially installed packages */
                        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 = NULL;
        while (dp) {
                if (strcmp(dp->pkginst, pkginst) == 0)
                        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;
        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);
}