about summary refs log tree commit diff
path: root/src/libutil
diff options
context:
space:
mode:
Diffstat (limited to 'src/libutil')
-rw-r--r--src/libutil/affinity.cc6
-rw-r--r--src/libutil/archive.cc4
-rw-r--r--src/libutil/args.hh2
-rw-r--r--src/libutil/json.cc176
-rw-r--r--src/libutil/json.hh184
-rw-r--r--src/libutil/logging.cc14
-rw-r--r--src/libutil/logging.hh11
-rw-r--r--src/libutil/serialise.cc2
-rw-r--r--src/libutil/thread-pool.cc2
-rw-r--r--src/libutil/types.hh69
-rw-r--r--src/libutil/util.cc108
-rw-r--r--src/libutil/util.hh74
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);
+    }
+}
+
+
 }