Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 11 additions & 9 deletions crates/fbuild-core/src/process_identity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 +
Expand Down Expand Up @@ -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<PathBuf> {
let link = PathBuf::from(format!("/proc/{pid}/exe"));
std::fs::read_link(link).ok()
pub fn pid_executable_path(pid: u32) -> Option<NormalizedPath> {
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<PathBuf> {
pub fn pid_executable_path(pid: u32) -> Option<NormalizedPath> {
// 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="])
Expand All @@ -84,12 +86,12 @@ pub fn pid_executable_path(pid: u32) -> Option<PathBuf> {
}
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<PathBuf> {
pub fn pid_executable_path(pid: u32) -> Option<NormalizedPath> {
use std::os::windows::raw::HANDLE;
#[allow(clippy::upper_case_acronyms)]
type DWORD = u32;
Expand Down Expand Up @@ -119,11 +121,11 @@ pub fn pid_executable_path(pid: u32) -> Option<PathBuf> {
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<PathBuf> {
pub fn pid_executable_path(_pid: u32) -> Option<NormalizedPath> {
None
}

Expand Down
23 changes: 22 additions & 1 deletion crates/fbuild-library/src/library/esp32_framework/libs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(());
Expand Down Expand Up @@ -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(());
Expand Down Expand Up @@ -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();
Expand Down
5 changes: 5 additions & 0 deletions dylints/ban_file_based_locks/src/allowlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading