From f30b3a50fd0e1fb9defa438e2598c6f5d7daa11b Mon Sep 17 00:00:00 2001 From: Matthew Flatt Date: Tue, 27 Jan 2015 17:37:52 -0700 Subject: [PATCH] Windows: fix problems with junctions and symlinks Racket wasn't reparsing correctly; the strategy worked ok for links created by `mklink`, but not with other tools that leave the "printed name" field blank. A consequence of various fixes is that reparse points like "My Documents" (in a typical configuration) correctly resolve to actual paths like "Documents". Finally, `directory-exists?` didn't handle root directories like "C:/" correctly. The query would actually report properties of the OS-level current working directory, and when junctions are involved, the current directory can be a link instead of a directory. Relevant to PR 14950 and PR 14912 --- racket/src/racket/src/file.c | 46 ++++++++++++++++++++++++++++++++---- 1 file changed, 42 insertions(+), 4 deletions(-) diff --git a/racket/src/racket/src/file.c b/racket/src/racket/src/file.c index 0541a5844d..3a7badb402 100644 --- a/racket/src/racket/src/file.c +++ b/racket/src/racket/src/file.c @@ -2241,7 +2241,7 @@ static char *UNC_readlink(const char *fn) if (!DeviceIoControlProc) return NULL; - h = CreateFileW(WIDE_PATH(fn), GENERIC_READ, + h = CreateFileW(WIDE_PATH(fn), FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | mzFILE_FLAG_OPEN_REPARSE_POINT, @@ -2276,13 +2276,42 @@ static char *UNC_readlink(const char *fn) return NULL; } - off = rp->u.SymbolicLinkReparseBuffer.PrintNameOffset; - len = rp->u.SymbolicLinkReparseBuffer.PrintNameLength; + if (rp->ReparseTag == IO_REPARSE_TAG_SYMLINK) { + off = rp->u.SymbolicLinkReparseBuffer.SubstituteNameOffset; + len = rp->u.SymbolicLinkReparseBuffer.SubstituteNameLength; + if (!len) { + off = rp->u.SymbolicLinkReparseBuffer.PrintNameOffset; + len = rp->u.SymbolicLinkReparseBuffer.PrintNameLength; + } + } else { + off = rp->u.MountPointReparseBuffer.SubstituteNameOffset; + len = rp->u.MountPointReparseBuffer.SubstituteNameLength; + if (!len) { + off = rp->u.MountPointReparseBuffer.PrintNameOffset; + len = rp->u.MountPointReparseBuffer.PrintNameLength; + } + } + lk = (wchar_t *)scheme_malloc_atomic(len + 2); - memcpy(lk, (char *)rp->u.SymbolicLinkReparseBuffer.PathBuffer + off, len); + if (rp->ReparseTag == IO_REPARSE_TAG_SYMLINK) + memcpy(lk, (char *)rp->u.SymbolicLinkReparseBuffer.PathBuffer + off, len); + else + memcpy(lk, (char *)rp->u.MountPointReparseBuffer.PathBuffer + off, len); + lk[len>>1] = 0; + if ((lk[0] == '\\') && (lk[1] == '?') && (lk[2] == '?') && (lk[3] == '\\')) { + /* "?\\" is a prefix that means "unparsed", or something like that */ + memmove(lk, lk+4, len - 8); + len -= 8; + lk[len>>1] = 0; + } + + /* Make sure it's not empty, because that would form a bad path: */ + if (!lk[0]) + return NULL; + return NARROW_PATH(lk); } @@ -2434,6 +2463,15 @@ static int UNC_stat(char *dirname, int len, int *flags, int *isdir, int *islink, drive_end -= 5; copy[len] = 0; } + /* If we ended up with "X:[/]", then add a "." at the end so that we get information + for the drive, not the current directory of the drive: */ + if (is_drive_letter(copy[0]) && (copy[1] == ':') + && (!copy[2] + || (IS_A_DOS_SEP(copy[2]) && !copy[3]))) { + copy[2] = '\\'; + copy[3] = '.'; + copy[4] = 0; + } if (!GetFileAttributesExW(WIDE_PATH(copy), GetFileExInfoStandard, &fd)) { errno = -1;