#include "util.hh" #include "sync.hh" #include "finally.hh" #include "serialise.hh" #include "cgroup.hh" #include "signals.hh" #include "environment-variables.hh" #include "file-system.hh" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __APPLE__ #include #include #endif #ifdef __linux__ #include #include #include #include #endif #ifdef NDEBUG #error "Lix may not be built with assertions disabled (i.e. with -DNDEBUG)." #endif namespace nix { std::string readLine(int fd) { std::string s; while (1) { checkInterrupt(); char ch; // FIXME: inefficient ssize_t rd = read(fd, &ch, 1); if (rd == -1) { if (errno != EINTR) throw SysError("reading a line"); } else if (rd == 0) throw EndOfFile("unexpected EOF reading a line"); else { if (ch == '\n') return s; s += ch; } } } void writeLine(int fd, std::string s) { s += '\n'; writeFull(fd, s); } std::string getUserName() { auto pw = getpwuid(geteuid()); std::string name = pw ? pw->pw_name : getEnv("USER").value_or(""); if (name.empty()) throw Error("cannot figure out user name"); return name; } Path getHomeOf(uid_t userId) { std::vector buf(16384); struct passwd pwbuf; struct passwd * pw; if (getpwuid_r(userId, &pwbuf, buf.data(), buf.size(), &pw) != 0 || !pw || !pw->pw_dir || !pw->pw_dir[0]) throw Error("cannot determine user's home directory"); return pw->pw_dir; } Path getHome() { static Path homeDir = []() { std::optional unownedUserHomeDir = {}; auto homeDir = getEnv("HOME"); if (homeDir) { // Only use $HOME if doesn't exist or is owned by the current user. struct stat st; int result = stat(homeDir->c_str(), &st); if (result != 0) { if (errno != ENOENT) { warn("couldn't stat $HOME ('%s') for reason other than not existing ('%d'), falling back to the one defined in the 'passwd' file", *homeDir, errno); homeDir.reset(); } } else if (st.st_uid != geteuid()) { unownedUserHomeDir.swap(homeDir); } } if (!homeDir) { homeDir = getHomeOf(geteuid()); if (unownedUserHomeDir.has_value() && unownedUserHomeDir != homeDir) { warn("$HOME ('%s') is not owned by you, falling back to the one defined in the 'passwd' file ('%s')", *unownedUserHomeDir, *homeDir); } } return *homeDir; }(); return homeDir; } Path getCacheDir() { auto cacheDir = getEnv("XDG_CACHE_HOME"); return cacheDir ? *cacheDir : getHome() + "/.cache"; } Path getConfigDir() { auto configDir = getEnv("XDG_CONFIG_HOME"); return configDir ? *configDir : getHome() + "/.config"; } std::vector getConfigDirs() { Path configHome = getConfigDir(); auto configDirs = getEnv("XDG_CONFIG_DIRS").value_or("/etc/xdg"); std::vector result = tokenizeString>(configDirs, ":"); result.insert(result.begin(), configHome); return result; } Path getDataDir() { auto dataDir = getEnv("XDG_DATA_HOME"); return dataDir ? *dataDir : getHome() + "/.local/share"; } Path getStateDir() { auto stateDir = getEnv("XDG_STATE_HOME"); return stateDir ? *stateDir : getHome() + "/.local/state"; } Path createNixStateDir() { Path dir = getStateDir() + "/nix"; createDirs(dir); return dir; } std::optional getSelfExe() { static auto cached = []() -> std::optional { #if __linux__ return readLink("/proc/self/exe"); #elif __APPLE__ char buf[1024]; uint32_t size = sizeof(buf); if (_NSGetExecutablePath(buf, &size) == 0) return buf; else return std::nullopt; #else return std::nullopt; #endif }(); return cached; } void readFull(int fd, char * buf, size_t count) { while (count) { checkInterrupt(); ssize_t res = read(fd, buf, count); if (res == -1) { if (errno == EINTR) continue; throw SysError("reading from file"); } if (res == 0) throw EndOfFile("unexpected end-of-file"); count -= res; buf += res; } } void writeFull(int fd, std::string_view s, bool allowInterrupts) { while (!s.empty()) { if (allowInterrupts) checkInterrupt(); ssize_t res = write(fd, s.data(), s.size()); if (res == -1 && errno != EINTR) throw SysError("writing to file"); if (res > 0) s.remove_prefix(res); } } std::string drainFD(int fd, bool block, const size_t reserveSize) { // the parser needs two extra bytes to append terminating characters, other users will // not care very much about the extra memory. StringSink sink(reserveSize + 2); drainFD(fd, sink, block); return std::move(sink.s); } void drainFD(int fd, Sink & sink, bool block) { // silence GCC maybe-uninitialized warning in finally int saved = 0; if (!block) { saved = fcntl(fd, F_GETFL); if (fcntl(fd, F_SETFL, saved | O_NONBLOCK) == -1) throw SysError("making file descriptor non-blocking"); } Finally finally([&]() { if (!block) { if (fcntl(fd, F_SETFL, saved) == -1) throw SysError("making file descriptor blocking"); } }); std::array buf; while (1) { checkInterrupt(); ssize_t rd = read(fd, buf.data(), buf.size()); if (rd == -1) { if (!block && (errno == EAGAIN || errno == EWOULDBLOCK)) break; if (errno != EINTR) throw SysError("reading from file"); } else if (rd == 0) break; else sink({(char *) buf.data(), (size_t) rd}); } } ////////////////////////////////////////////////////////////////////// unsigned int getMaxCPU() { #if __linux__ try { auto cgroupFS = getCgroupFS(); if (!cgroupFS) return 0; auto cgroups = getCgroups("/proc/self/cgroup"); auto cgroup = cgroups[""]; if (cgroup == "") return 0; auto cpuFile = *cgroupFS + "/" + cgroup + "/cpu.max"; auto cpuMax = readFile(cpuFile); auto cpuMaxParts = tokenizeString>(cpuMax, " \n"); if (cpuMaxParts.size() != 2) { return 0; } auto quota = cpuMaxParts[0]; auto period = cpuMaxParts[1]; if (quota != "max") return std::ceil(std::stoi(quota) / std::stof(period)); } catch (Error &) { ignoreException(lvlDebug); } #endif return 0; } ////////////////////////////////////////////////////////////////////// AutoCloseFD::AutoCloseFD() : fd{-1} {} AutoCloseFD::AutoCloseFD(int fd) : fd{fd} {} AutoCloseFD::AutoCloseFD(AutoCloseFD && that) : fd{that.fd} { that.fd = -1; } AutoCloseFD & AutoCloseFD::operator =(AutoCloseFD && that) { close(); fd = that.fd; that.fd = -1; return *this; } AutoCloseFD::~AutoCloseFD() { try { close(); } catch (...) { ignoreException(); } } int AutoCloseFD::get() const { return fd; } void AutoCloseFD::close() { if (fd != -1) { if (::close(fd) == -1) /* This should never happen. */ throw SysError("closing file descriptor %1%", fd); fd = -1; } } void AutoCloseFD::fsync() { if (fd != -1) { int result; #if __APPLE__ result = ::fcntl(fd, F_FULLFSYNC); #else result = ::fsync(fd); #endif if (result == -1) throw SysError("fsync file descriptor %1%", fd); } } AutoCloseFD::operator bool() const { return fd != -1; } int AutoCloseFD::release() { int oldFD = fd; fd = -1; return oldFD; } void Pipe::create() { int fds[2]; #if HAVE_PIPE2 if (pipe2(fds, O_CLOEXEC) != 0) throw SysError("creating pipe"); #else if (pipe(fds) != 0) throw SysError("creating pipe"); closeOnExec(fds[0]); closeOnExec(fds[1]); #endif readSide = AutoCloseFD{fds[0]}; writeSide = AutoCloseFD{fds[1]}; } void Pipe::close() { readSide.close(); writeSide.close(); } ////////////////////////////////////////////////////////////////////// Pid::Pid() { } Pid::Pid(pid_t pid) : pid(pid) { } Pid::~Pid() noexcept(false) { if (pid != -1) kill(); } void Pid::operator =(pid_t pid) { if (this->pid != -1 && this->pid != pid) kill(); this->pid = pid; killSignal = SIGKILL; // reset signal to default } Pid::operator pid_t() { return pid; } int Pid::kill() { assert(pid != -1); 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 process group (which hopefully includes *all* its children). */ if (::kill(separatePG ? -pid : pid, killSignal) != 0) { /* On BSDs, killing a process group will return EPERM if all processes in the group are zombies (or something like that). So try to detect and ignore that situation. */ #if __FreeBSD__ || __APPLE__ if (errno != EPERM || ::kill(pid, 0) != 0) #endif logError(SysError("killing process %d", pid).info()); } return wait(); } int Pid::wait() { assert(pid != -1); while (1) { int status; int res = waitpid(pid, &status, 0); if (res == pid) { pid = -1; return status; } if (errno != EINTR) throw SysError("cannot get exit status of PID %d", pid); checkInterrupt(); } } void Pid::setSeparatePG(bool separatePG) { this->separatePG = separatePG; } void Pid::setKillSignal(int signal) { this->killSignal = signal; } pid_t Pid::release() { pid_t p = pid; pid = -1; return p; } void killUser(uid_t uid) { debug("killing all processes running under uid '%1%'", uid); assert(uid != 0); /* just to be safe... */ /* The system call kill(-1, sig) sends the signal `sig' to all users to which the current process can send signals. So we fork a process, switch to uid, and send a mass kill. */ Pid pid = startProcess([&]() { if (setuid(uid) == -1) throw SysError("setting uid"); while (true) { #ifdef __APPLE__ /* OSX's kill syscall takes a third parameter that, among other things, determines if kill(-1, signo) affects the calling process. In the OSX libc, it's set to true, which means "follow POSIX", which we don't want here */ if (syscall(SYS_kill, -1, SIGKILL, false) == 0) break; #else if (kill(-1, SIGKILL) == 0) break; #endif if (errno == ESRCH || errno == EPERM) break; /* no more processes */ if (errno != EINTR) throw SysError("cannot kill processes for uid '%1%'", uid); } _exit(0); }); int status = pid.wait(); if (status != 0) 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 way to do so (I think). The most reliable way may be `ps -eo uid | grep -q $uid'. */ } ////////////////////////////////////////////////////////////////////// static pid_t doFork(std::function fun) { pid_t pid = fork(); if (pid != 0) return pid; fun(); abort(); } #if __linux__ static int childEntry(void * arg) { auto main = (std::function *) arg; (*main)(); return 1; } #endif pid_t startProcess(std::function fun, const ProcessOptions & options) { std::function wrapper = [&]() { logger = makeSimpleLogger(); try { #if __linux__ if (options.dieWithParent && prctl(PR_SET_PDEATHSIG, SIGKILL) == -1) throw SysError("setting death signal"); #endif fun(); } catch (std::exception & e) { try { std::cerr << options.errorPrefix << e.what() << "\n"; } catch (...) { } } catch (...) { } if (options.runExitHandlers) exit(1); else _exit(1); }; pid_t pid = -1; if (options.cloneFlags) { #ifdef __linux__ // Not supported, since then we don't know when to free the stack. assert(!(options.cloneFlags & CLONE_VM)); size_t stackSize = 1 * 1024 * 1024; auto stack = (char *) mmap(0, stackSize, PROT_WRITE | PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0); if (stack == MAP_FAILED) throw SysError("allocating stack"); Finally freeStack([&]() { munmap(stack, stackSize); }); pid = clone(childEntry, stack + stackSize, options.cloneFlags | SIGCHLD, &wrapper); #else throw Error("clone flags are only supported on Linux"); #endif } else pid = doFork(wrapper); if (pid == -1) throw SysError("unable to fork"); return pid; } std::vector stringsToCharPtrs(const Strings & ss) { std::vector res; for (auto & s : ss) res.push_back((char *) s.c_str()); res.push_back(0); return res; } std::string runProgram(Path program, bool searchPath, const Strings & args, const std::optional & input, bool isInteractive) { auto res = runProgram(RunOptions {.program = program, .searchPath = searchPath, .args = args, .input = input, .isInteractive = isInteractive}); if (!statusOk(res.first)) throw ExecError(res.first, "program '%1%' %2%", program, statusToString(res.first)); return res.second; } // Output = error code + "standard out" output stream std::pair runProgram(RunOptions && options) { StringSink sink; options.standardOut = &sink; int status = 0; try { runProgram2(options); } catch (ExecError & e) { status = e.status; } return {status, std::move(sink.s)}; } void runProgram2(const RunOptions & options) { checkInterrupt(); assert(!(options.standardIn && options.input)); std::unique_ptr source_; Source * source = options.standardIn; if (options.input) { source_ = std::make_unique(*options.input); source = source_.get(); } /* Create a pipe. */ Pipe out, in; if (options.standardOut) out.create(); if (source) in.create(); ProcessOptions processOptions; std::optional>> resumeLoggerDefer; if (options.isInteractive) { logger->pause(); resumeLoggerDefer.emplace( []() { logger->resume(); } ); } /* Fork. */ Pid pid = startProcess([&]() { if (options.environment) replaceEnv(*options.environment); if (options.standardOut && dup2(out.writeSide.get(), STDOUT_FILENO) == -1) throw SysError("dupping stdout"); if (options.mergeStderrToStdout) if (dup2(STDOUT_FILENO, STDERR_FILENO) == -1) throw SysError("cannot dup stdout into stderr"); if (source && dup2(in.readSide.get(), STDIN_FILENO) == -1) throw SysError("dupping stdin"); if (options.chdir && chdir((*options.chdir).c_str()) == -1) throw SysError("chdir failed"); if (options.gid && setgid(*options.gid) == -1) throw SysError("setgid failed"); /* Drop all other groups if we're setgid. */ if (options.gid && setgroups(0, 0) == -1) throw SysError("setgroups failed"); if (options.uid && setuid(*options.uid) == -1) throw SysError("setuid failed"); Strings args_(options.args); args_.push_front(options.program); restoreProcessContext(); if (options.searchPath) execvp(options.program.c_str(), stringsToCharPtrs(args_).data()); // This allows you to refer to a program with a pathname relative // to the PATH variable. else execv(options.program.c_str(), stringsToCharPtrs(args_).data()); throw SysError("executing '%1%'", options.program); }, processOptions); out.writeSide.close(); std::thread writerThread; std::promise promise; Finally doJoin([&]() { if (writerThread.joinable()) writerThread.join(); }); if (source) { in.readSide.close(); writerThread = std::thread([&]() { try { std::vector buf(8 * 1024); while (true) { size_t n; try { n = source->read(buf.data(), buf.size()); } catch (EndOfFile &) { break; } writeFull(in.writeSide.get(), {buf.data(), n}); } promise.set_value(); } catch (...) { promise.set_exception(std::current_exception()); } in.writeSide.close(); }); } if (options.standardOut) drainFD(out.readSide.get(), *options.standardOut); /* Wait for the child to finish. */ int status = pid.wait(); /* Wait for the writer thread to finish. */ if (source) promise.get_future().get(); if (status) throw ExecError(status, "program '%1%' %2%", options.program, statusToString(status)); } void closeMostFDs(const std::set & exceptions) { #if __linux__ try { for (auto & s : readDirectory("/proc/self/fd")) { auto fd = std::stoi(s.name); if (!exceptions.count(fd)) { debug("closing leaked FD %d", fd); close(fd); } } return; } catch (SysError &) { } #endif int maxFD = 0; maxFD = sysconf(_SC_OPEN_MAX); for (int fd = 0; fd < maxFD; ++fd) if (!exceptions.count(fd)) close(fd); /* ignore result */ } void closeOnExec(int fd) { int prev; if ((prev = fcntl(fd, F_GETFD, 0)) == -1 || fcntl(fd, F_SETFD, prev | FD_CLOEXEC) == -1) throw SysError("setting close-on-exec flag"); } ////////////////////////////////////////////////////////////////////// template C tokenizeString(std::string_view s, std::string_view separators) { C result; auto pos = s.find_first_not_of(separators, 0); while (pos != std::string_view::npos) { auto end = s.find_first_of(separators, pos + 1); if (end == std::string_view::npos) end = s.size(); result.insert(result.end(), std::string(s, pos, end - pos)); pos = s.find_first_not_of(separators, end); } return result; } template Strings tokenizeString(std::string_view s, std::string_view separators); template StringSet tokenizeString(std::string_view s, std::string_view separators); template std::vector tokenizeString(std::string_view s, std::string_view separators); std::string chomp(std::string_view s) { size_t i = s.find_last_not_of(" \n\r\t"); return i == std::string_view::npos ? "" : std::string(s, 0, i + 1); } std::string trim(std::string_view s, std::string_view whitespace) { auto i = s.find_first_not_of(whitespace); if (i == s.npos) return ""; auto j = s.find_last_not_of(whitespace); return std::string(s, i, j == s.npos ? j : j - i + 1); } std::string replaceStrings( std::string res, std::string_view from, std::string_view to) { if (from.empty()) return res; size_t pos = 0; while ((pos = res.find(from, pos)) != std::string::npos) { res.replace(pos, from.size(), to); pos += to.size(); } return res; } Rewriter::Rewriter(std::map rewrites) : rewrites(std::move(rewrites)) { for (const auto & [k, v] : this->rewrites) { assert(!k.empty()); initials.push_back(k[0]); } std::ranges::sort(initials); auto [firstDupe, end] = std::ranges::unique(initials); initials.erase(firstDupe, end); } std::string Rewriter::operator()(std::string s) { size_t j = 0; while ((j = s.find_first_of(initials, j)) != std::string::npos) { size_t skip = 1; for (auto & [from, to] : rewrites) { if (s.compare(j, from.size(), from) == 0) { s.replace(j, from.size(), to); skip = to.size(); break; } } j += skip; } return s; } std::string statusToString(int status) { if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { if (WIFEXITED(status)) return fmt("failed with exit code %1%", WEXITSTATUS(status)); else if (WIFSIGNALED(status)) { int sig = WTERMSIG(status); #if HAVE_STRSIGNAL const char * description = strsignal(sig); return fmt("failed due to signal %1% (%2%)", sig, description); #else return fmt("failed due to signal %1%", sig); #endif } else return "died abnormally"; } else return "succeeded"; } bool statusOk(int status) { return WIFEXITED(status) && WEXITSTATUS(status) == 0; } std::string toLower(const std::string & s) { std::string r(s); for (auto & c : r) c = std::tolower(c); return r; } std::string shellEscape(const std::string_view s) { std::string r; r.reserve(s.size() + 2); r += "'"; for (auto & i : s) if (i == '\'') r += "'\\''"; else r += i; r += '\''; return r; } void ignoreException(Verbosity lvl) { /* Make sure no exceptions leave this function. printError() also throws when remote is closed. */ try { try { throw; } catch (std::exception & e) { printMsg(lvl, "error (ignored): %1%", e.what()); } } catch (...) { } } constexpr char base64Chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; std::string base64Encode(std::string_view s) { std::string res; res.reserve((s.size() + 2) / 3 * 4); int data = 0, nbits = 0; for (char c : s) { data = data << 8 | (unsigned char) c; nbits += 8; while (nbits >= 6) { nbits -= 6; res.push_back(base64Chars[data >> nbits & 0x3f]); } } if (nbits) res.push_back(base64Chars[data << (6 - nbits) & 0x3f]); while (res.size() % 4) res.push_back('='); return res; } std::string base64Decode(std::string_view s) { constexpr char npos = -1; constexpr std::array base64DecodeChars = [&]() { std::array result{}; for (auto& c : result) c = npos; for (int i = 0; i < 64; i++) result[base64Chars[i]] = i; return result; }(); std::string res; // Some sequences are missing the padding consisting of up to two '='. // vvv res.reserve((s.size() + 2) / 4 * 3); unsigned int d = 0, bits = 0; for (char c : s) { if (c == '=') break; if (c == '\n') continue; char digit = base64DecodeChars[(unsigned char) c]; if (digit == npos) throw Error("invalid character in Base64 string: '%c'", c); bits += 6; d = d << 6 | digit; if (bits >= 8) { res.push_back(d >> (bits - 8) & 0xff); bits -= 8; } } return res; } std::string stripIndentation(std::string_view s) { size_t minIndent = 10000; size_t curIndent = 0; bool atStartOfLine = true; for (auto & c : s) { if (atStartOfLine && c == ' ') curIndent++; else if (c == '\n') { if (atStartOfLine) minIndent = std::max(minIndent, curIndent); curIndent = 0; atStartOfLine = true; } else { if (atStartOfLine) { minIndent = std::min(minIndent, curIndent); atStartOfLine = false; } } } std::string res; size_t pos = 0; while (pos < s.size()) { auto eol = s.find('\n', pos); if (eol == s.npos) eol = s.size(); if (eol - pos > minIndent) res.append(s.substr(pos + minIndent, eol - pos - minIndent)); res.push_back('\n'); pos = eol + 1; } return res; } std::pair getLine(std::string_view s) { auto newline = s.find('\n'); if (newline == s.npos) { return {s, ""}; } else { auto line = s.substr(0, newline); if (!line.empty() && line[line.size() - 1] == '\r') line = line.substr(0, line.size() - 1); return {line, s.substr(newline + 1)}; } } ////////////////////////////////////////////////////////////////////// rlim_t savedStackSize = 0; void setStackSize(rlim_t stackSize) { struct rlimit limit; if (getrlimit(RLIMIT_STACK, &limit) == 0 && limit.rlim_cur < stackSize) { savedStackSize = limit.rlim_cur; limit.rlim_cur = std::min(stackSize, limit.rlim_max); if (setrlimit(RLIMIT_STACK, &limit) != 0) { logger->log( lvlError, HintFmt( "Failed to increase stack size from %1% to %2% (maximum allowed stack size: %3%): %4%", savedStackSize, stackSize, limit.rlim_max, std::strerror(errno) ).str() ); } } } #if __linux__ static AutoCloseFD fdSavedMountNamespace; static AutoCloseFD fdSavedRoot; #endif void saveMountNamespace() { #if __linux__ static std::once_flag done; std::call_once(done, []() { fdSavedMountNamespace = AutoCloseFD{open("/proc/self/ns/mnt", O_RDONLY)}; if (!fdSavedMountNamespace) throw SysError("saving parent mount namespace"); fdSavedRoot = AutoCloseFD{open("/proc/self/root", O_RDONLY)}; }); #endif } void restoreMountNamespace() { #if __linux__ try { auto savedCwd = absPath("."); if (fdSavedMountNamespace && setns(fdSavedMountNamespace.get(), CLONE_NEWNS) == -1) throw SysError("restoring parent mount namespace"); if (fdSavedRoot) { if (fchdir(fdSavedRoot.get())) throw SysError("chdir into saved root"); if (chroot(".")) throw SysError("chroot into saved root"); } if (chdir(savedCwd.c_str()) == -1) throw SysError("restoring cwd"); } catch (Error & e) { debug(e.msg()); } #endif } void unshareFilesystem() { #ifdef __linux__ if (unshare(CLONE_FS) != 0 && errno != EPERM) throw SysError("unsharing filesystem state in download thread"); #endif } void restoreProcessContext(bool restoreMounts) { restoreSignals(); if (restoreMounts) { restoreMountNamespace(); } if (savedStackSize) { struct rlimit limit; if (getrlimit(RLIMIT_STACK, &limit) == 0) { limit.rlim_cur = savedStackSize; setrlimit(RLIMIT_STACK, &limit); } } } AutoCloseFD createUnixDomainSocket() { AutoCloseFD fdSocket{socket(PF_UNIX, SOCK_STREAM #ifdef SOCK_CLOEXEC | SOCK_CLOEXEC #endif , 0)}; if (!fdSocket) throw SysError("cannot create Unix domain socket"); closeOnExec(fdSocket.get()); return fdSocket; } AutoCloseFD createUnixDomainSocket(const Path & path, mode_t mode) { auto fdSocket = nix::createUnixDomainSocket(); bind(fdSocket.get(), path); chmodPath(path.c_str(), mode); if (listen(fdSocket.get(), 100) == -1) throw SysError("cannot listen on socket '%1%'", path); return fdSocket; } static void bindConnectProcHelper( std::string_view operationName, auto && operation, int fd, const std::string & path) { struct sockaddr_un addr; addr.sun_family = AF_UNIX; // Casting between types like these legacy C library interfaces // require is forbidden in C++. To maintain backwards // compatibility, the implementation of the bind/connect functions // contains some hints to the compiler that allow for this // special case. auto * psaddr = reinterpret_cast(&addr); if (path.size() + 1 >= sizeof(addr.sun_path)) { Pipe pipe; pipe.create(); Pid pid = startProcess([&] { try { pipe.readSide.close(); Path dir = dirOf(path); if (chdir(dir.c_str()) == -1) throw SysError("chdir to '%s' failed", dir); std::string base(baseNameOf(path)); if (base.size() + 1 >= sizeof(addr.sun_path)) throw Error("socket path '%s' is too long", base); memcpy(addr.sun_path, base.c_str(), base.size() + 1); if (operation(fd, psaddr, sizeof(addr)) == -1) throw SysError("cannot %s to socket at '%s'", operationName, path); writeFull(pipe.writeSide.get(), "0\n"); } catch (SysError & e) { writeFull(pipe.writeSide.get(), fmt("%d\n", e.errNo)); } catch (...) { writeFull(pipe.writeSide.get(), "-1\n"); } }); pipe.writeSide.close(); auto errNo = string2Int(chomp(drainFD(pipe.readSide.get()))); if (!errNo || *errNo == -1) throw Error("cannot %s to socket at '%s'", operationName, path); else if (*errNo > 0) { errno = *errNo; throw SysError("cannot %s to socket at '%s'", operationName, path); } } else { memcpy(addr.sun_path, path.c_str(), path.size() + 1); if (operation(fd, psaddr, sizeof(addr)) == -1) throw SysError("cannot %s to socket at '%s'", operationName, path); } } void bind(int fd, const std::string & path) { unlink(path.c_str()); bindConnectProcHelper("bind", ::bind, fd, path); } void connect(int fd, const std::string & path) { bindConnectProcHelper("connect", ::connect, fd, path); } std::string showBytes(uint64_t bytes) { return fmt("%.2f MiB", bytes / (1024.0 * 1024.0)); } // FIXME: move to libstore/build void commonChildInit() { logger = makeSimpleLogger(); const static std::string pathNullDevice = "/dev/null"; restoreProcessContext(false); /* 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("creating a new session"); /* 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("cannot open '%1%'", pathNullDevice); if (dup2(fdDevNull, STDIN_FILENO) == -1) throw SysError("cannot dup null device into stdin"); close(fdDevNull); } }