Files
wanderbooks/crates/ledger-store/tests/socket_path.rs
T
swaitsandClaude Fable 5.1 74f1691f77
portable / portable (macos-latest) (push) Waiting to run
portable / portable (windows-latest) (push) Waiting to run
check / check (push) Canceled after 0s
two-user / two-user (push) Canceled after 0s
fix(store): a root too deep for a socket's address is served on macOS too
A Unix socket's address holds about 104 bytes. Linux reached a deep
root's sockets through /proc/self/fd; every other Unix refused the
root ("too long for its sockets"), which the portable macOS job met
with a test home under the runner's temp folder, and a Mac with a deep
home would meet too. On those systems the socket path now goes through
a short link of this account's, /tmp/wanderbooks-<uid>/<hash of the
dir>, in a folder made 0700 and refused unless it is this account's
own; a stale or foreign link in its place is replaced or refused. The
server and its clients compute the same link, so nothing else changes.

Tests (tests/socket_path.rs, every Unix): a deep root gets a path that
fits and reaches the dir; the link is private, stable, and replaces a
stale one of ours.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
2026-10-29 18:09:00 -06:00

69 lines
2.6 KiB
Rust

//! Socket paths for roots too deep for a Unix socket's address (about 104
//! bytes): Linux reaches them through `/proc/self/fd`, the other Unixes
//! through a short private link under `/tmp`, so a books root in a deep
//! home is served like any other (the portable macOS job found the
//! refusal).
#![cfg(unix)]
use ledger_store::root::{SocketPath, short_link_for};
use std::path::PathBuf;
fn deep_dir(tag: &str) -> PathBuf {
let deep = std::env::temp_dir()
.join(format!("wb-socket-{tag}-{}", std::process::id()))
.join("a".repeat(60))
.join("b".repeat(60))
.join("run");
std::fs::create_dir_all(&deep).unwrap();
deep
}
/// A root deeper than the address allows still gets a socket path that
/// fits, and a second asker (a client) reaches the same place.
#[test]
fn a_deep_root_gets_a_socket_path_that_fits() {
let deep = deep_dir("fits");
assert!(deep.as_os_str().len() > 100);
let a = SocketPath::new(&deep, "mcp.sock").unwrap();
assert!(a.path().as_os_str().len() <= 100, "{:?}", a.path());
// The place it reaches is the dir itself.
let reached = std::fs::canonicalize(a.path().parent().unwrap()).unwrap();
assert_eq!(reached, std::fs::canonicalize(&deep).unwrap());
}
/// The short link is this account's, names the dir exactly, is the same
/// on a second ask, and replaces a stale link of ours in its place.
#[test]
fn the_short_link_is_private_stable_and_replaces_a_stale_one() {
use std::os::unix::fs::{MetadataExt, PermissionsExt};
let deep = deep_dir("link");
let link = short_link_for(&deep).unwrap();
assert!(
link.as_os_str().len() + "/lease.sock".len() <= 100,
"{link:?}"
);
// SAFETY: getuid has no preconditions and never fails.
let uid = unsafe { libc::getuid() };
assert_eq!(
link.parent().unwrap(),
PathBuf::from(format!("/tmp/wanderbooks-{uid}"))
);
let folder = std::fs::symlink_metadata(link.parent().unwrap()).unwrap();
assert_eq!(folder.uid(), uid);
assert_eq!(folder.permissions().mode() & 0o777, 0o700);
assert_eq!(
std::fs::read_link(&link).unwrap(),
std::fs::canonicalize(&deep).unwrap()
);
assert_eq!(short_link_for(&deep).unwrap(), link);
// A stale link of ours (to somewhere else) in that place is replaced.
let elsewhere = deep_dir("elsewhere");
std::fs::remove_file(&link).unwrap();
std::os::unix::fs::symlink(&elsewhere, &link).unwrap();
assert_eq!(short_link_for(&deep).unwrap(), link);
assert_eq!(
std::fs::read_link(&link).unwrap(),
std::fs::canonicalize(&deep).unwrap()
);
}