* cygheap.h (enum fcwd_version_t): Move here from path.cc.
(class fcwd_access_t): Ditto. Only declare methods. (class cwdstuff): Move fast_cwd_ptr and fast_cwd_version from shared DLL section here. * path.cc: Keep fcwd_access_t method definitions. (fcwd_access_t::fast_cwd_version): New method. (find_fast_cwd_pointer): Change comment. Mention test on W8CP. (cwdstuff::init): Initialize fast_cwd_ptr and fast_cwd_version.
This commit is contained in:
parent
75effa37fc
commit
a3904c655e
@ -1,3 +1,14 @@
|
||||
2012-03-05 Corinna Vinschen <corinna@vinschen.de>
|
||||
|
||||
* cygheap.h (enum fcwd_version_t): Move here from path.cc.
|
||||
(class fcwd_access_t): Ditto. Only declare methods.
|
||||
(class cwdstuff): Move fast_cwd_ptr and fast_cwd_version from shared
|
||||
DLL section here.
|
||||
* path.cc: Keep fcwd_access_t method definitions.
|
||||
(fcwd_access_t::fast_cwd_version): New method.
|
||||
(find_fast_cwd_pointer): Change comment. Mention test on W8CP.
|
||||
(cwdstuff::init): Initialize fast_cwd_ptr and fast_cwd_version.
|
||||
|
||||
2012-03-05 Corinna Vinschen <corinna@vinschen.de>
|
||||
|
||||
* dll_init.cc (dll_list::operator[]): Extend comment a bit more to
|
||||
|
@ -204,7 +204,87 @@ public:
|
||||
|
||||
/* cwd cache stuff. */
|
||||
|
||||
class muto;
|
||||
enum fcwd_version_t {
|
||||
FCWD_OLD,
|
||||
FCWD_W7,
|
||||
FCWD_W8
|
||||
};
|
||||
|
||||
/* 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.
|
||||
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. */
|
||||
UNICODE_STRING Path; /* Path's Buffer member always refers
|
||||
to the following Buffer array. */
|
||||
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;
|
||||
};
|
||||
|
||||
#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)
|
||||
void SetFSCharacteristics (LONG val);
|
||||
static fcwd_version_t &fast_cwd_version (void);
|
||||
|
||||
public:
|
||||
void CopyPath (UNICODE_STRING &target);
|
||||
void Free (PVOID heap);
|
||||
void FillIn (HANDLE dir, PUNICODE_STRING name, ULONG old_dismount_count);
|
||||
static void SetDirHandleFromBufferPointer (PWCHAR buf_p, HANDLE dir);
|
||||
static void SetVersionFromPointer (PBYTE buf_p, bool is_buffer);
|
||||
};
|
||||
|
||||
class cwdstuff
|
||||
{
|
||||
@ -217,6 +297,16 @@ private:
|
||||
a native Win32 application. See cwdstuff::set for
|
||||
how it gets set. See child_info_spawn::worker for how
|
||||
it's evaluated. */
|
||||
|
||||
friend class fcwd_access_t;
|
||||
/* fast_cwd_ptr is a pointer to the global RtlpCurDirRef pointer in
|
||||
ntdll.dll pointing to the FAST_CWD structure which constitutes the CWD.
|
||||
Unfortunately RtlpCurDirRef is not exported from ntdll.dll. */
|
||||
fcwd_access_t **fast_cwd_ptr;
|
||||
/* 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. */
|
||||
fcwd_version_t fast_cwd_version;
|
||||
void override_win32_cwd (bool, ULONG);
|
||||
|
||||
public:
|
||||
|
@ -3510,205 +3510,132 @@ copy_cwd_str (PUNICODE_STRING tgt, PUNICODE_STRING src)
|
||||
USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
||||
DAMAGE. */
|
||||
|
||||
/* 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.
|
||||
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. */
|
||||
UNICODE_STRING Path; /* Path's Buffer member always refers
|
||||
to the following Buffer array. */
|
||||
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)
|
||||
void
|
||||
fcwd_access_t::SetFSCharacteristics (LONG val)
|
||||
{
|
||||
/* Special case FSCharacteristics. Didn't exist originally. */
|
||||
void SetFSCharacteristics (LONG val)
|
||||
switch (fast_cwd_version ())
|
||||
{
|
||||
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);
|
||||
case FCWD_OLD:
|
||||
break;
|
||||
case FCWD_W7:
|
||||
f7.FSCharacteristics = val;
|
||||
break;
|
||||
case FCWD_W8:
|
||||
f8.FSCharacteristics = val;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void SetDirHandleFromBufferPointer (PWCHAR buf_p, HANDLE dir)
|
||||
fcwd_version_t &
|
||||
fcwd_access_t::fast_cwd_version ()
|
||||
{
|
||||
return cygheap->cwd.fast_cwd_version;
|
||||
}
|
||||
|
||||
void
|
||||
fcwd_access_t::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
|
||||
fcwd_access_t::Free (PVOID heap)
|
||||
{
|
||||
/* Decrement the reference count. If it's down to 0, free
|
||||
structure from heap. */
|
||||
if (this && InterlockedDecrement (&ReferenceCount ()) == 0)
|
||||
{
|
||||
/* 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)
|
||||
/* 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
|
||||
fcwd_access_t::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)
|
||||
{
|
||||
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));
|
||||
IO_STATUS_BLOCK io;
|
||||
FILE_FS_DEVICE_INFORMATION ffdi;
|
||||
if (NT_SUCCESS (NtQueryVolumeInformationFile (dir, &io, &ffdi,
|
||||
sizeof ffdi, FileFsDeviceInformation)))
|
||||
SetFSCharacteristics (ffdi.Characteristics);
|
||||
}
|
||||
f_cwd->DirectoryHandle () = dir;
|
||||
}
|
||||
static void SetVersionFromPointer (PBYTE buf_p, bool is_buffer)
|
||||
RtlInitEmptyUnicodeString (&Path (), Buffer (),
|
||||
MAX_PATH * sizeof (WCHAR));
|
||||
copy_cwd_str (&Path (), name);
|
||||
}
|
||||
|
||||
void
|
||||
fcwd_access_t::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 ())
|
||||
{
|
||||
/* 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;
|
||||
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));
|
||||
}
|
||||
};
|
||||
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 RtlpCurDirRef pointer in
|
||||
ntdll.dll pointing to the FAST_CWD structure which constitutes the CWD.
|
||||
Unfortunately RtlpCurDirRef is not exported from ntdll.dll.
|
||||
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 fcwd_access_t **fast_cwd_ptr
|
||||
__attribute__((section (".cygwin_dll_common"), shared))
|
||||
= (fcwd_access_t **) -1;
|
||||
f_cwd->DirectoryHandle () = dir;
|
||||
}
|
||||
|
||||
void
|
||||
fcwd_access_t::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;
|
||||
}
|
||||
|
||||
#define peek32(x) (*(uint32_t *)(x))
|
||||
|
||||
@ -3718,8 +3645,9 @@ static fcwd_access_t **fast_cwd_ptr
|
||||
Therefore we have to use some knowledge to figure out the address.
|
||||
|
||||
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... */
|
||||
Windows 7 32/64 bit, Server 2008 R2 (which is only 64 bit anyway),
|
||||
and W8CP 32/64 bit. There's some hope this will still work for
|
||||
Windows 8 RTM... */
|
||||
static fcwd_access_t **
|
||||
find_fast_cwd_pointer ()
|
||||
{
|
||||
@ -3950,8 +3878,13 @@ cwdstuff::init ()
|
||||
if (win32.Buffer)
|
||||
override_win32_cwd (true, SharedUserData.DismountCount);
|
||||
else
|
||||
/* Initially re-open the cwd to allow POSIX semantics. */
|
||||
set (NULL, NULL);
|
||||
{
|
||||
/* Initialize fast_cwd stuff. */
|
||||
fast_cwd_ptr = (fcwd_access_t **) -1;
|
||||
fast_cwd_version = FCWD_W7;
|
||||
/* Initially re-open the cwd to allow POSIX semantics. */
|
||||
set (NULL, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
/* Chdir and fill out the elements of a cwdstuff struct. */
|
||||
|
Loading…
x
Reference in New Issue
Block a user