/*
 * 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 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */
   
/*	Copyright (c) 1988 AT&T	*/
/*	Copyright (c) 1989 AT&T	*/
/*	  All Rights Reserved  	*/


/* UNIX HEADER */
#include <stdio.h>

/* SIZE HEADERS */
#include "defs.h"

/* ELF HEADERS */
#include "gelf.h"


/* SIZE FUNCTIONS CALLED */
extern void	error();


/* FORMAT STRINGS */

static const char *prusect[3] = {
	"%llx",
	"%llo",
	"%lld"
};

static const char *prusum[3] = {
	" = 0x%llx\n",
	" = 0%llo\n",
	" = %lld\n"
};

static const char *format[3] = {
	"%llx + %llx + %llx = 0x%llx\n",
	"%llo + %llo + %llo = 0%llo\n",
	"%lld + %lld + %lld = %lld\n"
};

static void	process_phdr(Elf *elf, GElf_Half num);

void
process(Elf * elf)
{
	/* EXTERNAL VARIABLES USED */
	extern int	fflag, /* full format for sections */
			Fflag, /* full format for segments */
			nflag; /* include non-loadable segments or sections */
	extern int	numbase; /* hex, octal, or decimal */
	extern char	*fname;
	extern char	*archive;
	extern int	is_archive;
	extern int	oneflag;

	/* LOCAL VARIABLES */
	GElf_Xword	size, /* total size in non-default case for sections */
			/*
			 * size of first, second, third number and total size
			 * in default case for sections.
			 */
			first,
			second,
			third,
			totsize;
	GElf_Ehdr	ehdr;
	GElf_Shdr	shdr;
	Elf_Scn		*scn;
	unsigned	ndx = 0;
	int		numsect = 0;
	int		notfirst = 0;
	int		i;
	char		*name = 0;

/*
 * If there is a program header and the -f flag requesting section infor-
 * mation is not set, then process segments with the process_phdr function.
 * Otherwise, process sections.  For the default case, the first number
 * shall be the size of all sections that are allocatable, nonwritable and
 * not of type NOBITS; the second number shall be the size of all sections
 * that are allocatable, writable, and not of type NOBITS; the third number
 * is the size of all sections that are writable and not of type NOBITS.
 * If -f is set, print the size of each allocatable section, followed by
 * the section name in parentheses.
 * If -n is set, print the size of all sections, followed by the section
 * name in parentheses.
 */

	if (gelf_getehdr(elf, &ehdr) == 0) {
		error(fname, "invalid file type");
		return;
	}
	if ((ehdr.e_phnum != 0) && !(fflag)) {
		process_phdr(elf, ehdr.e_phnum);
		return;
	}

	if (is_archive) {
		(void) printf("%s[%s]: ", archive, fname);
	} else if (!oneflag && !is_archive) {
		(void) printf("%s: ", fname);
	}
	ndx = ehdr.e_shstrndx;
	scn = 0;
	size = 0;
	first = second = third = totsize = 0;
	if (ehdr.e_shnum == 0) {
		error(fname, "no section data");
	}
	numsect = ehdr.e_shnum;
	for (i = 0; i < numsect; i++) {
		if ((scn = elf_nextscn(elf, scn)) == 0) {
			break;
		}
		if (gelf_getshdr(scn, &shdr) == 0) {
			error(fname, "could not get section header");
			break;
		}
		if ((Fflag) && !(fflag)) {
			error(fname, "no segment data");
			return;
		} else if ((!(shdr.sh_flags & SHF_ALLOC)) &&
			fflag && !(nflag)) {
			continue;
		} else if ((!(shdr.sh_flags & SHF_ALLOC)) && !(nflag)) {
			continue;
		} else if ((shdr.sh_flags & SHF_ALLOC) &&
			(!(shdr.sh_flags & SHF_WRITE)) &&
			(!(shdr.sh_type == SHT_NOBITS)) &&
			!(fflag) && !(nflag)) {
			first += shdr.sh_size;
		} else if ((shdr.sh_flags & SHF_ALLOC) &&
			(shdr.sh_flags & SHF_WRITE) &&
			(!(shdr.sh_type == SHT_NOBITS)) &&
			!(fflag) && !(nflag)) {
			second += shdr.sh_size;
		} else if ((shdr.sh_flags & SHF_WRITE) &&
			(shdr.sh_type == SHT_NOBITS) &&
			!(fflag) && !(nflag)) {
			third += shdr.sh_size;
		}
		name = elf_strptr(elf, ndx, (size_t)shdr.sh_name);

		if (fflag || nflag) {
			size += shdr.sh_size;
			if (notfirst) {
				(void) printf(" + ");
			}
			(void) printf(prusect[numbase], shdr.sh_size);
			(void) printf("(%s)", name);
		}
		notfirst++;
	}
	if ((fflag || nflag) && (numsect > 0)) {
		(void) printf(prusum[numbase], size);
	}

	if (!fflag && !nflag) {
		totsize = first + second + third;
		(void) printf(format[numbase],
			first, second, third, totsize);
	}

	if (Fflag) {
		if (ehdr.e_phnum != 0) {
			process_phdr(elf, ehdr.e_phnum);
			return;
		} else {
			error(fname, "no segment data");
			return;
		}
	}
}

/*
 * If there is a program exection header, process segments. In the default
 * case, the first number is the file size of all nonwritable segments
 * of type PT_LOAD; the second number is the file size of all writable
 * segments whose type is PT_LOAD; the third number is the memory size
 * minus the file size of all writable segments of type PT_LOAD.
 * If the -F flag is set, size will print the memory size of each loadable
 * segment, followed by its permission flags.
 * If -n is set, size will print the memory size of all loadable segments
 * and the file size of all non-loadable segments, followed by their
 * permission flags.
 */

static void
process_phdr(Elf * elf, GElf_Half num)
{
	int		i;
	int		notfirst = 0;
	GElf_Phdr	p;
	GElf_Xword	memsize,
			total,
			First,
			Second,
			Third,
			Totsize;
		extern int Fflag;
		extern int nflag;
		extern int numbase;
		extern char *fname;
		extern char *archive;
		extern int is_archive;
		extern int oneflag;

	memsize = total = 0;
	First = Second = Third = Totsize = 0;

	if (is_archive) {
		(void) printf("%s[%s]: ", archive, fname);
	} else if (!oneflag && !is_archive) {
		(void) printf("%s: ", fname);
	}

	for (i = 0; i < (int)num; i++) {
		if (gelf_getphdr(elf, i, &p) == NULL) {
			error(fname, "no segment data");
			return;
		}
		if ((!(p.p_flags & PF_W)) &&
		    (p.p_type == PT_LOAD) && !(Fflag)) {
			First += p.p_filesz;
		} else if ((p.p_flags & PF_W) &&
		    (p.p_type == PT_LOAD) && !(Fflag)) {
			Second += p.p_filesz;
			Third += p.p_memsz;
		}
		memsize += p.p_memsz;
		if ((p.p_type == PT_LOAD) && nflag) {
			if (notfirst) {
				(void) printf(" + ");
			}
			(void) printf(prusect[numbase], p.p_memsz);
			total += p.p_memsz;
			notfirst++;
		}
		if (!(p.p_type == PT_LOAD) && nflag) {
			if (notfirst) {
				(void) printf(" + ");
			}
			(void) printf(prusect[numbase], p.p_filesz);
			total += p.p_filesz;
			notfirst++;
		}
		if ((p.p_type == PT_LOAD) && Fflag && !nflag) {
			if (notfirst) {
				(void) printf(" + ");
			}
			(void) printf(prusect[numbase], p.p_memsz);
			notfirst++;
		}
		if ((Fflag) && !(nflag) && (!(p.p_type == PT_LOAD))) {
			continue;
		}
		if (Fflag || nflag) {
			switch (p.p_flags) {
			case 0: (void) printf("(---)"); break;
			case PF_X: (void) printf("(--x)"); break;
			case PF_W: (void) printf("(-w-)"); break;
			case PF_W+PF_X: (void) printf("(-wx)"); break;
			case PF_R: (void) printf("(r--)"); break;
			case PF_R+PF_X: (void) printf("(r-x)"); break;
			case PF_R+PF_W: (void) printf("(rw-)"); break;
			case PF_R+PF_W+PF_X: (void) printf("(rwx)"); break;
			default: (void) printf("flags(%#x)", p.p_flags);
			}
		}
	}
	if (nflag) {
		(void) printf(prusum[numbase], total);
	}
	if (Fflag && !nflag) {
		(void) printf(prusum[numbase], memsize);
	}
	if (!Fflag && !nflag) {
		Totsize = First + Second + (Third - Second);
		(void) printf(format[numbase],
			First, Second, Third - Second, Totsize);
	}
}