0
0
mirror of https://github.com/vim/vim.git synced 2025-09-25 03:54:15 -04:00

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 <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>
This commit is contained in:
LemonBoy
2024-06-18 20:43:51 +02:00
committed by Christian Brabandt
parent a821b609f9
commit 23c5ebeb95
6 changed files with 116 additions and 67 deletions

View File

@@ -3645,11 +3645,15 @@ dos_expandpath(
} }
else else
{ {
stat_T sb;
// no more wildcards, check if there is a match // no more wildcards, check if there is a match
// remove backslashes for the remaining components only // remove backslashes for the remaining components only
if (*path_end != 0) if (*path_end != 0)
backslash_halve(buf + len + 1); 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); addfile(gap, buf, flags);
} }
} }

View File

@@ -194,7 +194,11 @@
#ifdef HAVE_LSTAT #ifdef HAVE_LSTAT
# define mch_lstat(n, p) lstat((n), (p)) # define mch_lstat(n, p) lstat((n), (p))
#else #else
# ifdef MSWIN
# define mch_lstat(n, p) vim_lstat((n), (p))
# else
# define mch_lstat(n, p) mch_stat((n), (p)) # define mch_lstat(n, p) mch_stat((n), (p))
# endif
#endif #endif
#ifdef VMS #ifdef VMS

View 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 static int
read_reparse_point(const WCHAR *name, char_u *buf, DWORD *buf_len) 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; 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 * char_u *
resolve_appexeclink(char_u *fname) resolve_appexeclink(char_u *fname)
{ {
@@ -568,11 +506,76 @@ resolve_appexeclink(char_u *fname)
return utf16_to_enc(p, NULL); 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. * stat() can't handle a trailing '/' or '\', remove it first.
* When 'resolve' is true behave as lstat() wrt symlinks.
*/ */
int static int
vim_stat(const char *name, stat_T *stp) stat_impl(const char *name, stat_T *stp, const int resolve)
{ {
// WinNT and later can use _MAX_PATH wide characters for a pathname, which // 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 // 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) if (wp == NULL)
return -1; return -1;
n = wstat_symlink_aware(wp, stp); n = mswin_stat_impl(wp, stp, resolve);
vim_free(wp); vim_free(wp);
return n; 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) #if (defined(FEAT_GUI_MSWIN) && !defined(VIMDLL)) || defined(PROTO)
void void
mch_settmode(tmode_T tmode UNUSED) mch_settmode(tmode_T tmode UNUSED)

View File

@@ -11,6 +11,7 @@ int mch_isFullName(char_u *fname);
void slash_adjust(char_u *p); void slash_adjust(char_u *p);
char_u *resolve_appexeclink(char_u *fname); char_u *resolve_appexeclink(char_u *fname);
int vim_stat(const char *name, stat_T *stp); int vim_stat(const char *name, stat_T *stp);
int vim_lstat(const char *name, stat_T *stp);
void mch_settmode(tmode_T tmode); void mch_settmode(tmode_T tmode);
int mch_get_shellsize(void); int mch_get_shellsize(void);
void mch_set_shellsize(void); void mch_set_shellsize(void);

View File

@@ -3822,6 +3822,29 @@ func Test_glob2()
endif endif
endfunc 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() " Test for browse()
func Test_browse() func Test_browse()
CheckFeature browse CheckFeature browse

View File

@@ -704,6 +704,8 @@ static char *(features[]) =
static int included_patches[] = static int included_patches[] =
{ /* Add new patch number below this line */ { /* Add new patch number below this line */
/**/
499,
/**/ /**/
498, 498,
/**/ /**/