summaryrefslogtreecommitdiff
path: root/src/host_io.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/host_io.zig')
-rw-r--r--src/host_io.zig48
1 files changed, 41 insertions, 7 deletions
diff --git a/src/host_io.zig b/src/host_io.zig
index b138a840..f944e1cb 100644
--- a/src/host_io.zig
+++ b/src/host_io.zig
@@ -1287,7 +1287,7 @@ pub const ChildEnv = struct {
if (std.mem.eql(u8, name(entry.?), name(default))) break true;
} else false;
if (set) continue;
- if (n + own.len + 2 > slots.len) return null;
+ if (n + own.len + 4 > slots.len) return null;
slots[n] = default;
n += 1;
}
@@ -1305,23 +1305,51 @@ pub const ChildEnv = struct {
/// another; plain `pardes -` where the path cannot be read or quoted.
fn pardesPagers() []const [*:0]const u8 {
const S = struct {
- var pager: [std.fs.max_path_bytes + 16:0]u8 = undefined;
- var git_pager: [std.fs.max_path_bytes + 16:0]u8 = undefined;
- var entries: [2][*:0]const u8 = undefined;
+ var pager: [std.fs.max_path_bytes + 32:0]u8 = undefined;
+ var git_pager: [std.fs.max_path_bytes + 32:0]u8 = undefined;
+ var systemd_pager: [std.fs.max_path_bytes + 32:0]u8 = undefined;
+ var entries: [4][*:0]const u8 = undefined;
var ready = false;
};
if (!S.ready) {
var exe_buf: [std.fs.max_path_bytes]u8 = undefined;
+ var quoted_buf: [std.fs.max_path_bytes + 2]u8 = undefined;
const n = if (comptime builtin.os.tag == .linux) libc.readlink("/proc/self/exe", &exe_buf, exe_buf.len) else -1;
- const exe: []const u8 = if (n > 0 and @as(usize, @intCast(n)) < exe_buf.len and std.mem.indexOfScalar(u8, exe_buf[0..@intCast(n)], '\'') == null) exe_buf[0..@intCast(n)] else "pardes";
- S.entries[0] = if (std.fmt.bufPrintSentinel(&S.pager, "PAGER='{s}' -", .{exe}, 0)) |t| t.ptr else |_| "PAGER=pardes -";
- S.entries[1] = if (std.fmt.bufPrintSentinel(&S.git_pager, "GIT_PAGER='{s}' -", .{exe}, 0)) |t| t.ptr else |_| "GIT_PAGER=pardes -";
+ const exe: []const u8 = if (n > 0 and @as(usize, @intCast(n)) < exe_buf.len) exe_buf[0..@intCast(n)] else "pardes";
+ const command = pagerCommand(exe, &quoted_buf);
+ S.entries[0] = if (std.fmt.bufPrintSentinel(&S.pager, "PAGER={s} -", .{command}, 0)) |t| t.ptr else |_| "PAGER=pardes -";
+ S.entries[1] = if (std.fmt.bufPrintSentinel(&S.git_pager, "GIT_PAGER={s} -", .{command}, 0)) |t| t.ptr else |_| "GIT_PAGER=pardes -";
+ // systemd's tools (journalctl, systemctl) read their own, and
+ // in their secure mode run only less: this pager has no shell
+ // to escape to, so secure mode buys nothing.
+ S.entries[2] = if (std.fmt.bufPrintSentinel(&S.systemd_pager, "SYSTEMD_PAGER={s} -", .{command}, 0)) |t| t.ptr else |_| "SYSTEMD_PAGER=pardes -";
+ S.entries[3] = "SYSTEMD_PAGERSECURE=0";
S.ready = true;
}
return &S.entries;
}
+
+ /// The pager's path as a command line takes it: as it is when it needs
+ /// no quoting (systemd splits on blanks and execs, never through sh),
+ /// in single quotes when it holds a blank or a shell character, plain
+ /// `pardes` when even that cannot hold it.
+ fn pagerCommand(exe: []const u8, buf: []u8) []const u8 {
+ const plain = for (exe) |c| {
+ if (!(std.ascii.isAlphanumeric(c) or std.mem.indexOfScalar(u8, "/._+-,@%=:", c) != null)) break false;
+ } else true;
+ if (plain) return exe;
+ if (std.mem.indexOfScalar(u8, exe, '\'') != null) return "pardes";
+ return std.fmt.bufPrint(buf, "'{s}'", .{exe}) catch "pardes";
+ }
};
+test "the pager's path is quoted only when it needs it" {
+ var buf: [64]u8 = undefined;
+ try std.testing.expectEqualStrings("/usr/bin/pardes", ChildEnv.pagerCommand("/usr/bin/pardes", &buf));
+ try std.testing.expectEqualStrings("'/opt/my apps/pardes'", ChildEnv.pagerCommand("/opt/my apps/pardes", &buf));
+ try std.testing.expectEqualStrings("pardes", ChildEnv.pagerCommand("/odd'dir/pardes", &buf));
+}
+
test "the child environment replaces the launcher's terminal identity exactly once" {
const saved: ?[]const u8 = if (libc.getenv("TERM")) |t| std.mem.span(t) else null;
var saved_buf: [256]u8 = undefined;
@@ -1351,15 +1379,21 @@ test "the child environment replaces the launcher's terminal identity exactly on
var shell_slots: ChildEnv.Slots = undefined;
const shell_envp = ChildEnv.build(&shell_slots, paging) orelse return error.EnvironmentTooLarge;
var said: usize = 0;
+ var systemd: usize = 0;
var j: usize = 0;
while (shell_envp[j]) |entry| : (j += 1) {
const line = std.mem.span(entry);
+ if (std.c.getenv("SYSTEMD_PAGER") == null and std.mem.startsWith(u8, line, "SYSTEMD_PAGER=")) systemd += 1;
+ if (std.c.getenv("SYSTEMD_PAGERSECURE") == null and std.mem.eql(u8, line, "SYSTEMD_PAGERSECURE=0")) systemd += 1;
if (std.mem.startsWith(u8, line, "PAGER=") and std.c.getenv("PAGER") == null) {
try std.testing.expect(paging == .terminal and std.mem.endsWith(u8, line, " -"));
said += 1;
}
}
if (std.c.getenv("PAGER") == null) try std.testing.expectEqual(@as(usize, if (paging == .terminal) 1 else 0), said);
+ // systemd's pager and its secure mode off, with ours only.
+ if (std.c.getenv("SYSTEMD_PAGER") == null and std.c.getenv("SYSTEMD_PAGERSECURE") == null)
+ try std.testing.expectEqual(@as(usize, if (paging == .terminal) 2 else 0), systemd);
}
const envp = ChildEnv.build(&slots, .command) orelse return error.EnvironmentTooLarge;
var terms: usize = 0;