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.zig53
1 files changed, 45 insertions, 8 deletions
diff --git a/src/host_io.zig b/src/host_io.zig
index 8bbb0985..9e3fbf79 100644
--- a/src/host_io.zig
+++ b/src/host_io.zig
@@ -933,7 +933,7 @@ pub const Shell = struct {
if (libc.access(@ptrCast(&buf), 0) == 0) return std.fmt.bufPrint(said, "not a shell: {s} is not executable", .{bin}) catch "not a shell: not executable";
}
// "not found": the words a mount reads as ENOENT.
- return std.fmt.bufPrint(said, "no shell \"{s}\", not found (a name on the usual paths, or a path to one)", .{bin}) catch "no such shell";
+ return std.fmt.bufPrint(said, "shell \"{s}\" not found (a name on the usual paths, or a path to one)", .{bin}) catch "no such shell";
}
/// The program a script's `#!` line names, when it is not there to
@@ -970,7 +970,7 @@ pub const Shell = struct {
var said: [320]u8 = undefined;
try std.testing.expectEqualStrings("interpreter /no/such/interp not found", refusal(try std.fmt.bufPrint(&path, "{s}/bad", .{dir}), &said).?);
try std.testing.expect(refusal(try std.fmt.bufPrint(&path, "{s}/good", .{dir}), &said) == null);
- try std.testing.expect(std.mem.startsWith(u8, refusal(try std.fmt.bufPrint(&path, "{s}/none", .{dir}), &said).?, "no shell "));
+ try std.testing.expect(std.mem.startsWith(u8, refusal(try std.fmt.bufPrint(&path, "{s}/none", .{dir}), &said).?, "shell \""));
try tmp.dir.writeFile(std.testing.io, .{ .sub_path = "plain", .data = "echo\n" });
const plain = try std.fmt.bufPrint(&path, "{s}/plain", .{dir});
try std.testing.expect(std.mem.endsWith(u8, refusal(plain, &said).?, "plain is not executable"));
@@ -1048,7 +1048,7 @@ pub const Shell = struct {
var buf: [2048]u8 = undefined;
try std.testing.expectEqualStrings(bash_rc, readSmall(a_bash, &buf) orelse return error.ReadFailed);
try std.testing.expectEqualStrings(fish_rc, readSmall(a.fishPath().?, &buf) orelse return error.ReadFailed);
- const stat = try std.Io.Dir.cwd().statFile(std.testing.io, a_bash, .{});
+ const stat = try @import("fs.zig").statPath(std.testing.io, a_bash, .{});
try std.testing.expectEqual(0, stat.permissions.toMode() & 0o077);
var kept: [rc_path_capacity:0]u8 = @splat(0);
@memcpy(kept[0..a_bash.len], a_bash);
@@ -1075,6 +1075,45 @@ pub const Shell = struct {
}
};
+/// In a child between fork and exec: every signal back to its default and
+/// none blocked, whatever this process ignores or blocks (the tty's SIGWINCH,
+/// a SIGHUP nohup ignored). A handler resets at exec by itself; an ignored
+/// signal and the mask do not. Async-signal-safe: sigaction and sigprocmask.
+pub fn resetChildSignals() void {
+ const default: posix.Sigaction = .{ .handler = .{ .handler = posix.SIG.DFL }, .mask = posix.sigemptyset(), .flags = 0 };
+ var sig: u8 = 1;
+ while (sig < 65) : (sig += 1) {
+ if (sig == @intFromEnum(posix.SIG.KILL) or sig == @intFromEnum(posix.SIG.STOP)) continue;
+ _ = std.c.sigaction(@enumFromInt(sig), &default, null);
+ }
+ const none = posix.sigemptyset();
+ posix.sigprocmask(posix.SIG.SETMASK, &none, null);
+}
+
+test "a child starts with every signal at its default and none blocked" {
+ var blocked = posix.sigemptyset();
+ posix.sigaddset(&blocked, posix.SIG.WINCH);
+ posix.sigprocmask(posix.SIG.BLOCK, &blocked, null);
+ defer posix.sigprocmask(posix.SIG.UNBLOCK, &blocked, null);
+ const ignore: posix.Sigaction = .{ .handler = .{ .handler = posix.SIG.IGN }, .mask = posix.sigemptyset(), .flags = 0 };
+ var was: posix.Sigaction = undefined;
+ posix.sigaction(posix.SIG.HUP, &ignore, &was);
+ defer posix.sigaction(posix.SIG.HUP, &was, null);
+ const pid = libc.fork();
+ if (pid == 0) {
+ resetChildSignals();
+ var now: posix.Sigaction = undefined;
+ posix.sigaction(posix.SIG.HUP, null, &now);
+ var mask = posix.sigemptyset();
+ posix.sigprocmask(posix.SIG.BLOCK, null, &mask);
+ const ok = now.handler.handler == posix.SIG.DFL and !posix.sigismember(&mask, posix.SIG.WINCH);
+ libc._exit(if (ok) 0 else 1);
+ }
+ var status: c_int = 0;
+ _ = libc.waitpid(pid, &status, 0);
+ try std.testing.expectEqual(@as(c_int, 0), status);
+}
+
extern "c" fn forkpty(amaster: *c_int, name: ?[*:0]u8, termp: ?*const anyopaque, winp: ?*const posix.winsize) c_int;
extern "c" fn execv(path: [*:0]const u8, argv: [*:null]const ?[*:0]const u8) c_int;
extern "c" fn execve(path: [*:0]const u8, argv: [*:null]const ?[*:0]const u8, envp: [*:null]const ?[*:0]const u8) c_int;
@@ -1202,7 +1241,7 @@ pub fn forkShell(
// A shell's directory may be inside a mount this editor serves.
pardes.turn.yield();
defer pardes.turn.back();
- const stat = try std.Io.Dir.cwd().statFile(std.Io.Threaded.global_single_threaded.io(), path, .{});
+ const stat = try @import("fs.zig").statPath(std.Io.Threaded.global_single_threaded.io(), path, .{});
if (stat.kind != .directory) return error.NotDir;
break :dir path;
};
@@ -1278,9 +1317,7 @@ pub fn forkShell(
return error.ForkFailed;
}
if (pid == 0) {
- var set = posix.sigemptyset();
- posix.sigaddset(&set, posix.SIG.WINCH);
- posix.sigprocmask(posix.SIG.UNBLOCK, &set, null);
+ resetChildSignals();
if (cwd_z) |path| if (chdir(path.ptr) != 0) {
const why = [_]u8{ 'c', @truncate(@intFromEnum(libc.errno(@as(c_int, -1)))) };
_ = libc.write(told[1], &why, why.len);
@@ -1440,7 +1477,7 @@ pub fn shellCwd(pid: libc.pid_t, buf: []u8) ?[]const u8 {
// no directory to be named by, so the name it had stays, and a
// restart works there once it is back.
if (std.mem.endsWith(u8, link, " (deleted)")) {
- _ = std.Io.Dir.cwd().statFile(std.Io.Threaded.global_single_threaded.io(), link, .{}) catch return null;
+ _ = @import("fs.zig").statPath(std.Io.Threaded.global_single_threaded.io(), link, .{}) catch return null;
}
return link;
},