/* Derived from the file src/unexelf.c written by Spencer W. Thomas
 * (from the Emacs 18.58 distribution), to which the following copyright
 * applies:
 *
 *   Copyright (C) 1985, 1986, 1987, 1988 Free Software Foundation, Inc.
 *
 *    This program is free software; you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation; either version 1, or (at your option)
 *    any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program; if not, write to the Free Software
 *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *  In other words, you are welcome to use, share and improve this program.
 *  You are forbidden to forbid anyone else to use, share and improve
 *  what you give them.   Help stamp out software-hoarding!
 */
#include <sys/types.h>
#include <sys/stat.h>
#include <memory.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <elf.h>
#include <sys/mman.h>

/* Get the address of a particular section or program header entry,
 * accounting for the size of the entries.
 */
#define OLD_SECTION_H(n) \
     (*(Elf32_Shdr *) ((byte *) old_section_h + old_file_h->e_shentsize * (n)))
#define NEW_SECTION_H(n) \
     (*(Elf32_Shdr *) ((byte *) new_section_h + new_file_h->e_shentsize * (n)))
#define OLD_PROGRAM_H(n) \
     (*(Elf32_Phdr *) ((byte *) old_program_h + old_file_h->e_phentsize * (n)))
#define NEW_PROGRAM_H(n) \
     (*(Elf32_Phdr *) ((byte *) new_program_h + new_file_h->e_phentsize * (n)))

typedef unsigned char byte;

static int round_up (x, y) int x, y; {
    int rem = x % y;

    if (rem == 0)
	return x;
    return x - rem + y;
}

Object P_Dump (ofile) Object ofile; {
    unsigned int bss_end;
    int new_file_size;
    caddr_t old_base, new_base;
    /* Pointers to the file, program and section headers for the old and new
     * files.
     */
    Elf32_Ehdr *old_file_h, *new_file_h;
    Elf32_Phdr *old_program_h, *new_program_h;
    Elf32_Shdr *old_section_h, *new_section_h;
    /* Point to the section name table in the old file.
     */
    char *old_section_names;
    Elf32_Addr old_bss_addr, new_bss_addr;
    Elf32_Word old_bss_size, new_data2_size;
    Elf32_Off  new_data2_offset;
    Elf32_Addr new_data2_addr;
    int n, old_bss_index, old_data_index, new_data2_index;
    struct stat stat_buf;

    Dump_Prolog;

    Was_Dumped = 1;

    if (fstat (afd, &stat_buf) == -1) {
	Dump_Finalize;
	Primitive_Error ("cannot stat old a.out file: ~E");
    }
    old_base = mmap (0, stat_buf.st_size, PROT_READ, MAP_SHARED, afd, 0);
    if (old_base == (caddr_t)-1) {
	Dump_Finalize;
	Primitive_Error ("cannot mmap old a.out file: ~E");
    }
    /* Get pointers to headers & section names.
     */
    old_file_h = (Elf32_Ehdr *) old_base;
    old_program_h = (Elf32_Phdr *) ((byte *) old_base + old_file_h->e_phoff);
    old_section_h = (Elf32_Shdr *) ((byte *) old_base + old_file_h->e_shoff);
    old_section_names = (char *) old_base
	+ OLD_SECTION_H(old_file_h->e_shstrndx).sh_offset;

    /* Find the old .bss section.  Figure out parameters of the new
     * data2 and bss sections.
     */
    for (old_bss_index = 1; old_bss_index < old_file_h->e_shnum;
			    old_bss_index++) {
	if (strcmp (old_section_names + OLD_SECTION_H(old_bss_index).sh_name,
		".bss") == 0)
	    break;
    }
    if (old_bss_index == old_file_h->e_shnum) {
	Dump_Finalize;
	Primitive_Error ("cannot find .bss in old a.out file");
    }
    old_bss_addr = OLD_SECTION_H(old_bss_index).sh_addr;
    old_bss_size = OLD_SECTION_H(old_bss_index).sh_size;
    new_bss_addr = (Elf32_Addr)sbrk (0);
    new_data2_addr = old_bss_addr;
    new_data2_size = new_bss_addr - old_bss_addr;
    new_data2_offset = OLD_SECTION_H(old_bss_index).sh_offset;

    if ((unsigned) new_bss_addr < (unsigned) old_bss_addr + old_bss_size) {
	Dump_Finalize;
	Primitive_Error (".bss shrank");
    }
    /* Set the output file to the right size and mmap(2) it.  Set
     * pointers to various interesting objects.  stat_buf still has
     * old_file data.
     */
    new_file_size = stat_buf.st_size + old_file_h->e_shentsize + new_data2_size;
    if (ftruncate (ofd, new_file_size) == -1) {
	Dump_Finalize;
	Primitive_Error ("cannot ftruncate new a.out file: ~E");
    }
    new_base = mmap (0, new_file_size, PROT_READ | PROT_WRITE, MAP_SHARED,
	ofd, 0);
    if (new_base == (caddr_t)-1) {
	Dump_Finalize;
	Primitive_Error ("cannot mmap new a.out file: ~E");
    }
    new_file_h = (Elf32_Ehdr *) new_base;
    new_program_h = (Elf32_Phdr *) ((byte *) new_base + old_file_h->e_phoff);
    new_section_h = (Elf32_Shdr *)
	((byte *) new_base + old_file_h->e_shoff + new_data2_size);

    /* Make our new file, program and section headers as copies of the
     * originals.
     */
    memcpy (new_file_h, old_file_h, old_file_h->e_ehsize);
    memcpy (new_program_h, old_program_h,
	old_file_h->e_phnum * old_file_h->e_phentsize);
    memcpy (new_section_h, old_section_h,
	old_file_h->e_shnum * old_file_h->e_shentsize);

    /* Fix up file header.  We'll add one section.  Section header is
     * further away now.
     */
    new_file_h->e_shoff += new_data2_size;
    new_file_h->e_shnum += 1;

    /* Fix up a new program header.  Extend the writable data segment so
     * that the bss area is covered too. Find that segment by looking
     * for a segment that ends just before the .bss area.  Make sure
     * that no segments are above the new .data2.  Put a loop at the end
     * to adjust the offset and address of any segment that is above
     * data2, just in case we decide to allow this later.
     */
    for (n = new_file_h->e_phnum - 1; n >= 0; n--) {
	/* Compute maximum of all requirements for alignment of section.
	 */
	int alignment = (NEW_PROGRAM_H (n)).p_align;

	if ((OLD_SECTION_H (old_bss_index)).sh_addralign > alignment)
	    alignment = OLD_SECTION_H (old_bss_index).sh_addralign;
	if (NEW_PROGRAM_H(n).p_vaddr + NEW_PROGRAM_H(n).p_filesz
		> old_bss_addr) {
	    Dump_Finalize;
	    Primitive_Error ("program segment above .bss");
	}
	if (NEW_PROGRAM_H(n).p_type == PT_LOAD
		&& (round_up ((NEW_PROGRAM_H (n)).p_vaddr
		    + (NEW_PROGRAM_H (n)).p_filesz, alignment)
		    == round_up (old_bss_addr, alignment)))
	    break;
    }
    if (n < 0) {
	Dump_Finalize;
	Primitive_Error ("cannot find segment next to .bss");
    }
    NEW_PROGRAM_H(n).p_filesz += new_data2_size;
    NEW_PROGRAM_H(n).p_memsz = NEW_PROGRAM_H(n).p_filesz;

#if 0 /* Maybe allow section after data2 - does this ever happen? */
    for (n = new_file_h->e_phnum - 1; n >= 0; n--) {
	if (NEW_PROGRAM_H(n).p_vaddr
		&& NEW_PROGRAM_H(n).p_vaddr >= new_data2_addr)
	    NEW_PROGRAM_H(n).p_vaddr += new_data2_size - old_bss_size;
	if (NEW_PROGRAM_H(n).p_offset >= new_data2_offset)
	    NEW_PROGRAM_H(n).p_offset += new_data2_size;
    }
#endif
    /* Fix up section headers based on new .data2 section.  Any section
     * whose offset or virtual address is after the new .data2 section
     * gets its value adjusted.  .bss size becomes zero and new address
     * is set.  data2 section header gets added by copying the existing
     * .data header and modifying the offset, address and size.
     */
    for (n = 1; n < new_file_h->e_shnum; n++) {
	if (NEW_SECTION_H(n).sh_offset >= new_data2_offset)
	    NEW_SECTION_H(n).sh_offset += new_data2_size;
	if (NEW_SECTION_H(n).sh_addr
		&& NEW_SECTION_H(n).sh_addr >= new_data2_addr)
	NEW_SECTION_H(n).sh_addr += new_data2_size - old_bss_size;
    }
    new_data2_index = old_file_h->e_shnum;

    for (old_data_index = 1; old_data_index < old_file_h->e_shnum;
			     old_data_index++)
	if (strcmp (old_section_names + OLD_SECTION_H(old_data_index).sh_name,
		".data") == 0)
	    break;
    if (old_data_index == old_file_h->e_shnum) {
	Dump_Finalize;
	Primitive_Error ("cannot find .data");
    }
    memcpy (&NEW_SECTION_H(new_data2_index), &OLD_SECTION_H(old_data_index),
	new_file_h->e_shentsize);

    NEW_SECTION_H(new_data2_index).sh_addr = new_data2_addr;
    NEW_SECTION_H(new_data2_index).sh_offset = new_data2_offset;
    NEW_SECTION_H(new_data2_index).sh_size = new_data2_size;

    NEW_SECTION_H(old_bss_index).sh_size = 0;
    NEW_SECTION_H(old_bss_index).sh_addr = new_data2_addr + new_data2_size;

    /* Write out the sections. .data and .data1 (and data2, called
     * ".data" in the strings table) get copied from the current process
     * instead of the old file.
     */
    for (n = new_file_h->e_shnum - 1; n; n--) {
	caddr_t src;

	if (NEW_SECTION_H(n).sh_type == SHT_NULL
		|| NEW_SECTION_H(n).sh_type == SHT_NOBITS)
	continue;

	if (strcmp (old_section_names + NEW_SECTION_H(n).sh_name,
		".data") == 0 ||
		strcmp ((old_section_names + NEW_SECTION_H(n).sh_name),
		".data1") == 0)
	    src = (caddr_t) NEW_SECTION_H(n).sh_addr;
	else
	    src = old_base + OLD_SECTION_H(n).sh_offset;

	memcpy (NEW_SECTION_H(n).sh_offset + new_base, src,
	    NEW_SECTION_H(n).sh_size);
    }

    Dump_Epilog;
}
