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-15 23:42:24 -06:00
parent 6d79aa3d70
commit a0d1d0321e
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));
}
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)
{

View file

@ -521,6 +521,31 @@ public:
*/
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.
* Good practice in child processes.

View file

@ -77,9 +77,9 @@ struct CmdDoctor : StoreCommand
{
PathSet dirs;
for (auto & dir : tokenizeString<Strings>(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;

View file

@ -104,10 +104,8 @@ struct CmdUpgradeNix : MixDryRun, StoreCommand
{
Path where;
for (auto & dir : tokenizeString<Strings>(getEnv("PATH").value_or(""), ":"))
if (pathExists(dir + "/nix-env")) {
where = dir;
break;
if (auto nixEnvPath = which("nix-env")) {
where = dirOf(*nixEnvPath);
}
if (where == "")