From 027bb55af8ab99d3afe1edbc916d63ae1da346ca Mon Sep 17 00:00:00 2001 From: Yizhou Tong Date: Thu, 12 Mar 2026 01:14:41 +0800 Subject: [PATCH 1/2] fix: resolve executable path when run via PATH When clice is executed via PATH (e.g., 'clice' instead of '/path/to/clice'), resolve_self_path() incorrectly resolves the path relative to current directory instead of the actual executable location. This causes worker pool to fail with 'no such file or directory' error. Add search_in_path() function to search PATH environment variable when argv[0] contains no path separators. Fixes: worker pool fails to start when clice is in PATH --- src/clice.cc | 68 ++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 64 insertions(+), 4 deletions(-) diff --git a/src/clice.cc b/src/clice.cc index d31ab0967..482240147 100644 --- a/src/clice.cc +++ b/src/clice.cc @@ -87,17 +87,77 @@ struct CliOptions { DECO_CFG_END(); }; +auto search_in_path(std::string_view name) -> std::string { + const char* path_env = std::getenv("PATH"); + if(!path_env || name.empty()) { + return std::string(name); + } + +#ifdef _WIN32 + constexpr char path_sep = ';'; + constexpr auto is_executable = [](const std::filesystem::path& p) { + return std::filesystem::exists(p) && !std::filesystem::is_directory(p); + }; +#else + constexpr char path_sep = ':'; + constexpr auto is_executable = [](const std::filesystem::path& p) { + std::error_code ec; + auto status = std::filesystem::status(p, ec); + if(ec || !std::filesystem::exists(status) || std::filesystem::is_directory(status)) { + return false; + } + return (status.permissions() & (std::filesystem::perms::owner_exec | + std::filesystem::perms::group_exec | + std::filesystem::perms::others_exec)) != std::filesystem::perms::none; + }; +#endif + + std::string_view path_view(path_env); + size_t start = 0; + while(start < path_view.size()) { + size_t end = path_view.find(path_sep, start); + if(end == std::string_view::npos) { + end = path_view.size(); + } + + std::string_view dir = path_view.substr(start, end - start); + if(!dir.empty()) { + std::filesystem::path full_path = std::filesystem::path(dir) / name; + std::error_code ec; + auto canonical = std::filesystem::canonical(full_path, ec); + if(!ec && is_executable(canonical)) { + return canonical.string(); + } + } + + start = end + 1; + } + + return std::string(name); +} + auto resolve_self_path(int argc, const char** argv) -> std::string { if(argc <= 0 || argv == nullptr || argv[0] == nullptr) { return "clice"; } + std::string_view arg0(argv[0]); std::error_code ec; - auto absolute = std::filesystem::absolute(argv[0], ec); - if(ec) { - return std::string(argv[0]); + + // If arg0 contains a path separator, treat it as a path (relative or absolute) + if(arg0.find('/') != std::string_view::npos || arg0.find('\\') != std::string_view::npos) { + auto absolute = std::filesystem::absolute(arg0, ec); + if(!ec) { + auto canonical = std::filesystem::weakly_canonical(absolute, ec); + if(!ec) { + return canonical.string(); + } + } + return std::string(arg0); } - return absolute.string(); + + // No path separator - search in PATH + return search_in_path(arg0); } auto build_options(const CliOptions& cli_options, int argc, const char** argv) From 62272fcac349925114af5b41589a4f2cd092ac38 Mon Sep 17 00:00:00 2001 From: Yizhou Tong Date: Thu, 12 Mar 2026 01:16:50 +0800 Subject: [PATCH 2/2] fix: honor PATHEXT on Windows in search_in_path - Read PATHEXT environment variable or use default extensions - Try original name first, then each extension if name has no extension - Update Windows is_executable to use filesystem::status like POSIX --- src/clice.cc | 65 +++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 57 insertions(+), 8 deletions(-) diff --git a/src/clice.cc b/src/clice.cc index 482240147..ec8d5c9d1 100644 --- a/src/clice.cc +++ b/src/clice.cc @@ -95,19 +95,46 @@ auto search_in_path(std::string_view name) -> std::string { #ifdef _WIN32 constexpr char path_sep = ';'; - constexpr auto is_executable = [](const std::filesystem::path& p) { - return std::filesystem::exists(p) && !std::filesystem::is_directory(p); + + // Get PATHEXT or use default extensions + std::vector extensions; + const char* pathext_env = std::getenv("PATHEXT"); + if(pathext_env) { + std::string_view pathext_view(pathext_env); + size_t ext_start = 0; + while(ext_start < pathext_view.size()) { + size_t ext_end = pathext_view.find(';', ext_start); + if(ext_end == std::string_view::npos) { + ext_end = pathext_view.size(); + } + std::string_view ext = pathext_view.substr(ext_start, ext_end - ext_start); + if(!ext.empty()) { + extensions.emplace_back(ext); + } + ext_start = ext_end + 1; + } + } else { + extensions = {".exe", ".cmd", ".bat", ".com"}; + } + + // Check if name already has an extension + bool has_extension = name.find('.') != std::string_view::npos; + + auto is_executable = [](const std::filesystem::path& p) { + std::error_code ec; + auto status = std::filesystem::status(p, ec); + return !ec && std::filesystem::exists(status) && !std::filesystem::is_directory(status); }; #else constexpr char path_sep = ':'; - constexpr auto is_executable = [](const std::filesystem::path& p) { + auto is_executable = [](const std::filesystem::path& p) { std::error_code ec; auto status = std::filesystem::status(p, ec); if(ec || !std::filesystem::exists(status) || std::filesystem::is_directory(status)) { return false; } - return (status.permissions() & (std::filesystem::perms::owner_exec | - std::filesystem::perms::group_exec | + return (status.permissions() & (std::filesystem::perms::owner_exec | + std::filesystem::perms::group_exec | std::filesystem::perms::others_exec)) != std::filesystem::perms::none; }; #endif @@ -119,20 +146,42 @@ auto search_in_path(std::string_view name) -> std::string { if(end == std::string_view::npos) { end = path_view.size(); } - + std::string_view dir = path_view.substr(start, end - start); if(!dir.empty()) { +#ifdef _WIN32 + // Try the name as-is first std::filesystem::path full_path = std::filesystem::path(dir) / name; std::error_code ec; auto canonical = std::filesystem::canonical(full_path, ec); if(!ec && is_executable(canonical)) { return canonical.string(); } + + // If name doesn't have an extension, try each PATHEXT extension + if(!has_extension) { + for(const auto& ext : extensions) { + std::string name_with_ext = std::string(name) + ext; + full_path = std::filesystem::path(dir) / name_with_ext; + canonical = std::filesystem::canonical(full_path, ec); + if(!ec && is_executable(canonical)) { + return canonical.string(); + } + } + } +#else + std::filesystem::path full_path = std::filesystem::path(dir) / name; + std::error_code ec; + auto canonical = std::filesystem::canonical(full_path, ec); + if(!ec && is_executable(canonical)) { + return canonical.string(); + } +#endif } - + start = end + 1; } - + return std::string(name); }