1
0
Fork 0
mirror of https://github.com/NixOS/nix synced 2025-06-29 06:21:14 +02:00

Merge branch 'enum-class' into no-hash-type-unknown

This commit is contained in:
John Ericson 2020-06-18 21:58:27 +00:00
commit bbbf3602a3
151 changed files with 2757 additions and 1788 deletions

View file

@ -12,6 +12,17 @@ namespace nix {
#if __linux__
static bool didSaveAffinity = false;
static cpu_set_t savedAffinity;
std::ostream& operator<<(std::ostream &os, const cpu_set_t &cset)
{
auto count = CPU_COUNT(&cset);
for (int i=0; i < count; ++i)
{
os << (CPU_ISSET(i,&cset) ? "1" : "0");
}
return os;
}
#endif
@ -25,7 +36,7 @@ void setAffinityTo(int cpu)
CPU_ZERO(&newAffinity);
CPU_SET(cpu, &newAffinity);
if (sched_setaffinity(0, sizeof(cpu_set_t), &newAffinity) == -1)
printError(format("failed to lock thread to CPU %1%") % cpu);
printError("failed to lock thread to CPU %1%", cpu);
#endif
}
@ -47,7 +58,11 @@ void restoreAffinity()
#if __linux__
if (!didSaveAffinity) return;
if (sched_setaffinity(0, sizeof(cpu_set_t), &savedAffinity) == -1)
printError("failed to restore affinity %1%");
{
std::ostringstream oss;
oss << savedAffinity;
printError("failed to restore CPU affinity %1%", oss.str());
}
#endif
}

View file

@ -11,5 +11,7 @@ namespace nix {
#define ANSI_GREEN "\e[32;1m"
#define ANSI_YELLOW "\e[33;1m"
#define ANSI_BLUE "\e[34;1m"
#define ANSI_MAGENTA "\e[35m;1m"
#define ANSI_CYAN "\e[36m;1m"
}

View file

@ -46,7 +46,7 @@ static void dumpContents(const Path & path, size_t size,
sink << "contents" << size;
AutoCloseFD fd = open(path.c_str(), O_RDONLY | O_CLOEXEC);
if (!fd) throw SysError(format("opening file '%1%'") % path);
if (!fd) throw SysError("opening file '%1%'", path);
std::vector<unsigned char> buf(65536);
size_t left = size;
@ -68,7 +68,7 @@ static void dump(const Path & path, Sink & sink, PathFilter & filter)
struct stat st;
if (lstat(path.c_str(), &st))
throw SysError(format("getting attributes of path '%1%'") % path);
throw SysError("getting attributes of path '%1%'", path);
sink << "(";
@ -94,8 +94,9 @@ static void dump(const Path & path, Sink & sink, PathFilter & filter)
name.erase(pos);
}
if (unhacked.find(name) != unhacked.end())
throw Error(format("file name collision in between '%1%' and '%2%'")
% (path + "/" + unhacked[name]) % (path + "/" + i.name));
throw Error("file name collision in between '%1%' and '%2%'",
(path + "/" + unhacked[name]),
(path + "/" + i.name));
unhacked[name] = i.name;
} else
unhacked[i.name] = i.name;
@ -111,7 +112,7 @@ static void dump(const Path & path, Sink & sink, PathFilter & filter)
else if (S_ISLNK(st.st_mode))
sink << "type" << "symlink" << "target" << readLink(path);
else throw Error(format("file '%1%' has an unsupported type") % path);
else throw Error("file '%1%' has an unsupported type", path);
sink << ")";
}
@ -247,7 +248,7 @@ static void parse(ParseSink & sink, Source & source, const Path & path)
} else if (s == "name") {
name = readString(source);
if (name.empty() || name == "." || name == ".." || name.find('/') != string::npos || name.find((char) 0) != string::npos)
throw Error(format("NAR contains invalid file name '%1%'") % name);
throw Error("NAR contains invalid file name '%1%'", name);
if (name <= prevName)
throw Error("NAR directory is not sorted");
prevName = name;
@ -303,14 +304,14 @@ struct RestoreSink : ParseSink
{
Path p = dstPath + path;
if (mkdir(p.c_str(), 0777) == -1)
throw SysError(format("creating directory '%1%'") % p);
throw SysError("creating directory '%1%'", p);
};
void createRegularFile(const Path & path)
{
Path p = dstPath + path;
fd = open(p.c_str(), O_CREAT | O_EXCL | O_WRONLY | O_CLOEXEC, 0666);
if (!fd) throw SysError(format("creating file '%1%'") % p);
if (!fd) throw SysError("creating file '%1%'", p);
}
void isExecutable()
@ -332,7 +333,7 @@ struct RestoreSink : ParseSink
OpenSolaris). Since preallocation is just an
optimisation, ignore it. */
if (errno && errno != EINVAL && errno != EOPNOTSUPP && errno != ENOSYS)
throw SysError(format("preallocating file of %1% bytes") % len);
throw SysError("preallocating file of %1% bytes", len);
}
#endif
}

View file

@ -45,7 +45,7 @@ void Args::parseCmdline(const Strings & _cmdline)
}
else if (!dashDash && std::string(arg, 0, 1) == "-") {
if (!processFlag(pos, cmdline.end()))
throw UsageError(format("unrecognised flag '%1%'") % arg);
throw UsageError("unrecognised flag '%1%'", arg);
}
else {
pendingArgs.push_back(*pos++);
@ -130,7 +130,7 @@ bool Args::processArgs(const Strings & args, bool finish)
{
if (expectedArgs.empty()) {
if (!args.empty())
throw UsageError(format("unexpected argument '%1%'") % args.front());
throw UsageError("unexpected argument '%1%'", args.front());
return true;
}
@ -227,10 +227,15 @@ MultiCommand::MultiCommand(const Commands & commands)
{
expectedArgs.push_back(ExpectedArg{"command", 1, true, [=](std::vector<std::string> ss) {
assert(!command);
auto i = commands.find(ss[0]);
auto cmd = ss[0];
if (auto alias = get(deprecatedAliases, cmd)) {
warn("'%s' is a deprecated alias for '%s'", cmd, *alias);
cmd = *alias;
}
auto i = commands.find(cmd);
if (i == commands.end())
throw UsageError("'%s' is not a recognised command", ss[0]);
command = {ss[0], i->second()};
throw UsageError("'%s' is not a recognised command", cmd);
command = {cmd, i->second()};
}});
categories[Command::catDefault] = "Available commands";

View file

@ -235,6 +235,8 @@ public:
std::map<Command::Category, std::string> categories;
std::map<std::string, std::string> deprecatedAliases;
// Selected command, if any.
std::optional<std::pair<std::string, ref<Command>>> command;

View file

@ -481,7 +481,7 @@ ref<CompressionSink> makeCompressionSink(const std::string & method, Sink & next
else if (method == "br")
return make_ref<BrotliCompressionSink>(nextSink);
else
throw UnknownCompressionMethod(format("unknown compression method '%s'") % method);
throw UnknownCompressionMethod("unknown compression method '%s'", method);
}
ref<std::string> compress(const std::string & method, const std::string & in, const bool parallel)

View file

@ -1,3 +1,4 @@
#include <cassert>
#include <map>
#include <set>

View file

@ -2,9 +2,38 @@
#include <iostream>
#include <optional>
#include "serialise.hh"
#include <sstream>
namespace nix
namespace nix {
const std::string nativeSystem = SYSTEM;
// addPrefix is used for show-trace. Strings added with addPrefix
// will print ahead of the error itself.
BaseError & BaseError::addPrefix(const FormatOrString & fs)
{
prefix_ = fs.s + prefix_;
return *this;
}
// c++ std::exception descendants must have a 'const char* what()' function.
// This stringifies the error and caches it for use by what(), or similarly by msg().
const string& BaseError::calcWhat() const
{
if (what_.has_value())
return *what_;
else {
err.name = sname();
std::ostringstream oss;
oss << err;
what_ = oss.str();
return *what_;
}
}
std::optional<string> ErrorInfo::programName = std::nullopt;
@ -15,132 +44,178 @@ std::ostream& operator<<(std::ostream &os, const hintformat &hf)
string showErrPos(const ErrPos &errPos)
{
if (errPos.column > 0) {
return fmt("(%1%:%2%)", errPos.lineNumber, errPos.column);
} else {
return fmt("(%1%)", errPos.lineNumber);
};
if (errPos.line > 0) {
if (errPos.column > 0) {
return fmt("(%1%:%2%)", errPos.line, errPos.column);
} else {
return fmt("(%1%)", errPos.line);
}
}
else {
return "";
}
}
void printCodeLines(const string &prefix, const NixCode &nixCode)
// if nixCode contains lines of code, print them to the ostream, indicating the error column.
void printCodeLines(std::ostream &out, const string &prefix, const NixCode &nixCode)
{
// previous line of code.
if (nixCode.prevLineOfCode.has_value()) {
std::cout << fmt("%1% %|2$5d|| %3%",
prefix,
(nixCode.errPos.lineNumber - 1),
*nixCode.prevLineOfCode)
<< std::endl;
out << std::endl
<< fmt("%1% %|2$5d|| %3%",
prefix,
(nixCode.errPos.line - 1),
*nixCode.prevLineOfCode);
}
// line of code containing the error.%2$+5d%
std::cout << fmt("%1% %|2$5d|| %3%",
prefix,
(nixCode.errPos.lineNumber),
nixCode.errLineOfCode)
<< std::endl;
if (nixCode.errLineOfCode.has_value()) {
// line of code containing the error.
out << std::endl
<< fmt("%1% %|2$5d|| %3%",
prefix,
(nixCode.errPos.line),
*nixCode.errLineOfCode);
// error arrows for the column range.
if (nixCode.errPos.column > 0) {
int start = nixCode.errPos.column;
std::string spaces;
for (int i = 0; i < start; ++i) {
spaces.append(" ");
}
// error arrows for the column range.
if (nixCode.errPos.column > 0) {
int start = nixCode.errPos.column;
std::string spaces;
for (int i = 0; i < start; ++i) {
spaces.append(" ");
std::string arrows("^");
out << std::endl
<< fmt("%1% |%2%" ANSI_RED "%3%" ANSI_NORMAL,
prefix,
spaces,
arrows);
}
std::string arrows("^");
std::cout << fmt("%1% |%2%" ANSI_RED "%3%" ANSI_NORMAL,
prefix,
spaces,
arrows) << std::endl;
}
// next line of code.
if (nixCode.nextLineOfCode.has_value()) {
std::cout << fmt("%1% %|2$5d|| %3%",
prefix,
(nixCode.errPos.lineNumber + 1),
*nixCode.nextLineOfCode)
<< std::endl;
out << std::endl
<< fmt("%1% %|2$5d|| %3%",
prefix,
(nixCode.errPos.line + 1),
*nixCode.nextLineOfCode);
}
}
void printErrorInfo(const ErrorInfo &einfo)
std::ostream& operator<<(std::ostream &out, const ErrorInfo &einfo)
{
int errwidth = 80;
string prefix = " ";
auto errwidth = std::max<size_t>(getWindowSize().second, 20);
string prefix = "";
string levelString;
switch (einfo.level) {
case ErrLevel::elError: {
levelString = ANSI_RED;
levelString += "error:";
levelString += ANSI_NORMAL;
break;
}
case ErrLevel::elWarning: {
levelString = ANSI_YELLOW;
levelString += "warning:";
levelString += ANSI_NORMAL;
break;
}
default: {
levelString = fmt("invalid error level: %1%", einfo.level);
break;
}
}
int ndl = prefix.length() + levelString.length() + 3 + einfo.name.length() + einfo.programName.value_or("").length();
int dashwidth = ndl > (errwidth - 3) ? 3 : errwidth - ndl;
string dashes;
for (int i = 0; i < dashwidth; ++i)
dashes.append("-");
// divider.
std::cout << fmt("%1%%2%" ANSI_BLUE " %3% %4% %5% %6%" ANSI_NORMAL,
prefix,
levelString,
"---",
einfo.name,
dashes,
einfo.programName.value_or(""))
<< std::endl;
// filename.
if (einfo.nixCode.has_value()) {
if (einfo.nixCode->errPos.nixFile != "") {
string eline = einfo.nixCode->errLineOfCode != ""
? string(" ") + showErrPos(einfo.nixCode->errPos)
: "";
std::cout << fmt("%1%in file: " ANSI_BLUE "%2%%3%" ANSI_NORMAL,
prefix,
einfo.nixCode->errPos.nixFile,
eline) << std::endl;
std::cout << prefix << std::endl;
} else {
std::cout << fmt("%1%from command line argument", prefix) << std::endl;
std::cout << prefix << std::endl;
case Verbosity::Error: {
levelString = ANSI_RED;
levelString += "error:";
levelString += ANSI_NORMAL;
break;
}
case Verbosity::Warn: {
levelString = ANSI_YELLOW;
levelString += "warning:";
levelString += ANSI_NORMAL;
break;
}
case Verbosity::Info: {
levelString = ANSI_GREEN;
levelString += "info:";
levelString += ANSI_NORMAL;
break;
}
case Verbosity::Talkative: {
levelString = ANSI_GREEN;
levelString += "talk:";
levelString += ANSI_NORMAL;
break;
}
case Verbosity::Chatty: {
levelString = ANSI_GREEN;
levelString += "chat:";
levelString += ANSI_NORMAL;
break;
}
case Verbosity::Vomit: {
levelString = ANSI_GREEN;
levelString += "vomit:";
levelString += ANSI_NORMAL;
break;
}
case Verbosity::Debug: {
levelString = ANSI_YELLOW;
levelString += "debug:";
levelString += ANSI_NORMAL;
break;
}
default: {
levelString = fmt("invalid error level: %d", (uint8_t)einfo.level);
break;
}
}
auto ndl = prefix.length() + levelString.length() + 3 + einfo.name.length() + einfo.programName.value_or("").length();
auto dashwidth = ndl > (errwidth - 3) ? 3 : errwidth - ndl;
std::string dashes(dashwidth, '-');
// divider.
if (einfo.name != "")
out << fmt("%1%%2%" ANSI_BLUE " --- %3% %4% %5%" ANSI_NORMAL,
prefix,
levelString,
einfo.name,
dashes,
einfo.programName.value_or(""));
else
out << fmt("%1%%2%" ANSI_BLUE " -----%3% %4%" ANSI_NORMAL,
prefix,
levelString,
dashes,
einfo.programName.value_or(""));
bool nl = false; // intersperse newline between sections.
if (einfo.nixCode.has_value()) {
if (einfo.nixCode->errPos.file != "") {
// filename, line, column.
out << std::endl << fmt("%1%in file: " ANSI_BLUE "%2% %3%" ANSI_NORMAL,
prefix,
einfo.nixCode->errPos.file,
showErrPos(einfo.nixCode->errPos));
} else {
out << std::endl << fmt("%1%from command line argument", prefix);
}
nl = true;
}
// description
std::cout << prefix << einfo.description << std::endl;
std::cout << prefix << std::endl;
if (einfo.description != "") {
if (nl)
out << std::endl << prefix;
out << std::endl << prefix << einfo.description;
nl = true;
}
// lines of code.
if (einfo.nixCode->errLineOfCode != "") {
printCodeLines(prefix, *einfo.nixCode);
std::cout << prefix << std::endl;
if (einfo.nixCode.has_value() && einfo.nixCode->errLineOfCode.has_value()) {
if (nl)
out << std::endl << prefix;
printCodeLines(out, prefix, *einfo.nixCode);
nl = true;
}
// hint
if (einfo.hint.has_value()) {
std::cout << prefix << *einfo.hint << std::endl;
std::cout << prefix << std::endl;
if (nl)
out << std::endl << prefix;
out << std::endl << prefix << *einfo.hint;
nl = true;
}
}
return out;
}
}

View file

@ -1,107 +1,92 @@
#ifndef error_hh
#define error_hh
#pragma once
#include "ansicolor.hh"
#include <string>
#include <optional>
#include <iostream>
#include "ref.hh"
#include "types.hh"
namespace nix
{
#include <cstring>
#include <list>
#include <memory>
#include <map>
#include <optional>
typedef enum {
elWarning,
elError
} ErrLevel;
#include "fmt.hh"
struct ErrPos
{
int lineNumber;
int column;
string nixFile;
/* Before 4.7, gcc's std::exception uses empty throw() specifiers for
* its (virtual) destructor and what() in c++11 mode, in violation of spec
*/
#ifdef __GNUC__
#if __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 7)
#define EXCEPTION_NEEDS_THROW_SPEC
#endif
#endif
namespace nix {
/*
This file defines two main structs/classes used in nix error handling.
ErrorInfo provides a standard payload of error information, with conversion to string
happening in the logger rather than at the call site.
BaseError is the ancestor of nix specific exceptions (and Interrupted), and contains
an ErrorInfo.
ErrorInfo structs are sent to the logger as part of an exception, or directly with the
logError or logWarning macros.
See the error-demo.cc program for usage examples.
*/
enum struct Verbosity {
Error = 0,
Warn,
Info,
Talkative,
Chatty,
Debug,
Vomit,
};
// ErrPos indicates the location of an error in a nix file.
struct ErrPos {
int line = 0;
int column = 0;
string file;
operator bool() const
{
return line != 0;
}
// convert from the Pos struct, found in libexpr.
template <class P>
ErrPos& operator=(const P &pos)
{
lineNumber = pos.line;
line = pos.line;
column = pos.column;
nixFile = pos.file;
file = pos.file;
return *this;
}
template <class P>
ErrPos(const P &p)
{
*this = p;
*this = p;
}
};
struct NixCode
{
struct NixCode {
ErrPos errPos;
std::optional<string> prevLineOfCode;
string errLineOfCode;
std::optional<string> errLineOfCode;
std::optional<string> nextLineOfCode;
};
// ----------------------------------------------------------------
// format function for hints. same as fmt, except templated values
// are always in yellow.
template <class T>
struct yellowify
{
yellowify(T &s) : value(s) {}
T &value;
};
template <class T>
std::ostream& operator<<(std::ostream &out, const yellowify<T> &y)
{
return out << ANSI_YELLOW << y.value << ANSI_NORMAL;
}
class hintformat
{
public:
hintformat(string format) :fmt(format)
{
fmt.exceptions(boost::io::all_error_bits ^ boost::io::too_many_args_bit);
}
template<class T>
hintformat& operator%(const T &value)
{
fmt % yellowify(value);
return *this;
}
std::string str() const
{
return fmt.str();
}
template <typename U>
friend class AddHint;
private:
format fmt;
};
std::ostream& operator<<(std::ostream &os, const hintformat &hf);
template<typename... Args>
inline hintformat hintfmt(const std::string & fs, const Args & ... args)
{
hintformat f(fs);
formatHelper(f, args...);
return f;
}
// -------------------------------------------------
// ErrorInfo.
struct ErrorInfo
{
ErrLevel level;
struct ErrorInfo {
Verbosity level;
string name;
string description;
std::optional<hintformat> hint;
@ -110,12 +95,92 @@ struct ErrorInfo
static std::optional<string> programName;
};
// --------------------------------------------------------
// error printing
std::ostream& operator<<(std::ostream &out, const ErrorInfo &einfo);
// just to cout for now.
void printErrorInfo(const ErrorInfo &einfo);
/* BaseError should generally not be caught, as it has Interrupted as
a subclass. Catch Error instead. */
class BaseError : public std::exception
{
protected:
string prefix_; // used for location traces etc.
mutable ErrorInfo err;
mutable std::optional<string> what_;
const string& calcWhat() const;
public:
unsigned int status = 1; // exit status
template<typename... Args>
BaseError(unsigned int status, const Args & ... args)
: err { .level = Verbosity::Error,
.hint = hintfmt(args...)
}
, status(status)
{ }
template<typename... Args>
BaseError(const std::string & fs, const Args & ... args)
: err { .level = Verbosity::Error,
.hint = hintfmt(fs, args...)
}
{ }
BaseError(hintformat hint)
: err { .level = Verbosity::Error,
.hint = hint
}
{ }
BaseError(ErrorInfo && e)
: err(std::move(e))
{ }
BaseError(const ErrorInfo & e)
: err(e)
{ }
virtual const char* sname() const { return "BaseError"; }
#ifdef EXCEPTION_NEEDS_THROW_SPEC
~BaseError() throw () { };
const char * what() const throw () { return calcWhat().c_str(); }
#else
const char * what() const noexcept override { return calcWhat().c_str(); }
#endif
const string & msg() const { return calcWhat(); }
const string & prefix() const { return prefix_; }
BaseError & addPrefix(const FormatOrString & fs);
const ErrorInfo & info() { calcWhat(); return err; }
};
#define MakeError(newClass, superClass) \
class newClass : public superClass \
{ \
public: \
using superClass::superClass; \
virtual const char* sname() const override { return #newClass; } \
}
MakeError(Error, BaseError);
class SysError : public Error
{
public:
int errNo;
template<typename... Args>
SysError(const Args & ... args)
:Error("")
{
errNo = errno;
auto hf = hintfmt(args...);
err.hint = hintfmt("%1%: %2%", normaltxt(hf.str()), strerror(errNo));
}
virtual const char* sname() const override { return "SysError"; }
};
}
#endif

139
src/libutil/fmt.hh Normal file
View file

@ -0,0 +1,139 @@
#pragma once
#include <boost/format.hpp>
#include <string>
#include "ansicolor.hh"
namespace nix {
/* Inherit some names from other namespaces for convenience. */
using std::string;
using boost::format;
/* A variadic template that does nothing. Useful to call a function
for all variadic arguments but ignoring the result. */
struct nop { template<typename... T> nop(T...) {} };
struct FormatOrString
{
string s;
FormatOrString(const string & s) : s(s) { };
template<class F>
FormatOrString(const F & f) : s(f.str()) { };
FormatOrString(const char * s) : s(s) { };
};
/* A helper for formatting strings. fmt(format, a_0, ..., a_n) is
equivalent to boost::format(format) % a_0 % ... %
... a_n. However, fmt(s) is equivalent to s (so no %-expansion
takes place). */
template<class F>
inline void formatHelper(F & f)
{
}
template<class F, typename T, typename... Args>
inline void formatHelper(F & f, const T & x, const Args & ... args)
{
formatHelper(f % x, args...);
}
inline std::string fmt(const std::string & s)
{
return s;
}
inline std::string fmt(const char * s)
{
return s;
}
inline std::string fmt(const FormatOrString & fs)
{
return fs.s;
}
template<typename... Args>
inline std::string fmt(const std::string & fs, const Args & ... args)
{
boost::format f(fs);
f.exceptions(boost::io::all_error_bits ^ boost::io::too_many_args_bit);
formatHelper(f, args...);
return f.str();
}
// -----------------------------------------------------------------------------
// format function for hints in errors. same as fmt, except templated values
// are always in yellow.
template <class T>
struct yellowtxt
{
yellowtxt(const T &s) : value(s) {}
const T &value;
};
template <class T>
std::ostream& operator<<(std::ostream &out, const yellowtxt<T> &y)
{
return out << ANSI_YELLOW << y.value << ANSI_NORMAL;
}
template <class T>
struct normaltxt
{
normaltxt(const T &s) : value(s) {}
const T &value;
};
template <class T>
std::ostream& operator<<(std::ostream &out, const normaltxt<T> &y)
{
return out << ANSI_NORMAL << y.value;
}
class hintformat
{
public:
hintformat(const string &format) :fmt(format)
{
fmt.exceptions(boost::io::all_error_bits ^ boost::io::too_many_args_bit);
}
hintformat(const hintformat &hf)
: fmt(hf.fmt)
{}
template<class T>
hintformat& operator%(const T &value)
{
fmt % yellowtxt(value);
return *this;
}
std::string str() const
{
return fmt.str();
}
private:
format fmt;
};
std::ostream& operator<<(std::ostream &os, const hintformat &hf);
template<typename... Args>
inline hintformat hintfmt(const std::string & fs, const Args & ... args)
{
hintformat f(fs);
formatHelper(f, args...);
return f;
}
}

View file

@ -134,10 +134,10 @@ std::string Hash::to_string(Base base, bool includeType) const
return s;
}
Hash::Hash(const std::string & s, HashType type) : Hash(s, std::optional { type }) { }
Hash::Hash(const std::string & s) : Hash(s, std::optional<HashType>{}) { }
Hash::Hash(std::string_view s, HashType type) : Hash(s, std::optional { type }) { }
Hash::Hash(std::string_view s) : Hash(s, std::optional<HashType>{}) { }
Hash::Hash(const std::string & s, std::optional<HashType> type)
Hash::Hash(std::string_view s, std::optional<HashType> type)
: type(type)
{
size_t pos = 0;
@ -206,7 +206,7 @@ Hash::Hash(const std::string & s, std::optional<HashType> type)
}
else if (isSRI || size == base64Len()) {
auto d = base64Decode(std::string(s, pos));
auto d = base64Decode(s.substr(pos));
if (d.size() != hashSize)
throw BadHash("invalid %s hash '%s'", isSRI ? "SRI" : "base-64", s);
assert(hashSize);
@ -217,6 +217,18 @@ Hash::Hash(const std::string & s, std::optional<HashType> type)
throw BadHash("hash '%s' has wrong length for hash type '%s'", s, printHashType(*type));
}
Hash newHashAllowEmpty(std::string hashStr, std::optional<HashType> ht)
{
if (hashStr.empty()) {
if (!ht)
throw BadHash("empty hash requires explicit hash type");
Hash h(*ht);
warn("found empty hash, assuming '%s'", h.to_string(Base::SRI, true));
return h;
} else
return Hash(hashStr, ht);
}
union Ctx
{

View file

@ -25,7 +25,7 @@ const int sha512HashSize = 64;
extern const string base32Chars;
enum struct Base : int {
enum struct Base {
Base64,
Base32,
Base16,
@ -52,11 +52,11 @@ struct Hash
Subresource Integrity hash expression). If the 'type' argument
is not present, then the hash type must be specified in the
string. */
Hash(const std::string & s, std::optional<HashType> type);
Hash(std::string_view s, std::optional<HashType> type);
// type must be provided
Hash(const std::string & s, HashType type);
Hash(std::string_view s, HashType type);
// hash type must be part of string
Hash(const std::string & s);
Hash(std::string_view s);
void init();
@ -93,7 +93,7 @@ struct Hash
/* Return a string representation of the hash, in base-16, base-32
or base-64. By default, this is prefixed by the hash type
(e.g. "sha256:"). */
std::string to_string(Base base = Base::Base32, bool includeType = true) const;
std::string to_string(Base base, bool includeType) const;
std::string gitRev() const
{
@ -108,6 +108,8 @@ struct Hash
}
};
/* Helper that defaults empty hashes to the 0 hash. */
Hash newHashAllowEmpty(std::string hashStr, std::optional<HashType> ht);
/* Print a hash in base-16 if it's MD5, or base-32 otherwise. */
string printHash16or32(const Hash & hash);

View file

@ -173,7 +173,7 @@ JSONObject JSONPlaceholder::object()
JSONPlaceholder::~JSONPlaceholder()
{
assert(!first || std::uncaught_exception());
assert(!first || std::uncaught_exceptions());
}
}

View file

@ -7,5 +7,3 @@ libutil_DIR := $(d)
libutil_SOURCES := $(wildcard $(d)/*.cc)
libutil_LDFLAGS = $(LIBLZMA_LIBS) -lbz2 -pthread $(OPENSSL_LIBS) $(LIBBROTLI_LIBS) $(LIBARCHIVE_LIBS) $(BOOST_LDFLAGS) -lboost_context
libutil_LIBS = libnixrust

View file

@ -18,7 +18,7 @@ void setCurActivity(const ActivityId activityId)
curActivity = activityId;
}
Logger * logger = makeDefaultLogger();
Logger * logger = makeSimpleLogger(true);
void Logger::warn(const std::string & msg)
{
@ -35,13 +35,19 @@ class SimpleLogger : public Logger
public:
bool systemd, tty;
bool printBuildLogs;
SimpleLogger()
SimpleLogger(bool printBuildLogs)
: printBuildLogs(printBuildLogs)
{
systemd = getEnv("IN_SYSTEMD") == "1";
tty = isatty(STDERR_FILENO);
}
bool isVerbose() override {
return printBuildLogs;
}
void log(Verbosity lvl, const FormatOrString & fs) override
{
if (lvl > verbosity) return;
@ -63,13 +69,33 @@ public:
writeToStderr(prefix + filterANSIEscapes(fs.s, !tty) + "\n");
}
void logEI(const ErrorInfo & ei) override
{
std::stringstream oss;
oss << ei;
log(ei.level, oss.str());
}
void startActivity(ActivityId act, Verbosity lvl, ActivityType type,
const std::string & s, const Fields & fields, ActivityId parent)
override
override
{
if (lvl <= verbosity && !s.empty())
log(lvl, s + "...");
}
void result(ActivityId act, ResultType type, const Fields & fields) override
{
if (type == ResultType::BuildLogLine && printBuildLogs) {
auto lastLine = fields[0].s;
printError(lastLine);
}
else if (type == ResultType::PostBuildLogLine && printBuildLogs) {
auto lastLine = fields[0].s;
printError("post-build-hook: " + lastLine);
}
}
};
Verbosity verbosity = Verbosity::Info;
@ -94,9 +120,9 @@ void writeToStderr(const string & s)
}
}
Logger * makeDefaultLogger()
Logger * makeSimpleLogger(bool printBuildLogs)
{
return new SimpleLogger();
return new SimpleLogger(printBuildLogs);
}
std::atomic<uint64_t> nextId{(uint64_t) getpid() << 32};
@ -108,12 +134,15 @@ Activity::Activity(Logger & logger, Verbosity lvl, ActivityType type,
logger.startActivity(id, lvl, type, s, fields, parent);
}
struct JSONLogger : Logger
{
struct JSONLogger : Logger {
Logger & prevLogger;
JSONLogger(Logger & prevLogger) : prevLogger(prevLogger) { }
bool isVerbose() override {
return true;
}
void addFields(nlohmann::json & json, const Fields & fields)
{
if (fields.empty()) return;
@ -141,6 +170,19 @@ struct JSONLogger : Logger
write(json);
}
void logEI(const ErrorInfo & ei) override
{
std::ostringstream oss;
oss << ei;
nlohmann::json json;
json["action"] = "msg";
json["level"] = ei.level;
json["msg"] = oss.str();
write(json);
}
void startActivity(ActivityId act, Verbosity lvl, ActivityType type,
const std::string & s, const Fields & fields, ActivityId parent) override
{
@ -231,13 +273,17 @@ bool handleJSONLogMessage(const std::string & msg,
}
} catch (std::exception & e) {
printError("bad log message from builder: %s", e.what());
logError({
.name = "Json log message",
.hint = hintfmt("bad log message from builder: %s", e.what())
});
}
return true;
}
Activity::~Activity() {
Activity::~Activity()
{
try {
logger.stopActivity(id);
} catch (...) {

View file

@ -1,20 +1,11 @@
#pragma once
#include "types.hh"
#include "error.hh"
namespace nix {
enum struct Verbosity : uint64_t {
Error = 0,
Warn,
Info,
Talkative,
Chatty,
Debug,
Vomit,
};
enum struct ActivityType : uint64_t {
enum struct ActivityType {
Unknown = 0,
CopyPath = 100,
Download = 101,
@ -27,9 +18,10 @@ enum struct ActivityType : uint64_t {
Substitute = 108,
QueryPathInfo = 109,
PostBuildHook = 110,
BuildWaiting = 111,
};
enum struct ResultType : uint64_t {
enum struct ResultType {
FileLinked = 100,
BuildLogLine = 101,
UntrustedPath = 102,
@ -63,6 +55,11 @@ public:
virtual ~Logger() { }
virtual void stop() { };
// Whether the logger prints the whole build log
virtual bool isVerbose() { return false; }
virtual void log(Verbosity lvl, const FormatOrString & fs) = 0;
void log(const FormatOrString & fs)
@ -70,6 +67,14 @@ public:
log(Verbosity::Info, fs);
}
virtual void logEI(const ErrorInfo &ei) = 0;
void logEI(Verbosity lvl, ErrorInfo ei)
{
ei.level = lvl;
logEI(ei);
}
virtual void warn(const std::string & msg);
virtual void startActivity(ActivityId act, Verbosity lvl, ActivityType type,
@ -141,7 +146,7 @@ struct PushActivity
extern Logger * logger;
Logger * makeDefaultLogger();
Logger * makeSimpleLogger(bool printBuildLogs = true);
Logger * makeJSONLogger(Logger & prevLogger);
@ -151,9 +156,23 @@ bool handleJSONLogMessage(const std::string & msg,
extern Verbosity verbosity; /* suppress msgs > this */
/* Print a message if the current log level is at least the specified
level. Note that this has to be implemented as a macro to ensure
that the arguments are evaluated lazily. */
/* Print a message with the standard ErrorInfo format.
In general, use these 'log' macros for reporting problems that may require user
intervention or that need more explanation. Use the 'print' macros for more
lightweight status messages. */
#define logErrorInfo(level, errorInfo...) \
do { \
if (level <= nix::verbosity) { \
logger->logEI(level, errorInfo); \
} \
} while (0)
#define logError(errorInfo...) logErrorInfo(Verbosity::Error, errorInfo)
#define logWarning(errorInfo...) logErrorInfo(Verbosity::Warn, errorInfo)
/* Print a string message if the current log level is at least the specified
level. Note that this has to be implemented as a macro to ensure that the
arguments are evaluated lazily. */
#define printMsg(level, args...) \
do { \
if (level <= nix::verbosity) { \
@ -167,6 +186,7 @@ extern Verbosity verbosity; /* suppress msgs > this */
#define debug(args...) printMsg(Verbosity::Debug, args)
#define vomit(args...) printMsg(Verbosity::Vomit, args)
/* if verbosity >= Verbosity::Warn, print a message with a yellow 'warning:' prefix. */
template<typename... Args>
inline void warn(const std::string & fs, const Args & ... args)
{

View file

@ -1,5 +1,6 @@
#pragma once
#include <cassert>
#include <map>
#include <list>
#include <optional>

View file

@ -1,3 +1,4 @@
#if 0
#include "logging.hh"
#include "rust-ffi.hh"
@ -20,3 +21,4 @@ std::ostream & operator << (std::ostream & str, const String & s)
}
}
#endif

View file

@ -1,4 +1,5 @@
#pragma once
#if 0
#include "serialise.hh"
@ -185,3 +186,4 @@ struct Result
};
}
#endif

View file

@ -52,7 +52,10 @@ size_t threshold = 256 * 1024 * 1024;
static void warnLargeDump()
{
printError("warning: dumping very large path (> 256 MiB); this may run out of memory");
logWarning({
.name = "Large path",
.description = "dumping very large path (> 256 MiB); this may run out of memory"
});
}

View file

@ -11,28 +11,28 @@ namespace nix {
// values taken from: https://tools.ietf.org/html/rfc1321
auto s1 = "";
auto hash = hashString(HashType::MD5, s1);
ASSERT_EQ(hash.to_string(Base::Base16), "md5:d41d8cd98f00b204e9800998ecf8427e");
ASSERT_EQ(hash.to_string(Base::Base16, true), "md5:d41d8cd98f00b204e9800998ecf8427e");
}
TEST(hashString, testKnownMD5Hashes2) {
// values taken from: https://tools.ietf.org/html/rfc1321
auto s2 = "abc";
auto hash = hashString(HashType::MD5, s2);
ASSERT_EQ(hash.to_string(Base::Base16), "md5:900150983cd24fb0d6963f7d28e17f72");
ASSERT_EQ(hash.to_string(Base::Base16, true), "md5:900150983cd24fb0d6963f7d28e17f72");
}
TEST(hashString, testKnownSHA1Hashes1) {
// values taken from: https://tools.ietf.org/html/rfc3174
auto s = "abc";
auto hash = hashString(HashType::SHA1, s);
ASSERT_EQ(hash.to_string(Base::Base16),"sha1:a9993e364706816aba3e25717850c26c9cd0d89d");
ASSERT_EQ(hash.to_string(Base::Base16, true),"sha1:a9993e364706816aba3e25717850c26c9cd0d89d");
}
TEST(hashString, testKnownSHA1Hashes2) {
// values taken from: https://tools.ietf.org/html/rfc3174
auto s = "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq";
auto hash = hashString(HashType::SHA1, s);
ASSERT_EQ(hash.to_string(Base::Base16),"sha1:84983e441c3bd26ebaae4aa1f95129e5e54670f1");
ASSERT_EQ(hash.to_string(Base::Base16, true),"sha1:84983e441c3bd26ebaae4aa1f95129e5e54670f1");
}
TEST(hashString, testKnownSHA256Hashes1) {
@ -40,7 +40,7 @@ namespace nix {
auto s = "abc";
auto hash = hashString(HashType::SHA256, s);
ASSERT_EQ(hash.to_string(Base::Base16),
ASSERT_EQ(hash.to_string(Base::Base16, true),
"sha256:ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad");
}
@ -48,7 +48,7 @@ namespace nix {
// values taken from: https://tools.ietf.org/html/rfc4634
auto s = "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq";
auto hash = hashString(HashType::SHA256, s);
ASSERT_EQ(hash.to_string(Base::Base16),
ASSERT_EQ(hash.to_string(Base::Base16, true),
"sha256:248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1");
}
@ -56,7 +56,7 @@ namespace nix {
// values taken from: https://tools.ietf.org/html/rfc4634
auto s = "abc";
auto hash = hashString(HashType::SHA512, s);
ASSERT_EQ(hash.to_string(Base::Base16),
ASSERT_EQ(hash.to_string(Base::Base16, true),
"sha512:ddaf35a193617abacc417349ae20413112e6fa4e89a9"
"7ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd"
"454d4423643ce80e2a9ac94fa54ca49f");
@ -67,7 +67,7 @@ namespace nix {
auto s = "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu";
auto hash = hashString(HashType::SHA512, s);
ASSERT_EQ(hash.to_string(Base::Base16),
ASSERT_EQ(hash.to_string(Base::Base16, true),
"sha512:8e959b75dae313da8cf4f72814fc143f8f7779c6eb9f7fa1"
"7299aeadb6889018501d289e4900f7e4331b99dec4b5433a"
"c7d329eeb6dd26545e96e55b874be909");

View file

@ -8,7 +8,7 @@ libutil-tests_INSTALL_DIR :=
libutil-tests_SOURCES := $(wildcard $(d)/*.cc)
libutil-tests_CXXFLAGS += -I src/libutil
libutil-tests_CXXFLAGS += -I src/libutil -I src/libexpr
libutil-tests_LIBS = libutil

View file

@ -0,0 +1,255 @@
#include "logging.hh"
#include "nixexpr.hh"
#include "util.hh"
#include <gtest/gtest.h>
namespace nix {
/* ----------------------------------------------------------------------------
* logEI
* --------------------------------------------------------------------------*/
TEST(logEI, catpuresBasicProperties) {
MakeError(TestError, Error);
ErrorInfo::programName = std::optional("error-unit-test");
try {
throw TestError("an error for testing purposes");
} catch (Error &e) {
testing::internal::CaptureStderr();
logger->logEI(e.info());
auto str = testing::internal::GetCapturedStderr();
ASSERT_STREQ(str.c_str(),"\x1B[31;1merror:\x1B[0m\x1B[34;1m --- TestError --- error-unit-test\x1B[0m\nan error for testing purposes\n");
}
}
TEST(logEI, appendingHintsToPreviousError) {
MakeError(TestError, Error);
ErrorInfo::programName = std::optional("error-unit-test");
try {
auto e = Error("initial error");
throw TestError(e.info());
} catch (Error &e) {
ErrorInfo ei = e.info();
ei.hint = hintfmt("%s; subsequent error message.", normaltxt(e.info().hint ? e.info().hint->str() : ""));
testing::internal::CaptureStderr();
logger->logEI(ei);
auto str = testing::internal::GetCapturedStderr();
ASSERT_STREQ(str.c_str(), "\x1B[31;1merror:\x1B[0m\x1B[34;1m --- TestError --- error-unit-test\x1B[0m\n\x1B[33;1m\x1B[0minitial error\x1B[0m; subsequent error message.\n");
}
}
TEST(logEI, picksUpSysErrorExitCode) {
MakeError(TestError, Error);
ErrorInfo::programName = std::optional("error-unit-test");
try {
auto x = readFile(-1);
}
catch (SysError &e) {
testing::internal::CaptureStderr();
logError(e.info());
auto str = testing::internal::GetCapturedStderr();
ASSERT_STREQ(str.c_str(), "\x1B[31;1merror:\x1B[0m\x1B[34;1m --- SysError --- error-unit-test\x1B[0m\n\x1B[33;1m\x1B[0mstatting file\x1B[0m: \x1B[33;1mBad file descriptor\x1B[0m\n");
}
}
TEST(logEI, loggingErrorOnInfoLevel) {
testing::internal::CaptureStderr();
logger->logEI({ .level = Verbosity::Info,
.name = "Info name",
.description = "Info description",
});
auto str = testing::internal::GetCapturedStderr();
ASSERT_STREQ(str.c_str(), "\x1B[32;1minfo:\x1B[0m\x1B[34;1m --- Info name --- error-unit-test\x1B[0m\nInfo description\n");
}
TEST(logEI, loggingErrorOnTalkativeLevel) {
verbosity = Verbosity::Talkative;
testing::internal::CaptureStderr();
logger->logEI({ .level = Verbosity::Talkative,
.name = "Talkative name",
.description = "Talkative description",
});
auto str = testing::internal::GetCapturedStderr();
ASSERT_STREQ(str.c_str(), "\x1B[32;1mtalk:\x1B[0m\x1B[34;1m --- Talkative name --- error-unit-test\x1B[0m\nTalkative description\n");
}
TEST(logEI, loggingErrorOnChattyLevel) {
verbosity = Verbosity::Chatty;
testing::internal::CaptureStderr();
logger->logEI({ .level = Verbosity::Chatty,
.name = "Chatty name",
.description = "Talkative description",
});
auto str = testing::internal::GetCapturedStderr();
ASSERT_STREQ(str.c_str(), "\x1B[32;1mchat:\x1B[0m\x1B[34;1m --- Chatty name --- error-unit-test\x1B[0m\nTalkative description\n");
}
TEST(logEI, loggingErrorOnDebugLevel) {
verbosity = Verbosity::Debug;
testing::internal::CaptureStderr();
logger->logEI({ .level = Verbosity::Debug,
.name = "Debug name",
.description = "Debug description",
});
auto str = testing::internal::GetCapturedStderr();
ASSERT_STREQ(str.c_str(), "\x1B[33;1mdebug:\x1B[0m\x1B[34;1m --- Debug name --- error-unit-test\x1B[0m\nDebug description\n");
}
TEST(logEI, loggingErrorOnVomitLevel) {
verbosity = Verbosity::Vomit;
testing::internal::CaptureStderr();
logger->logEI({ .level = Verbosity::Vomit,
.name = "Vomit name",
.description = "Vomit description",
});
auto str = testing::internal::GetCapturedStderr();
ASSERT_STREQ(str.c_str(), "\x1B[32;1mvomit:\x1B[0m\x1B[34;1m --- Vomit name --- error-unit-test\x1B[0m\nVomit description\n");
}
/* ----------------------------------------------------------------------------
* logError
* --------------------------------------------------------------------------*/
TEST(logError, logErrorWithoutHintOrCode) {
testing::internal::CaptureStderr();
logError({
.name = "name",
.description = "error description",
});
auto str = testing::internal::GetCapturedStderr();
ASSERT_STREQ(str.c_str(), "\x1B[31;1merror:\x1B[0m\x1B[34;1m --- name --- error-unit-test\x1B[0m\nerror description\n");
}
TEST(logError, logErrorWithPreviousAndNextLinesOfCode) {
SymbolTable testTable;
auto problem_file = testTable.create("myfile.nix");
testing::internal::CaptureStderr();
logError({
.name = "error name",
.description = "error with code lines",
.hint = hintfmt("this hint has %1% templated %2%!!",
"yellow",
"values"),
.nixCode = NixCode {
.errPos = Pos(problem_file, 40, 13),
.prevLineOfCode = "previous line of code",
.errLineOfCode = "this is the problem line of code",
.nextLineOfCode = "next line of code",
}});
auto str = testing::internal::GetCapturedStderr();
ASSERT_STREQ(str.c_str(), "\x1B[31;1merror:\x1B[0m\x1B[34;1m --- error name --- error-unit-test\x1B[0m\nin file: \x1B[34;1mmyfile.nix (40:13)\x1B[0m\n\nerror with code lines\n\n 39| previous line of code\n 40| this is the problem line of code\n | \x1B[31;1m^\x1B[0m\n 41| next line of code\n\nthis hint has \x1B[33;1myellow\x1B[0m templated \x1B[33;1mvalues\x1B[0m!!\n");
}
TEST(logError, logErrorWithoutLinesOfCode) {
SymbolTable testTable;
auto problem_file = testTable.create("myfile.nix");
testing::internal::CaptureStderr();
logError({
.name = "error name",
.description = "error without any code lines.",
.hint = hintfmt("this hint has %1% templated %2%!!",
"yellow",
"values"),
.nixCode = NixCode {
.errPos = Pos(problem_file, 40, 13)
}});
auto str = testing::internal::GetCapturedStderr();
ASSERT_STREQ(str.c_str(), "\x1B[31;1merror:\x1B[0m\x1B[34;1m --- error name --- error-unit-test\x1B[0m\nin file: \x1B[34;1mmyfile.nix (40:13)\x1B[0m\n\nerror without any code lines.\n\nthis hint has \x1B[33;1myellow\x1B[0m templated \x1B[33;1mvalues\x1B[0m!!\n");
}
TEST(logError, logErrorWithOnlyHintAndName) {
SymbolTable testTable;
auto problem_file = testTable.create("myfile.nix");
testing::internal::CaptureStderr();
logError({
.name = "error name",
.hint = hintfmt("hint %1%", "only"),
.nixCode = NixCode {
.errPos = Pos(problem_file, 40, 13)
}});
auto str = testing::internal::GetCapturedStderr();
ASSERT_STREQ(str.c_str(), "\x1B[31;1merror:\x1B[0m\x1B[34;1m --- error name --- error-unit-test\x1B[0m\nin file: \x1B[34;1mmyfile.nix (40:13)\x1B[0m\n\nhint \x1B[33;1monly\x1B[0m\n");
}
/* ----------------------------------------------------------------------------
* logWarning
* --------------------------------------------------------------------------*/
TEST(logWarning, logWarningWithNameDescriptionAndHint) {
testing::internal::CaptureStderr();
logWarning({
.name = "name",
.description = "error description",
.hint = hintfmt("there was a %1%", "warning"),
});
auto str = testing::internal::GetCapturedStderr();
ASSERT_STREQ(str.c_str(), "\x1B[33;1mwarning:\x1B[0m\x1B[34;1m --- name --- error-unit-test\x1B[0m\nerror description\n\nthere was a \x1B[33;1mwarning\x1B[0m\n");
}
TEST(logWarning, logWarningWithFileLineNumAndCode) {
SymbolTable testTable;
auto problem_file = testTable.create("myfile.nix");
testing::internal::CaptureStderr();
logWarning({
.name = "warning name",
.description = "warning description",
.hint = hintfmt("this hint has %1% templated %2%!!",
"yellow",
"values"),
.nixCode = NixCode {
.errPos = Pos(problem_file, 40, 13),
.prevLineOfCode = std::nullopt,
.errLineOfCode = "this is the problem line of code",
.nextLineOfCode = std::nullopt
}});
auto str = testing::internal::GetCapturedStderr();
ASSERT_STREQ(str.c_str(), "\x1B[33;1mwarning:\x1B[0m\x1B[34;1m --- warning name --- error-unit-test\x1B[0m\nin file: \x1B[34;1mmyfile.nix (40:13)\x1B[0m\n\nwarning description\n\n 40| this is the problem line of code\n | \x1B[31;1m^\x1B[0m\n\nthis hint has \x1B[33;1myellow\x1B[0m templated \x1B[33;1mvalues\x1B[0m!!\n");
}
}

127
src/libutil/tests/pool.cc Normal file
View file

@ -0,0 +1,127 @@
#include "pool.hh"
#include <gtest/gtest.h>
namespace nix {
struct TestResource
{
TestResource() {
static int counter = 0;
num = counter++;
}
int dummyValue = 1;
bool good = true;
int num;
};
/* ----------------------------------------------------------------------------
* Pool
* --------------------------------------------------------------------------*/
TEST(Pool, freshPoolHasZeroCountAndSpecifiedCapacity) {
auto isGood = [](const ref<TestResource> & r) { return r->good; };
auto createResource = []() { return make_ref<TestResource>(); };
Pool<TestResource> pool = Pool<TestResource>((size_t)1, createResource, isGood);
ASSERT_EQ(pool.count(), 0);
ASSERT_EQ(pool.capacity(), 1);
}
TEST(Pool, freshPoolCanGetAResource) {
auto isGood = [](const ref<TestResource> & r) { return r->good; };
auto createResource = []() { return make_ref<TestResource>(); };
Pool<TestResource> pool = Pool<TestResource>((size_t)1, createResource, isGood);
ASSERT_EQ(pool.count(), 0);
TestResource r = *(pool.get());
ASSERT_EQ(pool.count(), 1);
ASSERT_EQ(pool.capacity(), 1);
ASSERT_EQ(r.dummyValue, 1);
ASSERT_EQ(r.good, true);
}
TEST(Pool, capacityCanBeIncremented) {
auto isGood = [](const ref<TestResource> & r) { return r->good; };
auto createResource = []() { return make_ref<TestResource>(); };
Pool<TestResource> pool = Pool<TestResource>((size_t)1, createResource, isGood);
ASSERT_EQ(pool.capacity(), 1);
pool.incCapacity();
ASSERT_EQ(pool.capacity(), 2);
}
TEST(Pool, capacityCanBeDecremented) {
auto isGood = [](const ref<TestResource> & r) { return r->good; };
auto createResource = []() { return make_ref<TestResource>(); };
Pool<TestResource> pool = Pool<TestResource>((size_t)1, createResource, isGood);
ASSERT_EQ(pool.capacity(), 1);
pool.decCapacity();
ASSERT_EQ(pool.capacity(), 0);
}
TEST(Pool, flushBadDropsOutOfScopeResources) {
auto isGood = [](const ref<TestResource> & r) { return false; };
auto createResource = []() { return make_ref<TestResource>(); };
Pool<TestResource> pool = Pool<TestResource>((size_t)1, createResource, isGood);
{
auto _r = pool.get();
ASSERT_EQ(pool.count(), 1);
}
pool.flushBad();
ASSERT_EQ(pool.count(), 0);
}
// Test that the resources we allocate are being reused when they are still good.
TEST(Pool, reuseResource) {
auto isGood = [](const ref<TestResource> & r) { return true; };
auto createResource = []() { return make_ref<TestResource>(); };
Pool<TestResource> pool = Pool<TestResource>((size_t)1, createResource, isGood);
// Compare the instance counter between the two handles. We expect them to be equal
// as the pool should hand out the same (still) good one again.
int counter = -1;
{
Pool<TestResource>::Handle h = pool.get();
counter = h->num;
} // the first handle goes out of scope
{ // the second handle should contain the same resource (with the same counter value)
Pool<TestResource>::Handle h = pool.get();
ASSERT_EQ(h->num, counter);
}
}
// Test that the resources we allocate are being thrown away when they are no longer good.
TEST(Pool, badResourceIsNotReused) {
auto isGood = [](const ref<TestResource> & r) { return false; };
auto createResource = []() { return make_ref<TestResource>(); };
Pool<TestResource> pool = Pool<TestResource>((size_t)1, createResource, isGood);
// Compare the instance counter between the two handles. We expect them
// to *not* be equal as the pool should hand out a new instance after
// the first one was returned.
int counter = -1;
{
Pool<TestResource>::Handle h = pool.get();
counter = h->num;
} // the first handle goes out of scope
{
// the second handle should contain a different resource (with a
//different counter value)
Pool<TestResource>::Handle h = pool.get();
ASSERT_NE(h->num, counter);
}
}
}

View file

@ -1,164 +1,29 @@
#pragma once
#include "ref.hh"
#include <string>
#include <list>
#include <set>
#include <memory>
#include <map>
#include <boost/format.hpp>
/* Before 4.7, gcc's std::exception uses empty throw() specifiers for
* its (virtual) destructor and what() in c++11 mode, in violation of spec
*/
#ifdef __GNUC__
#if __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 7)
#define EXCEPTION_NEEDS_THROW_SPEC
#endif
#endif
#include <vector>
namespace nix {
/* Inherit some names from other namespaces for convenience. */
using std::string;
using std::list;
using std::set;
using std::vector;
using boost::format;
/* A variadic template that does nothing. Useful to call a function
for all variadic arguments but ignoring the result. */
struct nop { template<typename... T> nop(T...) {} };
struct FormatOrString
{
string s;
FormatOrString(const string & s) : s(s) { };
template<class F>
FormatOrString(const F & f) : s(f.str()) { };
FormatOrString(const char * s) : s(s) { };
};
/* A helper for formatting strings. fmt(format, a_0, ..., a_n) is
equivalent to boost::format(format) % a_0 % ... %
... a_n. However, fmt(s) is equivalent to s (so no %-expansion
takes place). */
template<class F>
inline void formatHelper(F & f)
{
}
template<class F, typename T, typename... Args>
inline void formatHelper(F & f, const T & x, const Args & ... args)
{
formatHelper(f % x, args...);
}
inline std::string fmt(const std::string & s)
{
return s;
}
inline std::string fmt(const char * s)
{
return s;
}
inline std::string fmt(const FormatOrString & fs)
{
return fs.s;
}
template<typename... Args>
inline std::string fmt(const std::string & fs, const Args & ... args)
{
boost::format f(fs);
f.exceptions(boost::io::all_error_bits ^ boost::io::too_many_args_bit);
formatHelper(f, args...);
return f.str();
}
/* BaseError should generally not be caught, as it has Interrupted as
a subclass. Catch Error instead. */
class BaseError : public std::exception
{
protected:
string prefix_; // used for location traces etc.
string err;
public:
unsigned int status = 1; // exit status
template<typename... Args>
BaseError(unsigned int status, const Args & ... args)
: err(fmt(args...))
, status(status)
{
}
template<typename... Args>
BaseError(const Args & ... args)
: err(fmt(args...))
{
}
#ifdef EXCEPTION_NEEDS_THROW_SPEC
~BaseError() throw () { };
const char * what() const throw () { return err.c_str(); }
#else
const char * what() const noexcept { return err.c_str(); }
#endif
const string & msg() const { return err; }
const string & prefix() const { return prefix_; }
BaseError & addPrefix(const FormatOrString & fs);
};
#define MakeError(newClass, superClass) \
class newClass : public superClass \
{ \
public: \
using superClass::superClass; \
}
MakeError(Error, BaseError);
class SysError : public Error
{
public:
int errNo;
template<typename... Args>
SysError(const Args & ... args)
: Error(addErrno(fmt(args...)))
{ }
private:
std::string addErrno(const std::string & s);
};
using std::string;
typedef list<string> Strings;
typedef set<string> StringSet;
typedef std::map<std::string, std::string> StringMap;
typedef std::map<string, string> StringMap;
/* Paths are just strings. */
typedef string Path;
typedef list<Path> Paths;
typedef set<Path> PathSet;
/* Helper class to run code at startup. */
template<typename T>
struct OnStartup
@ -166,5 +31,4 @@ struct OnStartup
OnStartup(T && t) { t(); }
};
}

View file

@ -1,6 +1,6 @@
#pragma once
#include "types.hh"
#include "error.hh"
#include <regex>

View file

@ -35,29 +35,11 @@
#endif
extern char * * environ;
extern char * * environ __attribute__((weak));
namespace nix {
const std::string nativeSystem = SYSTEM;
BaseError & BaseError::addPrefix(const FormatOrString & fs)
{
prefix_ = fs.s + prefix_;
return *this;
}
std::string SysError::addErrno(const std::string & s)
{
errNo = errno;
return s + ": " + strerror(errNo);
}
std::optional<std::string> getEnv(const std::string & key)
{
char * value = getenv(key.c_str());
@ -129,7 +111,7 @@ Path canonPath(const Path & path, bool resolveSymlinks)
string s;
if (path[0] != '/')
throw Error(format("not an absolute path: '%1%'") % path);
throw Error("not an absolute path: '%1%'", path);
string::const_iterator i = path.begin(), end = path.end();
string temp;
@ -165,7 +147,7 @@ Path canonPath(const Path & path, bool resolveSymlinks)
the symlink target might contain new symlinks). */
if (resolveSymlinks && isLink(s)) {
if (++followCount >= maxFollow)
throw Error(format("infinite symlink recursion in path '%1%'") % path);
throw Error("infinite symlink recursion in path '%1%'", path);
temp = absPath(readLink(s), dirOf(s))
+ string(i, end);
i = temp.begin(); /* restart */
@ -226,7 +208,7 @@ struct stat lstat(const Path & path)
{
struct stat st;
if (lstat(path.c_str(), &st))
throw SysError(format("getting status of '%1%'") % path);
throw SysError("getting status of '%1%'", path);
return st;
}
@ -238,7 +220,7 @@ bool pathExists(const Path & path)
res = lstat(path.c_str(), &st);
if (!res) return true;
if (errno != ENOENT && errno != ENOTDIR)
throw SysError(format("getting status of %1%") % path);
throw SysError("getting status of %1%", path);
return false;
}
@ -286,7 +268,7 @@ DirEntries readDirectory(DIR *dir, const Path & path)
#endif
);
}
if (errno) throw SysError(format("reading directory '%1%'") % path);
if (errno) throw SysError("reading directory '%1%'", path);
return entries;
}
@ -294,7 +276,7 @@ DirEntries readDirectory(DIR *dir, const Path & path)
DirEntries readDirectory(const Path & path)
{
AutoCloseDir dir(opendir(path.c_str()));
if (!dir) throw SysError(format("opening directory '%1%'") % path);
if (!dir) throw SysError("opening directory '%1%'", path);
return readDirectory(dir.get(), path);
}
@ -324,7 +306,7 @@ string readFile(const Path & path)
{
AutoCloseFD fd = open(path.c_str(), O_RDONLY | O_CLOEXEC);
if (!fd)
throw SysError(format("opening file '%1%'") % path);
throw SysError("opening file '%1%'", path);
return readFile(fd.get());
}
@ -332,7 +314,8 @@ string readFile(const Path & path)
void readFile(const Path & path, Sink & sink)
{
AutoCloseFD fd = open(path.c_str(), O_RDONLY | O_CLOEXEC);
if (!fd) throw SysError("opening file '%s'", path);
if (!fd)
throw SysError("opening file '%s'", path);
drainFD(fd.get(), sink);
}
@ -341,7 +324,7 @@ void writeFile(const Path & path, const string & s, mode_t mode)
{
AutoCloseFD fd = open(path.c_str(), O_WRONLY | O_TRUNC | O_CREAT | O_CLOEXEC, mode);
if (!fd)
throw SysError(format("opening file '%1%'") % path);
throw SysError("opening file '%1%'", path);
writeFull(fd.get(), s);
}
@ -350,7 +333,7 @@ void writeFile(const Path & path, Source & source, mode_t mode)
{
AutoCloseFD fd = open(path.c_str(), O_WRONLY | O_TRUNC | O_CREAT | O_CLOEXEC, mode);
if (!fd)
throw SysError(format("opening file '%1%'") % path);
throw SysError("opening file '%1%'", path);
std::vector<unsigned char> buf(64 * 1024);
@ -400,7 +383,7 @@ static void _deletePath(int parentfd, const Path & path, unsigned long long & by
struct stat st;
if (fstatat(parentfd, name.c_str(), &st, AT_SYMLINK_NOFOLLOW) == -1) {
if (errno == ENOENT) return;
throw SysError(format("getting status of '%1%'") % path);
throw SysError("getting status of '%1%'", path);
}
if (!S_ISDIR(st.st_mode) && st.st_nlink == 1)
@ -411,15 +394,15 @@ static void _deletePath(int parentfd, const Path & path, unsigned long long & by
const auto PERM_MASK = S_IRUSR | S_IWUSR | S_IXUSR;
if ((st.st_mode & PERM_MASK) != PERM_MASK) {
if (fchmodat(parentfd, name.c_str(), st.st_mode | PERM_MASK, 0) == -1)
throw SysError(format("chmod '%1%'") % path);
throw SysError("chmod '%1%'", path);
}
int fd = openat(parentfd, path.c_str(), O_RDONLY);
if (!fd)
throw SysError(format("opening directory '%1%'") % path);
throw SysError("opening directory '%1%'", path);
AutoCloseDir dir(fdopendir(fd));
if (!dir)
throw SysError(format("opening directory '%1%'") % path);
throw SysError("opening directory '%1%'", path);
for (auto & i : readDirectory(dir.get(), path))
_deletePath(dirfd(dir.get()), path + "/" + i.name, bytesFreed);
}
@ -427,7 +410,7 @@ static void _deletePath(int parentfd, const Path & path, unsigned long long & by
int flags = S_ISDIR(st.st_mode) ? AT_REMOVEDIR : 0;
if (unlinkat(parentfd, name.c_str(), flags) == -1) {
if (errno == ENOENT) return;
throw SysError(format("cannot unlink '%1%'") % path);
throw SysError("cannot unlink '%1%'", path);
}
}
@ -443,7 +426,7 @@ static void _deletePath(const Path & path, unsigned long long & bytesFreed)
// for backwards compatibility.
if (errno == ENOENT) return;
throw SysError(format("opening directory '%1%'") % path);
throw SysError("opening directory '%1%'", path);
}
_deletePath(dirfd.get(), path, bytesFreed);
@ -497,12 +480,12 @@ Path createTempDir(const Path & tmpRoot, const Path & prefix,
"wheel", then "tar" will fail to unpack archives that
have the setgid bit set on directories. */
if (chown(tmpDir.c_str(), (uid_t) -1, getegid()) != 0)
throw SysError(format("setting group of directory '%1%'") % tmpDir);
throw SysError("setting group of directory '%1%'", tmpDir);
#endif
return tmpDir;
}
if (errno != EEXIST)
throw SysError(format("creating directory '%1%'") % tmpDir);
throw SysError("creating directory '%1%'", tmpDir);
}
}
@ -584,15 +567,15 @@ Paths createDirs(const Path & path)
if (lstat(path.c_str(), &st) == -1) {
created = createDirs(dirOf(path));
if (mkdir(path.c_str(), 0777) == -1 && errno != EEXIST)
throw SysError(format("creating directory '%1%'") % path);
throw SysError("creating directory '%1%'", path);
st = lstat(path);
created.push_back(path);
}
if (S_ISLNK(st.st_mode) && stat(path.c_str(), &st) == -1)
throw SysError(format("statting symlink '%1%'") % path);
throw SysError("statting symlink '%1%'", path);
if (!S_ISDIR(st.st_mode)) throw Error(format("'%1%' is not a directory") % path);
if (!S_ISDIR(st.st_mode)) throw Error("'%1%' is not a directory", path);
return created;
}
@ -601,7 +584,7 @@ Paths createDirs(const Path & path)
void createSymlink(const Path & target, const Path & link)
{
if (symlink(target.c_str(), link.c_str()))
throw SysError(format("creating symlink from '%1%' to '%2%'") % link % target);
throw SysError("creating symlink from '%1%' to '%2%'", link, target);
}
@ -618,7 +601,7 @@ void replaceSymlink(const Path & target, const Path & link)
}
if (rename(tmp.c_str(), link.c_str()) != 0)
throw SysError(format("renaming '%1%' to '%2%'") % tmp % link);
throw SysError("renaming '%1%' to '%2%'", tmp, link);
break;
}
@ -723,7 +706,7 @@ AutoDelete::~AutoDelete()
deletePath(path);
else {
if (remove(path.c_str()) == -1)
throw SysError(format("cannot unlink '%1%'") % path);
throw SysError("cannot unlink '%1%'", path);
}
}
} catch (...) {
@ -789,7 +772,7 @@ void AutoCloseFD::close()
if (fd != -1) {
if (::close(fd) == -1)
/* This should never happen. */
throw SysError(format("closing file descriptor %1%") % fd);
throw SysError("closing file descriptor %1%", fd);
}
}
@ -862,7 +845,7 @@ int Pid::kill()
{
assert(pid != -1);
debug(format("killing process %1%") % pid);
debug("killing process %1%", pid);
/* Send the requested signal to the child. If it has its own
process group, send the signal to every process in the child
@ -874,7 +857,7 @@ int Pid::kill()
#if __FreeBSD__ || __APPLE__
if (errno != EPERM || ::kill(pid, 0) != 0)
#endif
printError((SysError("killing process %d", pid).msg()));
logError(SysError("killing process %d", pid).info());
}
return wait();
@ -920,7 +903,7 @@ pid_t Pid::release()
void killUser(uid_t uid)
{
debug(format("killing all processes running under uid '%1%'") % uid);
debug("killing all processes running under uid '%1%'", uid);
assert(uid != 0); /* just to be safe... */
@ -949,7 +932,7 @@ void killUser(uid_t uid)
#endif
if (errno == ESRCH) break; /* no more processes */
if (errno != EINTR)
throw SysError(format("cannot kill processes for uid '%1%'") % uid);
throw SysError("cannot kill processes for uid '%1%'", uid);
}
_exit(0);
@ -957,7 +940,7 @@ void killUser(uid_t uid)
int status = pid.wait();
if (status != 0)
throw Error(format("cannot kill processes for uid '%1%': %2%") % uid % statusToString(status));
throw Error("cannot kill processes for uid '%1%': %2%", uid, statusToString(status));
/* !!! We should really do some check to make sure that there are
no processes left running under `uid', but there is no portable
@ -989,7 +972,7 @@ pid_t startProcess(std::function<void()> fun, const ProcessOptions & options)
{
auto wrapper = [&]() {
if (!options.allowVfork)
logger = makeDefaultLogger();
logger = makeSimpleLogger();
try {
#if __linux__
if (options.dieWithParent && prctl(PR_SET_PDEATHSIG, SIGKILL) == -1)
@ -1216,7 +1199,7 @@ void _interrupted()
/* Block user interrupts while an exception is being handled.
Throwing an exception while another exception is being handled
kills the program! */
if (!interruptThrown && !std::uncaught_exception()) {
if (!interruptThrown && !std::uncaught_exceptions()) {
interruptThrown = true;
throw Interrupted("interrupted by the user");
}
@ -1314,7 +1297,7 @@ bool statusOk(int status)
}
bool hasPrefix(const string & s, const string & prefix)
bool hasPrefix(std::string_view s, std::string_view prefix)
{
return s.compare(0, prefix.size(), prefix) == 0;
}
@ -1351,7 +1334,7 @@ void ignoreException()
try {
throw;
} catch (std::exception & e) {
printError(format("error (ignored): %1%") % e.what());
printError("error (ignored): %1%", e.what());
}
}
@ -1408,7 +1391,7 @@ std::string filterANSIEscapes(const std::string & s, bool filterAll, unsigned in
static char base64Chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
string base64Encode(const string & s)
string base64Encode(std::string_view s)
{
string res;
int data = 0, nbits = 0;
@ -1429,7 +1412,7 @@ string base64Encode(const string & s)
}
string base64Decode(const string & s)
string base64Decode(std::string_view s)
{
bool init = false;
char decode[256];
@ -1464,17 +1447,6 @@ string base64Decode(const string & s)
}
void callFailure(const std::function<void(std::exception_ptr exc)> & failure, std::exception_ptr exc)
{
try {
failure(exc);
} catch (std::exception & e) {
printError(format("uncaught exception: %s") % e.what());
abort();
}
}
static Sync<std::pair<unsigned short, unsigned short>> windowSize{{0, 0}};

View file

@ -1,6 +1,7 @@
#pragma once
#include "types.hh"
#include "error.hh"
#include "logging.hh"
#include "ansicolor.hh"
@ -416,7 +417,7 @@ template<class N> bool string2Float(const string & s, N & n)
/* Return true iff `s' starts with `prefix'. */
bool hasPrefix(const string & s, const string & prefix);
bool hasPrefix(std::string_view s, std::string_view prefix);
/* Return true iff `s' ends in `suffix'. */
@ -454,13 +455,12 @@ std::string filterANSIEscapes(const std::string & s,
unsigned int width = std::numeric_limits<unsigned int>::max());
/* Base::Base64 encoding/decoding. */
string base64Encode(const string & s);
string base64Decode(const string & s);
/* Base64 encoding/decoding. */
string base64Encode(std::string_view s);
string base64Decode(std::string_view s);
/* Get a value for the specified key from an associate container, or a
default value if the key doesn't exist. */
/* Get a value for the specified key from an associate container. */
template <class T>
std::optional<typename T::mapped_type> get(const T & map, const typename T::key_type & key)
{