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
This commit is contained in:
Qyriad 2024-04-16 11:07:56 -06:00
parent dd7b11e496
commit a9f0c5faad
4 changed files with 57 additions and 8 deletions

View file

@ -1299,6 +1299,32 @@ void runProgram2(const RunOptions & options)
throw ExecError(status, "program '%1%' %2%", options.program, statusToString(status)); throw ExecError(status, "program '%1%' %2%", options.program, statusToString(status));
} }
std::optional<Path> which(std::string_view cmdName)
{
for (auto const & dir : tokenizeString<StringSet>(getEnv("PATH").value_or(""), ":")) {
auto supposedExe = dir + "/" + cmdName;
if (pathExists(supposedExe)) {
return std::move(supposedExe);
}
}
return std::nullopt;
}
std::vector<Path> whichAll(std::string_view cmdName)
{
std::vector<Path> results;
for (auto const & dir : tokenizeString<StringSet>(getEnv("PATH").value_or(""), ":")) {
auto supposedExe = dir + "/" + cmdName;
if (pathExists(supposedExe)) {
results.push_back(std::move(supposedExe));
}
}
return results;
}
void closeMostFDs(const std::set<int> & exceptions) void closeMostFDs(const std::set<int> & exceptions)
{ {

View file

@ -521,6 +521,31 @@ public:
*/ */
std::vector<char *> stringsToCharPtrs(const Strings & ss); std::vector<char *> 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<Path> 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<Path> whichAll(std::string_view cmdName);
/** /**
* Close all file descriptors except those listed in the given set. * Close all file descriptors except those listed in the given set.
* Good practice in child processes. * Good practice in child processes.

View file

@ -77,9 +77,9 @@ struct CmdDoctor : StoreCommand
{ {
PathSet dirs; PathSet dirs;
for (auto & dir : tokenizeString<Strings>(getEnv("PATH").value_or(""), ":")) for (auto const & nixExeInPath : whichAll("nix-env")) {
if (pathExists(dir + "/nix-env")) dirs.insert(canonPath(dirOf(nixExeInPath), true));
dirs.insert(dirOf(canonPath(dir + "/nix-env", true))); }
if (dirs.size() != 1) { if (dirs.size() != 1) {
std::stringstream ss; std::stringstream ss;

View file

@ -104,11 +104,9 @@ struct CmdUpgradeNix : MixDryRun, StoreCommand
{ {
Path where; Path where;
for (auto & dir : tokenizeString<Strings>(getEnv("PATH").value_or(""), ":")) if (auto nixEnvPath = which("nix-env")) {
if (pathExists(dir + "/nix-env")) { where = dirOf(*nixEnvPath);
where = dir; }
break;
}
if (where == "") if (where == "")
throw Error("couldn't figure out how Nix is installed, so I can't upgrade it"); throw Error("couldn't figure out how Nix is installed, so I can't upgrade it");