diff options
Diffstat (limited to 'src/libutil')
-rw-r--r-- | src/libutil/affinity.cc | 6 | ||||
-rw-r--r-- | src/libutil/archive.cc | 4 | ||||
-rw-r--r-- | src/libutil/args.hh | 2 | ||||
-rw-r--r-- | src/libutil/json.cc | 176 | ||||
-rw-r--r-- | src/libutil/json.hh | 184 | ||||
-rw-r--r-- | src/libutil/logging.cc | 14 | ||||
-rw-r--r-- | src/libutil/logging.hh | 11 | ||||
-rw-r--r-- | src/libutil/serialise.cc | 2 | ||||
-rw-r--r-- | src/libutil/thread-pool.cc | 2 | ||||
-rw-r--r-- | src/libutil/types.hh | 69 | ||||
-rw-r--r-- | src/libutil/util.cc | 108 | ||||
-rw-r--r-- | src/libutil/util.hh | 74 |
12 files changed, 554 insertions, 98 deletions
diff --git a/src/libutil/affinity.cc b/src/libutil/affinity.cc index 3cbdf878617a..98f8287ada67 100644 --- a/src/libutil/affinity.cc +++ b/src/libutil/affinity.cc @@ -20,12 +20,12 @@ void setAffinityTo(int cpu) #if __linux__ if (sched_getaffinity(0, sizeof(cpu_set_t), &savedAffinity) == -1) return; didSaveAffinity = true; - printMsg(lvlDebug, format("locking this thread to CPU %1%") % cpu); + debug(format("locking this thread to CPU %1%") % cpu); cpu_set_t newAffinity; CPU_ZERO(&newAffinity); CPU_SET(cpu, &newAffinity); if (sched_setaffinity(0, sizeof(cpu_set_t), &newAffinity) == -1) - printMsg(lvlError, format("failed to lock thread to CPU %1%") % cpu); + printError(format("failed to lock thread to CPU %1%") % cpu); #endif } @@ -47,7 +47,7 @@ void restoreAffinity() #if __linux__ if (!didSaveAffinity) return; if (sched_setaffinity(0, sizeof(cpu_set_t), &savedAffinity) == -1) - printMsg(lvlError, "failed to restore affinity %1%"); + printError("failed to restore affinity %1%"); #endif } diff --git a/src/libutil/archive.cc b/src/libutil/archive.cc index edd4a881b485..b9b26c5f5f98 100644 --- a/src/libutil/archive.cc +++ b/src/libutil/archive.cc @@ -84,7 +84,7 @@ static void dump(const Path & path, Sink & sink, PathFilter & filter) string name(i.name); size_t pos = i.name.find(caseHackSuffix); if (pos != string::npos) { - printMsg(lvlDebug, format("removing case hack suffix from ‘%1%’") % (path + "/" + i.name)); + debug(format("removing case hack suffix from ‘%1%’") % (path + "/" + i.name)); name.erase(pos); } if (unhacked.find(name) != unhacked.end()) @@ -248,7 +248,7 @@ static void parse(ParseSink & sink, Source & source, const Path & path) if (useCaseHack) { auto i = names.find(name); if (i != names.end()) { - printMsg(lvlDebug, format("case collision between ‘%1%’ and ‘%2%’") % i->first % name); + debug(format("case collision between ‘%1%’ and ‘%2%’") % i->first % name); name += caseHackSuffix; name += std::to_string(++i->second); } else diff --git a/src/libutil/args.hh b/src/libutil/args.hh index 6aa08aacac9e..ac12f8be633a 100644 --- a/src/libutil/args.hh +++ b/src/libutil/args.hh @@ -8,7 +8,7 @@ namespace nix { -MakeError(UsageError, nix::Error); +MakeError(UsageError, Error); enum HashType : char; diff --git a/src/libutil/json.cc b/src/libutil/json.cc new file mode 100644 index 000000000000..ecc3fdfe514e --- /dev/null +++ b/src/libutil/json.cc @@ -0,0 +1,176 @@ +#include "json.hh" + +#include <iomanip> +#include <cstring> + +namespace nix { + +void toJSON(std::ostream & str, const char * start, const char * end) +{ + str << '"'; + for (auto i = start; i != end; i++) + if (*i == '\"' || *i == '\\') str << '\\' << *i; + else if (*i == '\n') str << "\\n"; + else if (*i == '\r') str << "\\r"; + else if (*i == '\t') str << "\\t"; + else if (*i >= 0 && *i < 32) + str << "\\u" << std::setfill('0') << std::setw(4) << std::hex << (uint16_t) *i << std::dec; + else str << *i; + str << '"'; +} + +void toJSON(std::ostream & str, const std::string & s) +{ + toJSON(str, s.c_str(), s.c_str() + s.size()); +} + +void toJSON(std::ostream & str, const char * s) +{ + if (!s) str << "null"; else toJSON(str, s, s + strlen(s)); +} + +void toJSON(std::ostream & str, unsigned long long n) +{ + str << n; +} + +void toJSON(std::ostream & str, unsigned long n) +{ + str << n; +} + +void toJSON(std::ostream & str, long n) +{ + str << n; +} + +void toJSON(std::ostream & str, double f) +{ + str << f; +} + +void toJSON(std::ostream & str, bool b) +{ + str << (b ? "true" : "false"); +} + +JSONWriter::JSONWriter(std::ostream & str, bool indent) + : state(new JSONState(str, indent)) +{ + state->stack.push_back(this); +} + +JSONWriter::JSONWriter(JSONState * state) + : state(state) +{ + state->stack.push_back(this); +} + +JSONWriter::~JSONWriter() +{ + assertActive(); + state->stack.pop_back(); + if (state->stack.empty()) 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() +{ + state->depth--; + if (state->indent && !first) indent(); + state->str << "}"; +} + +void JSONObject::attr(const std::string & s) +{ + comma(); + toJSON(state->str, s); + state->str << ':'; + if (state->indent) state->str << ' '; +} + +JSONList JSONObject::list(const std::string & name) +{ + attr(name); + return JSONList(state); +} + +JSONObject JSONObject::object(const std::string & name) +{ + attr(name); + return JSONObject(state); +} + +JSONPlaceholder JSONObject::placeholder(const std::string & name) +{ + attr(name); + return JSONPlaceholder(state); +} + +JSONList JSONPlaceholder::list() +{ + assertValid(); + first = false; + return JSONList(state); +} + +JSONObject JSONPlaceholder::object() +{ + assertValid(); + first = false; + return JSONObject(state); +} + +} diff --git a/src/libutil/json.hh b/src/libutil/json.hh new file mode 100644 index 000000000000..aec456845056 --- /dev/null +++ b/src/libutil/json.hh @@ -0,0 +1,184 @@ +#pragma once + +#include <iostream> +#include <vector> +#include <cassert> + +namespace nix { + +void toJSON(std::ostream & str, const char * start, const char * end); +void toJSON(std::ostream & str, const std::string & s); +void toJSON(std::ostream & str, const char * s); +void toJSON(std::ostream & str, unsigned long long n); +void toJSON(std::ostream & str, unsigned long n); +void toJSON(std::ostream & str, long n); +void toJSON(std::ostream & str, double f); +void toJSON(std::ostream & str, bool b); + +class JSONWriter +{ +protected: + + struct JSONState + { + std::ostream & str; + bool indent; + size_t depth = 0; + std::vector<JSONWriter *> stack; + JSONState(std::ostream & str, bool indent) : str(str), indent(indent) { } + ~JSONState() + { + assert(stack.empty()); + } + }; + + JSONState * state; + + bool first = true; + + JSONWriter(std::ostream & str, bool indent); + + JSONWriter(JSONState * state); + + ~JSONWriter(); + + void assertActive() + { + assert(!state->stack.empty() && state->stack.back() == this); + } + + void comma(); + + void indent(); +}; + +class JSONObject; +class JSONPlaceholder; + +class JSONList : JSONWriter +{ +private: + + friend class JSONObject; + friend class JSONPlaceholder; + + void open(); + + JSONList(JSONState * state) + : JSONWriter(state) + { + open(); + } + +public: + + JSONList(std::ostream & str, bool indent = false) + : JSONWriter(str, indent) + { + open(); + } + + ~JSONList(); + + template<typename T> + JSONList & elem(const T & v) + { + comma(); + toJSON(state->str, v); + return *this; + } + + JSONList list(); + + JSONObject object(); + + JSONPlaceholder placeholder(); +}; + +class JSONObject : JSONWriter +{ +private: + + friend class JSONList; + friend class JSONPlaceholder; + + void open(); + + JSONObject(JSONState * state) + : JSONWriter(state) + { + open(); + } + + void attr(const std::string & s); + +public: + + JSONObject(std::ostream & str, bool indent = false) + : JSONWriter(str, indent) + { + open(); + } + + ~JSONObject(); + + template<typename T> + JSONObject & attr(const std::string & name, const T & v) + { + attr(name); + toJSON(state->str, v); + return *this; + } + + JSONList list(const std::string & name); + + JSONObject object(const std::string & name); + + JSONPlaceholder placeholder(const std::string & name); +}; + +class JSONPlaceholder : JSONWriter +{ + +private: + + friend class JSONList; + friend class JSONObject; + + JSONPlaceholder(JSONState * state) + : JSONWriter(state) + { + } + + void assertValid() + { + assertActive(); + assert(first); + } + +public: + + JSONPlaceholder(std::ostream & str, bool indent = false) + : JSONWriter(str, indent) + { + } + + ~JSONPlaceholder() + { + assert(!first || std::uncaught_exception()); + } + + template<typename T> + void write(const T & v) + { + assertValid(); + first = false; + toJSON(state->str, v); + } + + JSONList list(); + + JSONObject object(); +}; + +} diff --git a/src/libutil/logging.cc b/src/libutil/logging.cc index 15bb1e175da6..d9e8d22d7685 100644 --- a/src/libutil/logging.cc +++ b/src/libutil/logging.cc @@ -52,7 +52,7 @@ Verbosity verbosity = lvlInfo; void warnOnce(bool & haveWarned, const FormatOrString & fs) { if (!haveWarned) { - printMsg(lvlError, format("warning: %1%") % fs.s); + printError(format("warning: %1%") % fs.s); haveWarned = true; } } @@ -60,14 +60,12 @@ void warnOnce(bool & haveWarned, const FormatOrString & fs) void writeToStderr(const string & s) { try { - writeFull(STDERR_FILENO, s); + writeFull(STDERR_FILENO, s, false); } catch (SysError & e) { - /* Ignore failing writes to stderr if we're in an exception - handler, otherwise throw an exception. We need to ignore - write errors in exception handlers to ensure that cleanup - code runs to completion if the other side of stderr has - been closed unexpectedly. */ - if (!std::uncaught_exception()) throw; + /* Ignore failing writes to stderr. We need to ignore write + errors to ensure that cleanup code that logs to stderr runs + to completion if the other side of stderr has been closed + unexpectedly. */ } } diff --git a/src/libutil/logging.hh b/src/libutil/logging.hh index 277dff280053..ba99a81c3826 100644 --- a/src/libutil/logging.hh +++ b/src/libutil/logging.hh @@ -66,14 +66,19 @@ Logger * makeDefaultLogger(); extern Verbosity verbosity; /* suppress msgs > this */ -#define printMsg(level, f) \ +/* Print a message if the current log level is at least the specified + level. Note that this has to be implemented as a macro to ensure + that the arguments are evaluated lazily. */ +#define printMsg(level, args...) \ do { \ if (level <= nix::verbosity) { \ - logger->log(level, (f)); \ + logger->log(level, fmt(args)); \ } \ } while (0) -#define debug(f) printMsg(lvlDebug, f) +#define printError(args...) printMsg(lvlError, args) +#define printInfo(args...) printMsg(lvlInfo, args) +#define debug(args...) printMsg(lvlDebug, args) void warnOnce(bool & haveWarned, const FormatOrString & fs); diff --git a/src/libutil/serialise.cc b/src/libutil/serialise.cc index 776308cdf321..24c6d107359e 100644 --- a/src/libutil/serialise.cc +++ b/src/libutil/serialise.cc @@ -49,7 +49,7 @@ size_t threshold = 256 * 1024 * 1024; static void warnLargeDump() { - printMsg(lvlError, "warning: dumping very large path (> 256 MiB); this may run out of memory"); + printError("warning: dumping very large path (> 256 MiB); this may run out of memory"); } diff --git a/src/libutil/thread-pool.cc b/src/libutil/thread-pool.cc index 696ecd6c38c8..0a3a407240f7 100644 --- a/src/libutil/thread-pool.cc +++ b/src/libutil/thread-pool.cc @@ -87,7 +87,7 @@ void ThreadPool::workerEntry() if (state->exception) { if (!dynamic_cast<Interrupted*>(&e) && !dynamic_cast<ThreadPoolShutDown*>(&e)) - printMsg(lvlError, format("error: %s") % e.what()); + printError(format("error: %s") % e.what()); } else { state->exception = std::current_exception(); work.notify_all(); diff --git a/src/libutil/types.hh b/src/libutil/types.hh index bd192b8506b2..b9a93d27d2ad 100644 --- a/src/libutil/types.hh +++ b/src/libutil/types.hh @@ -41,6 +41,45 @@ struct FormatOrString }; +/* A helper for formatting strings. ‘fmt(format, a_0, ..., a_n)’ is + equivalent to ‘boost::format(format) % a_0 % ... % + ... a_n’. However, ‘fmt(s)’ is equivalent to ‘s’ (so no %-expansion + takes place). */ + +inline void formatHelper(boost::format & f) +{ +} + +template<typename T, typename... Args> +inline void formatHelper(boost::format & f, T x, 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, Args... args) +{ + boost::format f(fs); + 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 @@ -49,14 +88,28 @@ protected: string prefix_; // used for location traces etc. string err; public: - unsigned int status; // exit status - BaseError(const FormatOrString & fs, unsigned int status = 1); + unsigned int status = 1; // exit status + + template<typename... Args> + BaseError(unsigned int status, Args... args) + : err(fmt(args...)) + , status(status) + { + } + + template<typename... Args> + BaseError(Args... args) + : err(fmt(args...)) + { + } + #ifdef EXCEPTION_NEEDS_THROW_SPEC ~BaseError() throw () { }; const char * what() const throw () { return err.c_str(); } #else const char * what() const noexcept { return err.c_str(); } #endif + const string & msg() const { return err; } const string & prefix() const { return prefix_; } BaseError & addPrefix(const FormatOrString & fs); @@ -66,7 +119,7 @@ public: class newClass : public superClass \ { \ public: \ - newClass(const FormatOrString & fs, unsigned int status = 1) : superClass(fs, status) { }; \ + using superClass::superClass; \ }; MakeError(Error, BaseError) @@ -75,7 +128,15 @@ class SysError : public Error { public: int errNo; - SysError(const FormatOrString & fs); + + template<typename... Args> + SysError(Args... args) + : Error(addErrno(fmt(args...))) + { } + +private: + + std::string addErrno(const std::string & s); }; diff --git a/src/libutil/util.cc b/src/libutil/util.cc index 95103c4e5d12..1f923fe6bc0f 100644 --- a/src/libutil/util.cc +++ b/src/libutil/util.cc @@ -9,6 +9,7 @@ #include <cstdlib> #include <sstream> #include <cstring> +#include <cctype> #include <sys/wait.h> #include <unistd.h> @@ -30,13 +31,6 @@ extern char * * environ; namespace nix { -BaseError::BaseError(const FormatOrString & fs, unsigned int status) - : status(status) -{ - err = fs.s; -} - - BaseError & BaseError::addPrefix(const FormatOrString & fs) { prefix_ = fs.s + prefix_; @@ -44,10 +38,10 @@ BaseError & BaseError::addPrefix(const FormatOrString & fs) } -SysError::SysError(const FormatOrString & fs) - : Error(format("%1%: %2%") % fs.s % strerror(errno)) - , errNo(errno) +std::string SysError::addErrno(const std::string & s) { + errNo = errno; + return s + ": " + strerror(errNo); } @@ -58,6 +52,21 @@ string getEnv(const string & key, const string & def) } +std::map<std::string, std::string> getEnv() +{ + std::map<std::string, std::string> env; + for (size_t i = 0; environ[i]; ++i) { + auto s = environ[i]; + auto eq = strchr(s, '='); + if (!eq) + // invalid env, just keep going + continue; + env.emplace(std::string(s, eq), std::string(eq + 1)); + } + return env; +} + + Path absPath(Path path, Path dir) { if (path[0] != '/') { @@ -474,24 +483,24 @@ void readFull(int fd, unsigned char * buf, size_t count) } -void writeFull(int fd, const unsigned char * buf, size_t count) +void writeFull(int fd, const unsigned char * buf, size_t count, bool allowInterrupts) { while (count) { - checkInterrupt(); ssize_t res = write(fd, (char *) buf, count); - if (res == -1) { - if (errno == EINTR) continue; + if (res == -1 && errno != EINTR) throw SysError("writing to file"); + if (res > 0) { + count -= res; + buf += res; } - count -= res; - buf += res; + if (allowInterrupts) checkInterrupt(); } } -void writeFull(int fd, const string & s) +void writeFull(int fd, const string & s, bool allowInterrupts) { - writeFull(fd, (const unsigned char *) s.data(), s.size()); + writeFull(fd, (const unsigned char *) s.data(), s.size(), allowInterrupts); } @@ -716,20 +725,20 @@ void Pid::kill(bool quiet) if (pid == -1 || pid == 0) return; if (!quiet) - printMsg(lvlError, format("killing process %1%") % pid); + printError(format("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 process group (which hopefully includes *all* its children). */ if (::kill(separatePG ? -pid : pid, killSignal) != 0) - printMsg(lvlError, (SysError(format("killing process %1%") % pid).msg())); + printError((SysError(format("killing process %1%") % pid).msg())); /* Wait until the child dies, disregarding the exit status. */ int status; while (waitpid(pid, &status, 0) == -1) { checkInterrupt(); if (errno != EINTR) { - printMsg(lvlError, + printError( (SysError(format("waiting for process %1%") % pid).msg())); break; } @@ -919,7 +928,7 @@ string runProgram(Path program, bool searchPath, const Strings & args, /* Wait for the child to finish. */ int status = pid.wait(true); if (!statusOk(status)) - throw ExecError(format("program ‘%1%’ %2%") + throw ExecError(status, format("program ‘%1%’ %2%") % program % statusToString(status)); return result; @@ -1088,44 +1097,12 @@ bool hasSuffix(const string & s, const string & suffix) } -void expect(std::istream & str, const string & s) -{ - char s2[s.size()]; - str.read(s2, s.size()); - if (string(s2, s.size()) != s) - throw FormatError(format("expected string ‘%1%’") % s); -} - - -string parseString(std::istream & str) +std::string toLower(const std::string & s) { - string res; - expect(str, "\""); - int c; - while ((c = str.get()) != '"') - if (c == '\\') { - c = str.get(); - if (c == 'n') res += '\n'; - else if (c == 'r') res += '\r'; - else if (c == 't') res += '\t'; - else res += c; - } - else res += c; - return res; -} - - -bool endOfList(std::istream & str) -{ - if (str.peek() == ',') { - str.get(); - return false; - } - if (str.peek() == ']') { - str.get(); - return true; - } - return false; + std::string r(s); + for (auto & c : r) + c = std::tolower(c); + return r; } @@ -1149,7 +1126,7 @@ void ignoreException() try { throw; } catch (std::exception & e) { - printMsg(lvlError, format("error (ignored): %1%") % e.what()); + printError(format("error (ignored): %1%") % e.what()); } } @@ -1247,4 +1224,15 @@ 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(); + } +} + + } diff --git a/src/libutil/util.hh b/src/libutil/util.hh index 819921dfff1e..259c73260da3 100644 --- a/src/libutil/util.hh +++ b/src/libutil/util.hh @@ -8,9 +8,11 @@ #include <dirent.h> #include <unistd.h> #include <signal.h> + #include <functional> #include <limits> #include <cstdio> +#include <map> #ifndef HAVE_STRUCT_DIRENT_D_TYPE #define DT_UNKNOWN 0 @@ -25,6 +27,9 @@ namespace nix { /* Return an environment variable. */ string getEnv(const string & key, const string & def = ""); +/* Get the entire environment. */ +std::map<std::string, std::string> getEnv(); + /* Return an absolutized path, resolving paths relative to the specified directory, or the current directory otherwise. The path is also canonicalised. */ @@ -120,8 +125,8 @@ void replaceSymlink(const Path & target, const Path & link); /* Wrappers arount read()/write() that read/write exactly the requested number of bytes. */ void readFull(int fd, unsigned char * buf, size_t count); -void writeFull(int fd, const unsigned char * buf, size_t count); -void writeFull(int fd, const string & s); +void writeFull(int fd, const unsigned char * buf, size_t count, bool allowInterrupts = true); +void writeFull(int fd, const string & s, bool allowInterrupts = true); MakeError(EndOfFile, Error) @@ -242,7 +247,16 @@ pid_t startProcess(std::function<void()> fun, const ProcessOptions & options = P string runProgram(Path program, bool searchPath = false, const Strings & args = Strings(), const string & input = ""); -MakeError(ExecError, Error) +class ExecError : public Error +{ +public: + int status; + + template<typename... Args> + ExecError(int status, Args... args) + : Error(args...), status(status) + { } +}; /* Convert a list of strings to a null-terminated vector of char *'s. The result must not be accessed beyond the lifetime of the @@ -334,18 +348,8 @@ bool hasPrefix(const string & s, const string & prefix); bool hasSuffix(const string & s, const string & suffix); -/* Read string `s' from stream `str'. */ -void expect(std::istream & str, const string & s); - -MakeError(FormatError, Error) - - -/* Read a C-style string from stream `str'. */ -string parseString(std::istream & str); - - -/* Utility function used to parse legacy ATerms. */ -bool endOfList(std::istream & str); +/* Convert a string to lower case. */ +std::string toLower(const std::string & s); /* Escape a string that contains octal-encoded escape codes such as @@ -386,4 +390,44 @@ string get(const T & map, const string & key, const string & def = "") } +/* Call ‘failure’ with the current exception as argument. If ‘failure’ + throws an exception, abort the program. */ +void callFailure(const std::function<void(std::exception_ptr exc)> & failure, + std::exception_ptr exc = std::current_exception()); + + +/* Evaluate the function ‘f’. If it returns a value, call ‘success’ + with that value as its argument. If it or ‘success’ throws an + exception, call ‘failure’. If ‘failure’ throws an exception, abort + the program. */ +template<class T> +void sync2async( + const std::function<void(T)> & success, + const std::function<void(std::exception_ptr exc)> & failure, + const std::function<T()> & f) +{ + try { + success(f()); + } catch (...) { + callFailure(failure); + } +} + + +/* Call the function ‘success’. If it throws an exception, call + ‘failure’. If that throws an exception, abort the program. */ +template<class T> +void callSuccess( + const std::function<void(T)> & success, + const std::function<void(std::exception_ptr exc)> & failure, + T && arg) +{ + try { + success(arg); + } catch (...) { + callFailure(failure); + } +} + + } |