diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 494c690..08ff091 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -70,8 +70,8 @@ jobs: # `--locked` was the gap: the release workflow and the PKGBUILD both # build locked, so a Cargo.lock that no longer agreed with Cargo.toml # passed every check and then failed the release, where it is expensive - # and public. It is asserted here rather than by a second, fat-LTO - # release build, which would cost minutes to learn the same thing. + # and public. It is asserted here rather than by a second release build + # with LTO, which would cost minutes to learn the same thing. - name: Test run: cargo test --verbose --locked diff --git a/Cargo.lock b/Cargo.lock index 9be390f..ba8e804 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -250,7 +250,7 @@ checksum = "4f8abc12baad266b1c8cec146854c195b5864b4221d4b2ca7296a7ae82d9e451" dependencies = [ "proc-macro2", "quote", - "syn 3.0.6", + "syn 3.0.9", ] [[package]] @@ -285,7 +285,7 @@ checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" dependencies = [ "proc-macro2", "quote", - "syn 3.0.6", + "syn 3.0.9", ] [[package]] @@ -387,7 +387,7 @@ checksum = "6a1f896587b6f2c069c73d2f0913e2d590c3990285cd2f0b6aa02b786b4c679c" dependencies = [ "proc-macro2", "quote", - "syn 3.0.6", + "syn 3.0.9", ] [[package]] @@ -454,9 +454,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.6.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f74872d07caf508b30a21f6836e7d7016a2eaf7d9ff4f48deaa58cd8a0407630" +checksum = "6651c9ed80effdc7db0ff72512157f901af5e3549e341e24b1dd4887d836d838" dependencies = [ "find-msvc-tools", "jobserver", @@ -520,7 +520,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 3.0.6", + "syn 3.0.9", ] [[package]] @@ -883,9 +883,9 @@ dependencies = [ [[package]] name = "equivalent" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +checksum = "00d174d5400e5e8fd687ad1049e2f578285fa914201b1af7e8b112a4546bd826" [[package]] name = "errno" @@ -1025,7 +1025,7 @@ checksum = "ea5190182e6915eb873ddbc16e23b711b6eb1f9c00a0d0a3a91b5f6228475225" dependencies = [ "proc-macro2", "quote", - "syn 3.0.6", + "syn 3.0.9", ] [[package]] @@ -1103,7 +1103,7 @@ checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" dependencies = [ "proc-macro2", "quote", - "syn 3.0.6", + "syn 3.0.9", ] [[package]] @@ -1740,9 +1740,9 @@ checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litrs" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" +checksum = "4744e383959f0db86ede514b809b1c53251889093803c05267acc7d4e7030d70" [[package]] name = "lock_api" @@ -2799,15 +2799,16 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn 3.0.6", + "syn 3.0.9", ] [[package]] name = "serde_json" -version = "1.0.151" +version = "1.0.154" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6" dependencies = [ + "foldhash 0.2.0", "indexmap", "itoa", "memchr", @@ -2824,7 +2825,7 @@ checksum = "8d3b1629de253c70a0508c3899572da79ca359fdab27c7920ff00406df418906" dependencies = [ "proc-macro2", "quote", - "syn 3.0.6", + "syn 3.0.9", ] [[package]] @@ -2905,9 +2906,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.16.2" +version = "1.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9395f0f0eee849a9b707b2f06bb92a6a422090e2123bb2ef8e87a0e61892a8e" +checksum = "5b3dc8af474f516a851ff4bd12db780f948b9250ad37211e4eec0bccea54e01b" [[package]] name = "smithay-client-toolkit" @@ -3069,9 +3070,9 @@ dependencies = [ [[package]] name = "syn" -version = "3.0.6" +version = "3.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee" +checksum = "d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b" dependencies = [ "proc-macro2", "quote", @@ -3146,7 +3147,7 @@ checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524" dependencies = [ "proc-macro2", "quote", - "syn 3.0.6", + "syn 3.0.9", ] [[package]] @@ -3195,9 +3196,9 @@ checksum = "fd3ca314f692efd6c868f8408f53fe444634a845f96c028b97d35f6a1f79f0ee" [[package]] name = "tokio" -version = "1.53.2" +version = "1.53.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e95f91fcc7a621e8b030f6aa23c71fe9838ae2fb4d8118b75602a328f5144044" +checksum = "ce3335fa71841cda333a58d7615b03901380ecf09d59b3296d21f8bbac0dde4e" dependencies = [ "pin-project-lite", ] @@ -3313,9 +3314,9 @@ checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" [[package]] name = "unicode-ident" -version = "1.0.26" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954" +checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563" [[package]] name = "unicode-linebreak" @@ -3426,7 +3427,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 3.0.6", + "syn 3.0.9", "wasm-bindgen-shared", ] @@ -4227,7 +4228,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 3.0.6", + "syn 3.0.9", "zbus_names", "zvariant", "zvariant_utils", @@ -4261,18 +4262,18 @@ checksum = "6df3dc4292935e51816d896edcd52aa30bc297907c26167fec31e2b0c6a32524" [[package]] name = "zerocopy" -version = "0.8.62" +version = "0.8.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86502bf56ac7c77571a32e2647bb2a15894565e981fb2a48d7bde2d91c965a9d" +checksum = "e5fe1f8f1b06191a00962174c61aa5005e0bb391a6d80d07e24d115c01a92ed8" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.62" +version = "0.8.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5457206954b06561e2608c7e19cf58b1926586d999c246eebe4502f7e2039d1a" +checksum = "863ad3ac83293fb4d740aedbfdc9240dd8d1a50c1099acd76ce80ce7c7230c7f" dependencies = [ "proc-macro2", "quote", @@ -4309,7 +4310,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 3.0.6", + "syn 3.0.9", "zvariant_utils", ] @@ -4322,6 +4323,6 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 3.0.6", + "syn 3.0.9", "winnow", ] diff --git a/Cargo.toml b/Cargo.toml index addb141..6101807 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,21 +28,20 @@ regf = "0.1.0" rustix = "1.1.5" serde = "1.0.229" thiserror = "2.0.21" -tokio = "1.53.2" +tokio = "1.53.3" toml = "1.1.8" -# Two binaries share this profile now, and they are paid for by different -# people. `raven` runs once per .exe launch, so start-up and size are all that -# matter, and both were measured before and after (numbers in the commit that -# added this). `raven-gui` gains nothing measurable from either setting - it is -# opened by hand, once, and then stays open - and pays for them in compile -# time, since it links wgpu and iced under one codegen unit. The settings are -# kept anyway: they are here for the launcher, which is the binary on the hot -# path, and a second profile for the window would buy a faster build at the -# cost of two release binaries built two different ways. +# The organisation's release profile (Project-Colony-Resources, +# design/releases.md, "Release profile"), shared by both binaries so they are +# built one way. `raven` runs once per .exe launch, so start-up and size are +# what it pays for. Against the default profile, the first measured profile (fat +# LTO) took it from 1.9 MB to 1.2 MB and left start-up unchanged within noise +# (numbers in docs/project/consolidation.md, 3.1). Fat LTO bought no start-up, +# so Raven uses the organisation's thin LTO, which also links `raven-gui` (wgpu +# and iced under one codegen unit) faster. # `panic = "abort"` is deliberately not set: it trades backtraces away for # little, and that decision is recorded in docs/project/consolidation.md. [profile.release] -lto = "fat" +lto = "thin" codegen-units = 1 strip = "symbols" diff --git a/README.md b/README.md index 6e5d056..7af925c 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Raven -**A real Windows installation, mounted as C:, with its programs launched from Linux.** +**A real Windows installation, mounted as C:, with its programs launched from Linux: for Linux users whose Windows software needs a genuine Windows, not Wine's reconstruction of one.** @@ -15,14 +15,11 @@ Wine and Proton run Windows programs against a *synthetic* Windows: a prefix Wine created, a registry Wine wrote as a text file, and reimplementations of the -libraries a program expects to find. It works remarkably well, and it is why the -prefix is disposable. It also means the program's whole world is a reconstruction. - -Raven keeps Wine where Wine is irreplaceable - translating NT calls into Linux -syscalls - and replaces everything above it with a genuine Windows installation -that you deploy, mount read-only, and write to through an overlay. The registry -comes from real hives. The libraries are Microsoft's, except for the precise set -that physically cannot be. +libraries a program expects to find. It works remarkably well, but the program's +whole world is a reconstruction. Raven keeps Wine only where it is +irreplaceable, translating NT calls into Linux syscalls, and replaces everything +above it with a genuine Windows installation that you deploy, mount read-only, +and write to through an overlay. > **Status:** the end-to-end story exists. A real Windows 11 Pro deploys from > an official ISO, mounts as C:, a real installer wrote 256 MB into an @@ -47,10 +44,11 @@ that physically cannot be. What you would otherwise reach for, and where it stops: -- **Wine / Proton** - the program runs at native speed, but inside an invented - Windows. Software that reads its own installation state, resolves COM servers - it registered at install time, or expects a library Wine has only partly - reimplemented, finds an environment that does not quite add up. +- **Wine / Proton** - the program runs at native speed, in a prefix that is + cheap to throw away, but inside an invented Windows. Software that reads its + own installation state, resolves COM servers it registered at install time, + or expects a library Wine has only partly reimplemented, finds an environment + that does not quite add up. - **A virtual machine** - perfect fidelity, because it is really Windows. It is also a second computer: its own RAM, its own filesystem, its own GPU story, and a window that is a screen rather than an application. That is isolation, which @@ -73,6 +71,10 @@ Raven's position is the one nobody occupies: NT → Linux syscalls → Wine, in every case; no alternative ``` +Above that bottom line, Raven's C: is the real thing: the registry comes from +real hives, and the libraries are Microsoft's, except for the precise set that +physically cannot be. + ## What it does - **Deploys a real Windows without a VM and without booting it.** An official @@ -223,7 +225,7 @@ to any server. | Windows bases | stored | `~/.local/share/Colony/Raven/bases/`: the installations deployed from images you supply | | Environments | stored | `~/.local/share/Colony/Raven/environments/`: each environment's overlay layer, Wine prefix, registry rules and projected registry | | DXVK and vkd3d archives | stored briefly | `~/.cache/Colony/Raven/unpack/`: unpacked in a private directory, copied into the environment, then removed | -| Mount points | while running | `$XDG_RUNTIME_DIR/raven//c`: where an environment's C: is mounted; runtime state, gone at reboot | +| Mount points and sessions | while running | `$XDG_RUNTIME_DIR/raven//`: `c` is where an environment's C: is mounted, `session.pid` names the process holding that mount so later launches join it, and `session.lock` exists while a session starts; runtime state, gone at reboot | | Windows registry | read | the hives of the Windows image you supply; only the allow-listed subtrees are copied into the environment's prefix, nothing leaves the machine | The `~/.config`, `~/.local/share` and `~/.cache` paths follow `XDG_CONFIG_HOME`, diff --git a/crates/raven-gui/src/preferences.rs b/crates/raven-gui/src/preferences.rs index 98affbf..27257b4 100644 --- a/crates/raven-gui/src/preferences.rs +++ b/crates/raven-gui/src/preferences.rs @@ -61,12 +61,17 @@ fn lenient_language<'de, D: Deserializer<'de>>(d: D) -> Result PathBuf { + config.join("preferences").join("preferences.toml") +} + impl Preferences { /// Where the file lives, without creating anything. pub fn path() -> io::Result { - Ok(colony_ui::paths::locate::config_dir(crate::PROGRAM)? - .join("preferences") - .join("preferences.toml")) + Ok(file_in(colony_ui::paths::locate::config_dir( + crate::PROGRAM, + )?)) } /// The saved preferences, or the defaults when there are none or they @@ -87,7 +92,9 @@ impl Preferences { /// crash mid-write leaves the previous preferences rather than half of /// these. pub fn save(&self) -> io::Result<()> { - let path = Self::path()?; + // `config_dir` creates Colony/Raven/ the way every Colony program + // does; the preferences/ folder under it is Raven's own. + let path = file_in(colony_ui::paths::config_dir(crate::PROGRAM)?); let dir = path.parent().expect("the file has a directory"); std::fs::create_dir_all(dir)?; let text = toml::to_string(self).map_err(io::Error::other)?; diff --git a/crates/raven/src/paths.rs b/crates/raven/src/paths.rs index cefc0b4..9150451 100644 --- a/crates/raven/src/paths.rs +++ b/crates/raven/src/paths.rs @@ -53,16 +53,21 @@ pub fn environment_dir(name: &str) -> Result { Ok(data_dir()?.join("environments").join(check_name(name)?)) } -/// Where an environment's C: is mounted while it runs. +/// An environment's runtime state: its mount point and its session record. /// -/// This is runtime state and lives under the runtime directory, not under data: -/// a mount must not survive a reboot, and a stale mount point found in a data +/// This lives under the runtime directory, not under data: a mount must not +/// survive a reboot, and a stale mount point or session record found in a data /// directory after a crash is worse than finding nothing. -pub fn mount_point(name: &str) -> Result { +pub fn runtime_dir(name: &str) -> Result { let base = std::env::var_os("XDG_RUNTIME_DIR") .map(PathBuf::from) .ok_or(Error::NoRuntimeDir)?; - Ok(base.join("raven").join(check_name(name)?).join("c")) + Ok(base.join("raven").join(check_name(name)?)) +} + +/// Where an environment's C: is mounted while it runs. +pub fn mount_point(name: &str) -> Result { + Ok(runtime_dir(name)?.join("c")) } fn xdg(var: &str, fallback: &str) -> Result { diff --git a/crates/raven/src/session.rs b/crates/raven/src/session.rs index d88fb36..f95a5b8 100644 --- a/crates/raven/src/session.rs +++ b/crates/raven/src/session.rs @@ -31,29 +31,38 @@ use std::path::PathBuf; use std::process::{Command, Stdio}; use crate::i18n::{t, tf}; -use crate::{Error, env::Environment}; +use crate::{Error, env::Environment, paths}; /// How long to wait for an anchor to mount and report itself. const READY_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30); impl Environment { - /// Where the anchor's pid is recorded. - pub fn session_file(&self) -> PathBuf { + /// Where the anchor's pid is recorded: beside the mount point, in the + /// runtime directory, so the record goes at reboot with the mount it + /// describes. + pub fn session_file(&self) -> Result { + Ok(paths::runtime_dir(&self.name)?.join("session.pid")) + } + + /// Where releases up to 0.5.0 recorded the pid, in the data directory. + fn legacy_session_file(&self) -> PathBuf { self.root.join("session.pid") } /// The live anchor's pid, if a session is running. /// - /// A recorded pid is not evidence on its own - it survives a reboot and - /// pids are reused. The check that matters is whether that process is - /// actually holding *this* environment's upper layer, which is the same - /// test `holders` performs and is immune to both. + /// A recorded pid is not evidence on its own - it outlives an anchor that + /// was killed, and pids are reused. The check that matters is whether that + /// process is actually holding *this* environment's upper layer, which is + /// the same test `holders` performs and is immune to both. pub fn session(&self) -> Option { - let pid: u32 = std::fs::read_to_string(self.session_file()) - .ok()? - .trim() - .parse() + // The legacy record is read too, so a launch right after an upgrade + // joins the session the older release started instead of refusing + // until it is stopped. A reboot ends every such session. + let text = std::fs::read_to_string(self.session_file().ok()?) + .or_else(|_| std::fs::read_to_string(self.legacy_session_file())) .ok()?; + let pid: u32 = text.trim().parse().ok()?; self.holds(pid).then_some(pid) } @@ -64,7 +73,7 @@ impl Environment { } // A dead session's file would otherwise sit there looking alive to a // reader that trusts it. - let _ = std::fs::remove_file(self.session_file()); + self.clear_session(); // No session, but something else still holds the upper layer - // typically the Wine services of a session whose anchor has just died, @@ -78,8 +87,11 @@ impl Environment { // together - a file manager double-click registering twice, a script // starting two programs - would both see no session and both start an // anchor. Only one can mount, and the loser died on a bare EBUSY. - // O_EXCL on a lock file settles which of them gets to try. - let lock = self.root.join("session.lock"); + // O_EXCL on a lock file settles which of them gets to try. It sits + // with the record, in a directory the first launch has to create. + let run = paths::runtime_dir(&self.name)?; + std::fs::create_dir_all(&run).map_err(|e| Error::Layer(run.clone(), e))?; + let lock = run.join("session.lock"); let held = loop { match std::fs::OpenOptions::new() .write(true) @@ -236,7 +248,10 @@ impl Environment { /// Forgets a session's record. The processes are `stop`'s business. pub fn clear_session(&self) { - let _ = std::fs::remove_file(self.session_file()); + if let Ok(file) = self.session_file() { + let _ = std::fs::remove_file(file); + } + let _ = std::fs::remove_file(self.legacy_session_file()); } } @@ -362,6 +377,10 @@ pub fn anchor(name: &str) -> ! { Ok(s) => s, Err(err) => report(format_args!("{}", crate::i18n::error_message(&err))), }; + let record = match e.session_file() { + Ok(f) => f, + Err(err) => report(format_args!("{}", crate::i18n::error_message(&err))), + }; if let Err(err) = std::fs::create_dir_all(&spec.target) { report(format_args!( "{}", @@ -389,13 +408,14 @@ pub fn anchor(name: &str) -> ! { } let pid = std::process::id(); // Written only after the mount exists, so a reader of this file never - // sees a session that cannot be joined. - if let Err(err) = std::fs::write(e.session_file(), format!("{pid}\n")) { + // sees a session that cannot be joined. Its directory is the mount + // point's parent, created above. + if let Err(err) = std::fs::write(&record, format!("{pid}\n")) { report(format_args!( "{}", tf( "anchor_no_record", - &[("path", &e.session_file().display()), ("why", &err)] + &[("path", &record.display()), ("why", &err)] ) )); } @@ -444,13 +464,17 @@ mod helper_tests { #[cfg(test)] mod tests { use crate::env::{Environment, Manifest}; + use crate::paths; + /// An environment whose name no real one has, so its runtime directory, + /// which these tests write to, is theirs alone. fn fake(name: &str) -> Environment { - let root = std::env::temp_dir().join(format!("raven-sess-{name}-{}", std::process::id())); + let name = format!("raven-sess-{name}-{}", std::process::id()); + let root = std::env::temp_dir().join(&name); let _ = std::fs::remove_dir_all(&root); std::fs::create_dir_all(root.join("upper")).unwrap(); Environment { - name: name.into(), + name, manifest: Manifest { base: "none".into(), }, @@ -458,11 +482,26 @@ mod tests { } } + fn record(e: &Environment, text: &str) { + let file = e + .session_file() + .expect("XDG_RUNTIME_DIR is set wherever Raven can run"); + std::fs::create_dir_all(file.parent().unwrap()).unwrap(); + std::fs::write(file, text).unwrap(); + } + + fn remove(e: &Environment) { + let _ = std::fs::remove_dir_all(&e.root); + if let Ok(run) = paths::runtime_dir(&e.name) { + let _ = std::fs::remove_dir_all(run); + } + } + #[test] fn no_file_means_no_session() { let e = fake("none"); assert_eq!(e.session(), None); - let _ = std::fs::remove_dir_all(&e.root); + remove(&e); } #[test] @@ -471,16 +510,42 @@ mod tests { // and has a valid pid, but holds no layer of ours. A reader trusting // the file alone would try to join a namespace that is not there. let e = fake("stale"); - std::fs::write(e.session_file(), format!("{}\n", std::process::id())).unwrap(); + record(&e, &format!("{}\n", std::process::id())); assert_eq!(e.session(), None); - let _ = std::fs::remove_dir_all(&e.root); + remove(&e); } #[test] fn nonsense_in_the_file_is_not_a_session() { let e = fake("junk"); - std::fs::write(e.session_file(), "not a pid\n").unwrap(); + record(&e, "not a pid\n"); assert_eq!(e.session(), None); - let _ = std::fs::remove_dir_all(&e.root); + remove(&e); + } + + #[test] + fn the_record_is_runtime_state_beside_the_mount() { + // A session dies at reboot with its mount, so its record goes with + // them: under the runtime directory, never in the data directory + // where it would outlive both. + let e = fake("where"); + let file = e + .session_file() + .expect("XDG_RUNTIME_DIR is set wherever Raven can run"); + assert_eq!(file.parent(), paths::mount_point(&e.name).unwrap().parent()); + assert!(!file.starts_with(paths::data_dir().unwrap())); + remove(&e); + } + + #[test] + fn clearing_forgets_the_legacy_record_too() { + let e = fake("legacy"); + record(&e, "1\n"); + let legacy = e.root.join("session.pid"); + std::fs::write(&legacy, "1\n").unwrap(); + e.clear_session(); + assert!(!e.session_file().unwrap().exists()); + assert!(!legacy.exists()); + remove(&e); } } diff --git a/crates/raven/tests/environment_recovery.rs b/crates/raven/tests/environment_recovery.rs index b5b722a..8a2259d 100644 --- a/crates/raven/tests/environment_recovery.rs +++ b/crates/raven/tests/environment_recovery.rs @@ -48,7 +48,9 @@ fn a_process_left_in_the_namespace_is_found_and_stopped() { // Constructed by hand rather than through `create()`: holders and stop // depend only on the layout, and this test must not need Wine. let env = Environment { - name: "recovery-test".into(), + // Unique, because the session record goes to this name's runtime + // directory and a real environment could be called anything. + name: format!("recovery-test-{}", std::process::id()), manifest: Manifest { base: "none".into(), }, @@ -96,19 +98,29 @@ fn a_process_left_in_the_namespace_is_found_and_stopped() { "a live process that holds nothing of ours is not a session" ); // And the recorded pid is believed only when it is really a holder. - fs::write(env.session_file(), "1\n").unwrap(); + let record = env.session_file().expect("XDG_RUNTIME_DIR is set"); + fs::create_dir_all(record.parent().unwrap()).unwrap(); + fs::write(&record, "1\n").unwrap(); assert_eq!( env.session(), None, "a stale pid must not pass for a session" ); - fs::write(env.session_file(), format!("{}\n", child.id())).unwrap(); + fs::write(&record, format!("{}\n", child.id())).unwrap(); assert_eq!( env.session(), Some(child.id()), "a real holder is the session" ); - let _ = fs::remove_file(env.session_file()); + let _ = fs::remove_file(&record); + // A session started by a release that kept the record in the data + // directory is still joined after an upgrade. + fs::write(root.join("session.pid"), format!("{}\n", child.id())).unwrap(); + assert_eq!( + env.session(), + Some(child.id()), + "the legacy record must still find a live session" + ); let stopped = env.stop().expect("stop must release the environment"); assert!( @@ -122,6 +134,7 @@ fn a_process_left_in_the_namespace_is_found_and_stopped() { // Reap it; stop already killed it. let _ = child.wait(); let _ = fs::remove_dir_all(&root); + let _ = fs::remove_dir_all(record.parent().unwrap()); } #[test] diff --git a/docs/internals/gui.md b/docs/internals/gui.md index 9856307..c110490 100644 --- a/docs/internals/gui.md +++ b/docs/internals/gui.md @@ -119,7 +119,7 @@ knows what was true. | | | |---|---| | `packaging/PKGBUILD` | builds and installs two binaries, plus a second desktop entry for `raven-gui`. The existing `raven.desktop` serves the `.exe` double-click and must not become the GUI launcher | -| `[profile.release]` | `lto = "fat"`, `codegen-units = 1` now apply to an iced binary too - much slower to compile, and to be accepted or adjusted deliberately | +| `[profile.release]` | `lto` and `codegen-units = 1` apply to an iced binary too. Fat LTO, used until v0.5.0, made it much slower to compile and bought the launcher no start-up, so the profile now uses the organisation's thin LTO | | `release-please` | tracks `.` as one package. **One version for both**: the GUI is unusable against a library of a different age | | the release workflow | builds, signs and publishes both binaries: `assets` names `raven-linux` and `raven-gui-linux` | diff --git a/docs/internals/mount-stack.md b/docs/internals/mount-stack.md index 36d2c98..5cb297c 100644 --- a/docs/internals/mount-stack.md +++ b/docs/internals/mount-stack.md @@ -177,7 +177,8 @@ everything sits under `Colony/Raven/`: ``` ~/.config/Colony/Raven/ what the user chose -└── default-environment where a .exe outside any environment runs +├── default-environment where a .exe outside any environment runs +└── preferences/preferences.toml the window's appearance and language ~/.local/share/Colony/Raven/ what Raven produced and cannot re-derive ├── bases// a deployed Windows @@ -192,7 +193,10 @@ everything sits under `Colony/Raven/`: ~/.cache/Colony/Raven/ deleting it costs only time └── unpack/d3d--