summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBram Moolenaar <Bram@vim.org>2019-02-10 23:18:53 +0100
committerBram Moolenaar <Bram@vim.org>2019-02-10 23:18:53 +0100
commitdce1e89be4675bcdbc9785584d3da25295481e63 (patch)
treebf5df09169e99ed6f60d67e2da4c4d29569d18a2
parent3615abb693ab2d9374ae72f0993128e32ffa3e1c (diff)
patch 8.1.0894: MS-Windows: resolve() does not return a reparse pointv8.1.0894
Problem: MS-Windows: resolve() does not return a reparse point. Solution: Improve resolve(). (Yasuhiro Matsumoto, closes #3896)
-rw-r--r--runtime/doc/eval.txt3
-rw-r--r--src/buffer.c2
-rw-r--r--src/evalfunc.c2
-rw-r--r--src/os_mswin.c182
-rw-r--r--src/proto/os_mswin.pro2
-rw-r--r--src/testdir/test_functions.vim99
-rw-r--r--src/version.c2
7 files changed, 286 insertions, 6 deletions
diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt
index 0fc249c8b2..c2150e5b90 100644
--- a/runtime/doc/eval.txt
+++ b/runtime/doc/eval.txt
@@ -7385,6 +7385,9 @@ repeat({expr}, {count}) *repeat()*
resolve({filename}) *resolve()* *E655*
On MS-Windows, when {filename} is a shortcut (a .lnk file),
returns the path the shortcut points to in a simplified form.
+ When {filename} is a symbolic link or junction point, return
+ the full path to the target. If the target of junction is
+ removed, return {filename}.
On Unix, repeat resolving symbolic links in all path
components of {filename} and return the simplified result.
To cope with link cycles, resolving of symbolic links is
diff --git a/src/buffer.c b/src/buffer.c
index e0b616498a..7dd4d34e28 100644
--- a/src/buffer.c
+++ b/src/buffer.c
@@ -4847,7 +4847,7 @@ fname_expand(
char_u *rfname;
// If the file name is a shortcut file, use the file it links to.
- rfname = mch_resolve_shortcut(*ffname);
+ rfname = mch_resolve_path(*ffname, FALSE);
if (rfname != NULL)
{
vim_free(*ffname);
diff --git a/src/evalfunc.c b/src/evalfunc.c
index c18ab31251..4c03470224 100644
--- a/src/evalfunc.c
+++ b/src/evalfunc.c
@@ -9912,7 +9912,7 @@ f_resolve(typval_T *argvars, typval_T *rettv)
{
char_u *v = NULL;
- v = mch_resolve_shortcut(p);
+ v = mch_resolve_path(p, TRUE);
if (v != NULL)
rettv->vval.v_string = v;
else
diff --git a/src/os_mswin.c b/src/os_mswin.c
index 363dc4666e..8d46e53cf1 100644
--- a/src/os_mswin.c
+++ b/src/os_mswin.c
@@ -1823,13 +1823,181 @@ mch_print_set_fg(long_u fgcol)
# include <shlobj.h>
# endif
+typedef enum _FILE_INFO_BY_HANDLE_CLASS_ {
+ FileBasicInfo_,
+ FileStandardInfo_,
+ FileNameInfo_,
+ FileRenameInfo_,
+ FileDispositionInfo_,
+ FileAllocationInfo_,
+ FileEndOfFileInfo_,
+ FileStreamInfo_,
+ FileCompressionInfo_,
+ FileAttributeTagInfo_,
+ FileIdBothDirectoryInfo_,
+ FileIdBothDirectoryRestartInfo_,
+ FileIoPriorityHintInfo_,
+ FileRemoteProtocolInfo_,
+ FileFullDirectoryInfo_,
+ FileFullDirectoryRestartInfo_,
+ FileStorageInfo_,
+ FileAlignmentInfo_,
+ FileIdInfo_,
+ FileIdExtdDirectoryInfo_,
+ FileIdExtdDirectoryRestartInfo_,
+ FileDispositionInfoEx_,
+ FileRenameInfoEx_,
+ MaximumFileInfoByHandleClass_
+} FILE_INFO_BY_HANDLE_CLASS_;
+
+typedef struct _FILE_NAME_INFO_ {
+ DWORD FileNameLength;
+ WCHAR FileName[1];
+} FILE_NAME_INFO_;
+
+typedef BOOL (WINAPI *pfnGetFileInformationByHandleEx)(
+ HANDLE hFile,
+ FILE_INFO_BY_HANDLE_CLASS_ FileInformationClass,
+ LPVOID lpFileInformation,
+ DWORD dwBufferSize);
+static pfnGetFileInformationByHandleEx pGetFileInformationByHandleEx = NULL;
+
+typedef BOOL (WINAPI *pfnGetVolumeInformationByHandleW)(
+ HANDLE hFile,
+ LPWSTR lpVolumeNameBuffer,
+ DWORD nVolumeNameSize,
+ LPDWORD lpVolumeSerialNumber,
+ LPDWORD lpMaximumComponentLength,
+ LPDWORD lpFileSystemFlags,
+ LPWSTR lpFileSystemNameBuffer,
+ DWORD nFileSystemNameSize);
+static pfnGetVolumeInformationByHandleW pGetVolumeInformationByHandleW = NULL;
+
+ char_u *
+resolve_reparse_point(char_u *fname)
+{
+ HANDLE h = INVALID_HANDLE_VALUE;
+ DWORD size;
+ char_u *rfname = NULL;
+ FILE_NAME_INFO_ *nameinfo = NULL;
+ WCHAR buff[MAX_PATH], *volnames = NULL;
+ HANDLE hv;
+ DWORD snfile, snfind;
+ static BOOL loaded = FALSE;
+
+ if (pGetFileInformationByHandleEx == NULL ||
+ pGetVolumeInformationByHandleW == NULL)
+ {
+ HMODULE hmod = GetModuleHandle("kernel32.dll");
+
+ if (loaded == TRUE)
+ return NULL;
+ pGetFileInformationByHandleEx = (pfnGetFileInformationByHandleEx)
+ GetProcAddress(hmod, "GetFileInformationByHandleEx");
+ pGetVolumeInformationByHandleW = (pfnGetVolumeInformationByHandleW)
+ GetProcAddress(hmod, "GetVolumeInformationByHandleW");
+ loaded = TRUE;
+ if (pGetFileInformationByHandleEx == NULL ||
+ pGetVolumeInformationByHandleW == NULL)
+ return NULL;
+ }
+
+ if (enc_codepage >= 0 && (int)GetACP() != enc_codepage)
+ {
+ WCHAR *p;
+
+ p = enc_to_utf16(fname, NULL);
+ if (p == NULL)
+ goto fail;
+
+ if ((GetFileAttributesW(p) & FILE_ATTRIBUTE_REPARSE_POINT) == 0)
+ {
+ vim_free(p);
+ goto fail;
+ }
+
+ h = CreateFileW(p, 0, 0, NULL, OPEN_EXISTING,
+ FILE_FLAG_BACKUP_SEMANTICS, NULL);
+ vim_free(p);
+ }
+ else
+ {
+ if ((GetFileAttributes((char*) fname) &
+ FILE_ATTRIBUTE_REPARSE_POINT) == 0)
+ goto fail;
+
+ h = CreateFile((char*) fname, 0, 0, NULL, OPEN_EXISTING,
+ FILE_FLAG_BACKUP_SEMANTICS, NULL);
+ }
+
+ if (h == INVALID_HANDLE_VALUE)
+ goto fail;
+
+ size = sizeof(FILE_NAME_INFO_) + sizeof(WCHAR) * (MAX_PATH - 1);
+ nameinfo = (FILE_NAME_INFO_*)alloc(size + sizeof(WCHAR));
+ if (nameinfo == NULL)
+ goto fail;
+
+ if (!pGetFileInformationByHandleEx(h, FileNameInfo_, nameinfo, size))
+ goto fail;
+
+ nameinfo->FileName[nameinfo->FileNameLength / sizeof(WCHAR)] = 0;
+
+ if (!pGetVolumeInformationByHandleW(
+ h, NULL, 0, &snfile, NULL, NULL, NULL, 0))
+ goto fail;
+
+ hv = FindFirstVolumeW(buff, MAX_PATH);
+ if (hv == INVALID_HANDLE_VALUE)
+ goto fail;
+
+ do {
+ GetVolumeInformationW(
+ buff, NULL, 0, &snfind, NULL, NULL, NULL, 0);
+ if (snfind == snfile)
+ break;
+ } while (FindNextVolumeW(hv, buff, MAX_PATH));
+
+ FindVolumeClose(hv);
+
+ if (snfind != snfile)
+ goto fail;
+
+ size = 0;
+ if (!GetVolumePathNamesForVolumeNameW(buff, NULL, 0, &size) &&
+ GetLastError() != ERROR_MORE_DATA)
+ goto fail;
+
+ volnames = (WCHAR*)alloc(size * sizeof(WCHAR));
+ if (!GetVolumePathNamesForVolumeNameW(buff, volnames, size,
+ &size))
+ goto fail;
+
+ wcscpy(buff, volnames);
+ if (nameinfo->FileName[0] == '\\')
+ wcscat(buff, nameinfo->FileName + 1);
+ else
+ wcscat(buff, nameinfo->FileName);
+ rfname = utf16_to_enc(buff, NULL);
+
+fail:
+ if (h != INVALID_HANDLE_VALUE)
+ CloseHandle(h);
+ if (nameinfo != NULL)
+ vim_free(nameinfo);
+ if (volnames != NULL)
+ vim_free(volnames);
+
+ return rfname;
+}
+
/*
* When "fname" is the name of a shortcut (*.lnk) resolve the file it points
* to and return that name in allocated memory.
* Otherwise NULL is returned.
*/
- char_u *
-mch_resolve_shortcut(char_u *fname)
+ static char_u *
+resolve_shortcut(char_u *fname)
{
HRESULT hr;
IShellLink *psl = NULL;
@@ -1937,6 +2105,16 @@ shortcut_end:
CoUninitialize();
return rfname;
}
+
+ char_u *
+mch_resolve_path(char_u *fname, int reparse_point)
+{
+ char_u *path = resolve_shortcut(fname);
+
+ if (path == NULL && reparse_point)
+ path = resolve_reparse_point(fname);
+ return path;
+}
#endif
#if (defined(FEAT_EVAL) && !defined(FEAT_GUI)) || defined(PROTO)
diff --git a/src/proto/os_mswin.pro b/src/proto/os_mswin.pro
index cc660a6d97..3e8d487077 100644
--- a/src/proto/os_mswin.pro
+++ b/src/proto/os_mswin.pro
@@ -37,7 +37,7 @@ int mch_print_text_out(char_u *p, int len);
void mch_print_set_font(int iBold, int iItalic, int iUnderline);
void mch_print_set_bg(long_u bgcol);
void mch_print_set_fg(long_u fgcol);
-char_u *mch_resolve_shortcut(char_u *fname);
+char_u *mch_resolve_path(char_u *fname, int reparse_point);
void win32_set_foreground(void);
void serverInitMessaging(void);
void serverSetName(char_u *name);
diff --git a/src/testdir/test_functions.vim b/src/testdir/test_functions.vim
index 69e6ce0f8f..a7caf22408 100644
--- a/src/testdir/test_functions.vim
+++ b/src/testdir/test_functions.vim
@@ -188,7 +188,7 @@ func Test_strftime()
call assert_fails('call strftime("%Y", [])', 'E745:')
endfunc
-func Test_resolve()
+func Test_resolve_unix()
if !has('unix')
return
endif
@@ -234,6 +234,103 @@ func Test_resolve()
call delete('Xlink1')
endfunc
+func s:normalize_fname(fname)
+ let ret = substitute(a:fname, '\', '/', 'g')
+ let ret = substitute(ret, '//', '/', 'g')
+ let ret = tolower(ret)
+endfunc
+
+func Test_resolve_win32()
+ if !has('win32')
+ return
+ endif
+
+ " test for shortcut file
+ if executable('cscript')
+ new Xfile
+ wq
+ call writefile([
+ \ 'Set fs = CreateObject("Scripting.FileSystemObject")',
+ \ 'Set ws = WScript.CreateObject("WScript.Shell")',
+ \ 'Set shortcut = ws.CreateShortcut("Xlink.lnk")',
+ \ 'shortcut.TargetPath = fs.BuildPath(ws.CurrentDirectory, "Xfile")',
+ \ 'shortcut.Save'
+ \], 'link.vbs')
+ silent !cscript link.vbs
+ call delete('link.vbs')
+ call assert_equal(s:normalize_fname(getcwd() . '\Xfile'), s:normalize_fname(resolve('./Xlink.lnk')))
+ call delete('Xfile')
+
+ call assert_equal(s:normalize_fname(getcwd() . '\Xfile'), s:normalize_fname(resolve('./Xlink.lnk')))
+ call delete('Xlink.lnk')
+ else
+ echomsg 'skipped test for shortcut file'
+ endif
+
+ " remove files
+ call delete('Xlink')
+ call delete('Xdir', 'd')
+ call delete('Xfile')
+
+ " test for symbolic link to a file
+ new Xfile
+ wq
+ silent !mklink Xlink Xfile
+ if !v:shell_error
+ call assert_equal(s:normalize_fname(getcwd() . '\Xfile'), s:normalize_fname(resolve('./Xlink')))
+ call delete('Xlink')
+ else
+ echomsg 'skipped test for symbolic link to a file'
+ endif
+ call delete('Xfile')
+
+ " test for junction to a directory
+ call mkdir('Xdir')
+ silent !mklink /J Xlink Xdir
+ if !v:shell_error
+ call assert_equal(s:normalize_fname(getcwd() . '\Xdir'), s:normalize_fname(resolve(getcwd() . '/Xlink')))
+
+ call delete('Xdir', 'd')
+
+ " test for junction already removed
+ call assert_equal(s:normalize_fname(getcwd() . '\Xlink'), s:normalize_fname(resolve(getcwd() . '/Xlink')))
+ call delete('Xlink')
+ else
+ echomsg 'skipped test for junction to a directory'
+ call delete('Xdir', 'd')
+ endif
+
+ " test for symbolic link to a directory
+ call mkdir('Xdir')
+ silent !mklink /D Xlink Xdir
+ if !v:shell_error
+ call assert_equal(s:normalize_fname(getcwd() . '\Xdir'), s:normalize_fname(resolve(getcwd() . '/Xlink')))
+
+ call delete('Xdir', 'd')
+
+ " test for symbolic link already removed
+ call assert_equal(s:normalize_fname(getcwd() . '\Xlink'), s:normalize_fname(resolve(getcwd() . '/Xlink')))
+ call delete('Xlink')
+ else
+ echomsg 'skipped test for symbolic link to a directory'
+ call delete('Xdir', 'd')
+ endif
+
+ " test for buffer name
+ new Xfile
+ wq
+ silent !mklink Xlink Xfile
+ if !v:shell_error
+ edit Xlink
+ call assert_equal('Xlink', bufname('%'))
+ call delete('Xlink')
+ bw!
+ else
+ echomsg 'skipped test for buffer name'
+ endif
+ call delete('Xfile')
+endfunc
+
func Test_simplify()
call assert_equal('', simplify(''))
call assert_equal('/', simplify('/'))
diff --git a/src/version.c b/src/version.c
index b86e5c8775..2563561644 100644
--- a/src/version.c
+++ b/src/version.c
@@ -784,6 +784,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
+ 894,
+/**/
893,
/**/
892,