This is the mail archive of the
cygwin-patches
mailing list for the Cygwin project.
Re: [PATCH 3/6] forkables: Create forkable hardlinks, yet unused.
- From: Michael Haubenwallner <michael dot haubenwallner at ssi-schaefer dot com>
- To: cygwin-patches at cygwin dot com
- Date: Wed, 16 Nov 2016 13:25:33 +0100
- Subject: Re: [PATCH 3/6] forkables: Create forkable hardlinks, yet unused.
- Authentication-results: sourceware.org; auth=none
- References: <1459364024-24891-1-git-send-email-michael.haubenwallner@ssi-schaefer.com> <1459364024-24891-4-git-send-email-michael.haubenwallner@ssi-schaefer.com>
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)
> {
>