2026-10-03 17:19:27 -07:00
|
|
|
use alloc::{format, string::String};
|
2026-10-03 19:09:17 -07:00
|
|
|
use anyhow::{Context, Result, anyhow};
|
2026-10-03 17:19:27 -07:00
|
|
|
use edera_sprout_bls::BootCounter;
|
|
|
|
|
use uefi::{
|
|
|
|
|
CString16, Handle,
|
2026-10-03 19:09:17 -07:00
|
|
|
data_types::Align,
|
|
|
|
|
fs::PathBuf,
|
|
|
|
|
mem::AlignedBuffer,
|
|
|
|
|
proto::media::file::{File, FileAttribute, FileInfo, FileMode},
|
2026-10-03 17:19:27 -07:00
|
|
|
proto::media::fs::SimpleFileSystem,
|
|
|
|
|
};
|
|
|
|
|
|
2026-10-03 19:09:17 -07:00
|
|
|
/// The size of the fixed part of a file info: three sizes, three timestamps and the attributes.
|
|
|
|
|
const FILE_INFO_HEADER_SIZE: usize = 3 * 8 + 3 * 16 + 8;
|
|
|
|
|
|
2026-10-03 17:19:27 -07:00
|
|
|
/// Where the boot counter of an entry is stored, so that a try can be consumed when the
|
|
|
|
|
/// entry is booted.
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
|
pub struct BootCounterTarget {
|
|
|
|
|
/// The counter as it is currently encoded in the file name.
|
|
|
|
|
pub counter: BootCounter,
|
|
|
|
|
/// The handle of the filesystem that holds the entry file.
|
|
|
|
|
pub filesystem: Handle,
|
|
|
|
|
/// The directory that holds the entry file.
|
|
|
|
|
pub directory: PathBuf,
|
|
|
|
|
/// The entry id, which is the file name without the counter and extension.
|
|
|
|
|
pub id: String,
|
|
|
|
|
/// The current file name, including the counter and the extension.
|
|
|
|
|
pub file_name: String,
|
2026-10-03 19:09:17 -07:00
|
|
|
/// The extension of the file name, such as `.conf`, in its original case.
|
|
|
|
|
pub extension: String,
|
2026-10-03 17:19:27 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl BootCounterTarget {
|
|
|
|
|
/// Consumes one try by renaming the entry file, such as `foo+3.conf` to `foo+2-1.conf`.
|
2026-10-03 19:09:17 -07:00
|
|
|
/// The file is renamed in place without copying it, as unified kernel images are large and
|
|
|
|
|
/// a copy interrupted by a crash would leave a truncated image behind.
|
2026-10-03 17:19:27 -07:00
|
|
|
/// Returns the path of the renamed file.
|
|
|
|
|
pub fn consume(&self) -> Result<PathBuf> {
|
|
|
|
|
let new_name = format!(
|
|
|
|
|
"{}{}",
|
|
|
|
|
self.counter.decremented().render(&self.id),
|
2026-10-03 19:09:17 -07:00
|
|
|
self.extension
|
2026-10-03 17:19:27 -07:00
|
|
|
);
|
2026-10-03 19:09:17 -07:00
|
|
|
let new_name_16 =
|
|
|
|
|
CString16::try_from(new_name.as_str()).context("invalid new entry file name")?;
|
2026-10-03 17:19:27 -07:00
|
|
|
|
2026-10-03 19:09:17 -07:00
|
|
|
let mut fs = uefi::boot::open_protocol_exclusive::<SimpleFileSystem>(self.filesystem)
|
|
|
|
|
.context("unable to open the entry filesystem")?;
|
|
|
|
|
let mut root = fs
|
|
|
|
|
.open_volume()
|
|
|
|
|
.context("unable to open the entry volume")?;
|
|
|
|
|
|
|
|
|
|
// Open the entry file for writing.
|
2026-10-03 17:19:27 -07:00
|
|
|
let mut from = self.directory.clone();
|
|
|
|
|
from.push(PathBuf::from(
|
|
|
|
|
CString16::try_from(self.file_name.as_str()).context("invalid entry file name")?,
|
|
|
|
|
));
|
2026-10-03 19:09:17 -07:00
|
|
|
let from_name =
|
|
|
|
|
CString16::try_from(format!("{}", from).as_str()).context("invalid entry file path")?;
|
|
|
|
|
let handle = root
|
|
|
|
|
.open(&from_name, FileMode::ReadWrite, FileAttribute::empty())
|
|
|
|
|
.context("unable to open the entry file")?;
|
|
|
|
|
let mut file = handle
|
|
|
|
|
.into_regular_file()
|
|
|
|
|
.ok_or_else(|| anyhow!("the entry is not a regular file"))?;
|
2026-10-03 17:19:27 -07:00
|
|
|
|
2026-10-03 19:09:17 -07:00
|
|
|
// Renaming a file is done by setting its file info with a different name.
|
|
|
|
|
let info = file
|
|
|
|
|
.get_boxed_info::<FileInfo>()
|
|
|
|
|
.context("unable to get the entry file info")?;
|
|
|
|
|
// The info is a fixed header followed by the null-terminated UCS-2 name.
|
|
|
|
|
let size =
|
|
|
|
|
FILE_INFO_HEADER_SIZE + 2 * (new_name.len() + 1) + <FileInfo as Align>::alignment();
|
|
|
|
|
let mut storage = AlignedBuffer::from_size_align(size, <FileInfo as Align>::alignment())
|
|
|
|
|
.context("unable to allocate the entry file info")?;
|
|
|
|
|
let renamed = FileInfo::new(
|
|
|
|
|
storage.as_slice_mut(),
|
|
|
|
|
info.file_size(),
|
|
|
|
|
info.physical_size(),
|
|
|
|
|
*info.create_time(),
|
|
|
|
|
*info.last_access_time(),
|
|
|
|
|
*info.modification_time(),
|
|
|
|
|
info.attribute(),
|
|
|
|
|
&new_name_16,
|
|
|
|
|
)
|
|
|
|
|
.map_err(|error| anyhow!("unable to build the renamed entry file info: {:?}", error))?;
|
|
|
|
|
file.set_info(renamed)
|
2026-10-03 17:19:27 -07:00
|
|
|
.context("unable to rename the entry file")?;
|
2026-10-03 19:09:17 -07:00
|
|
|
file.flush()
|
|
|
|
|
.context("unable to flush the renamed entry file")?;
|
|
|
|
|
|
|
|
|
|
let mut to = self.directory.clone();
|
|
|
|
|
to.push(PathBuf::from(new_name_16));
|
2026-10-03 17:19:27 -07:00
|
|
|
Ok(to)
|
|
|
|
|
}
|
|
|
|
|
}
|