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

/* Check loading of DLLs and copying of DLL data. */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <getopt.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <emx/syscalls.h>
#define INCL_DOSMODULEMGR
#include <os2.h>

char *cleanup_msg;

static void cleanup (void)
{
  printf ("Cleaning up %s...\n", cleanup_msg);
}

static HMODULE load (const char *name, void (**set)(void),
                     void (**check)(const char *where))
{
  HMODULE hmod;
  ULONG rc;
  char obj[80];

  rc = DosLoadModule (obj, sizeof (obj), name, &hmod);
  if (rc != 0)
    {
      fprintf (stderr, "DosLoadModule failed, rc=%lu, obj=\"%s\"\n", rc, obj);
      exit (2);
    }
  rc = DosQueryProcAddr (hmod, 0, "set", (PPFN)set);
  if (rc != 0)
    {
      fprintf (stderr, "DosQueryProcAddr failed, rc=%lu\n", rc);
      exit (2);
    }
  rc = DosQueryProcAddr (hmod, 0, "check", (PPFN)check);
  if (rc != 0)
    {
      fprintf (stderr, "DosQueryProcAddr failed, rc=%lu\n", rc);
      exit (2);
    }
  return hmod;
}

static HMODULE hmod_a, hmod_b;
static void (*dll_set_a)(void);
static void (*dll_set_b)(void);
static void (*dll_check_a)(const char *where);
static void (*dll_check_b)(const char *where);

static void test (void)
{
  char *s;
  int pid, st;

  puts ("Forking"); fflush (stdout);

  /* Fork.  The child process should receive the open stream. */

  pid = fork ();
  if (pid == 0)
    {
      ULONG written;

      /* Child. */

      DosWrite (1, "Forked\r\n", 8, &written);
      s = getenv ("FORKDLL2_ENV");
      if (_optind != 42 || s == NULL || strcmp (s, "Test string") != 0)
        {
          /* If _optind hasn't been copied, streams and low-level I/O
             probably won't work.  Use a syscall to print the
             message. */

          static char msg[] = "DLL data not copied\r\n";
          __write (2, msg, sizeof (msg) - 1);
          _exit (2);
        }

      if (hmod_a != NULLHANDLE)
        dll_check_a ("child");
      if (hmod_b != NULLHANDLE)
        dll_check_b ("child");

      cleanup_msg = "child";
      exit (0);
    }
  else if (pid == -1)
    {
      perror ("fork()");
      exit (2);
    }
  else
    {
      /* Parent.  Wait for termination of the child process. */

      cleanup_msg = "parent";
      if (waitpid (pid, &st, 0) == -1)
        {
          perror ("waitpid()");
          exit (2);
        }
      if (hmod_a != NULLHANDLE)
        dll_check_a ("parent");
      if (hmod_b != NULLHANDLE)
        dll_check_b ("parent");
      if (!WIFEXITED (st))
        {
          puts ("Child process did not terminate normally.");
          exit (2);
        }
      printf ("child process terminated with status %d\n",
              (int)WEXITSTATUS (st));
    }
}

int main (int argc, char *argv[])
{
  if (_osmode != OS2_MODE)
    {
      /* Well, we can't reach this code anyway because DLLs do not
         work under DOS. */

      puts ("This program requires OS/2.");
      return 0;
    }

  if (argc != 1)
    {
      puts ("Usage: forkdll2");
      return 1;
    }

  /* Load the DLLs. */

  hmod_a = load ("fork2a", &dll_set_a, &dll_check_a);
  hmod_b = load ("fork2b", &dll_set_b, &dll_check_b);

  if (hmod_a != NULLHANDLE)
    dll_set_a ();

  /* Let's modify some data in the CRT DLL's data segment: the
     _optind variable is in the CRT DLL. */

  _optind = 42;

  /* Let's modify some data in the CRT DLL's data segment: the environ
     variable is in the CRT DLL. */

  putenv ("FORKDLL2_ENV=Test string");

  /* Let's modify some data in the CRT DLL's data segment: the
     atexit() table is in the CRT DLL. */

  atexit (cleanup);
  cleanup_msg = "";

  if (hmod_b != NULLHANDLE)
    dll_set_b ();

  test ();
  DosFreeModule (hmod_a);
  hmod_a = NULLHANDLE;
  test ();
  DosFreeModule (hmod_b);
  hmod_b = NULLHANDLE;
  test ();
  return 0;
}
