/* Load an ELF sharable library into memory.

   Copyright (C) 1993, Eric Youngdale.

   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 2, 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.  */



/* This file contains the helper routines to load an ELF sharable
   library into memory and add the symbol table info to the chain. */

#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <sys/mman.h>
#include "hash.h"
#include "linuxelf.h"
#include <ibcs/unistd.h>
#include "syscall.h"
#include "string.h"

extern _dl_linux_resolve();
extern void _dl_load_shared_library(char * libname);


/*
 * Read one ELF library into memory, mmap it into the correct locations and
 * add the symbol info to the symbol chain.  Perform any relocations that
 * are required.
 */

int _dl_load_elf_shared_library(char * libname){
  struct elfhdr * epnt;
  char * strtab;
  struct Elf32_Sym * symtab;
  unsigned int dynamic_addr = 0;
  unsigned int dynamic_size = 0;
  struct dynamic * dpnt;
  struct Elf32_Rel * rel_addr;
  unsigned int rel_size;
  struct elf_resolve * tpnt;
  struct elf_phdr * ppnt;
  char * status;
#ifdef DEBUG
  int lastmapped = 0;
#endif
  int flags;
  char header[4096];
  int dynamic_info[24];
  int * lpnt;
  char  * zfile = "/dev/zero";
  unsigned int libaddr;

  int i;
  int infile, zerofile;

  if(_dl_check_hashed_files(libname)) return 1;

  libaddr = 0;
  infile = _dl_open(libname, O_RDONLY);
  if(infile < 0) return 0;
  zerofile = _dl_open(zfile, O_RDONLY);
  if(zerofile < 0) return 0;
  
  _dl_read(infile, header, sizeof(header));
  epnt = (struct elfhdr *) header;
  if (epnt->e_ident[0] != 0x7f || _dl_strncmp(&epnt->e_ident[1], "ELF",3)){
    SEND_STDERR(libname);
    SEND_STDERR(" is not an ELF file\n");
    _dl_exit(1);
  };
  
  if((epnt->e_type != ET_DYN && epnt->e_type != ET_EXEC) || 
     (epnt->e_machine != EM_386 && epnt->e_machine != EM_486)){
    SEND_STDERR(libname);
    SEND_STDERR(" is not an ELF executable for the 386/486\n");
    _dl_exit(1);
  };

  ppnt = (struct elf_phdr *) &header[epnt->e_phoff];
  for(i=0;i < epnt->e_phnum; i++){

    if(ppnt->p_type == PT_DYNAMIC) {
      if(dynamic_addr) SEND_STDERR("Error - more than one dynamic section\n");
      dynamic_addr = ppnt->p_vaddr;
      dynamic_size = ppnt->p_filesz;
    };
    ppnt++;
  };

  flags = MAP_PRIVATE;
    /* Get the memory to store the library */
  ppnt = (struct elf_phdr *) &header[epnt->e_phoff];
  for(i=0;i < epnt->e_phnum; i++){
    if(ppnt->p_type == PT_LOAD) {
      
      if(ppnt->p_flags & PROT_WRITE) {
	unsigned int map_size;
	char * cpnt;
	
	status = (char *) _dl_mmap((char *) (libaddr +
					     ppnt->p_vaddr & 0xfffff000),
			  (ppnt->p_vaddr & 0xfff) + ppnt->p_filesz, 
			  ppnt->p_flags, 
			  flags, infile,
			  ppnt->p_offset & 0x7ffff000);
	
	
	/* Pad the last page with zeroes. */
	cpnt =(char *) (status + (ppnt->p_vaddr & 0xfff) + ppnt->p_filesz);
	while(((unsigned int) cpnt) & 0xfff) *cpnt++ = 0;
	
/* I am not quite sure if this is completely correct to do or not, but
   the basic way that we handle bss segments is that we mmap /dev/zero if
   there are any pages left over that are not mapped as part of the file */

	map_size = (ppnt->p_vaddr + ppnt->p_filesz + 0xfff) & 0xfffff000;
	if(map_size < ppnt->p_vaddr + ppnt->p_memsz)
	  status = (char *) _dl_mmap((char *) map_size + libaddr, 
			    ppnt->p_vaddr + ppnt->p_memsz - map_size,
			    ppnt->p_flags,
			    flags, zerofile, 0);
      } else
	status = (char *) _dl_mmap((char *) (ppnt->p_vaddr & 0xfffff000) + 
				   libaddr, 
			  (ppnt->p_vaddr & 0xfff) + ppnt->p_filesz, 
			  ppnt->p_flags | PROT_WRITE, 
			  flags, infile, 
			  ppnt->p_offset & 0x7ffff000);
      if(status == (char *) 0xffffffff) {
	SEND_STDERR("mmap failed.\n")
	  _dl_exit(1);
      };
      if(libaddr == 0) {
	libaddr = (unsigned int) status;
	flags |= MAP_FIXED;
      };
    };
    ppnt++;
  };
  _dl_close(zerofile);
  _dl_close(infile);
  
  dynamic_addr += (unsigned int) libaddr;

 /* 
  * OK, the ELF library is now loaded into VM in the correct locations
  * The next step is to go through and do the dynamic linking (if needed).
  */
  
  /* Start by scanning the dynamic section to get all of the pointers */
  
  if(!dynamic_addr) {
    SEND_STDERR(libname);
    SEND_STDERR(" is missing a DYNAMIC section.\n");
    _dl_exit(1);
  }

  dpnt = (struct dynamic *) dynamic_addr;

  dynamic_size = dynamic_size / sizeof(struct dynamic);
  _dl_memset(dynamic_info, 0, sizeof(dynamic_info));
  for(i=0; i< dynamic_size; i++){
    dynamic_info[dpnt->d_tag] = dpnt->d_un.d_val;
    dpnt++;
  };

  tpnt = _dl_add_elf_hash_table(libname, (char *) libaddr, dynamic_info, dynamic_addr, 
			      dynamic_size);
  tpnt->ppnt = (struct elf_phdr *) (tpnt->loadaddr + epnt->e_phoff);
  tpnt->n_phent = epnt->e_phnum;
  
  /*
   * OK, the next thing we need to do is to insert the dynamic linker into
   * the proper entry in the GOT so that the PLT symbols can be properly
   * resolved. 
   */
  
  lpnt = (int *) dynamic_info[DT_PLTGOT];
  
  if(lpnt) {
    lpnt = (int *) (dynamic_info[DT_PLTGOT] + ((int) libaddr));
    lpnt[1] = (int) tpnt;
    lpnt[2] = (int) _dl_linux_resolve;
  };
  
  return 1;
}
