Skip to content

Commit b725981

Browse files
committed
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 <[email protected]>
1 parent 15f7c55 commit b725981

File tree

2 files changed

+96
-11
lines changed

2 files changed

+96
-11
lines changed

library/std/src/sys/fs/uefi.rs

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -385,8 +385,43 @@ pub fn unlink(p: &Path) -> io::Result<()> {
385385
}
386386
}
387387

388-
pub fn rename(_old: &Path, _new: &Path) -> io::Result<()> {
389-
unsupported()
388+
/// The implementation mirrors `mv` implementation in UEFI shell:
389+
/// https://github.com/tianocore/edk2/blob/66346d5edeac2a00d3cf2f2f3b5f66d423c07b3e/ShellPkg/Library/UefiShellLevel2CommandsLib/Mv.c#L455
390+
///
391+
/// In a nutshell we do the following:
392+
/// 1. Convert both old and new paths to absolute paths.
393+
/// 2. Check that both lie in the same disk.
394+
/// 3. Construct the target path relative to the current disk root.
395+
/// 4. Set this target path as the file_name in the file_info structure.
396+
pub fn rename(old: &Path, new: &Path) -> io::Result<()> {
397+
let old_absolute = crate::path::absolute(old)?;
398+
let new_absolute = crate::path::absolute(new)?;
399+
400+
let mut old_components = old_absolute.components();
401+
let mut new_components = new_absolute.components();
402+
403+
let Some(old_disk) = old_components.next() else {
404+
return Err(io::const_error!(io::ErrorKind::InvalidInput, "Old path is not valid"));
405+
};
406+
let Some(new_disk) = new_components.next() else {
407+
return Err(io::const_error!(io::ErrorKind::InvalidInput, "New path is not valid"));
408+
};
409+
410+
// Ensure that paths are on the same device.
411+
if old_disk != new_disk {
412+
return Err(io::const_error!(io::ErrorKind::CrossesDevices, "Cannot rename across device"));
413+
}
414+
415+
// Construct an path relative the current disk root.
416+
let new_relative =
417+
[crate::path::Component::RootDir].into_iter().chain(new_components).collect::<PathBuf>();
418+
419+
let f = uefi_fs::File::from_path(old, file::MODE_READ | file::MODE_WRITE, 0)?;
420+
let file_info = f.file_info()?;
421+
422+
let new_info = file_info.with_file_name(new_relative.as_os_str())?;
423+
424+
f.set_file_info(new_info)
390425
}
391426

392427
pub fn set_perm(p: &Path, perm: FilePermissions) -> io::Result<()> {
@@ -751,12 +786,7 @@ mod uefi_fs {
751786
}
752787

753788
pub(crate) fn file_name_from_uefi(info: &UefiBox<file::Info>) -> OsString {
754-
let file_name = {
755-
let size = unsafe { (*info.as_ptr()).size };
756-
let strlen = (size as usize - crate::mem::size_of::<file::Info<0>>() - 1) / 2;
757-
unsafe { crate::slice::from_raw_parts((*info.as_ptr()).file_name.as_ptr(), strlen) }
758-
};
759-
760-
OsString::from_wide(file_name)
789+
let fname = info.file_name();
790+
OsString::from_wide(&fname[..fname.len() - 1])
761791
}
762792
}

library/std/src/sys/pal/uefi/helpers.rs

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
//! - 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)
1111
1212
use r_efi::efi::{self, Guid};
13-
use r_efi::protocols::{device_path, device_path_to_text, service_binding, shell};
13+
use r_efi::protocols::{device_path, device_path_to_text, file, service_binding, shell};
1414

1515
use crate::alloc::Layout;
1616
use crate::ffi::{OsStr, OsString};
@@ -787,7 +787,7 @@ impl<T> UefiBox<T> {
787787

788788
match NonNull::new(ptr.cast()) {
789789
Some(inner) => Ok(Self { inner, size: len }),
790-
None => Err(io::Error::new(io::ErrorKind::OutOfMemory, "Allocation failed")),
790+
None => Err(const_error!(io::ErrorKind::OutOfMemory, "Allocation failed")),
791791
}
792792
}
793793

@@ -814,3 +814,58 @@ impl<T> Drop for UefiBox<T> {
814814
unsafe { crate::alloc::dealloc(self.inner.as_ptr().cast(), layout) };
815815
}
816816
}
817+
818+
impl UefiBox<file::Info> {
819+
fn size(&self) -> u64 {
820+
unsafe { (*self.as_ptr()).size }
821+
}
822+
823+
fn set_size(&mut self, s: u64) {
824+
unsafe { (*self.as_mut_ptr()).size = s }
825+
}
826+
827+
// Length of string (including NULL), not number of bytes.
828+
fn file_name_len(&self) -> usize {
829+
(self.size() as usize - size_of::<file::Info<0>>()) / size_of::<u16>()
830+
}
831+
832+
pub(crate) fn file_name(&self) -> &[u16] {
833+
unsafe {
834+
crate::slice::from_raw_parts((*self.as_ptr()).file_name.as_ptr(), self.file_name_len())
835+
}
836+
}
837+
838+
fn file_name_mut(&mut self) -> &mut [u16] {
839+
unsafe {
840+
crate::slice::from_raw_parts_mut(
841+
(*self.as_mut_ptr()).file_name.as_mut_ptr(),
842+
self.file_name_len(),
843+
)
844+
}
845+
}
846+
847+
pub(crate) fn with_file_name(mut self, name: &OsStr) -> io::Result<Self> {
848+
// os_string_to_raw returns NULL terminated string. So no need to handle it separately.
849+
let fname = os_string_to_raw(name)
850+
.ok_or(const_error!(io::ErrorKind::OutOfMemory, "Allocation failed"))?;
851+
let new_size = size_of::<file::Info<0>>() + fname.len() * size_of::<u16>();
852+
853+
// Reuse the current structure if the new name can fit in it.
854+
if self.size() >= new_size as u64 {
855+
self.file_name_mut()[..fname.len()].copy_from_slice(&fname);
856+
self.set_size(new_size as u64);
857+
858+
return Ok(self);
859+
}
860+
861+
let mut new_box = UefiBox::new(new_size)?;
862+
863+
unsafe {
864+
crate::ptr::copy_nonoverlapping(self.as_ptr(), new_box.as_mut_ptr(), 1);
865+
}
866+
new_box.set_size(new_size as u64);
867+
new_box.file_name_mut().copy_from_slice(&fname);
868+
869+
Ok(new_box)
870+
}
871+
}

0 commit comments

Comments
 (0)