update fds-flash tool

This commit is contained in:
2026-09-24 14:51:56 +08:00
parent 685b4135df
commit a14ea77145
18 changed files with 1761 additions and 10 deletions
+103
View File
@@ -0,0 +1,103 @@
//! Guided and unattended installation of complete disk images on Linux.
pub mod cli;
pub mod device;
mod image;
use fds_common::{Error, Result};
use serde::Serialize;
use std::{
fs::File,
os::{fd::AsRawFd, unix::fs::MetadataExt},
path::{Path, PathBuf},
};
#[derive(Serialize)]
pub struct Plan {
pub image_path: PathBuf,
pub image: image::Geometry,
pub sha256: String,
pub target: device::Target,
pub target_id: String,
pub confirmation: String,
}
pub struct Prepared {
pub plan: Plan,
source: File,
}
pub fn prepare(
image_path: &Path,
device_path: &Path,
file_target: bool,
expected_sha256: Option<&str>,
) -> Result<Prepared> {
let image_path = image_path.canonicalize()?;
let source = crate::cartridge::open_image(&image_path)?;
let geometry = image::inspect(&source, source.metadata()?.len())?;
let sha256 = fds_burn::image::digest(&source, geometry.bytes, |_| Ok(()))?;
if expected_sha256.is_some_and(|expected| expected != sha256) {
return Err(Error(
"Image SHA-256 does not match --sha256; target untouched".into(),
));
}
if image::inspect(&source, source.metadata()?.len())? != geometry {
return Err(Error("Image geometry changed during inspection".into()));
}
let target = device::inspect(device_path, file_target)?;
let metadata = std::fs::metadata(target.path())?;
if source.metadata()?.dev() == metadata.dev() && source.metadata()?.ino() == metadata.ino() {
return Err(Error("Image and target refer to the same file".into()));
}
target.protect()?;
if target.bytes() < geometry.bytes || target.bytes() % 512 != 0 {
return Err(Error(
"Destination is smaller than the image or is not sector aligned".into(),
));
}
let target_id = target.id()?;
let confirmation = format!("ERASE {}", target.path().display());
Ok(Prepared {
plan: Plan {
image_path,
image: geometry,
sha256,
target,
target_id,
confirmation,
},
source,
})
}
impl Prepared {
pub fn write(&self) -> Result<()> {
let target = self.plan.target.open()?;
let source_meta = self.source.metadata()?;
let target_meta = target.metadata()?;
if source_meta.dev() == target_meta.dev() && source_meta.ino() == target_meta.ino() {
return Err(Error("Image and destination are the same open file".into()));
}
image::transfer(
&self.source,
&target,
&self.plan.image,
&self.plan.sha256,
self.plan.target.bytes(),
|phase, bytes| {
self.plan.target.present()?;
eprintln!("{phase}: {bytes}/{} bytes", self.plan.image.bytes);
Ok(())
},
)?;
self.plan.target.present()?;
if !self.plan.target.is_file()
&& unsafe { libc::ioctl(target.as_raw_fd(), 0x125f as libc::Ioctl) } < 0
{
return Err(Error(format!(
"Image verified, but the kernel could not reload the partition table: {}. Do not use the disk until it is reconnected and inspected",
std::io::Error::last_os_error()
)));
}
Ok(())
}
}