std: sys: fs: uefi: Implement rename

- Using the file_name field in `EFI_FILE_INFO` works for renaming, even
  when changing directories.
- Does not work for cross-device rename, but that is already expected
  behaviour according to the docs:

  "This will not work if the new name is on a different mount point."

- Also add some helper code for dealing with UefiBox<file::Info>.
- Tested using OVMF in qemu.

Signed-off-by: Ayush Singh <ayush@beagleboard.org>
This commit is contained in:
Ayush Singh
2025-12-31 13:17:55 +05:30
parent 15f7c553a3
commit b725981fe2
2 changed files with 96 additions and 11 deletions
+39 -9
View File
@@ -385,8 +385,43 @@ pub fn unlink(p: &Path) -> io::Result<()> {
}
}
pub fn rename(_old: &Path, _new: &Path) -> io::Result<()> {
unsupported()
/// The implementation mirrors `mv` implementation in UEFI shell:
/// https://github.com/tianocore/edk2/blob/66346d5edeac2a00d3cf2f2f3b5f66d423c07b3e/ShellPkg/Library/UefiShellLevel2CommandsLib/Mv.c#L455
///
/// In a nutshell we do the following:
/// 1. Convert both old and new paths to absolute paths.
/// 2. Check that both lie in the same disk.
/// 3. Construct the target path relative to the current disk root.
/// 4. Set this target path as the file_name in the file_info structure.
pub fn rename(old: &Path, new: &Path) -> io::Result<()> {
let old_absolute = crate::path::absolute(old)?;
let new_absolute = crate::path::absolute(new)?;
let mut old_components = old_absolute.components();
let mut new_components = new_absolute.components();
let Some(old_disk) = old_components.next() else {
return Err(io::const_error!(io::ErrorKind::InvalidInput, "Old path is not valid"));
};
let Some(new_disk) = new_components.next() else {
return Err(io::const_error!(io::ErrorKind::InvalidInput, "New path is not valid"));
};
// Ensure that paths are on the same device.
if old_disk != new_disk {
return Err(io::const_error!(io::ErrorKind::CrossesDevices, "Cannot rename across device"));
}
// Construct an path relative the current disk root.
let new_relative =
[crate::path::Component::RootDir].into_iter().chain(new_components).collect::<PathBuf>();
let f = uefi_fs::File::from_path(old, file::MODE_READ | file::MODE_WRITE, 0)?;
let file_info = f.file_info()?;
let new_info = file_info.with_file_name(new_relative.as_os_str())?;
f.set_file_info(new_info)
}
pub fn set_perm(p: &Path, perm: FilePermissions) -> io::Result<()> {
@@ -751,12 +786,7 @@ pub(crate) fn systemtime_to_uefi(time: SystemTime) -> r_efi::efi::Time {
}
pub(crate) fn file_name_from_uefi(info: &UefiBox<file::Info>) -> OsString {
let file_name = {
let size = unsafe { (*info.as_ptr()).size };
let strlen = (size as usize - crate::mem::size_of::<file::Info<0>>() - 1) / 2;
unsafe { crate::slice::from_raw_parts((*info.as_ptr()).file_name.as_ptr(), strlen) }
};
OsString::from_wide(file_name)
let fname = info.file_name();
OsString::from_wide(&fname[..fname.len() - 1])
}
}
+57 -2
View File
@@ -10,7 +10,7 @@
//! - More information about protocols can be found [here](https://edk2-docs.gitbook.io/edk-ii-uefi-driver-writer-s-guide/3_foundation/36_protocols_and_handles)
use r_efi::efi::{self, Guid};
use r_efi::protocols::{device_path, device_path_to_text, service_binding, shell};
use r_efi::protocols::{device_path, device_path_to_text, file, service_binding, shell};
use crate::alloc::Layout;
use crate::ffi::{OsStr, OsString};
@@ -787,7 +787,7 @@ pub(crate) fn new(len: usize) -> io::Result<Self> {
match NonNull::new(ptr.cast()) {
Some(inner) => Ok(Self { inner, size: len }),
None => Err(io::Error::new(io::ErrorKind::OutOfMemory, "Allocation failed")),
None => Err(const_error!(io::ErrorKind::OutOfMemory, "Allocation failed")),
}
}
@@ -814,3 +814,58 @@ fn drop(&mut self) {
unsafe { crate::alloc::dealloc(self.inner.as_ptr().cast(), layout) };
}
}
impl UefiBox<file::Info> {
fn size(&self) -> u64 {
unsafe { (*self.as_ptr()).size }
}
fn set_size(&mut self, s: u64) {
unsafe { (*self.as_mut_ptr()).size = s }
}
// Length of string (including NULL), not number of bytes.
fn file_name_len(&self) -> usize {
(self.size() as usize - size_of::<file::Info<0>>()) / size_of::<u16>()
}
pub(crate) fn file_name(&self) -> &[u16] {
unsafe {
crate::slice::from_raw_parts((*self.as_ptr()).file_name.as_ptr(), self.file_name_len())
}
}
fn file_name_mut(&mut self) -> &mut [u16] {
unsafe {
crate::slice::from_raw_parts_mut(
(*self.as_mut_ptr()).file_name.as_mut_ptr(),
self.file_name_len(),
)
}
}
pub(crate) fn with_file_name(mut self, name: &OsStr) -> io::Result<Self> {
// os_string_to_raw returns NULL terminated string. So no need to handle it separately.
let fname = os_string_to_raw(name)
.ok_or(const_error!(io::ErrorKind::OutOfMemory, "Allocation failed"))?;
let new_size = size_of::<file::Info<0>>() + fname.len() * size_of::<u16>();
// Reuse the current structure if the new name can fit in it.
if self.size() >= new_size as u64 {
self.file_name_mut()[..fname.len()].copy_from_slice(&fname);
self.set_size(new_size as u64);
return Ok(self);
}
let mut new_box = UefiBox::new(new_size)?;
unsafe {
crate::ptr::copy_nonoverlapping(self.as_ptr(), new_box.as_mut_ptr(), 1);
}
new_box.set_size(new_size as u64);
new_box.file_name_mut().copy_from_slice(&fname);
Ok(new_box)
}
}