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

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


#include <stdio.h>
#include <ctype.h>
#include <dirent.h>
#include <limits.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <pkgstrct.h>
#include <errno.h>
#include <locale.h>
#include <libintl.h>
#include <pkglib.h>
#include "libadm.h"
#include "libinst.h"

extern int      holdcinfo;

#define WRN_SCARYLINK   "WARNING: <%s>, target of symlink <%s>, does not exist."

#define ERR_PATHLONG    "path argument too long"
#define ERR_CLASSLONG   "classname argument too long"
#define ERR_CLASSCHAR   "bad character in classname"
#define ERR_STAT        "unable to stat <%s>"
#define ERR_WRITE       "write of entry failed"
#define ERR_POPEN       "unable to create pipe to <%s>"
#define ERR_PCLOSE      "unable to close pipe to <%s>"
#define ERR_RDLINK      "unable to read link for <%s>"
#define ERR_MEMORY      "memory allocation failure, errno=%d"

#define LINK    1

struct link {
        char    *path;
        ino_t   ino;
        dev_t   dev;
        struct link *next;
};

static struct link *firstlink = (struct link *)0;
static struct link *lastlink = (struct link *)0;
static char *scan_raw_ln(char *targ_name, char *link_name);

static char     *def_class = "none";

static int      errflg = 0;
static int      iflag = 0;      /* follow symlinks */
static int      xflag = 0;      /* confirm contents of files */
static int      nflag = 0;
static char     construction[PATH_MAX], mylocal[PATH_MAX];

static void     findlink(struct cfent *ept, char *path, char *svpath);
static void     follow(char *path);
static void     output(char *path, int n, char *local);
static void     usage(void);

int
main(int argc, char *argv[])
{
        int c;
        char *pt, path[PATH_MAX];
        char    *abi_sym_ptr;
        extern char     *optarg;
        extern int      optind;

        (void) setlocale(LC_ALL, "");

#if !defined(TEXT_DOMAIN)       /* Should be defined by cc -D */
#define TEXT_DOMAIN "SYS_TEST"
#endif
        (void) textdomain(TEXT_DOMAIN);

        (void) set_prog_name(argv[0]);

        while ((c = getopt(argc, argv, "xnic:?")) != EOF) {
                switch (c) {
                    case 'x':   /* include content info */
                        xflag++;
                        break;

                    case 'n':
                        nflag++;
                        break;

                    case 'c':   /* assign class */
                        def_class = optarg;
                        /* validate that classname is acceptable */
                        if (strlen(def_class) > (size_t)CLSSIZ) {
                                progerr(gettext(ERR_CLASSLONG));
                                exit(1);
                        }
                        for (pt = def_class; *pt; pt++) {
                                if (!isalpha(*pt) && !isdigit(*pt)) {
                                        progerr(gettext(ERR_CLASSCHAR));
                                        exit(1);
                                }
                        }
                        break;

                    case 'i':   /* follow symlinks */
                        iflag++;
                        break;

                    default:
                        usage();
                }
        }

        if (iflag) {
                /* follow symlinks */
                set_nonABI_symlinks();
        } else {
                /* bug id 4244631, not ABI compliant */
                abi_sym_ptr = getenv("PKG_NONABI_SYMLINKS");
                if (abi_sym_ptr && strncasecmp(abi_sym_ptr, "TRUE", 4) == 0) {
                        set_nonABI_symlinks();
                }
        }
        holdcinfo = !xflag;
        if (optind == argc) {
                /* take path list from stdin */
                while (fgets(path, sizeof (path), stdin) != (char *)NULL) {
                        output(path, 0, NULL);
                }
        } else {
                while (optind < argc) {
                        follow(argv[optind++]);
                }
        }

        return (errflg ? 1 : 0);
}

static void
output(char *path, int n, char *local)
{
        char            mypath[PATH_MAX];
        int             len;
        int             s;
        struct cfent    entry;

        /*
         * remove any trailing newline characters from the end of path
         */

        len = strlen(path);
        while ((len > 0) && (path[len-1] == '\n')) {
                path[--len] = '\0';
        }

        entry.volno = 0;
        entry.ftype = '?';
        entry.path = mypath;
        (void) strlcpy(entry.pkg_class, def_class, sizeof (entry.pkg_class));
        (void) strlcpy(entry.path, path, PATH_MAX);
        entry.ainfo.local = NULL;
        entry.ainfo.mode = BADMODE;
        (void) strlcpy(entry.ainfo.owner, BADOWNER, sizeof (entry.ainfo.owner));
        (void) strlcpy(entry.ainfo.group, BADGROUP, sizeof (entry.ainfo.group));
        errflg = 0;

        if (xflag) {
                entry.ftype = '?';
                if (cverify(0, &entry.ftype, path, &entry.cinfo, 1)) {
                        errflg++;
                        logerr(gettext("ERROR: %s"), path);
                        logerr(getErrbufAddr());
                        return;
                }
        }

        /*
         * Use averify to figure out the attributes. This has trouble
         * divining the identity of a symlink which points to a
         * non-existant target. For that reason, if it comes back as
         * an existence problem, we fake in a symlink and see if averify
         * likes that. If it does, all we have is a risky symlink.
         */
        if ((s = averify(0, &entry.ftype, path, &entry.ainfo)) == VE_EXIST &&
            !iflag) {
                entry.ftype = 's';      /* try again assuming symlink */
                /* try to read what it points to */
                if ((s = readlink(path, mylocal, PATH_MAX)) > 0) {
                        mylocal[s] = '\000';    /* terminate it */
                        entry.ainfo.local = mylocal;
                        if (averify(0, &entry.ftype, path, &entry.ainfo)) {
                                errflg++;
                        } else
                                /* It's a link to a file not in this package. */
                                ptext(stderr, gettext(WRN_SCARYLINK),
                                    mylocal, path);
                } else {
                        errflg++;
                }
        } else if (s != 0 && s != VE_CONT)
                errflg++;

        if (errflg) {
                logerr(gettext("ERROR: %s"), path);
                logerr(getErrbufAddr());
                return;
        }

        if (n) {
                /* replace first n characters with 'local' */
                if (strchr("fev", entry.ftype)) {
                        entry.ainfo.local = mylocal;
                        (void) strlcpy(entry.ainfo.local, entry.path,
                                PATH_MAX);
                        canonize(entry.ainfo.local);
                }
                if (local[0]) {
                        entry.ainfo.local = mylocal;
                        (void) strlcpy(entry.path, local, PATH_MAX);
                        (void) strcat(entry.path, path+n);
                } else
                        (void) strlcpy(entry.path,
                                (path[n] == '/') ? path+n+1 : path+n,
                                PATH_MAX);
        }

        canonize(entry.path);
        if (entry.path[0]) {
                findlink(&entry, path, entry.path);
                if (strchr("dcbp", entry.ftype) ||
                (nflag && !strchr("sl", entry.ftype)))
                        entry.ainfo.local = NULL;
                if (ppkgmap(&entry, stdout)) {
                        progerr(gettext(ERR_WRITE));
                        exit(99);
                }
        }
}

static void
follow(char *path)
{
        struct stat stbuf;
        FILE    *pp;
        char    *pt,
                local[PATH_MAX],
                newpath[PATH_MAX],
                cmd[PATH_MAX+32];
        int n;

        errflg = 0;

        if (pt = strchr(path, '=')) {
                *pt++ = '\0';
                n = ((unsigned int)pt - (unsigned int)path - 1);
                if (n >= PATH_MAX) {
                        progerr(gettext(ERR_PATHLONG));
                        errflg++;
                        return;
                }

                n = strlen(pt);

                if (n < PATH_MAX) {
                        (void) strlcpy(local, pt, sizeof (local));
                        n = strlen(path);
                } else {
                        progerr(gettext(ERR_PATHLONG));
                        errflg++;
                        return;
                }
        } else {
                n = 0;
                local[0] = '\0';
        }

        if (stat(path, &stbuf)) {
                progerr(gettext(ERR_STAT), path);
                errflg++;
                return;
        }

        if (stbuf.st_mode & S_IFDIR) {
                (void) snprintf(cmd, sizeof (cmd), "find %s -print", path);
                if ((pp = popen(cmd, "r")) == NULL) {
                        progerr(gettext(ERR_POPEN), cmd);
                        exit(1);
                }
                while (fscanf(pp, "%[^\n]\n", newpath) == 1)
                        output(newpath, n, local);
                if (pclose(pp)) {
                        progerr(gettext(ERR_PCLOSE), cmd);
                        errflg++;
                }
        } else
                output(path, n, local);
}

/*
 * Scan a raw link for origination errors. Given
 *      targ_name = hlink/path/file1
 *              and
 *      link_name = hlink/path/file2
 * we don't want the link to be verbatim since link_name must be relative
 * to it's source. This functions checks for identical directory paths
 * and if it's clearly a misplaced relative path, the duplicate
 * directories are stripped. This is necessary because pkgadd is actually
 * in the source directory (hlink/path) when it creates the link.
 *
 * NOTE : The buffer we get with targ_name is going to be used later
 * and cannot be modified. That's why we have yet another PATH_MAX
 * size buffer in this function.
 */
static char *
scan_raw_ln(char *targ_name, char *link_name)
{
        char *const_ptr;        /* what we return */
        char *file_name;        /* name of the file in link_name */
        char *this_dir;         /* current directory in targ_name */
        char *next_dir;         /* next directory in targ_name  */
        char *targ_ptr;         /* current character in targ_name */

        const_ptr = targ_name;  /* Point to here 'til we know it's different. */

        /*
         * If the link is absolute or it is in the current directory, no
         * further testing necessary.
         */
        if (RELATIVE(targ_name) &&
            (file_name = strrchr(link_name, '/')) != NULL) {

                /*
                 * This will be walked down to the highest directory
                 * not common to both the link and the target.
                 */
                targ_ptr = targ_name;

                /*
                 * At this point targ_name is a relative path through at
                 * least one directory.
                 */
                this_dir = targ_ptr;    /* first directory in targ_name */
                file_name++;            /* point to the name not the '/' */

                /*
                 * Scan across the pathname until we reach a different
                 * directory or the final file name.
                 */
                do {
                        size_t str_size;

                        next_dir = strchr(targ_ptr, '/');
                        if (next_dir)
                                next_dir++;     /* point to name not '/' */
                        else    /* point to the end of the string */
                                next_dir = targ_ptr+strlen(targ_ptr);

                        /* length to compare */
                        str_size = ((ptrdiff_t)next_dir - (ptrdiff_t)this_dir);

                        /*
                         * If both paths begin with the same directory, then
                         * skip that common directory in both the link and
                         * the target.
                         */
                        if (strncmp(this_dir, link_name, str_size) == 0) {
                                /* point to the target so far */
                                const_ptr = this_dir = next_dir;
                                /* Skip past it in the target */
                                targ_ptr = (char *)(targ_ptr+str_size);
                                /* Skip past it in the link */
                                link_name = (char *)(link_name+str_size);
                        /*
                         * If these directories don't match then the
                         * directory above is the lowest common directory. We
                         * need to construct a relative path from the lowest
                         * child up to that directory.
                         */
                        } else {
                                int d = 0;
                                char *dptr = link_name;

                                /* Count the intermediate directories. */
                                while ((dptr = strchr(dptr, '/')) != NULL) {
                                        dptr++;
                                        d++;
                                }
                                /*
                                 * Now targ_ptr is pointing to the fork in
                                 * the path and dptr is pointing to the lowest
                                 * child in the link. We now insert the
                                 * appropriate number of "../'s" to get to
                                 * the first common directory. We'll
                                 * construct this in the construction
                                 * buffer.
                                 */
                                if (d) {
                                        char *tptr;

                                        const_ptr = tptr = construction;
                                        while (d--) {
                                                (void) strlcpy(tptr,
                                                        "../", PATH_MAX);
                                                tptr += 3;
                                        }
                                        (void) strlcpy(tptr, targ_ptr,
                                                PATH_MAX);
                                }
                                break;          /* done */
                        }
                } while (link_name != file_name);       /* at file name */
        }

        return (const_ptr);
}

static void
findlink(struct cfent *ept, char *path, char *svpath)
{
        struct stat     statbuf;
        struct link     *link, *new;
        char            buf[PATH_MAX];
        int             n;

        if (lstat(path, &statbuf)) {
                progerr(gettext(ERR_STAT), path);
                errflg++;
        }
        if ((statbuf.st_mode & S_IFMT) == S_IFLNK) {
                if (!iflag) {
                        ept->ainfo.local = mylocal;
                        ept->ftype = 's';
                        n = readlink(path, buf, PATH_MAX);
                        if (n <= 0) {
                                progerr(gettext(ERR_RDLINK), path);
                                errflg++;
                                (void) strlcpy(ept->ainfo.local,
                                        "unknown", PATH_MAX);
                        } else {
                                (void) strncpy(ept->ainfo.local, buf, n);
                                ept->ainfo.local[n] = '\0';
                        }
                }
                return;
        }

        if (stat(path, &statbuf))
                return;
        if (statbuf.st_nlink <= 1)
                return;

        for (link = firstlink; link; link = link->next) {
                if ((statbuf.st_ino == link->ino) &&
                (statbuf.st_dev == link->dev)) {
                        ept->ftype = 'l';
                        ept->ainfo.local = mylocal;
                        (void) strlcpy(ept->ainfo.local,
                                        scan_raw_ln(link->path, ept->path),
                                        PATH_MAX);
                        return;
                }
        }
        if ((new = (struct link *)calloc(1, sizeof (struct link))) == NULL) {
                progerr(gettext(ERR_MEMORY), errno);
                exit(1);
        }

        if (firstlink) {
                lastlink->next = new;
                lastlink = new;
        } else
                firstlink = lastlink = new;

        new->path = strdup(svpath);
        new->ino = statbuf.st_ino;
        new->dev = statbuf.st_dev;
}

static void
usage(void)
{
        (void) fprintf(stderr,
            gettext("usage: %s [-i] [-c class] [path ...]\n"), get_prog_name());
        exit(1);
        /*NOTREACHED*/
}