win: executable(): full path without extension

Absolute path is considered executable even *without* an extension.
This commit is contained in:
Justin M. Keyes 2017-02-03 16:56:31 +01:00
parent cd5b131575
commit 67fbbdb1b5
3 changed files with 89 additions and 73 deletions

View File

@ -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;

View File

@ -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;
}

View File

@ -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