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 @@
-**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--