Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
roberth committed Jul 12, 2024
1 parent 02f5953 commit a4b489d
Show file tree
Hide file tree
Showing 7 changed files with 339 additions and 3 deletions.
33 changes: 33 additions & 0 deletions src/libutil/environment-posix-path.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
///@file
///@brief A platform-agnostic implementation of the POSIX PATH environment variable logic
#include "environment-posix-path.hh"
#include "util.hh"
#include "strings.hh"

namespace nix {

std::string findExecutable(
const std::string & name,
std::optional<std::string> pathValue,
std::function<bool(const std::string &)> isExecutable)
{
// "If the pathname being sought contains a <slash>, the search through the path prefixes shall not be performed."
// https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html#tag_08_03
if (name.empty() || name.find('/') != std::string::npos) {
return name;
}

// "If PATH is unset or is set to null, the path search is implementation-defined."
// https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html#tag_08_03
auto path = pathValue.value_or("");

for (auto & dir : splitString<Strings>(path, ":")) {
auto combined = dir.empty() ? name : dir + "/" + name;
if (isExecutable(combined)) {
return combined;
}
}
return name;
}

} // namespace nix
39 changes: 39 additions & 0 deletions src/libutil/environment-posix-path.hh
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#pragma once

///@file
///@brief Cross-platform implementation of the POSIX PATH environment variable

#include <string>
#include <functional>
#include "environment-variables.hh"

namespace nix {

/**
* Interpret path as a location in the ambient file system and return whether
* it exists AND is executable.
*/
bool isExecutableAmbient(const std::string & path);

/**
* Search for an executable according to the POSIX spec for `PATH`.
* https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html#tag_08_03
*
* Notable additions:
* If `PATH` is unset, `name` is returned verbatim.
* If `PATH` contains a `/` but does not start with one, `name` is returned verbatim.
*
* This is a pure function, except for the default `isExecutable` argument, which
* uses the ambient file system to check if a file is executable (and exists).
*
* @param name A POSIX `pathname` to search for.
*
* @return `name` or path to a resolved executable
*
*/
std::string findExecutable(
const std::string & name,
std::optional<std::string> pathValue = getEnv("PATH"),
std::function<bool(const std::string &)> isExecutable = isExecutableAmbient);

} // namespace nix
11 changes: 11 additions & 0 deletions src/libutil/posix-environment-posix-path.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#include "environment-posix-path.hh"
#include <unistd.h>
namespace nix {

bool isExecutableAmbient(const std::string & path)
{
const char * cpath = path.c_str();
return access(cpath, X_OK) == 0;
}

} // namespace nix
20 changes: 20 additions & 0 deletions src/libutil/strings.hh
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#pragma once
#include <string_view>

namespace nix {
/**
* Split a string, preserving empty strings between separators, as well as at the start and end.
*
* Returns a non-empty collection of strings.
*/
template<typename C>
C splitString(std::string_view s, std::string_view separators);

/**
* Concatenate the given strings with a separator between the
* elements.
*/
template<class C>
std::string concatStringsSep(const std::string_view sep, const C & ss);

}
18 changes: 18 additions & 0 deletions src/libutil/util.cc
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,24 @@ template Strings tokenizeString(std::string_view s, std::string_view separators)
template StringSet tokenizeString(std::string_view s, std::string_view separators);
template std::vector<std::string> tokenizeString(std::string_view s, std::string_view separators);

template<class C> C splitString(std::string_view s, std::string_view separators)
{
C result;
size_t pos = 0;
while (pos <= s.size()) {
auto end = s.find_first_of(separators, pos);
if (end == s.npos) end = s.size();
result.insert(result.end(), std::string(s, pos, end - pos));
pos = end + 1;
}

return result;
}

template Strings splitString(std::string_view s, std::string_view separators);
template StringSet splitString(std::string_view s, std::string_view separators);
template std::vector<std::string> splitString(std::string_view s, std::string_view separators);


std::string chomp(std::string_view s)
{
Expand Down
11 changes: 8 additions & 3 deletions src/libutil/util.hh
Original file line number Diff line number Diff line change
Expand Up @@ -28,24 +28,29 @@ MakeError(FormatError, Error);

/**
* String tokenizer.
*
* See also `splitString()`, which preserves empty strings between separators, as well as at the start and end.
*/
template<class C> C tokenizeString(std::string_view s, std::string_view separators = " \t\n\r");


/**
* Concatenate the given strings with a separator between the
* Concatenate the non-empty strings out of the given strings with a separator between the
* elements.
*/
template<class C>
std::string concatStringsSep(const std::string_view sep, const C & ss)
{
size_t size = 0;
// need a cast to string_view since this is also called with Symbols
for (const auto & s : ss) size += sep.size() + std::string_view(s).size();
for (const auto & s : ss) {
if (size != 0) size += sep.size();
size += std::string_view(s).size();
}
std::string s;
s.reserve(size);
for (auto & i : ss) {
if (s.size() != 0) s += sep;
if (!s.empty()) s += sep;
s += i;
}
return s;
Expand Down
Loading

0 comments on commit a4b489d

Please sign in to comment.