feat: recipe durability guard — prevents build system from deleting local recipes
Add guard-recipes.sh with four modes: - --verify: check all local/recipes have correct symlinks into recipes/ - --fix: repair broken symlinks (run before builds) - --save-all: snapshot all recipe.toml into local/recipes/ - --restore: recreate all symlinks from local/recipes/ (run after sync-upstream) Wired into apply-patches.sh (post-patch) and sync-upstream.sh (post-sync). This prevents the build system from deleting recipe files during cargo cook, make distclean, or upstream source refresh.
This commit is contained in:
@@ -9,4 +9,6 @@ syscall = { package = "redox_syscall", version = "0.7", features = ["std"] }
|
||||
redox_scheme = { package = "redox-scheme", version = "0.11" }
|
||||
libredox = "0.1"
|
||||
log = { version = "0.4", features = ["std"] }
|
||||
sha2 = "0.10"
|
||||
thiserror = "2"
|
||||
toml = "0.8"
|
||||
|
||||
@@ -0,0 +1,328 @@
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::io::ErrorKind;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
use std::thread;
|
||||
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
|
||||
|
||||
pub struct FirmwareRequest {
|
||||
pub name: String,
|
||||
pub callback: Box<dyn FnOnce(Result<Vec<u8>, String>) + Send>,
|
||||
pub timeout_ms: u64,
|
||||
}
|
||||
|
||||
const POLL_INTERVAL_MS: u64 = 100;
|
||||
const DEFAULT_FIRMWARE_DIR: &str = "/lib/firmware";
|
||||
const DEFAULT_UEVENT_DIR: &str = "/run/firmware/uevents";
|
||||
|
||||
pub fn request_firmware_nowait(
|
||||
name: &str,
|
||||
timeout_ms: u64,
|
||||
callback: impl FnOnce(Result<Vec<u8>, String>) + Send + 'static,
|
||||
) {
|
||||
let request = FirmwareRequest {
|
||||
name: name.to_string(),
|
||||
callback: Box::new(callback),
|
||||
timeout_ms,
|
||||
};
|
||||
|
||||
thread::spawn(move || {
|
||||
execute_request(request);
|
||||
});
|
||||
}
|
||||
|
||||
fn execute_request(request: FirmwareRequest) {
|
||||
let start = Instant::now();
|
||||
let timeout = Duration::from_millis(request.timeout_ms);
|
||||
let firmware_path = firmware_path(&request.name);
|
||||
let mut callback = Some(request.callback);
|
||||
let mut dispatched_uevent = false;
|
||||
|
||||
loop {
|
||||
match fs::read(&firmware_path) {
|
||||
Ok(data) => {
|
||||
if let Some(callback) = callback.take() {
|
||||
callback(Ok(data));
|
||||
}
|
||||
return;
|
||||
}
|
||||
Err(err) if err.kind() == ErrorKind::NotFound => {}
|
||||
Err(err) => {
|
||||
if let Some(callback) = callback.take() {
|
||||
callback(Err(format!(
|
||||
"failed to read firmware {} from {}: {}",
|
||||
request.name,
|
||||
firmware_path.display(),
|
||||
err
|
||||
)));
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if !dispatched_uevent {
|
||||
if let Err(err) = dispatch_uevent(&request.name, request.timeout_ms) {
|
||||
log::warn!(
|
||||
"firmware-loader: failed to dispatch uevent for {}: {}",
|
||||
request.name,
|
||||
err
|
||||
);
|
||||
}
|
||||
dispatched_uevent = true;
|
||||
}
|
||||
|
||||
if start.elapsed() >= timeout {
|
||||
if let Some(callback) = callback.take() {
|
||||
callback(Err(format!(
|
||||
"timeout while waiting for firmware {} after {}ms",
|
||||
request.name, request.timeout_ms
|
||||
)));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let remaining = timeout.saturating_sub(start.elapsed());
|
||||
thread::sleep(Duration::from_millis(POLL_INTERVAL_MS).min(remaining));
|
||||
}
|
||||
}
|
||||
|
||||
fn firmware_path(name: &str) -> PathBuf {
|
||||
env::var_os("FIRMWARE_DIR")
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|| PathBuf::from(DEFAULT_FIRMWARE_DIR))
|
||||
.join(name)
|
||||
}
|
||||
|
||||
fn dispatch_uevent(name: &str, timeout_ms: u64) -> Result<(), String> {
|
||||
let content = uevent_content(name, timeout_ms);
|
||||
|
||||
if let Some(helper) = env::var_os("FIRMWARE_UEVENT_HELPER") {
|
||||
dispatch_helper(PathBuf::from(helper), name.to_string(), timeout_ms, content.clone())?;
|
||||
}
|
||||
|
||||
let spool_dir = env::var_os("FIRMWARE_UEVENT_DIR")
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|| PathBuf::from(DEFAULT_UEVENT_DIR));
|
||||
write_uevent_file(&spool_dir, name, &content)
|
||||
}
|
||||
|
||||
fn dispatch_helper(
|
||||
helper: PathBuf,
|
||||
name: String,
|
||||
timeout_ms: u64,
|
||||
content: String,
|
||||
) -> Result<(), String> {
|
||||
thread::spawn(move || {
|
||||
let result = Command::new(&helper)
|
||||
.env("ACTION", "add")
|
||||
.env("SUBSYSTEM", "firmware")
|
||||
.env("FIRMWARE", &name)
|
||||
.env("TIMEOUT_MS", timeout_ms.to_string())
|
||||
.env("DEVPATH", format!("/devices/virtual/firmware/{}", sanitize_name(&name)))
|
||||
.env("UEVENT_CONTENT", &content)
|
||||
.status();
|
||||
|
||||
match result {
|
||||
Ok(status) if !status.success() => log::warn!(
|
||||
"firmware-loader: uevent helper {} exited with status {} for {}",
|
||||
helper.display(),
|
||||
status,
|
||||
name
|
||||
),
|
||||
Ok(_) => {}
|
||||
Err(err) => log::warn!(
|
||||
"firmware-loader: failed to execute uevent helper {} for {}: {}",
|
||||
helper.display(),
|
||||
name,
|
||||
err
|
||||
),
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_uevent_file(spool_dir: &Path, name: &str, content: &str) -> Result<(), String> {
|
||||
fs::create_dir_all(spool_dir).map_err(|err| {
|
||||
format!(
|
||||
"failed to create uevent spool directory {}: {}",
|
||||
spool_dir.display(),
|
||||
err
|
||||
)
|
||||
})?;
|
||||
|
||||
let timestamp = match SystemTime::now().duration_since(UNIX_EPOCH) {
|
||||
Ok(duration) => duration.as_nanos(),
|
||||
Err(_) => 0,
|
||||
};
|
||||
let file_name = format!("{}-{timestamp}.uevent", sanitize_name(name));
|
||||
let path = spool_dir.join(file_name);
|
||||
|
||||
fs::write(&path, content).map_err(|err| {
|
||||
format!(
|
||||
"failed to write uevent file {} for firmware {}: {}",
|
||||
path.display(),
|
||||
name,
|
||||
err
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn sanitize_name(name: &str) -> String {
|
||||
name.chars()
|
||||
.map(|ch| if ch.is_ascii_alphanumeric() { ch } else { '_' })
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn uevent_content(name: &str, timeout_ms: u64) -> String {
|
||||
format!(
|
||||
"ACTION=add\nSUBSYSTEM=firmware\nDEVPATH=/devices/virtual/firmware/{}\nFIRMWARE={}\nTIMEOUT_MS={}\n",
|
||||
sanitize_name(name),
|
||||
name,
|
||||
timeout_ms
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::request_firmware_nowait;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::mpsc;
|
||||
use std::sync::{LazyLock, Mutex};
|
||||
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
||||
|
||||
static TEST_ENV_LOCK: LazyLock<Mutex<()>> = LazyLock::new(|| Mutex::new(()));
|
||||
|
||||
fn temp_root(prefix: &str) -> PathBuf {
|
||||
let stamp = match SystemTime::now().duration_since(UNIX_EPOCH) {
|
||||
Ok(duration) => duration.as_nanos(),
|
||||
Err(err) => panic!("system clock error while creating temp path: {err}"),
|
||||
};
|
||||
let path = std::env::temp_dir().join(format!("{prefix}-{stamp}"));
|
||||
if let Err(err) = fs::create_dir_all(&path) {
|
||||
panic!("failed to create temp directory {}: {err}", path.display());
|
||||
}
|
||||
path
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn request_firmware_nowait_returns_existing_blob() {
|
||||
let _guard = match TEST_ENV_LOCK.lock() {
|
||||
Ok(guard) => guard,
|
||||
Err(err) => panic!("failed to acquire test env lock: {err}"),
|
||||
};
|
||||
let root = temp_root("rbos-fw-async-ok");
|
||||
let uevent_dir = temp_root("rbos-fw-async-uevents");
|
||||
|
||||
if let Err(err) = fs::write(root.join("iwlwifi-test.ucode"), [9u8, 8, 7]) {
|
||||
panic!("failed to write async firmware blob: {err}");
|
||||
}
|
||||
|
||||
unsafe {
|
||||
std::env::set_var("FIRMWARE_DIR", &root);
|
||||
std::env::set_var("FIRMWARE_UEVENT_DIR", &uevent_dir);
|
||||
}
|
||||
|
||||
let (tx, rx) = mpsc::channel();
|
||||
request_firmware_nowait("iwlwifi-test.ucode", 500, move |result| {
|
||||
let _ = tx.send(result);
|
||||
});
|
||||
|
||||
let result = match rx.recv_timeout(Duration::from_secs(1)) {
|
||||
Ok(result) => result,
|
||||
Err(err) => panic!("async callback was not received in time: {err}"),
|
||||
};
|
||||
match result {
|
||||
Ok(bytes) => assert_eq!(bytes, vec![9u8, 8, 7]),
|
||||
Err(err) => panic!("unexpected async firmware error: {err}"),
|
||||
}
|
||||
|
||||
unsafe {
|
||||
std::env::remove_var("FIRMWARE_DIR");
|
||||
std::env::remove_var("FIRMWARE_UEVENT_DIR");
|
||||
}
|
||||
if let Err(err) = fs::remove_dir_all(&root) {
|
||||
panic!("failed to remove temp directory {}: {err}", root.display());
|
||||
}
|
||||
if let Err(err) = fs::remove_dir_all(&uevent_dir) {
|
||||
panic!("failed to remove temp directory {}: {err}", uevent_dir.display());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn request_firmware_nowait_dispatches_uevent_and_retries() {
|
||||
let _guard = match TEST_ENV_LOCK.lock() {
|
||||
Ok(guard) => guard,
|
||||
Err(err) => panic!("failed to acquire test env lock: {err}"),
|
||||
};
|
||||
let root = temp_root("rbos-fw-async-retry");
|
||||
let uevent_dir = temp_root("rbos-fw-async-spool");
|
||||
let firmware_name = "intel/ibt-test.sfi";
|
||||
let firmware_path = root.join(firmware_name);
|
||||
let parent = match firmware_path.parent() {
|
||||
Some(parent) => parent.to_path_buf(),
|
||||
None => panic!("firmware test path unexpectedly had no parent"),
|
||||
};
|
||||
|
||||
unsafe {
|
||||
std::env::set_var("FIRMWARE_DIR", &root);
|
||||
std::env::set_var("FIRMWARE_UEVENT_DIR", &uevent_dir);
|
||||
}
|
||||
|
||||
let writer_path = firmware_path.clone();
|
||||
let writer_dir = uevent_dir.clone();
|
||||
let writer = std::thread::spawn(move || {
|
||||
for _ in 0..50 {
|
||||
let has_uevent = match fs::read_dir(&writer_dir) {
|
||||
Ok(entries) => entries
|
||||
.filter_map(Result::ok)
|
||||
.any(|entry| entry.path().extension().and_then(|ext| ext.to_str()) == Some("uevent")),
|
||||
Err(_) => false,
|
||||
};
|
||||
if has_uevent {
|
||||
if let Err(err) = fs::create_dir_all(&parent) {
|
||||
panic!("failed to create parent firmware directory: {err}");
|
||||
}
|
||||
if let Err(err) = fs::write(&writer_path, [1u8, 2, 3, 4]) {
|
||||
panic!("failed to write firmware after uevent dispatch: {err}");
|
||||
}
|
||||
return;
|
||||
}
|
||||
std::thread::sleep(Duration::from_millis(10));
|
||||
}
|
||||
panic!("uevent dispatch file was not observed in time");
|
||||
});
|
||||
|
||||
let (tx, rx) = mpsc::channel();
|
||||
request_firmware_nowait(firmware_name, 1000, move |result| {
|
||||
let _ = tx.send(result);
|
||||
});
|
||||
|
||||
let result = match rx.recv_timeout(Duration::from_secs(2)) {
|
||||
Ok(result) => result,
|
||||
Err(err) => panic!("async retry callback was not received in time: {err}"),
|
||||
};
|
||||
match result {
|
||||
Ok(bytes) => assert_eq!(bytes, vec![1u8, 2, 3, 4]),
|
||||
Err(err) => panic!("unexpected async retry error: {err}"),
|
||||
}
|
||||
|
||||
match writer.join() {
|
||||
Ok(()) => {}
|
||||
Err(_) => panic!("uevent writer thread panicked"),
|
||||
}
|
||||
|
||||
unsafe {
|
||||
std::env::remove_var("FIRMWARE_DIR");
|
||||
std::env::remove_var("FIRMWARE_UEVENT_DIR");
|
||||
}
|
||||
if let Err(err) = fs::remove_dir_all(&root) {
|
||||
panic!("failed to remove temp directory {}: {err}", root.display());
|
||||
}
|
||||
if let Err(err) = fs::remove_dir_all(&uevent_dir) {
|
||||
panic!("failed to remove temp directory {}: {err}", uevent_dir.display());
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,6 @@
|
||||
mod r#async;
|
||||
mod blob;
|
||||
mod manifest;
|
||||
mod scheme;
|
||||
|
||||
use std::env;
|
||||
@@ -6,8 +8,10 @@ use std::env;
|
||||
use std::os::fd::RawFd;
|
||||
use std::path::PathBuf;
|
||||
use std::process;
|
||||
use std::sync::mpsc;
|
||||
use std::time::Duration;
|
||||
|
||||
use log::{error, info, LevelFilter, Metadata, Record};
|
||||
use log::{error, info, warn, LevelFilter, Metadata, Record};
|
||||
#[cfg(target_os = "redox")]
|
||||
use redox_scheme::{scheme::SchemeSync, SignalBehavior, Socket};
|
||||
|
||||
@@ -113,6 +117,85 @@ fn main() {
|
||||
|
||||
init_logging(log_level);
|
||||
|
||||
let args: Vec<String> = env::args().skip(1).collect();
|
||||
|
||||
if args.first().map(String::as_str) == Some("--generate-manifest") {
|
||||
let Some(path) = args.get(1) else {
|
||||
error!("firmware-loader: --generate-manifest requires a directory path");
|
||||
process::exit(2);
|
||||
};
|
||||
|
||||
if args.len() != 2 {
|
||||
error!("firmware-loader: --generate-manifest accepts exactly one directory path");
|
||||
process::exit(2);
|
||||
}
|
||||
|
||||
match manifest::generate_manifest(path) {
|
||||
Ok(()) => {
|
||||
println!("generated {}/MANIFEST.txt", path.trim_end_matches('/'));
|
||||
return;
|
||||
}
|
||||
Err(err) => {
|
||||
error!(
|
||||
"firmware-loader: failed to generate manifest for {}: {}",
|
||||
path, err
|
||||
);
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if args.first().map(String::as_str) == Some("--request-nowait") {
|
||||
let Some(name) = args.get(1) else {
|
||||
error!("firmware-loader: --request-nowait requires a firmware name");
|
||||
process::exit(2);
|
||||
};
|
||||
|
||||
if args.len() > 3 {
|
||||
error!(
|
||||
"firmware-loader: --request-nowait accepts a firmware name and optional timeout_ms"
|
||||
);
|
||||
process::exit(2);
|
||||
}
|
||||
|
||||
let timeout_ms = match args.get(2) {
|
||||
Some(value) => match value.parse::<u64>() {
|
||||
Ok(timeout_ms) => timeout_ms,
|
||||
Err(err) => {
|
||||
error!(
|
||||
"firmware-loader: invalid timeout for --request-nowait ({}): {}",
|
||||
value, err
|
||||
);
|
||||
process::exit(2);
|
||||
}
|
||||
},
|
||||
None => 5000,
|
||||
};
|
||||
|
||||
let (tx, rx) = mpsc::channel();
|
||||
r#async::request_firmware_nowait(name, timeout_ms, move |result| {
|
||||
let _ = tx.send(result);
|
||||
});
|
||||
|
||||
match rx.recv_timeout(Duration::from_millis(timeout_ms.saturating_add(1000))) {
|
||||
Ok(Ok(bytes)) => {
|
||||
println!("loaded={} bytes={}", name, bytes.len());
|
||||
return;
|
||||
}
|
||||
Ok(Err(err)) => {
|
||||
error!("firmware-loader: async firmware request failed for {}: {}", name, err);
|
||||
process::exit(1);
|
||||
}
|
||||
Err(err) => {
|
||||
error!(
|
||||
"firmware-loader: async firmware request channel failed for {}: {}",
|
||||
name, err
|
||||
);
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let firmware_dir = env::var("FIRMWARE_DIR")
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|_| default_firmware_dir());
|
||||
@@ -122,6 +205,19 @@ fn main() {
|
||||
firmware_dir.display()
|
||||
);
|
||||
|
||||
let firmware_dir_str = firmware_dir.to_string_lossy().into_owned();
|
||||
match manifest::generate_manifest(&firmware_dir_str) {
|
||||
Ok(()) => info!(
|
||||
"firmware-loader: generated firmware manifest at {}/MANIFEST.txt",
|
||||
firmware_dir.display()
|
||||
),
|
||||
Err(err) => warn!(
|
||||
"firmware-loader: failed to generate firmware manifest for {}: {}",
|
||||
firmware_dir.display(),
|
||||
err
|
||||
),
|
||||
}
|
||||
|
||||
let registry = match FirmwareRegistry::new(&firmware_dir) {
|
||||
Ok(registry) => registry,
|
||||
Err(blob::BlobError::DirNotFound(_)) => {
|
||||
@@ -143,7 +239,7 @@ fn main() {
|
||||
firmware_dir.display()
|
||||
);
|
||||
|
||||
if env::args().nth(1).as_deref() == Some("--probe") {
|
||||
if args.first().map(String::as_str) == Some("--probe") {
|
||||
println!("count={}", registry.len());
|
||||
let mut keys = registry.list_keys();
|
||||
keys.sort_unstable();
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
use std::fs;
|
||||
use std::io::{Error, ErrorKind};
|
||||
use std::path::Path;
|
||||
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
use crate::blob::{discover_firmware, BlobError};
|
||||
|
||||
pub fn generate_manifest(firmware_dir: &str) -> Result<(), std::io::Error> {
|
||||
let base_dir = Path::new(firmware_dir);
|
||||
let blobs = discover_firmware(base_dir).map_err(blob_error_to_io)?;
|
||||
|
||||
let mut keys: Vec<String> = blobs.keys().cloned().collect();
|
||||
keys.sort_unstable();
|
||||
|
||||
let mut manifest = String::new();
|
||||
for key in keys {
|
||||
let path = base_dir.join(&key);
|
||||
let bytes = fs::read(&path)?;
|
||||
let digest = Sha256::digest(&bytes);
|
||||
manifest.push_str(&encode_hex(&digest));
|
||||
manifest.push_str(" ");
|
||||
manifest.push_str(&bytes.len().to_string());
|
||||
manifest.push_str(" ");
|
||||
manifest.push_str(&key);
|
||||
manifest.push('\n');
|
||||
}
|
||||
|
||||
fs::write(base_dir.join("MANIFEST.txt"), manifest)
|
||||
}
|
||||
|
||||
fn blob_error_to_io(err: BlobError) -> std::io::Error {
|
||||
match err {
|
||||
BlobError::DirNotFound(path) => Error::new(
|
||||
ErrorKind::NotFound,
|
||||
format!("firmware directory not found: {}", path.display()),
|
||||
),
|
||||
BlobError::DirReadError(_, source) | BlobError::ReadError { source, .. } => source,
|
||||
BlobError::FirmwareNotFound(path) => Error::new(
|
||||
ErrorKind::NotFound,
|
||||
format!("firmware not found: {}", path.display()),
|
||||
),
|
||||
BlobError::LoadTimeout { key, timeout } => Error::new(
|
||||
ErrorKind::TimedOut,
|
||||
format!("firmware load timed out for {key} after {timeout:?}"),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn encode_hex(bytes: &[u8]) -> String {
|
||||
let mut hex = String::with_capacity(bytes.len() * 2);
|
||||
for byte in bytes {
|
||||
use std::fmt::Write as _;
|
||||
let _ = write!(&mut hex, "{byte:02x}");
|
||||
}
|
||||
hex
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::generate_manifest;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
fn temp_root(prefix: &str) -> PathBuf {
|
||||
let stamp = match SystemTime::now().duration_since(UNIX_EPOCH) {
|
||||
Ok(duration) => duration.as_nanos(),
|
||||
Err(err) => panic!("system clock error while creating temp path: {err}"),
|
||||
};
|
||||
let path = std::env::temp_dir().join(format!("{prefix}-{stamp}"));
|
||||
if let Err(err) = fs::create_dir_all(&path) {
|
||||
panic!("failed to create temp directory {}: {err}", path.display());
|
||||
}
|
||||
path
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generates_sha256_size_manifest_for_firmware_blobs_only() {
|
||||
let root = temp_root("rbos-fw-manifest");
|
||||
let intel_dir = root.join("intel");
|
||||
if let Err(err) = fs::create_dir_all(&intel_dir) {
|
||||
panic!("failed to create nested firmware directory: {err}");
|
||||
}
|
||||
if let Err(err) = fs::write(root.join("iwlwifi-test.ucode"), [1u8, 2, 3]) {
|
||||
panic!("failed to write ucode blob: {err}");
|
||||
}
|
||||
if let Err(err) = fs::write(intel_dir.join("ibt-test.sfi"), [4u8, 5, 6, 7]) {
|
||||
panic!("failed to write bluetooth blob: {err}");
|
||||
}
|
||||
if let Err(err) = fs::write(root.join("README"), "metadata") {
|
||||
panic!("failed to write metadata file: {err}");
|
||||
}
|
||||
|
||||
let root_str = root.to_string_lossy().into_owned();
|
||||
if let Err(err) = generate_manifest(&root_str) {
|
||||
panic!("failed to generate manifest: {err}");
|
||||
}
|
||||
|
||||
let manifest_path = root.join("MANIFEST.txt");
|
||||
let manifest = match fs::read_to_string(&manifest_path) {
|
||||
Ok(manifest) => manifest,
|
||||
Err(err) => panic!("failed to read generated manifest: {err}"),
|
||||
};
|
||||
|
||||
assert!(manifest.contains(" 3 iwlwifi-test.ucode\n"));
|
||||
assert!(manifest.contains(" 4 intel/ibt-test.sfi\n"));
|
||||
assert!(!manifest.contains("README"));
|
||||
|
||||
if let Err(err) = fs::remove_dir_all(&root) {
|
||||
panic!("failed to remove temp directory {}: {err}", root.display());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::sync::Arc;
|
||||
use std::time::Instant;
|
||||
|
||||
use log::warn;
|
||||
use redox_scheme::scheme::SchemeSync;
|
||||
@@ -12,6 +13,7 @@ use crate::blob::FirmwareRegistry;
|
||||
|
||||
#[cfg_attr(not(target_os = "redox"), allow(dead_code))]
|
||||
const SCHEME_ROOT_ID: usize = 1;
|
||||
const FIRMWARE_LOAD_TIMEOUT_MS: u64 = 5000;
|
||||
|
||||
#[cfg_attr(not(target_os = "redox"), allow(dead_code))]
|
||||
struct Handle {
|
||||
@@ -94,15 +96,22 @@ impl SchemeSync for FirmwareScheme {
|
||||
|
||||
let key = resolve_key(path).ok_or(Error::new(EISDIR))?;
|
||||
|
||||
if !self.registry.contains(&key) {
|
||||
warn!("firmware-loader: firmware not found: {}", path);
|
||||
return Err(Error::new(ENOENT));
|
||||
}
|
||||
|
||||
let data = self.registry.load(&key).map_err(|e| {
|
||||
warn!("firmware-loader: failed to load firmware '{}': {}", key, e);
|
||||
Error::new(ENOENT)
|
||||
})?;
|
||||
let started_at = Instant::now();
|
||||
let data = self
|
||||
.registry
|
||||
.load_with_timeout(
|
||||
&key,
|
||||
started_at,
|
||||
std::time::Duration::from_millis(FIRMWARE_LOAD_TIMEOUT_MS),
|
||||
)
|
||||
.map_err(|e| {
|
||||
warn!("firmware-loader: failed to load firmware '{}': {}", key, e);
|
||||
match e {
|
||||
crate::blob::BlobError::LoadTimeout { .. } => Error::new(ETIMEDOUT),
|
||||
crate::blob::BlobError::ReadError { .. } => Error::new(EIO),
|
||||
_ => Error::new(ENOENT),
|
||||
}
|
||||
})?;
|
||||
|
||||
let id = self.next_id;
|
||||
self.next_id += 1;
|
||||
@@ -172,7 +181,7 @@ impl SchemeSync for FirmwareScheme {
|
||||
stat.st_mode = MODE_FILE | 0o444;
|
||||
stat.st_size = handle.data.len() as u64;
|
||||
stat.st_blksize = 4096;
|
||||
stat.st_blocks = (handle.data.len() as u64 + 511) / 512;
|
||||
stat.st_blocks = (handle.data.len() as u64).div_ceil(512);
|
||||
stat.st_nlink = 1;
|
||||
|
||||
Ok(())
|
||||
@@ -386,9 +395,7 @@ mod tests {
|
||||
let mut scheme = FirmwareScheme::new(registry);
|
||||
let ctx = test_ctx();
|
||||
|
||||
let err = scheme
|
||||
.openat(SCHEME_ROOT_ID, "", 0, 0, &ctx)
|
||||
.unwrap_err();
|
||||
let err = scheme.openat(SCHEME_ROOT_ID, "", 0, 0, &ctx).unwrap_err();
|
||||
assert_eq!(err.errno, EISDIR);
|
||||
let _ = fs::remove_dir_all(&dir);
|
||||
}
|
||||
@@ -399,9 +406,7 @@ mod tests {
|
||||
let mut scheme = FirmwareScheme::new(registry);
|
||||
let ctx = test_ctx();
|
||||
|
||||
let err = scheme
|
||||
.openat(999, "test-blob.bin", 0, 0, &ctx)
|
||||
.unwrap_err();
|
||||
let err = scheme.openat(999, "test-blob.bin", 0, 0, &ctx).unwrap_err();
|
||||
assert_eq!(err.errno, EACCES);
|
||||
let _ = fs::remove_dir_all(&dir);
|
||||
}
|
||||
@@ -641,9 +646,7 @@ mod tests {
|
||||
let id = open_test_blob(&mut scheme);
|
||||
let ctx = test_ctx();
|
||||
|
||||
let flags = scheme
|
||||
.fevent(id, EventFlags::empty(), &ctx)
|
||||
.unwrap();
|
||||
let flags = scheme.fevent(id, EventFlags::empty(), &ctx).unwrap();
|
||||
assert_eq!(flags, EventFlags::empty());
|
||||
let _ = fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user