/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright (c) 1989, 2010, Oracle and/or its affiliates. All rights reserved.
 */

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

/*
 * Utility functions
 */
#include <unistd.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <errno.h>
#include <sgs.h>
#include <libintl.h>
#include <debug.h>
#include "msg.h"
#include "_libld.h"

/*
 * libld_malloc() and dz_map() are used for both performance and for ease of
 * programming:
 *
 * Performance:
 *	The link-edit is a short lived process which doesn't really free much
 *	of the dynamic memory that it requests.  Because of this, it is more
 *	important to optimize for quick memory allocations than the
 *	re-usability of the memory.
 *
 *	By also mmaping blocks of pages in from /dev/zero we don't need to
 *	waste the overhead of zeroing out these pages for calloc() requests.
 *
 * Memory Management:
 *	By doing all libld memory management through the ld_malloc routine
 *	it's much easier to free up all memory at the end by simply unmaping
 *	all of the blocks that were mapped in through dz_map().  This is much
 *	simpler then trying to track all of the libld structures that were
 *	dynamically allocate and are actually pointers into the ELF files.
 *
 *	It's important that we can free up all of our dynamic memory because
 *	libld is used by ld.so.1 when it performs dlopen()'s of relocatable
 *	objects.
 *
 * Format:
 *	The memory blocks for each allocation store the size of the allocation
 *	in the first 8 bytes of the block.  The pointer that is returned by
 *	libld_malloc() is actually the address of (block + 8):
 *
 *		(addr - 8)	block_size
 *		(addr)		<allocated block>
 *
 *	The size is retained in order to implement realloc(), and to perform
 *	the required memcpy().  8 bytes are uses, as the memory area returned
 *	by libld_malloc() must be 8 byte-aligned.  Even in a 32-bit environment,
 *	u_longlog_t pointers are employed.
 *
 * Map anonymous memory via MAP_ANON (added in Solaris 8).
 */
static void *
dz_map(size_t size)
{
	void	*addr;

	if ((addr = mmap(0, size, (PROT_READ | PROT_WRITE | PROT_EXEC),
	    (MAP_PRIVATE | MAP_ANON), -1, 0)) == MAP_FAILED) {
		int	err = errno;
		eprintf(NULL, ERR_FATAL, MSG_INTL(MSG_SYS_MMAPANON),
		    strerror(err));
		return (MAP_FAILED);
	}
	return (addr);
}

void *
libld_malloc(size_t size)
{
	Ld_heap		*chp = ld_heap;
	void		*vptr;
	size_t		asize = size + HEAPALIGN;

	/*
	 * If this is the first allocation, or the allocation request is greater
	 * than the current free space available, allocate a new heap.
	 */
	if ((chp == NULL) ||
	    (((size_t)chp->lh_end - (size_t)chp->lh_free) <= asize)) {
		Ld_heap	*nhp;
		size_t	hsize = (size_t)S_ROUND(sizeof (Ld_heap), HEAPALIGN);
		size_t	tsize = (size_t)S_ROUND((asize + hsize), HEAPALIGN);

		/*
		 * Allocate a block that is at minimum 'HEAPBLOCK' size
		 */
		if (tsize < HEAPBLOCK)
			tsize = HEAPBLOCK;

		if ((nhp = dz_map(tsize)) == MAP_FAILED)
			return (NULL);

		nhp->lh_next = chp;
		nhp->lh_free = (void *)((size_t)nhp + hsize);
		nhp->lh_end = (void *)((size_t)nhp + tsize);

		ld_heap = chp = nhp;
	}
	vptr = chp->lh_free;

	/*
	 * Assign size to head of allocated block (used by realloc), and
	 * memory arena as then next 8-byte aligned offset.
	 */
	*((size_t *)vptr) = size;
	vptr = (void *)((size_t)vptr + HEAPALIGN);

	/*
	 * Increment free to point to next available block
	 */
	chp->lh_free = (void *)S_ROUND((size_t)chp->lh_free + asize,
	    HEAPALIGN);

	return (vptr);
}

void *
libld_realloc(void *ptr, size_t size)
{
	size_t	psize;
	void	*vptr;

	if (ptr == NULL)
		return (libld_malloc(size));

	/*
	 * Size of the allocated blocks is stored *just* before the blocks
	 * address.
	 */
	psize = *((size_t *)((size_t)ptr - HEAPALIGN));

	/*
	 * If the block actually fits then just return.
	 */
	if (size <= psize)
		return (ptr);

	if ((vptr = libld_malloc(size)) != NULL)
		(void) memcpy(vptr, ptr, psize);

	return (vptr);
}

void
/* ARGSUSED 0 */
libld_free(void *ptr)
{
}

/*
 * Determine if a shared object definition structure already exists and if
 * not create one.  These definitions provide for recording information
 * regarding shared objects that are still to be processed.  Once processed
 * shared objects are maintained on the ofl_sos list.  The information
 * recorded in this structure includes:
 *
 *  o	DT_USED requirements.  In these cases definitions are added during
 *	mapfile processing of `-' entries (see map_dash()).
 *
 *  o	implicit NEEDED entries.  As shared objects are processed from the
 *	command line so any of their dependencies are recorded in these
 *	structures for later processing (see process_dynamic()).
 *
 *  o	version requirements.  Any explicit shared objects that have version
 *	dependencies on other objects have their version requirements recorded.
 *	In these cases definitions are added during mapfile processing of `-'
 *	entries (see map_dash()).  Also, shared objects may have versioning
 *	requirements on their NEEDED entries.  These cases are added during
 *	their version processing (see vers_need_process()).
 *
 *	Note: Both process_dynamic() and vers_need_process() may generate the
 *	initial version definition structure because you can't rely on what
 *	section (.dynamic or .SUNW_version) may be processed first from	any
 *	input file.
 */
Sdf_desc *
sdf_find(const char *name, APlist *alp)
{
	Aliste		idx;
	Sdf_desc	*sdf;

	for (APLIST_TRAVERSE(alp, idx, sdf))
		if (strcmp(name, sdf->sdf_name) == 0)
			return (sdf);

	return (NULL);
}

Sdf_desc *
sdf_add(const char *name, APlist **alpp)
{
	Sdf_desc	*sdf;

	if ((sdf = libld_calloc(sizeof (Sdf_desc), 1)) == NULL)
		return ((Sdf_desc *)S_ERROR);

	sdf->sdf_name = name;

	if (aplist_append(alpp, sdf, AL_CNT_OFL_LIBS) == NULL)
		return ((Sdf_desc *)S_ERROR);

	return (sdf);
}

/*
 * Add a string, separated by a colon, to an existing string.  Typically used
 * to maintain filter, rpath and audit names, of which there is normally only
 * one string supplied anyway.
 */
char *
add_string(char *old, char *str)
{
	char	*new;

	if (old) {
		char	*_str;
		size_t	len;

		/*
		 * If an original string exists, make sure this new string
		 * doesn't get duplicated.
		 */
		if ((_str = strstr(old, str)) != NULL) {
			if (((_str == old) ||
			    (*(_str - 1) == *(MSG_ORIG(MSG_STR_COLON)))) &&
			    (_str += strlen(str)) &&
			    ((*_str == '\0') ||
			    (*_str == *(MSG_ORIG(MSG_STR_COLON)))))
				return (old);
		}

		len = strlen(old) + strlen(str) + 2;
		if ((new = libld_calloc(1, len)) == NULL)
			return ((char *)S_ERROR);
		(void) snprintf(new, len, MSG_ORIG(MSG_FMT_COLPATH), old, str);
	} else {
		if ((new = libld_malloc(strlen(str) + 1)) == NULL)
			return ((char *)S_ERROR);
		(void) strcpy(new, str);
	}

	return (new);
}

/*
 * The GNU ld '-wrap=XXX' and '--wrap=XXX' options correspond to our
 * '-z wrap=XXX'. When str2chr() does this conversion, we end up with
 * the return character set to 'z' and optarg set to 'XXX'. This callback
 * changes optarg to include the missing wrap= prefix.
 *
 * exit:
 *	Returns c on success, or '?' on error.
 */
static int
str2chr_wrap_cb(int c)
{
	char    *str;
	size_t  len = MSG_ARG_WRAP_SIZE + strlen(optarg) + 1;

	if ((str = libld_malloc(len)) == NULL)
		return ('?');
	(void) snprintf(str, len, MSG_ORIG(MSG_FMT_STRCAT),
	    MSG_ORIG(MSG_ARG_WRAP), optarg);
	optarg = str;
	return (c);
}

/*
 * Determine whether this string, possibly with an associated option, should
 * be translated to an option character.  If so, update the optind and optarg
 * and optopt as described for short options in getopt(3c).
 *
 * entry:
 *	lml - Link map list for debug messages
 *	ndx - Starting optind for current item
 *	argc, argv - Command line arguments
 *	arg - Option to be examined
 *	c, opt - Option character (c) and corresponding long name (opt)
 *	optsz - 0 if option does not accept a value. If option does
 *		accept a value, strlen(opt), giving the offset to the
 *		value if the option and value are combined in one string.
 *	cbfunc - NULL, or pointer to function to call if a translation is
 *		successful.
 */
static int
str2chr(Lm_list *lml, int ndx, int argc, char **argv, char *arg, int c,
    const char *opt, size_t optsz, int cbfunc(int))
{
	if (optsz == 0) {
		/*
		 * Compare a single option (ie. there's no associated option
		 * argument).
		 */
		if (strcmp(arg, opt) == 0) {
			DBG_CALL(Dbg_args_str2chr(lml, ndx, opt, c));
			optind += 1;
			optopt = c;
			return (c);
		}
	} else if ((strcmp(arg, opt) == 0) ||
	    ((arg[optsz] == '=') && strncmp(arg, opt, optsz) == 0)) {
		/*
		 * Otherwise, compare the option name, which may be
		 * concatenated with the option argument.
		 */
		DBG_CALL(Dbg_args_str2chr(lml, ndx, opt, c));

		if (arg[optsz] == '\0') {
			/*
			 * Optarg is the next argument (white space separated).
			 * Make sure an optarg is available, and if not return
			 * a failure to prevent any fall-through to the generic
			 * getopt() processing.
			 *
			 * Since we'll be completely failing this option we
			 * don't want to update optopt with the translation,
			 * but also need to set it to _something_.  Setting it
			 * to the '-' of the argument causes us to behave
			 * correctly.
			 */
			if ((++optind + 1) > argc) {
				optopt = arg[0];
				return ('?');
			}
			optarg = argv[optind];
			optind++;
		} else {
			/*
			 * GNU option/option argument pairs can be represented
			 * with a "=" separator.  If this is the case, remove
			 * the separator.
			 */
			optarg = &arg[optsz];
			optind++;
			if (*optarg == '=') {
				if (*(++optarg) == '\0') {
					optopt = arg[0];
					return ('?');
				}
			}
		}

		if (cbfunc != NULL)
			c = (*cbfunc)(c);
		optopt = c;
		return (c);
	}
	return (0);
}

/*
 * Parse an individual option.  The intent of this function is to determine if
 * any known, non-Solaris options have been passed to ld(1).  This condition
 * can occur as a result of build configuration tools, because of users
 * familiarity with other systems, or simply the users preferences.  If a known
 * non-Solaris option can be determined, translate that option into the Solaris
 * counterpart.
 *
 * This function will probably never be a complete solution, as new, non-Solaris
 * options are discovered, their translation will have to be added.  Other
 * non-Solaris options are incompatible with the Solaris link-editor, and will
 * never be recognized.  We support what we can.
 */
int
ld_getopt(Lm_list *lml, int ndx, int argc, char **argv)
{
	int	c;

	if ((optind < argc) && argv[optind] && (argv[optind][0] == '-')) {
		char	*arg = &argv[optind][1];

		switch (*arg) {
		case 'r':
			/* Translate -rpath <optarg> to -R <optarg> */
			if ((c = str2chr(lml, ndx, argc, argv, arg, 'R',
			    MSG_ORIG(MSG_ARG_T_RPATH),
			    MSG_ARG_T_RPATH_SIZE, NULL)) != 0) {
				return (c);
			}
			break;
		case 's':
			/* Translate -shared to -G */
			if ((c = str2chr(lml, ndx, argc, argv, arg, 'G',
			    MSG_ORIG(MSG_ARG_T_SHARED), 0, NULL)) != 0) {
				return (c);

			/* Translate -soname <optarg> to -h <optarg> */
			} else if ((c = str2chr(lml, ndx, argc, argv, arg, 'h',
			    MSG_ORIG(MSG_ARG_T_SONAME),
			    MSG_ARG_T_SONAME_SIZE, NULL)) != 0) {
				return (c);
			}
			break;
		case 'w':
			/* Translate -wrap to -z wrap= */
			if ((c = str2chr(lml, ndx, argc, argv, arg, 'z',
			    MSG_ORIG(MSG_ARG_T_WRAP) + 1,
			    MSG_ARG_T_WRAP_SIZE - 1, str2chr_wrap_cb)) != 0) {
				return (c);
			}
			break;
		case '(':
			/*
			 * Translate -( to -z rescan-start
			 */
			if ((c = str2chr(lml, ndx, argc, argv,
			    arg, 'z', MSG_ORIG(MSG_ARG_T_OPAR), 0, NULL)) !=
			    0) {
				optarg = (char *)MSG_ORIG(MSG_ARG_RESCAN_START);
				return (c);
			}
			break;
		case ')':
			/*
			 * Translate -) to -z rescan-end
			 */
			if ((c = str2chr(lml, ndx, argc, argv,
			    arg, 'z', MSG_ORIG(MSG_ARG_T_CPAR), 0, NULL)) !=
			    0) {
				optarg = (char *)MSG_ORIG(MSG_ARG_RESCAN_END);
				return (c);
			}
			break;
		case '-':
			switch (*(arg + 1)) {
			case 'a':
				/*
				 * Translate --allow-multiple-definition to
				 * -zmuldefs
				 */
				if ((c = str2chr(lml, ndx, argc, argv, arg, 'z',
				    MSG_ORIG(MSG_ARG_T_MULDEFS), 0, NULL)) !=
				    0) {
					optarg =
					    (char *)MSG_ORIG(MSG_ARG_MULDEFS);
					return (c);

				/*
				 * Translate --auxiliary <optarg> to
				 * -f <optarg>
				 */
				} else if ((c = str2chr(lml, argc, ndx, argv,
				    arg, 'f', MSG_ORIG(MSG_ARG_T_AUXFLTR),
				    MSG_ARG_T_AUXFLTR_SIZE, NULL)) != 0) {
					return (c);
				}
				break;
			case 'd':
				/*
				 * Translate --dynamic-linker <optarg> to
				 * -I <optarg>
				 */
				if ((c = str2chr(lml, ndx, argc, argv, arg, 'I',
				    MSG_ORIG(MSG_ARG_T_INTERP),
				    MSG_ARG_T_INTERP_SIZE, NULL)) != 0) {
					return (c);
				}
				break;
			case 'e':
				/* Translate --entry <optarg> to -e <optarg> */
				if ((c = str2chr(lml, ndx, argc, argv, arg, 'e',
				    MSG_ORIG(MSG_ARG_T_ENTRY),
				    MSG_ARG_T_ENTRY_SIZE, NULL)) != 0) {
					return (c);
				}
				/*
				 * Translate --end-group to -z rescan-end
				 */
				if ((c = str2chr(lml, ndx, argc, argv,
				    arg, 'z', MSG_ORIG(MSG_ARG_T_ENDGROUP),
				    0, NULL)) != 0) {
					optarg = (char *)
					    MSG_ORIG(MSG_ARG_RESCAN_END);
					return (c);
				}
				break;
			case 'f':
				/*
				 * Translate --fatal-warnings to
				 * -z fatal-warnings.
				 */
				if ((c = str2chr(lml, ndx, argc, argv, arg, 'z',
				    MSG_ORIG(MSG_ARG_T_FATWARN),
				    0, NULL)) != 0) {
					optarg = (char *)
					    MSG_ORIG(MSG_ARG_FATWARN);
					return (c);
				}
				/* Translate --filter <optarg> to -F <optarg> */
				if ((c = str2chr(lml, ndx, argc, argv, arg, 'F',
				    MSG_ORIG(MSG_ARG_T_STDFLTR),
				    MSG_ARG_T_STDFLTR_SIZE, NULL)) != 0) {
					return (c);
				}
				break;
			case 'h':
				/* Translate --help to -zhelp */
				if ((c = str2chr(lml, ndx, argc, argv, arg, 'z',
				    MSG_ORIG(MSG_ARG_T_HELP), 0, NULL)) !=
				    0) {
					optarg = (char *)MSG_ORIG(MSG_ARG_HELP);
					return (c);
				}
				break;
			case 'l':
				/*
				 * Translate --library <optarg> to -l <optarg>
				 */
				if ((c = str2chr(lml, ndx, argc, argv, arg, 'l',
				    MSG_ORIG(MSG_ARG_T_LIBRARY),
				    MSG_ARG_T_LIBRARY_SIZE, NULL)) != 0) {
					return (c);

				/*
				 * Translate --library-path <optarg> to
				 * -L <optarg>
				 */
				} else if ((c = str2chr(lml, ndx, argc, argv,
				    arg, 'L', MSG_ORIG(MSG_ARG_T_LIBPATH),
				    MSG_ARG_T_LIBPATH_SIZE, NULL)) != 0) {
					return (c);
				}
				break;
			case 'n':
				/*
				 * Translate --no-fatal-warnings to
				 * -z nofatal-warnings.
				 */
				if ((c = str2chr(lml, ndx, argc, argv, arg, 'z',
				    MSG_ORIG(MSG_ARG_T_NOFATWARN),
				    0, NULL)) != 0) {
					optarg = (char *)
					    MSG_ORIG(MSG_ARG_NOFATWARN);
					return (c);
				}

				/* Translate --no-undefined to -zdefs */
				if ((c = str2chr(lml, ndx, argc, argv, arg, 'z',
				    MSG_ORIG(MSG_ARG_T_NOUNDEF), 0, NULL)) !=
				    0) {
					optarg = (char *)MSG_ORIG(MSG_ARG_DEFS);
					return (c);

				/*
				 * Translate --no-whole-archive to
				 * -z defaultextract
				 */
				} else if ((c = str2chr(lml, ndx, argc, argv,
				    arg, 'z', MSG_ORIG(MSG_ARG_T_NOWHOLEARC),
				    0, NULL)) != 0) {
					optarg =
					    (char *)MSG_ORIG(MSG_ARG_DFLEXTRT);
					return (c);
				}
				break;
			case 'o':
				/* Translate --output <optarg> to -o <optarg> */
				if ((c = str2chr(lml, ndx, argc, argv, arg, 'o',
				    MSG_ORIG(MSG_ARG_T_OUTPUT),
				    MSG_ARG_T_OUTPUT_SIZE, NULL)) != 0) {
					return (c);
				}
				break;
			case 'r':
				/* Translate --relocatable to -r */
				if ((c = str2chr(lml, ndx, argc, argv, arg, 'r',
				    MSG_ORIG(MSG_ARG_T_RELOCATABLE), 0,
				    NULL)) != 0) {
					return (c);
				}
				break;
			case 's':
				/* Translate --strip-all to -s */
				if ((c = str2chr(lml, ndx, argc, argv, arg, 's',
				    MSG_ORIG(MSG_ARG_T_STRIP), 0, NULL)) !=
				    0) {
					return (c);
				}
				/*
				 * Translate --start-group to -z rescan-start
				 */
				if ((c = str2chr(lml, ndx, argc, argv,
				    arg, 'z', MSG_ORIG(MSG_ARG_T_STARTGROUP),
				    0, NULL)) != 0) {
					optarg = (char *)
					    MSG_ORIG(MSG_ARG_RESCAN_START);
					return (c);
				}
				break;
			case 'u':
				/*
				 * Translate --undefined <optarg> to
				 * -u <optarg>
				 */
				if ((c = str2chr(lml, ndx, argc, argv, arg, 'u',
				    MSG_ORIG(MSG_ARG_T_UNDEF),
				    MSG_ARG_T_UNDEF_SIZE, NULL)) != 0) {
					return (c);
				}
				break;
			case 'v':
				/* Translate --version to -V */
				if ((c = str2chr(lml, ndx, argc, argv, arg, 'V',
				    MSG_ORIG(MSG_ARG_T_VERSION), 0, NULL)) !=
				    0) {
					return (c);
				}
				break;
			case 'w':
				/*
				 * Translate --whole-archive to -z alltextract
				 */
				if ((c = str2chr(lml, ndx, argc, argv,
				    arg, 'z', MSG_ORIG(MSG_ARG_T_WHOLEARC),
				    0, NULL)) != 0) {
					optarg =
					    (char *)MSG_ORIG(MSG_ARG_ALLEXTRT);
					return (c);
				}
				/*
				 * Translate --wrap to -z wrap=
				 */
				if ((c = str2chr(lml, ndx, argc, argv,
				    arg, 'z', MSG_ORIG(MSG_ARG_T_WRAP),
				    MSG_ARG_T_WRAP_SIZE, str2chr_wrap_cb)) !=
				    0) {
					return (c);
				}
				break;
			}
			break;
		}
	}

	if ((c = getopt(argc, argv, MSG_ORIG(MSG_STR_OPTIONS))) != -1) {
		/*
		 * It is possible that a "-Wl," argument has been used to
		 * specify an option.  This isn't advertized ld(1) syntax, but
		 * compiler drivers and configuration tools, have been known to
		 * pass this compiler option to ld(1).  Strip off the "-Wl,"
		 * prefix and pass the option through.
		 */
		if ((c == 'W') && (strncmp(optarg,
		    MSG_ORIG(MSG_ARG_T_WL), MSG_ARG_T_WL_SIZE) == 0)) {
			DBG_CALL(Dbg_args_Wldel(lml, ndx, optarg));
			c = optarg[MSG_ARG_T_WL_SIZE];
			optarg += MSG_ARG_T_WL_SIZE + 1;
		}
	}

	return (c);
}

/*
 * A compare routine for Isd_node AVL trees.
 */
int
isdavl_compare(const void *n1, const void *n2)
{
	uint_t		hash1, hash2;
	const char	*st1, *st2;
	int		rc;

	hash1 = ((Isd_node *)n1)->isd_hash;
	hash2 = ((Isd_node *)n2)->isd_hash;

	if (hash1 > hash2)
		return (1);
	if (hash1 < hash2)
		return (-1);

	st1 = ((Isd_node *)n1)->isd_name;
	st2 = ((Isd_node *)n2)->isd_name;

	rc = strcmp(st1, st2);
	if (rc > 0)
		return (1);
	if (rc < 0)
		return (-1);
	return (0);
}

/*
 * Messaging support - funnel everything through dgettext().
 */
const char *
_libld_msg(Msg mid)
{
	return (dgettext(MSG_ORIG(MSG_SUNW_OST_SGS), MSG_ORIG(mid)));
}

/*
 * Determine whether a symbol name should be demangled.
 */
const char *
demangle(const char *name)
{
	if (demangle_flag)
		return (Elf_demangle_name(name));
	else
		return (name);
}

/*
 * Compare a series of platform or machine hardware names.
 */
int
cap_names_match(Alist *alp1, Alist *alp2)
{
	Capstr		*capstr1;
	Aliste		idx1;
	int		match = 0;
	Word		nitems;

	if ((nitems = alist_nitems(alp1)) != alist_nitems(alp2))
		return (1);

	for (ALIST_TRAVERSE(alp1, idx1, capstr1)) {
		Capstr		*capstr2;
		Aliste 		idx2;

		for (ALIST_TRAVERSE(alp2, idx2, capstr2)) {
			if (strcmp(capstr1->cs_str, capstr2->cs_str))
				continue;

			match++;
			break;
		}
	}

	if (nitems == match)
		return (0);

	return (1);
}