/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (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 2005 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */
#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include	"_synonyms.h"

#include	<sys/types.h>
#include	<sys/mman.h>
#include	<dirent.h>
#include	<stdio.h>
#include	<string.h>
#include	<limits.h>
#include	"conv.h"
#include	"_rtld.h"
#include	"_audit.h"
#include	"_elf.h"
#include	"msg.h"
#include	"debug.h"

/*
 * qsort(3c) comparison function.
 */
static int
compare(const void *fdesc1, const void *fdesc2)
{
	ulong_t	hwcap1 = ((Fdesc *)fdesc1)->fd_fmap.fm_hwptr;
	ulong_t	hwcap2 = ((Fdesc *)fdesc2)->fd_fmap.fm_hwptr;

	if (hwcap1 && (hwcap2 == 0))
		return (-1);
	if ((hwcap1 == 0) && hwcap2)
		return (1);
	if ((hwcap1 == 0) && (hwcap2 == 0))
		return (0);

	if (hwcap1 > hwcap2)
		return (-1);
	if (hwcap1 < hwcap2)
		return (1);
	return (0);
}

/*
 * If this object defines a set of hardware capability requirements, insure the
 * kernal can cope with them.
 */
int
hwcap_check(Rej_desc *rej, Ehdr *ehdr)
{
	Cap	*cptr;
	Phdr	*phdr;
	int	cnt;

	/* LINTED */
	phdr = (Phdr *)((char *)ehdr + ehdr->e_phoff);
	for (cnt = 0; cnt < ehdr->e_phnum; cnt++, phdr++) {
		Lword	val;

		if (phdr->p_type != PT_SUNWCAP)
			continue;

		/* LINTED */
		cptr = (Cap *)((char *)ehdr + phdr->p_offset);
		while (cptr->c_tag != CA_SUNW_NULL) {
			if (cptr->c_tag == CA_SUNW_HW_1)
				break;
			cptr++;
		}
		if (cptr->c_tag == CA_SUNW_NULL)
			break;

		if ((val = (cptr->c_un.c_val & ~hwcap)) != 0) {
			rej->rej_type = SGS_REJ_HWCAP_1;
			rej->rej_str = conv_hwcap_1_str(val, M_MACH);
			return (0);
		}

		/*
		 * Retain this hardware capabilities pointer for possible later
		 * inspection should this object be processed as a filtee.
		 */
		fmap->fm_hwptr = cptr->c_un.c_val;
	}
	return (1);
}

static void
remove_fdesc(Fdesc *fdp)
{
#if	defined(MAP_ALIGN)
	if (fdp->fd_fmap.fm_maddr &&
	    ((fdp->fd_fmap.fm_mflags & MAP_ALIGN) == 0)) {
#else
	if (fdp->fd_fmap.fm_maddr) {
#endif
		(void) munmap(fdp->fd_fmap.fm_maddr, fdp->fd_fmap.fm_msize);

		/*
		 * Note, this file descriptor might be duplicating information
		 * from the global fmap descriptor.  If so, clean up the global
		 * descriptor to prevent a duplicate (unnecessary) unmap.
		 */
		if (fmap->fm_maddr == fdp->fd_fmap.fm_maddr)
			fmap->fm_maddr = 0;
	}
	if (fdp->fd_fd)
		(void) close(fdp->fd_fd);
	if (fdp->fd_pname && (fdp->fd_pname != fdp->fd_nname))
		free((void *)fdp->fd_pname);
	if (fdp->fd_nname)
		free((void *)fdp->fd_nname);
}

/*
 * When $HWCAP is used to represent dependencies, take the associated directory
 * and analyze all the files it contains.
 */
int
hwcap_dir(Alist **fdalpp, Lm_list *lml, const char *name, Rt_map *clmp,
    uint_t flags, Rej_desc *rej)
{
	char		path[PATH_MAX], *dst;
	const char	*src;
	DIR		*dir;
	struct dirent	*dirent;
	Aliste		off;
	Alist		*fdalp = 0;
	Fdesc		*fdp;
	int		error = 0;

	/*
	 * Access the directory in preparation for reading its entries.  If
	 * successful, establish the initial pathname.
	 */
	if ((dir = opendir(name)) == 0) {
		Rej_desc	_rej = { 0 };

		_rej.rej_type = SGS_REJ_STR;
		_rej.rej_name = name;
		_rej.rej_str = strerror(errno);
		DBG_CALL(Dbg_file_rejected(&_rej));
		rejection_inherit(rej, &_rej, 0);
		return (0);
	}

	for (dst = path, src = name; *src; dst++, src++)
		*dst = *src;
	*dst++ = '/';

	/*
	 * Read each entry from the directory and determine whether it is a
	 * valid ELF file.
	 */
	while ((dirent = readdir(dir)) != NULL) {
		const char	*file = dirent->d_name;
		char		*_dst;
		Fdesc		fdesc = { 0 };
		Rej_desc	_rej = { 0 };

		/*
		 * Ignore "." and ".." entries.
		 */
		if ((file[0] == '.') && ((file[1] == '\0') ||
		    ((file[1] == '.') && (file[2] == '\0'))))
			continue;

		/*
		 * Complete the full pathname, and verify its usability.
		 */
		for (_dst = dst, src = file, file = dst; *src; _dst++, src++)
			*_dst = *src;
		*_dst = '\0';

		if ((name = strdup(path)) == 0) {
			error = 1;
			break;
		}
		if ((name = load_trace(lml, name, clmp)) == 0)
			continue;

		if (find_path(lml, name, clmp, flags, &fdesc, &_rej) == 0)
			rejection_inherit(rej, &_rej, &fdesc);
		else {
			DBG_CALL(Dbg_cap_hw_candidate(name));

			/*
			 * If this object has already been loaded, obtain the
			 * hardware capabilities for later sorting.  Otherwise
			 * we have a new candidate.
			 */
			if (fdesc.fd_lmp)
				fdesc.fd_fmap.fm_hwptr = HWCAP(fdesc.fd_lmp);
			else
				fdesc.fd_fmap = *fmap;

			if (alist_append(&fdalp, &fdesc,
			    sizeof (Fdesc), 10) == 0) {
				remove_fdesc(&fdesc);
				error = 1;
				break;
			}
		}

		/*
		 * Clear the global file mapping structure so that the mapping
		 * for this file won't be overriden.
		 */
		fmap->fm_mflags = MAP_PRIVATE;
		fmap->fm_maddr = 0;
		fmap->fm_msize = syspagsz;
		fmap->fm_hwptr = 0;

	}
	(void) closedir(dir);

	/*
	 * If no objects have been found, we're done.  Also, if an allocation
	 * error occurred while processing any object, remove any objects that
	 * had already been added to the list and return.
	 */
	if ((fdalp == 0) || error) {
		if (fdalp) {
			for (ALIST_TRAVERSE(fdalp, off, fdp))
				remove_fdesc(fdp);
			free(fdalp);
		}
		return (0);
	}

	/*
	 * Having processed and retained all candidates from this directory,
	 * sort them, based on the precedence of their hardware capabilities.
	 */
	qsort(&(fdalp->al_data[0]), ((fdalp->al_next - (sizeof (Alist) -
	    sizeof (void *))) / fdalp->al_size), fdalp->al_size, compare);

	*fdalpp = fdalp;
	return (1);
}

static Pnode *
_hwcap_filtees(Pnode **pnpp, Aliste nlmco, Rt_map *flmp, const char *ref,
    const char *dir, int mode, uint_t flags)
{
	Alist		*fdalp = 0;
	Aliste		off;
	Pnode		*fpnp = 0, *lpnp, *npnp = (*pnpp)->p_next;
	Fdesc		*fdp;
	Lm_list		*lml = LIST(flmp);
	int		unused = 0;
	Rej_desc	rej = { 0 };

	if (hwcap_dir(&fdalp, lml, dir, flmp, flags, &rej) == 0) {
		remove_rej(&rej);
		return (0);
	}

	/*
	 * Now complete the mapping of each of the ordered objects, adding
	 * each object to a new Pnode.
	 */
	for (ALIST_TRAVERSE(fdalp, off, fdp)) {
		Rt_map	*nlmp;
		Grp_hdl	*ghp = 0;
		Pnode	*pnp;
		int	audit = 0;

		if (unused) {
			/*
			 * Flush out objects remaining.
			 */
			remove_fdesc(fdp);
			continue;
		}

		/*
		 * Complete mapping the file, obtaining a handle, and continue
		 * to analyze the object, establishing dependencies and
		 * relocating.  Remove the file descriptor at this point, as it
		 * is no longer required.
		 */
		DBG_CALL(Dbg_file_filtee(NAME(flmp), fdp->fd_nname, 0));

		nlmp = load_path(lml, nlmco, fdp->fd_nname, flmp, mode,
		    (flags | FLG_RT_HANDLE), &ghp, fdp, &rej);
		remove_fdesc(fdp);

		/*
		 * Create a new Pnode to represent this filtee, and substitute
		 * the calling Pnode (which was used to represent the hardware
		 * capability directory).
		 */
		if ((pnp = calloc(1, sizeof (Pnode))) == 0) {
			if (ghp)
				(void) dlclose_core(ghp, flmp);
			return (0);
		}
		if ((pnp->p_name = strdup(NAME(nlmp))) == 0) {
			if (ghp)
				(void) dlclose_core(ghp, flmp);
			free(pnp);
			return (0);
		}
		pnp->p_len = strlen(NAME(nlmp));
		pnp->p_info = (void *)ghp;
		pnp->p_next = npnp;

		if (fpnp == 0) {
			Pnode	*opnp = (*pnpp);

			/*
			 * If this is the first pnode, reuse the original after
			 * freeing any of its pathnames.
			 */
			if (opnp->p_name)
				free((void *)opnp->p_name);
			if (opnp->p_oname)
				free((void *)opnp->p_oname);
			*opnp = *pnp;
			free((void *)pnp);
			fpnp = lpnp = pnp = opnp;
		} else {
			lpnp->p_next = pnp;
			lpnp = pnp;
		}

		/*
		 * Establish the filter handle to prevent any recursion.
		 */
		if (nlmp && ghp) {
			ghp->gh_flags |= GPH_FILTEE;
			pnp->p_info = (void *)ghp;
		}

		/*
		 * Audit the filter/filtee established.  A return of 0
		 * indicates the auditor wishes to ignore this filtee.
		 */
		if (nlmp && (lml->lm_tflags | FLAGS1(flmp)) &
		    LML_TFLG_AUD_OBJFILTER) {
			if (audit_objfilter(flmp, ref, nlmp, 0) == 0) {
				audit = 1;
				nlmp = 0;
			}
		}

		/*
		 * Finish processing the objects associated with this request.
		 */
		if (nlmp && ghp && ((analyze_lmc(lml, nlmco, nlmp) == 0) ||
		    (relocate_lmc(lml, nlmco, nlmp) == 0)))
			nlmp = 0;

		/*
		 * Finally, if the filtee is part of a link-map control list
		 * that is equivalent, or less, than the filter control list,
		 * create an association between the filter and filtee.  This
		 * association provides sufficient information to tear down the
		 * filter and filtee if necessary.
		 */
		if (nlmp && ghp && (CNTL(nlmp) <= CNTL(flmp)) &&
		    (hdl_add(ghp, flmp, GPD_FILTER) == 0))
			nlmp = 0;

		/*
		 * If this object is marked an end-filtee, we're done.
		 */
		if (nlmp && ghp && (FLAGS1(nlmp) & FL1_RT_ENDFILTE))
			unused = 1;

		/*
		 * Generate a diagnostic if the filtee couldn't be loaded, null
		 * out the pnode entry, and continue the search.
		 */
		if (nlmp == 0) {
			pnp->p_info = 0;
			DBG_CALL(Dbg_file_filtee(0, pnp->p_name, audit));
			if (ghp)
				(void) dlclose_core(ghp, flmp);

			pnp->p_len = 0;
		}
	}

	free(fdalp);
	return (fpnp);
}

Pnode *
hwcap_filtees(Pnode **pnpp, Aliste nlmco, Dyninfo *dip, Rt_map *flmp,
    const char *ref, int mode, uint_t flags)
{
	Pnode		*pnp = *pnpp;
	const char	*dir = pnp->p_name;

	DBG_CALL(Dbg_cap_hw_filter(dir, NAME(flmp)));

	if ((pnp = _hwcap_filtees(pnpp, nlmco, flmp, ref, dir, mode,
	    flags)) != 0)
		return (pnp);

	/*
	 * If no hardware capability filtees have been found, provide suitable
	 * diagnostics and mark the incoming Pnode as unused.
	 */
	if ((LIST(flmp)->lm_flags & LML_FLG_TRC_ENABLE) &&
	    (dip->di_flags & FLG_DI_AUXFLTR) && (rtld_flags & RT_FL_WARNFLTR))
		(void) printf(MSG_INTL(MSG_LDD_HWCAP_NFOUND), dir);

	DBG_CALL(Dbg_cap_hw_filter(dir, 0));

	pnp = *pnpp;
	pnp->p_len = 0;
	return (pnp);
}

/*
 * Load an individual hardware capabilities object.
 */
Rt_map *
load_hwcap(Lm_list *lml, Aliste lmco, const char *dir, Rt_map *clmp,
    uint_t mode, uint_t flags, Grp_hdl **hdl, Rej_desc *rej)
{
	Alist		*fdalp = 0;
	Aliste		off;
	Fdesc		*fdp;
	int		found = 0;
	Rt_map		*lmp = 0;

	/*
	 * Obtain the sorted list of hardware capabilites objects available.
	 */
	if (hwcap_dir(&fdalp, lml, dir, clmp, flags, rej) == 0)
		return (0);

	/*
	 * From the list of hardware capability objects, use the first and
	 * discard the rest.
	 */
	for (ALIST_TRAVERSE(fdalp, off, fdp)) {
		if ((found == 0) && ((lmp = load_path(lml, lmco, fdp->fd_nname,
		    clmp, mode, flags, hdl, fdp, rej)) != 0))
			found++;

		/*
		 * Remove the used file descriptor and any objects remaining.
		 */
		remove_fdesc(fdp);
	}

	free(fdalp);
	return (lmp);
}