diff --git a/winsup/cygwin/path.cc b/winsup/cygwin/path.cc index 7d1d23d72..21cfdc33e 100644 --- a/winsup/cygwin/path.cc +++ b/winsup/cygwin/path.cc @@ -2261,6 +2261,50 @@ symlink_info::check_sysfile (HANDLE h) return res; } +static bool +is_absolute_reparse_target_posix_symlink_compatible (const wchar_t* path, size_t len) +{ + /* Native junction reparse points, or native non-relative + symbolic links, can be treated as posix symlinks only + if the SubstituteName can be converted from a native NT + object namespace name to a win32 name. We only know how + to convert names with two prefixes : + "\??\UNC\..." + "\??\X:..." + Other reparse points will be treated as files or + directories, not as posix symlinks. Possible values + include: + "\??\Volume{..." + "\Device\HarddiskVolume1\..." + "\Device\Lanman\...\..." + */ + wchar_t d; + if (len >= 6 && + path[0] == '\\' && + path[1] == '?' && + path[2] == '?' && + path[3] == '\\') + { + if (path[5] == ':' && (len == 6 || path[6] == '\\')) + { + d = path[4]; + if ((d >= 'a' && d <= 'z') || (d >= 'A' && d <= 'Z')) + { + return true; + } + } + else if (len > 7 && + ((d = path[4]) == 'u' || d == 'U') && + ((d = path[5]) == 'n' || d == 'N') && + ((d = path[6]) == 'c' || d == 'C') && + path[7] == '\\') + { + return true; + } + } + return false; +} + int symlink_info::check_reparse_point (HANDLE h, bool remote) { @@ -2299,14 +2343,27 @@ symlink_info::check_reparse_point (HANDLE h, bool remote) return 0; } if (rp->ReparseTag == IO_REPARSE_TAG_SYMLINK) - /* Windows evaluates native symlink literally. If a remote symlink points - 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, + { + /* Windows evaluates native symlink literally. If a remote symlink + points 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) && + !is_absolute_reparse_target_posix_symlink_compatible( + subst.Buffer, subst.Length/sizeof(WCHAR))) + { + /* The absolute native symbolic link target does not + have a prefix we understand, so can not generate a + useful value to return from readlink(). Treat as a + normal file or directory. */ + fileattr &= ~FILE_ATTRIBUTE_REPARSE_POINT; + return 0; + } + } else if (!remote && rp->ReparseTag == IO_REPARSE_TAG_MOUNT_POINT) { /* Don't handle junctions on remote filesystems as symlinks. This type @@ -2325,6 +2382,15 @@ symlink_info::check_reparse_point (HANDLE h, bool remote) volume mount point. */ return -1; } + else if (!is_absolute_reparse_target_posix_symlink_compatible( + subst.Buffer, subst.Length/sizeof(WCHAR))) + { + /* The junction target does not have a prefix we + understand, so can not generate a useful value to + return from readlink(). Treat as a normal directory. */ + fileattr &= ~FILE_ATTRIBUTE_REPARSE_POINT; + return 0; + } } else { @@ -2397,6 +2463,17 @@ symlink_info::posixify (char *srcbuf) The above rules are used exactly the same way on Cygwin specific symlinks (sysfiles and shortcuts) to eliminate non-POSIX paths in the output. */ + /* The following logic should use the relative flag from native NT symbolic + link reparse data. This logic functions without the flag because it makes + the following assumptions: + 1) Relative symlink targets never start with "\??\". + 2) Absolute symlink and junction targets always start with "\??\". + The first assumption, though arguably wrong, is unlikely to cause + issues since '?' characters are illegal on windows file systems. + The second assumption is incorrect and would cause compatibility + issues, but code in check_reparse_point() above makes sure the non- + conformers do not make it here. */ + /* Eliminate native NT prefixes. */ if (srcbuf[0] == '\\' && !strncmp (srcbuf + 1, "??\\", 3)) {