/* Create a.out from running interpreter
 *
 * COFF doesn't work together with dynamic loading.
 * Some of the COFF code has been taken from GNU Emacs's unexec.c
 * (in a modified form).
 */

#include <signal.h>

#include "scheme.h"

#ifdef CAN_DUMP

#include <sys/types.h>
#include <sys/stat.h>

#ifdef COFF
#  include <filehdr.h>
#  include <aouthdr.h>
#  include <scnhdr.h>
#  include <syms.h>
#  ifndef N_BADMAG
#    define N_BADMAG(x) (0)
#  endif
#else
#  include <a.out.h>
#endif

Object Dump_Control_Point;

Init_Dump () {
    Global_GC_Link (Dump_Control_Point);
}

Object P_Dump (ofile) Object ofile; {
#ifdef COFF
    static struct scnhdr thdr, dhdr, bhdr, scn;
    static struct filehdr hdr;
    static struct aouthdr ohdr;
    unsigned bias;
    unsigned lnno_start, syms_start;
    unsigned text_scn_start, data_scn_start;
    unsigned data_end;
    int pagemask = PAGESIZE-1;
#else
    struct exec hdr, shdr;
    unsigned data_start, data_end;
    int pagemask = getpagesize () - 1;
#endif
    char *afn;
    register n;
    char buf[BUFSIZ];
    Object ret, port;
    int ofd, afd;
    struct stat st;
    GC_Node;

    if (!EQ (Curr_Input_Port, Standard_Input_Port) ||
	    !EQ (Curr_Output_Port, Standard_Output_Port))
	Primitive_Error ("cannot dump with current ports redirected");
    Flush_Output (Curr_Output_Port);
    Close_All_Files ();

    GC_Link (ofile);
    n = stksize ();
    Dump_Control_Point = Make_Control_Point (n);
    SETFAST(ret,saveenv (CONTROL(Dump_Control_Point)->stack));
    if (TYPE(ret) != T_Special) {
	Enable_Interrupts;
	return ret;
    }
    GC_Unlink;

    Disable_Interrupts;
    port = General_Open_File (ofile, 0, Null);
    ofd = dup (fileno (PORT(port)->file));
    P_Close_Port (port);
    if (ofd < 0)
	Primitive_Error ("out of file descriptors");

    if ((afd = open (myname, 0)) == -1) {
	Saved_Errno = errno;
	close (ofd);
	Primitive_Error ("cannot open a.out file: ~E");
    }
    if (read (afd, (char *)&hdr, sizeof (hdr)) != sizeof (hdr)
	    || N_BADMAG(hdr)) {
badaout:
	close (ofd);
	close (afd);
	Primitive_Error ("corrupt a.out file");
    }
#ifdef COFF
    data_end = ((unsigned)sbrk (0) + pagemask) & ~pagemask;
    syms_start = sizeof (hdr);
    if (hdr.f_opthdr > 0) {
	if (read (afd, (char *)&ohdr, sizeof (ohdr)) != sizeof (ohdr))
	    goto badaout;
    }
    for (n = 0; n < hdr.f_nscns; n++) {
	if (read (afd, (char *)&scn, sizeof (scn)) != sizeof (scn))
	    goto badaout;
	if (scn.s_scnptr > 0 && syms_start < scn.s_scnptr + scn.s_size)
	    syms_start = scn.s_scnptr + scn.s_size;
	if (strcmp (scn.s_name, ".text") == 0)
	    thdr = scn;
	else if (strcmp (scn.s_name, ".data") == 0)
	    dhdr = scn;
	else if (strcmp (scn.s_name, ".bss") == 0)
	    bhdr = scn;
    }
    hdr.f_flags |= (F_RELFLG|F_EXEC);
    ohdr.dsize = data_end - ohdr.data_start;
    ohdr.bsize = 0;
    thdr.s_size = ohdr.tsize;
    thdr.s_scnptr = sizeof (hdr) + sizeof (ohdr)
	+ hdr.f_nscns * sizeof (thdr);
    lnno_start = thdr.s_lnnoptr;
    text_scn_start = thdr.s_scnptr;
    dhdr.s_paddr = dhdr.s_vaddr = ohdr.data_start;
    dhdr.s_size = ohdr.dsize;
    dhdr.s_scnptr = thdr.s_scnptr + thdr.s_size;
    data_scn_start = dhdr.s_scnptr;
    bhdr.s_paddr = bhdr.s_vaddr = ohdr.data_start + ohdr.dsize;
    bhdr.s_size = ohdr.bsize;
    bhdr.s_scnptr = 0;

    bias = dhdr.s_scnptr + dhdr.s_size - syms_start;
    if (hdr.f_symptr > 0)
	hdr.f_symptr += bias;
    if (thdr.s_lnnoptr > 0)
	thdr.s_lnnoptr += bias;

    if (write (ofd, (char *)&hdr, sizeof (hdr)) != sizeof (hdr)) {
badwrite:
	Saved_Errno = errno;
	close (ofd);
	close (afd);
	Primitive_Error ("error writing dump file: ~E");
    }
    if (write (ofd, (char *)&ohdr, sizeof (ohdr)) != sizeof (ohdr))
	goto badwrite;
    if (write (ofd, (char *)&thdr, sizeof (thdr)) != sizeof (thdr))
	goto badwrite;
    if (write (ofd, (char *)&dhdr, sizeof (dhdr)) != sizeof (dhdr))
	goto badwrite;
    if (write (ofd, (char *)&bhdr, sizeof (bhdr)) != sizeof (bhdr))
	goto badwrite;
    lseek (ofd, (long)text_scn_start, 0);
    if (write (ofd, (char *)ohdr.text_start, ohdr.tsize) != ohdr.tsize)
	goto badwrite;
    dumped = 1;
    lseek (ofd, (long)data_scn_start, 0);
    if (write (ofd, (char *)ohdr.data_start, ohdr.dsize) != ohdr.dsize)
	goto badwrite;
    lseek (afd, lnno_start ? (long)lnno_start : (long)syms_start, 0);
#else
    close (afd);
    data_start = hdr.a_text;
    data_start = (data_start + SEGMENT_SIZE-1) & ~(SEGMENT_SIZE-1);
    data_end = (unsigned)sbrk (0);
    data_end = (data_end + pagemask) & ~pagemask;
    hdr.a_data = data_end - data_start;
    hdr.a_bss = 0;
    hdr.a_trsize = hdr.a_drsize = 0;

    afn = Loader_Input;
    if (afn[0] == 0)
	afn = myname;
    if ((afd = open (afn, 0)) == -1) {
	Saved_Errno = errno;
	close (ofd);
	Primitive_Error ("cannot open symbol table file: ~E");
    }
    if (read (afd, (char *)&shdr, sizeof (shdr)) != sizeof (shdr)
	|| N_BADMAG(shdr)) {
	close (ofd);
	close (afd);
	Primitive_Error ("corrupt symbol table file");
    }
    hdr.a_syms = shdr.a_syms;

    if (write (ofd, (char *)&hdr, sizeof (hdr)) != sizeof(hdr)) {
badwrite:
	Saved_Errno = errno;
	close (ofd);
	close (afd);
	Primitive_Error ("error writing dump file: ~E");
    }

    (void)lseek (ofd, (long)FILE_TEXT_START, 0);
    n = hdr.a_text - TEXT_LENGTH_ADJ;
    if (write (ofd, (char *)MEM_TEXT_START, n) != n)
	goto badwrite;
    dumped = 1;
    if (Heap_Start > Free_Start) {
	n = (unsigned)Free_Start - data_start;
	if (write (ofd, (char *)data_start, n) != n)
	    goto badwrite;
	(void)lseek (ofd, (long)(Free_End - Free_Start), 1);
	n = Hp - Heap_Start;
	if (write (ofd, Heap_Start, n) != n)
	    goto badwrite;
	(void)lseek (ofd, (long)(Heap_End - Hp), 1);
	n = data_end - (unsigned)Heap_End;
	if (write (ofd, Heap_End, n) != n)
	    goto badwrite;
    } else {
	n = (unsigned)Hp - data_start;
	if (write (ofd, (char *)data_start, n) != n)
	    goto badwrite;
	(void)lseek (ofd, (long)(Free_End - Hp), 1);
	n = data_end - (unsigned)Free_End;
	if (write (ofd, Free_End, n) != n)
	    goto badwrite;
    }

    (void)lseek (afd, (long)N_SYMOFF(shdr), 0);
#endif
    while ((n = read (afd, buf, BUFSIZ)) > 0) {
	if (write (ofd, buf, n) != n)
	    goto badwrite;
    }
    if (n < 0) {
	Saved_Errno = errno;
	close (ofd);
	close (afd);
	Primitive_Error ("error reading symbol table: ~E");
    }
    close (afd);
    if (fstat (ofd, &st) != -1) {
	int omask = umask (0);
	(void)umask (omask);
#ifdef FCHMOD_BROKEN
	{
	    Object f = PORT(port)->name;
	    register n = STRING(f)->size;
	    register char *s = alloca (n+1);
	    bcopy (STRING(f)->data, s, n);
	    s[n] = '\0';
	    (void)chmod (s, st.st_mode & 0777 | 0111 & ~omask);
	}
#else
	(void)fchmod (ofd, st.st_mode & 0777 | 0111 & ~omask);
#endif
    }
    close (ofd);
    Enable_Interrupts;
    return False;
}
#endif
