aboutsummaryrefslogtreecommitdiff
path: root/src/libutil
diff options
context:
space:
mode:
Diffstat (limited to 'src/libutil')
-rw-r--r--src/libutil/archive.cc47
-rw-r--r--src/libutil/archive.hh10
-rw-r--r--src/libutil/args.cc21
-rw-r--r--src/libutil/args.hh2
-rw-r--r--src/libutil/config.cc16
-rw-r--r--src/libutil/error.cc22
-rw-r--r--src/libutil/error.hh41
-rw-r--r--src/libutil/experimental-features.cc2
-rw-r--r--src/libutil/experimental-features.hh4
-rw-r--r--src/libutil/finally.hh7
-rw-r--r--src/libutil/fmt.hh7
-rw-r--r--src/libutil/hash.cc19
-rw-r--r--src/libutil/hash.hh6
-rw-r--r--src/libutil/logging.cc80
-rw-r--r--src/libutil/logging.hh10
-rw-r--r--src/libutil/serialise.cc4
-rw-r--r--src/libutil/serialise.hh4
-rw-r--r--src/libutil/suggestions.cc114
-rw-r--r--src/libutil/suggestions.hh102
-rw-r--r--src/libutil/tarfile.cc26
-rw-r--r--src/libutil/tarfile.hh3
-rw-r--r--src/libutil/tests/logging.cc2
-rw-r--r--src/libutil/tests/suggestions.cc43
-rw-r--r--src/libutil/tests/url.cc18
-rw-r--r--src/libutil/types.hh21
-rw-r--r--src/libutil/util.cc200
-rw-r--r--src/libutil/util.hh48
-rw-r--r--src/libutil/xml-writer.cc10
-rw-r--r--src/libutil/xml-writer.hh14
29 files changed, 638 insertions, 265 deletions
diff --git a/src/libutil/archive.cc b/src/libutil/archive.cc
index fdee643b1..30b471af5 100644
--- a/src/libutil/archive.cc
+++ b/src/libutil/archive.cc
@@ -37,7 +37,7 @@ static GlobalConfig::Register rArchiveSettings(&archiveSettings);
const std::string narVersionMagic1 = "nix-archive-1";
-static string caseHackSuffix = "~nix~case~hack~";
+static std::string caseHackSuffix = "~nix~case~hack~";
PathFilter defaultPathFilter = [](const Path &) { return true; };
@@ -64,11 +64,12 @@ static void dumpContents(const Path & path, off_t size,
}
-static void dump(const Path & path, Sink & sink, PathFilter & filter)
+static time_t dump(const Path & path, Sink & sink, PathFilter & filter)
{
checkInterrupt();
auto st = lstat(path);
+ time_t result = st.st_mtime;
sink << "(";
@@ -84,12 +85,12 @@ static void dump(const Path & path, Sink & sink, PathFilter & filter)
/* If we're on a case-insensitive system like macOS, undo
the case hack applied by restorePath(). */
- std::map<string, string> unhacked;
+ std::map<std::string, std::string> unhacked;
for (auto & i : readDirectory(path))
if (archiveSettings.useCaseHack) {
- string name(i.name);
+ std::string name(i.name);
size_t pos = i.name.find(caseHackSuffix);
- if (pos != string::npos) {
+ if (pos != std::string::npos) {
debug(format("removing case hack suffix from '%1%'") % (path + "/" + i.name));
name.erase(pos);
}
@@ -103,7 +104,10 @@ static void dump(const Path & path, Sink & sink, PathFilter & filter)
for (auto & i : unhacked)
if (filter(path + "/" + i.first)) {
sink << "entry" << "(" << "name" << i.first << "node";
- dump(path + "/" + i.second, sink, filter);
+ auto tmp_mtime = dump(path + "/" + i.second, sink, filter);
+ if (tmp_mtime > result) {
+ result = tmp_mtime;
+ }
sink << ")";
}
}
@@ -114,23 +118,30 @@ static void dump(const Path & path, Sink & sink, PathFilter & filter)
else throw Error("file '%1%' has an unsupported type", path);
sink << ")";
+
+ return result;
}
-void dumpPath(const Path & path, Sink & sink, PathFilter & filter)
+time_t dumpPathAndGetMtime(const Path & path, Sink & sink, PathFilter & filter)
{
sink << narVersionMagic1;
- dump(path, sink, filter);
+ return dump(path, sink, filter);
+}
+
+void dumpPath(const Path & path, Sink & sink, PathFilter & filter)
+{
+ dumpPathAndGetMtime(path, sink, filter);
}
-void dumpString(const std::string & s, Sink & sink)
+void dumpString(std::string_view s, Sink & sink)
{
sink << narVersionMagic1 << "(" << "type" << "regular" << "contents" << s << ")";
}
-static SerialisationError badArchive(string s)
+static SerialisationError badArchive(const std::string & s)
{
return SerialisationError("bad archive: " + s);
}
@@ -171,7 +182,7 @@ static void parseContents(ParseSink & sink, Source & source, const Path & path)
struct CaseInsensitiveCompare
{
- bool operator() (const string & a, const string & b) const
+ bool operator() (const std::string & a, const std::string & b) const
{
return strcasecmp(a.c_str(), b.c_str()) < 0;
}
@@ -180,7 +191,7 @@ struct CaseInsensitiveCompare
static void parse(ParseSink & sink, Source & source, const Path & path)
{
- string s;
+ std::string s;
s = readString(source);
if (s != "(") throw badArchive("expected open tag");
@@ -201,7 +212,7 @@ static void parse(ParseSink & sink, Source & source, const Path & path)
else if (s == "type") {
if (type != tpUnknown)
throw badArchive("multiple type fields");
- string t = readString(source);
+ std::string t = readString(source);
if (t == "regular") {
type = tpRegular;
@@ -232,7 +243,7 @@ static void parse(ParseSink & sink, Source & source, const Path & path)
}
else if (s == "entry" && type == tpDirectory) {
- string name, prevName;
+ std::string name, prevName;
s = readString(source);
if (s != "(") throw badArchive("expected open tag");
@@ -246,7 +257,7 @@ static void parse(ParseSink & sink, Source & source, const Path & path)
break;
} else if (s == "name") {
name = readString(source);
- if (name.empty() || name == "." || name == ".." || name.find('/') != string::npos || name.find((char) 0) != string::npos)
+ if (name.empty() || name == "." || name == ".." || name.find('/') != std::string::npos || name.find((char) 0) != std::string::npos)
throw Error("NAR contains invalid file name '%1%'", name);
if (name <= prevName)
throw Error("NAR directory is not sorted");
@@ -269,7 +280,7 @@ static void parse(ParseSink & sink, Source & source, const Path & path)
}
else if (s == "target" && type == tpSymlink) {
- string target = readString(source);
+ std::string target = readString(source);
sink.createSymlink(path, target);
}
@@ -281,7 +292,7 @@ static void parse(ParseSink & sink, Source & source, const Path & path)
void parseDump(ParseSink & sink, Source & source)
{
- string version;
+ std::string version;
try {
version = readString(source, narVersionMagic1.size());
} catch (SerialisationError & e) {
@@ -345,7 +356,7 @@ struct RestoreSink : ParseSink
writeFull(fd.get(), data);
}
- void createSymlink(const Path & path, const string & target) override
+ void createSymlink(const Path & path, const std::string & target) override
{
Path p = dstPath + path;
nix::createSymlink(target, p);
diff --git a/src/libutil/archive.hh b/src/libutil/archive.hh
index 9e9e11b1a..79ce08df0 100644
--- a/src/libutil/archive.hh
+++ b/src/libutil/archive.hh
@@ -48,7 +48,11 @@ namespace nix {
void dumpPath(const Path & path, Sink & sink,
PathFilter & filter = defaultPathFilter);
-void dumpString(const std::string & s, Sink & sink);
+/* Same as `void dumpPath()`, but returns the last modified date of the path */
+time_t dumpPathAndGetMtime(const Path & path, Sink & sink,
+ PathFilter & filter = defaultPathFilter);
+
+void dumpString(std::string_view s, Sink & sink);
/* FIXME: fix this API, it sucks. */
struct ParseSink
@@ -60,7 +64,7 @@ struct ParseSink
virtual void preallocateContents(uint64_t size) { };
virtual void receiveContents(std::string_view data) { };
- virtual void createSymlink(const Path & path, const string & target) { };
+ virtual void createSymlink(const Path & path, const std::string & target) { };
};
/* If the NAR archive contains a single file at top-level, then save
@@ -82,7 +86,7 @@ struct RetrieveRegularNARSink : ParseSink
sink(data);
}
- void createSymlink(const Path & path, const string & target) override
+ void createSymlink(const Path & path, const std::string & target) override
{
regular = false;
}
diff --git a/src/libutil/args.cc b/src/libutil/args.cc
index a739d6b4e..69aa0d094 100644
--- a/src/libutil/args.cc
+++ b/src/libutil/args.cc
@@ -76,13 +76,13 @@ void Args::parseCmdline(const Strings & _cmdline)
/* Expand compound dash options (i.e., `-qlf' -> `-q -l -f',
`-j3` -> `-j 3`). */
if (!dashDash && arg.length() > 2 && arg[0] == '-' && arg[1] != '-' && isalpha(arg[1])) {
- *pos = (string) "-" + arg[1];
+ *pos = (std::string) "-" + arg[1];
auto next = pos; ++next;
for (unsigned int j = 2; j < arg.length(); j++)
if (isalpha(arg[j]))
- cmdline.insert(next, (string) "-" + arg[j]);
+ cmdline.insert(next, (std::string) "-" + arg[j]);
else {
- cmdline.insert(next, string(arg, j));
+ cmdline.insert(next, std::string(arg, j));
break;
}
arg = *pos;
@@ -139,7 +139,7 @@ bool Args::processFlag(Strings::iterator & pos, Strings::iterator end)
return true;
};
- if (string(*pos, 0, 2) == "--") {
+ if (std::string(*pos, 0, 2) == "--") {
if (auto prefix = needsCompletion(*pos)) {
for (auto & [name, flag] : longFlags) {
if (!hiddenCategories.count(flag->category)
@@ -147,12 +147,12 @@ bool Args::processFlag(Strings::iterator & pos, Strings::iterator end)
completions->add("--" + name, flag->description);
}
}
- auto i = longFlags.find(string(*pos, 2));
+ auto i = longFlags.find(std::string(*pos, 2));
if (i == longFlags.end()) return false;
return process("--" + i->first, *i->second);
}
- if (string(*pos, 0, 1) == "-" && pos->size() == 2) {
+ if (std::string(*pos, 0, 1) == "-" && pos->size() == 2) {
auto c = (*pos)[1];
auto i = shortFlags.find(c);
if (i == shortFlags.end()) return false;
@@ -328,8 +328,13 @@ MultiCommand::MultiCommand(const Commands & commands_)
completions->add(name);
}
auto i = commands.find(s);
- if (i == commands.end())
- throw UsageError("'%s' is not a recognised command", s);
+ if (i == commands.end()) {
+ std::set<std::string> commandNames;
+ for (auto & [name, _] : commands)
+ commandNames.insert(name);
+ auto suggestions = Suggestions::bestMatches(commandNames, s);
+ throw UsageError(suggestions, "'%s' is not a recognised command", s);
+ }
command = {s, i->second()};
command->second->parent = this;
}}
diff --git a/src/libutil/args.hh b/src/libutil/args.hh
index 76b1cfe92..fdd036f9a 100644
--- a/src/libutil/args.hh
+++ b/src/libutil/args.hh
@@ -158,7 +158,7 @@ public:
}
/* Expect a string argument. */
- void expectArg(const std::string & label, string * dest, bool optional = false)
+ void expectArg(const std::string & label, std::string * dest, bool optional = false)
{
expectArgs({
.label = label,
diff --git a/src/libutil/config.cc b/src/libutil/config.cc
index 92ab265d3..9bb412b4f 100644
--- a/src/libutil/config.cc
+++ b/src/libutil/config.cc
@@ -81,16 +81,16 @@ void AbstractConfig::applyConfig(const std::string & contents, const std::string
unsigned int pos = 0;
while (pos < contents.size()) {
- string line;
+ std::string line;
while (pos < contents.size() && contents[pos] != '\n')
line += contents[pos++];
pos++;
- string::size_type hash = line.find('#');
- if (hash != string::npos)
- line = string(line, 0, hash);
+ auto hash = line.find('#');
+ if (hash != std::string::npos)
+ line = std::string(line, 0, hash);
- vector<string> tokens = tokenizeString<vector<string> >(line);
+ auto tokens = tokenizeString<std::vector<std::string>>(line);
if (tokens.empty()) continue;
if (tokens.size() < 2)
@@ -120,9 +120,9 @@ void AbstractConfig::applyConfig(const std::string & contents, const std::string
if (tokens[1] != "=")
throw UsageError("illegal configuration line '%1%' in '%2%'", line, path);
- string name = tokens[0];
+ std::string name = tokens[0];
- vector<string>::iterator i = tokens.begin();
+ auto i = tokens.begin();
advance(i, 2);
set(name, concatStringsSep(" ", Strings(i, tokens.end()))); // FIXME: slow
@@ -132,7 +132,7 @@ void AbstractConfig::applyConfig(const std::string & contents, const std::string
void AbstractConfig::applyConfigFile(const Path & path)
{
try {
- string contents = readFile(path);
+ std::string contents = readFile(path);
applyConfig(contents, path);
} catch (SysError &) { }
}
diff --git a/src/libutil/error.cc b/src/libutil/error.cc
index dd9678ee7..02bc5caa5 100644
--- a/src/libutil/error.cc
+++ b/src/libutil/error.cc
@@ -9,15 +9,14 @@ namespace nix {
const std::string nativeSystem = SYSTEM;
-BaseError & BaseError::addTrace(std::optional<ErrPos> e, hintformat hint)
+void BaseError::addTrace(std::optional<ErrPos> e, hintformat hint)
{
err.traces.push_front(Trace { .pos = e, .hint = hint });
- return *this;
}
// c++ std::exception descendants must have a 'const char* what()' function.
// This stringifies the error and caches it for use by what(), or similarly by msg().
-const string & BaseError::calcWhat() const
+const std::string & BaseError::calcWhat() const
{
if (what_.has_value())
return *what_;
@@ -32,14 +31,14 @@ const string & BaseError::calcWhat() const
}
}
-std::optional<string> ErrorInfo::programName = std::nullopt;
+std::optional<std::string> ErrorInfo::programName = std::nullopt;
std::ostream & operator<<(std::ostream & os, const hintformat & hf)
{
return os << hf.str();
}
-string showErrPos(const ErrPos & errPos)
+std::string showErrPos(const ErrPos & errPos)
{
if (errPos.line > 0) {
if (errPos.column > 0) {
@@ -68,7 +67,7 @@ std::optional<LinesOfCode> getCodeLines(const ErrPos & errPos)
// count the newlines.
int count = 0;
- string line;
+ std::string line;
int pl = errPos.line - 1;
do
{
@@ -100,7 +99,7 @@ std::optional<LinesOfCode> getCodeLines(const ErrPos & errPos)
std::istringstream iss(errPos.file);
// count the newlines.
int count = 0;
- string line;
+ std::string line;
int pl = errPos.line - 1;
LinesOfCode loc;
@@ -132,7 +131,7 @@ std::optional<LinesOfCode> getCodeLines(const ErrPos & errPos)
// print lines of code to the ostream, indicating the error column.
void printCodeLines(std::ostream & out,
- const string & prefix,
+ const std::string & prefix,
const ErrPos & errPos,
const LinesOfCode & loc)
{
@@ -282,6 +281,13 @@ std::ostream & showErrorInfo(std::ostream & out, const ErrorInfo & einfo, bool s
}
}
+ auto suggestions = einfo.suggestions.trim();
+ if (! suggestions.suggestions.empty()){
+ oss << "Did you mean " <<
+ suggestions.trim() <<
+ "?" << std::endl;
+ }
+
// traces
if (showTrace && !einfo.traces.empty()) {
for (auto iter = einfo.traces.rbegin(); iter != einfo.traces.rend(); ++iter) {
diff --git a/src/libutil/error.hh b/src/libutil/error.hh
index 0c86f090e..d17575f47 100644
--- a/src/libutil/error.hh
+++ b/src/libutil/error.hh
@@ -1,5 +1,6 @@
#pragma once
+#include "suggestions.hh"
#include "ref.hh"
#include "types.hh"
#include "fmt.hh"
@@ -53,6 +54,7 @@ typedef enum {
lvlVomit
} Verbosity;
+/* adjust Pos::origin bit width when adding stuff here */
typedef enum {
foFile,
foStdin,
@@ -61,16 +63,16 @@ typedef enum {
// the lines of code surrounding an error.
struct LinesOfCode {
- std::optional<string> prevLineOfCode;
- std::optional<string> errLineOfCode;
- std::optional<string> nextLineOfCode;
+ std::optional<std::string> prevLineOfCode;
+ std::optional<std::string> errLineOfCode;
+ std::optional<std::string> nextLineOfCode;
};
// ErrPos indicates the location of an error in a nix file.
struct ErrPos {
int line = 0;
int column = 0;
- string file;
+ std::string file;
FileOrigin origin;
operator bool() const
@@ -80,7 +82,7 @@ struct ErrPos {
// convert from the Pos struct, found in libexpr.
template <class P>
- ErrPos& operator=(const P &pos)
+ ErrPos & operator=(const P & pos)
{
origin = pos.origin;
line = pos.line;
@@ -94,7 +96,7 @@ struct ErrPos {
}
template <class P>
- ErrPos(const P &p)
+ ErrPos(const P & p)
{
*this = p;
}
@@ -102,7 +104,7 @@ struct ErrPos {
std::optional<LinesOfCode> getCodeLines(const ErrPos & errPos);
void printCodeLines(std::ostream & out,
- const string & prefix,
+ const std::string & prefix,
const ErrPos & errPos,
const LinesOfCode & loc);
@@ -116,15 +118,17 @@ struct Trace {
struct ErrorInfo {
Verbosity level;
- string name; // FIXME: rename
+ std::string name; // FIXME: rename
hintformat msg;
std::optional<ErrPos> errPos;
std::list<Trace> traces;
- static std::optional<string> programName;
+ Suggestions suggestions;
+
+ static std::optional<std::string> programName;
};
-std::ostream& showErrorInfo(std::ostream &out, const ErrorInfo &einfo, bool showTrace);
+std::ostream & showErrorInfo(std::ostream & out, const ErrorInfo & einfo, bool showTrace);
/* BaseError should generally not be caught, as it has Interrupted as
a subclass. Catch Error instead. */
@@ -133,8 +137,8 @@ class BaseError : public std::exception
protected:
mutable ErrorInfo err;
- mutable std::optional<string> what_;
- const string& calcWhat() const;
+ mutable std::optional<std::string> what_;
+ const std::string & calcWhat() const;
public:
unsigned int status = 1; // exit status
@@ -150,6 +154,11 @@ public:
: err { .level = lvlError, .msg = hintfmt(fs, args...) }
{ }
+ template<typename... Args>
+ BaseError(const Suggestions & sug, const Args & ... args)
+ : err { .level = lvlError, .msg = hintfmt(args...), .suggestions = sug }
+ { }
+
BaseError(hintformat hint)
: err { .level = lvlError, .msg = hint }
{ }
@@ -171,16 +180,16 @@ public:
const char * what() const noexcept override { return calcWhat().c_str(); }
#endif
- const string & msg() const { return calcWhat(); }
+ const std::string & msg() const { return calcWhat(); }
const ErrorInfo & info() const { calcWhat(); return err; }
template<typename... Args>
- BaseError & addTrace(std::optional<ErrPos> e, const string &fs, const Args & ... args)
+ void addTrace(std::optional<ErrPos> e, const std::string & fs, const Args & ... args)
{
- return addTrace(e, hintfmt(fs, args...));
+ addTrace(e, hintfmt(fs, args...));
}
- BaseError & addTrace(std::optional<ErrPos> e, hintformat hint);
+ void addTrace(std::optional<ErrPos> e, hintformat hint);
bool hasTrace() const { return !err.traces.empty(); }
};
diff --git a/src/libutil/experimental-features.cc b/src/libutil/experimental-features.cc
index b49f47e1d..e033a4116 100644
--- a/src/libutil/experimental-features.cc
+++ b/src/libutil/experimental-features.cc
@@ -7,10 +7,12 @@ namespace nix {
std::map<ExperimentalFeature, std::string> stringifiedXpFeatures = {
{ Xp::CaDerivations, "ca-derivations" },
+ { Xp::ImpureDerivations, "impure-derivations" },
{ Xp::Flakes, "flakes" },
{ Xp::NixCommand, "nix-command" },
{ Xp::RecursiveNix, "recursive-nix" },
{ Xp::NoUrlLiterals, "no-url-literals" },
+ { Xp::FetchClosure, "fetch-closure" },
};
const std::optional<ExperimentalFeature> parseExperimentalFeature(const std::string_view & name)
diff --git a/src/libutil/experimental-features.hh b/src/libutil/experimental-features.hh
index 291a58e32..3a254b423 100644
--- a/src/libutil/experimental-features.hh
+++ b/src/libutil/experimental-features.hh
@@ -16,10 +16,12 @@ namespace nix {
enum struct ExperimentalFeature
{
CaDerivations,
+ ImpureDerivations,
Flakes,
NixCommand,
RecursiveNix,
- NoUrlLiterals
+ NoUrlLiterals,
+ FetchClosure,
};
/**
diff --git a/src/libutil/finally.hh b/src/libutil/finally.hh
index 7760cfe9a..dee2e8d2f 100644
--- a/src/libutil/finally.hh
+++ b/src/libutil/finally.hh
@@ -1,14 +1,13 @@
#pragma once
-#include <functional>
-
/* A trivial class to run a function at the end of a scope. */
+template<typename Fn>
class Finally
{
private:
- std::function<void()> fun;
+ Fn fun;
public:
- Finally(std::function<void()> fun) : fun(fun) { }
+ Finally(Fn fun) : fun(std::move(fun)) { }
~Finally() { fun(); }
};
diff --git a/src/libutil/fmt.hh b/src/libutil/fmt.hh
index a7126fb65..0821b3b74 100644
--- a/src/libutil/fmt.hh
+++ b/src/libutil/fmt.hh
@@ -10,7 +10,6 @@ namespace nix {
/* Inherit some names from other namespaces for convenience. */
-using std::string;
using boost::format;
@@ -21,8 +20,8 @@ struct nop { template<typename... T> nop(T...) {} };
struct FormatOrString
{
- string s;
- FormatOrString(const string & s) : s(s) { };
+ std::string s;
+ FormatOrString(std::string s) : s(std::move(s)) { };
template<class F>
FormatOrString(const F & f) : s(f.str()) { };
FormatOrString(const char * s) : s(s) { };
@@ -102,7 +101,7 @@ std::ostream & operator<<(std::ostream & out, const normaltxt<T> & y)
class hintformat
{
public:
- hintformat(const string & format) : fmt(format)
+ hintformat(const std::string & format) : fmt(format)
{
fmt.exceptions(boost::io::all_error_bits ^
boost::io::too_many_args_bit ^
diff --git a/src/libutil/hash.cc b/src/libutil/hash.cc
index 6ed00d43c..a4d632161 100644
--- a/src/libutil/hash.cc
+++ b/src/libutil/hash.cc
@@ -66,31 +66,31 @@ bool Hash::operator < (const Hash & h) const
}
-const string base16Chars = "0123456789abcdef";
+const std::string base16Chars = "0123456789abcdef";
-static string printHash16(const Hash & hash)
+static std::string printHash16(const Hash & hash)
{
char buf[hash.hashSize * 2];
for (unsigned int i = 0; i < hash.hashSize; i++) {
buf[i * 2] = base16Chars[hash.hash[i] >> 4];
buf[i * 2 + 1] = base16Chars[hash.hash[i] & 0x0f];
}
- return string(buf, hash.hashSize * 2);
+ return std::string(buf, hash.hashSize * 2);
}
// omitted: E O U T
-const string base32Chars = "0123456789abcdfghijklmnpqrsvwxyz";
+const std::string base32Chars = "0123456789abcdfghijklmnpqrsvwxyz";
-static string printHash32(const Hash & hash)
+static std::string printHash32(const Hash & hash)
{
assert(hash.hashSize);
size_t len = hash.base32Len();
assert(len);
- string s;
+ std::string s;
s.reserve(len);
for (int n = (int) len - 1; n >= 0; n--) {
@@ -107,7 +107,7 @@ static string printHash32(const Hash & hash)
}
-string printHash16or32(const Hash & hash)
+std::string printHash16or32(const Hash & hash)
{
assert(hash.type);
return hash.to_string(hash.type == htMD5 ? Base16 : Base32, false);
@@ -151,7 +151,8 @@ Hash Hash::parseSRI(std::string_view original) {
}
// Mutates the string to eliminate the prefixes when found
-static std::pair<std::optional<HashType>, bool> getParsedTypeAndSRI(std::string_view & rest) {
+static std::pair<std::optional<HashType>, bool> getParsedTypeAndSRI(std::string_view & rest)
+{
bool isSRI = false;
// Parse the has type before the separater, if there was one.
@@ -402,7 +403,7 @@ HashType parseHashType(std::string_view s)
throw UsageError("unknown hash algorithm '%1%'", s);
}
-string printHashType(HashType ht)
+std::string printHashType(HashType ht)
{
switch (ht) {
case htMD5: return "md5";
diff --git a/src/libutil/hash.hh b/src/libutil/hash.hh
index dff46542f..56b5938b3 100644
--- a/src/libutil/hash.hh
+++ b/src/libutil/hash.hh
@@ -20,7 +20,7 @@ const int sha512HashSize = 64;
extern std::set<std::string> hashTypes;
-extern const string base32Chars;
+extern const std::string base32Chars;
enum Base : int { Base64, Base32, Base16, SRI };
@@ -110,7 +110,7 @@ public:
Hash newHashAllowEmpty(std::string_view hashStr, std::optional<HashType> ht);
/* Print a hash in base-16 if it's MD5, or base-32 otherwise. */
-string printHash16or32(const Hash & hash);
+std::string printHash16or32(const Hash & hash);
/* Compute the hash of the given string. */
Hash hashString(HashType ht, std::string_view s);
@@ -135,7 +135,7 @@ HashType parseHashType(std::string_view s);
std::optional<HashType> parseHashTypeOpt(std::string_view s);
/* And the reverse. */
-string printHashType(HashType ht);
+std::string printHashType(HashType ht);
union Ctx;
diff --git a/src/libutil/logging.cc b/src/libutil/logging.cc
index f8a121ed1..cb2b15b41 100644
--- a/src/libutil/logging.cc
+++ b/src/libutil/logging.cc
@@ -113,7 +113,7 @@ void warnOnce(bool & haveWarned, const FormatOrString & fs)
}
}
-void writeToStderr(const string & s)
+void writeToStderr(std::string_view s)
{
try {
writeFull(STDERR_FILENO, s, false);
@@ -266,51 +266,63 @@ static Logger::Fields getFields(nlohmann::json & json)
return fields;
}
-bool handleJSONLogMessage(const std::string & msg,
- const Activity & act, std::map<ActivityId, Activity> & activities, bool trusted)
+std::optional<nlohmann::json> parseJSONMessage(const std::string & msg)
{
- if (!hasPrefix(msg, "@nix ")) return false;
-
+ if (!hasPrefix(msg, "@nix ")) return std::nullopt;
try {
- auto json = nlohmann::json::parse(std::string(msg, 5));
-
- std::string action = json["action"];
-
- if (action == "start") {
- auto type = (ActivityType) json["type"];
- if (trusted || type == actFileTransfer)
- activities.emplace(std::piecewise_construct,
- std::forward_as_tuple(json["id"]),
- std::forward_as_tuple(*logger, (Verbosity) json["level"], type,
- json["text"], getFields(json["fields"]), act.id));
- }
+ return nlohmann::json::parse(std::string(msg, 5));
+ } catch (std::exception & e) {
+ printError("bad JSON log message from builder: %s", e.what());
+ }
+ return std::nullopt;
+}
- else if (action == "stop")
- activities.erase((ActivityId) json["id"]);
+bool handleJSONLogMessage(nlohmann::json & json,
+ const Activity & act, std::map<ActivityId, Activity> & activities,
+ bool trusted)
+{
+ std::string action = json["action"];
+
+ if (action == "start") {
+ auto type = (ActivityType) json["type"];
+ if (trusted || type == actFileTransfer)
+ activities.emplace(std::piecewise_construct,
+ std::forward_as_tuple(json["id"]),
+ std::forward_as_tuple(*logger, (Verbosity) json["level"], type,
+ json["text"], getFields(json["fields"]), act.id));
+ }
- else if (action == "result") {
- auto i = activities.find((ActivityId) json["id"]);
- if (i != activities.end())
- i->second.result((ResultType) json["type"], getFields(json["fields"]));
- }
+ else if (action == "stop")
+ activities.erase((ActivityId) json["id"]);
- else if (action == "setPhase") {
- std::string phase = json["phase"];
- act.result(resSetPhase, phase);
- }
+ else if (action == "result") {
+ auto i = activities.find((ActivityId) json["id"]);
+ if (i != activities.end())
+ i->second.result((ResultType) json["type"], getFields(json["fields"]));
+ }
- else if (action == "msg") {
- std::string msg = json["msg"];
- logger->log((Verbosity) json["level"], msg);
- }
+ else if (action == "setPhase") {
+ std::string phase = json["phase"];
+ act.result(resSetPhase, phase);
+ }
- } catch (std::exception & e) {
- printError("bad JSON log message from builder: %s", e.what());
+ else if (action == "msg") {
+ std::string msg = json["msg"];
+ logger->log((Verbosity) json["level"], msg);
}
return true;
}
+bool handleJSONLogMessage(const std::string & msg,
+ const Activity & act, std::map<ActivityId, Activity> & activities, bool trusted)
+{
+ auto json = parseJSONMessage(msg);
+ if (!json) return false;
+
+ return handleJSONLogMessage(*json, act, activities, trusted);
+}
+
Activity::~Activity()
{
try {
diff --git a/src/libutil/logging.hh b/src/libutil/logging.hh
index 5560d2bed..6f81b92de 100644
--- a/src/libutil/logging.hh
+++ b/src/libutil/logging.hh
@@ -4,6 +4,8 @@
#include "error.hh"
#include "config.hh"
+#include <nlohmann/json_fwd.hpp>
+
namespace nix {
typedef enum {
@@ -166,6 +168,12 @@ Logger * makeSimpleLogger(bool printBuildLogs = true);
Logger * makeJSONLogger(Logger & prevLogger);
+std::optional<nlohmann::json> parseJSONMessage(const std::string & msg);
+
+bool handleJSONLogMessage(nlohmann::json & json,
+ const Activity & act, std::map<ActivityId, Activity> & activities,
+ bool trusted);
+
bool handleJSONLogMessage(const std::string & msg,
const Activity & act, std::map<ActivityId, Activity> & activities,
bool trusted);
@@ -216,6 +224,6 @@ inline void warn(const std::string & fs, const Args & ... args)
void warnOnce(bool & haveWarned, const FormatOrString & fs);
-void writeToStderr(const string & s);
+void writeToStderr(std::string_view s);
}
diff --git a/src/libutil/serialise.cc b/src/libutil/serialise.cc
index fe703de06..6445b3f1b 100644
--- a/src/libutil/serialise.cc
+++ b/src/libutil/serialise.cc
@@ -391,7 +391,7 @@ size_t readString(char * buf, size_t max, Source & source)
}
-string readString(Source & source, size_t max)
+std::string readString(Source & source, size_t max)
{
auto len = readNum<size_t>(source);
if (len > max) throw SerialisationError("string is too long");
@@ -401,7 +401,7 @@ string readString(Source & source, size_t max)
return res;
}
-Source & operator >> (Source & in, string & s)
+Source & operator >> (Source & in, std::string & s)
{
s = readString(in);
return in;
diff --git a/src/libutil/serialise.hh b/src/libutil/serialise.hh
index fdd35aa00..13da26c6a 100644
--- a/src/libutil/serialise.hh
+++ b/src/libutil/serialise.hh
@@ -364,10 +364,10 @@ inline uint64_t readLongLong(Source & source)
void readPadding(size_t len, 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());
+std::string readString(Source & source, size_t max = std::numeric_limits<size_t>::max());
template<class T> T readStrings(Source & source);
-Source & operator >> (Source & in, string & s);
+Source & operator >> (Source & in, std::string & s);
template<typename T>
Source & operator >> (Source & in, T & n)
diff --git a/src/libutil/suggestions.cc b/src/libutil/suggestions.cc
new file mode 100644
index 000000000..9510a5f0c
--- /dev/null
+++ b/src/libutil/suggestions.cc
@@ -0,0 +1,114 @@
+#include "suggestions.hh"
+#include "ansicolor.hh"
+#include "util.hh"
+#include <algorithm>
+
+namespace nix {
+
+int levenshteinDistance(std::string_view first, std::string_view second)
+{
+ // Implementation borrowed from
+ // https://en.wikipedia.org/wiki/Levenshtein_distance#Iterative_with_two_matrix_rows
+
+ int m = first.size();
+ int n = second.size();
+
+ auto v0 = std::vector<int>(n+1);
+ auto v1 = std::vector<int>(n+1);
+
+ for (auto i = 0; i <= n; i++)
+ v0[i] = i;
+
+ for (auto i = 0; i < m; i++) {
+ v1[0] = i+1;
+
+ for (auto j = 0; j < n; j++) {
+ auto deletionCost = v0[j+1] + 1;
+ auto insertionCost = v1[j] + 1;
+ auto substitutionCost = first[i] == second[j] ? v0[j] : v0[j] + 1;
+ v1[j+1] = std::min({deletionCost, insertionCost, substitutionCost});
+ }
+
+ std::swap(v0, v1);
+ }
+
+ return v0[n];
+}
+
+Suggestions Suggestions::bestMatches (
+ std::set<std::string> allMatches,
+ std::string query)
+{
+ std::set<Suggestion> res;
+ for (const auto & possibleMatch : allMatches) {
+ res.insert(Suggestion {
+ .distance = levenshteinDistance(query, possibleMatch),
+ .suggestion = possibleMatch,
+ });
+ }
+ return Suggestions { res };
+}
+
+Suggestions Suggestions::trim(int limit, int maxDistance) const
+{
+ std::set<Suggestion> res;
+
+ int count = 0;
+
+ for (auto & elt : suggestions) {
+ if (count >= limit || elt.distance > maxDistance)
+ break;
+ count++;
+ res.insert(elt);
+ }
+
+ return Suggestions{res};
+}
+
+std::string Suggestion::to_string() const
+{
+ return ANSI_WARNING + filterANSIEscapes(suggestion) + ANSI_NORMAL;
+}
+
+std::string Suggestions::to_string() const
+{
+ switch (suggestions.size()) {
+ case 0:
+ return "";
+ case 1:
+ return suggestions.begin()->to_string();
+ default: {
+ std::string res = "one of ";
+ auto iter = suggestions.begin();
+ res += iter->to_string(); // Iter can’t be end() because the container isn’t null
+ iter++;
+ auto last = suggestions.end(); last--;
+ for ( ; iter != suggestions.end() ; iter++) {
+ res += (iter == last) ? " or " : ", ";
+ res += iter->to_string();
+ }
+ return res;
+ }
+ }
+}
+
+Suggestions & Suggestions::operator+=(const Suggestions & other)
+{
+ suggestions.insert(
+ other.suggestions.begin(),
+ other.suggestions.end()
+ );
+ return *this;
+}
+
+std::ostream & operator<<(std::ostream & str, const Suggestion & suggestion)
+{
+ return str << suggestion.to_string();
+}
+
+std::ostream & operator<<(std::ostream & str, const Suggestions & suggestions)
+{
+ return str << suggestions.to_string();
+}
+
+}
diff --git a/src/libutil/suggestions.hh b/src/libutil/suggestions.hh
new file mode 100644
index 000000000..d54dd8e31
--- /dev/null
+++ b/src/libutil/suggestions.hh
@@ -0,0 +1,102 @@
+#pragma once
+
+#include "comparator.hh"
+#include "types.hh"
+#include <set>
+
+namespace nix {
+
+int levenshteinDistance(std::string_view first, std::string_view second);
+
+/**
+ * A potential suggestion for the cli interface.
+ */
+class Suggestion {
+public:
+ int distance; // The smaller the better
+ std::string suggestion;
+
+ std::string to_string() const;
+
+ GENERATE_CMP(Suggestion, me->distance, me->suggestion)
+};
+
+class Suggestions {
+public:
+ std::set<Suggestion> suggestions;
+
+ std::string to_string() const;
+
+ Suggestions trim(
+ int limit = 5,
+ int maxDistance = 2
+ ) const;
+
+ static Suggestions bestMatches (
+ std::set<std::string> allMatches,
+ std::string query
+ );
+
+ Suggestions& operator+=(const Suggestions & other);
+};
+
+std::ostream & operator<<(std::ostream & str, const Suggestion &);
+std::ostream & operator<<(std::ostream & str, const Suggestions &);
+
+// Either a value of type `T`, or some suggestions
+template<typename T>
+class OrSuggestions {
+public:
+ using Raw = std::variant<T, Suggestions>;
+
+ Raw raw;
+
+ T* operator ->()
+ {
+ return &**this;
+ }
+
+ T& operator *()
+ {
+ return std::get<T>(raw);
+ }
+
+ operator bool() const noexcept
+ {
+ return std::holds_alternative<T>(raw);
+ }
+
+ OrSuggestions(T t)
+ : raw(t)
+ {
+ }
+
+ OrSuggestions()
+ : raw(Suggestions{})
+ {
+ }
+
+ static OrSuggestions<T> failed(const Suggestions & s)
+ {
+ auto res = OrSuggestions<T>();
+ res.raw = s;
+ return res;
+ }
+
+ static OrSuggestions<T> failed()
+ {
+ return OrSuggestions<T>::failed(Suggestions{});
+ }
+
+ const Suggestions & getSuggestions()
+ {
+ static Suggestions noSuggestions;
+ if (const auto & suggestions = std::get_if<Suggestions>(&raw))
+ return *suggestions;
+ else
+ return noSuggestions;
+ }
+
+};
+
+}
diff --git a/src/libutil/tarfile.cc b/src/libutil/tarfile.cc
index 790bc943a..a7db58559 100644
--- a/src/libutil/tarfile.cc
+++ b/src/libutil/tarfile.cc
@@ -39,28 +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)
- : source(&source), buffer(4096)
+TarArchive::TarArchive(Source & source, bool raw) : buffer(4096)
{
- init();
- if (!raw)
+ this->archive = archive_read_new();
+ this->source = &source;
+
+ if (!raw) {
+ archive_read_support_filter_all(archive);
archive_read_support_format_all(archive);
- else
+ } else {
+ archive_read_support_filter_all(archive);
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)
{
- init();
- archive_read_support_format_all(archive);
- check(archive_read_open_filename(archive, path.c_str(), 16384), "failed to open archive: %s");
-}
+ this->archive = archive_read_new();
-void TarArchive::init()
-{
- archive = archive_read_new();
archive_read_support_filter_all(archive);
+ archive_read_support_format_all(archive);
+ check(archive_read_open_filename(archive, path.c_str(), 16384), "failed to open archive: %s");
}
void TarArchive::close()
diff --git a/src/libutil/tarfile.hh b/src/libutil/tarfile.hh
index f107a7e2e..4d9141fd4 100644
--- a/src/libutil/tarfile.hh
+++ b/src/libutil/tarfile.hh
@@ -17,13 +17,10 @@ struct TarArchive {
// disable copy constructor
TarArchive(const TarArchive &) = delete;
- void init();
-
void close();
~TarArchive();
};
-
void unpackTarfile(Source & source, const Path & destDir);
void unpackTarfile(const Path & tarFile, const Path & destDir);
diff --git a/src/libutil/tests/logging.cc b/src/libutil/tests/logging.cc
index cef3bd481..2ffdc2e9b 100644
--- a/src/libutil/tests/logging.cc
+++ b/src/libutil/tests/logging.cc
@@ -359,7 +359,7 @@ namespace nix {
// constructing without access violation.
ErrPos ep(invalid);
-
+
// assignment without access violation.
ep = invalid;
diff --git a/src/libutil/tests/suggestions.cc b/src/libutil/tests/suggestions.cc
new file mode 100644
index 000000000..279994abc
--- /dev/null
+++ b/src/libutil/tests/suggestions.cc
@@ -0,0 +1,43 @@
+#include "suggestions.hh"
+#include <gtest/gtest.h>
+
+namespace nix {
+
+ struct LevenshteinDistanceParam {
+ std::string s1, s2;
+ int distance;
+ };
+
+ class LevenshteinDistanceTest :
+ public testing::TestWithParam<LevenshteinDistanceParam> {
+ };
+
+ TEST_P(LevenshteinDistanceTest, CorrectlyComputed) {
+ auto params = GetParam();
+
+ ASSERT_EQ(levenshteinDistance(params.s1, params.s2), params.distance);
+ ASSERT_EQ(levenshteinDistance(params.s2, params.s1), params.distance);
+ }
+
+ INSTANTIATE_TEST_SUITE_P(LevenshteinDistance, LevenshteinDistanceTest,
+ testing::Values(
+ LevenshteinDistanceParam{"foo", "foo", 0},
+ LevenshteinDistanceParam{"foo", "", 3},
+ LevenshteinDistanceParam{"", "", 0},
+ LevenshteinDistanceParam{"foo", "fo", 1},
+ LevenshteinDistanceParam{"foo", "oo", 1},
+ LevenshteinDistanceParam{"foo", "fao", 1},
+ LevenshteinDistanceParam{"foo", "abc", 3}
+ )
+ );
+
+ TEST(Suggestions, Trim) {
+ auto suggestions = Suggestions::bestMatches({"foooo", "bar", "fo", "gao"}, "foo");
+ auto onlyOne = suggestions.trim(1);
+ ASSERT_EQ(onlyOne.suggestions.size(), 1);
+ ASSERT_TRUE(onlyOne.suggestions.begin()->suggestion == "fo");
+
+ auto closest = suggestions.trim(999, 2);
+ ASSERT_EQ(closest.suggestions.size(), 3);
+ }
+}
diff --git a/src/libutil/tests/url.cc b/src/libutil/tests/url.cc
index aff58e9ee..f20e2dc41 100644
--- a/src/libutil/tests/url.cc
+++ b/src/libutil/tests/url.cc
@@ -5,9 +5,9 @@ namespace nix {
/* ----------- tests for url.hh --------------------------------------------------*/
- string print_map(std::map<string, string> m) {
- std::map<string, string>::iterator it;
- string s = "{ ";
+ std::string print_map(std::map<std::string, std::string> m) {
+ std::map<std::string, std::string>::iterator it;
+ std::string s = "{ ";
for (it = m.begin(); it != m.end(); ++it) {
s += "{ ";
s += it->first;
@@ -262,21 +262,21 @@ namespace nix {
* --------------------------------------------------------------------------*/
TEST(percentDecode, decodesUrlEncodedString) {
- string s = "==@==";
- string d = percentDecode("%3D%3D%40%3D%3D");
+ std::string s = "==@==";
+ std::string d = percentDecode("%3D%3D%40%3D%3D");
ASSERT_EQ(d, s);
}
TEST(percentDecode, multipleDecodesAreIdempotent) {
- string once = percentDecode("%3D%3D%40%3D%3D");
- string twice = percentDecode(once);
+ std::string once = percentDecode("%3D%3D%40%3D%3D");
+ std::string twice = percentDecode(once);
ASSERT_EQ(once, twice);
}
TEST(percentDecode, trailingPercent) {
- string s = "==@==%";
- string d = percentDecode("%3D%3D%40%3D%3D%25");
+ std::string s = "==@==%";
+ std::string d = percentDecode("%3D%3D%40%3D%3D%25");
ASSERT_EQ(d, s);
}
diff --git a/src/libutil/types.hh b/src/libutil/types.hh
index e3aca20c9..00ba567c6 100644
--- a/src/libutil/types.hh
+++ b/src/libutil/types.hh
@@ -11,23 +11,18 @@
namespace nix {
-using std::list;
-using std::set;
-using std::vector;
-using std::string;
-
-typedef list<string> Strings;
-typedef set<string> StringSet;
-typedef std::map<string, string> StringMap;
+typedef std::list<std::string> Strings;
+typedef std::set<std::string> StringSet;
+typedef std::map<std::string, std::string> StringMap;
+typedef std::map<std::string, std::string> StringPairs;
/* Paths are just strings. */
-
-typedef string Path;
+typedef std::string Path;
typedef std::string_view PathView;
-typedef list<Path> Paths;
-typedef set<Path> PathSet;
+typedef std::list<Path> Paths;
+typedef std::set<Path> PathSet;
-typedef vector<std::pair<string, string>> Headers;
+typedef std::vector<std::pair<std::string, std::string>> Headers;
/* Helper class to run code at startup. */
template<typename T>
diff --git a/src/libutil/util.cc b/src/libutil/util.cc
index cd359cfee..c075a14b4 100644
--- a/src/libutil/util.cc
+++ b/src/libutil/util.cc
@@ -71,13 +71,11 @@ void clearEnv()
unsetenv(name.first.c_str());
}
-void replaceEnv(std::map<std::string, std::string> newEnv)
+void replaceEnv(const std::map<std::string, std::string> & newEnv)
{
clearEnv();
- for (auto newEnvVar : newEnv)
- {
+ for (auto & newEnvVar : newEnv)
setenv(newEnvVar.first.c_str(), newEnvVar.second.c_str(), 1);
- }
}
@@ -110,13 +108,13 @@ Path canonPath(PathView path, bool resolveSymlinks)
{
assert(path != "");
- string s;
+ std::string s;
s.reserve(256);
if (path[0] != '/')
throw Error("not an absolute path: '%1%'", path);
- string temp;
+ std::string temp;
/* Count the number of times we follow a symlink and stop at some
arbitrary (but high) limit to prevent infinite loops. */
@@ -142,7 +140,7 @@ Path canonPath(PathView path, bool resolveSymlinks)
/* Normal component; copy it. */
else {
s += '/';
- if (const auto slash = path.find('/'); slash == string::npos) {
+ if (const auto slash = path.find('/'); slash == std::string::npos) {
s += path;
path = {};
} else {
@@ -175,7 +173,7 @@ Path canonPath(PathView path, bool resolveSymlinks)
Path dirOf(const PathView path)
{
Path::size_type pos = path.rfind('/');
- if (pos == string::npos)
+ if (pos == std::string::npos)
return ".";
return pos == 0 ? "/" : Path(path, 0, pos);
}
@@ -191,7 +189,7 @@ std::string_view baseNameOf(std::string_view path)
last -= 1;
auto pos = path.rfind('/', last);
- if (pos == string::npos)
+ if (pos == std::string::npos)
pos = 0;
else
pos += 1;
@@ -249,7 +247,7 @@ Path readLink(const Path & path)
else
throw SysError("reading symbolic link '%1%'", path);
else if (rlSize < bufSize)
- return string(buf.data(), rlSize);
+ return std::string(buf.data(), rlSize);
}
}
@@ -269,7 +267,7 @@ DirEntries readDirectory(DIR *dir, const Path & path)
struct dirent * dirent;
while (errno = 0, dirent = readdir(dir)) { /* sic */
checkInterrupt();
- string name = dirent->d_name;
+ std::string name = dirent->d_name;
if (name == "." || name == "..") continue;
entries.emplace_back(name, dirent->d_ino,
#ifdef HAVE_STRUCT_DIRENT_D_TYPE
@@ -303,7 +301,7 @@ unsigned char getFileType(const Path & path)
}
-string readFile(int fd)
+std::string readFile(int fd)
{
struct stat st;
if (fstat(fd, &st) == -1)
@@ -313,7 +311,7 @@ string readFile(int fd)
}
-string readFile(const Path & path)
+std::string readFile(const Path & path)
{
AutoCloseFD fd = open(path.c_str(), O_RDONLY | O_CLOEXEC);
if (!fd)
@@ -366,9 +364,9 @@ void writeFile(const Path & path, Source & source, mode_t mode)
}
}
-string readLine(int fd)
+std::string readLine(int fd)
{
- string s;
+ std::string s;
while (1) {
checkInterrupt();
char ch;
@@ -387,7 +385,7 @@ string readLine(int fd)
}
-void writeLine(int fd, string s)
+void writeLine(int fd, std::string s)
{
s += '\n';
writeFull(fd, s);
@@ -398,7 +396,7 @@ static void _deletePath(int parentfd, const Path & path, uint64_t & bytesFreed)
{
checkInterrupt();
- string name(baseNameOf(path));
+ std::string name(baseNameOf(path));
struct stat st;
if (fstatat(parentfd, name.c_str(), &st, AT_SYMLINK_NOFOLLOW) == -1) {
@@ -406,8 +404,29 @@ static void _deletePath(int parentfd, const Path & path, uint64_t & bytesFreed)
throw SysError("getting status of '%1%'", path);
}
- if (!S_ISDIR(st.st_mode) && st.st_nlink == 1)
- bytesFreed += st.st_size;
+ if (!S_ISDIR(st.st_mode)) {
+ /* We are about to delete a file. Will it likely free space? */
+
+ switch (st.st_nlink) {
+ /* Yes: last link. */
+ case 1:
+ bytesFreed += st.st_size;
+ break;
+ /* Maybe: yes, if 'auto-optimise-store' or manual optimisation
+ was performed. Instead of checking for real let's assume
+ it's an optimised file and space will be freed.
+
+ In worst case we will double count on freed space for files
+ with exactly two hardlinks for unoptimised packages.
+ */
+ case 2:
+ bytesFreed += st.st_size;
+ break;
+ /* No: 3+ links. */
+ default:
+ break;
+ }
+ }
if (S_ISDIR(st.st_mode)) {
/* Make the directory accessible. */
@@ -566,8 +585,8 @@ Path getConfigDir()
std::vector<Path> getConfigDirs()
{
Path configHome = getConfigDir();
- string configDirs = getEnv("XDG_CONFIG_DIRS").value_or("/etc/xdg");
- std::vector<Path> result = tokenizeString<std::vector<string>>(configDirs, ":");
+ auto configDirs = getEnv("XDG_CONFIG_DIRS").value_or("/etc/xdg");
+ std::vector<Path> result = tokenizeString<std::vector<std::string>>(configDirs, ":");
result.insert(result.begin(), configHome);
return result;
}
@@ -670,7 +689,7 @@ void writeFull(int fd, std::string_view s, bool allowInterrupts)
}
-string drainFD(int fd, bool block, const size_t reserveSize)
+std::string drainFD(int fd, bool block, const size_t reserveSize)
{
// the parser needs two extra bytes to append terminating characters, other users will
// not care very much about the extra memory.
@@ -682,7 +701,14 @@ string drainFD(int fd, bool block, const size_t reserveSize)
void drainFD(int fd, Sink & sink, bool block)
{
- int saved;
+ // silence GCC maybe-uninitialized warning in finally
+ int saved = 0;
+
+ if (!block) {
+ saved = fcntl(fd, F_GETFL);
+ if (fcntl(fd, F_SETFL, saved | O_NONBLOCK) == -1)
+ throw SysError("making file descriptor non-blocking");
+ }
Finally finally([&]() {
if (!block) {
@@ -691,12 +717,6 @@ void drainFD(int fd, Sink & sink, bool block)
}
});
- if (!block) {
- saved = fcntl(fd, F_GETFL);
- if (fcntl(fd, F_SETFL, saved | O_NONBLOCK) == -1)
- throw SysError("making file descriptor non-blocking");
- }
-
std::vector<unsigned char> buf(64 * 1024);
while (1) {
checkInterrupt();
@@ -719,7 +739,7 @@ void drainFD(int fd, Sink & sink, bool block)
AutoDelete::AutoDelete() : del{false} {}
-AutoDelete::AutoDelete(const string & p, bool recursive) : path(p)
+AutoDelete::AutoDelete(const std::string & p, bool recursive) : path(p)
{
del = true;
this->recursive = recursive;
@@ -1036,7 +1056,7 @@ std::vector<char *> stringsToCharPtrs(const Strings & ss)
return res;
}
-string runProgram(Path program, bool searchPath, const Strings & args,
+std::string runProgram(Path program, bool searchPath, const Strings & args,
const std::optional<std::string> & input)
{
auto res = runProgram(RunOptions {.program = program, .searchPath = searchPath, .args = args, .input = input});
@@ -1174,7 +1194,7 @@ void runProgram2(const RunOptions & options)
}
-void closeMostFDs(const set<int> & exceptions)
+void closeMostFDs(const std::set<int> & exceptions)
{
#if __linux__
try {
@@ -1238,11 +1258,11 @@ void _interrupted()
template<class C> C tokenizeString(std::string_view s, std::string_view separators)
{
C result;
- string::size_type pos = s.find_first_not_of(separators, 0);
- while (pos != string::npos) {
- string::size_type end = s.find_first_of(separators, pos + 1);
- if (end == string::npos) end = s.size();
- result.insert(result.end(), string(s, pos, end - pos));
+ auto pos = s.find_first_not_of(separators, 0);
+ while (pos != std::string_view::npos) {
+ auto end = s.find_first_of(separators, pos + 1);
+ if (end == std::string_view::npos) end = s.size();
+ result.insert(result.end(), std::string(s, pos, end - pos));
pos = s.find_first_not_of(separators, end);
}
return result;
@@ -1250,29 +1270,30 @@ template<class C> C tokenizeString(std::string_view s, std::string_view separato
template Strings tokenizeString(std::string_view s, std::string_view separators);
template StringSet tokenizeString(std::string_view s, std::string_view separators);
-template vector<string> tokenizeString(std::string_view s, std::string_view separators);
+template std::vector<std::string> tokenizeString(std::string_view s, std::string_view separators);
-string chomp(std::string_view s)
+std::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);
+ return i == std::string_view::npos ? "" : std::string(s, 0, i + 1);
}
-string trim(const string & s, const string & whitespace)
+std::string trim(std::string_view s, std::string_view whitespace)
{
auto i = s.find_first_not_of(whitespace);
- if (i == string::npos) return "";
+ if (i == s.npos) return "";
auto j = s.find_last_not_of(whitespace);
- return string(s, i, j == string::npos ? j : j - i + 1);
+ return std::string(s, i, j == s.npos ? j : j - i + 1);
}
-string replaceStrings(std::string_view s,
- const std::string & from, const std::string & to)
+std::string replaceStrings(
+ std::string res,
+ std::string_view from,
+ std::string_view to)
{
- string res(s);
if (from.empty()) return res;
size_t pos = 0;
while ((pos = res.find(from, pos)) != std::string::npos) {
@@ -1283,20 +1304,19 @@ string replaceStrings(std::string_view s,
}
-std::string rewriteStrings(const std::string & _s, const StringMap & rewrites)
+std::string rewriteStrings(std::string s, const StringMap & rewrites)
{
- auto s = _s;
for (auto & i : rewrites) {
if (i.first == i.second) continue;
size_t j = 0;
- while ((j = s.find(i.first, j)) != string::npos)
+ while ((j = s.find(i.first, j)) != std::string::npos)
s.replace(j, i.first.size(), i.second);
}
return s;
}
-string statusToString(int status)
+std::string statusToString(int status)
{
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
if (WIFEXITED(status))
@@ -1358,11 +1378,15 @@ std::string shellEscape(const std::string_view s)
void ignoreException()
{
+ /* Make sure no exceptions leave this function.
+ printError() also throws when remote is closed. */
try {
- throw;
- } catch (std::exception & e) {
- printError("error (ignored): %1%", e.what());
- }
+ try {
+ throw;
+ } catch (std::exception & e) {
+ printError("error (ignored): %1%", e.what());
+ }
+ } catch (...) { }
}
bool shouldANSI()
@@ -1408,7 +1432,7 @@ std::string filterANSIEscapes(const std::string & s, bool filterAll, unsigned in
}
}
- else if (*i == '\r')
+ else if (*i == '\r' || *i == '\a')
// do nothing for now
i++;
@@ -1444,9 +1468,10 @@ std::string filterANSIEscapes(const std::string & s, bool filterAll, unsigned in
constexpr char base64Chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
-string base64Encode(std::string_view s)
+std::string base64Encode(std::string_view s)
{
- string res;
+ std::string res;
+ res.reserve((s.size() + 2) / 3 * 4);
int data = 0, nbits = 0;
for (char c : s) {
@@ -1465,7 +1490,7 @@ string base64Encode(std::string_view s)
}
-string base64Decode(std::string_view s)
+std::string base64Decode(std::string_view s)
{
constexpr char npos = -1;
constexpr std::array<char, 256> base64DecodeChars = [&]() {
@@ -1477,7 +1502,10 @@ string base64Decode(std::string_view s)
return result;
}();
- string res;
+ std::string res;
+ // Some sequences are missing the padding consisting of up to two '='.
+ // vvv
+ res.reserve((s.size() + 2) / 4 * 3);
unsigned int d = 0, bits = 0;
for (char c : s) {
@@ -1561,7 +1589,22 @@ std::pair<unsigned short, unsigned short> getWindowSize()
}
-static Sync<std::list<std::function<void()>>> _interruptCallbacks;
+/* We keep track of interrupt callbacks using integer tokens, so we can iterate
+ safely without having to lock the data structure while executing arbitrary
+ functions.
+ */
+struct InterruptCallbacks {
+ typedef int64_t Token;
+
+ /* We use unique tokens so that we can't accidentally delete the wrong
+ handler because of an erroneous double delete. */
+ Token nextToken = 0;
+
+ /* Used as a list, see InterruptCallbacks comment. */
+ std::map<Token, std::function<void()>> callbacks;
+};
+
+static Sync<InterruptCallbacks> _interruptCallbacks;
static void signalHandlerThread(sigset_t set)
{
@@ -1583,8 +1626,19 @@ void triggerInterrupt()
_isInterrupted = true;
{
- auto interruptCallbacks(_interruptCallbacks.lock());
- for (auto & callback : *interruptCallbacks) {
+ InterruptCallbacks::Token i = 0;
+ while (true) {
+ std::function<void()> callback;
+ {
+ auto interruptCallbacks(_interruptCallbacks.lock());
+ auto lb = interruptCallbacks->callbacks.lower_bound(i);
+ if (lb == interruptCallbacks->callbacks.end())
+ break;
+
+ callback = lb->second;
+ i = lb->first + 1;
+ }
+
try {
callback();
} catch (...) {
@@ -1638,7 +1692,9 @@ void setStackSize(size_t stackSize)
#endif
}
+#if __linux__
static AutoCloseFD fdSavedMountNamespace;
+#endif
void saveMountNamespace()
{
@@ -1657,8 +1713,13 @@ void restoreMountNamespace()
{
#if __linux__
try {
+ auto savedCwd = absPath(".");
+
if (fdSavedMountNamespace && setns(fdSavedMountNamespace.get(), CLONE_NEWNS) == -1)
throw SysError("restoring parent mount namespace");
+ if (chdir(savedCwd.c_str()) == -1) {
+ throw SysError("restoring cwd");
+ }
} catch (Error & e) {
debug(e.msg());
}
@@ -1694,21 +1755,22 @@ void restoreProcessContext(bool restoreMounts)
/* RAII helper to automatically deregister a callback. */
struct InterruptCallbackImpl : InterruptCallback
{
- std::list<std::function<void()>>::iterator it;
+ InterruptCallbacks::Token token;
~InterruptCallbackImpl() override
{
- _interruptCallbacks.lock()->erase(it);
+ auto interruptCallbacks(_interruptCallbacks.lock());
+ interruptCallbacks->callbacks.erase(token);
}
};
std::unique_ptr<InterruptCallback> createInterruptCallback(std::function<void()> callback)
{
auto interruptCallbacks(_interruptCallbacks.lock());
- interruptCallbacks->push_back(callback);
+ auto token = interruptCallbacks->nextToken++;
+ interruptCallbacks->callbacks.emplace(token, callback);
auto res = std::make_unique<InterruptCallbackImpl>();
- res->it = interruptCallbacks->end();
- res->it--;
+ res->token = token;
return std::unique_ptr<InterruptCallback>(res.release());
}
@@ -1804,7 +1866,7 @@ void connect(int fd, const std::string & path)
}
-string showBytes(uint64_t bytes)
+std::string showBytes(uint64_t bytes)
{
return fmt("%.2f MiB", bytes / (1024.0 * 1024.0));
}
@@ -1815,7 +1877,7 @@ void commonChildInit(Pipe & logPipe)
{
logger = makeSimpleLogger();
- const static string pathNullDevice = "/dev/null";
+ const static std::string pathNullDevice = "/dev/null";
restoreProcessContext(false);
/* Put the child in a separate session (and thus a separate
diff --git a/src/libutil/util.hh b/src/libutil/util.hh
index 579a42785..20591952d 100644
--- a/src/libutil/util.hh
+++ b/src/libutil/util.hh
@@ -92,22 +92,22 @@ bool isLink(const Path & path);
removed. */
struct DirEntry
{
- string name;
+ std::string name;
ino_t ino;
unsigned char type; // one of DT_*
- DirEntry(const string & name, ino_t ino, unsigned char type)
- : name(name), ino(ino), type(type) { }
+ DirEntry(std::string name, ino_t ino, unsigned char type)
+ : name(std::move(name)), ino(ino), type(type) { }
};
-typedef vector<DirEntry> DirEntries;
+typedef std::vector<DirEntry> DirEntries;
DirEntries readDirectory(const Path & path);
unsigned char getFileType(const Path & path);
/* Read the contents of a file into a string. */
-string readFile(int fd);
-string readFile(const Path & path);
+std::string readFile(int fd);
+std::string readFile(const Path & path);
void readFile(const Path & path, Sink & sink);
/* Write a string to a file. */
@@ -116,10 +116,10 @@ void writeFile(const Path & path, std::string_view s, mode_t mode = 0666);
void writeFile(const Path & path, Source & source, mode_t mode = 0666);
/* Read a line from a file descriptor. */
-string readLine(int fd);
+std::string readLine(int fd);
/* Write a line to a file descriptor. */
-void writeLine(int fd, string s);
+void writeLine(int fd, std::string s);
/* Delete a path; i.e., in the case of a directory, it is deleted
recursively. It's not an error if the path does not exist. The
@@ -170,7 +170,7 @@ MakeError(EndOfFile, Error);
/* Read a file descriptor until EOF occurs. */
-string drainFD(int fd, bool block = true, const size_t reserveSize=0);
+std::string drainFD(int fd, bool block = true, const size_t reserveSize=0);
void drainFD(int fd, Sink & sink, bool block = true);
@@ -268,7 +268,7 @@ void killUser(uid_t uid);
pid to the caller. */
struct ProcessOptions
{
- string errorPrefix = "";
+ std::string errorPrefix = "";
bool dieWithParent = true;
bool runExitHandlers = false;
bool allowVfork = false;
@@ -279,7 +279,7 @@ pid_t startProcess(std::function<void()> fun, const ProcessOptions & options = P
/* Run a program and return its stdout in a string (i.e., like the
shell backtick operator). */
-string runProgram(Path program, bool searchPath = false,
+std::string runProgram(Path program, bool searchPath = false,
const Strings & args = Strings(),
const std::optional<std::string> & input = {});
@@ -343,7 +343,7 @@ std::vector<char *> stringsToCharPtrs(const Strings & ss);
/* Close all file descriptors except those listed in the given set.
Good practice in child processes. */
-void closeMostFDs(const set<int> & exceptions);
+void closeMostFDs(const std::set<int> & exceptions);
/* Set the close-on-exec flag for the given file descriptor. */
void closeOnExec(int fd);
@@ -378,12 +378,12 @@ template<class C> C tokenizeString(std::string_view s, std::string_view separato
/* Concatenate the given strings with a separator between the
elements. */
template<class C>
-string concatStringsSep(const std::string_view sep, const C & ss)
+std::string concatStringsSep(const std::string_view sep, const C & ss)
{
size_t size = 0;
// need a cast to string_view since this is also called with Symbols
for (const auto & s : ss) size += sep.size() + std::string_view(s).size();
- string s;
+ std::string s;
s.reserve(size);
for (auto & i : ss) {
if (s.size() != 0) s += sep;
@@ -394,7 +394,7 @@ string concatStringsSep(const std::string_view sep, const C & ss)
template<class ... Parts>
auto concatStrings(Parts && ... parts)
- -> std::enable_if_t<(... && std::is_convertible_v<Parts, std::string_view>), string>
+ -> std::enable_if_t<(... && std::is_convertible_v<Parts, std::string_view>), std::string>
{
std::string_view views[sizeof...(parts)] = { parts... };
return concatStringsSep({}, views);
@@ -413,24 +413,26 @@ template<class C> Strings quoteStrings(const C & c)
/* Remove trailing whitespace from a string. FIXME: return
std::string_view. */
-string chomp(std::string_view s);
+std::string chomp(std::string_view s);
/* Remove whitespace from the start and end of a string. */
-string trim(const string & s, const string & whitespace = " \n\r\t");
+std::string trim(std::string_view s, std::string_view whitespace = " \n\r\t");
/* Replace all occurrences of a string inside another string. */
-string replaceStrings(std::string_view s,
- const std::string & from, const std::string & to);
+std::string replaceStrings(
+ std::string s,
+ std::string_view from,
+ std::string_view to);
-std::string rewriteStrings(const std::string & s, const StringMap & rewrites);
+std::string rewriteStrings(std::string s, const StringMap & rewrites);
/* Convert the exit status of a child as returned by wait() into an
error string. */
-string statusToString(int status);
+std::string statusToString(int status);
bool statusOk(int status);
@@ -525,8 +527,8 @@ std::string filterANSIEscapes(const std::string & s,
/* Base64 encoding/decoding. */
-string base64Encode(std::string_view s);
-string base64Decode(std::string_view s);
+std::string base64Encode(std::string_view s);
+std::string base64Decode(std::string_view s);
/* Remove common leading whitespace from the lines in the string
diff --git a/src/libutil/xml-writer.cc b/src/libutil/xml-writer.cc
index 68857e34d..7993bee9a 100644
--- a/src/libutil/xml-writer.cc
+++ b/src/libutil/xml-writer.cc
@@ -31,11 +31,12 @@ void XMLWriter::close()
void XMLWriter::indent_(size_t depth)
{
if (!indent) return;
- output << string(depth * 2, ' ');
+ output << std::string(depth * 2, ' ');
}
-void XMLWriter::openElement(const string & name,
+void XMLWriter::openElement(
+ std::string_view name,
const XMLAttrs & attrs)
{
assert(!closed);
@@ -44,7 +45,7 @@ void XMLWriter::openElement(const string & name,
writeAttrs(attrs);
output << ">";
if (indent) output << std::endl;
- pendingElems.push_back(name);
+ pendingElems.push_back(std::string(name));
}
@@ -59,7 +60,8 @@ void XMLWriter::closeElement()
}
-void XMLWriter::writeEmptyElement(const string & name,
+void XMLWriter::writeEmptyElement(
+ std::string_view name,
const XMLAttrs & attrs)
{
assert(!closed);
diff --git a/src/libutil/xml-writer.hh b/src/libutil/xml-writer.hh
index b98b44526..4c91adee6 100644
--- a/src/libutil/xml-writer.hh
+++ b/src/libutil/xml-writer.hh
@@ -8,12 +8,8 @@
namespace nix {
-using std::string;
-using std::map;
-using std::list;
-
-typedef map<string, string> XMLAttrs;
+typedef std::map<std::string, std::string> XMLAttrs;
class XMLWriter
@@ -25,7 +21,7 @@ private:
bool indent;
bool closed;
- list<string> pendingElems;
+ std::list<std::string> pendingElems;
public:
@@ -34,11 +30,11 @@ public:
void close();
- void openElement(const string & name,
+ void openElement(std::string_view name,
const XMLAttrs & attrs = XMLAttrs());
void closeElement();
- void writeEmptyElement(const string & name,
+ void writeEmptyElement(std::string_view name,
const XMLAttrs & attrs = XMLAttrs());
private:
@@ -53,7 +49,7 @@ class XMLOpenElement
private:
XMLWriter & writer;
public:
- XMLOpenElement(XMLWriter & writer, const string & name,
+ XMLOpenElement(XMLWriter & writer, std::string_view name,
const XMLAttrs & attrs = XMLAttrs())
: writer(writer)
{