diff options
Diffstat (limited to 'src/libutil')
-rw-r--r-- | src/libutil/archive.cc | 24 | ||||
-rw-r--r-- | src/libutil/archive.hh | 10 | ||||
-rw-r--r-- | src/libutil/args.cc | 149 | ||||
-rw-r--r-- | src/libutil/args.hh | 123 | ||||
-rw-r--r-- | src/libutil/comparator.hh | 30 | ||||
-rw-r--r-- | src/libutil/compression.cc | 113 | ||||
-rw-r--r-- | src/libutil/compute-levels.cc | 80 | ||||
-rw-r--r-- | src/libutil/compute-levels.hh | 7 | ||||
-rw-r--r-- | src/libutil/config.cc | 80 | ||||
-rw-r--r-- | src/libutil/config.hh | 11 | ||||
-rw-r--r-- | src/libutil/error.cc | 240 | ||||
-rw-r--r-- | src/libutil/error.hh | 22 | ||||
-rw-r--r-- | src/libutil/hash.cc | 20 | ||||
-rw-r--r-- | src/libutil/hash.hh | 2 | ||||
-rw-r--r-- | src/libutil/local.mk | 4 | ||||
-rw-r--r-- | src/libutil/logging.cc | 7 | ||||
-rw-r--r-- | src/libutil/logging.hh | 15 | ||||
-rw-r--r-- | src/libutil/serialise.cc | 135 | ||||
-rw-r--r-- | src/libutil/serialise.hh | 108 | ||||
-rw-r--r-- | src/libutil/tarfile.cc | 2 | ||||
-rw-r--r-- | src/libutil/tests/config.cc | 4 | ||||
-rw-r--r-- | src/libutil/tests/logging.cc | 41 | ||||
-rw-r--r-- | src/libutil/tests/tests.cc | 37 | ||||
-rw-r--r-- | src/libutil/types.hh | 12 | ||||
-rw-r--r-- | src/libutil/url-parts.hh | 5 | ||||
-rw-r--r-- | src/libutil/util.cc | 65 | ||||
-rw-r--r-- | src/libutil/util.hh | 54 |
27 files changed, 698 insertions, 702 deletions
diff --git a/src/libutil/archive.cc b/src/libutil/archive.cc index f1479329f..ed0eb2fb5 100644 --- a/src/libutil/archive.cc +++ b/src/libutil/archive.cc @@ -27,7 +27,7 @@ struct ArchiveSettings : Config #endif "use-case-hack", "Whether to enable a Darwin-specific hack for dealing with file name collisions."}; - Setting<bool> preallocateContents{this, true, "preallocate-contents", + Setting<bool> preallocateContents{this, false, "preallocate-contents", "Whether to preallocate files when writing objects with known size."}; }; @@ -50,14 +50,14 @@ static void dumpContents(const Path & path, size_t size, AutoCloseFD fd = open(path.c_str(), O_RDONLY | O_CLOEXEC); if (!fd) throw SysError("opening file '%1%'", path); - std::vector<unsigned char> buf(65536); + std::vector<char> buf(65536); size_t left = size; while (left > 0) { auto n = std::min(left, buf.size()); readFull(fd.get(), buf.data(), n); left -= n; - sink(buf.data(), n); + sink({buf.data(), n}); } writePadding(size, sink); @@ -155,14 +155,14 @@ static void parseContents(ParseSink & sink, Source & source, const Path & path) sink.preallocateContents(size); uint64_t left = size; - std::vector<unsigned char> buf(65536); + std::vector<char> buf(65536); while (left) { checkInterrupt(); auto n = buf.size(); if ((uint64_t)n > left) n = left; source(buf.data(), n); - sink.receiveContents(buf.data(), n); + sink.receiveContents({buf.data(), n}); left -= n; } @@ -300,21 +300,21 @@ struct RestoreSink : ParseSink Path dstPath; AutoCloseFD fd; - void createDirectory(const Path & path) + void createDirectory(const Path & path) override { Path p = dstPath + path; if (mkdir(p.c_str(), 0777) == -1) throw SysError("creating directory '%1%'", p); }; - void createRegularFile(const Path & path) + void createRegularFile(const Path & path) override { Path p = dstPath + path; fd = open(p.c_str(), O_CREAT | O_EXCL | O_WRONLY | O_CLOEXEC, 0666); if (!fd) throw SysError("creating file '%1%'", p); } - void isExecutable() + void isExecutable() override { struct stat st; if (fstat(fd.get(), &st) == -1) @@ -323,7 +323,7 @@ struct RestoreSink : ParseSink throw SysError("fchmod"); } - void preallocateContents(uint64_t len) + void preallocateContents(uint64_t len) override { if (!archiveSettings.preallocateContents) return; @@ -341,12 +341,12 @@ struct RestoreSink : ParseSink #endif } - void receiveContents(unsigned char * data, size_t len) + void receiveContents(std::string_view data) override { - writeFull(fd.get(), data, len); + writeFull(fd.get(), data); } - void createSymlink(const Path & path, const string & target) + void createSymlink(const Path & path, const string & target) override { Path p = dstPath + path; nix::createSymlink(target, p); diff --git a/src/libutil/archive.hh b/src/libutil/archive.hh index 5665732d2..9e9e11b1a 100644 --- a/src/libutil/archive.hh +++ b/src/libutil/archive.hh @@ -58,7 +58,7 @@ struct ParseSink virtual void createRegularFile(const Path & path) { }; virtual void isExecutable() { }; virtual void preallocateContents(uint64_t size) { }; - virtual void receiveContents(unsigned char * data, size_t len) { }; + virtual void receiveContents(std::string_view data) { }; virtual void createSymlink(const Path & path, const string & target) { }; }; @@ -72,17 +72,17 @@ struct RetrieveRegularNARSink : ParseSink RetrieveRegularNARSink(Sink & sink) : sink(sink) { } - void createDirectory(const Path & path) + void createDirectory(const Path & path) override { regular = false; } - void receiveContents(unsigned char * data, size_t len) + void receiveContents(std::string_view data) override { - sink(data, len); + sink(data); } - void createSymlink(const Path & path, const string & target) + void createSymlink(const Path & path, const string & target) override { regular = false; } diff --git a/src/libutil/args.cc b/src/libutil/args.cc index 8bd9c8aeb..75eb19d28 100644 --- a/src/libutil/args.cc +++ b/src/libutil/args.cc @@ -14,6 +14,8 @@ void Args::addFlag(Flag && flag_) assert(flag->handler.arity == flag->labels.size()); assert(flag->longName != ""); longFlags[flag->longName] = flag; + for (auto & alias : flag->aliases) + longFlags[alias] = flag; if (flag->shortName) shortFlags[flag->shortName] = flag; } @@ -58,6 +60,7 @@ void Args::parseCmdline(const Strings & _cmdline) verbosity = lvlError; } + bool argsSeen = false; for (auto pos = cmdline.begin(); pos != cmdline.end(); ) { auto arg = *pos; @@ -86,6 +89,11 @@ void Args::parseCmdline(const Strings & _cmdline) throw UsageError("unrecognised flag '%1%'", arg); } else { + if (!argsSeen) { + argsSeen = true; + initialFlagsProcessed(); + } + pos = rewriteArgs(cmdline, pos); pendingArgs.push_back(*pos++); if (processArgs(pendingArgs, false)) pendingArgs.clear(); @@ -93,41 +101,9 @@ void Args::parseCmdline(const Strings & _cmdline) } processArgs(pendingArgs, true); -} -void Args::printHelp(const string & programName, std::ostream & out) -{ - std::cout << fmt(ANSI_BOLD "Usage:" ANSI_NORMAL " %s " ANSI_ITALIC "FLAGS..." ANSI_NORMAL, programName); - for (auto & exp : expectedArgs) { - std::cout << renderLabels({exp.label}); - // FIXME: handle arity > 1 - if (exp.handler.arity == ArityAny) std::cout << "..."; - if (exp.optional) std::cout << "?"; - } - std::cout << "\n"; - - auto s = description(); - if (s != "") - std::cout << "\n" ANSI_BOLD "Summary:" ANSI_NORMAL " " << s << ".\n"; - - if (longFlags.size()) { - std::cout << "\n"; - std::cout << ANSI_BOLD "Flags:" ANSI_NORMAL "\n"; - printFlags(out); - } -} - -void Args::printFlags(std::ostream & out) -{ - Table2 table; - for (auto & flag : longFlags) { - if (hiddenCategories.count(flag.second->category)) continue; - table.push_back(std::make_pair( - (flag.second->shortName ? std::string("-") + flag.second->shortName + ", " : " ") - + "--" + flag.first + renderLabels(flag.second->labels), - flag.second->description)); - } - printTable(out, table); + if (!argsSeen) + initialFlagsProcessed(); } bool Args::processFlag(Strings::iterator & pos, Strings::iterator end) @@ -225,12 +201,12 @@ nlohmann::json Args::toJSON() for (auto & [name, flag] : longFlags) { auto j = nlohmann::json::object(); + if (flag->aliases.count(name)) continue; if (flag->shortName) j["shortName"] = std::string(1, flag->shortName); if (flag->description != "") j["description"] = flag->description; - if (flag->category != "") - j["category"] = flag->category; + j["category"] = flag->category; if (flag->handler.arity != ArityAny) j["arity"] = flag->handler.arity; if (!flag->labels.empty()) @@ -253,6 +229,8 @@ nlohmann::json Args::toJSON() res["description"] = description(); res["flags"] = std::move(flags); res["args"] = std::move(args); + auto s = doc(); + if (s != "") res.emplace("doc", stripIndentation(s)); return res; } @@ -328,72 +306,14 @@ Strings argvToStrings(int argc, char * * argv) return args; } -std::string renderLabels(const Strings & labels) -{ - std::string res; - for (auto label : labels) { - for (auto & c : label) c = std::toupper(c); - res += " " ANSI_ITALIC + label + ANSI_NORMAL; - } - return res; -} - -void printTable(std::ostream & out, const Table2 & table) -{ - size_t max = 0; - for (auto & row : table) - max = std::max(max, filterANSIEscapes(row.first, true).size()); - for (auto & row : table) { - out << " " << row.first - << std::string(max - filterANSIEscapes(row.first, true).size() + 2, ' ') - << row.second << "\n"; - } -} - -void Command::printHelp(const string & programName, std::ostream & out) -{ - Args::printHelp(programName, out); - - auto exs = examples(); - if (!exs.empty()) { - out << "\n" ANSI_BOLD "Examples:" ANSI_NORMAL "\n"; - for (auto & ex : exs) - out << "\n" - << " " << ex.description << "\n" // FIXME: wrap - << " $ " << ex.command << "\n"; - } -} - -nlohmann::json Command::toJSON() -{ - auto exs = nlohmann::json::array(); - - for (auto & example : examples()) { - auto ex = nlohmann::json::object(); - ex["description"] = example.description; - ex["command"] = chomp(stripIndentation(example.command)); - exs.push_back(std::move(ex)); - } - - auto res = Args::toJSON(); - res["examples"] = std::move(exs); - auto s = doc(); - if (s != "") res.emplace("doc", stripIndentation(s)); - return res; -} - -MultiCommand::MultiCommand(const Commands & commands) - : commands(commands) +MultiCommand::MultiCommand(const Commands & commands_) + : commands(commands_) { expectArgs({ .label = "subcommand", .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)) @@ -409,38 +329,6 @@ MultiCommand::MultiCommand(const Commands & commands) categories[Command::catDefault] = "Available commands"; } -void MultiCommand::printHelp(const string & programName, std::ostream & out) -{ - if (command) { - command->second->printHelp(programName + " " + command->first, out); - return; - } - - out << fmt(ANSI_BOLD "Usage:" ANSI_NORMAL " %s " ANSI_ITALIC "COMMAND FLAGS... ARGS..." ANSI_NORMAL "\n", programName); - - out << "\n" ANSI_BOLD "Common flags:" ANSI_NORMAL "\n"; - printFlags(out); - - std::map<Command::Category, std::map<std::string, ref<Command>>> commandsByCategory; - - for (auto & [name, commandFun] : commands) { - auto command = commandFun(); - commandsByCategory[command->category()].insert_or_assign(name, command); - } - - for (auto & [category, commands] : commandsByCategory) { - out << fmt("\n" ANSI_BOLD "%s:" ANSI_NORMAL "\n", categories[category]); - - Table2 table; - for (auto & [name, command] : commands) { - auto descr = command->description(); - if (!descr.empty()) - table.push_back(std::make_pair(name, descr)); - } - printTable(out, table); - } -} - bool MultiCommand::processFlag(Strings::iterator & pos, Strings::iterator end) { if (Args::processFlag(pos, end)) return true; @@ -463,7 +351,10 @@ nlohmann::json MultiCommand::toJSON() for (auto & [name, commandFun] : commands) { auto command = commandFun(); auto j = command->toJSON(); - j["category"] = categories[command->category()]; + auto cat = nlohmann::json::object(); + cat["id"] = command->category(); + cat["description"] = categories[command->category()]; + j["category"] = std::move(cat); cmds[name] = std::move(j); } diff --git a/src/libutil/args.hh b/src/libutil/args.hh index 26f1bc11b..4721c21df 100644 --- a/src/libutil/args.hh +++ b/src/libutil/args.hh @@ -20,11 +20,12 @@ public: wrong. */ void parseCmdline(const Strings & cmdline); - virtual void printHelp(const string & programName, std::ostream & out); - /* Return a short one-line description of the command. */ virtual std::string description() { return ""; } + /* Return documentation about this command, in Markdown format. */ + virtual std::string doc() { return ""; } + protected: static const size_t ArityAny = std::numeric_limits<size_t>::max(); @@ -65,8 +66,12 @@ protected: , arity(ArityAny) { } - template<class T> - Handler(T * dest) + Handler(std::string * dest) + : fun([=](std::vector<std::string> ss) { *dest = ss[0]; }) + , arity(1) + { } + + Handler(std::optional<std::string> * dest) : fun([=](std::vector<std::string> ss) { *dest = ss[0]; }) , arity(1) { } @@ -76,14 +81,23 @@ protected: : fun([=](std::vector<std::string> ss) { *dest = val; }) , arity(0) { } + + template<class I> + Handler(I * dest) + : fun([=](std::vector<std::string> ss) { + *dest = string2IntWithUnitPrefix<I>(ss[0]); + }) + , arity(1) + { } }; - /* Flags. */ + /* Options. */ struct Flag { typedef std::shared_ptr<Flag> ptr; std::string longName; + std::set<std::string> aliases; char shortName = 0; std::string description; std::string category; @@ -100,8 +114,6 @@ protected: virtual bool processFlag(Strings::iterator & pos, Strings::iterator end); - virtual void printFlags(std::ostream & out); - /* Positional arguments. */ struct ExpectedArg { @@ -115,73 +127,19 @@ protected: virtual bool processArgs(const Strings & args, bool finish); + virtual Strings::iterator rewriteArgs(Strings & args, Strings::iterator pos) + { return pos; } + std::set<std::string> hiddenCategories; + /* Called after all command line flags before the first non-flag + argument (if any) have been processed. */ + virtual void initialFlagsProcessed() {} + public: void addFlag(Flag && flag); - /* Helper functions for constructing flags / positional - arguments. */ - - void mkFlag1(char shortName, const std::string & longName, - const std::string & label, const std::string & description, - std::function<void(std::string)> fun) - { - addFlag({ - .longName = longName, - .shortName = shortName, - .description = description, - .labels = {label}, - .handler = {[=](std::string s) { fun(s); }} - }); - } - - void mkFlag(char shortName, const std::string & name, - const std::string & description, bool * dest) - { - mkFlag(shortName, name, description, dest, true); - } - - template<class T> - void mkFlag(char shortName, const std::string & longName, const std::string & description, - T * dest, const T & value) - { - addFlag({ - .longName = longName, - .shortName = shortName, - .description = description, - .handler = {[=]() { *dest = value; }} - }); - } - - template<class I> - void mkIntFlag(char shortName, const std::string & longName, - const std::string & description, I * dest) - { - mkFlag<I>(shortName, longName, description, [=](I n) { - *dest = n; - }); - } - - template<class I> - void mkFlag(char shortName, const std::string & longName, - const std::string & description, std::function<void(I)> fun) - { - addFlag({ - .longName = longName, - .shortName = shortName, - .description = description, - .labels = {"N"}, - .handler = {[=](std::string s) { - I n; - if (!string2Int(s, n)) - throw UsageError("flag '--%s' requires a integer argument", longName); - fun(n); - }} - }); - } - void expectArgs(ExpectedArg && arg) { expectedArgs.emplace_back(std::move(arg)); @@ -222,28 +180,11 @@ struct Command : virtual Args virtual void prepare() { }; virtual void run() = 0; - /* Return documentation about this command, in Markdown format. */ - virtual std::string doc() { return ""; } - - struct Example - { - std::string description; - std::string command; - }; - - typedef std::list<Example> Examples; - - virtual Examples examples() { return Examples(); } - typedef int Category; static constexpr Category catDefault = 0; virtual Category category() { return catDefault; } - - void printHelp(const string & programName, std::ostream & out) override; - - nlohmann::json toJSON() override; }; typedef std::map<std::string, std::function<ref<Command>()>> Commands; @@ -257,15 +198,11 @@ public: std::map<Command::Category, std::string> categories; - std::map<std::string, std::string> deprecatedAliases; - // Selected command, if any. std::optional<std::pair<std::string, ref<Command>>> command; MultiCommand(const Commands & commands); - void printHelp(const string & programName, std::ostream & out) override; - bool processFlag(Strings::iterator & pos, Strings::iterator end) override; bool processArgs(const Strings & args, bool finish) override; @@ -275,14 +212,6 @@ public: Strings argvToStrings(int argc, char * * argv); -/* Helper function for rendering argument labels. */ -std::string renderLabels(const Strings & labels); - -/* Helper function for printing 2-column tables. */ -typedef std::vector<std::pair<std::string, std::string>> Table2; - -void printTable(std::ostream & out, const Table2 & table); - struct Completion { std::string completion; std::string description; diff --git a/src/libutil/comparator.hh b/src/libutil/comparator.hh new file mode 100644 index 000000000..0315dc506 --- /dev/null +++ b/src/libutil/comparator.hh @@ -0,0 +1,30 @@ +#pragma once + +/* Awfull hacky generation of the comparison operators by doing a lexicographic + * comparison between the choosen fields. + * + * ``` + * GENERATE_CMP(ClassName, me->field1, me->field2, ...) + * ``` + * + * will generate comparison operators semantically equivalent to: + * + * ``` + * bool operator<(const ClassName& other) { + * return field1 < other.field1 && field2 < other.field2 && ...; + * } + * ``` + */ +#define GENERATE_ONE_CMP(COMPARATOR, MY_TYPE, FIELDS...) \ + bool operator COMPARATOR(const MY_TYPE& other) const { \ + const MY_TYPE* me = this; \ + auto fields1 = std::make_tuple( FIELDS ); \ + me = &other; \ + auto fields2 = std::make_tuple( FIELDS ); \ + return fields1 COMPARATOR fields2; \ + } +#define GENERATE_EQUAL(args...) GENERATE_ONE_CMP(==, args) +#define GENERATE_LEQ(args...) GENERATE_ONE_CMP(<, args) +#define GENERATE_CMP(args...) \ + GENERATE_EQUAL(args) \ + GENERATE_LEQ(args) diff --git a/src/libutil/compression.cc b/src/libutil/compression.cc index a117ddc72..986ba2976 100644 --- a/src/libutil/compression.cc +++ b/src/libutil/compression.cc @@ -22,18 +22,17 @@ struct ChunkedCompressionSink : CompressionSink { uint8_t outbuf[32 * 1024]; - void write(const unsigned char * data, size_t len) override + void write(std::string_view data) override { const size_t CHUNK_SIZE = sizeof(outbuf) << 2; - while (len) { - size_t n = std::min(CHUNK_SIZE, len); - writeInternal(data, n); - data += n; - len -= n; + while (!data.empty()) { + size_t n = std::min(CHUNK_SIZE, data.size()); + writeInternal(data); + data.remove_prefix(n); } } - virtual void writeInternal(const unsigned char * data, size_t len) = 0; + virtual void writeInternal(std::string_view data) = 0; }; struct NoneSink : CompressionSink @@ -41,7 +40,7 @@ struct NoneSink : CompressionSink Sink & nextSink; NoneSink(Sink & nextSink) : nextSink(nextSink) { } void finish() override { flush(); } - void write(const unsigned char * data, size_t len) override { nextSink(data, len); } + void write(std::string_view data) override { nextSink(data); } }; struct GzipDecompressionSink : CompressionSink @@ -75,28 +74,28 @@ struct GzipDecompressionSink : CompressionSink void finish() override { CompressionSink::flush(); - write(nullptr, 0); + write({}); } - void write(const unsigned char * data, size_t len) override + void write(std::string_view data) override { - assert(len <= std::numeric_limits<decltype(strm.avail_in)>::max()); + assert(data.size() <= std::numeric_limits<decltype(strm.avail_in)>::max()); - strm.next_in = (Bytef *) data; - strm.avail_in = len; + strm.next_in = (Bytef *) data.data(); + strm.avail_in = data.size(); - while (!finished && (!data || strm.avail_in)) { + while (!finished && (!data.data() || strm.avail_in)) { checkInterrupt(); int ret = inflate(&strm,Z_SYNC_FLUSH); if (ret != Z_OK && ret != Z_STREAM_END) throw CompressionError("error while decompressing gzip file: %d (%d, %d)", - zError(ret), len, strm.avail_in); + zError(ret), data.size(), strm.avail_in); finished = ret == Z_STREAM_END; if (strm.avail_out < sizeof(outbuf) || strm.avail_in == 0) { - nextSink(outbuf, sizeof(outbuf) - strm.avail_out); + nextSink({(char *) outbuf, sizeof(outbuf) - strm.avail_out}); strm.next_out = (Bytef *) outbuf; strm.avail_out = sizeof(outbuf); } @@ -130,25 +129,25 @@ struct XzDecompressionSink : CompressionSink void finish() override { CompressionSink::flush(); - write(nullptr, 0); + write({}); } - void write(const unsigned char * data, size_t len) override + void write(std::string_view data) override { - strm.next_in = data; - strm.avail_in = len; + strm.next_in = (const unsigned char *) data.data(); + strm.avail_in = data.size(); - while (!finished && (!data || strm.avail_in)) { + while (!finished && (!data.data() || strm.avail_in)) { checkInterrupt(); - lzma_ret ret = lzma_code(&strm, data ? LZMA_RUN : LZMA_FINISH); + lzma_ret ret = lzma_code(&strm, data.data() ? LZMA_RUN : LZMA_FINISH); if (ret != LZMA_OK && ret != LZMA_STREAM_END) throw CompressionError("error %d while decompressing xz file", ret); finished = ret == LZMA_STREAM_END; if (strm.avail_out < sizeof(outbuf) || strm.avail_in == 0) { - nextSink(outbuf, sizeof(outbuf) - strm.avail_out); + nextSink({(char *) outbuf, sizeof(outbuf) - strm.avail_out}); strm.next_out = outbuf; strm.avail_out = sizeof(outbuf); } @@ -181,15 +180,15 @@ struct BzipDecompressionSink : ChunkedCompressionSink void finish() override { flush(); - write(nullptr, 0); + write({}); } - void writeInternal(const unsigned char * data, size_t len) override + void writeInternal(std::string_view data) override { - assert(len <= std::numeric_limits<decltype(strm.avail_in)>::max()); + assert(data.size() <= std::numeric_limits<decltype(strm.avail_in)>::max()); - strm.next_in = (char *) data; - strm.avail_in = len; + strm.next_in = (char *) data.data(); + strm.avail_in = data.size(); while (strm.avail_in) { checkInterrupt(); @@ -201,7 +200,7 @@ struct BzipDecompressionSink : ChunkedCompressionSink finished = ret == BZ_STREAM_END; if (strm.avail_out < sizeof(outbuf) || strm.avail_in == 0) { - nextSink(outbuf, sizeof(outbuf) - strm.avail_out); + nextSink({(char *) outbuf, sizeof(outbuf) - strm.avail_out}); strm.next_out = (char *) outbuf; strm.avail_out = sizeof(outbuf); } @@ -230,17 +229,17 @@ struct BrotliDecompressionSink : ChunkedCompressionSink void finish() override { flush(); - writeInternal(nullptr, 0); + writeInternal({}); } - void writeInternal(const unsigned char * data, size_t len) override + void writeInternal(std::string_view data) override { - const uint8_t * next_in = data; - size_t avail_in = len; + auto next_in = (const uint8_t *) data.data(); + size_t avail_in = data.size(); uint8_t * next_out = outbuf; size_t avail_out = sizeof(outbuf); - while (!finished && (!data || avail_in)) { + while (!finished && (!data.data() || avail_in)) { checkInterrupt(); if (!BrotliDecoderDecompressStream(state, @@ -250,7 +249,7 @@ struct BrotliDecompressionSink : ChunkedCompressionSink throw CompressionError("error while decompressing brotli file"); if (avail_out < sizeof(outbuf) || avail_in == 0) { - nextSink(outbuf, sizeof(outbuf) - avail_out); + nextSink({(char *) outbuf, sizeof(outbuf) - avail_out}); next_out = outbuf; avail_out = sizeof(outbuf); } @@ -338,25 +337,25 @@ struct XzCompressionSink : CompressionSink void finish() override { CompressionSink::flush(); - write(nullptr, 0); + write({}); } - void write(const unsigned char * data, size_t len) override + void write(std::string_view data) override { - strm.next_in = data; - strm.avail_in = len; + strm.next_in = (const unsigned char *) data.data(); + strm.avail_in = data.size(); - while (!finished && (!data || strm.avail_in)) { + while (!finished && (!data.data() || strm.avail_in)) { checkInterrupt(); - lzma_ret ret = lzma_code(&strm, data ? LZMA_RUN : LZMA_FINISH); + lzma_ret ret = lzma_code(&strm, data.data() ? LZMA_RUN : LZMA_FINISH); if (ret != LZMA_OK && ret != LZMA_STREAM_END) throw CompressionError("error %d while compressing xz file", ret); finished = ret == LZMA_STREAM_END; if (strm.avail_out < sizeof(outbuf) || strm.avail_in == 0) { - nextSink(outbuf, sizeof(outbuf) - strm.avail_out); + nextSink({(const char *) outbuf, sizeof(outbuf) - strm.avail_out}); strm.next_out = outbuf; strm.avail_out = sizeof(outbuf); } @@ -389,27 +388,27 @@ struct BzipCompressionSink : ChunkedCompressionSink void finish() override { flush(); - writeInternal(nullptr, 0); + writeInternal({}); } - void writeInternal(const unsigned char * data, size_t len) override + void writeInternal(std::string_view data) override { - assert(len <= std::numeric_limits<decltype(strm.avail_in)>::max()); + assert(data.size() <= std::numeric_limits<decltype(strm.avail_in)>::max()); - strm.next_in = (char *) data; - strm.avail_in = len; + strm.next_in = (char *) data.data(); + strm.avail_in = data.size(); - while (!finished && (!data || strm.avail_in)) { + while (!finished && (!data.data() || strm.avail_in)) { checkInterrupt(); - int ret = BZ2_bzCompress(&strm, data ? BZ_RUN : BZ_FINISH); + int ret = BZ2_bzCompress(&strm, data.data() ? BZ_RUN : BZ_FINISH); if (ret != BZ_RUN_OK && ret != BZ_FINISH_OK && ret != BZ_STREAM_END) throw CompressionError("error %d while compressing bzip2 file", ret); finished = ret == BZ_STREAM_END; if (strm.avail_out < sizeof(outbuf) || strm.avail_in == 0) { - nextSink(outbuf, sizeof(outbuf) - strm.avail_out); + nextSink({(const char *) outbuf, sizeof(outbuf) - strm.avail_out}); strm.next_out = (char *) outbuf; strm.avail_out = sizeof(outbuf); } @@ -439,28 +438,28 @@ struct BrotliCompressionSink : ChunkedCompressionSink void finish() override { flush(); - writeInternal(nullptr, 0); + writeInternal({}); } - void writeInternal(const unsigned char * data, size_t len) override + void writeInternal(std::string_view data) override { - const uint8_t * next_in = data; - size_t avail_in = len; + auto next_in = (const uint8_t *) data.data(); + size_t avail_in = data.size(); uint8_t * next_out = outbuf; size_t avail_out = sizeof(outbuf); - while (!finished && (!data || avail_in)) { + while (!finished && (!data.data() || avail_in)) { checkInterrupt(); if (!BrotliEncoderCompressStream(state, - data ? BROTLI_OPERATION_PROCESS : BROTLI_OPERATION_FINISH, + data.data() ? BROTLI_OPERATION_PROCESS : BROTLI_OPERATION_FINISH, &avail_in, &next_in, &avail_out, &next_out, nullptr)) throw CompressionError("error while compressing brotli compression"); if (avail_out < sizeof(outbuf) || avail_in == 0) { - nextSink(outbuf, sizeof(outbuf) - avail_out); + nextSink({(const char *) outbuf, sizeof(outbuf) - avail_out}); next_out = outbuf; avail_out = sizeof(outbuf); } diff --git a/src/libutil/compute-levels.cc b/src/libutil/compute-levels.cc new file mode 100644 index 000000000..19eaedfa8 --- /dev/null +++ b/src/libutil/compute-levels.cc @@ -0,0 +1,80 @@ +#include "types.hh" + +#if HAVE_LIBCPUID +#include <libcpuid/libcpuid.h> +#endif + +namespace nix { + +#if HAVE_LIBCPUID + +StringSet computeLevels() { + StringSet levels; + + if (!cpuid_present()) + return levels; + + cpu_raw_data_t raw; + cpu_id_t data; + + if (cpuid_get_raw_data(&raw) < 0) + return levels; + + if (cpu_identify(&raw, &data) < 0) + return levels; + + if (!(data.flags[CPU_FEATURE_CMOV] && + data.flags[CPU_FEATURE_CX8] && + data.flags[CPU_FEATURE_FPU] && + data.flags[CPU_FEATURE_FXSR] && + data.flags[CPU_FEATURE_MMX] && + data.flags[CPU_FEATURE_SSE] && + data.flags[CPU_FEATURE_SSE2])) + return levels; + + levels.insert("x86_64-v1"); + + if (!(data.flags[CPU_FEATURE_CX16] && + data.flags[CPU_FEATURE_LAHF_LM] && + data.flags[CPU_FEATURE_POPCNT] && + // SSE3 + data.flags[CPU_FEATURE_PNI] && + data.flags[CPU_FEATURE_SSSE3] && + data.flags[CPU_FEATURE_SSE4_1] && + data.flags[CPU_FEATURE_SSE4_2])) + return levels; + + levels.insert("x86_64-v2"); + + if (!(data.flags[CPU_FEATURE_AVX] && + data.flags[CPU_FEATURE_AVX2] && + data.flags[CPU_FEATURE_F16C] && + data.flags[CPU_FEATURE_FMA3] && + // LZCNT + data.flags[CPU_FEATURE_ABM] && + data.flags[CPU_FEATURE_MOVBE])) + return levels; + + levels.insert("x86_64-v3"); + + if (!(data.flags[CPU_FEATURE_AVX512F] && + data.flags[CPU_FEATURE_AVX512BW] && + data.flags[CPU_FEATURE_AVX512CD] && + data.flags[CPU_FEATURE_AVX512DQ] && + data.flags[CPU_FEATURE_AVX512VL])) + return levels; + + levels.insert("x86_64-v4"); + + return levels; +} + +#else + +StringSet computeLevels() { + return StringSet{}; +} + +#endif // HAVE_LIBCPUID + +} diff --git a/src/libutil/compute-levels.hh b/src/libutil/compute-levels.hh new file mode 100644 index 000000000..8ded295f9 --- /dev/null +++ b/src/libutil/compute-levels.hh @@ -0,0 +1,7 @@ +#include "types.hh" + +namespace nix { + +StringSet computeLevels(); + +} diff --git a/src/libutil/config.cc b/src/libutil/config.cc index 521733025..7467e5ac0 100644 --- a/src/libutil/config.cc +++ b/src/libutil/config.cc @@ -8,9 +8,18 @@ namespace nix { bool Config::set(const std::string & name, const std::string & value) { + bool append = false; auto i = _settings.find(name); - if (i == _settings.end()) return false; - i->second.setting->set(value); + if (i == _settings.end()) { + if (hasPrefix(name, "extra-")) { + i = _settings.find(std::string(name, 6)); + if (i == _settings.end() || !i->second.setting->isAppendable()) + return false; + append = true; + } else + return false; + } + i->second.setting->set(value, append); i->second.setting->overriden = true; return true; } @@ -181,18 +190,33 @@ void AbstractSetting::convertToArg(Args & args, const std::string & category) } template<typename T> +bool BaseSetting<T>::isAppendable() +{ + return false; +} + +template<typename T> void BaseSetting<T>::convertToArg(Args & args, const std::string & category) { args.addFlag({ .longName = name, - .description = description, + .description = fmt("Set the `%s` setting.", name), .category = category, .labels = {"value"}, .handler = {[=](std::string s) { overriden = true; set(s); }}, }); + + if (isAppendable()) + args.addFlag({ + .longName = "extra-" + name, + .description = fmt("Append to the `%s` setting.", name), + .category = category, + .labels = {"value"}, + .handler = {[=](std::string s) { overriden = true; set(s, true); }}, + }); } -template<> void BaseSetting<std::string>::set(const std::string & str) +template<> void BaseSetting<std::string>::set(const std::string & str, bool append) { value = str; } @@ -203,10 +227,12 @@ template<> std::string BaseSetting<std::string>::to_string() const } template<typename T> -void BaseSetting<T>::set(const std::string & str) +void BaseSetting<T>::set(const std::string & str, bool append) { static_assert(std::is_integral<T>::value, "Integer required."); - if (!string2Int(str, value)) + if (auto n = string2Int<T>(str)) + value = *n; + else throw UsageError("setting '%s' has invalid value '%s'", name, str); } @@ -217,7 +243,7 @@ std::string BaseSetting<T>::to_string() const return std::to_string(value); } -template<> void BaseSetting<bool>::set(const std::string & str) +template<> void BaseSetting<bool>::set(const std::string & str, bool append) { if (str == "true" || str == "yes" || str == "1") value = true; @@ -236,21 +262,28 @@ template<> void BaseSetting<bool>::convertToArg(Args & args, const std::string & { args.addFlag({ .longName = name, - .description = description, + .description = fmt("Enable the `%s` setting.", name), .category = category, .handler = {[=]() { override(true); }} }); args.addFlag({ .longName = "no-" + name, - .description = description, + .description = fmt("Disable the `%s` setting.", name), .category = category, .handler = {[=]() { override(false); }} }); } -template<> void BaseSetting<Strings>::set(const std::string & str) +template<> void BaseSetting<Strings>::set(const std::string & str, bool append) +{ + auto ss = tokenizeString<Strings>(str); + if (!append) value.clear(); + for (auto & s : ss) value.push_back(std::move(s)); +} + +template<> bool BaseSetting<Strings>::isAppendable() { - value = tokenizeString<Strings>(str); + return true; } template<> std::string BaseSetting<Strings>::to_string() const @@ -258,9 +291,16 @@ template<> std::string BaseSetting<Strings>::to_string() const return concatStringsSep(" ", value); } -template<> void BaseSetting<StringSet>::set(const std::string & str) +template<> void BaseSetting<StringSet>::set(const std::string & str, bool append) +{ + if (!append) value.clear(); + for (auto & s : tokenizeString<StringSet>(str)) + value.insert(s); +} + +template<> bool BaseSetting<StringSet>::isAppendable() { - value = tokenizeString<StringSet>(str); + return true; } template<> std::string BaseSetting<StringSet>::to_string() const @@ -268,11 +308,10 @@ template<> std::string BaseSetting<StringSet>::to_string() const return concatStringsSep(" ", value); } -template<> void BaseSetting<StringMap>::set(const std::string & str) +template<> void BaseSetting<StringMap>::set(const std::string & str, bool append) { - auto kvpairs = tokenizeString<Strings>(str); - for (auto & s : kvpairs) - { + if (!append) value.clear(); + for (auto & s : tokenizeString<Strings>(str)) { auto eq = s.find_first_of('='); if (std::string::npos != eq) value.emplace(std::string(s, 0, eq), std::string(s, eq + 1)); @@ -280,6 +319,11 @@ template<> void BaseSetting<StringMap>::set(const std::string & str) } } +template<> bool BaseSetting<StringMap>::isAppendable() +{ + return true; +} + template<> std::string BaseSetting<StringMap>::to_string() const { Strings kvstrs; @@ -300,7 +344,7 @@ template class BaseSetting<Strings>; template class BaseSetting<StringSet>; template class BaseSetting<StringMap>; -void PathSetting::set(const std::string & str) +void PathSetting::set(const std::string & str, bool append) { if (str == "") { if (allowEmpty) diff --git a/src/libutil/config.hh b/src/libutil/config.hh index 1f5f4e7b9..71e31656d 100644 --- a/src/libutil/config.hh +++ b/src/libutil/config.hh @@ -202,7 +202,10 @@ protected: assert(created == 123); } - virtual void set(const std::string & value) = 0; + virtual void set(const std::string & value, bool append = false) = 0; + + virtual bool isAppendable() + { return false; } virtual std::string to_string() const = 0; @@ -243,7 +246,9 @@ public: void operator =(const T & v) { assign(v); } virtual void assign(const T & v) { value = v; } - void set(const std::string & str) override; + void set(const std::string & str, bool append = false) override; + + bool isAppendable() override; virtual void override(const T & v) { @@ -305,7 +310,7 @@ public: options->addSetting(this); } - void set(const std::string & str) override; + void set(const std::string & str, bool append = false) override; Path operator +(const char * p) const { return value + p; } diff --git a/src/libutil/error.cc b/src/libutil/error.cc index 803a72953..0eea3455d 100644 --- a/src/libutil/error.cc +++ b/src/libutil/error.cc @@ -43,9 +43,9 @@ string showErrPos(const ErrPos & errPos) { if (errPos.line > 0) { if (errPos.column > 0) { - return fmt("(%1%:%2%)", errPos.line, errPos.column); + return fmt("%d:%d", errPos.line, errPos.column); } else { - return fmt("(%1%)", errPos.line); + return fmt("%d", errPos.line); } } else { @@ -61,36 +61,31 @@ std::optional<LinesOfCode> getCodeLines(const ErrPos & errPos) if (errPos.origin == foFile) { LinesOfCode loc; try { + // FIXME: when running as the daemon, make sure we don't + // open a file to which the client doesn't have access. AutoCloseFD fd = open(errPos.file.c_str(), O_RDONLY | O_CLOEXEC); - if (!fd) { - logError(SysError("opening file '%1%'", errPos.file).info()); - return std::nullopt; - } - else + if (!fd) return {}; + + // count the newlines. + int count = 0; + string line; + int pl = errPos.line - 1; + do { - // count the newlines. - int count = 0; - string line; - int pl = errPos.line - 1; - do - { - line = readLine(fd.get()); - ++count; - if (count < pl) - { - ; - } - else if (count == pl) { - loc.prevLineOfCode = line; - } else if (count == pl + 1) { - loc.errLineOfCode = line; - } else if (count == pl + 2) { - loc.nextLineOfCode = line; - break; - } - } while (true); - return loc; - } + line = readLine(fd.get()); + ++count; + if (count < pl) + ; + else if (count == pl) + loc.prevLineOfCode = line; + else if (count == pl + 1) + loc.errLineOfCode = line; + else if (count == pl + 2) { + loc.nextLineOfCode = line; + break; + } + } while (true); + return loc; } catch (EndOfFile & eof) { if (loc.errLineOfCode.has_value()) @@ -99,7 +94,6 @@ std::optional<LinesOfCode> getCodeLines(const ErrPos & errPos) return std::nullopt; } catch (std::exception & e) { - printError("error reading nix file: %s\n%s", errPos.file, e.what()); return std::nullopt; } } else { @@ -186,24 +180,20 @@ void printCodeLines(std::ostream & out, } } -void printAtPos(const string & prefix, const ErrPos & pos, std::ostream & out) +void printAtPos(const ErrPos & pos, std::ostream & out) { - if (pos) - { + if (pos) { switch (pos.origin) { case foFile: { - out << prefix << ANSI_BLUE << "at: " << ANSI_YELLOW << showErrPos(pos) << - ANSI_BLUE << " in file: " << ANSI_NORMAL << pos.file; + out << fmt(ANSI_BLUE "at " ANSI_YELLOW "%s:%s" ANSI_NORMAL ":", pos.file, showErrPos(pos)); break; } case foString: { - out << prefix << ANSI_BLUE << "at: " << ANSI_YELLOW << showErrPos(pos) << - ANSI_BLUE << " from string" << ANSI_NORMAL; + out << fmt(ANSI_BLUE "at " ANSI_YELLOW "«string»:%s" ANSI_NORMAL ":", showErrPos(pos)); break; } case foStdin: { - out << prefix << ANSI_BLUE << "at: " << ANSI_YELLOW << showErrPos(pos) << - ANSI_BLUE << " from stdin" << ANSI_NORMAL; + out << fmt(ANSI_BLUE "at " ANSI_YELLOW "«stdin»:%s" ANSI_NORMAL ":", showErrPos(pos)); break; } default: @@ -212,168 +202,108 @@ void printAtPos(const string & prefix, const ErrPos & pos, std::ostream & out) } } -std::ostream & showErrorInfo(std::ostream & out, const ErrorInfo & einfo, bool showTrace) +static std::string indent(std::string_view indentFirst, std::string_view indentRest, std::string_view s) { - auto errwidth = std::max<size_t>(getWindowSize().second, 20); - string prefix = ""; + std::string res; + bool first = true; + + while (!s.empty()) { + auto end = s.find('\n'); + if (!first) res += "\n"; + res += chomp(std::string(first ? indentFirst : indentRest) + std::string(s.substr(0, end))); + first = false; + if (end == s.npos) break; + s = s.substr(end + 1); + } - string levelString; + return res; +} + +std::ostream & showErrorInfo(std::ostream & out, const ErrorInfo & einfo, bool showTrace) +{ + std::string prefix; switch (einfo.level) { case Verbosity::lvlError: { - levelString = ANSI_RED; - levelString += "error:"; - levelString += ANSI_NORMAL; + prefix = ANSI_RED "error"; + break; + } + case Verbosity::lvlNotice: { + prefix = ANSI_RED "note"; break; } case Verbosity::lvlWarn: { - levelString = ANSI_YELLOW; - levelString += "warning:"; - levelString += ANSI_NORMAL; + prefix = ANSI_YELLOW "warning"; break; } case Verbosity::lvlInfo: { - levelString = ANSI_GREEN; - levelString += "info:"; - levelString += ANSI_NORMAL; + prefix = ANSI_GREEN "info"; break; } case Verbosity::lvlTalkative: { - levelString = ANSI_GREEN; - levelString += "talk:"; - levelString += ANSI_NORMAL; + prefix = ANSI_GREEN "talk"; break; } case Verbosity::lvlChatty: { - levelString = ANSI_GREEN; - levelString += "chat:"; - levelString += ANSI_NORMAL; + prefix = ANSI_GREEN "chat"; break; } case Verbosity::lvlVomit: { - levelString = ANSI_GREEN; - levelString += "vomit:"; - levelString += ANSI_NORMAL; + prefix = ANSI_GREEN "vomit"; break; } case Verbosity::lvlDebug: { - levelString = ANSI_YELLOW; - levelString += "debug:"; - levelString += ANSI_NORMAL; - break; - } - default: { - levelString = fmt("invalid error level: %1%", einfo.level); + prefix = ANSI_YELLOW "debug"; break; } + default: + assert(false); } - auto ndl = prefix.length() - + filterANSIEscapes(levelString, true).length() - + 7 - + einfo.name.length() - + einfo.programName.value_or("").length(); - auto dashwidth = std::max<int>(errwidth - ndl, 3); - - std::string dashes(dashwidth, '-'); - - // divider. - if (einfo.name != "") - out << fmt("%1%%2%" ANSI_BLUE " --- %3% %4% %5%" ANSI_NORMAL, - prefix, - levelString, - einfo.name, - dashes, - einfo.programName.value_or("")); + // FIXME: show the program name as part of the trace? + if (einfo.programName && einfo.programName != ErrorInfo::programName) + prefix += fmt(" [%s]:" ANSI_NORMAL " ", einfo.programName.value_or("")); else - out << fmt("%1%%2%" ANSI_BLUE " -----%3% %4%" ANSI_NORMAL, - prefix, - levelString, - dashes, - einfo.programName.value_or("")); - - bool nl = false; // intersperse newline between sections. - if (einfo.errPos.has_value() && (*einfo.errPos)) { - out << prefix << std::endl; - printAtPos(prefix, *einfo.errPos, out); - nl = true; - } + prefix += ":" ANSI_NORMAL " "; - // description - if (einfo.description != "") { - if (nl) - out << std::endl << prefix; - out << std::endl << prefix << einfo.description; - nl = true; - } + std::ostringstream oss; + oss << einfo.msg << "\n"; + + if (einfo.errPos.has_value() && *einfo.errPos) { + oss << "\n"; + printAtPos(*einfo.errPos, oss); - if (einfo.errPos.has_value() && (*einfo.errPos)) { auto loc = getCodeLines(*einfo.errPos); // lines of code. if (loc.has_value()) { - if (nl) - out << std::endl << prefix; - printCodeLines(out, prefix, *einfo.errPos, *loc); - nl = true; + oss << "\n"; + printCodeLines(oss, "", *einfo.errPos, *loc); + oss << "\n"; } } - // hint - if (einfo.hint.has_value()) { - if (nl) - out << std::endl << prefix; - out << std::endl << prefix << *einfo.hint; - nl = true; - } - // traces - if (showTrace && !einfo.traces.empty()) - { - const string tracetitle(" show-trace "); - - int fill = errwidth - tracetitle.length(); - int lw = 0; - int rw = 0; - const int min_dashes = 3; - if (fill > min_dashes * 2) { - if (fill % 2 != 0) { - lw = fill / 2; - rw = lw + 1; - } - else - { - lw = rw = fill / 2; - } - } - else - lw = rw = min_dashes; - - if (nl) - out << std::endl << prefix; - - out << ANSI_BLUE << std::string(lw, '-') << tracetitle << std::string(rw, '-') << ANSI_NORMAL; - - for (auto iter = einfo.traces.rbegin(); iter != einfo.traces.rend(); ++iter) - { - out << std::endl << prefix; - out << ANSI_BLUE << "trace: " << ANSI_NORMAL << iter->hint.str(); + if (showTrace && !einfo.traces.empty()) { + for (auto iter = einfo.traces.rbegin(); iter != einfo.traces.rend(); ++iter) { + oss << "\n" << "… " << iter->hint.str() << "\n"; if (iter->pos.has_value() && (*iter->pos)) { auto pos = iter->pos.value(); - out << std::endl << prefix; - printAtPos(prefix, pos, out); + oss << "\n"; + printAtPos(pos, oss); auto loc = getCodeLines(pos); - if (loc.has_value()) - { - out << std::endl << prefix; - printCodeLines(out, prefix, pos, *loc); - out << std::endl << prefix; + if (loc.has_value()) { + oss << "\n"; + printCodeLines(oss, "", pos, *loc); + oss << "\n"; } } } } + out << indent(prefix, std::string(filterANSIEscapes(prefix, true).size(), ' '), chomp(oss.str())); + return out; } } diff --git a/src/libutil/error.hh b/src/libutil/error.hh index 97e11c98e..e55986d6f 100644 --- a/src/libutil/error.hh +++ b/src/libutil/error.hh @@ -38,13 +38,14 @@ namespace nix { ErrorInfo structs are sent to the logger as part of an exception, or directly with the logError or logWarning macros. - See the error-demo.cc program for usage examples. + See libutil/tests/logging.cc for usage examples. */ typedef enum { lvlError = 0, lvlWarn, + lvlNotice, lvlInfo, lvlTalkative, lvlChatty, @@ -106,9 +107,8 @@ struct Trace { struct ErrorInfo { Verbosity level; - string name; - string description; // FIXME: remove? it seems to be barely used - std::optional<hintformat> hint; + string name; // FIXME: rename + hintformat msg; std::optional<ErrPos> errPos; std::list<Trace> traces; @@ -132,23 +132,17 @@ public: template<typename... Args> BaseError(unsigned int status, const Args & ... args) - : err {.level = lvlError, - .hint = hintfmt(args...) - } + : err { .level = lvlError, .msg = hintfmt(args...) } , status(status) { } template<typename... Args> BaseError(const std::string & fs, const Args & ... args) - : err {.level = lvlError, - .hint = hintfmt(fs, args...) - } + : err { .level = lvlError, .msg = hintfmt(fs, args...) } { } BaseError(hintformat hint) - : err {.level = lvlError, - .hint = hint - } + : err { .level = lvlError, .msg = hint } { } BaseError(ErrorInfo && e) @@ -204,7 +198,7 @@ public: : Error(""), errNo(errno) { auto hf = hintfmt(args...); - err.hint = hintfmt("%1%: %2%", normaltxt(hf.str()), strerror(errNo)); + err.msg = hintfmt("%1%: %2%", normaltxt(hf.str()), strerror(errNo)); } virtual const char* sname() const override { return "SysError"; } diff --git a/src/libutil/hash.cc b/src/libutil/hash.cc index 4a94f0dfd..4df8b4ecb 100644 --- a/src/libutil/hash.cc +++ b/src/libutil/hash.cc @@ -192,7 +192,7 @@ Hash Hash::parseAny(std::string_view original, std::optional<HashType> optType) // Either the string or user must provide the type, if they both do they // must agree. if (!optParsedType && !optType) - throw BadHash("hash '%s' does not include a type, nor is the type otherwise known from context.", rest); + throw BadHash("hash '%s' does not include a type, nor is the type otherwise known from context", rest); else if (optParsedType && optType && *optParsedType != *optType) throw BadHash("hash '%s' should have type '%s'", original, printHashType(*optType)); @@ -291,12 +291,12 @@ static void start(HashType ht, Ctx & ctx) static void update(HashType ht, Ctx & ctx, - const unsigned char * bytes, size_t len) + std::string_view data) { - if (ht == htMD5) MD5_Update(&ctx.md5, bytes, len); - else if (ht == htSHA1) SHA1_Update(&ctx.sha1, bytes, len); - else if (ht == htSHA256) SHA256_Update(&ctx.sha256, bytes, len); - else if (ht == htSHA512) SHA512_Update(&ctx.sha512, bytes, len); + if (ht == htMD5) MD5_Update(&ctx.md5, data.data(), data.size()); + else if (ht == htSHA1) SHA1_Update(&ctx.sha1, data.data(), data.size()); + else if (ht == htSHA256) SHA256_Update(&ctx.sha256, data.data(), data.size()); + else if (ht == htSHA512) SHA512_Update(&ctx.sha512, data.data(), data.size()); } @@ -314,7 +314,7 @@ Hash hashString(HashType ht, std::string_view s) Ctx ctx; Hash hash(ht); start(ht, ctx); - update(ht, ctx, (const unsigned char *) s.data(), s.length()); + update(ht, ctx, s); finish(ht, ctx, hash.hash); return hash; } @@ -341,10 +341,10 @@ HashSink::~HashSink() delete ctx; } -void HashSink::write(const unsigned char * data, size_t len) +void HashSink::write(std::string_view data) { - bytes += len; - update(ht, *ctx, data, len); + bytes += data.size(); + update(ht, *ctx, data); } HashResult HashSink::finish() diff --git a/src/libutil/hash.hh b/src/libutil/hash.hh index 6d6eb70ca..1b626dd85 100644 --- a/src/libutil/hash.hh +++ b/src/libutil/hash.hh @@ -156,7 +156,7 @@ public: HashSink(HashType ht); HashSink(const HashSink & h); ~HashSink(); - void write(const unsigned char * data, size_t len) override; + void write(std::string_view data) override; HashResult finish() override; HashResult currentHash(); }; diff --git a/src/libutil/local.mk b/src/libutil/local.mk index ae7eb67ad..5341c58e6 100644 --- a/src/libutil/local.mk +++ b/src/libutil/local.mk @@ -7,3 +7,7 @@ libutil_DIR := $(d) libutil_SOURCES := $(wildcard $(d)/*.cc) libutil_LDFLAGS = $(LIBLZMA_LIBS) -lbz2 -pthread $(OPENSSL_LIBS) $(LIBBROTLI_LIBS) $(LIBARCHIVE_LIBS) $(BOOST_LDFLAGS) -lboost_context + +ifeq ($(HAVE_LIBCPUID), 1) + libutil_LDFLAGS += -lcpuid +endif diff --git a/src/libutil/logging.cc b/src/libutil/logging.cc index 8a6752e22..d2e801175 100644 --- a/src/libutil/logging.cc +++ b/src/libutil/logging.cc @@ -184,7 +184,7 @@ struct JSONLogger : Logger { json["action"] = "msg"; json["level"] = ei.level; json["msg"] = oss.str(); - json["raw_msg"] = ei.hint->str(); + json["raw_msg"] = ei.msg.str(); if (ei.errPos.has_value() && (*ei.errPos)) { json["line"] = ei.errPos->line; @@ -305,10 +305,7 @@ bool handleJSONLogMessage(const std::string & msg, } } catch (std::exception & e) { - logError({ - .name = "Json log message", - .hint = hintfmt("bad log message from builder: %s", e.what()) - }); + printError("bad JSON log message from builder: %s", e.what()); } return true; diff --git a/src/libutil/logging.hh b/src/libutil/logging.hh index 63cb2b268..96ad69790 100644 --- a/src/libutil/logging.hh +++ b/src/libutil/logging.hh @@ -100,12 +100,15 @@ public: virtual void writeToStdout(std::string_view s); template<typename... Args> - inline void stdout(const std::string & fs, const Args & ... args) + inline void cout(const std::string & fs, const Args & ... args) { boost::format f(fs); formatHelper(f, args...); writeToStdout(f.str()); } + + virtual std::optional<char> ask(std::string_view s) + { return {}; } }; ActivityId getCurActivity(); @@ -175,8 +178,8 @@ extern Verbosity verbosity; /* suppress msgs > this */ lightweight status messages. */ #define logErrorInfo(level, errorInfo...) \ do { \ - if (level <= nix::verbosity) { \ - logger->logEI(level, errorInfo); \ + if ((level) <= nix::verbosity) { \ + logger->logEI((level), errorInfo); \ } \ } while (0) @@ -188,12 +191,14 @@ extern Verbosity verbosity; /* suppress msgs > this */ arguments are evaluated lazily. */ #define printMsg(level, args...) \ do { \ - if (level <= nix::verbosity) { \ - logger->log(level, fmt(args)); \ + auto __lvl = level; \ + if (__lvl <= nix::verbosity) { \ + logger->log(__lvl, fmt(args)); \ } \ } while (0) #define printError(args...) printMsg(lvlError, args) +#define notice(args...) printMsg(lvlNotice, args) #define printInfo(args...) printMsg(lvlInfo, args) #define printTalkative(args...) printMsg(lvlTalkative, args) #define debug(args...) printMsg(lvlDebug, args) diff --git a/src/libutil/serialise.cc b/src/libutil/serialise.cc index 5c9f6f901..d1a16b6ba 100644 --- a/src/libutil/serialise.cc +++ b/src/libutil/serialise.cc @@ -11,23 +11,23 @@ namespace nix { -void BufferedSink::operator () (const unsigned char * data, size_t len) +void BufferedSink::operator () (std::string_view data) { - if (!buffer) buffer = decltype(buffer)(new unsigned char[bufSize]); + if (!buffer) buffer = decltype(buffer)(new char[bufSize]); - while (len) { + while (!data.empty()) { /* Optimisation: bypass the buffer if the data exceeds the buffer size. */ - if (bufPos + len >= bufSize) { + if (bufPos + data.size() >= bufSize) { flush(); - write(data, len); + write(data); break; } /* Otherwise, copy the bytes to the buffer. Flush the buffer when it's full. */ - size_t n = bufPos + len > bufSize ? bufSize - bufPos : len; - memcpy(buffer.get() + bufPos, data, n); - data += n; bufPos += n; len -= n; + size_t n = bufPos + data.size() > bufSize ? bufSize - bufPos : data.size(); + memcpy(buffer.get() + bufPos, data.data(), n); + data.remove_prefix(n); bufPos += n; if (bufPos == bufSize) flush(); } } @@ -38,7 +38,7 @@ void BufferedSink::flush() if (bufPos == 0) return; size_t n = bufPos; bufPos = 0; // don't trigger the assert() in ~BufferedSink() - write(buffer.get(), n); + write({buffer.get(), n}); } @@ -52,16 +52,13 @@ size_t threshold = 256 * 1024 * 1024; static void warnLargeDump() { - logWarning({ - .name = "Large path", - .description = "dumping very large path (> 256 MiB); this may run out of memory" - }); + warn("dumping very large path (> 256 MiB); this may run out of memory"); } -void FdSink::write(const unsigned char * data, size_t len) +void FdSink::write(std::string_view data) { - written += len; + written += data.size(); static bool warned = false; if (warn && !warned) { if (written > threshold) { @@ -70,7 +67,7 @@ void FdSink::write(const unsigned char * data, size_t len) } } try { - writeFull(fd, data, len); + writeFull(fd, data); } catch (SysError & e) { _good = false; throw; @@ -84,7 +81,7 @@ bool FdSink::good() } -void Source::operator () (unsigned char * data, size_t len) +void Source::operator () (char * data, size_t len) { while (len) { size_t n = read(data, len); @@ -96,12 +93,12 @@ void Source::operator () (unsigned char * data, size_t len) void Source::drainInto(Sink & sink) { std::string s; - std::vector<unsigned char> buf(8192); + std::vector<char> buf(8192); while (true) { size_t n; try { n = read(buf.data(), buf.size()); - sink(buf.data(), n); + sink({buf.data(), n}); } catch (EndOfFile &) { break; } @@ -117,9 +114,9 @@ std::string Source::drain() } -size_t BufferedSource::read(unsigned char * data, size_t len) +size_t BufferedSource::read(char * data, size_t len) { - if (!buffer) buffer = decltype(buffer)(new unsigned char[bufSize]); + if (!buffer) buffer = decltype(buffer)(new char[bufSize]); if (!bufPosIn) bufPosIn = readUnbuffered(buffer.get(), bufSize); @@ -138,12 +135,12 @@ bool BufferedSource::hasData() } -size_t FdSource::readUnbuffered(unsigned char * data, size_t len) +size_t FdSource::readUnbuffered(char * data, size_t len) { ssize_t n; do { checkInterrupt(); - n = ::read(fd, (char *) data, len); + n = ::read(fd, data, len); } while (n == -1 && errno == EINTR); if (n == -1) { _good = false; throw SysError("reading from file"); } if (n == 0) { _good = false; throw EndOfFile("unexpected end-of-file"); } @@ -158,10 +155,10 @@ bool FdSource::good() } -size_t StringSource::read(unsigned char * data, size_t len) +size_t StringSource::read(char * data, size_t len) { if (pos == s.size()) throw EndOfFile("end of string reached"); - size_t n = s.copy((char *) data, len, pos); + size_t n = s.copy(data, len, pos); pos += n; return n; } @@ -171,6 +168,39 @@ size_t StringSource::read(unsigned char * data, size_t len) #error Coroutines are broken in this version of Boost! #endif +/* A concrete datatype allow virtual dispatch of stack allocation methods. */ +struct VirtualStackAllocator { + StackAllocator *allocator = StackAllocator::defaultAllocator; + + boost::context::stack_context allocate() { + return allocator->allocate(); + } + + void deallocate(boost::context::stack_context sctx) { + allocator->deallocate(sctx); + } +}; + + +/* This class reifies the default boost coroutine stack allocation strategy with + a virtual interface. */ +class DefaultStackAllocator : public StackAllocator { + boost::coroutines2::default_stack stack; + + boost::context::stack_context allocate() { + return stack.allocate(); + } + + void deallocate(boost::context::stack_context sctx) { + stack.deallocate(sctx); + } +}; + +static DefaultStackAllocator defaultAllocatorSingleton; + +StackAllocator *StackAllocator::defaultAllocator = &defaultAllocatorSingleton; + + std::unique_ptr<Source> sinkToSource( std::function<void(Sink &)> fun, std::function<void()> eof) @@ -192,13 +222,13 @@ std::unique_ptr<Source> sinkToSource( std::string cur; size_t pos = 0; - size_t read(unsigned char * data, size_t len) override + size_t read(char * data, size_t len) override { if (!coro) - coro = coro_t::pull_type([&](coro_t::push_type & yield) { - LambdaSink sink([&](const unsigned char * data, size_t len) { - if (len) yield(std::string((const char *) data, len)); - }); + coro = coro_t::pull_type(VirtualStackAllocator{}, [&](coro_t::push_type & yield) { + LambdaSink sink([&](std::string_view data) { + if (!data.empty()) yield(std::string(data)); + }); fun(sink); }); @@ -211,7 +241,7 @@ std::unique_ptr<Source> sinkToSource( } auto n = std::min(cur.size() - pos, len); - memcpy(data, (unsigned char *) cur.data() + pos, n); + memcpy(data, cur.data() + pos, n); pos += n; return n; @@ -225,24 +255,24 @@ std::unique_ptr<Source> sinkToSource( void writePadding(size_t len, Sink & sink) { if (len % 8) { - unsigned char zero[8]; + char zero[8]; memset(zero, 0, sizeof(zero)); - sink(zero, 8 - (len % 8)); + sink({zero, 8 - (len % 8)}); } } -void writeString(const unsigned char * buf, size_t len, Sink & sink) +void writeString(std::string_view data, Sink & sink) { - sink << len; - sink(buf, len); - writePadding(len, sink); + sink << data.size(); + sink(data); + writePadding(data.size(), sink); } Sink & operator << (Sink & sink, const string & s) { - writeString((const unsigned char *) s.data(), s.size(), sink); + writeString(s, sink); return sink; } @@ -273,8 +303,7 @@ Sink & operator << (Sink & sink, const Error & ex) << "Error" << info.level << info.name - << info.description - << (info.hint ? info.hint->str() : "") + << info.msg.str() << 0 // FIXME: info.errPos << info.traces.size(); for (auto & trace : info.traces) { @@ -288,7 +317,7 @@ Sink & operator << (Sink & sink, const Error & ex) void readPadding(size_t len, Source & source) { if (len % 8) { - unsigned char zero[8]; + char zero[8]; size_t n = 8 - (len % 8); source(zero, n); for (unsigned int i = 0; i < n; i++) @@ -297,7 +326,7 @@ void readPadding(size_t len, Source & source) } -size_t readString(unsigned char * buf, size_t max, Source & source) +size_t readString(char * buf, size_t max, Source & source) { auto len = readNum<size_t>(source); if (len > max) throw SerialisationError("string is too long"); @@ -312,7 +341,7 @@ string readString(Source & source, size_t max) auto len = readNum<size_t>(source); if (len > max) throw SerialisationError("string is too long"); std::string res(len, 0); - source((unsigned char*) res.data(), len); + source(res.data(), len); readPadding(len, source); return res; } @@ -341,12 +370,14 @@ Error readError(Source & source) { auto type = readString(source); assert(type == "Error"); - ErrorInfo info; - info.level = (Verbosity) readInt(source); - info.name = readString(source); - info.description = readString(source); - auto hint = readString(source); - if (hint != "") info.hint = hintformat(std::move(format("%s") % hint)); + auto level = (Verbosity) readInt(source); + auto name = readString(source); + auto msg = readString(source); + ErrorInfo info { + .level = level, + .name = name, + .msg = hintformat(std::move(format("%s") % msg)), + }; auto havePos = readNum<size_t>(source); assert(havePos == 0); auto nrTraces = readNum<size_t>(source); @@ -361,17 +392,17 @@ Error readError(Source & source) } -void StringSink::operator () (const unsigned char * data, size_t len) +void StringSink::operator () (std::string_view data) { static bool warned = false; if (!warned && s->size() > threshold) { warnLargeDump(); warned = true; } - s->append((const char *) data, len); + s->append(data); } -size_t ChainSource::read(unsigned char * data, size_t len) +size_t ChainSource::read(char * data, size_t len) { if (useSecond) { return source2.read(data, len); diff --git a/src/libutil/serialise.hh b/src/libutil/serialise.hh index d7fe0b81e..5bbbc7ce3 100644 --- a/src/libutil/serialise.hh +++ b/src/libutil/serialise.hh @@ -5,6 +5,7 @@ #include "types.hh" #include "util.hh" +namespace boost::context { struct stack_context; } namespace nix { @@ -13,19 +14,14 @@ namespace nix { struct Sink { virtual ~Sink() { } - virtual void operator () (const unsigned char * data, size_t len) = 0; + virtual void operator () (std::string_view data) = 0; virtual bool good() { return true; } - - void operator () (const std::string & s) - { - (*this)((const unsigned char *) s.data(), s.size()); - } }; /* Just throws away data. */ struct NullSink : Sink { - void operator () (const unsigned char * data, size_t len) override + void operator () (std::string_view data) override { } }; @@ -34,21 +30,16 @@ struct NullSink : Sink struct BufferedSink : virtual Sink { size_t bufSize, bufPos; - std::unique_ptr<unsigned char[]> buffer; + std::unique_ptr<char[]> buffer; BufferedSink(size_t bufSize = 32 * 1024) : bufSize(bufSize), bufPos(0), buffer(nullptr) { } - void operator () (const unsigned char * data, size_t len) override; - - void operator () (const std::string & s) - { - Sink::operator()(s); - } + void operator () (std::string_view data) override; void flush(); - virtual void write(const unsigned char * data, size_t len) = 0; + virtual void write(std::string_view data) = 0; }; @@ -60,12 +51,12 @@ struct Source /* Store exactly ‘len’ bytes in the buffer pointed to by ‘data’. It blocks until all the requested data is available, or throws an error if it is not going to be available. */ - void operator () (unsigned char * data, size_t len); + void operator () (char * data, size_t len); /* Store up to ‘len’ in the buffer pointed to by ‘data’, and return the number of bytes stored. It blocks until at least one byte is available. */ - virtual size_t read(unsigned char * data, size_t len) = 0; + virtual size_t read(char * data, size_t len) = 0; virtual bool good() { return true; } @@ -80,18 +71,18 @@ struct Source struct BufferedSource : Source { size_t bufSize, bufPosIn, bufPosOut; - std::unique_ptr<unsigned char[]> buffer; + std::unique_ptr<char[]> buffer; BufferedSource(size_t bufSize = 32 * 1024) : bufSize(bufSize), bufPosIn(0), bufPosOut(0), buffer(nullptr) { } - size_t read(unsigned char * data, size_t len) override; + size_t read(char * data, size_t len) override; bool hasData(); protected: /* Underlying read call, to be overridden. */ - virtual size_t readUnbuffered(unsigned char * data, size_t len) = 0; + virtual size_t readUnbuffered(char * data, size_t len) = 0; }; @@ -118,7 +109,7 @@ struct FdSink : BufferedSink ~FdSink(); - void write(const unsigned char * data, size_t len) override; + void write(std::string_view data) override; bool good() override; @@ -147,7 +138,7 @@ struct FdSource : BufferedSource bool good() override; protected: - size_t readUnbuffered(unsigned char * data, size_t len) override; + size_t readUnbuffered(char * data, size_t len) override; private: bool _good = true; }; @@ -162,7 +153,7 @@ struct StringSink : Sink s->reserve(reservedSize); }; StringSink(ref<std::string> s) : s(s) { }; - void operator () (const unsigned char * data, size_t len) override; + void operator () (std::string_view data) override; }; @@ -172,7 +163,7 @@ struct StringSource : Source const string & s; size_t pos; StringSource(const string & _s) : s(_s), pos(0) { } - size_t read(unsigned char * data, size_t len) override; + size_t read(char * data, size_t len) override; }; @@ -181,10 +172,10 @@ 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) + virtual void operator () (std::string_view data) { - sink1(data, len); - sink2(data, len); + sink1(data); + sink2(data); } }; @@ -196,10 +187,10 @@ struct TeeSource : Source Sink & sink; TeeSource(Source & orig, Sink & sink) : orig(orig), sink(sink) { } - size_t read(unsigned char * data, size_t len) + size_t read(char * data, size_t len) { size_t n = orig.read(data, len); - sink(data, n); + sink({data, n}); return n; } }; @@ -211,7 +202,7 @@ struct SizedSource : Source size_t remain; SizedSource(Source & orig, size_t size) : orig(orig), remain(size) { } - size_t read(unsigned char * data, size_t len) + size_t read(char * data, size_t len) { if (this->remain <= 0) { throw EndOfFile("sized: unexpected end-of-file"); @@ -225,7 +216,7 @@ struct SizedSource : Source /* Consume the original source until no remain data is left to consume. */ size_t drainAll() { - std::vector<unsigned char> buf(8192); + std::vector<char> buf(8192); size_t sum = 0; while (this->remain > 0) { size_t n = read(buf.data(), buf.size()); @@ -240,24 +231,24 @@ struct LengthSink : Sink { uint64_t length = 0; - virtual void operator () (const unsigned char * _, size_t len) + void operator () (std::string_view data) override { - length += len; + length += data.size(); } }; /* Convert a function into a sink. */ struct LambdaSink : Sink { - typedef std::function<void(const unsigned char *, size_t)> lambda_t; + typedef std::function<void(std::string_view data)> lambda_t; lambda_t lambda; LambdaSink(const lambda_t & lambda) : lambda(lambda) { } - virtual void operator () (const unsigned char * data, size_t len) + void operator () (std::string_view data) override { - lambda(data, len); + lambda(data); } }; @@ -265,13 +256,13 @@ struct LambdaSink : Sink /* Convert a function into a source. */ struct LambdaSource : Source { - typedef std::function<size_t(unsigned char *, size_t)> lambda_t; + typedef std::function<size_t(char *, size_t)> lambda_t; lambda_t lambda; LambdaSource(const lambda_t & lambda) : lambda(lambda) { } - size_t read(unsigned char * data, size_t len) override + size_t read(char * data, size_t len) override { return lambda(data, len); } @@ -287,7 +278,7 @@ struct ChainSource : Source : source1(s1), source2(s2) { } - size_t read(unsigned char * data, size_t len) override; + size_t read(char * data, size_t len) override; }; @@ -301,7 +292,7 @@ std::unique_ptr<Source> sinkToSource( void writePadding(size_t len, Sink & sink); -void writeString(const unsigned char * buf, size_t len, Sink & sink); +void writeString(std::string_view s, Sink & sink); inline Sink & operator << (Sink & sink, uint64_t n) { @@ -314,7 +305,7 @@ inline Sink & operator << (Sink & sink, uint64_t n) buf[5] = (n >> 40) & 0xff; buf[6] = (n >> 48) & 0xff; buf[7] = (unsigned char) (n >> 56) & 0xff; - sink(buf, sizeof(buf)); + sink({(char *) buf, sizeof(buf)}); return sink; } @@ -331,7 +322,7 @@ template<typename T> T readNum(Source & source) { unsigned char buf[8]; - source(buf, sizeof(buf)); + source((char *) buf, sizeof(buf)); uint64_t n = ((uint64_t) buf[0]) | @@ -363,7 +354,7 @@ inline uint64_t readLongLong(Source & source) void readPadding(size_t len, Source & source); -size_t readString(unsigned char * buf, size_t max, Source & source); +size_t readString(char * buf, size_t max, Source & source); string readString(Source & source, size_t max = std::numeric_limits<size_t>::max()); template<class T> T readStrings(Source & source); @@ -395,9 +386,9 @@ struct StreamToSourceAdapter : Source : istream(istream) { } - size_t read(unsigned char * data, size_t len) override + size_t read(char * data, size_t len) override { - if (!istream->read((char *) data, len)) { + if (!istream->read(data, len)) { if (istream->eof()) { if (istream->gcount() == 0) throw EndOfFile("end of file"); @@ -420,7 +411,7 @@ struct FramedSource : Source { Source & from; bool eof = false; - std::vector<unsigned char> pending; + std::vector<char> pending; size_t pos = 0; FramedSource(Source & from) : from(from) @@ -432,13 +423,13 @@ struct FramedSource : Source while (true) { auto n = readInt(from); if (!n) break; - std::vector<unsigned char> data(n); + std::vector<char> data(n); from(data.data(), n); } } } - size_t read(unsigned char * data, size_t len) override + size_t read(char * data, size_t len) override { if (eof) throw EndOfFile("reached end of FramedSource"); @@ -448,7 +439,7 @@ struct FramedSource : Source eof = true; return 0; } - pending = std::vector<unsigned char>(len); + pending = std::vector<char>(len); pos = 0; from(pending.data(), len); } @@ -483,7 +474,7 @@ struct FramedSink : nix::BufferedSink } } - void write(const unsigned char * data, size_t len) override + void write(std::string_view data) override { /* Don't send more data if the remote has encountered an error. */ @@ -492,10 +483,23 @@ struct FramedSink : nix::BufferedSink ex = nullptr; std::rethrow_exception(ex2); } - to << len; - to(data, len); + to << data.size(); + to(data); }; }; +/* Stack allocation strategy for sinkToSource. + Mutable to avoid a boehm gc dependency in libutil. + + boost::context doesn't provide a virtual class, so we define our own. + */ +struct StackAllocator { + virtual boost::context::stack_context allocate() = 0; + virtual void deallocate(boost::context::stack_context sctx) = 0; + + /* The stack allocator to use in sinkToSource and potentially elsewhere. + It is reassigned by the initGC() method in libexpr. */ + static StackAllocator *defaultAllocator; +}; } diff --git a/src/libutil/tarfile.cc b/src/libutil/tarfile.cc index c4d8a4f91..2da169ba7 100644 --- a/src/libutil/tarfile.cc +++ b/src/libutil/tarfile.cc @@ -66,7 +66,7 @@ private: *buffer = self->buffer.data(); try { - return self->source->read(self->buffer.data(), 4096); + return self->source->read((char *) self->buffer.data(), 4096); } catch (EndOfFile &) { return 0; } catch (std::exception & err) { diff --git a/src/libutil/tests/config.cc b/src/libutil/tests/config.cc index c7777a21f..c305af9f5 100644 --- a/src/libutil/tests/config.cc +++ b/src/libutil/tests/config.cc @@ -80,8 +80,8 @@ namespace nix { class TestSetting : public AbstractSetting { public: TestSetting() : AbstractSetting("test", "test", {}) {} - void set(const std::string & value) {} - std::string to_string() const { return {}; } + void set(const std::string & value, bool append) override {} + std::string to_string() const override { return {}; } }; Config config; diff --git a/src/libutil/tests/logging.cc b/src/libutil/tests/logging.cc index d33bd7c1f..6c649820b 100644 --- a/src/libutil/tests/logging.cc +++ b/src/libutil/tests/logging.cc @@ -1,3 +1,5 @@ +#if 0 + #include "logging.hh" #include "nixexpr.hh" #include "util.hh" @@ -41,15 +43,14 @@ namespace nix { makeJSONLogger(*logger)->logEI({ .name = "error name", - .description = "error without any code lines.", - .hint = hintfmt("this hint has %1% templated %2%!!", + .msg = hintfmt("this hint has %1% templated %2%!!", "yellow", "values"), .errPos = Pos(foFile, problem_file, 02, 13) }); auto str = testing::internal::GetCapturedStderr(); - ASSERT_STREQ(str.c_str(), "\x1B[31;1merror:\x1B[0m\x1B[34;1m --- SysError --- error-unit-test\x1B[0m\nopening file '\x1B[33;1mrandom.nix\x1B[0m': \x1B[33;1mNo such file or directory\x1B[0m\n@nix {\"action\":\"msg\",\"column\":13,\"file\":\"random.nix\",\"level\":0,\"line\":2,\"msg\":\"\\u001b[31;1merror:\\u001b[0m\\u001b[34;1m --- error name --- error-unit-test\\u001b[0m\\n\\u001b[34;1mat: \\u001b[33;1m(2:13)\\u001b[34;1m in file: \\u001b[0mrandom.nix\\n\\nerror without any code lines.\\n\\nthis hint has \\u001b[33;1myellow\\u001b[0m templated \\u001b[33;1mvalues\\u001b[0m!!\",\"raw_msg\":\"this hint has \\u001b[33;1myellow\\u001b[0m templated \\u001b[33;1mvalues\\u001b[0m!!\"}\n"); + ASSERT_STREQ(str.c_str(), "@nix {\"action\":\"msg\",\"column\":13,\"file\":\"random.nix\",\"level\":0,\"line\":2,\"msg\":\"\\u001b[31;1merror:\\u001b[0m\\u001b[34;1m --- error name --- error-unit-test\\u001b[0m\\n\\u001b[34;1mat: \\u001b[33;1m(2:13)\\u001b[34;1m in file: \\u001b[0mrandom.nix\\n\\nerror without any code lines.\\n\\nthis hint has \\u001b[33;1myellow\\u001b[0m templated \\u001b[33;1mvalues\\u001b[0m!!\",\"raw_msg\":\"this hint has \\u001b[33;1myellow\\u001b[0m templated \\u001b[33;1mvalues\\u001b[0m!!\"}\n"); } TEST(logEI, appendingHintsToPreviousError) { @@ -62,7 +63,7 @@ namespace nix { throw TestError(e.info()); } catch (Error &e) { ErrorInfo ei = e.info(); - ei.hint = hintfmt("%s; subsequent error message.", normaltxt(e.info().hint ? e.info().hint->str() : "")); + ei.msg = hintfmt("%s; subsequent error message.", normaltxt(e.info().msg.str())); testing::internal::CaptureStderr(); logger->logEI(ei); @@ -95,7 +96,6 @@ namespace nix { logger->logEI({ .level = lvlInfo, .name = "Info name", - .description = "Info description", }); auto str = testing::internal::GetCapturedStderr(); @@ -109,7 +109,6 @@ namespace nix { logger->logEI({ .level = lvlTalkative, .name = "Talkative name", - .description = "Talkative description", }); auto str = testing::internal::GetCapturedStderr(); @@ -123,7 +122,6 @@ namespace nix { logger->logEI({ .level = lvlChatty, .name = "Chatty name", - .description = "Talkative description", }); auto str = testing::internal::GetCapturedStderr(); @@ -137,7 +135,6 @@ namespace nix { logger->logEI({ .level = lvlDebug, .name = "Debug name", - .description = "Debug description", }); auto str = testing::internal::GetCapturedStderr(); @@ -151,7 +148,6 @@ namespace nix { logger->logEI({ .level = lvlVomit, .name = "Vomit name", - .description = "Vomit description", }); auto str = testing::internal::GetCapturedStderr(); @@ -167,7 +163,6 @@ namespace nix { logError({ .name = "name", - .description = "error description", }); auto str = testing::internal::GetCapturedStderr(); @@ -182,8 +177,7 @@ namespace nix { logError({ .name = "error name", - .description = "error with code lines", - .hint = hintfmt("this hint has %1% templated %2%!!", + .msg = hintfmt("this hint has %1% templated %2%!!", "yellow", "values"), .errPos = Pos(foString, problem_file, 02, 13), @@ -200,15 +194,14 @@ namespace nix { logError({ .name = "error name", - .description = "error without any code lines.", - .hint = hintfmt("this hint has %1% templated %2%!!", + .msg = hintfmt("this hint has %1% templated %2%!!", "yellow", "values"), .errPos = Pos(foFile, problem_file, 02, 13) }); auto str = testing::internal::GetCapturedStderr(); - ASSERT_STREQ(str.c_str(), "\x1B[31;1merror:\x1B[0m\x1B[34;1m --- SysError --- error-unit-test\x1B[0m\nopening file '\x1B[33;1minvalid filename\x1B[0m': \x1B[33;1mNo such file or directory\x1B[0m\n\x1B[31;1merror:\x1B[0m\x1B[34;1m --- error name --- error-unit-test\x1B[0m\n\x1B[34;1mat: \x1B[33;1m(2:13)\x1B[34;1m in file: \x1B[0minvalid filename\n\nerror without any code lines.\n\nthis hint has \x1B[33;1myellow\x1B[0m templated \x1B[33;1mvalues\x1B[0m!!\n"); + ASSERT_STREQ(str.c_str(), "\x1B[31;1merror:\x1B[0m\x1B[34;1m --- error name --- error-unit-test\x1B[0m\n\x1B[34;1mat: \x1B[33;1m(2:13)\x1B[34;1m in file: \x1B[0minvalid filename\n\nerror without any code lines.\n\nthis hint has \x1B[33;1myellow\x1B[0m templated \x1B[33;1mvalues\x1B[0m!!\n"); } TEST(logError, logErrorWithOnlyHintAndName) { @@ -216,7 +209,7 @@ namespace nix { logError({ .name = "error name", - .hint = hintfmt("hint %1%", "only"), + .msg = hintfmt("hint %1%", "only"), }); auto str = testing::internal::GetCapturedStderr(); @@ -233,8 +226,7 @@ namespace nix { logWarning({ .name = "name", - .description = "warning description", - .hint = hintfmt("there was a %1%", "warning"), + .msg = hintfmt("there was a %1%", "warning"), }); auto str = testing::internal::GetCapturedStderr(); @@ -250,8 +242,7 @@ namespace nix { logWarning({ .name = "warning name", - .description = "warning description", - .hint = hintfmt("this hint has %1% templated %2%!!", + .msg = hintfmt("this hint has %1% templated %2%!!", "yellow", "values"), .errPos = Pos(foStdin, problem_file, 2, 13), @@ -274,8 +265,7 @@ namespace nix { auto e = AssertionError(ErrorInfo { .name = "wat", - .description = "show-traces", - .hint = hintfmt("it has been %1% days since our last error", "zero"), + .msg = hintfmt("it has been %1% days since our last error", "zero"), .errPos = Pos(foString, problem_file, 2, 13), }); @@ -290,7 +280,7 @@ namespace nix { logError(e.info()); auto str = testing::internal::GetCapturedStderr(); - ASSERT_STREQ(str.c_str(), "\x1B[31;1merror:\x1B[0m\x1B[34;1m --- SysError --- error-unit-test\x1B[0m\nopening file '\x1B[33;1minvalid filename\x1B[0m': \x1B[33;1mNo such file or directory\x1B[0m\n\x1B[31;1merror:\x1B[0m\x1B[34;1m --- AssertionError --- error-unit-test\x1B[0m\n\x1B[34;1mat: \x1B[33;1m(2:13)\x1B[34;1m from string\x1B[0m\n\nshow-traces\n\n 1| previous line of code\n 2| this is the problem line of code\n | \x1B[31;1m^\x1B[0m\n 3| next line of code\n\nit has been \x1B[33;1mzero\x1B[0m days since our last error\n\x1B[34;1m---- show-trace ----\x1B[0m\n\x1B[34;1mtrace: \x1B[0mwhile trying to compute \x1B[33;1m42\x1B[0m\n\x1B[34;1mat: \x1B[33;1m(1:19)\x1B[34;1m from stdin\x1B[0m\n\n 1| this is the other problem line of code\n | \x1B[31;1m^\x1B[0m\n\n\x1B[34;1mtrace: \x1B[0mwhile doing something without a \x1B[33;1mpos\x1B[0m\n\x1B[34;1mtrace: \x1B[0mmissing \x1B[33;1mnix file\x1B[0m\n\x1B[34;1mat: \x1B[33;1m(100:1)\x1B[34;1m in file: \x1B[0minvalid filename\n"); + ASSERT_STREQ(str.c_str(), "\x1B[31;1merror:\x1B[0m\x1B[34;1m --- AssertionError --- error-unit-test\x1B[0m\n\x1B[34;1mat: \x1B[33;1m(2:13)\x1B[34;1m from string\x1B[0m\n\nshow-traces\n\n 1| previous line of code\n 2| this is the problem line of code\n | \x1B[31;1m^\x1B[0m\n 3| next line of code\n\nit has been \x1B[33;1mzero\x1B[0m days since our last error\n\x1B[34;1m---- show-trace ----\x1B[0m\n\x1B[34;1mtrace: \x1B[0mwhile trying to compute \x1B[33;1m42\x1B[0m\n\x1B[34;1mat: \x1B[33;1m(1:19)\x1B[34;1m from stdin\x1B[0m\n\n 1| this is the other problem line of code\n | \x1B[31;1m^\x1B[0m\n\n\x1B[34;1mtrace: \x1B[0mwhile doing something without a \x1B[33;1mpos\x1B[0m\n\x1B[34;1mtrace: \x1B[0mmissing \x1B[33;1mnix file\x1B[0m\n\x1B[34;1mat: \x1B[33;1m(100:1)\x1B[34;1m in file: \x1B[0minvalid filename\n"); } TEST(addTrace, hideTracesWithoutShowTrace) { @@ -301,8 +291,7 @@ namespace nix { auto e = AssertionError(ErrorInfo { .name = "wat", - .description = "hide traces", - .hint = hintfmt("it has been %1% days since our last error", "zero"), + .msg = hintfmt("it has been %1% days since our last error", "zero"), .errPos = Pos(foString, problem_file, 2, 13), }); @@ -377,3 +366,5 @@ namespace nix { } } + +#endif diff --git a/src/libutil/tests/tests.cc b/src/libutil/tests/tests.cc index 8e77ccbe1..58df9c5ac 100644 --- a/src/libutil/tests/tests.cc +++ b/src/libutil/tests/tests.cc @@ -1,6 +1,7 @@ #include "util.hh" #include "types.hh" +#include <limits.h> #include <gtest/gtest.h> namespace nix { @@ -319,20 +320,15 @@ namespace nix { * --------------------------------------------------------------------------*/ TEST(string2Float, emptyString) { - double n; - ASSERT_EQ(string2Float("", n), false); + ASSERT_EQ(string2Float<double>(""), std::nullopt); } TEST(string2Float, trivialConversions) { - double n; - ASSERT_EQ(string2Float("1.0", n), true); - ASSERT_EQ(n, 1.0); + ASSERT_EQ(string2Float<double>("1.0"), 1.0); - ASSERT_EQ(string2Float("0.0", n), true); - ASSERT_EQ(n, 0.0); + ASSERT_EQ(string2Float<double>("0.0"), 0.0); - ASSERT_EQ(string2Float("-100.25", n), true); - ASSERT_EQ(n, (-100.25)); + ASSERT_EQ(string2Float<double>("-100.25"), -100.25); } /* ---------------------------------------------------------------------------- @@ -340,20 +336,15 @@ namespace nix { * --------------------------------------------------------------------------*/ TEST(string2Int, emptyString) { - double n; - ASSERT_EQ(string2Int("", n), false); + ASSERT_EQ(string2Int<int>(""), std::nullopt); } TEST(string2Int, trivialConversions) { - double n; - ASSERT_EQ(string2Int("1", n), true); - ASSERT_EQ(n, 1); + ASSERT_EQ(string2Int<int>("1"), 1); - ASSERT_EQ(string2Int("0", n), true); - ASSERT_EQ(n, 0); + ASSERT_EQ(string2Int<int>("0"), 0); - ASSERT_EQ(string2Int("-100", n), true); - ASSERT_EQ(n, (-100)); + ASSERT_EQ(string2Int<int>("-100"), -100); } /* ---------------------------------------------------------------------------- @@ -586,4 +577,14 @@ namespace nix { ASSERT_EQ(filterANSIEscapes(s, true), "foo bar baz" ); } + + TEST(filterANSIEscapes, utf8) { + ASSERT_EQ(filterANSIEscapes("foobar", true, 5), "fooba"); + ASSERT_EQ(filterANSIEscapes("fóóbär", true, 6), "fóóbär"); + ASSERT_EQ(filterANSIEscapes("fóóbär", true, 5), "fóóbä"); + ASSERT_EQ(filterANSIEscapes("fóóbär", true, 3), "fóó"); + ASSERT_EQ(filterANSIEscapes("f€€bär", true, 4), "f€€b"); + ASSERT_EQ(filterANSIEscapes("f𐍈𐍈bär", true, 4), "f𐍈𐍈b"); + } + } diff --git a/src/libutil/types.hh b/src/libutil/types.hh index 6c4c5ab74..9c85fef62 100644 --- a/src/libutil/types.hh +++ b/src/libutil/types.hh @@ -34,4 +34,16 @@ struct OnStartup OnStartup(T && t) { t(); } }; +/* Wrap bools to prevent string literals (i.e. 'char *') from being + cast to a bool in Attr. */ +template<typename T> +struct Explicit { + T t; + + bool operator ==(const Explicit<T> & other) const + { + return t == other.t; + } +}; + } diff --git a/src/libutil/url-parts.hh b/src/libutil/url-parts.hh index 68be15cb0..862d9fa6e 100644 --- a/src/libutil/url-parts.hh +++ b/src/libutil/url-parts.hh @@ -8,7 +8,8 @@ namespace nix { // URI stuff. const static std::string pctEncoded = "(?:%[0-9a-fA-F][0-9a-fA-F])"; const static std::string schemeRegex = "(?:[a-z][a-z0-9+.-]*)"; -const static std::string ipv6AddressRegex = "(?:\\[[0-9a-fA-F:]+\\])"; +const static std::string ipv6AddressSegmentRegex = "[0-9a-fA-F:]+"; +const static std::string ipv6AddressRegex = "(?:\\[" + ipv6AddressSegmentRegex + "\\]|" + ipv6AddressSegmentRegex + ")"; const static std::string unreservedRegex = "(?:[a-zA-Z0-9-._~])"; const static std::string subdelimsRegex = "(?:[!$&'\"()*+,;=])"; const static std::string hostnameRegex = "(?:(?:" + unreservedRegex + "|" + pctEncoded + "|" + subdelimsRegex + ")*)"; @@ -22,7 +23,7 @@ const static std::string absPathRegex = "(?:(?:/" + segmentRegex + ")*/?)"; const static std::string pathRegex = "(?:" + segmentRegex + "(?:/" + segmentRegex + ")*/?)"; // A Git ref (i.e. branch or tag name). -const static std::string refRegexS = "[a-zA-Z0-9][a-zA-Z0-9_.-]*"; // FIXME: check +const static std::string refRegexS = "[a-zA-Z0-9][a-zA-Z0-9_.\\/-]*"; // FIXME: check extern std::regex refRegex; // Instead of defining what a good Git Ref is, we define what a bad Git Ref is diff --git a/src/libutil/util.cc b/src/libutil/util.cc index 53342b5cb..89f7b58f8 100644 --- a/src/libutil/util.cc +++ b/src/libutil/util.cc @@ -320,7 +320,7 @@ void readFile(const Path & path, Sink & sink) } -void writeFile(const Path & path, const string & s, mode_t mode) +void writeFile(const Path & path, std::string_view s, mode_t mode) { AutoCloseFD fd = open(path.c_str(), O_WRONLY | O_TRUNC | O_CREAT | O_CLOEXEC, mode); if (!fd) @@ -340,13 +340,13 @@ void writeFile(const Path & path, Source & source, mode_t mode) if (!fd) throw SysError("opening file '%1%'", path); - std::vector<unsigned char> buf(64 * 1024); + std::vector<char> buf(64 * 1024); try { while (true) { try { auto n = source.read(buf.data(), buf.size()); - writeFull(fd.get(), (unsigned char *) buf.data(), n); + writeFull(fd.get(), {buf.data(), n}); } catch (EndOfFile &) { break; } } } catch (Error & e) { @@ -632,11 +632,11 @@ void replaceSymlink(const Path & target, const Path & link, } -void readFull(int fd, unsigned char * buf, size_t count) +void readFull(int fd, char * buf, size_t count) { while (count) { checkInterrupt(); - ssize_t res = read(fd, (char *) buf, count); + ssize_t res = read(fd, buf, count); if (res == -1) { if (errno == EINTR) continue; throw SysError("reading from file"); @@ -648,27 +648,19 @@ void readFull(int fd, unsigned char * buf, size_t count) } -void writeFull(int fd, const unsigned char * buf, size_t count, bool allowInterrupts) +void writeFull(int fd, std::string_view s, bool allowInterrupts) { - while (count) { + while (!s.empty()) { if (allowInterrupts) checkInterrupt(); - ssize_t res = write(fd, (char *) buf, count); + ssize_t res = write(fd, s.data(), s.size()); if (res == -1 && errno != EINTR) throw SysError("writing to file"); - if (res > 0) { - count -= res; - buf += res; - } + if (res > 0) + s.remove_prefix(res); } } -void writeFull(int fd, const string & s, bool allowInterrupts) -{ - writeFull(fd, (const unsigned char *) s.data(), s.size(), allowInterrupts); -} - - string drainFD(int fd, bool block, const size_t reserveSize) { StringSink sink(reserveSize); @@ -705,7 +697,7 @@ void drainFD(int fd, Sink & sink, bool block) throw SysError("reading from file"); } else if (rd == 0) break; - else sink(buf.data(), rd); + else sink({(char *) buf.data(), (size_t) rd}); } } @@ -1145,7 +1137,7 @@ void runProgram2(const RunOptions & options) in.readSide = -1; writerThread = std::thread([&]() { try { - std::vector<unsigned char> buf(8 * 1024); + std::vector<char> buf(8 * 1024); while (true) { size_t n; try { @@ -1153,7 +1145,7 @@ void runProgram2(const RunOptions & options) } catch (EndOfFile &) { break; } - writeFull(in.writeSide.get(), buf.data(), n); + writeFull(in.writeSide.get(), {buf.data(), n}); } promise.set_value(); } catch (...) { @@ -1257,7 +1249,7 @@ template StringSet tokenizeString(std::string_view s, const string & separators) template vector<string> tokenizeString(std::string_view s, const string & separators); -string chomp(const string & s) +string chomp(std::string_view s) { size_t i = s.find_last_not_of(" \n\r\t"); return i == string::npos ? "" : string(s, 0, i + 1); @@ -1273,11 +1265,11 @@ string trim(const string & s, const string & whitespace) } -string replaceStrings(const std::string & s, +string replaceStrings(std::string_view s, const std::string & from, const std::string & to) { - if (from.empty()) return s; - string res = s; + string res(s); + if (from.empty()) return res; size_t pos = 0; while ((pos = res.find(from, pos)) != std::string::npos) { res.replace(pos, from.size(), to); @@ -1409,7 +1401,28 @@ std::string filterANSIEscapes(const std::string & s, bool filterAll, unsigned in i++; else { - t += *i++; w++; + w++; + // Copy one UTF-8 character. + if ((*i & 0xe0) == 0xc0) { + t += *i++; + if (i != s.end() && ((*i & 0xc0) == 0x80)) t += *i++; + } else if ((*i & 0xf0) == 0xe0) { + t += *i++; + if (i != s.end() && ((*i & 0xc0) == 0x80)) { + t += *i++; + if (i != s.end() && ((*i & 0xc0) == 0x80)) t += *i++; + } + } else if ((*i & 0xf8) == 0xf0) { + t += *i++; + if (i != s.end() && ((*i & 0xc0) == 0x80)) { + t += *i++; + if (i != s.end() && ((*i & 0xc0) == 0x80)) { + t += *i++; + if (i != s.end() && ((*i & 0xc0) == 0x80)) t += *i++; + } + } + } else + t += *i++; } } diff --git a/src/libutil/util.hh b/src/libutil/util.hh index cafe93702..ad49c65b3 100644 --- a/src/libutil/util.hh +++ b/src/libutil/util.hh @@ -106,7 +106,7 @@ string readFile(const Path & path); void readFile(const Path & path, Sink & sink); /* Write a string to a file. */ -void writeFile(const Path & path, const string & s, mode_t mode = 0666); +void writeFile(const Path & path, std::string_view s, mode_t mode = 0666); void writeFile(const Path & path, Source & source, mode_t mode = 0666); @@ -155,9 +155,8 @@ void replaceSymlink(const Path & target, const Path & link, /* Wrappers arount read()/write() that read/write exactly the requested number of bytes. */ -void readFull(int fd, unsigned char * buf, size_t count); -void writeFull(int fd, const unsigned char * buf, size_t count, bool allowInterrupts = true); -void writeFull(int fd, const string & s, bool allowInterrupts = true); +void readFull(int fd, char * buf, size_t count); +void writeFull(int fd, std::string_view s, bool allowInterrupts = true); MakeError(EndOfFile, Error); @@ -374,8 +373,9 @@ template<class C> Strings quoteStrings(const C & c) } -/* Remove trailing whitespace from a string. */ -string chomp(const string & s); +/* Remove trailing whitespace from a string. FIXME: return + std::string_view. */ +string chomp(std::string_view s); /* Remove whitespace from the start and end of a string. */ @@ -383,7 +383,7 @@ string trim(const string & s, const string & whitespace = " \n\r\t"); /* Replace all occurrences of a string inside another string. */ -string replaceStrings(const std::string & s, +string replaceStrings(std::string_view s, const std::string & from, const std::string & to); @@ -398,21 +398,49 @@ bool statusOk(int status); /* Parse a string into an integer. */ -template<class N> bool string2Int(const string & s, N & n) +template<class N> +std::optional<N> string2Int(const std::string & s) { - if (string(s, 0, 1) == "-" && !std::numeric_limits<N>::is_signed) - return false; + if (s.substr(0, 1) == "-" && !std::numeric_limits<N>::is_signed) + return std::nullopt; std::istringstream str(s); + N n; str >> n; - return str && str.get() == EOF; + if (str && str.get() == EOF) return n; + return std::nullopt; +} + +/* Like string2Int(), but support an optional suffix 'K', 'M', 'G' or + 'T' denoting a binary unit prefix. */ +template<class N> +N string2IntWithUnitPrefix(std::string s) +{ + N multiplier = 1; + if (!s.empty()) { + char u = std::toupper(*s.rbegin()); + if (std::isalpha(u)) { + if (u == 'K') multiplier = 1ULL << 10; + else if (u == 'M') multiplier = 1ULL << 20; + else if (u == 'G') multiplier = 1ULL << 30; + else if (u == 'T') multiplier = 1ULL << 40; + else throw UsageError("invalid unit specifier '%1%'", u); + s.resize(s.size() - 1); + } + } + if (auto n = string2Int<N>(s)) + return *n * multiplier; + throw UsageError("'%s' is not an integer", s); } /* Parse a string into a float. */ -template<class N> bool string2Float(const string & s, N & n) +template<class N> +std::optional<N> string2Float(const string & s) { std::istringstream str(s); + N n; str >> n; - return str && str.get() == EOF; + if (str && str.get() == EOF) return n; + return std::nullopt; } |