/* sbrk2.c (emx+gcc) */

#define INCL_DOSMEMMGR
#define INCL_DOSERRORS
#include <os2.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <ulimit.h>
#include <assert.h>
#include <signal.h>
#include <setjmp.h>
#include <sys/uflags.h>

#ifdef SYS
/* Set the default heap size. */
unsigned long _sys_heap_size = HEAPSIZE;
#endif

#define CA_C_BASE      (PAG_READ|PAG_WRITE|PAG_COMMIT|PAG_BASE)
#define CA_C_NBASE     (PAG_READ|PAG_WRITE|PAG_COMMIT)
#define CA_NC_BASE     (PAG_READ|PAG_WRITE|PAG_BASE)
#define CA_NC_NBASE    (PAG_READ|PAG_WRITE)
#define CA_FREE        (PAG_FREE)


static void fail (const char *fmt, ...)
     __attribute__ ((__format__ (__printf__, 1, 2)));


static void fail (const char *fmt, ...)
{
  va_list ap;

  va_start (ap, fmt);
  vfprintf (stderr, fmt, ap);
  va_end (ap);
  fputc ('\n', stderr);
#ifdef SHOWMEM
  showmem ();
#endif
  abort ();
}


static char segv_flag;
static char segv_dummy;
static jmp_buf segv_jump;

static void segv_handler (int signo)
{
  segv_flag = 1;
  longjmp (segv_jump, 1);
}


static void check_access_segv (char *p, ULONG expected, int lineno)
{
  struct sigaction sa_new, sa_old;
  
  segv_flag = 0;
  sa_new.sa_handler = segv_handler;
  sa_new.sa_flags = 0;
  sigemptyset (&sa_new.sa_mask);
  if (sigaction (SIGSEGV, &sa_new, &sa_old) != 0)
    perror ("sigaction()");
  if (setjmp (segv_jump) == 0)
    {
      segv_dummy = *p;
      longjmp (segv_jump, 1);
    }

  if (sigaction (SIGSEGV, &sa_old, NULL) != 0)
    perror ("sigaction()");
  if ((expected == CA_C_BASE || expected == CA_C_NBASE) == segv_flag)
    fail ("l.%d: check_access(0x%lx): expected=0x%lx",
          lineno, (ULONG)p, expected);
}


static void check_access_os2 (char *p, ULONG expected, int lineno)
{
  ULONG rc, size, flags, base;

  base = (ULONG)p & ~0xfff;
  size = 0x1000; flags = 0;
  rc = DosQueryMem ((PVOID)base, &size, &flags);
  if (rc == ERROR_INVALID_ADDRESS && expected == CA_FREE)
    {
      rc = 0; flags = CA_FREE;
    }
  if (rc != 0)
    fail ("l.%d: DosQueryMem failed, rc=%lu", lineno, rc);
  if (size != 0x1000)
    fail ("l.%d: check_access(0x%lx): size=%lu", lineno, (ULONG)p, size);
  if (flags != expected)
    fail ("l.%d: check_access(0x%lx): flags=0x%lx, expected=0x%lx",
          lineno, (ULONG)p, flags, expected);
}


static void check_access (char *p, ULONG expected, int lineno)
{
  if (_osmode != DOS_MODE)
    check_access_os2 (p, expected, lineno);
  check_access_segv (p, expected, lineno);
}

#define CHECK_ACCESS(P,E) check_access (P, E, __LINE__)

static void check_rest (long expected, int lineno)
{
  long rest;

  rest = ulimit (UL_OBJREST);
  if (rest != expected)
    fail ("l.%d: check_rest: rest=%#lx, expected %#lx",
          lineno, rest, expected);
}

#define CHECK_REST(E) check_rest (E, __LINE__)


static void check_sbrk (int incr, char *exp_old, char *exp_new, int lineno)
{
  char *p;

  p = sbrk (incr);
  if (p != exp_old)
    fail ("l.%d: check_sbrk: p=0x%lx, exp_old=0x%lx",
          lineno, (ULONG)p, (ULONG)exp_old);
  if (exp_new != 0)
    {
      p = sbrk (0);
      if (p != exp_new)
        fail ("l.%d: check_sbrk: p=0x%lx, exp_new=0x%lx",
              lineno, (ULONG)p, (ULONG)exp_new);
    }
}

#define CHECK_SBRK(I,O,N) check_sbrk (I, O, N, __LINE__)


static void check_brk (void *addr, int exp_result, char *exp_new, int lineno)
{
  char *p;

  p = brk (addr);
  if (p != (char *)exp_result)
    fail ("l.%d: check_brk: p=%d, exp_result=%d", lineno, (int)p, exp_result);
  p = sbrk (0);
  if (p != exp_new)
    fail ("l.%d: check_brk: p=0x%lx, exp_new=0x%lx",
          lineno, (ULONG)p, (ULONG)exp_new);
}

#define CHECK_BRK(A,R,N) check_brk (A, R, N, __LINE__)


static void test_c (void)
{
  char *base;

  _uflags (_UF_SBRK_MODEL, _UF_SBRK_CONTIGUOUS);
  base = sbrk (0);
  assert (base != (char *)-1);
  assert (((ULONG)base & 0xffff) == 0);

  CHECK_ACCESS (base, CA_NC_BASE);
  CHECK_REST (HEAPSIZE);
  CHECK_SBRK (1, base, base + 1);
  CHECK_ACCESS (base, CA_C_BASE);
  CHECK_ACCESS (base + 0x1000, CA_NC_NBASE);
  CHECK_REST (HEAPSIZE - 1);

  CHECK_SBRK (-2, (char *)-1, base + 1);
  CHECK_SBRK (-1, base + 1, base);
  CHECK_ACCESS (base, CA_NC_BASE);
  CHECK_REST (HEAPSIZE);

  CHECK_SBRK (HEAPSIZE, base, base + HEAPSIZE);
  CHECK_ACCESS (base, CA_C_BASE);
  CHECK_ACCESS (base + HEAPSIZE - 0x1000, CA_C_NBASE);
  CHECK_REST (0);
  CHECK_SBRK (1, (char *)-1, base + HEAPSIZE);

  CHECK_SBRK (-HEAPSIZE, base + HEAPSIZE, base);
  CHECK_ACCESS (base, CA_NC_BASE);
  CHECK_REST (HEAPSIZE);

  CHECK_SBRK (HEAPSIZE + 1, (char *)-1, base);

  CHECK_BRK (base, 0, base);
  CHECK_BRK (base - 1, -1, base);
  CHECK_BRK (base + HEAPSIZE + 1, -1, base);

  CHECK_BRK (base + HEAPSIZE, 0, base + HEAPSIZE);
  CHECK_ACCESS (base, CA_C_BASE);
  CHECK_ACCESS (base + HEAPSIZE - 0x1000, CA_C_NBASE);
  CHECK_REST (0);

  CHECK_BRK (base + 1, 0, base + 1);
  CHECK_ACCESS (base, CA_C_BASE);
  CHECK_ACCESS (base + 0x1000, CA_NC_NBASE);
  CHECK_REST (HEAPSIZE - 1);

  CHECK_BRK (base, 0, base);
  CHECK_ACCESS (base, CA_NC_BASE);
  CHECK_REST (HEAPSIZE);
}


static void test_ma (int arbitrary)
{
  char *base1, *base2, *base3;

  _uflags (_UF_SBRK_MODEL,
           arbitrary ? _UF_SBRK_ARBITRARY : _UF_SBRK_MONOTONOUS);
  base1 = sbrk (0);
  assert (base1 != (char *)-1);
  assert (((ULONG)base1 & 0xffff) == 0);

  CHECK_ACCESS (base1, CA_NC_BASE);
  CHECK_REST (HEAPSIZE);
  CHECK_SBRK (1, base1, base1 + 1);
  CHECK_ACCESS (base1, CA_C_BASE);
  CHECK_ACCESS (base1 + 0x1000, CA_NC_NBASE);
  CHECK_REST (HEAPSIZE - 1);

  CHECK_SBRK (-1, base1 + 1, base1);
  CHECK_ACCESS (base1, CA_NC_BASE);
  CHECK_REST (HEAPSIZE);

  CHECK_SBRK (HEAPSIZE, base1, base1 + HEAPSIZE);
  CHECK_ACCESS (base1, CA_C_BASE);
  CHECK_ACCESS (base1 + HEAPSIZE - 0x1000, CA_C_NBASE);
  CHECK_REST (0);

  CHECK_SBRK (-HEAPSIZE, base1 + HEAPSIZE, 0);
#ifdef SYS
  /* We no longer have a heap object! */
  CHECK_ACCESS (base1, CA_FREE);
  base1 = sbrk (0);
  assert (base1 != (char *)-1);
  assert (((ULONG)base1 & 0xffff) == 0);
#endif
  CHECK_ACCESS (base1, CA_NC_BASE);
  CHECK_REST (HEAPSIZE);

  CHECK_BRK (base1, 0, base1);
  CHECK_BRK (base1 - 1, -1, base1);
  CHECK_BRK (base1 + HEAPSIZE + 1, -1, base1);

  CHECK_BRK (base1 + HEAPSIZE, 0, base1 + HEAPSIZE);
  CHECK_ACCESS (base1, CA_C_BASE);
  CHECK_ACCESS (base1 + HEAPSIZE - 0x1000, CA_C_NBASE);
  CHECK_REST (0);

  CHECK_BRK (base1 + 1, 0, base1 + 1);
  CHECK_ACCESS (base1, CA_C_BASE);
  CHECK_ACCESS (base1 + 0x1000, CA_NC_NBASE);
  CHECK_REST (HEAPSIZE - 1);

  CHECK_BRK (base1, 0, base1);
  CHECK_ACCESS (base1, CA_NC_BASE);
  CHECK_REST (HEAPSIZE);

  CHECK_SBRK (HEAPSIZE - 0x4000, base1, base1 + HEAPSIZE - 0x4000);
  CHECK_ACCESS (base1 + HEAPSIZE - 0x5000, CA_C_NBASE);
  CHECK_ACCESS (base1 + HEAPSIZE - 0x4000, CA_NC_NBASE);
  CHECK_REST (0x4000);

  /* The rest currently does not apply to DOS. */

  if (_osmode == DOS_MODE)
    return;

  /* Create 2nd heap object. */

  base2 = sbrk (0x8000);
  assert (arbitrary || base2 > base1);
  CHECK_ACCESS (base2, CA_C_BASE);
  CHECK_ACCESS (base2 + 0x8000, CA_NC_NBASE);
  CHECK_REST (HEAPSIZE - 0x8000);

  CHECK_SBRK (-0x4000, base2 + 0x8000, base2 + 0x4000);
  CHECK_ACCESS (base2 + 0x3000, CA_C_NBASE);
  CHECK_ACCESS (base2 + 0x4000, CA_NC_NBASE);
  CHECK_REST (HEAPSIZE - 0x4000);

  CHECK_BRK (base2 + 0x2000, 0, base2 + 0x2000);
  CHECK_REST (HEAPSIZE - 0x2000);
  CHECK_BRK (base2 + HEAPSIZE + 1, -1, base2 + 0x2000);

  /* Create 3rd heap object. */

  base3 = sbrk (HEAPSIZE);
  assert (arbitrary || base3 > base2);
  CHECK_ACCESS (base3, CA_C_BASE);
  CHECK_REST (0);

  CHECK_SBRK (-0x4000, base3 + HEAPSIZE, base3 + HEAPSIZE - 0x4000);
  CHECK_ACCESS (base3 + HEAPSIZE - 0x5000, CA_C_NBASE);
  CHECK_ACCESS (base3 + HEAPSIZE - 0x4000, CA_NC_NBASE);

  /* Remove 2nd and 3rd heap objects with brk(). */

  CHECK_BRK (base2 + 0x3000,            -1, base3 + HEAPSIZE - 0x4000);
  CHECK_BRK (base1 + HEAPSIZE - 0x3000, -1, base3 + HEAPSIZE - 0x4000);
  CHECK_BRK (base1 + HEAPSIZE - 0x5000,  0, base1 + HEAPSIZE - 0x5000);
  CHECK_ACCESS (base3, CA_FREE);
  CHECK_ACCESS (base2, CA_FREE);
  CHECK_ACCESS (base1 + HEAPSIZE - 0x6000, CA_C_NBASE);
  CHECK_ACCESS (base1 + HEAPSIZE - 0x5000, CA_NC_NBASE);
  CHECK_REST (0x5000);

  /* Create 2nd heap object. */

  base2 = sbrk (HEAPSIZE);
  assert (arbitrary || base2 > base1);
  CHECK_ACCESS (base2, CA_C_BASE);
  CHECK_REST (0);

  /* This may change in the future.  Then, sbrk(-n) will deallocate N
     bytes from as many heap objects as required. */

  CHECK_SBRK (-(HEAPSIZE+1), (char *)-1, base2 + HEAPSIZE);

  /* Remove 2nd heap object with sbrk(). */

  CHECK_SBRK (-HEAPSIZE, base2 + HEAPSIZE, base1 + HEAPSIZE - 0x5000);
  CHECK_ACCESS (base2, CA_FREE);
}


int main (int argc, char *argv[])
{
  setvbuf (stdout, NULL, _IONBF, 0);

  if (argc == 2 && strcmp (argv[1], "-c") == 0)
    test_c ();
  else if (argc == 2 && strcmp (argv[1], "-m") == 0)
    test_ma (0);
  else if (argc == 2 && strcmp (argv[1], "-a") == 0)
    test_ma (1);
  else
    {
      puts ("Usage:\n"
            "  sbrk2 -c         Test _UF_SBRK_CONTIGUOUS\n"
            "  sbrk2 -m         Test _UF_SBRK_MONOTONOUS\n"
            "  sbrk2 -a         Test _UF_SBRK_ARBITRARY");
      return 1;
    }
  return 0;
}
