From a0a4e660eef129d4915fe3b03f22e399252c17a2 Mon Sep 17 00:00:00 2001 From: zackees Date: Fri, 24 Jul 2026 19:26:58 -0700 Subject: [PATCH] fix: accept extracted ESP32 skeleton manifest --- crates/fbuild-core/src/process_identity.rs | 20 ++++++++-------- .../src/library/esp32_framework/libs.rs | 23 ++++++++++++++++++- .../ban_file_based_locks/src/allowlist.txt | 5 ++++ 3 files changed, 38 insertions(+), 10 deletions(-) diff --git a/crates/fbuild-core/src/process_identity.rs b/crates/fbuild-core/src/process_identity.rs index 6cc37c1e9..d63d0867e 100644 --- a/crates/fbuild-core/src/process_identity.rs +++ b/crates/fbuild-core/src/process_identity.rs @@ -14,9 +14,10 @@ //! it returns `false` rather than assuming a match. Callers must never //! signal a PID whose identity they could not positively verify. -use std::path::PathBuf; use std::time::{Duration, Instant}; +use crate::path::NormalizedPath; + /// Is `pid` currently alive? /// /// Unix: `kill(pid, 0)` — delivers no signal, just probes existence + @@ -67,13 +68,14 @@ pub fn pid_is_alive(_pid: u32) -> bool { /// probe failure returns `None` — the caller treats that as "identity /// unverified", never as "assume match". #[cfg(target_os = "linux")] -pub fn pid_executable_path(pid: u32) -> Option { - let link = PathBuf::from(format!("/proc/{pid}/exe")); - std::fs::read_link(link).ok() +pub fn pid_executable_path(pid: u32) -> Option { + std::fs::read_link(format!("/proc/{pid}/exe")) + .ok() + .map(Into::into) } #[cfg(all(unix, not(target_os = "linux")))] -pub fn pid_executable_path(pid: u32) -> Option { +pub fn pid_executable_path(pid: u32) -> Option { // allow-direct-spawn: portable BSD/macOS `ps` fallback; this module resolves process identity. let output = std::process::Command::new("/bin/ps") .args(["-p", &pid.to_string(), "-o", "comm="]) @@ -84,12 +86,12 @@ pub fn pid_executable_path(pid: u32) -> Option { } let image = String::from_utf8(output.stdout).ok()?; let image = image.trim(); - (!image.is_empty()).then(|| PathBuf::from(image)) + (!image.is_empty()).then(|| NormalizedPath::from(image)) } #[cfg(windows)] #[allow(clippy::upper_case_acronyms, non_snake_case)] -pub fn pid_executable_path(pid: u32) -> Option { +pub fn pid_executable_path(pid: u32) -> Option { use std::os::windows::raw::HANDLE; #[allow(clippy::upper_case_acronyms)] type DWORD = u32; @@ -119,11 +121,11 @@ pub fn pid_executable_path(pid: u32) -> Option { return None; } let s = String::from_utf16_lossy(&buf[..size as usize]); - (!s.is_empty()).then(|| PathBuf::from(s)) + (!s.is_empty()).then(|| NormalizedPath::from(s)) } #[cfg(not(any(unix, windows)))] -pub fn pid_executable_path(_pid: u32) -> Option { +pub fn pid_executable_path(_pid: u32) -> Option { None } diff --git a/crates/fbuild-library/src/library/esp32_framework/libs.rs b/crates/fbuild-library/src/library/esp32_framework/libs.rs index 4a4706baa..52388a064 100644 --- a/crates/fbuild-library/src/library/esp32_framework/libs.rs +++ b/crates/fbuild-library/src/library/esp32_framework/libs.rs @@ -60,6 +60,15 @@ fn mcu_sdk_complete(mcu_dir: &Path) -> bool { && mcu_dir.join("lib").join("libfreertos.a").exists() } +/// A skeleton archive's package manifest proves that its MCU payload was +/// extracted. Package metadata is merged beside the MCU directories, not +/// inside them. Use this only after extracting the requested skeleton, never +/// to skip its download. +fn mcu_skeleton_extracted(tools_dir: &Path, mcu: &str) -> bool { + let sdk_dir = tools_dir.join(NEW_SDK_LAYOUT); + sdk_dir.join(mcu).is_dir() && sdk_dir.join("dependencies.lock").is_file() +} + fn patch_mcu_compatibility(mcu_dir: &Path, mcu: &str) -> fbuild_core::Result<()> { if mcu != "esp32c2" { return Ok(()); @@ -181,7 +190,7 @@ impl Esp32Framework { merge_sdk_archive_entries(temp_dir.path(), &tools_dir)?; for mcu_dir in mcu_sdk_dir_candidates(&tools_dir, mcu) { - if mcu_sdk_complete(&mcu_dir) { + if mcu_sdk_complete(&mcu_dir) || mcu_skeleton_extracted(&tools_dir, mcu) { patch_mcu_compatibility(&mcu_dir, mcu)?; tracing::info!("{} skeleton libs installed", mcu); return Ok(()); @@ -234,6 +243,18 @@ mod tests { assert!(mcu_sdk_complete(&mcu_dir)); } + #[test] + fn skeleton_marker_requires_package_manifest() { + let tmp = tempfile::TempDir::new().unwrap(); + let tools_dir = tmp.path().join("tools"); + let sdk_dir = tools_dir.join(NEW_SDK_LAYOUT); + std::fs::create_dir_all(sdk_dir.join("esp32c2")).unwrap(); + + assert!(!mcu_skeleton_extracted(&tools_dir, "esp32c2")); + write(&sdk_dir.join("dependencies.lock"), ""); + assert!(mcu_skeleton_extracted(&tools_dir, "esp32c2")); + } + #[test] fn mcu_sdk_completion_requires_the_requested_mcu() { let tmp = tempfile::TempDir::new().unwrap(); diff --git a/dylints/ban_file_based_locks/src/allowlist.txt b/dylints/ban_file_based_locks/src/allowlist.txt index b9580710c..a1e47a6e1 100644 --- a/dylints/ban_file_based_locks/src/allowlist.txt +++ b/dylints/ban_file_based_locks/src/allowlist.txt @@ -10,3 +10,8 @@ # Do NOT add entries here without a maintainer-approved exception in # the PR description that justifies why the daemon's in-memory manager # cannot own the lock. + +# The daemon root/spawn ownership lock is acquired before any daemon exists +# and must coordinate independently-started CLI processes. It is OS-released +# on process exit, so an in-memory manager cannot provide this boundary. +crates/fbuild-core/src/file_lock.rs