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:
		
							
								
								
									
										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)
 | 
					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)) {
 | 
					#ifdef WIN32
 | 
				
			||||||
      // Check if the caller asked for a copy of the path.
 | 
					    bool ok = is_executable(buf) || is_executable_ext(buf, pathext);
 | 
				
			||||||
      if (abspath != NULL) {
 | 
					#else
 | 
				
			||||||
        *abspath = save_absolute_path(buf);
 | 
					    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;
 | 
					      rv = true;
 | 
				
			||||||
      goto end;
 | 
					      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) {
 | 
					    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