mirror of
				https://github.com/neovim/neovim.git
				synced 2025-11-04 01:34:25 +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)
 | 
			
		||||
  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);
 | 
			
		||||
 | 
			
		||||
    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;
 | 
			
		||||
    }
 | 
			
		||||
    append_path(buf, (char *)name, buf_len);
 | 
			
		||||
 | 
			
		||||
#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);
 | 
			
		||||
    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;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
      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
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user