root/usr/src/cmd/unpack/unpack.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) 1984, 1986, 1987, 1988, 1989 AT&T */
/*        All Rights Reserved   */


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

/*
 *      Huffman decompressor
 *      Usage:  pcat filename...
 *      or      unpack filename...
 */

#include <setjmp.h>
#include <signal.h>
#include <locale.h>
#include <utime.h>
#include <sys/param.h>
#include <sys/acl.h>
#include <aclutils.h>
#include <libcmdutils.h>

static struct utimbuf u_times;
static jmp_buf env;
static struct   stat status;
static char     *argv0, *argvk;

/* rmflg, when set it's ok to rm arvk file on caught signals */
static int      rmflg = 0;

#define SUF0    '.'
#define SUF1    'z'
#define US      037
#define RS      036

/* variables associated with i/o */
static char     filename[MAXPATHLEN];

static short    infile;
static short    outfile;
static short    inleft;
static short    is_eof = 0;
static char     *inp;
static char     *outp;
static char     inbuff[BUFSIZ];
static char     outbuff[BUFSIZ];

/* the dictionary */
static long     origsize;
static short    maxlev;
static short    intnodes[25];
static char     *tree[25];
static char     characters[256];
static char     *eof;

static void putch(char c);
static int expand();
static int decode();
static int getwdsize();
static int getch();
static int getdict();

/* Extended system attribute support */

static int saflg = 0;


/* read in the dictionary portion and build decoding structures */
/* return 1 if successful, 0 otherwise */
int
getdict()
{
        register int c, i, nchildren;

        /*
         * check two-byte header
         * get size of original file,
         * get number of levels in maxlev,
         * get number of leaves on level i in intnodes[i],
         * set tree[i] to point to leaves for level i
         */
        eof = &characters[0];

        inbuff[6] = 25;
        inleft = read(infile, &inbuff[0], BUFSIZ);
        if (inleft < 0) {
                (void) fprintf(stderr, gettext(
                    "%s: %s: read error: "), argv0, filename);
                perror("");
                return (0);
        }
        if (inbuff[0] != US)
                goto goof;

        if (inbuff[1] == US) {          /* oldstyle packing */
                if (setjmp(env))
                        return (0);
                return (expand());
        }
        if (inbuff[1] != RS)
                goto goof;

        inp = &inbuff[2];
        origsize = 0;
        for (i = 0; i < 4; i++)
                origsize = origsize*256 + ((*inp++) & 0377);
        maxlev = *inp++ & 0377;
        if (maxlev > 24) {
goof:           (void) fprintf(stderr, gettext(
                    "%s: %s: not in packed format\n"), argv0, filename);
                return (0);
        }
        for (i = 1; i <= maxlev; i++)
                intnodes[i] = *inp++ & 0377;
        for (i = 1; i <= maxlev; i++) {
                tree[i] = eof;
                for (c = intnodes[i]; c > 0; c--) {
                        if (eof >= &characters[255])
                                goto goof;
                        *eof++ = *inp++;
                }
        }
        *eof++ = *inp++;
        intnodes[maxlev] += 2;
        inleft -= inp - &inbuff[0];
        if (inleft < 0)
                goto goof;

        /*
         * convert intnodes[i] to be number of
         * internal nodes possessed by level i
         */

        nchildren = 0;
        for (i = maxlev; i >= 1; i--) {
                c = intnodes[i];
                intnodes[i] = nchildren /= 2;
                nchildren += c;
        }
        return (decode());
}

/* unpack the file */
/* return 1 if successful, 0 otherwise */
int
decode()
{
        register int bitsleft, c, i;
        int j, lev, cont = 1;
        char *p;

        outp = &outbuff[0];
        lev = 1;
        i = 0;
        while (cont) {
                if (inleft <= 0) {
                        inleft = read(infile, inp = &inbuff[0], BUFSIZ);
                        if (inleft < 0) {
                                (void) fprintf(stderr, gettext(
                                    "%s: %s: read error: "),
                                    argv0, filename);
                                perror("");
                                return (0);
                        }
                }
                if (--inleft < 0) {
uggh:                   (void) fprintf(stderr, gettext(
                            "%s: %s: unpacking error\n"),
                            argv0, filename);
                        return (0);
                }
                c = *inp++;
                bitsleft = 8;
                while (--bitsleft >= 0) {
                        i *= 2;
                        if (c & 0200)
                                i++;
                        c <<= 1;
                        if ((j = i - intnodes[lev]) >= 0) {
                                p = &tree[lev][j];
                                if (p == eof) {
                                        c = outp - &outbuff[0];
                                        if (write(outfile, &outbuff[0], c)
                                            != c) {
wrerr:                                          (void) fprintf(stderr,
                                                    gettext(
                                                    "%s: %s: write error: "),
                                                    argv0, argvk);
                                                perror("");
                                                return (0);
                                        }
                                        origsize -= c;
                                        if (origsize != 0)
                                                goto uggh;
                                        return (1);
                                }
                                *outp++ = *p;
                                if (outp == &outbuff[BUFSIZ]) {
                                        if (write(outfile, outp = &outbuff[0],
                                            BUFSIZ) != BUFSIZ)
                                                goto wrerr;
                                        origsize -= BUFSIZ;
                                }
                                lev = 1;
                                i = 0;
                        } else
                                lev++;
                }
        }
        return (1);     /* we won't get here , but lint is pleased */
}

int
main(int argc, char *argv[])
{
        extern int optind;
        int i, k;
        int error;
        int sep, errflg = 0, pcat = 0;
        register char *p1, *cp;
        int fcount = 0;         /* failure count */
        int max_name;
        void onsig(int);
        acl_t *aclp = NULL;
        int c;
        char *progname;
        int sattr_exist = 0;
        int xattr_exist = 0;

        if (signal(SIGHUP, SIG_IGN) != SIG_IGN)
#ifdef __STDC__
                (void) signal((int)SIGHUP, onsig);
#else
                (void) signal((int)SIGHUP, onsig);
#endif
        if (signal(SIGINT, SIG_IGN) != SIG_IGN)
#ifdef __STDC__
                (void) signal((int)SIGINT, onsig);
#else
                (void) signal((int)SIGINT, onsig);
#endif
        if (signal(SIGTERM, SIG_IGN) != SIG_IGN)
#ifdef __STDC__
                (void) signal((int)SIGTERM, onsig);
#else
                (void) signal(SIGTERM, onsig);
#endif

        (void) setlocale(LC_ALL, "");
#if !defined(TEXT_DOMAIN)
#define TEXT_DOMAIN "SYS_TEST"
#endif
        (void) textdomain(TEXT_DOMAIN);

        if (progname = strrchr(argv[0], '/'))
                ++progname;
        else
                progname = argv[0];

        p1 = *argv;
        while (*p1++) { };      /* Point p1 to end of argv[0] string */
        while (--p1 >= *argv)
                if (*p1 == '/')break;
        *argv = p1 + 1;
        argv0 = argv[0];
        if (**argv == 'p')pcat++;       /* User entered pcat (or /xx/xx/pcat) */

        while ((c = getopt(argc, argv, "/")) != EOF) {
                if (c == '/') {
                        if (pcat)
                                ++errflg;
                        else
                                saflg++;
                } else
                        ++errflg;
        }
        /*
         * Check for invalid option.  Also check for missing
         * file operand, ie: "unpack" or "pcat".
         */
        argc -= optind;
        argv = &argv[optind];
        if (errflg || argc < 1) {
                if (!pcat)
                        (void) fprintf(stderr,
                            gettext("usage: %s [-/] file...\n"), argv0);
                else
                        (void) fprintf(stderr, gettext("usage: %s file...\n"),
                            argv0);

                if (argc < 1) {
                        /*
                         * return 1 for usage error when no file was specified
                         */
                        return (1);
                }
        }
        /* loop through the file names */
        for (k = 0; k < argc; k++) {
                fcount++;       /* expect the worst */
                if (errflg) {
                        /*
                         * invalid option; just count the number of files not
                         * unpacked
                         */
                        continue;
                }
                /* remove any .z suffix the user may have added */
                for (cp = argv[k]; *cp != '\0'; ++cp)
                        ;
                if (cp[-1] == SUF1 && cp[-2] == SUF0) {
                        *cp-- = '\0'; *cp-- = '\0'; *cp = '\0';
                }
                sep = -1;
                cp = filename;
                argvk = argv[k];
                /* copy argv[k] to filename and count chars in base name */
                for (i = 0; i < (MAXPATHLEN-3) && (*cp = argvk[i]); i++)
                        if (*cp++ == '/')
                                sep = i;
                /* add .z suffix to filename */
                *cp++ = SUF0;
                *cp++ = SUF1;
                *cp = '\0';
                if ((infile = open(filename, O_RDONLY)) == -1) {
                        (void) fprintf(stderr, gettext(
                            "%s: %s: cannot open: "),
                            argv0, filename);
                        perror("");
                        goto done;
                }
                if (pcat)
                        outfile = 1;    /* standard output */
                else {

                        error = facl_get(infile, ACL_NO_TRIVIAL, &aclp);
                        if (error != 0) {
                                (void) printf(gettext(
                                    "%s: %s: cannot retrieve ACL : %s\n"),
                                    argv0, filename, acl_strerror(error));
                        }

                        max_name = pathconf(filename, _PC_NAME_MAX);
                        if (max_name == -1) {
                                /* no limit on length of filename */
                                max_name = _POSIX_NAME_MAX;
                        }
                        if (i >= (MAXPATHLEN-1) || (i - sep - 1) > max_name) {
                                (void) fprintf(stderr, gettext(
                                    "%s: %s: file name too long\n"),
                                    argv0, argvk);
                                goto done;
                        }
                        if (stat(argvk, &status) != -1) {
                                (void) fprintf(stderr, gettext(
                                    "%s: %s: already exists\n"),
                                    argv0, argvk);
                                goto done;
                        }
                        (void) fstat(infile, &status);
                        if (status.st_nlink != 1) {
                                (void) printf(gettext(
                                    "%s: %s: Warning: file has links\n"),
                                    argv0, filename);
                        }
                        if ((outfile = creat(argvk, status.st_mode)) == -1) {
                                (void) fprintf(stderr, gettext(
                                    "%s: %s: cannot create: "),
                                    argv0, argvk);
                                perror("");
                                goto done;
                        }
                        rmflg = 1;
                }

                if (getdict()) {        /* unpack */
                        if (pathconf(filename, _PC_XATTR_EXISTS) == 1)
                                xattr_exist = 1;
                        if (saflg && sysattr_support(filename,
                            _PC_SATTR_EXISTS) == 1)
                                sattr_exist = 1;
                        if (pcat || xattr_exist || sattr_exist) {
                                if (mv_xattrs(progname, filename, argv[k],
                                    sattr_exist, 0)
                                    != 0) {
                                        /* Move attributes back ... */
                                        xattr_exist = 0;
                                        sattr_exist = 0;
                                        if (pathconf(argvk, _PC_XATTR_EXISTS)
                                            == 1)
                                                xattr_exist = 1;
                                        if (saflg && sysattr_support(argvk,
                                            _PC_SATTR_EXISTS) == 1)
                                                sattr_exist = 1;
                                        if (!pcat && (xattr_exist ||
                                            sattr_exist)) {
                                                (void) mv_xattrs(progname,
                                                    argv[k], filename,
                                                    sattr_exist, 1);
                                                (void) unlink(argvk);
                                                goto done;
                                        }
                                } else {
                                        if (!pcat)
                                                (void) unlink(filename);
                                }
                        } else if (!pcat)
                                (void) unlink(filename);

                        if (!pcat) {
                                (void) printf(gettext("%s: %s: unpacked\n"),
                                    argv0, argvk);
                                /*
                                 * preserve acc & mod dates
                                 */
                                u_times.actime = status.st_atime;
                                u_times.modtime = status.st_mtime;
                                if (utime(argvk, &u_times) != 0) {
                                        errflg++;
                                        (void) fprintf(stderr, gettext(
                                            "%s: cannot change times on %s: "),
                                            argv0, argvk);
                                        perror("");
                                }
                                if (chmod(argvk, status.st_mode) != 0) {
                                        errflg++;
                                        (void) fprintf(stderr, gettext(
                                        "%s: cannot change mode to %o on %s: "),
                                            argv0, (uint_t)status.st_mode,
                                            argvk);
                                        perror("");
                                }
                                (void) chown(argvk,
                                    status.st_uid, status.st_gid);
                                if (aclp && (facl_set(outfile, aclp) < 0)) {
                                        (void) printf(gettext("%s: cannot "
                                            "set ACL on %s: "), argv0, argvk);
                                        perror("");
                                }

                                rmflg = 0;
                        }
                        if (!errflg)
                                fcount--;       /* success after all */
                }
done:           (void) close(infile);
                if (!pcat)
                        (void) close(outfile);

                if (aclp) {
                        acl_free(aclp);
                        aclp = NULL;
                }
        }
        return (fcount);
}

/*
 * This code is for unpacking files that
 * were packed using the previous algorithm.
 */

static int      Tree[1024];

/* return 1 if successful, 0 otherwise */

int
expand()
{
        int tp, bit;
        short word;
        int keysize, i, *t;

        outp = outbuff;
        inp = &inbuff[2];
        inleft -= 2;

        origsize = ((long)(unsigned)getwdsize())*256*256;
        origsize += (unsigned)getwdsize();
        if (origsize == 0 || is_eof) {
                (void) fprintf(stderr, gettext(
                    "%s: %s: not in packed format\n"),
                    argv0, filename);
                return (0);
        }
        t = Tree;
        for (keysize = getwdsize(); keysize--; ) {
                if ((i = getch()) == 0377)
                        *t++ = getwdsize();
                else {
                                /*
                                 * reached EOF unexpectedly
                                 */
                        if (is_eof) {
                                (void) fprintf(stderr, gettext(
                                    "%s: %s: not in packed format\n"),
                                    argv0, filename);
                                return (0);
                        }
                        *t++ = i & 0377;
                }
        }
                /*
                 * reached EOF unexpectedly
                 */
        if (is_eof) {
                (void) fprintf(stderr, gettext(
                    "%s: %s: not in packed format\n"),
                    argv0, filename);
                return (0);
        }


        bit = tp = 0;
        for (;;) {
                if (bit <= 0) {
                        word = getwdsize();
                        /*
                         * reached EOF unexpectedly
                         */
                        if (word == 0 && is_eof && origsize > 0) {
                                (void) fprintf(stderr, gettext(
                                    "%s: %s: not in packed format\n"),
                                    argv0, filename);
                                return (0);
                        }
                        bit = 16;
                }
                tp += Tree[tp + (word < 0)];
                word <<= 1;
                bit--;
                if (Tree[tp] == 0) {
                        putch(Tree[tp+1]);
                        tp = 0;
                        if ((origsize -= 1) == 0) {
                                (void) write(outfile, outbuff, outp - outbuff);
                                return (1);
                        }
                }
        }
}

int
getch()
{
        if (inleft <= 0) {
                inleft = read(infile, inp = inbuff, BUFSIZ);
                if (inleft < 0) {
                        (void) fprintf(stderr, gettext(
                            "%s: %s: read error: "),
                            argv0, filename);
                        perror("");
                        longjmp(env, 1);
                } else {                /* reached EOF, report it */
                        if (inleft == 0) {
                                is_eof = 1;
                                return (EOF);
                        }
                }
        }
        inleft--;
        return (*inp++ & 0377);
}

int
getwdsize()
{
        char c;
        int d;

        c = getch();
        d = getch();
        if (is_eof)
                return (0);
        d <<= 8;
        d |= c & 0377;
        return (d);
}

void
onsig(int sig)
{
                                /* could be running as unpack or pcat   */
                                /* but rmflg is set only when running   */
                                /* as unpack and only when file is      */
                                /* created by unpack and not yet done   */
        if (rmflg == 1)
                (void) unlink(argvk);
        /* To quiet lint noise */
        if (sig == SIGTERM || sig == SIGHUP || sig == SIGINT)
                exit(1);
}

void
putch(char c)
{
        int n;

        *outp++ = c;
        if (outp == &outbuff[BUFSIZ]) {
                n = write(outfile, outp = outbuff, BUFSIZ);
                if (n < BUFSIZ) {
                        (void) fprintf(stderr, gettext(
                            "%s: %s: write error: "),
                            argv0, argvk);
                        perror("");
                        longjmp(env, 2);
                }
        }
}