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
69 lines
2.6 KiB
Rust
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()
|
|
);
|
|
}
|