aboutsummaryrefslogtreecommitdiff
path: root/src/libutil
diff options
context:
space:
mode:
Diffstat (limited to 'src/libutil')
-rw-r--r--src/libutil/ansicolor.hh4
-rw-r--r--src/libutil/archive.hh5
-rw-r--r--src/libutil/args.cc139
-rw-r--r--src/libutil/args.hh131
-rw-r--r--src/libutil/error.hh1
-rw-r--r--src/libutil/hash.cc6
-rw-r--r--src/libutil/hash.hh3
-rw-r--r--src/libutil/istringstream_nocopy.hh92
-rw-r--r--src/libutil/serialise.hh46
-rw-r--r--src/libutil/util.cc30
-rw-r--r--src/libutil/util.hh10
11 files changed, 280 insertions, 187 deletions
diff --git a/src/libutil/ansicolor.hh b/src/libutil/ansicolor.hh
index a38c2d798..ae741f867 100644
--- a/src/libutil/ansicolor.hh
+++ b/src/libutil/ansicolor.hh
@@ -11,7 +11,7 @@ namespace nix {
#define ANSI_GREEN "\e[32;1m"
#define ANSI_YELLOW "\e[33;1m"
#define ANSI_BLUE "\e[34;1m"
-#define ANSI_MAGENTA "\e[35m;1m"
-#define ANSI_CYAN "\e[36m;1m"
+#define ANSI_MAGENTA "\e[35;1m"
+#define ANSI_CYAN "\e[36;1m"
}
diff --git a/src/libutil/archive.hh b/src/libutil/archive.hh
index 768fe2536..302b1bb18 100644
--- a/src/libutil/archive.hh
+++ b/src/libutil/archive.hh
@@ -63,11 +63,12 @@ struct ParseSink
virtual void createSymlink(const Path & path, const string & target) { };
};
-struct TeeSink : ParseSink
+struct TeeParseSink : ParseSink
{
+ StringSink saved;
TeeSource source;
- TeeSink(Source & source) : source(source) { }
+ TeeParseSink(Source & source) : source(source, saved) { }
};
void parseDump(ParseSink & sink, Source & source);
diff --git a/src/libutil/args.cc b/src/libutil/args.cc
index 1f0e4f803..986c5d1cd 100644
--- a/src/libutil/args.cc
+++ b/src/libutil/args.cc
@@ -1,6 +1,8 @@
#include "args.hh"
#include "hash.hh"
+#include <glob.h>
+
namespace nix {
void Args::addFlag(Flag && flag_)
@@ -13,6 +15,20 @@ void Args::addFlag(Flag && flag_)
if (flag->shortName) shortFlags[flag->shortName] = flag;
}
+bool pathCompletions = false;
+std::shared_ptr<std::set<std::string>> completions;
+
+std::string completionMarker = "___COMPLETE___";
+
+std::optional<std::string> needsCompletion(std::string_view s)
+{
+ if (!completions) return {};
+ auto i = s.find(completionMarker);
+ if (i != std::string::npos)
+ return std::string(s.begin(), i);
+ return {};
+}
+
void Args::parseCmdline(const Strings & _cmdline)
{
Strings pendingArgs;
@@ -20,6 +36,14 @@ void Args::parseCmdline(const Strings & _cmdline)
Strings cmdline(_cmdline);
+ if (auto s = getEnv("NIX_GET_COMPLETIONS")) {
+ size_t n = std::stoi(*s);
+ assert(n > 0 && n <= cmdline.size());
+ *std::next(cmdline.begin(), n - 1) += completionMarker;
+ completions = std::make_shared<decltype(completions)::element_type>();
+ verbosity = lvlError;
+ }
+
for (auto pos = cmdline.begin(); pos != cmdline.end(); ) {
auto arg = *pos;
@@ -63,7 +87,7 @@ void Args::printHelp(const string & programName, std::ostream & out)
for (auto & exp : expectedArgs) {
std::cout << renderLabels({exp.label});
// FIXME: handle arity > 1
- if (exp.arity == 0) std::cout << "...";
+ if (exp.handler.arity == ArityAny) std::cout << "...";
if (exp.optional) std::cout << "?";
}
std::cout << "\n";
@@ -99,18 +123,32 @@ bool Args::processFlag(Strings::iterator & pos, Strings::iterator end)
auto process = [&](const std::string & name, const Flag & flag) -> bool {
++pos;
std::vector<std::string> args;
+ bool anyCompleted = false;
for (size_t n = 0 ; n < flag.handler.arity; ++n) {
if (pos == end) {
if (flag.handler.arity == ArityAny) break;
throw UsageError("flag '%s' requires %d argument(s)", name, flag.handler.arity);
}
+ if (flag.completer)
+ if (auto prefix = needsCompletion(*pos)) {
+ anyCompleted = true;
+ flag.completer(n, *prefix);
+ }
args.push_back(*pos++);
}
- flag.handler.fun(std::move(args));
+ if (!anyCompleted)
+ flag.handler.fun(std::move(args));
return true;
};
if (string(*pos, 0, 2) == "--") {
+ if (auto prefix = needsCompletion(*pos)) {
+ for (auto & [name, flag] : longFlags) {
+ if (!hiddenCategories.count(flag->category)
+ && hasPrefix(name, std::string(*prefix, 2)))
+ completions->insert("--" + name);
+ }
+ }
auto i = longFlags.find(string(*pos, 2));
if (i == longFlags.end()) return false;
return process("--" + i->first, *i->second);
@@ -123,6 +161,14 @@ bool Args::processFlag(Strings::iterator & pos, Strings::iterator end)
return process(std::string("-") + c, *i->second);
}
+ if (auto prefix = needsCompletion(*pos)) {
+ if (prefix == "-") {
+ completions->insert("--");
+ for (auto & [flag, _] : shortFlags)
+ completions->insert(std::string("-") + flag);
+ }
+ }
+
return false;
}
@@ -138,12 +184,17 @@ bool Args::processArgs(const Strings & args, bool finish)
bool res = false;
- if ((exp.arity == 0 && finish) ||
- (exp.arity > 0 && args.size() == exp.arity))
+ if ((exp.handler.arity == ArityAny && finish) ||
+ (exp.handler.arity != ArityAny && args.size() == exp.handler.arity))
{
std::vector<std::string> ss;
- for (auto & s : args) ss.push_back(s);
- exp.handler(std::move(ss));
+ for (const auto &[n, s] : enumerate(args)) {
+ ss.push_back(s);
+ if (exp.completer)
+ if (auto prefix = needsCompletion(s))
+ exp.completer(n, *prefix);
+ }
+ exp.handler.fun(ss);
expectedArgs.pop_front();
res = true;
}
@@ -154,6 +205,13 @@ bool Args::processArgs(const Strings & args, bool finish)
return res;
}
+static void hashTypeCompleter(size_t index, std::string_view prefix)
+{
+ for (auto & type : hashTypes)
+ if (hasPrefix(type, prefix))
+ completions->insert(type);
+}
+
Args::Flag Args::Flag::mkHashTypeFlag(std::string && longName, HashType * ht)
{
return Flag {
@@ -162,7 +220,8 @@ Args::Flag Args::Flag::mkHashTypeFlag(std::string && longName, HashType * ht)
.labels = {"hash-algo"},
.handler = {[ht](std::string s) {
*ht = parseHashType(s);
- }}
+ }},
+ .completer = hashTypeCompleter
};
}
@@ -174,10 +233,42 @@ Args::Flag Args::Flag::mkHashTypeOptFlag(std::string && longName, std::optional<
.labels = {"hash-algo"},
.handler = {[oht](std::string s) {
*oht = std::optional<HashType> { parseHashType(s) };
- }}
+ }},
+ .completer = hashTypeCompleter
};
}
+static void completePath(std::string_view prefix, bool onlyDirs)
+{
+ pathCompletions = true;
+ glob_t globbuf;
+ int flags = GLOB_NOESCAPE | GLOB_TILDE;
+ #ifdef GLOB_ONLYDIR
+ if (onlyDirs)
+ flags |= GLOB_ONLYDIR;
+ #endif
+ if (glob((std::string(prefix) + "*").c_str(), flags, nullptr, &globbuf) == 0) {
+ for (size_t i = 0; i < globbuf.gl_pathc; ++i) {
+ if (onlyDirs) {
+ auto st = lstat(globbuf.gl_pathv[i]);
+ if (!S_ISDIR(st.st_mode)) continue;
+ }
+ completions->insert(globbuf.gl_pathv[i]);
+ }
+ globfree(&globbuf);
+ }
+}
+
+void completePath(size_t, std::string_view prefix)
+{
+ completePath(prefix, false);
+}
+
+void completeDir(size_t, std::string_view prefix)
+{
+ completePath(prefix, true);
+}
+
Strings argvToStrings(int argc, char * * argv)
{
Strings args;
@@ -225,18 +316,26 @@ void Command::printHelp(const string & programName, std::ostream & out)
MultiCommand::MultiCommand(const Commands & commands)
: commands(commands)
{
- expectedArgs.push_back(ExpectedArg{"command", 1, true, [=](std::vector<std::string> ss) {
- assert(!command);
- auto cmd = ss[0];
- if (auto alias = get(deprecatedAliases, cmd)) {
- warn("'%s' is a deprecated alias for '%s'", cmd, *alias);
- cmd = *alias;
- }
- auto i = commands.find(cmd);
- if (i == commands.end())
- throw UsageError("'%s' is not a recognised command", cmd);
- command = {cmd, i->second()};
- }});
+ expectArgs({
+ .label = "command",
+ .optional = true,
+ .handler = {[=](std::string s) {
+ assert(!command);
+ if (auto alias = get(deprecatedAliases, s)) {
+ warn("'%s' is a deprecated alias for '%s'", s, *alias);
+ s = *alias;
+ }
+ if (auto prefix = needsCompletion(s)) {
+ for (auto & [name, command] : commands)
+ if (hasPrefix(name, *prefix))
+ completions->insert(name);
+ }
+ auto i = commands.find(s);
+ if (i == commands.end())
+ throw UsageError("'%s' is not a recognised command", s);
+ command = {s, i->second()};
+ }}
+ });
categories[Command::catDefault] = "Available commands";
}
diff --git a/src/libutil/args.hh b/src/libutil/args.hh
index 433d26bba..97a517344 100644
--- a/src/libutil/args.hh
+++ b/src/libutil/args.hh
@@ -8,8 +8,6 @@
namespace nix {
-MakeError(UsageError, Error);
-
enum HashType : char;
class Args
@@ -28,61 +26,67 @@ protected:
static const size_t ArityAny = std::numeric_limits<size_t>::max();
+ struct Handler
+ {
+ std::function<void(std::vector<std::string>)> fun;
+ size_t arity;
+
+ Handler() {}
+
+ Handler(std::function<void(std::vector<std::string>)> && fun)
+ : fun(std::move(fun))
+ , arity(ArityAny)
+ { }
+
+ Handler(std::function<void()> && handler)
+ : fun([handler{std::move(handler)}](std::vector<std::string>) { handler(); })
+ , arity(0)
+ { }
+
+ Handler(std::function<void(std::string)> && handler)
+ : fun([handler{std::move(handler)}](std::vector<std::string> ss) {
+ handler(std::move(ss[0]));
+ })
+ , arity(1)
+ { }
+
+ Handler(std::function<void(std::string, std::string)> && handler)
+ : fun([handler{std::move(handler)}](std::vector<std::string> ss) {
+ handler(std::move(ss[0]), std::move(ss[1]));
+ })
+ , arity(2)
+ { }
+
+ Handler(std::vector<std::string> * dest)
+ : fun([=](std::vector<std::string> ss) { *dest = ss; })
+ , arity(ArityAny)
+ { }
+
+ template<class T>
+ Handler(T * dest)
+ : fun([=](std::vector<std::string> ss) { *dest = ss[0]; })
+ , arity(1)
+ { }
+
+ template<class T>
+ Handler(T * dest, const T & val)
+ : fun([=](std::vector<std::string> ss) { *dest = val; })
+ , arity(0)
+ { }
+ };
+
/* Flags. */
struct Flag
{
typedef std::shared_ptr<Flag> ptr;
- struct Handler
- {
- std::function<void(std::vector<std::string>)> fun;
- size_t arity;
-
- Handler() {}
-
- Handler(std::function<void(std::vector<std::string>)> && fun)
- : fun(std::move(fun))
- , arity(ArityAny)
- { }
-
- Handler(std::function<void()> && handler)
- : fun([handler{std::move(handler)}](std::vector<std::string>) { handler(); })
- , arity(0)
- { }
-
- Handler(std::function<void(std::string)> && handler)
- : fun([handler{std::move(handler)}](std::vector<std::string> ss) {
- handler(std::move(ss[0]));
- })
- , arity(1)
- { }
-
- Handler(std::function<void(std::string, std::string)> && handler)
- : fun([handler{std::move(handler)}](std::vector<std::string> ss) {
- handler(std::move(ss[0]), std::move(ss[1]));
- })
- , arity(2)
- { }
-
- template<class T>
- Handler(T * dest)
- : fun([=](std::vector<std::string> ss) { *dest = ss[0]; })
- , arity(1)
- { }
-
- template<class T>
- Handler(T * dest, const T & val)
- : fun([=](std::vector<std::string> ss) { *dest = val; })
- , arity(0)
- { }
- };
-
std::string longName;
char shortName = 0;
std::string description;
std::string category;
Strings labels;
Handler handler;
+ std::function<void(size_t, std::string_view)> completer;
static Flag mkHashTypeFlag(std::string && longName, HashType * ht);
static Flag mkHashTypeOptFlag(std::string && longName, std::optional<HashType> * oht);
@@ -99,9 +103,9 @@ protected:
struct ExpectedArg
{
std::string label;
- size_t arity; // 0 = any
- bool optional;
- std::function<void(std::vector<std::string>)> handler;
+ bool optional = false;
+ Handler handler;
+ std::function<void(size_t, std::string_view)> completer;
};
std::list<ExpectedArg> expectedArgs;
@@ -175,20 +179,28 @@ public:
});
}
+ void expectArgs(ExpectedArg && arg)
+ {
+ expectedArgs.emplace_back(std::move(arg));
+ }
+
/* Expect a string argument. */
void expectArg(const std::string & label, string * dest, bool optional = false)
{
- expectedArgs.push_back(ExpectedArg{label, 1, optional, [=](std::vector<std::string> ss) {
- *dest = ss[0];
- }});
+ expectArgs({
+ .label = label,
+ .optional = true,
+ .handler = {dest}
+ });
}
/* Expect 0 or more arguments. */
void expectArgs(const std::string & label, std::vector<std::string> * dest)
{
- expectedArgs.push_back(ExpectedArg{label, 0, false, [=](std::vector<std::string> ss) {
- *dest = std::move(ss);
- }});
+ expectArgs({
+ .label = label,
+ .handler = {dest}
+ });
}
friend class MultiCommand;
@@ -259,4 +271,13 @@ typedef std::vector<std::pair<std::string, std::string>> Table2;
void printTable(std::ostream & out, const Table2 & table);
+extern std::shared_ptr<std::set<std::string>> completions;
+extern bool pathCompletions;
+
+std::optional<std::string> needsCompletion(std::string_view s);
+
+void completePath(size_t, std::string_view prefix);
+
+void completeDir(size_t, std::string_view prefix);
+
}
diff --git a/src/libutil/error.hh b/src/libutil/error.hh
index f4b3f11bb..0daaf3be2 100644
--- a/src/libutil/error.hh
+++ b/src/libutil/error.hh
@@ -191,6 +191,7 @@ public:
}
MakeError(Error, BaseError);
+MakeError(UsageError, Error);
class SysError : public Error
{
diff --git a/src/libutil/hash.cc b/src/libutil/hash.cc
index 4b82f191e..c7ccb809d 100644
--- a/src/libutil/hash.cc
+++ b/src/libutil/hash.cc
@@ -9,7 +9,6 @@
#include "archive.hh"
#include "parser.hh"
#include "util.hh"
-#include "istringstream_nocopy.hh"
#include <sys/types.h>
#include <sys/stat.h>
@@ -17,6 +16,7 @@
namespace nix {
+
static size_t regularHashSize(HashType type) {
switch (type) {
case htMD5: return md5HashSize;
@@ -27,6 +27,10 @@ static size_t regularHashSize(HashType type) {
abort();
}
+
+std::set<std::string> hashTypes = { "md5", "sha1", "sha256", "sha512" };
+
+
Hash::Hash(HashType type) : type(type)
{
hashSize = regularHashSize(type);
diff --git a/src/libutil/hash.hh b/src/libutil/hash.hh
index af11a028d..026e80e71 100644
--- a/src/libutil/hash.hh
+++ b/src/libutil/hash.hh
@@ -18,6 +18,8 @@ const int sha1HashSize = 20;
const int sha256HashSize = 32;
const int sha512HashSize = 64;
+extern std::set<std::string> hashTypes;
+
extern const string base32Chars;
enum Base : int { Base64, Base32, Base16, SRI };
@@ -124,6 +126,7 @@ Hash compressHash(const Hash & hash, unsigned int newSize);
/* Parse a string representing a hash type. */
HashType parseHashType(std::string_view s);
+
/* Will return nothing on parse error */
std::optional<HashType> parseHashTypeOpt(std::string_view s);
diff --git a/src/libutil/istringstream_nocopy.hh b/src/libutil/istringstream_nocopy.hh
deleted file mode 100644
index f7beac578..000000000
--- a/src/libutil/istringstream_nocopy.hh
+++ /dev/null
@@ -1,92 +0,0 @@
-/* This file provides a variant of std::istringstream that doesn't
- copy its string argument. This is useful for large strings. The
- caller must ensure that the string object is not destroyed while
- it's referenced by this object. */
-
-#pragma once
-
-#include <string>
-#include <iostream>
-
-template <class CharT, class Traits = std::char_traits<CharT>, class Allocator = std::allocator<CharT>>
-class basic_istringbuf_nocopy : public std::basic_streambuf<CharT, Traits>
-{
-public:
- typedef std::basic_string<CharT, Traits, Allocator> string_type;
-
- typedef typename std::basic_streambuf<CharT, Traits>::off_type off_type;
-
- typedef typename std::basic_streambuf<CharT, Traits>::pos_type pos_type;
-
- typedef typename std::basic_streambuf<CharT, Traits>::int_type int_type;
-
- typedef typename std::basic_streambuf<CharT, Traits>::traits_type traits_type;
-
-private:
- const string_type & s;
-
- off_type off;
-
-public:
- basic_istringbuf_nocopy(const string_type & s) : s{s}, off{0}
- {
- }
-
-private:
- pos_type seekoff(off_type off, std::ios_base::seekdir dir, std::ios_base::openmode which)
- {
- if (which & std::ios_base::in) {
- this->off = dir == std::ios_base::beg
- ? off
- : (dir == std::ios_base::end
- ? s.size() + off
- : this->off + off);
- }
- return pos_type(this->off);
- }
-
- pos_type seekpos(pos_type pos, std::ios_base::openmode which)
- {
- return seekoff(pos, std::ios_base::beg, which);
- }
-
- std::streamsize showmanyc()
- {
- return s.size() - off;
- }
-
- int_type underflow()
- {
- if (typename string_type::size_type(off) == s.size())
- return traits_type::eof();
- return traits_type::to_int_type(s[off]);
- }
-
- int_type uflow()
- {
- if (typename string_type::size_type(off) == s.size())
- return traits_type::eof();
- return traits_type::to_int_type(s[off++]);
- }
-
- int_type pbackfail(int_type ch)
- {
- if (off == 0 || (ch != traits_type::eof() && ch != s[off - 1]))
- return traits_type::eof();
-
- return traits_type::to_int_type(s[--off]);
- }
-
-};
-
-template <class CharT, class Traits = std::char_traits<CharT>, class Allocator = std::allocator<CharT>>
-class basic_istringstream_nocopy : public std::basic_iostream<CharT, Traits>
-{
- typedef basic_istringbuf_nocopy<CharT, Traits, Allocator> buf_type;
- buf_type buf;
-public:
- basic_istringstream_nocopy(const typename buf_type::string_type & s) :
- std::basic_iostream<CharT, Traits>(&buf), buf(s) {};
-};
-
-typedef basic_istringstream_nocopy<char> istringstream_nocopy;
diff --git a/src/libutil/serialise.hh b/src/libutil/serialise.hh
index a04118512..8386a4991 100644
--- a/src/libutil/serialise.hh
+++ b/src/libutil/serialise.hh
@@ -166,17 +166,30 @@ struct StringSource : Source
};
-/* Adapter class of a Source that saves all data read to `s'. */
+/* A sink that writes all incoming data to two other sinks. */
+struct TeeSink : Sink
+{
+ Sink & sink1, & sink2;
+ TeeSink(Sink & sink1, Sink & sink2) : sink1(sink1), sink2(sink2) { }
+ virtual void operator () (const unsigned char * data, size_t len)
+ {
+ sink1(data, len);
+ sink2(data, len);
+ }
+};
+
+
+/* Adapter class of a Source that saves all data read to a sink. */
struct TeeSource : Source
{
Source & orig;
- ref<std::string> data;
- TeeSource(Source & orig)
- : orig(orig), data(make_ref<std::string>()) { }
+ Sink & sink;
+ TeeSource(Source & orig, Sink & sink)
+ : orig(orig), sink(sink) { }
size_t read(unsigned char * data, size_t len)
{
size_t n = orig.read(data, len);
- this->data->append((const char *) data, n);
+ sink(data, len);
return n;
}
};
@@ -336,4 +349,27 @@ Source & operator >> (Source & in, bool & b)
}
+/* An adapter that converts a std::basic_istream into a source. */
+struct StreamToSourceAdapter : Source
+{
+ std::shared_ptr<std::basic_istream<char>> istream;
+
+ StreamToSourceAdapter(std::shared_ptr<std::basic_istream<char>> istream)
+ : istream(istream)
+ { }
+
+ size_t read(unsigned char * data, size_t len) override
+ {
+ if (!istream->read((char *) data, len)) {
+ if (istream->eof()) {
+ if (istream->gcount() == 0)
+ throw EndOfFile("end of file");
+ } else
+ throw Error("I/O error in StreamToSourceAdapter");
+ }
+ return istream->gcount();
+ }
+};
+
+
}
diff --git a/src/libutil/util.cc b/src/libutil/util.cc
index ed43c403f..e520d83ad 100644
--- a/src/libutil/util.cc
+++ b/src/libutil/util.cc
@@ -23,6 +23,7 @@
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
+#include <sys/time.h>
#include <sys/un.h>
#include <unistd.h>
@@ -79,7 +80,7 @@ void replaceEnv(std::map<std::string, std::string> newEnv)
}
-Path absPath(Path path, std::optional<Path> dir)
+Path absPath(Path path, std::optional<Path> dir, bool resolveSymlinks)
{
if (path[0] != '/') {
if (!dir) {
@@ -100,7 +101,7 @@ Path absPath(Path path, std::optional<Path> dir)
}
path = *dir + "/" + path;
}
- return canonPath(path);
+ return canonPath(path, resolveSymlinks);
}
@@ -345,7 +346,6 @@ void writeFile(const Path & path, Source & source, mode_t mode)
}
}
-
string readLine(int fd)
{
string s;
@@ -581,20 +581,31 @@ Paths createDirs(const Path & path)
}
-void createSymlink(const Path & target, const Path & link)
+void createSymlink(const Path & target, const Path & link,
+ std::optional<time_t> mtime)
{
if (symlink(target.c_str(), link.c_str()))
throw SysError("creating symlink from '%1%' to '%2%'", link, target);
+ if (mtime) {
+ struct timeval times[2];
+ times[0].tv_sec = *mtime;
+ times[0].tv_usec = 0;
+ times[1].tv_sec = *mtime;
+ times[1].tv_usec = 0;
+ if (lutimes(link.c_str(), times))
+ throw SysError("setting time of symlink '%s'", link);
+ }
}
-void replaceSymlink(const Path & target, const Path & link)
+void replaceSymlink(const Path & target, const Path & link,
+ std::optional<time_t> mtime)
{
for (unsigned int n = 0; true; n++) {
Path tmp = canonPath(fmt("%s/.%d_%s", dirOf(link), n, baseNameOf(link)));
try {
- createSymlink(target, tmp);
+ createSymlink(target, tmp, mtime);
} catch (SysError & e) {
if (e.errNo == EEXIST) continue;
throw;
@@ -1006,12 +1017,14 @@ std::vector<char *> stringsToCharPtrs(const Strings & ss)
return res;
}
-
+// Output = "standard out" output stream
string runProgram(Path program, bool searchPath, const Strings & args,
const std::optional<std::string> & input)
{
RunOptions opts(program, args);
opts.searchPath = searchPath;
+ // This allows you to refer to a program with a pathname relative to the
+ // PATH variable.
opts.input = input;
auto res = runProgram(opts);
@@ -1022,6 +1035,7 @@ string runProgram(Path program, bool searchPath, const Strings & args,
return res.second;
}
+// Output = error code + "standard out" output stream
std::pair<int, std::string> runProgram(const RunOptions & options_)
{
RunOptions options(options_);
@@ -1094,6 +1108,8 @@ void runProgram2(const RunOptions & options)
if (options.searchPath)
execvp(options.program.c_str(), stringsToCharPtrs(args_).data());
+ // This allows you to refer to a program with a pathname relative
+ // to the PATH variable.
else
execv(options.program.c_str(), stringsToCharPtrs(args_).data());
diff --git a/src/libutil/util.hh b/src/libutil/util.hh
index 3641daaec..42130f6dc 100644
--- a/src/libutil/util.hh
+++ b/src/libutil/util.hh
@@ -49,7 +49,9 @@ void clearEnv();
/* Return an absolutized path, resolving paths relative to the
specified directory, or the current directory otherwise. The path
is also canonicalised. */
-Path absPath(Path path, std::optional<Path> dir = {});
+Path absPath(Path path,
+ std::optional<Path> dir = {},
+ bool resolveSymlinks = false);
/* Canonicalise a path by removing all `.' or `..' components and
double or trailing slashes. Optionally resolves all symlink
@@ -147,10 +149,12 @@ Path getDataDir();
Paths createDirs(const Path & path);
/* Create a symlink. */
-void createSymlink(const Path & target, const Path & link);
+void createSymlink(const Path & target, const Path & link,
+ std::optional<time_t> mtime = {});
/* Atomically create or replace a symlink. */
-void replaceSymlink(const Path & target, const Path & link);
+void replaceSymlink(const Path & target, const Path & link,
+ std::optional<time_t> mtime = {});
/* Wrappers arount read()/write() that read/write exactly the