root/usr/src/cmd/svr4pkg/pkgmk/splpkgmap.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.
 */

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


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

extern struct pkgdev pkgdev;

#define MALSIZ  500
#define EFACTOR 128ULL  /* typical size of a single entry in a pkgmap file */

#define WRN_LIMIT       "WARNING: -l limit (%llu blocks) exceeds device " \
                        "capacity (%llu blocks)"
#define ERR_MEMORY      "memory allocation failure, errno=%d"
#define ERR_TOOBIG      "%s (%llu blocks) does not fit on a volume"
#define ERR_INFOFIRST   "information file <%s> must appear on first part"
#define ERR_INFOSPACE   "all install files must appear on first part"
#define ERR_VOLBLKS     "Objects selected for part %d require %llu blocks, " \
                        "limit=%llu."
#define ERR_VOLFILES    "Objects selected for part %d require %llu files, " \
                        "limit=%llu."
#define ERR_FREE        "package does not fit space currently available in <%s>"

struct data {
        fsblkcnt_t      blks;
        struct cfent *ept;
};

struct class_type {
        char *name;
        int first;
        int last;
};

static fsblkcnt_t       btotal; /* blocks stored on current part */
static fsblkcnt_t       bmax;   /* maximum number of blocks on any part */

static fsfilcnt_t       ftotal; /* files stored on current part */
static fsfilcnt_t       fmax;   /* maximum number of files on any part */
static fsblkcnt_t       bpkginfo;       /* blocks used by pkginfo file */
static char     **dirlist;
static short    volno;          /* current part */
static int      nparts = -1;    /* total number of parts */
static int      nclass;
static fsblkcnt_t       DIRSIZE;
static struct   class_type *cl;

static int      nodecount(char *path);
static int      store(struct data **, unsigned int, char *, fsblkcnt_t,
    fsblkcnt_t);
static void     addclass(char *aclass, int vol);
static void     allocnode(char *path);
static void     newvolume(struct data **, unsigned int, fsblkcnt_t limit,
    fsblkcnt_t);
static void     sortsize(struct data *f, struct data **sf, unsigned int eptnum);

int
splpkgmap(struct cfent **eptlist, unsigned int eptnum, char *order[],
    ulong_t bsize, ulong_t frsize, fsblkcnt_t *plimit, fsfilcnt_t *pilimit,
    fsblkcnt_t *pllimit)
{
        struct data     *f, **sf;
        struct cfent    *ept;
        register int    i, j;
        int             new_vol_set;
        short           new_vol;
        int             flag, errflg;
        fsblkcnt_t      total;
        fsblkcnt_t      btemp;
        fsfilcnt_t      ftemp;

        f = (struct data *)calloc(eptnum, sizeof (struct data));
        if (f == NULL) {
                progerr(gettext(ERR_MEMORY), errno);
                quit(99);
        }

        sf = (struct data **)calloc(eptnum, sizeof (struct data *));
        if (sf == NULL) {
                progerr(gettext(ERR_MEMORY), errno);
                quit(99);
        }

        nclass = 0;
        cl = (struct class_type *)calloc(MALSIZ, sizeof (struct class_type));
        if (cl == NULL) {
                progerr(gettext(ERR_MEMORY), errno);
                quit(99);
        }

        errflg = 0;

        /*
         * The next bit of code checks to see if, when creating a package
         * on a directory, there are enough free blocks and inodes before
         * continuing.
         */
        total = 0;
        /*
         * DIRSIZE takes up 1 logical block, iff we have no frags, else
         * it just takes a frag
         */
        DIRSIZE = ((fsblkcnt_t)frsize > 0) ?
            howmany(frsize, DEV_BSIZE) :
            howmany(bsize, DEV_BSIZE);

        if (!pkgdev.mount) {
                allocnode(NULL);
                /*
                 * If we appear to have a valid value for free inodes
                 * and there's not enough for the package contents,
                 * then exit
                 */
                if ((*pilimit > 0) && (eptnum+1 > *pilimit)) {
                        progerr(gettext(ERR_FREE), pkgdev.dirname);
                        quit(1);
                }
                for (i = 0; i < eptnum; i++) {
                        if (strchr("dxslcbp", eptlist[i]->ftype))
                                continue;
                        else {
                                total +=
                                    (nodecount(eptlist[i]->path) * DIRSIZE);
                                total +=
                                    nblk(eptlist[i]->cinfo.size, bsize, frsize);
                                if (total > *plimit) {
                                        progerr(gettext(ERR_FREE),
                                            pkgdev.dirname);
                                        quit(1);
                                }
                                allocnode(eptlist[i]->path);
                        }
                }
        }
        /*
         * if there is a value in pllimit (-l specified limit), use that for
         * the limit from now on.
         */

        if (*pllimit != 0) {
                if (pkgdev.mount && *pllimit > *plimit)
                        logerr(gettext(WRN_LIMIT), *pllimit, *plimit);
                *plimit = *pllimit;
        }
        /*
         * calculate number of physical blocks used by each object
         */
        for (i = 0; i < eptnum; i++) {
                f[i].ept = ept = eptlist[i];
                if (ept->volno > nparts)
                        nparts = ept->volno;
                addclass(ept->pkg_class, 0);
                if (strchr("dxslcbp", ept->ftype))
                        /*
                         * virtual object (no contents)
                         */
                        f[i].blks = 0;
                else
                        /*
                         * space consumers
                         *
                         * (directories are space consumers as well, but they
                         * get accounted for later).
                         *
                         */

                        f[i].blks = nblk(ept->cinfo.size, bsize, frsize);

                if (!bpkginfo && (strcmp(f[i].ept->path, "pkginfo") == 0))
                        bpkginfo = f[i].blks;
        }

        /*
         * Make sure that items slated for a given 'part' do not exceed a single
         * volume.
         */
        for (i = 1; i <= nparts; i++) {
                btemp = (bpkginfo + 2LL);
                ftemp = 2LL;
                if (i == 1) {
                        /*
                         * save room for install directory
                         */
                        ftemp += 2;
                        btemp += nblk(eptnum * EFACTOR, bsize, frsize);
                        btemp += 2;
                }
                allocnode(NULL);
                for (j = 0; j < eptnum; j++) {
                        if (i == 1 && f[j].ept->ftype == 'i' &&
                            (strcmp(f[j].ept->path, "pkginfo") == 0 ||
                            strcmp(f[j].ept->path, "pkgmap") == 0))
                                continue;
                        if (f[j].ept->volno == i ||
                            (f[j].ept->ftype == 'i' && i == 1)) {
                                ftemp += nodecount(f[j].ept->path);
                                btemp += f[j].blks;
                                allocnode(f[j].ept->path);
                        }
                }
                btemp += (ftemp * DIRSIZE);
                if (btemp > *plimit) {
                        progerr(gettext(ERR_VOLBLKS), i, btemp, *plimit);
                        errflg++;
                /* If we have a valid inode limit, ensure this part will fit */
                } else if ((*pilimit > 0) && (ftemp+1 > *pilimit)) {
                        progerr(gettext(ERR_VOLFILES), i, ftemp + 1, *pilimit);
                        errflg++;
                }
        }
        if (errflg)
                quit(1);

        /*
         * "sf" - array sorted in decreasing file size order, based on "f".
         */
        sortsize(f, sf, eptnum);

        /*
         * initialize first volume
         */
        newvolume(sf, eptnum, *plimit, *pilimit);

        /*
         * reserve room on first volume for pkgmap
         */
        btotal += nblk((fsblkcnt_t)(eptnum * EFACTOR), bsize, frsize);
        ftotal++;


        /*
         * initialize directory info
         */
        allocnode(NULL);

        /*
         * place installation files on first volume!
         */
        flag = 0;
        for (j = 0; j < eptnum; ++j) {
                if (f[j].ept->ftype != 'i')
                        continue;
                else if (!flag++) {
                        /*
                         * save room for install directory
                         */
                        ftotal++;
                        btotal += 2ULL;
                }
                if (!f[j].ept->volno) {
                        f[j].ept->volno = 1;
                        ftotal++;
                        btotal += f[j].blks;
                } else if (f[j].ept->volno != 1) {
                        progerr(gettext(ERR_INFOFIRST), f[j].ept->path);
                        errflg++;
                }
        }

        if (errflg)
                quit(1);
        if (btotal > *plimit) {
                progerr(gettext(ERR_INFOSPACE));
                quit(1);
        }

        /*
         * Make sure that any given file will fit on a single volume, this
         * calculation has to take into account packaging overhead, otherwise
         * the function store() will go into a severe recursive plunge.
         */
        for (j = 0; j < eptnum; ++j) {
                /*
                 * directory overhead.
                 */
                btemp = nodecount(f[j].ept->path) * DIRSIZE;
                /*
                 * packaging overhead.
                 */
                btemp += (bpkginfo + 2L);       /* from newvolume() */
                if ((f[j].blks + btemp) > *plimit) {
                        errflg++;
                        progerr(gettext(ERR_TOOBIG), f[j].ept->path, f[j].blks);
                }
        }
        if (errflg)
                quit(1);

        /*
         * place classes listed on command line
         */
        if (order) {
                for (i = 0; order[i]; ++i)  {
                        while (store(sf, eptnum, order[i], *plimit, *pilimit))
                                /* stay in loop until store is complete */
                                /* void */;
                }
        }

        while (store(sf, eptnum, (char *)0, *plimit, *pilimit))
                /* stay in loop until store is complete */
                /* void */;

        /*
         * place all virtual objects, e.g. links and spec devices
         */
        for (i = 0; i < nclass; ++i) {
                /*
                 * if no objects were associated, attempt to
                 * distribute in order of class list
                 */
                if (cl[i].first == 0)
                        cl[i].last = cl[i].first = (i ? cl[i-1].last : 1);
                for (j = 0; j < eptnum; j++) {
                        if ((f[j].ept->volno == 0) &&
                            strcmp(f[j].ept->pkg_class, cl[i].name) == 0) {
                                if (strchr("sl", f[j].ept->ftype))
                                        f[j].ept->volno = cl[i].last;
                                else
                                        f[j].ept->volno = cl[i].first;
                        }
                }
        }

        if (btotal)
                newvolume(sf, eptnum, *plimit, *pilimit);

        if (nparts > (volno - 1)) {
                new_vol = volno;
                for (i = volno; i <= nparts; i++) {
                        new_vol_set = 0;
                        for (j = 0; j < eptnum; j++) {
                                if (f[j].ept->volno == i) {
                                        f[j].ept->volno = new_vol;
                                        new_vol_set = 1;
                                }
                        }
                        new_vol += new_vol_set;
                }
                nparts = new_vol - 1;
        } else
                nparts = volno - 1;

        *plimit = bmax;
        *pilimit = fmax;

        /*
         * free up dynamic space used by this module
         */
        free(f);
        free(sf);
        for (i = 0; i < nclass; ++i)
                free(cl[i].name);
        free(cl);
        for (i = 0; dirlist[i]; i++)
                free(dirlist[i]);
        free(dirlist);

        return (errflg ? -1 : nparts);
}

static int
store(struct data **sf, unsigned int eptnum, char *aclass, fsblkcnt_t limit,
    fsfilcnt_t ilimit)
{
        int     i, svnodes, choice, select;
        long    ftemp;
        fsblkcnt_t      btemp;

        svnodes = 0;
        select = 0;
        choice = (-1);
        for (i = 0; i < eptnum; ++i) {
                if (sf[i]->ept->volno || strchr("sldxcbp", sf[i]->ept->ftype))
                        continue; /* defer storage until class is selected */
                if (aclass && strcmp(aclass, sf[i]->ept->pkg_class))
                        continue;
                select++; /* we need to place at least one object */
                ftemp = nodecount(sf[i]->ept->path);
                btemp = sf[i]->blks + (ftemp * DIRSIZE);
                if (((limit == 0) || ((btotal + btemp) <= limit)) &&
                    ((ilimit == 0) || ((ftotal + ftemp) < ilimit))) {
                        /* largest object which fits on this volume */
                        choice = i;
                        svnodes = ftemp;
                        break;
                }
        }
        if (!select)
                return (0); /* no more to objects to place */

        if (choice < 0) {
                newvolume(sf, eptnum, limit, ilimit);
                return (store(sf, eptnum, aclass, limit, ilimit));
        }
        sf[choice]->ept->volno = (char)volno;
        ftotal += svnodes + 1;
        btotal += sf[choice]->blks + (svnodes * DIRSIZE);
        allocnode(sf[i]->ept->path);
        addclass(sf[choice]->ept->pkg_class, volno);
        return (++choice); /* return non-zero if more work to do */
}

static void
allocnode(char *path)
{
        register int i;
        int     found;
        char    *pt;

        if (path == NULL) {
                if (dirlist) {
                        /*
                         * free everything
                         */
                        for (i = 0; dirlist[i]; i++)
                                free(dirlist[i]);
                        free(dirlist);
                }
                dirlist = (char **)calloc(MALSIZ, sizeof (char *));
                if (dirlist == NULL) {
                        progerr(gettext(ERR_MEMORY), errno);
                        quit(99);
                }
                return;
        }

        pt = path;
        if (*pt == '/')
                pt++;
        /*
         * since the pathname supplied is never just a directory,
         * we store only the dirname of of the path.
         */
        while (pt = strchr(pt, '/')) {
                *pt = '\0';
                found = 0;
                for (i = 0; dirlist[i] != NULL; i++) {
                        if (strcmp(path, dirlist[i]) == 0) {
                                found++;
                                break;
                        }
                }
                if (!found) {
                        /* insert this path in node list */
                        dirlist[i] = qstrdup(path);
                        if ((++i % MALSIZ) == 0) {
                                dirlist = (char **)realloc(dirlist,
                                    (i+MALSIZ) * sizeof (char *));
                                if (dirlist == NULL) {
                                        progerr(gettext(ERR_MEMORY), errno);
                                        quit(99);
                                }
                        }
                        dirlist[i] = (char *)NULL;
                }
                *pt++ = '/';
        }
}

static int
nodecount(char *path)
{
        char    *pt;
        int     i, found, count;

        pt = path;
        if (*pt == '/')
                pt++;

        /*
         * we want to count the number of path
         * segments that need to be created, not
         * including the basename of the path;
         * this works only since we are never
         * passed a pathname which itself is a
         * directory
         */
        count = 0;
        while (pt = strchr(pt, '/')) {
                *pt = '\0';
                found = 0;
                for (i = 0; dirlist[i]; i++) {
                        if (strcmp(path, dirlist[i]) != 0) {
                                found++;
                                break;
                        }
                }
                if (!found)
                        count++;
                *pt++ = '/';
        }
        return (count);
}

static void
newvolume(struct data **sf, unsigned int eptnum, fsblkcnt_t limit,
    fsblkcnt_t ilimit)
{
        register int i;
        int     newnodes;

        if (volno) {
                (void) fprintf(stderr,
                    gettext("part %2d -- %llu blocks, %llu entries\n"),
                    volno, btotal, ftotal);
                if (btotal > bmax)
                        bmax = btotal;
                if (ftotal > fmax)
                        fmax = ftotal;
                btotal = bpkginfo + 2ULL;
                ftotal = 2;
        } else {
                btotal = 2ULL;
                ftotal = 1;
        }
        volno++;

        /*
         * zero out directory storage
         */
        allocnode((char *)0);

        /*
         * force storage of files whose volume number has already been assigned
         */
        for (i = 0; i < eptnum; i++) {
                if (sf[i]->ept->volno == volno) {
                        newnodes = nodecount(sf[i]->ept->path);
                        ftotal += newnodes + 1;
                        btotal += sf[i]->blks + (newnodes * DIRSIZE);
                        if (btotal > limit) {
                                progerr(gettext(ERR_VOLBLKS), volno, btotal,
                                    limit);
                                quit(1);
                        } else if ((ilimit == 0) && (ftotal+1 > ilimit)) {
                                progerr(gettext(ERR_VOLFILES), volno, ftotal+1,
                                    ilimit);
                                quit(1);
                        }
                }
        }
}

static void
addclass(char *aclass, int vol)
{
        int i;

        for (i = 0; i < nclass; ++i) {
                if (strcmp(cl[i].name, aclass) == 0) {
                        if (vol <= 0)
                                return;
                        if (!cl[i].first || (vol < cl[i].first))
                                cl[i].first = vol;
                        if (vol > cl[i].last)
                                cl[i].last = vol;
                        return;
                }
        }
        cl[nclass].name = qstrdup(aclass);
        cl[nclass].first = vol;
        cl[nclass].last = vol;
        if ((++nclass % MALSIZ) == 0) {
                cl = (struct class_type *)realloc((char *)cl,
                    sizeof (struct class_type) * (nclass+MALSIZ));
                if (!cl) {
                        progerr(gettext(ERR_MEMORY), errno);
                        quit(99);
                }
        }
}

static void
sortsize(struct data *f, struct data **sf, unsigned int eptnum)
{
        int     nsf;
        int     j, k;
        unsigned int    i;

        nsf = 0;
        for (i = 0; i < eptnum; i++) {
                for (j = 0; j < nsf; ++j) {
                        if (f[i].blks > sf[j]->blks) {
                                for (k = nsf; k > j; k--) {
                                        sf[k] = sf[k-1];
                                }
                                break;
                        }
                }
                sf[j] = &f[i];
                nsf++;
        }
}