mirror of
https://github.com/NixOS/nix.git
synced 2025-11-11 21:16:02 +01:00
Merge remote-tracking branch 'origin/master' into coerce-string
This commit is contained in:
commit
6b69652385
233 changed files with 5278 additions and 2874 deletions
|
|
@ -35,10 +35,6 @@ static ArchiveSettings archiveSettings;
|
|||
|
||||
static GlobalConfig::Register rArchiveSettings(&archiveSettings);
|
||||
|
||||
const std::string narVersionMagic1 = "nix-archive-1";
|
||||
|
||||
static std::string caseHackSuffix = "~nix~case~hack~";
|
||||
|
||||
PathFilter defaultPathFilter = [](const Path &) { return true; };
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -103,7 +103,9 @@ void copyNAR(Source & source, Sink & sink);
|
|||
void copyPath(const Path & from, const Path & to);
|
||||
|
||||
|
||||
extern const std::string narVersionMagic1;
|
||||
inline constexpr std::string_view narVersionMagic1 = "nix-archive-1";
|
||||
|
||||
inline constexpr std::string_view caseHackSuffix = "~nix~case~hack~";
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
103
src/libutil/canon-path.cc
Normal file
103
src/libutil/canon-path.cc
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
#include "canon-path.hh"
|
||||
#include "util.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
CanonPath CanonPath::root = CanonPath("/");
|
||||
|
||||
CanonPath::CanonPath(std::string_view raw)
|
||||
: path(absPath((Path) raw, "/"))
|
||||
{ }
|
||||
|
||||
CanonPath::CanonPath(std::string_view raw, const CanonPath & root)
|
||||
: path(absPath((Path) raw, root.abs()))
|
||||
{ }
|
||||
|
||||
std::optional<CanonPath> CanonPath::parent() const
|
||||
{
|
||||
if (isRoot()) return std::nullopt;
|
||||
return CanonPath(unchecked_t(), path.substr(0, std::max((size_t) 1, path.rfind('/'))));
|
||||
}
|
||||
|
||||
void CanonPath::pop()
|
||||
{
|
||||
assert(!isRoot());
|
||||
path.resize(std::max((size_t) 1, path.rfind('/')));
|
||||
}
|
||||
|
||||
bool CanonPath::isWithin(const CanonPath & parent) const
|
||||
{
|
||||
return !(
|
||||
path.size() < parent.path.size()
|
||||
|| path.substr(0, parent.path.size()) != parent.path
|
||||
|| (parent.path.size() > 1 && path.size() > parent.path.size()
|
||||
&& path[parent.path.size()] != '/'));
|
||||
}
|
||||
|
||||
CanonPath CanonPath::removePrefix(const CanonPath & prefix) const
|
||||
{
|
||||
assert(isWithin(prefix));
|
||||
if (prefix.isRoot()) return *this;
|
||||
if (path.size() == prefix.path.size()) return root;
|
||||
return CanonPath(unchecked_t(), path.substr(prefix.path.size()));
|
||||
}
|
||||
|
||||
void CanonPath::extend(const CanonPath & x)
|
||||
{
|
||||
if (x.isRoot()) return;
|
||||
if (isRoot())
|
||||
path += x.rel();
|
||||
else
|
||||
path += x.abs();
|
||||
}
|
||||
|
||||
CanonPath CanonPath::operator + (const CanonPath & x) const
|
||||
{
|
||||
auto res = *this;
|
||||
res.extend(x);
|
||||
return res;
|
||||
}
|
||||
|
||||
void CanonPath::push(std::string_view c)
|
||||
{
|
||||
assert(c.find('/') == c.npos);
|
||||
assert(c != "." && c != "..");
|
||||
if (!isRoot()) path += '/';
|
||||
path += c;
|
||||
}
|
||||
|
||||
CanonPath CanonPath::operator + (std::string_view c) const
|
||||
{
|
||||
auto res = *this;
|
||||
res.push(c);
|
||||
return res;
|
||||
}
|
||||
|
||||
bool CanonPath::isAllowed(const std::set<CanonPath> & allowed) const
|
||||
{
|
||||
/* Check if `this` is an exact match or the parent of an
|
||||
allowed path. */
|
||||
auto lb = allowed.lower_bound(*this);
|
||||
if (lb != allowed.end()) {
|
||||
if (lb->isWithin(*this))
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Check if a parent of `this` is allowed. */
|
||||
auto path = *this;
|
||||
while (!path.isRoot()) {
|
||||
path.pop();
|
||||
if (allowed.count(path))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::ostream & operator << (std::ostream & stream, const CanonPath & path)
|
||||
{
|
||||
stream << path.abs();
|
||||
return stream;
|
||||
}
|
||||
|
||||
}
|
||||
173
src/libutil/canon-path.hh
Normal file
173
src/libutil/canon-path.hh
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <optional>
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <set>
|
||||
|
||||
namespace nix {
|
||||
|
||||
/* A canonical representation of a path. It ensures the following:
|
||||
|
||||
- It always starts with a slash.
|
||||
|
||||
- It never ends with a slash, except if the path is "/".
|
||||
|
||||
- A slash is never followed by a slash (i.e. no empty components).
|
||||
|
||||
- There are no components equal to '.' or '..'.
|
||||
|
||||
Note that the path does not need to correspond to an actually
|
||||
existing path, and there is no guarantee that symlinks are
|
||||
resolved.
|
||||
*/
|
||||
class CanonPath
|
||||
{
|
||||
std::string path;
|
||||
|
||||
public:
|
||||
|
||||
/* Construct a canon path from a non-canonical path. Any '.', '..'
|
||||
or empty components are removed. */
|
||||
CanonPath(std::string_view raw);
|
||||
|
||||
explicit CanonPath(const char * raw)
|
||||
: CanonPath(std::string_view(raw))
|
||||
{ }
|
||||
|
||||
struct unchecked_t { };
|
||||
|
||||
CanonPath(unchecked_t _, std::string path)
|
||||
: path(std::move(path))
|
||||
{ }
|
||||
|
||||
static CanonPath root;
|
||||
|
||||
/* If `raw` starts with a slash, return
|
||||
`CanonPath(raw)`. Otherwise return a `CanonPath` representing
|
||||
`root + "/" + raw`. */
|
||||
CanonPath(std::string_view raw, const CanonPath & root);
|
||||
|
||||
bool isRoot() const
|
||||
{ return path.size() <= 1; }
|
||||
|
||||
explicit operator std::string_view() const
|
||||
{ return path; }
|
||||
|
||||
const std::string & abs() const
|
||||
{ return path; }
|
||||
|
||||
/* Like abs(), but return an empty string if this path is
|
||||
'/'. Thus the returned string never ends in a slash. */
|
||||
const std::string & absOrEmpty() const
|
||||
{
|
||||
const static std::string epsilon;
|
||||
return isRoot() ? epsilon : path;
|
||||
}
|
||||
|
||||
const char * c_str() const
|
||||
{ return path.c_str(); }
|
||||
|
||||
std::string_view rel() const
|
||||
{ return ((std::string_view) path).substr(1); }
|
||||
|
||||
struct Iterator
|
||||
{
|
||||
std::string_view remaining;
|
||||
size_t slash;
|
||||
|
||||
Iterator(std::string_view remaining)
|
||||
: remaining(remaining)
|
||||
, slash(remaining.find('/'))
|
||||
{ }
|
||||
|
||||
bool operator != (const Iterator & x) const
|
||||
{ return remaining.data() != x.remaining.data(); }
|
||||
|
||||
const std::string_view operator * () const
|
||||
{ return remaining.substr(0, slash); }
|
||||
|
||||
void operator ++ ()
|
||||
{
|
||||
if (slash == remaining.npos)
|
||||
remaining = remaining.substr(remaining.size());
|
||||
else {
|
||||
remaining = remaining.substr(slash + 1);
|
||||
slash = remaining.find('/');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Iterator begin() const { return Iterator(rel()); }
|
||||
Iterator end() const { return Iterator(rel().substr(path.size() - 1)); }
|
||||
|
||||
std::optional<CanonPath> parent() const;
|
||||
|
||||
/* Remove the last component. Panics if this path is the root. */
|
||||
void pop();
|
||||
|
||||
std::optional<std::string_view> dirOf() const
|
||||
{
|
||||
if (isRoot()) return std::nullopt;
|
||||
return ((std::string_view) path).substr(0, path.rfind('/'));
|
||||
}
|
||||
|
||||
std::optional<std::string_view> baseName() const
|
||||
{
|
||||
if (isRoot()) return std::nullopt;
|
||||
return ((std::string_view) path).substr(path.rfind('/') + 1);
|
||||
}
|
||||
|
||||
bool operator == (const CanonPath & x) const
|
||||
{ return path == x.path; }
|
||||
|
||||
bool operator != (const CanonPath & x) const
|
||||
{ return path != x.path; }
|
||||
|
||||
/* Compare paths lexicographically except that path separators
|
||||
are sorted before any other character. That is, in the sorted order
|
||||
a directory is always followed directly by its children. For
|
||||
instance, 'foo' < 'foo/bar' < 'foo!'. */
|
||||
bool operator < (const CanonPath & x) const
|
||||
{
|
||||
auto i = path.begin();
|
||||
auto j = x.path.begin();
|
||||
for ( ; i != path.end() && j != x.path.end(); ++i, ++j) {
|
||||
auto c_i = *i;
|
||||
if (c_i == '/') c_i = 0;
|
||||
auto c_j = *j;
|
||||
if (c_j == '/') c_j = 0;
|
||||
if (c_i < c_j) return true;
|
||||
if (c_i > c_j) return false;
|
||||
}
|
||||
return i == path.end() && j != x.path.end();
|
||||
}
|
||||
|
||||
/* Return true if `this` is equal to `parent` or a child of
|
||||
`parent`. */
|
||||
bool isWithin(const CanonPath & parent) const;
|
||||
|
||||
CanonPath removePrefix(const CanonPath & prefix) const;
|
||||
|
||||
/* Append another path to this one. */
|
||||
void extend(const CanonPath & x);
|
||||
|
||||
/* Concatenate two paths. */
|
||||
CanonPath operator + (const CanonPath & x) const;
|
||||
|
||||
/* Add a path component to this one. It must not contain any slashes. */
|
||||
void push(std::string_view c);
|
||||
|
||||
CanonPath operator + (std::string_view c) const;
|
||||
|
||||
/* Check whether access to this path is allowed, which is the case
|
||||
if 1) `this` is within any of the `allowed` paths; or 2) any of
|
||||
the `allowed` paths are within `this`. (The latter condition
|
||||
ensures access to the parents of allowed paths.) */
|
||||
bool isAllowed(const std::set<CanonPath> & allowed) const;
|
||||
};
|
||||
|
||||
std::ostream & operator << (std::ostream & stream, const CanonPath & path);
|
||||
|
||||
}
|
||||
148
src/libutil/cgroup.cc
Normal file
148
src/libutil/cgroup.cc
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
#if __linux__
|
||||
|
||||
#include "cgroup.hh"
|
||||
#include "util.hh"
|
||||
#include "finally.hh"
|
||||
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <regex>
|
||||
#include <unordered_set>
|
||||
#include <thread>
|
||||
|
||||
#include <dirent.h>
|
||||
#include <mntent.h>
|
||||
|
||||
namespace nix {
|
||||
|
||||
std::optional<Path> getCgroupFS()
|
||||
{
|
||||
static auto res = [&]() -> std::optional<Path> {
|
||||
auto fp = fopen("/proc/mounts", "r");
|
||||
if (!fp) return std::nullopt;
|
||||
Finally delFP = [&]() { fclose(fp); };
|
||||
while (auto ent = getmntent(fp))
|
||||
if (std::string_view(ent->mnt_type) == "cgroup2")
|
||||
return ent->mnt_dir;
|
||||
|
||||
return std::nullopt;
|
||||
}();
|
||||
return res;
|
||||
}
|
||||
|
||||
// FIXME: obsolete, check for cgroup2
|
||||
std::map<std::string, std::string> getCgroups(const Path & cgroupFile)
|
||||
{
|
||||
std::map<std::string, std::string> cgroups;
|
||||
|
||||
for (auto & line : tokenizeString<std::vector<std::string>>(readFile(cgroupFile), "\n")) {
|
||||
static std::regex regex("([0-9]+):([^:]*):(.*)");
|
||||
std::smatch match;
|
||||
if (!std::regex_match(line, match, regex))
|
||||
throw Error("invalid line '%s' in '%s'", line, cgroupFile);
|
||||
|
||||
std::string name = hasPrefix(std::string(match[2]), "name=") ? std::string(match[2], 5) : match[2];
|
||||
cgroups.insert_or_assign(name, match[3]);
|
||||
}
|
||||
|
||||
return cgroups;
|
||||
}
|
||||
|
||||
static CgroupStats destroyCgroup(const Path & cgroup, bool returnStats)
|
||||
{
|
||||
if (!pathExists(cgroup)) return {};
|
||||
|
||||
auto procsFile = cgroup + "/cgroup.procs";
|
||||
|
||||
if (!pathExists(procsFile))
|
||||
throw Error("'%s' is not a cgroup", cgroup);
|
||||
|
||||
/* Use the fast way to kill every process in a cgroup, if
|
||||
available. */
|
||||
auto killFile = cgroup + "/cgroup.kill";
|
||||
if (pathExists(killFile))
|
||||
writeFile(killFile, "1");
|
||||
|
||||
/* Otherwise, manually kill every process in the subcgroups and
|
||||
this cgroup. */
|
||||
for (auto & entry : readDirectory(cgroup)) {
|
||||
if (entry.type != DT_DIR) continue;
|
||||
destroyCgroup(cgroup + "/" + entry.name, false);
|
||||
}
|
||||
|
||||
int round = 1;
|
||||
|
||||
std::unordered_set<pid_t> pidsShown;
|
||||
|
||||
while (true) {
|
||||
auto pids = tokenizeString<std::vector<std::string>>(readFile(procsFile));
|
||||
|
||||
if (pids.empty()) break;
|
||||
|
||||
if (round > 20)
|
||||
throw Error("cannot kill cgroup '%s'", cgroup);
|
||||
|
||||
for (auto & pid_s : pids) {
|
||||
pid_t pid;
|
||||
if (auto o = string2Int<pid_t>(pid_s))
|
||||
pid = *o;
|
||||
else
|
||||
throw Error("invalid pid '%s'", pid);
|
||||
if (pidsShown.insert(pid).second) {
|
||||
try {
|
||||
auto cmdline = readFile(fmt("/proc/%d/cmdline", pid));
|
||||
using namespace std::string_literals;
|
||||
warn("killing stray builder process %d (%s)...",
|
||||
pid, trim(replaceStrings(cmdline, "\0"s, " ")));
|
||||
} catch (SysError &) {
|
||||
}
|
||||
}
|
||||
// FIXME: pid wraparound
|
||||
if (kill(pid, SIGKILL) == -1 && errno != ESRCH)
|
||||
throw SysError("killing member %d of cgroup '%s'", pid, cgroup);
|
||||
}
|
||||
|
||||
auto sleep = std::chrono::milliseconds((int) std::pow(2.0, std::min(round, 10)));
|
||||
if (sleep.count() > 100)
|
||||
printError("waiting for %d ms for cgroup '%s' to become empty", sleep.count(), cgroup);
|
||||
std::this_thread::sleep_for(sleep);
|
||||
round++;
|
||||
}
|
||||
|
||||
CgroupStats stats;
|
||||
|
||||
if (returnStats) {
|
||||
auto cpustatPath = cgroup + "/cpu.stat";
|
||||
|
||||
if (pathExists(cpustatPath)) {
|
||||
for (auto & line : tokenizeString<std::vector<std::string>>(readFile(cpustatPath), "\n")) {
|
||||
std::string_view userPrefix = "user_usec ";
|
||||
if (hasPrefix(line, userPrefix)) {
|
||||
auto n = string2Int<uint64_t>(line.substr(userPrefix.size()));
|
||||
if (n) stats.cpuUser = std::chrono::microseconds(*n);
|
||||
}
|
||||
|
||||
std::string_view systemPrefix = "system_usec ";
|
||||
if (hasPrefix(line, systemPrefix)) {
|
||||
auto n = string2Int<uint64_t>(line.substr(systemPrefix.size()));
|
||||
if (n) stats.cpuSystem = std::chrono::microseconds(*n);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (rmdir(cgroup.c_str()) == -1)
|
||||
throw SysError("deleting cgroup '%s'", cgroup);
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
CgroupStats destroyCgroup(const Path & cgroup)
|
||||
{
|
||||
return destroyCgroup(cgroup, true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
29
src/libutil/cgroup.hh
Normal file
29
src/libutil/cgroup.hh
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
#pragma once
|
||||
|
||||
#if __linux__
|
||||
|
||||
#include <chrono>
|
||||
#include <optional>
|
||||
|
||||
#include "types.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
std::optional<Path> getCgroupFS();
|
||||
|
||||
std::map<std::string, std::string> getCgroups(const Path & cgroupFile);
|
||||
|
||||
struct CgroupStats
|
||||
{
|
||||
std::optional<std::chrono::microseconds> cpuUser, cpuSystem;
|
||||
};
|
||||
|
||||
/* Destroy the cgroup denoted by 'path'. The postcondition is that
|
||||
'path' does not exist, and thus any processes in the cgroup have
|
||||
been killed. Also return statistics from the cgroup just before
|
||||
destruction. */
|
||||
CgroupStats destroyCgroup(const Path & cgroup);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -9,9 +9,9 @@ namespace nix {
|
|||
|
||||
const std::string nativeSystem = SYSTEM;
|
||||
|
||||
void BaseError::addTrace(std::optional<ErrPos> e, hintformat hint, bool frame)
|
||||
void BaseError::addTrace(std::shared_ptr<AbstractPos> && e, hintformat hint, bool frame)
|
||||
{
|
||||
err.traces.push_front(Trace { .pos = e, .hint = hint, .frame = frame });
|
||||
err.traces.push_front(Trace { .pos = std::move(e), .hint = hint, .frame = frame });
|
||||
}
|
||||
|
||||
// c++ std::exception descendants must have a 'const char* what()' function.
|
||||
|
|
@ -30,91 +30,46 @@ const std::string & BaseError::calcWhat() const
|
|||
|
||||
std::optional<std::string> ErrorInfo::programName = std::nullopt;
|
||||
|
||||
std::ostream & operator<<(std::ostream & os, const hintformat & hf)
|
||||
std::ostream & operator <<(std::ostream & os, const hintformat & hf)
|
||||
{
|
||||
return os << hf.str();
|
||||
}
|
||||
|
||||
std::string showErrPos(const ErrPos & errPos)
|
||||
std::ostream & operator <<(std::ostream & str, const AbstractPos & pos)
|
||||
{
|
||||
if (errPos.line > 0) {
|
||||
if (errPos.column > 0) {
|
||||
return fmt("%d:%d", errPos.line, errPos.column);
|
||||
} else {
|
||||
return fmt("%d", errPos.line);
|
||||
}
|
||||
}
|
||||
else {
|
||||
return "";
|
||||
}
|
||||
pos.print(str);
|
||||
str << ":" << pos.line;
|
||||
if (pos.column > 0)
|
||||
str << ":" << pos.column;
|
||||
return str;
|
||||
}
|
||||
|
||||
std::optional<LinesOfCode> getCodeLines(const ErrPos & errPos)
|
||||
std::optional<LinesOfCode> AbstractPos::getCodeLines() const
|
||||
{
|
||||
if (errPos.line <= 0)
|
||||
if (line == 0)
|
||||
return std::nullopt;
|
||||
|
||||
if (errPos.origin == foFile) {
|
||||
LinesOfCode loc;
|
||||
try {
|
||||
// FIXME: when running as the daemon, make sure we don't
|
||||
// open a file to which the client doesn't have access.
|
||||
AutoCloseFD fd = open(errPos.file.c_str(), O_RDONLY | O_CLOEXEC);
|
||||
if (!fd) return {};
|
||||
if (auto source = getSource()) {
|
||||
|
||||
// count the newlines.
|
||||
int count = 0;
|
||||
std::string line;
|
||||
int pl = errPos.line - 1;
|
||||
do
|
||||
{
|
||||
line = readLine(fd.get());
|
||||
++count;
|
||||
if (count < pl)
|
||||
;
|
||||
else if (count == pl)
|
||||
loc.prevLineOfCode = line;
|
||||
else if (count == pl + 1)
|
||||
loc.errLineOfCode = line;
|
||||
else if (count == pl + 2) {
|
||||
loc.nextLineOfCode = line;
|
||||
break;
|
||||
}
|
||||
} while (true);
|
||||
return loc;
|
||||
}
|
||||
catch (EndOfFile & eof) {
|
||||
if (loc.errLineOfCode.has_value())
|
||||
return loc;
|
||||
else
|
||||
return std::nullopt;
|
||||
}
|
||||
catch (std::exception & e) {
|
||||
return std::nullopt;
|
||||
}
|
||||
} else {
|
||||
std::istringstream iss(errPos.file);
|
||||
std::istringstream iss(*source);
|
||||
// count the newlines.
|
||||
int count = 0;
|
||||
std::string line;
|
||||
int pl = errPos.line - 1;
|
||||
std::string curLine;
|
||||
int pl = line - 1;
|
||||
|
||||
LinesOfCode loc;
|
||||
|
||||
do
|
||||
{
|
||||
std::getline(iss, line);
|
||||
do {
|
||||
std::getline(iss, curLine);
|
||||
++count;
|
||||
if (count < pl)
|
||||
{
|
||||
;
|
||||
}
|
||||
else if (count == pl) {
|
||||
loc.prevLineOfCode = line;
|
||||
loc.prevLineOfCode = curLine;
|
||||
} else if (count == pl + 1) {
|
||||
loc.errLineOfCode = line;
|
||||
loc.errLineOfCode = curLine;
|
||||
} else if (count == pl + 2) {
|
||||
loc.nextLineOfCode = line;
|
||||
loc.nextLineOfCode = curLine;
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -124,12 +79,14 @@ std::optional<LinesOfCode> getCodeLines(const ErrPos & errPos)
|
|||
|
||||
return loc;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// print lines of code to the ostream, indicating the error column.
|
||||
void printCodeLines(std::ostream & out,
|
||||
const std::string & prefix,
|
||||
const ErrPos & errPos,
|
||||
const AbstractPos & errPos,
|
||||
const LinesOfCode & loc)
|
||||
{
|
||||
// previous line of code.
|
||||
|
|
@ -176,32 +133,6 @@ void printCodeLines(std::ostream & out,
|
|||
}
|
||||
}
|
||||
|
||||
// Enough indent to align with with the `... `
|
||||
// prepended to each element of the trace
|
||||
#define ELLIPSIS_INDENT " "
|
||||
|
||||
void printAtPos(const ErrPos & pos, std::ostream & out)
|
||||
{
|
||||
if (pos) {
|
||||
switch (pos.origin) {
|
||||
case foFile: {
|
||||
out << fmt(ELLIPSIS_INDENT "at " ANSI_WARNING "%s:%s" ANSI_NORMAL ":", pos.file, showErrPos(pos));
|
||||
break;
|
||||
}
|
||||
case foString: {
|
||||
out << fmt(ELLIPSIS_INDENT "at " ANSI_WARNING "«string»:%s" ANSI_NORMAL ":", showErrPos(pos));
|
||||
break;
|
||||
}
|
||||
case foStdin: {
|
||||
out << fmt(ELLIPSIS_INDENT "at " ANSI_WARNING "«stdin»:%s" ANSI_NORMAL ":", showErrPos(pos));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw Error("invalid FileOrigin in errPos");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static std::string indent(std::string_view indentFirst, std::string_view indentRest, std::string_view s)
|
||||
{
|
||||
std::string res;
|
||||
|
|
@ -266,27 +197,8 @@ std::ostream & showErrorInfo(std::ostream & out, const ErrorInfo & einfo, bool s
|
|||
prefix += ":" ANSI_NORMAL " ";
|
||||
|
||||
std::ostringstream oss;
|
||||
oss << einfo.msg << "\n";
|
||||
|
||||
if (einfo.errPos.has_value() && *einfo.errPos) {
|
||||
printAtPos(*einfo.errPos, oss);
|
||||
|
||||
auto loc = getCodeLines(*einfo.errPos);
|
||||
|
||||
// lines of code.
|
||||
if (loc.has_value()) {
|
||||
oss << "\n";
|
||||
printCodeLines(oss, "", *einfo.errPos, *loc);
|
||||
}
|
||||
oss << "\n";
|
||||
}
|
||||
|
||||
auto suggestions = einfo.suggestions.trim();
|
||||
if (! suggestions.suggestions.empty()){
|
||||
oss << "Did you mean " <<
|
||||
suggestions.trim() <<
|
||||
"?" << std::endl;
|
||||
}
|
||||
auto noSource = ANSI_ITALIC " (source not available)" ANSI_NORMAL "\n";
|
||||
|
||||
/*
|
||||
* Traces
|
||||
|
|
@ -382,36 +294,61 @@ std::ostream & showErrorInfo(std::ostream & out, const ErrorInfo & einfo, bool s
|
|||
*
|
||||
*/
|
||||
|
||||
// Enough indent to align with with the `... `
|
||||
// prepended to each element of the trace
|
||||
auto ellipsisIndent = " ";
|
||||
|
||||
bool frameOnly = false;
|
||||
if (!einfo.traces.empty()) {
|
||||
unsigned int count = 0;
|
||||
for (auto iter = einfo.traces.rbegin(); iter != einfo.traces.rend(); ++iter) {
|
||||
size_t count = 0;
|
||||
for (const auto & trace : einfo.traces) {
|
||||
if (!showTrace && count > 3) {
|
||||
oss << "\n" << "(truncated)" << "\n";
|
||||
oss << "\n" << ANSI_ITALIC "(stack trace truncated)" ANSI_NORMAL << "\n";
|
||||
break;
|
||||
}
|
||||
|
||||
if (iter->hint.str().empty()) continue;
|
||||
if (frameOnly && !iter->frame) continue;
|
||||
if (trace.hint.str().empty()) continue;
|
||||
if (frameOnly && !trace.frame) continue;
|
||||
|
||||
count++;
|
||||
frameOnly = iter->frame;
|
||||
frameOnly = trace.frame;
|
||||
|
||||
oss << "\n" << "… " << iter->hint.str() << "\n";
|
||||
oss << "\n" << "… " << trace.hint.str() << "\n";
|
||||
|
||||
if (iter->pos.has_value() && (*iter->pos)) {
|
||||
if (trace.pos) {
|
||||
count++;
|
||||
auto pos = iter->pos.value();
|
||||
printAtPos(pos, oss);
|
||||
|
||||
auto loc = getCodeLines(pos);
|
||||
if (loc.has_value()) {
|
||||
oss << "\n" << ellipsisIndent << ANSI_BLUE << "at " ANSI_WARNING << *trace.pos << ANSI_NORMAL << ":";
|
||||
|
||||
if (auto loc = trace.pos->getCodeLines()) {
|
||||
oss << "\n";
|
||||
printCodeLines(oss, "", pos, *loc);
|
||||
}
|
||||
oss << "\n";
|
||||
printCodeLines(oss, "", *trace.pos, *loc);
|
||||
oss << "\n";
|
||||
} else
|
||||
oss << noSource;
|
||||
}
|
||||
}
|
||||
oss << "\n" << prefix;
|
||||
}
|
||||
|
||||
oss << einfo.msg << "\n";
|
||||
|
||||
if (einfo.errPos) {
|
||||
oss << "\n" << ANSI_BLUE << "at " ANSI_WARNING << *einfo.errPos << ANSI_NORMAL << ":";
|
||||
|
||||
if (auto loc = einfo.errPos->getCodeLines()) {
|
||||
oss << "\n";
|
||||
printCodeLines(oss, "", *einfo.errPos, *loc);
|
||||
oss << "\n";
|
||||
} else
|
||||
oss << noSource;
|
||||
}
|
||||
|
||||
auto suggestions = einfo.suggestions.trim();
|
||||
if (!suggestions.suggestions.empty()) {
|
||||
oss << "Did you mean " <<
|
||||
suggestions.trim() <<
|
||||
"?" << std::endl;
|
||||
}
|
||||
|
||||
out << indent(prefix, std::string(filterANSIEscapes(prefix, true).size(), ' '), chomp(oss.str()));
|
||||
|
|
|
|||
|
|
@ -54,13 +54,6 @@ typedef enum {
|
|||
lvlVomit
|
||||
} Verbosity;
|
||||
|
||||
/* adjust Pos::origin bit width when adding stuff here */
|
||||
typedef enum {
|
||||
foFile,
|
||||
foStdin,
|
||||
foString
|
||||
} FileOrigin;
|
||||
|
||||
// the lines of code surrounding an error.
|
||||
struct LinesOfCode {
|
||||
std::optional<std::string> prevLineOfCode;
|
||||
|
|
@ -68,47 +61,30 @@ struct LinesOfCode {
|
|||
std::optional<std::string> nextLineOfCode;
|
||||
};
|
||||
|
||||
// ErrPos indicates the location of an error in a nix file.
|
||||
struct ErrPos {
|
||||
int line = 0;
|
||||
int column = 0;
|
||||
std::string file;
|
||||
FileOrigin origin;
|
||||
/* An abstract type that represents a location in a source file. */
|
||||
struct AbstractPos
|
||||
{
|
||||
uint32_t line = 0;
|
||||
uint32_t column = 0;
|
||||
|
||||
operator bool() const
|
||||
{
|
||||
return line != 0;
|
||||
}
|
||||
/* Return the contents of the source file. */
|
||||
virtual std::optional<std::string> getSource() const
|
||||
{ return std::nullopt; };
|
||||
|
||||
// convert from the Pos struct, found in libexpr.
|
||||
template <class P>
|
||||
ErrPos & operator=(const P & pos)
|
||||
{
|
||||
origin = pos.origin;
|
||||
line = pos.line;
|
||||
column = pos.column;
|
||||
file = pos.file;
|
||||
return *this;
|
||||
}
|
||||
virtual void print(std::ostream & out) const = 0;
|
||||
|
||||
template <class P>
|
||||
ErrPos(const P & p)
|
||||
{
|
||||
*this = p;
|
||||
}
|
||||
std::optional<LinesOfCode> getCodeLines() const;
|
||||
};
|
||||
|
||||
std::optional<LinesOfCode> getCodeLines(const ErrPos & errPos);
|
||||
std::ostream & operator << (std::ostream & str, const AbstractPos & pos);
|
||||
|
||||
void printCodeLines(std::ostream & out,
|
||||
const std::string & prefix,
|
||||
const ErrPos & errPos,
|
||||
const AbstractPos & errPos,
|
||||
const LinesOfCode & loc);
|
||||
|
||||
void printAtPos(const ErrPos & pos, std::ostream & out);
|
||||
|
||||
struct Trace {
|
||||
std::optional<ErrPos> pos;
|
||||
std::shared_ptr<AbstractPos> pos;
|
||||
hintformat hint;
|
||||
bool frame;
|
||||
};
|
||||
|
|
@ -116,7 +92,7 @@ struct Trace {
|
|||
struct ErrorInfo {
|
||||
Verbosity level;
|
||||
hintformat msg;
|
||||
std::optional<ErrPos> errPos;
|
||||
std::shared_ptr<AbstractPos> errPos;
|
||||
std::list<Trace> traces;
|
||||
|
||||
Suggestions suggestions;
|
||||
|
|
@ -179,17 +155,18 @@ public:
|
|||
const std::string & msg() const { return calcWhat(); }
|
||||
const ErrorInfo & info() const { calcWhat(); return err; }
|
||||
|
||||
void pushTrace(Trace trace) {
|
||||
void pushTrace(Trace trace)
|
||||
{
|
||||
err.traces.push_front(trace);
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
void addTrace(std::optional<ErrPos> e, std::string_view fs, const Args & ... args)
|
||||
void addTrace(std::shared_ptr<AbstractPos> && e, std::string_view fs, const Args & ... args)
|
||||
{
|
||||
addTrace(e, hintfmt(std::string(fs), args...));
|
||||
addTrace(std::move(e), hintfmt(std::string(fs), args...));
|
||||
}
|
||||
|
||||
void addTrace(std::optional<ErrPos> e, hintformat hint, bool frame = false);
|
||||
void addTrace(std::shared_ptr<AbstractPos> && e, hintformat hint, bool frame = false);
|
||||
|
||||
bool hasTrace() const { return !err.traces.empty(); }
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ std::map<ExperimentalFeature, std::string> stringifiedXpFeatures = {
|
|||
{ Xp::NoUrlLiterals, "no-url-literals" },
|
||||
{ Xp::FetchClosure, "fetch-closure" },
|
||||
{ Xp::ReplFlake, "repl-flake" },
|
||||
{ Xp::AutoAllocateUids, "auto-allocate-uids" },
|
||||
{ Xp::Cgroups, "cgroups" },
|
||||
};
|
||||
|
||||
const std::optional<ExperimentalFeature> parseExperimentalFeature(const std::string_view & name)
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ enum struct ExperimentalFeature
|
|||
NoUrlLiterals,
|
||||
FetchClosure,
|
||||
ReplFlake,
|
||||
AutoAllocateUids,
|
||||
Cgroups,
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#include <sys/time.h>
|
||||
#include <filesystem>
|
||||
#include <atomic>
|
||||
|
||||
#include "finally.hh"
|
||||
#include "util.hh"
|
||||
|
|
@ -10,7 +11,7 @@ namespace fs = std::filesystem;
|
|||
namespace nix {
|
||||
|
||||
static Path tempName(Path tmpRoot, const Path & prefix, bool includePid,
|
||||
int & counter)
|
||||
std::atomic<unsigned int> & counter)
|
||||
{
|
||||
tmpRoot = canonPath(tmpRoot.empty() ? getEnv("TMPDIR").value_or("/tmp") : tmpRoot, true);
|
||||
if (includePid)
|
||||
|
|
@ -22,9 +23,9 @@ static Path tempName(Path tmpRoot, const Path & prefix, bool includePid,
|
|||
Path createTempDir(const Path & tmpRoot, const Path & prefix,
|
||||
bool includePid, bool useGlobalCounter, mode_t mode)
|
||||
{
|
||||
static int globalCounter = 0;
|
||||
int localCounter = 0;
|
||||
int & counter(useGlobalCounter ? globalCounter : localCounter);
|
||||
static std::atomic<unsigned int> globalCounter = 0;
|
||||
std::atomic<unsigned int> localCounter = 0;
|
||||
auto & counter(useGlobalCounter ? globalCounter : localCounter);
|
||||
|
||||
while (1) {
|
||||
checkInterrupt();
|
||||
|
|
|
|||
|
|
@ -148,7 +148,7 @@ inline hintformat hintfmt(const std::string & fs, const Args & ... args)
|
|||
return f;
|
||||
}
|
||||
|
||||
inline hintformat hintfmt(std::string plain_string)
|
||||
inline hintformat hintfmt(const std::string & plain_string)
|
||||
{
|
||||
// we won't be receiving any args in this case, so just print the original string
|
||||
return hintfmt("%s", normaltxt(plain_string));
|
||||
|
|
|
|||
|
|
@ -1,203 +0,0 @@
|
|||
#include "json.hh"
|
||||
|
||||
#include <iomanip>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
namespace nix {
|
||||
|
||||
template<>
|
||||
void toJSON<std::string_view>(std::ostream & str, const std::string_view & s)
|
||||
{
|
||||
constexpr size_t BUF_SIZE = 4096;
|
||||
char buf[BUF_SIZE + 7]; // BUF_SIZE + largest single sequence of puts
|
||||
size_t bufPos = 0;
|
||||
|
||||
const auto flush = [&] {
|
||||
str.write(buf, bufPos);
|
||||
bufPos = 0;
|
||||
};
|
||||
const auto put = [&] (char c) {
|
||||
buf[bufPos++] = c;
|
||||
};
|
||||
|
||||
put('"');
|
||||
for (auto i = s.begin(); i != s.end(); i++) {
|
||||
if (bufPos >= BUF_SIZE) flush();
|
||||
if (*i == '\"' || *i == '\\') { put('\\'); put(*i); }
|
||||
else if (*i == '\n') { put('\\'); put('n'); }
|
||||
else if (*i == '\r') { put('\\'); put('r'); }
|
||||
else if (*i == '\t') { put('\\'); put('t'); }
|
||||
else if (*i >= 0 && *i < 32) {
|
||||
const char hex[17] = "0123456789abcdef";
|
||||
put('\\');
|
||||
put('u');
|
||||
put(hex[(uint16_t(*i) >> 12) & 0xf]);
|
||||
put(hex[(uint16_t(*i) >> 8) & 0xf]);
|
||||
put(hex[(uint16_t(*i) >> 4) & 0xf]);
|
||||
put(hex[(uint16_t(*i) >> 0) & 0xf]);
|
||||
}
|
||||
else put(*i);
|
||||
}
|
||||
put('"');
|
||||
flush();
|
||||
}
|
||||
|
||||
void toJSON(std::ostream & str, const char * s)
|
||||
{
|
||||
if (!s) str << "null"; else toJSON(str, std::string_view(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, (std::string_view) s); }
|
||||
|
||||
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(std::string_view s)
|
||||
{
|
||||
comma();
|
||||
toJSON(state->str, s);
|
||||
state->str << ':';
|
||||
if (state->indent) state->str << ' ';
|
||||
}
|
||||
|
||||
JSONList JSONObject::list(std::string_view name)
|
||||
{
|
||||
attr(name);
|
||||
return JSONList(state);
|
||||
}
|
||||
|
||||
JSONObject JSONObject::object(std::string_view name)
|
||||
{
|
||||
attr(name);
|
||||
return JSONObject(state);
|
||||
}
|
||||
|
||||
JSONPlaceholder JSONObject::placeholder(std::string_view name)
|
||||
{
|
||||
attr(name);
|
||||
return JSONPlaceholder(state);
|
||||
}
|
||||
|
||||
JSONList JSONPlaceholder::list()
|
||||
{
|
||||
assertValid();
|
||||
first = false;
|
||||
return JSONList(state);
|
||||
}
|
||||
|
||||
JSONObject JSONPlaceholder::object()
|
||||
{
|
||||
assertValid();
|
||||
first = false;
|
||||
return JSONObject(state);
|
||||
}
|
||||
|
||||
JSONPlaceholder::~JSONPlaceholder()
|
||||
{
|
||||
if (first) {
|
||||
assert(std::uncaught_exceptions());
|
||||
if (state->stack != 0)
|
||||
write(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,185 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <cassert>
|
||||
|
||||
namespace nix {
|
||||
|
||||
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(std::string_view 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(std::string_view name, const T & v)
|
||||
{
|
||||
attr(name);
|
||||
toJSON(state->str, v);
|
||||
return *this;
|
||||
}
|
||||
|
||||
JSONList list(std::string_view name);
|
||||
|
||||
JSONObject object(std::string_view name);
|
||||
|
||||
JSONPlaceholder placeholder(std::string_view 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();
|
||||
|
||||
template<typename T>
|
||||
void write(const T & v)
|
||||
{
|
||||
assertValid();
|
||||
first = false;
|
||||
toJSON(state->str, v);
|
||||
}
|
||||
|
||||
JSONList list();
|
||||
|
||||
JSONObject object();
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -105,14 +105,6 @@ public:
|
|||
|
||||
Verbosity verbosity = lvlInfo;
|
||||
|
||||
void warnOnce(bool & haveWarned, const FormatOrString & fs)
|
||||
{
|
||||
if (!haveWarned) {
|
||||
warn(fs.s);
|
||||
haveWarned = true;
|
||||
}
|
||||
}
|
||||
|
||||
void writeToStderr(std::string_view s)
|
||||
{
|
||||
try {
|
||||
|
|
@ -130,15 +122,30 @@ Logger * makeSimpleLogger(bool printBuildLogs)
|
|||
return new SimpleLogger(printBuildLogs);
|
||||
}
|
||||
|
||||
std::atomic<uint64_t> nextId{(uint64_t) getpid() << 32};
|
||||
std::atomic<uint64_t> nextId{0};
|
||||
|
||||
Activity::Activity(Logger & logger, Verbosity lvl, ActivityType type,
|
||||
const std::string & s, const Logger::Fields & fields, ActivityId parent)
|
||||
: logger(logger), id(nextId++)
|
||||
: logger(logger), id(nextId++ + (((uint64_t) getpid()) << 32))
|
||||
{
|
||||
logger.startActivity(id, lvl, type, s, fields, parent);
|
||||
}
|
||||
|
||||
void to_json(nlohmann::json & json, std::shared_ptr<AbstractPos> pos)
|
||||
{
|
||||
if (pos) {
|
||||
json["line"] = pos->line;
|
||||
json["column"] = pos->column;
|
||||
std::ostringstream str;
|
||||
pos->print(str);
|
||||
json["file"] = str.str();
|
||||
} else {
|
||||
json["line"] = nullptr;
|
||||
json["column"] = nullptr;
|
||||
json["file"] = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
struct JSONLogger : Logger {
|
||||
Logger & prevLogger;
|
||||
|
||||
|
|
@ -185,27 +192,14 @@ struct JSONLogger : Logger {
|
|||
json["level"] = ei.level;
|
||||
json["msg"] = oss.str();
|
||||
json["raw_msg"] = ei.msg.str();
|
||||
|
||||
if (ei.errPos.has_value() && (*ei.errPos)) {
|
||||
json["line"] = ei.errPos->line;
|
||||
json["column"] = ei.errPos->column;
|
||||
json["file"] = ei.errPos->file;
|
||||
} else {
|
||||
json["line"] = nullptr;
|
||||
json["column"] = nullptr;
|
||||
json["file"] = nullptr;
|
||||
}
|
||||
to_json(json, ei.errPos);
|
||||
|
||||
if (loggerSettings.showTrace.get() && !ei.traces.empty()) {
|
||||
nlohmann::json traces = nlohmann::json::array();
|
||||
for (auto iter = ei.traces.rbegin(); iter != ei.traces.rend(); ++iter) {
|
||||
nlohmann::json stackFrame;
|
||||
stackFrame["raw_msg"] = iter->hint.str();
|
||||
if (iter->pos.has_value() && (*iter->pos)) {
|
||||
stackFrame["line"] = iter->pos->line;
|
||||
stackFrame["column"] = iter->pos->column;
|
||||
stackFrame["file"] = iter->pos->file;
|
||||
}
|
||||
to_json(stackFrame, iter->pos);
|
||||
traces.push_back(stackFrame);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ public:
|
|||
log(lvlInfo, fs);
|
||||
}
|
||||
|
||||
virtual void logEI(const ErrorInfo &ei) = 0;
|
||||
virtual void logEI(const ErrorInfo & ei) = 0;
|
||||
|
||||
void logEI(Verbosity lvl, ErrorInfo ei)
|
||||
{
|
||||
|
|
@ -225,7 +225,11 @@ inline void warn(const std::string & fs, const Args & ... args)
|
|||
logger->warn(f.str());
|
||||
}
|
||||
|
||||
void warnOnce(bool & haveWarned, const FormatOrString & fs);
|
||||
#define warnOnce(haveWarned, args...) \
|
||||
if (!haveWarned) { \
|
||||
haveWarned = true; \
|
||||
warn(args); \
|
||||
}
|
||||
|
||||
void writeToStderr(std::string_view s);
|
||||
|
||||
|
|
|
|||
|
|
@ -83,6 +83,11 @@ public:
|
|||
return p != other.p;
|
||||
}
|
||||
|
||||
bool operator < (const ref<T> & other) const
|
||||
{
|
||||
return p < other.p;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
template<typename T2, typename... Args>
|
||||
|
|
|
|||
|
|
@ -338,7 +338,7 @@ Sink & operator << (Sink & sink, const StringSet & s)
|
|||
|
||||
Sink & operator << (Sink & sink, const Error & ex)
|
||||
{
|
||||
auto info = ex.info();
|
||||
auto & info = ex.info();
|
||||
sink
|
||||
<< "Error"
|
||||
<< info.level
|
||||
|
|
|
|||
|
|
@ -331,17 +331,9 @@ T readNum(Source & source)
|
|||
unsigned char buf[8];
|
||||
source((char *) buf, sizeof(buf));
|
||||
|
||||
uint64_t n =
|
||||
((uint64_t) buf[0]) |
|
||||
((uint64_t) buf[1] << 8) |
|
||||
((uint64_t) buf[2] << 16) |
|
||||
((uint64_t) buf[3] << 24) |
|
||||
((uint64_t) buf[4] << 32) |
|
||||
((uint64_t) buf[5] << 40) |
|
||||
((uint64_t) buf[6] << 48) |
|
||||
((uint64_t) buf[7] << 56);
|
||||
auto n = readLittleEndian<uint64_t>(buf);
|
||||
|
||||
if (n > (uint64_t)std::numeric_limits<T>::max())
|
||||
if (n > (uint64_t) std::numeric_limits<T>::max())
|
||||
throw SerialisationError("serialised integer %d is too large for type '%s'", n, typeid(T).name());
|
||||
|
||||
return (T) n;
|
||||
|
|
|
|||
|
|
@ -77,9 +77,7 @@ TarArchive::~TarArchive()
|
|||
|
||||
static void extract_archive(TarArchive & archive, const Path & destDir)
|
||||
{
|
||||
int flags = ARCHIVE_EXTRACT_FFLAGS
|
||||
| ARCHIVE_EXTRACT_PERM
|
||||
| ARCHIVE_EXTRACT_TIME
|
||||
int flags = ARCHIVE_EXTRACT_TIME
|
||||
| ARCHIVE_EXTRACT_SECURE_SYMLINKS
|
||||
| ARCHIVE_EXTRACT_SECURE_NODOTDOT;
|
||||
|
||||
|
|
@ -98,6 +96,10 @@ static void extract_archive(TarArchive & archive, const Path & destDir)
|
|||
archive_entry_copy_pathname(entry,
|
||||
(destDir + "/" + name).c_str());
|
||||
|
||||
// sources can and do contain dirs with no rx bits
|
||||
if (archive_entry_filetype(entry) == AE_IFDIR && (archive_entry_mode(entry) & 0500) != 0500)
|
||||
archive_entry_set_mode(entry, archive_entry_mode(entry) | 0500);
|
||||
|
||||
// Patch hardlink path
|
||||
const char *original_hardlink = archive_entry_hardlink(entry);
|
||||
if (original_hardlink) {
|
||||
|
|
|
|||
155
src/libutil/tests/canon-path.cc
Normal file
155
src/libutil/tests/canon-path.cc
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
#include "canon-path.hh"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace nix {
|
||||
|
||||
TEST(CanonPath, basic) {
|
||||
{
|
||||
CanonPath p("/");
|
||||
ASSERT_EQ(p.abs(), "/");
|
||||
ASSERT_EQ(p.rel(), "");
|
||||
ASSERT_EQ(p.baseName(), std::nullopt);
|
||||
ASSERT_EQ(p.dirOf(), std::nullopt);
|
||||
ASSERT_FALSE(p.parent());
|
||||
}
|
||||
|
||||
{
|
||||
CanonPath p("/foo//");
|
||||
ASSERT_EQ(p.abs(), "/foo");
|
||||
ASSERT_EQ(p.rel(), "foo");
|
||||
ASSERT_EQ(*p.baseName(), "foo");
|
||||
ASSERT_EQ(*p.dirOf(), ""); // FIXME: do we want this?
|
||||
ASSERT_EQ(p.parent()->abs(), "/");
|
||||
}
|
||||
|
||||
{
|
||||
CanonPath p("foo/bar");
|
||||
ASSERT_EQ(p.abs(), "/foo/bar");
|
||||
ASSERT_EQ(p.rel(), "foo/bar");
|
||||
ASSERT_EQ(*p.baseName(), "bar");
|
||||
ASSERT_EQ(*p.dirOf(), "/foo");
|
||||
ASSERT_EQ(p.parent()->abs(), "/foo");
|
||||
}
|
||||
|
||||
{
|
||||
CanonPath p("foo//bar/");
|
||||
ASSERT_EQ(p.abs(), "/foo/bar");
|
||||
ASSERT_EQ(p.rel(), "foo/bar");
|
||||
ASSERT_EQ(*p.baseName(), "bar");
|
||||
ASSERT_EQ(*p.dirOf(), "/foo");
|
||||
}
|
||||
}
|
||||
|
||||
TEST(CanonPath, pop) {
|
||||
CanonPath p("foo/bar/x");
|
||||
ASSERT_EQ(p.abs(), "/foo/bar/x");
|
||||
p.pop();
|
||||
ASSERT_EQ(p.abs(), "/foo/bar");
|
||||
p.pop();
|
||||
ASSERT_EQ(p.abs(), "/foo");
|
||||
p.pop();
|
||||
ASSERT_EQ(p.abs(), "/");
|
||||
}
|
||||
|
||||
TEST(CanonPath, removePrefix) {
|
||||
CanonPath p1("foo/bar");
|
||||
CanonPath p2("foo/bar/a/b/c");
|
||||
ASSERT_EQ(p2.removePrefix(p1).abs(), "/a/b/c");
|
||||
ASSERT_EQ(p1.removePrefix(p1).abs(), "/");
|
||||
ASSERT_EQ(p1.removePrefix(CanonPath("/")).abs(), "/foo/bar");
|
||||
}
|
||||
|
||||
TEST(CanonPath, iter) {
|
||||
{
|
||||
CanonPath p("a//foo/bar//");
|
||||
std::vector<std::string_view> ss;
|
||||
for (auto & c : p) ss.push_back(c);
|
||||
ASSERT_EQ(ss, std::vector<std::string_view>({"a", "foo", "bar"}));
|
||||
}
|
||||
|
||||
{
|
||||
CanonPath p("/");
|
||||
std::vector<std::string_view> ss;
|
||||
for (auto & c : p) ss.push_back(c);
|
||||
ASSERT_EQ(ss, std::vector<std::string_view>());
|
||||
}
|
||||
}
|
||||
|
||||
TEST(CanonPath, concat) {
|
||||
{
|
||||
CanonPath p1("a//foo/bar//");
|
||||
CanonPath p2("xyzzy/bla");
|
||||
ASSERT_EQ((p1 + p2).abs(), "/a/foo/bar/xyzzy/bla");
|
||||
}
|
||||
|
||||
{
|
||||
CanonPath p1("/");
|
||||
CanonPath p2("/a/b");
|
||||
ASSERT_EQ((p1 + p2).abs(), "/a/b");
|
||||
}
|
||||
|
||||
{
|
||||
CanonPath p1("/a/b");
|
||||
CanonPath p2("/");
|
||||
ASSERT_EQ((p1 + p2).abs(), "/a/b");
|
||||
}
|
||||
|
||||
{
|
||||
CanonPath p("/foo/bar");
|
||||
ASSERT_EQ((p + "x").abs(), "/foo/bar/x");
|
||||
}
|
||||
|
||||
{
|
||||
CanonPath p("/");
|
||||
ASSERT_EQ((p + "foo" + "bar").abs(), "/foo/bar");
|
||||
}
|
||||
}
|
||||
|
||||
TEST(CanonPath, within) {
|
||||
{
|
||||
ASSERT_TRUE(CanonPath("foo").isWithin(CanonPath("foo")));
|
||||
ASSERT_FALSE(CanonPath("foo").isWithin(CanonPath("bar")));
|
||||
ASSERT_FALSE(CanonPath("foo").isWithin(CanonPath("fo")));
|
||||
ASSERT_TRUE(CanonPath("foo/bar").isWithin(CanonPath("foo")));
|
||||
ASSERT_FALSE(CanonPath("foo").isWithin(CanonPath("foo/bar")));
|
||||
ASSERT_TRUE(CanonPath("/foo/bar/default.nix").isWithin(CanonPath("/")));
|
||||
ASSERT_TRUE(CanonPath("/").isWithin(CanonPath("/")));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(CanonPath, sort) {
|
||||
ASSERT_FALSE(CanonPath("foo") < CanonPath("foo"));
|
||||
ASSERT_TRUE (CanonPath("foo") < CanonPath("foo/bar"));
|
||||
ASSERT_TRUE (CanonPath("foo/bar") < CanonPath("foo!"));
|
||||
ASSERT_FALSE(CanonPath("foo!") < CanonPath("foo"));
|
||||
ASSERT_TRUE (CanonPath("foo") < CanonPath("foo!"));
|
||||
}
|
||||
|
||||
TEST(CanonPath, allowed) {
|
||||
{
|
||||
std::set<CanonPath> allowed {
|
||||
CanonPath("foo/bar"),
|
||||
CanonPath("foo!"),
|
||||
CanonPath("xyzzy"),
|
||||
CanonPath("a/b/c"),
|
||||
};
|
||||
|
||||
ASSERT_TRUE (CanonPath("foo/bar").isAllowed(allowed));
|
||||
ASSERT_TRUE (CanonPath("foo/bar/bla").isAllowed(allowed));
|
||||
ASSERT_TRUE (CanonPath("foo").isAllowed(allowed));
|
||||
ASSERT_FALSE(CanonPath("bar").isAllowed(allowed));
|
||||
ASSERT_FALSE(CanonPath("bar/a").isAllowed(allowed));
|
||||
ASSERT_TRUE (CanonPath("a").isAllowed(allowed));
|
||||
ASSERT_TRUE (CanonPath("a/b").isAllowed(allowed));
|
||||
ASSERT_TRUE (CanonPath("a/b/c").isAllowed(allowed));
|
||||
ASSERT_TRUE (CanonPath("a/b/c/d").isAllowed(allowed));
|
||||
ASSERT_TRUE (CanonPath("a/b/c/d/e").isAllowed(allowed));
|
||||
ASSERT_FALSE(CanonPath("a/b/a").isAllowed(allowed));
|
||||
ASSERT_FALSE(CanonPath("a/b/d").isAllowed(allowed));
|
||||
ASSERT_FALSE(CanonPath("aaa").isAllowed(allowed));
|
||||
ASSERT_FALSE(CanonPath("zzz").isAllowed(allowed));
|
||||
ASSERT_TRUE (CanonPath("/").isAllowed(allowed));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,193 +0,0 @@
|
|||
#include "json.hh"
|
||||
#include <gtest/gtest.h>
|
||||
#include <sstream>
|
||||
|
||||
namespace nix {
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* toJSON
|
||||
* --------------------------------------------------------------------------*/
|
||||
|
||||
TEST(toJSON, quotesCharPtr) {
|
||||
const char* input = "test";
|
||||
std::stringstream out;
|
||||
toJSON(out, input);
|
||||
|
||||
ASSERT_EQ(out.str(), "\"test\"");
|
||||
}
|
||||
|
||||
TEST(toJSON, quotesStdString) {
|
||||
std::string input = "test";
|
||||
std::stringstream out;
|
||||
toJSON(out, input);
|
||||
|
||||
ASSERT_EQ(out.str(), "\"test\"");
|
||||
}
|
||||
|
||||
TEST(toJSON, convertsNullptrtoNull) {
|
||||
auto input = nullptr;
|
||||
std::stringstream out;
|
||||
toJSON(out, input);
|
||||
|
||||
ASSERT_EQ(out.str(), "null");
|
||||
}
|
||||
|
||||
TEST(toJSON, convertsNullToNull) {
|
||||
const char* input = 0;
|
||||
std::stringstream out;
|
||||
toJSON(out, input);
|
||||
|
||||
ASSERT_EQ(out.str(), "null");
|
||||
}
|
||||
|
||||
|
||||
TEST(toJSON, convertsFloat) {
|
||||
auto input = 1.024f;
|
||||
std::stringstream out;
|
||||
toJSON(out, input);
|
||||
|
||||
ASSERT_EQ(out.str(), "1.024");
|
||||
}
|
||||
|
||||
TEST(toJSON, convertsDouble) {
|
||||
const double input = 1.024;
|
||||
std::stringstream out;
|
||||
toJSON(out, input);
|
||||
|
||||
ASSERT_EQ(out.str(), "1.024");
|
||||
}
|
||||
|
||||
TEST(toJSON, convertsBool) {
|
||||
auto input = false;
|
||||
std::stringstream out;
|
||||
toJSON(out, input);
|
||||
|
||||
ASSERT_EQ(out.str(), "false");
|
||||
}
|
||||
|
||||
TEST(toJSON, quotesTab) {
|
||||
std::stringstream out;
|
||||
toJSON(out, "\t");
|
||||
|
||||
ASSERT_EQ(out.str(), "\"\\t\"");
|
||||
}
|
||||
|
||||
TEST(toJSON, quotesNewline) {
|
||||
std::stringstream out;
|
||||
toJSON(out, "\n");
|
||||
|
||||
ASSERT_EQ(out.str(), "\"\\n\"");
|
||||
}
|
||||
|
||||
TEST(toJSON, quotesCreturn) {
|
||||
std::stringstream out;
|
||||
toJSON(out, "\r");
|
||||
|
||||
ASSERT_EQ(out.str(), "\"\\r\"");
|
||||
}
|
||||
|
||||
TEST(toJSON, quotesCreturnNewLine) {
|
||||
std::stringstream out;
|
||||
toJSON(out, "\r\n");
|
||||
|
||||
ASSERT_EQ(out.str(), "\"\\r\\n\"");
|
||||
}
|
||||
|
||||
TEST(toJSON, quotesDoublequotes) {
|
||||
std::stringstream out;
|
||||
toJSON(out, "\"");
|
||||
|
||||
ASSERT_EQ(out.str(), "\"\\\"\"");
|
||||
}
|
||||
|
||||
TEST(toJSON, substringEscape) {
|
||||
std::stringstream out;
|
||||
std::string_view s = "foo\t";
|
||||
toJSON(out, s.substr(3));
|
||||
|
||||
ASSERT_EQ(out.str(), "\"\\t\"");
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* JSONObject
|
||||
* --------------------------------------------------------------------------*/
|
||||
|
||||
TEST(JSONObject, emptyObject) {
|
||||
std::stringstream out;
|
||||
{
|
||||
JSONObject t(out);
|
||||
}
|
||||
ASSERT_EQ(out.str(), "{}");
|
||||
}
|
||||
|
||||
TEST(JSONObject, objectWithList) {
|
||||
std::stringstream out;
|
||||
{
|
||||
JSONObject t(out);
|
||||
auto l = t.list("list");
|
||||
l.elem("element");
|
||||
}
|
||||
ASSERT_EQ(out.str(), R"#({"list":["element"]})#");
|
||||
}
|
||||
|
||||
TEST(JSONObject, objectWithListIndent) {
|
||||
std::stringstream out;
|
||||
{
|
||||
JSONObject t(out, true);
|
||||
auto l = t.list("list");
|
||||
l.elem("element");
|
||||
}
|
||||
ASSERT_EQ(out.str(),
|
||||
R"#({
|
||||
"list": [
|
||||
"element"
|
||||
]
|
||||
})#");
|
||||
}
|
||||
|
||||
TEST(JSONObject, objectWithPlaceholderAndList) {
|
||||
std::stringstream out;
|
||||
{
|
||||
JSONObject t(out);
|
||||
auto l = t.placeholder("list");
|
||||
l.list().elem("element");
|
||||
}
|
||||
|
||||
ASSERT_EQ(out.str(), R"#({"list":["element"]})#");
|
||||
}
|
||||
|
||||
TEST(JSONObject, objectWithPlaceholderAndObject) {
|
||||
std::stringstream out;
|
||||
{
|
||||
JSONObject t(out);
|
||||
auto l = t.placeholder("object");
|
||||
l.object().attr("key", "value");
|
||||
}
|
||||
|
||||
ASSERT_EQ(out.str(), R"#({"object":{"key":"value"}})#");
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* JSONList
|
||||
* --------------------------------------------------------------------------*/
|
||||
|
||||
TEST(JSONList, empty) {
|
||||
std::stringstream out;
|
||||
{
|
||||
JSONList l(out);
|
||||
}
|
||||
ASSERT_EQ(out.str(), R"#([])#");
|
||||
}
|
||||
|
||||
TEST(JSONList, withElements) {
|
||||
std::stringstream out;
|
||||
{
|
||||
JSONList l(out);
|
||||
l.elem("one");
|
||||
l.object();
|
||||
l.placeholder().write("three");
|
||||
}
|
||||
ASSERT_EQ(out.str(), R"#(["one",{},"three"])#");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
#include "sync.hh"
|
||||
#include "finally.hh"
|
||||
#include "serialise.hh"
|
||||
#include "cgroup.hh"
|
||||
|
||||
#include <array>
|
||||
#include <cctype>
|
||||
|
|
@ -36,7 +37,6 @@
|
|||
#include <sys/prctl.h>
|
||||
#include <sys/resource.h>
|
||||
|
||||
#include <mntent.h>
|
||||
#include <cmath>
|
||||
#endif
|
||||
|
||||
|
|
@ -727,45 +727,22 @@ unsigned int getMaxCPU()
|
|||
{
|
||||
#if __linux__
|
||||
try {
|
||||
FILE *fp = fopen("/proc/mounts", "r");
|
||||
if (!fp)
|
||||
return 0;
|
||||
auto cgroupFS = getCgroupFS();
|
||||
if (!cgroupFS) return 0;
|
||||
|
||||
Strings cgPathParts;
|
||||
auto cgroups = getCgroups("/proc/self/cgroup");
|
||||
auto cgroup = cgroups[""];
|
||||
if (cgroup == "") return 0;
|
||||
|
||||
struct mntent *ent;
|
||||
while ((ent = getmntent(fp))) {
|
||||
std::string mountType, mountPath;
|
||||
auto cpuFile = *cgroupFS + "/" + cgroup + "/cpu.max";
|
||||
|
||||
mountType = ent->mnt_type;
|
||||
mountPath = ent->mnt_dir;
|
||||
|
||||
if (mountType == "cgroup2") {
|
||||
cgPathParts.push_back(mountPath);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
|
||||
if (cgPathParts.size() > 0 && pathExists("/proc/self/cgroup")) {
|
||||
std::string currentCgroup = readFile("/proc/self/cgroup");
|
||||
Strings cgValues = tokenizeString<Strings>(currentCgroup, ":");
|
||||
cgPathParts.push_back(trim(cgValues.back(), "\n"));
|
||||
cgPathParts.push_back("cpu.max");
|
||||
std::string fullCgPath = canonPath(concatStringsSep("/", cgPathParts));
|
||||
|
||||
if (pathExists(fullCgPath)) {
|
||||
std::string cpuMax = readFile(fullCgPath);
|
||||
std::vector<std::string> cpuMaxParts = tokenizeString<std::vector<std::string>>(cpuMax, " ");
|
||||
std::string quota = cpuMaxParts[0];
|
||||
std::string period = trim(cpuMaxParts[1], "\n");
|
||||
|
||||
if (quota != "max")
|
||||
return std::ceil(std::stoi(quota) / std::stof(period));
|
||||
}
|
||||
}
|
||||
} catch (Error &) { ignoreException(); }
|
||||
auto cpuMax = readFile(cpuFile);
|
||||
auto cpuMaxParts = tokenizeString<std::vector<std::string>>(cpuMax, " \n");
|
||||
auto quota = cpuMaxParts[0];
|
||||
auto period = cpuMaxParts[1];
|
||||
if (quota != "max")
|
||||
return std::ceil(std::stoi(quota) / std::stof(period));
|
||||
} catch (Error &) { ignoreException(lvlDebug); }
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
|
|
@ -1427,7 +1404,7 @@ std::string shellEscape(const std::string_view s)
|
|||
}
|
||||
|
||||
|
||||
void ignoreException()
|
||||
void ignoreException(Verbosity lvl)
|
||||
{
|
||||
/* Make sure no exceptions leave this function.
|
||||
printError() also throws when remote is closed. */
|
||||
|
|
@ -1435,7 +1412,7 @@ void ignoreException()
|
|||
try {
|
||||
throw;
|
||||
} catch (std::exception & e) {
|
||||
printError("error (ignored): %1%", e.what());
|
||||
printMsg(lvl, "error (ignored): %1%", e.what());
|
||||
}
|
||||
} catch (...) { }
|
||||
}
|
||||
|
|
@ -1617,6 +1594,21 @@ std::string stripIndentation(std::string_view s)
|
|||
}
|
||||
|
||||
|
||||
std::pair<std::string_view, std::string_view> getLine(std::string_view s)
|
||||
{
|
||||
auto newline = s.find('\n');
|
||||
|
||||
if (newline == s.npos) {
|
||||
return {s, ""};
|
||||
} else {
|
||||
auto line = s.substr(0, newline);
|
||||
if (!line.empty() && line[line.size() - 1] == '\r')
|
||||
line = line.substr(0, line.size() - 1);
|
||||
return {line, s.substr(newline + 1)};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
static Sync<std::pair<unsigned short, unsigned short>> windowSize{{0, 0}};
|
||||
|
|
|
|||
|
|
@ -510,6 +510,18 @@ std::optional<N> string2Float(const std::string_view s)
|
|||
}
|
||||
|
||||
|
||||
/* Convert a little-endian integer to host order. */
|
||||
template<typename T>
|
||||
T readLittleEndian(unsigned char * p)
|
||||
{
|
||||
T x = 0;
|
||||
for (size_t i = 0; i < sizeof(x); ++i, ++p) {
|
||||
x |= ((T) *p) << (i * 8);
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
|
||||
/* Return true iff `s' starts with `prefix'. */
|
||||
bool hasPrefix(std::string_view s, std::string_view prefix);
|
||||
|
||||
|
|
@ -528,7 +540,7 @@ std::string shellEscape(const std::string_view s);
|
|||
|
||||
/* Exception handling in destructors: print an error message, then
|
||||
ignore the exception. */
|
||||
void ignoreException();
|
||||
void ignoreException(Verbosity lvl = lvlError);
|
||||
|
||||
|
||||
|
||||
|
|
@ -563,6 +575,12 @@ std::string base64Decode(std::string_view s);
|
|||
std::string stripIndentation(std::string_view s);
|
||||
|
||||
|
||||
/* Get the prefix of 's' up to and excluding the next line break (LF
|
||||
optionally preceded by CR), and the remainder following the line
|
||||
break. */
|
||||
std::pair<std::string_view, std::string_view> getLine(std::string_view s);
|
||||
|
||||
|
||||
/* Get a value for the specified key from an associate container. */
|
||||
template <class T>
|
||||
const typename T::mapped_type * get(const T & map, const typename T::key_type & key)
|
||||
|
|
@ -737,4 +755,13 @@ inline std::string operator + (std::string && s, std::string_view s2)
|
|||
return std::move(s);
|
||||
}
|
||||
|
||||
inline std::string operator + (std::string_view s1, const char * s2)
|
||||
{
|
||||
std::string s;
|
||||
s.reserve(s1.size() + strlen(s2));
|
||||
s.append(s1);
|
||||
s.append(s2);
|
||||
return s;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue