feat(host): the lease and MCP channels as named pipes with the SID check, and the host, bridge and commands on Windows (P11c)
Windows steps 3 of D-62. Every `cfg(unix)` gate on the host, the bridge, the opener, the books server and the command line is gone: they build on every OS, and the Unix behaviour is unchanged (the existing over-the-wire suites pass on Linux). `ledger_mcp::ipc` is the one seam for the channels. Unix: the same 0600 sockets as before, peer uid unchanged (the peer functions moved here). Windows: a named pipe `\\.\pipe\wanderbooks-<hash of the root's run/>-<name>` made with a DACL of one allow entry for this account's SID, remote clients refused, the first instance made once (a second server is refused, as a held lock refuses it on Unix), and 16 idle instances kept waiting so a client never finds all busy while the server makes the next. Where Unix reads `SO_PEERCRED`, each end of a pipe takes the other's process id (`GetNamedPipeClientProcessId`/`...ServerProcessId`) and compares that process's token user with its own SID; any doubt is "not us". Clients open with SECURITY_IDENTIFICATION so a pipe server cannot act as the client. A blocking client with a read limit is polled with PeekNamedPipe (a pipe read cannot be cut short), and a pipe that has gone reads as the end of the stream, as a closed socket does. Stop signals are Ctrl+C, Ctrl+Break, console close and shutdown where Unix has SIGTERM, SIGINT and SIGHUP. The host starts detached (DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP, leaving the opener's job when the job lets it: an agent's job must not take the host down, as an app's systemd scope must not on Linux) with a fixed environment; `close_range` and `seccomp` stay Linux-only (D-32). A replaced program is told by size and last write. Git for Windows is found and a quiet git is stopped with its tree (`taskkill /T`). The test harness of ledger-cli finds its root by `default_root`, cleans a child's environment per OS and reaches the server over its pipe; the bridge's scripted-server tests stay Unix-only. No new dependency (tokio's named pipes and reqwest's `windows_named_pipe` were already in the build); `windows-sys` stays in ledger-store. Tests: ledger-mcp `ipc::tests` (four on every OS, one Windows only), ledger-host `tests/windows_pipe.rs::a_second_user_cannot_open_the_pipe`, Windows variants of the host and start tests. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
This commit is contained in:
1 parent
5f5dd8f7da
commit
bd6448532e
21 files changed
+1471
-410
No files matched your search
@@ -59,10 +59,10 @@ fn gui_args(args: Vec<String>) -> Vec<String> {
|
||||
|
||||
/// The viewer's owner (ledger-gui owner.rs): its Books menu, done as the
|
||||
/// command line does it (the books server, the agents' own commands).
|
||||
#[cfg(all(feature = "gui", unix))]
|
||||
#[cfg(feature = "gui")]
|
||||
struct ViewerOwner(ledger_host::commands::Common);
|
||||
|
||||
#[cfg(all(feature = "gui", unix))]
|
||||
#[cfg(feature = "gui")]
|
||||
impl ledger_gui::owner::Owner for ViewerOwner {
|
||||
fn close_books(&self) -> Result<String, String> {
|
||||
ledger_host::commands::close_books(&self.0)
|
||||
@@ -124,7 +124,6 @@ impl ledger_gui::owner::Owner for ViewerOwner {
|
||||
#[cfg(feature = "gui")]
|
||||
fn run_gui(args: Vec<String>) -> i32 {
|
||||
let args = gui_args(args);
|
||||
#[cfg(unix)]
|
||||
{
|
||||
let value = |flag: &str| {
|
||||
// The last given, as the viewer reads it.
|
||||
@@ -256,7 +255,6 @@ fn run_setup(args: Vec<String>) -> i32 {
|
||||
|
||||
/// After setup: the owner passphrase, asked for at a terminal if none is
|
||||
/// set yet; elsewhere (a script, an agent) only said how to set it.
|
||||
#[cfg(unix)]
|
||||
fn owner_passphrase(root: &ledger_store::root::Root) -> i32 {
|
||||
let auth = match root.auth_dir() {
|
||||
Ok(dir) => dir,
|
||||
@@ -294,14 +292,8 @@ fn owner_passphrase(root: &ledger_store::root::Root) -> i32 {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
fn owner_passphrase(_root: &ledger_store::root::Root) -> i32 {
|
||||
0
|
||||
}
|
||||
|
||||
/// `wanderbooks server`: the books server for the data root, on its
|
||||
/// local sockets (plan 3.9). The host starts it.
|
||||
#[cfg(unix)]
|
||||
fn run_server(args: Vec<String>) -> i32 {
|
||||
match ledger_mcp::local::run("wanderbooks server", args) {
|
||||
Ok(()) => 0,
|
||||
@@ -317,7 +309,6 @@ fn run_server(args: Vec<String>) -> i32 {
|
||||
|
||||
/// The modes that open and hold the books (plan 3.9): the host, the
|
||||
/// agents' bridge, and the owner's commands.
|
||||
#[cfg(unix)]
|
||||
fn run_local(mode: &str, args: Vec<String>) -> i32 {
|
||||
let prog = format!("wanderbooks {mode}");
|
||||
let result = match mode {
|
||||
@@ -345,7 +336,6 @@ fn run_local(mode: &str, args: Vec<String>) -> i32 {
|
||||
|
||||
/// `upload` and `download`: the agent's file commands, whose words may
|
||||
/// be paths that aren't text.
|
||||
#[cfg(unix)]
|
||||
fn run_files(mode: &str, args: Vec<std::ffi::OsString>) -> i32 {
|
||||
let prog = format!("wanderbooks {mode}");
|
||||
let result = match mode {
|
||||
@@ -364,27 +354,6 @@ fn run_files(mode: &str, args: Vec<std::ffi::OsString>) -> i32 {
|
||||
}
|
||||
}
|
||||
|
||||
/// The file commands, where local sockets aren't built yet.
|
||||
#[cfg(not(unix))]
|
||||
fn run_files(_mode: &str, _args: Vec<std::ffi::OsString>) -> i32 {
|
||||
eprintln!("error: opening the books this way comes to this system with P11");
|
||||
2
|
||||
}
|
||||
|
||||
/// The modes that hold the books, where local sockets aren't built yet.
|
||||
#[cfg(not(unix))]
|
||||
fn run_local(_mode: &str, _args: Vec<String>) -> i32 {
|
||||
eprintln!("error: opening the books this way comes to this system with P11");
|
||||
2
|
||||
}
|
||||
|
||||
/// `wanderbooks server` where local sockets aren't built yet.
|
||||
#[cfg(not(unix))]
|
||||
fn run_server(_args: Vec<String>) -> i32 {
|
||||
eprintln!("error: the books server's local sockets come to this system with P11");
|
||||
2
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// A file's path may be any name the system takes: the file commands
|
||||
// get the words as given. Everything else is text.
|
||||
|
||||
Reference in new issue
Block a user