972 lines
28 KiB
C++
972 lines
28 KiB
C++
/* 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 "environ.h"
|
|
#include "security.h"
|
|
#include "path.h"
|
|
#include "fhandler.h"
|
|
#include "dtable.h"
|
|
#include "cygheap.h"
|
|
#include "pinfo.h"
|
|
#include "shared_info.h"
|
|
#include "dll_init.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"\\"
|
|
|
|
/* 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, NULL);
|
|
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_SHARE_WRITE,
|
|
FILE_CREATE,
|
|
FILE_DIRECTORY_FILE
|
|
| FILE_SYNCHRONOUS_IO_NONALERT,
|
|
NULL, 0);
|
|
if (NT_SUCCESS(status))
|
|
NtClose (dh);
|
|
else if (status != STATUS_OBJECT_NAME_COLLISION) /* already exists */
|
|
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 == INVALID_HANDLE_VALUE)
|
|
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);
|
|
}
|
|
|
|
/* Get the NTFS file id for the real file behind the dll handle.
|
|
As we may open a wrong (or no) file while the dll is renamed,
|
|
we retry until we get the same file id a second time.
|
|
We use NtQueryVirtualMemory (MemorySectionName) for the current
|
|
file name, as GetModuleFileNameW () yields the as-loaded name.
|
|
While we have the file handle open, also read the attributes.
|
|
NOTE: Uses dll_list::nt_max_path_buf (). */
|
|
bool
|
|
dll::stat_real_file_once ()
|
|
{
|
|
if (fii.IndexNumber.QuadPart != -1LL)
|
|
return true;
|
|
|
|
NTSTATUS fstatus;
|
|
HANDLE fhandle = dll_list::ntopenfile (ntname, &fstatus);
|
|
if (fhandle == INVALID_HANDLE_VALUE)
|
|
{
|
|
system_printf ("WARNING: Unable (ntstatus %y) to open real file %W",
|
|
fstatus, ntname);
|
|
return false;
|
|
}
|
|
|
|
if (!dll_list::read_fii (fhandle, &fii))
|
|
system_printf ("WARNING: Unable to read real file attributes for %W",
|
|
ntname);
|
|
|
|
NtClose (fhandle);
|
|
return fii.IndexNumber.QuadPart != -1LL;
|
|
}
|
|
|
|
/* easy use of NtOpenFile */
|
|
HANDLE
|
|
dll_list::ntopenfile (PCWCHAR ntname, NTSTATUS *pstatus, ULONG openopts,
|
|
ACCESS_MASK access, HANDLE rootDir)
|
|
{
|
|
NTSTATUS status;
|
|
if (!pstatus)
|
|
pstatus = &status;
|
|
|
|
UNICODE_STRING fn;
|
|
if (openopts & FILE_OPEN_BY_FILE_ID)
|
|
RtlInitCountedUnicodeString (&fn, ntname, 8);
|
|
else
|
|
RtlInitUnicodeString (&fn, ntname);
|
|
|
|
OBJECT_ATTRIBUTES oa;
|
|
InitializeObjectAttributes (&oa, &fn, 0, rootDir, NULL);
|
|
|
|
access |= FILE_READ_ATTRIBUTES;
|
|
if (openopts & FILE_DELETE_ON_CLOSE)
|
|
access |= DELETE;
|
|
if (openopts & FILE_DIRECTORY_FILE)
|
|
access |= FILE_LIST_DIRECTORY;
|
|
else
|
|
openopts |= FILE_NON_DIRECTORY_FILE;
|
|
|
|
access |= SYNCHRONIZE;
|
|
openopts |= FILE_SYNCHRONOUS_IO_NONALERT;
|
|
|
|
HANDLE fh = INVALID_HANDLE_VALUE;
|
|
ULONG share = FILE_SHARE_VALID_FLAGS;
|
|
IO_STATUS_BLOCK iosb;
|
|
*pstatus = NtOpenFile (&fh, access, &oa, &iosb, share, openopts);
|
|
if (openopts & FILE_OPEN_BY_FILE_ID)
|
|
debug_printf ("%y = NtOpenFile (%p, a %xh, sh %xh, o %xh, io %y, by id %llX)",
|
|
*pstatus, fh, access, share, openopts, iosb.Status, *(LONGLONG*)fn.Buffer);
|
|
else
|
|
debug_printf ("%y = NtOpenFile (%p, a %xh, sh %xh, o %xh, io %y, '%W')",
|
|
*pstatus, fh, access, share, openopts, iosb.Status, fn.Buffer);
|
|
|
|
return NT_SUCCESS(*pstatus) ? fh : INVALID_HANDLE_VALUE;
|
|
}
|
|
|
|
bool
|
|
dll_list::read_fii (HANDLE fh, PFILE_INTERNAL_INFORMATION pfii)
|
|
{
|
|
pfii->IndexNumber.QuadPart = -1LL;
|
|
|
|
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;
|
|
}
|
|
|
|
/* 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)
|
|
{
|
|
UNICODE_STRING &cygroot = cygheap->installation_root;
|
|
if (!buf)
|
|
return 6 /* "\??\UN" */ + cygroot.Length / sizeof (WCHAR);
|
|
return dll_list::form_ntname (buf, bufsize, cygroot.Buffer) - 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;
|
|
return format_IndexNumber (buf, bufsize, &d->fii.IndexNumber);
|
|
}
|
|
|
|
/* Into buf if not NULL, write the current Windows Thread Identifier.
|
|
Return the number of characters (that would be) written. */
|
|
static int
|
|
winthrname (PWCHAR buf, ssize_t bufsize)
|
|
{
|
|
if (!buf)
|
|
return sizeof (DWORD) * 4;
|
|
if (bufsize >= 0 && bufsize <= (int)sizeof (DWORD) * 4)
|
|
return 0;
|
|
|
|
return __small_swprintf (buf, L"%08X%08X",
|
|
GetCurrentProcessId(), GetCurrentThreadId());
|
|
}
|
|
|
|
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"<winthr>", winthrname, 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)
|
|
{
|
|
/* Multiple dynamically loaded dlls can have identical basenames
|
|
* when loaded from different directories. But still the original
|
|
* basename may serve as linked dependency for another dynamically
|
|
* loaded dll. So we have to create a separate directory for the
|
|
* dynamically loaded dll - using the dll's IndexNumber as name. */
|
|
next += format_IndexNumber (next, -1, &fii.IndexNumber);
|
|
next = wcpcpy (next, L"\\");
|
|
}
|
|
wcpcpy (next, modname);
|
|
}
|
|
|
|
/* Create the nominated hardlink for one indivitual dll,
|
|
inside another subdirectory when dynamically loaded.
|
|
|
|
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. */
|
|
bool
|
|
dll::create_forkable ()
|
|
{
|
|
if (!forkable_ntname || !*forkable_ntname)
|
|
return true; /* disabled */
|
|
|
|
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;
|
|
}
|
|
|
|
/* open device as parent handle for FILE_OPEN_BY_FILE_ID */
|
|
PWCHAR devname = dll_list::nt_max_path_buf ();
|
|
PWCHAR n = ntname;
|
|
PWCHAR d = devname;
|
|
int pathseps = 0;
|
|
while (*n)
|
|
{
|
|
if (*d == L'\\' && ++pathseps > 4)
|
|
break; // "\\??\\UNC\\server\\share"
|
|
*d = *n++;
|
|
if (*d++ == L':')
|
|
break; // "\\??\\C:"
|
|
}
|
|
*d = L'\0';
|
|
|
|
HANDLE devhandle = dll_list::ntopenfile (devname);
|
|
if (devhandle == INVALID_HANDLE_VALUE)
|
|
return false; /* impossible */
|
|
|
|
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;
|
|
|
|
/* When we get STATUS_TRANSACTION_NOT_ACTIVE from hardlink creation,
|
|
the current process has renamed the file while it had the readonly
|
|
attribute. The rename() function uses a transaction for combined
|
|
writeable+rename action if possible to provide atomicity.
|
|
Although the transaction is closed afterwards, creating a hardlink
|
|
for this file requires the FILE_WRITE_ATTRIBUTES access, for unknown
|
|
reason. On the other hand, always requesting FILE_WRITE_ATTRIBUTES
|
|
would fail for users that do not own the original file. */
|
|
bool ret = false;
|
|
int access = 0; /* first attempt */
|
|
while (true)
|
|
{
|
|
HANDLE fh = dll_list::ntopenfile ((PCWCHAR)&fii.IndexNumber, NULL,
|
|
FILE_OPEN_BY_FILE_ID,
|
|
access,
|
|
devhandle);
|
|
if (fh == INVALID_HANDLE_VALUE)
|
|
break; /* impossible */
|
|
|
|
IO_STATUS_BLOCK iosb;
|
|
NTSTATUS status = NtSetInformationFile (fh, &iosb, pfli, bufsize,
|
|
FileLinkInformation);
|
|
NtClose (fh);
|
|
debug_printf ("%y = NtSetInformationFile (%p, FileLink %W, iosb.Status %y)",
|
|
status, fh, pfli->FileName, iosb.Status);
|
|
if (NT_SUCCESS (status) || status == STATUS_OBJECT_NAME_COLLISION)
|
|
{
|
|
ret = true;
|
|
break;
|
|
}
|
|
|
|
if (status != STATUS_TRANSACTION_NOT_ACTIVE ||
|
|
access == FILE_WRITE_ATTRIBUTES)
|
|
break;
|
|
|
|
access = FILE_WRITE_ATTRIBUTES; /* second attempt */
|
|
}
|
|
|
|
NtClose (devhandle);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* return the number of characters necessary to store one forkable name */
|
|
size_t
|
|
dll_list::forkable_ntnamesize (dll_type type, PCWCHAR fullntname, PCWCHAR modname)
|
|
{
|
|
/* per process, this is the first forkables-method ever called */
|
|
if (cygwin_shared->forkable_hardlink_support == 0) /* Unknown */
|
|
{
|
|
/* check existence of forkables dir */
|
|
/* nt_max_path_buf () is already used in dll_list::alloc.
|
|
But as this is run in the very first cygwin process only,
|
|
using some heap is not a performance issue here. */
|
|
PWCHAR pbuf = (PWCHAR) cmalloc_abort (HEAP_BUF,
|
|
NT_MAX_PATH * sizeof (WCHAR));
|
|
PWCHAR pnext = pbuf;
|
|
for (namepart const *part = forkable_nameparts; part->text; ++part)
|
|
{
|
|
if (part->textfunc)
|
|
pnext += part->textfunc (pnext, -1);
|
|
else
|
|
pnext += __small_swprintf (pnext, L"%W", part->text);
|
|
if (part->mutex_from_dir)
|
|
break; /* up to first mutex-naming dir */
|
|
}
|
|
|
|
UNICODE_STRING fn;
|
|
RtlInitUnicodeString (&fn, pbuf);
|
|
|
|
HANDLE dh = INVALID_HANDLE_VALUE;
|
|
fs_info fsi;
|
|
if (fsi.update (&fn, NULL) &&
|
|
/* FIXME: !fsi.is_readonly () && */
|
|
fsi.is_ntfs ())
|
|
dh = ntopenfile (pbuf, NULL, FILE_DIRECTORY_FILE);
|
|
if (dh != INVALID_HANDLE_VALUE)
|
|
{
|
|
cygwin_shared->forkable_hardlink_support = 1; /* Yes */
|
|
NtClose (dh);
|
|
debug_printf ("enabled");
|
|
}
|
|
else
|
|
{
|
|
cygwin_shared->forkable_hardlink_support = -1; /* No */
|
|
debug_printf ("disabled, missing or not on NTFS %W", fn.Buffer);
|
|
}
|
|
cfree (pbuf);
|
|
}
|
|
|
|
if (!forkables_supported ())
|
|
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 ()
|
|
{
|
|
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 ()
|
|
{
|
|
if (forkables_created)
|
|
{
|
|
/* already have created hardlinks in this process, ... */
|
|
dll *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_created = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* 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, NULL);
|
|
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_SHARE_WRITE,
|
|
FILE_CREATE,
|
|
FILE_NON_DIRECTORY_FILE
|
|
| FILE_SYNCHRONOUS_IO_NONALERT,
|
|
NULL, 0);
|
|
if (NT_SUCCESS (status))
|
|
CloseHandle (hlocal);
|
|
else if (status != STATUS_OBJECT_NAME_COLLISION) /* already exists */
|
|
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)
|
|
{
|
|
debug_printf ("cleaning up for mutex %W", mutexname);
|
|
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 == INVALID_HANDLE_VALUE)
|
|
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 ()
|
|
{
|
|
if (!forkables_supported ())
|
|
return;
|
|
|
|
bool locked = close_mutex ();
|
|
|
|
/* 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 ()
|
|
{
|
|
*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
|
|
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);
|
|
}
|
|
|
|
/* create the forkable hardlinks, if necessary */
|
|
void
|
|
dll_list::request_forkables ()
|
|
{
|
|
if (!forkables_supported ())
|
|
return;
|
|
|
|
prepare_forkables_nomination ();
|
|
|
|
update_forkables_needs ();
|
|
|
|
set_forkables_inheritance (true);
|
|
|
|
if (forkables_created)
|
|
return; /* nothing new to create */
|
|
|
|
dll *d = &start;
|
|
while ((d = d->next))
|
|
d->nominate_forkable (forkables_dirx_ntname);
|
|
|
|
if (update_forkables ())
|
|
forkables_created = true;
|
|
}
|
|
|
|
|
|
void
|
|
dll_list::release_forkables ()
|
|
{
|
|
if (!forkables_supported ())
|
|
return;
|
|
|
|
set_forkables_inheritance (false);
|
|
}
|