diff options
Diffstat (limited to '9ns/src/ns.zig')
| -rw-r--r-- | 9ns/src/ns.zig | 83 |
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 |
