
/*
 *   DYNAMIC_LOADER.C
 *
 *   Dynamically load specified object module
 *
 */

#include <stdio.h>
#include <stddef.h>
#include <unistd.h>
#include <stdlib.h>
#include <a.out.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <sys/mman.h>
#include <fcntl.h>

#include "port.h"
#include "tmp/c.h"
#include "tmp/postgres.h"
#include "utils/log.h"
#include "utils/fmgr.h"


/*
 * I use memory mapped files, that guarantee an execute permissinos
 * for allocated memory.
 *
 * Yes, yes I know that 'gdld' is appropriate tool for this job
 * (not nm, ld, grep, sed, sort, join ...), but it works.
 * I havent time to port 'gdld' to 386bsd.
 *
 * 1. Fiding out what amount of memory should be allocated.
 * 2. Creating memory-mapped file of appropriate size and
 *    map it into program's address space (anonymous memory-mapped file).
 * 3. Linking an object file with image of 'postgres'.
 * 4. Reading linked file into memory-mapped file.
 * 5. Creating an entry points list.
 *
 *                Igor Bartchenkov;
 *                garry@maak.msk.su
 */



DynamicFunctionList *
dynamic_file_load(
		char **err,
		char *filename,
		char **start_address,
		int *size
		)

{
    char  * p;
    int     i;
    char    no_slash_filename[256];
    int     fd;
    struct exec exec_hdr;
    int     page_size;
    int     header_size;
    int     text_size;
    int     data_size;
    int     bss_size;
    char    command[512]; /* I hope filenames will not be very long */
    char    tm_file[256];
    char    nm_file[256];
    caddr_t load_address;
    int     load_length;
    FILE  * fp;
    char    entry_point_line[512];
    int     entry_point_addr;
    char    entry_point_name[256];
    DynamicFunctionList * head;
    DynamicFunctionList * curr;

    static char * tm_path = "/tmp/tm_%s.XXXXXX";
    static char * nm_path = "/tmp/nm_%s.XXXXXX";

    p = &no_slash_filename[0];
    for (i = 0; filename[i] != '\0'; i++)
      {
        *p++ = (filename[i] == '/' ? '~' : filename[i]);
      }

/*
 * FIRST, I find out size of loading file
 */

    if( (fd = open( filename, O_RDONLY )) == -1 )
      {
        *err = "unable to open object file";
        return (DynamicFunctionList *)NULL;
      }

    if( read( fd, &exec_hdr, sizeof exec_hdr ) != sizeof exec_hdr )
      {
        *err = "can't read object file header";
        return (DynamicFunctionList *)NULL;
      }

    page_size = getpagesize();

    /*
     * Note: I have no time to dig out how bss lie in processes
     * memory (on page boundary or not). I think it'll be
     * reasonable to allocate additional memory in data section
     * thus there is no error if bss section starts at page boundary.
     */

    if( exec_hdr.a_magic == OMAGIC )
      {
        header_size = sizeof exec_hdr;
        text_size = exec_hdr.a_text;
        data_size = exec_hdr.a_data;
        bss_size  = exec_hdr.a_bss +
                    ( exec_hdr.a_text + exec_hdr.a_data ) % page_size ;
      }
    else
      {
        *err = "invalid header of an object file";
        return (DynamicFunctionList *)NULL;
      }

    close( fd );

/*
 *  SECOND, creating an memory-mapped file large enough to fit
 *  loading file.
 */

    load_length = text_size + data_size;
    load_address = mmap( (caddr_t) NULL,
                         load_length + bss_size,
                         (PROT_EXEC | PROT_READ | PROT_WRITE),
                         (MAP_ANON | MAP_PRIVATE),
                         -1,
                         (off_t) 0
                       );
    if (load_address == (caddr_t) -1 )
      {
        *err = "can\'t create memory-mapped file";
        return (DynamicFunctionList *)NULL;
      }
    bzero (load_address, load_length + bss_size);


/*
 *  THIRD, linking an object file with 'postgres'
 */
 
    sprintf(tm_file, tm_path, no_slash_filename);
    mktemp( tm_file );
    sprintf( command, "increment_load %x %s %s", load_address, filename, tm_file );

    if( system( command ) )
      {
        munmap (load_address, load_length + bss_size);
        *err = "can\'t link object file with image of postgres";
        return (DynamicFunctionList *)NULL;
      }
/*
 *  FORTH, reading object file into memory-mapped (anonymous) file.
 */
    if ((fd = open (tm_file, O_RDONLY)) == -1)
      {
        munmap (load_address, load_length + bss_size);
        *err = "unable to open loading file";
        return (DynamicFunctionList *)NULL;
      }

    if (read (fd, &exec_hdr, sizeof exec_hdr) != sizeof exec_hdr)
      {
        close (fd);
        munmap (load_address, load_length + bss_size);
        *err = "can't read loading file header";
        return (DynamicFunctionList *)NULL;
      }

    if (read (fd, load_address, load_length) != load_length)
      {
        close (fd);
        munmap (load_address, load_length + bss_size);
        *err = "can't read loading file text and data segments";
        return (DynamicFunctionList *)NULL;
      }

    close (fd);

/*
 * FIFTH, creating an entry points list
 * thank you, mao@postgres.berkeley.edu!
 */

    sprintf(nm_file, nm_path, no_slash_filename);
    mktemp( nm_file );
    sprintf( command, "nm %s | grep \" T \" > %s", tm_file, nm_file );

    if (system (command))
      {
        munmap (load_address, load_length + bss_size);
        *err = "can't create entry points file";
        return (DynamicFunctionList *)NULL;
      }

    if ((fp = fopen (nm_file, "r")) == NULL)
      {
        munmap (load_address, load_length + bss_size);
        *err = "unable to open entry points file";
        return (DynamicFunctionList *)NULL;
      }

    head       = (DynamicFunctionList *)NULL;
    curr       = (DynamicFunctionList *)NULL;

    while (fgets (entry_point_line, sizeof entry_point_line, fp) != NULL)
      {
        DynamicFunctionList * tmp;

        sscanf (entry_point_line,
                "%lx T %s",
                &entry_point_addr,
                entry_point_name
               );
        if( strcmp ("_etext", entry_point_name) == 0)
          continue;

        tmp = (DynamicFunctionList *) malloc (sizeof (DynamicFunctionList));
        bzero (tmp, sizeof (DynamicFunctionList));
        if (tmp == (DynamicFunctionList *)NULL)
          {
            fclose (fp);
            unlink (nm_file);
            unlink (tm_file);
            munmap (load_address, load_length + bss_size);
            *err = "not enough memory for entry points list";
            return (DynamicFunctionList *)NULL;
          }
        if (curr == (DynamicFunctionList *)NULL)
          {
            head = tmp;
            curr = head;
          }
        else
          {
            curr->next = tmp;
            curr = curr->next;
          }
        strncpy (curr->funcname,
                 (entry_point_name[0] == '_' ? entry_point_name + 1
                                             : entry_point_name
                 ),
                 16
                ); /* bad length definition */
        curr->func = (func_ptr)entry_point_addr;
      }

    fclose (fp);
    unlink (nm_file);
    unlink (tm_file);

    if (head->func == (func_ptr)NULL)
      {
        free (head);
        munmap (load_address, load_length + bss_size);
        *err = "there is no entry points in object file";
        return (DynamicFunctionList *)NULL;
      }
    *start_address = load_address;
    *size          = load_length + bss_size;
    return (DynamicFunctionList *)head;
}
