Add 'third_party/nix/' from commit 'be66c7a6b24e3c3c6157fd37b86c7203d14acf10'
git-subtree-dir: third_party/nix
git-subtree-mainline: cf8cd640c1
git-subtree-split: be66c7a6b24e3c3c6157fd37b86c7203d14acf10
This commit is contained in:
commit
7994fd1d54
737 changed files with 105390 additions and 0 deletions
55
third_party/nix/src/libutil/affinity.cc
vendored
Normal file
55
third_party/nix/src/libutil/affinity.cc
vendored
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
#include "types.hh"
|
||||
#include "util.hh"
|
||||
#include "affinity.hh"
|
||||
|
||||
#if __linux__
|
||||
#include <sched.h>
|
||||
#endif
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
||||
#if __linux__
|
||||
static bool didSaveAffinity = false;
|
||||
static cpu_set_t savedAffinity;
|
||||
#endif
|
||||
|
||||
|
||||
void setAffinityTo(int cpu)
|
||||
{
|
||||
#if __linux__
|
||||
if (sched_getaffinity(0, sizeof(cpu_set_t), &savedAffinity) == -1) return;
|
||||
didSaveAffinity = true;
|
||||
debug(format("locking this thread to CPU %1%") % cpu);
|
||||
cpu_set_t newAffinity;
|
||||
CPU_ZERO(&newAffinity);
|
||||
CPU_SET(cpu, &newAffinity);
|
||||
if (sched_setaffinity(0, sizeof(cpu_set_t), &newAffinity) == -1)
|
||||
printError(format("failed to lock thread to CPU %1%") % cpu);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
int lockToCurrentCPU()
|
||||
{
|
||||
#if __linux__
|
||||
int cpu = sched_getcpu();
|
||||
if (cpu != -1) setAffinityTo(cpu);
|
||||
return cpu;
|
||||
#else
|
||||
return -1;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
void restoreAffinity()
|
||||
{
|
||||
#if __linux__
|
||||
if (!didSaveAffinity) return;
|
||||
if (sched_setaffinity(0, sizeof(cpu_set_t), &savedAffinity) == -1)
|
||||
printError("failed to restore affinity %1%");
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
9
third_party/nix/src/libutil/affinity.hh
vendored
Normal file
9
third_party/nix/src/libutil/affinity.hh
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
#pragma once
|
||||
|
||||
namespace nix {
|
||||
|
||||
void setAffinityTo(int cpu);
|
||||
int lockToCurrentCPU();
|
||||
void restoreAffinity();
|
||||
|
||||
}
|
||||
378
third_party/nix/src/libutil/archive.cc
vendored
Normal file
378
third_party/nix/src/libutil/archive.cc
vendored
Normal file
|
|
@ -0,0 +1,378 @@
|
|||
#include <cerrno>
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
#include <strings.h> // for strcasecmp
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <dirent.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include "archive.hh"
|
||||
#include "util.hh"
|
||||
#include "config.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
struct ArchiveSettings : Config
|
||||
{
|
||||
Setting<bool> useCaseHack{this,
|
||||
#if __APPLE__
|
||||
true,
|
||||
#else
|
||||
false,
|
||||
#endif
|
||||
"use-case-hack",
|
||||
"Whether to enable a Darwin-specific hack for dealing with file name collisions."};
|
||||
};
|
||||
|
||||
static ArchiveSettings archiveSettings;
|
||||
|
||||
static GlobalConfig::Register r1(&archiveSettings);
|
||||
|
||||
const std::string narVersionMagic1 = "nix-archive-1";
|
||||
|
||||
static string caseHackSuffix = "~nix~case~hack~";
|
||||
|
||||
PathFilter defaultPathFilter = [](const Path &) { return true; };
|
||||
|
||||
|
||||
static void dumpContents(const Path & path, size_t size,
|
||||
Sink & sink)
|
||||
{
|
||||
sink << "contents" << size;
|
||||
|
||||
AutoCloseFD fd = open(path.c_str(), O_RDONLY | O_CLOEXEC);
|
||||
if (!fd) throw SysError(format("opening file '%1%'") % path);
|
||||
|
||||
std::vector<unsigned 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);
|
||||
}
|
||||
|
||||
writePadding(size, sink);
|
||||
}
|
||||
|
||||
|
||||
static void dump(const Path & path, Sink & sink, PathFilter & filter)
|
||||
{
|
||||
checkInterrupt();
|
||||
|
||||
struct stat st;
|
||||
if (lstat(path.c_str(), &st))
|
||||
throw SysError(format("getting attributes of path '%1%'") % path);
|
||||
|
||||
sink << "(";
|
||||
|
||||
if (S_ISREG(st.st_mode)) {
|
||||
sink << "type" << "regular";
|
||||
if (st.st_mode & S_IXUSR)
|
||||
sink << "executable" << "";
|
||||
dumpContents(path, (size_t) st.st_size, sink);
|
||||
}
|
||||
|
||||
else if (S_ISDIR(st.st_mode)) {
|
||||
sink << "type" << "directory";
|
||||
|
||||
/* If we're on a case-insensitive system like macOS, undo
|
||||
the case hack applied by restorePath(). */
|
||||
std::map<string, string> unhacked;
|
||||
for (auto & i : readDirectory(path))
|
||||
if (archiveSettings.useCaseHack) {
|
||||
string name(i.name);
|
||||
size_t pos = i.name.find(caseHackSuffix);
|
||||
if (pos != string::npos) {
|
||||
debug(format("removing case hack suffix from '%1%'") % (path + "/" + i.name));
|
||||
name.erase(pos);
|
||||
}
|
||||
if (unhacked.find(name) != unhacked.end())
|
||||
throw Error(format("file name collision in between '%1%' and '%2%'")
|
||||
% (path + "/" + unhacked[name]) % (path + "/" + i.name));
|
||||
unhacked[name] = i.name;
|
||||
} else
|
||||
unhacked[i.name] = i.name;
|
||||
|
||||
for (auto & i : unhacked)
|
||||
if (filter(path + "/" + i.first)) {
|
||||
sink << "entry" << "(" << "name" << i.first << "node";
|
||||
dump(path + "/" + i.second, sink, filter);
|
||||
sink << ")";
|
||||
}
|
||||
}
|
||||
|
||||
else if (S_ISLNK(st.st_mode))
|
||||
sink << "type" << "symlink" << "target" << readLink(path);
|
||||
|
||||
else throw Error(format("file '%1%' has an unsupported type") % path);
|
||||
|
||||
sink << ")";
|
||||
}
|
||||
|
||||
|
||||
void dumpPath(const Path & path, Sink & sink, PathFilter & filter)
|
||||
{
|
||||
sink << narVersionMagic1;
|
||||
dump(path, sink, filter);
|
||||
}
|
||||
|
||||
|
||||
void dumpString(const std::string & s, Sink & sink)
|
||||
{
|
||||
sink << narVersionMagic1 << "(" << "type" << "regular" << "contents" << s << ")";
|
||||
}
|
||||
|
||||
|
||||
static SerialisationError badArchive(string s)
|
||||
{
|
||||
return SerialisationError("bad archive: " + s);
|
||||
}
|
||||
|
||||
|
||||
#if 0
|
||||
static void skipGeneric(Source & source)
|
||||
{
|
||||
if (readString(source) == "(") {
|
||||
while (readString(source) != ")")
|
||||
skipGeneric(source);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
static void parseContents(ParseSink & sink, Source & source, const Path & path)
|
||||
{
|
||||
unsigned long long size = readLongLong(source);
|
||||
|
||||
sink.preallocateContents(size);
|
||||
|
||||
unsigned long long left = size;
|
||||
std::vector<unsigned char> buf(65536);
|
||||
|
||||
while (left) {
|
||||
checkInterrupt();
|
||||
auto n = buf.size();
|
||||
if ((unsigned long long)n > left) n = left;
|
||||
source(buf.data(), n);
|
||||
sink.receiveContents(buf.data(), n);
|
||||
left -= n;
|
||||
}
|
||||
|
||||
readPadding(size, source);
|
||||
}
|
||||
|
||||
|
||||
struct CaseInsensitiveCompare
|
||||
{
|
||||
bool operator() (const string & a, const string & b) const
|
||||
{
|
||||
return strcasecmp(a.c_str(), b.c_str()) < 0;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
static void parse(ParseSink & sink, Source & source, const Path & path)
|
||||
{
|
||||
string s;
|
||||
|
||||
s = readString(source);
|
||||
if (s != "(") throw badArchive("expected open tag");
|
||||
|
||||
enum { tpUnknown, tpRegular, tpDirectory, tpSymlink } type = tpUnknown;
|
||||
|
||||
std::map<Path, int, CaseInsensitiveCompare> names;
|
||||
|
||||
while (1) {
|
||||
checkInterrupt();
|
||||
|
||||
s = readString(source);
|
||||
|
||||
if (s == ")") {
|
||||
break;
|
||||
}
|
||||
|
||||
else if (s == "type") {
|
||||
if (type != tpUnknown)
|
||||
throw badArchive("multiple type fields");
|
||||
string t = readString(source);
|
||||
|
||||
if (t == "regular") {
|
||||
type = tpRegular;
|
||||
sink.createRegularFile(path);
|
||||
}
|
||||
|
||||
else if (t == "directory") {
|
||||
sink.createDirectory(path);
|
||||
type = tpDirectory;
|
||||
}
|
||||
|
||||
else if (t == "symlink") {
|
||||
type = tpSymlink;
|
||||
}
|
||||
|
||||
else throw badArchive("unknown file type " + t);
|
||||
|
||||
}
|
||||
|
||||
else if (s == "contents" && type == tpRegular) {
|
||||
parseContents(sink, source, path);
|
||||
}
|
||||
|
||||
else if (s == "executable" && type == tpRegular) {
|
||||
auto s = readString(source);
|
||||
if (s != "") throw badArchive("executable marker has non-empty value");
|
||||
sink.isExecutable();
|
||||
}
|
||||
|
||||
else if (s == "entry" && type == tpDirectory) {
|
||||
string name, prevName;
|
||||
|
||||
s = readString(source);
|
||||
if (s != "(") throw badArchive("expected open tag");
|
||||
|
||||
while (1) {
|
||||
checkInterrupt();
|
||||
|
||||
s = readString(source);
|
||||
|
||||
if (s == ")") {
|
||||
break;
|
||||
} else if (s == "name") {
|
||||
name = readString(source);
|
||||
if (name.empty() || name == "." || name == ".." || name.find('/') != string::npos || name.find((char) 0) != string::npos)
|
||||
throw Error(format("NAR contains invalid file name '%1%'") % name);
|
||||
if (name <= prevName)
|
||||
throw Error("NAR directory is not sorted");
|
||||
prevName = name;
|
||||
if (archiveSettings.useCaseHack) {
|
||||
auto i = names.find(name);
|
||||
if (i != names.end()) {
|
||||
debug(format("case collision between '%1%' and '%2%'") % i->first % name);
|
||||
name += caseHackSuffix;
|
||||
name += std::to_string(++i->second);
|
||||
} else
|
||||
names[name] = 0;
|
||||
}
|
||||
} else if (s == "node") {
|
||||
if (s.empty()) throw badArchive("entry name missing");
|
||||
parse(sink, source, path + "/" + name);
|
||||
} else
|
||||
throw badArchive("unknown field " + s);
|
||||
}
|
||||
}
|
||||
|
||||
else if (s == "target" && type == tpSymlink) {
|
||||
string target = readString(source);
|
||||
sink.createSymlink(path, target);
|
||||
}
|
||||
|
||||
else
|
||||
throw badArchive("unknown field " + s);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void parseDump(ParseSink & sink, Source & source)
|
||||
{
|
||||
string version;
|
||||
try {
|
||||
version = readString(source, narVersionMagic1.size());
|
||||
} catch (SerialisationError & e) {
|
||||
/* This generally means the integer at the start couldn't be
|
||||
decoded. Ignore and throw the exception below. */
|
||||
}
|
||||
if (version != narVersionMagic1)
|
||||
throw badArchive("input doesn't look like a Nix archive");
|
||||
parse(sink, source, "");
|
||||
}
|
||||
|
||||
|
||||
struct RestoreSink : ParseSink
|
||||
{
|
||||
Path dstPath;
|
||||
AutoCloseFD fd;
|
||||
|
||||
void createDirectory(const Path & path)
|
||||
{
|
||||
Path p = dstPath + path;
|
||||
if (mkdir(p.c_str(), 0777) == -1)
|
||||
throw SysError(format("creating directory '%1%'") % p);
|
||||
};
|
||||
|
||||
void createRegularFile(const Path & path)
|
||||
{
|
||||
Path p = dstPath + path;
|
||||
fd = open(p.c_str(), O_CREAT | O_EXCL | O_WRONLY | O_CLOEXEC, 0666);
|
||||
if (!fd) throw SysError(format("creating file '%1%'") % p);
|
||||
}
|
||||
|
||||
void isExecutable()
|
||||
{
|
||||
struct stat st;
|
||||
if (fstat(fd.get(), &st) == -1)
|
||||
throw SysError("fstat");
|
||||
if (fchmod(fd.get(), st.st_mode | (S_IXUSR | S_IXGRP | S_IXOTH)) == -1)
|
||||
throw SysError("fchmod");
|
||||
}
|
||||
|
||||
void preallocateContents(unsigned long long len)
|
||||
{
|
||||
#if HAVE_POSIX_FALLOCATE
|
||||
if (len) {
|
||||
errno = posix_fallocate(fd.get(), 0, len);
|
||||
/* Note that EINVAL may indicate that the underlying
|
||||
filesystem doesn't support preallocation (e.g. on
|
||||
OpenSolaris). Since preallocation is just an
|
||||
optimisation, ignore it. */
|
||||
if (errno && errno != EINVAL && errno != EOPNOTSUPP && errno != ENOSYS)
|
||||
throw SysError(format("preallocating file of %1% bytes") % len);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void receiveContents(unsigned char * data, unsigned int len)
|
||||
{
|
||||
writeFull(fd.get(), data, len);
|
||||
}
|
||||
|
||||
void createSymlink(const Path & path, const string & target)
|
||||
{
|
||||
Path p = dstPath + path;
|
||||
nix::createSymlink(target, p);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
void restorePath(const Path & path, Source & source)
|
||||
{
|
||||
RestoreSink sink;
|
||||
sink.dstPath = path;
|
||||
parseDump(sink, source);
|
||||
}
|
||||
|
||||
|
||||
void copyNAR(Source & source, Sink & sink)
|
||||
{
|
||||
// FIXME: if 'source' is the output of dumpPath() followed by EOF,
|
||||
// we should just forward all data directly without parsing.
|
||||
|
||||
ParseSink parseSink; /* null sink; just parse the NAR */
|
||||
|
||||
LambdaSource wrapper([&](unsigned char * data, size_t len) {
|
||||
auto n = source.read(data, len);
|
||||
sink(data, n);
|
||||
return n;
|
||||
});
|
||||
|
||||
parseDump(parseSink, wrapper);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
84
third_party/nix/src/libutil/archive.hh
vendored
Normal file
84
third_party/nix/src/libutil/archive.hh
vendored
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
#pragma once
|
||||
|
||||
#include "types.hh"
|
||||
#include "serialise.hh"
|
||||
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
||||
/* dumpPath creates a Nix archive of the specified path. The format
|
||||
is as follows:
|
||||
|
||||
IF path points to a REGULAR FILE:
|
||||
dump(path) = attrs(
|
||||
[ ("type", "regular")
|
||||
, ("contents", contents(path))
|
||||
])
|
||||
|
||||
IF path points to a DIRECTORY:
|
||||
dump(path) = attrs(
|
||||
[ ("type", "directory")
|
||||
, ("entries", concat(map(f, sort(entries(path)))))
|
||||
])
|
||||
where f(fn) = attrs(
|
||||
[ ("name", fn)
|
||||
, ("file", dump(path + "/" + fn))
|
||||
])
|
||||
|
||||
where:
|
||||
|
||||
attrs(as) = concat(map(attr, as)) + encN(0)
|
||||
attrs((a, b)) = encS(a) + encS(b)
|
||||
|
||||
encS(s) = encN(len(s)) + s + (padding until next 64-bit boundary)
|
||||
|
||||
encN(n) = 64-bit little-endian encoding of n.
|
||||
|
||||
contents(path) = the contents of a regular file.
|
||||
|
||||
sort(strings) = lexicographic sort by 8-bit value (strcmp).
|
||||
|
||||
entries(path) = the entries of a directory, without `.' and
|
||||
`..'.
|
||||
|
||||
`+' denotes string concatenation. */
|
||||
|
||||
|
||||
void dumpPath(const Path & path, Sink & sink,
|
||||
PathFilter & filter = defaultPathFilter);
|
||||
|
||||
void dumpString(const std::string & s, Sink & sink);
|
||||
|
||||
/* FIXME: fix this API, it sucks. */
|
||||
struct ParseSink
|
||||
{
|
||||
virtual void createDirectory(const Path & path) { };
|
||||
|
||||
virtual void createRegularFile(const Path & path) { };
|
||||
virtual void isExecutable() { };
|
||||
virtual void preallocateContents(unsigned long long size) { };
|
||||
virtual void receiveContents(unsigned char * data, unsigned int len) { };
|
||||
|
||||
virtual void createSymlink(const Path & path, const string & target) { };
|
||||
};
|
||||
|
||||
struct TeeSink : ParseSink
|
||||
{
|
||||
TeeSource source;
|
||||
|
||||
TeeSink(Source & source) : source(source) { }
|
||||
};
|
||||
|
||||
void parseDump(ParseSink & sink, Source & source);
|
||||
|
||||
void restorePath(const Path & path, Source & source);
|
||||
|
||||
/* Read a NAR from 'source' and write it to 'sink'. */
|
||||
void copyNAR(Source & source, Sink & sink);
|
||||
|
||||
|
||||
extern const std::string narVersionMagic1;
|
||||
|
||||
|
||||
}
|
||||
203
third_party/nix/src/libutil/args.cc
vendored
Normal file
203
third_party/nix/src/libutil/args.cc
vendored
Normal file
|
|
@ -0,0 +1,203 @@
|
|||
#include "args.hh"
|
||||
#include "hash.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
Args::FlagMaker Args::mkFlag()
|
||||
{
|
||||
return FlagMaker(*this);
|
||||
}
|
||||
|
||||
Args::FlagMaker::~FlagMaker()
|
||||
{
|
||||
assert(flag->longName != "");
|
||||
args.longFlags[flag->longName] = flag;
|
||||
if (flag->shortName) args.shortFlags[flag->shortName] = flag;
|
||||
}
|
||||
|
||||
void Args::parseCmdline(const Strings & _cmdline)
|
||||
{
|
||||
Strings pendingArgs;
|
||||
bool dashDash = false;
|
||||
|
||||
Strings cmdline(_cmdline);
|
||||
|
||||
for (auto pos = cmdline.begin(); pos != cmdline.end(); ) {
|
||||
|
||||
auto arg = *pos;
|
||||
|
||||
/* 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];
|
||||
auto next = pos; ++next;
|
||||
for (unsigned int j = 2; j < arg.length(); j++)
|
||||
if (isalpha(arg[j]))
|
||||
cmdline.insert(next, (string) "-" + arg[j]);
|
||||
else {
|
||||
cmdline.insert(next, string(arg, j));
|
||||
break;
|
||||
}
|
||||
arg = *pos;
|
||||
}
|
||||
|
||||
if (!dashDash && arg == "--") {
|
||||
dashDash = true;
|
||||
++pos;
|
||||
}
|
||||
else if (!dashDash && std::string(arg, 0, 1) == "-") {
|
||||
if (!processFlag(pos, cmdline.end()))
|
||||
throw UsageError(format("unrecognised flag '%1%'") % arg);
|
||||
}
|
||||
else {
|
||||
pendingArgs.push_back(*pos++);
|
||||
if (processArgs(pendingArgs, false))
|
||||
pendingArgs.clear();
|
||||
}
|
||||
}
|
||||
|
||||
processArgs(pendingArgs, true);
|
||||
}
|
||||
|
||||
void Args::printHelp(const string & programName, std::ostream & out)
|
||||
{
|
||||
std::cout << "Usage: " << programName << " <FLAGS>...";
|
||||
for (auto & exp : expectedArgs) {
|
||||
std::cout << renderLabels({exp.label});
|
||||
// FIXME: handle arity > 1
|
||||
if (exp.arity == 0) std::cout << "...";
|
||||
if (exp.optional) std::cout << "?";
|
||||
}
|
||||
std::cout << "\n";
|
||||
|
||||
auto s = description();
|
||||
if (s != "")
|
||||
std::cout << "\nSummary: " << s << ".\n";
|
||||
|
||||
if (longFlags.size()) {
|
||||
std::cout << "\n";
|
||||
std::cout << "Flags:\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);
|
||||
}
|
||||
|
||||
bool Args::processFlag(Strings::iterator & pos, Strings::iterator end)
|
||||
{
|
||||
assert(pos != end);
|
||||
|
||||
auto process = [&](const std::string & name, const Flag & flag) -> bool {
|
||||
++pos;
|
||||
std::vector<std::string> args;
|
||||
for (size_t n = 0 ; n < flag.arity; ++n) {
|
||||
if (pos == end) {
|
||||
if (flag.arity == ArityAny) break;
|
||||
throw UsageError(format("flag '%1%' requires %2% argument(s)")
|
||||
% name % flag.arity);
|
||||
}
|
||||
args.push_back(*pos++);
|
||||
}
|
||||
flag.handler(std::move(args));
|
||||
return true;
|
||||
};
|
||||
|
||||
if (string(*pos, 0, 2) == "--") {
|
||||
auto i = longFlags.find(string(*pos, 2));
|
||||
if (i == longFlags.end()) return false;
|
||||
return process("--" + i->first, *i->second);
|
||||
}
|
||||
|
||||
if (string(*pos, 0, 1) == "-" && pos->size() == 2) {
|
||||
auto c = (*pos)[1];
|
||||
auto i = shortFlags.find(c);
|
||||
if (i == shortFlags.end()) return false;
|
||||
return process(std::string("-") + c, *i->second);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Args::processArgs(const Strings & args, bool finish)
|
||||
{
|
||||
if (expectedArgs.empty()) {
|
||||
if (!args.empty())
|
||||
throw UsageError(format("unexpected argument '%1%'") % args.front());
|
||||
return true;
|
||||
}
|
||||
|
||||
auto & exp = expectedArgs.front();
|
||||
|
||||
bool res = false;
|
||||
|
||||
if ((exp.arity == 0 && finish) ||
|
||||
(exp.arity > 0 && args.size() == exp.arity))
|
||||
{
|
||||
std::vector<std::string> ss;
|
||||
for (auto & s : args) ss.push_back(s);
|
||||
exp.handler(std::move(ss));
|
||||
expectedArgs.pop_front();
|
||||
res = true;
|
||||
}
|
||||
|
||||
if (finish && !expectedArgs.empty() && !expectedArgs.front().optional)
|
||||
throw UsageError("more arguments are required");
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
Args::FlagMaker & Args::FlagMaker::mkHashTypeFlag(HashType * ht)
|
||||
{
|
||||
arity(1);
|
||||
label("type");
|
||||
description("hash algorithm ('md5', 'sha1', 'sha256', or 'sha512')");
|
||||
handler([ht](std::string s) {
|
||||
*ht = parseHashType(s);
|
||||
if (*ht == htUnknown)
|
||||
throw UsageError("unknown hash type '%1%'", s);
|
||||
});
|
||||
return *this;
|
||||
}
|
||||
|
||||
Strings argvToStrings(int argc, char * * argv)
|
||||
{
|
||||
Strings args;
|
||||
argc--; argv++;
|
||||
while (argc--) args.push_back(*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 += " <" + label + ">";
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
void printTable(std::ostream & out, const Table2 & table)
|
||||
{
|
||||
size_t max = 0;
|
||||
for (auto & row : table)
|
||||
max = std::max(max, row.first.size());
|
||||
for (auto & row : table) {
|
||||
out << " " << row.first
|
||||
<< std::string(max - row.first.size() + 2, ' ')
|
||||
<< row.second << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
201
third_party/nix/src/libutil/args.hh
vendored
Normal file
201
third_party/nix/src/libutil/args.hh
vendored
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
|
||||
#include "util.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
MakeError(UsageError, Error);
|
||||
|
||||
enum HashType : char;
|
||||
|
||||
class Args
|
||||
{
|
||||
public:
|
||||
|
||||
/* Parse the command line, throwing a UsageError if something goes
|
||||
wrong. */
|
||||
void parseCmdline(const Strings & cmdline);
|
||||
|
||||
virtual void printHelp(const string & programName, std::ostream & out);
|
||||
|
||||
virtual std::string description() { return ""; }
|
||||
|
||||
protected:
|
||||
|
||||
static const size_t ArityAny = std::numeric_limits<size_t>::max();
|
||||
|
||||
/* Flags. */
|
||||
struct Flag
|
||||
{
|
||||
typedef std::shared_ptr<Flag> ptr;
|
||||
std::string longName;
|
||||
char shortName = 0;
|
||||
std::string description;
|
||||
Strings labels;
|
||||
size_t arity = 0;
|
||||
std::function<void(std::vector<std::string>)> handler;
|
||||
std::string category;
|
||||
};
|
||||
|
||||
std::map<std::string, Flag::ptr> longFlags;
|
||||
std::map<char, Flag::ptr> shortFlags;
|
||||
|
||||
virtual bool processFlag(Strings::iterator & pos, Strings::iterator end);
|
||||
|
||||
virtual void printFlags(std::ostream & out);
|
||||
|
||||
/* Positional arguments. */
|
||||
struct ExpectedArg
|
||||
{
|
||||
std::string label;
|
||||
size_t arity; // 0 = any
|
||||
bool optional;
|
||||
std::function<void(std::vector<std::string>)> handler;
|
||||
};
|
||||
|
||||
std::list<ExpectedArg> expectedArgs;
|
||||
|
||||
virtual bool processArgs(const Strings & args, bool finish);
|
||||
|
||||
std::set<std::string> hiddenCategories;
|
||||
|
||||
public:
|
||||
|
||||
class FlagMaker
|
||||
{
|
||||
Args & args;
|
||||
Flag::ptr flag;
|
||||
friend class Args;
|
||||
FlagMaker(Args & args) : args(args), flag(std::make_shared<Flag>()) { };
|
||||
public:
|
||||
~FlagMaker();
|
||||
FlagMaker & longName(const std::string & s) { flag->longName = s; return *this; };
|
||||
FlagMaker & shortName(char s) { flag->shortName = s; return *this; };
|
||||
FlagMaker & description(const std::string & s) { flag->description = s; return *this; };
|
||||
FlagMaker & label(const std::string & l) { flag->arity = 1; flag->labels = {l}; return *this; };
|
||||
FlagMaker & labels(const Strings & ls) { flag->arity = ls.size(); flag->labels = ls; return *this; };
|
||||
FlagMaker & arity(size_t arity) { flag->arity = arity; return *this; };
|
||||
FlagMaker & handler(std::function<void(std::vector<std::string>)> handler) { flag->handler = handler; return *this; };
|
||||
FlagMaker & handler(std::function<void()> handler) { flag->handler = [handler](std::vector<std::string>) { handler(); }; return *this; };
|
||||
FlagMaker & handler(std::function<void(std::string)> handler) {
|
||||
flag->arity = 1;
|
||||
flag->handler = [handler](std::vector<std::string> ss) { handler(std::move(ss[0])); };
|
||||
return *this;
|
||||
};
|
||||
FlagMaker & category(const std::string & s) { flag->category = s; return *this; };
|
||||
|
||||
template<class T>
|
||||
FlagMaker & dest(T * dest)
|
||||
{
|
||||
flag->arity = 1;
|
||||
flag->handler = [=](std::vector<std::string> ss) { *dest = ss[0]; };
|
||||
return *this;
|
||||
};
|
||||
|
||||
template<class T>
|
||||
FlagMaker & set(T * dest, const T & val)
|
||||
{
|
||||
flag->arity = 0;
|
||||
flag->handler = [=](std::vector<std::string> ss) { *dest = val; };
|
||||
return *this;
|
||||
};
|
||||
|
||||
FlagMaker & mkHashTypeFlag(HashType * ht);
|
||||
};
|
||||
|
||||
FlagMaker mkFlag();
|
||||
|
||||
/* 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)
|
||||
{
|
||||
mkFlag()
|
||||
.shortName(shortName)
|
||||
.longName(longName)
|
||||
.labels({label})
|
||||
.description(description)
|
||||
.arity(1)
|
||||
.handler([=](std::vector<std::string> ss) { fun(ss[0]); });
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
mkFlag()
|
||||
.shortName(shortName)
|
||||
.longName(longName)
|
||||
.description(description)
|
||||
.handler([=](std::vector<std::string> ss) { *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)
|
||||
{
|
||||
mkFlag()
|
||||
.shortName(shortName)
|
||||
.longName(longName)
|
||||
.labels({"N"})
|
||||
.description(description)
|
||||
.arity(1)
|
||||
.handler([=](std::vector<std::string> ss) {
|
||||
I n;
|
||||
if (!string2Int(ss[0], n))
|
||||
throw UsageError("flag '--%s' requires a integer argument", longName);
|
||||
fun(n);
|
||||
});
|
||||
}
|
||||
|
||||
/* Expect a string argument. */
|
||||
void expectArg(const std::string & label, string * dest, bool optional = false)
|
||||
{
|
||||
expectedArgs.push_back(ExpectedArg{label, 1, optional, [=](std::vector<std::string> ss) {
|
||||
*dest = ss[0];
|
||||
}});
|
||||
}
|
||||
|
||||
/* Expect 0 or more arguments. */
|
||||
void expectArgs(const std::string & label, std::vector<std::string> * dest)
|
||||
{
|
||||
expectedArgs.push_back(ExpectedArg{label, 0, false, [=](std::vector<std::string> ss) {
|
||||
*dest = std::move(ss);
|
||||
}});
|
||||
}
|
||||
|
||||
friend class MultiCommand;
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
}
|
||||
432
third_party/nix/src/libutil/compression.cc
vendored
Normal file
432
third_party/nix/src/libutil/compression.cc
vendored
Normal file
|
|
@ -0,0 +1,432 @@
|
|||
#include "compression.hh"
|
||||
#include "util.hh"
|
||||
#include "finally.hh"
|
||||
#include "logging.hh"
|
||||
|
||||
#include <lzma.h>
|
||||
#include <bzlib.h>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
#include <brotli/decode.h>
|
||||
#include <brotli/encode.h>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace nix {
|
||||
|
||||
// Don't feed brotli too much at once.
|
||||
struct ChunkedCompressionSink : CompressionSink
|
||||
{
|
||||
uint8_t outbuf[32 * 1024];
|
||||
|
||||
void write(const unsigned char * data, size_t len) 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;
|
||||
}
|
||||
}
|
||||
|
||||
virtual void writeInternal(const unsigned char * data, size_t len) = 0;
|
||||
};
|
||||
|
||||
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); }
|
||||
};
|
||||
|
||||
struct XzDecompressionSink : CompressionSink
|
||||
{
|
||||
Sink & nextSink;
|
||||
uint8_t outbuf[BUFSIZ];
|
||||
lzma_stream strm = LZMA_STREAM_INIT;
|
||||
bool finished = false;
|
||||
|
||||
XzDecompressionSink(Sink & nextSink) : nextSink(nextSink)
|
||||
{
|
||||
lzma_ret ret = lzma_stream_decoder(
|
||||
&strm, UINT64_MAX, LZMA_CONCATENATED);
|
||||
if (ret != LZMA_OK)
|
||||
throw CompressionError("unable to initialise lzma decoder");
|
||||
|
||||
strm.next_out = outbuf;
|
||||
strm.avail_out = sizeof(outbuf);
|
||||
}
|
||||
|
||||
~XzDecompressionSink()
|
||||
{
|
||||
lzma_end(&strm);
|
||||
}
|
||||
|
||||
void finish() override
|
||||
{
|
||||
CompressionSink::flush();
|
||||
write(nullptr, 0);
|
||||
}
|
||||
|
||||
void write(const unsigned char * data, size_t len) override
|
||||
{
|
||||
strm.next_in = data;
|
||||
strm.avail_in = len;
|
||||
|
||||
while (!finished && (!data || strm.avail_in)) {
|
||||
checkInterrupt();
|
||||
|
||||
lzma_ret ret = lzma_code(&strm, 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);
|
||||
strm.next_out = outbuf;
|
||||
strm.avail_out = sizeof(outbuf);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct BzipDecompressionSink : ChunkedCompressionSink
|
||||
{
|
||||
Sink & nextSink;
|
||||
bz_stream strm;
|
||||
bool finished = false;
|
||||
|
||||
BzipDecompressionSink(Sink & nextSink) : nextSink(nextSink)
|
||||
{
|
||||
memset(&strm, 0, sizeof(strm));
|
||||
int ret = BZ2_bzDecompressInit(&strm, 0, 0);
|
||||
if (ret != BZ_OK)
|
||||
throw CompressionError("unable to initialise bzip2 decoder");
|
||||
|
||||
strm.next_out = (char *) outbuf;
|
||||
strm.avail_out = sizeof(outbuf);
|
||||
}
|
||||
|
||||
~BzipDecompressionSink()
|
||||
{
|
||||
BZ2_bzDecompressEnd(&strm);
|
||||
}
|
||||
|
||||
void finish() override
|
||||
{
|
||||
flush();
|
||||
write(nullptr, 0);
|
||||
}
|
||||
|
||||
void writeInternal(const unsigned char * data, size_t len) override
|
||||
{
|
||||
assert(len <= std::numeric_limits<decltype(strm.avail_in)>::max());
|
||||
|
||||
strm.next_in = (char *) data;
|
||||
strm.avail_in = len;
|
||||
|
||||
while (strm.avail_in) {
|
||||
checkInterrupt();
|
||||
|
||||
int ret = BZ2_bzDecompress(&strm);
|
||||
if (ret != BZ_OK && ret != BZ_STREAM_END)
|
||||
throw CompressionError("error while decompressing bzip2 file");
|
||||
|
||||
finished = ret == BZ_STREAM_END;
|
||||
|
||||
if (strm.avail_out < sizeof(outbuf) || strm.avail_in == 0) {
|
||||
nextSink(outbuf, sizeof(outbuf) - strm.avail_out);
|
||||
strm.next_out = (char *) outbuf;
|
||||
strm.avail_out = sizeof(outbuf);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct BrotliDecompressionSink : ChunkedCompressionSink
|
||||
{
|
||||
Sink & nextSink;
|
||||
BrotliDecoderState * state;
|
||||
bool finished = false;
|
||||
|
||||
BrotliDecompressionSink(Sink & nextSink) : nextSink(nextSink)
|
||||
{
|
||||
state = BrotliDecoderCreateInstance(nullptr, nullptr, nullptr);
|
||||
if (!state)
|
||||
throw CompressionError("unable to initialize brotli decoder");
|
||||
}
|
||||
|
||||
~BrotliDecompressionSink()
|
||||
{
|
||||
BrotliDecoderDestroyInstance(state);
|
||||
}
|
||||
|
||||
void finish() override
|
||||
{
|
||||
flush();
|
||||
writeInternal(nullptr, 0);
|
||||
}
|
||||
|
||||
void writeInternal(const unsigned char * data, size_t len) override
|
||||
{
|
||||
const uint8_t * next_in = data;
|
||||
size_t avail_in = len;
|
||||
uint8_t * next_out = outbuf;
|
||||
size_t avail_out = sizeof(outbuf);
|
||||
|
||||
while (!finished && (!data || avail_in)) {
|
||||
checkInterrupt();
|
||||
|
||||
if (!BrotliDecoderDecompressStream(state,
|
||||
&avail_in, &next_in,
|
||||
&avail_out, &next_out,
|
||||
nullptr))
|
||||
throw CompressionError("error while decompressing brotli file");
|
||||
|
||||
if (avail_out < sizeof(outbuf) || avail_in == 0) {
|
||||
nextSink(outbuf, sizeof(outbuf) - avail_out);
|
||||
next_out = outbuf;
|
||||
avail_out = sizeof(outbuf);
|
||||
}
|
||||
|
||||
finished = BrotliDecoderIsFinished(state);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ref<std::string> decompress(const std::string & method, const std::string & in)
|
||||
{
|
||||
StringSink ssink;
|
||||
auto sink = makeDecompressionSink(method, ssink);
|
||||
(*sink)(in);
|
||||
sink->finish();
|
||||
return ssink.s;
|
||||
}
|
||||
|
||||
ref<CompressionSink> makeDecompressionSink(const std::string & method, Sink & nextSink)
|
||||
{
|
||||
if (method == "none" || method == "")
|
||||
return make_ref<NoneSink>(nextSink);
|
||||
else if (method == "xz")
|
||||
return make_ref<XzDecompressionSink>(nextSink);
|
||||
else if (method == "bzip2")
|
||||
return make_ref<BzipDecompressionSink>(nextSink);
|
||||
else if (method == "br")
|
||||
return make_ref<BrotliDecompressionSink>(nextSink);
|
||||
else
|
||||
throw UnknownCompressionMethod("unknown compression method '%s'", method);
|
||||
}
|
||||
|
||||
struct XzCompressionSink : CompressionSink
|
||||
{
|
||||
Sink & nextSink;
|
||||
uint8_t outbuf[BUFSIZ];
|
||||
lzma_stream strm = LZMA_STREAM_INIT;
|
||||
bool finished = false;
|
||||
|
||||
XzCompressionSink(Sink & nextSink, bool parallel) : nextSink(nextSink)
|
||||
{
|
||||
lzma_ret ret;
|
||||
bool done = false;
|
||||
|
||||
if (parallel) {
|
||||
#ifdef HAVE_LZMA_MT
|
||||
lzma_mt mt_options = {};
|
||||
mt_options.flags = 0;
|
||||
mt_options.timeout = 300; // Using the same setting as the xz cmd line
|
||||
mt_options.preset = LZMA_PRESET_DEFAULT;
|
||||
mt_options.filters = NULL;
|
||||
mt_options.check = LZMA_CHECK_CRC64;
|
||||
mt_options.threads = lzma_cputhreads();
|
||||
mt_options.block_size = 0;
|
||||
if (mt_options.threads == 0)
|
||||
mt_options.threads = 1;
|
||||
// FIXME: maybe use lzma_stream_encoder_mt_memusage() to control the
|
||||
// number of threads.
|
||||
ret = lzma_stream_encoder_mt(&strm, &mt_options);
|
||||
done = true;
|
||||
#else
|
||||
printMsg(lvlError, "warning: parallel XZ compression requested but not supported, falling back to single-threaded compression");
|
||||
#endif
|
||||
}
|
||||
|
||||
if (!done)
|
||||
ret = lzma_easy_encoder(&strm, 6, LZMA_CHECK_CRC64);
|
||||
|
||||
if (ret != LZMA_OK)
|
||||
throw CompressionError("unable to initialise lzma encoder");
|
||||
|
||||
// FIXME: apply the x86 BCJ filter?
|
||||
|
||||
strm.next_out = outbuf;
|
||||
strm.avail_out = sizeof(outbuf);
|
||||
}
|
||||
|
||||
~XzCompressionSink()
|
||||
{
|
||||
lzma_end(&strm);
|
||||
}
|
||||
|
||||
void finish() override
|
||||
{
|
||||
CompressionSink::flush();
|
||||
write(nullptr, 0);
|
||||
}
|
||||
|
||||
void write(const unsigned char * data, size_t len) override
|
||||
{
|
||||
strm.next_in = data;
|
||||
strm.avail_in = len;
|
||||
|
||||
while (!finished && (!data || strm.avail_in)) {
|
||||
checkInterrupt();
|
||||
|
||||
lzma_ret ret = lzma_code(&strm, 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);
|
||||
strm.next_out = outbuf;
|
||||
strm.avail_out = sizeof(outbuf);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct BzipCompressionSink : ChunkedCompressionSink
|
||||
{
|
||||
Sink & nextSink;
|
||||
bz_stream strm;
|
||||
bool finished = false;
|
||||
|
||||
BzipCompressionSink(Sink & nextSink) : nextSink(nextSink)
|
||||
{
|
||||
memset(&strm, 0, sizeof(strm));
|
||||
int ret = BZ2_bzCompressInit(&strm, 9, 0, 30);
|
||||
if (ret != BZ_OK)
|
||||
throw CompressionError("unable to initialise bzip2 encoder");
|
||||
|
||||
strm.next_out = (char *) outbuf;
|
||||
strm.avail_out = sizeof(outbuf);
|
||||
}
|
||||
|
||||
~BzipCompressionSink()
|
||||
{
|
||||
BZ2_bzCompressEnd(&strm);
|
||||
}
|
||||
|
||||
void finish() override
|
||||
{
|
||||
flush();
|
||||
writeInternal(nullptr, 0);
|
||||
}
|
||||
|
||||
void writeInternal(const unsigned char * data, size_t len) override
|
||||
{
|
||||
assert(len <= std::numeric_limits<decltype(strm.avail_in)>::max());
|
||||
|
||||
strm.next_in = (char *) data;
|
||||
strm.avail_in = len;
|
||||
|
||||
while (!finished && (!data || strm.avail_in)) {
|
||||
checkInterrupt();
|
||||
|
||||
int ret = BZ2_bzCompress(&strm, 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);
|
||||
strm.next_out = (char *) outbuf;
|
||||
strm.avail_out = sizeof(outbuf);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct BrotliCompressionSink : ChunkedCompressionSink
|
||||
{
|
||||
Sink & nextSink;
|
||||
uint8_t outbuf[BUFSIZ];
|
||||
BrotliEncoderState *state;
|
||||
bool finished = false;
|
||||
|
||||
BrotliCompressionSink(Sink & nextSink) : nextSink(nextSink)
|
||||
{
|
||||
state = BrotliEncoderCreateInstance(nullptr, nullptr, nullptr);
|
||||
if (!state)
|
||||
throw CompressionError("unable to initialise brotli encoder");
|
||||
}
|
||||
|
||||
~BrotliCompressionSink()
|
||||
{
|
||||
BrotliEncoderDestroyInstance(state);
|
||||
}
|
||||
|
||||
void finish() override
|
||||
{
|
||||
flush();
|
||||
writeInternal(nullptr, 0);
|
||||
}
|
||||
|
||||
void writeInternal(const unsigned char * data, size_t len) override
|
||||
{
|
||||
const uint8_t * next_in = data;
|
||||
size_t avail_in = len;
|
||||
uint8_t * next_out = outbuf;
|
||||
size_t avail_out = sizeof(outbuf);
|
||||
|
||||
while (!finished && (!data || avail_in)) {
|
||||
checkInterrupt();
|
||||
|
||||
if (!BrotliEncoderCompressStream(state,
|
||||
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);
|
||||
next_out = outbuf;
|
||||
avail_out = sizeof(outbuf);
|
||||
}
|
||||
|
||||
finished = BrotliEncoderIsFinished(state);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ref<CompressionSink> makeCompressionSink(const std::string & method, Sink & nextSink, const bool parallel)
|
||||
{
|
||||
if (method == "none")
|
||||
return make_ref<NoneSink>(nextSink);
|
||||
else if (method == "xz")
|
||||
return make_ref<XzCompressionSink>(nextSink, parallel);
|
||||
else if (method == "bzip2")
|
||||
return make_ref<BzipCompressionSink>(nextSink);
|
||||
else if (method == "br")
|
||||
return make_ref<BrotliCompressionSink>(nextSink);
|
||||
else
|
||||
throw UnknownCompressionMethod(format("unknown compression method '%s'") % method);
|
||||
}
|
||||
|
||||
ref<std::string> compress(const std::string & method, const std::string & in, const bool parallel)
|
||||
{
|
||||
StringSink ssink;
|
||||
auto sink = makeCompressionSink(method, ssink, parallel);
|
||||
(*sink)(in);
|
||||
sink->finish();
|
||||
return ssink.s;
|
||||
}
|
||||
|
||||
}
|
||||
28
third_party/nix/src/libutil/compression.hh
vendored
Normal file
28
third_party/nix/src/libutil/compression.hh
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
#pragma once
|
||||
|
||||
#include "ref.hh"
|
||||
#include "types.hh"
|
||||
#include "serialise.hh"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace nix {
|
||||
|
||||
struct CompressionSink : BufferedSink
|
||||
{
|
||||
virtual void finish() = 0;
|
||||
};
|
||||
|
||||
ref<std::string> decompress(const std::string & method, const std::string & in);
|
||||
|
||||
ref<CompressionSink> makeDecompressionSink(const std::string & method, Sink & nextSink);
|
||||
|
||||
ref<std::string> compress(const std::string & method, const std::string & in, const bool parallel = false);
|
||||
|
||||
ref<CompressionSink> makeCompressionSink(const std::string & method, Sink & nextSink, const bool parallel = false);
|
||||
|
||||
MakeError(UnknownCompressionMethod, Error);
|
||||
|
||||
MakeError(CompressionError, Error);
|
||||
|
||||
}
|
||||
338
third_party/nix/src/libutil/config.cc
vendored
Normal file
338
third_party/nix/src/libutil/config.cc
vendored
Normal file
|
|
@ -0,0 +1,338 @@
|
|||
#include "config.hh"
|
||||
#include "args.hh"
|
||||
#include "json.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
bool Config::set(const std::string & name, const std::string & value)
|
||||
{
|
||||
auto i = _settings.find(name);
|
||||
if (i == _settings.end()) return false;
|
||||
i->second.setting->set(value);
|
||||
i->second.setting->overriden = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void Config::addSetting(AbstractSetting * setting)
|
||||
{
|
||||
_settings.emplace(setting->name, Config::SettingData(false, setting));
|
||||
for (auto & alias : setting->aliases)
|
||||
_settings.emplace(alias, Config::SettingData(true, setting));
|
||||
|
||||
bool set = false;
|
||||
|
||||
auto i = unknownSettings.find(setting->name);
|
||||
if (i != unknownSettings.end()) {
|
||||
setting->set(i->second);
|
||||
setting->overriden = true;
|
||||
unknownSettings.erase(i);
|
||||
set = true;
|
||||
}
|
||||
|
||||
for (auto & alias : setting->aliases) {
|
||||
auto i = unknownSettings.find(alias);
|
||||
if (i != unknownSettings.end()) {
|
||||
if (set)
|
||||
warn("setting '%s' is set, but it's an alias of '%s' which is also set",
|
||||
alias, setting->name);
|
||||
else {
|
||||
setting->set(i->second);
|
||||
setting->overriden = true;
|
||||
unknownSettings.erase(i);
|
||||
set = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AbstractConfig::warnUnknownSettings()
|
||||
{
|
||||
for (auto & s : unknownSettings)
|
||||
warn("unknown setting '%s'", s.first);
|
||||
}
|
||||
|
||||
void AbstractConfig::reapplyUnknownSettings()
|
||||
{
|
||||
auto unknownSettings2 = std::move(unknownSettings);
|
||||
for (auto & s : unknownSettings2)
|
||||
set(s.first, s.second);
|
||||
}
|
||||
|
||||
void Config::getSettings(std::map<std::string, SettingInfo> & res, bool overridenOnly)
|
||||
{
|
||||
for (auto & opt : _settings)
|
||||
if (!opt.second.isAlias && (!overridenOnly || opt.second.setting->overriden))
|
||||
res.emplace(opt.first, SettingInfo{opt.second.setting->to_string(), opt.second.setting->description});
|
||||
}
|
||||
|
||||
void AbstractConfig::applyConfigFile(const Path & path)
|
||||
{
|
||||
try {
|
||||
string contents = readFile(path);
|
||||
|
||||
unsigned int pos = 0;
|
||||
|
||||
while (pos < contents.size()) {
|
||||
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);
|
||||
|
||||
vector<string> tokens = tokenizeString<vector<string> >(line);
|
||||
if (tokens.empty()) continue;
|
||||
|
||||
if (tokens.size() < 2)
|
||||
throw UsageError("illegal configuration line '%1%' in '%2%'", line, path);
|
||||
|
||||
auto include = false;
|
||||
auto ignoreMissing = false;
|
||||
if (tokens[0] == "include")
|
||||
include = true;
|
||||
else if (tokens[0] == "!include") {
|
||||
include = true;
|
||||
ignoreMissing = true;
|
||||
}
|
||||
|
||||
if (include) {
|
||||
if (tokens.size() != 2)
|
||||
throw UsageError("illegal configuration line '%1%' in '%2%'", line, path);
|
||||
auto p = absPath(tokens[1], dirOf(path));
|
||||
if (pathExists(p)) {
|
||||
applyConfigFile(p);
|
||||
} else if (!ignoreMissing) {
|
||||
throw Error("file '%1%' included from '%2%' not found", p, path);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (tokens[1] != "=")
|
||||
throw UsageError("illegal configuration line '%1%' in '%2%'", line, path);
|
||||
|
||||
string name = tokens[0];
|
||||
|
||||
vector<string>::iterator i = tokens.begin();
|
||||
advance(i, 2);
|
||||
|
||||
set(name, concatStringsSep(" ", Strings(i, tokens.end()))); // FIXME: slow
|
||||
};
|
||||
} catch (SysError &) { }
|
||||
}
|
||||
|
||||
void Config::resetOverriden()
|
||||
{
|
||||
for (auto & s : _settings)
|
||||
s.second.setting->overriden = false;
|
||||
}
|
||||
|
||||
void Config::toJSON(JSONObject & out)
|
||||
{
|
||||
for (auto & s : _settings)
|
||||
if (!s.second.isAlias) {
|
||||
JSONObject out2(out.object(s.first));
|
||||
out2.attr("description", s.second.setting->description);
|
||||
JSONPlaceholder out3(out2.placeholder("value"));
|
||||
s.second.setting->toJSON(out3);
|
||||
}
|
||||
}
|
||||
|
||||
void Config::convertToArgs(Args & args, const std::string & category)
|
||||
{
|
||||
for (auto & s : _settings)
|
||||
if (!s.second.isAlias)
|
||||
s.second.setting->convertToArg(args, category);
|
||||
}
|
||||
|
||||
AbstractSetting::AbstractSetting(
|
||||
const std::string & name,
|
||||
const std::string & description,
|
||||
const std::set<std::string> & aliases)
|
||||
: name(name), description(description), aliases(aliases)
|
||||
{
|
||||
}
|
||||
|
||||
void AbstractSetting::toJSON(JSONPlaceholder & out)
|
||||
{
|
||||
out.write(to_string());
|
||||
}
|
||||
|
||||
void AbstractSetting::convertToArg(Args & args, const std::string & category)
|
||||
{
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void BaseSetting<T>::toJSON(JSONPlaceholder & out)
|
||||
{
|
||||
out.write(value);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void BaseSetting<T>::convertToArg(Args & args, const std::string & category)
|
||||
{
|
||||
args.mkFlag()
|
||||
.longName(name)
|
||||
.description(description)
|
||||
.arity(1)
|
||||
.handler([=](std::vector<std::string> ss) { overriden = true; set(ss[0]); })
|
||||
.category(category);
|
||||
}
|
||||
|
||||
template<> void BaseSetting<std::string>::set(const std::string & str)
|
||||
{
|
||||
value = str;
|
||||
}
|
||||
|
||||
template<> std::string BaseSetting<std::string>::to_string()
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void BaseSetting<T>::set(const std::string & str)
|
||||
{
|
||||
static_assert(std::is_integral<T>::value, "Integer required.");
|
||||
if (!string2Int(str, value))
|
||||
throw UsageError("setting '%s' has invalid value '%s'", name, str);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
std::string BaseSetting<T>::to_string()
|
||||
{
|
||||
static_assert(std::is_integral<T>::value, "Integer required.");
|
||||
return std::to_string(value);
|
||||
}
|
||||
|
||||
template<> void BaseSetting<bool>::set(const std::string & str)
|
||||
{
|
||||
if (str == "true" || str == "yes" || str == "1")
|
||||
value = true;
|
||||
else if (str == "false" || str == "no" || str == "0")
|
||||
value = false;
|
||||
else
|
||||
throw UsageError("Boolean setting '%s' has invalid value '%s'", name, str);
|
||||
}
|
||||
|
||||
template<> std::string BaseSetting<bool>::to_string()
|
||||
{
|
||||
return value ? "true" : "false";
|
||||
}
|
||||
|
||||
template<> void BaseSetting<bool>::convertToArg(Args & args, const std::string & category)
|
||||
{
|
||||
args.mkFlag()
|
||||
.longName(name)
|
||||
.description(description)
|
||||
.handler([=](std::vector<std::string> ss) { override(true); })
|
||||
.category(category);
|
||||
args.mkFlag()
|
||||
.longName("no-" + name)
|
||||
.description(description)
|
||||
.handler([=](std::vector<std::string> ss) { override(false); })
|
||||
.category(category);
|
||||
}
|
||||
|
||||
template<> void BaseSetting<Strings>::set(const std::string & str)
|
||||
{
|
||||
value = tokenizeString<Strings>(str);
|
||||
}
|
||||
|
||||
template<> std::string BaseSetting<Strings>::to_string()
|
||||
{
|
||||
return concatStringsSep(" ", value);
|
||||
}
|
||||
|
||||
template<> void BaseSetting<Strings>::toJSON(JSONPlaceholder & out)
|
||||
{
|
||||
JSONList list(out.list());
|
||||
for (auto & s : value)
|
||||
list.elem(s);
|
||||
}
|
||||
|
||||
template<> void BaseSetting<StringSet>::set(const std::string & str)
|
||||
{
|
||||
value = tokenizeString<StringSet>(str);
|
||||
}
|
||||
|
||||
template<> std::string BaseSetting<StringSet>::to_string()
|
||||
{
|
||||
return concatStringsSep(" ", value);
|
||||
}
|
||||
|
||||
template<> void BaseSetting<StringSet>::toJSON(JSONPlaceholder & out)
|
||||
{
|
||||
JSONList list(out.list());
|
||||
for (auto & s : value)
|
||||
list.elem(s);
|
||||
}
|
||||
|
||||
template class BaseSetting<int>;
|
||||
template class BaseSetting<unsigned int>;
|
||||
template class BaseSetting<long>;
|
||||
template class BaseSetting<unsigned long>;
|
||||
template class BaseSetting<long long>;
|
||||
template class BaseSetting<unsigned long long>;
|
||||
template class BaseSetting<bool>;
|
||||
template class BaseSetting<std::string>;
|
||||
template class BaseSetting<Strings>;
|
||||
template class BaseSetting<StringSet>;
|
||||
|
||||
void PathSetting::set(const std::string & str)
|
||||
{
|
||||
if (str == "") {
|
||||
if (allowEmpty)
|
||||
value = "";
|
||||
else
|
||||
throw UsageError("setting '%s' cannot be empty", name);
|
||||
} else
|
||||
value = canonPath(str);
|
||||
}
|
||||
|
||||
bool GlobalConfig::set(const std::string & name, const std::string & value)
|
||||
{
|
||||
for (auto & config : *configRegistrations)
|
||||
if (config->set(name, value)) return true;
|
||||
|
||||
unknownSettings.emplace(name, value);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void GlobalConfig::getSettings(std::map<std::string, SettingInfo> & res, bool overridenOnly)
|
||||
{
|
||||
for (auto & config : *configRegistrations)
|
||||
config->getSettings(res, overridenOnly);
|
||||
}
|
||||
|
||||
void GlobalConfig::resetOverriden()
|
||||
{
|
||||
for (auto & config : *configRegistrations)
|
||||
config->resetOverriden();
|
||||
}
|
||||
|
||||
void GlobalConfig::toJSON(JSONObject & out)
|
||||
{
|
||||
for (auto & config : *configRegistrations)
|
||||
config->toJSON(out);
|
||||
}
|
||||
|
||||
void GlobalConfig::convertToArgs(Args & args, const std::string & category)
|
||||
{
|
||||
for (auto & config : *configRegistrations)
|
||||
config->convertToArgs(args, category);
|
||||
}
|
||||
|
||||
GlobalConfig globalConfig;
|
||||
|
||||
GlobalConfig::ConfigRegistrations * GlobalConfig::configRegistrations;
|
||||
|
||||
GlobalConfig::Register::Register(Config * config)
|
||||
{
|
||||
if (!configRegistrations)
|
||||
configRegistrations = new ConfigRegistrations;
|
||||
configRegistrations->emplace_back(config);
|
||||
}
|
||||
|
||||
}
|
||||
261
third_party/nix/src/libutil/config.hh
vendored
Normal file
261
third_party/nix/src/libutil/config.hh
vendored
Normal file
|
|
@ -0,0 +1,261 @@
|
|||
#include <map>
|
||||
#include <set>
|
||||
|
||||
#include "types.hh"
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace nix {
|
||||
|
||||
class Args;
|
||||
class AbstractSetting;
|
||||
class JSONPlaceholder;
|
||||
class JSONObject;
|
||||
|
||||
class AbstractConfig
|
||||
{
|
||||
protected:
|
||||
StringMap unknownSettings;
|
||||
|
||||
AbstractConfig(const StringMap & initials = {})
|
||||
: unknownSettings(initials)
|
||||
{ }
|
||||
|
||||
public:
|
||||
|
||||
virtual bool set(const std::string & name, const std::string & value) = 0;
|
||||
|
||||
struct SettingInfo
|
||||
{
|
||||
std::string value;
|
||||
std::string description;
|
||||
};
|
||||
|
||||
virtual void getSettings(std::map<std::string, SettingInfo> & res, bool overridenOnly = false) = 0;
|
||||
|
||||
void applyConfigFile(const Path & path);
|
||||
|
||||
virtual void resetOverriden() = 0;
|
||||
|
||||
virtual void toJSON(JSONObject & out) = 0;
|
||||
|
||||
virtual void convertToArgs(Args & args, const std::string & category) = 0;
|
||||
|
||||
void warnUnknownSettings();
|
||||
|
||||
void reapplyUnknownSettings();
|
||||
};
|
||||
|
||||
/* A class to simplify providing configuration settings. The typical
|
||||
use is to inherit Config and add Setting<T> members:
|
||||
|
||||
class MyClass : private Config
|
||||
{
|
||||
Setting<int> foo{this, 123, "foo", "the number of foos to use"};
|
||||
Setting<std::string> bar{this, "blabla", "bar", "the name of the bar"};
|
||||
|
||||
MyClass() : Config(readConfigFile("/etc/my-app.conf"))
|
||||
{
|
||||
std::cout << foo << "\n"; // will print 123 unless overriden
|
||||
}
|
||||
};
|
||||
*/
|
||||
|
||||
class Config : public AbstractConfig
|
||||
{
|
||||
friend class AbstractSetting;
|
||||
|
||||
public:
|
||||
|
||||
struct SettingData
|
||||
{
|
||||
bool isAlias;
|
||||
AbstractSetting * setting;
|
||||
SettingData(bool isAlias, AbstractSetting * setting)
|
||||
: isAlias(isAlias), setting(setting)
|
||||
{ }
|
||||
};
|
||||
|
||||
typedef std::map<std::string, SettingData> Settings;
|
||||
|
||||
private:
|
||||
|
||||
Settings _settings;
|
||||
|
||||
public:
|
||||
|
||||
Config(const StringMap & initials = {})
|
||||
: AbstractConfig(initials)
|
||||
{ }
|
||||
|
||||
bool set(const std::string & name, const std::string & value) override;
|
||||
|
||||
void addSetting(AbstractSetting * setting);
|
||||
|
||||
void getSettings(std::map<std::string, SettingInfo> & res, bool overridenOnly = false) override;
|
||||
|
||||
void resetOverriden() override;
|
||||
|
||||
void toJSON(JSONObject & out) override;
|
||||
|
||||
void convertToArgs(Args & args, const std::string & category) override;
|
||||
};
|
||||
|
||||
class AbstractSetting
|
||||
{
|
||||
friend class Config;
|
||||
|
||||
public:
|
||||
|
||||
const std::string name;
|
||||
const std::string description;
|
||||
const std::set<std::string> aliases;
|
||||
|
||||
int created = 123;
|
||||
|
||||
bool overriden = false;
|
||||
|
||||
protected:
|
||||
|
||||
AbstractSetting(
|
||||
const std::string & name,
|
||||
const std::string & description,
|
||||
const std::set<std::string> & aliases);
|
||||
|
||||
virtual ~AbstractSetting()
|
||||
{
|
||||
// Check against a gcc miscompilation causing our constructor
|
||||
// not to run (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80431).
|
||||
assert(created == 123);
|
||||
}
|
||||
|
||||
virtual void set(const std::string & value) = 0;
|
||||
|
||||
virtual std::string to_string() = 0;
|
||||
|
||||
virtual void toJSON(JSONPlaceholder & out);
|
||||
|
||||
virtual void convertToArg(Args & args, const std::string & category);
|
||||
|
||||
bool isOverriden() { return overriden; }
|
||||
};
|
||||
|
||||
/* A setting of type T. */
|
||||
template<typename T>
|
||||
class BaseSetting : public AbstractSetting
|
||||
{
|
||||
protected:
|
||||
|
||||
T value;
|
||||
|
||||
public:
|
||||
|
||||
BaseSetting(const T & def,
|
||||
const std::string & name,
|
||||
const std::string & description,
|
||||
const std::set<std::string> & aliases = {})
|
||||
: AbstractSetting(name, description, aliases)
|
||||
, value(def)
|
||||
{ }
|
||||
|
||||
operator const T &() const { return value; }
|
||||
operator T &() { return value; }
|
||||
const T & get() const { return value; }
|
||||
bool operator ==(const T & v2) const { return value == v2; }
|
||||
bool operator !=(const T & v2) const { return value != v2; }
|
||||
void operator =(const T & v) { assign(v); }
|
||||
virtual void assign(const T & v) { value = v; }
|
||||
|
||||
void set(const std::string & str) override;
|
||||
|
||||
virtual void override(const T & v)
|
||||
{
|
||||
overriden = true;
|
||||
value = v;
|
||||
}
|
||||
|
||||
std::string to_string() override;
|
||||
|
||||
void convertToArg(Args & args, const std::string & category) override;
|
||||
|
||||
void toJSON(JSONPlaceholder & out) override;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
std::ostream & operator <<(std::ostream & str, const BaseSetting<T> & opt)
|
||||
{
|
||||
str << (const T &) opt;
|
||||
return str;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
bool operator ==(const T & v1, const BaseSetting<T> & v2) { return v1 == (const T &) v2; }
|
||||
|
||||
template<typename T>
|
||||
class Setting : public BaseSetting<T>
|
||||
{
|
||||
public:
|
||||
Setting(Config * options,
|
||||
const T & def,
|
||||
const std::string & name,
|
||||
const std::string & description,
|
||||
const std::set<std::string> & aliases = {})
|
||||
: BaseSetting<T>(def, name, description, aliases)
|
||||
{
|
||||
options->addSetting(this);
|
||||
}
|
||||
|
||||
void operator =(const T & v) { this->assign(v); }
|
||||
};
|
||||
|
||||
/* A special setting for Paths. These are automatically canonicalised
|
||||
(e.g. "/foo//bar/" becomes "/foo/bar"). */
|
||||
class PathSetting : public BaseSetting<Path>
|
||||
{
|
||||
bool allowEmpty;
|
||||
|
||||
public:
|
||||
|
||||
PathSetting(Config * options,
|
||||
bool allowEmpty,
|
||||
const Path & def,
|
||||
const std::string & name,
|
||||
const std::string & description,
|
||||
const std::set<std::string> & aliases = {})
|
||||
: BaseSetting<Path>(def, name, description, aliases)
|
||||
, allowEmpty(allowEmpty)
|
||||
{
|
||||
options->addSetting(this);
|
||||
}
|
||||
|
||||
void set(const std::string & str) override;
|
||||
|
||||
Path operator +(const char * p) const { return value + p; }
|
||||
|
||||
void operator =(const Path & v) { this->assign(v); }
|
||||
};
|
||||
|
||||
struct GlobalConfig : public AbstractConfig
|
||||
{
|
||||
typedef std::vector<Config*> ConfigRegistrations;
|
||||
static ConfigRegistrations * configRegistrations;
|
||||
|
||||
bool set(const std::string & name, const std::string & value) override;
|
||||
|
||||
void getSettings(std::map<std::string, SettingInfo> & res, bool overridenOnly = false) override;
|
||||
|
||||
void resetOverriden() override;
|
||||
|
||||
void toJSON(JSONObject & out) override;
|
||||
|
||||
void convertToArgs(Args & args, const std::string & category) override;
|
||||
|
||||
struct Register
|
||||
{
|
||||
Register(Config * config);
|
||||
};
|
||||
};
|
||||
|
||||
extern GlobalConfig globalConfig;
|
||||
|
||||
}
|
||||
14
third_party/nix/src/libutil/finally.hh
vendored
Normal file
14
third_party/nix/src/libutil/finally.hh
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
|
||||
/* A trivial class to run a function at the end of a scope. */
|
||||
class Finally
|
||||
{
|
||||
private:
|
||||
std::function<void()> fun;
|
||||
|
||||
public:
|
||||
Finally(std::function<void()> fun) : fun(fun) { }
|
||||
~Finally() { fun(); }
|
||||
};
|
||||
355
third_party/nix/src/libutil/hash.cc
vendored
Normal file
355
third_party/nix/src/libutil/hash.cc
vendored
Normal file
|
|
@ -0,0 +1,355 @@
|
|||
#include <iostream>
|
||||
#include <cstring>
|
||||
|
||||
#include <openssl/md5.h>
|
||||
#include <openssl/sha.h>
|
||||
|
||||
#include "hash.hh"
|
||||
#include "archive.hh"
|
||||
#include "util.hh"
|
||||
#include "istringstream_nocopy.hh"
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
||||
void Hash::init()
|
||||
{
|
||||
if (type == htMD5) hashSize = md5HashSize;
|
||||
else if (type == htSHA1) hashSize = sha1HashSize;
|
||||
else if (type == htSHA256) hashSize = sha256HashSize;
|
||||
else if (type == htSHA512) hashSize = sha512HashSize;
|
||||
else abort();
|
||||
assert(hashSize <= maxHashSize);
|
||||
memset(hash, 0, maxHashSize);
|
||||
}
|
||||
|
||||
|
||||
bool Hash::operator == (const Hash & h2) const
|
||||
{
|
||||
if (hashSize != h2.hashSize) return false;
|
||||
for (unsigned int i = 0; i < hashSize; i++)
|
||||
if (hash[i] != h2.hash[i]) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool Hash::operator != (const Hash & h2) const
|
||||
{
|
||||
return !(*this == h2);
|
||||
}
|
||||
|
||||
|
||||
bool Hash::operator < (const Hash & h) const
|
||||
{
|
||||
if (hashSize < h.hashSize) return true;
|
||||
if (hashSize > h.hashSize) return false;
|
||||
for (unsigned int i = 0; i < hashSize; i++) {
|
||||
if (hash[i] < h.hash[i]) return true;
|
||||
if (hash[i] > h.hash[i]) return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
const string base16Chars = "0123456789abcdef";
|
||||
|
||||
|
||||
static 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);
|
||||
}
|
||||
|
||||
|
||||
// omitted: E O U T
|
||||
const string base32Chars = "0123456789abcdfghijklmnpqrsvwxyz";
|
||||
|
||||
|
||||
static string printHash32(const Hash & hash)
|
||||
{
|
||||
assert(hash.hashSize);
|
||||
size_t len = hash.base32Len();
|
||||
assert(len);
|
||||
|
||||
string s;
|
||||
s.reserve(len);
|
||||
|
||||
for (int n = (int) len - 1; n >= 0; n--) {
|
||||
unsigned int b = n * 5;
|
||||
unsigned int i = b / 8;
|
||||
unsigned int j = b % 8;
|
||||
unsigned char c =
|
||||
(hash.hash[i] >> j)
|
||||
| (i >= hash.hashSize - 1 ? 0 : hash.hash[i + 1] << (8 - j));
|
||||
s.push_back(base32Chars[c & 0x1f]);
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
string printHash16or32(const Hash & hash)
|
||||
{
|
||||
return hash.to_string(hash.type == htMD5 ? Base16 : Base32, false);
|
||||
}
|
||||
|
||||
|
||||
std::string Hash::to_string(Base base, bool includeType) const
|
||||
{
|
||||
std::string s;
|
||||
if (base == SRI || includeType) {
|
||||
s += printHashType(type);
|
||||
s += base == SRI ? '-' : ':';
|
||||
}
|
||||
switch (base) {
|
||||
case Base16:
|
||||
s += printHash16(*this);
|
||||
break;
|
||||
case Base32:
|
||||
s += printHash32(*this);
|
||||
break;
|
||||
case Base64:
|
||||
case SRI:
|
||||
s += base64Encode(std::string((const char *) hash, hashSize));
|
||||
break;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
Hash::Hash(const std::string & s, HashType type)
|
||||
: type(type)
|
||||
{
|
||||
size_t pos = 0;
|
||||
bool isSRI = false;
|
||||
|
||||
auto sep = s.find(':');
|
||||
if (sep == string::npos) {
|
||||
sep = s.find('-');
|
||||
if (sep != string::npos) {
|
||||
isSRI = true;
|
||||
} else if (type == htUnknown)
|
||||
throw BadHash("hash '%s' does not include a type", s);
|
||||
}
|
||||
|
||||
if (sep != string::npos) {
|
||||
string hts = string(s, 0, sep);
|
||||
this->type = parseHashType(hts);
|
||||
if (this->type == htUnknown)
|
||||
throw BadHash("unknown hash type '%s'", hts);
|
||||
if (type != htUnknown && type != this->type)
|
||||
throw BadHash("hash '%s' should have type '%s'", s, printHashType(type));
|
||||
pos = sep + 1;
|
||||
}
|
||||
|
||||
init();
|
||||
|
||||
size_t size = s.size() - pos;
|
||||
|
||||
if (!isSRI && size == base16Len()) {
|
||||
|
||||
auto parseHexDigit = [&](char c) {
|
||||
if (c >= '0' && c <= '9') return c - '0';
|
||||
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
|
||||
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
|
||||
throw BadHash("invalid base-16 hash '%s'", s);
|
||||
};
|
||||
|
||||
for (unsigned int i = 0; i < hashSize; i++) {
|
||||
hash[i] =
|
||||
parseHexDigit(s[pos + i * 2]) << 4
|
||||
| parseHexDigit(s[pos + i * 2 + 1]);
|
||||
}
|
||||
}
|
||||
|
||||
else if (!isSRI && size == base32Len()) {
|
||||
|
||||
for (unsigned int n = 0; n < size; ++n) {
|
||||
char c = s[pos + size - n - 1];
|
||||
unsigned char digit;
|
||||
for (digit = 0; digit < base32Chars.size(); ++digit) /* !!! slow */
|
||||
if (base32Chars[digit] == c) break;
|
||||
if (digit >= 32)
|
||||
throw BadHash("invalid base-32 hash '%s'", s);
|
||||
unsigned int b = n * 5;
|
||||
unsigned int i = b / 8;
|
||||
unsigned int j = b % 8;
|
||||
hash[i] |= digit << j;
|
||||
|
||||
if (i < hashSize - 1) {
|
||||
hash[i + 1] |= digit >> (8 - j);
|
||||
} else {
|
||||
if (digit >> (8 - j))
|
||||
throw BadHash("invalid base-32 hash '%s'", s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else if (isSRI || size == base64Len()) {
|
||||
auto d = base64Decode(std::string(s, pos));
|
||||
if (d.size() != hashSize)
|
||||
throw BadHash("invalid %s hash '%s'", isSRI ? "SRI" : "base-64", s);
|
||||
assert(hashSize);
|
||||
memcpy(hash, d.data(), hashSize);
|
||||
}
|
||||
|
||||
else
|
||||
throw BadHash("hash '%s' has wrong length for hash type '%s'", s, printHashType(type));
|
||||
}
|
||||
|
||||
|
||||
union Ctx
|
||||
{
|
||||
MD5_CTX md5;
|
||||
SHA_CTX sha1;
|
||||
SHA256_CTX sha256;
|
||||
SHA512_CTX sha512;
|
||||
};
|
||||
|
||||
|
||||
static void start(HashType ht, Ctx & ctx)
|
||||
{
|
||||
if (ht == htMD5) MD5_Init(&ctx.md5);
|
||||
else if (ht == htSHA1) SHA1_Init(&ctx.sha1);
|
||||
else if (ht == htSHA256) SHA256_Init(&ctx.sha256);
|
||||
else if (ht == htSHA512) SHA512_Init(&ctx.sha512);
|
||||
}
|
||||
|
||||
|
||||
static void update(HashType ht, Ctx & ctx,
|
||||
const unsigned char * bytes, size_t len)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
static void finish(HashType ht, Ctx & ctx, unsigned char * hash)
|
||||
{
|
||||
if (ht == htMD5) MD5_Final(hash, &ctx.md5);
|
||||
else if (ht == htSHA1) SHA1_Final(hash, &ctx.sha1);
|
||||
else if (ht == htSHA256) SHA256_Final(hash, &ctx.sha256);
|
||||
else if (ht == htSHA512) SHA512_Final(hash, &ctx.sha512);
|
||||
}
|
||||
|
||||
|
||||
Hash hashString(HashType ht, const string & s)
|
||||
{
|
||||
Ctx ctx;
|
||||
Hash hash(ht);
|
||||
start(ht, ctx);
|
||||
update(ht, ctx, (const unsigned char *) s.data(), s.length());
|
||||
finish(ht, ctx, hash.hash);
|
||||
return hash;
|
||||
}
|
||||
|
||||
|
||||
Hash hashFile(HashType ht, const Path & path)
|
||||
{
|
||||
Ctx ctx;
|
||||
Hash hash(ht);
|
||||
start(ht, ctx);
|
||||
|
||||
AutoCloseFD fd = open(path.c_str(), O_RDONLY | O_CLOEXEC);
|
||||
if (!fd) throw SysError(format("opening file '%1%'") % path);
|
||||
|
||||
std::vector<unsigned char> buf(8192);
|
||||
ssize_t n;
|
||||
while ((n = read(fd.get(), buf.data(), buf.size()))) {
|
||||
checkInterrupt();
|
||||
if (n == -1) throw SysError(format("reading file '%1%'") % path);
|
||||
update(ht, ctx, buf.data(), n);
|
||||
}
|
||||
|
||||
finish(ht, ctx, hash.hash);
|
||||
return hash;
|
||||
}
|
||||
|
||||
|
||||
HashSink::HashSink(HashType ht) : ht(ht)
|
||||
{
|
||||
ctx = new Ctx;
|
||||
bytes = 0;
|
||||
start(ht, *ctx);
|
||||
}
|
||||
|
||||
HashSink::~HashSink()
|
||||
{
|
||||
bufPos = 0;
|
||||
delete ctx;
|
||||
}
|
||||
|
||||
void HashSink::write(const unsigned char * data, size_t len)
|
||||
{
|
||||
bytes += len;
|
||||
update(ht, *ctx, data, len);
|
||||
}
|
||||
|
||||
HashResult HashSink::finish()
|
||||
{
|
||||
flush();
|
||||
Hash hash(ht);
|
||||
nix::finish(ht, *ctx, hash.hash);
|
||||
return HashResult(hash, bytes);
|
||||
}
|
||||
|
||||
HashResult HashSink::currentHash()
|
||||
{
|
||||
flush();
|
||||
Ctx ctx2 = *ctx;
|
||||
Hash hash(ht);
|
||||
nix::finish(ht, ctx2, hash.hash);
|
||||
return HashResult(hash, bytes);
|
||||
}
|
||||
|
||||
|
||||
HashResult hashPath(
|
||||
HashType ht, const Path & path, PathFilter & filter)
|
||||
{
|
||||
HashSink sink(ht);
|
||||
dumpPath(path, sink, filter);
|
||||
return sink.finish();
|
||||
}
|
||||
|
||||
|
||||
Hash compressHash(const Hash & hash, unsigned int newSize)
|
||||
{
|
||||
Hash h;
|
||||
h.hashSize = newSize;
|
||||
for (unsigned int i = 0; i < hash.hashSize; ++i)
|
||||
h.hash[i % newSize] ^= hash.hash[i];
|
||||
return h;
|
||||
}
|
||||
|
||||
|
||||
HashType parseHashType(const string & s)
|
||||
{
|
||||
if (s == "md5") return htMD5;
|
||||
else if (s == "sha1") return htSHA1;
|
||||
else if (s == "sha256") return htSHA256;
|
||||
else if (s == "sha512") return htSHA512;
|
||||
else return htUnknown;
|
||||
}
|
||||
|
||||
|
||||
string printHashType(HashType ht)
|
||||
{
|
||||
if (ht == htMD5) return "md5";
|
||||
else if (ht == htSHA1) return "sha1";
|
||||
else if (ht == htSHA256) return "sha256";
|
||||
else if (ht == htSHA512) return "sha512";
|
||||
else abort();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
131
third_party/nix/src/libutil/hash.hh
vendored
Normal file
131
third_party/nix/src/libutil/hash.hh
vendored
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
#pragma once
|
||||
|
||||
#include "types.hh"
|
||||
#include "serialise.hh"
|
||||
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
||||
MakeError(BadHash, Error);
|
||||
|
||||
|
||||
enum HashType : char { htUnknown, htMD5, htSHA1, htSHA256, htSHA512 };
|
||||
|
||||
|
||||
const int md5HashSize = 16;
|
||||
const int sha1HashSize = 20;
|
||||
const int sha256HashSize = 32;
|
||||
const int sha512HashSize = 64;
|
||||
|
||||
extern const string base32Chars;
|
||||
|
||||
enum Base : int { Base64, Base32, Base16, SRI };
|
||||
|
||||
|
||||
struct Hash
|
||||
{
|
||||
static const unsigned int maxHashSize = 64;
|
||||
unsigned int hashSize = 0;
|
||||
unsigned char hash[maxHashSize] = {};
|
||||
|
||||
HashType type = htUnknown;
|
||||
|
||||
/* Create an unset hash object. */
|
||||
Hash() { };
|
||||
|
||||
/* Create a zero-filled hash object. */
|
||||
Hash(HashType type) : type(type) { init(); };
|
||||
|
||||
/* Initialize the hash from a string representation, in the format
|
||||
"[<type>:]<base16|base32|base64>" or "<type>-<base64>" (a
|
||||
Subresource Integrity hash expression). If the 'type' argument
|
||||
is htUnknown, then the hash type must be specified in the
|
||||
string. */
|
||||
Hash(const std::string & s, HashType type = htUnknown);
|
||||
|
||||
void init();
|
||||
|
||||
/* Check whether a hash is set. */
|
||||
operator bool () const { return type != htUnknown; }
|
||||
|
||||
/* Check whether two hash are equal. */
|
||||
bool operator == (const Hash & h2) const;
|
||||
|
||||
/* Check whether two hash are not equal. */
|
||||
bool operator != (const Hash & h2) const;
|
||||
|
||||
/* For sorting. */
|
||||
bool operator < (const Hash & h) const;
|
||||
|
||||
/* Returns the length of a base-16 representation of this hash. */
|
||||
size_t base16Len() const
|
||||
{
|
||||
return hashSize * 2;
|
||||
}
|
||||
|
||||
/* Returns the length of a base-32 representation of this hash. */
|
||||
size_t base32Len() const
|
||||
{
|
||||
return (hashSize * 8 - 1) / 5 + 1;
|
||||
}
|
||||
|
||||
/* Returns the length of a base-64 representation of this hash. */
|
||||
size_t base64Len() const
|
||||
{
|
||||
return ((4 * hashSize / 3) + 3) & ~3;
|
||||
}
|
||||
|
||||
/* Return a string representation of the hash, in base-16, base-32
|
||||
or base-64. By default, this is prefixed by the hash type
|
||||
(e.g. "sha256:"). */
|
||||
std::string to_string(Base base = Base32, bool includeType = true) const;
|
||||
};
|
||||
|
||||
|
||||
/* Print a hash in base-16 if it's MD5, or base-32 otherwise. */
|
||||
string printHash16or32(const Hash & hash);
|
||||
|
||||
/* Compute the hash of the given string. */
|
||||
Hash hashString(HashType ht, const string & s);
|
||||
|
||||
/* Compute the hash of the given file. */
|
||||
Hash hashFile(HashType ht, const Path & path);
|
||||
|
||||
/* Compute the hash of the given path. The hash is defined as
|
||||
(essentially) hashString(ht, dumpPath(path)). */
|
||||
typedef std::pair<Hash, unsigned long long> HashResult;
|
||||
HashResult hashPath(HashType ht, const Path & path,
|
||||
PathFilter & filter = defaultPathFilter);
|
||||
|
||||
/* Compress a hash to the specified number of bytes by cyclically
|
||||
XORing bytes together. */
|
||||
Hash compressHash(const Hash & hash, unsigned int newSize);
|
||||
|
||||
/* Parse a string representing a hash type. */
|
||||
HashType parseHashType(const string & s);
|
||||
|
||||
/* And the reverse. */
|
||||
string printHashType(HashType ht);
|
||||
|
||||
|
||||
union Ctx;
|
||||
|
||||
class HashSink : public BufferedSink
|
||||
{
|
||||
private:
|
||||
HashType ht;
|
||||
Ctx * ctx;
|
||||
unsigned long long bytes;
|
||||
|
||||
public:
|
||||
HashSink(HashType ht);
|
||||
HashSink(const HashSink & h);
|
||||
~HashSink();
|
||||
void write(const unsigned char * data, size_t len);
|
||||
HashResult finish();
|
||||
HashResult currentHash();
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
92
third_party/nix/src/libutil/istringstream_nocopy.hh
vendored
Normal file
92
third_party/nix/src/libutil/istringstream_nocopy.hh
vendored
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
/* This file provides a variant of std::istringstream that doesn't
|
||||
copy its string argument. This is useful for large strings. The
|
||||
caller must ensure that the string object is not destroyed while
|
||||
it's referenced by this object. */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
|
||||
template <class CharT, class Traits = std::char_traits<CharT>, class Allocator = std::allocator<CharT>>
|
||||
class basic_istringbuf_nocopy : public std::basic_streambuf<CharT, Traits>
|
||||
{
|
||||
public:
|
||||
typedef std::basic_string<CharT, Traits, Allocator> string_type;
|
||||
|
||||
typedef typename std::basic_streambuf<CharT, Traits>::off_type off_type;
|
||||
|
||||
typedef typename std::basic_streambuf<CharT, Traits>::pos_type pos_type;
|
||||
|
||||
typedef typename std::basic_streambuf<CharT, Traits>::int_type int_type;
|
||||
|
||||
typedef typename std::basic_streambuf<CharT, Traits>::traits_type traits_type;
|
||||
|
||||
private:
|
||||
const string_type & s;
|
||||
|
||||
off_type off;
|
||||
|
||||
public:
|
||||
basic_istringbuf_nocopy(const string_type & s) : s{s}, off{0}
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
pos_type seekoff(off_type off, std::ios_base::seekdir dir, std::ios_base::openmode which)
|
||||
{
|
||||
if (which & std::ios_base::in) {
|
||||
this->off = dir == std::ios_base::beg
|
||||
? off
|
||||
: (dir == std::ios_base::end
|
||||
? s.size() + off
|
||||
: this->off + off);
|
||||
}
|
||||
return pos_type(this->off);
|
||||
}
|
||||
|
||||
pos_type seekpos(pos_type pos, std::ios_base::openmode which)
|
||||
{
|
||||
return seekoff(pos, std::ios_base::beg, which);
|
||||
}
|
||||
|
||||
std::streamsize showmanyc()
|
||||
{
|
||||
return s.size() - off;
|
||||
}
|
||||
|
||||
int_type underflow()
|
||||
{
|
||||
if (typename string_type::size_type(off) == s.size())
|
||||
return traits_type::eof();
|
||||
return traits_type::to_int_type(s[off]);
|
||||
}
|
||||
|
||||
int_type uflow()
|
||||
{
|
||||
if (typename string_type::size_type(off) == s.size())
|
||||
return traits_type::eof();
|
||||
return traits_type::to_int_type(s[off++]);
|
||||
}
|
||||
|
||||
int_type pbackfail(int_type ch)
|
||||
{
|
||||
if (off == 0 || (ch != traits_type::eof() && ch != s[off - 1]))
|
||||
return traits_type::eof();
|
||||
|
||||
return traits_type::to_int_type(s[--off]);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
template <class CharT, class Traits = std::char_traits<CharT>, class Allocator = std::allocator<CharT>>
|
||||
class basic_istringstream_nocopy : public std::basic_iostream<CharT, Traits>
|
||||
{
|
||||
typedef basic_istringbuf_nocopy<CharT, Traits, Allocator> buf_type;
|
||||
buf_type buf;
|
||||
public:
|
||||
basic_istringstream_nocopy(const typename buf_type::string_type & s) :
|
||||
std::basic_iostream<CharT, Traits>(&buf), buf(s) {};
|
||||
};
|
||||
|
||||
typedef basic_istringstream_nocopy<char> istringstream_nocopy;
|
||||
174
third_party/nix/src/libutil/json.cc
vendored
Normal file
174
third_party/nix/src/libutil/json.cc
vendored
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
#include "json.hh"
|
||||
|
||||
#include <iomanip>
|
||||
#include <cstring>
|
||||
|
||||
namespace nix {
|
||||
|
||||
void toJSON(std::ostream & str, const char * start, const char * end)
|
||||
{
|
||||
str << '"';
|
||||
for (auto i = start; i != end; i++)
|
||||
if (*i == '\"' || *i == '\\') str << '\\' << *i;
|
||||
else if (*i == '\n') str << "\\n";
|
||||
else if (*i == '\r') str << "\\r";
|
||||
else if (*i == '\t') str << "\\t";
|
||||
else if (*i >= 0 && *i < 32)
|
||||
str << "\\u" << std::setfill('0') << std::setw(4) << std::hex << (uint16_t) *i << std::dec;
|
||||
else str << *i;
|
||||
str << '"';
|
||||
}
|
||||
|
||||
void toJSON(std::ostream & str, const char * s)
|
||||
{
|
||||
if (!s) str << "null"; else toJSON(str, s, s + strlen(s));
|
||||
}
|
||||
|
||||
template<> void toJSON<int>(std::ostream & str, const int & n) { str << n; }
|
||||
template<> void toJSON<unsigned int>(std::ostream & str, const unsigned int & n) { str << n; }
|
||||
template<> void toJSON<long>(std::ostream & str, const long & n) { str << n; }
|
||||
template<> void toJSON<unsigned long>(std::ostream & str, const unsigned long & n) { str << n; }
|
||||
template<> void toJSON<long long>(std::ostream & str, const long long & n) { str << n; }
|
||||
template<> void toJSON<unsigned long long>(std::ostream & str, const unsigned long long & n) { str << n; }
|
||||
template<> void toJSON<float>(std::ostream & str, const float & n) { str << n; }
|
||||
template<> void toJSON<double>(std::ostream & str, const double & n) { str << n; }
|
||||
|
||||
template<> void toJSON<std::string>(std::ostream & str, const std::string & s)
|
||||
{
|
||||
toJSON(str, s.c_str(), s.c_str() + s.size());
|
||||
}
|
||||
|
||||
template<> void toJSON<bool>(std::ostream & str, const bool & b)
|
||||
{
|
||||
str << (b ? "true" : "false");
|
||||
}
|
||||
|
||||
template<> void toJSON<std::nullptr_t>(std::ostream & str, const std::nullptr_t & b)
|
||||
{
|
||||
str << "null";
|
||||
}
|
||||
|
||||
JSONWriter::JSONWriter(std::ostream & str, bool indent)
|
||||
: state(new JSONState(str, indent))
|
||||
{
|
||||
state->stack++;
|
||||
}
|
||||
|
||||
JSONWriter::JSONWriter(JSONState * state)
|
||||
: state(state)
|
||||
{
|
||||
state->stack++;
|
||||
}
|
||||
|
||||
JSONWriter::~JSONWriter()
|
||||
{
|
||||
if (state) {
|
||||
assertActive();
|
||||
state->stack--;
|
||||
if (state->stack == 0) delete state;
|
||||
}
|
||||
}
|
||||
|
||||
void JSONWriter::comma()
|
||||
{
|
||||
assertActive();
|
||||
if (first) {
|
||||
first = false;
|
||||
} else {
|
||||
state->str << ',';
|
||||
}
|
||||
if (state->indent) indent();
|
||||
}
|
||||
|
||||
void JSONWriter::indent()
|
||||
{
|
||||
state->str << '\n' << std::string(state->depth * 2, ' ');
|
||||
}
|
||||
|
||||
void JSONList::open()
|
||||
{
|
||||
state->depth++;
|
||||
state->str << '[';
|
||||
}
|
||||
|
||||
JSONList::~JSONList()
|
||||
{
|
||||
state->depth--;
|
||||
if (state->indent && !first) indent();
|
||||
state->str << "]";
|
||||
}
|
||||
|
||||
JSONList JSONList::list()
|
||||
{
|
||||
comma();
|
||||
return JSONList(state);
|
||||
}
|
||||
|
||||
JSONObject JSONList::object()
|
||||
{
|
||||
comma();
|
||||
return JSONObject(state);
|
||||
}
|
||||
|
||||
JSONPlaceholder JSONList::placeholder()
|
||||
{
|
||||
comma();
|
||||
return JSONPlaceholder(state);
|
||||
}
|
||||
|
||||
void JSONObject::open()
|
||||
{
|
||||
state->depth++;
|
||||
state->str << '{';
|
||||
}
|
||||
|
||||
JSONObject::~JSONObject()
|
||||
{
|
||||
if (state) {
|
||||
state->depth--;
|
||||
if (state->indent && !first) indent();
|
||||
state->str << "}";
|
||||
}
|
||||
}
|
||||
|
||||
void JSONObject::attr(const std::string & s)
|
||||
{
|
||||
comma();
|
||||
toJSON(state->str, s);
|
||||
state->str << ':';
|
||||
if (state->indent) state->str << ' ';
|
||||
}
|
||||
|
||||
JSONList JSONObject::list(const std::string & name)
|
||||
{
|
||||
attr(name);
|
||||
return JSONList(state);
|
||||
}
|
||||
|
||||
JSONObject JSONObject::object(const std::string & name)
|
||||
{
|
||||
attr(name);
|
||||
return JSONObject(state);
|
||||
}
|
||||
|
||||
JSONPlaceholder JSONObject::placeholder(const std::string & name)
|
||||
{
|
||||
attr(name);
|
||||
return JSONPlaceholder(state);
|
||||
}
|
||||
|
||||
JSONList JSONPlaceholder::list()
|
||||
{
|
||||
assertValid();
|
||||
first = false;
|
||||
return JSONList(state);
|
||||
}
|
||||
|
||||
JSONObject JSONPlaceholder::object()
|
||||
{
|
||||
assertValid();
|
||||
first = false;
|
||||
return JSONObject(state);
|
||||
}
|
||||
|
||||
}
|
||||
189
third_party/nix/src/libutil/json.hh
vendored
Normal file
189
third_party/nix/src/libutil/json.hh
vendored
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <cassert>
|
||||
|
||||
namespace nix {
|
||||
|
||||
void toJSON(std::ostream & str, const char * start, const char * end);
|
||||
void toJSON(std::ostream & str, const char * s);
|
||||
|
||||
template<typename T>
|
||||
void toJSON(std::ostream & str, const T & n);
|
||||
|
||||
class JSONWriter
|
||||
{
|
||||
protected:
|
||||
|
||||
struct JSONState
|
||||
{
|
||||
std::ostream & str;
|
||||
bool indent;
|
||||
size_t depth = 0;
|
||||
size_t stack = 0;
|
||||
JSONState(std::ostream & str, bool indent) : str(str), indent(indent) { }
|
||||
~JSONState()
|
||||
{
|
||||
assert(stack == 0);
|
||||
}
|
||||
};
|
||||
|
||||
JSONState * state;
|
||||
|
||||
bool first = true;
|
||||
|
||||
JSONWriter(std::ostream & str, bool indent);
|
||||
|
||||
JSONWriter(JSONState * state);
|
||||
|
||||
~JSONWriter();
|
||||
|
||||
void assertActive()
|
||||
{
|
||||
assert(state->stack != 0);
|
||||
}
|
||||
|
||||
void comma();
|
||||
|
||||
void indent();
|
||||
};
|
||||
|
||||
class JSONObject;
|
||||
class JSONPlaceholder;
|
||||
|
||||
class JSONList : JSONWriter
|
||||
{
|
||||
private:
|
||||
|
||||
friend class JSONObject;
|
||||
friend class JSONPlaceholder;
|
||||
|
||||
void open();
|
||||
|
||||
JSONList(JSONState * state)
|
||||
: JSONWriter(state)
|
||||
{
|
||||
open();
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
JSONList(std::ostream & str, bool indent = false)
|
||||
: JSONWriter(str, indent)
|
||||
{
|
||||
open();
|
||||
}
|
||||
|
||||
~JSONList();
|
||||
|
||||
template<typename T>
|
||||
JSONList & elem(const T & v)
|
||||
{
|
||||
comma();
|
||||
toJSON(state->str, v);
|
||||
return *this;
|
||||
}
|
||||
|
||||
JSONList list();
|
||||
|
||||
JSONObject object();
|
||||
|
||||
JSONPlaceholder placeholder();
|
||||
};
|
||||
|
||||
class JSONObject : JSONWriter
|
||||
{
|
||||
private:
|
||||
|
||||
friend class JSONList;
|
||||
friend class JSONPlaceholder;
|
||||
|
||||
void open();
|
||||
|
||||
JSONObject(JSONState * state)
|
||||
: JSONWriter(state)
|
||||
{
|
||||
open();
|
||||
}
|
||||
|
||||
void attr(const std::string & s);
|
||||
|
||||
public:
|
||||
|
||||
JSONObject(std::ostream & str, bool indent = false)
|
||||
: JSONWriter(str, indent)
|
||||
{
|
||||
open();
|
||||
}
|
||||
|
||||
JSONObject(const JSONObject & obj) = delete;
|
||||
|
||||
JSONObject(JSONObject && obj)
|
||||
: JSONWriter(obj.state)
|
||||
{
|
||||
obj.state = 0;
|
||||
}
|
||||
|
||||
~JSONObject();
|
||||
|
||||
template<typename T>
|
||||
JSONObject & attr(const std::string & name, const T & v)
|
||||
{
|
||||
attr(name);
|
||||
toJSON(state->str, v);
|
||||
return *this;
|
||||
}
|
||||
|
||||
JSONList list(const std::string & name);
|
||||
|
||||
JSONObject object(const std::string & name);
|
||||
|
||||
JSONPlaceholder placeholder(const std::string & name);
|
||||
};
|
||||
|
||||
class JSONPlaceholder : JSONWriter
|
||||
{
|
||||
|
||||
private:
|
||||
|
||||
friend class JSONList;
|
||||
friend class JSONObject;
|
||||
|
||||
JSONPlaceholder(JSONState * state)
|
||||
: JSONWriter(state)
|
||||
{
|
||||
}
|
||||
|
||||
void assertValid()
|
||||
{
|
||||
assertActive();
|
||||
assert(first);
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
JSONPlaceholder(std::ostream & str, bool indent = false)
|
||||
: JSONWriter(str, indent)
|
||||
{
|
||||
}
|
||||
|
||||
~JSONPlaceholder()
|
||||
{
|
||||
assert(!first || std::uncaught_exception());
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void write(const T & v)
|
||||
{
|
||||
assertValid();
|
||||
first = false;
|
||||
toJSON(state->str, v);
|
||||
}
|
||||
|
||||
JSONList list();
|
||||
|
||||
JSONObject object();
|
||||
};
|
||||
|
||||
}
|
||||
48
third_party/nix/src/libutil/lazy.hh
vendored
Normal file
48
third_party/nix/src/libutil/lazy.hh
vendored
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
#include <exception>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
|
||||
namespace nix {
|
||||
|
||||
/* A helper class for lazily-initialized variables.
|
||||
|
||||
Lazy<T> var([]() { return value; });
|
||||
|
||||
declares a variable of type T that is initialized to 'value' (in a
|
||||
thread-safe way) on first use, that is, when var() is first
|
||||
called. If the initialiser code throws an exception, then all
|
||||
subsequent calls to var() will rethrow that exception. */
|
||||
template<typename T>
|
||||
class Lazy
|
||||
{
|
||||
|
||||
typedef std::function<T()> Init;
|
||||
|
||||
Init init;
|
||||
|
||||
std::once_flag done;
|
||||
|
||||
T value;
|
||||
|
||||
std::exception_ptr ex;
|
||||
|
||||
public:
|
||||
|
||||
Lazy(Init init) : init(init)
|
||||
{ }
|
||||
|
||||
const T & operator () ()
|
||||
{
|
||||
std::call_once(done, [&]() {
|
||||
try {
|
||||
value = init();
|
||||
} catch (...) {
|
||||
ex = std::current_exception();
|
||||
}
|
||||
});
|
||||
if (ex) std::rethrow_exception(ex);
|
||||
return value;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
9
third_party/nix/src/libutil/local.mk
vendored
Normal file
9
third_party/nix/src/libutil/local.mk
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
libraries += libutil
|
||||
|
||||
libutil_NAME = libnixutil
|
||||
|
||||
libutil_DIR := $(d)
|
||||
|
||||
libutil_SOURCES := $(wildcard $(d)/*.cc)
|
||||
|
||||
libutil_LDFLAGS = $(LIBLZMA_LIBS) -lbz2 -pthread $(OPENSSL_LIBS) $(LIBBROTLI_LIBS) $(BOOST_LDFLAGS) -lboost_context
|
||||
242
third_party/nix/src/libutil/logging.cc
vendored
Normal file
242
third_party/nix/src/libutil/logging.cc
vendored
Normal file
|
|
@ -0,0 +1,242 @@
|
|||
#include "logging.hh"
|
||||
#include "util.hh"
|
||||
|
||||
#include <atomic>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
namespace nix {
|
||||
|
||||
static thread_local ActivityId curActivity = 0;
|
||||
|
||||
ActivityId getCurActivity()
|
||||
{
|
||||
return curActivity;
|
||||
}
|
||||
void setCurActivity(const ActivityId activityId)
|
||||
{
|
||||
curActivity = activityId;
|
||||
}
|
||||
|
||||
Logger * logger = makeDefaultLogger();
|
||||
|
||||
void Logger::warn(const std::string & msg)
|
||||
{
|
||||
log(lvlWarn, ANSI_RED "warning:" ANSI_NORMAL " " + msg);
|
||||
}
|
||||
|
||||
class SimpleLogger : public Logger
|
||||
{
|
||||
public:
|
||||
|
||||
bool systemd, tty;
|
||||
|
||||
SimpleLogger()
|
||||
{
|
||||
systemd = getEnv("IN_SYSTEMD") == "1";
|
||||
tty = isatty(STDERR_FILENO);
|
||||
}
|
||||
|
||||
void log(Verbosity lvl, const FormatOrString & fs) override
|
||||
{
|
||||
if (lvl > verbosity) return;
|
||||
|
||||
std::string prefix;
|
||||
|
||||
if (systemd) {
|
||||
char c;
|
||||
switch (lvl) {
|
||||
case lvlError: c = '3'; break;
|
||||
case lvlWarn: c = '4'; break;
|
||||
case lvlInfo: c = '5'; break;
|
||||
case lvlTalkative: case lvlChatty: c = '6'; break;
|
||||
default: c = '7';
|
||||
}
|
||||
prefix = std::string("<") + c + ">";
|
||||
}
|
||||
|
||||
writeToStderr(prefix + filterANSIEscapes(fs.s, !tty) + "\n");
|
||||
}
|
||||
|
||||
void startActivity(ActivityId act, Verbosity lvl, ActivityType type,
|
||||
const std::string & s, const Fields & fields, ActivityId parent)
|
||||
override
|
||||
{
|
||||
if (lvl <= verbosity && !s.empty())
|
||||
log(lvl, s + "...");
|
||||
}
|
||||
};
|
||||
|
||||
Verbosity verbosity = lvlInfo;
|
||||
|
||||
void warnOnce(bool & haveWarned, const FormatOrString & fs)
|
||||
{
|
||||
if (!haveWarned) {
|
||||
warn(fs.s);
|
||||
haveWarned = true;
|
||||
}
|
||||
}
|
||||
|
||||
void writeToStderr(const string & s)
|
||||
{
|
||||
try {
|
||||
writeFull(STDERR_FILENO, s, false);
|
||||
} catch (SysError & e) {
|
||||
/* Ignore failing writes to stderr. We need to ignore write
|
||||
errors to ensure that cleanup code that logs to stderr runs
|
||||
to completion if the other side of stderr has been closed
|
||||
unexpectedly. */
|
||||
}
|
||||
}
|
||||
|
||||
Logger * makeDefaultLogger()
|
||||
{
|
||||
return new SimpleLogger();
|
||||
}
|
||||
|
||||
std::atomic<uint64_t> nextId{(uint64_t) getpid() << 32};
|
||||
|
||||
Activity::Activity(Logger & logger, Verbosity lvl, ActivityType type,
|
||||
const std::string & s, const Logger::Fields & fields, ActivityId parent)
|
||||
: logger(logger), id(nextId++)
|
||||
{
|
||||
logger.startActivity(id, lvl, type, s, fields, parent);
|
||||
}
|
||||
|
||||
struct JSONLogger : Logger
|
||||
{
|
||||
Logger & prevLogger;
|
||||
|
||||
JSONLogger(Logger & prevLogger) : prevLogger(prevLogger) { }
|
||||
|
||||
void addFields(nlohmann::json & json, const Fields & fields)
|
||||
{
|
||||
if (fields.empty()) return;
|
||||
auto & arr = json["fields"] = nlohmann::json::array();
|
||||
for (auto & f : fields)
|
||||
if (f.type == Logger::Field::tInt)
|
||||
arr.push_back(f.i);
|
||||
else if (f.type == Logger::Field::tString)
|
||||
arr.push_back(f.s);
|
||||
else
|
||||
abort();
|
||||
}
|
||||
|
||||
void write(const nlohmann::json & json)
|
||||
{
|
||||
prevLogger.log(lvlError, "@nix " + json.dump());
|
||||
}
|
||||
|
||||
void log(Verbosity lvl, const FormatOrString & fs) override
|
||||
{
|
||||
nlohmann::json json;
|
||||
json["action"] = "msg";
|
||||
json["level"] = lvl;
|
||||
json["msg"] = fs.s;
|
||||
write(json);
|
||||
}
|
||||
|
||||
void startActivity(ActivityId act, Verbosity lvl, ActivityType type,
|
||||
const std::string & s, const Fields & fields, ActivityId parent) override
|
||||
{
|
||||
nlohmann::json json;
|
||||
json["action"] = "start";
|
||||
json["id"] = act;
|
||||
json["level"] = lvl;
|
||||
json["type"] = type;
|
||||
json["text"] = s;
|
||||
addFields(json, fields);
|
||||
// FIXME: handle parent
|
||||
write(json);
|
||||
}
|
||||
|
||||
void stopActivity(ActivityId act) override
|
||||
{
|
||||
nlohmann::json json;
|
||||
json["action"] = "stop";
|
||||
json["id"] = act;
|
||||
write(json);
|
||||
}
|
||||
|
||||
void result(ActivityId act, ResultType type, const Fields & fields) override
|
||||
{
|
||||
nlohmann::json json;
|
||||
json["action"] = "result";
|
||||
json["id"] = act;
|
||||
json["type"] = type;
|
||||
addFields(json, fields);
|
||||
write(json);
|
||||
}
|
||||
};
|
||||
|
||||
Logger * makeJSONLogger(Logger & prevLogger)
|
||||
{
|
||||
return new JSONLogger(prevLogger);
|
||||
}
|
||||
|
||||
static Logger::Fields getFields(nlohmann::json & json)
|
||||
{
|
||||
Logger::Fields fields;
|
||||
for (auto & f : json) {
|
||||
if (f.type() == nlohmann::json::value_t::number_unsigned)
|
||||
fields.emplace_back(Logger::Field(f.get<uint64_t>()));
|
||||
else if (f.type() == nlohmann::json::value_t::string)
|
||||
fields.emplace_back(Logger::Field(f.get<std::string>()));
|
||||
else throw Error("unsupported JSON type %d", (int) f.type());
|
||||
}
|
||||
return fields;
|
||||
}
|
||||
|
||||
bool handleJSONLogMessage(const std::string & msg,
|
||||
const Activity & act, std::map<ActivityId, Activity> & activities, bool trusted)
|
||||
{
|
||||
if (!hasPrefix(msg, "@nix ")) return false;
|
||||
|
||||
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 == actDownload)
|
||||
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 == "stop")
|
||||
activities.erase((ActivityId) json["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 == "setPhase") {
|
||||
std::string phase = json["phase"];
|
||||
act.result(resSetPhase, phase);
|
||||
}
|
||||
|
||||
else if (action == "msg") {
|
||||
std::string msg = json["msg"];
|
||||
logger->log((Verbosity) json["level"], msg);
|
||||
}
|
||||
|
||||
} catch (std::exception & e) {
|
||||
printError("bad log message from builder: %s", e.what());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Activity::~Activity() {
|
||||
try {
|
||||
logger.stopActivity(id);
|
||||
} catch (...) {
|
||||
ignoreException();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
172
third_party/nix/src/libutil/logging.hh
vendored
Normal file
172
third_party/nix/src/libutil/logging.hh
vendored
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
#pragma once
|
||||
|
||||
#include "types.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
typedef enum {
|
||||
lvlError = 0,
|
||||
lvlWarn,
|
||||
lvlInfo,
|
||||
lvlTalkative,
|
||||
lvlChatty,
|
||||
lvlDebug,
|
||||
lvlVomit
|
||||
} Verbosity;
|
||||
|
||||
typedef enum {
|
||||
actUnknown = 0,
|
||||
actCopyPath = 100,
|
||||
actDownload = 101,
|
||||
actRealise = 102,
|
||||
actCopyPaths = 103,
|
||||
actBuilds = 104,
|
||||
actBuild = 105,
|
||||
actOptimiseStore = 106,
|
||||
actVerifyPaths = 107,
|
||||
actSubstitute = 108,
|
||||
actQueryPathInfo = 109,
|
||||
actPostBuildHook = 110,
|
||||
} ActivityType;
|
||||
|
||||
typedef enum {
|
||||
resFileLinked = 100,
|
||||
resBuildLogLine = 101,
|
||||
resUntrustedPath = 102,
|
||||
resCorruptedPath = 103,
|
||||
resSetPhase = 104,
|
||||
resProgress = 105,
|
||||
resSetExpected = 106,
|
||||
resPostBuildLogLine = 107,
|
||||
} ResultType;
|
||||
|
||||
typedef uint64_t ActivityId;
|
||||
|
||||
class Logger
|
||||
{
|
||||
friend struct Activity;
|
||||
|
||||
public:
|
||||
|
||||
struct Field
|
||||
{
|
||||
// FIXME: use std::variant.
|
||||
enum { tInt = 0, tString = 1 } type;
|
||||
uint64_t i = 0;
|
||||
std::string s;
|
||||
Field(const std::string & s) : type(tString), s(s) { }
|
||||
Field(const char * s) : type(tString), s(s) { }
|
||||
Field(const uint64_t & i) : type(tInt), i(i) { }
|
||||
};
|
||||
|
||||
typedef std::vector<Field> Fields;
|
||||
|
||||
virtual ~Logger() { }
|
||||
|
||||
virtual void log(Verbosity lvl, const FormatOrString & fs) = 0;
|
||||
|
||||
void log(const FormatOrString & fs)
|
||||
{
|
||||
log(lvlInfo, fs);
|
||||
}
|
||||
|
||||
virtual void warn(const std::string & msg);
|
||||
|
||||
virtual void startActivity(ActivityId act, Verbosity lvl, ActivityType type,
|
||||
const std::string & s, const Fields & fields, ActivityId parent) { };
|
||||
|
||||
virtual void stopActivity(ActivityId act) { };
|
||||
|
||||
virtual void result(ActivityId act, ResultType type, const Fields & fields) { };
|
||||
};
|
||||
|
||||
ActivityId getCurActivity();
|
||||
void setCurActivity(const ActivityId activityId);
|
||||
|
||||
struct Activity
|
||||
{
|
||||
Logger & logger;
|
||||
|
||||
const ActivityId id;
|
||||
|
||||
Activity(Logger & logger, Verbosity lvl, ActivityType type, const std::string & s = "",
|
||||
const Logger::Fields & fields = {}, ActivityId parent = getCurActivity());
|
||||
|
||||
Activity(Logger & logger, ActivityType type,
|
||||
const Logger::Fields & fields = {}, ActivityId parent = getCurActivity())
|
||||
: Activity(logger, lvlError, type, "", fields, parent) { };
|
||||
|
||||
Activity(const Activity & act) = delete;
|
||||
|
||||
~Activity();
|
||||
|
||||
void progress(uint64_t done = 0, uint64_t expected = 0, uint64_t running = 0, uint64_t failed = 0) const
|
||||
{ result(resProgress, done, expected, running, failed); }
|
||||
|
||||
void setExpected(ActivityType type2, uint64_t expected) const
|
||||
{ result(resSetExpected, type2, expected); }
|
||||
|
||||
template<typename... Args>
|
||||
void result(ResultType type, const Args & ... args) const
|
||||
{
|
||||
Logger::Fields fields;
|
||||
nop{(fields.emplace_back(Logger::Field(args)), 1)...};
|
||||
result(type, fields);
|
||||
}
|
||||
|
||||
void result(ResultType type, const Logger::Fields & fields) const
|
||||
{
|
||||
logger.result(id, type, fields);
|
||||
}
|
||||
|
||||
friend class Logger;
|
||||
};
|
||||
|
||||
struct PushActivity
|
||||
{
|
||||
const ActivityId prevAct;
|
||||
PushActivity(ActivityId act) : prevAct(getCurActivity()) { setCurActivity(act); }
|
||||
~PushActivity() { setCurActivity(prevAct); }
|
||||
};
|
||||
|
||||
extern Logger * logger;
|
||||
|
||||
Logger * makeDefaultLogger();
|
||||
|
||||
Logger * makeJSONLogger(Logger & prevLogger);
|
||||
|
||||
bool handleJSONLogMessage(const std::string & msg,
|
||||
const Activity & act, std::map<ActivityId, Activity> & activities,
|
||||
bool trusted);
|
||||
|
||||
extern Verbosity verbosity; /* suppress msgs > this */
|
||||
|
||||
/* Print a message if the current log level is at least the specified
|
||||
level. Note that this has to be implemented as a macro to ensure
|
||||
that the arguments are evaluated lazily. */
|
||||
#define printMsg(level, args...) \
|
||||
do { \
|
||||
if (level <= nix::verbosity) { \
|
||||
logger->log(level, fmt(args)); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define printError(args...) printMsg(lvlError, args)
|
||||
#define printInfo(args...) printMsg(lvlInfo, args)
|
||||
#define printTalkative(args...) printMsg(lvlTalkative, args)
|
||||
#define debug(args...) printMsg(lvlDebug, args)
|
||||
#define vomit(args...) printMsg(lvlVomit, args)
|
||||
|
||||
template<typename... Args>
|
||||
inline void warn(const std::string & fs, Args... args)
|
||||
{
|
||||
boost::format f(fs);
|
||||
nop{boost::io::detail::feed(f, args)...};
|
||||
logger->warn(f.str());
|
||||
}
|
||||
|
||||
void warnOnce(bool & haveWarned, const FormatOrString & fs);
|
||||
|
||||
void writeToStderr(const string & s);
|
||||
|
||||
}
|
||||
92
third_party/nix/src/libutil/lru-cache.hh
vendored
Normal file
92
third_party/nix/src/libutil/lru-cache.hh
vendored
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <list>
|
||||
#include <optional>
|
||||
|
||||
namespace nix {
|
||||
|
||||
/* A simple least-recently used cache. Not thread-safe. */
|
||||
template<typename Key, typename Value>
|
||||
class LRUCache
|
||||
{
|
||||
private:
|
||||
|
||||
size_t capacity;
|
||||
|
||||
// Stupid wrapper to get around circular dependency between Data
|
||||
// and LRU.
|
||||
struct LRUIterator;
|
||||
|
||||
using Data = std::map<Key, std::pair<LRUIterator, Value>>;
|
||||
using LRU = std::list<typename Data::iterator>;
|
||||
|
||||
struct LRUIterator { typename LRU::iterator it; };
|
||||
|
||||
Data data;
|
||||
LRU lru;
|
||||
|
||||
public:
|
||||
|
||||
LRUCache(size_t capacity) : capacity(capacity) { }
|
||||
|
||||
/* Insert or upsert an item in the cache. */
|
||||
void upsert(const Key & key, const Value & value)
|
||||
{
|
||||
if (capacity == 0) return;
|
||||
|
||||
erase(key);
|
||||
|
||||
if (data.size() >= capacity) {
|
||||
/* Retire the oldest item. */
|
||||
auto oldest = lru.begin();
|
||||
data.erase(*oldest);
|
||||
lru.erase(oldest);
|
||||
}
|
||||
|
||||
auto res = data.emplace(key, std::make_pair(LRUIterator(), value));
|
||||
assert(res.second);
|
||||
auto & i(res.first);
|
||||
|
||||
auto j = lru.insert(lru.end(), i);
|
||||
|
||||
i->second.first.it = j;
|
||||
}
|
||||
|
||||
bool erase(const Key & key)
|
||||
{
|
||||
auto i = data.find(key);
|
||||
if (i == data.end()) return false;
|
||||
lru.erase(i->second.first.it);
|
||||
data.erase(i);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Look up an item in the cache. If it exists, it becomes the most
|
||||
recently used item. */
|
||||
std::optional<Value> get(const Key & key)
|
||||
{
|
||||
auto i = data.find(key);
|
||||
if (i == data.end()) return {};
|
||||
|
||||
/* Move this item to the back of the LRU list. */
|
||||
lru.erase(i->second.first.it);
|
||||
auto j = lru.insert(lru.end(), i);
|
||||
i->second.first.it = j;
|
||||
|
||||
return i->second.second;
|
||||
}
|
||||
|
||||
size_t size()
|
||||
{
|
||||
return data.size();
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
data.clear();
|
||||
lru.clear();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
58
third_party/nix/src/libutil/monitor-fd.hh
vendored
Normal file
58
third_party/nix/src/libutil/monitor-fd.hh
vendored
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
#pragma once
|
||||
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
|
||||
#include <cstdlib>
|
||||
#include <poll.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
||||
class MonitorFdHup
|
||||
{
|
||||
private:
|
||||
std::thread thread;
|
||||
|
||||
public:
|
||||
MonitorFdHup(int fd)
|
||||
{
|
||||
thread = std::thread([fd]() {
|
||||
while (true) {
|
||||
/* Wait indefinitely until a POLLHUP occurs. */
|
||||
struct pollfd fds[1];
|
||||
fds[0].fd = fd;
|
||||
/* This shouldn't be necessary, but macOS doesn't seem to
|
||||
like a zeroed out events field.
|
||||
See rdar://37537852.
|
||||
*/
|
||||
fds[0].events = POLLHUP;
|
||||
auto count = poll(fds, 1, -1);
|
||||
if (count == -1) abort(); // can't happen
|
||||
/* This shouldn't happen, but can on macOS due to a bug.
|
||||
See rdar://37550628.
|
||||
|
||||
This may eventually need a delay or further
|
||||
coordination with the main thread if spinning proves
|
||||
too harmful.
|
||||
*/
|
||||
if (count == 0) continue;
|
||||
assert(fds[0].revents & POLLHUP);
|
||||
triggerInterrupt();
|
||||
break;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
~MonitorFdHup()
|
||||
{
|
||||
pthread_cancel(thread.native_handle());
|
||||
thread.join();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
187
third_party/nix/src/libutil/pool.hh
vendored
Normal file
187
third_party/nix/src/libutil/pool.hh
vendored
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <cassert>
|
||||
|
||||
#include "sync.hh"
|
||||
#include "ref.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
/* This template class implements a simple pool manager of resources
|
||||
of some type R, such as database connections. It is used as
|
||||
follows:
|
||||
|
||||
class Connection { ... };
|
||||
|
||||
Pool<Connection> pool;
|
||||
|
||||
{
|
||||
auto conn(pool.get());
|
||||
conn->exec("select ...");
|
||||
}
|
||||
|
||||
Here, the Connection object referenced by ‘conn’ is automatically
|
||||
returned to the pool when ‘conn’ goes out of scope.
|
||||
*/
|
||||
|
||||
template <class R>
|
||||
class Pool
|
||||
{
|
||||
public:
|
||||
|
||||
/* A function that produces new instances of R on demand. */
|
||||
typedef std::function<ref<R>()> Factory;
|
||||
|
||||
/* A function that checks whether an instance of R is still
|
||||
usable. Unusable instances are removed from the pool. */
|
||||
typedef std::function<bool(const ref<R> &)> Validator;
|
||||
|
||||
private:
|
||||
|
||||
Factory factory;
|
||||
Validator validator;
|
||||
|
||||
struct State
|
||||
{
|
||||
size_t inUse = 0;
|
||||
size_t max;
|
||||
std::vector<ref<R>> idle;
|
||||
};
|
||||
|
||||
Sync<State> state;
|
||||
|
||||
std::condition_variable wakeup;
|
||||
|
||||
public:
|
||||
|
||||
Pool(size_t max = std::numeric_limits<size_t>::max(),
|
||||
const Factory & factory = []() { return make_ref<R>(); },
|
||||
const Validator & validator = [](ref<R> r) { return true; })
|
||||
: factory(factory)
|
||||
, validator(validator)
|
||||
{
|
||||
auto state_(state.lock());
|
||||
state_->max = max;
|
||||
}
|
||||
|
||||
void incCapacity()
|
||||
{
|
||||
auto state_(state.lock());
|
||||
state_->max++;
|
||||
/* we could wakeup here, but this is only used when we're
|
||||
* about to nest Pool usages, and we want to save the slot for
|
||||
* the nested use if we can
|
||||
*/
|
||||
}
|
||||
|
||||
void decCapacity()
|
||||
{
|
||||
auto state_(state.lock());
|
||||
state_->max--;
|
||||
}
|
||||
|
||||
~Pool()
|
||||
{
|
||||
auto state_(state.lock());
|
||||
assert(!state_->inUse);
|
||||
state_->max = 0;
|
||||
state_->idle.clear();
|
||||
}
|
||||
|
||||
class Handle
|
||||
{
|
||||
private:
|
||||
Pool & pool;
|
||||
std::shared_ptr<R> r;
|
||||
bool bad = false;
|
||||
|
||||
friend Pool;
|
||||
|
||||
Handle(Pool & pool, std::shared_ptr<R> r) : pool(pool), r(r) { }
|
||||
|
||||
public:
|
||||
Handle(Handle && h) : pool(h.pool), r(h.r) { h.r.reset(); }
|
||||
|
||||
Handle(const Handle & l) = delete;
|
||||
|
||||
~Handle()
|
||||
{
|
||||
if (!r) return;
|
||||
{
|
||||
auto state_(pool.state.lock());
|
||||
if (!bad)
|
||||
state_->idle.push_back(ref<R>(r));
|
||||
assert(state_->inUse);
|
||||
state_->inUse--;
|
||||
}
|
||||
pool.wakeup.notify_one();
|
||||
}
|
||||
|
||||
R * operator -> () { return &*r; }
|
||||
R & operator * () { return *r; }
|
||||
|
||||
void markBad() { bad = true; }
|
||||
};
|
||||
|
||||
Handle get()
|
||||
{
|
||||
{
|
||||
auto state_(state.lock());
|
||||
|
||||
/* If we're over the maximum number of instance, we need
|
||||
to wait until a slot becomes available. */
|
||||
while (state_->idle.empty() && state_->inUse >= state_->max)
|
||||
state_.wait(wakeup);
|
||||
|
||||
while (!state_->idle.empty()) {
|
||||
auto p = state_->idle.back();
|
||||
state_->idle.pop_back();
|
||||
if (validator(p)) {
|
||||
state_->inUse++;
|
||||
return Handle(*this, p);
|
||||
}
|
||||
}
|
||||
|
||||
state_->inUse++;
|
||||
}
|
||||
|
||||
/* We need to create a new instance. Because that might take a
|
||||
while, we don't hold the lock in the meantime. */
|
||||
try {
|
||||
Handle h(*this, factory());
|
||||
return h;
|
||||
} catch (...) {
|
||||
auto state_(state.lock());
|
||||
state_->inUse--;
|
||||
wakeup.notify_one();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
size_t count()
|
||||
{
|
||||
auto state_(state.lock());
|
||||
return state_->idle.size() + state_->inUse;
|
||||
}
|
||||
|
||||
size_t capacity()
|
||||
{
|
||||
return state.lock()->max;
|
||||
}
|
||||
|
||||
void flushBad()
|
||||
{
|
||||
auto state_(state.lock());
|
||||
std::vector<ref<R>> left;
|
||||
for (auto & p : state_->idle)
|
||||
if (validator(p))
|
||||
left.push_back(p);
|
||||
std::swap(state_->idle, left);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
92
third_party/nix/src/libutil/ref.hh
vendored
Normal file
92
third_party/nix/src/libutil/ref.hh
vendored
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <exception>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace nix {
|
||||
|
||||
/* A simple non-nullable reference-counted pointer. Actually a wrapper
|
||||
around std::shared_ptr that prevents non-null constructions. */
|
||||
template<typename T>
|
||||
class ref
|
||||
{
|
||||
private:
|
||||
|
||||
std::shared_ptr<T> p;
|
||||
|
||||
public:
|
||||
|
||||
ref<T>(const ref<T> & r)
|
||||
: p(r.p)
|
||||
{ }
|
||||
|
||||
explicit ref<T>(const std::shared_ptr<T> & p)
|
||||
: p(p)
|
||||
{
|
||||
if (!p)
|
||||
throw std::invalid_argument("null pointer cast to ref");
|
||||
}
|
||||
|
||||
explicit ref<T>(T * p)
|
||||
: p(p)
|
||||
{
|
||||
if (!p)
|
||||
throw std::invalid_argument("null pointer cast to ref");
|
||||
}
|
||||
|
||||
T* operator ->() const
|
||||
{
|
||||
return &*p;
|
||||
}
|
||||
|
||||
T& operator *() const
|
||||
{
|
||||
return *p;
|
||||
}
|
||||
|
||||
operator std::shared_ptr<T> () const
|
||||
{
|
||||
return p;
|
||||
}
|
||||
|
||||
std::shared_ptr<T> get_ptr() const
|
||||
{
|
||||
return p;
|
||||
}
|
||||
|
||||
template<typename T2>
|
||||
ref<T2> cast() const
|
||||
{
|
||||
return ref<T2>(std::dynamic_pointer_cast<T2>(p));
|
||||
}
|
||||
|
||||
template<typename T2>
|
||||
std::shared_ptr<T2> dynamic_pointer_cast() const
|
||||
{
|
||||
return std::dynamic_pointer_cast<T2>(p);
|
||||
}
|
||||
|
||||
template<typename T2>
|
||||
operator ref<T2> () const
|
||||
{
|
||||
return ref<T2>((std::shared_ptr<T2>) p);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
template<typename T2, typename... Args>
|
||||
friend ref<T2>
|
||||
make_ref(Args&&... args);
|
||||
|
||||
};
|
||||
|
||||
template<typename T, typename... Args>
|
||||
inline ref<T>
|
||||
make_ref(Args&&... args)
|
||||
{
|
||||
auto p = std::make_shared<T>(std::forward<Args>(args)...);
|
||||
return ref<T>(p);
|
||||
}
|
||||
|
||||
}
|
||||
323
third_party/nix/src/libutil/serialise.cc
vendored
Normal file
323
third_party/nix/src/libutil/serialise.cc
vendored
Normal file
|
|
@ -0,0 +1,323 @@
|
|||
#include "serialise.hh"
|
||||
#include "util.hh"
|
||||
|
||||
#include <cstring>
|
||||
#include <cerrno>
|
||||
#include <memory>
|
||||
|
||||
#include <boost/coroutine2/coroutine.hpp>
|
||||
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
||||
void BufferedSink::operator () (const unsigned char * data, size_t len)
|
||||
{
|
||||
if (!buffer) buffer = decltype(buffer)(new unsigned char[bufSize]);
|
||||
|
||||
while (len) {
|
||||
/* Optimisation: bypass the buffer if the data exceeds the
|
||||
buffer size. */
|
||||
if (bufPos + len >= bufSize) {
|
||||
flush();
|
||||
write(data, len);
|
||||
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;
|
||||
if (bufPos == bufSize) flush();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void BufferedSink::flush()
|
||||
{
|
||||
if (bufPos == 0) return;
|
||||
size_t n = bufPos;
|
||||
bufPos = 0; // don't trigger the assert() in ~BufferedSink()
|
||||
write(buffer.get(), n);
|
||||
}
|
||||
|
||||
|
||||
FdSink::~FdSink()
|
||||
{
|
||||
try { flush(); } catch (...) { ignoreException(); }
|
||||
}
|
||||
|
||||
|
||||
size_t threshold = 256 * 1024 * 1024;
|
||||
|
||||
static void warnLargeDump()
|
||||
{
|
||||
printError("warning: dumping very large path (> 256 MiB); this may run out of memory");
|
||||
}
|
||||
|
||||
|
||||
void FdSink::write(const unsigned char * data, size_t len)
|
||||
{
|
||||
written += len;
|
||||
static bool warned = false;
|
||||
if (warn && !warned) {
|
||||
if (written > threshold) {
|
||||
warnLargeDump();
|
||||
warned = true;
|
||||
}
|
||||
}
|
||||
try {
|
||||
writeFull(fd, data, len);
|
||||
} catch (SysError & e) {
|
||||
_good = false;
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool FdSink::good()
|
||||
{
|
||||
return _good;
|
||||
}
|
||||
|
||||
|
||||
void Source::operator () (unsigned char * data, size_t len)
|
||||
{
|
||||
while (len) {
|
||||
size_t n = read(data, len);
|
||||
data += n; len -= n;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
std::string Source::drain()
|
||||
{
|
||||
std::string s;
|
||||
std::vector<unsigned char> buf(8192);
|
||||
while (true) {
|
||||
size_t n;
|
||||
try {
|
||||
n = read(buf.data(), buf.size());
|
||||
s.append((char *) buf.data(), n);
|
||||
} catch (EndOfFile &) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
size_t BufferedSource::read(unsigned char * data, size_t len)
|
||||
{
|
||||
if (!buffer) buffer = decltype(buffer)(new unsigned char[bufSize]);
|
||||
|
||||
if (!bufPosIn) bufPosIn = readUnbuffered(buffer.get(), bufSize);
|
||||
|
||||
/* Copy out the data in the buffer. */
|
||||
size_t n = len > bufPosIn - bufPosOut ? bufPosIn - bufPosOut : len;
|
||||
memcpy(data, buffer.get() + bufPosOut, n);
|
||||
bufPosOut += n;
|
||||
if (bufPosIn == bufPosOut) bufPosIn = bufPosOut = 0;
|
||||
return n;
|
||||
}
|
||||
|
||||
|
||||
bool BufferedSource::hasData()
|
||||
{
|
||||
return bufPosOut < bufPosIn;
|
||||
}
|
||||
|
||||
|
||||
size_t FdSource::readUnbuffered(unsigned char * data, size_t len)
|
||||
{
|
||||
ssize_t n;
|
||||
do {
|
||||
checkInterrupt();
|
||||
n = ::read(fd, (char *) 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"); }
|
||||
read += n;
|
||||
return n;
|
||||
}
|
||||
|
||||
|
||||
bool FdSource::good()
|
||||
{
|
||||
return _good;
|
||||
}
|
||||
|
||||
|
||||
size_t StringSource::read(unsigned char * data, size_t len)
|
||||
{
|
||||
if (pos == s.size()) throw EndOfFile("end of string reached");
|
||||
size_t n = s.copy((char *) data, len, pos);
|
||||
pos += n;
|
||||
return n;
|
||||
}
|
||||
|
||||
|
||||
#if BOOST_VERSION >= 106300 && BOOST_VERSION < 106600
|
||||
#error Coroutines are broken in this version of Boost!
|
||||
#endif
|
||||
|
||||
std::unique_ptr<Source> sinkToSource(
|
||||
std::function<void(Sink &)> fun,
|
||||
std::function<void()> eof)
|
||||
{
|
||||
struct SinkToSource : Source
|
||||
{
|
||||
typedef boost::coroutines2::coroutine<std::string> coro_t;
|
||||
|
||||
std::function<void(Sink &)> fun;
|
||||
std::function<void()> eof;
|
||||
std::optional<coro_t::pull_type> coro;
|
||||
bool started = false;
|
||||
|
||||
SinkToSource(std::function<void(Sink &)> fun, std::function<void()> eof)
|
||||
: fun(fun), eof(eof)
|
||||
{
|
||||
}
|
||||
|
||||
std::string cur;
|
||||
size_t pos = 0;
|
||||
|
||||
size_t read(unsigned 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));
|
||||
});
|
||||
fun(sink);
|
||||
});
|
||||
|
||||
if (!*coro) { eof(); abort(); }
|
||||
|
||||
if (pos == cur.size()) {
|
||||
if (!cur.empty()) (*coro)();
|
||||
cur = coro->get();
|
||||
pos = 0;
|
||||
}
|
||||
|
||||
auto n = std::min(cur.size() - pos, len);
|
||||
memcpy(data, (unsigned char *) cur.data() + pos, n);
|
||||
pos += n;
|
||||
|
||||
return n;
|
||||
}
|
||||
};
|
||||
|
||||
return std::make_unique<SinkToSource>(fun, eof);
|
||||
}
|
||||
|
||||
|
||||
void writePadding(size_t len, Sink & sink)
|
||||
{
|
||||
if (len % 8) {
|
||||
unsigned char zero[8];
|
||||
memset(zero, 0, sizeof(zero));
|
||||
sink(zero, 8 - (len % 8));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void writeString(const unsigned char * buf, size_t len, Sink & sink)
|
||||
{
|
||||
sink << len;
|
||||
sink(buf, len);
|
||||
writePadding(len, sink);
|
||||
}
|
||||
|
||||
|
||||
Sink & operator << (Sink & sink, const string & s)
|
||||
{
|
||||
writeString((const unsigned char *) s.data(), s.size(), sink);
|
||||
return sink;
|
||||
}
|
||||
|
||||
|
||||
template<class T> void writeStrings(const T & ss, Sink & sink)
|
||||
{
|
||||
sink << ss.size();
|
||||
for (auto & i : ss)
|
||||
sink << i;
|
||||
}
|
||||
|
||||
Sink & operator << (Sink & sink, const Strings & s)
|
||||
{
|
||||
writeStrings(s, sink);
|
||||
return sink;
|
||||
}
|
||||
|
||||
Sink & operator << (Sink & sink, const StringSet & s)
|
||||
{
|
||||
writeStrings(s, sink);
|
||||
return sink;
|
||||
}
|
||||
|
||||
|
||||
void readPadding(size_t len, Source & source)
|
||||
{
|
||||
if (len % 8) {
|
||||
unsigned char zero[8];
|
||||
size_t n = 8 - (len % 8);
|
||||
source(zero, n);
|
||||
for (unsigned int i = 0; i < n; i++)
|
||||
if (zero[i]) throw SerialisationError("non-zero padding");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
size_t readString(unsigned char * buf, size_t max, Source & source)
|
||||
{
|
||||
auto len = readNum<size_t>(source);
|
||||
if (len > max) throw SerialisationError("string is too long");
|
||||
source(buf, len);
|
||||
readPadding(len, source);
|
||||
return len;
|
||||
}
|
||||
|
||||
|
||||
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);
|
||||
readPadding(len, source);
|
||||
return res;
|
||||
}
|
||||
|
||||
Source & operator >> (Source & in, string & s)
|
||||
{
|
||||
s = readString(in);
|
||||
return in;
|
||||
}
|
||||
|
||||
|
||||
template<class T> T readStrings(Source & source)
|
||||
{
|
||||
auto count = readNum<size_t>(source);
|
||||
T ss;
|
||||
while (count--)
|
||||
ss.insert(ss.end(), readString(source));
|
||||
return ss;
|
||||
}
|
||||
|
||||
template Paths readStrings(Source & source);
|
||||
template PathSet readStrings(Source & source);
|
||||
|
||||
|
||||
void StringSink::operator () (const unsigned char * data, size_t len)
|
||||
{
|
||||
static bool warned = false;
|
||||
if (!warned && s->size() > threshold) {
|
||||
warnLargeDump();
|
||||
warned = true;
|
||||
}
|
||||
s->append((const char *) data, len);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
337
third_party/nix/src/libutil/serialise.hh
vendored
Normal file
337
third_party/nix/src/libutil/serialise.hh
vendored
Normal file
|
|
@ -0,0 +1,337 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "types.hh"
|
||||
#include "util.hh"
|
||||
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
||||
/* Abstract destination of binary data. */
|
||||
struct Sink
|
||||
{
|
||||
virtual ~Sink() { }
|
||||
virtual void operator () (const unsigned char * data, size_t len) = 0;
|
||||
virtual bool good() { return true; }
|
||||
|
||||
void operator () (const std::string & s)
|
||||
{
|
||||
(*this)((const unsigned char *) s.data(), s.size());
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/* A buffered abstract sink. */
|
||||
struct BufferedSink : Sink
|
||||
{
|
||||
size_t bufSize, bufPos;
|
||||
std::unique_ptr<unsigned 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 flush();
|
||||
|
||||
virtual void write(const unsigned char * data, size_t len) = 0;
|
||||
};
|
||||
|
||||
|
||||
/* Abstract source of binary data. */
|
||||
struct Source
|
||||
{
|
||||
virtual ~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);
|
||||
|
||||
/* 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 bool good() { return true; }
|
||||
|
||||
std::string drain();
|
||||
};
|
||||
|
||||
|
||||
/* A buffered abstract source. */
|
||||
struct BufferedSource : Source
|
||||
{
|
||||
size_t bufSize, bufPosIn, bufPosOut;
|
||||
std::unique_ptr<unsigned 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;
|
||||
|
||||
|
||||
bool hasData();
|
||||
|
||||
protected:
|
||||
/* Underlying read call, to be overridden. */
|
||||
virtual size_t readUnbuffered(unsigned char * data, size_t len) = 0;
|
||||
};
|
||||
|
||||
|
||||
/* A sink that writes data to a file descriptor. */
|
||||
struct FdSink : BufferedSink
|
||||
{
|
||||
int fd;
|
||||
bool warn = false;
|
||||
size_t written = 0;
|
||||
|
||||
FdSink() : fd(-1) { }
|
||||
FdSink(int fd) : fd(fd) { }
|
||||
FdSink(FdSink&&) = default;
|
||||
|
||||
FdSink& operator=(FdSink && s)
|
||||
{
|
||||
flush();
|
||||
fd = s.fd;
|
||||
s.fd = -1;
|
||||
warn = s.warn;
|
||||
written = s.written;
|
||||
return *this;
|
||||
}
|
||||
|
||||
~FdSink();
|
||||
|
||||
void write(const unsigned char * data, size_t len) override;
|
||||
|
||||
bool good() override;
|
||||
|
||||
private:
|
||||
bool _good = true;
|
||||
};
|
||||
|
||||
|
||||
/* A source that reads data from a file descriptor. */
|
||||
struct FdSource : BufferedSource
|
||||
{
|
||||
int fd;
|
||||
size_t read = 0;
|
||||
|
||||
FdSource() : fd(-1) { }
|
||||
FdSource(int fd) : fd(fd) { }
|
||||
FdSource(FdSource&&) = default;
|
||||
|
||||
FdSource& operator=(FdSource && s)
|
||||
{
|
||||
fd = s.fd;
|
||||
s.fd = -1;
|
||||
read = s.read;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool good() override;
|
||||
protected:
|
||||
size_t readUnbuffered(unsigned char * data, size_t len) override;
|
||||
private:
|
||||
bool _good = true;
|
||||
};
|
||||
|
||||
|
||||
/* A sink that writes data to a string. */
|
||||
struct StringSink : Sink
|
||||
{
|
||||
ref<std::string> s;
|
||||
StringSink() : s(make_ref<std::string>()) { };
|
||||
StringSink(ref<std::string> s) : s(s) { };
|
||||
void operator () (const unsigned char * data, size_t len) override;
|
||||
};
|
||||
|
||||
|
||||
/* A source that reads data from a string. */
|
||||
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;
|
||||
};
|
||||
|
||||
|
||||
/* Adapter class of a Source that saves all data read to `s'. */
|
||||
struct TeeSource : Source
|
||||
{
|
||||
Source & orig;
|
||||
ref<std::string> data;
|
||||
TeeSource(Source & orig)
|
||||
: orig(orig), data(make_ref<std::string>()) { }
|
||||
size_t read(unsigned char * data, size_t len)
|
||||
{
|
||||
size_t n = orig.read(data, len);
|
||||
this->data->append((const char *) data, n);
|
||||
return n;
|
||||
}
|
||||
};
|
||||
|
||||
/* A reader that consumes the original Source until 'size'. */
|
||||
struct SizedSource : Source
|
||||
{
|
||||
Source & orig;
|
||||
size_t remain;
|
||||
SizedSource(Source & orig, size_t size)
|
||||
: orig(orig), remain(size) { }
|
||||
size_t read(unsigned char * data, size_t len)
|
||||
{
|
||||
if (this->remain <= 0) {
|
||||
throw EndOfFile("sized: unexpected end-of-file");
|
||||
}
|
||||
len = std::min(len, this->remain);
|
||||
size_t n = this->orig.read(data, len);
|
||||
this->remain -= n;
|
||||
return n;
|
||||
}
|
||||
|
||||
/* Consume the original source until no remain data is left to consume. */
|
||||
size_t drainAll()
|
||||
{
|
||||
std::vector<unsigned char> buf(8192);
|
||||
size_t sum = 0;
|
||||
while (this->remain > 0) {
|
||||
size_t n = read(buf.data(), buf.size());
|
||||
sum += n;
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
};
|
||||
|
||||
/* Convert a function into a sink. */
|
||||
struct LambdaSink : Sink
|
||||
{
|
||||
typedef std::function<void(const unsigned char *, size_t)> lambda_t;
|
||||
|
||||
lambda_t lambda;
|
||||
|
||||
LambdaSink(const lambda_t & lambda) : lambda(lambda) { }
|
||||
|
||||
virtual void operator () (const unsigned char * data, size_t len)
|
||||
{
|
||||
lambda(data, len);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/* Convert a function into a source. */
|
||||
struct LambdaSource : Source
|
||||
{
|
||||
typedef std::function<size_t(unsigned 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
|
||||
{
|
||||
return lambda(data, len);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/* Convert a function that feeds data into a Sink into a Source. The
|
||||
Source executes the function as a coroutine. */
|
||||
std::unique_ptr<Source> sinkToSource(
|
||||
std::function<void(Sink &)> fun,
|
||||
std::function<void()> eof = []() {
|
||||
throw EndOfFile("coroutine has finished");
|
||||
});
|
||||
|
||||
|
||||
void writePadding(size_t len, Sink & sink);
|
||||
void writeString(const unsigned char * buf, size_t len, Sink & sink);
|
||||
|
||||
inline Sink & operator << (Sink & sink, uint64_t n)
|
||||
{
|
||||
unsigned char buf[8];
|
||||
buf[0] = n & 0xff;
|
||||
buf[1] = (n >> 8) & 0xff;
|
||||
buf[2] = (n >> 16) & 0xff;
|
||||
buf[3] = (n >> 24) & 0xff;
|
||||
buf[4] = (n >> 32) & 0xff;
|
||||
buf[5] = (n >> 40) & 0xff;
|
||||
buf[6] = (n >> 48) & 0xff;
|
||||
buf[7] = (unsigned char) (n >> 56) & 0xff;
|
||||
sink(buf, sizeof(buf));
|
||||
return sink;
|
||||
}
|
||||
|
||||
Sink & operator << (Sink & sink, const string & s);
|
||||
Sink & operator << (Sink & sink, const Strings & s);
|
||||
Sink & operator << (Sink & sink, const StringSet & s);
|
||||
|
||||
|
||||
MakeError(SerialisationError, Error)
|
||||
|
||||
|
||||
template<typename T>
|
||||
T readNum(Source & source)
|
||||
{
|
||||
unsigned char buf[8];
|
||||
source(buf, sizeof(buf));
|
||||
|
||||
uint64_t n =
|
||||
((unsigned long long) buf[0]) |
|
||||
((unsigned long long) buf[1] << 8) |
|
||||
((unsigned long long) buf[2] << 16) |
|
||||
((unsigned long long) buf[3] << 24) |
|
||||
((unsigned long long) buf[4] << 32) |
|
||||
((unsigned long long) buf[5] << 40) |
|
||||
((unsigned long long) buf[6] << 48) |
|
||||
((unsigned long long) buf[7] << 56);
|
||||
|
||||
if (n > std::numeric_limits<T>::max())
|
||||
throw SerialisationError("serialised integer %d is too large for type '%s'", n, typeid(T).name());
|
||||
|
||||
return (T) n;
|
||||
}
|
||||
|
||||
|
||||
inline unsigned int readInt(Source & source)
|
||||
{
|
||||
return readNum<unsigned int>(source);
|
||||
}
|
||||
|
||||
|
||||
inline uint64_t readLongLong(Source & source)
|
||||
{
|
||||
return readNum<uint64_t>(source);
|
||||
}
|
||||
|
||||
|
||||
void readPadding(size_t len, Source & source);
|
||||
size_t readString(unsigned 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);
|
||||
|
||||
Source & operator >> (Source & in, string & s);
|
||||
|
||||
template<typename T>
|
||||
Source & operator >> (Source & in, T & n)
|
||||
{
|
||||
n = readNum<T>(in);
|
||||
return in;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
Source & operator >> (Source & in, bool & b)
|
||||
{
|
||||
b = readNum<uint64_t>(in);
|
||||
return in;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
88
third_party/nix/src/libutil/sync.hh
vendored
Normal file
88
third_party/nix/src/libutil/sync.hh
vendored
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstdlib>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <cassert>
|
||||
|
||||
namespace nix {
|
||||
|
||||
/* This template class ensures synchronized access to a value of type
|
||||
T. It is used as follows:
|
||||
|
||||
struct Data { int x; ... };
|
||||
|
||||
Sync<Data> data;
|
||||
|
||||
{
|
||||
auto data_(data.lock());
|
||||
data_->x = 123;
|
||||
}
|
||||
|
||||
Here, "data" is automatically unlocked when "data_" goes out of
|
||||
scope.
|
||||
*/
|
||||
|
||||
template<class T, class M = std::mutex>
|
||||
class Sync
|
||||
{
|
||||
private:
|
||||
M mutex;
|
||||
T data;
|
||||
|
||||
public:
|
||||
|
||||
Sync() { }
|
||||
Sync(const T & data) : data(data) { }
|
||||
Sync(T && data) noexcept : data(std::move(data)) { }
|
||||
|
||||
class Lock
|
||||
{
|
||||
private:
|
||||
Sync * s;
|
||||
std::unique_lock<M> lk;
|
||||
friend Sync;
|
||||
Lock(Sync * s) : s(s), lk(s->mutex) { }
|
||||
public:
|
||||
Lock(Lock && l) : s(l.s) { abort(); }
|
||||
Lock(const Lock & l) = delete;
|
||||
~Lock() { }
|
||||
T * operator -> () { return &s->data; }
|
||||
T & operator * () { return s->data; }
|
||||
|
||||
void wait(std::condition_variable & cv)
|
||||
{
|
||||
assert(s);
|
||||
cv.wait(lk);
|
||||
}
|
||||
|
||||
template<class Rep, class Period>
|
||||
std::cv_status wait_for(std::condition_variable & cv,
|
||||
const std::chrono::duration<Rep, Period> & duration)
|
||||
{
|
||||
assert(s);
|
||||
return cv.wait_for(lk, duration);
|
||||
}
|
||||
|
||||
template<class Rep, class Period, class Predicate>
|
||||
bool wait_for(std::condition_variable & cv,
|
||||
const std::chrono::duration<Rep, Period> & duration,
|
||||
Predicate pred)
|
||||
{
|
||||
assert(s);
|
||||
return cv.wait_for(lk, duration, pred);
|
||||
}
|
||||
|
||||
template<class Clock, class Duration>
|
||||
std::cv_status wait_until(std::condition_variable & cv,
|
||||
const std::chrono::time_point<Clock, Duration> & duration)
|
||||
{
|
||||
assert(s);
|
||||
return cv.wait_until(lk, duration);
|
||||
}
|
||||
};
|
||||
|
||||
Lock lock() { return Lock(this); }
|
||||
};
|
||||
|
||||
}
|
||||
156
third_party/nix/src/libutil/thread-pool.cc
vendored
Normal file
156
third_party/nix/src/libutil/thread-pool.cc
vendored
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
#include "thread-pool.hh"
|
||||
#include "affinity.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
ThreadPool::ThreadPool(size_t _maxThreads)
|
||||
: maxThreads(_maxThreads)
|
||||
{
|
||||
restoreAffinity(); // FIXME
|
||||
|
||||
if (!maxThreads) {
|
||||
maxThreads = std::thread::hardware_concurrency();
|
||||
if (!maxThreads) maxThreads = 1;
|
||||
}
|
||||
|
||||
debug("starting pool of %d threads", maxThreads - 1);
|
||||
}
|
||||
|
||||
ThreadPool::~ThreadPool()
|
||||
{
|
||||
shutdown();
|
||||
}
|
||||
|
||||
void ThreadPool::shutdown()
|
||||
{
|
||||
std::vector<std::thread> workers;
|
||||
{
|
||||
auto state(state_.lock());
|
||||
quit = true;
|
||||
std::swap(workers, state->workers);
|
||||
}
|
||||
|
||||
if (workers.empty()) return;
|
||||
|
||||
debug("reaping %d worker threads", workers.size());
|
||||
|
||||
work.notify_all();
|
||||
|
||||
for (auto & thr : workers)
|
||||
thr.join();
|
||||
}
|
||||
|
||||
void ThreadPool::enqueue(const work_t & t)
|
||||
{
|
||||
auto state(state_.lock());
|
||||
if (quit)
|
||||
throw ThreadPoolShutDown("cannot enqueue a work item while the thread pool is shutting down");
|
||||
state->pending.push(t);
|
||||
/* Note: process() also executes items, so count it as a worker. */
|
||||
if (state->pending.size() > state->workers.size() + 1 && state->workers.size() + 1 < maxThreads)
|
||||
state->workers.emplace_back(&ThreadPool::doWork, this, false);
|
||||
work.notify_one();
|
||||
}
|
||||
|
||||
void ThreadPool::process()
|
||||
{
|
||||
state_.lock()->draining = true;
|
||||
|
||||
/* Do work until no more work is pending or active. */
|
||||
try {
|
||||
doWork(true);
|
||||
|
||||
auto state(state_.lock());
|
||||
|
||||
assert(quit);
|
||||
|
||||
if (state->exception)
|
||||
std::rethrow_exception(state->exception);
|
||||
|
||||
} catch (...) {
|
||||
/* In the exceptional case, some workers may still be
|
||||
active. They may be referencing the stack frame of the
|
||||
caller. So wait for them to finish. (~ThreadPool also does
|
||||
this, but it might be destroyed after objects referenced by
|
||||
the work item lambdas.) */
|
||||
shutdown();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void ThreadPool::doWork(bool mainThread)
|
||||
{
|
||||
if (!mainThread)
|
||||
interruptCheck = [&]() { return (bool) quit; };
|
||||
|
||||
bool didWork = false;
|
||||
std::exception_ptr exc;
|
||||
|
||||
while (true) {
|
||||
work_t w;
|
||||
{
|
||||
auto state(state_.lock());
|
||||
|
||||
if (didWork) {
|
||||
assert(state->active);
|
||||
state->active--;
|
||||
|
||||
if (exc) {
|
||||
|
||||
if (!state->exception) {
|
||||
state->exception = exc;
|
||||
// Tell the other workers to quit.
|
||||
quit = true;
|
||||
work.notify_all();
|
||||
} else {
|
||||
/* Print the exception, since we can't
|
||||
propagate it. */
|
||||
try {
|
||||
std::rethrow_exception(exc);
|
||||
} catch (std::exception & e) {
|
||||
if (!dynamic_cast<Interrupted*>(&e) &&
|
||||
!dynamic_cast<ThreadPoolShutDown*>(&e))
|
||||
ignoreException();
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Wait until a work item is available or we're asked to
|
||||
quit. */
|
||||
while (true) {
|
||||
if (quit) return;
|
||||
|
||||
if (!state->pending.empty()) break;
|
||||
|
||||
/* If there are no active or pending items, and the
|
||||
main thread is running process(), then no new items
|
||||
can be added. So exit. */
|
||||
if (!state->active && state->draining) {
|
||||
quit = true;
|
||||
work.notify_all();
|
||||
return;
|
||||
}
|
||||
|
||||
state.wait(work);
|
||||
}
|
||||
|
||||
w = std::move(state->pending.front());
|
||||
state->pending.pop();
|
||||
state->active++;
|
||||
}
|
||||
|
||||
try {
|
||||
w();
|
||||
} catch (...) {
|
||||
exc = std::current_exception();
|
||||
}
|
||||
|
||||
didWork = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
143
third_party/nix/src/libutil/thread-pool.hh
vendored
Normal file
143
third_party/nix/src/libutil/thread-pool.hh
vendored
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
#pragma once
|
||||
|
||||
#include "sync.hh"
|
||||
#include "util.hh"
|
||||
|
||||
#include <queue>
|
||||
#include <functional>
|
||||
#include <thread>
|
||||
#include <map>
|
||||
#include <atomic>
|
||||
|
||||
namespace nix {
|
||||
|
||||
MakeError(ThreadPoolShutDown, Error)
|
||||
|
||||
/* A simple thread pool that executes a queue of work items
|
||||
(lambdas). */
|
||||
class ThreadPool
|
||||
{
|
||||
public:
|
||||
|
||||
ThreadPool(size_t maxThreads = 0);
|
||||
|
||||
~ThreadPool();
|
||||
|
||||
// FIXME: use std::packaged_task?
|
||||
typedef std::function<void()> work_t;
|
||||
|
||||
/* Enqueue a function to be executed by the thread pool. */
|
||||
void enqueue(const work_t & t);
|
||||
|
||||
/* Execute work items until the queue is empty. Note that work
|
||||
items are allowed to add new items to the queue; this is
|
||||
handled correctly. Queue processing stops prematurely if any
|
||||
work item throws an exception. This exception is propagated to
|
||||
the calling thread. If multiple work items throw an exception
|
||||
concurrently, only one item is propagated; the others are
|
||||
printed on stderr and otherwise ignored. */
|
||||
void process();
|
||||
|
||||
private:
|
||||
|
||||
size_t maxThreads;
|
||||
|
||||
struct State
|
||||
{
|
||||
std::queue<work_t> pending;
|
||||
size_t active = 0;
|
||||
std::exception_ptr exception;
|
||||
std::vector<std::thread> workers;
|
||||
bool draining = false;
|
||||
};
|
||||
|
||||
std::atomic_bool quit{false};
|
||||
|
||||
Sync<State> state_;
|
||||
|
||||
std::condition_variable work;
|
||||
|
||||
void doWork(bool mainThread);
|
||||
|
||||
void shutdown();
|
||||
};
|
||||
|
||||
/* Process in parallel a set of items of type T that have a partial
|
||||
ordering between them. Thus, any item is only processed after all
|
||||
its dependencies have been processed. */
|
||||
template<typename T>
|
||||
void processGraph(
|
||||
ThreadPool & pool,
|
||||
const std::set<T> & nodes,
|
||||
std::function<std::set<T>(const T &)> getEdges,
|
||||
std::function<void(const T &)> processNode)
|
||||
{
|
||||
struct Graph {
|
||||
std::set<T> left;
|
||||
std::map<T, std::set<T>> refs, rrefs;
|
||||
};
|
||||
|
||||
Sync<Graph> graph_(Graph{nodes, {}, {}});
|
||||
|
||||
std::function<void(const T &)> worker;
|
||||
|
||||
worker = [&](const T & node) {
|
||||
|
||||
{
|
||||
auto graph(graph_.lock());
|
||||
auto i = graph->refs.find(node);
|
||||
if (i == graph->refs.end())
|
||||
goto getRefs;
|
||||
goto doWork;
|
||||
}
|
||||
|
||||
getRefs:
|
||||
{
|
||||
auto refs = getEdges(node);
|
||||
refs.erase(node);
|
||||
|
||||
{
|
||||
auto graph(graph_.lock());
|
||||
for (auto & ref : refs)
|
||||
if (graph->left.count(ref)) {
|
||||
graph->refs[node].insert(ref);
|
||||
graph->rrefs[ref].insert(node);
|
||||
}
|
||||
if (graph->refs[node].empty())
|
||||
goto doWork;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
doWork:
|
||||
processNode(node);
|
||||
|
||||
/* Enqueue work for all nodes that were waiting on this one
|
||||
and have no unprocessed dependencies. */
|
||||
{
|
||||
auto graph(graph_.lock());
|
||||
for (auto & rref : graph->rrefs[node]) {
|
||||
auto & refs(graph->refs[rref]);
|
||||
auto i = refs.find(node);
|
||||
assert(i != refs.end());
|
||||
refs.erase(i);
|
||||
if (refs.empty())
|
||||
pool.enqueue(std::bind(worker, rref));
|
||||
}
|
||||
graph->left.erase(node);
|
||||
graph->refs.erase(node);
|
||||
graph->rrefs.erase(node);
|
||||
}
|
||||
};
|
||||
|
||||
for (auto & node : nodes)
|
||||
pool.enqueue(std::bind(worker, std::ref(node)));
|
||||
|
||||
pool.process();
|
||||
|
||||
if (!graph_.lock()->left.empty())
|
||||
throw Error("graph processing incomplete (cyclic reference?)");
|
||||
}
|
||||
|
||||
}
|
||||
150
third_party/nix/src/libutil/types.hh
vendored
Normal file
150
third_party/nix/src/libutil/types.hh
vendored
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
#pragma once
|
||||
|
||||
|
||||
#include "ref.hh"
|
||||
|
||||
#include <string>
|
||||
#include <list>
|
||||
#include <set>
|
||||
#include <memory>
|
||||
#include <map>
|
||||
|
||||
#include <boost/format.hpp>
|
||||
|
||||
/* Before 4.7, gcc's std::exception uses empty throw() specifiers for
|
||||
* its (virtual) destructor and what() in c++11 mode, in violation of spec
|
||||
*/
|
||||
#ifdef __GNUC__
|
||||
#if __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 7)
|
||||
#define EXCEPTION_NEEDS_THROW_SPEC
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
||||
/* Inherit some names from other namespaces for convenience. */
|
||||
using std::string;
|
||||
using std::list;
|
||||
using std::set;
|
||||
using std::vector;
|
||||
using boost::format;
|
||||
|
||||
|
||||
/* A variadic template that does nothing. Useful to call a function
|
||||
for all variadic arguments but ignoring the result. */
|
||||
struct nop { template<typename... T> nop(T...) {} };
|
||||
|
||||
|
||||
struct FormatOrString
|
||||
{
|
||||
string s;
|
||||
FormatOrString(const string & s) : s(s) { };
|
||||
FormatOrString(const format & f) : s(f.str()) { };
|
||||
FormatOrString(const char * s) : s(s) { };
|
||||
};
|
||||
|
||||
|
||||
/* A helper for formatting strings. ‘fmt(format, a_0, ..., a_n)’ is
|
||||
equivalent to ‘boost::format(format) % a_0 % ... %
|
||||
... a_n’. However, ‘fmt(s)’ is equivalent to ‘s’ (so no %-expansion
|
||||
takes place). */
|
||||
|
||||
inline std::string fmt(const std::string & s)
|
||||
{
|
||||
return s;
|
||||
}
|
||||
|
||||
inline std::string fmt(const char * s)
|
||||
{
|
||||
return s;
|
||||
}
|
||||
|
||||
inline std::string fmt(const FormatOrString & fs)
|
||||
{
|
||||
return fs.s;
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
inline std::string fmt(const std::string & fs, Args... args)
|
||||
{
|
||||
boost::format f(fs);
|
||||
f.exceptions(boost::io::all_error_bits ^ boost::io::too_many_args_bit);
|
||||
nop{boost::io::detail::feed(f, args)...};
|
||||
return f.str();
|
||||
}
|
||||
|
||||
|
||||
/* BaseError should generally not be caught, as it has Interrupted as
|
||||
a subclass. Catch Error instead. */
|
||||
class BaseError : public std::exception
|
||||
{
|
||||
protected:
|
||||
string prefix_; // used for location traces etc.
|
||||
string err;
|
||||
public:
|
||||
unsigned int status = 1; // exit status
|
||||
|
||||
template<typename... Args>
|
||||
BaseError(unsigned int status, Args... args)
|
||||
: err(fmt(args...))
|
||||
, status(status)
|
||||
{
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
BaseError(Args... args)
|
||||
: err(fmt(args...))
|
||||
{
|
||||
}
|
||||
|
||||
#ifdef EXCEPTION_NEEDS_THROW_SPEC
|
||||
~BaseError() throw () { };
|
||||
const char * what() const throw () { return err.c_str(); }
|
||||
#else
|
||||
const char * what() const noexcept { return err.c_str(); }
|
||||
#endif
|
||||
|
||||
const string & msg() const { return err; }
|
||||
const string & prefix() const { return prefix_; }
|
||||
BaseError & addPrefix(const FormatOrString & fs);
|
||||
};
|
||||
|
||||
#define MakeError(newClass, superClass) \
|
||||
class newClass : public superClass \
|
||||
{ \
|
||||
public: \
|
||||
using superClass::superClass; \
|
||||
};
|
||||
|
||||
MakeError(Error, BaseError)
|
||||
|
||||
class SysError : public Error
|
||||
{
|
||||
public:
|
||||
int errNo;
|
||||
|
||||
template<typename... Args>
|
||||
SysError(Args... args)
|
||||
: Error(addErrno(fmt(args...)))
|
||||
{ }
|
||||
|
||||
private:
|
||||
|
||||
std::string addErrno(const std::string & s);
|
||||
};
|
||||
|
||||
|
||||
typedef list<string> Strings;
|
||||
typedef set<string> StringSet;
|
||||
typedef std::map<std::string, std::string> StringMap;
|
||||
|
||||
|
||||
/* Paths are just strings. */
|
||||
typedef string Path;
|
||||
typedef list<Path> Paths;
|
||||
typedef set<Path> PathSet;
|
||||
|
||||
|
||||
}
|
||||
1552
third_party/nix/src/libutil/util.cc
vendored
Normal file
1552
third_party/nix/src/libutil/util.cc
vendored
Normal file
File diff suppressed because it is too large
Load diff
542
third_party/nix/src/libutil/util.hh
vendored
Normal file
542
third_party/nix/src/libutil/util.hh
vendored
Normal file
|
|
@ -0,0 +1,542 @@
|
|||
#pragma once
|
||||
|
||||
#include "types.hh"
|
||||
#include "logging.hh"
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <dirent.h>
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <cstdio>
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
#include <optional>
|
||||
#include <future>
|
||||
|
||||
#ifndef HAVE_STRUCT_DIRENT_D_TYPE
|
||||
#define DT_UNKNOWN 0
|
||||
#define DT_REG 1
|
||||
#define DT_LNK 2
|
||||
#define DT_DIR 3
|
||||
#endif
|
||||
|
||||
namespace nix {
|
||||
|
||||
struct Sink;
|
||||
struct Source;
|
||||
|
||||
|
||||
/* The system for which Nix is compiled. */
|
||||
extern const std::string nativeSystem;
|
||||
|
||||
|
||||
/* Return an environment variable. */
|
||||
string getEnv(const string & key, const string & def = "");
|
||||
|
||||
/* Get the entire environment. */
|
||||
std::map<std::string, std::string> getEnv();
|
||||
|
||||
/* Clear the environment. */
|
||||
void clearEnv();
|
||||
|
||||
/* Return an absolutized path, resolving paths relative to the
|
||||
specified directory, or the current directory otherwise. The path
|
||||
is also canonicalised. */
|
||||
Path absPath(Path path, Path dir = "");
|
||||
|
||||
/* Canonicalise a path by removing all `.' or `..' components and
|
||||
double or trailing slashes. Optionally resolves all symlink
|
||||
components such that each component of the resulting path is *not*
|
||||
a symbolic link. */
|
||||
Path canonPath(const Path & path, bool resolveSymlinks = false);
|
||||
|
||||
/* Return the directory part of the given canonical path, i.e.,
|
||||
everything before the final `/'. If the path is the root or an
|
||||
immediate child thereof (e.g., `/foo'), this means an empty string
|
||||
is returned. */
|
||||
Path dirOf(const Path & path);
|
||||
|
||||
/* Return the base name of the given canonical path, i.e., everything
|
||||
following the final `/'. */
|
||||
string baseNameOf(const Path & path);
|
||||
|
||||
/* Check whether 'path' is a descendant of 'dir'. */
|
||||
bool isInDir(const Path & path, const Path & dir);
|
||||
|
||||
/* Check whether 'path' is equal to 'dir' or a descendant of 'dir'. */
|
||||
bool isDirOrInDir(const Path & path, const Path & dir);
|
||||
|
||||
/* Get status of `path'. */
|
||||
struct stat lstat(const Path & path);
|
||||
|
||||
/* Return true iff the given path exists. */
|
||||
bool pathExists(const Path & path);
|
||||
|
||||
/* Read the contents (target) of a symbolic link. The result is not
|
||||
in any way canonicalised. */
|
||||
Path readLink(const Path & path);
|
||||
|
||||
bool isLink(const Path & path);
|
||||
|
||||
/* Read the contents of a directory. The entries `.' and `..' are
|
||||
removed. */
|
||||
struct DirEntry
|
||||
{
|
||||
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) { }
|
||||
};
|
||||
|
||||
typedef 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, bool drain = false);
|
||||
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, Source & source, mode_t mode = 0666);
|
||||
|
||||
/* Read a line from a file descriptor. */
|
||||
string readLine(int fd);
|
||||
|
||||
/* Write a line to a file descriptor. */
|
||||
void writeLine(int fd, 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
|
||||
second variant returns the number of bytes and blocks freed. */
|
||||
void deletePath(const Path & path);
|
||||
|
||||
void deletePath(const Path & path, unsigned long long & bytesFreed);
|
||||
|
||||
/* Create a temporary directory. */
|
||||
Path createTempDir(const Path & tmpRoot = "", const Path & prefix = "nix",
|
||||
bool includePid = true, bool useGlobalCounter = true, mode_t mode = 0755);
|
||||
|
||||
std::string getUserName();
|
||||
|
||||
/* Return $HOME or the user's home directory from /etc/passwd. */
|
||||
Path getHome();
|
||||
|
||||
/* Return $XDG_CACHE_HOME or $HOME/.cache. */
|
||||
Path getCacheDir();
|
||||
|
||||
/* Return $XDG_CONFIG_HOME or $HOME/.config. */
|
||||
Path getConfigDir();
|
||||
|
||||
/* Return the directories to search for user configuration files */
|
||||
std::vector<Path> getConfigDirs();
|
||||
|
||||
/* Return $XDG_DATA_HOME or $HOME/.local/share. */
|
||||
Path getDataDir();
|
||||
|
||||
/* Create a directory and all its parents, if necessary. Returns the
|
||||
list of created directories, in order of creation. */
|
||||
Paths createDirs(const Path & path);
|
||||
|
||||
/* Create a symlink. */
|
||||
void createSymlink(const Path & target, const Path & link);
|
||||
|
||||
/* Atomically create or replace a symlink. */
|
||||
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);
|
||||
|
||||
MakeError(EndOfFile, Error)
|
||||
|
||||
|
||||
/* Read a file descriptor until EOF occurs. */
|
||||
string drainFD(int fd, bool block = true);
|
||||
|
||||
void drainFD(int fd, Sink & sink, bool block = true);
|
||||
|
||||
|
||||
/* Automatic cleanup of resources. */
|
||||
|
||||
|
||||
class AutoDelete
|
||||
{
|
||||
Path path;
|
||||
bool del;
|
||||
bool recursive;
|
||||
public:
|
||||
AutoDelete();
|
||||
AutoDelete(const Path & p, bool recursive = true);
|
||||
~AutoDelete();
|
||||
void cancel();
|
||||
void reset(const Path & p, bool recursive = true);
|
||||
operator Path() const { return path; }
|
||||
};
|
||||
|
||||
|
||||
class AutoCloseFD
|
||||
{
|
||||
int fd;
|
||||
void close();
|
||||
public:
|
||||
AutoCloseFD();
|
||||
AutoCloseFD(int fd);
|
||||
AutoCloseFD(const AutoCloseFD & fd) = delete;
|
||||
AutoCloseFD(AutoCloseFD&& fd);
|
||||
~AutoCloseFD();
|
||||
AutoCloseFD& operator =(const AutoCloseFD & fd) = delete;
|
||||
AutoCloseFD& operator =(AutoCloseFD&& fd);
|
||||
int get() const;
|
||||
explicit operator bool() const;
|
||||
int release();
|
||||
};
|
||||
|
||||
|
||||
class Pipe
|
||||
{
|
||||
public:
|
||||
AutoCloseFD readSide, writeSide;
|
||||
void create();
|
||||
};
|
||||
|
||||
|
||||
struct DIRDeleter
|
||||
{
|
||||
void operator()(DIR * dir) const {
|
||||
closedir(dir);
|
||||
}
|
||||
};
|
||||
|
||||
typedef std::unique_ptr<DIR, DIRDeleter> AutoCloseDir;
|
||||
|
||||
|
||||
class Pid
|
||||
{
|
||||
pid_t pid = -1;
|
||||
bool separatePG = false;
|
||||
int killSignal = SIGKILL;
|
||||
public:
|
||||
Pid();
|
||||
Pid(pid_t pid);
|
||||
~Pid();
|
||||
void operator =(pid_t pid);
|
||||
operator pid_t();
|
||||
int kill();
|
||||
int wait();
|
||||
|
||||
void setSeparatePG(bool separatePG);
|
||||
void setKillSignal(int signal);
|
||||
pid_t release();
|
||||
};
|
||||
|
||||
|
||||
/* Kill all processes running under the specified uid by sending them
|
||||
a SIGKILL. */
|
||||
void killUser(uid_t uid);
|
||||
|
||||
|
||||
/* Fork a process that runs the given function, and return the child
|
||||
pid to the caller. */
|
||||
struct ProcessOptions
|
||||
{
|
||||
string errorPrefix = "error: ";
|
||||
bool dieWithParent = true;
|
||||
bool runExitHandlers = false;
|
||||
bool allowVfork = true;
|
||||
};
|
||||
|
||||
pid_t startProcess(std::function<void()> fun, const ProcessOptions & options = ProcessOptions());
|
||||
|
||||
|
||||
/* Run a program and return its stdout in a string (i.e., like the
|
||||
shell backtick operator). */
|
||||
string runProgram(Path program, bool searchPath = false,
|
||||
const Strings & args = Strings(),
|
||||
const std::optional<std::string> & input = {});
|
||||
|
||||
struct RunOptions
|
||||
{
|
||||
std::optional<uid_t> uid;
|
||||
std::optional<uid_t> gid;
|
||||
std::optional<Path> chdir;
|
||||
std::optional<std::map<std::string, std::string>> environment;
|
||||
Path program;
|
||||
bool searchPath = true;
|
||||
Strings args;
|
||||
std::optional<std::string> input;
|
||||
Source * standardIn = nullptr;
|
||||
Sink * standardOut = nullptr;
|
||||
bool mergeStderrToStdout = false;
|
||||
bool _killStderr = false;
|
||||
|
||||
RunOptions(const Path & program, const Strings & args)
|
||||
: program(program), args(args) { };
|
||||
|
||||
RunOptions & killStderr(bool v) { _killStderr = true; return *this; }
|
||||
};
|
||||
|
||||
std::pair<int, std::string> runProgram(const RunOptions & options);
|
||||
|
||||
void runProgram2(const RunOptions & options);
|
||||
|
||||
|
||||
class ExecError : public Error
|
||||
{
|
||||
public:
|
||||
int status;
|
||||
|
||||
template<typename... Args>
|
||||
ExecError(int status, Args... args)
|
||||
: Error(args...), status(status)
|
||||
{ }
|
||||
};
|
||||
|
||||
/* Convert a list of strings to a null-terminated vector of char
|
||||
*'s. The result must not be accessed beyond the lifetime of the
|
||||
list of strings. */
|
||||
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);
|
||||
|
||||
/* Set the close-on-exec flag for the given file descriptor. */
|
||||
void closeOnExec(int fd);
|
||||
|
||||
|
||||
/* User interruption. */
|
||||
|
||||
extern bool _isInterrupted;
|
||||
|
||||
extern thread_local std::function<bool()> interruptCheck;
|
||||
|
||||
void setInterruptThrown();
|
||||
|
||||
void _interrupted();
|
||||
|
||||
void inline checkInterrupt()
|
||||
{
|
||||
if (_isInterrupted || (interruptCheck && interruptCheck()))
|
||||
_interrupted();
|
||||
}
|
||||
|
||||
MakeError(Interrupted, BaseError)
|
||||
|
||||
|
||||
MakeError(FormatError, Error)
|
||||
|
||||
|
||||
/* String tokenizer. */
|
||||
template<class C> C tokenizeString(const string & s, const string & separators = " \t\n\r");
|
||||
|
||||
|
||||
/* Concatenate the given strings with a separator between the
|
||||
elements. */
|
||||
string concatStringsSep(const string & sep, const Strings & ss);
|
||||
string concatStringsSep(const string & sep, const StringSet & ss);
|
||||
|
||||
|
||||
/* Remove trailing whitespace from a string. */
|
||||
string chomp(const string & s);
|
||||
|
||||
|
||||
/* Remove whitespace from the start and end of a string. */
|
||||
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,
|
||||
const std::string & from, const std::string & to);
|
||||
|
||||
|
||||
/* Convert the exit status of a child as returned by wait() into an
|
||||
error string. */
|
||||
string statusToString(int status);
|
||||
|
||||
bool statusOk(int status);
|
||||
|
||||
|
||||
/* Parse a string into an integer. */
|
||||
template<class N> bool string2Int(const string & s, N & n)
|
||||
{
|
||||
if (string(s, 0, 1) == "-" && !std::numeric_limits<N>::is_signed)
|
||||
return false;
|
||||
std::istringstream str(s);
|
||||
str >> n;
|
||||
return str && str.get() == EOF;
|
||||
}
|
||||
|
||||
/* Parse a string into a float. */
|
||||
template<class N> bool string2Float(const string & s, N & n)
|
||||
{
|
||||
std::istringstream str(s);
|
||||
str >> n;
|
||||
return str && str.get() == EOF;
|
||||
}
|
||||
|
||||
|
||||
/* Return true iff `s' starts with `prefix'. */
|
||||
bool hasPrefix(const string & s, const string & prefix);
|
||||
|
||||
|
||||
/* Return true iff `s' ends in `suffix'. */
|
||||
bool hasSuffix(const string & s, const string & suffix);
|
||||
|
||||
|
||||
/* Convert a string to lower case. */
|
||||
std::string toLower(const std::string & s);
|
||||
|
||||
|
||||
/* Escape a string as a shell word. */
|
||||
std::string shellEscape(const std::string & s);
|
||||
|
||||
|
||||
/* Exception handling in destructors: print an error message, then
|
||||
ignore the exception. */
|
||||
void ignoreException();
|
||||
|
||||
|
||||
/* Some ANSI escape sequences. */
|
||||
#define ANSI_NORMAL "\e[0m"
|
||||
#define ANSI_BOLD "\e[1m"
|
||||
#define ANSI_FAINT "\e[2m"
|
||||
#define ANSI_RED "\e[31;1m"
|
||||
#define ANSI_GREEN "\e[32;1m"
|
||||
#define ANSI_BLUE "\e[34;1m"
|
||||
|
||||
|
||||
/* Truncate a string to 'width' printable characters. If 'filterAll'
|
||||
is true, all ANSI escape sequences are filtered out. Otherwise,
|
||||
some escape sequences (such as colour setting) are copied but not
|
||||
included in the character count. Also, tabs are expanded to
|
||||
spaces. */
|
||||
std::string filterANSIEscapes(const std::string & s,
|
||||
bool filterAll = false,
|
||||
unsigned int width = std::numeric_limits<unsigned int>::max());
|
||||
|
||||
|
||||
/* Base64 encoding/decoding. */
|
||||
string base64Encode(const string & s);
|
||||
string base64Decode(const string & s);
|
||||
|
||||
|
||||
/* Get a value for the specified key from an associate container, or a
|
||||
default value if the key doesn't exist. */
|
||||
template <class T>
|
||||
string get(const T & map, const string & key, const string & def = "")
|
||||
{
|
||||
auto i = map.find(key);
|
||||
return i == map.end() ? def : i->second;
|
||||
}
|
||||
|
||||
|
||||
/* A callback is a wrapper around a lambda that accepts a valid of
|
||||
type T or an exception. (We abuse std::future<T> to pass the value or
|
||||
exception.) */
|
||||
template<typename T>
|
||||
class Callback
|
||||
{
|
||||
std::function<void(std::future<T>)> fun;
|
||||
std::atomic_flag done = ATOMIC_FLAG_INIT;
|
||||
|
||||
public:
|
||||
|
||||
Callback(std::function<void(std::future<T>)> fun) : fun(fun) { }
|
||||
|
||||
Callback(Callback && callback) : fun(std::move(callback.fun))
|
||||
{
|
||||
auto prev = callback.done.test_and_set();
|
||||
if (prev) done.test_and_set();
|
||||
}
|
||||
|
||||
void operator()(T && t) noexcept
|
||||
{
|
||||
auto prev = done.test_and_set();
|
||||
assert(!prev);
|
||||
std::promise<T> promise;
|
||||
promise.set_value(std::move(t));
|
||||
fun(promise.get_future());
|
||||
}
|
||||
|
||||
void rethrow(const std::exception_ptr & exc = std::current_exception()) noexcept
|
||||
{
|
||||
auto prev = done.test_and_set();
|
||||
assert(!prev);
|
||||
std::promise<T> promise;
|
||||
promise.set_exception(exc);
|
||||
fun(promise.get_future());
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/* Start a thread that handles various signals. Also block those signals
|
||||
on the current thread (and thus any threads created by it). */
|
||||
void startSignalHandlerThread();
|
||||
|
||||
/* Restore default signal handling. */
|
||||
void restoreSignals();
|
||||
|
||||
struct InterruptCallback
|
||||
{
|
||||
virtual ~InterruptCallback() { };
|
||||
};
|
||||
|
||||
/* Register a function that gets called on SIGINT (in a non-signal
|
||||
context). */
|
||||
std::unique_ptr<InterruptCallback> createInterruptCallback(
|
||||
std::function<void()> callback);
|
||||
|
||||
void triggerInterrupt();
|
||||
|
||||
/* A RAII class that causes the current thread to receive SIGUSR1 when
|
||||
the signal handler thread receives SIGINT. That is, this allows
|
||||
SIGINT to be multiplexed to multiple threads. */
|
||||
struct ReceiveInterrupts
|
||||
{
|
||||
pthread_t target;
|
||||
std::unique_ptr<InterruptCallback> callback;
|
||||
|
||||
ReceiveInterrupts()
|
||||
: target(pthread_self())
|
||||
, callback(createInterruptCallback([&]() { pthread_kill(target, SIGUSR1); }))
|
||||
{ }
|
||||
};
|
||||
|
||||
|
||||
|
||||
/* A RAII helper that increments a counter on construction and
|
||||
decrements it on destruction. */
|
||||
template<typename T>
|
||||
struct MaintainCount
|
||||
{
|
||||
T & counter;
|
||||
long delta;
|
||||
MaintainCount(T & counter, long delta = 1) : counter(counter), delta(delta) { counter += delta; }
|
||||
~MaintainCount() { counter -= delta; }
|
||||
};
|
||||
|
||||
|
||||
/* Return the number of rows and columns of the terminal. */
|
||||
std::pair<unsigned short, unsigned short> getWindowSize();
|
||||
|
||||
|
||||
/* Used in various places. */
|
||||
typedef std::function<bool(const Path & path)> PathFilter;
|
||||
|
||||
extern PathFilter defaultPathFilter;
|
||||
|
||||
|
||||
}
|
||||
94
third_party/nix/src/libutil/xml-writer.cc
vendored
Normal file
94
third_party/nix/src/libutil/xml-writer.cc
vendored
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
#include <assert.h>
|
||||
|
||||
#include "xml-writer.hh"
|
||||
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
||||
XMLWriter::XMLWriter(bool indent, std::ostream & output)
|
||||
: output(output), indent(indent)
|
||||
{
|
||||
output << "<?xml version='1.0' encoding='utf-8'?>" << std::endl;
|
||||
closed = false;
|
||||
}
|
||||
|
||||
|
||||
XMLWriter::~XMLWriter()
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
||||
|
||||
void XMLWriter::close()
|
||||
{
|
||||
if (closed) return;
|
||||
while (!pendingElems.empty()) closeElement();
|
||||
closed = true;
|
||||
}
|
||||
|
||||
|
||||
void XMLWriter::indent_(size_t depth)
|
||||
{
|
||||
if (!indent) return;
|
||||
output << string(depth * 2, ' ');
|
||||
}
|
||||
|
||||
|
||||
void XMLWriter::openElement(const string & name,
|
||||
const XMLAttrs & attrs)
|
||||
{
|
||||
assert(!closed);
|
||||
indent_(pendingElems.size());
|
||||
output << "<" << name;
|
||||
writeAttrs(attrs);
|
||||
output << ">";
|
||||
if (indent) output << std::endl;
|
||||
pendingElems.push_back(name);
|
||||
}
|
||||
|
||||
|
||||
void XMLWriter::closeElement()
|
||||
{
|
||||
assert(!pendingElems.empty());
|
||||
indent_(pendingElems.size() - 1);
|
||||
output << "</" << pendingElems.back() << ">";
|
||||
if (indent) output << std::endl;
|
||||
pendingElems.pop_back();
|
||||
if (pendingElems.empty()) closed = true;
|
||||
}
|
||||
|
||||
|
||||
void XMLWriter::writeEmptyElement(const string & name,
|
||||
const XMLAttrs & attrs)
|
||||
{
|
||||
assert(!closed);
|
||||
indent_(pendingElems.size());
|
||||
output << "<" << name;
|
||||
writeAttrs(attrs);
|
||||
output << " />";
|
||||
if (indent) output << std::endl;
|
||||
}
|
||||
|
||||
|
||||
void XMLWriter::writeAttrs(const XMLAttrs & attrs)
|
||||
{
|
||||
for (auto & i : attrs) {
|
||||
output << " " << i.first << "=\"";
|
||||
for (size_t j = 0; j < i.second.size(); ++j) {
|
||||
char c = i.second[j];
|
||||
if (c == '"') output << """;
|
||||
else if (c == '<') output << "<";
|
||||
else if (c == '>') output << ">";
|
||||
else if (c == '&') output << "&";
|
||||
/* Escape newlines to prevent attribute normalisation (see
|
||||
XML spec, section 3.3.3. */
|
||||
else if (c == '\n') output << "
";
|
||||
else output << c;
|
||||
}
|
||||
output << "\"";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
69
third_party/nix/src/libutil/xml-writer.hh
vendored
Normal file
69
third_party/nix/src/libutil/xml-writer.hh
vendored
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <list>
|
||||
#include <map>
|
||||
|
||||
|
||||
namespace nix {
|
||||
|
||||
using std::string;
|
||||
using std::map;
|
||||
using std::list;
|
||||
|
||||
|
||||
typedef map<string, string> XMLAttrs;
|
||||
|
||||
|
||||
class XMLWriter
|
||||
{
|
||||
private:
|
||||
|
||||
std::ostream & output;
|
||||
|
||||
bool indent;
|
||||
bool closed;
|
||||
|
||||
list<string> pendingElems;
|
||||
|
||||
public:
|
||||
|
||||
XMLWriter(bool indent, std::ostream & output);
|
||||
~XMLWriter();
|
||||
|
||||
void close();
|
||||
|
||||
void openElement(const string & name,
|
||||
const XMLAttrs & attrs = XMLAttrs());
|
||||
void closeElement();
|
||||
|
||||
void writeEmptyElement(const string & name,
|
||||
const XMLAttrs & attrs = XMLAttrs());
|
||||
|
||||
private:
|
||||
void writeAttrs(const XMLAttrs & attrs);
|
||||
|
||||
void indent_(size_t depth);
|
||||
};
|
||||
|
||||
|
||||
class XMLOpenElement
|
||||
{
|
||||
private:
|
||||
XMLWriter & writer;
|
||||
public:
|
||||
XMLOpenElement(XMLWriter & writer, const string & name,
|
||||
const XMLAttrs & attrs = XMLAttrs())
|
||||
: writer(writer)
|
||||
{
|
||||
writer.openElement(name, attrs);
|
||||
}
|
||||
~XMLOpenElement()
|
||||
{
|
||||
writer.closeElement();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue