]> granicus.if.org Git - vim/commitdiff
patch 8.2.4875: MS-Windows: some .exe files are not recognized v8.2.4875
authorLemonBoy <thatlemon@gmail.com>
Thu, 5 May 2022 19:18:16 +0000 (20:18 +0100)
committerBram Moolenaar <Bram@vim.org>
Thu, 5 May 2022 19:18:16 +0000 (20:18 +0100)
Problem:    MS-Windows: some .exe files are not recognized.
Solution:   Parse APPEXECLINK junctions. (closes #10302)

src/os_mswin.c
src/os_win32.c
src/os_win32.h
src/proto/os_mswin.pro
src/testdir/test_functions.vim
src/version.c

index 57ac5828ca6a6101979e51487ee9244d103890ee..fb9a3beb3c856e937d84e3a209ca86679ca8d32f 100644 (file)
@@ -439,6 +439,27 @@ slash_adjust(char_u *p)
 #define _wstat _wstat64
 #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)
 {
@@ -491,6 +512,61 @@ wstat_symlink_aware(const WCHAR *name, stat_T *stp)
     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.
  */
index 97595d6ebaab8ae5214d6a1054462f42f86a15f0..7224fadef59a0271c61e8f058bc3f5d100f7ed67 100644 (file)
@@ -2127,13 +2127,27 @@ theend:
     static int
 executable_file(char *name, char_u **path)
 {
-    if (mch_getperm((char_u *)name) != -1 && !mch_isdir((char_u *)name))
+    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 = FullName_save((char_u *)name, FALSE);
-       return TRUE;
+           *path = res;
+       else
+           vim_free(res);
     }
-    return FALSE;
+    else if (path != NULL)
+       *path = FullName_save((char_u *)name, FALSE);
+    return TRUE;
 }
 
 /*
index 464357070d3868fc2cfbbff9a1baec3d75d9c15b..a98951e3dfc91339fc82d6cc12ca0b99b323dd2e 100644 (file)
 #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
index e8a6cb9f15fcd0e2b542b3b080dfa8b813441d05..e3c5b60974d1b20c1ab83a2e14ad70593b82bb8d 100644 (file)
@@ -50,4 +50,5 @@ char *charset_id2name(int id);
 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 : */
index 87b3844f2b3232d3d70561b98f2f75eade6e560a..9eae10da3a3b7a942b94e46cdb39ff87c19860e5 100644 (file)
@@ -1398,6 +1398,28 @@ func Test_Executable()
   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
 
index 33e2a5f544c9a26a04b832290e31bc3567486297..59df1adaf03a96e20d424234b28de0471c788d09 100644 (file)
@@ -746,6 +746,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    4875,
 /**/
     4874,
 /**/