summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLemonBoy <thatlemon@gmail.com>2024-06-18 20:43:51 +0200
committerChristian Brabandt <cb@256bit.org>2024-06-18 20:43:51 +0200
commit23c5ebeb95cb942df307946e3ced230a7c8312eb (patch)
treeba0d773ef75a8b7a58b77cb9cb675fce1d28827c
parenta821b609f9bb9daef032fe1cb8fb95995822e367 (diff)
patch 9.1.0499: MS-Windows: doesn't handle symlinks properlyv9.1.0499
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 <kentkt@csc.jp> Signed-off-by: LemonBoy <thatlemon@gmail.com> Signed-off-by: K.Takata <kentkt@csc.jp> Signed-off-by: Christian Brabandt <cb@256bit.org>
-rw-r--r--src/filepath.c6
-rw-r--r--src/macros.h6
-rw-r--r--src/os_mswin.c145
-rw-r--r--src/proto/os_mswin.pro1
-rw-r--r--src/testdir/test_functions.vim23
-rw-r--r--src/version.c2
6 files changed, 116 insertions, 67 deletions
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
@@ -705,6 +705,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
+ 499,
+/**/
498,
/**/
497,