summaryrefslogtreecommitdiff
path: root/9ns/src/ns.zig
diff options
context:
space:
mode:
Diffstat (limited to '9ns/src/ns.zig')
-rw-r--r--9ns/src/ns.zig83
1 files changed, 67 insertions, 16 deletions
diff --git a/9ns/src/ns.zig b/9ns/src/ns.zig
index 6da2c7f..b4342e5 100644
--- a/9ns/src/ns.zig
+++ b/9ns/src/ns.zig
@@ -198,25 +198,47 @@ fn buildArgv(gpa: Allocator, argv: []const []const u8) ![:null]?[*:0]const u8 {
/// Make sure `path` is a directory, inside the *current* mount namespace:
///
/// * already a directory → done;
-/// * else `mkdir`; on `EACCES`/`EPERM`/`EROFS` shadow the parent directory
-/// with a tmpfs that re-exposes every existing entry (bind mounts for
-/// directories and files, recreated symlinks) and `mkdir` inside it;
+/// * else find the deepest existing ancestor and `mkdir` the missing
+/// components under it one by one (`mkdir -p`); the first of them failing
+/// with `EACCES`/`EPERM`/`EROFS` (the normal case for `/mnt/9p/<name>` as
+/// a plain user) means **shadow that ancestor**: mount a `tmpfs` over it
+/// that re-exposes every existing entry (bind mounts for directories and
+/// files, recreated symlinks), then create the missing components inside;
/// * anything else fails with the errno and a hint.
///
+/// So `/mnt/9p/x` on a host without `/mnt/9p` shadows `/mnt` and creates
+/// `9p/x`; with a root-owned `/mnt/9p` it shadows `/mnt/9p`; inside a 9ns
+/// namespace, where `/mnt/9p` is ours, it just creates `x`. `/` and `/proc`
+/// are never shadowed, nor a directory with more than `max_shadow_entries`.
+///
/// Every failure prints `9ns: <step> <path>: E<errno>` to stderr before
/// returning. Meant to be called in the child of `spawn` (or from a
/// throwaway namespace: `unshare -Urm`).
pub fn ensureMountpoint(gpa: Allocator, path: [:0]const u8) !void {
- if (fileType(linux.AT.FDCWD, path, false)) |ft| {
+ if (try existingKind(path)) |ft| {
if (ft == .dir) return;
std.debug.print("9ns: mountpoint {s}: exists but is not a directory\n", .{path});
return error.Mountpoint;
}
- if (fileType(linux.AT.FDCWD, path, true) == .symlink) {
- std.debug.print("9ns: mountpoint {s}: dangling symlink\n", .{path});
- return error.Mountpoint;
+ // Deepest existing ancestor: walk up until something is there.
+ var base: []const u8 = path;
+ while (true) {
+ base = std.fs.path.dirname(base) orelse "/";
+ var base_buf: [path_max]u8 = undefined;
+ const base_z = std.fmt.bufPrintZ(&base_buf, "{s}", .{base}) catch {
+ std.debug.print("9ns: mountpoint {s}: path too long\n", .{path});
+ return error.Mountpoint;
+ };
+ const kind = try existingKind(base_z) orelse continue;
+ if (kind != .dir) {
+ std.debug.print("9ns: mountpoint {s}: {s} is not a directory\n", .{ path, base });
+ return error.Mountpoint;
+ }
+ break;
}
- const mk = linux.errno(linux.mkdirat(linux.AT.FDCWD, path, 0o755));
+ // Missing components, deepest ancestor first.
+ const missing = path[base.len..];
+ const mk = mkdirComponents(path, base.len, missing);
switch (mk) {
.SUCCESS => return,
.ACCES, .PERM, .ROFS => {},
@@ -225,21 +247,20 @@ pub fn ensureMountpoint(gpa: Allocator, path: [:0]const u8) !void {
return error.Mountpoint;
},
}
- const parent = std.fs.path.dirname(path) orelse "/";
- if (std.mem.eql(u8, parent, "/") or isSameDirectory(parent, "/")) {
+ if (std.mem.eql(u8, base, "/") or isSameDirectory(base, "/")) {
std.debug.print("9ns: mkdir {s}: E{t}; refusing to shadow / (pass --mount an existing directory)\n", .{ path, mk });
return error.Mountpoint;
}
// The shadow rebuilds entries from /proc/self/fd/<fd>/<name>; a tmpfs
// over /proc (or a subtree of it) would take that away from itself.
- if (std.mem.eql(u8, parent, "/proc") or std.mem.startsWith(u8, parent, "/proc/")) {
- std.debug.print("9ns: mkdir {s}: E{t}; refusing to shadow {s} (pass --mount an existing directory)\n", .{ path, mk, parent });
+ if (std.mem.eql(u8, base, "/proc") or std.mem.startsWith(u8, base, "/proc/")) {
+ std.debug.print("9ns: mkdir {s}: E{t}; refusing to shadow {s} (pass --mount an existing directory)\n", .{ path, mk, base });
return error.Mountpoint;
}
- const parent_z = try gpa.dupeZ(u8, parent);
- defer gpa.free(parent_z);
- try shadowDirectory(gpa, parent_z);
- switch (linux.errno(linux.mkdirat(linux.AT.FDCWD, path, 0o755))) {
+ const base_z = try gpa.dupeZ(u8, base);
+ defer gpa.free(base_z);
+ try shadowDirectory(gpa, base_z);
+ switch (mkdirComponents(path, base.len, missing)) {
.SUCCESS => {},
else => |e| {
std.debug.print("9ns: mkdir {s} (in shadow tmpfs): E{t}\n", .{ path, e });
@@ -248,6 +269,36 @@ pub fn ensureMountpoint(gpa: Allocator, path: [:0]const u8) !void {
}
}
+/// What `path` is, following symlinks: null when nothing is there; an
+/// error (reported) for a dangling symlink.
+fn existingKind(path: [*:0]const u8) !?FileType {
+ if (fileType(linux.AT.FDCWD, path, false)) |ft| return ft;
+ if (fileType(linux.AT.FDCWD, path, true) == .symlink) {
+ std.debug.print("9ns: mountpoint {s}: dangling symlink\n", .{std.mem.span(path)});
+ return error.Mountpoint;
+ }
+ return null;
+}
+
+/// `mkdir` each component of `missing` (which is `path[base_len..]`, so
+/// it starts with '/') under the existing prefix `path[0..base_len]`, in
+/// order. Returns the errno of the first failure (`.SUCCESS` when all were
+/// created); `EEXIST` on a component is fine (another process, or a retry).
+fn mkdirComponents(path: []const u8, base_len: usize, missing: []const u8) E {
+ var buf: [path_max]u8 = undefined;
+ var end: usize = base_len;
+ var it = std.mem.tokenizeScalar(u8, missing, '/');
+ while (it.next()) |comp| {
+ end += 1 + comp.len;
+ const prefix = std.fmt.bufPrintZ(&buf, "{s}", .{path[0..end]}) catch return .NAMETOOLONG;
+ switch (linux.errno(linux.mkdirat(linux.AT.FDCWD, prefix, 0o755))) {
+ .SUCCESS, .EXIST => {},
+ else => |e| return e,
+ }
+ }
+ return .SUCCESS;
+}
+
const FileType = enum { dir, symlink, other };
/// True when both paths resolve (following symlinks, including magic ones