// This source code is released into the public domain. module; #include // access() export module nihil.posix:find_in_path; import nihil.std; import nihil.core; import :fd; import :getenv; import :paths; namespace nihil { // Find an executable by searching the given path string, which should be a colon-separated list of // directories, and return the full path. If the file can't be found or is not executable, returns // an appropriate error. export [[nodiscard]] auto find_in_path(std::filesystem::path const &file, std::string_view path) -> std::expected { auto try_return = [](std::filesystem::path file) -> std::expected { auto ret = ::access(file.string().c_str(), X_OK); if (ret == 0) return {std::move(file)}; return std::unexpected(error(sys_error())); }; // Absolute pathname skips the search. if (file.is_absolute()) return try_return(file); // Default to ENOENT as the error. auto err = error(std::errc::no_such_file_or_directory); for (auto &&dir : path | std::views::split(':')) { // An empty $PATH element means cwd. auto sdir = dir.empty() ? std::filesystem::path(".") : std::filesystem::path(std::string_view(dir)); if (auto ret = try_return(sdir / file); ret) return ret; // If we get an error other than ENOENT, cache it to return to the caller. // This means we can propagate access() errors. else if (ret.error().root_cause() != std::errc::no_such_file_or_directory) err = std::move(ret.error()); } return std::unexpected(std::move(err)); } // Find an executable in $PATH and return the full path. If $PATH is not set, uses _PATH_DEFPATH. // If the file can't be found or is not executable, returns an appropriate error. export [[nodiscard]] auto find_in_path(std::filesystem::path const &file) -> std::expected { auto const path = getenv("PATH").value_or(std::string(paths::defpath)); // NOLINT return find_in_path(file, path); } } // namespace nihil