Patch 8.2.4875
Problem: MS-Windows: some .exe files are not recognized.
Solution: Parse APPEXECLINK junctions. (closes #10302)
Files: src/os_mswin.c, src/proto/
os_mswin.pro, src/os_win32.c,
src/os_win32.h, src/testdir/test_functions.vim
*** ../vim-8.2.4874/src/os_mswin.c 2022-01-24 11:23:59.859900461 +0000
--- src/os_mswin.c 2022-05-05 20:15:19.984347430 +0100
***************
*** 440,445 ****
--- 440,466 ----
#define _fstat _fstat64
static int
+ read_reparse_point(const WCHAR *name, char_u *buf, DWORD *buf_len)
+ {
+ HANDLE h;
+ BOOL ok;
+
+ h = CreateFileW(name, FILE_READ_ATTRIBUTES,
+ FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
+ OPEN_EXISTING,
+ FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT,
+ NULL);
+ if (h == INVALID_HANDLE_VALUE)
+ return FAIL;
+
+ ok = DeviceIoControl(h, FSCTL_GET_REPARSE_POINT, NULL, 0, buf, *buf_len,
+ buf_len, NULL);
+ CloseHandle(h);
+
+ return ok ? OK : FAIL;
+ }
+
+ static int
wstat_symlink_aware(const WCHAR *name, stat_T *stp)
{
#if (defined(_MSC_VER) && (_MSC_VER < 1900)) || defined(__MINGW32__)
***************
*** 491,496 ****
--- 512,572 ----
return _wstat(name, (struct _stat *)stp);
}
+ char_u *
+ resolve_appexeclink(char_u *fname)
+ {
+ DWORD attr = 0;
+ int idx;
+ WCHAR *p, *end, *wname;
+ // The buffer size is arbitrarily chosen to be "big enough" (TM), the
+ // ceiling should be around 16k.
+ char_u buf[4096];
+ DWORD buf_len = sizeof(buf);
+ REPARSE_DATA_BUFFER *rb = (REPARSE_DATA_BUFFER *)buf;
+
+ wname = enc_to_utf16(fname, NULL);
+ if (wname == NULL)
+ return NULL;
+
+ attr = GetFileAttributesW(wname);
+ if (attr == INVALID_FILE_ATTRIBUTES ||
+ (attr & FILE_ATTRIBUTE_REPARSE_POINT) == 0)
+ {
+ vim_free(wname);
+ return NULL;
+ }
+
+ // The applinks are similar to symlinks but with a huge difference: they can
+ // only be executed, any other I/O operation on them is bound to fail with
+ // ERROR_FILE_NOT_FOUND even though the file exists.
+ if (read_reparse_point(wname, buf, &buf_len) == FAIL)
+ {
+ vim_free(wname);
+ return NULL;
+ }
+ vim_free(wname);
+
+ if (rb->ReparseTag != IO_REPARSE_TAG_APPEXECLINK)
+ return NULL;
+
+ // The (undocumented) reparse buffer contains a set of N null-terminated
+ // Unicode strings, the application path is stored in the third one.
+ if (rb->AppExecLinkReparseBuffer.StringCount < 3)
+ return NULL;
+
+ p = rb->AppExecLinkReparseBuffer.StringList;
+ end = p + rb->ReparseDataLength / sizeof(WCHAR);
+ for (idx = 0; p < end
+ && idx < (int)rb->AppExecLinkReparseBuffer.StringCount
+ && idx != 2; )
+ {
+ if ((*p++ == L'\0'))
+ ++idx;
+ }
+
+ return utf16_to_enc(p, NULL);
+ }
+
/*
* stat() can't handle a trailing '/' or '\', remove it first.
*/
*** ../vim-8.2.4874/src/proto/
os_mswin.pro 2020-05-17 13:06:07.313201564 +0100
--- src/proto/
os_mswin.pro 2022-05-05 20:15:19.984347430 +0100
***************
*** 50,53 ****
--- 50,54 ----
char *quality_id2name(DWORD id);
int get_logfont(LOGFONTW *lf, char_u *name, HDC printer_dc, int verbose);
void channel_init_winsock(void);
+ char_u *resolve_appexeclink(char_u *fname);
/* vim: set ft=c : */
*** ../vim-8.2.4874/src/os_win32.c 2022-05-03 11:01:59.058826963 +0100
--- src/os_win32.c 2022-05-05 20:15:19.984347430 +0100
***************
*** 2127,2139 ****
static int
executable_file(char *name, char_u **path)
{
! if (mch_getperm((char_u *)name) != -1 && !mch_isdir((char_u *)name))
{
if (path != NULL)
! *path = FullName_save((char_u *)name, FALSE);
! return TRUE;
}
! return FALSE;
}
/*
--- 2127,2153 ----
static int
executable_file(char *name, char_u **path)
{
! int attrs = win32_getattrs((char_u *)name);
!
! // The file doesn't exist or is a folder.
! if (attrs == -1 || (attrs & FILE_ATTRIBUTE_DIRECTORY))
! return FALSE;
! // Check if the file is an AppExecLink, a special alias used by Windows
! // Store for its apps.
! if (attrs & FILE_ATTRIBUTE_REPARSE_POINT)
{
+ char_u *res = resolve_appexeclink((char_u *)name);
+ if (res == NULL)
+ return FALSE;
+ // The path is already absolute.
if (path != NULL)
! *path = res;
! else
! vim_free(res);
}
! else if (path != NULL)
! *path = FullName_save((char_u *)name, FALSE);
! return TRUE;
}
/*
*** ../vim-8.2.4874/src/os_win32.h 2022-02-04 10:45:34.944240854 +0000
--- src/os_win32.h 2022-05-05 20:15:19.984347430 +0100
***************
*** 126,131 ****
--- 126,170 ----
#ifndef IO_REPARSE_TAG_SYMLINK
# define IO_REPARSE_TAG_SYMLINK 0xA000000C
#endif
+ #ifndef IO_REPARSE_TAG_APPEXECLINK
+ # define IO_REPARSE_TAG_APPEXECLINK 0x8000001B
+ #endif
+
+ /*
+ * Definition of the reparse point buffer.
+ * This is usually defined in the DDK, copy the definition here to avoid
+ * adding it as a dependence only for a single structure.
+ */
+ typedef struct _REPARSE_DATA_BUFFER {
+ ULONG ReparseTag;
+ USHORT ReparseDataLength;
+ USHORT Reserved;
+ union {
+ struct {
+ USHORT SubstituteNameOffset;
+ USHORT SubstituteNameLength;
+ USHORT PrintNameOffset;
+ USHORT PrintNameLength;
+ ULONG Flags;
+ WCHAR PathBuffer[1];
+ } SymbolicLinkReparseBuffer;
+ struct {
+ USHORT SubstituteNameOffset;
+ USHORT SubstituteNameLength;
+ USHORT PrintNameOffset;
+ USHORT PrintNameLength;
+ WCHAR PathBuffer[1];
+ } MountPointReparseBuffer;
+ struct {
+ UCHAR DataBuffer[1];
+ } GenericReparseBuffer;
+ struct
+ {
+ ULONG StringCount;
+ WCHAR StringList[1];
+ } AppExecLinkReparseBuffer;
+ } DUMMYUNIONNAME;
+ } REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER;
#ifdef _MSC_VER
// Support for __try / __except. All versions of MSVC are
*** ../vim-8.2.4874/src/testdir/test_functions.vim 2022-05-03 11:01:59.058826963 +0100
--- src/testdir/test_functions.vim 2022-05-05 20:15:19.984347430 +0100
***************
*** 1398,1403 ****
--- 1398,1425 ----
endif
endfunc
+ func Test_executable_windows_store_apps()
+ CheckMSWindows
+
+ " Windows Store apps install some 'decoy' .exe that require some careful
+ " handling as they behave similarly to symlinks.
+ let app_dir = expand("$LOCALAPPDATA\\Microsoft\\WindowsApps")
+ if !isdirectory(app_dir)
+ return
+ endif
+
+ let save_path = $PATH
+ let $PATH = app_dir
+ " Ensure executable() finds all the app .exes
+ for entry in readdir(app_dir)
+ if entry =~ '\.exe$'
+ call assert_true(executable(entry))
+ endif
+ endfor
+
+ let $PATH = save_path
+ endfunc
+
func Test_executable_longname()
CheckMSWindows
*** ../vim-8.2.4874/src/version.c 2022-05-05 19:23:03.123905358 +0100
--- src/version.c 2022-05-05 20:17:13.140209924 +0100
***************
*** 748,749 ****
--- 748,751 ----
{ /* Add new patch number below this line */
+ /**/
+ 4875,
/**/
--
hundred-and-one symptoms of being an internet addict:
104. When people ask about the Presidential Election you ask "Which country?"
/// Bram Moolenaar -- Br...@Moolenaar.net --
http://www.Moolenaar.net \\\
/// \\\
\\\ sponsor Vim, vote for features --
http://www.Vim.org/sponsor/ ///
\\\ help me help AIDS victims --
http://ICCF-Holland.org ///