root/usr/src/lib/libadm/common/pkginfo.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (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) 1984, 1986, 1987, 1988, 1989 AT&T */
/*        All Rights Reserved   */


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

/*LINTLIBRARY*/

/*  5-20-92   added newroot functions  */

#include <stdio.h>
#include <limits.h>
#include <stdarg.h>
#include <unistd.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <pkginfo.h>
#include <pkgstrct.h>
#include <pkglocs.h>
#include <errno.h>
#include "libadm.h"

static void     initpkg(struct pkginfo *);
static int      rdconfig(struct pkginfo *, char *, char *);
static int      ckinfo(char *, char *, char *);
static int      ckinst(char *, char *, char *, char *, char *);
static int      verscmp(char *, char *);
static int      archcmp(char *, char *);
static int      compver(char *, char *);

/*
 * Globals:
 *      pkgdir - specifies the directory where information about packages
 *          resides, i.e. the pkginfo file is located in a subdirectory
 *
 * Caveats:
 *      The structure provided via "info" will contain malloc'd information;
 *          this will be free'd upon the next call to pkginfo with this
 *          same structure.  Application calls must make sure this structure
 *          is null on the first call, or else we'll free static memory areas
 *      If the "pkg" argument is a wildcard specification, the next found
 *          instance available which matches the request will be returned
 *      If the "pkg" argument is a NULL pointer, the structure pointed to
 *          via "info" will have its elements deallocated and all files
 *          associated with this routine will be closed
 *
 * Return codes:
 *      A non-zero exit code indicates error with "errno" appropriately set:
 *          EINVAL - invalid argument
 *          ESRCH - there are no more instances of this package around
 *          EACCESS - unable to access files which should have been there
 */

/*VARARGS*/
int
pkginfo(struct pkginfo *info, char *pkginst, ...)
{
        char    *ckarch, *ckvers;
        int     check;
        va_list ap;

        va_start(ap, pkginst);
        if (info == NULL) {
                errno = EINVAL;
                return (-1);
        }
        if (pkginst == NULL) {
                info->pkginst = NULL;
                (void) fpkginfo(info, NULL);
                (void) fpkginst(NULL);
                return (0);
        }
        ckarch = va_arg(ap, char *);
        ckvers = va_arg(ap, char *);
        va_end(ap);

        check = 0;
        if (pkgnmchk(pkginst, "all", 1)) {
                /* wild card specification */
                pkginst = fpkginst(pkginst, ckarch, ckvers);
                if (pkginst == NULL)
                        return (-1);
        } else {
                /* request to check indicated instance */
                if (ckarch || ckvers)
                        check++;
        }

        info->pkginst = NULL;
        if (fpkginfo(info, pkginst))
                return (-1);

        if (check) {
                /*
                 * verify that the provided instance matches
                 * any arch & vers specs that were provided
                 */
                if (ckinst(pkginst, info->arch, info->version, ckarch,
                    ckvers)) {
                        errno = ESRCH;
                        return (-1);
                }
        }
        return (0);
}
/*ARGSUSED*/

int
fpkginfo(struct pkginfo *info, char *pkginst)
{

        if (info == NULL) {
                errno = EINVAL;
                return (-1);
        }

        initpkg(info);

        if (pkginst == NULL)
                return (0);
        else if (pkgnmchk(pkginst, "all", 1)) {
                errno = EINVAL; /* not an instance identifier */
                return (-1);
        }
        if (pkgdir == NULL)
                pkgdir = get_PKGLOC();

        if (rdconfig(info, pkginst, NULL)) {
                initpkg(info);
                return (-1);
        }
        return (0);
}

static void
initpkg(struct pkginfo *info)
{
        /* free previously allocated space */
        if (info->pkginst) {
                free(info->pkginst);
                if (info->arch)
                        free(info->arch);
                if (info->version)
                        free(info->version);
                if (info->basedir)
                        free(info->basedir);
                if (info->name)
                        free(info->name);
                if (info->vendor)
                        free(info->vendor);
                if (info->catg)
                        free(info->catg);
        }

        info->pkginst = NULL;
        info->arch = info->version = NULL;
        info->basedir = info->name = NULL;
        info->vendor = info->catg = NULL;
        info->status = PI_UNKNOWN;
}

static int
rdconfig(struct pkginfo *info, char *pkginst, char *ckvers)
{
        FILE    *fp;
        char    temp[256];
        char    *value, *pt, *copy, **memloc;
        int     count;

        if ((fp = pkginfopen(pkgdir, pkginst)) == NULL) {
                errno = EACCES;
                return (-1);
        }

        *temp = '\0';
        count = 0;
        while (value = fpkgparam(fp, temp)) {
                if (strcmp(temp, "ARCH") == 0 ||
                    strcmp(temp, "CATEGORY") == 0) {
                        /* remove all whitespace from value */
                        pt = copy = value;
                        while (*pt) {
                                if (!isspace((unsigned char)*pt))
                                        *copy++ = *pt;
                                pt++;
                        }
                        *copy = '\0';
                }
                count++;
                memloc = NULL;
                if (strcmp(temp, "NAME") == 0)
                        memloc = &info->name;
                else if (strcmp(temp, "VERSION") == 0)
                        memloc = &info->version;
                else if (strcmp(temp, "ARCH") == 0)
                        memloc = &info->arch;
                else if (strcmp(temp, "VENDOR") == 0)
                        memloc = &info->vendor;
                else if (strcmp(temp, "BASEDIR") == 0)
                        memloc = &info->basedir;
                else if (strcmp(temp, "CATEGORY") == 0)
                        memloc = &info->catg;

                temp[0] = '\0';
                if (memloc == NULL)
                        continue; /* not a parameter we're looking for */

                *memloc = strdup(value);
                if (!*memloc) {
                        (void) fclose(fp);
                        errno = ENOMEM;
                        return (-1); /* malloc from strdup failed */
                }
        }
        (void) fclose(fp);

        if (!count) {
                errno = ESRCH;
                return (-1);
        }

        info->status = (strcmp(pkgdir, get_PKGLOC()) ? PI_SPOOLED :
            PI_INSTALLED);

        if (info->status == PI_INSTALLED) {
                (void) snprintf(temp, sizeof (temp),
                    "%s/%s/!I-Lock!", pkgdir, pkginst);
                if (access(temp, 0) == 0)
                        info->status = PI_PARTIAL;
                else {
                        (void) snprintf(temp, sizeof (temp),
                            "%s/%s/!R-Lock!", pkgdir, pkginst);
                        if (access(temp, 0) == 0)
                                info->status = PI_PARTIAL;
                }
        }
        info->pkginst = strdup(pkginst);
        return (0);
}

static int
ckinst(char *pkginst, char *pkgarch, char *pkgvers, char *ckarch, char *ckvers)
{
        if (ckarch && archcmp(ckarch, pkgarch))
                return (-1);
        if (ckvers) {
                /* Check for exact version match */
                if (verscmp(ckvers, pkgvers)) {
                        /* Check for compatable version */
                        if (compver(pkginst, ckvers))
                                return (-1);
                }
        }
        return (0);
}

/*VARARGS*/
char *
fpkginst(char *pkg, ...)
{
        static char pkginst[PKGSIZ+1];
        static DIR *pdirfp;
        struct dirent64 *dp;
        char    *ckarch, *ckvers;
        va_list ap;

        va_start(ap, pkg);

        if (pkg == NULL) {
                /* request to close or rewind the file */
                if (pdirfp) {
                        (void) closedir(pdirfp);
                        pdirfp = NULL;
                }
                return (NULL);
        }

        ckarch = va_arg(ap, char *);
        ckvers = va_arg(ap, char *);
        va_end(ap);

        if (!pkgdir)
                pkgdir = get_PKGLOC();

        if (!pdirfp && ((pdirfp = opendir(pkgdir)) == NULL)) {
                errno = EACCES;
                return (NULL);
        }

        while ((dp = readdir64(pdirfp)) != NULL) {
                if (dp->d_name[0] == '.')
                        continue;

                if (pkgnmchk(dp->d_name, pkg, 0))
                        continue; /* ignore invalid SVR4 package names */

                if (ckinfo(dp->d_name, ckarch, ckvers))
                        continue;

                /*
                 * Leave directory open in case user requests another
                 * instance.
                 */
                (void) strcpy(pkginst, dp->d_name);
                return (pkginst);
        }

        errno = ESRCH;
        /* close any file we might have open */
        (void) closedir(pdirfp);
        pdirfp = NULL;
        return (NULL);
}

static int
verscmp(char *request, char *actual)
{
        /* eat leading white space */
        while (isspace((unsigned char)*actual))
                actual++;
        while (isspace((unsigned char)*request))
                request++;

        while (*request || *actual) {
                /*
                 * Once the pointers don't match, return an error condition.
                 */

                if (*request++ != *actual++)
                        return (-1);

                /* eat white space if any in both the strings */
                if (isspace((unsigned char)*request)) {
                        if (*actual && !isspace((unsigned char)*actual))
                                return (-1);
                        while (isspace((unsigned char)*request))
                                request++;
                        while (isspace((unsigned char)*actual))
                                actual++;
                }
        }

        return (0);

}

static int
compver(char *pkginst, char *version)
{
        FILE *fp;
        char temp[256];

        (void) snprintf(temp, sizeof (temp),
            "%s/%s/install/compver", get_PKGLOC(), pkginst);
        if ((fp = fopen(temp, "r")) == NULL)
                return (-1);

        while (fgets(temp, 256, fp)) {
                if (*temp == '#')
                        continue;
                if (verscmp(temp, version) == 0) {
                        (void) fclose(fp);
                        return (0);
                }
        }
        (void) fclose(fp);
        return (-1);
}

static int
archcmp(char *arch, char *archlist)
{
        char *pt;

        if (arch == NULL)
                return (0);

        /* arch and archlist must not contain whitespace! */

        while (*archlist) {
                for (pt = arch; *pt && (*pt == *archlist); )
                        pt++, archlist++;
                if (!*pt && (!*archlist || (*archlist == ',')))
                        return (0);
                while (*archlist) {
                        if (*archlist++ == ',')
                                break;
                }
        }
        return (-1);
}

static int
ckinfo(char *inst, char *arch, char *vers)
{
        FILE    *fp;
        char    temp[128];
        char    file[PATH_MAX];
        char    *pt, *copy, *value, *myarch, *myvers;
        int     errflg;

        (void) snprintf(file, sizeof (file), "%s/%s/pkginfo", pkgdir, inst);
        if ((fp = fopen(file, "r")) == NULL)
                return (1);

        if ((arch == NULL) && (vers == NULL)) {
                (void) fclose(fp);
                return (0);
        }
        temp[0] = '\0';
        myarch = myvers = NULL;
        while (value = fpkgparam(fp, temp)) {
                if (strcmp(temp, "ARCH") == 0) {
                        /* remove all whitespace from value */
                        pt = copy = value;
                        while (*pt) {
                                if (!isspace((unsigned char)*pt))
                                        *copy++ = *pt;
                                pt++;
                        }
                        *copy = '\0';
                        myarch = value;
                        if (myvers)
                                break;
                } else if (strcmp(temp, "VERSION") == 0) {
                        myvers = value;
                        if (myarch)
                                break;
                } else
                        free(value);
                temp[0] = '\0';
        }
        (void) fclose(fp);
        errflg = 0;

        if (ckinst(inst, myarch, myvers, arch, vers))
                errflg++;

        if (myarch)
                free(myarch);
        if (myvers)
                free(myvers);

        return (errflg);
}