Skip to content

Commit 31fb12c

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 31fb12c

File tree

2 files changed

+72
-10
lines changed

2 files changed

+72
-10
lines changed

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

Lines changed: 38 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,6 @@ 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+
OsString::from_wide(info.file_name())
761790
}
762791
}

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

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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,36 @@ impl<T> Drop for UefiBox<T> {
814814
unsafe { crate::alloc::dealloc(self.inner.as_ptr().cast(), layout) };
815815
}
816816
}
817+
818+
impl UefiBox<r_efi::efi::protocols::file::Info> {
819+
fn size(&self) -> u64 {
820+
unsafe { (*self.as_ptr()).size }
821+
}
822+
823+
pub(crate) fn file_name(&self) -> &[u16] {
824+
let strlen =
825+
(self.size() as usize - size_of::<r_efi::efi::protocols::file::Info<0>>() - 1) / 2;
826+
unsafe { crate::slice::from_raw_parts((*self.as_ptr()).file_name.as_ptr(), strlen) }
827+
}
828+
829+
pub(crate) fn with_file_name(&self, name: &OsStr) -> io::Result<Self> {
830+
// os_string_to_raw returns NULL terminated string. So no need to handle it separately.
831+
let fname = os_string_to_raw(name)
832+
.ok_or(const_error!(io::ErrorKind::OutOfMemory, "Allocation failed"))?;
833+
let new_size =
834+
size_of::<r_efi::efi::protocols::file::Info<0>>() + fname.len() * size_of::<u16>();
835+
836+
let mut new_box = UefiBox::new(new_size)?;
837+
838+
unsafe {
839+
crate::ptr::copy_nonoverlapping(self.as_ptr(), new_box.as_mut_ptr(), 1);
840+
crate::ptr::copy_nonoverlapping(
841+
fname.as_ptr(),
842+
(*new_box.as_mut_ptr()).file_name.as_mut_ptr(),
843+
fname.len(),
844+
);
845+
}
846+
847+
Ok(new_box)
848+
}
849+
}

0 commit comments

Comments
 (0)