/* dll_init.cc

   Copyright 1998, 1999, 2000 Cygnus Solutions.

This software is a copyrighted work licensed under the terms of the
Cygwin license.  Please consult the file "CYGWIN_LICENSE" for
details. */

#include <stdlib.h>
#include "winsup.h"
#include "exceptions.h"
#include "dll_init.h"

extern void __stdcall check_sanity_and_sync (per_process *);

#ifdef _MT_SAFE
extern ResourceLocks _reslock NO_COPY;
extern MTinterface _mtinterf NO_COPY;
#endif /*_MT_SAFE*/

/* WARNING: debug can't be called before init !!!! */

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// the private structure

typedef enum   { NONE, LINK, LOAD } dllType;

struct dll
{
  per_process *p;
  HMODULE handle;
  const char *name;
  dllType type;
};

//-----------------------------------------------------------------------------

#define MAX_DLL_BEFORE_INIT	100 // FIXME: enough ???
static dll _list_before_init[MAX_DLL_BEFORE_INIT];

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// local variables

static DllList _the;
static int _last = 0;
static int _max = MAX_DLL_BEFORE_INIT;
static dll *_list = _list_before_init;
static int _initCalled = 0;
static int _numberOfOpenedDlls = 0;
static int _forkeeMustReloadDlls = 0;
static int _in_forkee = 0;
static const char *_dlopenedLib = 0;
static int _dlopenIndex = -1;

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------

static int __dll_global_dtors_recorded = 0;

static void
__dll_global_dtors()
{
  _the.doGlobalDestructorsOfDlls();
}

static void
doGlobalCTORS (per_process *p)
{
  void (**pfunc)() = p->ctors;

  /* Run ctors backwards, so skip the first entry and find how many
    there are, then run them.  */

  if (pfunc)
    {
      int i;
      for (i = 1; pfunc[i]; i++);

      for (int j = i - 1; j > 0; j-- )
	(pfunc[j]) ();
    }
}

static void
doGlobalDTORS (per_process *p)
{
  if (!p)
    return;
  void (**pfunc)() = p->dtors;
  for (int i = 1; pfunc[i]; i++)
    (pfunc[i]) ();
}

#define INC 500

static int
add (HMODULE h, char *name, per_process *p, dllType type)
{
  int ret = -1;

  if (p)
    check_sanity_and_sync (p);

  if (_last == _max)
    {
      if (!_initCalled) // we try to load more than MAX_DLL_BEFORE_INIT
	{
	  small_printf ("try to load more dll than max allowed=%d\n",
		       MAX_DLL_BEFORE_INIT);
	  ExitProcess (1);
	}

      dll* newArray = new dll[_max+INC];
      if (_list)
	{
	  memcpy (newArray, _list, _max * sizeof (dll));
	  if (_list != _list_before_init)
	    delete []_list;
	}
      _list = newArray;
      _max += INC;
    }

  _list[_last].name = name && type == LOAD ? strdup (name) : NULL;
  _list[_last].handle = h;
  _list[_last].p = p;
  _list[_last].type = type;

  ret = _last++;
  return ret;
}

static int
initOneDll (per_process *p)
{
  /* global variable user_data must be initialized */
  if (user_data == NULL)
    {
      small_printf ("WARNING: process not inited while trying to init a DLL!\n");
      return 0;
    }

  /* init impure_ptr */
  *(p->impure_ptr_ptr) = *(user_data->impure_ptr_ptr);

  /* FIXME: init environment (useful?) */
  *(p->envptr) = *(user_data->envptr);

  /* FIXME: need other initializations? */

  int ret = 1;
  if (!_in_forkee)
    {
      /* global contructors */
      doGlobalCTORS (p);

      /* entry point of dll (use main of per_process with null args...) */
      if (p->main)
	ret = (*(p->main)) (0, 0, 0);
    }

  return ret;
}

DllList&
DllList::the ()
{
  return _the;
}

void
DllList::currentDlOpenedLib (const char *name)
{
  if (_dlopenedLib != 0)
    small_printf ("WARNING: previous dlopen of %s wasn't correctly performed\n", _dlopenedLib);
  _dlopenedLib = name;
  _dlopenIndex = -1;
}

int
DllList::recordDll (HMODULE h, per_process *p)
{
  int ret = -1;

  /* debug_printf ("Record a dll p=%p\n", p); see WARNING */
  dllType type = LINK;
  if (_initCalled)
    {
      type = LOAD;
      _numberOfOpenedDlls++;
      forkeeMustReloadDlls (1);
    }

  if (_in_forkee)
    {
      ret = 0;		// Just a flag
      goto out;
    }

  char buf[MAX_PATH];
  GetModuleFileName (h, buf, MAX_PATH);

  if (type == LOAD && _dlopenedLib !=0)
    {
    // it is not the current dlopened lib
    // so we insert one empty lib to preserve place for current dlopened lib
    if (!strcasematch (_dlopenedLib, buf))
      {
      if (_dlopenIndex == -1)
	_dlopenIndex = add (0, 0, 0, NONE);
      ret = add (h, buf, p, type);
      }
    else // it is the current dlopened lib
      {
	if (_dlopenIndex != -1)
	  {
	    _list[_dlopenIndex].handle = h;
	    _list[_dlopenIndex].p = p;
	    _list[_dlopenIndex].type = type;
	    ret = _dlopenIndex;
	    _dlopenIndex = -1;
	  }
	else // it this case the dlopened lib doesn't need other lib
	  ret = add (h, buf, p, type);
	_dlopenedLib = 0;
      }
    }
  else
    ret = add (h, buf, p, type);

out:
  if (_initCalled) // main module is already initialized
    {
      if (!initOneDll (p))
	ret = -1;
    }
  return ret;
}

void
DllList::detachDll (int dll_index)
{
  if (dll_index != -1)
    {
      dll *aDll = &(_list[dll_index]);
      doGlobalDTORS (aDll->p);
      if (aDll->type == LOAD)
	_numberOfOpenedDlls--;
      aDll->type = NONE;
    }
  else
    small_printf ("WARNING: try to detach an already detached dll ...\n");
}

void
DllList::initAll ()
{
  // init for destructors
  // because initAll isn't called in forked process, this exit function will
  // be recorded only once
  if (!__dll_global_dtors_recorded)
    {
      atexit (__dll_global_dtors);
      __dll_global_dtors_recorded = 1;
    }

  if (!_initCalled)
    {
      debug_printf ("call to DllList::initAll");
      for (int i = 0; i < _last; i++)
	{
	  per_process *p = _list[i].p;
	  if (p)
	    initOneDll (p);
	}
      _initCalled = 1;
    }
}

void
DllList::doGlobalDestructorsOfDlls ()
{
  // global destructors in reverse order
  for (int i = _last - 1; i >= 0; i--)
    {
      if (_list[i].type != NONE)
	{
	  per_process *p = _list[i].p;
	  if (p)
	    doGlobalDTORS (p);
	}
    }
}

int
DllList::numberOfOpenedDlls ()
{
  return _numberOfOpenedDlls;
}

int
DllList::forkeeMustReloadDlls ()
{
  return _forkeeMustReloadDlls;
}

void
DllList::forkeeMustReloadDlls (int i)
{
  _forkeeMustReloadDlls = i;
}

#define A64K (64 * 1024)

/* Mark every memory address up to "here" as reserved.  This may force
   Windows NT to load a DLL in the next available, lowest slot. */
void
reserve_upto (const char *name, DWORD here)
{
  DWORD size;
  MEMORY_BASIC_INFORMATION mb;
  for (DWORD start = 0x10000; start < here; start += size)
    if (!VirtualQuery ((void *) start, &mb, sizeof (mb)))
      size = 64 * 1024;
    else
      {
	size = A64K * ((mb.RegionSize + A64K - 1) / A64K);
	start = A64K * (((DWORD) mb.BaseAddress + A64K - 1) / A64K);

	if (start + size > here)
	  size = here - start;
	if (mb.State == MEM_FREE &&
	    !VirtualAlloc ((void *) start, size, MEM_RESERVE, PAGE_NOACCESS))
	  api_fatal ("couldn't allocate memory %p(%d) for '%s' alignment, %E\n",
		     start, size, name);
      }
}

/* Release all of the memory previously allocated by "upto" above.
   Note that this may also free otherwise reserved memory.  If that becomes
   a problem, we'll have to keep track of the memory that we reserve above. */
void
release_upto (const char *name, DWORD here)
{
  DWORD size;
  MEMORY_BASIC_INFORMATION mb;
  for (DWORD start = 0x10000; start < here; start += size)
    if (!VirtualQuery ((void *) start, &mb, sizeof (mb)))
      size = 64 * 1024;
    else
      {
	size = mb.RegionSize;
	if (!(mb.State == MEM_RESERVE && mb.AllocationProtect == PAGE_NOACCESS &&
	    ((void *) start < user_data->heapbase || (void *) start > user_data->heaptop)))
	  continue;
	if (!VirtualFree ((void *) start, 0, MEM_RELEASE))
	  api_fatal ("couldn't release memory %p(%d) for '%s' alignment, %E\n",
		     start, size, name);
      }
}

/* Reload DLLs after a fork.  Iterates over the list of dynamically loaded DLLs
   and attempts to load them in the same place as they were loaded in the parent. */
void
DllList::forkeeLoadDlls ()
{
  _initCalled = 1;
  _in_forkee = 1;
  int try2 = 0;
  for (int i = 0; i < _last; i++)
    if (_list[i].type == LOAD)
      {
	const char *name = _list[i].name;
	HMODULE handle = _list[i].handle;
	HMODULE h = LoadLibraryEx (name, NULL, DONT_RESOLVE_DLL_REFERENCES);

	if (h == handle)
	  {
	    FreeLibrary (h);
	    LoadLibrary (name);
	  }
	else if (try2)
	  api_fatal ("unable to remap %s to same address as parent -- %p", name, h);
	else
	  {
	    FreeLibrary (h);
	    reserve_upto (name, (DWORD) handle);
	    try2 = 1;
	    i--;
	    continue;
	  }
	if (try2)
	  {
	    release_upto (name, (DWORD) handle);
	    try2 = 0;
	  }
      }
  _in_forkee = 0;
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// iterators

DllListIterator::DllListIterator (int type) : _type (type), _index (-1)
{
  operator++ ();
}

DllListIterator::~DllListIterator ()
{
}

DllListIterator::operator per_process* ()
{
  return _list[index ()].p;
}

void
DllListIterator::operator++ ()
{
  _index++;
  while (_index < _last && (int) (_list[_index].type) != _type)
    _index++;
  if (_index == _last)
    _index = -1;
}

LinkedDllIterator::LinkedDllIterator () : DllListIterator ((int) LINK)
{
}

LinkedDllIterator::~LinkedDllIterator ()
{
}

LoadedDllIterator::LoadedDllIterator () : DllListIterator ((int) LOAD)
{
}

LoadedDllIterator::~LoadedDllIterator ()
{
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// the extern symbols

extern "C"
{
  /* This is an exported copy of environ which can be used by DLLs
     which use cygwin.dll.  */
  extern struct _reent reent_data;
};

extern "C"
int
dll_dllcrt0 (HMODULE h, per_process *p)
{
  /* Partially initialize Cygwin guts for non-cygwin apps. */
  if (dynamically_loaded && (! user_data || user_data->magic_biscuit == 0))
    {
      dll_crt0 (p);
    }
  return _the.recordDll (h, p);
}

/* OBSOLETE: This function is obsolescent and will go away in the
   future.  Cygwin can now handle being loaded from a noncygwin app
   using the same entry point. */

extern "C"
int
dll_noncygwin_dllcrt0 (HMODULE h, per_process *p)
{
  return dll_dllcrt0 (h, p);
}

extern "C"
void
cygwin_detach_dll (int dll_index)
{
  _the.detachDll (dll_index);
}

extern "C"
void
dlfork (int val)
{
  _the.forkeeMustReloadDlls (val);
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------