From a0d1d0321ea2d0aee18676eeaf48420cda14045f Mon Sep 17 00:00:00 2001 From: Qyriad Date: Mon, 15 Apr 2024 23:42:24 -0600 Subject: [PATCH] libutil: add which() and whichAll(); migrate usage Adds two util API functions: which(), and whichAll(), which look for matching file(s) in every directory in PATH, and migrates the few uses of this pattern that currently exist in the codebase to use these functions. This will also be used in future commits. WIP TODO: tests, search path as PATH default argument? Change-Id: I2bf80d09196b5c84891043e09b23e012fe6cfc4c --- src/libutil/util.cc | 26 ++++++++++++++++++++++++++ src/libutil/util.hh | 25 +++++++++++++++++++++++++ src/nix/doctor.cc | 6 +++--- src/nix/upgrade-nix.cc | 8 +++----- 4 files changed, 57 insertions(+), 8 deletions(-) diff --git a/src/libutil/util.cc b/src/libutil/util.cc index dc724db3e..f65332b45 100644 --- a/src/libutil/util.cc +++ b/src/libutil/util.cc @@ -1299,6 +1299,32 @@ void runProgram2(const RunOptions & options) throw ExecError(status, "program '%1%' %2%", options.program, statusToString(status)); } +std::optional which(std::string_view cmdName) +{ + for (auto const & dir : tokenizeString(getEnv("PATH").value_or(""), ":")) { + auto supposedExe = dir + "/" + cmdName; + if (pathExists(supposedExe)) { + return std::move(supposedExe); + } + } + + return std::nullopt; +} + +std::vector whichAll(std::string_view cmdName) +{ + std::vector results; + + for (auto const & dir : tokenizeString(getEnv("PATH").value_or(""), ":")) { + auto supposedExe = dir + "/" + cmdName; + if (pathExists(supposedExe)) { + results.push_back(std::move(supposedExe)); + } + } + + return results; +} + void closeMostFDs(const std::set & exceptions) { diff --git a/src/libutil/util.hh b/src/libutil/util.hh index 9c2385e84..fc20e88e1 100644 --- a/src/libutil/util.hh +++ b/src/libutil/util.hh @@ -521,6 +521,31 @@ public: */ std::vector stringsToCharPtrs(const Strings & ss); +/** Gets the first file called `cmdName` in colon-separated PATH. + * + * @param cmdName The name of the file to look for in PATH. + * + * @return std::nullopt if the specified filename was not found. + * + * @note This does *not* check if the found file is executable or not. + * + * @see whichAll() + */ +std::optional which(std::string_view cmdName); + +/** Gets all files called `cmdName` in colon-separated PATH. + * + * @param cmdName The name of the file to look for in PATH. + * + * @return an std::vector of @ref Path "Paths", which will be empty if + * none were found. + * + * @note This does *not* check if the found files are executable or not. + * + * @see which() + */ +std::vector whichAll(std::string_view cmdName); + /** * Close all file descriptors except those listed in the given set. * Good practice in child processes. diff --git a/src/nix/doctor.cc b/src/nix/doctor.cc index da7a1d7a0..88c6b78ba 100644 --- a/src/nix/doctor.cc +++ b/src/nix/doctor.cc @@ -77,9 +77,9 @@ struct CmdDoctor : StoreCommand { PathSet dirs; - for (auto & dir : tokenizeString(getEnv("PATH").value_or(""), ":")) - if (pathExists(dir + "/nix-env")) - dirs.insert(dirOf(canonPath(dir + "/nix-env", true))); + for (auto const & nixExeInPath : whichAll("nix-env")) { + dirs.insert(canonPath(dirOf(nixExeInPath), true)); + } if (dirs.size() != 1) { std::stringstream ss; diff --git a/src/nix/upgrade-nix.cc b/src/nix/upgrade-nix.cc index af219c1b9..bed160e55 100644 --- a/src/nix/upgrade-nix.cc +++ b/src/nix/upgrade-nix.cc @@ -104,11 +104,9 @@ struct CmdUpgradeNix : MixDryRun, StoreCommand { Path where; - for (auto & dir : tokenizeString(getEnv("PATH").value_or(""), ":")) - if (pathExists(dir + "/nix-env")) { - where = dir; - break; - } + if (auto nixEnvPath = which("nix-env")) { + where = dirOf(*nixEnvPath); + } if (where == "") throw Error("couldn't figure out how Nix is installed, so I can't upgrade it");