104 lines
3.3 KiB
Rust
104 lines
3.3 KiB
Rust
//! 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(())
|
|
}
|
|
}
|