/*
 *  linux/ibcs/binfmt_xout.c
 *
 *  Copyright (C) 1994  Mike Jagdis (jaggy@purplet.demon.co.uk)
 *
 * $Id: binfmt_xout.c,v 1.6 1994/05/26 13:26:17 mike Exp $
 * $Source: /var/CVS/ibcs/binfmt_xout.c,v $
 *
 * This file is based upon code written by Al Longyear for the COFF file
 * format which is in turn based upon code written by Eric Youndale for
 * the ELF object file format. Any errors are most likely my own however.
 */

#include <linux/fs.h>
#include <linux/sched.h>
#include <linux/mm.h>
#include <linux/mman.h>
#include <linux/a.out.h>
#include <linux/errno.h>
#include <linux/signal.h>
#include <linux/binfmts.h>
#include <asm/segment.h>
#include <linux/string.h>
#include <linux/fcntl.h>
#include <linux/ptrace.h>
#include <linux/malloc.h>
#include <linux/personality.h>
#include <linux/config.h>

#ifndef CONFIG_BINFMT_IBCS
#include <linux/module.h>
#endif

#include <ibcs/ibcs.h>
#include <ibcs/xout.h>


/* If you compile with XOUT_FORCE_PAGE defined (the default)
 * then if all segments are aligned on 1k bounaries within the
 * file and 4k boundaries within the address space we assume
 * that no overlaps occur within the same VM page and the
 * executable can thus be mmapped if the filesystem allows it.
 * I believe this is the case for all 386 small model binaries
 * - which is all we support anyway.
 */
#define XOUT_FORCE_PAGE


static int load_object(struct linux_binprm *bprm,
			struct pt_regs *regs,
		 	int lib_ok);


static inline int
is_properly_aligned(struct xseg *seg)
{
#ifdef XOUT_DEBUG
	printk(KERN_DEBUG "XOUT: %04x %04x %04x %02x %08lx %08lx %08lx %08lx\n",
		seg->xs_type, seg->xs_attr, seg->xs_seg, seg->xs_align,
		seg->xs_filpos, seg->xs_psize, seg->xs_vsize, seg->xs_rbase);
#endif
	/* If you compile with XOUT_FORCE_PAGE defined (the default)
	 * then if all segments are aligned on 1k bounaries within the
	 * file and 4k boundaries within the address space we assume
	 * that no overlaps occur within the same VM page and the
	 * executable can thus be mmapped if the filesystem allows it.
	 * I believe this is the case for all 386 small model binaries
	 * - which is all we support anyway.
	 */
#ifdef XOUT_FORCE_PAGE
	/* XXXX The file alignment should be dependent on the block size
	 * of the filesystem shouldn't it?
	 */
#  ifdef XOUT_DEBUG
	if ((seg->xs_filpos & 0x3ff) | (seg->xs_rbase & ~PAGE_MASK))
		printk(KERN_DEBUG "XOUT: bad page alignment - demand paging disabled\n");
#  endif
	return ((seg->xs_filpos & 0x3ff) | (seg->xs_rbase & ~PAGE_MASK));
#else
#  ifdef XOUT_DEBUG
	if ((seg->xs_filpos - seg->xs_rbase) & ~PAGE_MASK)
		printk(KERN_DEBUG "XOUT: bad page alignment - demand paging disabled\n");
#  endif
	return ((seg->xs_filpos - seg->xs_rbase) & ~PAGE_MASK);
#endif
}


static int
clear_memory(unsigned long addr, unsigned long size)
{
	int status;

	size = (PAGE_SIZE - (addr & ~PAGE_MASK)) & ~PAGE_MASK;
	if (size == 0)
		status = 0;
	else {
#ifdef XOUT_DEBUG
		printk(KERN_DEBUG "XOUT: un-initialized storage in last page %lu\n", size);
#endif

		status = verify_area(VERIFY_WRITE, (void *) addr, size);
#ifdef XOUT_DEBUG
		printk("result from verify_area = %d\n", status);
#endif

		if (status >= 0)
			while (size-- != 0)
				put_fs_byte (0, addr++);
	}
	return status;
}

/*
 *  Helper function to process the load operation.
 */

static int
load_object (struct linux_binprm * bprm, struct pt_regs *regs, int lib_ok)
{
	struct xexec *xexec = (struct xexec *)bprm->buf;
	struct xext *xext = (struct xext *)(bprm->buf + sizeof(struct xexec));
	struct xseg *seglist;
	int nsegs, ntext, ndata;
	int not_pageable;
	int old_fs;
	int i;
	int status;
	int fd = -1;
	struct file *fp = NULL;

#ifdef XOUT_DEBUG
	printk(KERN_DEBUG "XOUT: binfmt_xout entry: %s\n", bprm->filename);
#endif

	/* Validate the magic value for the object file. */
	if (xexec->x_magic != X_MAGIC) {
#ifdef XOUT_DEBUG
		printk(KERN_DEBUG "XOUT: bad magic %04x\n", xexec->x_magic);
#endif
		return -ENOEXEC;
	}

	/* Check the CPU type. */
	switch (xexec->x_cpu & XC_CPU) {
		case XC_386:
			break;
		case XC_8086:	case XC_286:	case XC_286V:
		case XC_186:
			/* one day...? */
		default:
#ifdef XOUT_DEBUG
			printk(KERN_DEBUG "XOUT: unsupported CPU type %02x\n",
				xexec->x_cpu);
#endif
			return -ENOEXEC;
	}

	/* We can't handle byte or word swapped headers. Well, we
	 * *could* but they should never happen surely?
	 */
	if ((xexec->x_cpu & (XC_BSWAP | XC_WSWAP)) != XC_WSWAP) {
#ifdef XOUT_DEBUG
		printk(KERN_DEBUG "XOUT: wrong byte or word sex %02x\n",
			xexec->x_cpu);
#endif
		return -ENOEXEC;
	}

	/* Check it's an executable. */
	if (!(xexec->x_renv & XE_EXEC)) {
#ifdef XOUT_DEBUG
		printk(KERN_DEBUG "XOUT: not executable\n");
#endif
		return -ENOEXEC;
	}

	/* There should be an extended header and there should be
	 * some segments. At this stage we don't handle non-segmented
	 * binaries. I'm not sure you can get them under Xenix anyway.
	 */
	if (xexec->x_ext != sizeof(struct xext)
	|| !(xexec->x_renv & XE_SEG)
	|| !xext->xe_segsize) {
#ifdef XOUT_DEBUG
		printk(KERN_DEBUG "XOUT: bad extended header or not segmented\n");
#endif
		return -ENOEXEC;
	}

	/* Read the segment table. */
	seglist = (struct xseg *)kmalloc(xext->xe_segsize, GFP_KERNEL);
	if (!seglist) {
#ifdef XOUT_DEBUG
		printk(KERN_DEBUG "XOUT: kmalloc failed when getting segment list\n");
#endif
		return -ENOEXEC;
	}
	old_fs = get_fs ();
	set_fs(get_ds());
	status = read_exec(bprm->inode, xext->xe_segpos,
			(char *)seglist, xext->xe_segsize);
	set_fs (old_fs);
	if (status < 0) {
#ifdef XOUT_DEBUG
		printk(KERN_DEBUG "XOUT: problem reading segment table\n");
#endif
		kfree(seglist);
		return status;
	}

	nsegs = xext->xe_segsize / sizeof(struct xseg);

	/* Check we have at least one text and one data segment and
	 * determine whether or not it is pageable.
	 */
	if (!bprm->inode->i_op || !bprm->inode->i_op->default_file_ops->mmap) {
#ifdef XOUT_DEBUG
		printk(KERN_DEBUG "XOUT: filesystem does not support mmap - demand paging disabled\n");
#endif
		not_pageable = 1;
	} else {
		not_pageable = 0;
	}
	ntext = ndata = 0;
	for (i=0; i<nsegs; i++) {
		switch (seglist[i].xs_type) {
			case XS_TTEXT:
				not_pageable |= is_properly_aligned(seglist+i);
				ntext++;
				break;
			case XS_TDATA:
				not_pageable |= is_properly_aligned(seglist+i);
				ndata++;
				break;
		}
	}
	if (!ntext || (lib_ok && !ndata)) {
		kfree(seglist);
		return -ENOEXEC;
	}


	/*
	 *  Fetch a file pointer to the executable.
	 */
	fd = open_inode (bprm->inode, O_RDONLY);
	if (fd < 0) {
		kfree(seglist);
		return fd;
	} else {
		fp = current->FD[fd];
	}

	/*
	 *  Generate the proper values for the text fields
	 *
	 *  THIS IS THE POINT OF NO RETURN. THE NEW PROCESS WILL TRAP OUT SHOULD
	 *  SOMETHING FAIL IN THE LOAD SEQUENCE FROM THIS POINT ONWARD.
	 */

	/*
	 *  Flush the executable from memory. At this point the executable is
	 *  committed to being defined or a segmentation violation will occur.
	 */
	if (lib_ok) {
#ifdef XOUT_DEBUG
		printk(KERN_DEBUG "XOUT: flushing executable\n");
#endif
		flush_old_exec(bprm);
/*
 *  Define the initial locations for the various items in the new process
 */
		current->MM(mmap)        = NULL;
		current->MM(rss)         = 0;
/*
 *  Construct the parameter and environment string table entries.
 */
		bprm->p += change_ldt(0, bprm->page);
		bprm->p -= MAX_ARG_PAGES*PAGE_SIZE;
		bprm->p  = (unsigned long)create_tables((char *) bprm->p,
						      bprm->argc,
						      bprm->envc,
						      1);
/*
 *  Do the end processing once the stack has been constructed
 */
		current->MM(start_code)  = 0;
		current->MM(end_code)    = xexec->x_text;
		current->MM(end_data)    = xexec->x_text + xexec->x_data;
		current->MM(start_brk)   =
		current->MM(brk)         = xexec->x_text + xexec->x_data + xexec->x_bss;
		current->suid        =
		current->euid        = bprm->e_uid;
		current->sgid        =
		current->egid        = bprm->e_gid;
		current->executable  = bprm->inode; /* Store inode for file  */
		++bprm->inode->i_count;             /* Count the open inode  */
		regs->eip            = xexec->x_entry;
		regs->esp            =
		current->MM(start_stack) = bprm->p;

		current->personality = PER_SVR3;
#ifdef INIT_MM
		current->lcall7 = iABI_emulate_real;
		current->signal_map = signal_map_to_linux[PER_SVR3 & PER_MASK];
		current->signal_invmap = signal_map_from_linux[PER_SVR3 & PER_MASK];
#endif
	}


	/* Scan the segments and map them into the process space. If this
	 * executable is pageable (unlikely since Xenix aligns to 1k
	 * boundaries and we want it aligned to 4k boundaries) this is
	 * all we need to do. If it isn't pageable we go round again
	 * afterwards and load the data. We have to do this in two steps
	 * because if segments overlap within a 4K page we'll lose the
	 * first instance when we remap the page. Hope that's clear...
	 *
	 * N.B. If you compile with XOUT_FORCE_PAGE defined (the default)
	 * then if all segments are aligned on 1k bounaries within the
	 * file and 4k boundaries within the address space we assume
	 * that no overlaps occur within the same VM page and the
	 * executable can thus be mmapped if the filesystem allows it.
	 * I believe this is the case for all 386 small model binaries
	 * - which is all we support anyway.
	 */
	for (i=status=0; status >= 0 && i<nsegs; i++) {
		if (seglist[i].xs_type == XS_TTEXT
		|| seglist[i].xs_type == XS_TDATA) {
			do_mmap(not_pageable ? NULL : fp,
  				seglist[i].xs_rbase,
				seglist[i].xs_psize,
				seglist[i].xs_type == XS_TTEXT
  					? PROT_READ|PROT_EXEC
					: PROT_READ|PROT_WRITE|PROT_EXEC,
				seglist[i].xs_type == XS_TTEXT
  					? MAP_FIXED|MAP_SHARED
					: MAP_FIXED|MAP_PRIVATE,
  				seglist[i].xs_filpos);

			/* Map uninitialised data. */
			if (seglist[i].xs_vsize-seglist[i].xs_psize) {
				zeromap_page_range(
					PAGE_ALIGN(seglist[i].xs_rbase
						+ seglist[i].xs_psize),
					PAGE_ALIGN(seglist[i].xs_vsize
						- seglist[i].xs_psize),
					PAGE_COPY);
			}
		}
	}

	if (status >= 0 && not_pageable) {
		for (i=0; status >= 0 && i<nsegs; i++) {
			if (seglist[i].xs_type == XS_TTEXT
			|| seglist[i].xs_type == XS_TDATA) {
				status = read_exec(bprm->inode,
					seglist[i].xs_filpos,
    					(char *)seglist[i].xs_rbase,
					seglist[i].xs_psize);

				if (status >= 0
				&& seglist[i].xs_vsize-seglist[i].xs_psize)
					status = clear_memory(
						seglist[i].xs_rbase
							+ seglist[i].xs_psize,
						seglist[i].xs_vsize
							- seglist[i].xs_psize);
			}
		}
	}

/*
 *   Generate any needed trap for this process. If an error occured then
 *   generate a segmentation violation. If the process is being debugged
 *   then generate the load trap. (Note: If this is a library load then
 *   do not generate the trap here. Pass the error to the caller who
 *   will do it for the process in the outer lay of this procedure call.)
 */
	if (lib_ok) {
		if (status)
			send_sig(SIGSEGV, current, 0);
		else if (current->flags & PF_PTRACED) {
			send_sig(SIGTRAP, current, 0);
		}
		status = 0;
	}

	SYS(close)(fd);

	kfree(seglist);

#ifdef XOUT_DEBUG
	printk(KERN_DEBUG "XOUT: binfmt_xout: result = %d\n", status);
#endif
	return (status);
}


/*
 *  This procedure is called by the main load sequence. It will load
 *  the executable and prepare it for execution. It provides the additional
 *  parameters used by the recursive xout loader and tells the loader that
 *  this is the main executable. How simple it is . . . .
 */

static int
load_xout_binary(struct linux_binprm *bprm, struct pt_regs *regs)
{
	int ret;

#ifndef CONFIG_BINFMT_IBCS
	MOD_INC_USE_COUNT;
#endif
	ret = load_object(bprm, regs, 1);
#ifndef CONFIG_BINFMT_IBCS
	MOD_DEC_USE_COUNT;
#endif
	return ret;
}

/*
 *   Load the image for any shared library.
 *
 *   This is called when we need to load a library based upon a file name.
 */

static int
load_xout_library(int fd)
{
	struct linux_binprm *bprm;  /* Parameters for the load operation   */
	int    status;              /* Status of the request               */

#ifndef CONFIG_BINFMT_IBCS
	MOD_INC_USE_COUNT;
#endif

	bprm = (struct linux_binprm *)kmalloc (sizeof (struct linux_binprm),
					    GFP_KERNEL);
	if (!bprm) {
#ifdef XOUT_DEBUG
		printk(KERN_DEBUG "XOUT: kmalloc failed\n");
#endif
		status = -ENOEXEC;
	} else {
		struct file *file;
		struct pt_regs regs;
		int old_fs = get_fs ();

		memset(bprm, '\0', sizeof (struct linux_binprm));

		file           = current->FD[fd];
		bprm->inode    = file->f_inode;
		bprm->filename = "";

		set_fs(get_ds());
		status = read_exec(bprm->inode, 0L,
			    bprm->buf, sizeof (bprm->buf));
		set_fs(old_fs);

		status = load_object(bprm, &regs, 0);

		kfree(bprm);
	}

#ifndef CONFIG_BINFMT_IBCS
	MOD_DEC_USE_COUNT;
#endif

	return (status);
}

#ifdef INIT_MM
struct linux_binfmt xout_format = { NULL, load_xout_binary, load_xout_library };
#else
struct linux_binfmt xout_format = { load_xout_binary, load_xout_library };
#endif
