mirror of
https://github.com/NixOS/nix.git
synced 2025-11-25 03:39:36 +01:00
Move /src to /subprojects
This will facilitate breaking up Nix into multiple packages for each component with Meson.
This commit is contained in:
parent
4db9487823
commit
84e2963f8e
737 changed files with 504 additions and 505 deletions
166
subprojects/libutil/linux/cgroup.cc
Normal file
166
subprojects/libutil/linux/cgroup.cc
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
#include "cgroup.hh"
|
||||
#include "signals.hh"
|
||||
#include "util.hh"
|
||||
#include "file-system.hh"
|
||||
#include "finally.hh"
|
||||
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <regex>
|
||||
#include <unordered_set>
|
||||
#include <thread>
|
||||
|
||||
#include <dirent.h>
|
||||
#include <mntent.h>
|
||||
|
||||
namespace nix {
|
||||
|
||||
std::optional<Path> getCgroupFS()
|
||||
{
|
||||
static auto res = [&]() -> std::optional<Path> {
|
||||
auto fp = fopen("/proc/mounts", "r");
|
||||
if (!fp) return std::nullopt;
|
||||
Finally delFP = [&]() { fclose(fp); };
|
||||
while (auto ent = getmntent(fp))
|
||||
if (std::string_view(ent->mnt_type) == "cgroup2")
|
||||
return ent->mnt_dir;
|
||||
|
||||
return std::nullopt;
|
||||
}();
|
||||
return res;
|
||||
}
|
||||
|
||||
// FIXME: obsolete, check for cgroup2
|
||||
std::map<std::string, std::string> getCgroups(const Path & cgroupFile)
|
||||
{
|
||||
std::map<std::string, std::string> cgroups;
|
||||
|
||||
for (auto & line : tokenizeString<std::vector<std::string>>(readFile(cgroupFile), "\n")) {
|
||||
static std::regex regex("([0-9]+):([^:]*):(.*)");
|
||||
std::smatch match;
|
||||
if (!std::regex_match(line, match, regex))
|
||||
throw Error("invalid line '%s' in '%s'", line, cgroupFile);
|
||||
|
||||
std::string name = hasPrefix(std::string(match[2]), "name=") ? std::string(match[2], 5) : match[2];
|
||||
cgroups.insert_or_assign(name, match[3]);
|
||||
}
|
||||
|
||||
return cgroups;
|
||||
}
|
||||
|
||||
static CgroupStats destroyCgroup(const std::filesystem::path & cgroup, bool returnStats)
|
||||
{
|
||||
if (!pathExists(cgroup)) return {};
|
||||
|
||||
auto procsFile = cgroup / "cgroup.procs";
|
||||
|
||||
if (!pathExists(procsFile))
|
||||
throw Error("'%s' is not a cgroup", cgroup);
|
||||
|
||||
/* Use the fast way to kill every process in a cgroup, if
|
||||
available. */
|
||||
auto killFile = cgroup / "cgroup.kill";
|
||||
if (pathExists(killFile))
|
||||
writeFile(killFile, "1");
|
||||
|
||||
/* Otherwise, manually kill every process in the subcgroups and
|
||||
this cgroup. */
|
||||
for (auto & entry : std::filesystem::directory_iterator{cgroup}) {
|
||||
checkInterrupt();
|
||||
if (entry.symlink_status().type() != std::filesystem::file_type::directory) continue;
|
||||
destroyCgroup(cgroup / entry.path().filename(), false);
|
||||
}
|
||||
|
||||
int round = 1;
|
||||
|
||||
std::unordered_set<pid_t> pidsShown;
|
||||
|
||||
while (true) {
|
||||
auto pids = tokenizeString<std::vector<std::string>>(readFile(procsFile));
|
||||
|
||||
if (pids.empty()) break;
|
||||
|
||||
if (round > 20)
|
||||
throw Error("cannot kill cgroup '%s'", cgroup);
|
||||
|
||||
for (auto & pid_s : pids) {
|
||||
pid_t pid;
|
||||
if (auto o = string2Int<pid_t>(pid_s))
|
||||
pid = *o;
|
||||
else
|
||||
throw Error("invalid pid '%s'", pid);
|
||||
if (pidsShown.insert(pid).second) {
|
||||
try {
|
||||
auto cmdline = readFile(fmt("/proc/%d/cmdline", pid));
|
||||
using namespace std::string_literals;
|
||||
warn("killing stray builder process %d (%s)...",
|
||||
pid, trim(replaceStrings(cmdline, "\0"s, " ")));
|
||||
} catch (SystemError &) {
|
||||
}
|
||||
}
|
||||
// FIXME: pid wraparound
|
||||
if (kill(pid, SIGKILL) == -1 && errno != ESRCH)
|
||||
throw SysError("killing member %d of cgroup '%s'", pid, cgroup);
|
||||
}
|
||||
|
||||
auto sleep = std::chrono::milliseconds((int) std::pow(2.0, std::min(round, 10)));
|
||||
if (sleep.count() > 100)
|
||||
printError("waiting for %d ms for cgroup '%s' to become empty", sleep.count(), cgroup);
|
||||
std::this_thread::sleep_for(sleep);
|
||||
round++;
|
||||
}
|
||||
|
||||
CgroupStats stats;
|
||||
|
||||
if (returnStats) {
|
||||
auto cpustatPath = cgroup / "cpu.stat";
|
||||
|
||||
if (pathExists(cpustatPath)) {
|
||||
for (auto & line : tokenizeString<std::vector<std::string>>(readFile(cpustatPath), "\n")) {
|
||||
std::string_view userPrefix = "user_usec ";
|
||||
if (hasPrefix(line, userPrefix)) {
|
||||
auto n = string2Int<uint64_t>(line.substr(userPrefix.size()));
|
||||
if (n) stats.cpuUser = std::chrono::microseconds(*n);
|
||||
}
|
||||
|
||||
std::string_view systemPrefix = "system_usec ";
|
||||
if (hasPrefix(line, systemPrefix)) {
|
||||
auto n = string2Int<uint64_t>(line.substr(systemPrefix.size()));
|
||||
if (n) stats.cpuSystem = std::chrono::microseconds(*n);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (rmdir(cgroup.c_str()) == -1)
|
||||
throw SysError("deleting cgroup '%s'", cgroup);
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
CgroupStats destroyCgroup(const Path & cgroup)
|
||||
{
|
||||
return destroyCgroup(cgroup, true);
|
||||
}
|
||||
|
||||
std::string getCurrentCgroup()
|
||||
{
|
||||
auto cgroupFS = getCgroupFS();
|
||||
if (!cgroupFS)
|
||||
throw Error("cannot determine the cgroups file system");
|
||||
|
||||
auto ourCgroups = getCgroups("/proc/self/cgroup");
|
||||
auto ourCgroup = ourCgroups[""];
|
||||
if (ourCgroup == "")
|
||||
throw Error("cannot determine cgroup name from /proc/self/cgroup");
|
||||
return ourCgroup;
|
||||
}
|
||||
|
||||
std::string getRootCgroup()
|
||||
{
|
||||
static std::string rootCgroup = getCurrentCgroup();
|
||||
return rootCgroup;
|
||||
}
|
||||
|
||||
}
|
||||
37
subprojects/libutil/linux/cgroup.hh
Normal file
37
subprojects/libutil/linux/cgroup.hh
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
#pragma once
|
||||
///@file
|
||||
|
||||
#include <chrono>
|
||||
#include <optional>
|
||||
|
||||
#include "types.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
std::optional<Path> getCgroupFS();
|
||||
|
||||
std::map<std::string, std::string> getCgroups(const Path & cgroupFile);
|
||||
|
||||
struct CgroupStats
|
||||
{
|
||||
std::optional<std::chrono::microseconds> cpuUser, cpuSystem;
|
||||
};
|
||||
|
||||
/**
|
||||
* Destroy the cgroup denoted by 'path'. The postcondition is that
|
||||
* 'path' does not exist, and thus any processes in the cgroup have
|
||||
* been killed. Also return statistics from the cgroup just before
|
||||
* destruction.
|
||||
*/
|
||||
CgroupStats destroyCgroup(const Path & cgroup);
|
||||
|
||||
std::string getCurrentCgroup();
|
||||
|
||||
/**
|
||||
* Get the cgroup that should be used as the parent when creating new
|
||||
* sub-cgroups. The first time this is called, the current cgroup will be
|
||||
* returned, and then all subsequent calls will return the original cgroup.
|
||||
*/
|
||||
std::string getRootCgroup();
|
||||
|
||||
}
|
||||
11
subprojects/libutil/linux/meson.build
Normal file
11
subprojects/libutil/linux/meson.build
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
sources += files(
|
||||
'cgroup.cc',
|
||||
'namespaces.cc',
|
||||
)
|
||||
|
||||
include_dirs += include_directories('.')
|
||||
|
||||
headers += files(
|
||||
'cgroup.hh',
|
||||
'namespaces.hh',
|
||||
)
|
||||
146
subprojects/libutil/linux/namespaces.cc
Normal file
146
subprojects/libutil/linux/namespaces.cc
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
#include "current-process.hh"
|
||||
#include "util.hh"
|
||||
#include "finally.hh"
|
||||
#include "file-system.hh"
|
||||
#include "processes.hh"
|
||||
#include "signals.hh"
|
||||
|
||||
#include <mutex>
|
||||
#include <sys/resource.h>
|
||||
#include "cgroup.hh"
|
||||
|
||||
#include <sys/mount.h>
|
||||
|
||||
namespace nix {
|
||||
|
||||
bool userNamespacesSupported()
|
||||
{
|
||||
static auto res = [&]() -> bool
|
||||
{
|
||||
if (!pathExists("/proc/self/ns/user")) {
|
||||
debug("'/proc/self/ns/user' does not exist; your kernel was likely built without CONFIG_USER_NS=y");
|
||||
return false;
|
||||
}
|
||||
|
||||
Path maxUserNamespaces = "/proc/sys/user/max_user_namespaces";
|
||||
if (!pathExists(maxUserNamespaces) ||
|
||||
trim(readFile(maxUserNamespaces)) == "0")
|
||||
{
|
||||
debug("user namespaces appear to be disabled; check '/proc/sys/user/max_user_namespaces'");
|
||||
return false;
|
||||
}
|
||||
|
||||
Path procSysKernelUnprivilegedUsernsClone = "/proc/sys/kernel/unprivileged_userns_clone";
|
||||
if (pathExists(procSysKernelUnprivilegedUsernsClone)
|
||||
&& trim(readFile(procSysKernelUnprivilegedUsernsClone)) == "0")
|
||||
{
|
||||
debug("user namespaces appear to be disabled; check '/proc/sys/kernel/unprivileged_userns_clone'");
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
Pid pid = startProcess([&]()
|
||||
{
|
||||
_exit(0);
|
||||
}, {
|
||||
.cloneFlags = CLONE_NEWUSER
|
||||
});
|
||||
|
||||
auto r = pid.wait();
|
||||
assert(!r);
|
||||
} catch (SysError & e) {
|
||||
debug("user namespaces do not work on this system: %s", e.msg());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}();
|
||||
return res;
|
||||
}
|
||||
|
||||
bool mountAndPidNamespacesSupported()
|
||||
{
|
||||
static auto res = [&]() -> bool
|
||||
{
|
||||
try {
|
||||
|
||||
Pid pid = startProcess([&]()
|
||||
{
|
||||
/* Make sure we don't remount the parent's /proc. */
|
||||
if (mount(0, "/", 0, MS_PRIVATE | MS_REC, 0) == -1)
|
||||
_exit(1);
|
||||
|
||||
/* Test whether we can remount /proc. The kernel disallows
|
||||
this if /proc is not fully visible, i.e. if there are
|
||||
filesystems mounted on top of files inside /proc. See
|
||||
https://lore.kernel.org/lkml/87tvsrjai0.fsf@xmission.com/T/. */
|
||||
if (mount("none", "/proc", "proc", 0, 0) == -1)
|
||||
_exit(2);
|
||||
|
||||
_exit(0);
|
||||
}, {
|
||||
.cloneFlags = CLONE_NEWNS | CLONE_NEWPID | (userNamespacesSupported() ? CLONE_NEWUSER : 0)
|
||||
});
|
||||
|
||||
if (pid.wait()) {
|
||||
debug("PID namespaces do not work on this system: cannot remount /proc");
|
||||
return false;
|
||||
}
|
||||
|
||||
} catch (SysError & e) {
|
||||
debug("mount namespaces do not work on this system: %s", e.msg());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}();
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
static AutoCloseFD fdSavedMountNamespace;
|
||||
static AutoCloseFD fdSavedRoot;
|
||||
|
||||
void saveMountNamespace()
|
||||
{
|
||||
static std::once_flag done;
|
||||
std::call_once(done, []() {
|
||||
fdSavedMountNamespace = open("/proc/self/ns/mnt", O_RDONLY);
|
||||
if (!fdSavedMountNamespace)
|
||||
throw SysError("saving parent mount namespace");
|
||||
|
||||
fdSavedRoot = open("/proc/self/root", O_RDONLY);
|
||||
});
|
||||
}
|
||||
|
||||
void restoreMountNamespace()
|
||||
{
|
||||
try {
|
||||
auto savedCwd = std::filesystem::current_path();
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
void tryUnshareFilesystem()
|
||||
{
|
||||
if (unshare(CLONE_FS) != 0 && errno != EPERM && errno != ENOSYS)
|
||||
throw SysError("unsharing filesystem state");
|
||||
}
|
||||
|
||||
}
|
||||
35
subprojects/libutil/linux/namespaces.hh
Normal file
35
subprojects/libutil/linux/namespaces.hh
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
#pragma once
|
||||
///@file
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "types.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
/**
|
||||
* Save the current mount namespace. Ignored if called more than
|
||||
* once.
|
||||
*/
|
||||
void saveMountNamespace();
|
||||
|
||||
/**
|
||||
* Restore the mount namespace saved by saveMountNamespace(). Ignored
|
||||
* if saveMountNamespace() was never called.
|
||||
*/
|
||||
void restoreMountNamespace();
|
||||
|
||||
/**
|
||||
* Cause this thread to try to not share any FS attributes with the main
|
||||
* thread, because this causes setns() in restoreMountNamespace() to
|
||||
* fail.
|
||||
*
|
||||
* This is best effort -- EPERM and ENOSYS failures are just ignored.
|
||||
*/
|
||||
void tryUnshareFilesystem();
|
||||
|
||||
bool userNamespacesSupported();
|
||||
|
||||
bool mountAndPidNamespacesSupported();
|
||||
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue