0
0
mirror of https://github.com/vim/vim.git synced 2025-07-26 11:04:33 -04:00

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 #3896)
This commit is contained in:
Bram Moolenaar 2019-02-10 23:18:53 +01:00
parent 3615abb693
commit dce1e89be4
7 changed files with 286 additions and 6 deletions

View 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

View 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);

View 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

View 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)

View 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);

View 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('/'))

View File

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