diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-29 16:20:57 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 00:12:16 -0300 |
| commit | db12ca703dd2acbeee2d7b6acc03dd8a93909755 (patch) | |
| tree | 3000772520ce9189104c6c76d750878d38629126 | |
| parent | d6445bb28bcb4a0d0e5f29c6308682737d253ebe (diff) | |
| download | pardes-db12ca703dd2acbeee2d7b6acc03dd8a93909755.tar.gz pardes-db12ca703dd2acbeee2d7b6acc03dd8a93909755.zip | |
pty/ctl exec on a command pane is refused: a command pane does not restart
exec on a finished command pane ran its line again, silently, with no header, footer or records. It is now refused, EINVAL, invalid exec: a command pane does not restart.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
| -rw-r--r-- | docs/fs.md | 4 | ||||
| -rw-r--r-- | src/ninep/pty.zig | 15 |
2 files changed, 18 insertions, 1 deletions
@@ -993,7 +993,9 @@ which is also when the third field of `pty/status` reads 1. `pty/status` is one line, three right-aligned fields and a newline: the pty's columns and rows, then busy (0 or 1). `pty/ctl` takes `winsize C R`, `sig INT|TERM|HUP|QUIT|KILL` and `exec`, which starts the pane's shell again in -its directory: one that is gone is refused before anything runs, `exec: +its directory (a command pane's child is its command, which does not +restart: `exec` there is refused, EINVAL, `a command pane does not +restart`): one that is gone is refused before anything runs, `exec: <dir>: no such directory` (ENOENT), and a shell the host cannot start (not there, not executable, a script whose interpreter is not there) fails the write with why -- `shell: shell not found`, or `shell: interpreter diff --git a/src/ninep/pty.zig b/src/ninep/pty.zig index d32c35fd..41748bdc 100644 --- a/src/ninep/pty.zig +++ b/src/ninep/pty.zig @@ -61,6 +61,10 @@ pub fn writeCtl(p: *Pardes, req: Req, id: usize) Reply { while (it.next()) |raw| { const line = std.mem.trim(u8, raw, " \t\r"); if (line.len == 0) continue; + // A command pane's child is its command, done or running: exec + // would run its line again with no header, footer or records. + if (!apply and std.mem.eql(u8, line, "exec")) if (p.panes[id]) |pane| if (pane.command != null) + return tree.failText(req.tag, E.INVAL, "invalid exec: a command pane does not restart"); // A shell restarted where the directory is gone would not start: // refused before anything runs, as Tty there is. if (!apply and std.mem.eql(u8, line, "exec")) if (p.panes[id]) |pane| if (pardes.exec.missingDir(p, pane.cwdSlice())) { @@ -1087,3 +1091,14 @@ test "a run's answer longer than its room is cut, its newline kept" { try testing.expectEqual(slot.answer.len, slot.len); try testing.expectEqual(@as(u8, '\n'), slot.answer[slot.len - 1]); } + +test "exec on a command pane is refused: a command pane does not restart" { + const p = try th.withTerm(testing.allocator); + defer p.deinit(); + const serial = th.serialOf(p); + const pane = p.panes[p.paneBySerial(serial).?].?; + pane.command = try p.gpa.dupe(u8, "echo hi"); + const r = th.wr(p, tree.Node.of(serial, .pty_ctl), "exec\n"); + try testing.expectEqual(tree.E.INVAL, r.errno()); + try testing.expectEqualStrings("invalid exec: a command pane does not restart", r.reply.ename); +} |
