mirror of
https://github.com/neovim/neovim.git
synced 2025-02-25 18:55:25 -06:00
win: executable(): full path without extension
Absolute path is considered executable even *without* an extension.
This commit is contained in:
parent
cd5b131575
commit
67fbbdb1b5
111
src/nvim/os/fs.c
111
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;
|
||||
|
@ -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) {
|
||||
// Do not append empty string or a dot.
|
||||
if (to_append_length == 0 || strcmp(to_append, ".") == 0) {
|
||||
return OK;
|
||||
}
|
||||
|
||||
// Do not append a dot.
|
||||
if (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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user