* path.cc (copy_cwd_str): Move up in file to be accessible from

class fcwd_access_t.
	(class fcwd_access_t): New class to consolidate and hide the details
	of the various FAST_CWD implementations.  Add implementation for
	Windows 8 Developer Preview.
	(fast_cwd_version): Make static private member of fcwd_access_t.
	(fast_cwd_ptr): Change base type to fcwd_access_t.
	(find_fast_cwd_pointer): Return fcwd_access_t**.
	(find_fast_cwd): Ditto.  Rip out all FAST_CWD implementations and use
	fcwd_access_t methods instead.
	(cwdstuff::override_win32_cwd): Ditto.
This commit is contained in:
Corinna Vinschen 2011-10-13 15:32:10 +00:00
parent 5de3a1c10f
commit 40187f9007
2 changed files with 243 additions and 168 deletions

View File

@ -1,3 +1,17 @@
2011-10-13 Corinna Vinschen <corinna@vinschen.de>
* path.cc (copy_cwd_str): Move up in file to be accessible from
class fcwd_access_t.
(class fcwd_access_t): New class to consolidate and hide the details
of the various FAST_CWD implementations. Add implementation for
Windows 8 Developer Preview.
(fast_cwd_version): Make static private member of fcwd_access_t.
(fast_cwd_ptr): Change base type to fcwd_access_t.
(find_fast_cwd_pointer): Return fcwd_access_t**.
(find_fast_cwd): Ditto. Rip out all FAST_CWD implementations and use
fcwd_access_t methods instead.
(cwdstuff::override_win32_cwd): Ditto.
2011-10-12 Corinna Vinschen <corinna@vinschen.de>
* fhandler_console.cc (fhandler_console::cursor_set): Disable forcing

View File

@ -3386,6 +3386,17 @@ cygwin_split_path (const char *path, char *dir, char *file)
file[end - last_slash - 1] = 0;
}
static inline void
copy_cwd_str (PUNICODE_STRING tgt, PUNICODE_STRING src)
{
RtlCopyUnicodeString (tgt, src);
if (tgt->Buffer[tgt->Length / sizeof (WCHAR) - 1] != L'\\')
{
tgt->Buffer[tgt->Length / sizeof (WCHAR)] = L'\\';
tgt->Length += sizeof (WCHAR);
}
}
/*****************************************************************************/
/* The find_fast_cwd_pointer function and parts of the
@ -3420,50 +3431,204 @@ cygwin_split_path (const char *path, char *dir, char *file)
USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
DAMAGE. */
/* This structure is used to store the CWD starting with Windows Vista.
/* This class is used to store the CWD starting with Windows Vista.
The CWD storage in the RTL_USER_PROCESS_PARAMETERS block is only
an afterthought now. The actual CWD storage is a FAST_CWD structure
which is allocated on the process heap. The new method only requires
minimal locking and it's much more multi-thread friendly. Presumably
it minimizes contention when accessing the CWD. */
typedef struct _FAST_CWD {
UNICODE_STRING Path; /* Path's Buffer member always refers
to the following Buffer array. */
HANDLE DirectoryHandle;
LONG FSCharacteristics; /* Taken from FileFsDeviceInformation */
LONG ReferenceCount; /* Only release when this is 0. */
ULONG OldDismountCount; /* Reflects the system DismountCount
it minimizes contention when accessing the CWD.
The class fcwd_access_t is supposed to encapsulate the gory implementation
details depending on OS version from the calling functions. */
class fcwd_access_t {
/* This is the layout used in Windows 8 developer preview. */
struct FAST_CWD_8 {
LONG ReferenceCount; /* Only release when this is 0. */
HANDLE DirectoryHandle;
ULONG OldDismountCount; /* Reflects the system DismountCount
at the time the CWD has been set. */
WCHAR Buffer[MAX_PATH];
} FAST_CWD, *PFAST_CWD;
/* This is the old FAST_CWD structure up to the patch from KB 2393802,
release in February 2011. Hopefully it's not used for long anymore,
but for quite some time we can't rely on this fact. */
typedef struct _FAST_CWD_OLD {
LONG ReferenceCount; /* Only release when this is 0. */
HANDLE DirectoryHandle;
ULONG OldDismountCount; /* Reflects the system DismountCount
at the time the CWD has been set. */
UNICODE_STRING Path; /* Path's Buffer member always refers
UNICODE_STRING Path; /* Path's Buffer member always refers
to the following Buffer array. */
WCHAR Buffer[MAX_PATH];
} FAST_CWD_OLD, *PFAST_CWD_OLD;
LONG FSCharacteristics; /* Taken from FileFsDeviceInformation */
WCHAR Buffer[MAX_PATH];
};
/* This is the layout used in Windows 7 and Vista. */
struct FAST_CWD_7 {
UNICODE_STRING Path; /* Path's Buffer member always refers
to the following Buffer array. */
HANDLE DirectoryHandle;
LONG FSCharacteristics; /* Taken from FileFsDeviceInformation */
LONG ReferenceCount; /* Only release when this is 0. */
ULONG OldDismountCount; /* Reflects the system DismountCount
at the time the CWD has been set. */
WCHAR Buffer[MAX_PATH];
};
/* This is the old FAST_CWD structure up to the patch from KB 2393802,
release in February 2011. */
struct FAST_CWD_OLD {
LONG ReferenceCount; /* Only release when this is 0. */
HANDLE DirectoryHandle;
ULONG OldDismountCount; /* Reflects the system DismountCount
at the time the CWD has been set. */
UNICODE_STRING Path; /* Path's Buffer member always refers
to the following Buffer array. */
WCHAR Buffer[MAX_PATH];
};
union {
FAST_CWD_OLD fold;
FAST_CWD_7 f7;
FAST_CWD_8 f8;
};
/* Type of FAST_CWD used on this system. Keeping this information available
in shared memory avoids to test for the version every time around.
Default to new version. */
enum fcwd_version_t {
FCWD_OLD,
FCWD_W7,
FCWD_W8
};
static fcwd_version_t fast_cwd_version;
#define IMPLEMENT(type, name) \
type name () { \
switch (fast_cwd_version) { \
case FCWD_OLD: \
default: \
return fold.name; \
case FCWD_W7: \
return f7.name; \
case FCWD_W8: \
return f8.name; \
} \
}
IMPLEMENT (LONG &, ReferenceCount)
IMPLEMENT (HANDLE &, DirectoryHandle)
IMPLEMENT (ULONG &, OldDismountCount)
IMPLEMENT (UNICODE_STRING &, Path)
IMPLEMENT (WCHAR *, Buffer)
/* Special case FSCharacteristics. Didn't exist originally. */
void SetFSCharacteristics (LONG val)
{
switch (fast_cwd_version)
{
case FCWD_OLD:
break;
case FCWD_W7:
f7.FSCharacteristics = val;
break;
case FCWD_W8:
f8.FSCharacteristics = val;
break;
}
}
public:
void CopyPath (UNICODE_STRING &target)
{
/* Copy the Path contents over into the UNICODE_STRING referenced by
target. This is used to set the CurrentDirectoryName in the
user parameter block. */
target = Path ();
}
void Free (PVOID heap)
{
/* Decrement the reference count. If it's down to 0, free
structure from heap. */
if (this && InterlockedDecrement (&ReferenceCount ()) == 0)
{
/* In contrast to pre-Vista, the handle on init is always a
fresh one and not the handle inherited from the parent
process. So we always have to close it here. However, the
handle could be NULL, if we cd'ed into a virtual dir. */
HANDLE h = DirectoryHandle ();
if (h)
NtClose (h);
RtlFreeHeap (heap, 0, this);
}
}
void FillIn (HANDLE dir, PUNICODE_STRING name, ULONG old_dismount_count)
{
/* Fill in all values into this FAST_CWD structure. */
DirectoryHandle () = dir;
ReferenceCount () = 1;
OldDismountCount () = old_dismount_count;
/* The new structure stores the device characteristics of the
volume holding the dir. RtlGetCurrentDirectory_U checks
if the FILE_REMOVABLE_MEDIA flag is set and, if so, checks if
the volume is still the same as the one used when opening
the directory handle.
We don't call NtQueryVolumeInformationFile for the \\?\PIPE,
though. It just returns STATUS_INVALID_HANDLE anyway. */
if (fast_cwd_version != FCWD_OLD)
{
SetFSCharacteristics (0);
if (name != &ro_u_pipedir)
{
IO_STATUS_BLOCK io;
FILE_FS_DEVICE_INFORMATION ffdi;
if (NT_SUCCESS (NtQueryVolumeInformationFile (dir, &io, &ffdi,
sizeof ffdi, FileFsDeviceInformation)))
SetFSCharacteristics (ffdi.Characteristics);
}
}
RtlInitEmptyUnicodeString (&Path (), Buffer (),
MAX_PATH * sizeof (WCHAR));
copy_cwd_str (&Path (), name);
}
static void SetDirHandleFromBufferPointer (PWCHAR buf_p, HANDLE dir)
{
/* Input: The buffer pointer as it's stored in the user parameter block
and a directory handle.
This function computes the address to the FAST_CWD structure based
on the version and overwrites the directory handle. It is only
used if we couldn't figure out the address of fast_cwd_ptr. */
fcwd_access_t *f_cwd;
switch (fast_cwd_version)
{
case FCWD_OLD:
default:
f_cwd = (fcwd_access_t *)
((PBYTE) buf_p - __builtin_offsetof (FAST_CWD_OLD, Buffer));
case FCWD_W7:
f_cwd = (fcwd_access_t *)
((PBYTE) buf_p - __builtin_offsetof (FAST_CWD_7, Buffer));
case FCWD_W8:
f_cwd = (fcwd_access_t *)
((PBYTE) buf_p - __builtin_offsetof (FAST_CWD_8, Buffer));
}
f_cwd->DirectoryHandle () = dir;
}
static void SetVersionFromPointer (PBYTE buf_p, bool is_buffer)
{
/* Given a pointer to the FAST_CWD structure (is_buffer == false) or a
pointer to the Buffer within (is_buffer == true), this function
computes the FAST_CWD version by checking that Path.MaximumLength
equals MAX_PATH, and that Path.Buffer == Buffer. */
if (is_buffer)
buf_p -= __builtin_offsetof (FAST_CWD_8, Buffer);
fcwd_access_t *f_cwd = (fcwd_access_t *) buf_p;
if (f_cwd->f8.Path.MaximumLength == MAX_PATH * sizeof (WCHAR)
&& f_cwd->f8.Path.Buffer == f_cwd->f8.Buffer)
fast_cwd_version = FCWD_W8;
else if (f_cwd->f7.Path.MaximumLength == MAX_PATH * sizeof (WCHAR)
&& f_cwd->f7.Path.Buffer == f_cwd->f7.Buffer)
fast_cwd_version = FCWD_W7;
else
fast_cwd_version = FCWD_OLD;
}
};
fcwd_access_t::fcwd_version_t fcwd_access_t::fast_cwd_version
__attribute__((section (".cygwin_dll_common"), shared))
= fcwd_access_t::FCWD_W7;
/* fast_cwd_ptr is a pointer to the global pointer in ntdll.dll pointing
to the FAST_CWD structure which constitutes the CWD.
We put the pointer into the common shared DLL segment. This allows to
restrict the call to find_fast_cwd_pointer() to once per Cygwin session
per user session. This works, because ASLR randomizes the load address
of DLLs only once at boot time. */
static PFAST_CWD *fast_cwd_ptr
__attribute__((section (".cygwin_dll_common"), shared)) = (PFAST_CWD *) -1;
/* Type of FAST_CWD used on this system. Keeping this information available
in shared memory avoids to test for the version every time around.
Default to new version. */
static int fast_cwd_version
__attribute__((section (".cygwin_dll_common"), shared)) = 1;
static fcwd_access_t **fast_cwd_ptr
__attribute__((section (".cygwin_dll_common"), shared))
= (fcwd_access_t **) -1;
#define peek32(x) (*(uint32_t *)(x))
@ -3475,7 +3640,7 @@ static int fast_cwd_version
This code has been tested on Vista 32/64 bit, Server 2008 32/64 bit,
Windows 7 32/64 bit, and Server 2008 R2 (which is only 64 bit anyway).
There's some hope that this will still work for Windows 8... */
static PFAST_CWD *
static fcwd_access_t **
find_fast_cwd_pointer ()
{
/* Fetch entry points of relevant functions in ntdll.dll. */
@ -3523,58 +3688,30 @@ find_fast_cwd_pointer ()
const uint8_t *movesi = rcall + 5;
if (movesi[0] != 0x8b)
return NULL;
return (PFAST_CWD *) peek32 (movesi + 2);
return (fcwd_access_t **) peek32 (movesi + 2);
}
static inline void
copy_cwd_str (PUNICODE_STRING tgt, PUNICODE_STRING src)
{
RtlCopyUnicodeString (tgt, src);
if (tgt->Buffer[tgt->Length / sizeof (WCHAR) - 1] != L'\\')
{
tgt->Buffer[tgt->Length / sizeof (WCHAR)] = L'\\';
tgt->Length += sizeof (WCHAR);
}
}
static PFAST_CWD *
static fcwd_access_t **
find_fast_cwd ()
{
/* Fetch the pointer but don't set the global fast_cwd_ptr yet. First
we have to make sure we know the version of the FAST_CWD structure
used on the system. */
PFAST_CWD *f_cwd_ptr = find_fast_cwd_pointer ();
fcwd_access_t **f_cwd_ptr = find_fast_cwd_pointer ();
if (!f_cwd_ptr)
system_printf ("WARNING: Couldn't compute FAST_CWD pointer. "
"Please report this problem to\nthe public mailing "
"list cygwin@cygwin.com");
if (f_cwd_ptr && *f_cwd_ptr)
{
/* Fortunately it's simple to check for the new structure.
Path.MaximumLength takes the same space as the high word of
the old ReferenceCount. We know that MaximumLength is always
MAX_PATH. For the ref count this would account for a
pratically impossible value between 34078720 and 34079240. */
fast_cwd_version = ((*f_cwd_ptr)->Path.MaximumLength
== MAX_PATH * sizeof (WCHAR)) ? 1 : 0;
/* Just evaluate structure version. */
fcwd_access_t::SetVersionFromPointer ((PBYTE) *f_cwd_ptr, false);
}
else
{
/* If we couldn't fetch fast_cwd_ptr, or if fast_cwd_ptr is
NULL(*) we have to figure out the version from the Buffer
pointer in the ProcessParameters. We must make sure not
to access memory outside of the structure. Therefore we
check the Path.Buffer pointer,
which would be the ReferenceCount in the old structure.
(*) This is very unlikely to happen when starting the first
Cygwin process, since it only happens when starting the
process in a directory which can't be used as CWD by Win32, or
if the directory doesn't exist. But *if* it happens, we have
no valid FAST_CWD structure, even though upp_cwd_str.Buffer is
not NULL in that case. So we let the OS create a valid
FAST_CWD structure temporarily to have something to work with.
We know the pipe FS works. */
/* If we couldn't fetch fast_cwd_ptr, or if fast_cwd_ptr is NULL(*) we
have to figure out the version from the Buffer pointer in the
ProcessParameters. */
PEB &peb = *NtCurrentTeb ()->Peb;
if (f_cwd_ptr /* so *f_cwd_ptr == NULL */
@ -3582,10 +3719,8 @@ find_fast_cwd ()
api_fatal ("Couldn't set directory to %S temporarily.\n"
"Cannot continue.", &ro_u_pipedir);
RtlEnterCriticalSection (peb.FastPebLock);
PFAST_CWD f_cwd = (PFAST_CWD)
((PBYTE) peb.ProcessParameters->CurrentDirectoryName.Buffer
- __builtin_offsetof (struct _FAST_CWD, Buffer));
fast_cwd_version = (f_cwd->Path.Buffer == f_cwd->Buffer) ? 1 : 0;
fcwd_access_t::SetVersionFromPointer
((PBYTE) peb.ProcessParameters->CurrentDirectoryName.Buffer, true);
RtlLeaveCriticalSection (peb.FastPebLock);
}
/* Eventually, after we set the version as well, set fast_cwd_ptr. */
@ -3595,8 +3730,6 @@ find_fast_cwd ()
void
cwdstuff::override_win32_cwd (bool init, ULONG old_dismount_count)
{
NTSTATUS status;
IO_STATUS_BLOCK io;
HANDLE h = NULL;
PEB &peb = *NtCurrentTeb ()->Peb;
@ -3605,7 +3738,7 @@ cwdstuff::override_win32_cwd (bool init, ULONG old_dismount_count)
if (wincap.has_fast_cwd ())
{
if (fast_cwd_ptr == (PFAST_CWD *) -1)
if (fast_cwd_ptr == (fcwd_access_t **) -1)
fast_cwd_ptr = find_fast_cwd ();
if (fast_cwd_ptr)
{
@ -3613,91 +3746,32 @@ cwdstuff::override_win32_cwd (bool init, ULONG old_dismount_count)
fast_cwd_ptr, we can simply replace the RtlSetCurrentDirectory_U
function entirely, just as on pre-Vista. */
PVOID heap = peb.ProcessHeap;
/* First allocate a new FAST_CWD structure on the heap.
The new FAST_CWD structure is 4 byte bigger than the old one,
/* First allocate a new fcwd_access_t structure on the heap.
The new fcwd_access_t structure is 4 byte bigger than the old one,
but we simply don't care, so we allocate always room for the
new one. */
PFAST_CWD f_cwd = (PFAST_CWD)
RtlAllocateHeap (heap, 0, sizeof (FAST_CWD));
fcwd_access_t *f_cwd = (fcwd_access_t *)
RtlAllocateHeap (heap, 0, sizeof (fcwd_access_t));
if (!f_cwd)
{
debug_printf ("RtlAllocateHeap failed");
return;
}
/* Fill in the values. */
if (fast_cwd_version)
{
/* New FAST_CWD structure. */
FILE_FS_DEVICE_INFORMATION ffdi;
RtlInitEmptyUnicodeString (&f_cwd->Path, f_cwd->Buffer,
MAX_PATH * sizeof (WCHAR));
f_cwd->DirectoryHandle = dir;
/* The new structure stores the device characteristics of the
volume holding the dir. RtlGetCurrentDirectory_U checks
if the FILE_REMOVABLE_MEDIA flag is set and, if so, checks if
the volume is still the same as the one used when opening
the directory handle.
We don't call NtQueryVolumeInformationFile for the \\?\PIPE,
though. It just returns STATUS_INVALID_HANDLE anyway. */
f_cwd->FSCharacteristics =
(!error
&& NT_SUCCESS (NtQueryVolumeInformationFile (dir, &io, &ffdi,
sizeof ffdi, FileFsDeviceInformation)))
? ffdi.Characteristics : 0;
f_cwd->ReferenceCount = 1;
f_cwd->OldDismountCount = old_dismount_count;
copy_cwd_str (&f_cwd->Path, error ? &ro_u_pipedir : &win32);
/* Use PEB lock when switching fast_cwd_ptr to the new FAST_CWD
structure and writing the CWD to the user process parameter
block. This is equivalent to calling RtlAcquirePebLock/
RtlReleasePebLock, but without having to go through the FS
selector again. */
RtlEnterCriticalSection (peb.FastPebLock);
PFAST_CWD old_cwd = *fast_cwd_ptr;
*fast_cwd_ptr = f_cwd;
upp_cwd_str = f_cwd->Path;
upp_cwd_hdl = dir;
RtlLeaveCriticalSection (peb.FastPebLock);
/* Decrement the reference count. If it's down to 0, free
structure from heap. */
if (old_cwd
&& InterlockedDecrement (&old_cwd->ReferenceCount) == 0)
{
/* In contrast to pre-Vista, the handle on init is always a
fresh one and not the handle inherited from the parent
process. So we always have to close it here. However, the
handle could be NULL, if we cd'ed into a virtual dir. */
if (old_cwd->DirectoryHandle)
NtClose (old_cwd->DirectoryHandle);
RtlFreeHeap (heap, 0, old_cwd);
}
}
else
{
/* Old FAST_CWD structure. Same procedure as above, except for
the non-existant FSCharacteristics member. */
PFAST_CWD_OLD f_cwd_old = (PFAST_CWD_OLD) f_cwd;
f_cwd_old->ReferenceCount = 1;
f_cwd_old->DirectoryHandle = dir;
f_cwd_old->OldDismountCount = old_dismount_count;
RtlInitEmptyUnicodeString (&f_cwd_old->Path, f_cwd_old->Buffer,
MAX_PATH * sizeof (WCHAR));
copy_cwd_str (&f_cwd_old->Path, error ? &ro_u_pipedir : &win32);
RtlEnterCriticalSection (peb.FastPebLock);
PFAST_CWD_OLD old_cwd = (PFAST_CWD_OLD) *fast_cwd_ptr;
*fast_cwd_ptr = (PFAST_CWD) f_cwd_old;
upp_cwd_str = f_cwd_old->Path;
upp_cwd_hdl = dir;
RtlLeaveCriticalSection (peb.FastPebLock);
if (old_cwd
&& InterlockedDecrement (&old_cwd->ReferenceCount) == 0)
{
if (old_cwd->DirectoryHandle)
NtClose (old_cwd->DirectoryHandle);
RtlFreeHeap (heap, 0, old_cwd);
}
}
f_cwd->FillIn (dir, error ? &ro_u_pipedir : &win32,
old_dismount_count);
/* Use PEB lock when switching fast_cwd_ptr to the new FAST_CWD
structure and writing the CWD to the user process parameter
block. This is equivalent to calling RtlAcquirePebLock/
RtlReleasePebLock, but without having to go through the FS
selector again. */
RtlEnterCriticalSection (peb.FastPebLock);
fcwd_access_t *old_cwd = *fast_cwd_ptr;
*fast_cwd_ptr = f_cwd;
f_cwd->CopyPath (upp_cwd_str);
upp_cwd_hdl = dir;
RtlLeaveCriticalSection (peb.FastPebLock);
old_cwd->Free (heap);
}
else
{
@ -3727,8 +3801,8 @@ cwdstuff::override_win32_cwd (bool init, ULONG old_dismount_count)
Therefore, use this *only* as a fallback. */
if (!init)
{
status = RtlSetCurrentDirectory_U (error ? &ro_u_pipedir
: &win32);
NTSTATUS status =
RtlSetCurrentDirectory_U (error ? &ro_u_pipedir : &win32);
if (!NT_SUCCESS (status))
{
debug_printf ("RtlSetCurrentDirectory_U(%S) failed, %p",
@ -3739,20 +3813,7 @@ cwdstuff::override_win32_cwd (bool init, ULONG old_dismount_count)
else if (upp_cwd_hdl == NULL)
return;
RtlEnterCriticalSection (peb.FastPebLock);
if (fast_cwd_version)
{
PFAST_CWD f_cwd = (PFAST_CWD)
((PBYTE) upp_cwd_str.Buffer
- __builtin_offsetof (struct _FAST_CWD, Buffer));
f_cwd->DirectoryHandle = dir;
}
else
{
PFAST_CWD_OLD f_cwd_old = (PFAST_CWD_OLD)
((PBYTE) upp_cwd_str.Buffer
- __builtin_offsetof (struct _FAST_CWD_OLD, Buffer));
f_cwd_old->DirectoryHandle = dir;
}
fcwd_access_t::SetDirHandleFromBufferPointer(upp_cwd_str.Buffer, dir);
h = upp_cwd_hdl;
upp_cwd_hdl = dir;
RtlLeaveCriticalSection (peb.FastPebLock);