2000-02-18 03:38:33 +08:00
|
|
|
/* dll_init.cc
|
|
|
|
|
2001-09-12 04:01:02 +08:00
|
|
|
Copyright 1998, 1999, 2000, 2001 Red Hat, Inc.
|
2000-02-18 03:38:33 +08:00
|
|
|
|
|
|
|
This software is a copyrighted work licensed under the terms of the
|
|
|
|
Cygwin license. Please consult the file "CYGWIN_LICENSE" for
|
|
|
|
details. */
|
|
|
|
|
|
|
|
#include "winsup.h"
|
2000-08-03 00:28:18 +08:00
|
|
|
#include <stdlib.h>
|
2000-02-18 03:38:33 +08:00
|
|
|
#include "exceptions.h"
|
2000-08-22 11:58:47 +08:00
|
|
|
#include "cygerrno.h"
|
2000-09-08 10:56:55 +08:00
|
|
|
#include "perprocess.h"
|
|
|
|
#include "dll_init.h"
|
|
|
|
#include "environ.h"
|
2001-09-08 05:32:07 +08:00
|
|
|
#include "security.h"
|
|
|
|
#include "fhandler.h"
|
|
|
|
#include "dtable.h"
|
|
|
|
#include "cygheap.h"
|
2000-02-18 03:38:33 +08:00
|
|
|
|
|
|
|
extern void __stdcall check_sanity_and_sync (per_process *);
|
|
|
|
|
2000-07-15 10:48:11 +08:00
|
|
|
dll_list NO_COPY dlls;
|
2000-02-18 03:38:33 +08:00
|
|
|
|
2001-09-06 13:17:22 +08:00
|
|
|
static NO_COPY int in_forkee = 0;
|
2001-09-06 12:41:59 +08:00
|
|
|
static int dll_global_dtors_recorded;
|
2000-02-18 03:38:33 +08:00
|
|
|
|
2000-07-15 10:48:11 +08:00
|
|
|
/* Run destructors for all DLLs on exit. */
|
2000-02-18 03:38:33 +08:00
|
|
|
static void
|
2000-07-15 10:48:11 +08:00
|
|
|
dll_global_dtors()
|
2000-02-18 03:38:33 +08:00
|
|
|
{
|
2000-07-15 10:48:11 +08:00
|
|
|
for (dll *d = dlls.istart (DLL_ANY); d; d = dlls.inext ())
|
|
|
|
d->p.run_dtors ();
|
2000-02-18 03:38:33 +08:00
|
|
|
}
|
|
|
|
|
2000-07-15 10:48:11 +08:00
|
|
|
/* Run all constructors associated with a dll */
|
|
|
|
void
|
|
|
|
per_module::run_ctors ()
|
2000-02-18 03:38:33 +08:00
|
|
|
{
|
2000-07-15 10:48:11 +08:00
|
|
|
void (**pfunc)() = ctors;
|
2000-02-18 03:38:33 +08:00
|
|
|
|
|
|
|
/* 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++);
|
|
|
|
|
2000-09-05 01:52:42 +08:00
|
|
|
for (int j = i - 1; j > 0; j--)
|
2000-02-18 03:38:33 +08:00
|
|
|
(pfunc[j]) ();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2000-07-15 10:48:11 +08:00
|
|
|
/* Run all destructors associated with a dll */
|
|
|
|
void
|
|
|
|
per_module::run_dtors ()
|
2000-02-18 03:38:33 +08:00
|
|
|
{
|
2000-07-15 10:48:11 +08:00
|
|
|
void (**pfunc)() = dtors;
|
2000-02-18 03:38:33 +08:00
|
|
|
for (int i = 1; pfunc[i]; i++)
|
|
|
|
(pfunc[i]) ();
|
|
|
|
}
|
|
|
|
|
2000-07-15 10:48:11 +08:00
|
|
|
/* Initialize an individual DLL */
|
|
|
|
int
|
|
|
|
dll::init ()
|
2000-02-18 03:38:33 +08:00
|
|
|
{
|
2000-07-15 10:48:11 +08:00
|
|
|
int ret = 1;
|
2000-02-18 03:38:33 +08:00
|
|
|
|
2000-07-15 10:48:11 +08:00
|
|
|
/* Why didn't we just import this variable? */
|
|
|
|
*(p.envptr) = __cygwin_environ;
|
2000-02-18 03:38:33 +08:00
|
|
|
|
2000-07-15 10:48:11 +08:00
|
|
|
/* Don't run constructors or the "main" if we've forked. */
|
|
|
|
if (!in_forkee)
|
2000-02-18 03:38:33 +08:00
|
|
|
{
|
|
|
|
/* global contructors */
|
2000-07-15 10:48:11 +08:00
|
|
|
p.run_ctors ();
|
2000-02-18 03:38:33 +08:00
|
|
|
|
|
|
|
/* entry point of dll (use main of per_process with null args...) */
|
2000-07-15 10:48:11 +08:00
|
|
|
if (p.main)
|
|
|
|
ret = (*(p.main)) (0, 0, 0);
|
2000-02-18 03:38:33 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2000-07-15 10:48:11 +08:00
|
|
|
/* Look for a dll based on name */
|
|
|
|
dll *
|
|
|
|
dll_list::operator[] (const char *name)
|
2000-02-18 03:38:33 +08:00
|
|
|
{
|
2000-07-15 10:48:11 +08:00
|
|
|
dll *d = &start;
|
|
|
|
while ((d = d->next) != NULL)
|
|
|
|
if (strcasematch (name, d->name))
|
|
|
|
return d;
|
2000-02-18 03:38:33 +08:00
|
|
|
|
2000-07-15 10:48:11 +08:00
|
|
|
return NULL;
|
2000-02-18 03:38:33 +08:00
|
|
|
}
|
|
|
|
|
2000-07-15 10:48:11 +08:00
|
|
|
#define RETRIES 100
|
|
|
|
|
2000-07-16 12:10:33 +08:00
|
|
|
/* Allocate space for a dll struct contiguous with the just-loaded dll. */
|
2000-07-15 10:48:11 +08:00
|
|
|
dll *
|
|
|
|
dll_list::alloc (HINSTANCE h, per_process *p, dll_type type)
|
2000-02-18 03:38:33 +08:00
|
|
|
{
|
2000-07-15 10:48:11 +08:00
|
|
|
char name[MAX_PATH + 1];
|
|
|
|
DWORD namelen = GetModuleFileName (h, name, sizeof (name));
|
2000-02-18 03:38:33 +08:00
|
|
|
|
2000-07-15 10:48:11 +08:00
|
|
|
/* Already loaded? */
|
|
|
|
dll *d = dlls[name];
|
|
|
|
if (d)
|
2000-02-18 03:38:33 +08:00
|
|
|
{
|
2000-07-15 10:48:11 +08:00
|
|
|
d->count++; /* Yes. Bump the usage count. */
|
|
|
|
return d; /* Return previously allocated pointer. */
|
2000-02-18 03:38:33 +08:00
|
|
|
}
|
|
|
|
|
2000-07-18 03:18:21 +08:00
|
|
|
SYSTEM_INFO s1;
|
|
|
|
GetSystemInfo (&s1);
|
|
|
|
|
2000-07-15 10:48:11 +08:00
|
|
|
int i;
|
|
|
|
void *s = p->bss_end;
|
2000-07-18 03:18:21 +08:00
|
|
|
DWORD n;
|
2000-07-15 10:48:11 +08:00
|
|
|
MEMORY_BASIC_INFORMATION m;
|
|
|
|
/* Search for space after the DLL */
|
2000-07-18 03:18:21 +08:00
|
|
|
for (i = 0; i <= RETRIES; i++, s = (char *) m.BaseAddress + m.RegionSize)
|
2000-02-18 03:38:33 +08:00
|
|
|
{
|
2000-07-15 10:48:11 +08:00
|
|
|
if (!VirtualQuery (s, &m, sizeof (m)))
|
|
|
|
return NULL; /* Can't do it. */
|
|
|
|
if (m.State == MEM_FREE)
|
2000-07-18 03:18:21 +08:00
|
|
|
{
|
|
|
|
/* Couldn't find any. Uh oh. FIXME: Issue an error? */
|
|
|
|
if (i == RETRIES)
|
|
|
|
return NULL; /* Oh well. Couldn't locate free space. */
|
|
|
|
|
|
|
|
/* Ensure that this is rounded to the nearest page boundary.
|
|
|
|
FIXME: Should this be ensured by VirtualQuery? */
|
|
|
|
n = (DWORD) m.BaseAddress;
|
|
|
|
DWORD r = n % s1.dwAllocationGranularity;
|
|
|
|
|
|
|
|
if (r)
|
|
|
|
n = ((n - r) + s1.dwAllocationGranularity);
|
|
|
|
|
|
|
|
/* First reserve the area of memory, then commit it. */
|
|
|
|
if (VirtualAlloc ((void *) n, sizeof (dll), MEM_RESERVE, PAGE_READWRITE))
|
|
|
|
d = (dll *) VirtualAlloc ((void *) n, sizeof (dll), MEM_COMMIT,
|
|
|
|
PAGE_READWRITE);
|
|
|
|
if (d)
|
|
|
|
break;
|
|
|
|
}
|
2000-02-18 03:38:33 +08:00
|
|
|
}
|
|
|
|
|
2000-07-16 12:10:33 +08:00
|
|
|
/* Did we succeed? */
|
2000-07-15 12:36:10 +08:00
|
|
|
if (d == NULL)
|
2000-07-16 12:10:33 +08:00
|
|
|
{ /* Nope. */
|
2000-07-15 12:36:10 +08:00
|
|
|
#ifdef DEBUGGING
|
2000-07-18 10:26:46 +08:00
|
|
|
system_printf ("VirtualAlloc failed, %E");
|
2000-07-15 12:36:10 +08:00
|
|
|
#endif
|
|
|
|
__seterrno ();
|
|
|
|
return NULL;
|
|
|
|
}
|
2000-07-15 10:48:11 +08:00
|
|
|
|
|
|
|
/* Now we've allocated a block of information. Fill it in with the supplied
|
|
|
|
info about this DLL. */
|
|
|
|
d->count = 1;
|
|
|
|
d->namelen = namelen;
|
|
|
|
strcpy (d->name, name);
|
|
|
|
d->handle = h;
|
|
|
|
d->p = p;
|
|
|
|
d->type = type;
|
|
|
|
if (end == NULL)
|
|
|
|
end = &start; /* Point to "end" of dll chain. */
|
|
|
|
end->next = d; /* Standard linked list stuff. */
|
|
|
|
d->next = NULL;
|
|
|
|
d->prev = end;
|
|
|
|
end = d;
|
|
|
|
tot++;
|
|
|
|
if (type == DLL_LOAD)
|
|
|
|
loaded_dlls++;
|
|
|
|
return d;
|
2000-02-18 03:38:33 +08:00
|
|
|
}
|
|
|
|
|
2000-07-15 10:48:11 +08:00
|
|
|
/* Detach a DLL from the chain. */
|
2000-02-18 03:38:33 +08:00
|
|
|
void
|
2000-07-15 10:48:11 +08:00
|
|
|
dll_list::detach (dll *d)
|
2000-02-18 03:38:33 +08:00
|
|
|
{
|
2000-07-15 10:48:11 +08:00
|
|
|
if (d->count <= 0)
|
|
|
|
system_printf ("WARNING: try to detach an already detached dll ...\n");
|
|
|
|
else if (--d->count == 0)
|
2000-02-18 03:38:33 +08:00
|
|
|
{
|
2000-07-15 10:48:11 +08:00
|
|
|
d->p.run_dtors ();
|
|
|
|
d->prev->next = d->next;
|
|
|
|
if (d->next)
|
|
|
|
d->next->prev = d->prev;
|
|
|
|
if (d->type == DLL_LOAD)
|
|
|
|
loaded_dlls--;
|
|
|
|
if (end == d)
|
|
|
|
end = d->prev;
|
2000-07-15 12:36:10 +08:00
|
|
|
VirtualFree (d, 0, MEM_RELEASE);
|
2000-02-18 03:38:33 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2000-07-16 12:10:33 +08:00
|
|
|
/* Initialization for all linked DLLs, called by dll_crt0_1. */
|
2000-02-18 03:38:33 +08:00
|
|
|
void
|
2000-07-15 10:48:11 +08:00
|
|
|
dll_list::init ()
|
2000-02-18 03:38:33 +08:00
|
|
|
{
|
2000-07-15 10:48:11 +08:00
|
|
|
debug_printf ("here");
|
|
|
|
/* Make sure that destructors are called on exit. */
|
|
|
|
if (!dll_global_dtors_recorded)
|
2000-02-18 03:38:33 +08:00
|
|
|
{
|
2000-07-15 10:48:11 +08:00
|
|
|
atexit (dll_global_dtors);
|
|
|
|
dll_global_dtors_recorded = 1;
|
2000-02-18 03:38:33 +08:00
|
|
|
}
|
|
|
|
|
2000-07-15 10:48:11 +08:00
|
|
|
/* Walk the dll chain, initializing each dll */
|
|
|
|
dll *d = &start;
|
|
|
|
while ((d = d->next))
|
|
|
|
d->init ();
|
2000-02-18 03:38:33 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
#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. */
|
2000-07-15 10:48:11 +08:00
|
|
|
static void
|
2000-02-18 03:38:33 +08:00
|
|
|
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. */
|
2000-07-15 10:48:11 +08:00
|
|
|
static void
|
2000-02-18 03:38:33 +08:00
|
|
|
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 &&
|
2001-09-08 05:32:07 +08:00
|
|
|
((void *) start < cygheap->heapbase || (void *) start > cygheap->heaptop)))
|
2000-02-18 03:38:33 +08:00
|
|
|
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
|
2000-07-15 10:48:11 +08:00
|
|
|
dll_list::load_after_fork (HANDLE parent, dll *first)
|
2000-02-18 03:38:33 +08:00
|
|
|
{
|
2000-07-15 10:48:11 +08:00
|
|
|
in_forkee = 1;
|
2000-02-18 03:38:33 +08:00
|
|
|
int try2 = 0;
|
2000-07-15 10:48:11 +08:00
|
|
|
dll d;
|
2000-02-18 03:38:33 +08:00
|
|
|
|
2000-10-28 13:41:44 +08:00
|
|
|
void *next = first;
|
2000-07-15 10:48:11 +08:00
|
|
|
while (next)
|
|
|
|
{
|
|
|
|
DWORD nb;
|
|
|
|
/* Read the dll structure from the parent. */
|
2000-07-16 12:10:33 +08:00
|
|
|
if (!ReadProcessMemory (parent, next, &d, sizeof (dll), &nb) ||
|
|
|
|
nb != sizeof (dll))
|
2000-07-15 10:48:11 +08:00
|
|
|
return;
|
2000-07-16 12:10:33 +08:00
|
|
|
|
2000-07-15 10:48:11 +08:00
|
|
|
/* We're only interested in dynamically loaded dlls.
|
|
|
|
Hopefully, this function wouldn't even have been called unless
|
|
|
|
the parent had some of those. */
|
|
|
|
if (d.type == DLL_LOAD)
|
|
|
|
{
|
|
|
|
HMODULE h = LoadLibraryEx (d.name, NULL, DONT_RESOLVE_DLL_REFERENCES);
|
|
|
|
|
|
|
|
/* See if DLL will load in proper place. If so, free it and reload
|
|
|
|
it the right way.
|
|
|
|
It sort of stinks that we can't invert the order of the FreeLibrary
|
|
|
|
and LoadLibrary since Microsoft documentation seems to imply that that
|
|
|
|
should do what we want. However, since the library was loaded above,
|
2000-07-16 12:10:33 +08:00
|
|
|
the second LoadLibrary does not execute it's startup code unless it
|
2000-07-15 10:48:11 +08:00
|
|
|
is first unloaded. */
|
|
|
|
if (h == d.handle)
|
|
|
|
{
|
|
|
|
FreeLibrary (h);
|
|
|
|
LoadLibrary (d.name);
|
|
|
|
}
|
|
|
|
else if (try2)
|
|
|
|
api_fatal ("unable to remap %s to same address as parent -- %p", d.name, h);
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* It loaded in the wrong place. Dunno why this happens but it always
|
|
|
|
seems to happen when there are multiple DLLs attempting to load into
|
|
|
|
the same address space. In the "forked" process, the second DLL always
|
|
|
|
loads into a different location. */
|
|
|
|
FreeLibrary (h);
|
|
|
|
/* Block all of the memory up to the new load address. */
|
|
|
|
reserve_upto (d.name, (DWORD) d.handle);
|
|
|
|
try2 = 1; /* And try */
|
|
|
|
continue; /* again. */
|
|
|
|
}
|
|
|
|
/* If we reached here, and try2 is set, then there is a lot of memory to
|
|
|
|
release. */
|
|
|
|
if (try2)
|
|
|
|
{
|
|
|
|
release_upto (d.name, (DWORD) d.handle);
|
|
|
|
try2 = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
next = d.next; /* Get the address of the next DLL. */
|
|
|
|
}
|
|
|
|
in_forkee = 0;
|
2000-02-18 03:38:33 +08:00
|
|
|
}
|
|
|
|
|
2000-07-12 12:49:07 +08:00
|
|
|
extern "C" int
|
2000-02-18 03:38:33 +08:00
|
|
|
dll_dllcrt0 (HMODULE h, per_process *p)
|
|
|
|
{
|
2000-07-06 07:46:44 +08:00
|
|
|
if (p == NULL)
|
|
|
|
p = &__cygwin_user_data;
|
|
|
|
else
|
2000-07-12 12:49:07 +08:00
|
|
|
*(p->impure_ptr_ptr) = __cygwin_user_data.impure_ptr;
|
2000-07-06 07:46:44 +08:00
|
|
|
|
2000-02-18 03:38:33 +08:00
|
|
|
/* Partially initialize Cygwin guts for non-cygwin apps. */
|
2000-07-06 07:46:44 +08:00
|
|
|
if (dynamically_loaded && user_data->magic_biscuit == 0)
|
|
|
|
dll_crt0 (p);
|
2000-07-18 03:18:21 +08:00
|
|
|
else
|
2000-07-15 10:48:11 +08:00
|
|
|
check_sanity_and_sync (p);
|
|
|
|
|
|
|
|
dll_type type;
|
|
|
|
|
|
|
|
/* If this function is called before cygwin has finished
|
|
|
|
initializing, then the DLL must be a cygwin-aware DLL
|
|
|
|
that was explicitly linked into the program rather than
|
|
|
|
a dlopened DLL. */
|
|
|
|
if (!cygwin_finished_initializing)
|
|
|
|
type = DLL_LINK;
|
|
|
|
else
|
|
|
|
{
|
|
|
|
type = DLL_LOAD;
|
|
|
|
dlls.reload_on_fork = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Allocate and initialize space for the DLL. */
|
|
|
|
dll *d = dlls.alloc (h, p, type);
|
|
|
|
|
|
|
|
/* If d == NULL, then something is broken.
|
|
|
|
Otherwise, if we've finished initializing, it's ok to
|
|
|
|
initialize the DLL. If we haven't finished initializing,
|
|
|
|
it may not be safe to call the dll's "main" since not
|
|
|
|
all of cygwin's internal structures may have been set up. */
|
|
|
|
if (!d || (cygwin_finished_initializing && !d->init ()))
|
|
|
|
return -1;
|
2000-10-28 13:41:44 +08:00
|
|
|
|
2000-07-15 10:48:11 +08:00
|
|
|
return (DWORD) d;
|
2000-02-18 03:38:33 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/* 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. */
|
|
|
|
|
2000-07-15 10:48:11 +08:00
|
|
|
extern "C" int
|
2000-02-18 03:38:33 +08:00
|
|
|
dll_noncygwin_dllcrt0 (HMODULE h, per_process *p)
|
|
|
|
{
|
|
|
|
return dll_dllcrt0 (h, p);
|
|
|
|
}
|
|
|
|
|
2000-07-15 10:48:11 +08:00
|
|
|
extern "C" void
|
|
|
|
cygwin_detach_dll (dll *d)
|
2000-02-18 03:38:33 +08:00
|
|
|
{
|
2000-07-15 10:48:11 +08:00
|
|
|
dlls.detach (d);
|
2000-02-18 03:38:33 +08:00
|
|
|
}
|
|
|
|
|
2000-07-15 10:48:11 +08:00
|
|
|
extern "C" void
|
2000-02-18 03:38:33 +08:00
|
|
|
dlfork (int val)
|
|
|
|
{
|
2000-07-15 10:48:11 +08:00
|
|
|
dlls.reload_on_fork = val;
|
2000-02-18 03:38:33 +08:00
|
|
|
}
|
|
|
|
|
2000-07-15 10:48:11 +08:00
|
|
|
/* Called from various places to update all of the individual
|
|
|
|
ideas of the environ block. Explain to me again why we didn't
|
|
|
|
just import __cygwin_environ? */
|
|
|
|
void __stdcall
|
|
|
|
update_envptrs ()
|
|
|
|
{
|
|
|
|
extern char ***main_environ;
|
|
|
|
for (dll *d = dlls.istart (DLL_ANY); d; d = dlls.inext ())
|
|
|
|
{
|
|
|
|
*(d->p.envptr) = __cygwin_environ;
|
|
|
|
}
|
|
|
|
*main_environ = __cygwin_environ;
|
|
|
|
}
|