1
1
Fork 0
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:
Eelco Dolstra 2023-01-02 20:53:39 +01:00
commit 6b69652385
233 changed files with 5278 additions and 2874 deletions

View file

@ -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; };

View file

@ -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
View 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
View 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
View 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
View 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

View file

@ -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()));

View file

@ -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(); }

View file

@ -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)

View file

@ -23,6 +23,8 @@ enum struct ExperimentalFeature
NoUrlLiterals,
FetchClosure,
ReplFlake,
AutoAllocateUids,
Cgroups,
};
/**

View file

@ -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();

View file

@ -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));

View file

@ -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);
}
}
}

View file

@ -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();
};
}

View file

@ -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);
}

View file

@ -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);

View file

@ -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>

View file

@ -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

View file

@ -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;

View file

@ -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) {

View 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));
}
}
}

View file

@ -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"])#");
}
}

View file

@ -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}};

View file

@ -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;
}
}