/* Prospect.
   Copyright 1993, 1994, 1995 Tristan Gingold
		  Written August 1993 by Tristan Gingold

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 of the
License, or (at your option) any later version.

This library 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; see the file COPYING.  If not, write to
the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.

The author may be reached by US/French mail:
		Tristan Gingold 
		8 rue Parmentier
		F-91120 PALAISEAU
		FRANCE
*/

#include <stdio.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#ifdef HAVE_SYS_RESOURCE_H
#include <sys/resource.h>
#endif
#ifdef HAVE_SYS_MMAN_H
#include <sys/mman.h>
#endif
#ifndef MAP_FILE
#define MAP_FILE 0
#endif
#include <errno.h>
#include <fcntl.h>

/* How to stringify...  */
#define STR(c) #c
#define STR1(c) STR(c)

extern char etext, edata, end;

/* Verbose.  */
int flag_verbose;

#define MB 1024*1024

#define DEFAULT_STACK_LIMIT 8*MB
#define DEFAULT_PAGESIZE 4*1024
int mm_heap_size = 64*MB;
int mm_stack_size = 64*MB;
int mm_mem_size = 64*MB;
int mm_sym_size = 64*MB;
#define CHECKER_NEEDED_MEMORY (mm_heap_size + mm_stack_size + mm_mem_size + mm_sym_size)
#define MAX_NEEDED_MEMORY (4 * 64 * MB)

#ifndef HAVE_GETPAGESIZE

#ifdef VMS
#define getpagesize() 512
#endif

#ifdef HAVE_SC_PAGESIZE
#define getpagesize() sysconf(_SC_PAGESIZE)
#else

#ifdef HAVE_SC_PAGE_SIZE
#define getpagesize() sysconf(_SC_PAGE_SIZE)
#else

#include <sys/param.h>

#ifdef EXEC_PAGESIZE
#define getpagesize() EXEC_PAGESIZE
#else
#ifdef NBPG
#define getpagesize() NBPG * CLSIZE
#ifndef CLSIZE
#define CLSIZE 1
#endif /* no CLSIZE */
#else /* no NBPG */
#ifdef NBPC
#define getpagesize() NBPC
#else
#define getpagesize() DEFAULT_PAGESIZE
#define DEFAULT_PAGESIZE_USED
#endif /* NBPC */
#endif /* no NBPG */
#endif /* no EXEC_PAGESIZE */
#endif /* no _SC_PAGE_SIZE */
#endif /* no _SC_PAGESIZE */

#endif /* not HAVE_GETPAGESIZE */

int
get_max_stack_size ()
{
#ifdef RLIMIT_STACK
  struct rlimit stack_limit;
  if (getrlimit (RLIMIT_STACK, &stack_limit) == 0)
    if (stack_limit.rlim_max != RLIM_INFINITY)
      return stack_limit.rlim_max;
#endif
  return DEFAULT_STACK_LIMIT;
}

int *
call_for_sp()
{
  int d;
  int *res = &d;
  
  return res;
}

void
buggy_os (void)
{
  fprintf (stderr, "This OS is too bad.  Find another one!");
  exit (1);
}

#undef MY_MIN
#undef MY_MAX
#undef BASE 
#define MY_MIN(a,b) ((a) < (b) ? (a) : (b))
#define MY_MAX(a,b) ((a) > (b) ? (a) : (b))
#define BASE(a,b) (stack_grows_way == -1 ? MY_MAX((a),(b)) : MY_MIN((a),(b)))

unsigned int stack_top_int;
unsigned int stack_base_int;
unsigned int checker_base_int;
int stack_grows_way;
int pagesize;

/* 0: bad, 1: Ok */
int
check_for_ptr (char *ptr, int fd, int fd_file)
{
  int res;
  caddr_t addr;
  
  /* An available area must not be busy.  */
  res = write (fd_file, ptr, 8);
  if (res == -1)
    {
      if (errno != EFAULT)
        buggy_os ();
    }
  else
    return 0;	/* Don't touch.  */
       
  /* An available area must be usuable.  */
  addr = mmap (ptr, pagesize, PROT_READ | PROT_WRITE, 
	       MAP_PRIVATE | MAP_FILE | MAP_FIXED, fd, 0);
  if (addr == (caddr_t) -1)
    return 0;
  else
    munmap (addr, pagesize);
  return 1;
}

#define TMP_FILE "tmp-prosp"

void
find_available_memory (void)
{
  char map[256];		/* 0: available, 1: busy.  */
#define AVAILABLE	0
#define BUSY		1
#define NULLTRAP	2
#define STACK		3
#define OSAVAILABLE	4
#define ACCURATEBUSY	5
  int fd;
  int fd_file;
  caddr_t addr;
  unsigned int i, j;
  unsigned int i1, j1;
  int begin, len;
  int deepsearch;
 
  fd = open ("/dev/zero", O_RDWR);
  if (fd == -1)
    buggy_os ();
  fd_file = open (TMP_FILE, O_RDWR | O_CREAT, 0666);
  if (fd_file == -1)
    buggy_os ();
  if (write (fd_file, "hello", 4) != 4)
    buggy_os ();
 
  /* Search for available area.  */
  map[0] = NULLTRAP;
  for (i = 1; i < 256; i++)
    {
      if (check_for_ptr ((char *) (i << 24), fd, fd_file) == 0)
        map[i] = BUSY;
      else
        {
          map[i] = AVAILABLE;
          if (flag_verbose)
            fprintf (stderr, "Available memory in: 0x%08x-0x%08x\n", i << 24, ((i + 1) << 24) - 1);
        }
    }
      
  /* The stack is not available.  */
  if (stack_grows_way > 0)
    for (i = stack_base_int >> 24; i <= (stack_top_int >> 24); i++)
      map[i] = STACK;
  else
    for (i = stack_top_int >> 24; i <= (stack_base_int >> 24); i++)
      map[i] = STACK;
  
  /* Find the most available area from the os...  It is not available for
     Checker.  */
  addr = mmap ((caddr_t) 0, pagesize, PROT_READ | PROT_WRITE,
  	       MAP_PRIVATE | MAP_FILE, fd, 0);
  if (addr == (caddr_t) -1)
    buggy_os ();
  munmap (addr, pagesize);
  i = ((unsigned long int) addr) >> 24;
  map[i] = OSAVAILABLE;
  if (flag_verbose)
    fprintf (stderr, "Available area from the OS: 0x%08x\n", (unsigned int)addr);
    
  /* If all the memory is busy, search deeply...  This feature is useful
     when the verbose flag is set.  */
  deepsearch = 1;
  for (i = 1; i < 256; i++)
    if (map[i] == AVAILABLE)
      {
        deepsearch = 0;
        break;
      }
    
  /* Be more accurate.  */
  for (i = 0; i < 256; i++)
    if (map[i] == AVAILABLE || deepsearch)
      {
        if (flag_verbose)
          fprintf (stderr, "Check for 0x%02x**0000\n", i);
        for (j = 0; j < 256; j++)
          {
            if (check_for_ptr ((char *)(((i << 8) + j) << 16), fd, fd_file) == 0)
              {
                map[i] = ACCURATEBUSY;
                if (flag_verbose)
                  fprintf (stderr, "Fail at 0x%08x (i:%2x, j:%2x)\n", ((i << 8) + j) << 16, i, j);
                if (!deepsearch)
                  break;
              }
          }
      }
  
  close (fd);
  close (fd_file);
  unlink (TMP_FILE);
  
  /* Speak again.  */
  if (flag_verbose)
    for (i = 0; i < 256; i++)
      if (map[i] == AVAILABLE)
        {
          j = i + 1;
          while (map[j] == AVAILABLE && j < 256)
            j++;
          fprintf (stderr, "Available memory in: 0x%08x-0x%08x\n", i << 24, (j << 24) - 1);
          i = j;
        }

  /* Find a big enough available zone.  */
  begin = 0;
  len = 0;
  for (i = 1; i < 256; i++)
      if (map[i] == AVAILABLE)
        {
          j = i + 1;
          while (map[j] == AVAILABLE && j < 256)
            j++;
          /* If this zone is too near from the available space given by the
             os, don't accept it.  */
          i1 = i;
          j1 = j;
          if (map[i1 - 1] == OSAVAILABLE)
            i1++;
          if (j1 > i1 && map[j1] == OSAVAILABLE)
            j1--;
          if (j1 - i1 > len)
            {
              len = j1 - i1;
              begin = i1;
            }
          i = j;
        }

  if (begin == 0)
    return;
  
  /* Convert len into bytes.  */
  if (len >= (MAX_NEEDED_MEMORY >> 24))
    len = MAX_NEEDED_MEMORY;
  else
    len <<= 24;
  
  checker_base_int = begin << 24;
  /* Equipartition.  */
  mm_heap_size = mm_stack_size = mm_mem_size = mm_sym_size = len / 4;
}

int
main (int argc, char *argv[], char *envp[])
{
  int dummy;
  int exit_status;
  int i;
  char *stack_base;
  char *p;
  
  if (argc == 2 && (strcmp (argv[1], "-v") == 0 || strcmp (argv[1], "--verbose") == 0))
    flag_verbose = 1;
  else
    flag_verbose = 0;
 
  exit_status = 0;
  
  /* Page size.  */
  pagesize = getpagesize ();
  printf (
	"/* The page size.  Note: CHKR_PAGESIZE = 1 << LOG_PAGESIZE. */\n"
	"#undef CHKR_PAGESIZE		/* this is defined in sys/param.h */\n"
	"#define CHKR_PAGESIZE pagesize\n"
	"#define LOG_PAGESIZE log_pagesize\n"
	"#define INIT_PAGESIZE " STR1(getpagesize())
#ifdef DEFAULT_PAGESIZE_USED
	"/* default value.  */"
#endif
	"\n\n");

  /* Find the behavior of the stack.  */
  printf ("/* Define STACK_GROWS_DOWNWARD is the stack grows downward.  */\n");
  if (call_for_sp() < &dummy)
    {
      if (flag_verbose)
        printf("Stack seems to grow down.\n");
      printf("#define STACK_GROWS_DOWNWARD\n");
      stack_grows_way = -1;
    }
  else
    {
      if (flag_verbose)
        printf("Stack seems to grow up.\n");
      printf("#undef STACK_GROWS_DOWNWARD\n");
      stack_grows_way = 1;
    }
  printf ("\n");
  
  /* Find the base of the stack.  */
  stack_base = (char*)&argc;
  for (i = 0; i < argc; i++)
    {
      p = argv[i];
      stack_base = BASE(stack_base, p);
      p = argv[i] + strlen(argv[i]) + 1;
      stack_base = BASE(stack_base, p);
    }
  for (i = 0; envp[i]; i++)
    {
      p = envp[i];
      stack_base = BASE(stack_base, p);
      p = envp[i] + strlen(envp[i]) + 1;
      stack_base = BASE(stack_base, p);
    }
  stack_base_int = ((int)stack_base + pagesize - 1) & ~(pagesize-1);
  if (flag_verbose)
    {
      printf("The raw stack base: %p\n", stack_base);
      printf("The rounded stack base: 0x%08x\n", stack_base_int);
    }
  printf ("/* The stack base, ie where the stack begins.  */\n");
  printf("#define STACK_BASE 0x%08x\n\n", stack_base_int);
  
  stack_top_int = stack_base_int + stack_grows_way * get_max_stack_size ();
  printf ("/* The stack top.  */\n");
  printf ("#define STACK_TOP 0x%08x\n\n", stack_top_int);
  
  /* Try to find enough available memory for Checker.  */
  find_available_memory ();
  
  /* Default available memory.  */
  if (!checker_base_int)
    {
      if (stack_grows_way == 1)
        checker_base_int = stack_top_int;
      else
        checker_base_int = stack_top_int - CHECKER_NEEDED_MEMORY;
      printf ("/* Aie Aie Aie... \n"
      	      "   `prospect' fails to find available memory.\n"
      	      "   Try to analyse the output of `./prospect -v'.  \n");
      exit_status = 1;
    }
    
  printf (
#ifdef MAP_ANONYMOUS
	"/* MAP_ANONYMOUS is available.  */\n"
	"#define HAVE_ANONYMOUS\n"
#else
	"/* ANONYMOUS is not available.  Use devzero_fd. */\n"
	"#undef HAVE_ANONYMOUS\n"
#endif
	"\n"
	"#ifdef NEED_MM\n"
	"#include <sys/mman.h>\n"
	"#ifndef MAP_FILE\n"
	"#define MAP_FILE 0\n"
	"#endif\n"
	"#define MM_PROT 	PROT_READ | PROT_WRITE\n"
#ifdef MAP_ANONYMOUS
	"#define MM_FLAGS	MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS\n"
	"#define MM_FILE	(-1)\n"
#else
	"#define MM_FLAGS	MAP_FIXED | MAP_PRIVATE\n"
	"#define MM_FILE	devzero_fd\n"
#endif
	);
  
  printf ("/* Between MM_LOW and MM_HIGH, the user can't access.  */\n");
  printf ("#define MM_LOW 0x%08x\n", checker_base_int);
  printf ("#define MM_HIGH 0x%08x\n\n", checker_base_int + CHECKER_NEEDED_MEMORY);
  
  printf ("/* Memory above MM_HEAP is used by sys_malloc (ie the internal heap, used only"
	  "   Checker). */\n");
  printf ("#define MM_HEAP 0x%08x\n\n", checker_base_int);
  
  printf ("/* Where the stack bitmap begins.  */\n");
  printf ("#define MM_STACK 0x%08x\n\n", checker_base_int + mm_heap_size);
  
  printf ("/* Where the bitmap for heaps begins.  */\n");
  printf ("#define MM_MEM 0x%08x\n\n", checker_base_int + mm_heap_size + mm_stack_size);
  
  printf ("/* Where the symbol table is loaded.  */\n");
  printf ("#define MM_SYM 0x%08x\n\n", checker_base_int + mm_heap_size + mm_stack_size + mm_mem_size);
  
  printf ("#endif /* NEED_MM */\n");
  
  if (flag_verbose)
    {
      fprintf(stderr, "&etext = 0x%08x; &edata = 0x%08x; &end = 0x%08x\n", (int)&etext, (int)&edata, (int)&end);
    }
    
  return exit_status;
}
