/*
 * 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.
 */

#include	<string.h>
#include	<stdio.h>
#include	<debug.h>
#include	"msg.h"
#include	"_libld.h"

/*
 * Locate a version descriptor.
 */
Ver_desc *
ld_vers_find(const char *name, Word hash, APlist *alp)
{
	Aliste		idx;
	Ver_desc	*vdp;

	for (APLIST_TRAVERSE(alp, idx, vdp)) {
		if (vdp->vd_hash != hash)
			continue;
		if (strcmp(vdp->vd_name, name) == 0)
			return (vdp);
	}
	return (NULL);
}

/*
 * Add a new version descriptor to a version descriptor list.  Note, users of
 * this are responsible for determining if the version descriptor already
 * exists (this can reduce the need to allocate storage for descriptor names
 * until it is determined a descriptor need be created (see map_symbol())).
 */
Ver_desc *
ld_vers_desc(const char *name, Word hash, APlist **alpp)
{
	Ver_desc	*vdp;

	if ((vdp = libld_calloc(sizeof (Ver_desc), 1)) == NULL)
		return ((Ver_desc *)S_ERROR);

	vdp->vd_name = name;
	vdp->vd_hash = hash;

	if (aplist_append(alpp, vdp, AL_CNT_VERDESCS) == NULL)
		return ((Ver_desc *)S_ERROR);

	return (vdp);
}

/*
 * Now that all explict files have been processed validate any version
 * definitions.  Insure that any version references are available (a version
 * has been defined when it's been assigned an index).  Also calculate the
 * number of .version section entries that will be required to hold this
 * information.
 */
#define	_NUM_OF_VERS_	40	/* twice as big as the depth for libc version */
typedef struct {
	Ver_desc	**ver_stk;
	int 		ver_sp;
	int 		ver_lmt;
} Ver_Stack;

static uintptr_t
vers_visit_children(Ofl_desc *ofl, Ver_desc *vp, int flag)
{
	Aliste			idx;
	Ver_desc		*vdp;
	static int		err = 0;
	static Ver_Stack	ver_stk = {0, 0, 0};
	int			tmp_sp;

	/*
	 * If there was any fatal error,
	 * just return.
	 */
	if (err == S_ERROR)
		return (err);

	/*
	 * if this is called from, ver_check_defs(), initialize sp.
	 */
	if (flag == 0)
		ver_stk.ver_sp = 0;

	/*
	 * Check if passed version pointer vp is already in the stack.
	 */
	for (tmp_sp = 0; tmp_sp < ver_stk.ver_sp; tmp_sp++) {
		Ver_desc *v;

		v = ver_stk.ver_stk[tmp_sp];
		if (v == vp) {
			/*
			 * cyclic dependency.
			 */
			if (err == 0) {
				eprintf(ofl->ofl_lml, ERR_FATAL,
				    MSG_INTL(MSG_VER_CYCLIC));
				err = 1;
			}
			for (tmp_sp = 0; tmp_sp < ver_stk.ver_sp; tmp_sp++) {
				v = ver_stk.ver_stk[tmp_sp];
				if ((v->vd_flags & FLG_VER_CYCLIC) == 0) {
					v->vd_flags |= FLG_VER_CYCLIC;
					eprintf(ofl->ofl_lml, ERR_NONE,
					    MSG_INTL(MSG_VER_ADDVER),
					    v->vd_name);
				}
			}
			if ((vp->vd_flags & FLG_VER_CYCLIC) == 0) {
				vp->vd_flags |= FLG_VER_CYCLIC;
				eprintf(ofl->ofl_lml, ERR_NONE,
				    MSG_INTL(MSG_VER_ADDVER), vp->vd_name);
			}
			return (err);
		}
	}

	/*
	 * Push version on the stack.
	 */
	if (ver_stk.ver_sp >= ver_stk.ver_lmt) {
		ver_stk.ver_lmt += _NUM_OF_VERS_;
		if ((ver_stk.ver_stk = (Ver_desc **)
		    libld_realloc((void *)ver_stk.ver_stk,
		    ver_stk.ver_lmt * sizeof (Ver_desc *))) == NULL)
			return (S_ERROR);
	}
	ver_stk.ver_stk[(ver_stk.ver_sp)++] = vp;

	/*
	 * Now visit children.
	 */
	for (APLIST_TRAVERSE(vp->vd_deps, idx, vdp))
		if (vers_visit_children(ofl, vdp, 1) == S_ERROR)
			return (S_ERROR);

	/*
	 * Pop version from the stack.
	 */
	(ver_stk.ver_sp)--;

	return (err);
}

uintptr_t
ld_vers_check_defs(Ofl_desc *ofl)
{
	Aliste		idx1;
	Ver_desc	*vdp;
	uintptr_t 	is_cyclic = 0;

	DBG_CALL(Dbg_ver_def_title(ofl->ofl_lml, ofl->ofl_name));

	/*
	 * First check if there are any cyclic dependency
	 */
	for (APLIST_TRAVERSE(ofl->ofl_verdesc, idx1, vdp))
		if ((is_cyclic = vers_visit_children(ofl, vdp, 0)) == S_ERROR)
			return (S_ERROR);

	if (is_cyclic)
		ofl->ofl_flags |= FLG_OF_FATAL;

	for (APLIST_TRAVERSE(ofl->ofl_verdesc, idx1, vdp)) {
		Byte		cnt;
		Sym		*sym;
		Sym_desc	*sdp;
		const char	*name = vdp->vd_name;
		uchar_t		bind;
		Ver_desc	*_vdp;
		avl_index_t	where;
		Aliste		idx2;

		if (vdp->vd_ndx == 0) {
			eprintf(ofl->ofl_lml, ERR_FATAL,
			    MSG_INTL(MSG_VER_UNDEF), name, vdp->vd_ref->vd_name,
			    vdp->vd_ref->vd_file->ifl_name);
			ofl->ofl_flags |= FLG_OF_FATAL;
			continue;
		}

		DBG_CALL(Dbg_ver_desc_entry(ofl->ofl_lml, vdp));

		/*
		 * If a version definition contains no symbols this is possibly
		 * a mapfile error.
		 */
		if ((vdp->vd_flags &
		    (VER_FLG_BASE | VER_FLG_WEAK | FLG_VER_REFER)) == 0)
			DBG_CALL(Dbg_ver_nointerface(ofl->ofl_lml,
			    vdp->vd_name));

		/*
		 * Update the version entry count to account for this new
		 * version descriptor (the count is the size in bytes).
		 */
		ofl->ofl_verdefsz += sizeof (Verdef);

		/*
		 * Traverse this versions dependency list to determine what
		 * additional version dependencies we must account for against
		 * this descriptor.
		 */
		cnt = 1;
		for (APLIST_TRAVERSE(vdp->vd_deps, idx2, _vdp)) {
#if	defined(__lint)
			/* get lint to think `_vdp' is used... */
			vdp = _vdp;
#endif
			cnt++;
		}
		ofl->ofl_verdefsz += (cnt * sizeof (Verdaux));

		/*
		 * Except for the base version descriptor, generate an absolute
		 * symbol to reflect this version.
		 */
		if (vdp->vd_flags & VER_FLG_BASE)
			continue;

		if (vdp->vd_flags & VER_FLG_WEAK)
			bind = STB_WEAK;
		else
			bind = STB_GLOBAL;

		if (sdp = ld_sym_find(name, vdp->vd_hash, &where, ofl)) {
			/*
			 * If the symbol already exists and is undefined or was
			 * defined in a shared library, convert it to an
			 * absolute.
			 */
			if ((sdp->sd_sym->st_shndx == SHN_UNDEF) ||
			    (sdp->sd_ref != REF_REL_NEED)) {
				sdp->sd_shndx = sdp->sd_sym->st_shndx = SHN_ABS;
				sdp->sd_sym->st_info =
				    ELF_ST_INFO(bind, STT_OBJECT);
				sdp->sd_ref = REF_REL_NEED;
				sdp->sd_flags |= (FLG_SY_SPECSEC |
				    FLG_SY_DEFAULT | FLG_SY_EXPDEF);
				sdp->sd_aux->sa_overndx = vdp->vd_ndx;

				/*
				 * If the reference originated from a mapfile
				 * insure we mark the symbol as used.
				 */
				if (sdp->sd_flags & FLG_SY_MAPREF)
					sdp->sd_flags |= FLG_SY_MAPUSED;

			} else if ((sdp->sd_flags & FLG_SY_SPECSEC) &&
			    (sdp->sd_sym->st_shndx != SHN_ABS) &&
			    (sdp->sd_ref == REF_REL_NEED)) {
				eprintf(ofl->ofl_lml, ERR_WARNING,
				    MSG_INTL(MSG_VER_DEFINED), name,
				    sdp->sd_file->ifl_name);
			}
		} else {
			/*
			 * If the symbol does not exist create it.
			 */
			if ((sym = libld_calloc(sizeof (Sym), 1)) == NULL)
				return (S_ERROR);

			sym->st_shndx = SHN_ABS;
			sym->st_info = ELF_ST_INFO(bind, STT_OBJECT);
			DBG_CALL(Dbg_ver_symbol(ofl->ofl_lml, name));

			if ((sdp = ld_sym_enter(name, sym, vdp->vd_hash,
			    vdp->vd_file, ofl, 0, SHN_ABS,
			    (FLG_SY_SPECSEC | FLG_SY_DEFAULT | FLG_SY_EXPDEF),
			    &where)) == (Sym_desc *)S_ERROR)
				return (S_ERROR);

			sdp->sd_ref = REF_REL_NEED;
			sdp->sd_aux->sa_overndx = vdp->vd_ndx;
		}
	}
	return (1);
}

/*
 * Dereference dependencies as a part of normalizing (allows recursion).
 */
static void
vers_derefer(Ifl_desc *ifl, Ver_desc *vdp, int weak)
{
	Aliste		idx;
	Ver_desc	*_vdp;
	Ver_index	*vip = &ifl->ifl_verndx[vdp->vd_ndx];

	/*
	 * Set the INFO bit on all dependencies that ld.so.1
	 * can skip verification for. These are the dependencies
	 * that are inherited by others -- verifying the inheriting
	 * version implicitily covers this one.
	 *
	 * If the head of the list was a weak then we only mark
	 * weak dependencies, but if the head of the list was 'strong'
	 * we set INFO on all dependencies.
	 */
	if ((weak && (vdp->vd_flags & VER_FLG_WEAK)) || (!weak))
		vip->vi_flags |= VER_FLG_INFO;

	for (APLIST_TRAVERSE(vdp->vd_deps, idx, _vdp))
		vers_derefer(ifl, _vdp, weak);
}

/*
 * If we need to record the versions of any needed dependencies traverse the
 * shared object dependency list and calculate what version needed entries are
 * required.
 */
uintptr_t
ld_vers_check_need(Ofl_desc *ofl)
{
	Aliste		idx1;
	Ifl_desc	*ifl;
	Half		needndx;
	Str_tbl		*strtbl;


	/*
	 * Determine which string table is appropriate.
	 */
	strtbl = (OFL_IS_STATIC_OBJ(ofl)) ? ofl->ofl_strtab :
	    ofl->ofl_dynstrtab;

	/*
	 * Versym indexes for needed versions start with the next
	 * available version after the final definied version.
	 * However, it can never be less than 2. 0 is always for local
	 * scope, and 1 is always the first global definition.
	 */
	needndx = (ofl->ofl_vercnt > 0) ? (ofl->ofl_vercnt + 1) : 2;

	/*
	 * Traverse the shared object list looking for dependencies.
	 */
	for (APLIST_TRAVERSE(ofl->ofl_sos, idx1, ifl)) {
		Aliste		idx2;
		Ver_index	*vip;
		Ver_desc	*vdp;
		Byte		cnt, need = 0;

		if (!(ifl->ifl_flags & FLG_IF_NEEDED))
			continue;

		if (ifl->ifl_vercnt <= VER_NDX_GLOBAL)
			continue;

		/*
		 * Scan the version index list and if any weak version
		 * definition has been referenced by the user promote the
		 * dependency to be non-weak.  Weak version dependencies do not
		 * cause fatal errors from the runtime linker, non-weak
		 * dependencies do.
		 */
		for (cnt = 0; cnt <= ifl->ifl_vercnt; cnt++) {
			vip = &ifl->ifl_verndx[cnt];
			vdp = vip->vi_desc;

			if ((vip->vi_flags & (FLG_VER_REFER | VER_FLG_WEAK)) ==
			    (FLG_VER_REFER | VER_FLG_WEAK))
				vdp->vd_flags &= ~VER_FLG_WEAK;

			/*
			 * Mark any weak reference as referred to so as to
			 * simplify normalization and later version dependency
			 * manipulation.
			 */
			if (vip->vi_flags & VER_FLG_WEAK)
				vip->vi_flags |= FLG_VER_REFER;
		}

		/*
		 * Scan the version dependency list to normalize the referenced
		 * dependencies.  Any needed version that is inherited by
		 * another like version is dereferenced as it is not necessary
		 * to make this part of the version dependencies.
		 */
		for (APLIST_TRAVERSE(ifl->ifl_verdesc, idx2, vdp)) {
			Aliste		idx3;
			Ver_desc	*_vdp;
			int		type;

			vip = &ifl->ifl_verndx[vdp->vd_ndx];

			if (!(vip->vi_flags & FLG_VER_REFER))
				continue;

			type = vdp->vd_flags & VER_FLG_WEAK;
			for (APLIST_TRAVERSE(vdp->vd_deps, idx3, _vdp))
				vers_derefer(ifl, _vdp, type);
		}

		/*
		 * Finally, determine how many of the version dependencies need
		 * to be recorded.
		 */
		for (cnt = 0; cnt <= ifl->ifl_vercnt; cnt++) {
			vip = &ifl->ifl_verndx[cnt];

			/*
			 * If a version has been referenced then record it as a
			 * version dependency.
			 */
			if (vip->vi_flags & FLG_VER_REFER) {
				/* Assign a VERSYM index for it */
				vip->vi_overndx = needndx++;

				ofl->ofl_verneedsz += sizeof (Vernaux);
				if (st_insert(strtbl, vip->vi_name) == -1)
					return (S_ERROR);
				need++;
			}
		}

		if (need) {
			ifl->ifl_flags |= FLG_IF_VERNEED;
			ofl->ofl_verneedsz += sizeof (Verneed);
			if (st_insert(strtbl, ifl->ifl_soname) == -1)
				return (S_ERROR);
		}
	}

	/*
	 * If no version needed information is required unset the output file
	 * flag.
	 */
	if (ofl->ofl_verneedsz == 0)
		ofl->ofl_flags &= ~FLG_OF_VERNEED;

	return (1);
}

/*
 * Indicate dependency selection (allows recursion).
 */
static void
vers_select(Ofl_desc *ofl, Ifl_desc *ifl, Ver_desc *vdp, const char *ref)
{
	Aliste		idx;
	Ver_desc	*_vdp;
	Ver_index	*vip = &ifl->ifl_verndx[vdp->vd_ndx];

	vip->vi_flags |= FLG_VER_AVAIL;
	DBG_CALL(Dbg_ver_avail_entry(ofl->ofl_lml, vip, ref));

	for (APLIST_TRAVERSE(vdp->vd_deps, idx, _vdp))
		vers_select(ofl, ifl, _vdp, ref);
}

static Ver_index *
vers_index(Ofl_desc *ofl, Ifl_desc *ifl, int avail)
{
	Aliste		idx1;
	Ver_desc	*vdp;
	Ver_index	*vip;
	Sdf_desc	*sdf = ifl->ifl_sdfdesc;
	Word		count = ifl->ifl_vercnt;
	Sdv_desc	*sdv;

	/*
	 * Allocate an index array large enough to hold all of the files
	 * version descriptors.
	 */
	if ((vip = libld_calloc(sizeof (Ver_index), (count + 1))) == NULL)
		return ((Ver_index *)S_ERROR);

	for (APLIST_TRAVERSE(ifl->ifl_verdesc, idx1, vdp)) {
		int	ndx = vdp->vd_ndx;

		vip[ndx].vi_name = vdp->vd_name;
		vip[ndx].vi_desc = vdp;

		/*
		 * Any relocatable object versions, and the `base' version are
		 * always available.
		 */
		if (avail || (vdp->vd_flags & VER_FLG_BASE))
			vip[ndx].vi_flags |= FLG_VER_AVAIL;

		/*
		 * If this is a weak version mark it as such.  Weak versions
		 * are always dragged into any version dependencies created,
		 * and if a weak version is referenced it will be promoted to
		 * a non-weak version dependency.
		 */
		if (vdp->vd_flags & VER_FLG_WEAK)
			vip[ndx].vi_flags |= VER_FLG_WEAK;
		/*
		 * If this version is mentioned in a mapfile using ADDVERS
		 * syntax then check to see if it corresponds to an actual
		 * version in the file.
		 */
		if (sdf && (sdf->sdf_flags & FLG_SDF_ADDVER)) {
			Aliste	idx2;

			for (ALIST_TRAVERSE(sdf->sdf_verneed, idx2, sdv)) {
				if (strcmp(vip[ndx].vi_name, sdv->sdv_name))
					continue;

				vip[ndx].vi_flags |= FLG_VER_REFER;
				sdv->sdv_flags |= FLG_SDV_MATCHED;
				break;
			}
		}
	}

	/*
	 * if $ADDVER was specified for this object verify that
	 * all of it's dependent upon versions were refered to.
	 */
	if (sdf && (sdf->sdf_flags & FLG_SDF_ADDVER)) {
		int	fail = 0;

		for (ALIST_TRAVERSE(sdf->sdf_verneed, idx1, sdv)) {
			if (sdv->sdv_flags & FLG_SDV_MATCHED)
				continue;

			if (fail++ == 0) {
				eprintf(ofl->ofl_lml, ERR_NONE,
				    MSG_INTL(MSG_VER_ADDVERS), sdf->sdf_rfile,
				    sdf->sdf_name);
			}
			eprintf(ofl->ofl_lml, ERR_NONE,
			    MSG_INTL(MSG_VER_ADDVER), sdv->sdv_name);
		}
		if (fail)
			return ((Ver_index *)S_ERROR);
	}

	return (vip);
}

/*
 * Process a version symbol index section.
 */
int
ld_vers_sym_process(Lm_list *lml, Is_desc *isp, Ifl_desc *ifl)
{
	Shdr	*symshdr;
	Shdr	*vershdr = isp->is_shdr;

	/*
	 * Verify that the versym is the same size as the linked symbol table.
	 * If these two get out of sync the file is considered corrupted.
	 */
	symshdr = ifl->ifl_isdesc[vershdr->sh_link]->is_shdr;
	if ((symshdr->sh_size / symshdr->sh_entsize) != (vershdr->sh_size /
	    vershdr->sh_entsize)) {
		Is_desc	*sym_isp = ifl->ifl_isdesc[vershdr->sh_link];

		eprintf(lml, ERR_WARNING, MSG_INTL(MSG_ELF_VERSYM),
		    ifl->ifl_name, EC_WORD(isp->is_scnndx), isp->is_name,
		    EC_WORD(vershdr->sh_size / vershdr->sh_entsize),
		    EC_WORD(sym_isp->is_scnndx), sym_isp->is_name,
		    EC_WORD(symshdr->sh_size / symshdr->sh_entsize));
		return (1);
	}
	ifl->ifl_versym = (Versym *)isp->is_indata->d_buf;
	return (1);
}

/*
 * Process a version definition section from an input file.  A list of version
 * descriptors is created and associated with the input files descriptor.  If
 * this is a shared object these descriptors will be used to indicate the
 * availability of each version.  If this is a relocatable object then these
 * descriptors will be promoted (concatenated) to the output files image.
 */
uintptr_t
ld_vers_def_process(Is_desc *isp, Ifl_desc *ifl, Ofl_desc *ofl)
{
	const char	*str, *file = ifl->ifl_name;
	Sdf_desc	*sdf = ifl->ifl_sdfdesc;
	Sdv_desc	*sdv;
	Word		num, _num;
	Verdef		*vdf;
	int		relobj;

	/*
	 * If there is no version section then simply indicate that all version
	 * definitions asked for do not exist.
	 */
	if (isp == NULL) {
		Aliste	idx;

		for (ALIST_TRAVERSE(sdf->sdf_vers, idx, sdv)) {
			eprintf(ofl->ofl_lml, ERR_FATAL,
			    MSG_INTL(MSG_VER_NOEXIST), ifl->ifl_name,
			    sdv->sdv_name, sdv->sdv_ref);
			ofl->ofl_flags |= FLG_OF_FATAL;
		}
		return (0);
	}

	vdf = (Verdef *)isp->is_indata->d_buf;

	/*
	 * Verify the version revision.  We only check the first version
	 * structure as it is assumed all other version structures in this
	 * data section will be of the same revision.
	 */
	if (vdf->vd_version > VER_DEF_CURRENT)
		(void) eprintf(ofl->ofl_lml, ERR_WARNING,
		    MSG_INTL(MSG_VER_HIGHER), ifl->ifl_name, vdf->vd_version,
		    VER_DEF_CURRENT);


	num = isp->is_shdr->sh_info;
	str = (char *)ifl->ifl_isdesc[isp->is_shdr->sh_link]->is_indata->d_buf;

	if (ifl->ifl_ehdr->e_type == ET_REL)
		relobj = 1;
	else
		relobj = 0;

	DBG_CALL(Dbg_ver_def_title(ofl->ofl_lml, file));

	/*
	 * Loop through the version information setting up a version descriptor
	 * for each version definition.
	 */
	for (_num = 1; _num <= num; _num++,
	    vdf = (Verdef *)((uintptr_t)vdf + vdf->vd_next)) {
		const char	*name;
		Ver_desc	*ivdp, *ovdp = NULL;
		Word		hash;
		Half 		cnt = vdf->vd_cnt;
		Half		ndx = vdf->vd_ndx;
		Verdaux		*vdap = (Verdaux *)((uintptr_t)vdf +
		    vdf->vd_aux);

		/*
		 * Keep track of the largest index for use in creating a
		 * version index array later, and create a version descriptor.
		 */
		if (ndx > ifl->ifl_vercnt)
			ifl->ifl_vercnt = ndx;

		name = (char *)(str + vdap->vda_name);
		/* LINTED */
		hash = (Word)elf_hash(name);
		if (((ivdp = ld_vers_find(name, hash,
		    ifl->ifl_verdesc)) == NULL) &&
		    ((ivdp = ld_vers_desc(name, hash,
		    &ifl->ifl_verdesc)) == (Ver_desc *)S_ERROR))
			return (S_ERROR);

		ivdp->vd_ndx = ndx;
		ivdp->vd_file = ifl;
		ivdp->vd_flags = vdf->vd_flags;

		/*
		 * If we're processing a relocatable object then this version
		 * definition needs to be propagated to the output file.
		 * Generate a new output file version and associated this input
		 * version to it.  During symbol processing the version index of
		 * the symbol will be promoted from the input file to the output
		 * files version definition.
		 */
		if (relobj) {
			if (!(ofl->ofl_flags & FLG_OF_RELOBJ))
				ofl->ofl_flags |= FLG_OF_PROCRED;

			if ((ivdp->vd_flags & VER_FLG_BASE) == 0) {
				/*
				 * If no version descriptors have yet been set
				 * up, initialize a base version to represent
				 * the output file itself.  This `base' version
				 * catches any internally generated symbols
				 * (_end, _etext, etc.) and
				 * serves to initialize the output version
				 * descriptor count.
				 */
				if (ofl->ofl_vercnt == 0) {
					if (ld_vers_base(ofl) ==
					    (Ver_desc *)S_ERROR)
						return (S_ERROR);
				}
				ofl->ofl_flags |= FLG_OF_VERDEF;
				if ((ovdp = ld_vers_find(name, hash,
				    ofl->ofl_verdesc)) == NULL) {
					if ((ovdp = ld_vers_desc(name, hash,
					    &ofl->ofl_verdesc)) ==
					    (Ver_desc *)S_ERROR)
						return (S_ERROR);

					/* LINTED */
					ovdp->vd_ndx = (Half)++ofl->ofl_vercnt;
					ovdp->vd_file = ifl;
					ovdp->vd_flags = vdf->vd_flags;
				}
			}

			/*
			 * Maintain the association between the input version
			 * descriptor and the output version descriptor so that
			 * an associated symbols will be assigned to the
			 * correct version.
			 */
			ivdp->vd_ref = ovdp;
		}

		/*
		 * Process any dependencies this version may have.
		 */
		vdap = (Verdaux *)((uintptr_t)vdap + vdap->vda_next);
		for (cnt--; cnt; cnt--,
		    vdap = (Verdaux *)((uintptr_t)vdap + vdap->vda_next)) {
			Ver_desc	*_ivdp;

			name = (char *)(str + vdap->vda_name);
			/* LINTED */
			hash = (Word)elf_hash(name);

			if (((_ivdp = ld_vers_find(name, hash,
			    ifl->ifl_verdesc)) == NULL) &&
			    ((_ivdp = ld_vers_desc(name, hash,
			    &ifl->ifl_verdesc)) == (Ver_desc *)S_ERROR))
				return (S_ERROR);

			if (aplist_append(&ivdp->vd_deps, _ivdp,
			    AL_CNT_VERDESCS) == NULL)
				return (S_ERROR);
		}
		DBG_CALL(Dbg_ver_desc_entry(ofl->ofl_lml, ivdp));
	}

	/*
	 * Now that we know the total number of version definitions for this
	 * file, build an index array for fast access when processing symbols.
	 */
	if ((ifl->ifl_verndx =
	    vers_index(ofl, ifl, relobj)) == (Ver_index *)S_ERROR)
		return (S_ERROR);

	if (relobj)
		return (1);

	/*
	 * If this object has version control definitions against it then these
	 * must be processed so as to select those version definitions to which
	 * symbol bindings can occur.  Otherwise simply mark all versions as
	 * available.
	 */
	DBG_CALL(Dbg_ver_avail_title(ofl->ofl_lml, file));

	if (sdf && (sdf->sdf_flags & FLG_SDF_SELECT)) {
		Aliste	idx1;

		for (ALIST_TRAVERSE(sdf->sdf_vers, idx1, sdv)) {
			Aliste		idx2;
			Ver_desc	*vdp;
			int		found = 0;

			for (APLIST_TRAVERSE(ifl->ifl_verdesc, idx2, vdp)) {
				if (strcmp(sdv->sdv_name, vdp->vd_name) == 0) {
					found++;
					break;
				}
			}
			if (found)
				vers_select(ofl, ifl, vdp, sdv->sdv_ref);
			else {
				eprintf(ofl->ofl_lml, ERR_FATAL,
				    MSG_INTL(MSG_VER_NOEXIST), ifl->ifl_name,
				    sdv->sdv_name, sdv->sdv_ref);
				ofl->ofl_flags |= FLG_OF_FATAL;
			}
		}
	} else {
		Ver_index	*vip;
		int		cnt;

		for (cnt = VER_NDX_GLOBAL; cnt <= ifl->ifl_vercnt; cnt++) {
			vip = &ifl->ifl_verndx[cnt];
			vip->vi_flags |= FLG_VER_AVAIL;
			DBG_CALL(Dbg_ver_avail_entry(ofl->ofl_lml, vip, 0));
		}
	}

	/*
	 * If this is an explict dependency indicate that this file is a
	 * candidate for requiring version needed information to be recorded in
	 * the image we're creating.
	 */
	if (ifl->ifl_flags & FLG_IF_NEEDED)
		ofl->ofl_flags |= FLG_OF_VERNEED;

	return (1);
}

/*
 * Process a version needed section.
 */
uintptr_t
ld_vers_need_process(Is_desc *isp, Ifl_desc *ifl, Ofl_desc *ofl)
{
	const char	*str, *file = ifl->ifl_name;
	Word		num, _num;
	Verneed		*vnd;

	vnd = (Verneed *)isp->is_indata->d_buf;

	/*
	 * Verify the version revision.  We only check the first version
	 * structure as it is assumed all other version structures in this
	 * data section will be of the same revision.
	 */
	if (vnd->vn_version > VER_DEF_CURRENT) {
		(void) eprintf(ofl->ofl_lml, ERR_WARNING,
		    MSG_INTL(MSG_VER_HIGHER), ifl->ifl_name, vnd->vn_version,
		    VER_DEF_CURRENT);
	}

	num = isp->is_shdr->sh_info;
	str = (char *)ifl->ifl_isdesc[isp->is_shdr->sh_link]->is_indata->d_buf;

	DBG_CALL(Dbg_ver_need_title(ofl->ofl_lml, file));

	/*
	 * Loop through the version information setting up a version descriptor
	 * for each version definition.
	 */
	for (_num = 1; _num <= num; _num++,
	    vnd = (Verneed *)((uintptr_t)vnd + vnd->vn_next)) {
		Sdf_desc	*sdf;
		const char	*name;
		Half		cnt = vnd->vn_cnt;
		Vernaux		*vnap = (Vernaux *)((uintptr_t)vnd +
		    vnd->vn_aux);
		Half		_cnt;

		name = (char *)(str + vnd->vn_file);

		/*
		 * Set up a shared object descriptor and add to it the necessary
		 * needed versions.  This information may also have been added
		 * by a mapfile (see map_dash()).
		 */
		if ((sdf = sdf_find(name, ofl->ofl_soneed)) == NULL) {
			if ((sdf = sdf_add(name, &ofl->ofl_soneed)) ==
			    (Sdf_desc *)S_ERROR)
				return (S_ERROR);
			sdf->sdf_rfile = file;
			sdf->sdf_flags |= FLG_SDF_VERIFY;
		}

		for (_cnt = 0; cnt; _cnt++, cnt--,
		    vnap = (Vernaux *)((uintptr_t)vnap + vnap->vna_next)) {
			Sdv_desc	sdv;

			sdv.sdv_name = str + vnap->vna_name;
			sdv.sdv_ref = file;
			sdv.sdv_flags = 0;

			if (alist_append(&sdf->sdf_vers, &sdv,
			    sizeof (Sdv_desc), AL_CNT_SDF_VERSIONS) == NULL)
				return (S_ERROR);

			DBG_CALL(Dbg_ver_need_entry(ofl->ofl_lml, _cnt, name,
			    sdv.sdv_name));
		}
	}
	return (1);
}

/*
 * If a symbol is obtained from a versioned relocatable object then the symbols
 * version association must be promoted to the version definition as it will be
 * represented in the output file.
 */
void
ld_vers_promote(Sym_desc *sdp, Word ndx, Ifl_desc *ifl, Ofl_desc *ofl)
{
	Half 	vndx;

	/*
	 * A version symbol index of 0 implies the symbol is local.  A value of
	 * VER_NDX_GLOBAL implies the symbol is global but has not been
	 * assigned to a specfic version definition.
	 */
	vndx = ifl->ifl_versym[ndx];
	if (vndx == 0) {
		sdp->sd_flags |= (FLG_SY_REDUCED | FLG_SY_HIDDEN);
		return;
	}

	if (vndx == VER_NDX_ELIMINATE) {
		sdp->sd_flags |= (FLG_SY_REDUCED | FLG_SY_HIDDEN | FLG_SY_ELIM);
		return;
	}

	if (vndx == VER_NDX_GLOBAL) {
		if (!SYM_IS_HIDDEN(sdp))
			sdp->sd_flags |= (FLG_SY_DEFAULT | FLG_SY_EXPDEF);
		if (sdp->sd_aux->sa_overndx <= VER_NDX_GLOBAL)
			sdp->sd_aux->sa_overndx = VER_NDX_GLOBAL;
		return;
	}

	/*
	 * Any other version index requires association to the appropriate
	 * version definition.
	 */
	if ((ifl->ifl_verndx == 0) || (vndx > ifl->ifl_vercnt)) {
		eprintf(ofl->ofl_lml, ERR_FATAL, MSG_INTL(MSG_VER_INVALNDX),
		    sdp->sd_name, ifl->ifl_name, vndx);
		ofl->ofl_flags |= FLG_OF_FATAL;
		return;
	}

	if (!SYM_IS_HIDDEN(sdp))
		sdp->sd_flags |= (FLG_SY_DEFAULT | FLG_SY_EXPDEF);

	/*
	 * Promote the symbols version index to the appropriate output version
	 * definition.
	 */
	if (!(sdp->sd_flags & FLG_SY_VERSPROM)) {
		Ver_index	*vip;

		vip = &ifl->ifl_verndx[vndx];
		sdp->sd_aux->sa_overndx = vip->vi_desc->vd_ref->vd_ndx;
		sdp->sd_flags |= FLG_SY_VERSPROM;
	}
}

/*
 * If any versioning is called for make sure an initial version descriptor is
 * assigned to represent the file itself.  Known as the base version.
 */
Ver_desc *
ld_vers_base(Ofl_desc *ofl)
{
	Ver_desc	*vdp;
	const char	*name;

	/*
	 * Determine the filename to associate to the version descriptor.  This
	 * is either the SONAME (if one has been supplied) or the basename of
	 * the output file.
	 */
	if ((name = ofl->ofl_soname) == NULL) {
		const char	*str = ofl->ofl_name;

		while (*str != '\0') {
			if (*str++ == '/')
				name = str;
		}
		if (name == NULL)
			name = ofl->ofl_name;
	}

	/*
	 * Generate the version descriptor.
	 */
	/* LINTED */
	if ((vdp = ld_vers_desc(name, (Word)elf_hash(name),
	    &ofl->ofl_verdesc)) == (Ver_desc *)S_ERROR)
		return ((Ver_desc *)S_ERROR);

	/*
	 * Assign the base index to this version and initialize the output file
	 * descriptor with the number of version descriptors presently in use.
	 */
	vdp->vd_ndx = ofl->ofl_vercnt = VER_NDX_GLOBAL;
	vdp->vd_flags |= VER_FLG_BASE;

	return (vdp);
}

/*
 * Now that all input shared objects have been processed, verify that all
 * version requirements have been met.  Any version control requirements will
 * have been specified by the user (and placed on the ofl_oscntl list) and are
 * verified at the time the object was processed (see ver_def_process()).
 * Here we process all version requirements established from shared objects
 * themselves (ie,. NEEDED dependencies).
 */
int
ld_vers_verify(Ofl_desc *ofl)
{
	Aliste		idx1;
	Sdf_desc	*sdf;
	char		*nv;

	/*
	 * As with the runtime environment, disable all version verification if
	 * requested.
	 */
#if	defined(_ELF64)
	if ((nv = getenv(MSG_ORIG(MSG_LD_NOVERSION_64))) == NULL)
#else
	if ((nv = getenv(MSG_ORIG(MSG_LD_NOVERSION_32))) == NULL)
#endif
		nv = getenv(MSG_ORIG(MSG_LD_NOVERSION));

	if (nv && nv[0])
		return (1);

	for (APLIST_TRAVERSE(ofl->ofl_soneed, idx1, sdf)) {
		Aliste		idx2;
		Sdv_desc	*sdv;
		Ifl_desc	*ifl = sdf->sdf_file;

		if (!(sdf->sdf_flags & FLG_SDF_VERIFY))
			continue;

		/*
		 * If this file contains no version definitions then ignore
		 * any versioning verification.  This is the same model as
		 * carried out by ld.so.1 and is intended to allow backward
		 * compatibility should a shared object with a version
		 * requirement be returned to an older system on which a
		 * non-versioned shared object exists.
		 */
		if ((ifl == NULL) || (ifl->ifl_verdesc == NULL))
			continue;

		/*
		 * If individual versions were specified for this file make
		 * sure that they actually exist in the appropriate file, and
		 * that they are available for binding.
		 */
		for (ALIST_TRAVERSE(sdf->sdf_vers, idx2, sdv)) {
			Aliste		idx3;
			Ver_desc	*vdp;
			int		found = 0;

			for (APLIST_TRAVERSE(ifl->ifl_verdesc, idx3, vdp)) {
				if (strcmp(sdv->sdv_name, vdp->vd_name) == 0) {
					found++;
					break;
				}
			}
			if (found) {
				Ver_index	*vip;

				vip = &ifl->ifl_verndx[vdp->vd_ndx];
				if (!(vip->vi_flags & FLG_VER_AVAIL)) {
					eprintf(ofl->ofl_lml, ERR_FATAL,
					    MSG_INTL(MSG_VER_UNAVAIL),
					    ifl->ifl_name, sdv->sdv_name,
					    sdv->sdv_ref);
					ofl->ofl_flags |= FLG_OF_FATAL;
				}
			} else {
				eprintf(ofl->ofl_lml, ERR_FATAL,
				    MSG_INTL(MSG_VER_NOEXIST), ifl->ifl_name,
				    sdv->sdv_name, sdv->sdv_ref);
				ofl->ofl_flags |= FLG_OF_FATAL;
			}
		}
	}
	return (1);
}