/*
 * 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 2007 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */
#pragma ident	"%Z%%M%	%I%	%E% SMI"

/*
 * Dump an elf file.
 */
#include	<sys/param.h>
#include	<fcntl.h>
#include	<stdio.h>
#include	<stdlib.h>
#include	<ctype.h>
#include	<libelf.h>
#include	<link.h>
#include	<stdarg.h>
#include	<unistd.h>
#include	<libgen.h>
#include	<libintl.h>
#include	<locale.h>
#include	<errno.h>
#include	<strings.h>
#include	<debug.h>
#include	<conv.h>
#include	<msg.h>
#include	<_elfdump.h>

const Cache	cache_init = {NULL, NULL, NULL, NULL, 0};



/* MATCH is  used to retain information about -N and -I options */
typedef enum {
	MATCH_T_NAME,		/* Record contains a name */
	MATCH_T_NDX,		/* Record contains a single index */
	MATCH_T_RANGE		/* Record contains an index range */
} MATCH_T;

typedef struct _match {
	struct _match	*next;		/* Pointer to next item in list */
	MATCH_T		type;
	union {
		const char	*name;	/* MATCH_T_NAME */
		struct {		/* MATCH_T_NDX and MATCH_T_RANGE */
			int	start;
			int	end;	/* Only for MATCH_T_RANGE */
		} ndx;
	} value;
} MATCH;

/* List of MATCH records used by match() to implement -N and -I options */
static MATCH *match_list = NULL;

const char *
_elfdump_msg(Msg mid)
{
	return (gettext(MSG_ORIG(mid)));
}

/*
 * Determine whether a symbol name should be demangled.
 */
const char *
demangle(const char *name, uint_t flags)
{
	if (flags & FLG_DEMANGLE)
		return (Elf_demangle_name(name));
	else
		return ((char *)name);
}

/*
 * Define our own standard error routine.
 */
void
failure(const char *file, const char *func)
{
	(void) fprintf(stderr, MSG_INTL(MSG_ERR_FAILURE),
	    file, func, elf_errmsg(elf_errno()));
}

/*
 * The full usage message
 */
static void
detail_usage()
{
	(void) fprintf(stderr, MSG_INTL(MSG_USAGE_DETAIL1));
	(void) fprintf(stderr, MSG_INTL(MSG_USAGE_DETAIL2));
	(void) fprintf(stderr, MSG_INTL(MSG_USAGE_DETAIL3));
	(void) fprintf(stderr, MSG_INTL(MSG_USAGE_DETAIL4));
	(void) fprintf(stderr, MSG_INTL(MSG_USAGE_DETAIL5));
	(void) fprintf(stderr, MSG_INTL(MSG_USAGE_DETAIL6));
	(void) fprintf(stderr, MSG_INTL(MSG_USAGE_DETAIL7));
	(void) fprintf(stderr, MSG_INTL(MSG_USAGE_DETAIL8));
	(void) fprintf(stderr, MSG_INTL(MSG_USAGE_DETAIL9));
	(void) fprintf(stderr, MSG_INTL(MSG_USAGE_DETAIL10));
	(void) fprintf(stderr, MSG_INTL(MSG_USAGE_DETAIL11));
	(void) fprintf(stderr, MSG_INTL(MSG_USAGE_DETAIL12));
	(void) fprintf(stderr, MSG_INTL(MSG_USAGE_DETAIL13));
	(void) fprintf(stderr, MSG_INTL(MSG_USAGE_DETAIL14));
	(void) fprintf(stderr, MSG_INTL(MSG_USAGE_DETAIL15));
	(void) fprintf(stderr, MSG_INTL(MSG_USAGE_DETAIL16));
	(void) fprintf(stderr, MSG_INTL(MSG_USAGE_DETAIL17));
	(void) fprintf(stderr, MSG_INTL(MSG_USAGE_DETAIL18));
	(void) fprintf(stderr, MSG_INTL(MSG_USAGE_DETAIL19));
	(void) fprintf(stderr, MSG_INTL(MSG_USAGE_DETAIL20));
	(void) fprintf(stderr, MSG_INTL(MSG_USAGE_DETAIL21));
	(void) fprintf(stderr, MSG_INTL(MSG_USAGE_DETAIL22));
	(void) fprintf(stderr, MSG_INTL(MSG_USAGE_DETAIL23));
	(void) fprintf(stderr, MSG_INTL(MSG_USAGE_DETAIL24));
}

/*
 * Convert the ASCII representation of an index, or index range, into
 * binary form, and store it in rec:
 *
 *	index: An positive or 0 valued integer
 *	range: Two indexes, separated by a ':' character, denoting
 *		a range of allowed values. If the second value is omitted,
 *		any values equal to or greater than the first will match.
 *
 * exit:
 *	On success, *rec is filled in with a MATCH_T_NDX or MATCH_T_RANGE
 *	value, and this function returns (1). On failure, the contents
 *	of *rec are undefined, and (0) is returned.
 */
int
process_index_opt(const char *str, MATCH *rec)
{
#define	SKIP_BLANK for (; *str && isspace(*str); str++)

	char	*endptr;

	rec->value.ndx.start = strtol(str, &endptr, 10);
	/* Value must use some of the input, and be 0 or positive */
	if ((str == endptr) || (rec->value.ndx.start < 0))
		return (0);
	str = endptr;

	SKIP_BLANK;
	if (*str != ':') {
		rec->type = MATCH_T_NDX;
	} else {
		str++;					/* Skip the ':' */
		rec->type = MATCH_T_RANGE;
		SKIP_BLANK;
		if (*str == '\0') {
			rec->value.ndx.end = -1;	/* Indicates "to end" */
		} else {
			rec->value.ndx.end = strtol(str, &endptr, 10);
			if ((str == endptr) || (rec->value.ndx.end < 0))
				return (0);
			str = endptr;
			SKIP_BLANK;
		}
	}

	/* Syntax error if anything is left over */
	if (*str != '\0')
		return (0);

	return (1);

#undef	SKIP_BLANK
}

/*
 * Returns True (1) if the item with the given name or index should
 * be displayed, and False (0) if it should not be.
 *
 * entry:
 *	strict - A strict match requires an explicit match to
 *		a user specified -I or -N option. A non-strict match
 *		succeeds if the match list is empty.
 *	name - Name of item under consideration, or NULL if the name
 *		should not be considered.
 *	ndx - if (ndx >= 0) index of item under consideration.
 *		A negative value indicates that the item has no index.
 *
 * exit:
 *	True will be returned if the given name/index matches those given
 *	by one of the -N or -I command line options, or if no such option
 *	was used in the command invocation.
 */
int
match(int strict, const char *name, int ndx)
{
	MATCH *list;

	/* If no match options were specified, allow everything */
	if (!strict && (match_list == NULL))
		return (1);

	/* Run through the match records and check for a hit */
	for (list = match_list; list; list = list->next) {
		switch (list->type) {
		case MATCH_T_NAME:
			if ((name != NULL) &&
			    (strcmp(list->value.name, name) == 0))
				return (1);
			break;
		case MATCH_T_NDX:
			if (ndx == list->value.ndx.start)
				return (1);
			break;
		case MATCH_T_RANGE:
			/*
			 * A range end value less than 0 means that any value
			 * above the start is acceptible.
			 */
			if ((ndx >= list->value.ndx.start) &&
			    ((list->value.ndx.end < 0) ||
			    (ndx <= list->value.ndx.end)))
				return (1);
			break;
		}
	}

	/* Nothing matched */
	return (0);
}

/*
 * Add an entry to match_list for use by match().
 *
 * Return True (1) for success. On failure, an error is written
 * to stderr, and False (0) is returned.
 */
static int
add_match_record(char *argv0, MATCH *data)
{
	MATCH *rec;
	MATCH *list;

	if ((rec = malloc(sizeof (*rec))) == NULL) {
		int err = errno;
		(void) fprintf(stderr, MSG_INTL(MSG_ERR_MALLOC),
		    basename(argv0), strerror(err));
		return (0);
	}

	*rec = *data;

	/* Insert at end of match_list */
	if (match_list == NULL) {
		match_list = rec;
	} else {
		for (list = match_list; list->next != NULL; list = list->next)
			;
		list->next = rec;
	}

	rec->next = NULL;
	return (1);
}

static void
decide(const char *file, int fd, Elf *elf, uint_t flags, int wfd)
{
	if (gelf_getclass(elf) == ELFCLASS64)
		regular64(file, fd, elf, flags, wfd);
	else
		regular32(file, fd, elf, flags, wfd);
}

static void
archive(const char *file, int fd, Elf *elf, uint_t flags, int wfd)
{
	Elf_Cmd		cmd = ELF_C_READ;
	Elf_Arhdr	*arhdr;
	Elf		*_elf = 0;
	size_t		ptr;
	Elf_Arsym	*arsym = 0;

	/*
	 * Determine if the archive symbol table itself is required.
	 */
	if ((flags & FLG_SYMBOLS) && match(0, MSG_ORIG(MSG_ELF_ARSYM), -1)) {
		/*
		 * Get the archive symbol table.
		 */
		if (((arsym = elf_getarsym(elf, &ptr)) == 0) && elf_errno()) {
			/*
			 * The arsym could be 0 even though there was no error.
			 * Print the error message only when there was
			 * real error from elf_getarsym().
			 */
			failure(file, MSG_ORIG(MSG_ELF_GETARSYM));
			return;
		}
	}

	/*
	 * Print the archive symbol table only when the archive symbol
	 * table exists and it was requested to print.
	 */
	if (arsym) {
		size_t		cnt;
		char		index[MAXNDXSIZE];
		size_t		offset = 0, _offset = 0;

		/*
		 * Print out all the symbol entries.
		 */
		dbg_print(0, MSG_INTL(MSG_ARCHIVE_SYMTAB));
		dbg_print(0, MSG_INTL(MSG_ARCHIVE_FIELDS));

		for (cnt = 0; cnt < ptr; cnt++, arsym++) {
			/*
			 * For each object obtain an elf descriptor so that we
			 * can establish the members name.  Note, we have had
			 * archives where the archive header has not been
			 * obtainable so be lenient with errors.
			 */
			if ((offset == 0) || ((arsym->as_off != 0) &&
			    (arsym->as_off != _offset))) {

				if (_elf)
					(void) elf_end(_elf);

				if (elf_rand(elf, arsym->as_off) !=
				    arsym->as_off) {
					failure(file, MSG_ORIG(MSG_ELF_RAND));
					arhdr = 0;
				} else if ((_elf = elf_begin(fd,
				    ELF_C_READ, elf)) == 0) {
					failure(file, MSG_ORIG(MSG_ELF_BEGIN));
					arhdr = 0;
				} else if ((arhdr = elf_getarhdr(_elf)) == 0) {
					failure(file,
					    MSG_ORIG(MSG_ELF_GETARHDR));
					arhdr = 0;
				}

				_offset = arsym->as_off;
				if (offset == 0)
					offset = _offset;
			}

			(void) snprintf(index, MAXNDXSIZE,
			    MSG_ORIG(MSG_FMT_INDEX), EC_XWORD(cnt));
			if (arsym->as_off)
				dbg_print(0, MSG_ORIG(MSG_FMT_ARSYM1), index,
				    /* LINTED */
				    (int)arsym->as_off, arhdr ? arhdr->ar_name :
				    MSG_INTL(MSG_STR_UNKNOWN), (arsym->as_name ?
				    demangle(arsym->as_name, flags) :
				    MSG_INTL(MSG_STR_NULL)));
			else
				dbg_print(0, MSG_ORIG(MSG_FMT_ARSYM2), index,
				    /* LINTED */
				    (int)arsym->as_off);
		}

		if (_elf)
			(void) elf_end(_elf);

		/*
		 * If we only need the archive symbol table return.
		 */
		if ((flags & FLG_SYMBOLS) &&
		    match(1, MSG_ORIG(MSG_ELF_ARSYM), -1))
			return;

		/*
		 * Reset elf descriptor in preparation for processing each
		 * member.
		 */
		if (offset)
			(void) elf_rand(elf, offset);
	}

	/*
	 * Process each object within the archive.
	 */
	while ((_elf = elf_begin(fd, cmd, elf)) != NULL) {
		char	name[MAXPATHLEN];

		if ((arhdr = elf_getarhdr(_elf)) == NULL) {
			failure(file, MSG_ORIG(MSG_ELF_GETARHDR));
			return;
		}
		if (*arhdr->ar_name != '/') {
			(void) snprintf(name, MAXPATHLEN,
			    MSG_ORIG(MSG_FMT_ARNAME), file, arhdr->ar_name);
			dbg_print(0, MSG_ORIG(MSG_FMT_NLSTR), name);

			switch (elf_kind(_elf)) {
			case ELF_K_AR:
				archive(name, fd, _elf, flags, wfd);
				break;
			case ELF_K_ELF:
				decide(name, fd, _elf, flags, wfd);
				break;
			default:
				(void) fprintf(stderr,
				    MSG_INTL(MSG_ERR_BADFILE), name);
				break;
			}
		}

		cmd = elf_next(_elf);
		(void) elf_end(_elf);
	}
}

int
main(int argc, char **argv, char **envp)
{
	Elf		*elf;
	int		var, fd, wfd = 0;
	char		*wname = 0;
	uint_t		flags = 0;
	MATCH		match_data;

	/*
	 * If we're on a 64-bit kernel, try to exec a full 64-bit version of
	 * the binary.  If successful, conv_check_native() won't return.
	 */
	(void) conv_check_native(argv, envp);

	/*
	 * Establish locale.
	 */
	(void) setlocale(LC_MESSAGES, MSG_ORIG(MSG_STR_EMPTY));
	(void) textdomain(MSG_ORIG(MSG_SUNW_OST_SGS));

	(void) setvbuf(stdout, NULL, _IOLBF, 0);
	(void) setvbuf(stderr, NULL, _IOLBF, 0);

	opterr = 0;
	while ((var = getopt(argc, argv, MSG_ORIG(MSG_STR_OPTIONS))) != EOF) {
		switch (var) {
		case 'C':
			flags |= FLG_DEMANGLE;
			break;
		case 'c':
			flags |= FLG_SHDR;
			break;
		case 'd':
			flags |= FLG_DYNAMIC;
			break;
		case 'e':
			flags |= FLG_EHDR;
			break;
		case 'G':
			flags |= FLG_GOT;
			break;
		case 'g':
			flags |= FLG_GROUP;
			break;
		case 'H':
			flags |= FLG_CAP;
			break;
		case 'h':
			flags |= FLG_HASH;
			break;
		case 'I':
			if (!process_index_opt(optarg, &match_data)) {
				(void) fprintf(stderr,
				    MSG_INTL(MSG_USAGE_BRIEF),
				    basename(argv[0]));
				return (1);
			}
			if (!add_match_record(argv[0], &match_data))
				return (1);
			break;
		case 'i':
			flags |= FLG_INTERP;
			break;
		case 'k':
			flags |= FLG_CHECKSUM;
			break;
		case 'l':
			flags |= FLG_LONGNAME;
			break;
		case 'm':
			flags |= FLG_MOVE;
			break;
		case 'N':
			match_data.type = MATCH_T_NAME;
			match_data.value.name = optarg;
			if (!add_match_record(argv[0], &match_data))
				return (1);
			break;
		case 'n':
			flags |= FLG_NOTE;
			break;
		case 'P':
			flags |= FLG_FAKESHDR;
			break;
		case 'p':
			flags |= FLG_PHDR;
			break;
		case 'r':
			flags |= FLG_RELOC;
			break;
		case 'S':
			flags |= FLG_SORT;
			break;
		case 's':
			flags |= FLG_SYMBOLS;
			break;
		case 'u':
			flags |= FLG_UNWIND;
			break;
		case 'v':
			flags |= FLG_VERSIONS;
			break;
		case 'w':
			wname = optarg;
			break;
		case 'y':
			flags |= FLG_SYMINFO;
			break;
		case '?':
			(void) fprintf(stderr, MSG_INTL(MSG_USAGE_BRIEF),
			    basename(argv[0]));
			detail_usage();
			return (1);
		default:
			break;
		}
	}

	/*
	 * Validate any arguments.
	 */
	if ((flags & ~(FLG_DEMANGLE | FLG_LONGNAME| FLG_FAKESHDR)) == 0) {
		if (!wname && (match_list == NULL)) {
			flags |= FLG_EVERYTHING;
		} else if (!wname || (match_list == NULL)) {
			(void) fprintf(stderr, MSG_INTL(MSG_USAGE_BRIEF),
			    basename(argv[0]));
			return (1);
		}
	}

	if ((var = argc - optind) == 0) {
		(void) fprintf(stderr, MSG_INTL(MSG_USAGE_BRIEF),
		    basename(argv[0]));
		return (1);
	}

	/*
	 * If the -l/-C option is specified, set up the liblddbg.so.
	 */
	if (flags & FLG_LONGNAME)
		dbg_desc->d_extra |= DBG_E_LONG;
	if (flags & FLG_DEMANGLE)
		dbg_desc->d_extra |= DBG_E_DEMANGLE;

	/*
	 * If the -w option has indicated an output file open it.  It's
	 * arguable whether this option has much use when multiple files are
	 * being processed.
	 */
	if (wname) {
		if ((wfd = open(wname, (O_RDWR | O_CREAT | O_TRUNC),
		    0666)) < 0) {
			int err = errno;
			(void) fprintf(stderr, MSG_INTL(MSG_ERR_OPEN),
			    wname, strerror(err));
			wfd = 0;
		}
	}

	/*
	 * Open the input file and initialize the elf interface.
	 */
	for (; optind < argc; optind++) {
		const char	*file = argv[optind];

		if ((fd = open(argv[optind], O_RDONLY)) == -1) {
			int err = errno;
			(void) fprintf(stderr, MSG_INTL(MSG_ERR_OPEN),
			    file, strerror(err));
			continue;
		}
		(void) elf_version(EV_CURRENT);
		if ((elf = elf_begin(fd, ELF_C_READ, NULL)) == NULL) {
			failure(file, MSG_ORIG(MSG_ELF_BEGIN));
			(void) close(fd);
			continue;
		}

		if (var > 1)
			dbg_print(0, MSG_ORIG(MSG_FMT_NLSTRNL), file);

		switch (elf_kind(elf)) {
		case ELF_K_AR:
			archive(file, fd, elf, flags, wfd);
			break;
		case ELF_K_ELF:
			decide(file, fd, elf, flags, wfd);
			break;
		default:
			(void) fprintf(stderr, MSG_INTL(MSG_ERR_BADFILE), file);
			break;
		}

		(void) close(fd);
		(void) elf_end(elf);
	}

	if (wfd)
		(void) close(wfd);
	return (0);
}