mirror of
https://github.com/NixOS/nix.git
synced 2025-11-11 21:16:02 +01:00
Merge branch 'master' into debug-merge
This commit is contained in:
commit
64c4ba8f66
348 changed files with 20809 additions and 9834 deletions
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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)) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
69
src/libutil/closure.hh
Normal file
69
src/libutil/closure.hh
Normal file
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -99,6 +99,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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
59
src/libutil/experimental-features.cc
Normal file
59
src/libutil/experimental-features.cc
Normal file
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
56
src/libutil/experimental-features.hh
Normal file
56
src/libutil/experimental-features.hh
Normal file
|
|
@ -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";
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
70
src/libutil/tests/closure.cc
Normal file
70
src/libutil/tests/closure.cc
Normal file
|
|
@ -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
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
* --------------------------------------------------------------------------*/
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue