root/usr/src/cmd/sgs/libld/common/map_v2.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 2010 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * Copyright 2019 Joyent, Inc.
 * Copyright 2022 Oxide Computer Company
 */

/*
 * Map file parsing, Version 2 syntax (solaris).
 */
#include        <stdio.h>
#include        <unistd.h>
#include        <ctype.h>
#include        <sys/elf_amd64.h>   /* SHF_AMD64_LARGE */
#include        <elfcap.h>
#include        "msg.h"
#include        "_libld.h"
#include        "_map.h"

/*
 * Use a case insensitive string match when looking up capability mask
 * values by name, and omit the AV_ prefix.
 */
#define ELFCAP_STYLE ELFCAP_STYLE_LC | ELFCAP_STYLE_F_ICMP

/*
 * Signature for functions used to parse top level mapfile directives
 */
typedef Token (*dir_func_t)(Mapfile *mf);

/*
 * Signature for functions used to parse attribute level assignments
 *      mf - Mapfile descriptor
 *      eq_tok - One of the equal tokens (TK_EQUAL, TK_PLUSEQ, TK_MINUSEQ)
 *              or TK_ERROR. See the comment for attr_fmt_t below.
 *      uvalue - An arbitrary pointer "user value" passed by the
 *              caller to parse_attributes() for use by the function.
 */
typedef Token (* attr_func_t)(Mapfile *mf, Token eq_tok, void *uvalue);

/*
 * Signature for gettoken_str() err_func argument. This is a function
 * called to issue an appropriate error message.
 *
 * The gts prefix stands for "Get Token Str"
 */
typedef void (* gts_efunc_t)(Mapfile *mf, Token tok, ld_map_tkval_t *tkv);

/*
 * The attr_fmt_t tells parse_attributes how far to go in parsing
 * an attribute before it calls the at_func function to take over:
 *
 *      ATTR_FMT_NAME - Parse the name, and immediately call the function.
 *              This is useful in cases where there is more than
 *              one possible syntax for a given attribute. The value of
 *              eq_tok passed to the at_func function will be TK_ERROR,
 *              reflecting the fact that it has no meaning in this context.
 *
 *      ATTR_FMT_EQ - Parse the name, and the following '=', and then call
 *              the function. The value passed to the at_func function for
 *              eq_tok will be TK_EQUAL.
 *
 *      ATTR_FMT_EQ_PEQ - Parse the name, and a following equal token which
 *              can be '=' or '+=', and then call the function. The value
 *              passed to the at_func function for eq_tok will be one of
 *              TK_EQUAL, or TK_PLUSEQ.
 *
 *      ATTR_FMT_EQ_ALL - Parse the name, and a following equal token which
 *              can be any of the three forms (=, +=, -=), and then call
 *              the function. The value passed to the at_func function for
 *              eq_tok will be one of TK_EQUAL, TK_PLUSEQ, or TK_MINUSEQ.
 */
typedef enum {
        ATTR_FMT_NAME,
        ATTR_FMT_EQ,
        ATTR_FMT_EQ_PEQ,
        ATTR_FMT_EQ_ALL,
} attr_fmt_t;

/*
 * Type used to describe a set of valid attributes to parse_attributes():
 *      at_name - Name of attribute
 *      at_func - Function to call when attribute is recognized,
 *      at_all_eq - True if attribute allows the '+=' and '-=' forms of
 *              assignment token, and False to only allow '='.
 *
 * The array of these structs passed to parse_attributes() must be
 * NULL terminated (the at_name field must be set to NULL).
 */
typedef struct {
        const char      *at_name;       /* Name of attribute */
        attr_func_t     at_func;        /* Function to call */
        attr_fmt_t      at_fmt;         /* How much to parse before calling */
                                        /*      at_func */
} attr_t;

/*
 * Mapfile version and symbol state are separate but related concepts
 * that are best represented using two different types. However, our
 * style of passing a single uvalue via parse_attributes() makes it
 * convenient to be able to reference them from a single address.
 */
typedef struct {
        ld_map_ver_t    ss_mv;
        ld_map_sym_t    ss_ms;
        Ass_desc        ss_ma;
} symbol_state_t;

/*
 * Process an expected equal operator. Deals with the fact that we
 * have three variants.
 *
 * entry:
 *      mf - Mapfile descriptor
 *      eq_type - Types of equal operators accepted. One of ATTR_FMT_EQ,
 *              ATTR_FMT_EQ_PEQ, or ATTR_FMT_EQ_ALL.
 *      lhs - Name that appears on the left hand side of the expected
 *              equal operator.
 *
 * exit:
 *      Returns one of TK_EQUAL, TK_PLUSEQ, TK_MINUSEQ, or TK_ERROR.
 */
static Token
gettoken_eq(Mapfile *mf, attr_fmt_t eq_type, const char *lhs)
{
        Token           tok;
        ld_map_tkval_t  tkv;
        const char      *err;
        Conv_inv_buf_t  inv_buf;

        switch (tok = ld_map_gettoken(mf, 0, &tkv)) {
        case TK_ERROR:
        case TK_EQUAL:
                return (tok);

        case TK_PLUSEQ:
                switch (eq_type) {
                case ATTR_FMT_EQ_PEQ:
                case ATTR_FMT_EQ_ALL:
                        return (tok);
                }
                break;

        case TK_MINUSEQ:
                if (eq_type == ATTR_FMT_EQ_ALL)
                        return (tok);
                break;
        }

        switch (eq_type) {
        case ATTR_FMT_EQ:
                err = MSG_INTL(MSG_MAP_EXP_EQ);
                break;
        case ATTR_FMT_EQ_PEQ:
                err = MSG_INTL(MSG_MAP_EXP_EQ_PEQ);
                break;
        case ATTR_FMT_EQ_ALL:
                err = MSG_INTL(MSG_MAP_EXP_EQ_ALL);
                break;
        default:
                /*NOTREACHED*/
                assert(0);
        }
        mf_fatal(mf, err, lhs, ld_map_tokenstr(tok, &tkv, &inv_buf));
        return (TK_ERROR);
}

/*
 * Apply one of the three equal tokens to a bitmask value
 *
 * entry:
 *      dst - Address of bitmask variable to alter
 *      eq_tok - One of TK_EQUAL, TK_PLUSEQ, TK_MINUSEQ, representing
 *              the operation to carry out.
 *      value - Value for right hand side
 *
 * exit:
 *      The operation has been carried out:
 *
 *      TK_EQUAL - *dst is set to value
 *      TK_PLUSEQ - Bits in value have been set in *dst
 *      TK_MINUSEQ - Bits in value have been removed from *dst
 */
static void
setflags_eq(Word *dst, Token eq_tok, Word value)
{
        switch (eq_tok) {
        case TK_EQUAL:
                *dst = value;
                break;
        case TK_PLUSEQ:
                *dst |= value;
                break;
        case TK_MINUSEQ:
                *dst &= ~value;
                break;
        default:
                /*NOTREACHED*/
                assert(0);
        }
}

/*
 * Apply one of the three equal tokens to a capabilities Capmask.
 *
 * entry:
 *      mf - Mapfile descriptor
 *      capmask - Address of Capmask variable to alter
 *      eq_tok - One of TK_EQUAL, TK_PLUSEQ, TK_MINUSEQ, representing
 *              the operation to carry out.
 *      type - Capability type (CA_SUNW_*)
 *      value - Value for right hand side
 *      title - True if a title is needed, False otherwise.
 *
 * exit:
 *      On success, returns TRUE (1), otherwise FALSE (0)
 */
static Boolean
set_capmask(Mapfile *mf, Capmask *capmask, Token eq_tok,
    Word type, elfcap_mask_t value, Boolean title)
{
        if (title)
                DBG_CALL(Dbg_cap_mapfile_title(mf->mf_ofl->ofl_lml,
                    mf->mf_lineno));
        DBG_CALL(Dbg_cap_val_entry(mf->mf_ofl->ofl_lml, DBG_STATE_CURRENT,
            type, capmask->cm_val, ld_targ.t_m.m_mach));

        switch (eq_tok) {
        case TK_EQUAL:
                capmask->cm_val = value;
                capmask->cm_exc = 0;
                ld_map_cap_set_ovflag(mf, type);
                DBG_CALL(Dbg_cap_val_entry(mf->mf_ofl->ofl_lml,
                    DBG_STATE_RESET, type, capmask->cm_val,
                    ld_targ.t_m.m_mach));
                break;
        case TK_PLUSEQ:
                DBG_CALL(Dbg_cap_val_entry(mf->mf_ofl->ofl_lml,
                    DBG_STATE_ADD, type, value, ld_targ.t_m.m_mach));
                capmask->cm_val |= value;
                capmask->cm_exc &= ~value;
                break;
        case TK_MINUSEQ:
                DBG_CALL(Dbg_cap_val_entry(mf->mf_ofl->ofl_lml,
                    DBG_STATE_EXCLUDE, type, value, ld_targ.t_m.m_mach));
                capmask->cm_val &= ~value;
                capmask->cm_exc |= value;
                break;
        default:
                /*NOTREACHED*/
                assert(0);
        }

        /* Sanity check the resulting bits */
        if (!ld_map_cap_sanitize(mf, type, capmask))
                return (FALSE);

        /* Report the final configuration */
        DBG_CALL(Dbg_cap_val_entry(mf->mf_ofl->ofl_lml,
            DBG_STATE_RESOLVED, type, capmask->cm_val, ld_targ.t_m.m_mach));

        return (TRUE);
}

/*
 * Apply one of the three equal tokens to a capabilities Caplist.
 *
 * entry:
 *      mf - Mapfile descriptor
 *      caplist - Address of Caplist variable to alter
 *      eq_tok - One of TK_EQUAL, TK_PLUSEQ, TK_MINUSEQ, representing
 *              the operation to carry out.
 *      type - Capability type (CA_SUNW_*)
 *      str - String for right hand side
 *      title - True if a title is needed, False otherwise.
 *
 * exit:
 *      On success, returns TRUE (1), otherwise FALSE (0)
 */
static Boolean
set_capstr(Mapfile *mf, Caplist *caplist, Token eq_tok,
    Word type, APlist *strs)
{
        Capstr          *capstr;
        Aliste          idx1;
        char            *str;

        DBG_CALL(Dbg_cap_mapfile_title(mf->mf_ofl->ofl_lml, mf->mf_lineno));

        if ((caplist->cl_val == NULL) || (alist_nitems(caplist->cl_val) == 0)) {
                DBG_CALL(Dbg_cap_ptr_entry(mf->mf_ofl->ofl_lml,
                    DBG_STATE_CURRENT, type, NULL));
        } else {
                for (ALIST_TRAVERSE(caplist->cl_val, idx1, capstr)) {
                        DBG_CALL(Dbg_cap_ptr_entry(mf->mf_ofl->ofl_lml,
                            DBG_STATE_CURRENT, type, capstr->cs_str));
                }
        }

        switch (eq_tok) {
        case TK_EQUAL:
                if (caplist->cl_val) {
                        (void) free(caplist->cl_val);
                        caplist->cl_val = NULL;
                }
                if (caplist->cl_exc) {
                        (void) free(caplist->cl_exc);
                        caplist->cl_exc = NULL;
                }
                if (strs) {
                        for (APLIST_TRAVERSE(strs, idx1, str)) {
                                if ((capstr = alist_append(&caplist->cl_val,
                                    NULL, sizeof (Capstr),
                                    AL_CNT_CAP_NAMES)) == NULL)
                                        return (FALSE);
                                capstr->cs_str = str;
                                DBG_CALL(Dbg_cap_ptr_entry(mf->mf_ofl->ofl_lml,
                                    DBG_STATE_RESET, type, capstr->cs_str));
                        }
                } else {
                        DBG_CALL(Dbg_cap_ptr_entry(mf->mf_ofl->ofl_lml,
                            DBG_STATE_RESET, type, NULL));
                }
                ld_map_cap_set_ovflag(mf, type);
                break;
        case TK_PLUSEQ:
                for (APLIST_TRAVERSE(strs, idx1, str)) {
                        Aliste          idx2;
                        const char      *ostr;
                        int             found = 0;

                        /*
                         * Add this name to the list of names, provided the
                         * name doesn't already exist.
                         */
                        for (ALIST_TRAVERSE(caplist->cl_val, idx2, capstr)) {
                                if (strcmp(str, capstr->cs_str) == 0) {
                                        found++;
                                        break;
                                }
                        }
                        if ((found == 0) && ((capstr =
                            (Capstr *)alist_append(&caplist->cl_val, NULL,
                            sizeof (Capstr), AL_CNT_CAP_NAMES)) == NULL))
                                return (FALSE);
                        capstr->cs_str = str;

                        /*
                         * Remove this name from the list of excluded names,
                         * provided the name already exists.
                         */
                        for (APLIST_TRAVERSE(caplist->cl_exc, idx2, ostr)) {
                                if (strcmp(str, ostr) == 0) {
                                        aplist_delete(caplist->cl_exc, &idx2);
                                        break;
                                }
                        }
                        DBG_CALL(Dbg_cap_ptr_entry(mf->mf_ofl->ofl_lml,
                            DBG_STATE_ADD, type, str));
                }
                break;
        case TK_MINUSEQ:
                for (APLIST_TRAVERSE(strs, idx1, str)) {
                        Aliste          idx2;
                        const char      *ostr;
                        int             found = 0;

                        /*
                         * Delete this name from the list of names, provided
                         * the name already exists.
                         */
                        for (ALIST_TRAVERSE(caplist->cl_val, idx2, capstr)) {
                                if (strcmp(str, capstr->cs_str) == 0) {
                                        alist_delete(caplist->cl_val, &idx2);
                                        break;
                                }
                        }

                        /*
                         * Add this name to the list of excluded names,
                         * provided the name already exists.
                         */
                        for (APLIST_TRAVERSE(caplist->cl_exc, idx2, ostr)) {
                                if (strcmp(str, ostr) == 0) {
                                        found++;
                                        break;
                                }
                        }
                        if ((found == 0) && (aplist_append(&caplist->cl_exc,
                            str, AL_CNT_CAP_NAMES) == NULL))
                                return (FALSE);

                        DBG_CALL(Dbg_cap_ptr_entry(mf->mf_ofl->ofl_lml,
                            DBG_STATE_EXCLUDE, type, str));
                }
                break;
        default:
                /*NOTREACHED*/
                assert(0);
        }

        /* Report the final configuration */
        if ((caplist->cl_val == NULL) || (alist_nitems(caplist->cl_val) == 0)) {
                DBG_CALL(Dbg_cap_ptr_entry(mf->mf_ofl->ofl_lml,
                    DBG_STATE_RESOLVED, type, NULL));
        } else {
                for (ALIST_TRAVERSE(caplist->cl_val, idx1, capstr)) {
                        DBG_CALL(Dbg_cap_ptr_entry(mf->mf_ofl->ofl_lml,
                            DBG_STATE_RESOLVED, type, capstr->cs_str));
                }
        }

        return (TRUE);
}

/*
 * Process the next token, which is expected to start an optional
 * nesting of attributes (';' or '{').
 *
 * entry:
 *      mf - Mapfile descriptor
 *      lhs - Name of the directive or attribute being processed.
 *
 * exit:
 *      Returns TK_SEMICOLON or TK_LEFTBKT for success, and TK_ERROR otherwise.
 */
static Token
gettoken_optattr(Mapfile *mf, const char *lhs)
{
        Token           tok;
        ld_map_tkval_t  tkv;
        Conv_inv_buf_t  inv_buf;

        switch (tok = ld_map_gettoken(mf, 0, &tkv)) {
        case TK_ERROR:
        case TK_SEMICOLON:
        case TK_LEFTBKT:
                return (tok);
        }

        mf_fatal(mf, MSG_INTL(MSG_MAP_EXP_SEMLBKT), lhs,
            ld_map_tokenstr(tok, &tkv, &inv_buf));
        return (TK_ERROR);
}

/*
 * Process the next token, which is expected to be a line terminator
 * (';' or '}').
 *
 * entry:
 *      mf - Mapfile descriptor
 *      lhs - Name of the directive or attribute being processed.
 *
 * exit:
 *      Returns TK_SEMICOLON or TK_RIGHTBKT for success, and TK_ERROR otherwise.
 */
static Token
gettoken_term(Mapfile *mf, const char *lhs)
{
        Token           tok;
        ld_map_tkval_t  tkv;
        Conv_inv_buf_t  inv_buf;

        switch (tok = ld_map_gettoken(mf, 0, &tkv)) {
        case TK_ERROR:
        case TK_SEMICOLON:
        case TK_RIGHTBKT:
                return (tok);
        }

        mf_fatal(mf, MSG_INTL(MSG_MAP_EXP_SEMRBKT), lhs,
            ld_map_tokenstr(tok, &tkv, &inv_buf));
        return (TK_ERROR);
}

/*
 * Process the next token, which is expected to be a semicolon.
 *
 * entry:
 *      mf - Mapfile descriptor
 *      lhs - Name of the directive or attribute being processed.
 *
 * exit:
 *      Returns TK_SEMICOLON for success, and TK_ERROR otherwise.
 */
static Token
gettoken_semicolon(Mapfile *mf, const char *lhs)
{
        Token           tok;
        ld_map_tkval_t  tkv;
        Conv_inv_buf_t  inv_buf;

        switch (tok = ld_map_gettoken(mf, 0, &tkv)) {
        case TK_ERROR:
        case TK_SEMICOLON:
                return (tok);
        }

        mf_fatal(mf, MSG_INTL(MSG_MAP_EXP_SEM), lhs,
            ld_map_tokenstr(tok, &tkv, &inv_buf));
        return (TK_ERROR);
}

/*
 * Process the next token, which is expected to be a '{'
 *
 * entry:
 *      mf - Mapfile descriptor
 *      lhs - Name of the item directly to the left of the expected left
 *              bracket.
 *
 * exit:
 *      Returns TK_LEFTBKT for success, and TK_ERROR otherwise.
 */
static Token
gettoken_leftbkt(Mapfile *mf, const char *lhs)
{
        Token           tok;
        ld_map_tkval_t  tkv;
        Conv_inv_buf_t  inv_buf;

        switch (tok = ld_map_gettoken(mf, 0, &tkv)) {
        case TK_ERROR:
        case TK_LEFTBKT:
                return (tok);
        }

        mf_fatal(mf, MSG_INTL(MSG_MAP_EXP_LBKT), lhs,
            ld_map_tokenstr(tok, &tkv, &inv_buf));
        return (TK_ERROR);
}

/*
 * Process the next token, which is expected to be an integer
 *
 * entry:
 *      mf - Mapfile descriptor
 *      lhs - Name of the directive or attribute being processed.
 *      tkv - Address of token value struct to be filled in
 *
 * exit:
 *      Updates *tkv and returns TK_INT for success, TK_ERROR otherwise.
 */
static Token
gettoken_int(Mapfile *mf, const char *lhs, ld_map_tkval_t *tkv, int flags)
{
        Token           tok;
        Conv_inv_buf_t  inv_buf;
        char            *start = mf->mf_next;

        switch (tok = ld_map_gettoken(mf, flags, tkv)) {
        case TK_ERROR:
                return (tok);
        case TK_STRING:
                if (strcmp(MSG_ORIG(MSG_MAP_ADDRSIZE), tkv->tkv_str) == 0) {
                        tkv->tkv_int.tkvi_str = tkv->tkv_str;
                        switch (ld_targ.t_m.m_class) {
                        case ELFCLASS32:
                                tkv->tkv_int.tkvi_value = sizeof (Elf32_Addr);
                                break;
                        case ELFCLASS64:
                                tkv->tkv_int.tkvi_value = sizeof (Elf64_Addr);
                                break;
                        case ELFCLASSNONE:
                                tkv->tkv_int.tkvi_value = 0;
                                break;
                        default:
                                assert(0);
                        }
                        tkv->tkv_int.tkvi_cnt = MSG_MAP_ADDRSIZE_SIZE;
                        tok = TK_INT;
                } else {
                        break;
                }
                /* FALLTHROUGH */
        case TK_INT:
                if ((flags & TK_F_MULOK) &&
                    (ld_map_peektoken(mf) == TK_LEFTSQR)) {
                        ld_map_tkval_t mltplr;
                        Xword oldval;

                        /* read the [, which we know must be there */
                        (void) ld_map_gettoken(mf, flags, &mltplr);

                        if (ld_map_gettoken(mf, flags & ~TK_F_MULOK,
                            &mltplr) != TK_INT) {
                                tkv->tkv_int.tkvi_cnt = mf->mf_next - start;
                                mf_fatal(mf, MSG_INTL(MSG_MAP_EXP_INT),
                                    MSG_ORIG(MSG_QSTR_LEFTSQR),
                                    ld_map_tokenstr(TK_INT, tkv, &inv_buf));
                                return (TK_ERROR);
                        }

                        if (ld_map_peektoken(mf) != TK_RIGHTSQR) {
                                tkv->tkv_int.tkvi_cnt = mf->mf_next - start;
                                mf_fatal(mf, MSG_INTL(MSG_MAP_EXP_RIGHTSQ),
                                    ld_map_tokenstr(TK_INT, tkv, &inv_buf));
                                return (TK_ERROR);
                        }

                        /* Read the right ] */
                        (void) ld_map_gettoken(mf, flags, NULL);
                        tkv->tkv_int.tkvi_cnt = mf->mf_next - start;
                        oldval = tkv->tkv_int.tkvi_value;
                        tkv->tkv_int.tkvi_value *= mltplr.tkv_int.tkvi_value;

                        if ((tkv->tkv_int.tkvi_value /
                            mltplr.tkv_int.tkvi_value) != oldval) {
                                mf_fatal(mf, MSG_INTL(MSG_MAP_MULOVERFLOW),
                                    tkv->tkv_int.tkvi_value,
                                    mltplr.tkv_int.tkvi_value);
                                return (TK_ERROR);
                        }
                }

                return (tok);
        }
        mf_fatal(mf, MSG_INTL(MSG_MAP_EXP_INT), lhs,
            ld_map_tokenstr(tok, tkv, &inv_buf));
        return (TK_ERROR);
}

/*
 * Process the next token, which is expected to be a string
 *
 * entry:
 *      mf - Mapfile descriptor
 *      lhs - Name of the directive or attribute being processed.
 *      tkv - Address of token value struct to be filled in
 *      err_func - Function to call if an error occurs
 *
 * exit:
 *      Updates *tkv and returns TK_STRING for success. Calls the
 *      supplied err_func function and returns TK_ERROR otherwise.
 */
static Token
gettoken_str(Mapfile *mf, int flags, ld_map_tkval_t *tkv, gts_efunc_t efunc)
{
        Token           tok;

        switch (tok = ld_map_gettoken(mf, flags, tkv)) {
        case TK_ERROR:
        case TK_STRING:
                return (tok);
        }

        /* User supplied function reports the error */
        (* efunc)(mf, tok, tkv);

        return (TK_ERROR);
}

/*
 * Given a construct of the following common form:
 *
 *      item_name {
 *              attribute = ...;
 *              ...
 *      }
 *
 * where the caller has detected the item_name and opening bracket,
 * parse the construct and call the attribute functions for each
 * attribute detected, stopping when the closing '}' is seen.
 *
 * entry:
 *      mf - Mapfile descriptor
 *      item_name - Already detected name of item for which attributes
 *              are being parsed.
 *      attr_list - NULL terminated array of attr_t structures describing the
 *              valid attributes for the item.
 *      expect_str - Comma separated string listing the names of expected
 *              attributes.
 *      uvalue - User value, passed to the attribute functions without
 *              examination by parse_attributes(), usable for maintaining
 *              shared state between the caller and the functions.
 *
 * exit:
 *      parse_attributes() reads the attribute name and equality token,
 *      and then calls the attribute function given by the attr_list array
 *      to handle everything up to and including the terminating ';'.
 *      This continues until the closing '}' is seen.
 *
 *      If everything is successful, TK_RIGHTBKT is returned. Otherwise,
 *      a suitable error is issued and TK_ERROR is returned.
 */
static Token
parse_attributes(Mapfile *mf, const char *item_name, attr_t *attr_list,
    size_t attr_list_bufsize, void *uvalue)
{
        attr_t          *attr;
        Token           tok, op_tok;
        ld_map_tkval_t  tkv;
        int             done;
        int             attr_cnt = 0;
        Conv_inv_buf_t  inv_buf;

        /* Read attributes until the closing '}' is seen */
        for (done = 0; done == 0; ) {
                switch (tok = ld_map_gettoken(mf, TK_F_KEYWORD, &tkv)) {
                case TK_ERROR:
                        return (TK_ERROR);

                case TK_STRING:
                        attr = ld_map_kwfind(tkv.tkv_str, attr_list,
                            SGSOFFSETOF(attr_t, at_name), sizeof (attr[0]));
                        if (attr == NULL)
                                goto bad_attr;

                        /*
                         * Depending on the value of at_fmt, there are
                         * fout different actions to take:
                         *      ATTR_FMT_NAME - Call at_func function
                         *      ATTR_FMT_EQ - Read and verify a TK_EQUAL
                         *      ATTR_FMT_EQ_PEQ - Read and verify a TK_EQUAL
                         *              or TK_PLUSEQ.
                         *      ATTR_FMT_EQ_ALL - Read/Verify one of the
                         *              three possible equal tokens
                         *              (TK_EQUAL, TK_PLUSEQ, TK_MINUSEQ).
                         */
                        if (attr->at_fmt == ATTR_FMT_NAME) {
                                /* Arbitrary value to pass to at_func */
                                op_tok = TK_ERROR;
                        } else {
                                /* Read/Verify appropriate equal operator */
                                op_tok = gettoken_eq(mf, attr->at_fmt,
                                    attr->at_name);
                                if (op_tok == TK_ERROR)
                                        return (TK_ERROR);
                        }

                        /* Call the associated function */
                        switch (tok = attr->at_func(mf, op_tok, uvalue)) {
                        default:
                                return (TK_ERROR);
                        case TK_SEMICOLON:
                                break;
                        case TK_RIGHTBKT:
                                done = 1;
                                break;
                        }
                        attr_cnt++;
                        break;

                case TK_RIGHTBKT:
                        done = 1;
                        break;

                case TK_SEMICOLON:
                        break;          /* Ignore empty statement */

                default:
                bad_attr:
                        {
                                char buf[VLA_SIZE(attr_list_bufsize)];

                                mf_fatal(mf, MSG_INTL(MSG_MAP_EXP_ATTR),
                                    ld_map_kwnames(attr_list,
                                    SGSOFFSETOF(attr_t, at_name),
                                    sizeof (attr[0]), buf, attr_list_bufsize),
                                    ld_map_tokenstr(tok, &tkv, &inv_buf));
                        }
                        return (TK_ERROR);
                }
        }

        /* Make sure there was at least one attribute between the {} brackets */
        if (attr_cnt == 0) {
                mf_fatal(mf, MSG_INTL(MSG_MAP_NOATTR), item_name);
                return (TK_ERROR);
        }

        return (tok);
}

/*
 * Read whitespace delimited segment flags from the input and convert into
 * bitmask of PF_ values they represent. Flags are terminated by a semicolon
 * or right bracket.
 *
 * entry:
 *      mf - Mapfile descriptor
 *      flags - Address of variable to be set to resulting flags value
 *
 * exit:
 *      Returns the terminator token (TK_SEMICOLON or TK_LEFTBKT) on success,
 *      and TK_ERROR otherwise.
 */
static Token
parse_segment_flags(Mapfile *mf, Xword *flags)
{
        /*
         * Map flag names to their values. Since DATA and STACK have
         * platform dependent values, we have to determine them at runtime.
         * We indicate this by setting the top bit.
         */
#define PF_DATA         0x80000000
#define PF_STACK        0x80000001
        typedef struct {
                const char      *name;
                Word            value;
        } segflag_t;
        static segflag_t flag_list[] = {
                { MSG_ORIG(MSG_MAPKW_DATA),     PF_DATA },
                { MSG_ORIG(MSG_MAPKW_EXECUTE),  PF_X },
                { MSG_ORIG(MSG_MAPKW_READ),     PF_R },
                { MSG_ORIG(MSG_MAPKW_STACK),    PF_STACK },
                { MSG_ORIG(MSG_MAPKW_WRITE),    PF_W },

                /* List must be null terminated */
                { 0 },
        };

        /*
         * Size of buffer needed to format the names in flag_list[]. Must
         * be kept in sync with flag_list.
         */
        static size_t   flag_list_bufsize =
            KW_NAME_SIZE(MSG_MAPKW_DATA) +
            KW_NAME_SIZE(MSG_MAPKW_EXECUTE) +
            KW_NAME_SIZE(MSG_MAPKW_READ) +
            KW_NAME_SIZE(MSG_MAPKW_STACK) +
            KW_NAME_SIZE(MSG_MAPKW_WRITE);

        Token           tok;
        ld_map_tkval_t  tkv;
        segflag_t       *flag;
        size_t          cnt = 0;
        int             done;
        Conv_inv_buf_t  inv_buf;

        *flags = 0;

        /* Read attributes until the ';' terminator is seen */
        for (done = 0; done == 0; ) {
                switch (tok = ld_map_gettoken(mf, TK_F_KEYWORD, &tkv)) {
                case TK_ERROR:
                        return (TK_ERROR);

                case TK_STRING:
                        flag = ld_map_kwfind(tkv.tkv_str, flag_list,
                            SGSOFFSETOF(segflag_t, name),
                            sizeof (flag_list[0]));
                        if (flag == NULL)
                                goto bad_flag;
                        switch (flag->value) {
                        case PF_DATA:
                                *flags |= ld_targ.t_m.m_dataseg_perm;
                                break;
                        case PF_STACK:
                                *flags |= ld_targ.t_m.m_stack_perm;
                                break;
                        default:
                                *flags |= flag->value;
                        }
                        cnt++;
                        break;

                case TK_INT:
                        /*
                         * Accept 0 for notational convenience, but refuse
                         * any other value. Note that we don't actually have
                         * to set the flags to 0 here, because there are
                         * already initialized to that before the main loop.
                         */
                        if (tkv.tkv_int.tkvi_value != 0)
                                goto bad_flag;
                        cnt++;
                        break;

                case TK_SEMICOLON:
                case TK_RIGHTBKT:
                        done = 1;
                        break;

                default:
                bad_flag:
                        {
                                char buf[VLA_SIZE(flag_list_bufsize)];

                                mf_fatal(mf, MSG_INTL(MSG_MAP_EXP_SEGFLAG),
                                    ld_map_kwnames(flag_list,
                                    SGSOFFSETOF(segflag_t, name),
                                    sizeof (flag[0]), buf, flag_list_bufsize),
                                    ld_map_tokenstr(tok, &tkv, &inv_buf));
                        }
                        return (TK_ERROR);
                }
        }

        /* Make sure there was at least one flag */
        if (cnt == 0) {
                mf_fatal(mf, MSG_INTL(MSG_MAP_NOVALUES),
                    MSG_ORIG(MSG_MAPKW_FLAGS));
                return (TK_ERROR);
        }

        return (tok);

#undef PF_DATA
#undef PF_STACK
}

/*
 * Parse one of the capabilities attributes that corresponds directly to a
 * capabilities bitmask value (CA_SUNW_HW_x, CA_SUNW_SF_xx).  Values can be
 * integers, or symbolic names that correspond to the capabilities mask
 * in question.
 *
 * entry:
 *      mf - Mapfile descriptor
 *      eq_tok - One of TK_EQUAL, TK_PLUSEQ, TK_MINUSEQ, representing
 *              the operation to carry out.
 *      capmask - Capmask from output descriptor for capability being processed.
 *      type - Capability type (CA_SUNW_*)
 *      elfcap_from_str_func - pointer to elfcap-string-to-value function
 *              for capability being processed.
 *
 * exit:
 *      Returns TK_SEMICOLON or TK_RIGHTBKT for success, and TK_ERROR otherwise.
 */
static Token
parse_cap_mask(Mapfile *mf, Token eq_tok, Capmask *capmask,
    Word type, elfcap_from_str_func_t *elfcap_from_str_func)
{
        int             done;
        Token           tok;
        ld_map_tkval_t  tkv;
        Conv_inv_buf_t  inv_buf;
        elfcap_mask_t   value = 0;
        uint64_t        v;

        for (done = 0; done == 0; ) {
                switch (tok = ld_map_gettoken(mf, TK_F_KEYWORD, &tkv)) {
                case TK_ERROR:
                        return (TK_ERROR);

                case TK_STRING:
                        if ((v = (* elfcap_from_str_func)(ELFCAP_STYLE,
                            tkv.tkv_str, ld_targ.t_m.m_mach)) != 0) {
                                value |= v;
                                break;
                        }
                        goto bad_flag;

                case TK_INT:
                        value |= tkv.tkv_int.tkvi_value;
                        break;

                case TK_SEMICOLON:
                case TK_RIGHTBKT:
                        done = 1;
                        break;

                default:
                bad_flag:
                        mf_fatal(mf, MSG_INTL(MSG_MAP_EXP_CAPMASK),
                            ld_map_tokenstr(tok, &tkv, &inv_buf));
                        return (TK_ERROR);
                }
        }

        if (!set_capmask(mf, capmask, eq_tok, type, value, TRUE))
                return (TK_ERROR);
        return (tok);
}

/*
 * Parse one of the capabilities attributes that manages lists of names
 * (CA_SUNW_PLAT and CA_SUNW_MACH).  Values are symbolic names that correspond
 * to the capabilities mask in question.
 *
 * entry:
 *      mf - Mapfile descriptor
 *      eq_tok - One of TK_EQUAL, TK_PLUSEQ, TK_MINUSEQ, representing
 *              the operation to carry out.
 *      caplist - Caplist from output descriptor for capability being processed.
 *      type - Capability type (CA_SUNW_*)
 *
 * exit:
 *      Returns TK_SEMICOLON or TK_RIGHTBKT for success, and TK_ERROR otherwise.
 */
static Token
parse_cap_list(Mapfile *mf, Token eq_tok, Caplist *caplist,
    Word type)
{
        int             done, found;
        Token           tok;
        ld_map_tkval_t  tkv;
        Conv_inv_buf_t  inv_buf;
        APlist          *strs = NULL;
        Aliste          idx;
        const char      *str;

        for (done = 0, found = 0; done == 0; found = 0) {
                switch (tok = ld_map_gettoken(mf, 0, &tkv)) {
                case TK_ERROR:
                        return (TK_ERROR);

                case TK_STRING:
                        /*
                         * The name is in tkv.tkv_str.  Save this string for
                         * set_capstr() processing, but remove any duplicates.
                         */
                        for (APLIST_TRAVERSE(strs, idx, str)) {
                                if (strcmp(str, tkv.tkv_str) == 0) {
                                        found++;
                                        break;
                                }
                        }
                        if ((found == 0) && (aplist_append(&strs, tkv.tkv_str,
                            AL_CNT_CAP_NAMES) == NULL))
                                return (TK_ERROR);
                        break;

                case TK_SEMICOLON:
                case TK_RIGHTBKT:
                        done = 1;
                        break;

                default:
                        mf_fatal(mf, MSG_INTL(MSG_MAP_EXP_CAPNAME),
                            ld_map_tokenstr(tok, &tkv, &inv_buf));
                        return (TK_ERROR);
                }
        }

        if (!set_capstr(mf, caplist, eq_tok, type, strs))
                return (TK_ERROR);
        return (tok);
}

/*
 * CAPABILITY [capid] { HW = hwcap_flags...
 * -------------------------^
 */
static Token
at_cap_hw(Mapfile *mf, Token eq_tok, void *uvalue)
{
        int             done;
        Token           tok;
        ld_map_tkval_t  tkv;
        Conv_inv_buf_t  inv_buf;
        Word            hw1 = 0, hw2 = 0, hw3 = 0;
        uint64_t        v;

        for (done = 0; done == 0; ) {
                switch (tok = ld_map_gettoken(mf, TK_F_KEYWORD, &tkv)) {
                case TK_ERROR:
                        return (TK_ERROR);

                case TK_STRING:
                        if ((v = elfcap_hw1_from_str(ELFCAP_STYLE,
                            tkv.tkv_str, ld_targ.t_m.m_mach)) != 0) {
                                hw1 |= v;
                                break;
                        }
                        if ((v = elfcap_hw2_from_str(ELFCAP_STYLE,
                            tkv.tkv_str, ld_targ.t_m.m_mach)) != 0) {
                                hw2 |= v;
                                break;
                        }

                        if ((v = elfcap_hw3_from_str(ELFCAP_STYLE,
                            tkv.tkv_str, ld_targ.t_m.m_mach)) != 0) {
                                hw3 |= v;
                                break;
                        }
                        goto bad_flag;

                case TK_SEMICOLON:
                case TK_RIGHTBKT:
                        done = 1;
                        break;

                default:
                bad_flag:
                        mf_fatal(mf, MSG_INTL(MSG_MAP_EXP_CAPHW),
                            ld_map_tokenstr(tok, &tkv, &inv_buf));
                        return (TK_ERROR);
                }
        }

        if (!set_capmask(mf, &mf->mf_ofl->ofl_ocapset.oc_hw_1, eq_tok,
            CA_SUNW_HW_1, hw1, TRUE))
                return (TK_ERROR);
        if (!set_capmask(mf, &mf->mf_ofl->ofl_ocapset.oc_hw_2, eq_tok,
            CA_SUNW_HW_2, hw2, FALSE))
                return (TK_ERROR);
        if (!set_capmask(mf, &mf->mf_ofl->ofl_ocapset.oc_hw_3, eq_tok,
            CA_SUNW_HW_3, hw3, FALSE))
                return (TK_ERROR);
        return (tok);
}

/*
 * CAPABILITY [capid] { HW_1 = value ;
 * ---------------------------^
 */
static Token
at_cap_hw_1(Mapfile *mf, Token eq_tok, void *uvalue)
{
        return (parse_cap_mask(mf, eq_tok, &mf->mf_ofl->ofl_ocapset.oc_hw_1,
            CA_SUNW_HW_1, elfcap_hw1_from_str));
}

/*
 * CAPABILITY [capid] { HW_2 = value ;
 * ---------------------------^
 */
static Token
at_cap_hw_2(Mapfile *mf, Token eq_tok, void *uvalue)
{
        return (parse_cap_mask(mf, eq_tok, &mf->mf_ofl->ofl_ocapset.oc_hw_2,
            CA_SUNW_HW_2, elfcap_hw2_from_str));
}

/*
 * CAPABILITY [capid] { SF = sfcap_flags...
 * -------------------------^
 */
static Token
at_cap_sf(Mapfile *mf, Token eq_tok, void *uvalue)
{
        int             done;
        Token           tok;
        ld_map_tkval_t  tkv;
        Conv_inv_buf_t  inv_buf;
        Word            sf1 = 0;
        uint64_t        v;

        for (done = 0; done == 0; ) {
                switch (tok = ld_map_gettoken(mf, TK_F_KEYWORD, &tkv)) {
                case TK_ERROR:
                        return (TK_ERROR);

                case TK_STRING:
                        if ((v = elfcap_sf1_from_str(ELFCAP_STYLE,
                            tkv.tkv_str, ld_targ.t_m.m_mach)) != 0) {
                                sf1 |= v;
                                break;
                        }
                        goto bad_flag;

                case TK_SEMICOLON:
                case TK_RIGHTBKT:
                        done = 1;
                        break;

                default:
                bad_flag:
                        mf_fatal(mf, MSG_INTL(MSG_MAP_EXP_CAPSF),
                            ld_map_tokenstr(tok, &tkv, &inv_buf));
                        return (TK_ERROR);
                }
        }

        if (!set_capmask(mf, &mf->mf_ofl->ofl_ocapset.oc_sf_1, eq_tok,
            CA_SUNW_SF_1, sf1, TRUE))
                return (TK_ERROR);

        return (tok);
}

/*
 * CAPABILITY [capid] { SF_1 = value ;
 * ---------------------------^
 */
static Token
at_cap_sf_1(Mapfile *mf, Token eq_tok, void *uvalue)
{
        return (parse_cap_mask(mf, eq_tok, &mf->mf_ofl->ofl_ocapset.oc_sf_1,
            CA_SUNW_SF_1, elfcap_sf1_from_str));
}

/*
 * CAPABILITY [capid] { MACHINE = value ;
 * ------------------------------^
 */
static Token
at_cap_mach(Mapfile *mf, Token eq_tok, void *uvalue)
{
        return (parse_cap_list(mf, eq_tok, &mf->mf_ofl->ofl_ocapset.oc_mach,
            CA_SUNW_MACH));
}

/*
 * CAPABILITY [capid] { PLATFORM = value ;
 * -------------------------------^
 */
static Token
at_cap_plat(Mapfile *mf, Token eq_tok, void *uvalue)
{
        return (parse_cap_list(mf, eq_tok, &mf->mf_ofl->ofl_ocapset.oc_plat,
            CA_SUNW_PLAT));
}

/*
 * CAPABILITY [capid] { HW_3 = value ;
 * ---------------------------^
 */
static Token
at_cap_hw_3(Mapfile *mf, Token eq_tok, void *uvalue)
{
        return (parse_cap_mask(mf, eq_tok, &mf->mf_ofl->ofl_ocapset.oc_hw_3,
            CA_SUNW_HW_3, elfcap_hw3_from_str));
}

/*
 * Top Level Directive:
 *
 * CAPABILITY [capid] { ...
 * ----------^
 */
static Token
dir_capability(Mapfile *mf)
{
        /* CAPABILITY attributes */
        static attr_t attr_list[] = {
                { MSG_ORIG(MSG_MAPKW_HW),       at_cap_hw, ATTR_FMT_EQ_ALL },
                { MSG_ORIG(MSG_MAPKW_HW_1),     at_cap_hw_1, ATTR_FMT_EQ_ALL },
                { MSG_ORIG(MSG_MAPKW_HW_2),     at_cap_hw_2, ATTR_FMT_EQ_ALL },
                { MSG_ORIG(MSG_MAPKW_HW_3),     at_cap_hw_3, ATTR_FMT_EQ_ALL },

                { MSG_ORIG(MSG_MAPKW_MACHINE),  at_cap_mach, ATTR_FMT_EQ_ALL },
                { MSG_ORIG(MSG_MAPKW_PLATFORM), at_cap_plat, ATTR_FMT_EQ_ALL },

                { MSG_ORIG(MSG_MAPKW_SF),       at_cap_sf, ATTR_FMT_EQ_ALL },
                { MSG_ORIG(MSG_MAPKW_SF_1),     at_cap_sf_1, ATTR_FMT_EQ_ALL },

                /* List must be null terminated */
                { 0 }
        };

        /*
         * Size of buffer needed to format the names in attr_list[]. Must
         * be kept in sync with attr_list.
         */
        static size_t   attr_list_bufsize =
            KW_NAME_SIZE(MSG_MAPKW_HW) +
            KW_NAME_SIZE(MSG_MAPKW_HW_1) +
            KW_NAME_SIZE(MSG_MAPKW_HW_2) +
            KW_NAME_SIZE(MSG_MAPKW_HW_3) +
            KW_NAME_SIZE(MSG_MAPKW_MACHINE) +
            KW_NAME_SIZE(MSG_MAPKW_PLATFORM) +
            KW_NAME_SIZE(MSG_MAPKW_SF) +
            KW_NAME_SIZE(MSG_MAPKW_SF_1);

        Capstr          *capstr;
        Token           tok;
        ld_map_tkval_t  tkv;
        Conv_inv_buf_t  inv_buf;

        /*
         * The first token can be one of:
         * -    An opening '{'
         * -    A name, followed by a '{', or a ';'.
         * Read this initial sequence.
         */

        switch (tok = ld_map_gettoken(mf, 0, &tkv)) {
        case TK_ERROR:
                return (TK_ERROR);

        case TK_STRING:
                capstr = &mf->mf_ofl->ofl_ocapset.oc_id;

                /*
                 * The ID name is in tkv.tkv_str.  Save this name in the output
                 * capabilities structure.  Note, should multiple ID entries
                 * be encounterd, the last entry wins.
                 */
                DBG_CALL(Dbg_cap_id(mf->mf_ofl->ofl_lml, mf->mf_lineno,
                    capstr->cs_str, tkv.tkv_str));

                capstr->cs_str = tkv.tkv_str;
                mf->mf_ofl->ofl_ocapset.oc_flags |= FLG_OCS_USRDEFID;

                /*
                 * The name can be followed by an opening '{', or a
                 * terminating ';'
                 */
                switch (tok = gettoken_optattr(mf, capstr->cs_str)) {
                case TK_SEMICOLON:
                        return (TK_SEMICOLON);
                case TK_LEFTBKT:
                        break;
                default:
                        return (TK_ERROR);
                }
                break;

        case TK_LEFTBKT:
                /* Directive has no capid, but does supply attributes */
                break;

        default:
                mf_fatal(mf, MSG_INTL(MSG_MAP_EXP_CAPID),
                    MSG_ORIG(MSG_MAPKW_CAPABILITY),
                    ld_map_tokenstr(tok, &tkv, &inv_buf));
                return (TK_ERROR);
        }

        /* Parse the attributes */
        if (parse_attributes(mf, MSG_ORIG(MSG_MAPKW_CAPABILITY),
            attr_list, attr_list_bufsize, NULL) == TK_ERROR)
                return (TK_ERROR);

        /* Terminating ';' */
        return (gettoken_semicolon(mf, MSG_ORIG(MSG_MAPKW_CAPABILITY)));
}

/*
 * at_dv_allow(): Value for ALLOW= is not a version string
 */
static void
gts_efunc_at_dv_allow(Mapfile *mf, Token tok, ld_map_tkval_t *tkv)
{
        Conv_inv_buf_t  inv_buf;

        mf_fatal(mf, MSG_INTL(MSG_MAP_EXP_VERSION),
            MSG_ORIG(MSG_MAPKW_ALLOW), ld_map_tokenstr(tok, tkv, &inv_buf));
}

/*
 * DEPEND_VERSIONS object_name { ALLOW = version
 * -------------------------------------^
 */
static Token
at_dv_allow(Mapfile *mf, Token eq_tok, void *uvalue)
{
        ld_map_tkval_t  tkv;

        if (gettoken_str(mf, 0, &tkv, gts_efunc_at_dv_allow) == TK_ERROR)
                return (TK_ERROR);

        /* Enter the version. uvalue points at the Sdf_desc descriptor */
        if (!ld_map_dv_entry(mf, uvalue, FALSE, tkv.tkv_str))
                return (TK_ERROR);

        /* terminator */
        return (gettoken_term(mf, MSG_ORIG(MSG_MAPKW_ALLOW)));
}

/*
 * at_dv_allow(): Value for REQUIRE= is not a version string
 */
static void
gts_efunc_at_dv_require(Mapfile *mf, Token tok, ld_map_tkval_t *tkv)
{
        Conv_inv_buf_t  inv_buf;

        mf_fatal(mf, MSG_INTL(MSG_MAP_EXP_VERSION),
            MSG_ORIG(MSG_MAPKW_REQUIRE), ld_map_tokenstr(tok, tkv, &inv_buf));
}

/*
 * DEPEND_VERSIONS object_name { REQURE = version
 * --------------------------------------^
 */
static Token
at_dv_require(Mapfile *mf, Token eq_tok, void *uvalue)
{
        ld_map_tkval_t  tkv;

        /* version_name */
        if (gettoken_str(mf, 0, &tkv, gts_efunc_at_dv_require) == TK_ERROR)
                return (TK_ERROR);

        /* Enter the version. uvalue points at the Sdf_desc descriptor */
        if (!ld_map_dv_entry(mf, uvalue, TRUE, tkv.tkv_str))
                return (TK_ERROR);

        /* terminator */
        return (gettoken_term(mf, MSG_ORIG(MSG_MAPKW_REQUIRE)));
}

/*
 * dir_depend_versions(): Expected object name is not present
 */
static void
gts_efunc_dir_depend_versions(Mapfile *mf, Token tok, ld_map_tkval_t *tkv)
{
        Conv_inv_buf_t  inv_buf;

        mf_fatal(mf, MSG_INTL(MSG_MAP_EXP_OBJNAM),
            MSG_ORIG(MSG_MAPKW_DEPEND_VERSIONS),
            ld_map_tokenstr(tok, tkv, &inv_buf));
}

/*
 * Top Level Directive:
 *
 * DEPEND_VERSIONS object_name { ATTR = ...
 * ---------------^
 */
static Token
dir_depend_versions(Mapfile *mf)
{
        /* DEPEND_VERSIONS attributes */
        static attr_t attr_list[] = {
                { MSG_ORIG(MSG_MAPKW_ALLOW),    at_dv_allow,    ATTR_FMT_EQ },
                { MSG_ORIG(MSG_MAPKW_REQUIRE),  at_dv_require,  ATTR_FMT_EQ },

                /* List must be null terminated */
                { 0 }
        };

        /*
         * Size of buffer needed to format the names in attr_list[]. Must
         * be kept in sync with attr_list.
         */
        static size_t   attr_list_bufsize =
            KW_NAME_SIZE(MSG_MAPKW_ALLOW) +
            KW_NAME_SIZE(MSG_MAPKW_REQUIRE);

        ld_map_tkval_t  tkv;
        Sdf_desc        *sdf;

        /* object_name */
        if (gettoken_str(mf, 0, &tkv, gts_efunc_dir_depend_versions) ==
            TK_ERROR)
                return (TK_ERROR);

        /* Get descriptor for dependency */
        if ((sdf = ld_map_dv(mf, tkv.tkv_str)) == NULL)
                return (TK_ERROR);

        /* Opening '{' token */
        if (gettoken_leftbkt(mf, tkv.tkv_str) == TK_ERROR)
                return (TK_ERROR);

        /* Parse the attributes */
        if (parse_attributes(mf, MSG_ORIG(MSG_MAPKW_DEPEND_VERSIONS),
            attr_list, attr_list_bufsize, sdf) == TK_ERROR)
                return (TK_ERROR);

        /* Terminating ';' */
        return (gettoken_semicolon(mf, MSG_ORIG(MSG_MAPKW_DEPEND_VERSIONS)));
}

/*
 * Top Level Directive:
 *
 * HDR_NOALLOC ;
 * -----------^
 */
static Token
dir_hdr_noalloc(Mapfile *mf)
{
        mf->mf_ofl->ofl_dtflags_1 |= DF_1_NOHDR;
        DBG_CALL(Dbg_map_hdr_noalloc(mf->mf_ofl->ofl_lml, mf->mf_lineno));

        /* ';' terminator token */
        return (gettoken_semicolon(mf, MSG_ORIG(MSG_MAPKW_HDR_NOALLOC)));
}

/*
 * Top Level Directive:
 *
 * PHDR_ADD_NULL = cnt ;
 * -------------^
 */
static Token
dir_phdr_add_null(Mapfile *mf)
{
        Sg_desc         *sgp;
        ld_map_tkval_t  tkv;            /* Value of token */

        /* '=' token */
        if (gettoken_eq(mf, ATTR_FMT_EQ,
            MSG_ORIG(MSG_MAPKW_PHDR_ADD_NULL)) == TK_ERROR)
                return (TK_ERROR);

        /* integer token */
        if (gettoken_int(mf, MSG_ORIG(MSG_MAPKW_PHDR_ADD_NULL), &tkv, 0) ==
            TK_ERROR)
                return (TK_ERROR);

        while (tkv.tkv_int.tkvi_value-- > 0) {
                if ((sgp = ld_map_seg_alloc(NULL, PT_NULL,
                    FLG_SG_P_TYPE | FLG_SG_EMPTY)) == NULL)
                        return (TK_ERROR);
                if (ld_map_seg_insert(mf, DBG_STATE_NEW, sgp, 0) ==
                    SEG_INS_FAIL)
                        return (TK_ERROR);
        }

        /* ';' terminator token */
        return (gettoken_semicolon(mf, MSG_ORIG(MSG_MAPKW_PHDR_ADD_NULL)));
}

/*
 * segment_directive segment_name { ALIGN = value
 * ----------------------------------------^
 */
static Token
at_seg_align(Mapfile *mf, Token eq_tok, void *uvalue)
{
        Sg_desc         *sgp = uvalue;
        ld_map_tkval_t  tkv;

        /* value */
        if (gettoken_int(mf, MSG_ORIG(MSG_MAPKW_ALIGN), &tkv, 0) == TK_ERROR)
                return (TK_ERROR);

        sgp->sg_phdr.p_align = tkv.tkv_int.tkvi_value;
        sgp->sg_flags |= FLG_SG_P_ALIGN;

        /* terminator */
        return (gettoken_term(mf, MSG_ORIG(MSG_MAPKW_ALIGN)));
}

/*
 * at_seg_assign_file_basename(): Value for FILE_BASENAME= is not a file name
 */
static void
gts_efunc_at_seg_assign_file_basename(Mapfile *mf, Token tok,
    ld_map_tkval_t *tkv)
{
        Conv_inv_buf_t  inv_buf;

        mf_fatal(mf, MSG_INTL(MSG_MAP_EXP_FILNAM),
            MSG_ORIG(MSG_MAPKW_FILE_BASENAME),
            ld_map_tokenstr(tok, tkv, &inv_buf));
}

/*
 * segment_directive segment_name { ASSIGN { FILE_BASENAME = file_name
 * ---------------------------------------------------------^
 */
static Token
at_seg_assign_file_basename(Mapfile *mf, Token eq_tok, void *uvalue)
{
        Ent_desc        *enp = uvalue;
        ld_map_tkval_t  tkv;

        /* file_name */
        if (gettoken_str(mf, 0, &tkv, gts_efunc_at_seg_assign_file_basename) ==
            TK_ERROR)
                return (TK_ERROR);

        if (!ld_map_seg_ent_files(mf, enp, TYP_ECF_BASENAME, tkv.tkv_str))
                return (TK_ERROR);

        /* terminator */
        return (gettoken_term(mf, MSG_ORIG(MSG_MAPKW_FILE_BASENAME)));
}

/*
 * at_seg_assign_file_objname(): Value for FILE_OBJNAME= is not an object name
 */
static void
gts_efunc_at_seg_assign_file_objname(Mapfile *mf, Token tok,
    ld_map_tkval_t *tkv)
{
        Conv_inv_buf_t  inv_buf;

        mf_fatal(mf, MSG_INTL(MSG_MAP_EXP_OBJNAM),
            MSG_ORIG(MSG_MAPKW_FILE_OBJNAME),
            ld_map_tokenstr(tok, tkv, &inv_buf));
}

/*
 * segment_directive segment_name { ASSIGN { FILE_OBJNAME = name
 * --------------------------------------------------------^
 */
static Token
at_seg_assign_file_objname(Mapfile *mf, Token eq_tok, void *uvalue)
{
        Ent_desc        *enp = uvalue;
        ld_map_tkval_t  tkv;

        /* file_objname */
        if (gettoken_str(mf, 0, &tkv, gts_efunc_at_seg_assign_file_objname) ==
            TK_ERROR)
                return (TK_ERROR);

        if (!ld_map_seg_ent_files(mf, enp, TYP_ECF_OBJNAME, tkv.tkv_str))
                return (TK_ERROR);

        /* terminator */
        return (gettoken_term(mf, MSG_ORIG(MSG_MAPKW_FILE_OBJNAME)));
}

/*
 * at_seg_assign_file_path(): Value for FILE_PATH= is not a file path
 */
static void
gts_efunc_at_seg_assign_file_path(Mapfile *mf, Token tok, ld_map_tkval_t *tkv)
{
        Conv_inv_buf_t  inv_buf;

        mf_fatal(mf, MSG_INTL(MSG_MAP_EXP_FILPATH),
            MSG_ORIG(MSG_MAPKW_FILE_PATH),
            ld_map_tokenstr(tok, tkv, &inv_buf));
}

/*
 * segment_directive segment_name { ASSIGN { FILE_PATH = file_path
 * -----------------------------------------------------^
 */
static Token
at_seg_assign_file_path(Mapfile *mf, Token eq_tok, void *uvalue)
{
        Ent_desc        *enp = uvalue;
        ld_map_tkval_t  tkv;

        /* file_path */
        if (gettoken_str(mf, 0, &tkv, gts_efunc_at_seg_assign_file_path) ==
            TK_ERROR)
                return (TK_ERROR);

        if (!ld_map_seg_ent_files(mf, enp, TYP_ECF_PATH, tkv.tkv_str))
                return (TK_ERROR);

        /* terminator */
        return (gettoken_term(mf, MSG_ORIG(MSG_MAPKW_FILE_PATH)));
}

/*
 * segment_directive segment_name { ASSIGN { FLAGS = ... ;
 * -------------------------------------------------^
 */
static Token
at_seg_assign_flags(Mapfile *mf, Token eq_tok, void *uvalue)
{
        typedef struct {
                const char      *name;
                Word            value;
        } secflag_t;
        static secflag_t flag_list[] = {
                { MSG_ORIG(MSG_MAPKW_ALLOC),            SHF_ALLOC },
                { MSG_ORIG(MSG_MAPKW_EXECUTE),          SHF_EXECINSTR },
                { MSG_ORIG(MSG_MAPKW_WRITE),            SHF_WRITE },
                { MSG_ORIG(MSG_MAPKW_AMD64_LARGE),      SHF_AMD64_LARGE },

                /* List must be null terminated */
                { 0 },
        };

        /*
         * Size of buffer needed to format the names in flag_list[]. Must
         * be kept in sync with flag_list.
         */
        static size_t   flag_list_bufsize =
            KW_NAME_SIZE(MSG_MAPKW_ALLOC) +
            KW_NAME_SIZE(MSG_MAPKW_EXECUTE) +
            KW_NAME_SIZE(MSG_MAPKW_WRITE) +
            KW_NAME_SIZE(MSG_MAPKW_AMD64_LARGE);

        Ent_desc        *enp = uvalue;
        int             bcnt = 0, cnt = 0;
        secflag_t       *flag;
        int             done;
        Token           tok;
        ld_map_tkval_t  tkv;
        Conv_inv_buf_t  inv_buf;

        /* Read and process tokens until the closing terminator is seen */
        for (done = 0; done == 0; ) {
                switch (tok = ld_map_gettoken(mf, 0, &tkv)) {
                case TK_ERROR:
                        return (TK_ERROR);

                case TK_BANG:
                        /* Ensure ! only specified once per flag */
                        if (bcnt != 0) {
                                mf_fatal0(mf, MSG_INTL(MSG_MAP_SFLG_ONEBANG));
                                return (TK_ERROR);
                        }
                        bcnt++;
                        break;

                case TK_STRING:
                        flag = ld_map_kwfind(tkv.tkv_str, flag_list,
                            SGSOFFSETOF(secflag_t, name), sizeof (flag[0]));
                        if (flag == NULL)
                                goto bad_flag;
                        cnt++;
                        enp->ec_attrmask |= flag->value;
                        if (bcnt == 0)
                                enp->ec_attrbits |=  flag->value;
                        bcnt = 0;
                        break;

                case TK_RIGHTBKT:
                case TK_SEMICOLON:
                        done = 1;
                        break;

                default:
                bad_flag:
                        {
                                char buf[VLA_SIZE(flag_list_bufsize)];

                                mf_fatal(mf, MSG_INTL(MSG_MAP_EXP_SECFLAG),
                                    ld_map_kwnames(flag_list,
                                    SGSOFFSETOF(secflag_t, name),
                                    sizeof (flag[0]), buf, flag_list_bufsize),
                                    ld_map_tokenstr(tok, &tkv, &inv_buf));
                        }
                        return (TK_ERROR);
                }
        }

        /*
         * Ensure that a trailing '!' was not left at the end of the line
         * without a corresponding flag to apply it to.
         */
        if (bcnt != 0) {
                mf_fatal0(mf, MSG_INTL(MSG_MAP_SFLG_EXBANG));
                return (TK_ERROR);
        }

        /* Make sure there was at least one flag */
        if (cnt == 0) {
                mf_fatal(mf, MSG_INTL(MSG_MAP_NOVALUES),
                    MSG_ORIG(MSG_MAPKW_FLAGS));
                return (TK_ERROR);
        }

        return (tok);           /* Either TK_SEMICOLON or TK_RIGHTBKT */
}

/*
 * at_seg_assign_is_name(): Value for IS_NAME= is not a section name
 */
static void
gts_efunc_at_seg_assign_is_name(Mapfile *mf, Token tok, ld_map_tkval_t *tkv)
{
        Conv_inv_buf_t  inv_buf;

        mf_fatal(mf, MSG_INTL(MSG_MAP_EXP_SECNAM),
            MSG_ORIG(MSG_MAPKW_IS_NAME), ld_map_tokenstr(tok, tkv, &inv_buf));
}

/*
 * segment_directive segment_name { ASSIGN { IS_NAME = section_name ;
 * ---------------------------------------------------^
 */
static Token
at_seg_assign_is_name(Mapfile *mf, Token eq_tok, void *uvalue)
{
        Ent_desc        *enp = uvalue;
        ld_map_tkval_t  tkv;

        /* section_name */
        if (gettoken_str(mf, 0, &tkv, gts_efunc_at_seg_assign_is_name) ==
            TK_ERROR)
                return (TK_ERROR);
        enp->ec_is_name = tkv.tkv_str;

        /* terminator */
        return (gettoken_term(mf, MSG_ORIG(MSG_MAPKW_IS_NAME)));
}

/*
 * at_seg_assign_type(): Value for TYPE= is not a section type
 */
static void
gts_efunc_at_seg_assign_type(Mapfile *mf, Token tok, ld_map_tkval_t *tkv)
{
        Conv_inv_buf_t  inv_buf;

        mf_fatal(mf, MSG_INTL(MSG_MAP_EXP_SHTYPE),
            ld_map_tokenstr(tok, tkv, &inv_buf));
}

/*
 * segment_directive segment_name { ASSIGN { TYPE = section_type ;
 * ------------------------------------------------^
 */
static Token
at_seg_assign_type(Mapfile *mf, Token eq_tok, void *uvalue)
{
        Ent_desc                *enp = uvalue;
        ld_map_tkval_t          tkv;
        conv_strtol_uvalue_t    conv_uvalue;

        /* section type */
        if (gettoken_str(mf, TK_F_KEYWORD, &tkv,
            gts_efunc_at_seg_assign_type) == TK_ERROR)
                return (TK_ERROR);

        /*
         * Use the libconv iteration facility to map the given name to
         * its value. This allows us to keep up with any new sections
         * without having to change this code.
         */
        if (conv_iter_strtol_init(tkv.tkv_str, &conv_uvalue) != 0) {
                conv_iter_ret_t status;

                /* Look at the canonical form */
                status = conv_iter_sec_type(CONV_OSABI_ALL, CONV_MACH_ALL,
                    CONV_FMT_ALT_CF, conv_iter_strtol, &conv_uvalue);

                /* Failing that, look at the normal form */
                if (status != CONV_ITER_DONE)
                        (void) conv_iter_sec_type(CONV_OSABI_ALL,
                            CONV_MACH_ALL, CONV_FMT_ALT_NF, conv_iter_strtol,
                            &conv_uvalue);

                /* If we didn't match anything report error */
                if (!conv_uvalue.csl_found) {
                        gts_efunc_at_seg_assign_type(mf, TK_STRING, &tkv);
                        return (TK_ERROR);
                }
        }

        enp->ec_type = conv_uvalue.csl_value;

        /* terminator */
        return (gettoken_term(mf, MSG_ORIG(MSG_MAPKW_TYPE)));
}

/*
 * segment_directive segment_name { ASSIGN { ...
 * -----------------------------------------^
 */
static Token
at_seg_assign(Mapfile *mf, Token eq_tok, void *uvalue)
{
        /* segment_directive ASSIGN sub-attributes */
        static attr_t attr_list[] = {
                { MSG_ORIG(MSG_MAPKW_FILE_BASENAME),
                    at_seg_assign_file_basename,        ATTR_FMT_EQ },
                { MSG_ORIG(MSG_MAPKW_FILE_OBJNAME),
                    at_seg_assign_file_objname,         ATTR_FMT_EQ },
                { MSG_ORIG(MSG_MAPKW_FILE_PATH),
                    at_seg_assign_file_path,            ATTR_FMT_EQ },
                { MSG_ORIG(MSG_MAPKW_FLAGS),
                    at_seg_assign_flags,                ATTR_FMT_EQ_ALL },
                { MSG_ORIG(MSG_MAPKW_IS_NAME),
                    at_seg_assign_is_name,              ATTR_FMT_EQ },
                { MSG_ORIG(MSG_MAPKW_TYPE),
                    at_seg_assign_type,                 ATTR_FMT_EQ },

                /* List must be null terminated */
                { 0 }
        };

        /*
         * Size of buffer needed to format the names in attr_list[]. Must
         * be kept in sync with attr_list.
         */
        static size_t   attr_list_bufsize =
            KW_NAME_SIZE(MSG_MAPKW_FILE_BASENAME) +
            KW_NAME_SIZE(MSG_MAPKW_FILE_PATH) +
            KW_NAME_SIZE(MSG_MAPKW_FLAGS) +
            KW_NAME_SIZE(MSG_MAPKW_FILE_OBJNAME) +
            KW_NAME_SIZE(MSG_MAPKW_IS_NAME) +
            KW_NAME_SIZE(MSG_MAPKW_TYPE);

        Sg_desc         *sgp = uvalue;
        Token           tok;
        ld_map_tkval_t  tkv;
        Conv_inv_buf_t  inv_buf;
        const char      *name = NULL;
        Ent_desc        *enp;

        /*
         * ASSIGN takes an optional name, plus attributes are optional,
         * so expect a name, an opening '{', or a ';'.
         */
        tok = ld_map_gettoken(mf, 0, &tkv);
        switch (tok) {
        case TK_ERROR:
                return (TK_ERROR);

        case TK_STRING:
                name = tkv.tkv_str;
                tok = ld_map_gettoken(mf, 0, &tkv);
                break;
        }

        /* Add a new entrance criteria descriptor to the segment */
        if ((enp = ld_map_seg_ent_add(mf, sgp, name)) == NULL)
                return (TK_ERROR);

        /* Having handled the name, expect either '{' or ';' */
        switch (tok) {
        default:
                mf_fatal(mf, MSG_INTL(MSG_MAP_EXP_SEMLBKT),
                    MSG_ORIG(MSG_MAPKW_ASSIGN_SECTION),
                    ld_map_tokenstr(tok, &tkv, &inv_buf));
                return (TK_ERROR);
        case TK_ERROR:
                return (TK_ERROR);
        case TK_SEMICOLON:
        case TK_RIGHTBKT:
                /* No attributes: It will match anything */
                enp->ec_flags |= FLG_EC_CATCHALL;
                break;
        case TK_LEFTBKT:
                /* Parse the attributes */
                if (parse_attributes(mf, MSG_ORIG(MSG_MAPKW_ASSIGN_SECTION),
                    attr_list, attr_list_bufsize, enp) == TK_ERROR)
                        return (TK_ERROR);

                /* Terminating ';',  or '}' which also terminates caller */
                tok = gettoken_term(mf, MSG_ORIG(MSG_MAPKW_ASSIGN_SECTION));
                if (tok == TK_ERROR)
                        return (TK_ERROR);
                break;
        }

        DBG_CALL(Dbg_map_ent(mf->mf_ofl->ofl_lml, enp, mf->mf_ofl,
            mf->mf_lineno));
        return (tok);
}

/*
 * segment_directive segment_name { DISABLE ;
 * ----------------------------------------^
 */
static Token
at_seg_disable(Mapfile *mf, Token eq_tok, void *uvalue)
{
        Sg_desc         *sgp = uvalue;

        /* If the segment cannot be disabled, issue error */
        if (sgp->sg_flags & FLG_SG_NODISABLE) {
                mf_fatal(mf, MSG_INTL(MSG_MAP_CNTDISSEG), sgp->sg_name);
                return (TK_ERROR);
        }

        /* Disable the segment */
        sgp->sg_flags |= FLG_SG_DISABLED;

        /* terminator */
        return (gettoken_semicolon(mf, MSG_ORIG(MSG_MAPKW_DISABLE)));
}

/*
 * segment_directive segment_name { FLAGS eq-op ...
 * --------------------------------------------^
 *
 * Note that this routine is also used for the STACK directive,
 * as STACK also manipulates a segment descriptor.
 *
 * STACK { FLAGS eq-op ... ;
 * -------------------^
 */
static Token
at_seg_flags(Mapfile *mf, Token eq_tok, void *uvalue)
{
        Sg_desc         *sgp = uvalue;
        Token           tok;
        Xword           flags;

        tok = parse_segment_flags(mf, &flags);
        if (tok == TK_ERROR)
                return (TK_ERROR);

        setflags_eq(&sgp->sg_phdr.p_flags, eq_tok, flags);
        sgp->sg_flags |= FLG_SG_P_FLAGS;

        return (tok);
}

/*
 * segment_directive segment_name { IS_ORDER eq_op value
 * -----------------------------------------------^
 */
static Token
at_seg_is_order(Mapfile *mf, Token eq_tok, void *uvalue)
{
        Sg_desc         *sgp = uvalue;
        Token           tok;
        ld_map_tkval_t  tkv;
        Conv_inv_buf_t  inv_buf;
        int             done;
        Aliste          idx;
        Ent_desc        *enp, *enp2;

        /*
         * The '=' form of assignment resets the list. The list contains
         * pointers to our mapfile text, so we do not have to free anything.
         */
        if (eq_tok == TK_EQUAL)
                aplist_reset(sgp->sg_is_order);

        /*
         * One or more ASSIGN names, terminated by a semicolon.
         */
        for (done = 0; done == 0; ) {
                switch (tok = ld_map_gettoken(mf, 0, &tkv)) {
                case TK_ERROR:
                        return (TK_ERROR);

                case TK_STRING:
                        /*
                         * The referenced entrance criteria must have
                         * already been defined.
                         */
                        enp = ld_ent_lookup(mf->mf_ofl, tkv.tkv_str, NULL);
                        if (enp == NULL) {
                                mf_fatal(mf, MSG_INTL(MSG_MAP_UNKENT),
                                    tkv.tkv_str);
                                return (TK_ERROR);
                        }

                        /*
                         * Make sure it's not already on the list
                         */
                        for (APLIST_TRAVERSE(sgp->sg_is_order, idx, enp2))
                                if (enp == enp2) {
                                        mf_fatal(mf,
                                            MSG_INTL(MSG_MAP_DUP_IS_ORD),
                                            tkv.tkv_str);
                                        return (TK_ERROR);
                                }

                        /* Put it at the end of the order list */
                        if (aplist_append(&sgp->sg_is_order, enp,
                            AL_CNT_SG_IS_ORDER) == NULL)
                                return (TK_ERROR);
                        break;

                case TK_SEMICOLON:
                case TK_RIGHTBKT:
                        done = 1;
                        break;

                default:
                        mf_fatal(mf, MSG_INTL(MSG_MAP_EXP_ECNAM),
                            ld_map_tokenstr(tok, &tkv, &inv_buf));
                        return (TK_ERROR);
                }
        }

        return (tok);
}

/*
 * segment_directive segment_name { MAX_SIZE = value
 * -------------------------------------------^
 */
static Token
at_seg_max_size(Mapfile *mf, Token eq_tok, void *uvalue)
{
        Sg_desc         *sgp = uvalue;
        ld_map_tkval_t  tkv;

        /* value */
        if (gettoken_int(mf, MSG_ORIG(MSG_MAPKW_MAX_SIZE), &tkv, 0) == TK_ERROR)
                return (TK_ERROR);

        sgp->sg_length = tkv.tkv_int.tkvi_value;
        sgp->sg_flags |= FLG_SG_LENGTH;

        /* terminator */
        return (gettoken_term(mf, MSG_ORIG(MSG_MAPKW_MAX_SIZE)));
}

/*
 * segment_directive segment_name { NOHDR ;
 * --------------------------------------^
 */
static Token
at_seg_nohdr(Mapfile *mf, Token eq_tok, void *uvalue)
{
        Sg_desc         *sgp = uvalue;

        /*
         * Set the nohdr flag on the segment. If this segment is the
         * first loadable segment, the ELF and program headers will
         * not be included.
         *
         * The HDR_NOALLOC top level directive is preferred. This feature
         * exists to give 1:1 feature parity with version 1 mapfiles that
         * use the ?N segment flag and expect it to only take effect
         * if that segment ends up being first.
         */
        sgp->sg_flags |= FLG_SG_NOHDR;

        /* terminator */
        return (gettoken_semicolon(mf, MSG_ORIG(MSG_MAPKW_NOHDR)));
}

/*
 * segment_directive segment_name { OS_ORDER eq_op assign_name...
 * -----------------------------------------------^
 */
static Token
at_seg_os_order(Mapfile *mf, Token eq_tok, void *uvalue)
{
        Sg_desc         *sgp = uvalue;
        Token           tok;
        ld_map_tkval_t  tkv;
        Conv_inv_buf_t  inv_buf;
        int             done;

        /*
         * The '=' form of assignment resets the list. The list contains
         * pointers to our mapfile text, so we do not have to free anything.
         */
        if (eq_tok == TK_EQUAL)
                alist_reset(sgp->sg_os_order);

        /*
         * One or more section names, terminated by a semicolon.
         */
        for (done = 0; done == 0; ) {
                switch (tok = ld_map_gettoken(mf, 0, &tkv)) {
                case TK_ERROR:
                        return (TK_ERROR);

                case TK_STRING:
                        if (!ld_map_seg_os_order_add(mf, sgp, tkv.tkv_str))
                                return (TK_ERROR);
                        break;

                case TK_SEMICOLON:
                case TK_RIGHTBKT:
                        done = 1;
                        break;

                default:
                        mf_fatal(mf, MSG_INTL(MSG_MAP_EXP_SECNAM),
                            ld_map_tokenstr(tok, &tkv, &inv_buf));
                        return (TK_ERROR);
                }
        }

        return (tok);
}

/*
 * segment_directive segment_name { PADDR = paddr
 * ----------------------------------------^
 */
static Token
at_seg_paddr(Mapfile *mf, Token eq_tok, void *uvalue)
{
        Sg_desc         *sgp = uvalue, *sgp2;
        Aliste          idx;
        ld_map_tkval_t  tkv;

        /*
         * Ensure that the segment isn't in the segment order list.
         */
        for (APLIST_TRAVERSE(mf->mf_ofl->ofl_segs_order, idx, sgp2))
                if (sgp == sgp2) {
                        mf_fatal(mf,
                            MSG_INTL(MSG_MAP_CNTADDRORDER), sgp->sg_name);
                        return (TK_ERROR);
                }

        /* value */
        if (gettoken_int(mf, MSG_ORIG(MSG_MAPKW_PADDR), &tkv, 0) == TK_ERROR)
                return (TK_ERROR);

        sgp->sg_phdr.p_paddr = tkv.tkv_int.tkvi_value;
        sgp->sg_flags |= FLG_SG_P_PADDR;

        /* terminator */
        return (gettoken_term(mf, MSG_ORIG(MSG_MAPKW_PADDR)));
}

/*
 * segment_directive segment_name { ROUND = value
 * ----------------------------------------^
 */
static Token
at_seg_round(Mapfile *mf, Token eq_tok, void *uvalue)
{
        Sg_desc         *sgp = uvalue;
        ld_map_tkval_t  tkv;

        /* value */
        if (gettoken_int(mf, MSG_ORIG(MSG_MAPKW_ROUND), &tkv, 0) == TK_ERROR)
                return (TK_ERROR);

        sgp->sg_round = tkv.tkv_int.tkvi_value;
        sgp->sg_flags |= FLG_SG_ROUND;

        /* terminator */
        return (gettoken_term(mf, MSG_ORIG(MSG_MAPKW_ROUND)));
}

/*
 * segment_directive segment_name { SIZE_SYMBOL = symbol_name
 * ----------------------------------------------^
 */
static Token
at_seg_size_symbol(Mapfile *mf, Token eq_tok, void *uvalue)
{
        Sg_desc         *sgp = uvalue;
        Token           tok;
        ld_map_tkval_t  tkv;
        Conv_inv_buf_t  inv_buf;
        int             done, cnt = 0;

        /*
         * One or more symbol names, terminated by a semicolon.
         */
        for (done = 0; done == 0; ) {
                switch (tok = ld_map_gettoken(mf, 0, &tkv)) {
                case TK_ERROR:
                        return (TK_ERROR);

                case TK_STRING:
                        if (!ld_map_seg_size_symbol(mf, sgp, eq_tok,
                            tkv.tkv_str))
                                return (TK_ERROR);
                        cnt++;

                        /*
                         * If the operator is TK_EQUAL, turn it into
                         * TK_PLUSEQ for any symbol names after the first.
                         * These additional symbols are added, and are not
                         * replacements for the first one.
                         */
                        eq_tok = TK_PLUSEQ;
                        break;

                case TK_SEMICOLON:
                case TK_RIGHTBKT:
                        done = 1;
                        break;

                default:
                        mf_fatal(mf, MSG_INTL(MSG_MAP_EXP_SYMNAM),
                            MSG_ORIG(MSG_MAPKW_SIZE_SYMBOL),
                            ld_map_tokenstr(tok, &tkv, &inv_buf));
                        return (TK_ERROR);
                }
        }

        /* Make sure there was at least one name */
        if (cnt == 0) {
                mf_fatal(mf, MSG_INTL(MSG_MAP_NOVALUES),
                    MSG_ORIG(MSG_MAPKW_SIZE_SYMBOL));
                return (TK_ERROR);
        }

        return (tok);
}

/*
 * segment_directive segment_name { VADDR = vaddr
 * ----------------------------------------^
 */
static Token
at_seg_vaddr(Mapfile *mf, Token eq_tok, void *uvalue)
{
        Sg_desc         *sgp = uvalue, *sgp2;
        Aliste          idx;
        ld_map_tkval_t  tkv;

        /*
         * Ensure that the segment isn't in the segment order list.
         */
        for (APLIST_TRAVERSE(mf->mf_ofl->ofl_segs_order, idx, sgp2))
                if (sgp == sgp2) {
                        mf_fatal(mf,
                            MSG_INTL(MSG_MAP_CNTADDRORDER), sgp->sg_name);
                        return (TK_ERROR);
                }

        /* value */
        if (gettoken_int(mf, MSG_ORIG(MSG_MAPKW_VADDR), &tkv, 0) == TK_ERROR)
                return (TK_ERROR);

        sgp->sg_phdr.p_vaddr = tkv.tkv_int.tkvi_value;
        sgp->sg_flags |= FLG_SG_P_VADDR;

        /* terminator */
        return (gettoken_term(mf, MSG_ORIG(MSG_MAPKW_VADDR)));
}

/*
 * Top Level Directive:
 *
 * {LOAD|NOTE|NULL}_SEGMENT segment_name { ...
 * ------------------------^
 *
 * Common implementation body for the family of segment directives. These
 * take the same syntax, and share a common subset of attributes. They differ
 * in the type of segments they handle and the specific attributes accepted.
 *
 * entry:
 *      mf - Mapfile descriptor ({LOAD|NOTE|NULL}_SEGMENT)
 *      dir_name - Name of directive.
 *      seg_type - Type of segment (PT_LOAD, PT_NOTE, PT_NULL).
 *      attr_list - NULL terminated attribute array
 *      attr_list_bufsize - Size of required buffer to format all the
 *              names in attr_list.
 *      gts_efunc - Error function to pass to gettoken_str() when trying
 *              to obtain a segment name token.
 */
static Token
dir_segment_inner(Mapfile *mf, const char *dir_name, Word seg_type,
    attr_t *attr_list, size_t attr_list_bufsize, gts_efunc_t gts_efunc)
{
        Token           tok;
        ld_map_tkval_t  tkv;
        Sg_desc         *sgp;
        Boolean         new_segment;
        Xword           ndx;
        avl_index_t     where;

        /* segment_name */
        if (gettoken_str(mf, 0, &tkv, gts_efunc) == TK_ERROR)
                return (TK_ERROR);
        sgp = ld_seg_lookup(mf->mf_ofl, tkv.tkv_str, &where);
        new_segment = (sgp == NULL);

        if (new_segment) {
                /* Allocate a descriptor for new segment */
                if ((sgp = ld_map_seg_alloc(tkv.tkv_str, seg_type,
                    FLG_SG_P_TYPE)) == NULL)
                        return (TK_ERROR);
        } else {
                /* Make sure it's the right type of segment */
                if (sgp->sg_phdr.p_type != seg_type) {
                        Conv_inv_buf_t  inv_buf;

                        mf_fatal(mf, MSG_INTL(MSG_MAP_EXPSEGTYPE),
                            conv_phdr_type(ELFOSABI_SOLARIS, ld_targ.t_m.m_mach,
                            sgp->sg_phdr.p_type, CONV_FMT_ALT_CF, &inv_buf),
                            dir_name, tkv.tkv_str);
                        return (TK_ERROR);
                }

                /* If it was disabled, being referenced enables it */
                sgp->sg_flags &= ~FLG_SG_DISABLED;

                if (DBG_ENABLED) {
                        /*
                         * Not a new segment, so show the initial value
                         * before modifying it.
                         */
                        ndx = ld_map_seg_index(mf, sgp);
                        DBG_CALL(Dbg_map_seg(mf->mf_ofl, DBG_STATE_MOD_BEFORE,
                            ndx, sgp, mf->mf_lineno));
                }
        }

        /*
         * Attributes are optional, so expect an opening '{', or a ';'.
         */
        switch (tok = gettoken_optattr(mf, dir_name)) {
        default:
                tok = TK_ERROR;
                break;
        case TK_SEMICOLON:
                break;
        case TK_LEFTBKT:
                /* Parse the attributes */
                if (parse_attributes(mf, dir_name,
                    attr_list, attr_list_bufsize, sgp) == TK_ERROR)
                        return (TK_ERROR);

                /* Terminating ';' */
                tok = gettoken_semicolon(mf, dir_name);
                if (tok == TK_ERROR)
                        return (TK_ERROR);

                break;
        }

        /*
         * If this is a new segment, finish its initialization
         * and insert it into the segment list.
         */
        if (new_segment) {
                if (ld_map_seg_insert(mf, DBG_STATE_NEW, sgp, where) ==
                    SEG_INS_FAIL)
                        return (TK_ERROR);
        } else {
                /* Not new. Show what's changed */
                DBG_CALL(Dbg_map_seg(mf->mf_ofl, DBG_STATE_MOD_AFTER,
                    ndx, sgp, mf->mf_lineno));
        }

        return (tok);
}

/*
 * dir_load_segment(): Expected loadable segment name is not present
 */
static void
gts_efunc_dir_load_segment(Mapfile *mf, Token tok, ld_map_tkval_t *tkv)
{
        Conv_inv_buf_t  inv_buf;

        mf_fatal(mf, MSG_INTL(MSG_MAP_EXP_SEGNAM),
            MSG_ORIG(MSG_MAPKW_LOAD_SEGMENT),
            ld_map_tokenstr(tok, tkv, &inv_buf));
}

/*
 * Top Level Directive:
 *
 * LOAD_SEGMENT segment_name { ...
 * ------------^
 */
static Token
dir_load_segment(Mapfile *mf)
{
        /* LOAD_SEGMENT attributes */
        static attr_t attr_list[] = {
                { MSG_ORIG(MSG_MAPKW_ALIGN),    at_seg_align,   ATTR_FMT_EQ },
                { MSG_ORIG(MSG_MAPKW_ASSIGN_SECTION),
                    at_seg_assign,      ATTR_FMT_NAME },
                { MSG_ORIG(MSG_MAPKW_DISABLE),  at_seg_disable, ATTR_FMT_NAME },
                { MSG_ORIG(MSG_MAPKW_FLAGS),    at_seg_flags,
                    ATTR_FMT_EQ_ALL },
                { MSG_ORIG(MSG_MAPKW_IS_ORDER), at_seg_is_order,
                    ATTR_FMT_EQ_PEQ },
                { MSG_ORIG(MSG_MAPKW_MAX_SIZE), at_seg_max_size, ATTR_FMT_EQ },
                { MSG_ORIG(MSG_MAPKW_NOHDR),    at_seg_nohdr,   ATTR_FMT_NAME },
                { MSG_ORIG(MSG_MAPKW_OS_ORDER), at_seg_os_order,
                    ATTR_FMT_EQ_PEQ },
                { MSG_ORIG(MSG_MAPKW_PADDR),    at_seg_paddr,   ATTR_FMT_EQ },
                { MSG_ORIG(MSG_MAPKW_ROUND),    at_seg_round,   ATTR_FMT_EQ },
                { MSG_ORIG(MSG_MAPKW_SIZE_SYMBOL),
                    at_seg_size_symbol, ATTR_FMT_EQ_PEQ },
                { MSG_ORIG(MSG_MAPKW_VADDR),    at_seg_vaddr,   ATTR_FMT_EQ },

                /* List must be null terminated */
                { 0 }
        };

        /*
         * Size of buffer needed to format the names in attr_list[]. Must
         * be kept in sync with attr_list.
         */
        static size_t   attr_list_bufsize =
            KW_NAME_SIZE(MSG_MAPKW_ALIGN) +
            KW_NAME_SIZE(MSG_MAPKW_ASSIGN_SECTION) +
            KW_NAME_SIZE(MSG_MAPKW_DISABLE) +
            KW_NAME_SIZE(MSG_MAPKW_FLAGS) +
            KW_NAME_SIZE(MSG_MAPKW_IS_ORDER) +
            KW_NAME_SIZE(MSG_MAPKW_MAX_SIZE) +
            KW_NAME_SIZE(MSG_MAPKW_PADDR) +
            KW_NAME_SIZE(MSG_MAPKW_ROUND) +
            KW_NAME_SIZE(MSG_MAPKW_OS_ORDER) +
            KW_NAME_SIZE(MSG_MAPKW_SIZE_SYMBOL) +
            KW_NAME_SIZE(MSG_MAPKW_VADDR);

        return (dir_segment_inner(mf, MSG_ORIG(MSG_MAPKW_LOAD_SEGMENT),
            PT_LOAD, attr_list, attr_list_bufsize, gts_efunc_dir_load_segment));

}

/*
 * Common shared segment directive attributes
 */
static attr_t segment_core_attr_list[] = {
        { MSG_ORIG(MSG_MAPKW_ASSIGN_SECTION), at_seg_assign, ATTR_FMT_NAME },
        { MSG_ORIG(MSG_MAPKW_DISABLE),  at_seg_disable,  ATTR_FMT_NAME },
        { MSG_ORIG(MSG_MAPKW_IS_ORDER), at_seg_is_order, ATTR_FMT_EQ_PEQ },
        { MSG_ORIG(MSG_MAPKW_OS_ORDER), at_seg_os_order, ATTR_FMT_EQ_PEQ },

        /* List must be null terminated */
        { 0 }
};

/*
 * Size of buffer needed to format the names in segment_core_attr_list[].
 * Must be kept in sync with segment_core_attr_list.
 */
static size_t   segment_core_attr_list_bufsize =
        KW_NAME_SIZE(MSG_MAPKW_ASSIGN_SECTION) +
        KW_NAME_SIZE(MSG_MAPKW_DISABLE) +
        KW_NAME_SIZE(MSG_MAPKW_IS_ORDER) +
        KW_NAME_SIZE(MSG_MAPKW_OS_ORDER);

/*
 * dir_note_segment(): Expected note segment name is not present
 */
static void
gts_efunc_dir_note_segment(Mapfile *mf, Token tok, ld_map_tkval_t *tkv)
{
        Conv_inv_buf_t  inv_buf;

        mf_fatal(mf, MSG_INTL(MSG_MAP_EXP_SEGNAM),
            MSG_ORIG(MSG_MAPKW_NOTE_SEGMENT),
            ld_map_tokenstr(tok, tkv, &inv_buf));
}

/*
 * Top Level Directive:
 *
 * NOTE_SEGMENT segment_name { ...
 * ------------^
 */
static Token
dir_note_segment(Mapfile *mf)
{
        return (dir_segment_inner(mf, MSG_ORIG(MSG_MAPKW_NOTE_SEGMENT),
            PT_NOTE, segment_core_attr_list, segment_core_attr_list_bufsize,
            gts_efunc_dir_note_segment));

}

/*
 * dir_null_segment(): Expected null segment name is not present
 */
static void
gts_efunc_dir_null_segment(Mapfile *mf, Token tok, ld_map_tkval_t *tkv)
{
        Conv_inv_buf_t  inv_buf;

        mf_fatal(mf, MSG_INTL(MSG_MAP_EXP_SEGNAM),
            MSG_ORIG(MSG_MAPKW_NULL_SEGMENT),
            ld_map_tokenstr(tok, tkv, &inv_buf));
}

/*
 * Top Level Directive:
 *
 * NULL_SEGMENT segment_name { ...
 * ------------^
 */
static Token
dir_null_segment(Mapfile *mf)
{
        return (dir_segment_inner(mf, MSG_ORIG(MSG_MAPKW_NULL_SEGMENT),
            PT_NULL, segment_core_attr_list, segment_core_attr_list_bufsize,
            gts_efunc_dir_null_segment));

}

/*
 * Top Level Directive:
 *
 * SEGMENT_ORDER segment_name ... ;
 */
static Token
dir_segment_order(Mapfile *mf)
{
        Token           tok;
        ld_map_tkval_t  tkv;
        Conv_inv_buf_t  inv_buf;
        Aliste          idx;
        Sg_desc         *sgp, *sgp2;
        int             done;

        /* Expect either a '=' or '+=' */
        tok = gettoken_eq(mf, ATTR_FMT_EQ_PEQ,
            MSG_ORIG(MSG_MAPKW_SEGMENT_ORDER));
        if (tok == TK_ERROR)
                return (TK_ERROR);

        DBG_CALL(Dbg_map_seg_order(mf->mf_ofl, ELFOSABI_SOLARIS,
            ld_targ.t_m.m_mach, DBG_STATE_MOD_BEFORE, mf->mf_lineno));

        /*
         * The '=' form of assignment resets the list. The list contains
         * pointers to our mapfile text, so we do not have to free anything.
         */
        if (tok == TK_EQUAL)
                aplist_reset(mf->mf_ofl->ofl_segs_order);

        /* Read segment names, and add to list until terminator (';') is seen */
        for (done = 0; done == 0; ) {
                switch (tok = ld_map_gettoken(mf, 0, &tkv)) {
                case TK_ERROR:
                        return (TK_ERROR);

                case TK_STRING:
                        /*
                         * The segment must have already been defined.
                         */
                        sgp = ld_seg_lookup(mf->mf_ofl, tkv.tkv_str, NULL);
                        if (sgp == NULL) {
                                mf_fatal(mf, MSG_INTL(MSG_MAP_UNKSEG),
                                    tkv.tkv_str);
                                return (TK_ERROR);
                        }

                        /*
                         * Make sure it's not already on the list
                         */
                        for (APLIST_TRAVERSE(mf->mf_ofl->ofl_segs_order,
                            idx, sgp2))
                                if (sgp == sgp2) {
                                        mf_fatal(mf,
                                            MSG_INTL(MSG_MAP_DUPORDSEG),
                                            MSG_ORIG(MSG_MAPKW_SEGMENT_ORDER),
                                            tkv.tkv_str);
                                        return (TK_ERROR);
                                }

                        /*
                         * It can't be ordered and also have an explicit
                         * paddr or vaddr.
                         */
                        if (sgp->sg_flags & (FLG_SG_P_PADDR | FLG_SG_P_VADDR)) {
                                mf_fatal(mf, MSG_INTL(MSG_MAP_CNTADDRORDER),
                                    sgp->sg_name);
                                return (TK_ERROR);
                        }


                        /* Put it at the end of the list */
                        if (aplist_append(&mf->mf_ofl->ofl_segs_order, sgp,
                            AL_CNT_SG_IS_ORDER) == NULL)
                                return (TK_ERROR);
                        break;

                case TK_SEMICOLON:
                        done = 1;
                        break;

                default:
                        mf_fatal(mf, MSG_INTL(MSG_MAP_EXP_SEGNAM),
                            MSG_ORIG(MSG_MAPKW_SEGMENT_ORDER),
                            ld_map_tokenstr(tok, &tkv, &inv_buf));
                        return (TK_ERROR);
                }
        }

        DBG_CALL(Dbg_map_seg_order(mf->mf_ofl, ELFOSABI_SOLARIS,
            ld_targ.t_m.m_mach, DBG_STATE_MOD_AFTER, mf->mf_lineno));

        return (tok);
}

/*
 * Top Level Directive:
 *
 * STACK { ...
 * -----^
 */
static Token
dir_stack(Mapfile *mf)
{
        /* STACK attributes */
        static attr_t attr_list[] = {
                { MSG_ORIG(MSG_MAPKW_FLAGS), at_seg_flags, ATTR_FMT_EQ_ALL },

                /* List must be null terminated */
                { 0 }
        };

        /*
         * Size of buffer needed to format the names in attr_list[]. Must
         * be kept in sync with attr_list.
         */
        static size_t   attr_list_bufsize =
            KW_NAME_SIZE(MSG_MAPKW_FLAGS);

        Sg_desc *sgp;
        Token   tok;


        /* Opening '{' token */
        if (gettoken_leftbkt(mf, MSG_ORIG(MSG_MAPKW_STACK)) == TK_ERROR)
                return (TK_ERROR);

        /* Fetch the PT_SUNWSTACK segment descriptor */
        sgp = ld_map_seg_stack(mf);

        /* Parse the attributes */
        if (parse_attributes(mf, MSG_ORIG(MSG_MAPKW_STACK),
            attr_list, attr_list_bufsize, sgp) == TK_ERROR)
                return (TK_ERROR);

        /* Terminating ';' */
        tok = gettoken_semicolon(mf, MSG_ORIG(MSG_MAPKW_STACK));
        if (tok == TK_ERROR)
                return (TK_ERROR);

        if (DBG_ENABLED) {
                Xword ndx = ld_map_seg_index(mf, sgp);

                Dbg_map_seg(mf->mf_ofl, DBG_STATE_MOD_AFTER, ndx, sgp,
                    mf->mf_lineno);
        }

        return (tok);
}

/*
 * at_sym_aux(): Value for AUXILIARY= is not an object name
 */
static void
gts_efunc_at_sym_aux(Mapfile *mf, Token tok, ld_map_tkval_t *tkv)
{
        Conv_inv_buf_t  inv_buf;

        mf_fatal(mf, MSG_INTL(MSG_MAP_EXP_OBJNAM),
            MSG_ORIG(MSG_MAPKW_AUX), ld_map_tokenstr(tok, tkv, &inv_buf));
}

/*
 * SYMBOL [version_name] { symbol_name { AUXILIARY = soname
 * -------------------------------------------------^
 */
static Token
at_sym_aux(Mapfile *mf, Token eq_tok, void *uvalue)
{
        symbol_state_t  *ss = uvalue;
        ld_map_tkval_t  tkv;

        /* auxiliary filter soname */
        if (gettoken_str(mf, 0, &tkv, gts_efunc_at_sym_aux) == TK_ERROR)
                return (TK_ERROR);

        ld_map_sym_filtee(mf, &ss->ss_mv, &ss->ss_ms, FLG_SY_AUXFLTR,
            tkv.tkv_str);

        /* terminator */
        return (gettoken_term(mf, MSG_ORIG(MSG_MAPKW_AUX)));
}

/*
 * at_sym_filter(): Value for FILTER= is not an object name
 */
static void
gts_efunc_at_sym_filter(Mapfile *mf, Token tok, ld_map_tkval_t *tkv)
{
        Conv_inv_buf_t  inv_buf;

        mf_fatal(mf, MSG_INTL(MSG_MAP_EXP_OBJNAM),
            MSG_ORIG(MSG_MAPKW_FILTER), ld_map_tokenstr(tok, tkv, &inv_buf));
}

/*
 * SYMBOL [version_name] { symbol_name { FILTER = soname
 * ----------------------------------------------^
 */
static Token
at_sym_filter(Mapfile *mf, Token eq_tok, void *uvalue)
{
        symbol_state_t  *ss = uvalue;
        ld_map_tkval_t  tkv;

        /* filter soname */
        if (gettoken_str(mf, 0, &tkv, gts_efunc_at_sym_filter) == TK_ERROR)
                return (TK_ERROR);

        ld_map_sym_filtee(mf, &ss->ss_mv, &ss->ss_ms, FLG_SY_STDFLTR,
            tkv.tkv_str);

        /* terminator */
        return (gettoken_term(mf, MSG_ORIG(MSG_MAPKW_FILTER)));
}

/*
 * SYMBOL [version_name] { symbol_name { FLAGS = ...
 * ---------------------------------------------^
 */
static Token
at_sym_flags(Mapfile *mf, Token eq_tok, void *uvalue)
{
        typedef struct {
                const char      *name;
                sd_flag_t       value;
        } symflag_t;

        static symflag_t symflag_list[] = {
                { MSG_ORIG(MSG_MAPKW_DIRECT),           FLG_SY_DIR },
                { MSG_ORIG(MSG_MAPKW_DYNSORT),          FLG_SY_DYNSORT },
                { MSG_ORIG(MSG_MAPKW_EXTERN),           FLG_SY_EXTERN },
                { MSG_ORIG(MSG_MAPKW_INTERPOSE),        FLG_SY_INTPOSE },
                { MSG_ORIG(MSG_MAPKW_NODIRECT),         FLG_SY_NDIR },
                { MSG_ORIG(MSG_MAPKW_NODYNSORT),        FLG_SY_NODYNSORT },
                { MSG_ORIG(MSG_MAPKW_PARENT),           FLG_SY_PARENT },

                /* List must be null terminated */
                { 0 }
        };

        /*
         * Size of buffer needed to format the names in flag_list[]. Must
         * be kept in sync with flag_list.
         */
        static size_t   symflag_list_bufsize =
            KW_NAME_SIZE(MSG_MAPKW_DIRECT) +
            KW_NAME_SIZE(MSG_MAPKW_DYNSORT) +
            KW_NAME_SIZE(MSG_MAPKW_EXTERN) +
            KW_NAME_SIZE(MSG_MAPKW_INTERPOSE) +
            KW_NAME_SIZE(MSG_MAPKW_NODIRECT) +
            KW_NAME_SIZE(MSG_MAPKW_NODYNSORT) +
            KW_NAME_SIZE(MSG_MAPKW_PARENT);

        symbol_state_t  *ss = uvalue;
        int             done;
        symflag_t       *symflag;
        int             cnt = 0;
        Token           tok;
        ld_map_tkval_t  tkv;
        Conv_inv_buf_t  inv_buf;
        Ofl_desc        *ofl = mf->mf_ofl;

        for (done = 0; done == 0; ) {
                switch (tok = ld_map_gettoken(mf, TK_F_KEYWORD, &tkv)) {
                case TK_ERROR:
                        return (TK_ERROR);

                case TK_STRING:
                        symflag = ld_map_kwfind(tkv.tkv_str, symflag_list,
                            SGSOFFSETOF(symflag_t, name), sizeof (symflag[0]));
                        if (symflag == NULL)
                                goto bad_flag;
                        cnt++;
                        /*
                         * Apply the flag:
                         *
                         * Although tempting to make all of this table-driven
                         * via added fields in symflag_t, there's enough
                         * variation in what each flag does to make that
                         * not quite worthwhile.
                         *
                         * Similarly, it is tempting to use common code to
                         * to do this work from map_support.c. However, the
                         * v1 code mixes unrelated things (flags, symbol types,
                         * value, size, etc) in single cascading series of
                         * strcmps, whereas our parsing separates those things
                         * from each other. Merging the code would require doing
                         * two strcmps for each item, or other complexity,
                         * which I judge not to be worthwhile.
                         */
                        switch (symflag->value) {
                        case FLG_SY_DIR:
                                ss->ss_ms.ms_sdflags |= FLG_SY_DIR;
                                ofl->ofl_flags |= FLG_OF_SYMINFO;
                                break;
                        case FLG_SY_DYNSORT:
                                ss->ss_ms.ms_sdflags |= FLG_SY_DYNSORT;
                                ss->ss_ms.ms_sdflags &= ~FLG_SY_NODYNSORT;
                                break;
                        case FLG_SY_EXTERN:
                                ss->ss_ms.ms_sdflags |= FLG_SY_EXTERN;
                                ofl->ofl_flags |= FLG_OF_SYMINFO;
                                break;
                        case FLG_SY_INTPOSE:
                                if (!(ofl->ofl_flags & FLG_OF_EXEC)) {
                                        mf_fatal0(mf,
                                            MSG_INTL(MSG_MAP_NOINTPOSE));
                                        ss->ss_mv.mv_errcnt++;
                                        break;
                                }
                                ss->ss_ms.ms_sdflags |= FLG_SY_INTPOSE;
                                ofl->ofl_flags |= FLG_OF_SYMINFO;
                                ofl->ofl_dtflags_1 |= DF_1_SYMINTPOSE;
                                break;
                        case FLG_SY_NDIR:
                                ss->ss_ms.ms_sdflags |= FLG_SY_NDIR;
                                ofl->ofl_flags |= FLG_OF_SYMINFO;
                                ofl->ofl_flags1 |=
                                    (FLG_OF1_NDIRECT | FLG_OF1_NGLBDIR);
                                break;
                        case FLG_SY_NODYNSORT:
                                ss->ss_ms.ms_sdflags &= ~FLG_SY_DYNSORT;
                                ss->ss_ms.ms_sdflags |= FLG_SY_NODYNSORT;
                                break;
                        case FLG_SY_PARENT:
                                ss->ss_ms.ms_sdflags |= FLG_SY_PARENT;
                                ofl->ofl_flags |= FLG_OF_SYMINFO;
                                break;
                        }
                        break;
                case TK_RIGHTBKT:
                case TK_SEMICOLON:
                        done = 1;
                        break;

                default:
                bad_flag:
                        {
                                char buf[VLA_SIZE(symflag_list_bufsize)];

                                mf_fatal(mf, MSG_INTL(MSG_MAP_EXP_SYMFLAG),
                                    ld_map_kwnames(symflag_list,
                                    SGSOFFSETOF(symflag_t, name),
                                    sizeof (symflag[0]), buf,
                                    symflag_list_bufsize),
                                    ld_map_tokenstr(tok, &tkv, &inv_buf));
                        }
                        return (TK_ERROR);
                }
        }

        /* Make sure there was at least one flag specified */
        if (cnt == 0) {
                mf_fatal(mf, MSG_INTL(MSG_MAP_NOVALUES),
                    MSG_ORIG(MSG_MAPKW_FLAGS));
                return (TK_ERROR);
        }

        return (tok);           /* Either TK_SEMICOLON or TK_RIGHTBKT */
}

/*
 * SYMBOL [version_name] { symbol_name { SIZE = value
 * --------------------------------------------^
 */
static Token
at_sym_size(Mapfile *mf, Token eq_tok, void *uvalue)
{
        symbol_state_t  *ss = uvalue;
        ld_map_tkval_t  tkv;

        /* value */
        if (gettoken_int(mf, MSG_ORIG(MSG_MAPKW_SIZE), &tkv,
            TK_F_MULOK) == TK_ERROR)
                return (TK_ERROR);

        ss->ss_ms.ms_size = tkv.tkv_int.tkvi_value;
        ss->ss_ms.ms_size_set = TRUE;

        /* terminator */
        return (gettoken_term(mf, MSG_ORIG(MSG_MAPKW_SIZE)));
}

typedef struct {
        const char      *name;          /* type name */
        Word            ms_shndx;       /* symbol section index */
        uchar_t         ms_type;        /* STT_ symbol type */
} at_sym_type_t;

static at_sym_type_t at_sym_type_list[] = {
        { MSG_ORIG(MSG_MAPKW_COMMON),   SHN_COMMON,     STT_OBJECT },
        { MSG_ORIG(MSG_MAPKW_DATA),     SHN_ABS,        STT_OBJECT },
        { MSG_ORIG(MSG_MAPKW_FUNCTION), SHN_ABS,        STT_FUNC },

        /* List must be null terminated */
        { 0 }
};

/*
 * Size of buffer needed to format the names in at_sym_type_list[]. Must
 * be kept in sync with at_sym_type_list.
 */
static size_t   at_sym_type_list_bufsize =
    KW_NAME_SIZE(MSG_MAPKW_COMMON) +
    KW_NAME_SIZE(MSG_MAPKW_DATA) +
    KW_NAME_SIZE(MSG_MAPKW_FUNCTION);

/*
 * at_sym_type(): Value for TYPE= is not a symbol type
 */
static void
gts_efunc_at_sym_type(Mapfile *mf, Token tok, ld_map_tkval_t *tkv)
{
        Conv_inv_buf_t  inv_buf;
        char            buf[VLA_SIZE(at_sym_type_list_bufsize)];

        mf_fatal(mf, MSG_INTL(MSG_MAP_EXP_SYMTYPE),
            ld_map_kwnames(at_sym_type_list, SGSOFFSETOF(at_sym_type_t, name),
            sizeof (at_sym_type_list[0]), buf, at_sym_type_list_bufsize),
            ld_map_tokenstr(tok, tkv, &inv_buf));
}

/*
 * SYMBOL [version_name] { symbol_name { TYPE = symbol_type
 * --------------------------------------------^
 */
static Token
at_sym_type(Mapfile *mf, Token eq_tok, void *uvalue)
{
        symbol_state_t  *ss = uvalue;
        at_sym_type_t   *type;
        ld_map_tkval_t  tkv;

        /* type keyword */
        if (gettoken_str(mf, TK_F_KEYWORD, &tkv, gts_efunc_at_sym_type) ==
            TK_ERROR)
                return (TK_ERROR);

        type = ld_map_kwfind(tkv.tkv_str, at_sym_type_list,
            SGSOFFSETOF(at_sym_type_t, name), sizeof (type[0]));
        if (type == NULL) {
                gts_efunc_at_sym_type(mf, TK_STRING, &tkv);
                return (TK_ERROR);
        }

        ss->ss_ms.ms_shndx = type->ms_shndx;
        ss->ss_ms.ms_sdflags |= FLG_SY_SPECSEC;
        ss->ss_ms.ms_type = type->ms_type;

        /* terminator */
        return (gettoken_term(mf, MSG_ORIG(MSG_MAPKW_TYPE)));
}

/*
 * SYMBOL [version_name] { symbol_name { VALUE = value
 * ---------------------------------------------^
 */
static Token
at_sym_value(Mapfile *mf, Token eq_tok, void *uvalue)
{
        symbol_state_t  *ss = uvalue;
        ld_map_tkval_t  tkv;

        /* value */
        if (gettoken_int(mf, MSG_ORIG(MSG_MAPKW_VALUE), &tkv, 0) == TK_ERROR)
                return (TK_ERROR);

        ss->ss_ms.ms_value = tkv.tkv_int.tkvi_value;
        ss->ss_ms.ms_value_set = TRUE;

        /* terminator */
        return (gettoken_term(mf, MSG_ORIG(MSG_MAPKW_VALUE)));
}

typedef struct {
        const char      *name;
        uchar_t         ms_type;
} at_ass_type_t;

static at_ass_type_t    at_ass_type_list[] = {
        /* Accept DATA as well to match sym attrs */
        { MSG_ORIG(MSG_MAPKW_DATA),     STT_OBJECT },
        { MSG_ORIG(MSG_MAPKW_OBJECT),   STT_OBJECT },
        { MSG_ORIG(MSG_MAPKW_FUNC),     STT_FUNC },
        /* Accept FUNCTION as well to match sym attrs */
        { MSG_ORIG(MSG_MAPKW_FUNCTION), STT_FUNC },
        { MSG_ORIG(MSG_MAPKW_SECTION),  STT_SECTION },
        { MSG_ORIG(MSG_MAPKW_FILE),     STT_FILE },
        { MSG_ORIG(MSG_MAPKW_COMMON),   STT_COMMON },
        { MSG_ORIG(MSG_MAPKW_TLS),      STT_TLS },
        { 0 }
};

static size_t   at_ass_type_list_bufsize =
    KW_NAME_SIZE(MSG_MAPKW_OBJECT) +
    KW_NAME_SIZE(MSG_MAPKW_FUNC) +
    KW_NAME_SIZE(MSG_MAPKW_FUNCTION) +
    KW_NAME_SIZE(MSG_MAPKW_SECTION) +
    KW_NAME_SIZE(MSG_MAPKW_FILE) +
    KW_NAME_SIZE(MSG_MAPKW_COMMON) +
    KW_NAME_SIZE(MSG_MAPKW_TLS);

static void
gts_efunc_at_ass_type(Mapfile *mf, Token tok, ld_map_tkval_t *tkv)
{
        Conv_inv_buf_t  inv_buf;
        char            buf[VLA_SIZE(at_ass_type_list_bufsize)];

        mf_fatal(mf, MSG_INTL(MSG_MAP_EXP_SYMTYPE),
            ld_map_kwnames(at_ass_type_list, SGSOFFSETOF(at_ass_type_t, name),
            sizeof (at_ass_type_list[0]), buf, at_ass_type_list_bufsize),
            ld_map_tokenstr(tok, tkv, &inv_buf));
}

typedef struct {
        const char      *name;          /* attribute name */
        Boolean         ms_bits;        /* bits? */
} at_ass_shattr_t;

static at_ass_shattr_t  at_ass_shattr_list[] = {
        { MSG_ORIG(MSG_MAPKW_BITS),     TRUE },
        { MSG_ORIG(MSG_MAPKW_NOBITS),   FALSE },
        { 0 }
};

static size_t   at_ass_shattr_list_bufsize =
    KW_NAME_SIZE(MSG_MAPKW_BITS) +
    KW_NAME_SIZE(MSG_MAPKW_NOBITS);

static void
gts_efunc_at_ass_shattr(Mapfile *mf, Token tok, ld_map_tkval_t *tkv)
{
        Conv_inv_buf_t  inv_buf;
        char            buf[VLA_SIZE(at_ass_shattr_list_bufsize)];

        mf_fatal(mf, MSG_INTL(MSG_MAP_EXP_SHATTRTYPE),
            ld_map_kwnames(at_ass_shattr_list,
            SGSOFFSETOF(at_ass_shattr_t, name), sizeof (at_ass_shattr_list[0]),
            buf, at_ass_shattr_list_bufsize),
            ld_map_tokenstr(tok, tkv, &inv_buf));
}

static Token
at_ass_shattr(Mapfile *mf, Token eq_tok, void *uvalue)
{
        symbol_state_t  *ss = uvalue;
        at_ass_shattr_t *shattr;
        ld_map_tkval_t  tkv;

        if (gettoken_str(mf, TK_F_KEYWORD, &tkv,
            gts_efunc_at_ass_shattr) == TK_ERROR)
                return (TK_ERROR);

        shattr = ld_map_kwfind(tkv.tkv_str, at_ass_shattr_list,
            SGSOFFSETOF(at_ass_shattr_t, name), sizeof (shattr[0]));

        if (shattr == NULL) {
                gts_efunc_at_ass_shattr(mf, TK_STRING, &tkv);
                return (TK_ERROR);
        }

        ss->ss_ma.ass_bits = shattr->ms_bits;
        ss->ss_ma.ass_enabled |= SYM_ASSERT_BITS;

        return (gettoken_term(mf, MSG_ORIG(MSG_MAPKW_SHATTR)));
}

typedef struct {
        const char      *name;          /* binding name */
        uchar_t         ms_bind;        /* STB_ value */
} at_ass_bind_t;

static at_ass_bind_t    at_ass_bind_list[] = {
        { MSG_ORIG(MSG_MAPKW_GLOBAL),   STB_GLOBAL },
        { MSG_ORIG(MSG_MAPKW_LOCAL),    STB_LOCAL },
        { MSG_ORIG(MSG_MAPKW_WEAK),     STB_WEAK },
        { 0 }
};

static size_t   at_ass_bind_list_bufsize =
    KW_NAME_SIZE(MSG_MAPKW_GLOBAL) +
    KW_NAME_SIZE(MSG_MAPKW_LOCAL) +
    KW_NAME_SIZE(MSG_MAPKW_WEAK);

static void
gts_efunc_at_ass_bind(Mapfile *mf, Token tok, ld_map_tkval_t *tkv)
{
        Conv_inv_buf_t  inv_buf;
        char            buf[VLA_SIZE(at_ass_bind_list_bufsize)];

        mf_fatal(mf, MSG_INTL(MSG_MAP_EXP_BINDTYPE),
            ld_map_kwnames(at_ass_bind_list, SGSOFFSETOF(at_ass_bind_t, name),
            sizeof (at_ass_bind_list[0]), buf, at_ass_bind_list_bufsize),
            ld_map_tokenstr(tok, tkv, &inv_buf));
}

static Token
at_ass_bind(Mapfile *mf, Token eq_tok, void *uvalue)
{
        symbol_state_t  *ss = uvalue;
        at_ass_bind_t   *bind;
        ld_map_tkval_t  tkv;

        if (gettoken_str(mf, TK_F_KEYWORD, &tkv,
            gts_efunc_at_ass_bind) == TK_ERROR)
                return (TK_ERROR);

        bind = ld_map_kwfind(tkv.tkv_str, at_ass_bind_list,
            SGSOFFSETOF(at_ass_bind_t, name), sizeof (bind[0]));

        if (bind == NULL) {
                gts_efunc_at_ass_bind(mf, TK_STRING, &tkv);
                return (TK_ERROR);
        }

        ss->ss_ma.ass_bind = bind->ms_bind;
        ss->ss_ma.ass_enabled |= SYM_ASSERT_BIND;

        return (gettoken_term(mf, MSG_ORIG(MSG_MAPKW_BIND)));
}

static Token
at_ass_size(Mapfile *mf, Token eq_tok, void *uvalue)
{
        symbol_state_t  *ss = uvalue;
        ld_map_tkval_t  tkv;

        if (gettoken_int(mf, MSG_ORIG(MSG_MAPKW_SIZE), &tkv,
            TK_F_MULOK) == TK_ERROR)
                return (TK_ERROR);

        ss->ss_ma.ass_size = tkv.tkv_int.tkvi_value;
        ss->ss_ma.ass_enabled |= SYM_ASSERT_SIZE;

        return (gettoken_term(mf, MSG_ORIG(MSG_MAPKW_SIZE)));
}

static Token
at_ass_alias(Mapfile *mf, Token eq_tok, void *uvalue)
{
        symbol_state_t  *ss = uvalue;
        ld_map_tkval_t  tkv;

        if (ld_map_gettoken(mf, 0, &tkv) != TK_STRING) {
                mf_fatal0(mf, MSG_INTL(MSG_MAP_BADALIAS));
                return (TK_ERROR);
        }

        ss->ss_ma.ass_alias = tkv.tkv_str;
        ss->ss_ma.ass_enabled |= SYM_ASSERT_ALIAS;

        return (gettoken_term(mf, MSG_ORIG(MSG_MAPKW_ALIAS)));
}

static Token
at_ass_type(Mapfile *mf, Token eq_tok, void *uvalue)
{
        ld_map_tkval_t   tkv;
        at_ass_type_t   *type;
        symbol_state_t  *ss = uvalue;

        if (gettoken_str(mf, TK_F_KEYWORD, &tkv,
            gts_efunc_at_ass_type) == TK_ERROR)
                return (TK_ERROR);

        type = ld_map_kwfind(tkv.tkv_str, at_ass_type_list,
            SGSOFFSETOF(at_sym_type_t, name), sizeof (type[0]));

        if (type == NULL) {
                gts_efunc_at_ass_type(mf, TK_STRING, &tkv);
                return (TK_ERROR);
        }

        ss->ss_ma.ass_type = type->ms_type;
        ss->ss_ma.ass_enabled |= SYM_ASSERT_TYPE;

        return (gettoken_term(mf, MSG_ORIG(MSG_MAPKW_ASSERT)));
}

static Token
at_sym_assert(Mapfile *mf, Token eq_tok, void *uvalue)
{
        static attr_t   attr_list[] = {
                { MSG_ORIG(MSG_MAPKW_ALIAS),    at_ass_alias,   ATTR_FMT_EQ },
                /*
                 * The Solaris manuals describe both BIND and BINDING, take both
                 * but prefer BINDING
                 */
                { MSG_ORIG(MSG_MAPKW_BIND),     at_ass_bind,    ATTR_FMT_EQ },
                { MSG_ORIG(MSG_MAPKW_BINDING),  at_ass_bind,    ATTR_FMT_EQ },
                { MSG_ORIG(MSG_MAPKW_SHATTR),   at_ass_shattr,  ATTR_FMT_EQ },
                { MSG_ORIG(MSG_MAPKW_SIZE),     at_ass_size,    ATTR_FMT_EQ },
                { MSG_ORIG(MSG_MAPKW_TYPE),     at_ass_type,    ATTR_FMT_EQ },
                { 0 }
        };

        static size_t   attr_list_bufsize =
            KW_NAME_SIZE(MSG_MAPKW_ALIAS) +
            KW_NAME_SIZE(MSG_MAPKW_BIND) +
            KW_NAME_SIZE(MSG_MAPKW_BINDING) +
            KW_NAME_SIZE(MSG_MAPKW_SHATTR) +
            KW_NAME_SIZE(MSG_MAPKW_SIZE) +
            KW_NAME_SIZE(MSG_MAPKW_TYPE);

        symbol_state_t *ss = uvalue;
        int done = 0;
        Token tok;
        ld_map_tkval_t tkv;
        Conv_inv_buf_t  inv_buf;

        /* Read assertions until the closing } */
        for (done = 0; done == 0; ) {
                switch (tok = ld_map_gettoken(mf, 0, &tkv)) {
                case TK_ERROR:
                        return (TK_ERROR);
                case TK_LEFTBKT:
                        if (parse_attributes(mf, ss->ss_ms.ms_name,
                            attr_list, attr_list_bufsize, ss) == TK_ERROR)
                                return (TK_ERROR);

                        /*
                         * If we're stating we're an alias for another symbol,
                         * the only other thing that maybe specified for
                         * _this_ symbol is its binding.
                         */
                        if ((ss->ss_ma.ass_enabled & SYM_ASSERT_ALIAS) &&
                            (ss->ss_ma.ass_enabled &
                            ~(SYM_ASSERT_ALIAS|SYM_ASSERT_BIND))) {
                                mf_fatal(mf, MSG_INTL(MSG_MAP_ALIAS_COMBO),
                                    ss->ss_ms.ms_name);
                                return (TK_ERROR);
                        }

                        tok = gettoken_term(mf, MSG_INTL(MSG_MAP_SYMATTR));
                        if (tok == TK_ERROR)
                                return (TK_ERROR);
                        if (tok == TK_SEMICOLON)
                                return (tok);
                        break;
                default:
                        mf_fatal(mf, MSG_INTL(MSG_MAP_EXP_SYMDELIM),
                            ld_map_tokenstr(tok, &tkv, &inv_buf));
                        return (TK_ERROR);
                }
        }

        /* If we drop through here, something is wrong */
        return (TK_ERROR);
}

/*
 * Parse the attributes for a SCOPE or VERSION symbol directive.
 *
 * entry:
 *      mf - Mapfile descriptor
 *      dir_name - Name of directive.
 *      ss - Pointer to symbol state block that has had its ss_mv
 *              member initialzed via a call to ld_map_sym_ver_init().
 *
 * exit:
 *      parse_symbol_attributes() returns TK_RIGHTBKT on success, and TK_ERROR
 *      on failure.
 */
static Token
parse_symbol_attributes(Mapfile *mf, const char *dir_name, symbol_state_t *ss)
{
        /* Symbol attributes */
        static attr_t attr_list[] = {
                { MSG_ORIG(MSG_MAPKW_AUX),      at_sym_aux,     ATTR_FMT_EQ },
                { MSG_ORIG(MSG_MAPKW_FILTER),   at_sym_filter,  ATTR_FMT_EQ },
                { MSG_ORIG(MSG_MAPKW_FLAGS),    at_sym_flags,   ATTR_FMT_EQ },
                { MSG_ORIG(MSG_MAPKW_SIZE),     at_sym_size,    ATTR_FMT_EQ },
                { MSG_ORIG(MSG_MAPKW_TYPE),     at_sym_type,    ATTR_FMT_EQ },
                { MSG_ORIG(MSG_MAPKW_VALUE),    at_sym_value,   ATTR_FMT_EQ },
                { MSG_ORIG(MSG_MAPKW_ASSERT),   at_sym_assert,  ATTR_FMT_EQ },

                /* List must be null terminated */
                { 0 }
        };

        /*
         * Size of buffer needed to format the names in attr_list[]. Must
         * be kept in sync with attr_list.
         */
        static size_t   attr_list_bufsize =
            KW_NAME_SIZE(MSG_MAPKW_AUX) +
            KW_NAME_SIZE(MSG_MAPKW_FILTER) +
            KW_NAME_SIZE(MSG_MAPKW_FLAGS) +
            KW_NAME_SIZE(MSG_MAPKW_SIZE) +
            KW_NAME_SIZE(MSG_MAPKW_TYPE) +
            KW_NAME_SIZE(MSG_MAPKW_VALUE) +
            KW_NAME_SIZE(MSG_MAPKW_ASSERT);

        Token           tok;
        ld_map_tkval_t  tkv, tkv_sym;
        int             done;
        Conv_inv_buf_t  inv_buf;

        /* Read attributes until the closing '}' is seen */
        for (done = 0; done == 0; ) {
                /*
                 * We have to allow quotes around symbol names, but the
                 * name we read may also be a symbol scope keyword. We won't
                 * know which until we read the following token, and so have
                 * to allow quotes for both. Hence, symbol scope names can
                 * be quoted --- an unlikely occurrence and not worth
                 * complicating the code.
                 */
                switch (tok = ld_map_gettoken(mf, 0, &tkv_sym)) {
                case TK_ERROR:
                        return (TK_ERROR);

                case TK_STRING:
                        /* Default value for all symbol attributes is 0 */
                        (void) memset(&ss->ss_ms, 0, sizeof (ss->ss_ms));
                        (void) memset(&ss->ss_ma, 0, sizeof (ss->ss_ma));
                        ss->ss_ms.ms_name = tkv_sym.tkv_str;
                        ss->ss_ma.ass_file = mf->mf_name;
                        ss->ss_ma.ass_lineno = mf->mf_lineno;

                        /*
                         * Turn off the WEAK flag to indicate that definitions
                         * are associated with this version. It would probably
                         * be more accurate to only remove this flag with the
                         * specification of global symbols, however setting it
                         * here allows enough slop to compensate for the
                         * various user inputs we've seen so far. Only if a
                         * closed version is specified (i.e., "SUNW_1.x {};")
                         * will a user get a weak version (which is how we
                         * document the creation of weak versions).
                         */
                        ss->ss_mv.mv_vdp->vd_flags &= ~VER_FLG_WEAK;

                        /*
                         * The meaning of this name depends on the following
                         * character:
                         *
                         *      :       Scope
                         *      ;       Symbol without attributes
                         *      {       Symbol with attributes
                         */
                        switch (tok = ld_map_gettoken(mf, 0, &tkv)) {
                        case TK_ERROR:
                                return (TK_ERROR);

                        case TK_COLON:
                                ld_map_sym_scope(mf, tkv_sym.tkv_str,
                                    &ss->ss_mv);
                                break;
                        case TK_LEFTBKT:
                                /* name is a symbol with attributes */
                                if (parse_attributes(mf, tkv_sym.tkv_str,
                                    attr_list, attr_list_bufsize, ss) ==
                                    TK_ERROR)
                                        return (TK_ERROR);
                                /* Terminating ';', or '}' */
                                tok = gettoken_term(mf,
                                    MSG_INTL(MSG_MAP_SYMATTR));
                                if (tok == TK_ERROR)
                                        return (TK_ERROR);
                                if (tok == TK_RIGHTBKT)
                                        done = 1;

                                /* FALLTHROUGH */
                        case TK_SEMICOLON: {
                                ld_map_sym_t *ms = &ss->ss_ms;
                                /*
                                 * If an EXTERN or PARENT symbol has
                                 * assertions issue an error, since we can't
                                 * check them.
                                 */
                                if ((ss->ss_ma.ass_enabled != 0) &&
                                    (((ms->ms_sdflags & FLG_SY_PARENT) != 0) ||
                                    ((ms->ms_sdflags & FLG_SY_EXTERN) != 0))) {
                                        mf_fatal(mf,
                                            MSG_INTL(MSG_MAP_EXTPAR_ASSERT),
                                            ms->ms_name);
                                        return (TK_ERROR);
                                }

                                /*
                                 * Add the new symbol. It should be noted that
                                 * all symbols added by the mapfile start out
                                 * with global scope, thus they will fall
                                 * through the normal symbol resolution
                                 * process.  Symbols defined as locals will
                                 * be reduced in scope after all input file
                                 * processing.
                                 */
                                if (!ld_map_sym_enter(mf, &ss->ss_mv,
                                    &ss->ss_ms, &ss->ss_ma))
                                        return (TK_ERROR);
                                break;
                        }
                        default:
                                mf_fatal(mf, MSG_INTL(MSG_MAP_EXP_SYMDELIM),
                                    ld_map_tokenstr(tok, &tkv, &inv_buf));
                                return (TK_ERROR);
                        }
                        break;

                case TK_RIGHTBKT:
                        done = 1;
                        break;

                case TK_SEMICOLON:
                        break;          /* Ignore empty statement */

                case TK_STAR:
                        /*
                         * Turn off the WEAK flag, as explained above for
                         * TK_STRING.
                         */
                        ss->ss_mv.mv_vdp->vd_flags &= ~VER_FLG_WEAK;

                        ld_map_sym_autoreduce(mf, &ss->ss_mv);

                        /*
                         * Following token must be ';' to terminate the stmt,
                         * or '}' to terminate the whole directive.
                         */
                        switch (tok = gettoken_term(mf, dir_name)) {
                        case TK_ERROR:
                                return (TK_ERROR);
                        case TK_RIGHTBKT:
                                done = 1;
                                break;
                        }
                        break;

                default:
                        mf_fatal(mf, MSG_INTL(MSG_MAP_EXP_SYM),
                            ld_map_tokenstr(tok, &tkv_sym, &inv_buf));
                        return (TK_ERROR);
                }
        }

        /*
         * In the SYMBOL directive, we keep parsing in the face of
         * errors that don't involve resources, to maximize what we
         * can report in a single invocation. If we encountered such
         * an error, act on the error(s) now.
         */
        if (ss->ss_mv.mv_errcnt)
                return (TK_ERROR);

        return (tok);
}


/*
 * Top Level Directive:
 *
 * SYMBOL_SCOPE { ...
 * ------------^
 */
static Token
dir_symbol_scope(Mapfile *mf)
{
        symbol_state_t  ss;

        /* The first token must be a '{' */
        if (gettoken_leftbkt(mf, MSG_ORIG(MSG_MAPKW_SYMBOL_SCOPE)) == TK_ERROR)
                return (TK_ERROR);

        /* Establish the version descriptor and related data */
        if (!ld_map_sym_ver_init(mf, NULL, &ss.ss_mv))
                return (TK_ERROR);

        /* Read attributes until the closing '}' is seen */
        if (parse_symbol_attributes(mf, MSG_ORIG(MSG_MAPKW_SYMBOL_SCOPE),
            &ss) == TK_ERROR)
                return (TK_ERROR);

        /* Terminating ';' */
        return (gettoken_semicolon(mf, MSG_ORIG(MSG_MAPKW_SYMBOL_SCOPE)));
}


/*
 * at_dv_allow(): Value for ALLOW= is not a version string
 */
static void
gts_efunc_dir_symbol_version(Mapfile *mf, Token tok, ld_map_tkval_t *tkv)
{
        Conv_inv_buf_t  inv_buf;

        mf_fatal(mf, MSG_INTL(MSG_MAP_EXP_VERSION),
            MSG_ORIG(MSG_MAPKW_SYMBOL_VERSION),
            ld_map_tokenstr(tok, tkv, &inv_buf));
}

/*
 * Top Level Directive:
 *
 * SYMBOL_VERSION version_name { ...
 * --------------^
 */
static Token
dir_symbol_version(Mapfile *mf)
{

        ld_map_tkval_t  tkv;
        symbol_state_t  ss;

        /* The first token must be a version name */
        if (gettoken_str(mf, 0, &tkv, gts_efunc_dir_symbol_version) == TK_ERROR)
                return (TK_ERROR);

        /* The next token is expected to be '{' */
        if (gettoken_leftbkt(mf, MSG_ORIG(MSG_MAPKW_SYMBOL_VERSION)) ==
            TK_ERROR)
                return (TK_ERROR);

        /* Establish the version descriptor and related data */
        if (!ld_map_sym_ver_init(mf, tkv.tkv_str, &ss.ss_mv))
                return (TK_ERROR);

        /* Read attributes until the closing '}' is seen */
        if (parse_symbol_attributes(mf, MSG_ORIG(MSG_MAPKW_SYMBOL_VERSION),
            &ss) == TK_ERROR)
                return (TK_ERROR);

        /*
         * Determine if any version references are provided after the close
         * bracket, parsing up to the terminating ';'.
         */
        if (!ld_map_sym_ver_fini(mf, &ss.ss_mv))
                return (TK_ERROR);

        return (TK_SEMICOLON);
}


/*
 * Parse the mapfile --- Solaris syntax
 */
Boolean
ld_map_parse_v2(Mapfile *mf)
{
        /* Valid top level mapfile directives */
        typedef struct {
                const char      *name;  /* Directive */
                dir_func_t      func;   /* Function to parse directive */
        } tldir_t;


        tldir_t dirlist[] = {
                { MSG_ORIG(MSG_MAPKW_CAPABILITY),       dir_capability },
                { MSG_ORIG(MSG_MAPKW_DEPEND_VERSIONS),  dir_depend_versions },
                { MSG_ORIG(MSG_MAPKW_HDR_NOALLOC),      dir_hdr_noalloc },
                { MSG_ORIG(MSG_MAPKW_LOAD_SEGMENT),     dir_load_segment },
                { MSG_ORIG(MSG_MAPKW_NOTE_SEGMENT),     dir_note_segment },
                { MSG_ORIG(MSG_MAPKW_NULL_SEGMENT),     dir_null_segment },
                { MSG_ORIG(MSG_MAPKW_PHDR_ADD_NULL),    dir_phdr_add_null },
                { MSG_ORIG(MSG_MAPKW_SEGMENT_ORDER),    dir_segment_order },
                { MSG_ORIG(MSG_MAPKW_STACK),            dir_stack },
                { MSG_ORIG(MSG_MAPKW_SYMBOL_SCOPE),     dir_symbol_scope },
                { MSG_ORIG(MSG_MAPKW_SYMBOL_VERSION),   dir_symbol_version },

                /* List must be null terminated */
                { 0 }
        };

        /*
         * Size of buffer needed to format the names in dirlist[]. Must
         * be kept in sync with dirlist.
         */
        static size_t dirlist_bufsize =
            KW_NAME_SIZE(MSG_MAPKW_CAPABILITY) +
            KW_NAME_SIZE(MSG_MAPKW_DEPEND_VERSIONS) +
            KW_NAME_SIZE(MSG_MAPKW_HDR_NOALLOC) +
            KW_NAME_SIZE(MSG_MAPKW_LOAD_SEGMENT) +
            KW_NAME_SIZE(MSG_MAPKW_NOTE_SEGMENT) +
            KW_NAME_SIZE(MSG_MAPKW_NULL_SEGMENT) +
            KW_NAME_SIZE(MSG_MAPKW_PHDR_ADD_NULL) +
            KW_NAME_SIZE(MSG_MAPKW_SEGMENT_ORDER) +
            KW_NAME_SIZE(MSG_MAPKW_STACK) +
            KW_NAME_SIZE(MSG_MAPKW_SYMBOL_SCOPE) +
            KW_NAME_SIZE(MSG_MAPKW_SYMBOL_VERSION);

        Token           tok;            /* current token. */
        ld_map_tkval_t  tkv;            /* Value of token */
        tldir_t         *tldir;
        Conv_inv_buf_t  inv_buf;

        for (;;) {
                tok = ld_map_gettoken(mf, TK_F_EOFOK | TK_F_KEYWORD, &tkv);
                switch (tok) {
                case TK_ERROR:
                        return (FALSE);
                case TK_EOF:
                        return (TRUE);
                case TK_SEMICOLON: /* Terminator, or empty directive: Ignore */
                        break;
                case TK_STRING:
                        /* Map name to entry in dirlist[] */
                        tldir = ld_map_kwfind(tkv.tkv_str, dirlist,
                            SGSOFFSETOF(tldir_t, name), sizeof (dirlist[0]));

                        /* Not a directive we know? */
                        if (tldir == NULL)
                                goto bad_dirtok;

                        /* Call the function associated with this directive */
                        if (tldir->func(mf) == TK_ERROR)
                                return (FALSE);
                        break;
                default:
                bad_dirtok:
                        {
                                char buf[VLA_SIZE(dirlist_bufsize)];

                                mf_fatal(mf, MSG_INTL(MSG_MAP_EXP_DIR),
                                    ld_map_kwnames(dirlist,
                                    SGSOFFSETOF(tldir_t, name),
                                    sizeof (dirlist[0]), buf, dirlist_bufsize),
                                    ld_map_tokenstr(tok, &tkv, &inv_buf));
                        }
                        return (FALSE);
                }
        }
}