diff options
Diffstat (limited to 'src/host_io.zig')
| -rw-r--r-- | src/host_io.zig | 53 |
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; }, |
