/*

 This code was written by Fred Gilham, and has been placed in the public
 domain.  It is provided "as-is" and without warranty of any kind.

 Modified to add elf write code for writing out executables using
 (save-lisp).  FMG 27-Aug-2003

 Above changes put into main CVS branch. 05-Jul-2007.

 $Id: elf.c,v 1.14 2007/08/14 15:57:47 rtoy Exp $
*/

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>

#include "os.h"
#include "core.h"
#include "internals.h"
#include "globals.h"
#include "elf.h"

static char elf_magic_string[] = {ELFMAG0, ELFMAG1, ELFMAG2, ELFMAG3};

/* Elf data structures. */
static Elf_Ehdr eh;
static Elf_Shdr sh;

/* Names of the Lisp image ELF sections. These names must be the same as
   the corresponding names found in the linker script.  */

static char *section_names[] = {"CORDYN", "CORSTA", "CORRO"};

#ifdef SOLARIS
#include "sparc-validate.h"
/*
 * Starting address of the three ELF sections/spaces.  These must be
 * in the same order as section_names above!
 *
 * XXX: Should we combine these into a structure?  Should we do this
 * for all architectures?
 */
static os_vm_address_t section_addr[] =
{
    DYNAMIC_0_SPACE_START,
    STATIC_SPACE_START,
    READ_ONLY_SPACE_START
};
#endif  

/* Note: write errors are not fatal. */
static int
ewrite(int fd, const void *buf, size_t nbytes, const char *func)
{
    if (write(fd, buf, nbytes) < nbytes) {
	perror(func);
	return -1;	/* Simple way to indicate error. */
    }
    return 0;
}

/*
  Read errors are fatal, because these reads have to succeed for lisp to
  get off the ground.
 */
static void
eread(int d, void *buf, size_t nbytes, const char *func)
{
    int res = read(d, buf, nbytes);

    if (res == -1) {
	perror(func);
	exit(-1);
    }

    if (res < nbytes) {
	fprintf(stderr, "Short read in %s!\n", func);
	exit(-1);
    }
}

static void
elseek(int d, off_t o, const char *func)
{
    if (lseek(d, o, SEEK_SET) == -1) {
	perror(func);
	exit(-1);
    }
}


static int
create_elf_file (const char *dir, int id)
{
    char outfilename[MAXPATHLEN + 1];
    int out;

    /* Note: the space id will be either 1, 2 or 3.  Subtract one to index
       the name array. */
    snprintf(outfilename, MAXPATHLEN, "%s/%s.o", dir, section_names[id - 1]);
    out = open(outfilename, O_WRONLY | O_CREAT | O_TRUNC, 0666);

    if(!out) {
	perror("write_elf_object: can't open file");
	fprintf(stderr, "%s\n", outfilename);
    }

    return out;
}


static int
write_elf_header(int fd)
{
    extern Elf_Ehdr eh;

    /* Ident array. */
    eh.e_ident[EI_MAG0]		= ELFMAG0;
    eh.e_ident[EI_MAG1]		= ELFMAG1;
    eh.e_ident[EI_MAG2]		= ELFMAG2;
    eh.e_ident[EI_MAG3]		= ELFMAG3;

    eh.e_ident[EI_CLASS]	= ELFCLASS32;
#ifdef SOLARIS
    eh.e_ident[EI_DATA]		= ELFDATA2MSB;
#else
    eh.e_ident[EI_DATA]		= ELFDATA2LSB;
#endif
    eh.e_ident[EI_VERSION]	= EV_CURRENT;
#ifdef SOLARIS
    eh.e_ident[EI_OSABI]	= ELFOSABI_SOLARIS;
#else
    eh.e_ident[EI_OSABI]	= ELFOSABI_FREEBSD;
#endif

#ifdef SOLARIS
    eh.e_type		= ET_REL;	/* ???? */
#else
    eh.e_type		= ET_NONE;	/* ???? */
#endif
#ifdef SOLARIS
    /*
     * We only support 32-bit code right now, and our binaries are
     * v8plus binaries.
     */
    eh.e_machine	= EM_SPARC32PLUS;
#else
    eh.e_machine	= EM_386;
#endif
    eh.e_version	= EV_CURRENT;
    eh.e_entry		= 0;
    eh.e_phoff		= 0;
    eh.e_shoff		= sizeof(Elf_Ehdr);
    eh.e_flags		= 0;
    eh.e_ehsize		= sizeof(Elf_Ehdr);
    eh.e_phentsize	= 0;
    eh.e_phnum		= 0;
    eh.e_shentsize	= sizeof(Elf_Shdr);
    eh.e_shnum		= 3;		/* Number of lisp spaces. */
    eh.e_shstrndx	= 2;

    return ewrite(fd, &eh, sizeof(Elf_Ehdr), __func__);
}


static int
write_zero_section_header(int fd)
{
    /* Section index 0. */
    sh.sh_name		= 0;
    sh.sh_type		= SHT_NULL;
    sh.sh_flags		= 0;
    sh.sh_addr		= 0;
    sh.sh_offset	= 0;
    sh.sh_size		= 0;
    sh.sh_link		= SHN_UNDEF;
    sh.sh_info		= 0;
    sh.sh_addralign	= 0;
    sh.sh_entsize	= 0;

    return ewrite(fd, &sh, eh.e_shentsize, __func__);
}


static char *string_table_name = ".shstrtab";
static char *object_name;

static int
write_object_section_header(int fd, long length, os_vm_address_t addr)
{
    extern Elf_Shdr sh;
    Elf_Word flags = SHF_ALLOC | SHF_EXECINSTR | SHF_WRITE;

    sh.sh_name		= strlen(string_table_name) + 2;
    sh.sh_type		= SHT_PROGBITS;
    sh.sh_flags		= flags;
    sh.sh_addr		= (Elf_Addr)addr;

    /* The object section offset is figured out as follows: elf-header +
       three section headers + string table.  The string table is the
       initial null byte followed by the name strings for the string
       table section and the object section. */
    sh.sh_offset	= sizeof(Elf_Ehdr) + 3 * sizeof(Elf_Shdr) + 1 +
	(strlen(string_table_name) + 1) + (strlen(object_name) + 1);
    sh.sh_size		= length;
    sh.sh_link		= SHN_UNDEF;
    sh.sh_info		= 0;
    sh.sh_addralign	= os_vm_page_size;	/* Must be page aligned. */
    sh.sh_entsize	= 0;

    return ewrite(fd, &sh, eh.e_shentsize, __func__);
}


static int
write_string_section_header(int fd)
{
    /* Section index 0. */

    extern Elf_Shdr sh;

    sh.sh_name		= 1;
    sh.sh_type		= SHT_STRTAB;
    sh.sh_flags		= 0;
    sh.sh_addr		= 0;
    sh.sh_offset	= sizeof(Elf_Ehdr) + 3 * sizeof(Elf_Shdr);
    /* The size of this section is the lengths of the strings plus the
       nulls. */
    sh.sh_size		= strlen(string_table_name) + strlen(object_name) + 3;
    sh.sh_link		= SHN_UNDEF;
    sh.sh_info		= 0;
    sh.sh_addralign	= 0;
    sh.sh_entsize	= 0;

    return ewrite(fd, &sh, eh.e_shentsize, __func__);
}


static int
write_string_section(int fd)
{
    char *buffer = malloc(1 + strlen(string_table_name) + 1 +
			  strlen(object_name) + 1);
    int ret;

    if(buffer == NULL) {

	write(0, "Out of memory in write_string_section()\n", 40);
	ret = -1;

    } else {

	*buffer = '\0';
	memcpy(buffer + 1, string_table_name, strlen(string_table_name) + 1);
	memcpy(buffer + 1 + strlen(string_table_name) + 1,
	       object_name,
	       strlen(object_name) + 1);
	ret = ewrite(fd, buffer,
		     1 + strlen(string_table_name) + 1 + strlen(object_name) + 1,
		     __func__);
	free(buffer);
    }

    return ret;
}


static int
write_object_section(int fd, long length, os_vm_address_t real_addr)
{
    return ewrite(fd, (void *)real_addr, length, __func__);
}


int
write_elf_object(const char *dir, int id, os_vm_address_t start, os_vm_address_t end)
{
    int out = create_elf_file(dir, id);
    int ret = 0;
    /* The length should be a multiple of the page size. */
    size_t length = end - start + (os_vm_page_size -
				   ((end - start) % os_vm_page_size));

    if(id < 1 || id > 3) {
	fprintf(stderr, "Invalid space id in %s: %d\n", __func__, id);
	fprintf(stderr, "Executable not built.\n");
	ret = -1;
    }

    /* Make id be 0-based to match array. */
    id--;

    object_name = section_names[id];

    if ((write_elf_header(out) == -1) ||
	(write_zero_section_header(out) == -1) ||
	(write_object_section_header(out, length, start) == -1) ||
	(write_string_section_header(out) == -1) ||
	(write_string_section(out) == -1) ||
	(write_object_section(out, length, start) == -1)) {
	fprintf(stderr, "Executable not built.\n");
	ret = -1;
    }

    close(out);
    return ret;
}

void
elf_cleanup(const char *dirname)
{
    char filename[MAXPATHLEN];
    int i;

    /* Get rid of lisp space files. */
    for(i = 0; i < 3; i++) {
	/* Delete core space .o files. */
	sprintf(filename, "%s/%s.o", dirname, section_names[i]);
	unlink(filename);
    }
}

int
elf_run_linker(long init_func_address, char *file)
{

    lispobj libstring = SymbolValue(CMUCL_LIB);     /* Get library: */
    struct vector *vec = (struct vector *)PTR(libstring);
    char *paths = strdup((char *)vec->data);
    char command[MAXPATHLEN + 1];
    char command_line[MAXPATHLEN + MAXPATHLEN + 10];
    char *strptr;
    struct stat st;
    int ret;

    strptr = strtok(paths, ":");

    while(strptr != NULL) {

	sprintf(command, "%s/%s", strptr, LINKER_SCRIPT);

	if(stat(command, &st) == 0) {
	    free(paths);
	    printf("\t[%s: linking %s... \n", command, file);
	    fflush(stdout);
	    sprintf(command_line, "%s %s 0x%lx %s", command, C_COMPILER,
                    init_func_address, file);
	    ret = system(command_line);
	    if(ret == -1) {
		perror("Can't run link script");
	    } else {
		printf("\tdone]\n");
		fflush(stdout);
	    }
	    return ret;
	}
	strptr = strtok(NULL, ":");
    }

    fprintf(stderr,
	    "Can't find %s script in CMUCL library directory list.\n", LINKER_SCRIPT);
    free(paths);
    return -1;
}


/* Read the ELF header from a file descriptor and stuff it into a
	 structure.	 Make sure it is really an elf header etc. */
static void
read_elf_header(int fd, Elf_Ehdr *ehp)
{
    eread(fd, ehp, sizeof(Elf_Ehdr), __func__);

    if (strncmp(ehp->e_ident, elf_magic_string, 4)) {
	fprintf(stderr,
		"Bad ELF magic number --- not an elf file.	Exiting in %s.\n",
		__func__);
	exit(-1);
    }
}


static void
read_section_header_entry(int fd, Elf_Shdr *shp)
{
    eread(fd, shp, eh.e_shentsize, __func__);
}


/*
  Map the built-in lisp core sections.

  NOTE!  We need to do this without using malloc because the memory layout
  is not set until some time after this is done.
*/
void
map_core_sections(char *exec_name)
{
    int exec_fd;
    Elf_Shdr sh;		/* A section header entry. */
    Elf_Shdr strsecent;
    char nambuf[10];
    int soff;
    int strsecoff;		/* File offset to string table section. */
    int sections_remaining = 3;
    int i, j;
    extern int
	image_dynamic_space_size,
	image_static_space_size,
	image_read_only_space_size;

    if (!(exec_fd = open(exec_name, O_RDONLY))) {
	perror("Can't open executable!");
	exit(-1);
    }

    read_elf_header(exec_fd, &eh);

    /* Find the section name string section.	Save its file offset. */
    soff = eh.e_shoff + eh.e_shstrndx * eh.e_shentsize;
    elseek(exec_fd, soff, __func__);
    read_section_header_entry(exec_fd, &strsecent);
    strsecoff = strsecent.sh_offset;

    for (i = 0; i < eh.e_shnum && sections_remaining > 0; i++) {

	/* Read an entry from the section header table. */
	elseek(exec_fd, eh.e_shoff + i * eh.e_shentsize, __func__);
	read_section_header_entry(exec_fd, &sh);

	/* Read the name from the string section. */
	elseek(exec_fd, strsecoff + sh.sh_name, __func__);
	eread(exec_fd, nambuf, 6, __func__);

	if (sh.sh_type == SHT_PROGBITS) {
	    /* See if this section is one of the lisp core sections. */
	    for (j = 0; j < 3; j++) {
		if (!strncmp(nambuf, section_names[j], 6)) {
		    os_vm_address_t addr;

#ifdef SOLARIS
		    /*
		     * On Solaris, the section header sets the addr
		     * field to 0 because the linker script says the
		     * sections are NOTE sections.  Hence, we need to
		     * look up the section addresses ourselves.
		     */
		    addr = section_addr[j];
#else
		    addr = (os_vm_address_t) sh.sh_addr;
#endif                  
		    /* Found a core section. Map it! */
		    if ((os_vm_address_t) os_map(exec_fd, sh.sh_offset,
						 addr, sh.sh_size)
			== (os_vm_address_t) -1) {
			fprintf(stderr, "%s: Can't map section %s\n", __func__, section_names[j]);
			exit(-1);
		    }
		    switch(j) {
		    case 0: /* Dynamic space. */
		        /* Dynamic space variables are set in lisp.c. */
			image_dynamic_space_size = sh.sh_size;
			break;
		    case 1: /* Static space. */
			image_static_space_size = sh.sh_size;
			break;
		    case 2: /* Read-only space. */
			image_read_only_space_size = sh.sh_size;
			break;
		    default:
			/* Should never get here. */
			abort();
			break;
		    }

		    sections_remaining--;
		    /* Found a core section, don't check the other core section names. */
		    break;
		}
	    }
	}
    }

    close(exec_fd);

    if (sections_remaining != 0) {
	fprintf(stderr, "Couldn't map all core sections!	Exiting!\n");
	exit(-1);
    }
}