# HG changeset patch # User Bram Moolenaar # Date 1549837808 -3600 # Node ID c4efa095f3238f56f6787fef79fd12d091c2c9c1 # Parent e3c2762bc354b361b9274cd65010f9647164e561 patch 8.1.0894: MS-Windows: resolve() does not return a reparse point commit https://github.com/vim/vim/commit/dce1e89be4675bcdbc9785584d3da25295481e63 Author: Bram Moolenaar Date: Sun Feb 10 23:18:53 2019 +0100 patch 8.1.0894: MS-Windows: resolve() does not return a reparse point Problem: MS-Windows: resolve() does not return a reparse point. Solution: Improve resolve(). (Yasuhiro Matsumoto, closes https://github.com/vim/vim/issues/3896) diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt --- 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 --- 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 --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -9912,7 +9912,7 @@ f_resolve(typval_T *argvars, typval_T *r { 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 --- a/src/os_mswin.c +++ b/src/os_mswin.c @@ -1823,13 +1823,181 @@ mch_print_set_fg(long_u fgcol) # include # 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 --- 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 le 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 --- 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 --- 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,