/* $Id$ */
/* Copyright (c) 2010-2021 Pierre Pronchery <khorben@defora.org> */
/* This file is part of DeforaOS System Loader */
/* All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */



#include <sys/stat.h>


/* macros */
# undef ELFFUNC
# undef ELFTYPE
#if ELFSIZE == 32
# define ELFFUNC(func) _do_ ## func ## 32
# define ELFTYPE(type) Elf ## 32 ## _ ## type
#elif ELFSIZE == 64
# define ELFFUNC(func) _do_ ## func ## 64
# define ELFTYPE(type) Elf ## 64 ## _ ## type
#else
# error ELFSIZE is not defined
#endif


/* prototypes */
static int ELFFUNC(ldd)(char const * filename, FILE * fp, ELFTYPE(Ehdr) * ehdr,
		char const * ldpath);


/* functions */
/* ldd */
static int ELFFUNC(phdr)(char const * filename, FILE * fp, ELFTYPE(Ehdr) * ehdr,
		ELFTYPE(Phdr) * phdr, char const * ldpath);
static int ELFFUNC(phdr_dyn)(char const * filename, FILE * fp,
		ELFTYPE(Ehdr) * ehdr, ELFTYPE(Phdr) * phdr, ELFTYPE(Dyn) * dyn,
		char const * ldpath);
static int ELFFUNC(phdr_dyn_print)(char const * filename, char const * rpath);
static char * ELFFUNC(string)(char const * filename, FILE * fp,
		ELFTYPE(Ehdr) * ehdr, ELFTYPE(Addr) addr, ELFTYPE(Addr) index);

static int ELFFUNC(ldd)(char const * filename, FILE * fp, ELFTYPE(Ehdr) * ehdr,
		char const * ldpath)
{
	int ret;
	ELFTYPE(Phdr) * phdr;

	if(ehdr->e_phnum == 0)
		return -_error(filename, "No program header found", 1);
	if(ehdr->e_phentsize != sizeof(*phdr))
		return -_error(filename, "Unexpected program header size", 1);
	if(fseek(fp, ehdr->e_phoff, SEEK_SET) != 0)
		return -_error(filename, strerror(errno), 1);
	if((phdr = malloc(sizeof(*phdr) * ehdr->e_phnum)) == NULL)
		return -_error(filename, strerror(errno), 1);
	ret = ELFFUNC(phdr)(filename, fp, ehdr, phdr, ldpath);
	free(phdr);
	return ret;
}

static int ELFFUNC(phdr)(char const * filename, FILE * fp, ELFTYPE(Ehdr) * ehdr,
		ELFTYPE(Phdr) * phdr, char const * ldpath)
{
	int ret = 0;
	size_t i;
	size_t cnt;
	ELFTYPE(Dyn) * dyn;

	if(fread(phdr, sizeof(*phdr), ehdr->e_phnum, fp) != ehdr->e_phnum)
	{
		if(ferror(fp) != 0)
			ret = -_error(filename, strerror(errno), 1);
		else
			ret = -_error(filename, "Corrupted ELF file", 1);
		return ret;
	}
	for(i = 0, cnt = 0; i < ehdr->e_phnum; i++)
	{
		if(phdr[i].p_type != PT_DYNAMIC)
			continue;
		if(cnt++ == 0)
			printf("%s:\n", filename);
		if(fseek(fp, phdr[i].p_offset, SEEK_SET) != 0)
		{
			ret |= -_error(filename, strerror(errno), 1);
			continue;
		}
		if((dyn = malloc(phdr[i].p_filesz)) == NULL)
		{
			ret |= -_error(filename, strerror(errno), 1);
			continue;
		}
		if(fread(dyn, phdr[i].p_filesz, 1, fp) == 1)
			ret = ELFFUNC(phdr_dyn)(filename, fp, ehdr, &phdr[i],
					dyn, ldpath);
		else
		{
			if(ferror(fp) != 0)
				ret |= -_error(filename, strerror(errno), 1);
			else
				ret |= -_error(filename, "Corrupted ELF file",
						1);
		}
		free(dyn);
	}
	if(cnt == 0)
		ret |= -_error(filename, "Not a dynamic ELF object", 1);
	return ret;
}

static int ELFFUNC(phdr_dyn)(char const * filename, FILE * fp,
		ELFTYPE(Ehdr) * ehdr, ELFTYPE(Phdr) * phdr, ELFTYPE(Dyn) * dyn,
		char const * ldpath)
{
	ssize_t s = -1;
	ssize_t r = -1;
	size_t i;
	char * p;
	char * rpath = NULL;

	for(i = 0; (i + 1) * sizeof(*dyn) < phdr->p_filesz; i++)
	{
		if(dyn[i].d_tag == DT_NULL)
			break;
		if(dyn[i].d_tag == DT_STRTAB)
			s = i;
		else if(dyn[i].d_tag == DT_RPATH)
			r = i;
	}
	if(s < 0)
		return -_error(filename, "Missing string section", 1);
	if(s >= 0 && r >= 0)
		rpath = ELFFUNC(string)(filename, fp, ehdr, dyn[s].d_un.d_val,
				dyn[r].d_un.d_val);
#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s() strtab=%ld rpath=%ld \"%s\"\n", __func__,
			dyn[s].d_un.d_val, dyn[r].d_un.d_val, rpath);
#endif
	for(i = 0; (i + 1) * sizeof(*dyn) < phdr->p_filesz; i++)
	{
		if(dyn[i].d_tag == DT_NULL)
			break;
		if(dyn[i].d_tag != DT_NEEDED)
			continue;
		if((p = ELFFUNC(string)(filename, fp, ehdr, dyn[s].d_un.d_ptr,
						dyn[i].d_un.d_val)) == NULL)
			continue;
		if(ELFFUNC(phdr_dyn_print)(p, ldpath) != 0
				&& ELFFUNC(phdr_dyn_print)(p, rpath) != 0
				&& ELFFUNC(phdr_dyn_print)(p,
					"/usr/lib:/lib") != 0)
			printf("\t%s\n", p);
		free(p);
	}
	return 0;
}

static int ELFFUNC(phdr_dyn_print)(char const * filename, char const * rpath)
{
	size_t len = strlen(filename);
	size_t i;
	char * p;
	struct stat st;
	int res;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s(\"%s\", \"%s\")\n", __func__, filename,
			rpath);
#endif
	if(rpath == NULL)
		return -1;
	for(i = 0;;)
	{
		if(rpath[i] != ':' && rpath[i] != '\0')
		{
			i++;
			continue;
		}
		p = malloc(i + len + 2);
		snprintf(p, i + 1, "%s", rpath);
		snprintf(&p[i], len + 2, "/%s", filename);
		if((res = stat(p, &st)) == 0)
			printf("\t%s => %s\n", filename, p);
		free(p);
		if(res == 0)
			return 0;
		if(rpath[i] == '\0')
			break;
		rpath += i + 1;
		i = 0;
	}
	return -1;
}

static char * ELFFUNC(string)(char const * filename, FILE * fp,
		ELFTYPE(Ehdr) * ehdr, ELFTYPE(Addr) addr, ELFTYPE(Addr) index)
{
	char * ret;
	size_t i;
	ELFTYPE(Shdr) shdr;
	char * p = NULL;

	if(fseek(fp, ehdr->e_shoff, SEEK_SET) != 0)
	{
		_error(filename, strerror(errno), 1);
		return NULL;
	}
	for(i = 0; i < ehdr->e_shnum; i++)
	{
		if(fread(&shdr, sizeof(shdr), 1, fp) != 1)
		{
			if(ferror(fp) != 0)
				_error(filename, strerror(errno), 1);
			else
				_error(filename, "Corrupted ELF file", 1);
			break;
		}
		if(shdr.sh_type != SHT_STRTAB)
			continue;
		if(shdr.sh_addr != addr)
			continue;
		if(fseek(fp, shdr.sh_offset, SEEK_SET) != 0
				|| (p = malloc(shdr.sh_size)) == NULL)
		{
			_error(filename, strerror(errno), 1);
			break;
		}
		if(fread(p, sizeof(*p), shdr.sh_size, fp) != shdr.sh_size)
		{
			if(ferror(fp) != 0)
				_error(filename, strerror(errno), 1);
			else
				_error(filename, "Corrupted ELF file", 1);
			break;
		}
		for(i = index; i < shdr.sh_size; i++)
		{
			if(p[i] != '\0')
				continue;
			if((ret = strdup(&p[index])) == NULL)
				_error(filename, strerror(errno), 1);
			free(p);
			return ret;
		}
		break;
	}
	free(p);
	return NULL;
}
