]> granicus.if.org Git - vim/commitdiff
patch 8.1.0894: MS-Windows: resolve() does not return a reparse point v8.1.0894
authorBram Moolenaar <Bram@vim.org>
Sun, 10 Feb 2019 22:18:53 +0000 (23:18 +0100)
committerBram Moolenaar <Bram@vim.org>
Sun, 10 Feb 2019 22:18:53 +0000 (23:18 +0100)
Problem:    MS-Windows: resolve() does not return a reparse point.
Solution:   Improve resolve(). (Yasuhiro Matsumoto, closes #3896)

runtime/doc/eval.txt
src/buffer.c
src/evalfunc.c
src/os_mswin.c
src/proto/os_mswin.pro
src/testdir/test_functions.vim
src/version.c

index 0fc249c8b27e135d8c18bd31f1641a873c82ed73..c2150e5b90b801faaa0f48c3157211240e2cf309 100644 (file)
@@ -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
index e0b616498a7b97d7be1d9748466befca8931a9e4..7dd4d34e281296891931bc5aaa1afd46f303f4e6 100644 (file)
@@ -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);
index c18ab312510d9c601160f5d28f60f78f91f65c40..4c034702245984f5a64812d185a8e9baca1a9a9d 100644 (file)
@@ -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
index 363dc4666e1d7522d8025bde31c39126c0929e9c..8d46e53cf1ebc72b3aecfc2ada34f4b02534a042 100644 (file)
@@ -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)
index cc660a6d9719be1f10b3e7402fca3de7c73fcaba..3e8d4870777cc4c28e4effec8570fb7f3be2c4d9 100644 (file)
@@ -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);
index 69e6ce0f8f82b9195d46a25601db27488cb4d8d3..a7caf224082111949d7dc564696b5e469512c913 100644 (file)
@@ -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('/'))
index b86e5c877529617ea80d384043103e9a300b59b8..2563561644560c05bf5c12a924cfc82582e887aa 100644 (file)
@@ -783,6 +783,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    894,
 /**/
     893,
 /**/