From 23c5ebeb95cb942df307946e3ced230a7c8312eb Mon Sep 17 00:00:00 2001 From: LemonBoy Date: Tue, 18 Jun 2024 20:43:51 +0200 Subject: patch 9.1.0499: MS-Windows: doesn't handle symlinks properly Problem: MS-Windows: doesn't handle symlinks properly (Timothy Madden) Solution: Implement lstat() on MS-Windows (author) lstat() differs from stat() in how it handles symbolic links, the former doesn't resolve the symlink while the latter does so. Implement a simple yet effective fallback using Win32 APIs. fixes #14933 closes: #15014 Co-authored-by: K.Takata Signed-off-by: LemonBoy Signed-off-by: K.Takata Signed-off-by: Christian Brabandt --- src/filepath.c | 6 +- src/macros.h | 6 +- src/os_mswin.c | 145 +++++++++++++++++++++++------------------ src/proto/os_mswin.pro | 1 + src/testdir/test_functions.vim | 23 +++++++ src/version.c | 2 + 6 files changed, 116 insertions(+), 67 deletions(-) (limited to 'src') diff --git a/src/filepath.c b/src/filepath.c index 9f68d7c688..788d3bbe5b 100644 --- a/src/filepath.c +++ b/src/filepath.c @@ -3645,11 +3645,15 @@ dos_expandpath( } else { + stat_T sb; + // no more wildcards, check if there is a match // remove backslashes for the remaining components only if (*path_end != 0) backslash_halve(buf + len + 1); - if (mch_getperm(buf) >= 0) // add existing file + // add existing file + if ((flags & EW_ALLLINKS) ? mch_lstat((char *)buf, &sb) >= 0 + : mch_getperm(buf) >= 0) addfile(gap, buf, flags); } } diff --git a/src/macros.h b/src/macros.h index 190778eca3..38983ac9c5 100644 --- a/src/macros.h +++ b/src/macros.h @@ -194,7 +194,11 @@ #ifdef HAVE_LSTAT # define mch_lstat(n, p) lstat((n), (p)) #else -# define mch_lstat(n, p) mch_stat((n), (p)) +# ifdef MSWIN +# define mch_lstat(n, p) vim_lstat((n), (p)) +# else +# define mch_lstat(n, p) mch_stat((n), (p)) +# endif #endif #ifdef VMS diff --git a/src/os_mswin.c b/src/os_mswin.c index 512fa40896..149883b41e 100644 --- a/src/os_mswin.c +++ b/src/os_mswin.c @@ -430,16 +430,6 @@ slash_adjust(char_u *p) } } -// Use 64-bit stat functions. -#undef stat -#undef _stat -#undef _wstat -#undef _fstat -#define stat _stat64 -#define _stat _stat64 -#define _wstat _wstat64 -#define _fstat _fstat64 - static int read_reparse_point(const WCHAR *name, char_u *buf, DWORD *buf_len) { @@ -461,58 +451,6 @@ read_reparse_point(const WCHAR *name, char_u *buf, DWORD *buf_len) return ok ? OK : FAIL; } - static int -wstat_symlink_aware(const WCHAR *name, stat_T *stp) -{ -#if (defined(_MSC_VER) && (_MSC_VER < 1900)) || defined(__MINGW32__) - // Work around for VC12 or earlier (and MinGW). _wstat() can't handle - // symlinks properly. - // VC9 or earlier: _wstat() doesn't support a symlink at all. It retrieves - // status of a symlink itself. - // VC10: _wstat() supports a symlink to a normal file, but it doesn't - // support a symlink to a directory (always returns an error). - // VC11 and VC12: _wstat() doesn't return an error for a symlink to a - // directory, but it doesn't set S_IFDIR flag. - // MinGW: Same as VC9. - int n; - BOOL is_symlink = FALSE; - HANDLE hFind, h; - DWORD attr = 0; - WIN32_FIND_DATAW findDataW; - - hFind = FindFirstFileW(name, &findDataW); - if (hFind != INVALID_HANDLE_VALUE) - { - attr = findDataW.dwFileAttributes; - if ((attr & FILE_ATTRIBUTE_REPARSE_POINT) - && (findDataW.dwReserved0 == IO_REPARSE_TAG_SYMLINK)) - is_symlink = TRUE; - FindClose(hFind); - } - if (is_symlink) - { - h = CreateFileW(name, FILE_READ_ATTRIBUTES, - FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, - OPEN_EXISTING, - (attr & FILE_ATTRIBUTE_DIRECTORY) - ? FILE_FLAG_BACKUP_SEMANTICS : 0, - NULL); - if (h != INVALID_HANDLE_VALUE) - { - int fd; - - fd = _open_osfhandle((intptr_t)h, _O_RDONLY); - n = _fstat(fd, (struct _stat *)stp); - if ((n == 0) && (attr & FILE_ATTRIBUTE_DIRECTORY)) - stp->st_mode = (stp->st_mode & ~S_IFREG) | S_IFDIR; - _close(fd); - return n; - } - } -#endif - return _wstat(name, (struct _stat *)stp); -} - char_u * resolve_appexeclink(char_u *fname) { @@ -568,11 +506,76 @@ resolve_appexeclink(char_u *fname) return utf16_to_enc(p, NULL); } +// Use 64-bit stat functions. +#undef stat +#undef _stat +#undef _wstat +#undef _fstat +#define stat _stat64 +#define _stat _stat64 +#define _wstat _wstat64 +#define _fstat _fstat64 + +/* + * Implements lstat() and stat() that can handle symlinks properly. + */ + static int +mswin_stat_impl(const WCHAR *name, stat_T *stp, const int resolve) +{ + int n; + int fd; + BOOL is_symlink = FALSE; + HANDLE hFind, h; + DWORD attr = 0; + DWORD flag = 0; + WIN32_FIND_DATAW findDataW; + +#ifdef _UCRT + if (resolve) + // Universal CRT can handle symlinks properly. + return _wstat(name, stp); +#endif + + hFind = FindFirstFileW(name, &findDataW); + if (hFind != INVALID_HANDLE_VALUE) + { + attr = findDataW.dwFileAttributes; + if ((attr & FILE_ATTRIBUTE_REPARSE_POINT) + && (findDataW.dwReserved0 == IO_REPARSE_TAG_SYMLINK)) + is_symlink = TRUE; + FindClose(hFind); + } + + // Use the plain old stat() whenever it's possible. + if (!is_symlink) + return _wstat(name, stp); + + if (!resolve && is_symlink) + flag = FILE_FLAG_OPEN_REPARSE_POINT; + if (attr & FILE_ATTRIBUTE_DIRECTORY) + flag |= FILE_FLAG_BACKUP_SEMANTICS; + + h = CreateFileW(name, FILE_READ_ATTRIBUTES, + FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, flag, + NULL); + if (h == INVALID_HANDLE_VALUE) + return -1; + + fd = _open_osfhandle((intptr_t)h, _O_RDONLY); + n = _fstat(fd, (struct _stat *)stp); + if ((n == 0) && (attr & FILE_ATTRIBUTE_DIRECTORY)) + stp->st_mode = (stp->st_mode & ~S_IFMT) | S_IFDIR; + _close(fd); + + return n; +} + /* * stat() can't handle a trailing '/' or '\', remove it first. + * When 'resolve' is true behave as lstat() wrt symlinks. */ - int -vim_stat(const char *name, stat_T *stp) + static int +stat_impl(const char *name, stat_T *stp, const int resolve) { // WinNT and later can use _MAX_PATH wide characters for a pathname, which // means that the maximum pathname is _MAX_PATH * 3 bytes when 'enc' is @@ -607,11 +610,23 @@ vim_stat(const char *name, stat_T *stp) if (wp == NULL) return -1; - n = wstat_symlink_aware(wp, stp); + n = mswin_stat_impl(wp, stp, resolve); vim_free(wp); return n; } + int +vim_lstat(const char *name, stat_T *stp) +{ + return stat_impl(name, stp, FALSE); +} + + int +vim_stat(const char *name, stat_T *stp) +{ + return stat_impl(name, stp, TRUE); +} + #if (defined(FEAT_GUI_MSWIN) && !defined(VIMDLL)) || defined(PROTO) void mch_settmode(tmode_T tmode UNUSED) diff --git a/src/proto/os_mswin.pro b/src/proto/os_mswin.pro index 47310104b8..383dcbad1e 100644 --- a/src/proto/os_mswin.pro +++ b/src/proto/os_mswin.pro @@ -11,6 +11,7 @@ int mch_isFullName(char_u *fname); void slash_adjust(char_u *p); char_u *resolve_appexeclink(char_u *fname); int vim_stat(const char *name, stat_T *stp); +int vim_lstat(const char *name, stat_T *stp); void mch_settmode(tmode_T tmode); int mch_get_shellsize(void); void mch_set_shellsize(void); diff --git a/src/testdir/test_functions.vim b/src/testdir/test_functions.vim index acdb9544fd..ba8f18fa5a 100644 --- a/src/testdir/test_functions.vim +++ b/src/testdir/test_functions.vim @@ -3822,6 +3822,29 @@ func Test_glob2() endif endfunc +func Test_glob_symlinks() + call writefile([], 'Xglob1') + + if has("win32") + silent !mklink XglobBad DoesNotExist + if v:shell_error + throw 'Skipped: cannot create symlinks' + endif + silent !mklink XglobOk Xglob1 + else + silent !ln -s DoesNotExist XglobBad + silent !ln -s Xglob1 XglobOk + endif + + " The broken symlink is excluded when alllinks is false. + call assert_equal(['Xglob1', 'XglobBad', 'XglobOk'], sort(glob('Xglob*', 0, 1, 1))) + call assert_equal(['Xglob1', 'XglobOk'], sort(glob('Xglob*', 0, 1, 0))) + + call delete('Xglob1') + call delete('XglobBad') + call delete('XglobOk') +endfunc + " Test for browse() func Test_browse() CheckFeature browse diff --git a/src/version.c b/src/version.c index 7f84d9dfde..2d83fe6555 100644 --- a/src/version.c +++ b/src/version.c @@ -704,6 +704,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 499, /**/ 498, /**/ -- cgit v1.2.3