aboutsummaryrefslogtreecommitdiff
path: root/src/libutil
diff options
context:
space:
mode:
authorJohn Ericson <John.Ericson@Obsidian.Systems>2023-02-28 11:34:34 -0500
committerJohn Ericson <John.Ericson@Obsidian.Systems>2023-02-28 11:34:34 -0500
commitd12f57c2c0ef32180875aa4a0b803c838a7d988f (patch)
tree2c046d98bc0bd9171881ff0b6d56fa89c7c642e6 /src/libutil
parentc36b584f8eb103afa152ef4304cf9fd5c3ebaaf0 (diff)
parent4489def1b36aeaee2254159efc1c21c868cc8585 (diff)
Merge remote-tracking branch 'upstream/master' into path-info
Diffstat (limited to 'src/libutil')
-rw-r--r--src/libutil/args.cc10
-rw-r--r--src/libutil/namespaces.cc97
-rw-r--r--src/libutil/namespaces.hh13
-rw-r--r--src/libutil/tests/url.cc33
-rw-r--r--src/libutil/url.cc17
-rw-r--r--src/libutil/url.hh1
-rw-r--r--src/libutil/util.cc46
-rw-r--r--src/libutil/util.hh7
8 files changed, 215 insertions, 9 deletions
diff --git a/src/libutil/args.cc b/src/libutil/args.cc
index 2930913d6..35686a8aa 100644
--- a/src/libutil/args.cc
+++ b/src/libutil/args.cc
@@ -29,7 +29,15 @@ void Args::removeFlag(const std::string & longName)
void Completions::add(std::string completion, std::string description)
{
- assert(description.find('\n') == std::string::npos);
+ description = trim(description);
+ // ellipsize overflowing content on the back of the description
+ auto end_index = description.find_first_of(".\n");
+ if (end_index != std::string::npos) {
+ auto needs_ellipsis = end_index != description.size() - 1;
+ description.resize(end_index);
+ if (needs_ellipsis)
+ description.append(" [...]");
+ }
insert(Completion {
.completion = completion,
.description = description
diff --git a/src/libutil/namespaces.cc b/src/libutil/namespaces.cc
new file mode 100644
index 000000000..f66accb10
--- /dev/null
+++ b/src/libutil/namespaces.cc
@@ -0,0 +1,97 @@
+#if __linux__
+
+#include "namespaces.hh"
+#include "util.hh"
+#include "finally.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;
+}
+
+}
+
+#endif
diff --git a/src/libutil/namespaces.hh b/src/libutil/namespaces.hh
new file mode 100644
index 000000000..e82379b9c
--- /dev/null
+++ b/src/libutil/namespaces.hh
@@ -0,0 +1,13 @@
+#pragma once
+
+namespace nix {
+
+#if __linux__
+
+bool userNamespacesSupported();
+
+bool mountAndPidNamespacesSupported();
+
+#endif
+
+}
diff --git a/src/libutil/tests/url.cc b/src/libutil/tests/url.cc
index e0c438b4d..a908631e6 100644
--- a/src/libutil/tests/url.cc
+++ b/src/libutil/tests/url.cc
@@ -302,4 +302,37 @@ namespace nix {
ASSERT_EQ(d, s);
}
+
+ /* ----------------------------------------------------------------------------
+ * percentEncode
+ * --------------------------------------------------------------------------*/
+
+ TEST(percentEncode, encodesUrlEncodedString) {
+ std::string s = percentEncode("==@==");
+ std::string d = "%3D%3D%40%3D%3D";
+ ASSERT_EQ(d, s);
+ }
+
+ TEST(percentEncode, keepArgument) {
+ std::string a = percentEncode("abd / def");
+ std::string b = percentEncode("abd / def", "/");
+ ASSERT_EQ(a, "abd%20%2F%20def");
+ ASSERT_EQ(b, "abd%20/%20def");
+ }
+
+ TEST(percentEncode, inverseOfDecode) {
+ std::string original = "%3D%3D%40%3D%3D";
+ std::string once = percentEncode(original);
+ std::string back = percentDecode(once);
+
+ ASSERT_EQ(back, original);
+ }
+
+ TEST(percentEncode, trailingPercent) {
+ std::string s = percentEncode("==@==%");
+ std::string d = "%3D%3D%40%3D%3D%25";
+
+ ASSERT_EQ(d, s);
+ }
+
}
diff --git a/src/libutil/url.cc b/src/libutil/url.cc
index 4e43455e1..9e44241ac 100644
--- a/src/libutil/url.cc
+++ b/src/libutil/url.cc
@@ -88,17 +88,22 @@ std::map<std::string, std::string> decodeQuery(const std::string & query)
return result;
}
-std::string percentEncode(std::string_view s)
+const static std::string allowedInQuery = ":@/?";
+const static std::string allowedInPath = ":@/";
+
+std::string percentEncode(std::string_view s, std::string_view keep)
{
std::string res;
for (auto & c : s)
+ // unreserved + keep
if ((c >= 'a' && c <= 'z')
|| (c >= 'A' && c <= 'Z')
|| (c >= '0' && c <= '9')
- || strchr("-._~!$&'()*+,;=:@", c))
+ || strchr("-._~", c)
+ || keep.find(c) != std::string::npos)
res += c;
else
- res += fmt("%%%02x", (unsigned int) c);
+ res += fmt("%%%02X", (unsigned int) c);
return res;
}
@@ -109,9 +114,9 @@ std::string encodeQuery(const std::map<std::string, std::string> & ss)
for (auto & [name, value] : ss) {
if (!first) res += '&';
first = false;
- res += percentEncode(name);
+ res += percentEncode(name, allowedInQuery);
res += '=';
- res += percentEncode(value);
+ res += percentEncode(value, allowedInQuery);
}
return res;
}
@@ -122,7 +127,7 @@ std::string ParsedURL::to_string() const
scheme
+ ":"
+ (authority ? "//" + *authority : "")
- + path
+ + percentEncode(path, allowedInPath)
+ (query.empty() ? "" : "?" + encodeQuery(query))
+ (fragment.empty() ? "" : "#" + percentEncode(fragment));
}
diff --git a/src/libutil/url.hh b/src/libutil/url.hh
index 2a9fb34c1..ddd673d65 100644
--- a/src/libutil/url.hh
+++ b/src/libutil/url.hh
@@ -22,6 +22,7 @@ struct ParsedURL
MakeError(BadURL, Error);
std::string percentDecode(std::string_view in);
+std::string percentEncode(std::string_view s, std::string_view keep="");
std::map<std::string, std::string> decodeQuery(const std::string & query);
diff --git a/src/libutil/util.cc b/src/libutil/util.cc
index 40faa4bf2..885bae69c 100644
--- a/src/libutil/util.cc
+++ b/src/libutil/util.cc
@@ -36,6 +36,7 @@
#ifdef __linux__
#include <sys/prctl.h>
#include <sys/resource.h>
+#include <sys/mman.h>
#include <cmath>
#endif
@@ -608,6 +609,19 @@ Path getDataDir()
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<Path> getSelfExe()
{
@@ -1051,9 +1065,17 @@ static pid_t doFork(bool allowVfork, std::function<void()> fun)
}
+static int childEntry(void * arg)
+{
+ auto main = (std::function<void()> *) arg;
+ (*main)();
+ return 1;
+}
+
+
pid_t startProcess(std::function<void()> fun, const ProcessOptions & options)
{
- auto wrapper = [&]() {
+ std::function<void()> wrapper = [&]() {
if (!options.allowVfork)
logger = makeSimpleLogger();
try {
@@ -1073,7 +1095,27 @@ pid_t startProcess(std::function<void()> fun, const ProcessOptions & options)
_exit(1);
};
- pid_t pid = doFork(options.allowVfork, wrapper);
+ 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(options.allowVfork, wrapper);
+
if (pid == -1) throw SysError("unable to fork");
return pid;
diff --git a/src/libutil/util.hh b/src/libutil/util.hh
index 266da0ae3..b5625ecef 100644
--- a/src/libutil/util.hh
+++ b/src/libutil/util.hh
@@ -158,6 +158,12 @@ Path getDataDir();
/* Return the path of the current executable. */
std::optional<Path> getSelfExe();
+/* Return $XDG_STATE_HOME or $HOME/.local/state. */
+Path getStateDir();
+
+/* Create the Nix state directory and return the path to it. */
+Path createNixStateDir();
+
/* Create a directory and all its parents, if necessary. Returns the
list of created directories, in order of creation. */
Paths createDirs(const Path & path);
@@ -301,6 +307,7 @@ struct ProcessOptions
bool dieWithParent = true;
bool runExitHandlers = false;
bool allowVfork = false;
+ int cloneFlags = 0; // use clone() with the specified flags (Linux only)
};
pid_t startProcess(std::function<void()> fun, const ProcessOptions & options = ProcessOptions());