root/usr/src/cmd/svr4pkg/libinst/pkgdbmerg.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) 1989, 2010, Oracle and/or its affiliates. All rights reserved.
 */

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


#include <stdio.h>
#include <signal.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <assert.h>
#include <pkgstrct.h>
#include <sys/stat.h>
#include <locale.h>
#include <libintl.h>
#include <pkginfo.h>
#include <instzones_api.h>
#include <pkglib.h>
#include <libinst.h>
#include <messages.h>

/* merg() return codes */
#define MRG_SAME        0
#define MRG_DIFFERENT   1
#define MRG_REPLACE     2

/* typechg() return codes */
#define TYPE_OK         0
#define TYPE_WARNING    1
#define TYPE_IGNORED    2
#define TYPE_REPLACE    3
#define TYPE_FATAL      4

/* message pool */
#define ERR_OUTPUT      "unable to update package database"
#define ERR_PINFO       "missing pinfo structure for <%s>"
#define INFO_PROCESS    "   %2ld%% of information processed; continuing ..."

#define WRN_NOTFILE     "WARNING: %s <no longer a regular file>"
#define WRN_NOTSYMLN    "WARNING: %s <no longer a symbolic link>"
#define WRN_NOTLINK     "WARNING: %s <no longer a linked file>"
#define WRN_NOTDIR      "WARNING: %s <no longer a directory>"
#define WRN_NOTCHAR     "WARNING: %s <no longer a character special device>"
#define WRN_NOTBLOCK    "WARNING: %s <no longer a block special device>"
#define WRN_NOTPIPE     "WARNING: %s <no longer a named pipe>"
#define WRN_TOEXCL      "WARNING: cannot convert %s to an exclusive directory."
#define WRN_ODDVERIFY   "WARNING: quick verify disabled for class %s."

#define MSG_TYPIGN      "Object type change ignored."
#define MSG_TYPE_ERR    "Package attempts fatal object type change."

extern char     *pkginst;
extern int      nosetuid, nocnflct, otherstoo;

/* pkgobjmap.c */
extern int      cp_cfent(struct cfent *cf_ent, struct cfextra *el_ent);

/* setlist.c */
extern void     cl_def_dverify(int idx);

char dbst = '\0';       /* usually set by installf() or removef() */

int files_installed(void);      /* return number of files installed. */

static int      errflg = 0;
static int      eptnum;
static int      installed;      /* # of files, already properly installed. */
static struct   pinfo   *pkgpinfo = (struct pinfo *)0;

static int      is_setuid(struct cfent *ent);
static int      is_setgid(struct cfent *ent);
static int      merg(struct cfextra *el_ent, struct cfent *cf_ent);
static int      do_like_ent(VFP_T *vfpo, struct cfextra *el_ent,
                    struct cfent *cf_ent, int ctrl);
static int      do_new_ent(VFP_T *vfpo, struct cfextra *el_ent, int ctrl);
static int      typechg(struct cfent *el_ent, struct cfent *cf_ent,
                    struct mergstat *mstat);

static void     set_change(struct cfextra *el_ent);
static void     chgclass(struct cfent *cf_ent, struct pinfo *pinfo);
static void     output(VFP_T *vfpo, struct cfent *ent, struct pinfo *pinfo);

/*
 * This scans the extlist (pkgmap) and matches them to the database, copying
 * out the modified contents to the file at tmpfp. It updates the mergstat
 * structures and deals with administrative defaults regarding setuid and
 * conflict.
 */

int
pkgdbmerg(PKGserver server, VFP_T *tmpvfp, struct cfextra **extlist)
{
        static  struct  cfent   cf_ent; /* scratch area */
        struct  cfextra *el_ent;        /* extlist entry under review */
        int     n;
        int     changed;
        int     assume_ok = 0;

        cf_ent.pinfo = (NULL);
        errflg = 0;
        installed = changed = 0;

        vfpRewind(tmpvfp);

        for (eptnum = 0; (el_ent = extlist[eptnum]) != NULL; eptnum++) {
                /*
                 * If there's an entry in the extlist at this position,
                 * process that entry.
                 */
                /* Metafiles don't get merged. */
                if ((el_ent->cf_ent.ftype == 'i') ||
                    (el_ent->cf_ent.ftype == 'n')) {
                        continue;
                }

                /*
                 * Copy cfextra structure for duplicated paths.
                 * This is not just an optimization, it is
                 * necessary for correct operation of algorithm.
                 */
                if ((eptnum > 0) && (strncmp(el_ent->cf_ent.path,
                    extlist[eptnum-1]->cf_ent.path, PATH_MAX) == 0)) {
                        memcpy(extlist[eptnum], extlist[eptnum-1],
                            sizeof (struct cfextra));
                        continue;
                }

                /*
                 * Normally dbst comes to us from installf() or
                 * removef() in order to specify their special
                 * database status codes. They cannot implement a
                 * quick verify (it just doesn't make sense). For
                 * that reason, we can test to see if we already have
                 * a special database status. If we don't (it's from
                 * pkgadd) then we can test to see if this is calling
                 * for a quick verify wherein we assume the install
                 * will work and fix it if it doesn't. In that case
                 * we set our own dbst to be ENTRY_OK.
                 */
                if (dbst == '\0') {
                        if (cl_dvfy(el_ent->cf_ent.pkg_class_idx) ==
                            QKVERIFY) {
                                assume_ok = 1;
                        }
                } else {
                        /*
                         * If we DO end up with an installf/quick
                         * verify combination, we fix that by simply
                         * denying the quick verify for this class.
                         * This forces everything to come out alright
                         * by forcing the standard assumptions as
                         * regards package database for the rest of
                         * the load.
                         */
                        if (cl_dvfy(el_ent->cf_ent.pkg_class_idx) ==
                            QKVERIFY) {
                                logerr(gettext(WRN_ODDVERIFY),
                                    cl_nam(el_ent->cf_ent.pkg_class_idx));
                                /*
                                 * Set destination verification to
                                 * default.
                                 */
                                cl_def_dverify(el_ent->cf_ent.pkg_class_idx);
                        }
                }

                /*
                 * Comply with administrative requirements regarding
                 * setuid/setgid processes.
                 */
                if (is_setuid(&(el_ent->cf_ent))) {
                        el_ent->mstat.setuid = 1;
                }
                if (is_setgid(&(el_ent->cf_ent))) {
                        el_ent->mstat.setgid = 1;
                }

                /*
                 * If setuid/setgid processes are not allowed, reset
                 * those bits.
                 */
                if (nosetuid && (el_ent->mstat.setgid ||
                    el_ent->mstat.setuid)) {
                        el_ent->cf_ent.ainfo.mode &= ~(S_ISUID | S_ISGID);
                }

                /* Search package database for this entry. */
                n = srchcfile(&cf_ent, el_ent->cf_ent.path, server);

                /*
                 * If there was an error, note it and return an error
                 * flag.
                 */
                if (n < 0) {
                        char    *errstr = getErrstr();
                        progerr(ERR_CFBAD);
                        logerr(gettext("pathname: %s"),
                            (cf_ent.path && *cf_ent.path) ?
                            cf_ent.path : "Unknown");
                        logerr(gettext("problem: %s"),
                            (errstr && *errstr) ? errstr : "Unknown");
                        return (-1);
                /*
                 * If there was a match, then merge them into a
                 * single entry.
                 */
                } else if (n == 1) {
                        /*
                         * If this package is overwriting a setuid or
                         * setgid process, set the status bits so we
                         * can inform the administrator.
                         */
                        if (is_setuid(&cf_ent)) {
                                el_ent->mstat.osetuid = 1;
                        }

                        if (is_setgid(&cf_ent)) {
                                el_ent->mstat.osetgid = 1;
                        }
                        /*
                         * Detect if a symlink has changed to directory
                         * If so mark all the files/dir supposed to be
                         * iniside this dir, so that they are not miss
                         * understood by do_new_ent later as already
                         * installed.
                         */
                        if ((cf_ent.ftype == 's') &&
                            (el_ent->cf_ent.ftype == 'd')) {
                                int i;
                                int plen = strlen(el_ent->cf_ent.path);
                                for (i = eptnum + 1; extlist[i]; i++) {
                                        if (strncmp(el_ent->cf_ent.path,
                                            extlist[i]->cf_ent.path,
                                            plen) != 0)
                                                break;
                                        extlist[i]->mstat.parentsyml2dir
                                            = 1;
                                }
                        }

                        if (do_like_ent(tmpvfp, el_ent, &cf_ent, assume_ok)) {
                                changed++;
                        }

                } else {
                        /*
                         * The file doesn't exist in the database.
                         */
                        if (do_new_ent(tmpvfp, el_ent, assume_ok)) {
                                changed++;
                        }
                }
        }

        return (errflg ? -1 : changed);
}

/*
 * Merge a new entry with an installed package object of the same name and
 * insert that object into the package database. Obey administrative defaults
 * as regards conflicting files.
 */

static int
do_like_ent(VFP_T *vfpo, struct cfextra *el_ent, struct cfent *cf_ent, int ctrl)
{
        int     stflag, ignore, changed, mrg_result = -1;

        ignore = changed = 0;

        /*
         * Construct the record defining the current package. If there are
         * other packages involved, this will be appended to the existing
         * list. If this is an update of the same package, it will get merged
         * with the existing record. If this is a preloaded record (like from
         * a dryrun file), it will keep it's current pinfo pointer and will
         * pass it on to the record from the contents file - because on the
         * final continuation, the contents file will be wrong.
         */
        if (el_ent->mstat.preloaded) {
                struct pinfo *pkginfo;

                /* Contents file is not to be trusted for this list. */
                pkginfo = cf_ent->pinfo;

                /* Free the potentially bogus list. */
                while (pkginfo) {
                        struct pinfo *next;
                        next = pkginfo->next;
                        free(pkginfo);
                        pkginfo = next;
                }

                cf_ent->pinfo = el_ent->cf_ent.pinfo;
        }

        pkgpinfo = eptstat(cf_ent, pkginst, DUP_ENTRY);

        stflag = pkgpinfo->status;

        if (otherstoo)
                el_ent->mstat.shared = 1;

        /* If it's marked for erasure, make it official */
        if (el_ent->cf_ent.ftype == RM_RDY) {
                if (!errflg) {
                        pkgpinfo = eptstat(cf_ent, pkginst, RM_RDY);

                        /*
                         * Get copy of status character in case the object is
                         * "shared" by a server, in which case we need to
                         * maintain the shared status after the entry is
                         * written to the package database with RM_RDY
                         * status. This is needed to support the `removef'
                         * command.
                         */
                        stflag = pkgpinfo->status;
                        pkgpinfo->status = RM_RDY;

                        if (putcvfpfile(cf_ent, vfpo)) {
                                progerr(gettext(ERR_OUTPUT));
                                quit(99);
                        }

                        /*
                         * If object is provided by a server, allocate an
                         * info block and set the status to indicate this.
                         * This is needed to support the `removef' command.
                         */
                        if (stflag == SERVED_FILE) {
                                el_ent->cf_ent.pinfo =
                                    (struct pinfo *)calloc(1,
                                    sizeof (struct pinfo));
                                el_ent->cf_ent.pinfo->next = NULL;
                                el_ent->cf_ent.pinfo->status = SERVED_FILE;
                        }
                }
                return (1);
        }

        /*
         * If there is no package associated with it, there's something
         * very wrong.
         */
        if (!pkgpinfo) {
                progerr(gettext(ERR_PINFO), cf_ent->path);
                quit(99);
        }

        /*
         * Do not allow installation if nocnflct is set and other packages
         * reference this pathname. The cp_cfent() function below writes the
         * information from the installed file over the new entry, so the
         * package database will be unchanged.
         *
         * By the way, ftype "e" is often shared and that's OK, so ftype
         * "e" doesn't count here.
         */
        if ((nocnflct && el_ent->mstat.shared && el_ent->cf_ent.ftype != 'e')) {
                /*
                 * First set the attrchg and contchg entries for proper
                 * messaging in the install phase.
                 */
                set_change(el_ent);

                /*
                 * Now overwrite the new entry with the entry for the
                 * currently installed object.
                 */
                if (cp_cfent(cf_ent, el_ent) == 0)
                        quit(99);

                ignore++;
        } else {
                mrg_result = merg(el_ent, cf_ent);

                switch (mrg_result) {
                case MRG_SAME:
                        break;

                case MRG_DIFFERENT:
                        changed++;
                        break;

                case MRG_REPLACE:
                        /*
                         * We'll pick one or the other later. For now, cf_ent
                         * will have the fault value and el_ent will retain
                         * the other value. This is the only state that allows
                         * the database and the pkgmap to differ.
                         */

                        el_ent->mstat.contchg = 1;      /* subject to change */
                        ignore++;
                        break;

                default:
                        break;
                }
        }

        /* el_ent structure now contains updated entry */
        if (!el_ent->mstat.contchg && !ignore) {
                /*
                 * We know the DB entry matches the pkgmap, so now we need to
                 * see if the actual object matches the pkgmap.
                 */
                set_change(el_ent);
        }

        if (!errflg) {
                if (ctrl == 1) {        /* quick verify assumes OK */
                        /*
                         * The pkgpinfo entry is already correctly
                         * constructed. Look into dropping this soon.
                         */
                        pkgpinfo = eptstat(&(el_ent->cf_ent), pkginst,
                            ENTRY_OK);

                        if (stflag != DUP_ENTRY) {
                                changed++;
                        }

                        /*
                         * We could trust the prior pkginfo entry, but things
                         * could have changed and  we need to update the
                         * fs_tab[] anyway. We check for a server object
                         * here.
                         */
                        if (is_served(el_ent->server_path,
                            &(el_ent->fsys_value)))
                                pkgpinfo->status = SERVED_FILE;
                } else {
                        if (!ignore && el_ent->mstat.contchg) {
                                pkgpinfo =
                                    eptstat(&(el_ent->cf_ent), pkginst,
                                    (dbst ? dbst : CONFIRM_CONT));
                        } else if (!ignore && el_ent->mstat.attrchg) {
                                pkgpinfo =
                                    eptstat(&(el_ent->cf_ent), pkginst,
                                    (dbst ? dbst : CONFIRM_ATTR));
                        } else if (!ignore && el_ent->mstat.shared) {
                                pkgpinfo =
                                    eptstat(&(el_ent->cf_ent), pkginst,
                                    dbst);
                                changed++;
                        } else if (stflag != DUP_ENTRY) {
                                pkgpinfo = eptstat(&(el_ent->cf_ent),
                                    pkginst, '\0');
                                if (stflag != ENTRY_OK) {
                                        changed++;
                                }
                        }
                }

                if (mrg_result == MRG_REPLACE) {
                        /*
                         * Put the original package database entry back into
                         * the package database for now.
                         */
                        output(vfpo, cf_ent, pkgpinfo);
                } else {
                        /* Put the merged entry into the package database. */
                        output(vfpo, &(el_ent->cf_ent), pkgpinfo);
                }
        }

        if (pkgpinfo->aclass[0] != '\0') {
                (void) strcpy(el_ent->cf_ent.pkg_class, pkgpinfo->aclass);
        }

        /*
         * If a sym link entry exists in the contents file and
         * and the destination of the link does not exist on the the system
         * then the contents file needs to be updated appropriately so a
         * subsequent invocation of "installf -f" will create the destination.
         */
        if (el_ent->mstat.contchg && pkgpinfo->status == INST_RDY) {
                changed++;
        }

        if (!(el_ent->mstat.preloaded))
                el_ent->cf_ent.pinfo = NULL;

        /*
         * If no change during the merg and we don't have a case where types
         * were different in odd ways, count this as installed.
         */
        if (!el_ent->mstat.attrchg && !el_ent->mstat.contchg &&
            !el_ent->mstat.replace)
                installed++;
        return (changed);
}

/* Insert an entirely new entry into the package database. */
static int
do_new_ent(VFP_T *vfpo, struct cfextra *el_ent, int ctrl)
{
        struct pinfo    *pinfo;
        char            *tp;
        int             changed = 0;

        if (el_ent->cf_ent.ftype == RM_RDY) {
                return (0);
        }

        tp = el_ent->server_path;
        /*
         * Check the file/dir existence only if any of the parent directory
         * of the file/dir has not changed from symbolic link to directory.
         * At this time we are only doing a dry run, the symlink is not yet
         * replaced, so if this is done directly then access will result in
         * incorrect information in case a file with the same attr and cont
         * exists in the link target.
         */
        if ((!el_ent->mstat.parentsyml2dir) && (access(tp, F_OK) == 0)) {
                /*
                 * Path exists, and although its not referenced by any
                 * package we make it look like it is so it appears as a
                 * conflicting file in case the user doesn't want it
                 * installed. We set the rogue flag to distinguish this from
                 * package object conflicts if the administrator is queried
                 * about this later. Note that noconflict means NO conflict
                 * at the file level. Even rogue files count.
                 */
                el_ent->mstat.shared = 1;
                el_ent->mstat.rogue = 1;
                set_change(el_ent);
        } else {
                /* since path doesn't exist, we're changing everything */
                el_ent->mstat.rogue = 0;
                el_ent->mstat.contchg = 1;
                el_ent->mstat.attrchg = 1;
        }

        if (el_ent->cf_ent.ainfo.mode == WILDCARD) {
                if (el_ent->cf_ent.ftype == 'd') {
                        el_ent->cf_ent.ainfo.mode = DEFAULT_MODE;
                } else {
                        el_ent->cf_ent.ainfo.mode = DEFAULT_MODE_FILE;
                }
                logerr(WRN_SET_DEF_MODE, el_ent->cf_ent.path,
                    (int)el_ent->cf_ent.ainfo.mode);
        }

        if (strcmp(el_ent->cf_ent.ainfo.owner, DB_UNDEFINED_ENTRY) == 0)
                (void) strcpy(el_ent->cf_ent.ainfo.owner, DEFAULT_OWNER);
        if (strcmp(el_ent->cf_ent.ainfo.group, DB_UNDEFINED_ENTRY) == 0)
                (void) strcpy(el_ent->cf_ent.ainfo.group, DEFAULT_GROUP);

        /*
         * Do not allow installation if nocnflct is set and this pathname is
         * already in place. Since this entry is new (not associated with a
         * package), we don't issue anything to the database we're building.
         */
        if (nocnflct && el_ent->mstat.shared) {
                return (0);
        }

        if (!errflg) {
                if (el_ent->mstat.preloaded) {
                        /* Add this package to the already established list. */
                        pinfo = eptstat(&(el_ent->cf_ent), pkginst, DUP_ENTRY);
                } else {
                        el_ent->cf_ent.npkgs = 1;
                        pinfo = (struct pinfo *)calloc(1,
                            sizeof (struct pinfo));
                        if (!pinfo) {
                                progerr(gettext(ERR_MEMORY), errno);
                                quit(99);
                        }
                        el_ent->cf_ent.pinfo = pinfo;
                        (void) strcpy(pinfo->pkg, pkginst);
                }

                if (ctrl == 1) {        /* quick verify assumes OK */
                        pinfo->status = dbst ? dbst : ENTRY_OK;
                        /*
                         * The entry won't be verified, but the entry in the
                         * database isn't necessarily ENTRY_OK. If this is
                         * coming from a server, we need to note that
                         * instead.
                         */
                        if (is_served(el_ent->server_path,
                            &(el_ent->fsys_value)))
                                pinfo->status = SERVED_FILE;
                } else {
                        pinfo->status = dbst ? dbst : CONFIRM_CONT;
                }

                output(vfpo, &(el_ent->cf_ent), pinfo);
                changed++;

                free(pinfo);
                el_ent->cf_ent.pinfo = NULL;
                }
        if (!el_ent->mstat.attrchg && !el_ent->mstat.contchg) {
                installed++;
        }

        return (changed);
}

int
files_installed(void)
{
        return (installed);
}

/*
 * This function determines if there is a difference between the file on
 * the disk and the file to be laid down. It set's mstat flags attrchg
 * and contchg accordingly.
 */
static void
set_change(struct cfextra *el_ent)
{
        int     n;
        char    *tp;

        tp = el_ent->server_path;
        if ((el_ent->cf_ent.ftype == 'f') || (el_ent->cf_ent.ftype == 'e') ||
            (el_ent->cf_ent.ftype == 'v')) {
                if (cverify(0, &(el_ent->cf_ent.ftype), tp,
                    &(el_ent->cf_ent.cinfo), 1)) {
                        el_ent->mstat.contchg = 1;
                } else if (!el_ent->mstat.contchg && !el_ent->mstat.attrchg) {
                        if (averify(0, &(el_ent->cf_ent.ftype), tp,
                            &(el_ent->cf_ent.ainfo)))
                                el_ent->mstat.attrchg = 1;
                }
        } else if (!el_ent->mstat.attrchg &&
            ((el_ent->cf_ent.ftype == 'd') ||
            (el_ent->cf_ent.ftype == 'x') ||
            (el_ent->cf_ent.ftype == 'c') ||
            (el_ent->cf_ent.ftype == 'b') ||
            (el_ent->cf_ent.ftype == 'p'))) {
                n = averify(0, &(el_ent->cf_ent.ftype), tp,
                    &(el_ent->cf_ent.ainfo));
                if (n == VE_ATTR)
                        el_ent->mstat.attrchg = 1;
                else if (n && (n != VE_EXIST)) {
                        el_ent->mstat.contchg = 1;
                }
        } else if (!el_ent->mstat.attrchg &&
            ((el_ent->cf_ent.ftype == 's') ||
            (el_ent->cf_ent.ftype == 'l'))) {
                n = averify(0, &(el_ent->cf_ent.ftype), tp,
                    &(el_ent->cf_ent.ainfo));
                if (n == VE_ATTR)
                        el_ent->mstat.attrchg = 1;
                else if (n && (n == VE_EXIST)) {
                        el_ent->mstat.contchg = 1;
                }
        }
}

static int
is_setuid(struct cfent *ent)
{
        return (((ent->ftype == 'f') || (ent->ftype == 'v') ||
            (ent->ftype == 'e')) &&
            (ent->ainfo.mode != BADMODE) &&
            (ent->ainfo.mode != WILDCARD) &&
            (ent->ainfo.mode & S_ISUID));
}

static int
is_setgid(struct cfent *ent)
{
        return (((ent->ftype == 'f') || (ent->ftype == 'v') ||
            (ent->ftype == 'e')) && (ent->ainfo.mode != BADMODE) &&
            (ent->ainfo.mode != WILDCARD) &&
            (ent->ainfo.mode & S_ISGID) &&
            (ent->ainfo.mode & (S_IEXEC|S_IXUSR|S_IXOTH)));
}

char *types[] = {
        "fev",  /* type 1, regular files */
        "s",    /* type 2, symbolic links */
        "l",    /* type 3, linked files */
        "dx",   /* type 4, directories */
        "c",    /* type 5, character special devices */
        "b",    /* type 6, block special devices */
        "p",    /* type 7, named pipes */
        NULL
};

/*
 * This determines if the ftype of the file on the disk and the file to be
 * laid down are close enough. If they aren't, this either returns an error
 * or displays a warning. This returns :
 *      TYPE_OK         they're identical or close enough
 *      TYPE_WARNING    they're pretty close (probably no problem)
 *      TYPE_IGNORED    the type change was not allowed
 *      TYPE_REPLACE    to be reviewed later - in endofclass() maybe
 *      TYPE_FATAL      something awful happened
 */
static int
typechg(struct cfent *el_ent, struct cfent *cf_ent, struct mergstat *mstat)
{
        int     i, etype, itype, retcode;

        /* If they are identical, return OK */
        if (cf_ent->ftype == el_ent->ftype)
                return (TYPE_OK);

        /*
         * If package database entry is ambiguous, set it to the new entity's
         * ftype
         */
        if (cf_ent->ftype == BADFTYPE) {
                cf_ent->ftype = el_ent->ftype;
                return (TYPE_OK); /* do nothing; not really different */
        }

        /* If the new entity is ambiguous, wait for the verify */
        if (el_ent->ftype == BADFTYPE)
                return (TYPE_OK);

        /*
         * If we're trying to convert an existing regular directory to an
         * exclusive directory, this is very dangerous. We will continue, but
         * we will deny the conversion.
         */
        if (el_ent->ftype == 'x' && cf_ent->ftype == 'd') {
                logerr(gettext(WRN_TOEXCL), el_ent->path);
                return (TYPE_IGNORED);
        }

        etype = itype = 0;

        /* Set etype to that of the new entity */
        for (i = 0; types[i]; ++i) {
                if (strchr(types[i], el_ent->ftype)) {
                        etype = i+1;
                        break;
                }
        }

        /* Set itype to that in the package database. */
        for (i = 0; types[i]; ++i) {
                if (strchr(types[i], cf_ent->ftype)) {
                        itype = i+1;
                        break;
                }
        }

        if (itype == etype) {
                /* same basic object type */
                return (TYPE_OK);
        }

        retcode = TYPE_WARNING;

        /*
         * If a simple object (like a file) is overwriting a directory, mark
         * it for full inspection during installation.
         */
        if (etype != 4 && itype == 4) {
                mstat->dir2nondir = 1;
                retcode = TYPE_REPLACE;
        }

        /* allow change, but warn user of possible problems */
        switch (itype) {
        case 1:
                logerr(gettext(WRN_NOTFILE), el_ent->path);
                break;

        case 2:
                logerr(gettext(WRN_NOTSYMLN), el_ent->path);
                break;

        case 3:
                logerr(gettext(WRN_NOTLINK), el_ent->path);
                break;

        case 4:
                logerr(gettext(WRN_NOTDIR), el_ent->path);
                break;

        case 5:
                logerr(gettext(WRN_NOTCHAR), el_ent->path);
                break;

        case 6:
                logerr(gettext(WRN_NOTBLOCK), el_ent->path);
                break;

        case 7:
                logerr(gettext(WRN_NOTPIPE), el_ent->path);
                break;

        default:
                break;
        }
        return (retcode);
}

/*
 * This function takes el_ent (the entry from the pkgmap) and cf_ent (the
 * entry from the package database) and merge them into el_ent. The rules
 * are still being figured out, but the comments should make the approach
 * pretty clear.
 *
 * RETURN CODES:
 *      MRG_DIFFERENT   The two entries are different and el_ent now contains
 *                      the intended new entry to be installed.
 *      MRG_SAME        The two entries were identical and the old database
 *                      entry will be replaced unchanged.
 *      MRG_REPLACE     One or the other entry will be used but the decision
 *                      has to be made at install time.
 */
static int
merg(struct cfextra *el_ent, struct cfent *cf_ent)
{
        int     n, changed = 0;

        /*
         * We need to change the original entry to make it look like the new
         * entry (the eptstat() routine has already added appropriate package
         * information, but not about 'aclass' which may represent a change
         * in class from the previous installation.
         *
         * NOTE: elent->cf_ent.pinfo (the list of associated packages) is NULL
         * upon entry to this function.
         */

        el_ent->cf_ent.pinfo = cf_ent->pinfo;

        if (dbst == INST_RDY && el_ent->cf_ent.ftype == '?') {
                el_ent->cf_ent.ftype = cf_ent->ftype;
        }

        /*
         * Evaluate the ftype change. Usually the ftype won't change. If it
         * does it may be easy (s -> f), not allowed (d -> x), so complex we
         * can't figure it 'til later (d -> s) or fatal (a hook for later).
         */
        if (cf_ent->ftype != el_ent->cf_ent.ftype) {
                n = typechg(&(el_ent->cf_ent), cf_ent, &(el_ent->mstat));

                switch (n) {
                case TYPE_OK:
                        break;

                    /* This is an allowable change. */
                case TYPE_WARNING:
                        el_ent->mstat.contchg = 1;
                        break;

                    /* Not allowed, but leaving it as is is OK. */
                case TYPE_IGNORED:
                        logerr(gettext(MSG_TYPIGN));
                        if (cp_cfent(cf_ent, el_ent) == 0)
                                quit(99);
                        return (MRG_SAME);

                    /* Future analysis will reveal if this is OK. */
                case TYPE_REPLACE:
                        el_ent->mstat.replace = 1;
                        return (MRG_REPLACE);

                    /* Kill it before it does any damage. */
                case TYPE_FATAL:
                        logerr(gettext(MSG_TYPE_ERR));
                        quit(99);

                default:
                        break;
                }

                changed++;
        }

        /* Evaluate and merge the class. */
        if (strcmp(cf_ent->pkg_class, el_ent->cf_ent.pkg_class)) {
                /*
                 * we always allow a class change as long as we have
                 * consistent ftypes, which at this point we must
                 */
                changed++;
                if (strcmp(cf_ent->pkg_class, "?")) {
                        (void) strcpy(pkgpinfo->aclass,
                            el_ent->cf_ent.pkg_class);
                        (void) strcpy(el_ent->cf_ent.pkg_class,
                            cf_ent->pkg_class);
                        chgclass(&(el_ent->cf_ent), pkgpinfo);
                }
        }

        /*
         * Evaluate and merge based upon the ftype of the intended package
         * database entry.
         */
        if (((el_ent->cf_ent.ftype == 's') || (el_ent->cf_ent.ftype == 'l'))) {

                /* If both have link sources, then they need to be merged. */
                if (cf_ent->ainfo.local && el_ent->cf_ent.ainfo.local) {
                        /*
                         * If both sources are identical, the merge is
                         * already done.
                         */
                        if (strcmp(cf_ent->ainfo.local,
                            el_ent->cf_ent.ainfo.local) != 0) {
                                changed++;

                                /*
                                 * Otherwise, if the pkgmap entry is
                                 * ambiguous, it will inherit the database
                                 * entry.
                                 */
                                if (strcmp(el_ent->cf_ent.ainfo.local,
                                    "?") == 0) {
                                        (void) strlcpy(
                                            el_ent->cf_ent.ainfo.local,
                                            cf_ent->ainfo.local,
                                            PATH_MAX);
                                } else {
                                        el_ent->mstat.contchg = 1;
                                }
                        }
                }
                return (changed ? MRG_DIFFERENT : MRG_SAME);

        } else if (el_ent->cf_ent.ftype == 'e') {

                /*
                 * The contents of edittable files are assumed to be changing
                 * since some class action script will be doing the work and
                 * we have no way of evaluating what it will actually do.
                 */
                el_ent->mstat.contchg = 1;
                changed++;
        } else if (((el_ent->cf_ent.ftype == 'f') ||
            (el_ent->cf_ent.ftype == 'v'))) {
                /*
                 * For regular files, Look at content information; a BADCONT
                 * in any el_ent field indicates the contents are unknown --
                 * since cf_ent is guaranteed to have a valid entry here (bad
                 * assumption?) this function will recognize this as a
                 * change. The ambiguous el_ent values will be evaluated and
                 * set later.
                 */

                if (cf_ent->cinfo.size != el_ent->cf_ent.cinfo.size) {
                        changed++;
                        el_ent->mstat.contchg = 1;
                } else if (cf_ent->cinfo.modtime !=
                    el_ent->cf_ent.cinfo.modtime) {
                        changed++;
                        el_ent->mstat.contchg = 1;
                } else if (cf_ent->cinfo.cksum != el_ent->cf_ent.cinfo.cksum) {
                        changed++;
                        el_ent->mstat.contchg = 1;
                }
        } else if (((el_ent->cf_ent.ftype == 'c') ||
            (el_ent->cf_ent.ftype == 'b'))) {
                /*
                 * For devices, if major or minor numbers are identical the
                 * merge is trivial. If the el_ent value is ambiguous (BAD),
                 * the cf_ent value is inherited. Otherwise, the el_ent value
                 * is preserved.
                 */
                if (cf_ent->ainfo.major != el_ent->cf_ent.ainfo.major) {
                        changed++;
                        if (el_ent->cf_ent.ainfo.major == BADMAJOR) {
                                el_ent->cf_ent.ainfo.major =
                                    cf_ent->ainfo.major;
                        } else {
                                el_ent->mstat.contchg = 1;
                        }
                }
                if (cf_ent->ainfo.minor != el_ent->cf_ent.ainfo.minor) {
                        changed++;
                        if (el_ent->cf_ent.ainfo.minor == BADMINOR)
                                el_ent->cf_ent.ainfo.minor =
                                    cf_ent->ainfo.minor;
                        else
                                el_ent->mstat.contchg = 1;
                }
        }

        /*
         * For mode, owner and group follow the same rules as above - if
         * ambiguous, inherit, otherwise keep the new one.
         */
        if (cf_ent->ainfo.mode != el_ent->cf_ent.ainfo.mode) {
                changed++;  /* attribute info is changing */
                if (el_ent->cf_ent.ainfo.mode == BADMODE) {
                        el_ent->cf_ent.ainfo.mode = cf_ent->ainfo.mode;
                } else if (el_ent->cf_ent.ainfo.mode == WILDCARD) {
                        /*
                         * If pkgmap has a '?' set for mode, use the mode from
                         * the pkg DB (contents file).
                         */
                        el_ent->cf_ent.ainfo.mode = cf_ent->ainfo.mode;
                        el_ent->mstat.attrchg = 0;
                } else {
                        el_ent->mstat.attrchg = 1;
                }
        }
        if (strcmp(cf_ent->ainfo.owner, el_ent->cf_ent.ainfo.owner) != 0) {
                changed++;  /* attribute info is changing */
                if (strcmp(el_ent->cf_ent.ainfo.owner, BADOWNER) == 0)
                        (void) strcpy(el_ent->cf_ent.ainfo.owner,
                            cf_ent->ainfo.owner);
                else
                        el_ent->mstat.attrchg = 1;
        }
        if (strcmp(cf_ent->ainfo.group, el_ent->cf_ent.ainfo.group) != 0) {
                changed++;  /* attribute info is changing */
                if (strcmp(el_ent->cf_ent.ainfo.group, BADGROUP) == 0)
                        (void) strcpy(el_ent->cf_ent.ainfo.group,
                            cf_ent->ainfo.group);
                else
                        el_ent->mstat.attrchg = 1;
        }
        return (changed ? MRG_DIFFERENT : MRG_SAME);
}

/*
 * This puts the current entry into the package database in the appropriate
 * intermediate format for this stage of the installation. This also assures
 * the correct format for the various package object ftypes, stripping the
 * link name before storing a regular file and stuff like that.
 */

static void
output(VFP_T *vfpo, struct cfent *ent, struct pinfo *pinfo)
{
        short   svvolno;
        char    *svpt;

        /* output without volume information */
        svvolno = ent->volno;
        ent->volno = 0;

        pinfo->editflag = 0;
        if (((ent->ftype == 's') || (ent->ftype == 'l'))) {
                if (putcvfpfile(ent, vfpo)) {
                        progerr(gettext(ERR_OUTPUT));
                        quit(99);
                }
        } else {

                /* output without local pathname */
                svpt = ent->ainfo.local;
                ent->ainfo.local = NULL;
                if (putcvfpfile(ent, vfpo)) {
                        progerr(gettext(ERR_OUTPUT));
                        quit(99);
                }

                ent->ainfo.local = svpt;
                /*
                 * If this entry represents a file which is being edited, we
                 * need to store in memory the fact that it is an edittable
                 * file so that when we audit it after installation we do not
                 * worry about its contents; we do this by resetting the ftype
                 * to 'e' in the memory array which is later used to control
                 * the audit
                 */
                if (pinfo->editflag)
                        ent->ftype = 'e';
        }
        /* restore volume information */
        ent->volno = svvolno;
}

static void
chgclass(struct cfent *cf_ent, struct pinfo *pinfo)
{
        struct pinfo *pp;
        char    *oldclass, newclass[CLSSIZ+1];
        int     newcnt, oldcnt;

        /*
         * we use this routine to minimize the use of the aclass element by
         * optimizing the use of the cf_ent->pkg_class element
         */

        (void) strlcpy(newclass, pinfo->aclass, sizeof (newclass));
        newcnt = 1;

        oldclass = cf_ent->pkg_class;
        oldcnt = 0;

        /*
         * count the number of times the newclass will be used and see if it
         * exceeds the number of times the oldclass is referenced
         */
        pp = cf_ent->pinfo;
        while (pp) {
                if (pp->aclass[0] != '\0') {
                        if (strcmp(pp->aclass, newclass) == 0)
                                newcnt++;
                        else if (strcmp(pp->aclass, oldclass) == 0)
                                oldcnt++;
                }
                pp = pp->next;
        }
        if (newcnt > oldcnt) {
                pp = cf_ent->pinfo;
                while (pp) {
                        if (pp->aclass[0] == '\0') {
                                (void) strcpy(pp->aclass, oldclass);
                        } else if (strcmp(pp->aclass, newclass) == 0) {
                                pp->aclass[0] = '\0';
                        }
                        pp = pp->next;
                }
                (void) strcpy(cf_ent->pkg_class, newclass);
        }
}