summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBram Moolenaar <Bram@vim.org>2020-05-30 18:37:55 +0200
committerBram Moolenaar <Bram@vim.org>2020-05-30 18:37:55 +0200
commit95da136142628e06425f9d9eb2d1ca56a9e48feb (patch)
treedb98440ec0c907019fab436cecf7499b1b9ac572
parent041c7107f23d3b49ab62c1d7e36af90421db8b63 (diff)
patch 8.2.0850: MS-Windows: exepath() works different from cmd.exev8.2.0850
Problem: MS-Windows: exepath() works different from cmd.exe. Solution: Make exepath() work better on MS-Windows. (closes #6115)
-rw-r--r--runtime/doc/eval.txt2
-rw-r--r--src/os_win32.c281
-rw-r--r--src/testdir/test_functions.vim24
-rw-r--r--src/version.c2
4 files changed, 208 insertions, 101 deletions
diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt
index 89f011b8cf..0585ce020f 100644
--- a/runtime/doc/eval.txt
+++ b/runtime/doc/eval.txt
@@ -4034,7 +4034,7 @@ executable({expr}) *executable()*
On MS-Windows the ".exe", ".bat", etc. can optionally be
included. Then the extensions in $PATHEXT are tried. Thus if
"foo.exe" does not exist, "foo.exe.bat" can be found. If
- $PATHEXT is not set then ".exe;.com;.bat;.cmd" is used. A dot
+ $PATHEXT is not set then ".com;.exe;.bat;.cmd" is used. A dot
by itself can be used in $PATHEXT to try using the name
without an extension. When 'shell' looks like a Unix shell,
then the name is also tried without adding an extension.
diff --git a/src/os_win32.c b/src/os_win32.c
index 2c5670d9dd..6356076eb9 100644
--- a/src/os_win32.c
+++ b/src/os_win32.c
@@ -2080,57 +2080,200 @@ theend:
#endif
/*
+ * Return TRUE if "name" is an executable file, FALSE if not or it doesn't exist.
+ * When returning TRUE and "path" is not NULL save the path and set "*path" to
+ * the allocated memory.
+ * TODO: Should somehow check if it's really executable.
+ */
+ 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;
+}
+
+/*
* If "use_path" is TRUE: Return TRUE if "name" is in $PATH.
* If "use_path" is FALSE: Return TRUE if "name" exists.
+ * If "use_pathext" is TRUE search "name" with extensions in $PATHEXT.
* When returning TRUE and "path" is not NULL save the path and set "*path" to
* the allocated memory.
- * TODO: Should somehow check if it's really executable.
*/
static int
-executable_exists(char *name, char_u **path, int use_path)
+executable_exists(char *name, char_u **path, int use_path, int use_pathext)
{
- WCHAR *p;
- WCHAR fnamew[_MAX_PATH];
- WCHAR *dumw;
- WCHAR *wcurpath, *wnewpath;
- long n;
+ // WinNT and later can use _MAX_PATH wide characters for a pathname, which
+ // means that the maximum pathname is _MAX_PATH * 3 bytes when 'enc' is
+ // UTF-8.
+ char_u buf[_MAX_PATH * 3];
+ size_t len = STRLEN(name);
+ size_t tmplen;
+ char_u *p, *e, *e2;
+ char_u *pathbuf = NULL;
+ char_u *pathext = NULL;
+ char_u *pathextbuf = NULL;
+ int noext = FALSE;
+ int retval = FALSE;
- if (!use_path)
+ if (len >= sizeof(buf)) // safety check
+ return FALSE;
+
+ // Using the name directly when a Unix-shell like 'shell'.
+ if (strstr((char *)gettail(p_sh), "sh") != NULL)
+ noext = TRUE;
+
+ if (use_pathext)
{
- if (mch_getperm((char_u *)name) != -1 && !mch_isdir((char_u *)name))
+ pathext = mch_getenv("PATHEXT");
+ if (pathext == NULL)
+ pathext = (char_u *)".com;.exe;.bat;.cmd";
+
+ if (noext == FALSE)
{
- if (path != NULL)
+ /*
+ * Loop over all extensions in $PATHEXT.
+ * Check "name" ends with extension.
+ */
+ p = pathext;
+ while (*p)
{
- if (mch_isFullName((char_u *)name))
- *path = vim_strsave((char_u *)name);
- else
- *path = FullName_save((char_u *)name, FALSE);
+ if (p[0] == ';'
+ || (p[0] == '.' && (p[1] == NUL || p[1] == ';')))
+ {
+ // Skip empty or single ".".
+ ++p;
+ continue;
+ }
+ e = vim_strchr(p, ';');
+ if (e == NULL)
+ e = p + STRLEN(p);
+ tmplen = e - p;
+
+ if (_strnicoll(name + len - tmplen, (char *)p, tmplen) == 0)
+ {
+ noext = TRUE;
+ break;
+ }
+
+ p = e;
}
- return TRUE;
}
- return FALSE;
}
- p = enc_to_utf16((char_u *)name, NULL);
- if (p == NULL)
- return FALSE;
+ // Prepend single "." to pathext, it's means no extension added.
+ if (pathext == NULL)
+ pathext = (char_u *)".";
+ else if (noext == TRUE)
+ {
+ if (pathextbuf == NULL)
+ pathextbuf = alloc(STRLEN(pathext) + 3);
+ if (pathextbuf == NULL)
+ {
+ retval = FALSE;
+ goto theend;
+ }
+ STRCPY(pathextbuf, ".;");
+ STRCAT(pathextbuf, pathext);
+ pathext = pathextbuf;
+ }
- wcurpath = _wgetenv(L"PATH");
- wnewpath = ALLOC_MULT(WCHAR, wcslen(wcurpath) + 3);
- if (wnewpath == NULL)
- return FALSE;
- wcscpy(wnewpath, L".;");
- wcscat(wnewpath, wcurpath);
- n = (long)SearchPathW(wnewpath, p, NULL, _MAX_PATH, fnamew, &dumw);
- vim_free(wnewpath);
- vim_free(p);
- if (n == 0)
- return FALSE;
- if (GetFileAttributesW(fnamew) & FILE_ATTRIBUTE_DIRECTORY)
- return FALSE;
- if (path != NULL)
- *path = utf16_to_enc(fnamew, NULL);
- return TRUE;
+ // Use $PATH when "use_path" is TRUE and "name" is basename.
+ if (use_path && gettail((char_u *)name) == (char_u *)name)
+ {
+ p = mch_getenv("PATH");
+ if (p != NULL)
+ {
+ pathbuf = alloc(STRLEN(p) + 3);
+ if (pathbuf == NULL)
+ {
+ retval = FALSE;
+ goto theend;
+ }
+ STRCPY(pathbuf, ".;");
+ STRCAT(pathbuf, p);
+ }
+ }
+
+ /*
+ * Walk through all entries in $PATH to check if "name" exists there and
+ * is an executable file.
+ */
+ p = (pathbuf != NULL) ? pathbuf : (char_u *)".";
+ while (*p)
+ {
+ if (*p == ';') // Skip empty entry
+ {
+ ++p;
+ continue;
+ }
+ e = vim_strchr(p, ';');
+ if (e == NULL)
+ e = p + STRLEN(p);
+
+ if (e - p + len + 2 > sizeof(buf))
+ {
+ retval = FALSE;
+ goto theend;
+ }
+ // A single "." that means current dir.
+ if (e - p == 1 && *p == '.')
+ STRCPY(buf, name);
+ else
+ {
+ vim_strncpy(buf, p, e - p);
+ add_pathsep(buf);
+ STRCAT(buf, name);
+ }
+ tmplen = STRLEN(buf);
+
+ /*
+ * Loop over all extensions in $PATHEXT.
+ * Check "name" with extension added.
+ */
+ p = pathext;
+ while (*p)
+ {
+ if (*p == ';')
+ {
+ // Skip empty entry
+ ++p;
+ continue;
+ }
+ e2 = vim_strchr(p, (int)';');
+ if (e2 == NULL)
+ e2 = p + STRLEN(p);
+
+ if (!(p[0] == '.' && (p[1] == NUL || p[1] == ';')))
+ {
+ // Not a single "." that means no extension is added.
+ if (e2 - p + tmplen + 1 > sizeof(buf))
+ {
+ retval = FALSE;
+ goto theend;
+ }
+ vim_strncpy(buf + tmplen, p, e2 - p);
+ }
+ if (executable_file((char *)buf, path))
+ {
+ retval = TRUE;
+ goto theend;
+ }
+
+ p = e2;
+ }
+
+ p = e;
+ }
+
+theend:
+ free(pathextbuf);
+ free(pathbuf);
+ return retval;
}
#if (defined(__MINGW32__) && __MSVCRT_VERSION__ >= 0x800) || \
@@ -2210,7 +2353,7 @@ mch_init_g(void)
vimrun_path = (char *)vim_strsave(vimrun_location);
s_dont_use_vimrun = FALSE;
}
- else if (executable_exists("vimrun.exe", NULL, TRUE))
+ else if (executable_exists("vimrun.exe", NULL, TRUE, FALSE))
s_dont_use_vimrun = FALSE;
// Don't give the warning for a missing vimrun.exe right now, but only
@@ -2224,7 +2367,7 @@ mch_init_g(void)
* If "finstr.exe" doesn't exist, use "grep -n" for 'grepprg'.
* Otherwise the default "findstr /n" is used.
*/
- if (!executable_exists("findstr.exe", NULL, TRUE))
+ if (!executable_exists("findstr.exe", NULL, TRUE, FALSE))
set_option_value((char_u *)"grepprg", 0, (char_u *)"grep -n", 0);
# ifdef FEAT_CLIPBOARD
@@ -3306,69 +3449,7 @@ mch_writable(char_u *name)
int
mch_can_exe(char_u *name, char_u **path, int use_path)
{
- // WinNT and later can use _MAX_PATH wide characters for a pathname, which
- // means that the maximum pathname is _MAX_PATH * 3 bytes when 'enc' is
- // UTF-8.
- char_u buf[_MAX_PATH * 3];
- int len = (int)STRLEN(name);
- char_u *p, *saved;
-
- if (len >= sizeof(buf)) // safety check
- return FALSE;
-
- // Try using the name directly when a Unix-shell like 'shell'.
- if (strstr((char *)gettail(p_sh), "sh") != NULL)
- if (executable_exists((char *)name, path, use_path))
- return TRUE;
-
- /*
- * Loop over all extensions in $PATHEXT.
- */
- p = mch_getenv("PATHEXT");
- if (p == NULL)
- p = (char_u *)".com;.exe;.bat;.cmd";
- saved = vim_strsave(p);
- if (saved == NULL)
- return FALSE;
- p = saved;
- while (*p)
- {
- char_u *tmp = vim_strchr(p, ';');
-
- if (tmp != NULL)
- *tmp = NUL;
- if (_stricoll((char *)name + len - STRLEN(p), (char *)p) == 0
- && executable_exists((char *)name, path, use_path))
- {
- vim_free(saved);
- return TRUE;
- }
- if (tmp == NULL)
- break;
- p = tmp + 1;
- }
- vim_free(saved);
-
- vim_strncpy(buf, name, sizeof(buf) - 1);
- p = mch_getenv("PATHEXT");
- if (p == NULL)
- p = (char_u *)".com;.exe;.bat;.cmd";
- while (*p)
- {
- if (p[0] == '.' && (p[1] == NUL || p[1] == ';'))
- {
- // A single "." means no extension is added.
- buf[len] = NUL;
- ++p;
- if (*p)
- ++p;
- }
- else
- copy_option_part(&p, buf + len, sizeof(buf) - len, ";");
- if (executable_exists((char *)buf, path, use_path))
- return TRUE;
- }
- return FALSE;
+ return executable_exists((char *)name, path, TRUE, TRUE);
}
/*
diff --git a/src/testdir/test_functions.vim b/src/testdir/test_functions.vim
index e5c74e02ef..9b5d97679d 100644
--- a/src/testdir/test_functions.vim
+++ b/src/testdir/test_functions.vim
@@ -1187,6 +1187,30 @@ func Test_Executable()
call assert_equal(0, executable('notepad.exe.exe'))
call assert_equal(0, executable('shell32.dll'))
call assert_equal(0, executable('win.ini'))
+
+ " get "notepad" path and remove the leading drive and sep. (ex. 'C:\')
+ let notepadcmd = exepath('notepad.exe')
+ let driveroot = notepadcmd[:2]
+ let notepadcmd = notepadcmd[3:]
+ new
+ " check that the relative path works in /
+ execute 'lcd' driveroot
+ call assert_equal(1, executable(notepadcmd))
+ call assert_equal(driveroot .. notepadcmd, notepadcmd->exepath())
+ bwipe
+
+ " create "notepad.bat"
+ call mkdir('Xdir')
+ let notepadbat = fnamemodify('Xdir/notepad.bat', ':p')
+ call writefile([], notepadbat)
+ new
+ " check that the path and the pathext order is valid
+ lcd Xdir
+ let [pathext, $PATHEXT] = [$PATHEXT, '.com;.exe;.bat;.cmd']
+ call assert_equal(notepadbat, exepath('notepad'))
+ let $PATHEXT = pathext
+ bwipe
+ eval 'Xdir'->delete('rf')
elseif has('unix')
call assert_equal(1, 'cat'->executable())
call assert_equal(0, executable('nodogshere'))
diff --git a/src/version.c b/src/version.c
index 59c314bb6d..6fb570d1fb 100644
--- a/src/version.c
+++ b/src/version.c
@@ -747,6 +747,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
+ 850,
+/**/
849,
/**/
848,