mirror of
https://github.com/neovim/neovim.git
synced 2025-09-10 21:38:19 +00:00
win: executable(): full path without extension
Absolute path is considered executable even *without* an extension.
This commit is contained in:
109
src/nvim/os/fs.c
109
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)
|
bool os_can_exe(const char_u *name, char_u **abspath, bool use_path)
|
||||||
FUNC_ATTR_NONNULL_ARG(1)
|
FUNC_ATTR_NONNULL_ARG(1)
|
||||||
{
|
{
|
||||||
// when use_path is false or if it's an absolute or relative path don't
|
bool no_path = !use_path || path_is_absolute_path(name);
|
||||||
// need to use $PATH.
|
#ifndef WIN32
|
||||||
if (!use_path || path_is_absolute_path(name)
|
// If the filename is "qualified" (relative or absolute) do not check $PATH.
|
||||||
|| (name[0] == '.'
|
no_path |= (name[0] == '.'
|
||||||
&& (name[1] == '/' || (name[1] == '.' && name[2] == '/')))) {
|
&& (name[1] == '/' || (name[1] == '.' && name[2] == '/')));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (no_path) {
|
||||||
#ifdef WIN32
|
#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
|
#else
|
||||||
// Must have path separator, cannot execute files in the current directory.
|
// 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
|
#endif
|
||||||
if (ok) {
|
if (ok) {
|
||||||
if (abspath != NULL) {
|
if (abspath != NULL) {
|
||||||
*abspath = save_absolute_path(name);
|
*abspath = save_absolute_path(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return is_executable_in_path(name, abspath);
|
return is_executable_in_path(name, abspath);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return true if "name" is an executable file, false if not or it doesn't
|
/// Returns true if `name` is an executable file.
|
||||||
// exist.
|
static bool is_executable(const char *name)
|
||||||
static bool is_executable(const char_u *name)
|
|
||||||
FUNC_ATTR_NONNULL_ALL
|
FUNC_ATTR_NONNULL_ALL
|
||||||
{
|
{
|
||||||
int32_t mode = os_getperm(name);
|
int32_t mode = os_getperm((char_u *)name);
|
||||||
|
|
||||||
if (mode < 0) {
|
if (mode < 0) {
|
||||||
return false;
|
return false;
|
||||||
@@ -264,6 +269,37 @@ static bool is_executable(const char_u *name)
|
|||||||
#endif
|
#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.
|
/// Checks if a file is inside the `$PATH` and is executable.
|
||||||
///
|
///
|
||||||
/// @param[in] name The name of the 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) {
|
if (!pathext) {
|
||||||
pathext = ".com;.exe;.bat;.cmd";
|
pathext = ".com;.exe;.bat;.cmd";
|
||||||
}
|
}
|
||||||
|
|
||||||
buf_len += strlen(pathext);
|
buf_len += strlen(pathext);
|
||||||
#endif
|
#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
|
// Walk through all entries in $PATH to check if "name" exists there and
|
||||||
// is an executable file.
|
// is an executable file.
|
||||||
@@ -307,50 +342,24 @@ static bool is_executable_in_path(const char_u *name, char_u **abspath)
|
|||||||
for (;; ) {
|
for (;; ) {
|
||||||
char *e = xstrchrnul(p, ENV_SEPCHAR);
|
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);
|
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
rv = true;
|
|
||||||
goto end;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef WIN32
|
#ifdef WIN32
|
||||||
// Try appending file extensions from $PATHEXT to the name.
|
bool ok = is_executable(buf) || is_executable_ext(buf, pathext);
|
||||||
char *buf_end = xstrchrnul((char *)buf, '\0');
|
#else
|
||||||
for (const char *ext = pathext; *ext; ext++) {
|
bool ok = is_executable(buf);
|
||||||
// Skip the extension if there is no suffix after a '.'.
|
#endif
|
||||||
if (ext[0] == '.' && (ext[1] == '\0' || ext[1] == ENV_SEPCHAR)) {
|
if (ok) {
|
||||||
*ext++;
|
if (abspath != NULL) { // Caller asked for a copy of the path.
|
||||||
continue;
|
*abspath = save_absolute_path((char_u *)buf);
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
rv = true;
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (*ext_end != ENV_SEPCHAR) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
ext = ext_end;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (*e != ENV_SEPCHAR) {
|
if (*e != ENV_SEPCHAR) {
|
||||||
// End of $PATH without finding any executable called name.
|
// End of $PATH without finding any executable called name.
|
||||||
goto end;
|
goto end;
|
||||||
|
@@ -995,12 +995,10 @@ static void uniquefy_paths(garray_T *gap, char_u *pattern)
|
|||||||
ga_remove_duplicate_strings(gap);
|
ga_remove_duplicate_strings(gap);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/// Return the end of the directory name, on the first path
|
||||||
* Return the end of the directory name, on the first path
|
/// separator:
|
||||||
* separator:
|
/// "/path/file", "/path/dir/", "/path//dir", "/file"
|
||||||
* "/path/file", "/path/dir/", "/path//dir", "/file"
|
/// ^ ^ ^ ^
|
||||||
* ^ ^ ^ ^
|
|
||||||
*/
|
|
||||||
char_u *gettail_dir(const char_u *fname)
|
char_u *gettail_dir(const char_u *fname)
|
||||||
{
|
{
|
||||||
const char_u *dir_end = 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 current_length = strlen(path);
|
||||||
size_t to_append_length = strlen(to_append);
|
size_t to_append_length = strlen(to_append);
|
||||||
|
|
||||||
// Do not append empty strings.
|
// Do not append empty string or a dot.
|
||||||
if (to_append_length == 0) {
|
if (to_append_length == 0 || strcmp(to_append, ".") == 0) {
|
||||||
return OK;
|
return OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do not append a dot.
|
// Combine the path segments, separated by a slash.
|
||||||
if (STRCMP(to_append, ".") == 0) {
|
|
||||||
return OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Glue both paths with a slash.
|
|
||||||
if (current_length > 0 && !vim_ispathsep_nocolon(path[current_length-1])) {
|
if (current_length > 0 && !vim_ispathsep_nocolon(path[current_length-1])) {
|
||||||
current_length += 1; // Count the trailing slash.
|
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;
|
return FAIL;
|
||||||
}
|
}
|
||||||
|
|
||||||
STRCAT(path, PATHSEPSTR);
|
xstrlcat(path, PATHSEPSTR, max_len);
|
||||||
}
|
}
|
||||||
|
|
||||||
// +1 for the NUL at the end.
|
// +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;
|
return FAIL;
|
||||||
}
|
}
|
||||||
|
|
||||||
STRCAT(path, to_append);
|
xstrlcat(path, to_append, max_len);
|
||||||
return OK;
|
return OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -20,14 +20,8 @@ describe('executable()', function()
|
|||||||
-- Windows: siblings are in Nvim's "pseudo-$PATH".
|
-- Windows: siblings are in Nvim's "pseudo-$PATH".
|
||||||
local expected = iswin() and 1 or 0
|
local expected = iswin() and 1 or 0
|
||||||
if iswin() then
|
if iswin() then
|
||||||
print('XXXXXXXXXXXXXXXXXXXXXXXXX')
|
|
||||||
print(helpers.eval('$PATH'))
|
|
||||||
print('XXXXXXXXXXXXXXXXXXXXXXXXX')
|
|
||||||
-- $PATH on AppVeyor CI might be oversized, redefine it to a minimal one.
|
-- $PATH on AppVeyor CI might be oversized, redefine it to a minimal one.
|
||||||
clear({env={PATH=[[C:\Windows\system32;C:\Windows]]}})
|
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;',
|
eq('arg1=lemon;arg2=sky;arg3=tree;',
|
||||||
call('system', sibling_exe..' lemon sky tree'))
|
call('system', sibling_exe..' lemon sky tree'))
|
||||||
end
|
end
|
||||||
@@ -103,6 +97,26 @@ describe('executable() (Windows)', function()
|
|||||||
eq(0, call('executable', '.\\test_executable_zzz'))
|
eq(0, call('executable', '.\\test_executable_zzz'))
|
||||||
end)
|
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()
|
it('respects $PATHEXT when trying extensions on a filename', function()
|
||||||
clear({env={PATHEXT='.zzz'}})
|
clear({env={PATHEXT='.zzz'}})
|
||||||
for _,ext in ipairs(exts) do
|
for _,ext in ipairs(exts) do
|
||||||
|
Reference in New Issue
Block a user