root/usr/src/cmd/sgs/ar/common/cmd.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) 1995, 2010, Oracle and/or its affiliates. All rights reserved.
 * Copyright 2021 Oxide Computer Company
 */

/*
 *      Copyright (c) 1988 AT&T
 *        All Rights Reserved
 *
 */

/*
 * Incompatible Archive Header
 *
 * The archive file member header used in SunOS 4.1 archive files and
 * Solaris archive files are incompatible. The header file is:
 *      /usr/include/ar.h, struct ar_hdr.
 * The member ar_name[] in Solaris comforms with Standard and the
 * member name terminates with '/'. The SunOS's member does not terminate
 * with '/' character. A bug 4046054 was filed:
 *      The ar command in Solaris 2.5.1 is incompatible with archives
 *      created on 4.x.
 *
 * To handle archive files created in SunOS 4.1 system on Solaris, the
 * following changes were made:
 *
 *      1. file.c/writefile()
 *              Before writing each member files into the output
 *              archive file, ar_name[] is checked. If it is NULL,
 *              it means that the original archive header for this
 *              member was incompatible with Solaris format.
 *
 *              The original Solaris ar command ended up having
 *              NULL name for the header. The change here uses the
 *              ar_rawname, which is much closer to the original
 *              name.
 *
 *      2. cmd.c
 *              For the p command, the code used to use only ar_longname
 *              to seach the matching name. The member is set to NULL
 *              if the archive member header was incompatible.
 *              The ar_rawname is also used to find the matching member name.
 *
 *              For commands to update the archive file, we do not
 *              use ar_rawname, and just use the ar_longname. The commands are
 *              r (replace), m (modify the position) and d (delete).
 */

#include "inc.h"

/*
 * Forward Declarations
 */
static void     ar_select(int *, unsigned long);
static void     cleanup(Cmd_info *);
static int      create_extract(ARFILE *, int, int, Cmd_info *);
static char     *match(char *, Cmd_info *);
static void     mesg(int, char *, Cmd_info *);
static void     movefil(ARFILE *, struct stat *);
static FILE     *stats(char *, struct stat *);

/*
 * Commands
 */
void
rcmd(Cmd_info *cmd_info)
{
        FILE            *f;
        ARFILE          *fileptr;
        ARFILE          *abifile = NULL;
        ARFILE          *backptr = NULL;
        ARFILE          *endptr;
        ARFILE          *moved_files;
        ARFILE          *prev_entry, *new_listhead, *new_listend;
        int             deleted;
        struct stat     stbuf;
        char            *gfile;

        new_listhead  = NULL;
        new_listend   = NULL;
        prev_entry    = NULL;

        for (fileptr = getfile(cmd_info);
            fileptr; fileptr = getfile(cmd_info)) {
                deleted = 0;
                if (!abifile && cmd_info->ponam &&
                    strcmp(fileptr->ar_longname, cmd_info->ponam) == 0)
                        abifile = fileptr;
                else if (!abifile)
                        backptr = fileptr;

                if (cmd_info->namc == 0 ||
                    (gfile = match(fileptr->ar_longname, cmd_info)) != NULL) {
                        /*
                         * NOTE:
                         *      Refer to "Incompatible Archive Header"
                         *      blocked comment at the beginning of this file.
                         */
                        f = stats(gfile, &stbuf); /* gfile is set by match */
                        if (f == NULL) {
                                if (cmd_info->namc) {
                                        int err = errno;
                                        (void) fprintf(stderr,
                                            MSG_INTL(MSG_SYS_OPEN),
                                            gfile, strerror(err));
                                }
                                /*
                                 * Created
                                 */
                                mesg('c', gfile, cmd_info);
                        } else {
                                if ((cmd_info->opt_flgs & u_FLAG) &&
                                    stbuf.st_mtime <= fileptr->ar_date) {
                                        (void) fclose(f);
                                        continue;
                                }
                                /*
                                 * Replaced
                                 */
                                mesg('r', fileptr->ar_longname, cmd_info);
                                movefil(fileptr, &stbuf);
                                /*
                                 * Clear the previous contents.
                                 */
                                if (fileptr->ar_flag & F_ELFRAW) {
                                        /*
                                         * clear ar_elf
                                         */
                                        (void) elf_end(fileptr->ar_elf);
                                        fileptr->ar_elf = 0;
                                }
                                /* clear 'ar_flag' */
                                fileptr->ar_flag &= ~F_ELFRAW;

                                /*
                                 * Defer reading contents until needed, and
                                 * then use an in-kernel file-to-file transfer
                                 * to avoid excessive in-process memory use.
                                 */
                                fileptr->ar_contents = NULL;

                                if (fileptr->ar_pathname != NULL)
                                        free(fileptr->ar_pathname);
                                if ((fileptr->ar_pathname =
                                    malloc(strlen(gfile) + 1)) == NULL) {
                                        int err = errno;
                                        (void) fprintf(stderr,
                                            MSG_INTL(MSG_MALLOC),
                                            strerror(err));
                                        exit(1);
                                }

                                (void) strcpy(fileptr->ar_pathname, gfile);
                                (void) fclose(f);

                                if (cmd_info->ponam && (abifile != fileptr)) {
                                        deleted = 1;
                                        /* remove from archive list */
                                        if (prev_entry != NULL)
                                                prev_entry->ar_next = NULL;
                                        else
                                                listhead = NULL;
                                        listend = prev_entry;

                                        /* add to moved list */
                                        if (new_listhead == NULL)
                                                new_listhead = fileptr;
                                        else
                                                new_listend->ar_next = fileptr;
                                        new_listend = fileptr;
                                }
                                cmd_info->modified++;
                        }
                }
                else
                        /*
                         * Unchaged
                         */
                        mesg('u', fileptr->ar_longname, cmd_info);

                if (deleted)
                        deleted = 0;
                else
                        prev_entry = fileptr;
        }

        endptr = listend;
        cleanup(cmd_info);
        if (cmd_info->ponam && endptr &&
            (((moved_files = endptr->ar_next) != NULL) || new_listhead)) {
                if (!abifile) {
                        (void) fprintf(stderr, MSG_INTL(MSG_NOT_FOUND_POSNAM),
                            cmd_info->ponam);
                        exit(2);
                }
                endptr->ar_next = NULL;

                /*
                 * link new/moved files into archive entry list...
                 * 1: prepend newlist to moved/appended list
                 */
                if (new_listhead) {
                        if (!moved_files)
                                listend = new_listend;
                        new_listend->ar_next = moved_files;
                        moved_files = new_listhead;
                }
                /* 2: insert at appropriate position... */
                if (cmd_info->opt_flgs & b_FLAG)
                        abifile = backptr;
                if (abifile) {
                        listend->ar_next = abifile->ar_next;
                        abifile->ar_next = moved_files;
                } else {
                        listend->ar_next = listhead;
                        listhead = moved_files;
                }
                listend = endptr;
        } else if (cmd_info->ponam && !abifile)
                (void) fprintf(stderr, MSG_INTL(MSG_NOT_FOUND_POSNAM),
                    cmd_info->ponam);
}

void
dcmd(Cmd_info *cmd_info)
{
        ARFILE  *fptr;
        ARFILE *backptr = NULL;

        for (fptr = getfile(cmd_info); fptr; fptr = getfile(cmd_info)) {
                if (match(fptr->ar_longname, cmd_info) != NULL) {
                        /*
                         * NOTE:
                         *      Refer to "Incompatible Archive Header"
                         *      blocked comment at the beginning of this file.
                         */

                        /*
                         * Deleted
                         */
                        mesg('d', fptr->ar_longname, cmd_info);
                        if (backptr == NULL) {
                                listhead = NULL;
                                listend = NULL;
                        } else {
                                backptr->ar_next = NULL;
                                listend = backptr;
                        }
                        cmd_info->modified = 1;
                } else {
                        /*
                         * Unchaged
                         */
                        mesg('u', fptr->ar_longname, cmd_info);
                        backptr = fptr;
                }
        }
}

void
xcmd(Cmd_info *cmd_info)
{
        int     f;
        ARFILE  *next;
        int     rawname = 0;
        long    f_len = 0;

        /*
         * If -T is specified, get the maximum file name length.
         */
        if (cmd_info->opt_flgs & T_FLAG) {
                f_len = pathconf(MSG_ORIG(MSG_STR_PERIOD), _PC_NAME_MAX);
                if (f_len == -1) {
                        int err = errno;
                        (void) fprintf(stderr, MSG_INTL(MSG_PATHCONF),
                            strerror(err));
                        exit(1);
                }
        }
        for (next = getfile(cmd_info); next; next = getfile(cmd_info)) {
                if ((next->ar_longname[0] == 0) && (next->ar_rawname[0] != 0))
                        rawname = 1;
                if (cmd_info->namc == 0 ||
                    match(next->ar_longname, cmd_info) != NULL ||
                    match(next->ar_rawname, cmd_info) != NULL) {
                        /*
                         * NOTE:
                         *      Refer to "Incompatible Archive Header"
                         *      blocked comment at the beginning of this file.
                         */
                        f = create_extract(next, rawname, f_len, cmd_info);
                        if (f >= 0) {
                                if (rawname) {
                                        /*
                                         * eXtracted
                                         */
                                        mesg('x', next->ar_rawname, cmd_info);
                                        if (write(f, next->ar_contents,
                                            (unsigned)next->ar_size) !=
                                            next->ar_size) {
                                                int err = errno;
                                                (void) fprintf(stderr,
                                                    MSG_INTL(MSG_SYS_WRITE),
                                                    next->ar_rawname,
                                                    strerror(err));
                                                exit(1);
                                        }
                                } else {
                                        /*
                                         * eXtracted
                                         */
                                        mesg('x', next->ar_longname, cmd_info);
                                        if (write(f, next->ar_contents,
                                            (unsigned)next->ar_size) !=
                                            next->ar_size) {
                                                int err = errno;
                                                (void) fprintf(stderr,
                                                    MSG_INTL(MSG_SYS_WRITE),
                                                    next->ar_longname,
                                                    strerror(err));
                                                exit(1);
                                        }
                                }
                                (void) close(f);
                        } else
                                exit(1);
                }
                rawname = 0;
        } /* for */
}

void
pcmd(Cmd_info *cmd_info)
{
        ARFILE  *next;

        for (next = getfile(cmd_info); next; next = getfile(cmd_info)) {
                if (cmd_info->namc == 0 ||
                    match(next->ar_longname, cmd_info) != NULL ||
                    match(next->ar_rawname, cmd_info) != NULL) {
                        /*
                         * NOTE:
                         *      Refer to "Incompatible Archive Header"
                         *      blocked comment at the beginning of this file.
                         */
                        if (cmd_info->opt_flgs & v_FLAG) {
                                (void) fprintf(stdout,
                                    MSG_ORIG(MSG_FMT_P_TITLE),
                                    next->ar_longname);
                                (void) fflush(stdout);
                        }
                        (void) fwrite(next->ar_contents, sizeof (char),
                            next->ar_size, stdout);
                }
        }
}

void
mcmd(Cmd_info *cmd_info)
{
        ARFILE  *fileptr;
        ARFILE  *abifile = NULL;
        ARFILE  *tmphead = NULL;
        ARFILE  *tmpend = NULL;
        ARFILE  *backptr1 = NULL;
        ARFILE  *backptr2 = NULL;

        for (fileptr = getfile(cmd_info);
            fileptr; fileptr = getfile(cmd_info)) {
                if (match(fileptr->ar_longname, cmd_info) != NULL) {
                        /*
                         * position Modified
                         */
                        mesg('m', fileptr->ar_longname, cmd_info);
                        if (tmphead)
                                tmpend->ar_next = fileptr;
                        else
                                tmphead = fileptr;
                        tmpend = fileptr;
                        if (backptr1) {
                                listend = backptr1;
                                listend->ar_next = NULL;
                        }
                        else
                                listhead = NULL;
                        continue;
                }
                /*
                 * position Unchaged
                 */
                mesg('u', fileptr->ar_longname, cmd_info);
                backptr1 = fileptr;
                if (cmd_info->ponam && !abifile) {
                        if (strcmp(fileptr->ar_longname, cmd_info->ponam) == 0)
                                abifile = fileptr;
                        else
                                backptr2 = fileptr;
                }
        }

        if (!tmphead)
                return;

        if (!cmd_info->ponam)
                listend->ar_next = tmphead;
        else {
                if (!abifile) {
                        (void) fprintf(stderr, MSG_INTL(MSG_NOT_FOUND_POSNAM),
                            cmd_info->ponam);
                        exit(2);
                }
                if (cmd_info->opt_flgs & b_FLAG)
                        abifile = backptr2;
                if (abifile) {
                        tmpend->ar_next = abifile->ar_next;
                        abifile->ar_next = tmphead;
                } else {
                        tmphead->ar_next = listhead;
                        listhead = tmphead;
                }
        }
        (cmd_info->modified)++;
}

void
tcmd(Cmd_info *cmd_info)
{
        ARFILE  *next;
        int     **mp;
        char   buf[DATESIZE];
        int m1[] = {1, S_IRUSR, 'r', '-'};
        int m2[] = {1, S_IWUSR, 'w', '-'};
        int m3[] = {2, S_ISUID, 's', S_IXUSR, 'x', '-'};
        int m4[] = {1, S_IRGRP, 'r', '-'};
        int m5[] = {1, S_IWGRP, 'w', '-'};
        int m6[] = {2, S_ISGID, 's', S_IXGRP, 'x', '-'};
        int m7[] = {1, S_IROTH, 'r', '-'};
        int m8[] = {1, S_IWOTH, 'w', '-'};
        int m9[] = {2, S_ISVTX, 't', S_IXOTH, 'x', '-'};
        int *m[10];

        m[0] = m1;
        m[1] = m2;
        m[2] = m3;
        m[3] = m4;
        m[4] = m5;
        m[5] = m6;
        m[6] = m7;
        m[7] = m8;
        m[8] = m9;
        m[9] = 0;

        for (next = getfile(cmd_info); next; next = getfile(cmd_info)) {
                if (cmd_info->namc == 0 ||
                    match(next->ar_longname, cmd_info) != NULL ||
                    match(next->ar_rawname, cmd_info) != NULL) {
                        /*
                         * NOTE:
                         *      Refer to "Incompatible Archive Header"
                         *      blocked comment at the beginning of this file.
                         */
                        if ((cmd_info->opt_flgs & (t_FLAG | v_FLAG)) ==
                            (t_FLAG | v_FLAG)) {
                                for (mp = &m[0]; mp < &m[9]; )
                                        ar_select(*mp++, next->ar_mode);

                                (void) fprintf(stdout, MSG_ORIG(MSG_FMT_T_IDSZ),
                                    next->ar_uid, next->ar_gid,
                                    EC_XWORD(next->ar_size));
                                if ((strftime(buf,
                                    DATESIZE, MSG_ORIG(MSG_FMT_T_DATE),
                                    localtime(&(next->ar_date)))) == 0) {
                                        (void) fprintf(stderr,
                                            MSG_INTL(MSG_LOCALTIME));
                                        exit(1);
                                }
                                (void) fprintf(stdout,
                                    MSG_ORIG(MSG_FMT_SPSTRSP), buf);
                        }
                        if (cmd_info->opt_flgs & t_FLAG) {
                                if ((next->ar_longname[0] == 0) &&
                                    (next->ar_rawname[0] != 0)) {
                                        (void) fprintf(stdout,
                                            MSG_ORIG(MSG_FMT_STRNL),
                                            trim(next->ar_rawname));
                                } else {
                                        (void) fprintf(stdout,
                                            MSG_ORIG(MSG_FMT_STRNL),
                                            trim(next->ar_longname));
                                }
                        }
                }
        }
}

void
qcmd(Cmd_info *cmd_info)
{
        ARFILE *fptr;

        if (cmd_info->opt_flgs & (a_FLAG | b_FLAG)) {
                (void) fprintf(stderr, MSG_INTL(MSG_USAGE_Q_BAD_ARG));
                exit(1);
        }
        for (fptr = getfile(cmd_info); fptr; fptr = getfile(cmd_info))
                ;
        cleanup(cmd_info);
}

/*
 * Supplementary functions
 */
static char *
match(char *file, Cmd_info *cmd_info)
{
        int i;

        for (i = 0; i < cmd_info->namc; i++) {
                if (cmd_info->namv[i] == 0)
                        continue;
                if (strcmp(trim(cmd_info->namv[i]), file) == 0) {
                        file = cmd_info->namv[i];
                        cmd_info->namv[i] = 0;
                        return (file);
                }
        }
        return (NULL);
}

/*
 * puts the file which was in the list in the linked list
 */
static void
cleanup(Cmd_info *cmd_info)
{
        int i;
        FILE    *f;
        ARFILE  *fileptr;
        struct stat stbuf;

        for (i = 0; i < cmd_info->namc; i++) {
                if (cmd_info->namv[i] == 0)
                        continue;
                /*
                 * Appended
                 */
                mesg('a', cmd_info->namv[i], cmd_info);
                f = stats(cmd_info->namv[i], &stbuf);
                if (f == NULL) {
                        int err = errno;
                        (void) fprintf(stderr, MSG_INTL(MSG_SYS_OPEN),
                            cmd_info->namv[i], strerror(err));
                } else {
                        fileptr = newfile();
                        /* if short name */
                        (void) strncpy(fileptr->ar_name,
                            trim(cmd_info->namv[i]), SNAME);

                        if ((fileptr->ar_longname =
                            malloc(strlen(trim(cmd_info->namv[i])) + 1)) ==
                            NULL) {
                                int err = errno;
                                (void) fprintf(stderr, MSG_INTL(MSG_MALLOC),
                                    strerror(err));
                                exit(1);
                        }

                        (void) strcpy(fileptr->ar_longname,
                            trim(cmd_info->namv[i]));

                        if ((fileptr->ar_pathname =
                            malloc(strlen(cmd_info->namv[i]) + 1)) == NULL) {
                                int err = errno;
                                (void) fprintf(stderr, MSG_INTL(MSG_MALLOC),
                                    strerror(err));
                                exit(1);
                        }

                        (void) strcpy(fileptr->ar_pathname, cmd_info->namv[i]);

                        movefil(fileptr, &stbuf);

                        /* clear 'ar_flag' */
                        fileptr->ar_flag &= ~F_ELFRAW;

                        /*
                         * Defer reading contents until needed, and then use
                         * an in-kernel file-to-file transfer to avoid
                         * excessive in-process memory use.
                         */
                        fileptr->ar_contents = NULL;

                        (void) fclose(f);
                        (cmd_info->modified)++;
                        cmd_info->namv[i] = 0;
                }
        }
}

/*
 * insert the file 'file' into the temporary file
 */
static void
movefil(ARFILE *fileptr, struct stat *stbuf)
{
        fileptr->ar_size = stbuf->st_size;
        fileptr->ar_date = stbuf->st_mtime;
        fileptr->ar_mode = stbuf->st_mode;

        /*
         * The format of an 'ar' file includes a 6 character
         * decimal string to contain the uid.
         *
         * If the uid or gid is too big to fit, then set it to
         * nobody (for want of a better value).  Clear the
         * setuid/setgid bits in the mode to avoid setuid nobody
         * or setgid nobody files unexpectedly coming into existence.
         */
        if ((fileptr->ar_uid = stbuf->st_uid) > 999999) {
                fileptr->ar_uid = UID_NOBODY;
                if (S_ISREG(fileptr->ar_mode))
                        fileptr->ar_mode &= ~S_ISUID;
        }
        if ((fileptr->ar_gid = stbuf->st_gid) > 999999) {
                fileptr->ar_gid = GID_NOBODY;
                if (S_ISREG(fileptr->ar_mode))
                        fileptr->ar_mode &= ~S_ISGID;
        }
}

static FILE *
stats(char *file, struct stat *stbuf)
{
        FILE *f;

        f = fopen(file, MSG_ORIG(MSG_STR_LCR));
        if (f == NULL)
                return (f);
        if (stat(file, stbuf) < 0) {
                (void) fclose(f);
                return (NULL);
        }
        return (f);
}

/*
 * Used by xcmd()
 */
int
create_extract(ARFILE *a, int rawname, int f_len, Cmd_info *cmd_info)
{

        int f;
        char *f_name;
        char *dup = NULL;
        if (rawname)
                f_name = a->ar_rawname;
        else
                f_name = a->ar_longname;

        /*
         * If -T is specified, check the file length.
         */
        if (cmd_info->opt_flgs & T_FLAG) {
                int len;
                len = strlen(f_name);
                if (f_len <= len) {
                        dup = malloc(f_len+1);
                        if (dup == NULL) {
                                int err = errno;
                                (void) fprintf(stderr, MSG_INTL(MSG_MALLOC),
                                    strerror(err));
                                exit(1);
                        }
                        (void) strncpy(dup, f_name, f_len);
                }
                f_name = dup;
        }

        /*
         * Bug 4052067 - If a file to be extracted has the same
         * filename as the archive, the archive gets overwritten
         * which can lead to a corrupted archive or worse, a ufs
         * deadlock because libelf has mmap'ed the archive!  We
         * can't rely on strcmp() to test for this case because
         * the archive could be prefixed with a partial or full
         * path (and we could be using the rawname from the archive)
         * This means we have to do the same thing we did for mv,
         * which is to explicitly check if the file we would extract
         * to is identical to the archive.  Because part of this
         * test is essentially what the -C flag does, I've merged
         * the code together.
         */
        if (access(f_name, F_OK) != -1) {
                struct stat s1, s2;

                /*
                 * If -C is specified, this is an error anyway
                 */
                if (cmd_info->opt_flgs & C_FLAG) {
                        (void) fprintf(stderr, MSG_INTL(MSG_OVERRIDE_WARN),
                            f_name);
                        if (dup != NULL)
                                free(dup);
                        return (-1);
                }

                /*
                 * Okay, -C wasn't specified.  However, now we do
                 * the check to see if the archive would be overwritten
                 * by extracting this file.  stat() both objects and
                 * test to see if their identical.
                 */
                if ((stat(f_name, &s1) == 0) &&
                    (stat(cmd_info->arnam, &s2) == 0)) {

                        if ((s1.st_dev == s2.st_dev) &&
                            (s1.st_ino == s2.st_ino)) {

                                (void) fprintf(stderr,
                                    MSG_INTL(MSG_OVERRIDE_WARN), f_name);
                                if (dup != NULL)
                                        free(dup);
                                return (-1);
                        }
                }
        }

        /*
         * Okay to create extraction file...
         */
        f = creat(f_name, (mode_t)a->ar_mode & 0777);
        if (f < 0) {
                int err = errno;
                (void) fprintf(stderr, MSG_INTL(MSG_SYS_OPEN), f_name,
                    strerror(err));
                /*
                 * Created
                 */
                mesg('c', f_name, cmd_info);
        }
        if (dup)
                free(dup);
        return (f);
}

static void
mesg(int c, char *file, Cmd_info *cmd_info)
{
#ifdef XPG4
        /*
         * XPG4 does not have any message defined for
         * 'c' operation.
         * In fact, XPG only defines messages for
         *      d, r, a and x at the present. (03/05/'96)
         */
        if (c == 'c' || c == 'u' || c == 'm')
                return;
#endif
        /*
         * If 'u' is passed, convert it to 'c'.
         * 'u' makes more sense since the operation did not
         * do anything, Unchanged, but 'c' has been used so
         * I do no want to break the compatibility at this moment.
         * (03/05/'96).
         */
        if (c == 'u')
                c = 'c';
        if (cmd_info->opt_flgs & v_FLAG)
                if (c != 'c')
                        (void) fprintf(stdout, MSG_ORIG(MSG_FMT_FILE), c, file);
}

static void
ar_select(int *pairp, unsigned long mode)
{
        int n, *ap;

        ap = pairp;
        n = *ap++;
        while (--n >= 0 && (mode & *ap++) == 0)
                ap++;
        (void) putchar(*ap);
}