root/usr/src/cmd/svr4pkg/libinst/pkgobjmap.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 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */


#include <stdio.h>
#include <limits.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <pkgstrct.h>
#include <locale.h>
#include <libintl.h>
#include <pkglib.h>
#include <install.h>
#include <libinst.h>

#define WRN_NOPKGOBJ    "WARNING: no package objects found"

#define ERR_MEMORY      "memory allocation failure"
#define ERR_DUPPATH     "duplicate pathname <%s>"

/* libpkg/gpkgmap */
extern int      getmapmode(void);

#define EPTMALLOC       512

static struct cfextra **extlist;

int     eptnum;
static int      array_preloaded = 0;
static int      errflg;
static int      nparts;
static int      xspace = -1;

void    pkgobjinit(void);
static int      pkgobjassign(struct cfent *ept, char **server_local,
                    char **client_local, char **server_path,
                    char **client_path, char **map_path, int mapflag,
                    int nc);

static int      ckdup(struct cfent *ept1, struct cfent *ept2);
static int      sortentry(int index);
static int      dup_merg(struct cfextra *ext1, struct cfextra *ext2);

void
pkgobjinit(void)
{
        if (array_preloaded)    /* Already done. */
                return;

        errflg = nparts = eptnum = 0;

        if (xspace != -1) {
                ar_free(xspace);
                xspace = -1;
        }

        /*
         * initialize dynamic memory used to store
         * path information which is read in
         */
        (void) pathdup((char *)0);
}

/*
 * This function assigns appropriate values based upon the pkgmap entry
 * in the cfent structure.
 */
static int
pkgobjassign(struct cfent *ept, char **server_local, char **client_local,
    char **server_path, char **client_path, char **map_path, int mapflag,
    int nc)
{
        int     path_duped = 0;
        int     local_duped = 0;
        char    source[PATH_MAX+1];

        if (nc >= 0 && ept->ftype != 'i')
                if ((ept->pkg_class_idx = cl_idx(ept->pkg_class)) == -1)
                        return (1);

        if (ept->volno > nparts)
                nparts++;

        /*
         * Generate local (delivered source) paths for files
         * which need them so that the install routine will know
         * where to get the file from the package. Note that we
         * do not resolve path environment variables here since
         * they won't be resolved in the reloc directory.
         */
        if ((mapflag > 1) && strchr("fve", ept->ftype)) {
                if (ept->ainfo.local == NULL) {
                        source[0] = '~';
                        (void) strlcpy(&source[1], ept->path,
                                                sizeof (source)-1);
                        ept->ainfo.local = pathdup(source);
                        *server_local = ept->ainfo.local;
                        *client_local = ept->ainfo.local;

                        local_duped = 1;
                }
        }

        /*
         * Evaluate the destination path based upon available
         * environment, then produce a client-relative and
         * server-relative canonized path.
         */
        if (mapflag && (ept->ftype != 'i')) {
                mappath(getmapmode(), ept->path); /* evaluate variables */
                canonize(ept->path);    /* Fix path as necessary. */

                (void) eval_path(server_path,
                    client_path,
                    map_path,
                    ept->path);
                path_duped = 1; /* eval_path dup's it */
                ept->path = *server_path;       /* default */
        }

        /*
         * Deal with source for hard and soft links.
         */
        if (strchr("sl", ept->ftype)) {
                if (mapflag) {
                        mappath(getmapmode(), ept->ainfo.local);
                        if (!RELATIVE(ept->ainfo.local)) {
                                canonize(ept->ainfo.local);

                                /* check for hard link */
                                if (ept->ftype == 'l') {
                                        (void) eval_path(
                                            server_local,
                                            client_local,
                                            NULL,
                                            ept->ainfo.local);
                                        local_duped = 1;

                                        /* Default to server. */
                                        ept->ainfo.local = *server_local;
                                }
                        }
                }
        }

        /*
         * For the paths (both source and target) that were too mundane to
         * have been copied into dup space yet, do that.
         */
        if (!path_duped) {
                *server_path = pathdup(ept->path);
                *client_path = *server_path;
                ept->path = *server_path;

                path_duped = 1;
        }
        if (ept->ainfo.local != NULL)
                if (!local_duped) {
                        *server_local = pathdup(ept->ainfo.local);
                        ept->ainfo.local = *server_local;
                        *client_local = ept->ainfo.local;

                local_duped = 1;
        }

        return (0);
}

/* This initializes the package object array. */
int
init_pkgobjspace(void)
{
        if (array_preloaded)    /* Already done. */
                return (1);

        if (xspace == -1) {
                xspace = ar_create(EPTMALLOC, sizeof (struct cfextra),
                    "package object");
                if (xspace == -1) {
                        progerr(gettext(ERR_MEMORY));
                        return (0);
                }
        }

        return (1);
}

int
seed_pkgobjmap(struct cfextra *ext_entry, char *path, char *local)
{
        struct cfextra *ext, **ext_ptr;

        /* offsets for the various path images. */
        int client_path_os;
        int server_path_os;
        int map_path_os;
        int client_local_os;
        int server_local_os;

        ext_ptr = (struct cfextra **)ar_next_avail(xspace);

        if (ext_ptr == NULL || *ext_ptr == NULL) {
                progerr(gettext(ERR_MEMORY));
                return (0);
        }

        ext = *ext_ptr;

        (void) memcpy(ext, ext_entry, sizeof (struct cfextra));

        /* Figure out all of the offsets. */
        client_path_os = ((ptrdiff_t)ext->client_path -
                        (ptrdiff_t)ext->cf_ent.path);
        server_path_os = ((ptrdiff_t)ext->server_path -
                        (ptrdiff_t)ext->cf_ent.path);
        map_path_os = ((ptrdiff_t)ext->map_path -
                        (ptrdiff_t)ext->cf_ent.path);
        client_local_os = ((ptrdiff_t)ext->client_local -
                        (ptrdiff_t)ext->cf_ent.ainfo.local);
        server_local_os = ((ptrdiff_t)ext->server_local -
                        (ptrdiff_t)ext->cf_ent.ainfo.local);

        /* Allocate and store the path name. */
        ext->cf_ent.path = pathdup(path);

        /* Assign the path substring pointers. */
        ext->client_path = (ext->cf_ent.path + client_path_os);
        ext->server_path = (ext->cf_ent.path + server_path_os);
        ext->map_path = (ext->cf_ent.path + map_path_os);

        /* If there's a local entry, allocate and store it as well. */
        if (local) {
                ext->cf_ent.ainfo.local = pathdup(local);

                ext->client_local = (ext->cf_ent.ainfo.local + client_local_os);
                ext->server_local = (ext->cf_ent.ainfo.local + server_local_os);
        } else {
                ext->cf_ent.ainfo.local = NULL;
                ext->client_local = NULL;
                ext->server_local = NULL;
        }

        eptnum++;
        array_preloaded = 1;

        return (0);
}

/*
 * This function reads the pkgmap (or any file similarly formatted) and
 * returns a pointer to a list of struct cfextra (each of which
 * contains a struct cfent) representing the contents of that file.
 */

/* ARGSUSED ir in pkgobjmap */
struct cfextra **
pkgobjmap(VFP_T *vfp, int mapflag, char *ir)
{
        struct  cfextra *ext, **ext_ptr;
        struct  cfent *ept, map_entry;
        int     i;
        int     n;
        int     nc;

        pkgobjinit();
        if (!init_pkgobjspace())
                quit(99);

        nc = cl_getn();
        for (;;) {
                /* Clear the buffer. */
                (void) memset(&map_entry, '\000', sizeof (struct cfent));

                /*
                 * Fill in a cfent structure in a very preliminary fashion.
                 * ept->path and ept->ainfo.local point to static memory
                 * areas of size PATH_MAX. These are manipulated and
                 * then provided their own allocations later in this function.
                 */
                n = gpkgmapvfp(&map_entry, vfp);

                if (n == 0)
                        break; /* no more entries in pkgmap */
                else if (n < 0) {
                        char    *errstr = getErrstr();
                        progerr(gettext("bad entry read in pkgmap"));
                        logerr(gettext("pathname=%s"),
                            (map_entry.path && *map_entry.path) ?
                            map_entry.path : "Unknown");
                        logerr(gettext("problem=%s"),
                            (errstr && *errstr) ? errstr : "Unknown");
                        return (NULL);
                }

                /*
                 * A valid entry was found in the map, so allocate an
                 * official record.
                 */
                ext_ptr = (struct cfextra **)ar_next_avail(xspace);
                if (ext_ptr == NULL || *ext_ptr == NULL) {
                        progerr(gettext(ERR_MEMORY));
                        return (NULL);
                }

                ext = *ext_ptr;
                ept = &(ext->cf_ent);

                /* Transfer what we just read in. */
                (void) memcpy(ept, &map_entry, sizeof (struct cfent));

                /* And process it into the cfextra structure. */
                if (pkgobjassign(ept,
                    &(ext->server_local),
                    &(ext->client_local),
                    &(ext->server_path),
                    &(ext->client_path),
                    &(ext->map_path),
                    mapflag, nc)) {
                        /* It didn't take. */
                        (void) ar_delete(xspace, eptnum);
                        continue;
                }

                eptnum++;
                ext->fsys_value = BADFSYS;      /* No file system data yet */
                ext->fsys_base = BADFSYS;
        }

        if (eptnum == 0) {
                logerr(gettext(WRN_NOPKGOBJ));
                return (NULL);
        }

        /* setup a pointer array to point to malloc'd entries space */
        extlist = (struct cfextra **)ar_get_head(xspace);
        if (extlist == NULL) {
                progerr(gettext(ERR_MEMORY));
                return (NULL);
        }

        (void) sortentry(-1);
        for (i = 0; i < eptnum; /* void */) {
                if (!sortentry(i))
                        i++;
        }

        return (errflg ? NULL : extlist);
}

/*
 * This function sorts the final list of cfextra entries. If index = -1, the
 * function is initialized. index = 0 doesn't get us anywhere because this
 * sorts against index-1. Positive natural index values are compared and
 * sorted into the array appropriately. Yes, it does seem we should use a
 * quicksort on the whole array or something. The apparent reason for taking
 * this approach is that there are enough special considerations to be
 * applied to each package object that inserting them one-by-one doesn't cost
 * that much.
 */
static int
sortentry(int index)
{
        struct cfextra *ext;
        struct cfent *ept, *ept_i;
        static int last = 0;
        int     i, n, j;
        int     upper, lower;

        if (index == 0)
                return (0);
        else if (index < 0) {
                last = 0;
                return (0);
        }

        /*
         * Based on the index, this is the package object we're going to
         * review. It may stay where it is or it may be repositioned in the
         * array.
         */
        ext = extlist[index];
        ept = &(ext->cf_ent);

        /* quick comparison optimization for pre-sorted arrays */
        if (strcmp(ept->path, extlist[index-1]->cf_ent.path) > 0) {
                /* do nothing */
                last = index-1;
                return (0);
        }

        lower = 0;              /* lower bound of the unsorted elements */
        upper = index;          /* upper bound */
        i = last;
        do {
                /*
                 * NOTE: This does a binary sort on path. There are lots of
                 * other worthy items in the array, but path is the key into
                 * the package database.
                 */
                ept_i = &(extlist[i]->cf_ent);

                n = strcmp(ept->path, ept_i->path);
                if (n == 0) {
                        if (!ckdup(ept, ept_i)) {
                                /*
                                 * If the array was seeded then there are
                                 * bound to be occasional duplicates.
                                 * Otherwise, duplicates are definitely a
                                 * sign of major damage.
                                 */
                                if (array_preloaded) {
                                        if (!dup_merg(ext, extlist[i])) {
                                                progerr(gettext(ERR_DUPPATH),
                                                    ept->path);
                                                errflg++;
                                        }
                                } else {
                                        progerr(gettext(ERR_DUPPATH),
                                            ept->path);
                                        errflg++;
                                }
                        }
                        /* remove the entry at index */
                        (void) ar_delete(xspace, index);

                        eptnum--;
                        return (1);     /* Use this index again. */
                } else if (n < 0) {
                        /*
                         * The path of interest is smaller than the path
                         * under test. Move down array using the method of
                         * division
                         */
                        upper = i;
                        i = lower + (upper-lower)/2;
                } else {
                        /* Move up array */
                        lower = i+1;
                        i = upper - (upper-lower)/2 - 1;
                }
        } while (upper != lower);
        last = i = upper;

        /* expand to insert at i */
        for (j = index; j > i; j--)
                extlist[j] = extlist[j-1];

        extlist[i] = ext;

        return (0);
}

/* Return the number of blocks required by the package object provided. */
static fsblkcnt_t
nblks(short fsys_entry, struct cfextra *ext)
{
        fsblkcnt_t blk;
        ulong_t block_size;
        ulong_t frag_size;

        block_size = (ulong_t)get_blk_size_n(fsys_entry);
        frag_size = (ulong_t)get_frag_size_n(fsys_entry);

        if (strchr("dxs", ext->cf_ent.ftype))
                blk =
                    nblk(block_size, block_size, frag_size);
        else if (ext->cf_ent.cinfo.size != BADCONT)
                blk = nblk(ext->cf_ent.cinfo.size, block_size,
                    frag_size);
        else
                blk = 0;

        return (blk);
}

/* Remove ext1 from the filesystem size calculations and add ext2. */
static void
size_xchng(struct cfextra *ext1, struct cfextra *ext2)
{
        fsblkcnt_t bused;
        ulong_t block_size;
        ulong_t frag_size;
        fsblkcnt_t      blks1, blks2;
        short   fsys_entry;

        /*
         * Since these are on the same filesystem, either one will yield the
         * correct block and fragment size.
         */
        fsys_entry = ext1->fsys_base;
        block_size = (ulong_t)get_blk_size_n(fsys_entry);
        frag_size = (ulong_t)get_frag_size_n(fsys_entry);

        blks1 = nblk(ext1->cf_ent.cinfo.size, block_size, frag_size);
        blks2 = nblk(ext2->cf_ent.cinfo.size, block_size, frag_size);

        if (blks1 != blks2) {
                /* First, lose the old size, then add the new size. */
                bused = get_blk_used_n(fsys_entry);
                bused -= nblks(fsys_entry, ext1);
                bused += nblks(fsys_entry, ext2);

                set_blk_used_n(fsys_entry, bused);
        }
}

/*
 * This function merges duplicate non-directory entries resulting from a
 * dryrun or other procedure which preloads the extlist. It uses an odd
 * heuristic to determine which package object is newest: only package
 * objects from the dryrun file will have pinfo pointers. Therefore, the
 * object with a pinfo pointer is from the dryrun file and it will be
 * overwritten by the object being installed by this package.
 *
 * Assumptions:
 *      1. The newer object will be overwriting the older object.
 *      2. The two objects are close enough to the same size that
 *         the sizing is still OK.
 *
 * The calling routine will overwrite ept1, so this must return ept2 with
 * the correct data to keep. There being only one logical outcome of a
 * failure, this returns 1 for OK and 0 for FAIL.
 */
static int
dup_merg(struct cfextra *ext1, struct cfextra *ext2)
{
        struct cfent *ept1, *ept2;

        ept1 = &(ext1->cf_ent);
        ept2 = &(ext2->cf_ent);

        if (strchr("?dx", ept1->ftype))
                return (0);

        if (strchr("?dx", ept2->ftype))
                return (0);

        /* First, which is the eldest? */
        if (ext2->mstat.preloaded) {
                /*
                 * While ept2 has the correct pinfo list (it was preloaded into
                 * the array before the pkgmap was read), ept1 has everything
                 * else. Here we copy the guts of ept1 into ept2.
                 *
                 * Start by grabbing the pointers to the ext2 items that we
                 * need to either restore or free.
                 */
                /* to free() */
                char *path = ept2->path;
                char *local = ept2->ainfo.local;

                /* to preserve */
                short npkgs = ept2->npkgs;
                struct pinfo *pinfo = ept2->pinfo;

                /* Copy everything from the new entry to the old */
                (void) memcpy(ept2, ept1, sizeof (struct cfent));

                /* Now restore the original stuff.. */
                ept2->path = path;
                ept2->ainfo.local = local;
                ept2->npkgs = npkgs;
                ept2->pinfo = pinfo;

                size_xchng(ext2, ext1);
        } else if (ext1->mstat.preloaded) {
                /*
                 * ept2 is already the one we will keep. All we have to do is
                 * copy over the pinfo pointer.
                 */
                ept2->pinfo = ept1->pinfo;
                size_xchng(ext1, ext2);
        } else
                return (0);

        return (1);
}

/*
 * Check duplicate entries in the package object list. If it's a directory,
 * this just merges them, if not, it returns a 0 to force further processing.
 */
static int
ckdup(struct cfent *ept1, struct cfent *ept2)
{
        /* ept2 will be modified to contain "merged" entries */

        if (!strchr("?dx", ept1->ftype))
                return (0);

        if (!strchr("?dx", ept2->ftype))
                return (0);

        if (ept2->ainfo.mode == BADMODE)
                ept2->ainfo.mode = ept1->ainfo.mode;
        if ((ept1->ainfo.mode != ept2->ainfo.mode) &&
            (ept1->ainfo.mode != BADMODE))
                return (0);

        if (strcmp(ept2->ainfo.owner, "?") == 0)
                (void) strlcpy(ept2->ainfo.owner, ept1->ainfo.owner,
                        sizeof (ept2->ainfo.owner));
        if (strcmp(ept1->ainfo.owner, ept2->ainfo.owner) &&
            strcmp(ept1->ainfo.owner, "?"))
                return (0);

        if (strcmp(ept2->ainfo.group, "?") == 0)
                (void) strlcpy(ept2->ainfo.group, ept1->ainfo.group,
                        sizeof (ept2->ainfo.group));
        if (strcmp(ept1->ainfo.group, ept2->ainfo.group) &&
            strcmp(ept1->ainfo.group, "?"))
                return (0);

        if (ept1->pinfo) {
                ept2->npkgs = ept1->npkgs;
                ept2->pinfo = ept1->pinfo;
        }

        return (1);
}

/*
 * Replace the old package database entry with the new one preserving the
 * data which remains constant across the replacement.
 *      copied directly:
 *              ftype, pkg_class
 *
 *      preserved from old:
 *              path, npkgs, pinfo
 */
void
repl_cfent(struct cfent *new, struct cfent *old)
{
        char *path = old->path;
        short npkgs = old->npkgs;
        struct pinfo *pinfo = old->pinfo;

        /* Copy everything from the new entry over */
        (void) memcpy(old, new, sizeof (struct cfent));

        if (strchr("sl", new->ftype) == NULL)
                old->ainfo.local = NULL;

        old->path = path;
        old->npkgs = npkgs;
        old->pinfo = pinfo;

        old->volno = 0;
}

/*
 * Copy critical portions of cf_ent (from the package database) and el_ent
 * (constructed from the pkgmap) into a merged cfent structure, tp. Then copy
 * that to the el_ent structure. The approach we take here is to copy over
 * everything from the package database entry, condition the paths based upon
 * the currently installed path and then insert the following entries from
 * the new structure :
 *      cfent.volno
 *      pkg_class
 *      pkg_class_idx
 *
 * The pinfo list is then copied from the cfent list. While
 * fsys_value is also copied over, it hasn't been set yet. This function
 * copies over whatever the default value is from the new structure.
 *
 * The copied entry is returned in the el_ent argument and the function
 * value is 1 on success, 0 on failure. There is no recovery plan for
 * failure.
 */
int
cp_cfent(struct cfent *cf_ent, struct cfextra *el_ent)
{
        struct cfextra  *tp;

        /* Allocate space for cfent copy */
        if ((tp = (struct cfextra *)calloc(1,
            sizeof (struct cfextra))) == NULL) {
                progerr(gettext("cp_cfent: memory allocation error"));
                return (0);
        }

        /* Copy everything from the package database over */
        (void) memcpy(&(tp->cf_ent), cf_ent, sizeof (struct cfent));

        /* Now overlay new items from the pkgmap */
        tp->fsys_value = el_ent->fsys_value;
        tp->cf_ent.volno = el_ent->cf_ent.volno;
        (void) strlcpy(tp->cf_ent.pkg_class, el_ent->cf_ent.pkg_class,
                        sizeof (tp->cf_ent.pkg_class));
        tp->cf_ent.pkg_class_idx = el_ent->cf_ent.pkg_class_idx;
        tp->cf_ent.pinfo = cf_ent->pinfo;

        /*
         * The paths are identical, so we get them from the new entry.  These
         * are pointing to a malloc'd section of memory containing a string
         * that we aren't moving in this operation, so everybody points to
         * the same thing during these transfers.
         */
        tp->cf_ent.path = el_ent->client_path;
        tp->server_path = el_ent->server_path;
        tp->client_path = el_ent->client_path;
        tp->map_path = el_ent->map_path;

        /*
         * Since instvol() expects to work with the *original* mstat data,
         * mstat is just copied here. NOTE: mstat looks like a structure, but
         * it's really a short bit array.
         */
        tp->mstat = el_ent->mstat;

        /* Copy everything from the temporary structure to the new entry */
        (void) memcpy(el_ent, tp, sizeof (struct cfextra));
        free(tp);

        return (1);
}