fix: Windows reads of lock files, update paths cfg(unix), merge main again (P11c)
spec_vault swept every file under a book, lock files included; on Windows a held LockFileEx lock is mandatory and the read failed. The locks are already dedicated files (never data), so the test skips *.lock. Merged main (P13 step 6, the macOS server.log assert, P11a's AppImage identity): host.rs keeps main's five-field identity on Unix and size/last-write on Windows; the update poll and set_program_version are Unix-only. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
This commit is contained in:
commit
7749afc557
234 files changed
+13028
-18411
No files matched your search
@@ -13,7 +13,8 @@
|
||||
//! it names a socket this account owns (the owner's ssh agent, so the
|
||||
//! server's backups reach a private git repository, D-30). Nothing
|
||||
//! else passes (a scope's own identifiers, such as INVOCATION_ID, are
|
||||
//! systemd's).
|
||||
//! systemd's). One more: APPIMAGE, only when it names an absolute
|
||||
//! file, so the host knows its own AppImage (below).
|
||||
//! 2. Its standard input and output are null, its errors go to
|
||||
//! run/host.log (0600); it starts a new session with umask 077. Every
|
||||
//! other descriptor is closed (std opens them close-on-exec, and the
|
||||
@@ -23,7 +24,11 @@
|
||||
//! scope couldn't be made (systemd-run failed without the host ever
|
||||
//! writing its first line), or there is no such scope, directly. A
|
||||
//! host that ran and stopped is never started a second time.
|
||||
//! 4. It runs this executable's file as it is on disk now.
|
||||
//! 4. It runs this executable's file as it is on disk now; for an
|
||||
//! AppImage that is the image file (APPIMAGE), not the program inside
|
||||
//! its mount, which goes away with the image it came from. The host
|
||||
//! reads that path about itself and never tells it to an agent (I-2),
|
||||
//! so a replaced image restarts the books like any replaced program.
|
||||
//! 5. The opener doesn't wait for it, and reaps it when it ends.
|
||||
//! ```
|
||||
|
||||
@@ -65,6 +70,9 @@ const LOG_LIMIT: u64 = 8 * 1024 * 1024;
|
||||
/// replaced file reads as "… (deleted)"; the path, not the old file, is
|
||||
/// what a new host runs.
|
||||
pub fn this_executable() -> Result<PathBuf, String> {
|
||||
if let Some(image) = own_appimage(std::env::var_os("APPIMAGE").as_deref()) {
|
||||
return Ok(PathBuf::from(image));
|
||||
}
|
||||
let exe = std::env::current_exe().map_err(|e| format!("can't find this program: {e}"))?;
|
||||
let text = exe.to_string_lossy();
|
||||
if let Some(path) = text.strip_suffix(" (deleted)")
|
||||
@@ -75,6 +83,13 @@ pub fn this_executable() -> Result<PathBuf, String> {
|
||||
Ok(exe)
|
||||
}
|
||||
|
||||
/// The AppImage this process runs from: `APPIMAGE` when it is an
|
||||
/// absolute path to a file.
|
||||
pub fn own_appimage(value: Option<&std::ffi::OsStr>) -> Option<std::ffi::OsString> {
|
||||
let path = Path::new(value?);
|
||||
(path.is_absolute() && path.is_file()).then(|| path.as_os_str().to_os_string())
|
||||
}
|
||||
|
||||
/// This account's user ID.
|
||||
#[cfg(unix)]
|
||||
fn own_uid() -> u32 {
|
||||
@@ -151,7 +166,11 @@ fn own_agent(named: Option<&std::ffi::OsStr>) -> Option<std::ffi::OsString> {
|
||||
#[cfg(unix)]
|
||||
/// The host's environment (INVARIANT 1), given the opener's
|
||||
/// `SSH_AUTH_SOCK`.
|
||||
fn host_env(home: &Path, ssh_agent: Option<&std::ffi::OsStr>) -> Vec<(String, std::ffi::OsString)> {
|
||||
fn host_env(
|
||||
home: &Path,
|
||||
ssh_agent: Option<&std::ffi::OsStr>,
|
||||
appimage: Option<&std::ffi::OsStr>,
|
||||
) -> Vec<(String, std::ffi::OsString)> {
|
||||
let mut env = vec![
|
||||
("PATH".to_string(), PATH.into()),
|
||||
("HOME".to_string(), home.as_os_str().to_os_string()),
|
||||
@@ -165,6 +184,9 @@ fn host_env(home: &Path, ssh_agent: Option<&std::ffi::OsStr>) -> Vec<(String, st
|
||||
if let Some(agent) = own_agent(ssh_agent) {
|
||||
env.push(("SSH_AUTH_SOCK".into(), agent));
|
||||
}
|
||||
if let Some(image) = own_appimage(appimage) {
|
||||
env.push(("APPIMAGE".into(), image));
|
||||
}
|
||||
env
|
||||
}
|
||||
|
||||
@@ -209,6 +231,10 @@ pub fn start_host(
|
||||
let found = ledger_store::root::Root::find(root).map_err(|e| e.to_string())?;
|
||||
let run = found.run_dir().map_err(|e| e.to_string())?;
|
||||
let home = ledger_store::root::account_home().map_err(|e| e.to_string())?;
|
||||
// A staged update is applied here and nowhere while books are open:
|
||||
// no host runs now, so no call is cut off (update.rs INVARIANT 9). A
|
||||
// failure leaves the program as it was.
|
||||
let _ = crate::update::apply_staged(&found, crate::update::program_version());
|
||||
let exe = this_executable()?;
|
||||
let mut args: Vec<std::ffi::OsString> = vec!["host".into()];
|
||||
// An explicit root goes on as found (absolute): the host starts in /.
|
||||
@@ -253,6 +279,7 @@ pub fn start_host(
|
||||
.envs(host_env(
|
||||
&home,
|
||||
std::env::var_os("SSH_AUTH_SOCK").as_deref(),
|
||||
std::env::var_os("APPIMAGE").as_deref(),
|
||||
))
|
||||
.current_dir("/")
|
||||
.stdin(Stdio::null())
|
||||
@@ -518,7 +545,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn the_host_environment_carries_nothing_else() {
|
||||
let env = host_env(Path::new("/home/someone"), None);
|
||||
let env = host_env(Path::new("/home/someone"), None, None);
|
||||
let names: Vec<&str> = env.iter().map(|(n, _)| n.as_str()).collect();
|
||||
assert_eq!(&names[..2], ["PATH", "HOME"]);
|
||||
for n in &names[2..] {
|
||||
@@ -526,7 +553,8 @@ mod tests {
|
||||
[
|
||||
"XDG_RUNTIME_DIR",
|
||||
"DBUS_SESSION_BUS_ADDRESS",
|
||||
"SSH_AUTH_SOCK"
|
||||
"SSH_AUTH_SOCK",
|
||||
"APPIMAGE"
|
||||
]
|
||||
.contains(n),
|
||||
"{n}"
|
||||
@@ -536,6 +564,22 @@ mod tests {
|
||||
assert_eq!(env[1].1, "/home/someone");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn only_an_absolute_file_passes_as_the_appimage() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let image = dir.path().join("W.AppImage");
|
||||
std::fs::write(&image, b"x").unwrap();
|
||||
let home = Path::new("/home/someone");
|
||||
let has = |v: Option<&std::ffi::OsStr>| {
|
||||
host_env(home, None, v).iter().any(|(n, _)| n == "APPIMAGE")
|
||||
};
|
||||
assert!(has(Some(image.as_os_str())));
|
||||
assert!(!has(None));
|
||||
assert!(!has(Some("W.AppImage".as_ref())), "relative");
|
||||
assert!(!has(Some(dir.path().as_os_str())), "a folder");
|
||||
assert!(!has(Some(dir.path().join("gone").as_os_str())));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn only_an_own_ssh_agent_socket_passes() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
@@ -546,7 +590,7 @@ mod tests {
|
||||
Some(sock.clone().into_os_string())
|
||||
);
|
||||
// It reaches the host's environment, once.
|
||||
let env = host_env(Path::new("/home/someone"), Some(sock.as_os_str()));
|
||||
let env = host_env(Path::new("/home/someone"), Some(sock.as_os_str()), None);
|
||||
let agents: Vec<_> = env.iter().filter(|(n, _)| n == "SSH_AUTH_SOCK").collect();
|
||||
assert_eq!(agents.len(), 1);
|
||||
assert_eq!(agents[0].1, sock.as_os_str());
|
||||
@@ -555,7 +599,7 @@ mod tests {
|
||||
let link = dir.path().join("link.sock");
|
||||
std::os::unix::fs::symlink(&sock, &link).unwrap();
|
||||
assert_eq!(own_agent(Some(link.as_os_str())), None);
|
||||
let env = host_env(Path::new("/home/someone"), Some(link.as_os_str()));
|
||||
let env = host_env(Path::new("/home/someone"), Some(link.as_os_str()), None);
|
||||
assert!(env.iter().all(|(n, _)| n != "SSH_AUTH_SOCK"));
|
||||
// Named relative to here: not taken, even where it leads to one.
|
||||
let here = std::env::current_dir().unwrap();
|
||||
|
||||
Reference in new issue
Block a user