root/usr/src/cmd/sgs/ar/common/file.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 2022 Oxide Computer Company
 */

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

#include <sys/sendfile.h>
#include "inc.h"
#include "gelf.h"

/*
 * List of archive members, accessed globally by cmd and file.
 */
ARFILE  *listhead, *listend;

/*
 * Type used to manage string tables. Archives can have two of these:
 *
 * sym_strtbl: String table included at the end of the symbol table
 *      archive member, following the offset array.
 *
 * long_strtbl: String table used to hold member names that exceed 15
 *      characters in length, found in the long names archive member.
 */
typedef struct {
        char    *base;          /* Base of string table memory */
        size_t  used;           /* # bytes used from allocation */
        size_t  size;           /* Size of allocation */
} ARSTRTBL;

static ARSTRTBL sym_strtbl;
static ARSTRTBL long_strtbl;


/*
 * Name and file descriptor used when creating a new archive.
 * If this variable references an open file when exit_cleanup()
 * executes, it will close and remove the file, preventing incomplete
 * temporary files from being left behind in the case of a failure
 * or interruption.
 */
static struct {
        int             fd;     /* -1, or open file descriptor */
        const char      *path;  /* Path to open file */
} ar_outfile;

/*
 * The ar file format requires objects to be padded to an even size.
 * We do that, but it turns out to be beneficial to go farther.
 *
 * ld(1) accesses archives by mmapping them into memory. If the mapped
 * objects (member data) have the proper alignment, we can access them
 * directly. If the data alignment is wrong, libelf "slides" them over the
 * archive header to correct the misalignment. This is expensive in time
 * (to copy memory) and space (it causes swap to be allocated by the system
 * to back the now-modified pages). Hence, we really want to ensure that
 * the alignment is right.
 *
 * We used to align 32-bit objects at 4-byte boundaries, and 64-bit objects
 * at 8-byte. More recently, an elf section type has appeared that has
 * 8-byte alignment requirements (SUNW_move) even in 32-bit objects. So,
 * the current strategy is to align all objects to 8-bytes.
 *
 * There are two important things to consider when setting this value:
 *      1) If a new elf section that ld(1) accesses in memory appears
 *         with a greater than 8-byte alignment requirement, this value
 *         will need to be raised. Or, alternatively, the entire approach may
 *         need reconsideration.
 *      2) The size of this padding must be smaller than the size of the
 *         smallest possible ELF section. Otherwise, the logic contained
 *         in recover_padding() can be tricked.
 */
#define PADSZ 8

/*
 * Forward Declarations
 */
static void             arwrite(const char *, int, const char *, size_t);
static size_t           mklong_tab();
static size_t           mksymtab(const char *, ARFILEP **, int *);
static const char       *make_tmpname(const char *);
static size_t           sizeof_symtbl(size_t, int, size_t);
static void             savelongname(ARFILE *);
static void             savename(char *);
static int              search_sym_tab(const char *, ARFILE *, Elf *,
                            Elf_Scn *, size_t *, ARFILEP **, size_t *);
static size_t           sizeofmembers(size_t);
static char             *sputl32(uint32_t, char *);
static char             *sputl64(uint64_t, char *);
static void             strtbl_pad(ARSTRTBL *, size_t, int);
static char             *trimslash(char *s);
static void             writesymtab(const char *, int fd, size_t, ARFILEP *,
                            size_t);


/*
 * Function to be called on exit to clean up incomplete new archive.
 */
static void
exit_cleanup(void)
{
        if (ar_outfile.fd != -1) {
                /* Both of these system calls are Async-Signal-Safe */
                (void)  close(ar_outfile.fd);
                (void) unlink(ar_outfile.path);
        }
}

/*
 * Open an existing archive.
 */
int
getaf(Cmd_info *cmd_info)
{
        Elf_Cmd cmd;
        int fd;
        char *arnam = cmd_info->arnam;

        if (elf_version(EV_CURRENT) == EV_NONE) {
                (void) fprintf(stderr, MSG_INTL(MSG_ELF_VERSION),
                    elf_errmsg(-1));
                exit(1);
        }

        if ((cmd_info->afd = fd = open(arnam, O_RDONLY)) == -1) {
                int err = errno;

                if (err == ENOENT) {
                        /* archive does not exist yet, may have to create one */
                        return (fd);
                } else {
                        /* problem other than "does not exist" */
                        (void) fprintf(stderr, MSG_INTL(MSG_SYS_OPEN),
                            arnam, strerror(err));
                        exit(1);
                }
        }

        cmd = ELF_C_READ;
        cmd_info->arf = elf_begin(fd, cmd, (Elf *)0);

        if (elf_kind(cmd_info->arf) != ELF_K_AR) {
                (void) fprintf(stderr, MSG_INTL(MSG_NOT_ARCHIVE), arnam);
                if (cmd_info->opt_flgs & (a_FLAG | b_FLAG))
                        (void) fprintf(stderr, MSG_INTL(MSG_USAGE_POSNAME),
                            cmd_info->ponam);
                exit(1);
        }
        return (fd);
}

/*
 * Given a value, and a pad alignment, return the number of bytes
 * required to pad the value to the next alignment boundary.
 */
static size_t
pad(size_t n, size_t align)
{
        size_t r;

        r = n % align;
        if (r)
                r = align - r;

        return (r);
}

/*
 * If the current archive item is an ELF object, then ar(1) may have added
 * newline padding at the end in order to bring the following object
 * into PADSZ alignment within the file. This padding cannot be
 * distinguished from data using the information kept in the member header.
 * This routine examines the objects, using knowledge of
 * ELF and how our tools lay out objects to determine whether padding was
 * added to an archive item. If so, it adjusts the st_size and
 * st_padding fields of the file argument to reflect it.
 */
static void
recover_padding(Elf *elf, ARFILE *file)
{
        size_t          extent;
        size_t          padding;
        size_t          shnum;
        GElf_Ehdr       ehdr;


        /* ar(1) only pads objects, so bail if not looking at one */
        if (gelf_getclass(elf) == ELFCLASSNONE)
                return;

        /*
         * libelf always puts the section header array at the end
         * of the object, and all of our compilers and other tools
         * use libelf or follow this convention. So, it is extremely
         * likely that the section header array is at the end of this
         * object: Find the address at the end of the array and compare
         * it to the archive ar_size. If they are within PADSZ bytes, then
         * we've found the end, and the difference is padding (We assume
         * that no ELF section can fit into PADSZ bytes).
         */
        if (elf_getshdrnum(elf, &shnum) == -1)
                return;

        extent = gelf_getehdr(elf, &ehdr)
            ? (ehdr.e_shoff + (shnum * ehdr.e_shentsize)) : 0;

        /*
         * If the extent exceeds the end of the archive member
         * (negative padding), then we don't know what is going on
         * and simply leave things alone.
         */
        if (extent > file->ar_size)
                return;

        padding = file->ar_size - extent;
        if (padding >= PADSZ) {
                /*
                 * The section header array is not at the end of the object.
                 * Traverse the section headers and look for the one with
                 * the highest used address. If this address is within
                 * PADSZ bytes of ar_size, then this is the end of the object.
                 */
                Elf_Scn *scn = NULL;

                do {
                        scn = elf_nextscn(elf, scn);
                        if (scn) {
                                GElf_Shdr shdr;

                                if (gelf_getshdr(scn, &shdr)) {
                                        size_t t;

                                        t = shdr.sh_offset + shdr.sh_size;
                                        if (t > extent)
                                                extent = t;
                                }
                        }
                } while (scn);

                if (extent > file->ar_size)
                        return;
                padding = file->ar_size - extent;
        }

        /*
         * Now, test the padding. We only act on padding in the range
         * (0 < pad < PADSZ) (ar(1) will never add more than this). A pad
         * of 0 requires no action, and any other size above (PADSZ-1) means
         * that we don't understand the layout of this object, and as such,
         * cannot do anything.
         *
         * If the padding is in range, and the raw data for the
         * object is available, then we perform one additional sanity
         * check before moving forward: ar(1) always pads with newline
         * characters. If anything else is seen, it is not padding so
         * leave it alone.
         */
        if (padding < PADSZ) {
                if (file->ar_contents) {
                        size_t cnt = padding;
                        char *p = file->ar_contents + extent;

                        while (cnt--) {
                                if (*p++ != '\n') {   /* No padding */
                                        padding = 0;
                                        break;
                                }
                        }
                }

                /* Remove the padding from the size */
                file->ar_size -= padding;
                file->ar_padding = padding;
        }
}

/*
 * Each call to getfile() returns the next unread archive member
 * from the archive opened by getaf(). Returns NULL if no more
 * archive members are left.
 */
ARFILE *
getfile(Cmd_info *cmd_info)
{
        Elf_Arhdr *mem_header = NULL;
        ARFILE  *file;
        char *tmp_rawname, *file_rawname;
        Elf *elf;
        char *arnam = cmd_info->arnam;
        int fd = cmd_info->afd;
        Elf *arf = cmd_info->arf;

        if (fd == -1)
                return (NULL); /* the archive doesn't exist */

        while (mem_header == NULL) {
                if ((elf = elf_begin(fd, ELF_C_READ, arf)) == 0)
                        return (NULL);  /* archive is empty or have hit end */

                if ((mem_header = elf_getarhdr(elf)) == NULL) {
                        (void) fprintf(stderr, MSG_INTL(MSG_ELF_MALARCHIVE),
                            arnam, EC_XWORD(elf_getbase(elf)), elf_errmsg(-1));
                        exit(1);
                }

                /* Ignore special members like the symbol and string tables */
                if (mem_header->ar_name[0] == '/') {
                        (void) elf_next(elf);
                        (void) elf_end(elf);
                        mem_header = NULL;
                }
        }

        /*
         * NOTE:
         *      The mem_header->ar_name[] is set to a NULL string
         *      if the archive member header has some error.
         *      (See elf_getarhdr() man page.)
         *      It is set to NULL for example, the ar command reads
         *      the archive files created by SunOS 4.1 system.
         *      See c block comment in cmd.c, "Incompatible Archive Header".
         */
        file = newfile();
        (void) strncpy(file->ar_name, mem_header->ar_name, SNAME);

        if ((file->ar_longname = malloc(strlen(mem_header->ar_name) + 1))
            == NULL) {
                int err = errno;
                (void) fprintf(stderr, MSG_INTL(MSG_MALLOC), strerror(err));
                exit(1);
        }
        (void) strcpy(file->ar_longname, mem_header->ar_name);
        if ((file->ar_rawname = malloc(strlen(mem_header->ar_rawname) + 1))
            == NULL) {
                int err = errno;
                (void) fprintf(stderr, MSG_INTL(MSG_MALLOC), strerror(err));
                exit(1);
        }
        tmp_rawname = mem_header->ar_rawname;
        file_rawname = file->ar_rawname;
        while (!isspace(*tmp_rawname) &&
            ((*file_rawname = *tmp_rawname) != '\0')) {
                file_rawname++;
                tmp_rawname++;
        }
        if (!(*tmp_rawname == '\0'))
                *file_rawname = '\0';

        file->ar_date = mem_header->ar_date;
        file->ar_uid  = mem_header->ar_uid;
        file->ar_gid  = mem_header->ar_gid;
        file->ar_mode = (unsigned long) mem_header->ar_mode;
        file->ar_size = mem_header->ar_size;

        /* reverse logic */
        if ((cmd_info->opt_flgs & (t_FLAG | s_FLAG)) != t_FLAG) {
                size_t ptr;
                file->ar_flag = F_ELFRAW;
                if ((file->ar_contents = elf_rawfile(elf, &ptr))
                    == NULL) {
                        if (ptr != 0) {
                                (void) fprintf(stderr,
                                    MSG_INTL(MSG_ELF_RAWFILE), elf_errmsg(-1));
                                exit(1);
                        }
                }
                file->ar_elf = elf;
        }

        recover_padding(elf, file);

        (void) elf_next(elf);
        return (file);
}

/*
 * Allocate a new archive member descriptor and add it to the list.
 */
ARFILE *
newfile(void)
{
        static ARFILE   *buffer =  NULL;
        static size_t   count = 0;
        ARFILE          *fileptr;

        if (count == 0) {
                if ((buffer = (ARFILE *) calloc(CHUNK, sizeof (ARFILE)))
                    == NULL) {
                        int err = errno;
                        (void) fprintf(stderr, MSG_INTL(MSG_MALLOC),
                            strerror(err));
                        exit(1);
                }
                count = CHUNK;
        }
        count--;
        fileptr = buffer++;

        if (listhead)
                listend->ar_next = fileptr;
        else
                listhead = fileptr;
        listend = fileptr;
        return (fileptr);
}

static char *
trimslash(char *s)
{
        static char buf[SNAME];

        (void) strncpy(buf, trim(s), SNAME - 2);
        buf[SNAME - 2] = '\0';
        return (strcat(buf, MSG_ORIG(MSG_STR_SLASH)));
}

char *
trim(char *s)
{
        char *p1, *p2;

        for (p1 = s; *p1; p1++)
                ;
        while (p1 > s) {
                if (*--p1 != '/')
                        break;
                *p1 = 0;
        }
        p2 = s;
        for (p1 = s; *p1; p1++)
                if (*p1 == '/')
                        p2 = p1 + 1;
        return (p2);
}


/*
 * Find all the global symbols exported by ELF archive members, and
 * build a list associating each one with the archive member that
 * provides it.
 *
 * exit:
 *      *symlist is set to the list of symbols. If any ELF object was
 *      found, *found_obj is set to TRUE (1). Returns the number of symbols
 *      located.
 */
static size_t
mksymtab(const char *arname, ARFILEP **symlist, int *found_obj)
{
        ARFILE          *fptr;
        size_t          mem_offset = 0;
        Elf             *elf;
        Elf_Scn         *scn;
        GElf_Ehdr       ehdr;
        int             newfd;
        size_t          nsyms = 0;
        int             class = 0;
        Elf_Data        *data;
        size_t          num_errs = 0;

        newfd = 0;
        for (fptr = listhead; fptr; fptr = fptr->ar_next) {
                /* determine if file is coming from the archive or not */
                if ((fptr->ar_elf != NULL) && (fptr->ar_pathname == NULL)) {
                        /*
                         * I can use the saved elf descriptor.
                         */
                        elf = fptr->ar_elf;
                } else if ((fptr->ar_elf == NULL) &&
                    (fptr->ar_pathname != NULL)) {
#ifdef _LP64
                        /*
                         * The archive member header ar_size field is 10
                         * decimal digits, sufficient to represent a 32-bit
                         * value, but not a 64-bit one. Hence, we reject
                         * attempts to insert a member larger than 4GB.
                         *
                         * One obvious way to extend the format without altering
                         * the ar_hdr struct is to use the same mechanism used
                         * for ar_name: Put the size string into the long name
                         * string table and write a string /xxx into ar_size,
                         * where xxx is the string table offset.
                         *
                         * At the time of this writing (June 2010), the largest
                         * relocatable objects are measured in 10s or 100s
                         * of megabytes, so we still have many years to go
                         * before this becomes limiting. By that time, it may
                         * turn out that a completely new archive format is
                         * a better solution, as the current format has many
                         * warts and inefficiencies. In the meantime, we
                         * won't burden the current implementation with support
                         * for a bandaid feature that will have little use.
                         */
                        if (fptr->ar_size > 0xffffffff) {
                                (void) fprintf(stderr,
                                    MSG_INTL(MSG_ERR_MEMBER4G),
                                    fptr->ar_pathname);
                                num_errs++;
                                continue;
                        }
#endif
                        if ((newfd  =
                            open(fptr->ar_pathname, O_RDONLY)) == -1) {
                                int err = errno;
                                (void) fprintf(stderr, MSG_INTL(MSG_SYS_OPEN),
                                    fptr->ar_pathname, strerror(err));
                                num_errs++;
                                continue;
                        }

                        if ((elf = elf_begin(newfd,
                            ELF_C_READ, (Elf *)0)) == 0) {
                                (void) fprintf(stderr,
                                    MSG_INTL(MSG_ELF_BEGIN_FILE),
                                    fptr->ar_pathname, elf_errmsg(-1));
                                (void) close(newfd);
                                newfd = 0;
                                num_errs++;
                                continue;
                        }
                        if (elf_kind(elf) == ELF_K_AR) {
                                if (newfd) {
                                        (void) close(newfd);
                                        newfd = 0;
                                }
                                (void) elf_end(elf);
                                continue;
                        }
                } else {
                        (void) fprintf(stderr, MSG_INTL(MSG_INTERNAL_01));
                        exit(1);
                }
                if (gelf_getehdr(elf, &ehdr) != 0) {
                        size_t shstrndx = 0;
                        if ((class = gelf_getclass(elf)) == ELFCLASS64) {
                                fptr->ar_flag |= F_CLASS64;
                        } else if (class == ELFCLASS32)
                                fptr->ar_flag |= F_CLASS32;

                        if (elf_getshdrstrndx(elf, &shstrndx) == -1) {
                                if (fptr->ar_pathname != NULL) {
                                        (void) fprintf(stderr,
                                            MSG_INTL(MSG_ELF_GETSHSTRNDX_FILE),
                                            fptr->ar_pathname, elf_errmsg(-1));
                                } else {
                                        (void) fprintf(stderr,
                                            MSG_INTL(MSG_ELF_GETSHSTRNDX_AR),
                                            arname, fptr->ar_longname,
                                            elf_errmsg(-1));
                                }
                                num_errs++;
                                if (newfd) {
                                        (void) close(newfd);
                                        newfd = 0;
                                }
                                (void) elf_end(elf);
                                continue;
                        }

                        scn = elf_getscn(elf, shstrndx);
                        if (scn == NULL) {
                                if (fptr->ar_pathname != NULL)
                                        (void) fprintf(stderr,
                                            MSG_INTL(MSG_ELF_GETSCN_FILE),
                                            fptr->ar_pathname, elf_errmsg(-1));
                                else
                                        (void) fprintf(stderr,
                                            MSG_INTL(MSG_ELF_GETSCN_AR),
                                            arname, fptr->ar_longname,
                                            elf_errmsg(-1));
                                num_errs++;
                                if (newfd) {
                                        (void) close(newfd);
                                        newfd = 0;
                                }
                                (void) elf_end(elf);
                                continue;
                        }

                        data = 0;
                        data = elf_getdata(scn, data);
                        if (data == NULL) {
                                if (fptr->ar_pathname != NULL)
                                        (void) fprintf(stderr,
                                            MSG_INTL(MSG_ELF_GETDATA_FILE),
                                            fptr->ar_pathname, elf_errmsg(-1));
                                else
                                        (void) fprintf(stderr,
                                            MSG_INTL(MSG_ELF_GETDATA_AR),
                                            arname, fptr->ar_longname,
                                            elf_errmsg(-1));
                                num_errs++;
                                if (newfd) {
                                        (void) close(newfd);
                                        newfd = 0;
                                }
                                (void) elf_end(elf);
                                continue;
                        }
                        if (data->d_size == 0) {
                                if (fptr->ar_pathname != NULL)
                                        (void) fprintf(stderr,
                                            MSG_INTL(MSG_W_ELF_NODATA_FILE),
                                            fptr->ar_pathname);
                                else
                                        (void) fprintf(stderr,
                                            MSG_INTL(MSG_W_ELF_NODATA_AR),
                                            arname, fptr->ar_longname);
                                if (newfd) {
                                        (void) close(newfd);
                                        newfd = 0;
                                }
                                (void) elf_end(elf);
                                num_errs++;
                                continue;
                        }

                        /* loop through sections to find symbol table */
                        scn = 0;
                        while ((scn = elf_nextscn(elf, scn)) != 0) {
                                GElf_Shdr shdr;
                                if (gelf_getshdr(scn, &shdr) == NULL) {
                                        /* BEGIN CSTYLED */
                                        if (fptr->ar_pathname != NULL)
                                            (void) fprintf(stderr,
                                                MSG_INTL(MSG_ELF_GETDATA_FILE),
                                                fptr->ar_pathname,
                                                elf_errmsg(-1));
                                        else
                                            (void) fprintf(stderr,
                                                MSG_INTL(MSG_ELF_GETDATA_AR),
                                                arname, fptr->ar_longname,
                                                elf_errmsg(-1));
                                        /* END CSTYLED */
                                        if (newfd) {
                                                (void) close(newfd);
                                                newfd = 0;
                                        }
                                        num_errs++;
                                        (void) elf_end(elf);
                                        continue;
                                }
                                *found_obj = 1;
                                if (shdr.sh_type == SHT_SYMTAB) {
                                        if (search_sym_tab(arname, fptr, elf,
                                            scn, &nsyms, symlist,
                                            &num_errs) == -1) {
                                                if (newfd) {
                                                        (void) close(newfd);
                                                        newfd = 0;
                                                }
                                                continue;
                                        }
                                }
                        }
                }
                mem_offset += sizeof (struct ar_hdr) + fptr->ar_size;
                if (fptr->ar_size & 01)
                        mem_offset++;
                (void) elf_end(elf);
                if (newfd) {
                        (void) close(newfd);
                        newfd = 0;
                }
        }
        if (num_errs)
                exit(1);

        if (found_obj) {
                if (nsyms == 0) {
                        /*
                         * It is possible, though rare, to have ELF objects
                         * that do not export any global symbols. Presumably
                         * such objects operate via their .init/.fini
                         * sections. In this case, we produce an empty
                         * symbol table, so that applications that rely
                         * on a successful call to elf_getarsym() to determine
                         * if ELF objects are present will succeed. To do this,
                         * we require a small empty symbol string table.
                         */
                        strtbl_pad(&sym_strtbl, 4, '\0');
                } else {
                        /*
                         * Historical behavior is to pad string tables
                         * to a multiple of 4.
                         */
                        strtbl_pad(&sym_strtbl, pad(sym_strtbl.used, 4), '\0');
                }

        }

        return (nsyms);
}

/*
 * Output a member header.
 */
/*ARGSUSED*/
static void
write_member_header(const char *filename, int fd, int is_elf,
    const char *name, time_t timestamp, uid_t uid, gid_t gid, mode_t mode,
    size_t size)
{
        char    buf[sizeof (struct ar_hdr) + 1];
        int     len;

        len = snprintf(buf, sizeof (buf), MSG_ORIG(MSG_MH_FORMAT), name,
            EC_WORD(timestamp), EC_WORD(uid), EC_WORD(gid), EC_WORD(mode),
            EC_XWORD(size), ARFMAG);

        /*
         * If snprintf() reports that it needed more space than we gave
         * it, it means that the caller fed us a long name, which is a
         * fatal internal error.
         */
        if (len != sizeof (struct ar_hdr)) {
                (void) fprintf(stderr, MSG_INTL(MSG_INTERNAL_02));
                exit(1);
        }

        arwrite(filename, fd, buf, len);

        /*
         * We inject inter-member padding to ensure that ELF object
         * member data is aligned on PADSZ. If this is a debug build,
         * verify that the computations were right.
         */
        assert(!is_elf || (pad(lseek(fd, 0, SEEK_CUR), PADSZ) == 0));
}

/*
 * Write the archive symbol table member to the output archive file.
 *
 * note:
 *      sizeofmembers() must have been called to establish member offset
 *      and padding values before writesymtab() is used.
 */
static void
writesymtab(const char *filename, int fd, size_t nsyms, ARFILEP *symlist,
    size_t eltsize)
{
        size_t  i, j;
        ARFILEP *ptr;
        size_t  tblsize;
        char    *buf, *dst;
        int     is64 = (eltsize == 8);

        /*
         * We require a buffer large enough to hold a symbol table count,
         * plus one offset for each symbol.
         */
        tblsize = (nsyms + 1) * eltsize;
        if ((buf = dst = malloc(tblsize)) == NULL) {
                int err = errno;
                (void) fprintf(stderr, MSG_INTL(MSG_MALLOC), strerror(err));
                exit(1);
        }

        write_member_header(filename, fd, 0,
            (is64 ? MSG_ORIG(MSG_STR_SYM64) : MSG_ORIG(MSG_STR_SLASH)),
            time(0), 0, 0, 0, tblsize + sym_strtbl.used);

        dst = is64 ? sputl64(nsyms, dst) : sputl32(nsyms, dst);

        for (i = 0, j = SYMCHUNK, ptr = symlist; i < nsyms; i++, j--, ptr++) {
                if (!j) {
                        j = SYMCHUNK;
                        ptr = (ARFILEP *)*ptr;
                }
                dst = is64 ? sputl64((*ptr)->ar_offset, dst) :
                    sputl32((*ptr)->ar_offset, dst);
        }
        arwrite(filename, fd, buf, tblsize);
        free(buf);
        arwrite(filename, fd, sym_strtbl.base, sym_strtbl.used);
}

/*
 * Grow the size of the given string table so that there is room
 * for at least need bytes.
 *
 * entry:
 *      strtbl - String table to grow
 *      need - Amount of space required by caller
 */
static void
strtbl_alloc(ARSTRTBL *strtbl, size_t need)
{
#define STRTBL_INITSZ   8196

        /*
         * On 32-bit systems, we require a larger integer type in order
         * to avoid overflow and wraparound when doing our computations.
         */
        uint64_t        need64 = need;
        uint64_t        used64 = strtbl->used;
        uint64_t        size64 = strtbl->size;
        uint64_t        target = need64 + used64;

        int             sys32, tbl32;

        if (target <= size64)
                return;

        /*
         * Detect 32-bit system. We might usually do this with the preprocessor,
         * but it can serve as a predicate in tests that also apply to 64-bit
         * systems.
         */
        sys32 = (sizeof (size_t) == 4);

        /*
         * The symbol string table can be larger than 32-bits on a 64-bit
         * system. However, the long name table must stay below that limit.
         * The reason for this is that there is not enough room in the ar_name
         * field of the member header to represent 64-bit offsets.
         */
        tbl32 = (strtbl == &long_strtbl);

        /*
         * If request is larger than 4GB and we can't do it because we
         * are a 32-bit program, or because the table is format limited,
         * we can go no further.
         */
        if ((target > 0xffffffff) && (sys32 || tbl32))
                goto limit_fail;

        /* Default starting size */
        if (strtbl->base == NULL)
                size64 = STRTBL_INITSZ;

        /*
         * Our strategy is to double the size until we find a size that
         * exceeds the request. However, if this table cannot exceed 4GB,
         * then once we exceed 2GB, we switch to a strategy of taking the
         * current request and rounding it up to STRTBL_INITSZ.
         */
        while (target > size64) {
                if ((target > 0x7fffffff) && (sys32 || tbl32)) {
                        size64 = ((target + STRTBL_INITSZ) / STRTBL_INITSZ) *
                            STRTBL_INITSZ;

                        /*
                         * If we are so close to the line that this small
                         * increment exceeds 4GB, give it up.
                         */
                        if ((size64 > 0xffffffff) && (sys32 || tbl32))
                                goto limit_fail;

                        break;
                }

                size64 *= 2;
        }

        strtbl->base = realloc(strtbl->base, size64);
        if (strtbl->base == NULL) {
                int err = errno;
                (void) fprintf(stderr, MSG_INTL(MSG_MALLOC), strerror(err));
                exit(1);
        }
        strtbl->size = (size_t)size64;
        return;

limit_fail:
        /*
         * Control comes here if we are unable to allocate more than 4GB of
         * memory for the string table due to one of the following reasons:
         *
         * - A 32-bit process is attempting to be larger than 4GB
         *
         * - A 64-bit process is attempting to grow the long names string
         *      table beyond the ar format limit of 32-bits.
         */
        if (sys32)
                (void) fprintf(stderr, MSG_INTL(MSG_MALLOC), strerror(ENOMEM));
        else
                (void) fprintf(stderr, MSG_INTL(MSG_ERR_LONGSTRTBLSZ));
        exit(1);

#undef STRTBL_INITSZ
}

/*
 * Add the specified number of pad characters to the end of the
 * given string table.
 *
 * entry:
 *      strtbl - String table to pad
 *      n - # of pad characters to add
 *      ch - Pad character to use
 */
static void
strtbl_pad(ARSTRTBL *strtbl, size_t n, int ch)
{
        if (n == 0)
                return;

        if ((n + strtbl->used) > strtbl->size)
                strtbl_alloc(strtbl, n);

        while (n--)
                strtbl->base[strtbl->used++] = ch;
}

/*
 * Enter a symbol name into the symbol string table.
 */
static void
savename(char *symbol)
{
        size_t need;

        need = strlen(symbol) + 1;
        if ((need + sym_strtbl.used) > sym_strtbl.size)
                strtbl_alloc(&sym_strtbl, need);

        (void) strcpy(sym_strtbl.base + sym_strtbl.used, symbol);
        sym_strtbl.used += need;
}

/*
 * Prepare an archive member with a long (>15 characters) name for
 * the output archive.
 *
 * entry:
 *      fptr - pointer to archive member with long name
 *
 * exit:
 *      The long name is entered into the long name string table,
 *      and fptr->ar_name has been replaced with the special /xxx
 *      name used to indicate that the real name is in the string table
 *      at offset xxx.
 */
static void
savelongname(ARFILE *fptr)
{
        size_t  len, need;
        char    *p;

        /* Size of new item to add */
        len = strlen(fptr->ar_longname);
        need = len + 2;

        /* Ensure there's room */
        if ((need + long_strtbl.used) > long_strtbl.size)
                strtbl_alloc(&long_strtbl, need);

        /*
         * Generate the index string to be written into the member header
         *
         * This will not overflow the ar_name field because that field is
         * 16 characters in size, and a 32-bit unsigned value can be formatted
         * in 10 characters. Allowing a character for the leading '/', and one
         * for the NULL termination, that leaves us with 4 extra spaces.
         */
        (void) snprintf(fptr->ar_name, sizeof (fptr->ar_name),
            MSG_ORIG(MSG_FMT_LLINT), EC_XWORD(long_strtbl.used));

        /*
         * Enter long name into reserved spot, terminated with a slash
         * and a newline character.
         */
        p = long_strtbl.base + long_strtbl.used;
        long_strtbl.used += need;
        (void) strcpy(p, fptr->ar_longname);
        p += len;
        *p++ = '/';
        *p++ = '\n';
}

/*
 * Determine if the archive we're about to write will exceed the
 * 32-bit limit of 4GB.
 *
 * entry:
 *      mksymtab() and mklong_tab() have been called to set up
 *      the string tables.
 *
 * exit:
 *      Returns TRUE (1) if the 64-bit symbol table is needed, and
 *      FALSE (0) otherwise.
 *
 */
static int
require64(size_t nsyms, int found_obj, size_t longnames)
{
        ARFILE          *fptr;
        uint64_t        size;

        /*
         * If there are more than 4GB symbols, we have to use
         * the 64-bit form. Note that longnames cannot exceed 4GB
         * because that symbol table is limited to a length of 4GB by
         * the archive format.
         */
        if (nsyms > 0xffffffff)
                return (1);

        /*
         * Make a worst case estimate for the size of the resulting
         * archive by assuming full padding between members.
         */
        size = SARMAG;
        if (longnames)
                size += sizeof (struct ar_hdr) + long_strtbl.used + PADSZ;

        if (found_obj)
                size += sizeof_symtbl(nsyms, found_obj, 4) + PADSZ;

        if (size > 0xffffffff)
                return (1);

        for (fptr = listhead; fptr; fptr = fptr->ar_next) {
                size += sizeof (struct ar_hdr) + fptr->ar_size + PADSZ;

                if (size > 0xffffffff)
                        return (1);
        }

        /* 32-bit symbol table will suffice */
        return (0);
}

void
writefile(Cmd_info *cmd_info)
{
        ARFILE          *fptr;
        ARFILEP         *symlist = 0;
        size_t          longnames;
        size_t          nsyms;
        int             new_archive = 0;
        char            *name = cmd_info->arnam;
        size_t          arsize; /* Size of magic # and special members */
        size_t          symtbl_eltsize = 4;
        int             found_obj = 0;
        int             fd;
        off_t           off;
        struct stat     stbuf, ar_stbuf;
        char            pad_bytes[PADSZ];
        size_t          pad_cnt;
        int             is_elf;

        /*
         * Gather the list of symbols and associate each one to the
         * ARFILE descriptor of the object it belongs to. At the same
         * time, tag each ELF object with the appropriate F_CLASSxx
         * flag.
         */
        nsyms = mksymtab(name, &symlist, &found_obj);

        /* Generate the string table for long member names */
        longnames = mklong_tab();

        /*
         * Will this archive exceed 4GB? If we're a 32-bit process, we can't
         * do it. If we're a 64-bit process, then we'll have to use a
         * 64-bit symbol table.
         */
        if (require64(nsyms, found_obj, longnames)) {
#ifdef _LP64
                symtbl_eltsize = 8;
#else
                (void) fprintf(stderr, MSG_INTL(MSG_TOOBIG4G));
                exit(1);
#endif
        }

        /*
         * If the user requested it, use the 64-bit symbol table even if
         * a 32-bit one would suffice. 32-bit tables are more portable and
         * take up less room, so this feature is primarily for testing.
         */
        if (cmd_info->opt_flgs & S_FLAG)
                symtbl_eltsize = 8;

        /*
         * If the first non-special archive member is an ELF object, then we
         * need to arrange for its data to have an alignment of PADSZ. The
         * preceeding special member will be the symbol table, or the long
         * name string table. We pad the string table that precedes the
         * ELF member in order to achive the desired alignment.
         */
        is_elf = listhead && (listhead->ar_flag & (F_CLASS32 | F_CLASS64));
        arsize = SARMAG;
        if (found_obj) {
                arsize += sizeof_symtbl(nsyms, found_obj, symtbl_eltsize);
                if (is_elf && (longnames == 0)) {
                        pad_cnt = pad(arsize + sizeof (struct ar_hdr), PADSZ);
                        strtbl_pad(&sym_strtbl, pad_cnt, '\0');
                        arsize += pad_cnt;
                }
        }
        if (longnames > 0) {
                arsize += sizeof (struct ar_hdr) + long_strtbl.used;
                if (is_elf) {
                        pad_cnt = pad(arsize + sizeof (struct ar_hdr), PADSZ);
                        strtbl_pad(&long_strtbl, pad_cnt, '\0');
                        arsize += pad_cnt;
                }
        }

        /*
         * For each user visible (non-special) archive member, determine
         * the header offset, and the size of any required padding.
         */
        (void) sizeofmembers(arsize);

        /*
         * Is this a new archive, or are we updating an existing one?
         *
         * A subtlety here is that POSIX says we are not supposed
         * to replace a non-writable file. The only 100% reliable test
         * against this is to open the file for non-destructive
         * write access. If the open succeeds, we are clear to
         * replace it, and if not, then the error generated is
         * the error we need to report.
         */
        if ((fd = open(name, O_RDWR)) < 0) {
                int     err = errno;

                if (err != ENOENT) {
                        (void) fprintf(stderr, MSG_INTL(MSG_SYS_OPEN),
                            name, strerror(err));
                        exit(1);
                }
                new_archive = 1;
                if ((cmd_info->opt_flgs & c_FLAG) == 0) {
                        (void) fprintf(stderr, MSG_INTL(MSG_BER_MES_CREATE),
                            cmd_info->arnam);
                }
        } else {
                /* Capture mode and owner information to apply to replacement */
                if (fstat(fd, &ar_stbuf) < 0) {
                        int err = errno;
                        (void) fprintf(stderr, MSG_INTL(MSG_SYS_STAT),
                            name, strerror(err));
                        (void) close(fd);
                        exit(1);
                }
                (void) close(fd);
                new_archive = 0;
        }


        /*
         * Register exit handler function to clean up after us if we exit
         * before completing the new archive. atexit() is defined as
         * only being able to fail due to memory exhaustion.
         */
        if (atexit(exit_cleanup) != 0) {
                (void) fprintf(stderr, MSG_INTL(MSG_MALLOC), strerror(ENOMEM));
                exit(1);
        }

        /*
         * If a new archive, create it in place. If updating an archive,
         * create the replacement under a temporary name and then rename it
         * into place.
         */
        ar_outfile.path = new_archive ? name : make_tmpname(name);
        ar_outfile.fd = open(ar_outfile.path, O_RDWR|O_CREAT|O_LARGEFILE, 0666);
        if (ar_outfile.fd == -1) {
                int err = errno;
                (void) fprintf(stderr, new_archive ?
                    MSG_INTL(MSG_BAD_CREATE) : MSG_INTL(MSG_SYS_OPEN),
                    ar_outfile.path, strerror(err));
                exit(1);
        }

        /* Output magic string */
        arwrite(name, ar_outfile.fd, ARMAG, SARMAG);

        /*
         * The symbol table member is always first if present. Note that
         * writesymtab() uses the member offsets computed by sizeofmembers()
         * above.
         */
        if (found_obj)
                writesymtab(name, ar_outfile.fd, nsyms, symlist,
                    symtbl_eltsize);

        if (longnames) {
                write_member_header(name, ar_outfile.fd, 0,
                    MSG_ORIG(MSG_STR_DSLASH), time(0), 0, 0, 0,
                    long_strtbl.used);
                arwrite(name, ar_outfile.fd, long_strtbl.base,
                    long_strtbl.used);
        }

        /*
         * The accuracy of the symbol table depends on our having calculated
         * the size of the archive accurately to this point. If this is a
         * debug build, verify it.
         */
        assert(arsize == lseek(ar_outfile.fd, 0, SEEK_CUR));

#ifndef XPG4
        if (cmd_info->opt_flgs & v_FLAG) {
                (void) fprintf(stderr, MSG_INTL(MSG_BER_MES_WRITE),
                    cmd_info->arnam);
        }
#endif

        /*
         * Fill pad_bytes array with newline characters. This array
         * is used to supply padding bytes at the end of ELF objects.
         * There can never be more tha PADSZ such bytes, so this number
         * will always suffice.
         */
        for (pad_cnt = 0; pad_cnt < PADSZ; pad_cnt++)
                pad_bytes[pad_cnt] = '\n';

        for (fptr = listhead; fptr; fptr = fptr->ar_next) {
                /*
                 * We computed the expected offset for each ELF member and
                 * used those offsets to fill the symbol table. If this is
                 * a debug build, verify that the computed offset was right.
                 */
                is_elf = (fptr->ar_flag & (F_CLASS32 | F_CLASS64)) != 0;
                assert(!is_elf ||
                    (fptr->ar_offset == lseek(ar_outfile.fd, 0, SEEK_CUR)));

                /*
                 * NOTE:
                 * The mem_header->ar_name[] is set to a NULL string
                 * if the archive member header has some error.
                 * (See elf_getarhdr() man page.)
                 * It is set to NULL for example, the ar command reads
                 * the archive files created by SunOS 4.1 system.
                 * See c block comment in cmd.c, "Incompatible Archive Header".
                 */
                if (fptr->ar_name[0] == 0) {
                        fptr->ar_longname = fptr->ar_rawname;
                        (void) strncpy(fptr->ar_name, fptr->ar_rawname, SNAME);
                }
                write_member_header(name, ar_outfile.fd, is_elf,
                    (strlen(fptr->ar_longname) <= (unsigned)SNAME-2) ?
                    trimslash(fptr->ar_longname) : fptr->ar_name,
                    EC_WORD(fptr->ar_date), fptr->ar_uid, fptr->ar_gid,
                    fptr->ar_mode, fptr->ar_size + fptr->ar_padding);


                if ((fptr->ar_flag & F_ELFRAW) == 0) {
                        /*
                         * The file doesn't come from the archive, and is
                         * therefore not already in memory(fptr->ar_contents)
                         * so open it and do a direct file-to-file transfer of
                         * its contents. We use the sendfile() system call
                         * to make the kernel do the transfer, so we don't have
                         * to buffer data in process, and we trust that the
                         * kernel will use an optimal transfer strategy.
                         */
                        if ((fd = open(fptr->ar_pathname, O_RDONLY)) == -1) {
                                int err = errno;
                                (void) fprintf(stderr, MSG_INTL(MSG_SYS_OPEN),
                                    fptr->ar_longname, strerror(err));
                                exit(1);
                        }
                        if (stat(fptr->ar_pathname, &stbuf) < 0) {
                                int err = errno;
                                (void) fprintf(stderr, MSG_INTL(MSG_SYS_OPEN),
                                    fptr->ar_longname, strerror(err));
                                (void) close(fd);
                                exit(1);
                        }
                        off = 0;
                        if (sendfile(ar_outfile.fd, fd, &off,
                            stbuf.st_size) != stbuf.st_size) {
                                int err = errno;
                                (void) fprintf(stderr, MSG_INTL(MSG_SYS_WRITE),
                                    name, strerror(err));
                                exit(2);
                        }
                        (void) close(fd);
                } else {
                        /* Archive member is in memory. Write it out */
                        arwrite(name, ar_outfile.fd, fptr->ar_contents,
                            fptr->ar_size);
                }

                /*
                 * All archive members are padded to at least a boundary of 2.
                 * The expression ((fptr->ar_size & 0x1) != 0) yields 1 for
                 * odd boundaries, and 0 for even ones. To this, we add
                 * whatever padding is needed for ELF objects.
                 */
                pad_cnt = ((fptr->ar_size & 0x1) != 0) + fptr->ar_padding;
                if (pad_cnt > 0)
                        arwrite(name, ar_outfile.fd, pad_bytes, pad_cnt);
        }

        /*
         * All archive output is done.
         */
        if (close(ar_outfile.fd) < 0) {
                int err = errno;
                (void) fprintf(stderr, MSG_INTL(MSG_SYS_CLOSE), ar_outfile.path,
                    strerror(err));
                exit(1);
        }
        ar_outfile.fd = -1;     /* Prevent removal on exit */
        (void) elf_end(cmd_info->arf);
        (void) close(cmd_info->afd);

        /*
         * If updating an existing archive, rename the new version on
         * top of the original.
         */
        if (!new_archive) {
                /*
                 * Prevent the replacement of the original archive from
                 * being interrupted, to lower the possibility of an
                 * interrupt destroying a pre-existing archive.
                 */
                establish_sighandler(SIG_IGN);

                if (rename(ar_outfile.path, name) < 0) {
                        int err = errno;
                        (void) fprintf(stderr, MSG_INTL(MSG_SYS_RENAME),
                            ar_outfile.path, name, strerror(err));
                        (void) unlink(ar_outfile.path);
                        exit(1);
                }
                (void) chmod(name, ar_stbuf.st_mode & 0777);
                if (chown(name, ar_stbuf.st_uid, ar_stbuf.st_gid) >= 0)
                        (void) chmod(name, ar_stbuf.st_mode & 07777);

        }
}

/*
 * Examine all the archive members, enter any member names longer than
 * 15 characters into the long name string table, and count the number
 * of names found.
 *
 * Returns the size of the resulting archive member, including the
 * member header.
 */
static size_t
mklong_tab(void)
{
        ARFILE  *fptr;
        size_t longnames = 0;

        for (fptr = listhead; fptr; fptr = fptr->ar_next) {
                if (strlen(fptr->ar_longname) >= (unsigned)SNAME-1) {
                        longnames++;
                        savelongname(fptr);
                }
        }

        /* round up table that keeps the long filenames */
        if (longnames > 0)
                strtbl_pad(&long_strtbl, pad(long_strtbl.used, 4), '\n');

        return (longnames);
}

/*
 * Write 32/64-bit words into buffer in archive symbol table
 * standard byte order (MSB).
 */
static char *
sputl32(uint32_t n, char *cp)
{
        *cp++ = n >> 24;
        *cp++ = n >> 16;
        *cp++ = n >> 8;

        *cp++ = n & 255;

        return (cp);
}

static char *
sputl64(uint64_t n, char *cp)
{
        *cp++ = n >> 56;
        *cp++ = n >> 48;
        *cp++ = n >> 40;
        *cp++ = n >> 32;

        *cp++ = n >> 24;
        *cp++ = n >> 16;
        *cp++ = n >> 8;

        *cp++ = n & 255;

        return (cp);
}

static int
search_sym_tab(const char *arname, ARFILE *fptr, Elf *elf, Elf_Scn *scn,
    size_t *nsyms, ARFILEP **symlist, size_t *num_errs)
{
        Elf_Data *str_data, *sym_data; /* string table, symbol table */
        Elf_Scn *str_scn;
        GElf_Sxword no_of_symbols;
        GElf_Shdr shdr;
        int counter;
        int str_shtype;
        char *symname;
        static ARFILEP *sym_ptr = 0;
        static ARFILEP *nextsym = NULL;
        static int syms_left = 0;
        char *fname = fptr->ar_pathname;

        (void) gelf_getshdr(scn, &shdr);
        str_scn = elf_getscn(elf, shdr.sh_link); /* index for string table */
        if (str_scn == NULL) {
                if (fname != NULL)
                        (void) fprintf(stderr, MSG_INTL(MSG_ELF_GETDATA_FILE),
                            fname, elf_errmsg(-1));
                else
                        (void) fprintf(stderr, MSG_INTL(MSG_ELF_GETDATA_AR),
                            arname, fptr->ar_longname, elf_errmsg(-1));
                (*num_errs)++;
                return (-1);
        }

        no_of_symbols = shdr.sh_size / shdr.sh_entsize;
        if (no_of_symbols == -1) {
                (void) fprintf(stderr, MSG_INTL(MSG_SYMTAB_01));
                return (-1);
        }

        (void) gelf_getshdr(str_scn, &shdr);
        str_shtype = shdr.sh_type;
        if (str_shtype == -1) {
                if (fname != NULL)
                        (void) fprintf(stderr, MSG_INTL(MSG_ELF_GETDATA_FILE),
                            fname, elf_errmsg(-1));
                else
                        (void) fprintf(stderr, MSG_INTL(MSG_ELF_GETDATA_AR),
                            arname, fptr->ar_longname, elf_errmsg(-1));
                (*num_errs)++;
                return (-1);
        }

        /* This test must happen before testing the string table. */
        if (no_of_symbols == 1)
                return (0);     /* no symbols; 0th symbol is the non-symbol */

        if (str_shtype != SHT_STRTAB) {
                if (fname != NULL)
                        (void) fprintf(stderr, MSG_INTL(MSG_SYMTAB_NOSTR_FILE),
                            fname);
                else
                        (void) fprintf(stderr, MSG_INTL(MSG_SYMTAB_NOSTR_AR),
                            arname, fptr->ar_longname);
                return (0);
        }
        str_data = 0;
        if ((str_data = elf_getdata(str_scn, str_data)) == 0) {
                if (fname != NULL)
                        (void) fprintf(stderr, MSG_INTL(MSG_SYMTAB_NODAT_FILE),
                            fname);
                else
                        (void) fprintf(stderr, MSG_INTL(MSG_SYMTAB_NODAT_AR),
                            arname, fptr->ar_longname);
                return (0);
        }
        if (str_data->d_size == 0) {
                if (fname != NULL)
                        (void) fprintf(stderr, MSG_INTL(MSG_SYMTAB_ZDAT_FILE),
                            fname);
                else
                        (void) fprintf(stderr, MSG_INTL(MSG_SYMTAB_ZDAT_AR),
                            arname, fptr->ar_longname);
                return (0);
        }
        sym_data = 0;
        if ((sym_data = elf_getdata(scn, sym_data)) == NULL) {
                if (fname != NULL)
                        (void) fprintf(stderr, MSG_INTL(MSG_ELF_LIB_FILE),
                            fname, elf_errmsg(-1));
                else
                        (void) fprintf(stderr, MSG_INTL(MSG_ELF_LIB_AR),
                            arname, fptr->ar_longname, elf_errmsg(-1));
                return (0);
        }

        /* start at 1, first symbol entry is ignored */
        for (counter = 1; counter < no_of_symbols; counter++) {
                GElf_Sym sym;
                (void) gelf_getsym(sym_data, counter, &sym);

                symname = (char *)(str_data->d_buf) + sym.st_name;

                if (((GELF_ST_BIND(sym.st_info) == STB_GLOBAL) ||
                    (GELF_ST_BIND(sym.st_info) == STB_WEAK)) &&
                    (sym.st_shndx != SHN_UNDEF)) {
                        if (!syms_left) {
                                sym_ptr = malloc((SYMCHUNK+1)
                                    * sizeof (ARFILEP));
                                if (sym_ptr == NULL) {
                                        int err = errno;
                                        (void) fprintf(stderr,
                                            MSG_INTL(MSG_MALLOC),
                                            strerror(err));
                                        exit(1);
                                }
                                syms_left = SYMCHUNK;
                                if (nextsym)
                                        *nextsym = (ARFILEP)sym_ptr;
                                else
                                        *symlist = sym_ptr;
                                nextsym = sym_ptr;
                        }
                        sym_ptr = nextsym;
                        nextsym++;
                        syms_left--;
                        (*nsyms)++;
                        *sym_ptr = fptr;
                        savename(symname);      /* put name in the archiver's */
                                                /* symbol table string table */
                }
        }
        return (0);
}

/*
 * Get the output file size
 */
static size_t
sizeofmembers(size_t psum)
{
        size_t  sum = 0;
        ARFILE  *fptr;
        size_t  hdrsize = sizeof (struct ar_hdr);

        for (fptr = listhead; fptr; fptr = fptr->ar_next) {
                fptr->ar_offset = psum + sum;
                sum += fptr->ar_size;
                if (fptr->ar_size & 01)
                        sum++;
                sum += hdrsize;

                /*
                 * If the current item, and the next item are both ELF
                 * objects, then add padding to current item so that the
                 * data in the next item will have PADSZ alignment.
                 *
                 * In any other case, set the padding to 0. If the
                 * item comes from another archive, it may be carrying
                 * a non-zero padding value from that archive that does
                 * not apply to the one we are about to build.
                 */
                if ((fptr->ar_flag & (F_CLASS32 | F_CLASS64)) &&
                    fptr->ar_next &&
                    (fptr->ar_next->ar_flag & (F_CLASS32 | F_CLASS64))) {
                        fptr->ar_padding = pad(psum + sum + hdrsize, PADSZ);
                        sum += fptr->ar_padding;
                } else {
                        fptr->ar_padding = 0;
                }
        }
        return (sum);
}

/*
 * Compute the size of the symbol table archive member.
 *
 * entry:
 *      nsyms - # of symbols in the table
 *      found_obj - TRUE if the archive contains any ELF objects
 *      eltsize - Size of the integer type to use for the symbol
 *              table. 4 for 32-bit tables, and 8 for 64-bit tables.
 */
static size_t
sizeof_symtbl(size_t nsyms, int found_obj, size_t eltsize)
{
        size_t sum = 0;

        if (found_obj) {
                /* Member header, symbol count, and one slot per symbol */
                sum += sizeof (struct ar_hdr) + ((nsyms + 1) * eltsize);
                sum += sym_strtbl.used;
        }

        return (sum);
}

static void
arwrite(const char *name, int nfd, const char *dst, size_t size)
{
        if (write(nfd, dst, size) != size) {
                int err = errno;
                (void) fprintf(stderr, MSG_INTL(MSG_SYS_WRITE),
                    name, strerror(err));
                exit(2);
        }
}

static const char *
make_tmpname(const char *filename)
{
        char    *slash, *tmpname;
        size_t  prefix_cnt = 0;

        /*
         * If there is a path prefix in front of the filename, we
         * want to put the temporary file in the same directory.
         * Determine the length of the path.
         */
        slash = strrchr(filename, '/');
        if (slash != NULL)
                prefix_cnt = slash - filename + 1;
        tmpname = malloc(prefix_cnt + MSG_STR_MKTEMP_SIZE + 1);
        if (tmpname == NULL) {
                int err = errno;
                (void) fprintf(stderr, MSG_INTL(MSG_MALLOC), strerror(err));
                exit(1);
        }

        if (prefix_cnt > 0)
                (void) strncpy(tmpname, filename, prefix_cnt);
        (void) strcpy(tmpname + prefix_cnt, MSG_ORIG(MSG_STR_MKTEMP));
        (void) mktemp(tmpname);

        return (tmpname);
}