]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 9.1.0499: MS-Windows: doesn't handle symlinks properly v9.1.0499
authorLemonBoy <thatlemon@gmail.com>
Tue, 18 Jun 2024 18:43:51 +0000 (20:43 +0200)
committerChristian Brabandt <cb@256bit.org>
Tue, 18 Jun 2024 18:43:51 +0000 (20:43 +0200)
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>
src/filepath.c
src/macros.h
src/os_mswin.c
src/proto/os_mswin.pro
src/testdir/test_functions.vim
src/version.c

index 9f68d7c6881e7ad7e42ebd2b65952970fce6d4ca..788d3bbe5b3cac75ad6061362edb789180da1b0f 100644 (file)
@@ -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);
            }
        }
index 190778eca39e80dd80ce273d919a3776fba1631b..38983ac9c54e7ffaa61df8825e1d66ff30fca460 100644 (file)
 #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
index 512fa408960dc7471854e90c1debcbd469acbadc..149883b41e5012b14f75b0a7024c31981b30386c 100644 (file)
@@ -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)
index 47310104b8ec6ca3acdccaff9e57c558dd6e2230..383dcbad1e924f3ee6aeaea5f76561389acf3228 100644 (file)
@@ -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);
index acdb9544fd62b4ce6498056e56b6a35d551ef089..ba8f18fa5ae53eb77dbc2bf44bf02bbf80576a13 100644 (file)
@@ -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
index 7f84d9dfde7baa9978564422930dbf441af73479..2d83fe6555548c47b33239abcc9d4f9ec9a529e0 100644 (file)
@@ -704,6 +704,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    499,
 /**/
     498,
 /**/