This is the mail archive of the cygwin-patches 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]

Re: [PATCH 3/6] forkables: Create forkable hardlinks, yet unused.


On 03/30/2016 08:53 PM, Michael Haubenwallner wrote:
> In preparation to protect fork() against dll- and exe-updates, create
> hardlinks to the main executable and each loaded dll in subdirectories
> of /var/run/cygfork/, if that one exists on the NTFS file system.
> 
> The directory names consist of the user sid, the main executable's NTFS
> IndexNumber, and the most recent LastWriteTime of all involved binaries
> (dlls and main executable).  Next to the main.exe hardlink we create the
> empty file main.exe.local to enable dll redirection.
> 
> The name of the mutex to synchronize hardlink creation/cleanup also is
> assembled from these directory names, to allow for synchronized cleanup
> of even orphaned hardlink directories.
> 
> The hardlink to each dynamically loaded dll goes into another directory,
> named using the NTFS IndexNumber of the dll's original directory.
> 
> 	* dll_init.h (struct dll): Declare member variables fbi, fii,
> 	forkable_ntname.  Declare methods nominate_forkable,
> 	create_forkable.  Define inline method forkedntname.
> 	(struct dll_list): Declare enum forkables_needs.  Declare member
> 	variables forkables_dirx_size, forkables_dirx_ntname,
> 	forkables_mutex_name, forkables_mutex.  Declare private methods
> 	forkable_ntnamesize, prepare_forkables_nomination,
> 	update_forkables_needs, update_forkables, create_forkables,
> 	denominate_forkables, close_mutex, try_remove_forkables.
> 	Declare public method cleanup_forkables.
> 	* dll_init.cc (dll_list::alloc): Allocate memory to hold the
> 	name of the hardlink in struct dll member forkable_ntname.
> 	Initialize struct dll members fbi, fii.
> 	* forkable.cc: Implement static functions mkdirs, rmdirs,
> 	rmdirs_synchronized, read_fii, read_fbi, format_IndexNumber,
> 	rootname, sidname, exename, lwtimename.  Define static array
> 	forkable_nameparts.
> 	(struct dll): Implement nominate_forkable, create_forkable.
> 	(struct dll_list): Implement forkable_ntnamesize,
> 	prepare_forkables_nomination, update_forkables_needs,
> 	update_forkables, create_forkables, close_mutex,
> 	cleanup_forkables, try_remove_forkables, denominate_forkables.
> 	(dll_list::set_forkables_inheritance): Also for forkables_mutex.
> 	(dll_list::request_forkables): Use new methods to create the
> 	hardlinks as necessary.
> 	(dll_list::release_forkables): When hardlink creation turned out
> 	to be impossible, close all the related handles and free the
> 	distinct memory.
> 	* pinfo.cc (pinfo::exit): Call dlls.cleanup_forkables.
> 	* syscalls.cc (_unlink_nt): Rename public unlink_nt function to
> 	static _unlink_nt, with 'shareable' as additional argument.
> 	(unlink_nt): New, wrap _unlink_nt for original behaviour.
> 	(unlink_nt_shareable): New, wrap _unlink_nt to keep a binary
> 	file still loadable while removing one of its hardlinks.
> ---
>  winsup/cygwin/dll_init.cc |   28 +-
>  winsup/cygwin/dll_init.h  |   33 ++
>  winsup/cygwin/forkable.cc | 1036 +++++++++++++++++++++++++++++++++++++++++++++
>  winsup/cygwin/pinfo.cc    |    3 +
>  winsup/cygwin/syscalls.cc |   24 +-
>  5 files changed, 1115 insertions(+), 9 deletions(-)
> 
> diff --git a/winsup/cygwin/dll_init.cc b/winsup/cygwin/dll_init.cc
> index fd807c9..e44ee84 100644
> --- a/winsup/cygwin/dll_init.cc
> +++ b/winsup/cygwin/dll_init.cc
> @@ -372,8 +372,11 @@ dll_list::alloc (HINSTANCE h, per_process *p, dll_type type)
>      }
>    else
>      {
> +      size_t forkntsize = forkable_ntnamesize (type, ntname, modname);
> +
>        /* FIXME: Change this to new at some point. */
> -      d = (dll *) cmalloc (HEAP_2_DLL, sizeof (*d) + (ntnamelen * sizeof (*ntname)));
> +      d = (dll *) cmalloc (HEAP_2_DLL, sizeof (*d)
> +			   + ((ntnamelen + forkntsize) * sizeof (*ntname)));
>  
>        /* Now we've allocated a block of information.  Fill it in with the
>  	 supplied info about this DLL. */
> @@ -389,11 +392,24 @@ dll_list::alloc (HINSTANCE h, per_process *p, dll_type type)
>        d->image_size = ((pefile*)h)->optional_hdr ()->SizeOfImage;
>        d->preferred_base = (void*) ((pefile*)h)->optional_hdr()->ImageBase;
>        d->type = type;
> -      NTSTATUS status;
> -      d->fhandle = ntopenfile (d->ntname, &status);
> -      if (!d->fhandle)
> -	system_printf ("Unable (ntstatus %y) to open file %W",
> -		       status, d->ntname);
> +      d->fhandle = NULL;
> +      d->fbi.FileAttributes = INVALID_FILE_ATTRIBUTES;
> +      d->fii.IndexNumber.QuadPart = 0;
> +      d->forkable_ntname = NULL;
> +      if (forkntsize)
> +	{
> +	  NTSTATUS status;
> +	  d->fhandle = ntopenfile (d->ntname, &status);
> +	  if (!d->fhandle)
> +	    system_printf ("Unable (ntstatus %y) to open file %W",
> +			   status, d->ntname);
> +	  else
> +	    {
> +	      /* may create a hardlink */
> +	      d->forkable_ntname = d->ntname + ntnamelen + 1;
> +	      *d->forkable_ntname = L'\0';
> +	    }
> +	}
>        append (d);
>        if (type == DLL_LOAD)
>  	loaded_dlls++;
> diff --git a/winsup/cygwin/dll_init.h b/winsup/cygwin/dll_init.h
> index c50f889..554435d 100644
> --- a/winsup/cygwin/dll_init.h
> +++ b/winsup/cygwin/dll_init.h
> @@ -65,6 +65,15 @@ struct dll
>  
>    /* forkable */
>    HANDLE fhandle;
> +  FILE_BASIC_INFORMATION fbi;
> +  FILE_INTERNAL_INFORMATION fii;
> +  PWCHAR forkable_ntname;
> +  void nominate_forkable (PCWCHAR);
> +  bool create_forkable ();
> +  PWCHAR forkedntname ()
> +  {
> +    return forkable_ntname && *forkable_ntname ? forkable_ntname : ntname;
> +  }
>  
>    WCHAR ntname[1]; /* must be the last data member */
>    void detach ();
> @@ -83,7 +92,30 @@ struct dll
>  
>  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_ntname;
> +  PWCHAR forkables_mutex_name;
> +  HANDLE forkables_mutex;
>    void track_self ();
> +  size_t forkable_ntnamesize (dll_type, PCWCHAR fullntname, PCWCHAR modname);
> +  void prepare_forkables_nomination ();
> +  void update_forkables_needs ();
> +  bool update_forkables ();
> +  bool create_forkables ();
> +  void denominate_forkables ();
> +  bool close_mutex ();
> +  void try_remove_forkables (PWCHAR dirbuf, size_t dirlen, size_t dirbufsize);
>    void set_forkables_inheritance (bool);
>  
>    dll *end;
> @@ -97,6 +129,7 @@ public:
>    dll *main_executable;
>    void request_forkables ();
>    void release_forkables ();
> +  void cleanup_forkables ();
>  
>    static HANDLE ntopenfile (PCWCHAR ntname, NTSTATUS *pstatus = NULL,
>  			    ULONG openopts = FILE_NON_DIRECTORY_FILE);
> diff --git a/winsup/cygwin/forkable.cc b/winsup/cygwin/forkable.cc
> index 5592985..0a8a528 100644
> --- a/winsup/cygwin/forkable.cc
> +++ b/winsup/cygwin/forkable.cc
> @@ -27,6 +27,998 @@ details. */
>  #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"\\"
> +
> +/* Create the lastsepcount directories found in ntdirname, where
> +   counting is done along path separators (including trailing ones).
> +   Returns true when these directories exist afterwards, false otherways.
> +   The ntdirname is used for the path-splitting. */
> +static bool
> +mkdirs (PWCHAR ntdirname, int lastsepcount)
> +{
> +  bool success = true;
> +  int i = lastsepcount;
> +  for (--i; i > 0; --i)
> +    {
> +      PWCHAR lastsep = wcsrchr (ntdirname, L'\\');
> +      if (!lastsep)
> +	break;
> +      *lastsep = L'\0';
> +    }
> +
> +  for (++i; i <= lastsepcount; ++i)
> +    {
> +      if (success && (i == 0 || wcslen (wcsrchr (ntdirname, L'\\')) > 1))
> +	{
> +	  UNICODE_STRING dn;
> +	  RtlInitUnicodeString (&dn, ntdirname);
> +	  OBJECT_ATTRIBUTES oa;
> +	  InitializeObjectAttributes (&oa, &dn, 0, NULL,
> +				      sec_none_nih.lpSecurityDescriptor);
> +	  HANDLE dh = NULL;
> +	  NTSTATUS status;
> +	  IO_STATUS_BLOCK iosb;
> +	  status = NtCreateFile (&dh, GENERIC_READ | SYNCHRONIZE,
> +				 &oa, &iosb, NULL, FILE_ATTRIBUTE_NORMAL,
> +				 FILE_SHARE_READ,
> +				 FILE_OPEN_IF, /* allow concurrency */
> +				 FILE_DIRECTORY_FILE
> +				 | FILE_SYNCHRONOUS_IO_NONALERT,
> +				 NULL, 0);
> +	  if (NT_SUCCESS(status))
> +	    NtClose (dh);
> +	  else
> +	    success = false;
> +	  debug_printf ("%y = NtCreateFile (%p, dir %W)", status, dh, ntdirname);
> +	}
> +      if (i < lastsepcount)
> +	ntdirname[wcslen (ntdirname)] = 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_list::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);
> +}
> +
> +static bool
> +read_fii (HANDLE fh, PFILE_INTERNAL_INFORMATION pfii, bool once = false)
> +{
> +  if (once && pfii->IndexNumber.QuadPart != 0)
> +    return true;
> +
> +  pfii->IndexNumber.QuadPart = 0LL;
> +
> +  NTSTATUS status;
> +  IO_STATUS_BLOCK iosb;
> +  status = NtQueryInformationFile (fh, &iosb,
> +				   pfii, sizeof (*pfii),
> +				   FileInternalInformation);
> +  if (!NT_SUCCESS (status))
> +    {
> +      system_printf ("WARNING: %y = NtQueryInformationFile (%p,"
> +		     " InternalInfo, io.Status %y)",
> +		     status, fh, iosb.Status);
> +      pfii->IndexNumber.QuadPart = -1LL;
> +      return false;
> +    }
> +  return true;
> +}
> +
> +static bool
> +read_fbi (HANDLE fh, PFILE_BASIC_INFORMATION pfbi, bool once = false)
> +{
> +  if (once && pfbi->FileAttributes != INVALID_FILE_ATTRIBUTES)
> +    return true;
> +
> +  pfbi->FileAttributes = INVALID_FILE_ATTRIBUTES;
> +  pfbi->LastWriteTime.QuadPart = 0LL;
> +
> +  NTSTATUS status;
> +  IO_STATUS_BLOCK iosb;
> +  status = NtQueryInformationFile (fh, &iosb,
> +				   pfbi, sizeof (*pfbi),
> +				   FileBasicInformation);
> +  if (!NT_SUCCESS (status))
> +    {
> +      system_printf ("WARNING: %y = NtQueryInformationFile (%p,"
> +		     " BasicInfo, io.Status %y)",
> +		     status, fh, iosb.Status);
> +      pfbi->FileAttributes = 0;
> +      pfbi->LastWriteTime.QuadPart = -1LL;
> +      return false;
> +    }
> +  return true;
> +}
> +
> +/* Into buf if not NULL, write the IndexNumber in pli.
> +   Return the number of characters (that would be) written. */
> +static int
> +format_IndexNumber (PWCHAR buf, ssize_t bufsize, LARGE_INTEGER const *pli)
> +{
> +  if (!buf)
> +    return 16;
> +  if (bufsize >= 0 && bufsize <= 16)
> +    return 0;
> +  return __small_swprintf (buf, L"%016X", pli->QuadPart);
> +}
> +
> +/* Into buf if not NULL, write the ntname of cygwin installation_root.
> +   Return the number of characters (that would be) written. */
> +static int
> +rootname (PWCHAR buf, ssize_t bufsize)
> +{
> +  PWCHAR cygroot = cygheap->installation_root;
> +  if (!buf)
> +    return 6 /* "\??\UN" */ + wcslen (cygroot);
> +  return dll_list::form_ntname (buf, bufsize, cygroot) - buf;
> +}
> +
> +/* Into buf if not NULL, write the string representation of current user sid.
> +   Return the number of characters (that would be) written. */
> +static int
> +sidname (PWCHAR buf, ssize_t bufsize)
> +{
> +  if (!buf)
> +    return 128;
> +  if (bufsize >= 0 && bufsize <= 128)
> +    return 0;
> +  UNICODE_STRING sid;
> +  WCHAR sidbuf[128+1];
> +  RtlInitEmptyUnicodeString (&sid, sidbuf, sizeof (sidbuf));
> +  RtlConvertSidToUnicodeString (&sid, cygheap->user.sid (), FALSE);
> +  return wcpcpy (buf, sid.Buffer) - buf;
> +}
> +
> +/* Into buf if not NULL, write the IndexNumber of the main executable.
> +   Return the number of characters (that would be) written. */
> +static int
> +exename (PWCHAR buf, ssize_t bufsize)
> +{
> +  if (!buf)
> +    return format_IndexNumber (NULL, bufsize, NULL);
> +  dll *d = dlls.main_executable;
> +  if (d->fhandle)
> +    (void) read_fii (d->fhandle, &d->fii, true);
> +  return format_IndexNumber (buf, bufsize, &d->fii.IndexNumber);
> +}
> +
> +/* Into buf if not NULL, write the newest dll's LastWriteTime.
> +   Return the number of characters (that would be) written. */
> +static int
> +lwtimename (PWCHAR buf, ssize_t bufsize)
> +{
> +  if (!buf)
> +    return sizeof (LARGE_INTEGER) * 2;
> +  if (bufsize >= 0 && bufsize <= (int)sizeof (LARGE_INTEGER) * 2)
> +    return 0;
> +
> +  LARGE_INTEGER 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;
> +
> +      (void)read_fbi (d->fhandle, &d->fbi, true);
> +
> +      /* LastWriteTime more properly tells the last file-content modification
> +	 time, because a newly created hardlink may have a different
> +	 CreationTime compared to the original file. */
> +      if (d->fbi.LastWriteTime.QuadPart > newest.QuadPart)
> +	newest = d->fbi.LastWriteTime;
> +    }
> +
> +  return __small_swprintf (buf, L"%016X", newest);
> +}
> +
> +struct namepart {
> +  PCWCHAR text; /* used when no pathfunc, description otherwise */
> +  int (*textfunc)(PWCHAR buf, ssize_t bufsize);
> +  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>",    rootname,          false, false, },
> +  { L"\\var\\run\\",     NULL,          false, false, },
> +  { L"cygfork",          NULL,          true,  false, },
> +  { L"<sid>",         sidname,          true,  true,  },
> +  { L"<exe>",         exename,          false, false, },
> +  { MUTEXSEP,            NULL,          false, false, },
> +  { L"<ctime>",    lwtimename,          true,  true,  },
> +
> +  { NULL, NULL },
> +};
> +
> +/* 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, ntname);
> +      forkable_ntname = NULL; /* never create a hardlink for this dll */
> +    }
> +
> +  if (!forkable_ntname)
> +    return;
> +
> +  PWCHAR next = wcpcpy (forkable_ntname, dirx_name);
> +
> +  if (!*forkable_ntname)
> +    return; /* denominate */
> +
> +  if (type < DLL_LOAD)
> +    wcpcpy (next, 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 = wcsrchr (ntname, L'\\');
> +      if (!lastpathsep)
> +        {
> +	  forkable_ntname = NULL;
> +	  return;
> +	}
> +      *lastpathsep = L'\0';
> +      HANDLE fh = dll_list::ntopenfile (ntname, NULL, FILE_DIRECTORY_FILE);
> +      *lastpathsep = L'\\';
> +
> +      FILE_INTERNAL_INFORMATION fii = { 0 };
> +      if (fh)
> +	{
> +	  read_fii (fh, &fii);
> +	  NtClose (fh);
> +	}
> +      next += format_IndexNumber (next, -1, &fii.IndexNumber);
> +      wcpcpy (next, lastpathsep);
> +    }
> +}
> +
> +/* Create the nominated hardlink for one indivitual dll,
> +   inside another subdirectory when dynamically loaded. */
> +bool
> +dll::create_forkable ()
> +{
> +  if (!forkable_ntname || !*forkable_ntname)
> +    return true; /* disabled */
> +
> +  if (!fhandle)
> +    return false; /* impossible */
> +
> +  PWCHAR ntname = forkable_ntname;
> +
> +  PWCHAR last = NULL;
> +  bool success = true;
> +  if (type >= DLL_LOAD)
> +    {
> +      last = wcsrchr (ntname, L'\\');
> +      if (!last)
> +	return false;
> +      *last = L'\0';
> +      success = mkdirs (ntname, 1);
> +      *last = L'\\';
> +      if (!success)
> +	return false;
> +    }
> +
> +  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; /* allow concurrency */
> +  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;
> +}
> +
> +/* return the number of characters necessary to store one forkable name */
> +size_t
> +dll_list::forkable_ntnamesize (dll_type type, PCWCHAR fullntname, PCWCHAR modname)
> +{
> +  if (forkables_needs == forkables_impossible)
> +    return 0;
> +
> +  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, 0);
> +	  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_ntname = (PWCHAR) cmalloc (HEAP_2_DLL,
> +	  (forkables_dirx_size + forkables_mutex_size) *
> +	    sizeof (*forkables_dirx_ntname));
> +      *forkables_dirx_ntname = L'\0';
> +
> +      forkables_mutex_name = forkables_dirx_ntname + forkables_dirx_size;
> +      *forkables_mutex_name = L'\0';
> +    }
> +
> +  size_t ret = forkables_dirx_size;
> +  if (type >= DLL_LOAD)
> +    ret += format_IndexNumber (NULL, -1, NULL) + 1; /* one more directory */
> +  return ret + wcslen (modname);
> +}
> +
> +/* Prepare top-level names necessary to nominate individual DLL hardlinks,
> +   eventually releasing/removing previous forkable hardlinks. */
> +void
> +dll_list::prepare_forkables_nomination ()
> +{
> +  if (!forkables_dirx_ntname)
> +    return;
> +
> +  PWCHAR pbuf = nt_max_path_buf ();
> +
> +  bool needsep = false;
> +  bool domutex = false;
> +  namepart const *part;
> +  for (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, -1);
> +      else
> +	pbuf += __small_swprintf (pbuf, L"%W", part->text);
> +    }
> +
> +  if (!wcscmp (forkables_mutex_name, nt_max_path_buf ()))
> +    return; /* nothing changed */
> +
> +  if (*forkables_mutex_name &&
> +      wcscmp (forkables_mutex_name, nt_max_path_buf ()))
> +    {
> +      /* The mutex name has changed since last fork and 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.
> +	 Removing from the file system is done later, upon exit. */
> +      close_mutex ();
> +      denominate_forkables ();
> +    }
> +  wcscpy (forkables_mutex_name, nt_max_path_buf ());
> +
> +  pbuf = forkables_dirx_ntname;
> +  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, -1);
> +      else
> +	pbuf += __small_swprintf (pbuf, L"%W", part->text);
> +    }
> +  pbuf += __small_swprintf (pbuf, L"%W", PATHSEP);
> +
> +  debug_printf ("forkables dir %W", forkables_dirx_ntname);
> +  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;
> +
> +  if (forkables_needs == forkables_unknown)
> +    {
> +      /* check if filesystem of forkables dir is NTFS */
> +      PWCHAR pbuf = nt_max_path_buf ();
> +      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, -1);
> +	  else
> +	    pbuf += __small_swprintf (pbuf, L"%W", part->text);
> +	}
> +
> +      UNICODE_STRING fn;
> +      RtlInitUnicodeString (&fn, nt_max_path_buf ());
> +
> +      fs_info fsi;
> +      if (fsi.update (&fn, NULL) &&
> +/* FIXME: !fsi.is_readonly () && */
> +	  fsi.is_ntfs ())
> +	forkables_needs = forkables_disabled; /* check directory itself */
> +      else
> +	{
> +	  debug_printf ("impossible, not on NTFS %W", fn.Buffer);
> +	  forkables_needs = forkables_impossible;
> +	}
> +    }
> +
> +  if (forkables_needs == forkables_impossible)
> +    return; /* we have not created any hardlink, nothing to clean up */
> +
> +  if (forkables_needs == forkables_disabled ||
> +      forkables_needs == forkables_needless ||
> +      forkables_needs == forkables_created)
> +    {
> +      /* (re-)check existence of forkables dir */
> +      PWCHAR pbuf = nt_max_path_buf ();
> +      for (namepart const *part = forkable_nameparts; part->text; ++part)
> +	{
> +	  if (part->textfunc)
> +	    pbuf += part->textfunc (pbuf, -1);
> +	  else
> +	    pbuf += __small_swprintf (pbuf, L"%W", part->text);
> +	  if (part->mutex_from_dir)
> +	    break; /* up to first mutex-naming dir */
> +	}
> +      pbuf = nt_max_path_buf ();
> +
> +      HANDLE dh = ntopenfile (pbuf, NULL, FILE_DIRECTORY_FILE);
> +      if (dh)
> +	{
> +	  NtClose (dh);
> +	  if (forkables_needs == forkables_disabled)
> +	    forkables_needs = forkables_needless;
> +	}
> +      else if (forkables_needs != forkables_disabled)
> +	{
> +	  debug_printf ("disabled, disappearing %W", pbuf);
> +	  close_mutex ();
> +	  denominate_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_ntname && !*d->forkable_ntname)
> +	  {
> +	    /* ... but another dll was loaded since last fork */
> +	    debug_printf ("needed, since last fork loaded %W", d->ntname);
> +	    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)
> +    {
> +      /* debugging: check for ".unchecked" file in toplevel forkables dir,
> +	 that is: always (try to) create the hardlinks.  Need to find out
> +	 if just trying to create the hardlinks is faster than reading
> +	 all the hardlink's internal and basic informations. */
> +      PWCHAR pbuf = nt_max_path_buf ();
> +      for (namepart const *part = forkable_nameparts; part->text; ++part)
> +	{
> +	  if (part->textfunc)
> +	    pbuf += part->textfunc (pbuf, -1);
> +	  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.unchecked", PATHSEP);
> +
> +      pbuf = nt_max_path_buf ();
> +      HANDLE dh = ntopenfile (pbuf);
> +      if (dh)
> +	{
> +	  NtClose (dh);
> +	  forkables_needs = forkables_needed;
> +	  debug_printf ("needed, found %W", pbuf);
> +	  return;
> +	}
> +    }
> +
> +  /* 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 (!d->fhandle)
> +	continue;
> +
> +      if (fh)
> +	{
> +	  NtClose (fh);
> +	  fh = NULL;
> +	}
> +
> +      PWCHAR ntname = d->forkedntname ();
> +      fh = ntopenfile (ntname);
> +      if (!fh)
> +	{
> +	  debug_printf ("needed, something wrong with %W", ntname);
> +	  forkables_needs = forkables_needed;
> +	  break;
> +	}
> +
> +      FILE_INTERNAL_INFORMATION fii_now;
> +      if (!read_fii (d->fhandle, &d->fii, true) ||
> +	  !read_fii (fh, &fii_now) ||
> +	  d->fii.IndexNumber.QuadPart != fii_now.IndexNumber.QuadPart)
> +        {
> +	  debug_printf ("needed, found modified %W", ntname);
> +	  forkables_needs = forkables_needed;
> +	  break;
> +	}
> +
> +      FILE_BASIC_INFORMATION fbi_now;
> +      if (!read_fbi (d->fhandle, &d->fbi, true) ||
> +	  !read_fbi (fh, &fbi_now) ||
> +	  d->fbi.LastWriteTime.QuadPart != fbi_now.LastWriteTime.QuadPart)
> +	{
> +	  system_printf ("WARNING: changed by same file id %W"
> +			 " (now lastwritetime %016X, old lastwritetime %016X)",
> +			 ntname,
> +			 fbi_now.LastWriteTime.QuadPart,
> +			 d->fbi.LastWriteTime.QuadPart);
> +	  forkables_needs = forkables_needed;
> +	  break;
> +	}
> +    }
> +  if (fh)
> +    NtClose (fh);
> +
> +  if (forkables_needs == forkables_needless && !forkables_mutex)
> +    {
> +      /* debugging: check for ".needed" file in toplevel forkables dir */
> +      PWCHAR pbuf = nt_max_path_buf ();
> +      for (namepart const *part = forkable_nameparts; part->text; ++part)
> +	{
> +	  if (part->textfunc)
> +	    pbuf += part->textfunc (pbuf, -1);
> +	  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 = nt_max_path_buf ();
> +      HANDLE dh = ntopenfile (pbuf);
> +      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 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;
> +
> +      /* Make sure another process does not rmdirs_synchronized () */
> +      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;
> +	}
> +
> +      BOOL bret = ReleaseMutex (forkables_mutex);
> +      debug_printf ("%d = ReleaseMutex (%p, %W)",
> +		    bret, forkables_mutex, forkables_mutex_name);
> +    }
> +
> +  return create_forkables ();
> +}
> +
> +/* 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 ntname = nt_max_path_buf ();
> +  wcsncpy (ntname, forkables_dirx_ntname, NT_MAX_PATH);
> +
> +  if (!mkdirs (ntname, lastsepcount))
> +    success = false;
> +
> +  if (success)
> +    {
> +      /* create the DotLocal file as empty file */
> +      wcsncat (ntname, main_executable->modname, NT_MAX_PATH);
> +      wcsncat (ntname, L".local", NT_MAX_PATH);
> +
> +      UNICODE_STRING fn;
> +      RtlInitUnicodeString (&fn, ntname);
> +
> +      OBJECT_ATTRIBUTES oa;
> +      InitializeObjectAttributes (&oa, &fn, 0, NULL,
> +				  sec_none_nih.lpSecurityDescriptor);
> +      HANDLE hlocal = NULL;
> +      NTSTATUS status;
> +      IO_STATUS_BLOCK iosb;
> +      status = NtCreateFile (&hlocal, GENERIC_WRITE | SYNCHRONIZE,
> +			     &oa, &iosb, NULL, FILE_ATTRIBUTE_NORMAL,
> +			     FILE_SHARE_READ,
> +			     FILE_OPEN_IF, /* allow concurrency */
> +			     FILE_NON_DIRECTORY_FILE
> +			     | FILE_SYNCHRONOUS_IO_NONALERT,
> +			     NULL, 0);
> +      if (NT_SUCCESS (status))
> +	CloseHandle (hlocal);
> +      else
> +	success = false;
> +      debug_printf ("%y = NtCreateFile (%p, %W)", status, hlocal, ntname);
> +    }
> +
> +  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';
> +      PWCHAR mutexnext = mutexname;
> +
> +      /* 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)
> +	    mutexnext = wcpcpy (mutexnext, MUTEXSEP);
> +	  mutexnext = wcpcpy (mutexnext, &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_list::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);
> +}
> +
> +/* Try to lock the mutex handle with almost no timeout, then close the
> +   mutex handle.  Locking before closing is to get the mutex closing
> +   promoted synchronously, otherways we might end up with no one
> +   succeeding in create-with-lock, which is the precondition
> +   to actually remove the hardlinks from the filesystem. */
> +bool
> +dll_list::close_mutex ()
> +{
> +  if (!forkables_mutex || !*forkables_mutex_name)
> +    return false;
> +
> +  HANDLE hmutex = forkables_mutex;
> +  forkables_mutex = NULL;
> +
> +  bool locked = false;
> +  DWORD ret = WaitForSingleObject (hmutex, 1);
> +  debug_printf ("%u = WFSO (%p, %W, 1)",
> +		ret, hmutex, 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 (hmutex);
> +  debug_printf ("%d = CloseHandle (%p, %W): %E",
> +		bret, hmutex, forkables_mutex_name);
> +  return locked;
> +}
> +
> +/* Release the forkable hardlinks, and remove them if the
> +   mutex can be create-locked after locked-closing. */
> +void
> +dll_list::cleanup_forkables ()
> +{
> +  bool locked = close_mutex ();
> +
> +  if (!forkables_dirx_ntname)
> +    return;
> +
> +  /* Start the removal below with current forkables dir,
> +     which is cleaned in denominate_forkables (). */
> +  PWCHAR buf = nt_max_path_buf ();
> +  PWCHAR pathsep = wcpncpy (buf, forkables_dirx_ntname, NT_MAX_PATH);
> +  buf[NT_MAX_PATH-1] = L'\0';
> +
> +  denominate_forkables ();
> +
> +  if (!locked)
> +    return;
> +
> +  /* drop last path separator */
> +  while (--pathsep >= buf && *pathsep != L'\\');
> +  *pathsep = L'\0';
> +
> +  try_remove_forkables (buf, pathsep - buf, NT_MAX_PATH);
> +}
> +
> +void
> +dll_list::try_remove_forkables (PWCHAR dirbuf, size_t dirlen, size_t dirbufsize)
> +{
> +  /* Instead of just the current forkables, try to remove any forkables
> +     found, to ensure some cleanup even in situations like power-loss. */
> +  PWCHAR end = dirbuf + wcslen (dirbuf);
> +  int backcount = 0;
> +  for (namepart const *part = forkable_nameparts; part->text; ++part)
> +    if (part->create_dir)
> +      {
> +	/* drop one path separator per create_dir */
> +	while (--end >= dirbuf && *end != L'\\');
> +	if (end < dirbuf)
> +	  return;
> +	*end = L'\0';
> +	++backcount;
> +      }
> +
> +  /* reading one at a time to reduce stack pressure */
> +  struct {
> +    FILE_DIRECTORY_INFORMATION fdi;
> +    WCHAR buf[NAME_MAX];
> +  } fdibuf;
> +  rmdirs_synchronized (dirbuf, 0, backcount, &fdibuf.fdi, sizeof (fdibuf));
> +}
> +
> +void
> +dll_list::denominate_forkables ()
> +{
> +  if (forkables_dirx_ntname)
> +    {
> +      *forkables_dirx_ntname = L'\0';
> +      *forkables_mutex_name = L'\0';
> +    }
> +
> +  dll *d = &start;
> +  while ((d = d->next))
> +    d->nominate_forkable (forkables_dirx_ntname);
> +}
> +
>  /* Set or clear HANDLE_FLAG_INHERIT for all handles necessary
>     to maintain forkables-hardlinks. */
>  void
> @@ -35,6 +1027,9 @@ 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)
> @@ -45,11 +1040,52 @@ dll_list::set_forkables_inheritance (bool inherit)
>  void
>  dll_list::request_forkables ()
>  {
> +  /* Even on forkables_impossible, keep the number of open handles
> +     stable across the fork, and close them when releasing only. */
> +  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_ntname);
> +
> +  bool updated = update_forkables ();
> +
> +  if (!updated)
> +    forkables_needs = forkables_needless;
> +  else
> +    forkables_needs = forkables_created;
>  }
>  
> +
>  void
>  dll_list::release_forkables ()
>  {
>    set_forkables_inheritance (false);
> +
> +  if (forkables_needs == forkables_impossible)
> +    {
> +      cleanup_forkables ();
> +
> +      dll *d = &start;
> +      while ((d = d->next))
> +	if (d->fhandle)
> +	  {
> +	    NtClose (d->fhandle);
> +	    d->fhandle = NULL;
> +	    d->forkable_ntname = NULL;
> +	  }
> +
> +      if (forkables_dirx_ntname) {
> +	cfree (forkables_dirx_ntname);
> +	forkables_dirx_ntname = NULL;
> +	forkables_mutex_name = NULL;
> +      }
> +    }
>  }
> diff --git a/winsup/cygwin/pinfo.cc b/winsup/cygwin/pinfo.cc
> index d4b2afb..e414a26 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 15fb8ce..7f5354e 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)
>  {
> 


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