gnu: Add python-pysctp.
[jackhill/guix/guix.git] / nix / libutil / util.cc
index 14026ab..69f1c63 100644 (file)
@@ -177,8 +177,13 @@ struct stat lstat(const Path & path)
 bool pathExists(const Path & path)
 {
     int res;
+#ifdef HAVE_STATX
+    struct statx st;
+    res = statx(AT_FDCWD, path.c_str(), AT_SYMLINK_NOFOLLOW, 0, &st);
+#else
     struct stat st;
     res = lstat(path.c_str(), &st);
+#endif
     if (!res) return true;
     if (errno != ENOENT && errno != ENOTDIR)
         throw SysError(format("getting status of %1%") % path);
@@ -300,16 +305,27 @@ void writeLine(int fd, string s)
 }
 
 
-static void _deletePath(const Path & path, unsigned long long & bytesFreed)
+static void _deletePath(const Path & path, unsigned long long & bytesFreed, size_t linkThreshold)
 {
     checkInterrupt();
 
     printMsg(lvlVomit, format("%1%") % path);
 
+#ifdef HAVE_STATX
+# define st_mode stx_mode
+# define st_size stx_size
+# define st_nlink stx_nlink
+    struct statx st;
+    if (statx(AT_FDCWD, path.c_str(),
+             AT_SYMLINK_NOFOLLOW,
+             STATX_SIZE | STATX_NLINK | STATX_MODE, &st) == -1)
+       throw SysError(format("getting status of `%1%'") % path);
+#else
     struct stat st = lstat(path);
+#endif
 
-    if (!S_ISDIR(st.st_mode) && st.st_nlink == 1)
-        bytesFreed += st.st_blocks * 512;
+    if (!S_ISDIR(st.st_mode) && st.st_nlink <= linkThreshold)
+       bytesFreed += st.st_size;
 
     if (S_ISDIR(st.st_mode)) {
         /* Make the directory writable. */
@@ -319,8 +335,11 @@ static void _deletePath(const Path & path, unsigned long long & bytesFreed)
         }
 
         for (auto & i : readDirectory(path))
-            _deletePath(path + "/" + i.name, bytesFreed);
+            _deletePath(path + "/" + i.name, bytesFreed, linkThreshold);
     }
+#undef st_mode
+#undef st_size
+#undef st_nlink
 
     if (remove(path.c_str()) == -1)
         throw SysError(format("cannot unlink `%1%'") % path);
@@ -334,12 +353,12 @@ void deletePath(const Path & path)
 }
 
 
-void deletePath(const Path & path, unsigned long long & bytesFreed)
+void deletePath(const Path & path, unsigned long long & bytesFreed, size_t linkThreshold)
 {
     startNest(nest, lvlDebug,
         format("recursively deleting path `%1%'") % path);
     bytesFreed = 0;
-    _deletePath(path, bytesFreed);
+    _deletePath(path, bytesFreed, linkThreshold);
 }
 
 
@@ -433,7 +452,7 @@ Nest::~Nest()
 
 static string escVerbosity(Verbosity level)
 {
-    return int2String((int) level);
+    return std::to_string((int) level);
 }
 
 
@@ -842,6 +861,10 @@ void killUser(uid_t uid)
                which means "follow POSIX", which we don't want here
                  */
             if (syscall(SYS_kill, -1, SIGKILL, false) == 0) break;
+#elif __GNU__
+            /* Killing all a user's processes using PID=-1 does currently
+               not work on the Hurd.  */
+            if (kill(getpid(), SIGKILL) == 0) break;
 #else
             if (kill(-1, SIGKILL) == 0) break;
 #endif
@@ -854,6 +877,10 @@ void killUser(uid_t uid)
     });
 
     int status = pid.wait(true);
+#if __GNU__
+    /* When the child killed itself, status = SIGKILL.  */
+    if (status == SIGKILL) return;
+#endif
     if (status != 0)
         throw Error(format("cannot kill processes for uid `%1%': %2%") % uid % statusToString(status));
 
@@ -1106,27 +1133,100 @@ bool endOfList(std::istream & str)
 }
 
 
-string decodeOctalEscaped(const string & s)
+void ignoreException()
 {
-    string r;
-    for (string::const_iterator i = s.begin(); i != s.end(); ) {
-        if (*i != '\\') { r += *i++; continue; }
-        unsigned char c = 0;
-        ++i;
-        while (i != s.end() && *i >= '0' && *i < '8')
-            c = c * 8 + (*i++ - '0');
-        r += c;
+    try {
+        throw;
+    } catch (std::exception & e) {
+        printMsg(lvlError, format("error (ignored): %1%") % e.what());
     }
-    return r;
 }
 
+static const string pathNullDevice = "/dev/null";
 
-void ignoreException()
+/* Common initialisation performed in child processes. */
+void commonChildInit(Pipe & logPipe)
+{
+    /* Put the child in a separate session (and thus a separate
+       process group) so that it has no controlling terminal (meaning
+       that e.g. ssh cannot open /dev/tty) and it doesn't receive
+       terminal signals. */
+    if (setsid() == -1)
+        throw SysError(format("creating a new session"));
+
+    /* Dup the write side of the logger pipe into stderr. */
+    if (dup2(logPipe.writeSide, STDERR_FILENO) == -1)
+        throw SysError("cannot pipe standard error into log file");
+
+    /* Dup stderr to stdout. */
+    if (dup2(STDERR_FILENO, STDOUT_FILENO) == -1)
+        throw SysError("cannot dup stderr into stdout");
+
+    /* Reroute stdin to /dev/null. */
+    int fdDevNull = open(pathNullDevice.c_str(), O_RDWR);
+    if (fdDevNull == -1)
+        throw SysError(format("cannot open `%1%'") % pathNullDevice);
+    if (dup2(fdDevNull, STDIN_FILENO) == -1)
+        throw SysError("cannot dup null device into stdin");
+    close(fdDevNull);
+}
+
+//////////////////////////////////////////////////////////////////////
+
+Agent::Agent(const string &command, const Strings &args, const std::map<string, string> &env)
+{
+    debug(format("starting agent '%1%'") % command);
+
+    /* Create a pipe to get the output of the child. */
+    fromAgent.create();
+
+    /* Create the communication pipes. */
+    toAgent.create();
+
+    /* Create a pipe to get the output of the builder. */
+    builderOut.create();
+
+    /* Fork the hook. */
+    pid = startProcess([&]() {
+
+        commonChildInit(fromAgent);
+
+       for (auto pair: env) {
+           setenv(pair.first.c_str(), pair.second.c_str(), 1);
+       }
+
+        if (chdir("/") == -1) throw SysError("changing into `/");
+
+        /* Dup the communication pipes. */
+        if (dup2(toAgent.readSide, STDIN_FILENO) == -1)
+            throw SysError("dupping to-hook read side");
+
+        /* Use fd 4 for the builder's stdout/stderr. */
+        if (dup2(builderOut.writeSide, 4) == -1)
+            throw SysError("dupping builder's stdout/stderr");
+
+       Strings allArgs;
+       allArgs.push_back(command);
+       allArgs.insert(allArgs.end(), args.begin(), args.end()); // append
+
+        execv(command.c_str(), stringsToCharPtrs(allArgs).data());
+
+        throw SysError(format("executing `%1%'") % command);
+    });
+
+    pid.setSeparatePG(true);
+    fromAgent.writeSide.close();
+    toAgent.readSide.close();
+}
+
+
+Agent::~Agent()
 {
     try {
-        throw;
-    } catch (std::exception & e) {
-        printMsg(lvlError, format("error (ignored): %1%") % e.what());
+        toAgent.writeSide.close();
+        pid.kill(true);
+    } catch (...) {
+        ignoreException();
     }
 }