From e279798b429505307c9e7f897a0b7aef93721d91 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Fri, 2 Oct 2026 08:49:20 +0000 Subject: [PATCH 1/9] fix(git): report a repo-less mount as absent, not unreadable --- crates/tracedecay-runtime-core/src/git_repository.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/crates/tracedecay-runtime-core/src/git_repository.rs b/crates/tracedecay-runtime-core/src/git_repository.rs index f41d617c18..fffa5b8878 100644 --- a/crates/tracedecay-runtime-core/src/git_repository.rs +++ b/crates/tracedecay-runtime-core/src/git_repository.rs @@ -881,9 +881,14 @@ impl GitRepositoryAuthority { crate::git_open::discover(path) ) .map_err(|error| match error { - gix::discover::Error::Discover(gix::discover::upwards::Error::NoGitRepository { - .. - }) => GitRepositoryError::NotARepository { + // Like git without GIT_DISCOVERY_ACROSS_FILESYSTEM, a walk that + // stops at a mount boundary (a tmpfs /tmp, say) or a ceiling has + // found no repository; nothing on disk was unreadable. + gix::discover::Error::Discover( + gix::discover::upwards::Error::NoGitRepository { .. } + | gix::discover::upwards::Error::NoGitRepositoryWithinFs { .. } + | gix::discover::upwards::Error::NoGitRepositoryWithinCeiling { .. }, + ) => GitRepositoryError::NotARepository { path: path.display().to_string(), }, error => GitRepositoryError::UnreadableRepository { From 1d16ecb60229e31e12241c325024a0e3aa4ace41 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Fri, 2 Oct 2026 08:49:22 +0000 Subject: [PATCH 2/9] fix(daemon-control): skip systemctl when no unit is installed --- .../tracedecay-daemon-control/src/service.rs | 13 ++-- .../src/service/runner.rs | 71 +++++++++++++------ .../src/service/tests.rs | 36 ++++++++++ 3 files changed, 94 insertions(+), 26 deletions(-) diff --git a/crates/tracedecay-daemon-control/src/service.rs b/crates/tracedecay-daemon-control/src/service.rs index b04749b7ae..927612ded2 100644 --- a/crates/tracedecay-daemon-control/src/service.rs +++ b/crates/tracedecay-daemon-control/src/service.rs @@ -213,7 +213,7 @@ impl QuiescedDaemonLifecycle { profile, operation, expected_version, - ServiceRunner::current()?, + ServiceRunner::current_for_installed_unit()?, timeout, ) } @@ -1155,7 +1155,7 @@ fn refresh_installed_service_with_state( expected_version: &str, ) -> Result> { refresh_installed_service_with_state_and_runner( - &ServiceRunner::current()?, + &ServiceRunner::current_for_installed_unit()?, spec, previous_state, expected_version, @@ -1208,7 +1208,7 @@ pub fn quiesce_installed_service_before_lease( ) -> Result { quiesce_installed_service_before_lease_with_runner( profile, - &ServiceRunner::current()?, + &ServiceRunner::current_for_installed_unit()?, expected_version, ) } @@ -1262,7 +1262,10 @@ fn quiesce_installed_service_before_lease_with_runner( pub fn verify_installed_service_quiesced_under_lease( profile: &ProfileRoot, ) -> Result { - verify_installed_service_quiesced_under_lease_with_runner(profile, &ServiceRunner::current()?) + verify_installed_service_quiesced_under_lease_with_runner( + profile, + &ServiceRunner::current_for_installed_unit()?, + ) } fn verify_installed_service_quiesced_under_lease_with_runner( @@ -1319,7 +1322,7 @@ pub fn restore_installed_service_after_update( ) -> Result<()> { restore_installed_service_after_update_with_runner( profile, - &ServiceRunner::current()?, + &ServiceRunner::current_for_installed_unit()?, previous_state, expected_version, ) diff --git a/crates/tracedecay-daemon-control/src/service/runner.rs b/crates/tracedecay-daemon-control/src/service/runner.rs index ac12cc8d83..a07b0bcf9f 100644 --- a/crates/tracedecay-daemon-control/src/service/runner.rs +++ b/crates/tracedecay-daemon-control/src/service/runner.rs @@ -12,8 +12,15 @@ use tracedecay_runtime_core::config::ProfileRoot; /// All variants exist on every platform so that dispatch stays exhaustive. #[derive(Clone, Debug)] pub(super) enum ServiceRunner { - Systemd { systemctl: PathBuf }, - Launchd { launchctl: PathBuf, id: PathBuf }, + /// `None` only from [`ServiceRunner::current_for_installed_unit`]: the + /// missing `systemctl` is reported when a unit operation first needs it. + Systemd { + systemctl: Option, + }, + Launchd { + launchctl: PathBuf, + id: PathBuf, + }, WindowsTask, } @@ -63,13 +70,25 @@ impl ServiceRunner { } } + /// [`Self::current`] for paths that consult the service manager only once + /// a unit is installed. A Linux host without systemd (a container, say) + /// has no managed unit, so its maintenance must not require `systemctl`. + pub(super) fn current_for_installed_unit() -> Result { + match Self::current() { + Err(TraceDecayError::HostCliUnavailable { program, .. }) if program == "systemctl" => { + Ok(Self::Systemd { systemctl: None }) + } + runner => runner, + } + } + pub(super) fn systemd(systemctl: impl AsRef) -> Result { Ok(Self::Systemd { - systemctl: required_service_program( + systemctl: Some(required_service_program( "systemctl", "systemd user service management", systemctl.as_ref(), - )?, + )?), }) } @@ -96,7 +115,7 @@ impl ServiceRunner { match self { Self::Systemd { systemctl } => { for arguments in systemd_install_command_plan(start) { - run_systemctl(systemctl, &arguments)?; + run_systemctl(systemctl.as_deref(), &arguments)?; } Ok(()) } @@ -125,9 +144,9 @@ impl ServiceRunner { ) -> Result<()> { match self { Self::Systemd { systemctl } => { - run_systemctl(systemctl, &["daemon-reload"])?; + run_systemctl(systemctl.as_deref(), &["daemon-reload"])?; if previous_state.is_running() { - run_systemctl(systemctl, &["restart", crate::SERVICE_NAME])?; + run_systemctl(systemctl.as_deref(), &["restart", crate::SERVICE_NAME])?; } Ok(()) } @@ -156,13 +175,13 @@ impl ServiceRunner { ) -> std::result::Result { match self { Self::Systemd { systemctl } => { - let activity = systemctl_unit_query(systemctl, "is-active")?; + let activity = systemctl_unit_query(systemctl.as_deref(), "is-active")?; let running = match activity.as_str() { "active" | "reloading" | "refreshing" => true, "inactive" | "failed" | "activating" | "deactivating" | "maintenance" => false, _ => return Err(systemctl_unknown_state("is-active", &activity).into()), }; - let enablement = systemctl_unit_query(systemctl, "is-enabled")?; + let enablement = systemctl_unit_query(systemctl.as_deref(), "is-enabled")?; if enablement.starts_with("masked") { Ok(DaemonServiceState::Masked) } else if running && enablement.starts_with("enabled") { @@ -189,7 +208,10 @@ impl ServiceRunner { match self { Self::Systemd { systemctl } => { if stop { - let _ = run_systemctl(systemctl, &["disable", "--now", crate::SERVICE_NAME]); + let _ = run_systemctl( + systemctl.as_deref(), + &["disable", "--now", crate::SERVICE_NAME], + ); } Ok(()) } @@ -209,7 +231,7 @@ impl ServiceRunner { match self { Self::Systemd { systemctl } => { for arguments in systemd_start_command_plan() { - run_systemctl(systemctl, &arguments)?; + run_systemctl(systemctl.as_deref(), &arguments)?; } Ok(()) } @@ -230,7 +252,9 @@ impl ServiceRunner { #[hotpath::measure(label = "daemon.service.runner.stop")] pub(super) fn stop(&self, expected_version: &str) -> Result<()> { match self { - Self::Systemd { systemctl } => run_systemctl(systemctl, &["stop", crate::SERVICE_NAME]), + Self::Systemd { systemctl } => { + run_systemctl(systemctl.as_deref(), &["stop", crate::SERVICE_NAME]) + } Self::Launchd { launchctl, id } => launchd_stop(launchctl, id), Self::WindowsTask => windows_task::stop(expected_version), } @@ -253,13 +277,13 @@ impl ServiceRunner { } match self { Self::Systemd { systemctl } => { - run_systemctl(systemctl, &["daemon-reload"])?; + run_systemctl(systemctl.as_deref(), &["daemon-reload"])?; if previous_state.is_enabled() { - run_systemctl(systemctl, &["enable", crate::SERVICE_NAME])?; + run_systemctl(systemctl.as_deref(), &["enable", crate::SERVICE_NAME])?; } else { - run_systemctl(systemctl, &["disable", crate::SERVICE_NAME])?; + run_systemctl(systemctl.as_deref(), &["disable", crate::SERVICE_NAME])?; } - run_systemctl(systemctl, &["start", crate::SERVICE_NAME])?; + run_systemctl(systemctl.as_deref(), &["start", crate::SERVICE_NAME])?; // `systemctl start` reports the fork, not a serving daemon. // Restore success must mean an authenticated daemon at the // expected version answering from the installed unit's socket. @@ -295,7 +319,7 @@ impl ServiceRunner { match self { Self::Systemd { systemctl } => { if stop { - let _ = run_systemctl(systemctl, &["daemon-reload"]); + let _ = run_systemctl(systemctl.as_deref(), &["daemon-reload"]); } } Self::Launchd { .. } | Self::WindowsTask => {} @@ -351,8 +375,13 @@ fn run_launchctl(launchctl: &Path, args: &[&str]) -> Result Result<()> { - let output = Command::new(systemctl) +fn require_systemctl(systemctl: Option<&Path>) -> Result<&Path> { + systemctl + .ok_or_else(|| service_program_unavailable("systemctl", "systemd user service management")) +} + +fn run_systemctl(systemctl: Option<&Path>, args: &[&str]) -> Result<()> { + let output = Command::new(require_systemctl(systemctl)?) .arg("--user") .args(args) .output() @@ -514,10 +543,10 @@ impl From for TraceDecayError { /// or disabled unit and when the user manager is unreachable; only the printed /// state tells them apart, so an empty answer is an error, not "stopped". fn systemctl_unit_query( - systemctl: &Path, + systemctl: Option<&Path>, verb: &str, ) -> std::result::Result { - let output = Command::new(systemctl) + let output = Command::new(require_systemctl(systemctl)?) .args(["--user", verb, crate::SERVICE_NAME]) .output() .map_err(|error| { diff --git a/crates/tracedecay-daemon-control/src/service/tests.rs b/crates/tracedecay-daemon-control/src/service/tests.rs index 023249e1ed..00891e272a 100644 --- a/crates/tracedecay-daemon-control/src/service/tests.rs +++ b/crates/tracedecay-daemon-control/src/service/tests.rs @@ -1654,6 +1654,42 @@ fn post_update_rejects_reachable_unmanaged_daemon() { assert!(error.to_string().contains("stop")); } +/// A Linux host without systemd (a container) has no managed unit, so +/// maintenance that only quiesces an installed unit must not need `systemctl`; +/// a lifecycle command that drives the unit still reports it missing. +#[cfg(target_os = "linux")] +#[test] +fn maintenance_without_an_installed_unit_does_not_need_systemctl() { + let dir = TempDir::new().expect("temp dir"); + let data_dir = dir.path().join("profile"); + std::fs::create_dir_all(&data_dir).expect("data dir"); + let profile = ProfileRoot::new(&data_dir).with_xdg_config_home(dir.path().join("config")); + let empty_bin = dir.path().join("bin"); + std::fs::create_dir_all(&empty_bin).expect("empty bin dir"); + let _path_guard = tracedecay_runtime_core::config::HostProgramSearchPathGuard::set(&empty_bin); + + assert_eq!( + super::quiesce_installed_service_before_lease(&profile, TEST_BUILD_VERSION) + .expect("quiesce without a unit"), + DaemonServiceState::Missing + ); + assert_eq!( + super::verify_installed_service_quiesced_under_lease(&profile) + .expect("verify without a unit"), + DaemonServiceState::Missing + ); + let error = ServiceRunner::current_for_installed_unit() + .expect("deferred runner") + .stop(TEST_BUILD_VERSION) + .expect_err("driving the service manager still needs systemctl"); + assert!( + error + .to_string() + .contains("host CLI `systemctl` is unavailable"), + "{error}" + ); +} + #[cfg(target_os = "linux")] #[test] fn refresh_installed_service_preserves_existing_socket_path() { From 9fa1ed452892b5722ad0d2a0f765095bae2817ab Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Fri, 2 Oct 2026 08:49:23 +0000 Subject: [PATCH 3/9] test(private-fs): skip inode generation on non-ext4 mounts --- .../src/inode_generation.rs | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/crates/tracedecay-private-fs/src/inode_generation.rs b/crates/tracedecay-private-fs/src/inode_generation.rs index c6477a2c66..deef16bfe4 100644 --- a/crates/tracedecay-private-fs/src/inode_generation.rs +++ b/crates/tracedecay-private-fs/src/inode_generation.rs @@ -65,9 +65,16 @@ mod tests { let temp = tempfile::tempdir().unwrap(); let path = temp.path().join("rollout.jsonl"); std::fs::write(&path, b"{}\n").unwrap(); - let first = inode_generation(&std::fs::File::open(&path).unwrap()) - .unwrap() - .expect("ext4 reports an inode generation"); + let Some(first) = inode_generation(&std::fs::File::open(&path).unwrap()).unwrap() else { + // tmpfs (a container's /tmp) and ZFS do not implement + // FS_IOC_GETVERSION; only ext4 is required to report one here. + assert!(!is_ext4(temp.path()), "ext4 reports an inode generation"); + eprintln!( + "skipping: the filesystem under {} reports no inode generation", + temp.path().display() + ); + return; + }; std::fs::remove_file(&path).unwrap(); std::fs::write(&path, b"{}\n").unwrap(); let second = inode_generation(&std::fs::File::open(&path).unwrap()) @@ -75,4 +82,15 @@ mod tests { .expect("ext4 reports an inode generation"); assert_ne!(first, second); } + + #[cfg(target_os = "linux")] + fn is_ext4(path: &std::path::Path) -> bool { + use std::os::unix::ffi::OsStrExt; + let path = std::ffi::CString::new(path.as_os_str().as_bytes()).unwrap(); + // SAFETY: `statfs` is a plain C struct, valid when zeroed. + let mut stat: libc::statfs = unsafe { std::mem::zeroed() }; + // SAFETY: `path` is NUL-terminated and `stat` is a writable `statfs`. + assert_eq!(unsafe { libc::statfs(path.as_ptr(), &mut stat) }, 0); + stat.f_type == libc::EXT4_SUPER_MAGIC + } } From 7fc6fd23a3e6d4faec5f54926a455f1b215d7714 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Fri, 2 Oct 2026 08:49:24 +0000 Subject: [PATCH 4/9] test(sessions): place codex lookup target mid-corpus --- .../src/runtime/hosts/codex/tests.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/crates/tracedecay-sessions/src/runtime/hosts/codex/tests.rs b/crates/tracedecay-sessions/src/runtime/hosts/codex/tests.rs index 53a9e3dfac..c0ef493bf9 100644 --- a/crates/tracedecay-sessions/src/runtime/hosts/codex/tests.rs +++ b/crates/tracedecay-sessions/src/runtime/hosts/codex/tests.rs @@ -1806,16 +1806,20 @@ mod recent_first_discovery_tests { let home = resolved_home.as_path(); let directory = home.join(".codex/sessions/2026/08/23"); std::fs::create_dir_all(&directory).unwrap(); + let session_id = "0198-session-beyond-default-budget"; + let expected = directory.join(format!("rollout-2026-08-23-{session_id}.jsonl")); + // Write the target mid-corpus: tmpfs lists newest entries first and + // btrfs oldest first, so either end would land in the first slice. for index in 0..4_100 { + if index == 2_050 { + std::fs::write(&expected, b"{}\n").unwrap(); + } std::fs::write( directory.join(format!("rollout-distractor-{index:04}.jsonl")), b"{}\n", ) .unwrap(); } - let session_id = "0198-session-beyond-default-budget"; - let expected = directory.join(format!("rollout-2026-08-23-{session_id}.jsonl")); - std::fs::write(&expected, b"{}\n").unwrap(); let source = CodexSource::with_home(home); let mut calls = 0_u64; From 22181a2606fbcaf489b1cc5dced3a55b80f5a329 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Fri, 2 Oct 2026 08:49:25 +0000 Subject: [PATCH 5/9] test(code-index): expect aarch64 SwissTable group width --- .../src/graph_projection/interactive/tests.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/crates/tracedecay-code-index/src/graph_projection/interactive/tests.rs b/crates/tracedecay-code-index/src/graph_projection/interactive/tests.rs index 2c5a5d48cb..d228f6ad3e 100644 --- a/crates/tracedecay-code-index/src/graph_projection/interactive/tests.rs +++ b/crates/tracedecay-code-index/src/graph_projection/interactive/tests.rs @@ -1133,7 +1133,14 @@ fn a_released_catalog_gives_back_its_bytes_and_rebuilds_in_the_background() { .symbols, ); let held = store.interactive_catalog_bytes(); - assert_eq!(held, Some(6_187)); + // Three SwissTables each carry one trailing control group: 16 bytes on + // x86 SSE2, 8 on aarch64 NEON. + let expected = if cfg!(target_arch = "aarch64") { + 6_163 + } else { + 6_187 + }; + assert_eq!(held, Some(expected)); assert_eq!( store.release_interactive_catalog(), From 7363e29ecdeac788d1b976e6e7c45bae0213c33a Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Fri, 2 Oct 2026 08:49:26 +0000 Subject: [PATCH 6/9] test(mcp): assert ingest commit, then read projected rows --- .../hook_runtime_request_test.rs | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/crates/tracedecay/tests/mcp_suite/mcp_handler_test/hook_runtime_request_test.rs b/crates/tracedecay/tests/mcp_suite/mcp_handler_test/hook_runtime_request_test.rs index 0d704f3249..891ff9d701 100644 --- a/crates/tracedecay/tests/mcp_suite/mcp_handler_test/hook_runtime_request_test.rs +++ b/crates/tracedecay/tests/mcp_suite/mcp_handler_test/hook_runtime_request_test.rs @@ -204,7 +204,34 @@ async fn project_transcript_ingest_settles_emitted_hints_in_the_served_profile() }), ) .await; - assert_eq!(ingest["messages_upserted"], 2, "ingest: {ingest}"); + // Admission is the commit. `messages_upserted` counts only what this + // pass's own drain projected, and a peer drain (or a deferred one behind + // the predecessor rebuild on a slow host) may project the rows instead, + // so assert the commit here and the projection through the read path. + assert_eq!(ingest["observations_committed"], 2, "ingest: {ingest}"); + let roles = tokio::time::timeout(std::time::Duration::from_secs(30), async { + loop { + let loaded = answer_tool( + &fixture, + "tracedecay_lcm_load_session", + json!({"provider": "cursor", "session_id": "cursor-session", "limit": 10}), + ) + .await; + let messages = &loaded["outcome"]["value"]["payload"]["messages"]; + if messages.as_array().is_some_and(|m| m.len() == 2) { + break messages + .as_array() + .into_iter() + .flatten() + .filter_map(|message| message["role"].as_str().map(str::to_owned)) + .collect::>(); + } + tokio::time::sleep(std::time::Duration::from_millis(50)).await; + } + }) + .await + .expect("both committed cursor messages are projected"); + assert_eq!(roles, ["assistant".to_owned(), "user".to_owned()].into()); assert_eq!( ingest["hint_outcomes"], json!({ From 1e19ffbe16c62f76626f66b4d481308f3ae4084b Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Fri, 2 Oct 2026 08:49:27 +0000 Subject: [PATCH 7/9] ci(hauler): expose rustup proxies in the sandbox CARGO_HOME --- .github/hauler-ci.json | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/hauler-ci.json b/.github/hauler-ci.json index 01c38e24e0..0384ae1b44 100644 --- a/.github/hauler-ci.json +++ b/.github/hauler-ci.json @@ -29,6 +29,7 @@ "prepare": [ "rm -rf target/nextest target/test-profile", "pnpm install --frozen-lockfile --store-dir /cache/pnpm", + "mkdir -p \"$CARGO_HOME/bin\" && ln -sf /opt/cargo/bin/* \"$CARGO_HOME/bin/\"", "rustup show active-toolchain && rustup which rust-analyzer", "python3 scripts/test-linux-test-partitions.py && python3 scripts/linux-test-partitions.py check", "python3 -c 'import json,subprocess; actual={row[\"group\"] for row in json.loads(subprocess.check_output([\"python3\",\"scripts/linux-test-partitions.py\",\"linux-matrix\"]))[\"include\"]}; assert actual == {'\"'\"'core-contracts'\"'\"', '\"'\"'runtime'\"'\"', '\"'\"'root-lib-sessions'\"'\"', '\"'\"'root-journeys'\"'\"', '\"'\"'core-storage'\"'\"', '\"'\"'root-transport'\"'\"'}, \"Linux groups changed; update the trusted Hauler recipe before delegation\"'" From 0f36a171b60bfc508c94665af0e39dbe6a2c6b6f Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Fri, 2 Oct 2026 10:28:11 +0000 Subject: [PATCH 8/9] fix(ci): skip proxy links when CARGO_HOME is the image's The image sets CARGO_HOME=/opt/cargo. Linking /opt/cargo/bin/* onto itself makes ln fail with 'are the same file', failing prepare on any run that keeps the image default. Link only into a different CARGO_HOME. --- .github/hauler-ci.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/hauler-ci.json b/.github/hauler-ci.json index 0384ae1b44..e108355be6 100644 --- a/.github/hauler-ci.json +++ b/.github/hauler-ci.json @@ -29,7 +29,7 @@ "prepare": [ "rm -rf target/nextest target/test-profile", "pnpm install --frozen-lockfile --store-dir /cache/pnpm", - "mkdir -p \"$CARGO_HOME/bin\" && ln -sf /opt/cargo/bin/* \"$CARGO_HOME/bin/\"", + "[ \"${CARGO_HOME:-/opt/cargo}\" = /opt/cargo ] || { mkdir -p \"$CARGO_HOME/bin\" && ln -sf /opt/cargo/bin/* \"$CARGO_HOME/bin/\"; }", "rustup show active-toolchain && rustup which rust-analyzer", "python3 scripts/test-linux-test-partitions.py && python3 scripts/linux-test-partitions.py check", "python3 -c 'import json,subprocess; actual={row[\"group\"] for row in json.loads(subprocess.check_output([\"python3\",\"scripts/linux-test-partitions.py\",\"linux-matrix\"]))[\"include\"]}; assert actual == {'\"'\"'core-contracts'\"'\"', '\"'\"'runtime'\"'\"', '\"'\"'root-lib-sessions'\"'\"', '\"'\"'root-journeys'\"'\"', '\"'\"'core-storage'\"'\"', '\"'\"'root-transport'\"'\"'}, \"Linux groups changed; update the trusted Hauler recipe before delegation\"'" From 16d824e336bf75905d5c2cc519d5467cc890b974 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Fri, 2 Oct 2026 11:44:29 +0000 Subject: [PATCH 9/9] style(private-fs): hoist ext4 test import to module scope --- crates/tracedecay-private-fs/src/inode_generation.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/tracedecay-private-fs/src/inode_generation.rs b/crates/tracedecay-private-fs/src/inode_generation.rs index deef16bfe4..bd948dc650 100644 --- a/crates/tracedecay-private-fs/src/inode_generation.rs +++ b/crates/tracedecay-private-fs/src/inode_generation.rs @@ -56,6 +56,9 @@ fn generation_bits(generation: libc::c_long) -> u64 { #[cfg(test)] mod tests { + #[cfg(target_os = "linux")] + use std::os::unix::ffi::OsStrExt; + #[cfg(target_os = "linux")] use super::inode_generation; @@ -85,7 +88,6 @@ mod tests { #[cfg(target_os = "linux")] fn is_ext4(path: &std::path::Path) -> bool { - use std::os::unix::ffi::OsStrExt; let path = std::ffi::CString::new(path.as_os_str().as_bytes()).unwrap(); // SAFETY: `statfs` is a plain C struct, valid when zeroed. let mut stat: libc::statfs = unsafe { std::mem::zeroed() };