aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/nvim/os/fs.c111
-rw-r--r--src/nvim/path.c25
-rw-r--r--test/functional/eval/executable_spec.lua26
3 files changed, 89 insertions, 73 deletions
diff --git a/src/nvim/os/fs.c b/src/nvim/os/fs.c
index 4ca67d1f1a..7266e54b58 100644
--- a/src/nvim/os/fs.c
+++ b/src/nvim/os/fs.c
@@ -219,37 +219,42 @@ int os_exepath(char *buffer, size_t *size)
bool os_can_exe(const char_u *name, char_u **abspath, bool use_path)
FUNC_ATTR_NONNULL_ARG(1)
{
- // when use_path is false or if it's an absolute or relative path don't
- // need to use $PATH.
- if (!use_path || path_is_absolute_path(name)
- || (name[0] == '.'
- && (name[1] == '/' || (name[1] == '.' && name[2] == '/')))) {
+ bool no_path = !use_path || path_is_absolute_path(name);
+#ifndef WIN32
+ // If the filename is "qualified" (relative or absolute) do not check $PATH.
+ no_path |= (name[0] == '.'
+ && (name[1] == '/' || (name[1] == '.' && name[2] == '/')));
+#endif
+
+ if (no_path) {
#ifdef WIN32
- bool ok = is_executable(name);
+ const char *pathext = os_getenv("PATHEXT");
+ if (!pathext) {
+ pathext = ".com;.exe;.bat;.cmd";
+ }
+ bool ok = is_executable((char *)name) || is_executable_ext((char *)name,
+ pathext);
#else
// Must have path separator, cannot execute files in the current directory.
- bool ok = gettail_dir(name) != name && is_executable(name);
+ bool ok = gettail_dir(name) != name && is_executable((char *)name);
#endif
if (ok) {
if (abspath != NULL) {
*abspath = save_absolute_path(name);
}
-
return true;
}
-
return false;
}
return is_executable_in_path(name, abspath);
}
-// Return true if "name" is an executable file, false if not or it doesn't
-// exist.
-static bool is_executable(const char_u *name)
+/// Returns true if `name` is an executable file.
+static bool is_executable(const char *name)
FUNC_ATTR_NONNULL_ALL
{
- int32_t mode = os_getperm(name);
+ int32_t mode = os_getperm((char_u *)name);
if (mode < 0) {
return false;
@@ -264,6 +269,37 @@ static bool is_executable(const char_u *name)
#endif
}
+#ifdef WIN32
+/// Appends file extensions from `pathext` to `name` and returns true if any
+/// such combination is executable.
+static bool is_executable_ext(char *name, const char *pathext)
+ FUNC_ATTR_NONNULL_ALL
+{
+ xstrlcpy((char *)NameBuff, name, sizeof(NameBuff));
+ char *buf_end = xstrchrnul((char *)NameBuff, '\0');
+ for (const char *ext = pathext; *ext; ext++) {
+ // Skip the extension if there is no suffix after a '.'.
+ if (ext[0] == '.' && (ext[1] == '\0' || ext[1] == ENV_SEPCHAR)) {
+ ext++;
+ continue;
+ }
+
+ const char *ext_end = xstrchrnul(ext, ENV_SEPCHAR);
+ STRLCPY(buf_end, ext, ext_end - ext + 1);
+
+ if (is_executable((char *)NameBuff)) {
+ return true;
+ }
+
+ if (*ext_end != ENV_SEPCHAR) {
+ break;
+ }
+ ext = ext_end;
+ }
+ return false;
+}
+#endif
+
/// Checks if a file is inside the `$PATH` and is executable.
///
/// @param[in] name The name of the executable.
@@ -294,11 +330,10 @@ static bool is_executable_in_path(const char_u *name, char_u **abspath)
if (!pathext) {
pathext = ".com;.exe;.bat;.cmd";
}
-
buf_len += strlen(pathext);
#endif
- char_u *buf = xmalloc(buf_len);
+ char *buf = xmalloc(buf_len);
// Walk through all entries in $PATH to check if "name" exists there and
// is an executable file.
@@ -307,50 +342,24 @@ static bool is_executable_in_path(const char_u *name, char_u **abspath)
for (;; ) {
char *e = xstrchrnul(p, ENV_SEPCHAR);
- // Glue the directory from $PATH with `name` and save into buf.
+ // Combine the $PATH segment with `name`.
STRLCPY(buf, p, e - p + 1);
- append_path((char *)buf, (char *)name, buf_len);
+ append_path(buf, (char *)name, buf_len);
- if (is_executable(buf)) {
- // Check if the caller asked for a copy of the path.
- if (abspath != NULL) {
- *abspath = save_absolute_path(buf);
+#ifdef WIN32
+ bool ok = is_executable(buf) || is_executable_ext(buf, pathext);
+#else
+ bool ok = is_executable(buf);
+#endif
+ if (ok) {
+ if (abspath != NULL) { // Caller asked for a copy of the path.
+ *abspath = save_absolute_path((char_u *)buf);
}
rv = true;
goto end;
}
-#ifdef WIN32
- // Try appending file extensions from $PATHEXT to the name.
- char *buf_end = xstrchrnul((char *)buf, '\0');
- for (const char *ext = pathext; *ext; ext++) {
- // Skip the extension if there is no suffix after a '.'.
- if (ext[0] == '.' && (ext[1] == '\0' || ext[1] == ENV_SEPCHAR)) {
- *ext++;
- continue;
- }
-
- const char *ext_end = xstrchrnul(ext, ENV_SEPCHAR);
- STRLCPY(buf_end, ext, ext_end - ext + 1);
-
- if (is_executable(buf)) {
- // Check if the caller asked for a copy of the path.
- if (abspath != NULL) {
- *abspath = save_absolute_path(buf);
- }
-
- rv = true;
- goto end;
- }
-
- if (*ext_end != ENV_SEPCHAR) {
- break;
- }
- ext = ext_end;
- }
-#endif
-
if (*e != ENV_SEPCHAR) {
// End of $PATH without finding any executable called name.
goto end;
diff --git a/src/nvim/path.c b/src/nvim/path.c
index 374d72ddd3..ea06fb8dde 100644
--- a/src/nvim/path.c
+++ b/src/nvim/path.c
@@ -995,12 +995,10 @@ static void uniquefy_paths(garray_T *gap, char_u *pattern)
ga_remove_duplicate_strings(gap);
}
-/*
- * Return the end of the directory name, on the first path
- * separator:
- * "/path/file", "/path/dir/", "/path//dir", "/file"
- * ^ ^ ^ ^
- */
+/// Return the end of the directory name, on the first path
+/// separator:
+/// "/path/file", "/path/dir/", "/path//dir", "/file"
+/// ^ ^ ^ ^
char_u *gettail_dir(const char_u *fname)
{
const char_u *dir_end = fname;
@@ -2131,17 +2129,12 @@ int append_path(char *path, const char *to_append, size_t max_len)
size_t current_length = strlen(path);
size_t to_append_length = strlen(to_append);
- // Do not append empty strings.
- if (to_append_length == 0) {
- return OK;
- }
-
- // Do not append a dot.
- if (STRCMP(to_append, ".") == 0) {
+ // Do not append empty string or a dot.
+ if (to_append_length == 0 || strcmp(to_append, ".") == 0) {
return OK;
}
- // Glue both paths with a slash.
+ // Combine the path segments, separated by a slash.
if (current_length > 0 && !vim_ispathsep_nocolon(path[current_length-1])) {
current_length += 1; // Count the trailing slash.
@@ -2150,7 +2143,7 @@ int append_path(char *path, const char *to_append, size_t max_len)
return FAIL;
}
- STRCAT(path, PATHSEPSTR);
+ xstrlcat(path, PATHSEPSTR, max_len);
}
// +1 for the NUL at the end.
@@ -2158,7 +2151,7 @@ int append_path(char *path, const char *to_append, size_t max_len)
return FAIL;
}
- STRCAT(path, to_append);
+ xstrlcat(path, to_append, max_len);
return OK;
}
diff --git a/test/functional/eval/executable_spec.lua b/test/functional/eval/executable_spec.lua
index 13b9261d82..7948ddaa40 100644
--- a/test/functional/eval/executable_spec.lua
+++ b/test/functional/eval/executable_spec.lua
@@ -20,14 +20,8 @@ describe('executable()', function()
-- Windows: siblings are in Nvim's "pseudo-$PATH".
local expected = iswin() and 1 or 0
if iswin() then
- print('XXXXXXXXXXXXXXXXXXXXXXXXX')
- print(helpers.eval('$PATH'))
- print('XXXXXXXXXXXXXXXXXXXXXXXXX')
-- $PATH on AppVeyor CI might be oversized, redefine it to a minimal one.
clear({env={PATH=[[C:\Windows\system32;C:\Windows]]}})
- print(helpers.eval('$PATH'))
- print('XXXXXXXXXXXXXXXXXXXXXXXXX')
- print(helpers.eval("echo glob(fnamemodify(exepath(v:progpath), ':h').'/*')"))
eq('arg1=lemon;arg2=sky;arg3=tree;',
call('system', sibling_exe..' lemon sky tree'))
end
@@ -103,6 +97,26 @@ describe('executable() (Windows)', function()
eq(0, call('executable', '.\\test_executable_zzz'))
end)
+ it('full path with extension', function()
+ -- Some executable we can expect in the test env.
+ local exe = 'printargs-test'
+ local exedir = helpers.eval("fnamemodify(v:progpath, ':h')")
+ local exepath = exedir..'/'..exe..'.exe'
+ eq(1, call('executable', exepath))
+ eq('arg1=lemon;arg2=sky;arg3=tree;',
+ call('system', exepath..' lemon sky tree'))
+ end)
+
+ it('full path without extension', function()
+ -- Some executable we can expect in the test env.
+ local exe = 'printargs-test'
+ local exedir = helpers.eval("fnamemodify(v:progpath, ':h')")
+ local exepath = exedir..'/'..exe
+ eq('arg1=lemon;arg2=sky;arg3=tree;',
+ call('system', exepath..' lemon sky tree'))
+ eq(1, call('executable', exepath))
+ end)
+
it('respects $PATHEXT when trying extensions on a filename', function()
clear({env={PATHEXT='.zzz'}})
for _,ext in ipairs(exts) do