aboutsummaryrefslogtreecommitdiff
path: root/src/libutil
diff options
context:
space:
mode:
authorBen Burdette <bburdette@gmail.com>2021-11-25 08:53:59 -0700
committerBen Burdette <bburdette@gmail.com>2021-11-25 08:53:59 -0700
commit64c4ba8f66c7569478fd5f19ebb72c9590cc2b45 (patch)
tree65d874c35432e81c3d244caadd7c467eccd0b87d /src/libutil
parent69e26c5c4ba106bd16f60bfaac88ccf888b4383f (diff)
parentca82967ee3276e2aa8b02ea7e6d19cfd4fa75f4c (diff)
Merge branch 'master' into debug-merge
Diffstat (limited to 'src/libutil')
-rw-r--r--src/libutil/ansicolor.hh2
-rw-r--r--src/libutil/archive.cc4
-rw-r--r--src/libutil/args.cc1
-rw-r--r--src/libutil/args.hh16
-rw-r--r--src/libutil/closure.hh69
-rw-r--r--src/libutil/comparator.hh4
-rw-r--r--src/libutil/compression.cc26
-rw-r--r--src/libutil/compression.hh4
-rw-r--r--src/libutil/config.cc52
-rw-r--r--src/libutil/config.hh13
-rw-r--r--src/libutil/error.cc10
-rw-r--r--src/libutil/experimental-features.cc59
-rw-r--r--src/libutil/experimental-features.hh56
-rw-r--r--src/libutil/fmt.hh2
-rw-r--r--src/libutil/local.mk2
-rw-r--r--src/libutil/logging.cc6
-rw-r--r--src/libutil/ref.hh55
-rw-r--r--src/libutil/serialise.cc3
-rw-r--r--src/libutil/tarfile.cc33
-rw-r--r--src/libutil/tarfile.hh3
-rw-r--r--src/libutil/tests/closure.cc70
-rw-r--r--src/libutil/tests/logging.cc4
-rw-r--r--src/libutil/tests/tests.cc17
-rw-r--r--src/libutil/url.cc2
-rw-r--r--src/libutil/util.cc184
-rw-r--r--src/libutil/util.hh65
26 files changed, 642 insertions, 120 deletions
diff --git a/src/libutil/ansicolor.hh b/src/libutil/ansicolor.hh
index ae741f867..38305e71c 100644
--- a/src/libutil/ansicolor.hh
+++ b/src/libutil/ansicolor.hh
@@ -9,7 +9,7 @@ namespace nix {
#define ANSI_ITALIC "\e[3m"
#define ANSI_RED "\e[31;1m"
#define ANSI_GREEN "\e[32;1m"
-#define ANSI_YELLOW "\e[33;1m"
+#define ANSI_WARNING "\e[35;1m"
#define ANSI_BLUE "\e[34;1m"
#define ANSI_MAGENTA "\e[35;1m"
#define ANSI_CYAN "\e[36;1m"
diff --git a/src/libutil/archive.cc b/src/libutil/archive.cc
index ed0eb2fb5..d78ec2b93 100644
--- a/src/libutil/archive.cc
+++ b/src/libutil/archive.cc
@@ -42,7 +42,7 @@ static string caseHackSuffix = "~nix~case~hack~";
PathFilter defaultPathFilter = [](const Path &) { return true; };
-static void dumpContents(const Path & path, size_t size,
+static void dumpContents(const Path & path, off_t size,
Sink & sink)
{
sink << "contents" << size;
@@ -76,7 +76,7 @@ static void dump(const Path & path, Sink & sink, PathFilter & filter)
sink << "type" << "regular";
if (st.st_mode & S_IXUSR)
sink << "executable" << "";
- dumpContents(path, (size_t) st.st_size, sink);
+ dumpContents(path, st.st_size, sink);
}
else if (S_ISDIR(st.st_mode)) {
diff --git a/src/libutil/args.cc b/src/libutil/args.cc
index afed0670f..9df279faf 100644
--- a/src/libutil/args.cc
+++ b/src/libutil/args.cc
@@ -331,6 +331,7 @@ MultiCommand::MultiCommand(const Commands & commands_)
if (i == commands.end())
throw UsageError("'%s' is not a recognised command", s);
command = {s, i->second()};
+ command->second->parent = this;
}}
});
diff --git a/src/libutil/args.hh b/src/libutil/args.hh
index c08ba8abd..7521b3065 100644
--- a/src/libutil/args.hh
+++ b/src/libutil/args.hh
@@ -12,6 +12,8 @@ namespace nix {
enum HashType : char;
+class MultiCommand;
+
class Args
{
public:
@@ -89,6 +91,14 @@ protected:
})
, arity(1)
{ }
+
+ template<class I>
+ Handler(std::optional<I> * dest)
+ : fun([=](std::vector<std::string> ss) {
+ *dest = string2IntWithUnitPrefix<I>(ss[0]);
+ })
+ , arity(1)
+ { }
};
/* Options. */
@@ -169,11 +179,13 @@ public:
virtual nlohmann::json toJSON();
friend class MultiCommand;
+
+ MultiCommand * parent = nullptr;
};
/* A command is an argument parser that can be executed by calling its
run() method. */
-struct Command : virtual Args
+struct Command : virtual public Args
{
friend class MultiCommand;
@@ -193,7 +205,7 @@ typedef std::map<std::string, std::function<ref<Command>()>> Commands;
/* An argument parser that supports multiple subcommands,
i.e. ‘<command> <subcommand>’. */
-class MultiCommand : virtual Args
+class MultiCommand : virtual public Args
{
public:
Commands commands;
diff --git a/src/libutil/closure.hh b/src/libutil/closure.hh
new file mode 100644
index 000000000..779b9b2d5
--- /dev/null
+++ b/src/libutil/closure.hh
@@ -0,0 +1,69 @@
+#include <set>
+#include <future>
+#include "sync.hh"
+
+using std::set;
+
+namespace nix {
+
+template<typename T>
+using GetEdgesAsync = std::function<void(const T &, std::function<void(std::promise<set<T>> &)>)>;
+
+template<typename T>
+void computeClosure(
+ const set<T> startElts,
+ set<T> & res,
+ GetEdgesAsync<T> getEdgesAsync
+)
+{
+ struct State
+ {
+ size_t pending;
+ set<T> & res;
+ std::exception_ptr exc;
+ };
+
+ Sync<State> state_(State{0, res, 0});
+
+ std::function<void(const T &)> enqueue;
+
+ std::condition_variable done;
+
+ enqueue = [&](const T & current) -> void {
+ {
+ auto state(state_.lock());
+ if (state->exc) return;
+ if (!state->res.insert(current).second) return;
+ state->pending++;
+ }
+
+ getEdgesAsync(current, [&](std::promise<set<T>> & prom) {
+ try {
+ auto children = prom.get_future().get();
+ for (auto & child : children)
+ enqueue(child);
+ {
+ auto state(state_.lock());
+ assert(state->pending);
+ if (!--state->pending) done.notify_one();
+ }
+ } catch (...) {
+ auto state(state_.lock());
+ if (!state->exc) state->exc = std::current_exception();
+ assert(state->pending);
+ if (!--state->pending) done.notify_one();
+ };
+ });
+ };
+
+ for (auto & startElt : startElts)
+ enqueue(startElt);
+
+ {
+ auto state(state_.lock());
+ while (state->pending) state.wait(done);
+ if (state->exc) std::rethrow_exception(state->exc);
+ }
+}
+
+}
diff --git a/src/libutil/comparator.hh b/src/libutil/comparator.hh
index 0315dc506..eecd5b819 100644
--- a/src/libutil/comparator.hh
+++ b/src/libutil/comparator.hh
@@ -25,6 +25,8 @@
}
#define GENERATE_EQUAL(args...) GENERATE_ONE_CMP(==, args)
#define GENERATE_LEQ(args...) GENERATE_ONE_CMP(<, args)
+#define GENERATE_NEQ(args...) GENERATE_ONE_CMP(!=, args)
#define GENERATE_CMP(args...) \
GENERATE_EQUAL(args) \
- GENERATE_LEQ(args)
+ GENERATE_LEQ(args) \
+ GENERATE_NEQ(args)
diff --git a/src/libutil/compression.cc b/src/libutil/compression.cc
index 7e725cae1..f80ca664c 100644
--- a/src/libutil/compression.cc
+++ b/src/libutil/compression.cc
@@ -12,12 +12,12 @@
#include <brotli/decode.h>
#include <brotli/encode.h>
-#include <zlib.h>
-
#include <iostream>
namespace nix {
+static const int COMPRESSION_LEVEL_DEFAULT = -1;
+
// Don't feed brotli too much at once.
struct ChunkedCompressionSink : CompressionSink
{
@@ -67,14 +67,16 @@ struct ArchiveCompressionSink : CompressionSink
Sink & nextSink;
struct archive * archive;
- ArchiveCompressionSink(Sink & nextSink, std::string format, bool parallel) : nextSink(nextSink) {
+ ArchiveCompressionSink(Sink & nextSink, std::string format, bool parallel, int level = COMPRESSION_LEVEL_DEFAULT) : nextSink(nextSink)
+ {
archive = archive_write_new();
if (!archive) throw Error("failed to initialize libarchive");
check(archive_write_add_filter_by_name(archive, format.c_str()), "couldn't initialize compression (%s)");
check(archive_write_set_format_raw(archive));
- if (format == "xz" && parallel) {
+ if (parallel)
check(archive_write_set_filter_option(archive, format.c_str(), "threads", "0"));
- }
+ if (level != COMPRESSION_LEVEL_DEFAULT)
+ check(archive_write_set_filter_option(archive, format.c_str(), "compression-level", std::to_string(level).c_str()));
// disable internal buffering
check(archive_write_set_bytes_per_block(archive, 0));
// disable output padding
@@ -128,7 +130,11 @@ private:
struct NoneSink : CompressionSink
{
Sink & nextSink;
- NoneSink(Sink & nextSink) : nextSink(nextSink) { }
+ NoneSink(Sink & nextSink, int level = COMPRESSION_LEVEL_DEFAULT) : nextSink(nextSink)
+ {
+ if (level != COMPRESSION_LEVEL_DEFAULT)
+ warn("requested compression level '%d' not supported by compression method 'none'", level);
+ }
void finish() override { flush(); }
void write(std::string_view data) override { nextSink(data); }
};
@@ -259,13 +265,13 @@ struct BrotliCompressionSink : ChunkedCompressionSink
}
};
-ref<CompressionSink> makeCompressionSink(const std::string & method, Sink & nextSink, const bool parallel)
+ref<CompressionSink> makeCompressionSink(const std::string & method, Sink & nextSink, const bool parallel, int level)
{
std::vector<std::string> la_supports = {
"bzip2", "compress", "grzip", "gzip", "lrzip", "lz4", "lzip", "lzma", "lzop", "xz", "zstd"
};
if (std::find(la_supports.begin(), la_supports.end(), method) != la_supports.end()) {
- return make_ref<ArchiveCompressionSink>(nextSink, method, parallel);
+ return make_ref<ArchiveCompressionSink>(nextSink, method, parallel, level);
}
if (method == "none")
return make_ref<NoneSink>(nextSink);
@@ -275,10 +281,10 @@ ref<CompressionSink> makeCompressionSink(const std::string & method, Sink & next
throw UnknownCompressionMethod("unknown compression method '%s'", method);
}
-ref<std::string> compress(const std::string & method, const std::string & in, const bool parallel)
+ref<std::string> compress(const std::string & method, const std::string & in, const bool parallel, int level)
{
StringSink ssink;
- auto sink = makeCompressionSink(method, ssink, parallel);
+ auto sink = makeCompressionSink(method, ssink, parallel, level);
(*sink)(in);
sink->finish();
return ssink.s;
diff --git a/src/libutil/compression.hh b/src/libutil/compression.hh
index 338a0d9f2..9b1e4a9d4 100644
--- a/src/libutil/compression.hh
+++ b/src/libutil/compression.hh
@@ -19,9 +19,9 @@ ref<std::string> decompress(const std::string & method, const std::string & in);
std::unique_ptr<FinishSink> makeDecompressionSink(const std::string & method, Sink & nextSink);
-ref<std::string> compress(const std::string & method, const std::string & in, const bool parallel = false);
+ref<std::string> compress(const std::string & method, const std::string & in, const bool parallel = false, int level = -1);
-ref<CompressionSink> makeCompressionSink(const std::string & method, Sink & nextSink, const bool parallel = false);
+ref<CompressionSink> makeCompressionSink(const std::string & method, Sink & nextSink, const bool parallel = false, int level = -1);
MakeError(UnknownCompressionMethod, Error);
diff --git a/src/libutil/config.cc b/src/libutil/config.cc
index bda07cd55..92ab265d3 100644
--- a/src/libutil/config.cc
+++ b/src/libutil/config.cc
@@ -1,6 +1,7 @@
#include "config.hh"
#include "args.hh"
#include "abstract-setting-to-json.hh"
+#include "experimental-features.hh"
#include <nlohmann/json.hpp>
@@ -152,6 +153,16 @@ nlohmann::json Config::toJSON()
return res;
}
+std::string Config::toKeyValue()
+{
+ auto res = std::string();
+ for (auto & s : _settings)
+ if (!s.second.isAlias) {
+ res += fmt("%s = %s\n", s.first, s.second.setting->to_string());
+ }
+ return res;
+}
+
void Config::convertToArgs(Args & args, const std::string & category)
{
for (auto & s : _settings)
@@ -167,11 +178,6 @@ AbstractSetting::AbstractSetting(
{
}
-void AbstractSetting::setDefault(const std::string & str)
-{
- if (!overridden) set(str);
-}
-
nlohmann::json AbstractSetting::toJSON()
{
return nlohmann::json(toJSONObject());
@@ -308,6 +314,31 @@ template<> std::string BaseSetting<StringSet>::to_string() const
return concatStringsSep(" ", value);
}
+template<> void BaseSetting<std::set<ExperimentalFeature>>::set(const std::string & str, bool append)
+{
+ if (!append) value.clear();
+ for (auto & s : tokenizeString<StringSet>(str)) {
+ auto thisXpFeature = parseExperimentalFeature(s);
+ if (thisXpFeature)
+ value.insert(thisXpFeature.value());
+ else
+ warn("unknown experimental feature '%s'", s);
+ }
+}
+
+template<> bool BaseSetting<std::set<ExperimentalFeature>>::isAppendable()
+{
+ return true;
+}
+
+template<> std::string BaseSetting<std::set<ExperimentalFeature>>::to_string() const
+{
+ StringSet stringifiedXpFeatures;
+ for (auto & feature : value)
+ stringifiedXpFeatures.insert(std::string(showExperimentalFeature(feature)));
+ return concatStringsSep(" ", stringifiedXpFeatures);
+}
+
template<> void BaseSetting<StringMap>::set(const std::string & str, bool append)
{
if (!append) value.clear();
@@ -343,6 +374,7 @@ template class BaseSetting<std::string>;
template class BaseSetting<Strings>;
template class BaseSetting<StringSet>;
template class BaseSetting<StringMap>;
+template class BaseSetting<std::set<ExperimentalFeature>>;
void PathSetting::set(const std::string & str, bool append)
{
@@ -385,6 +417,16 @@ nlohmann::json GlobalConfig::toJSON()
return res;
}
+std::string GlobalConfig::toKeyValue()
+{
+ std::string res;
+ std::map<std::string, Config::SettingInfo> settings;
+ globalConfig.getSettings(settings);
+ for (auto & s : settings)
+ res += fmt("%s = %s\n", s.first, s.second.value);
+ return res;
+}
+
void GlobalConfig::convertToArgs(Args & args, const std::string & category)
{
for (auto & config : *configRegistrations)
diff --git a/src/libutil/config.hh b/src/libutil/config.hh
index bf81b4892..736810bf3 100644
--- a/src/libutil/config.hh
+++ b/src/libutil/config.hh
@@ -100,6 +100,12 @@ public:
virtual nlohmann::json toJSON() = 0;
/**
+ * Outputs all settings in a key-value pair format suitable to be used as
+ * `nix.conf`
+ */
+ virtual std::string toKeyValue() = 0;
+
+ /**
* Converts settings to `Args` to be used on the command line interface
* - args: args to write to
* - category: category of the settings
@@ -169,6 +175,8 @@ public:
nlohmann::json toJSON() override;
+ std::string toKeyValue() override;
+
void convertToArgs(Args & args, const std::string & category) override;
};
@@ -186,8 +194,6 @@ public:
bool overridden = false;
- void setDefault(const std::string & str);
-
protected:
AbstractSetting(
@@ -245,6 +251,7 @@ public:
bool operator !=(const T & v2) const { return value != v2; }
void operator =(const T & v) { assign(v); }
virtual void assign(const T & v) { value = v; }
+ void setDefault(const T & v) { if (!overridden) value = v; }
void set(const std::string & str, bool append = false) override;
@@ -330,6 +337,8 @@ struct GlobalConfig : public AbstractConfig
nlohmann::json toJSON() override;
+ std::string toKeyValue() override;
+
void convertToArgs(Args & args, const std::string & category) override;
struct Register
diff --git a/src/libutil/error.cc b/src/libutil/error.cc
index 0eea3455d..203d79087 100644
--- a/src/libutil/error.cc
+++ b/src/libutil/error.cc
@@ -185,15 +185,15 @@ void printAtPos(const ErrPos & pos, std::ostream & out)
if (pos) {
switch (pos.origin) {
case foFile: {
- out << fmt(ANSI_BLUE "at " ANSI_YELLOW "%s:%s" ANSI_NORMAL ":", pos.file, showErrPos(pos));
+ out << fmt(ANSI_BLUE "at " ANSI_WARNING "%s:%s" ANSI_NORMAL ":", pos.file, showErrPos(pos));
break;
}
case foString: {
- out << fmt(ANSI_BLUE "at " ANSI_YELLOW "«string»:%s" ANSI_NORMAL ":", showErrPos(pos));
+ out << fmt(ANSI_BLUE "at " ANSI_WARNING "«string»:%s" ANSI_NORMAL ":", showErrPos(pos));
break;
}
case foStdin: {
- out << fmt(ANSI_BLUE "at " ANSI_YELLOW "«stdin»:%s" ANSI_NORMAL ":", showErrPos(pos));
+ out << fmt(ANSI_BLUE "at " ANSI_WARNING "«stdin»:%s" ANSI_NORMAL ":", showErrPos(pos));
break;
}
default:
@@ -232,7 +232,7 @@ std::ostream & showErrorInfo(std::ostream & out, const ErrorInfo & einfo, bool s
break;
}
case Verbosity::lvlWarn: {
- prefix = ANSI_YELLOW "warning";
+ prefix = ANSI_WARNING "warning";
break;
}
case Verbosity::lvlInfo: {
@@ -252,7 +252,7 @@ std::ostream & showErrorInfo(std::ostream & out, const ErrorInfo & einfo, bool s
break;
}
case Verbosity::lvlDebug: {
- prefix = ANSI_YELLOW "debug";
+ prefix = ANSI_WARNING "debug";
break;
}
default:
diff --git a/src/libutil/experimental-features.cc b/src/libutil/experimental-features.cc
new file mode 100644
index 000000000..b49f47e1d
--- /dev/null
+++ b/src/libutil/experimental-features.cc
@@ -0,0 +1,59 @@
+#include "experimental-features.hh"
+#include "util.hh"
+
+#include "nlohmann/json.hpp"
+
+namespace nix {
+
+std::map<ExperimentalFeature, std::string> stringifiedXpFeatures = {
+ { Xp::CaDerivations, "ca-derivations" },
+ { Xp::Flakes, "flakes" },
+ { Xp::NixCommand, "nix-command" },
+ { Xp::RecursiveNix, "recursive-nix" },
+ { Xp::NoUrlLiterals, "no-url-literals" },
+};
+
+const std::optional<ExperimentalFeature> parseExperimentalFeature(const std::string_view & name)
+{
+ using ReverseXpMap = std::map<std::string_view, ExperimentalFeature>;
+
+ static auto reverseXpMap = []()
+ {
+ auto reverseXpMap = std::make_unique<ReverseXpMap>();
+ for (auto & [feature, name] : stringifiedXpFeatures)
+ (*reverseXpMap)[name] = feature;
+ return reverseXpMap;
+ }();
+
+ if (auto feature = get(*reverseXpMap, name))
+ return *feature;
+ else
+ return std::nullopt;
+}
+
+std::string_view showExperimentalFeature(const ExperimentalFeature feature)
+{
+ return stringifiedXpFeatures.at(feature);
+}
+
+std::set<ExperimentalFeature> parseFeatures(const std::set<std::string> & rawFeatures)
+{
+ std::set<ExperimentalFeature> res;
+ for (auto & rawFeature : rawFeatures) {
+ if (auto feature = parseExperimentalFeature(rawFeature))
+ res.insert(*feature);
+ }
+ return res;
+}
+
+MissingExperimentalFeature::MissingExperimentalFeature(ExperimentalFeature feature)
+ : Error("experimental Nix feature '%1%' is disabled; use '--extra-experimental-features %1%' to override", showExperimentalFeature(feature))
+ , missingFeature(feature)
+{}
+
+std::ostream & operator <<(std::ostream & str, const ExperimentalFeature & feature)
+{
+ return str << showExperimentalFeature(feature);
+}
+
+}
diff --git a/src/libutil/experimental-features.hh b/src/libutil/experimental-features.hh
new file mode 100644
index 000000000..291a58e32
--- /dev/null
+++ b/src/libutil/experimental-features.hh
@@ -0,0 +1,56 @@
+#pragma once
+
+#include "comparator.hh"
+#include "error.hh"
+#include "nlohmann/json_fwd.hpp"
+#include "types.hh"
+
+namespace nix {
+
+/**
+ * The list of available experimental features.
+ *
+ * If you update this, don’t forget to also change the map defining their
+ * string representation in the corresponding `.cc` file.
+ **/
+enum struct ExperimentalFeature
+{
+ CaDerivations,
+ Flakes,
+ NixCommand,
+ RecursiveNix,
+ NoUrlLiterals
+};
+
+/**
+ * Just because writing `ExperimentalFeature::CaDerivations` is way too long
+ */
+using Xp = ExperimentalFeature;
+
+const std::optional<ExperimentalFeature> parseExperimentalFeature(
+ const std::string_view & name);
+std::string_view showExperimentalFeature(const ExperimentalFeature);
+
+std::ostream & operator<<(
+ std::ostream & str,
+ const ExperimentalFeature & feature);
+
+/**
+ * Parse a set of strings to the corresponding set of experimental features,
+ * ignoring (but warning for) any unkwown feature.
+ */
+std::set<ExperimentalFeature> parseFeatures(const std::set<std::string> &);
+
+class MissingExperimentalFeature : public Error
+{
+public:
+ ExperimentalFeature missingFeature;
+
+ MissingExperimentalFeature(ExperimentalFeature);
+ virtual const char * sname() const override
+ {
+ return "MissingExperimentalFeature";
+ }
+};
+
+}
diff --git a/src/libutil/fmt.hh b/src/libutil/fmt.hh
index 85c0e9429..fd335b811 100644
--- a/src/libutil/fmt.hh
+++ b/src/libutil/fmt.hh
@@ -82,7 +82,7 @@ struct yellowtxt
template <class T>
std::ostream & operator<<(std::ostream & out, const yellowtxt<T> & y)
{
- return out << ANSI_YELLOW << y.value << ANSI_NORMAL;
+ return out << ANSI_WARNING << y.value << ANSI_NORMAL;
}
template <class T>
diff --git a/src/libutil/local.mk b/src/libutil/local.mk
index 3a6415ee3..f880c0fc5 100644
--- a/src/libutil/local.mk
+++ b/src/libutil/local.mk
@@ -6,7 +6,7 @@ libutil_DIR := $(d)
libutil_SOURCES := $(wildcard $(d)/*.cc)
-libutil_LDFLAGS = -pthread $(OPENSSL_LIBS) $(LIBBROTLI_LIBS) $(LIBARCHIVE_LIBS) $(BOOST_LDFLAGS) -lboost_context
+libutil_LDFLAGS += -pthread $(OPENSSL_LIBS) $(LIBBROTLI_LIBS) $(LIBARCHIVE_LIBS) $(BOOST_LDFLAGS) -lboost_context
ifeq ($(HAVE_LIBCPUID), 1)
libutil_LDFLAGS += -lcpuid
diff --git a/src/libutil/logging.cc b/src/libutil/logging.cc
index d2e801175..f8a121ed1 100644
--- a/src/libutil/logging.cc
+++ b/src/libutil/logging.cc
@@ -27,7 +27,7 @@ Logger * logger = makeSimpleLogger(true);
void Logger::warn(const std::string & msg)
{
- log(lvlWarn, ANSI_YELLOW "warning:" ANSI_NORMAL " " + msg);
+ log(lvlWarn, ANSI_WARNING "warning:" ANSI_NORMAL " " + msg);
}
void Logger::writeToStdout(std::string_view s)
@@ -46,7 +46,7 @@ public:
: printBuildLogs(printBuildLogs)
{
systemd = getEnv("IN_SYSTEMD") == "1";
- tty = isatty(STDERR_FILENO);
+ tty = shouldANSI();
}
bool isVerbose() override {
@@ -163,7 +163,7 @@ struct JSONLogger : Logger {
void write(const nlohmann::json & json)
{
- prevLogger.log(lvlError, "@nix " + json.dump());
+ prevLogger.log(lvlError, "@nix " + json.dump(-1, ' ', false, nlohmann::json::error_handler_t::replace));
}
void log(Verbosity lvl, const FormatOrString & fs) override
diff --git a/src/libutil/ref.hh b/src/libutil/ref.hh
index 0be2a7e74..347b81f73 100644
--- a/src/libutil/ref.hh
+++ b/src/libutil/ref.hh
@@ -17,7 +17,7 @@ private:
public:
- ref<T>(const ref<T> & r)
+ ref(const ref<T> & r)
: p(r.p)
{ }
@@ -73,6 +73,16 @@ public:
return ref<T2>((std::shared_ptr<T2>) p);
}
+ bool operator == (const ref<T> & other) const
+ {
+ return p == other.p;
+ }
+
+ bool operator != (const ref<T> & other) const
+ {
+ return p != other.p;
+ }
+
private:
template<typename T2, typename... Args>
@@ -89,4 +99,47 @@ make_ref(Args&&... args)
return ref<T>(p);
}
+
+/* A non-nullable pointer.
+ This is similar to a C++ "& reference", but mutable.
+ This is similar to ref<T> but backed by a regular pointer instead of a smart pointer.
+ */
+template<typename T>
+class ptr {
+private:
+ T * p;
+
+public:
+ ptr<T>(const ptr<T> & r)
+ : p(r.p)
+ { }
+
+ explicit ptr<T>(T * p)
+ : p(p)
+ {
+ if (!p)
+ throw std::invalid_argument("null pointer cast to ptr");
+ }
+
+ T* operator ->() const
+ {
+ return &*p;
+ }
+
+ T& operator *() const
+ {
+ return *p;
+ }
+
+ bool operator == (const ptr<T> & other) const
+ {
+ return p == other.p;
+ }
+
+ bool operator != (const ptr<T> & other) const
+ {
+ return p != other.p;
+ }
+};
+
}
diff --git a/src/libutil/serialise.cc b/src/libutil/serialise.cc
index 374b48d79..16f3476c2 100644
--- a/src/libutil/serialise.cc
+++ b/src/libutil/serialise.cc
@@ -244,7 +244,8 @@ std::unique_ptr<FinishSink> sourceToSink(std::function<void(Source &)> fun)
if (!cur.empty()) (*coro)(false);
}
- void finish() {
+ void finish() override
+ {
if (!coro) return;
if (!*coro) abort();
(*coro)(true);
diff --git a/src/libutil/tarfile.cc b/src/libutil/tarfile.cc
index 24905130d..50e691a3d 100644
--- a/src/libutil/tarfile.cc
+++ b/src/libutil/tarfile.cc
@@ -39,32 +39,30 @@ void TarArchive::check(int err, const std::string & reason)
throw Error(reason, archive_error_string(this->archive));
}
-TarArchive::TarArchive(Source & source, bool raw) : buffer(4096)
+TarArchive::TarArchive(Source & source, bool raw)
+ : source(&source), buffer(4096)
{
- this->archive = archive_read_new();
- this->source = &source;
-
- if (!raw) {
- archive_read_support_filter_all(archive);
+ init();
+ if (!raw)
archive_read_support_format_all(archive);
- } else {
- archive_read_support_filter_all(archive);
+ else
archive_read_support_format_raw(archive);
- archive_read_support_format_empty(archive);
- }
check(archive_read_open(archive, (void *)this, callback_open, callback_read, callback_close), "Failed to open archive (%s)");
}
-
TarArchive::TarArchive(const Path & path)
{
- this->archive = archive_read_new();
-
- archive_read_support_filter_all(archive);
+ init();
archive_read_support_format_all(archive);
check(archive_read_open_filename(archive, path.c_str(), 16384), "failed to open archive: %s");
}
+void TarArchive::init()
+{
+ archive = archive_read_new();
+ archive_read_support_filter_all(archive);
+}
+
void TarArchive::close()
{
check(archive_read_close(this->archive), "Failed to close archive (%s)");
@@ -87,13 +85,16 @@ static void extract_archive(TarArchive & archive, const Path & destDir)
struct archive_entry * entry;
int r = archive_read_next_header(archive.archive, &entry);
if (r == ARCHIVE_EOF) break;
- else if (r == ARCHIVE_WARN)
+ auto name = archive_entry_pathname(entry);
+ if (!name)
+ throw Error("cannot get archive member name: %s", archive_error_string(archive.archive));
+ if (r == ARCHIVE_WARN)
warn(archive_error_string(archive.archive));
else
archive.check(r);
archive_entry_set_pathname(entry,
- (destDir + "/" + archive_entry_pathname(entry)).c_str());
+ (destDir + "/" + name).c_str());
archive.check(archive_read_extract(archive.archive, entry, flags));
}
diff --git a/src/libutil/tarfile.hh b/src/libutil/tarfile.hh
index 4d9141fd4..f107a7e2e 100644
--- a/src/libutil/tarfile.hh
+++ b/src/libutil/tarfile.hh
@@ -17,10 +17,13 @@ struct TarArchive {
// disable copy constructor
TarArchive(const TarArchive &) = delete;
+ void init();
+
void close();
~TarArchive();
};
+
void unpackTarfile(Source & source, const Path & destDir);
void unpackTarfile(const Path & tarFile, const Path & destDir);
diff --git a/src/libutil/tests/closure.cc b/src/libutil/tests/closure.cc
new file mode 100644
index 000000000..7597e7807
--- /dev/null
+++ b/src/libutil/tests/closure.cc
@@ -0,0 +1,70 @@
+#include "closure.hh"
+#include <gtest/gtest.h>
+
+namespace nix {
+
+using namespace std;
+
+map<string, set<string>> testGraph = {
+ { "A", { "B", "C", "G" } },
+ { "B", { "A" } }, // Loops back to A
+ { "C", { "F" } }, // Indirect reference
+ { "D", { "A" } }, // Not reachable, but has backreferences
+ { "E", {} }, // Just not reachable
+ { "F", {} },
+ { "G", { "G" } }, // Self reference
+};
+
+TEST(closure, correctClosure) {
+ set<string> aClosure;
+ set<string> expectedClosure = {"A", "B", "C", "F", "G"};
+ computeClosure<string>(
+ {"A"},
+ aClosure,
+ [&](const string currentNode, function<void(promise<set<string>> &)> processEdges) {
+ promise<set<string>> promisedNodes;
+ promisedNodes.set_value(testGraph[currentNode]);
+ processEdges(promisedNodes);
+ }
+ );
+
+ ASSERT_EQ(aClosure, expectedClosure);
+}
+
+TEST(closure, properlyHandlesDirectExceptions) {
+ struct TestExn {};
+ set<string> aClosure;
+ EXPECT_THROW(
+ computeClosure<string>(
+ {"A"},
+ aClosure,
+ [&](const string currentNode, function<void(promise<set<string>> &)> processEdges) {
+ throw TestExn();
+ }
+ ),
+ TestExn
+ );
+}
+
+TEST(closure, properlyHandlesExceptionsInPromise) {
+ struct TestExn {};
+ set<string> aClosure;
+ EXPECT_THROW(
+ computeClosure<string>(
+ {"A"},
+ aClosure,
+ [&](const string currentNode, function<void(promise<set<string>> &)> processEdges) {
+ promise<set<string>> promise;
+ try {
+ throw TestExn();
+ } catch (...) {
+ promise.set_exception(std::current_exception());
+ }
+ processEdges(promise);
+ }
+ ),
+ TestExn
+ );
+}
+
+}
diff --git a/src/libutil/tests/logging.cc b/src/libutil/tests/logging.cc
index d990e5499..cef3bd481 100644
--- a/src/libutil/tests/logging.cc
+++ b/src/libutil/tests/logging.cc
@@ -336,7 +336,7 @@ namespace nix {
ASSERT_STREQ(
hintfmt("only one arg %1% %2%", "fulfilled").str().c_str(),
- "only one arg " ANSI_YELLOW "fulfilled" ANSI_NORMAL " ");
+ "only one arg " ANSI_WARNING "fulfilled" ANSI_NORMAL " ");
}
@@ -344,7 +344,7 @@ namespace nix {
ASSERT_STREQ(
hintfmt("what about this %1% %2%", "%3%", "one", "two").str().c_str(),
- "what about this " ANSI_YELLOW "%3%" ANSI_NORMAL " " ANSI_YELLOW "one" ANSI_NORMAL);
+ "what about this " ANSI_WARNING "%3%" ANSI_NORMAL " " ANSI_YELLOW "one" ANSI_NORMAL);
}
diff --git a/src/libutil/tests/tests.cc b/src/libutil/tests/tests.cc
index 58df9c5ac..92972ed14 100644
--- a/src/libutil/tests/tests.cc
+++ b/src/libutil/tests/tests.cc
@@ -4,6 +4,8 @@
#include <limits.h>
#include <gtest/gtest.h>
+#include <numeric>
+
namespace nix {
/* ----------- tests for util.hh ------------------------------------------------*/
@@ -282,6 +284,17 @@ namespace nix {
ASSERT_EQ(decoded, s);
}
+ TEST(base64Encode, encodeAndDecodeNonPrintable) {
+ char s[256];
+ std::iota(std::rbegin(s), std::rend(s), 0);
+
+ auto encoded = base64Encode(s);
+ auto decoded = base64Decode(encoded);
+
+ EXPECT_EQ(decoded.length(), 255);
+ ASSERT_EQ(decoded, s);
+ }
+
/* ----------------------------------------------------------------------------
* base64Decode
* --------------------------------------------------------------------------*/
@@ -294,6 +307,10 @@ namespace nix {
ASSERT_EQ(base64Decode("cXVvZCBlcmF0IGRlbW9uc3RyYW5kdW0="), "quod erat demonstrandum");
}
+ TEST(base64Decode, decodeThrowsOnInvalidChar) {
+ ASSERT_THROW(base64Decode("cXVvZCBlcm_0IGRlbW9uc3RyYW5kdW0="), Error);
+ }
+
/* ----------------------------------------------------------------------------
* toLower
* --------------------------------------------------------------------------*/
diff --git a/src/libutil/url.cc b/src/libutil/url.cc
index c1bab866c..f6232d255 100644
--- a/src/libutil/url.cc
+++ b/src/libutil/url.cc
@@ -32,7 +32,7 @@ ParsedURL parseURL(const std::string & url)
auto isFile = scheme.find("file") != std::string::npos;
if (authority && *authority != "" && isFile)
- throw Error("file:// URL '%s' has unexpected authority '%s'",
+ throw BadURL("file:// URL '%s' has unexpected authority '%s'",
url, *authority);
if (isFile && path.empty())
diff --git a/src/libutil/util.cc b/src/libutil/util.cc
index 5f597bf06..defb77a10 100644
--- a/src/libutil/util.cc
+++ b/src/libutil/util.cc
@@ -4,16 +4,18 @@
#include "finally.hh"
#include "serialise.hh"
+#include <array>
#include <cctype>
#include <cerrno>
+#include <climits>
#include <cstdio>
#include <cstdlib>
#include <cstring>
-#include <climits>
+#include <future>
#include <iostream>
+#include <mutex>
#include <sstream>
#include <thread>
-#include <future>
#include <fcntl.h>
#include <grp.h>
@@ -155,6 +157,9 @@ Path canonPath(const Path & path, bool resolveSymlinks)
s.clear(); /* restart for symlinks pointing to absolute path */
} else {
s = dirOf(s);
+ if (s == "/") { // we don’t want trailing slashes here, which dirOf only produces if s = /
+ s.clear();
+ }
}
}
}
@@ -410,7 +415,7 @@ static void _deletePath(int parentfd, const Path & path, uint64_t & bytesFreed)
}
int fd = openat(parentfd, path.c_str(), O_RDONLY);
- if (!fd)
+ if (fd == -1)
throw SysError("opening directory '%1%'", path);
AutoCloseDir dir(fdopendir(fd));
if (!dir)
@@ -432,12 +437,9 @@ static void _deletePath(const Path & path, uint64_t & bytesFreed)
if (dir == "")
dir = "/";
- AutoCloseFD dirfd(open(dir.c_str(), O_RDONLY));
+ AutoCloseFD dirfd{open(dir.c_str(), O_RDONLY)};
if (!dirfd) {
- // This really shouldn't fail silently, but it's left this way
- // for backwards compatibility.
if (errno == ENOENT) return;
-
throw SysError("opening directory '%1%'", path);
}
@@ -560,7 +562,7 @@ Path getConfigDir()
std::vector<Path> getConfigDirs()
{
Path configHome = getConfigDir();
- string configDirs = getEnv("XDG_CONFIG_DIRS").value_or("");
+ string configDirs = getEnv("XDG_CONFIG_DIRS").value_or("/etc/xdg");
std::vector<Path> result = tokenizeString<std::vector<string>>(configDirs, ":");
result.insert(result.begin(), configHome);
return result;
@@ -901,7 +903,7 @@ int Pid::wait()
return status;
}
if (errno != EINTR)
- throw SysError("cannot get child exit status");
+ throw SysError("cannot get exit status of PID %d", pid);
checkInterrupt();
}
}
@@ -937,9 +939,6 @@ void killUser(uid_t uid)
users to which the current process can send signals. So we
fork a process, switch to uid, and send a mass kill. */
- ProcessOptions options;
- options.allowVfork = false;
-
Pid pid = startProcess([&]() {
if (setuid(uid) == -1)
@@ -962,7 +961,7 @@ void killUser(uid_t uid)
}
_exit(0);
- }, options);
+ });
int status = pid.wait();
if (status != 0)
@@ -1032,17 +1031,10 @@ 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);
+ auto res = runProgram(RunOptions {.program = program, .searchPath = searchPath, .args = args, .input = input});
if (!statusOk(res.first))
throw ExecError(res.first, fmt("program '%1%' %2%", program, statusToString(res.first)));
@@ -1051,9 +1043,8 @@ string runProgram(Path program, bool searchPath, const Strings & args,
}
// Output = error code + "standard out" output stream
-std::pair<int, std::string> runProgram(const RunOptions & options_)
+std::pair<int, std::string> runProgram(RunOptions && options)
{
- RunOptions options(options_);
StringSink sink;
options.standardOut = &sink;
@@ -1091,8 +1082,7 @@ void runProgram2(const RunOptions & options)
// vfork implies that the environment of the main process and the fork will
// be shared (technically this is undefined, but in practice that's the
// case), so we can't use it if we alter the environment
- if (options.environment)
- processOptions.allowVfork = false;
+ processOptions.allowVfork = !options.environment;
/* Fork. */
Pid pid = startProcess([&]() {
@@ -1215,7 +1205,7 @@ void closeOnExec(int fd)
//////////////////////////////////////////////////////////////////////
-bool _isInterrupted = false;
+std::atomic<bool> _isInterrupted = false;
static thread_local bool interruptThrown = false;
thread_local std::function<bool()> interruptCheck;
@@ -1369,6 +1359,12 @@ void ignoreException()
}
}
+bool shouldANSI()
+{
+ return isatty(STDERR_FILENO)
+ && getEnv("TERM").value_or("dumb") != "dumb"
+ && !getEnv("NO_COLOR").has_value();
+}
std::string filterANSIEscapes(const std::string & s, bool filterAll, unsigned int width)
{
@@ -1440,8 +1436,7 @@ std::string filterANSIEscapes(const std::string & s, bool filterAll, unsigned in
}
-static char base64Chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
-
+constexpr char base64Chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
string base64Encode(std::string_view s)
{
@@ -1466,15 +1461,15 @@ string base64Encode(std::string_view s)
string base64Decode(std::string_view s)
{
- bool init = false;
- char decode[256];
- if (!init) {
- // FIXME: not thread-safe.
- memset(decode, -1, sizeof(decode));
+ constexpr char npos = -1;
+ constexpr std::array<char, 256> base64DecodeChars = [&]() {
+ std::array<char, 256> result{};
+ for (auto& c : result)
+ c = npos;
for (int i = 0; i < 64; i++)
- decode[(int) base64Chars[i]] = i;
- init = true;
- }
+ result[base64Chars[i]] = i;
+ return result;
+ }();
string res;
unsigned int d = 0, bits = 0;
@@ -1483,8 +1478,8 @@ string base64Decode(std::string_view s)
if (c == '=') break;
if (c == '\n') continue;
- char digit = decode[(unsigned char) c];
- if (digit == -1)
+ char digit = base64DecodeChars[(unsigned char) c];
+ if (digit == npos)
throw Error("invalid character in Base64 string: '%c'", c);
bits += 6;
@@ -1637,9 +1632,39 @@ void setStackSize(size_t stackSize)
#endif
}
-void restoreProcessContext()
+static AutoCloseFD fdSavedMountNamespace;
+
+void saveMountNamespace()
+{
+#if __linux__
+ static std::once_flag done;
+ std::call_once(done, []() {
+ AutoCloseFD fd = open("/proc/self/ns/mnt", O_RDONLY);
+ if (!fd)
+ throw SysError("saving parent mount namespace");
+ fdSavedMountNamespace = std::move(fd);
+ });
+#endif
+}
+
+void restoreMountNamespace()
+{
+#if __linux__
+ try {
+ if (fdSavedMountNamespace && setns(fdSavedMountNamespace.get(), CLONE_NEWNS) == -1)
+ throw SysError("restoring parent mount namespace");
+ } catch (Error & e) {
+ debug(e.msg());
+ }
+#endif
+}
+
+void restoreProcessContext(bool restoreMounts)
{
restoreSignals();
+ if (restoreMounts) {
+ restoreMountNamespace();
+ }
restoreAffinity();
@@ -1677,7 +1702,7 @@ std::unique_ptr<InterruptCallback> createInterruptCallback(std::function<void()>
}
-AutoCloseFD createUnixDomainSocket(const Path & path, mode_t mode)
+AutoCloseFD createUnixDomainSocket()
{
AutoCloseFD fdSocket = socket(PF_UNIX, SOCK_STREAM
#ifdef SOCK_CLOEXEC
@@ -1686,19 +1711,16 @@ AutoCloseFD createUnixDomainSocket(const Path & path, mode_t mode)
, 0);
if (!fdSocket)
throw SysError("cannot create Unix domain socket");
-
closeOnExec(fdSocket.get());
+ return fdSocket;
+}
- struct sockaddr_un addr;
- addr.sun_family = AF_UNIX;
- if (path.size() + 1 >= sizeof(addr.sun_path))
- throw Error("socket path '%1%' is too long", path);
- strcpy(addr.sun_path, path.c_str());
- unlink(path.c_str());
+AutoCloseFD createUnixDomainSocket(const Path & path, mode_t mode)
+{
+ auto fdSocket = nix::createUnixDomainSocket();
- if (bind(fdSocket.get(), (struct sockaddr *) &addr, sizeof(addr)) == -1)
- throw SysError("cannot bind to socket '%1%'", path);
+ bind(fdSocket.get(), path);
if (chmod(path.c_str(), mode) == -1)
throw SysError("changing permissions on '%1%'", path);
@@ -1710,6 +1732,66 @@ AutoCloseFD createUnixDomainSocket(const Path & path, mode_t mode)
}
+void bind(int fd, const std::string & path)
+{
+ unlink(path.c_str());
+
+ struct sockaddr_un addr;
+ addr.sun_family = AF_UNIX;
+
+ if (path.size() + 1 >= sizeof(addr.sun_path)) {
+ Pid pid = startProcess([&]() {
+ auto dir = dirOf(path);
+ if (chdir(dir.c_str()) == -1)
+ throw SysError("chdir to '%s' failed", dir);
+ std::string base(baseNameOf(path));
+ if (base.size() + 1 >= sizeof(addr.sun_path))
+ throw Error("socket path '%s' is too long", base);
+ memcpy(addr.sun_path, base.c_str(), base.size() + 1);
+ if (bind(fd, (struct sockaddr *) &addr, sizeof(addr)) == -1)
+ throw SysError("cannot bind to socket '%s'", path);
+ _exit(0);
+ });
+ int status = pid.wait();
+ if (status != 0)
+ throw Error("cannot bind to socket '%s'", path);
+ } else {
+ memcpy(addr.sun_path, path.c_str(), path.size() + 1);
+ if (bind(fd, (struct sockaddr *) &addr, sizeof(addr)) == -1)
+ throw SysError("cannot bind to socket '%s'", path);
+ }
+}
+
+
+void connect(int fd, const std::string & path)
+{
+ struct sockaddr_un addr;
+ addr.sun_family = AF_UNIX;
+
+ if (path.size() + 1 >= sizeof(addr.sun_path)) {
+ Pid pid = startProcess([&]() {
+ auto dir = dirOf(path);
+ if (chdir(dir.c_str()) == -1)
+ throw SysError("chdir to '%s' failed", dir);
+ std::string base(baseNameOf(path));
+ if (base.size() + 1 >= sizeof(addr.sun_path))
+ throw Error("socket path '%s' is too long", base);
+ memcpy(addr.sun_path, base.c_str(), base.size() + 1);
+ if (connect(fd, (struct sockaddr *) &addr, sizeof(addr)) == -1)
+ throw SysError("cannot connect to socket at '%s'", path);
+ _exit(0);
+ });
+ int status = pid.wait();
+ if (status != 0)
+ throw Error("cannot connect to socket at '%s'", path);
+ } else {
+ memcpy(addr.sun_path, path.c_str(), path.size() + 1);
+ if (connect(fd, (struct sockaddr *) &addr, sizeof(addr)) == -1)
+ throw SysError("cannot connect to socket at '%s'", path);
+ }
+}
+
+
string showBytes(uint64_t bytes)
{
return fmt("%.2f MiB", bytes / (1024.0 * 1024.0));
@@ -1719,8 +1801,10 @@ string showBytes(uint64_t bytes)
// FIXME: move to libstore/build
void commonChildInit(Pipe & logPipe)
{
+ logger = makeSimpleLogger();
+
const static string pathNullDevice = "/dev/null";
- restoreProcessContext();
+ restoreProcessContext(false);
/* Put the child in a separate session (and thus a separate
process group) so that it has no controlling terminal (meaning
diff --git a/src/libutil/util.hh b/src/libutil/util.hh
index f84d0fb31..0bdb37a79 100644
--- a/src/libutil/util.hh
+++ b/src/libutil/util.hh
@@ -259,10 +259,10 @@ void killUser(uid_t uid);
pid to the caller. */
struct ProcessOptions
{
- string errorPrefix = "error: ";
+ string errorPrefix = "";
bool dieWithParent = true;
bool runExitHandlers = false;
- bool allowVfork = true;
+ bool allowVfork = false;
};
pid_t startProcess(std::function<void()> fun, const ProcessOptions & options = ProcessOptions());
@@ -276,26 +276,20 @@ string runProgram(Path program, bool searchPath = false,
struct RunOptions
{
+ Path program;
+ bool searchPath = true;
+ Strings args;
std::optional<uid_t> uid;
std::optional<uid_t> gid;
std::optional<Path> chdir;
std::optional<std::map<std::string, std::string>> environment;
- Path program;
- bool searchPath = true;
- Strings args;
std::optional<std::string> input;
Source * standardIn = nullptr;
Sink * standardOut = nullptr;
bool mergeStderrToStdout = false;
- bool _killStderr = false;
-
- RunOptions(const Path & program, const Strings & args)
- : program(program), args(args) { };
-
- RunOptions & killStderr(bool v) { _killStderr = true; return *this; }
};
-std::pair<int, std::string> runProgram(const RunOptions & options);
+std::pair<int, std::string> runProgram(RunOptions && options);
void runProgram2(const RunOptions & options);
@@ -306,7 +300,15 @@ void setStackSize(size_t stackSize);
/* Restore the original inherited Unix process context (such as signal
masks, stack size, CPU affinity). */
-void restoreProcessContext();
+void restoreProcessContext(bool restoreMounts = true);
+
+/* Save the current mount namespace. Ignored if called more than
+ once. */
+void saveMountNamespace();
+
+/* Restore the mount namespace saved by saveMountNamespace(). Ignored
+ if saveMountNamespace() was never called. */
+void restoreMountNamespace();
class ExecError : public Error
@@ -335,7 +337,7 @@ void closeOnExec(int fd);
/* User interruption. */
-extern bool _isInterrupted;
+extern std::atomic<bool> _isInterrupted;
extern thread_local std::function<bool()> interruptCheck;
@@ -482,6 +484,9 @@ constexpr char treeLast[] = "└───";
constexpr char treeLine[] = "│ ";
constexpr char treeNull[] = " ";
+/* Determine whether ANSI escape sequences are appropriate for the
+ present output. */
+bool shouldANSI();
/* Truncate a string to 'width' printable characters. If 'filterAll'
is true, all ANSI escape sequences are filtered out. Otherwise,
@@ -514,6 +519,29 @@ std::optional<typename T::mapped_type> get(const T & map, const typename T::key_
}
+/* Remove and return the first item from a container. */
+template <class T>
+std::optional<typename T::value_type> remove_begin(T & c)
+{
+ auto i = c.begin();
+ if (i == c.end()) return {};
+ auto v = std::move(*i);
+ c.erase(i);
+ return v;
+}
+
+
+/* Remove and return the first item from a container. */
+template <class T>
+std::optional<typename T::value_type> pop(T & c)
+{
+ if (c.empty()) return {};
+ auto v = std::move(c.front());
+ c.pop();
+ return v;
+}
+
+
template<typename T>
class Callback;
@@ -574,9 +602,18 @@ extern PathFilter defaultPathFilter;
/* Common initialisation performed in child processes. */
void commonChildInit(Pipe & logPipe);
+/* Create a Unix domain socket. */
+AutoCloseFD createUnixDomainSocket();
+
/* Create a Unix domain socket in listen mode. */
AutoCloseFD createUnixDomainSocket(const Path & path, mode_t mode);
+/* Bind a Unix domain socket to a path. */
+void bind(int fd, const std::string & path);
+
+/* Connect to a Unix domain socket. */
+void connect(int fd, const std::string & path);
+
// A Rust/Python-like enumerate() iterator adapter.
// Borrowed from http://reedbeta.com/blog/python-like-enumerate-in-cpp17.