cygwin: unify reparse point checking code into single function

So far we had two functions checking the content of a reparse point,
readdir_check_reparse_point in fhandler_disk_file.cc for the sake of
readdir, and symlink_info::check_reparse_point for the sake of
generic path checking.

* Rename check_reparse_point_target helper to check_reparse_point_string
  and convert to static function.
* Create new check_reparse_point_target helper containing the core
  reparse point checking code
* Just call check_reparse_point_target from readdir_check_reparse_point
  and symlink_info::check_reparse_point and only perform the unique
  task in those functions.

Signed-off-by: Corinna Vinschen <corinna@vinschen.de>
This commit is contained in:
Corinna Vinschen 2017-07-24 17:33:20 +02:00
parent 42f1be581c
commit 3e80cefb16
3 changed files with 68 additions and 87 deletions

View File

@ -161,62 +161,25 @@ path_conv::isgood_inode (ino_t ino) const
return true;
}
/* Check reparse point to determine if it should be treated as a posix symlink
or as a normal file/directory. Mount points are treated as normal directories
to match behavior of other systems. Unknown reparse tags are used for
things other than links (HSM, compression, dedup), and generally should be
treated as a normal file/directory. Native symlinks and mount points are
treated as posix symlinks, depending on the prefix of the target name.
This logic needs to agree with equivalent logic in path.cc
symlink_info::check_reparse_point() .
*/
/* Check reparse point to determine if it should be treated as a
posix symlink or as a normal file/directory. Logic is explained
in detail in check_reparse_point_target in path.cc. */
static inline bool
readdir_check_reparse_point (POBJECT_ATTRIBUTES attr, bool remote)
{
bool ret = false;
IO_STATUS_BLOCK io;
NTSTATUS status;
HANDLE reph;
UNICODE_STRING subst;
IO_STATUS_BLOCK io;
tmp_pathbuf tp;
UNICODE_STRING symbuf;
bool ret = false;
if (NT_SUCCESS (NtOpenFile (&reph, READ_CONTROL, attr, &io,
FILE_SHARE_VALID_FLAGS,
FILE_OPEN_FOR_BACKUP_INTENT
| FILE_OPEN_REPARSE_POINT)))
status = NtOpenFile (&reph, READ_CONTROL, attr, &io, FILE_SHARE_VALID_FLAGS,
FILE_OPEN_FOR_BACKUP_INTENT | FILE_OPEN_REPARSE_POINT);
if (NT_SUCCESS (status))
{
PREPARSE_DATA_BUFFER rp = (PREPARSE_DATA_BUFFER)
alloca (MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
if (NT_SUCCESS (NtFsControlFile (reph, NULL, NULL, NULL,
&io, FSCTL_GET_REPARSE_POINT, NULL, 0,
(LPVOID) rp, MAXIMUM_REPARSE_DATA_BUFFER_SIZE)))
{
/* If reparse point is stored on a remote volume, lstat returns
them as normal files or dirs, not as symlink. For a description,
see the comment preceeding remote check in
symlink_info::check_reparse_point. */
if (!remote && rp->ReparseTag == IO_REPARSE_TAG_MOUNT_POINT)
{
RtlInitCountedUnicodeString (&subst,
(WCHAR *)((char *)rp->MountPointReparseBuffer.PathBuffer
+ rp->MountPointReparseBuffer.SubstituteNameOffset),
rp->MountPointReparseBuffer.SubstituteNameLength);
if (check_reparse_point_target (&subst))
ret = true;
}
else if (rp->ReparseTag == IO_REPARSE_TAG_SYMLINK)
{
if (rp->SymbolicLinkReparseBuffer.Flags & SYMLINK_FLAG_RELATIVE)
ret = true;
else
{
RtlInitCountedUnicodeString (&subst,
(WCHAR *)((char *)rp->SymbolicLinkReparseBuffer.PathBuffer
+ rp->SymbolicLinkReparseBuffer.SubstituteNameOffset),
rp->SymbolicLinkReparseBuffer.SubstituteNameLength);
if (check_reparse_point_target (&subst))
ret = true;
}
}
}
PREPARSE_DATA_BUFFER rp = (PREPARSE_DATA_BUFFER) tp.c_get ();
ret = (check_reparse_point_target (reph, remote, rp, &symbuf) > 0);
NtClose (reph);
}
return ret;

View File

@ -1898,7 +1898,7 @@ symlink_worker (const char *oldpath, const char *newpath, bool isdevice)
Win32 prefix for long pathnames! So we have to tack off
the prefix and convert the path to the "normal" syntax
for ParseDisplayName. */
WCHAR *wc = wc_path + 4;
PWCHAR wc = wc_path + 4;
if (wc[1] != L':') /* native UNC path */
*(wc += 2) = L'\\';
HRESULT res;
@ -2261,8 +2261,8 @@ symlink_info::check_sysfile (HANDLE h)
return res;
}
bool
check_reparse_point_target (PUNICODE_STRING subst)
static bool
check_reparse_point_string (PUNICODE_STRING subst)
{
/* Native mount points, or native non-relative symbolic links,
can be treated as posix symlinks only if the SubstituteName
@ -2286,15 +2286,17 @@ check_reparse_point_target (PUNICODE_STRING subst)
return false;
}
/* Return values:
<0: Negative errno.
0: No symlink.
1: Symlink.
*/
int
symlink_info::check_reparse_point (HANDLE h, bool remote)
check_reparse_point_target (HANDLE h, bool remote, PREPARSE_DATA_BUFFER rp,
PUNICODE_STRING psymbuf)
{
tmp_pathbuf tp;
NTSTATUS status;
IO_STATUS_BLOCK io;
PREPARSE_DATA_BUFFER rp = (PREPARSE_DATA_BUFFER) tp.c_get ();
UNICODE_STRING subst;
char srcbuf[SYMLINK_MAX + 7];
/* On remote drives or under heavy load, NtFsControlFile can return with
STATUS_PENDING. If so, instead of creating an event object, just set
@ -2319,9 +2321,9 @@ symlink_info::check_reparse_point (HANDLE h, bool remote)
the followup call to NtFsControlFile(FSCTL_GET_REPARSE_POINT)
returns with STATUS_NOT_A_REPARSE_POINT. That's quite buggy, but
we cope here with this scenario by not setting an error code. */
if (status != STATUS_NOT_A_REPARSE_POINT)
set_error (EIO);
return 0;
if (status == STATUS_NOT_A_REPARSE_POINT)
return 0;
return -EIO;
}
if (rp->ReparseTag == IO_REPARSE_TAG_SYMLINK)
{
@ -2329,18 +2331,13 @@ symlink_info::check_reparse_point (HANDLE h, bool remote)
to, say, C:\foo, it will be handled as if the target is the local file
C:\foo. That comes in handy since that's how symlinks are treated under
POSIX as well. */
RtlInitCountedUnicodeString (&subst,
(WCHAR *)((char *)rp->SymbolicLinkReparseBuffer.PathBuffer
+ rp->SymbolicLinkReparseBuffer.SubstituteNameOffset),
rp->SymbolicLinkReparseBuffer.SubstituteNameLength);
if (!(rp->SymbolicLinkReparseBuffer.Flags & SYMLINK_FLAG_RELATIVE) &&
!check_reparse_point_target (&subst))
{
/* Unsupport native symlink target prefix. Not treated as symlink.
The return value of -1 indicates name needs to be opened without
FILE_OPEN_REPARSE_POINT flag. */
return -1;
}
RtlInitCountedUnicodeString (psymbuf,
(PWCHAR)((PBYTE) rp->SymbolicLinkReparseBuffer.PathBuffer
+ rp->SymbolicLinkReparseBuffer.SubstituteNameOffset),
rp->SymbolicLinkReparseBuffer.SubstituteNameLength);
if ((rp->SymbolicLinkReparseBuffer.Flags & SYMLINK_FLAG_RELATIVE) ||
check_reparse_point_string (psymbuf))
return 1;
}
else if (!remote && rp->ReparseTag == IO_REPARSE_TAG_MOUNT_POINT)
{
@ -2349,28 +2346,48 @@ symlink_info::check_reparse_point (HANDLE h, bool remote)
target of the junction is the remote directory it is supposed to
point to. If we handle it as symlink, it will be mistreated as
pointing to a dir on the local system. */
RtlInitCountedUnicodeString (&subst,
(WCHAR *)((char *)rp->MountPointReparseBuffer.PathBuffer
+ rp->MountPointReparseBuffer.SubstituteNameOffset),
RtlInitCountedUnicodeString (psymbuf,
(PWCHAR)((PBYTE) rp->MountPointReparseBuffer.PathBuffer
+ rp->MountPointReparseBuffer.SubstituteNameOffset),
rp->MountPointReparseBuffer.SubstituteNameLength);
if (!check_reparse_point_target (&subst))
if (RtlEqualUnicodePathPrefix (psymbuf, &ro_u_volume, TRUE))
{
/* Volume mount point, or unsupported native target prefix. Not
treated as symlink. The return value of -1 indicates name needs
to be opened without FILE_OPEN_REPARSE_POINT flag. */
return -1;
/* Volume mount point. Not treated as symlink. The return
value -EPERM is a hint for the caller to treat this as a
volume mount point. */
return -EPERM;
}
if (check_reparse_point_string (psymbuf))
return 1;
}
else
return 0;
}
int
symlink_info::check_reparse_point (HANDLE h, bool remote)
{
tmp_pathbuf tp;
PREPARSE_DATA_BUFFER rp = (PREPARSE_DATA_BUFFER) tp.c_get ();
UNICODE_STRING symbuf;
char srcbuf[SYMLINK_MAX + 7];
int ret = check_reparse_point_target (h, remote, rp, &symbuf);
if (ret <= 0)
{
if (ret == -EIO)
{
set_error (EIO);
return 0;
}
/* Maybe it's a reparse point, but it's certainly not one we recognize.
Drop REPARSE attribute so we don't try to use the flag accidentally.
It's just some arbitrary file or directory for us. */
fileattr &= ~FILE_ATTRIBUTE_REPARSE_POINT;
return 0;
return ret;
}
sys_wcstombs (srcbuf, SYMLINK_MAX + 7, subst.Buffer,
subst.Length / sizeof (WCHAR));
/* ret is > 0, so it's a reparse point, path in symbuf. */
sys_wcstombs (srcbuf, SYMLINK_MAX + 7, symbuf.Buffer,
symbuf.Length / sizeof (WCHAR));
pflags |= PATH_SYMLINK | PATH_REP;
/* A symlink is never a directory. */
fileattr &= ~FILE_ATTRIBUTE_DIRECTORY;
@ -2993,7 +3010,7 @@ restart:
filesystem information again, but with a NULL handle.
This does what we want because fs_info::update opens the
handle without FILE_OPEN_REPARSE_POINT. */
if (res == -1)
if (res < 0)
fs.update (&upath, NULL);
}
}

View File

@ -88,7 +88,8 @@ enum path_types
};
NTSTATUS file_get_fai (HANDLE, PFILE_ALL_INFORMATION);
bool check_reparse_point_target (PUNICODE_STRING);
int check_reparse_point_target (HANDLE, bool, PREPARSE_DATA_BUFFER,
PUNICODE_STRING);
class symlink_info;