This is the mail archive of the cygwin-developers mailing list for the Cygwin project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

[PATCH] Protect fork() against dll- and exe-updates.


To support in-cygwin package managers, fork() implementation must not
rely on .exe and .dll files from the original directories, as the
package manager's job is to modify these files.  Instead, create
hardlinks to the originally binaries in /var/run/cygfork/ and use them
during fork, if necessary.

Creating an app.exe.local file also redirects LoadLibrary calls even
with absolute file names to this directory, using the loaded dll's
basename.

The hardlink for dynamically loaded dlls is created inside another
single subdirectory, which's name is mangled from the dll's full path,
while the dll's base filename is preserved to probably serve as linked
dependency for other dynamically loaded dlls.

As only a few users probably need an update-safe fork, the hardlink
creation is enabled only when /var/run/cygfork/<sid> can be created.
That is, when /var/run/cygfork/ does not exist, no hardlinks are
created.  Users have to create that directory manually for now if
needed, using: mkdir --mode=a=rwxt /var/run/cygfork

    * Makefile.in (DLL_OFILES): Add forkable.o.
    * dll_init.h (enum dll_type): Define DLL_SELF.
    (struct dll): Declare members fhandle, bhfi, forkable_name.  Declare
    methods nominate_forkable, create_forkable.  Implement forkedname.
    Declare static methods nt_max_path_buf, to_ntname, ntopenfile.
    (struct dll_list): Declare private members forkables_needs,
    forkables_dirx_size, forkables_dirx_name, forkables_mutex_name,
    forkables_mutex.  Declare private methods track_self,
    find_by_forkedname, forkable_namesize, prepare_forkables_nomination,
    update_forkables_needs, update_forkables, create_forkables,
    denominate_forkables, set_forkables_inheritance.
    Declare public static methods rootname, sidname, exename, ctimename.
    Declare public member main_executable.  Declare public methods
    request_forkables, release_forkables, cleanup_forkables.
    * dll_init.cc (in_load_after_fork): Define earlier in file.
    (struct dll): Implement static methods nt_max_path_buf, to_ntname,
    ntopenfile.
    (struct dll_list): Implement find_by_forkedname, track_self.
    (dll_list::alloc): Use dll::nt_max_path_buf method instead of local
    buffer.  Search for registered dll using find_by_forkedname method
    when in_load_after_fork.  Allocate memory for dll::forkable_name
    using forkable_namesize method.  Do not indicate has_dtors for
    DLL_SELF.  Open file handle as fhandle for the added dll.
    (dll_list::detach): Do not detach DLL_SELF.  Close the fhandle.
    (dll_list::init): Call track_self, for the parent process.
    Do not call dll::init method for DLL_SELF.
    (dll_list::load_after_fork): Call track_self, for the child process.
    (dll_list::load_after_fork_impl): Load dlls using dll::forkedname.
    * fork.cc (frok::child): Call dlls.load_after_fork even without
    dynamically loaded dlls, to track_self at least.
    (frok::parent): Call dlls.request_forkables before CreateProcessW,
    dlls.release_forkables afterwards.  Use dlls.main_executable's
    forkedname method for CreateProcessW.
    * pinfo.cc (pinfo::exit): Run dlls.cleanup_forkables.
    * syscalls.cc (_unlink_nt): Move public unlink_nt function to static
    _unlink_nt function, with 'shareable' as additional argument.
    (unlink_nt): New, wrap _unlink_nt for original behaviour.
    (unlink_nt_shareable): New, wrap _unlink_nt to keep (hardlinked)
    binary files still loadable while removing (their hardlinks).
    * forkable.cc: New file.  Implements maintaining hardlinks for dlls
    and the main_executable necessary during fork when the original
    files have been removed.
---
 winsup/cygwin/Makefile.in |   1 +
 winsup/cygwin/dll_init.cc | 176 +++++++--
 winsup/cygwin/dll_init.h  |  65 ++-
 winsup/cygwin/fork.cc     |  43 +-
 winsup/cygwin/forkable.cc | 987 ++++++++++++++++++++++++++++++++++++++++++++++
 winsup/cygwin/pinfo.cc    |   3 +
 winsup/cygwin/syscalls.cc |  24 +-
 7 files changed, 1245 insertions(+), 54 deletions(-)
 create mode 100644 winsup/cygwin/forkable.cc

diff --git a/winsup/cygwin/Makefile.in b/winsup/cygwin/Makefile.in
index 271a5be..8bc2fac 100644
--- a/winsup/cygwin/Makefile.in
+++ b/winsup/cygwin/Makefile.in
@@ -173,6 +173,7 @@ DLL_OFILES:= \
 	dir.o \
 	dlfcn.o \
 	dll_init.o \
+	forkable.o \
 	dtable.o \
 	environ.o \
 	errno.o \
diff --git a/winsup/cygwin/dll_init.cc b/winsup/cygwin/dll_init.cc
index 8deceb9..5cf9abf 100644
--- a/winsup/cygwin/dll_init.cc
+++ b/winsup/cygwin/dll_init.cc
@@ -37,6 +37,77 @@ muto dll_list::protect;
 
 static bool dll_global_dtors_recorded;
 
+/* We need the in_load_after_fork flag so dll_dllcrt0_1 can decide at fork
+   time if this is a linked DLL or a dynamically loaded DLL.  In either case,
+   both, cygwin_finished_initializing and in_forkee are true, so they are not
+   sufficient to discern the situation. */
+static bool NO_COPY in_load_after_fork;
+
+/* Use this buffer under loader lock conditions only. */
+PWCHAR
+dll::nt_max_path_buf ()
+{
+  static WCHAR NO_COPY buf[NT_MAX_PATH];
+  return buf;
+}
+
+/* Return *nameptr probably prefixed with "\\??\\", but
+   update *nameptr to stay without the "\\??\\" prefix. */
+PWCHAR
+dll::to_ntname (PWCHAR *nameptr, WCHAR ntnamebuf[NT_MAX_PATH])
+{
+  /* avoid using path_conv here: cygheap might not be
+     initialized when started from non-cygwin process,
+     or still might be frozen in_forkee */
+  PWCHAR ntname = *nameptr;
+  if ((*nameptr)[1] == L':')
+    {
+      ntname = ntnamebuf;
+      memmove (ntname + 4, *nameptr,
+	sizeof (*nameptr) * min (NT_MAX_PATH - 4, wcslen (*nameptr) + 1));
+      wcsncpy (ntname, L"\\??\\", 4);
+      ntname[NT_MAX_PATH - 1] = L'\0';
+      *nameptr = ntname + 4;
+    }
+  else
+  if (!wcsncmp (*nameptr, L"\\??\\", 4))
+    *nameptr += 4;
+  return ntname;
+}
+
+/* easy use of NtOpenFile */
+HANDLE
+dll::ntopenfile (PWCHAR ntname, NTSTATUS *pstatus, ULONG openopts)
+{
+  NTSTATUS status;
+  if (!pstatus)
+    pstatus = &status;
+
+  UNICODE_STRING fn;
+  RtlInitUnicodeString (&fn, ntname);
+
+  OBJECT_ATTRIBUTES oa;
+  InitializeObjectAttributes (&oa, &fn, 0, NULL, NULL);
+
+  ACCESS_MASK access = FILE_READ_ATTRIBUTES;
+  if (openopts & FILE_DELETE_ON_CLOSE)
+    access |= DELETE;
+  if (openopts & FILE_DIRECTORY_FILE)
+    access |= FILE_LIST_DIRECTORY;
+
+  access |= SYNCHRONIZE;
+  openopts |= FILE_SYNCHRONOUS_IO_NONALERT;
+
+  HANDLE fh = NULL;
+  ULONG share = FILE_SHARE_VALID_FLAGS;
+  IO_STATUS_BLOCK iosb;
+  *pstatus = NtOpenFile (&fh, access, &oa, &iosb, share, openopts);
+  debug_printf ("%y = NtOpenFile (%p, a %xh, sh %xh, o %xh, io %y, '%W')",
+      *pstatus, fh, access, share, openopts, iosb.Status, fn.Buffer);
+
+  return fh;
+}
+
 /* Run destructors for all DLLs on exit. */
 void
 dll_global_dtors ()
@@ -173,6 +244,19 @@ dll_list::find_by_modname (const PWCHAR modname)
   return NULL;
 }
 
+/* Look for a dll based on the name used
+   to dynamically reload in forked child. */
+dll *
+dll_list::find_by_forkedname (PCWCHAR name)
+{
+  dll *d = &start;
+  while ((d = d->next) != NULL)
+    if (!wcscasecmp (name, d->forkedname ()))
+      return d;
+
+  return NULL;
+}
+
 #define RETRIES 1000
 
 /* Allocate space for a dll struct. */
@@ -180,10 +264,9 @@ dll *
 dll_list::alloc (HINSTANCE h, per_process *p, dll_type type)
 {
   /* Called under loader lock conditions so this function can't be called
-     multiple times in parallel.  A static buffer is safe. */
-  static WCHAR buf[NT_MAX_PATH];
-  GetModuleFileNameW (h, buf, NT_MAX_PATH);
-  PWCHAR name = buf;
+     multiple times in parallel.  The static buffer is safe. */
+  PWCHAR name = dll::nt_max_path_buf ();
+  GetModuleFileNameW (h, name, NT_MAX_PATH);
   if (!wcsncmp (name, L"\\\\?\\", 4))
     {
       name += 4;
@@ -199,8 +282,12 @@ dll_list::alloc (HINSTANCE h, per_process *p, dll_type type)
   guard (true);
   /* Already loaded?  For linked DLLs, only compare the basenames.  Linked
      DLLs are loaded using just the basename and the default DLL search path.
-     The Windows loader picks up the first one it finds.  */
-  dll *d = (type == DLL_LINK) ? dlls.find_by_modname (modname) : dlls[name];
+     The Windows loader picks up the first one it finds.
+     This also applies to cygwin1.dll and the main-executable (DLL_SELF).
+     When in_load_after_fork, dynamically loaded dll's are reloaded
+     using their parent's forkable_name, if available. */
+  dll *d = (type <= DLL_LINK) ? dlls.find_by_modname (modname) :
+	   in_load_after_fork ? dlls.find_by_forkedname (name) : dlls[name];
   if (d)
     {
       if (!in_forkee)
@@ -227,8 +314,11 @@ dll_list::alloc (HINSTANCE h, per_process *p, dll_type type)
     }
   else
     {
+      size_t forknamesize = forkable_namesize (type, name, modname);
+
       /* FIXME: Change this to new at some point. */
-      d = (dll *) cmalloc (HEAP_2_DLL, sizeof (*d) + (namelen * sizeof (*name)));
+      d = (dll *) cmalloc (HEAP_2_DLL, sizeof (*d) +
+	    ((namelen + 1 + forknamesize) * sizeof (*name)));
 
       /* Now we've allocated a block of information.  Fill it in with the
 	 supplied info about this DLL. */
@@ -236,13 +326,22 @@ dll_list::alloc (HINSTANCE h, per_process *p, dll_type type)
       wcscpy (d->name, name);
       d->modname = d->name + (modname - name);
       d->handle = h;
-      d->has_dtors = true;
+      d->has_dtors = type > DLL_SELF;
       d->p = p;
       d->ndeps = 0;
       d->deps = NULL;
       d->image_size = ((pefile*)h)->optional_hdr ()->SizeOfImage;
       d->preferred_base = (void*) ((pefile*)h)->optional_hdr()->ImageBase;
       d->type = type;
+      d->forkable_name = d->name + namelen + 1;
+      *d->forkable_name = L'\0';
+      d->bhfi.dwFileAttributes = INVALID_FILE_ATTRIBUTES;
+      PWCHAR ntname = dll::to_ntname (&name, dll::nt_max_path_buf ());
+      NTSTATUS status;
+      d->fhandle = dll::ntopenfile (ntname, &status);
+      if (!d->fhandle)
+	system_printf ("Unable (ntstatus %y) to open file %W",
+		       status, ntname);
       append (d);
       if (type == DLL_LOAD)
 	loaded_dlls++;
@@ -396,7 +495,7 @@ dll_list::detach (void *retaddr)
   if (!myself || in_forkee)
     return;
   guard (true);
-  if ((d = find (retaddr)))
+  if ((d = find (retaddr)) && d->type != DLL_SELF)
     {
       if (d->count <= 0)
 	system_printf ("WARNING: trying to detach an already detached dll ...");
@@ -408,6 +507,9 @@ dll_list::detach (void *retaddr)
 	  if (!exit_state)
 	    __cxa_finalize (d->handle);
 	  d->run_dtors ();
+	  if (d->fhandle)
+	    NtClose (d->fhandle);
+	  d->fhandle = NULL;
 	  d->prev->next = d->next;
 	  if (d->next)
 	    d->next->prev = d->prev;
@@ -425,11 +527,22 @@ dll_list::detach (void *retaddr)
 void
 dll_list::init ()
 {
+  track_self ();
+
   /* Walk the dll chain, initializing each dll */
   dll *d = &start;
   dll_global_dtors_recorded = d->next != NULL;
   while ((d = d->next))
-    d->init ();
+    if (d->type > DLL_SELF) /* linked and early loaded dlls */
+      d->init ();
+}
+
+void
+dll_list::track_self ()
+{
+  /* for cygwin1.dll and main-executable: maintain hardlinks only */
+  alloc (cygwin_hmodule, user_data, DLL_SELF);
+  main_executable = alloc (GetModuleHandle (NULL), user_data, DLL_SELF);
 }
 
 #define A64K (64 * 1024)
@@ -494,15 +607,9 @@ dll_list::reserve_space ()
 	      d->modname, d->handle);
 }
 
-/* We need the in_load_after_fork flag so dll_dllcrt0_1 can decide at fork
-   time if this is a linked DLL or a dynamically loaded DLL.  In either case,
-   both, cygwin_finished_initializing and in_forkee are true, so they are not
-   sufficient to discern the situation. */
-static bool NO_COPY in_load_after_fork;
-
 /* 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. */
+   parent.  Updates main-executable and cygwin1.dll tracking. */
 void
 dll_list::load_after_fork (HANDLE parent)
 {
@@ -511,6 +618,8 @@ dll_list::load_after_fork (HANDLE parent)
 
   in_load_after_fork = true;
   load_after_fork_impl (parent, dlls.istart (DLL_LOAD), 0);
+  track_self ();
+  release_forkables ();
   in_load_after_fork = false;
 }
 
@@ -544,18 +653,19 @@ void dll_list::load_after_fork_impl (HANDLE parent, dll* d, int retries)
 	   dll's protective reservation from step 1
 	 */
 	if (!retries && !VirtualFree (d->handle, 0, MEM_RELEASE))
-	  fabort ("unable to release protective reservation for %W (%p), %E",
-		  d->modname, d->handle);
+	  fabort ("unable to release protective reservation (%p) for %W (using %W), %E",
+		  d->handle, d->name, d->forkedname ());
 
-	HMODULE h = LoadLibraryExW (d->name, NULL, DONT_RESOLVE_DLL_REFERENCES);
+	HMODULE h = LoadLibraryExW (d->forkedname (), NULL, DONT_RESOLVE_DLL_REFERENCES);
 	if (!h)
-	  fabort ("unable to create interim mapping for %W, %E", d->name);
+	  fabort ("unable to create interim mapping for %W (using %W), %E",
+		  d->name, d->forkedname ());
 	if (h != d->handle)
 	  {
-	    sigproc_printf ("%W loaded in wrong place: %p != %p",
-			    d->modname, h, d->handle);
+	    sigproc_printf ("%W (using %W) loaded in wrong place: %p != %p",
+			    d->name, d->forkedname (), h, d->handle);
 	    FreeLibrary (h);
-	    PVOID reservation = reserve_at (d->modname, h,
+	    PVOID reservation = reserve_at (d->forkedname (), h,
 					    d->handle, d->image_size);
 	    if (!reservation)
 	      fabort ("unable to block off %p to prevent %W from loading there",
@@ -565,12 +675,12 @@ void dll_list::load_after_fork_impl (HANDLE parent, dll* d, int retries)
 	      load_after_fork_impl (parent, d, retries+1);
 	    else
 	       fabort ("unable to remap %W to same address as parent (%p) - try running rebaseall",
-		       d->modname, d->handle);
+		       d->forkedname (), d->handle);
 
 	    /* once the above returns all the dlls are mapped; release
 	       the reservation and continue unwinding */
 	    sigproc_printf ("releasing blocked space at %p", reservation);
-	    release_at (d->modname, reservation);
+	    release_at (d->forkedname (), reservation);
 	    return;
 	  }
       }
@@ -586,22 +696,22 @@ void dll_list::load_after_fork_impl (HANDLE parent, dll* d, int retries)
 	{
 	  if (!VirtualFree (d->handle, 0, MEM_RELEASE))
 	    fabort ("unable to release protective reservation for %W (%p), %E",
-		    d->modname, d->handle);
+		    d->forkedname (), d->handle);
 	}
       else
 	{
 	  /* Free the library using our parent's handle: it's identical
 	     to ours or we wouldn't have gotten this far */
 	  if (!FreeLibrary (d->handle))
-	    fabort ("unable to unload interim mapping of %W, %E",
-		    d->modname);
+	    fabort ("unable to unload interim mapping of %W (using %W), %E",
+		    d->name, d->forkedname ());
 	}
-      HMODULE h = LoadLibraryW (d->name);
+      HMODULE h = LoadLibraryW (d->forkedname ());
       if (!h)
-	fabort ("unable to map %W, %E", d->name);
+	fabort ("unable to map %W (using %W), %E", d->name, d->forkedname ());
       if (h != d->handle)
-	fabort ("unable to map %W to same address as parent: %p != %p",
-		d->modname, d->handle, h);
+	fabort ("unable to map %W (using %W) to same address as parent: %p != %p",
+		d->name, d->forkedname (), d->handle, h);
     }
 }
 
diff --git a/winsup/cygwin/dll_init.h b/winsup/cygwin/dll_init.h
index 8127d0b..ac6ee6b 100644
--- a/winsup/cygwin/dll_init.h
+++ b/winsup/cygwin/dll_init.h
@@ -43,6 +43,7 @@ struct per_module
 typedef enum
 {
   DLL_NONE,
+  DLL_SELF, /* main-program.exe, cygwin1.dll */
   DLL_LINK,
   DLL_LOAD,
   DLL_ANY
@@ -61,7 +62,12 @@ struct dll
   DWORD image_size;
   void* preferred_base;
   PWCHAR modname;
-  WCHAR name[1];
+  /* forkable { */
+  HANDLE fhandle;
+  BY_HANDLE_FILE_INFORMATION bhfi;
+  PWCHAR forkable_name;
+  /* } forkable */
+  WCHAR name[1]; /* must be the last data member */
   void detach ();
   int init ();
   void run_dtors ()
@@ -72,17 +78,74 @@ struct dll
 	p.run_dtors ();
       }
   }
+  /* forkable { */
+  void nominate_forkable (PCWCHAR);
+  bool create_forkable ();
+  PWCHAR forkedname ()
+  {
+    return forkable_name && *forkable_name ? forkable_name : name;
+  }
+
+  /* various helpers */
+  static PWCHAR nt_max_path_buf ();
+  static PWCHAR to_ntname (PWCHAR *nameptr, WCHAR ntnamebuf[NT_MAX_PATH]);
+  static HANDLE ntopenfile (PWCHAR ntname, NTSTATUS *pstatus = NULL,
+			    ULONG openopts = FILE_NON_DIRECTORY_FILE);
+  /* } forkable */
 };
 
 #define MAX_DLL_BEFORE_INIT     100
 
 class dll_list
 {
+  /* forkables { */
+  enum
+    {
+      forkables_unknown,
+      forkables_impossible,
+      forkables_disabled,
+      forkables_needless,
+      forkables_needed,
+      forkables_created,
+    }
+    forkables_needs;
+
+  DWORD forkables_dirx_size;
+  PWCHAR forkables_dirx_name;
+  PWCHAR forkables_mutex_name;
+  HANDLE forkables_mutex;
+
+  void track_self ();
+  dll *find_by_forkedname (PCWCHAR name);
+
+  size_t forkable_namesize (dll_type, PCWCHAR fullname, PCWCHAR modname);
+  void prepare_forkables_nomination ();
+  void update_forkables_needs ();
+  bool update_forkables ();
+  bool create_forkables ();
+  void denominate_forkables ();
+  void set_forkables_inheritance (bool);
+  /* } forkables */
+
   dll *end;
   dll *hold;
   dll_type hold_type;
   static muto protect;
 public:
+  /* helpers to nominate forkables { */
+  static int rootname (PWCHAR);
+  static int sidname (PWCHAR);
+  static int exename (PWCHAR);
+  static int ctimename (PWCHAR);
+  /* } helpers to nominate forkables */
+
+  /* forkables { */
+  dll *main_executable;
+  void request_forkables ();
+  void release_forkables ();
+  void cleanup_forkables ();
+  /* } forkables */
+
   dll start;
   int loaded_dlls;
   int reload_on_fork;
diff --git a/winsup/cygwin/fork.cc b/winsup/cygwin/fork.cc
index 084f8f0..dd0f9a6 100644
--- a/winsup/cygwin/fork.cc
+++ b/winsup/cygwin/fork.cc
@@ -188,21 +188,18 @@ frok::child (volatile char * volatile here)
 
   MALLOC_CHECK;
 
-  /* If we haven't dynamically loaded any dlls, just signal
-     the parent.  Otherwise, load all the dlls, tell the parent
-      that we're done, and wait for the parent to fill in the.
-      loaded dlls' data/bss. */
+  /* load dynamic dlls, if any, re-track main-executable and cygwin1.dll */
+  dlls.load_after_fork (hParent);
+
+  cygheap->fdtab.fixup_after_fork (hParent);
+
+  /* If we haven't dynamically loaded any dlls, just signal the parent.
+     Otherwise, tell the parent that we've loaded all the dlls
+     and wait for the parent to fill in the loaded dlls' data/bss. */
   if (!load_dlls)
-    {
-      cygheap->fdtab.fixup_after_fork (hParent);
-      sync_with_parent ("performed fork fixup", false);
-    }
+    sync_with_parent ("performed fork fixup", false);
   else
-    {
-      dlls.load_after_fork (hParent);
-      cygheap->fdtab.fixup_after_fork (hParent);
-      sync_with_parent ("loaded dlls", true);
-    }
+    sync_with_parent ("loaded dlls", true);
 
   init_console_handler (myself->ctty > 0);
   ForceCloseHandle1 (fork_info->forker_finished, forker_finished);
@@ -344,8 +341,6 @@ frok::parent (volatile char * volatile stack_here)
   si.lpReserved2 = (LPBYTE) &ch;
   si.cbReserved2 = sizeof (ch);
 
-  syscall_printf ("CreateProcessW (%W, %W, 0, 0, 1, %y, 0, 0, %p, %p)",
-		  myself->progname, myself->progname, c_flags, &si, &pi);
   bool locked = __malloc_lock ();
 
   /* Remove impersonation */
@@ -356,8 +351,19 @@ frok::parent (volatile char * volatile stack_here)
 
   while (1)
     {
+      dlls.request_forkables ();
+
+      PWCHAR forking_progname = NULL;
+      if (dlls.main_executable)
+        forking_progname = dlls.main_executable->forkedname();
+      if (!forking_progname || !*forking_progname)
+	forking_progname = myself->progname;
+
+      debug_printf ("CreateProcessW (%W, %W, 0, 0, 1, %y, 0, 0, %p, %p)",
+		    forking_progname, myself->progname, c_flags, &si, &pi);
+
       hchild = NULL;
-      rc = CreateProcessW (myself->progname,	/* image to run */
+      rc = CreateProcessW (forking_progname,	/* image to run */
 			   GetCommandLineW (),	/* Take same space for command
 						   line as in parent to make
 						   sure child stack is allocated
@@ -377,7 +383,8 @@ frok::parent (volatile char * volatile stack_here)
       else
 	{
 	  this_errno = geterrno_from_win_error ();
-	  error ("CreateProcessW failed for '%W'", myself->progname);
+	  error ("CreateProcessW failed for '%W' (using '%W')", myself->progname, forking_progname);
+	  dlls.release_forkables ();
 	  memset (&pi, 0, sizeof (pi));
 	  goto cleanup;
 	}
@@ -391,6 +398,8 @@ frok::parent (volatile char * volatile stack_here)
       CloseHandle (pi.hThread);
       hchild = pi.hProcess;
 
+      dlls.release_forkables ();
+
       /* Protect the handle but name it similarly to the way it will
 	 be called in subproc handling. */
       ProtectHandle1 (hchild, childhProc);
diff --git a/winsup/cygwin/forkable.cc b/winsup/cygwin/forkable.cc
new file mode 100644
index 0000000..b2a841a
--- /dev/null
+++ b/winsup/cygwin/forkable.cc
@@ -0,0 +1,987 @@
+/* forkable.cc
+
+   Copyright 2015 Red Hat, Inc.
+
+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"
+#include "cygerrno.h"
+#include "perprocess.h"
+#include "sync.h"
+#include "dll_init.h"
+#include "environ.h"
+#include "security.h"
+#include "path.h"
+#include "fhandler.h"
+#include "dtable.h"
+#include "cygheap.h"
+#include "pinfo.h"
+#include "child_info.h"
+#include "cygtls.h"
+#include "exception.h"
+#include <wchar.h>
+#include <sys/reent.h>
+#include <assert.h>
+#include <tls_pbuf.h>
+
+/* Allow concurrent processes to use the same dll or exe
+ * via their hardlink while we delete our hardlink. */
+extern NTSTATUS unlink_nt_shareable (path_conv &pc);
+
+#define MUTEXSEP L"@"
+#define PATHSEP L"\\"
+
+struct namepart {
+  PCWCHAR text; /* used when no pathfunc, description otherwise */
+  int (*textfunc)(PWCHAR buf);
+  bool mutex_from_dir; /* on path-separators add mutex-separator */
+  bool create_dir;
+};
+/* mutex name is formed along dir names */
+static namepart NO_COPY_RO const forkable_nameparts[] = {
+ /* text                       textfunc  mutex_from_dir  create */
+  { L"<cygroot>",    dll_list::rootname,          false, false, },
+  { L"\\var\\run\\",               NULL,          false, false, },
+  { L"cygfork",                    NULL,          true,  false, },
+  { L"<sid>",         dll_list::sidname,          true,  true,  },
+  { L"<exe>",         dll_list::exename,          false, false, },
+  { MUTEXSEP,                      NULL,          false, false, },
+  { L"<ctime>",     dll_list::ctimename,          true,  true,  },
+
+  { NULL, NULL },
+};
+
+/* Mangle full srcpath as one single filename into targetbuf,
+   optionally telling the last path separator to allow for
+   restoring the file's basename.
+   Return value is the number of characters mangled. */
+static int
+mangle_as_filename (PWCHAR targetbuf, PCWCHAR srcpath, PWCHAR *lastpathsep)
+{
+  PWCHAR target = targetbuf;
+  if (lastpathsep)
+    *lastpathsep = NULL;
+
+  for (; *srcpath; ++srcpath)
+    switch (*srcpath)
+      {
+	case L'\\':
+	  if (lastpathsep)
+	    *lastpathsep = target;
+	  *(target++) = L',';
+	  break;
+	case L'?':
+	case L':':
+	  *(target++) = L'_';
+	  break;
+	default:
+	  *(target++) = *srcpath;
+	  break;
+      }
+  return target - targetbuf;
+}
+
+/* Create the lastsepcount directories found in dirname, where
+   counting is done along path separators (including trailing ones).
+   Returns true when these directories exist afterwards, false otherways.
+   The dirname is used for the path-splitting. */
+static
+bool mkdirs (PWCHAR dirname, SECURITY_ATTRIBUTES *psec, int lastsepcount)
+{
+  bool success = true;
+  int i = lastsepcount;
+  for (--i; i > 0; --i)
+    {
+      PWCHAR lastsep = wcsrchr (dirname, L'\\');
+      if (!lastsep)
+        break;
+      *lastsep = L'\0';
+    }
+
+  for (++i; i <= lastsepcount; ++i)
+    {
+      if (success && (i == 0 || wcslen (wcsrchr (dirname, L'\\')) > 1))
+	{
+	  BOOL ret = CreateDirectoryW (dirname, psec);
+	  if (!ret && GetLastError () != ERROR_ALREADY_EXISTS)
+	    success = false;
+	  debug_printf ("%d = CreateDirectoryW (%W) %E", ret, dirname);
+	}
+      if (i < lastsepcount)
+	dirname[wcslen (dirname)] = L'\\'; /* restore original value */
+    }
+  return success;
+}
+
+/* Recursively remove the directory specified in ntmaxpathbuf,
+   using ntmaxpathbuf as the buffer to form subsequent filenames. */
+static void
+rmdirs (WCHAR ntmaxpathbuf[NT_MAX_PATH])
+{
+  PWCHAR basebuf = wcsrchr (ntmaxpathbuf, L'\\'); /* find last pathsep */
+  if (basebuf && *(basebuf+1))
+    basebuf += wcslen (basebuf); /* last pathsep is not trailing one */
+  if (!basebuf)
+    basebuf = ntmaxpathbuf + wcslen (ntmaxpathbuf);
+  *basebuf = L'\0'; /* kill trailing pathsep, if any */
+
+  NTSTATUS status;
+  HANDLE hdir = dll::ntopenfile (ntmaxpathbuf, &status,
+				 FILE_DIRECTORY_FILE | FILE_DELETE_ON_CLOSE);
+  if (!hdir)
+    return;
+
+  *basebuf++ = L'\\'; /* (re-)add trailing pathsep */
+
+  struct {
+    FILE_DIRECTORY_INFORMATION fdi;
+    WCHAR buf[NAME_MAX];
+  } fdibuf;
+  IO_STATUS_BLOCK iosb;
+
+  while (NT_SUCCESS (status = NtQueryDirectoryFile (hdir, NULL, NULL, NULL,
+						    &iosb,
+						    &fdibuf, sizeof (fdibuf),
+						    FileDirectoryInformation,
+						    FALSE, NULL, FALSE)))
+    {
+      PFILE_DIRECTORY_INFORMATION pfdi = &fdibuf.fdi;
+      while (true)
+        {
+	  int namelen = pfdi->FileNameLength / sizeof (WCHAR);
+	  wcsncpy (basebuf, pfdi->FileName, namelen);
+	  basebuf[namelen] = L'\0';
+
+	  if (pfdi->FileAttributes & FILE_ATTRIBUTE_DIRECTORY)
+	    {
+	      if (wcscmp (basebuf, L".") && wcscmp (basebuf, L".."))
+		rmdirs (ntmaxpathbuf);
+	    }
+	  else
+	    {
+	      UNICODE_STRING fn;
+	      RtlInitUnicodeString (&fn, ntmaxpathbuf);
+
+	      path_conv pc (&fn);
+	      unlink_nt_shareable (pc); /* move to bin */
+	    }
+
+	  if (!pfdi->NextEntryOffset)
+	    break;
+	  pfdi = (PFILE_DIRECTORY_INFORMATION)((caddr_t)pfdi
+					       + pfdi->NextEntryOffset);
+	}
+    }
+  if (status != STATUS_NO_MORE_FILES)
+    debug_printf ("%y = NtQueryDirectoryFile (%p, io %y, info %d)",
+		  status, hdir, iosb.Status, iosb.Information);
+
+  CloseHandle (hdir);
+}
+
+/* Nominate the hardlink to an individual DLL inside dirx_name,
+   that ends with the path separator (hence the "x" varname).
+   With NULL as dirx_name, never nominate the hardlink any more.
+   With "" as dirx_name, denominate the hardlink. */
+void
+dll::nominate_forkable (PCWCHAR dirx_name)
+{
+  if (!dirx_name)
+    {
+      debug_printf ("type %d disable %W", type, name);
+      forkable_name = NULL; /* never create a hardlink for this dll */
+    }
+
+  if (!forkable_name)
+    return;
+
+  wcscpy (forkable_name, dirx_name);
+
+  if (!*forkable_name)
+    return; /* denominate */
+
+  if (type < DLL_LOAD)
+    wcscat (forkable_name, modname);
+  else
+    {
+      /* Avoid lots of extra directories for loaded dll's:
+       * mangle full path into one single directory name,
+       * just keep original filename intact. The original
+       * filename is necessary to serve as linked
+       * dependencies of dynamically loaded dlls. */
+      PWCHAR lastpathsep;
+      mangle_as_filename (forkable_name + wcslen (forkable_name), name,
+			  &lastpathsep);
+      if (lastpathsep)
+	*lastpathsep = L'\\';
+    }
+}
+
+/* Create the nominated hardlink for one indivitual dll,
+   inside another subdirectory when dynamically loaded. */
+bool
+dll::create_forkable ()
+{
+  if (!forkable_name || !*forkable_name)
+    return true; /* disabled */
+
+  if (!fhandle)
+    return false;
+
+  PWCHAR last = NULL;
+  bool success = true;
+  if (type >= DLL_LOAD)
+    {
+      last = wcsrchr (forkable_name, L'\\');
+      if (!last)
+        return false;
+      *last = L'\0';
+      success = mkdirs (forkable_name, &sec_none_nih, 1);
+      *last = L'\\';
+      if (!success)
+        return false;
+    }
+ 
+  PWCHAR name = forkable_name;
+  PWCHAR ntname = to_ntname (&name, nt_max_path_buf ());
+
+  int ntlen = wcslen (ntname);
+  int bufsize = sizeof (FILE_LINK_INFORMATION) + ntlen * sizeof (*ntname);
+  PFILE_LINK_INFORMATION pfli = (PFILE_LINK_INFORMATION) alloca (bufsize);
+
+  wcscpy (pfli->FileName, ntname);
+
+  pfli->FileNameLength = ntlen * sizeof (*ntname);
+  pfli->ReplaceIfExists = FALSE;
+  pfli->RootDirectory = NULL;
+
+  IO_STATUS_BLOCK iosb;
+  NTSTATUS status = NtSetInformationFile (fhandle, &iosb, pfli, bufsize,
+					  FileLinkInformation);
+  debug_printf ("%y = NtSetInformationFile (%p, FileLink %W, iosb.Status %y)",
+		status, fhandle, pfli->FileName, iosb.Status);
+  if (NT_SUCCESS (status) || status == STATUS_OBJECT_NAME_COLLISION)
+    /* We've not found a performant way yet to protect fork against updates
+       to main executables and/or dlls that do not reside on the same NTFS
+       filesystem as the <cygroot>/var/run/cygfork/ directory.
+       But as long as the main executable can be hardlinked, dll redirection
+       works for any other hardlink-able dll, while non-hardlink-able dlls
+       are used from their original location. */
+    return true;
+
+  return false;
+}
+
+/* Into buf if not NULL, write the ntname of cygwin installation_root.
+   Return the number of characters (that would be) written. */
+int
+dll_list::rootname (PWCHAR buf)
+{
+  PWCHAR cygroot = cygheap->installation_root;
+  if (!buf)
+    return wcslen (cygroot);
+
+  dll::to_ntname (&cygroot, dll::nt_max_path_buf ());
+  wcscpy (buf, cygroot);
+  return wcslen (buf);
+}
+
+/* Into buf if not NULL, write the string representation of current user sid.
+   Return the number of characters (that would be) written. */
+int
+dll_list::sidname (PWCHAR buf)
+{
+  if (!buf)
+    return 128;
+
+  UNICODE_STRING sid;
+  WCHAR sidbuf[128+1];
+  RtlInitEmptyUnicodeString (&sid, sidbuf, sizeof (sidbuf));
+  RtlConvertSidToUnicodeString (&sid, cygheap->user.sid (), FALSE);
+  wcscpy (buf, sid.Buffer);
+  return wcslen (buf);
+}
+
+/* Into buf if not NULL, write the mangled full path of the main executable.
+   Return the number of characters (that would be) written. */
+int
+dll_list::exename (PWCHAR buf)
+{
+  if (!buf)
+    return wcslen (global_progname);
+
+  return mangle_as_filename (buf, dlls.main_executable->name, NULL);
+}
+
+/* Into buf if not NULL, write the string representation of most recent dll.
+   Return the number of characters (that would be) written. */
+int
+dll_list::ctimename (PWCHAR buf)
+{
+  if (!buf)
+    return 16;
+
+  FILETIME newest = { 0 };
+  /* Need by-handle-file-information for _all_ loaded dlls,
+     as most recent ctime forms the hardlinks directory. */
+  dll *d = &dlls.start;
+  while ((d = d->next))
+    {
+      if (!d->fhandle)
+        continue;
+      if (d->bhfi.dwFileAttributes == INVALID_FILE_ATTRIBUTES)
+	{
+	  /* Relevant BY_HANDLE_FILE_INFORMATION members are
+	     valid in child when cygheap-copied from parent,
+	     so caching them is fine. */
+	  BOOL bret = GetFileInformationByHandle (d->fhandle, &d->bhfi);
+	  if (!bret)
+	    system_printf ("WARNING: %d = GetFileInfoByHandle (%p, %W) %E",
+			   bret, d->fhandle, d->name);
+	}
+
+      if (d->bhfi.ftLastWriteTime.dwHighDateTime > newest.dwHighDateTime ||
+	  (d->bhfi.ftLastWriteTime.dwHighDateTime == newest.dwHighDateTime &&
+	   d->bhfi.ftLastWriteTime.dwLowDateTime > newest.dwLowDateTime))
+	newest = d->bhfi.ftLastWriteTime;
+    }
+
+  return __small_swprintf (buf, L"%08x%08x",
+			   newest.dwHighDateTime, newest.dwLowDateTime);
+}
+
+/* return the number of characters necessary to store one forkable name */
+size_t
+dll_list::forkable_namesize (dll_type type, PCWCHAR fullname, PCWCHAR modname)
+{
+  if (!forkables_dirx_size)
+    {
+      DWORD forkables_mutex_size = 0;
+      bool needsep = false;
+      for (namepart const *part = forkable_nameparts; part->text; ++part)
+	{
+	  if (needsep)
+	    {
+	      forkables_dirx_size += wcslen (PATHSEP);
+	      forkables_mutex_size += wcslen (MUTEXSEP);
+	    }
+	  needsep = part->mutex_from_dir;
+	  int len = 0;
+	  if (part->textfunc)
+	    len = part->textfunc (NULL);
+	  else
+	    len = wcslen (part->text);
+	  forkables_dirx_size += len;
+	  forkables_mutex_size += len;
+	}
+      /* trailing path sep */
+      forkables_dirx_size += wcslen (PATHSEP);
+      /* trailing zeros */
+      ++forkables_dirx_size;
+      ++forkables_mutex_size;
+
+      /* allocate here, to avoid cygheap size changes during fork */
+      forkables_dirx_name = (PWCHAR) cmalloc (HEAP_2_DLL,
+	  (forkables_dirx_size + forkables_mutex_size) *
+	    sizeof (*forkables_dirx_name));
+      *forkables_dirx_name = L'\0';
+
+      forkables_mutex_name = forkables_dirx_name + forkables_dirx_size;
+      *forkables_mutex_name = L'\0';
+    }
+
+  if (type < DLL_LOAD)
+    return forkables_dirx_size + wcslen (modname);
+  else
+    return forkables_dirx_size + wcslen (fullname);
+}
+
+/* Prepare top-level names necessary to nominate individual DLL hardlinks,
+   eventually releasing/removing previous forkable hardlinks. */
+void
+dll_list::prepare_forkables_nomination ()
+{
+  PWCHAR pbuf;
+
+  pbuf = dll::nt_max_path_buf ();
+  bool needsep = false;
+  bool domutex = false;
+  for (namepart const *part = forkable_nameparts; part->text; ++part)
+    {
+      if (part->mutex_from_dir)
+        domutex = true; /* mutex naming starts with first mutex_from_dir */
+      if (!domutex)
+        continue;
+      if (needsep)
+	pbuf += __small_swprintf (pbuf, L"%W", MUTEXSEP);
+      needsep = part->mutex_from_dir;
+      if (part->textfunc)
+	pbuf += part->textfunc (pbuf);
+      else
+	pbuf += __small_swprintf (pbuf, L"%W", part->text);
+    }
+
+  if (*forkables_mutex_name &&
+      wcscmp (forkables_mutex_name, dll::nt_max_path_buf ()))
+    {
+      /* mutex name has changed since last fork: We either have
+         dlopen'ed a more recent or dlclose'd the most recent dll,
+	 so we will not use the current forkable hardlinks any more. */
+      cleanup_forkables ();
+    }
+  wcscpy (forkables_mutex_name, dll::nt_max_path_buf ());
+
+  pbuf = forkables_dirx_name;
+  needsep = false;
+  for (namepart const *part = forkable_nameparts; part->text; ++part)
+    {
+      if (needsep)
+	pbuf += __small_swprintf (pbuf, L"%W", PATHSEP);
+      needsep = part->mutex_from_dir;
+      if (part->textfunc)
+	pbuf += part->textfunc (pbuf);
+      else
+	pbuf += __small_swprintf (pbuf, L"%W", part->text);
+    }
+  pbuf += __small_swprintf (pbuf, L"%W", PATHSEP);
+
+  debug_printf ("forkables dir %W", forkables_dirx_name);
+  debug_printf ("forkables mutex %W", forkables_mutex_name);
+}
+
+/* Test if creating hardlinks is necessary. If creating hardlinks is possible
+   in general, each individual dll is tested if its previously created
+   hardlink (if any, or the original file) still is the same.
+   Testing is protected against hardlink removal by concurrent processes. */
+void
+dll_list::update_forkables_needs ()
+{
+  dll *d;
+  PWCHAR maxpathbuf = dll::nt_max_path_buf ();
+
+  if (forkables_needs == forkables_unknown)
+    {
+      /* check if filesystem of forkables dir is NTFS */
+      PWCHAR pbuf = maxpathbuf;
+      for (namepart const *part = forkable_nameparts; part->text; ++part)
+	{
+	  if (part->mutex_from_dir)
+	    break; /* leading non-mutex-naming dirs, must exist anyway */
+	  if (part->textfunc)
+	    pbuf += part->textfunc (pbuf);
+	  else
+	    pbuf += __small_swprintf (pbuf, L"%W", part->text);
+	}
+
+      pbuf = maxpathbuf;
+
+      UNICODE_STRING fn;
+      RtlInitUnicodeString (&fn, dll::to_ntname (&pbuf, maxpathbuf));
+
+      fs_info fsi;
+      if (fsi.update (&fn, NULL) &&
+/* FIXME: !fsi.is_readonly () && */
+          fsi.is_ntfs ())
+	forkables_needs = forkables_disabled;
+      else
+	{
+	  debug_printf ("impossible, not on NTFS %W", pbuf);
+	  forkables_needs = forkables_impossible;
+	}
+    }
+
+  if (forkables_needs == forkables_impossible)
+    return; /* no need to check anything else */
+
+  if (forkables_needs == forkables_disabled ||
+      forkables_needs == forkables_needless ||
+      forkables_needs == forkables_created)
+    {
+      /* (re-)check existence of forkables dir */
+      PWCHAR pbuf = maxpathbuf;
+      for (namepart const *part = forkable_nameparts; part->text; ++part)
+	{
+	  if (part->textfunc)
+	    pbuf += part->textfunc (pbuf);
+	  else
+	    pbuf += __small_swprintf (pbuf, L"%W", part->text);
+	  if (part->mutex_from_dir)
+	    break; /* up to first mutex-naming dir */
+	}
+
+      pbuf = maxpathbuf;
+      PWCHAR ntname = dll::to_ntname (&pbuf, maxpathbuf);
+      HANDLE dh = dll::ntopenfile (ntname, NULL, FILE_DIRECTORY_FILE);
+      if (dh)
+	{
+	  NtClose (dh);
+	  forkables_needs = forkables_needless;
+	}
+      else if (forkables_needs != forkables_disabled)
+	{
+	  debug_printf ("disabled, disappearing %W", pbuf);
+	  cleanup_forkables ();
+	  forkables_needs = forkables_disabled;
+	}
+      else
+	debug_printf ("disabled, missing %W", pbuf);
+    }
+
+  if (forkables_needs == forkables_disabled)
+    return;
+
+  if (forkables_needs == forkables_created)
+    {
+      /* already have created hardlinks in this process, ... */
+      forkables_needs = forkables_needless;
+      d = &start;
+      while ((d = d->next) != NULL)
+	if (d->forkable_name && !*d->forkable_name)
+	  {
+	    /* ... but another dll was loaded since last fork */
+	    debug_printf ("needed, since last fork loaded %W", d->name);
+	    forkables_needs = forkables_needed;
+	    break;
+	  }
+    }
+
+  if (forkables_needs > forkables_needless)
+    return; /* no need to check anything else */
+
+  if (forkables_needs != forkables_needless)
+    {
+      /* paranoia */
+      system_printf ("WARNING: invalid forkables_needs value %d",
+		     forkables_needs);
+      return;
+    }
+
+  if (forkables_mutex)
+    {
+      /* We have the mutex already, so we are checking hardlinks now:
+	 Make sure another process is not about to cleanup_forkables (),
+	 which operates only when the mutex was nonexistent.
+	 We don't care if another process is about to update_forkables (),
+	 as we will either detect hardlinks as sufficient already,
+	 or we will run update_forkables () ourselves too. */
+      debug_printf ("WFSO (%p, %W, inf)...",
+		    forkables_mutex, forkables_mutex_name);
+      DWORD ret = WaitForSingleObject (forkables_mutex, INFINITE);
+      debug_printf ("%u = WFSO (%p, %W, inf)",
+		    ret, forkables_mutex, forkables_mutex_name);
+      switch (ret)
+	{
+	case WAIT_OBJECT_0:
+	case WAIT_ABANDONED:
+	  break;
+	default:
+	  return;
+	}
+      BOOL bret = ReleaseMutex (forkables_mutex);
+      debug_printf ("%d = ReleaseMutex (%p, %W): %E",
+		    bret, forkables_mutex, forkables_mutex_name);
+    }
+
+  /* We have to check the main-executable and all dlls loaded so far via
+     their forked (if available, or their original) filename, if they are
+     still available for loading again. */
+  HANDLE fh = NULL;
+  d = &start;
+  while ((d = d->next) != NULL)
+    {
+      if (fh)
+	{
+	  NtClose (fh);
+	  fh = NULL;
+	}
+
+      PWCHAR name = d->forkedname ();
+      PWCHAR ntname = dll::to_ntname (&name, maxpathbuf);
+      fh = dll::ntopenfile (ntname);
+      if (!fh)
+	{
+	  debug_printf ("needed, something wrong with %W", ntname);
+	  forkables_needs = forkables_needed;
+	  break;
+	}
+
+      BY_HANDLE_FILE_INFORMATION bhfi_now;
+      BOOL bret = GetFileInformationByHandle (fh, &bhfi_now);
+      if (!bret)
+	{
+	  system_printf ("WARNING: %d = GetFileInfoByHandle (%p, %W) %E",
+			 bret, fh, name);
+	  forkables_needs = forkables_needed;
+	  break;
+	}
+
+#define bhfi_diff(member) (bhfi_now.member != d->bhfi.member)
+      if (bhfi_diff (dwVolumeSerialNumber) ||
+	  bhfi_diff (nFileIndexHigh) ||
+	  bhfi_diff (nFileIndexLow))
+	{
+	  debug_printf ("needed, found modified %W", name);
+	  forkables_needs = forkables_needed;
+	  break;
+	}
+
+      if (bhfi_diff (nFileSizeHigh) ||
+	  bhfi_diff (nFileSizeLow) ||
+	  bhfi_diff (ftLastWriteTime.dwHighDateTime) ||
+	  bhfi_diff (ftLastWriteTime.dwLowDateTime))
+	{
+	  /* paranoia? */
+	  system_printf ("WARNING: changed by same file id %W (now size %08x %08x ctime %08x %08x, old size %08x %08x ctime %08x %08x)",
+	      name,
+	      bhfi_now.nFileSizeHigh, bhfi_now.nFileSizeLow, bhfi_now.ftLastWriteTime.dwHighDateTime, bhfi_now.ftLastWriteTime.dwLowDateTime,
+	       d->bhfi.nFileSizeHigh,  d->bhfi.nFileSizeLow,  d->bhfi.ftLastWriteTime.dwHighDateTime,  d->bhfi.ftLastWriteTime.dwLowDateTime);
+	  forkables_needs = forkables_needed;
+	  break;
+	}
+#undef bhfi_diff
+    }
+  if (fh)
+    NtClose (fh);
+
+  if (forkables_needs == forkables_needless && !forkables_mutex)
+    {
+      /* debugging: check for ".needed" file in toplevel forkables dir */
+      PWCHAR pbuf = maxpathbuf;
+      for (namepart const *part = forkable_nameparts; part->text; ++part)
+	{
+	  if (part->textfunc)
+	    pbuf += part->textfunc (pbuf);
+	  else
+	    pbuf += __small_swprintf (pbuf, L"%W", part->text);
+	  if (part->mutex_from_dir)
+	    break; /* up to first mutex-naming dir */
+	}
+      pbuf += __small_swprintf (pbuf, L"%W.needed", PATHSEP);
+
+      pbuf = maxpathbuf;
+      HANDLE dh = dll::ntopenfile (dll::to_ntname (&pbuf, maxpathbuf));
+      if (dh)
+	{
+	  NtClose (dh);
+	  forkables_needs = forkables_needed;
+	  debug_printf ("needed, found %W", pbuf);
+	}
+    }
+}
+
+/* Create the nominated forkable hardlinks and directories as necessary,
+   mutex-protected to avoid concurrent processes removing them. */
+bool
+dll_list::update_forkables ()
+{
+  /* existence of mutex indicates that we will use these hardlinks */
+  if (!forkables_mutex)
+    {
+      /* neither my parent nor myself did have need for hardlinks yet */
+      forkables_mutex = CreateMutexW (&sec_none, FALSE,
+				      forkables_mutex_name);
+      debug_printf ("%p = CreateMutexW (%W): %E",
+		    forkables_mutex, forkables_mutex_name);
+    if (!forkables_mutex)
+      return false;
+    }
+
+  bool success = true;
+
+  /* Make sure another process is not about to cleanup_forkables ()
+     or about to create_forkables () at the same time. */
+  debug_printf ("WFSO (%p, %W, inf)...",
+		forkables_mutex, forkables_mutex_name);
+  DWORD ret = WaitForSingleObject (forkables_mutex, INFINITE);
+  debug_printf ("%u = WFSO (%p, %W)",
+		ret, forkables_mutex, forkables_mutex_name);
+  switch (ret)
+    {
+    case WAIT_OBJECT_0:
+    case WAIT_ABANDONED:
+      break;
+    default:
+      system_printf ("cannot wait for mutex %W: %E",
+		     forkables_mutex_name);
+      return false;
+    }
+
+  if (!create_forkables ())
+    success = false;
+
+  BOOL bret = ReleaseMutex (forkables_mutex);
+  debug_printf ("%d = ReleaseMutex (%p, %W)",
+		bret, forkables_mutex, forkables_mutex_name);
+
+  return success;
+}
+
+/* Create the nominated forkable hardlinks and directories as necessary,
+   as well as the .local file for dll-redirection. */
+bool
+dll_list::create_forkables ()
+{
+  bool success = true;
+
+  int lastsepcount = 1; /* we have trailing pathsep */
+  for (namepart const *part = forkable_nameparts; part->text; ++part)
+    if (part->create_dir)
+      ++lastsepcount;
+
+  PWCHAR buf = dll::nt_max_path_buf ();
+  wcsncpy (buf, forkables_dirx_name, NT_MAX_PATH);
+
+  if (!mkdirs (buf, &sec_none_nih, lastsepcount))
+    success = false;
+
+  if (success)
+    {
+      /* create the DotLocal file as empty file */
+      wcsncat (buf, main_executable->modname, NT_MAX_PATH);
+      wcsncat (buf, L".local", NT_MAX_PATH);
+      HANDLE hlocal = CreateFileW (buf, GENERIC_WRITE,
+				   FILE_SHARE_READ, &sec_none_nih,
+				   OPEN_ALWAYS, 0, NULL);
+      debug_printf ("%p = CreateFileW (%W) %E", hlocal, buf);
+      if (hlocal != INVALID_HANDLE_VALUE)
+	CloseHandle (hlocal);
+      else
+	success = false;
+    }
+
+  if (success)
+    {
+      dll *d = &start;
+      while ((d = d->next))
+	if (!d->create_forkable ())
+	  d->nominate_forkable (NULL); /* never again */
+      debug_printf ("hardlinks created");
+    }
+
+  return success;
+}
+
+static void
+rmdirs_synchronized (WCHAR ntbuf[NT_MAX_PATH], int depth, int maxdepth,
+		     PFILE_DIRECTORY_INFORMATION pfdi, ULONG fdisize)
+{
+  if (depth == maxdepth)
+    {
+      debug_printf ("sync on %W", ntbuf);
+      /* calculate mutex name from path parts, using
+         full path name length to allocate mutex name buffer */
+      WCHAR mutexname[wcslen (ntbuf)];
+      mutexname[0] = L'\0';
+
+      /* mutex name is formed by dir names */
+      int pathcount = 0;
+      for (namepart const *part = forkable_nameparts; part->text; ++part)
+	if (part->mutex_from_dir)
+	  ++pathcount;
+
+      PWCHAR pathseps[pathcount];
+
+      /* along the path separators split needed path parts */
+      int i = pathcount;
+      while (--i >= 0)
+	if ((pathseps[i] = wcsrchr (ntbuf, L'\\')))
+	  *pathseps[i] = L'\0';
+	else
+	  return; /* something's wrong */
+
+      /* build the mutex name from dir names */
+      for (i = 0; i < pathcount; ++i)
+	{
+	  if (i > 0)
+	    wcscat (mutexname, MUTEXSEP);
+	  wcscat (mutexname, &pathseps[i][1]);
+	  *pathseps[i] = L'\\'; /* restore full path */
+	}
+
+      HANDLE mutex = CreateMutexW (&sec_none_nih, TRUE, mutexname);
+      DWORD lasterror = GetLastError ();
+      debug_printf ("%p = CreateMutexW (%W): %E", mutex, mutexname);
+      if (mutex)
+        {
+	  if (lasterror != ERROR_ALREADY_EXISTS)
+	    rmdirs (ntbuf);
+	  BOOL bret = CloseHandle (mutex);
+	  debug_printf ("%d = CloseHandle (%p, %W): %E",
+	                bret, mutex, mutexname);
+	}
+      return;
+    }
+
+  IO_STATUS_BLOCK iosb;
+  NTSTATUS status;
+
+  HANDLE hdir = dll::ntopenfile (ntbuf, &status,
+				 FILE_DIRECTORY_FILE |
+				 (depth ? FILE_DELETE_ON_CLOSE : 0));
+  if (!hdir)
+    return;
+
+  PWCHAR plast = ntbuf + wcslen (ntbuf);
+  while (NT_SUCCESS (status = NtQueryDirectoryFile (hdir,
+						    NULL, NULL, NULL, &iosb,
+						    pfdi, fdisize,
+						    FileDirectoryInformation,
+						    TRUE, NULL, FALSE)))
+    if (pfdi->FileAttributes & FILE_ATTRIBUTE_DIRECTORY)
+      {
+	int namelen = pfdi->FileNameLength / sizeof (WCHAR);
+	if (!wcsncmp (pfdi->FileName, L".", namelen) ||
+	    !wcsncmp (pfdi->FileName, L"..", namelen))
+	  continue;
+	*plast = L'\\';
+	wcsncpy (plast+1, pfdi->FileName, namelen);
+	plast[1+namelen] = L'\0';
+	rmdirs_synchronized (ntbuf, depth+1, maxdepth, pfdi, fdisize);
+	*plast = L'\0';
+      }
+  if (status != STATUS_NO_MORE_FILES)
+    debug_printf ("%y = NtQueryDirectoryFile (%p, io %y, info %d)",
+		  status, hdir, iosb.Status, iosb.Information);
+  CloseHandle (hdir);
+}
+
+/* Release the forkable hardlinks, and remove them if the
+   mutex can be create-locked after locked-closing. */
+void
+dll_list::cleanup_forkables ()
+{
+  if (!forkables_mutex_name || !*forkables_mutex_name)
+    return;
+
+  /* Make sure there is no concurrent process using these forkables before
+     removing them, via must-not-exist-creating-with-lock the mutex. */
+  bool locked = false;
+  if (forkables_mutex)
+    {
+      /* We try to lock the mutex before closing, so concurrent processes
+         can synchronize on the mutex-closing. Otherways we might end up with
+	 no one successfully must-not-exist-creating-with-lock the mutex,
+	 as closing an unlocked mutex seems to be promoted asynchronously. */
+      DWORD ret = WaitForSingleObject (forkables_mutex, 1);
+      debug_printf ("%u = WFSO (%p, %W, 1)",
+		    ret, forkables_mutex, forkables_mutex_name);
+      switch (ret)
+	{
+	case WAIT_OBJECT_0:
+	case WAIT_ABANDONED:
+	  locked = true;
+	  break;
+	case WAIT_TIMEOUT:
+	  break;
+	default:
+	  system_printf ("error locking mutex %W: %E", forkables_mutex_name);
+	  break;
+	}
+      BOOL bret = CloseHandle (forkables_mutex);
+      debug_printf ("%d = CloseHandle (%p, %W): %E",
+		    bret, forkables_mutex, forkables_mutex_name);
+      forkables_mutex = NULL;
+    }
+
+  /* start the removal below with current forkables dir */
+  PWCHAR buf = dll::nt_max_path_buf ();
+  wcsncpy (buf, forkables_dirx_name, NT_MAX_PATH);
+  buf[NT_MAX_PATH-1] = L'\0';
+
+  denominate_forkables ();
+
+  if (!locked)
+    return;
+
+  /* Instead of just the current forkables, try to remove any forkables found,
+     to ensure some cleanup even in case of situations like power-loss. */
+
+  PWCHAR pathsep = wcsrchr (buf, L'\\');
+  if (!pathsep)
+    return;
+  *pathsep = L'\0'; /* drop trailing path separator */
+
+  int backcount = 0;
+  for (namepart const *part = forkable_nameparts; part->text; ++part)
+    if (part->create_dir)
+      {
+	/* drop one path separator per create_dir */
+	pathsep = wcsrchr (buf, L'\\');
+	if (!pathsep)
+	  return;
+	*pathsep = L'\0';
+	++backcount;
+      }
+
+  /* reading one at a time to reduce stack pressure */
+  struct {
+    FILE_DIRECTORY_INFORMATION fdi;
+    WCHAR buf[NAME_MAX];
+  } fdibuf;
+  rmdirs_synchronized (dll::to_ntname (&buf, buf), 0, backcount,
+		       &fdibuf.fdi, sizeof (fdibuf));
+}
+
+void
+dll_list::denominate_forkables ()
+{
+  *forkables_dirx_name = L'\0';
+  *forkables_mutex_name = L'\0';
+
+  dll *d = &start;
+  while ((d = d->next))
+    d->nominate_forkable (forkables_dirx_name);
+}
+
+/* Set or clear HANDLE_FLAG_INHERIT for all handles necessary
+   to maintain forkables-hardlinks. */
+void
+dll_list::set_forkables_inheritance (bool inherit)
+{
+  DWORD mask = HANDLE_FLAG_INHERIT;
+  DWORD flags = inherit ? HANDLE_FLAG_INHERIT : 0;
+  if (forkables_mutex)
+    SetHandleInformation (forkables_mutex, mask, flags);
+
+  dll *d = &start;
+  while ((d = d->next))
+    if (d->fhandle)
+      SetHandleInformation (d->fhandle, mask, flags);
+}
+
+/* create the forkable hardlinks, if necessary */
+void
+dll_list::request_forkables ()
+{
+  prepare_forkables_nomination ();
+
+  update_forkables_needs ();
+
+  set_forkables_inheritance (true);
+
+  if (forkables_needs <= forkables_needless)
+    return;
+
+  dll *d = &start;
+  while ((d = d->next))
+    d->nominate_forkable (forkables_dirx_name);
+
+  bool updated = update_forkables ();
+
+  if (!updated)
+    {
+      cleanup_forkables ();
+      forkables_needs = forkables_needless;
+    }
+  else
+    forkables_needs = forkables_created;
+}
+
+
+void
+dll_list::release_forkables ()
+{
+  set_forkables_inheritance (false);
+}
diff --git a/winsup/cygwin/pinfo.cc b/winsup/cygwin/pinfo.cc
index d0b4cd9..f397961 100644
--- a/winsup/cygwin/pinfo.cc
+++ b/winsup/cygwin/pinfo.cc
@@ -28,6 +28,7 @@ details. */
 #include "cygtls.h"
 #include "tls_pbuf.h"
 #include "child_info.h"
+#include "dll_init.h"
 
 class pinfo_basic: public _pinfo
 {
@@ -225,6 +226,8 @@ pinfo::exit (DWORD n)
   int exitcode = self->exitcode & 0xffff;
   if (!self->cygstarted)
     exitcode = ((exitcode & 0xff) << 8) | ((exitcode >> 8) & 0xff);
+  sigproc_printf ("Calling dlls.cleanup_forkables n %y, exitcode %y", n, exitcode);
+  dlls.cleanup_forkables ();
   sigproc_printf ("Calling ExitProcess n %y, exitcode %y", n, exitcode);
   if (!TerminateProcess (GetCurrentProcess (), exitcode))
     system_printf ("TerminateProcess failed, %E");
diff --git a/winsup/cygwin/syscalls.cc b/winsup/cygwin/syscalls.cc
index c08d12f..a2da7e3 100644
--- a/winsup/cygwin/syscalls.cc
+++ b/winsup/cygwin/syscalls.cc
@@ -689,8 +689,8 @@ check_dir_not_empty (HANDLE dir, path_conv &pc)
   return STATUS_SUCCESS;
 }
 
-NTSTATUS
-unlink_nt (path_conv &pc)
+static NTSTATUS
+_unlink_nt (path_conv &pc, bool shareable)
 {
   NTSTATUS status;
   HANDLE fh, fh_ro = NULL;
@@ -771,6 +771,9 @@ retry_open:
      bin so that it actually disappears from its directory even though its
      in use.  Otherwise, if opening doesn't fail, the file is not in use and
      we can go straight to setting the delete disposition flag.
+     However, while we have the file open with FILE_SHARE_DELETE, using
+     this file via another hardlink for anything other than DELETE by
+     concurrent processes fails. The 'shareable' argument is to prevent this.
 
      NOTE: The missing sharing modes FILE_SHARE_READ and FILE_SHARE_WRITE do
 	   NOT result in a STATUS_SHARING_VIOLATION, if another handle is
@@ -780,7 +783,10 @@ retry_open:
 	   will succeed.  So, apparently there is no reliable way to find out
 	   if a file is already open elsewhere for other purposes than
 	   reading and writing data.  */
-  status = NtOpenFile (&fh, access, &attr, &io, FILE_SHARE_DELETE, flags);
+  if (shareable)
+    status = STATUS_SHARING_VIOLATION;
+  else
+    status = NtOpenFile (&fh, access, &attr, &io, FILE_SHARE_DELETE, flags);
   /* STATUS_SHARING_VIOLATION is what we expect. STATUS_LOCK_NOT_GRANTED can
      be generated under not quite clear circumstances when trying to open a
      file on NFS with FILE_SHARE_DELETE only.  This has been observed with
@@ -1026,6 +1032,18 @@ out:
   return status;
 }
 
+NTSTATUS
+unlink_nt (path_conv &pc)
+{
+  return _unlink_nt (pc, false);
+}
+
+NTSTATUS
+unlink_nt_shareable (path_conv &pc)
+{
+  return _unlink_nt (pc, true);
+}
+
 extern "C" int
 unlink (const char *ourname)
 {
-- 
2.4.6


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]