1 use super::FileAttribute; 2 use crate::data_types::Align; 3 use crate::runtime::Time; 4 use crate::{CStr16, Char16, Guid, Identify}; 5 use core::ffi::c_void; 6 use core::fmt::{self, Display, Formatter}; 7 use core::{mem, ptr}; 8 use ptr_meta::Pointee; 9 10 /// Common trait for data structures that can be used with 11 /// `File::set_info()` or `File::get_info()`. 12 /// 13 /// The long-winded name is needed because "FileInfo" is already taken by UEFI. 14 pub trait FileProtocolInfo: Align + Identify + FromUefi {} 15 16 /// Trait for going from an UEFI-originated pointer to a Rust reference 17 /// 18 /// This is trivial for `Sized` types, but requires some work when operating on 19 /// dynamic-sized types like `NamedFileProtocolInfo`, as the second member of 20 /// the fat pointer must be reconstructed using hidden UEFI-provided metadata. 21 pub trait FromUefi { 22 /// Turn an UEFI-provided pointer-to-base into a (possibly fat) Rust reference 23 /// 24 /// # Safety 25 /// 26 /// This function can lead to undefined behavior if the given pointer is not 27 /// pointing to a valid object of the specified type. from_uefi<'ptr>(ptr: *mut c_void) -> &'ptr mut Self28 unsafe fn from_uefi<'ptr>(ptr: *mut c_void) -> &'ptr mut Self; 29 } 30 31 /// Internal trait for initializing one of the info types. 32 /// 33 /// This is used with `FileInfo`, `FileSystemInfo`, and 34 /// `FileSystemVolumeLabel`, all of which are dynamically-sized structs 35 /// that have zero or more header fields followed by a variable-length 36 /// [Char16] name. 37 trait InfoInternal: Align + ptr_meta::Pointee<Metadata = usize> { 38 /// Offset in bytes of the start of the name slice at the end of 39 /// the struct. name_offset() -> usize40 fn name_offset() -> usize; 41 42 /// Get a mutable pointer to the name slice at the end of the 43 /// struct. name_ptr(ptr: *mut u8) -> *mut Char1644 unsafe fn name_ptr(ptr: *mut u8) -> *mut Char16 { 45 let offset_of_str = Self::name_offset(); 46 ptr.add(offset_of_str).cast::<Char16>() 47 } 48 49 /// Create a new info type in user-provided storage. 50 /// 51 /// The structure will be created in-place within the provided 52 /// `storage` buffer. The alignment and size of the buffer is 53 /// checked, then the `init` function is called to fill in the 54 /// struct header (everything except for the name slice). The name 55 /// slice is then initialized, and at that point the struct is fully 56 /// initialized so it's safe to create a reference. 57 /// 58 /// # Safety 59 /// 60 /// The `init` function must initialize the entire struct except for 61 /// the name slice. new_impl<'buf, F>( storage: &'buf mut [u8], name: &CStr16, init: F, ) -> Result<&'buf mut Self, FileInfoCreationError> where F: FnOnce(*mut Self, u64),62 unsafe fn new_impl<'buf, F>( 63 storage: &'buf mut [u8], 64 name: &CStr16, 65 init: F, 66 ) -> Result<&'buf mut Self, FileInfoCreationError> 67 where 68 F: FnOnce(*mut Self, u64), 69 { 70 // Calculate the final size of the struct. 71 let name_length_ucs2 = name.as_slice_with_nul().len(); 72 let name_size = mem::size_of_val(name.as_slice_with_nul()); 73 let info_size = Self::name_offset() + name_size; 74 let info_size = Self::round_up_to_alignment(info_size); 75 76 // Make sure that the storage is properly aligned 77 let storage = Self::align_buf(storage) 78 .ok_or(FileInfoCreationError::InsufficientStorage(info_size))?; 79 Self::assert_aligned(storage); 80 81 // Make sure that the storage is large enough for our needs 82 if storage.len() < info_size { 83 return Err(FileInfoCreationError::InsufficientStorage(info_size)); 84 } 85 86 // Create a raw fat pointer using the `storage` as a base. 87 let info_ptr: *mut Self = 88 ptr_meta::from_raw_parts_mut(storage.as_mut_ptr().cast::<()>(), name_length_ucs2); 89 90 // Initialize the struct header. 91 init(info_ptr, info_size as u64); 92 93 // Create a pointer to the part of info where the name is 94 // stored. Note that `info_ptr` is used rather than `storage` to 95 // comply with Stacked Borrows. 96 let info_name_ptr = Self::name_ptr(info_ptr.cast::<u8>()); 97 98 // Initialize the name slice. 99 ptr::copy(name.as_ptr(), info_name_ptr, name_length_ucs2); 100 101 // The struct is now valid and safe to dereference. 102 let info = &mut *info_ptr; 103 Ok(info) 104 } 105 } 106 107 impl<T> FromUefi for T 108 where 109 T: InfoInternal + ?Sized, 110 { from_uefi<'ptr>(ptr: *mut c_void) -> &'ptr mut Self111 unsafe fn from_uefi<'ptr>(ptr: *mut c_void) -> &'ptr mut Self { 112 let name_ptr = Self::name_ptr(ptr.cast::<u8>()); 113 let name = CStr16::from_ptr(name_ptr); 114 let name_len = name.as_slice_with_nul().len(); 115 &mut *ptr_meta::from_raw_parts_mut(ptr.cast::<()>(), name_len) 116 } 117 } 118 119 /// Errors that can occur when creating a `FileProtocolInfo` 120 #[derive(Clone, Copy, Debug, Eq, PartialEq)] 121 pub enum FileInfoCreationError { 122 /// The provided buffer was too small to hold the `FileInfo`. You need at 123 /// least the indicated buffer size (in bytes). Please remember that using 124 /// a misaligned buffer will cause a decrease of usable storage capacity. 125 InsufficientStorage(usize), 126 } 127 128 impl Display for FileInfoCreationError { fmt(&self, f: &mut Formatter<'_>) -> fmt::Result129 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 130 match self { 131 Self::InsufficientStorage(bytes) => write!( 132 f, 133 "provided buffer was too small. need at least {} bytes", 134 bytes 135 ), 136 } 137 } 138 } 139 140 #[cfg(feature = "unstable")] 141 impl core::error::Error for FileInfoCreationError {} 142 143 /// Generic file information 144 /// 145 /// The following rules apply when using this struct with `set_info()`: 146 /// 147 /// - On directories, the file size is determined by the contents of the 148 /// directory and cannot be changed by setting `file_size`. This member is 149 /// ignored by `set_info()`. 150 /// - The `physical_size` is determined by the `file_size` and cannot be 151 /// changed. This member is ignored by `set_info()`. 152 /// - The `FileAttribute::DIRECTORY` bit cannot be changed. It must match the 153 /// file’s actual type. 154 /// - A value of zero in create_time, last_access, or modification_time causes 155 /// the fields to be ignored (and not updated). 156 /// - It is forbidden to change the name of a file to the name of another 157 /// existing file in the same directory. 158 /// - If a file is read-only, the only allowed change is to remove the read-only 159 /// attribute. Other changes must be carried out in a separate transaction. 160 #[derive(Debug, Eq, PartialEq, Pointee)] 161 #[repr(C)] 162 pub struct FileInfo { 163 size: u64, 164 file_size: u64, 165 physical_size: u64, 166 create_time: Time, 167 last_access_time: Time, 168 modification_time: Time, 169 attribute: FileAttribute, 170 file_name: [Char16], 171 } 172 173 impl FileInfo { 174 /// Create a `FileInfo` structure 175 /// 176 /// The structure will be created in-place within the provided storage 177 /// buffer. The buffer must be large enough to hold the data structure, 178 /// including a null-terminated UCS-2 `name` string. 179 /// 180 /// The buffer must be correctly aligned. You can query the required 181 /// alignment using the `alignment()` method of the `Align` trait that this 182 /// struct implements. 183 #[allow(clippy::too_many_arguments)] new<'buf>( storage: &'buf mut [u8], file_size: u64, physical_size: u64, create_time: Time, last_access_time: Time, modification_time: Time, attribute: FileAttribute, file_name: &CStr16, ) -> core::result::Result<&'buf mut Self, FileInfoCreationError>184 pub fn new<'buf>( 185 storage: &'buf mut [u8], 186 file_size: u64, 187 physical_size: u64, 188 create_time: Time, 189 last_access_time: Time, 190 modification_time: Time, 191 attribute: FileAttribute, 192 file_name: &CStr16, 193 ) -> core::result::Result<&'buf mut Self, FileInfoCreationError> { 194 unsafe { 195 Self::new_impl(storage, file_name, |ptr, size| { 196 ptr::addr_of_mut!((*ptr).size).write(size); 197 ptr::addr_of_mut!((*ptr).file_size).write(file_size); 198 ptr::addr_of_mut!((*ptr).physical_size).write(physical_size); 199 ptr::addr_of_mut!((*ptr).create_time).write(create_time); 200 ptr::addr_of_mut!((*ptr).last_access_time).write(last_access_time); 201 ptr::addr_of_mut!((*ptr).modification_time).write(modification_time); 202 ptr::addr_of_mut!((*ptr).attribute).write(attribute); 203 }) 204 } 205 } 206 207 /// File size (number of bytes stored in the file) 208 #[must_use] file_size(&self) -> u64209 pub const fn file_size(&self) -> u64 { 210 self.file_size 211 } 212 213 /// Physical space consumed by the file on the file system volume 214 #[must_use] physical_size(&self) -> u64215 pub const fn physical_size(&self) -> u64 { 216 self.physical_size 217 } 218 219 /// Time when the file was created 220 #[must_use] create_time(&self) -> &Time221 pub const fn create_time(&self) -> &Time { 222 &self.create_time 223 } 224 225 /// Time when the file was last accessed 226 #[must_use] last_access_time(&self) -> &Time227 pub const fn last_access_time(&self) -> &Time { 228 &self.last_access_time 229 } 230 231 /// Time when the file's contents were last modified 232 #[must_use] modification_time(&self) -> &Time233 pub const fn modification_time(&self) -> &Time { 234 &self.modification_time 235 } 236 237 /// Attribute bits for the file 238 #[must_use] attribute(&self) -> FileAttribute239 pub const fn attribute(&self) -> FileAttribute { 240 self.attribute 241 } 242 243 /// Name of the file 244 #[must_use] file_name(&self) -> &CStr16245 pub fn file_name(&self) -> &CStr16 { 246 unsafe { CStr16::from_ptr(self.file_name.as_ptr()) } 247 } 248 249 /// Returns if the file is a directory. 250 #[must_use] is_directory(&self) -> bool251 pub const fn is_directory(&self) -> bool { 252 self.attribute.contains(FileAttribute::DIRECTORY) 253 } 254 255 /// Returns if the file is a regular file. 256 #[must_use] is_regular_file(&self) -> bool257 pub const fn is_regular_file(&self) -> bool { 258 !self.is_directory() 259 } 260 } 261 262 impl Align for FileInfo { alignment() -> usize263 fn alignment() -> usize { 264 8 265 } 266 } 267 268 unsafe impl Identify for FileInfo { 269 const GUID: Guid = uefi_raw::protocol::file_system::FileInfo::ID; 270 } 271 272 impl InfoInternal for FileInfo { name_offset() -> usize273 fn name_offset() -> usize { 274 80 275 } 276 } 277 278 impl FileProtocolInfo for FileInfo {} 279 280 /// System volume information 281 /// 282 /// May only be obtained on the root directory's file handle. 283 /// 284 /// Please note that only the system volume's volume label may be set using 285 /// this information structure. Consider using `FileSystemVolumeLabel` instead. 286 #[derive(Debug, Eq, PartialEq, Pointee)] 287 #[repr(C)] 288 pub struct FileSystemInfo { 289 size: u64, 290 read_only: bool, 291 volume_size: u64, 292 free_space: u64, 293 block_size: u32, 294 volume_label: [Char16], 295 } 296 297 impl FileSystemInfo { 298 /// Create a `FileSystemInfo` structure 299 /// 300 /// The structure will be created in-place within the provided storage 301 /// buffer. The buffer must be large enough to hold the data structure, 302 /// including a null-terminated UCS-2 `name` string. 303 /// 304 /// The buffer must be correctly aligned. You can query the required 305 /// alignment using the `alignment()` method of the `Align` trait that this 306 /// struct implements. new<'buf>( storage: &'buf mut [u8], read_only: bool, volume_size: u64, free_space: u64, block_size: u32, volume_label: &CStr16, ) -> core::result::Result<&'buf mut Self, FileInfoCreationError>307 pub fn new<'buf>( 308 storage: &'buf mut [u8], 309 read_only: bool, 310 volume_size: u64, 311 free_space: u64, 312 block_size: u32, 313 volume_label: &CStr16, 314 ) -> core::result::Result<&'buf mut Self, FileInfoCreationError> { 315 unsafe { 316 Self::new_impl(storage, volume_label, |ptr, size| { 317 ptr::addr_of_mut!((*ptr).size).write(size); 318 ptr::addr_of_mut!((*ptr).read_only).write(read_only); 319 ptr::addr_of_mut!((*ptr).volume_size).write(volume_size); 320 ptr::addr_of_mut!((*ptr).free_space).write(free_space); 321 ptr::addr_of_mut!((*ptr).block_size).write(block_size); 322 }) 323 } 324 } 325 326 /// Truth that the volume only supports read access 327 #[must_use] read_only(&self) -> bool328 pub const fn read_only(&self) -> bool { 329 self.read_only 330 } 331 332 /// Number of bytes managed by the file system 333 #[must_use] volume_size(&self) -> u64334 pub const fn volume_size(&self) -> u64 { 335 self.volume_size 336 } 337 338 /// Number of available bytes for use by the file system 339 #[must_use] free_space(&self) -> u64340 pub const fn free_space(&self) -> u64 { 341 self.free_space 342 } 343 344 /// Nominal block size by which files are typically grown 345 #[must_use] block_size(&self) -> u32346 pub const fn block_size(&self) -> u32 { 347 self.block_size 348 } 349 350 /// Volume label 351 #[must_use] volume_label(&self) -> &CStr16352 pub fn volume_label(&self) -> &CStr16 { 353 unsafe { CStr16::from_ptr(self.volume_label.as_ptr()) } 354 } 355 } 356 357 impl Align for FileSystemInfo { alignment() -> usize358 fn alignment() -> usize { 359 8 360 } 361 } 362 363 unsafe impl Identify for FileSystemInfo { 364 const GUID: Guid = uefi_raw::protocol::file_system::FileSystemInfo::ID; 365 } 366 367 impl InfoInternal for FileSystemInfo { name_offset() -> usize368 fn name_offset() -> usize { 369 36 370 } 371 } 372 373 impl FileProtocolInfo for FileSystemInfo {} 374 375 /// System volume label 376 /// 377 /// May only be obtained on the root directory's file handle. 378 #[derive(Debug, Eq, PartialEq, Pointee)] 379 #[repr(C)] 380 pub struct FileSystemVolumeLabel { 381 volume_label: [Char16], 382 } 383 384 impl FileSystemVolumeLabel { 385 /// Create a `FileSystemVolumeLabel` structure 386 /// 387 /// The structure will be created in-place within the provided storage 388 /// buffer. The buffer must be large enough to hold the data structure, 389 /// including a null-terminated UCS-2 `name` string. 390 /// 391 /// The buffer must be correctly aligned. You can query the required 392 /// alignment using the `alignment()` method of the `Align` trait that this 393 /// struct implements. new<'buf>( storage: &'buf mut [u8], volume_label: &CStr16, ) -> core::result::Result<&'buf mut Self, FileInfoCreationError>394 pub fn new<'buf>( 395 storage: &'buf mut [u8], 396 volume_label: &CStr16, 397 ) -> core::result::Result<&'buf mut Self, FileInfoCreationError> { 398 unsafe { Self::new_impl(storage, volume_label, |_ptr, _size| {}) } 399 } 400 401 /// Volume label 402 #[must_use] volume_label(&self) -> &CStr16403 pub fn volume_label(&self) -> &CStr16 { 404 unsafe { CStr16::from_ptr(self.volume_label.as_ptr()) } 405 } 406 } 407 408 impl Align for FileSystemVolumeLabel { alignment() -> usize409 fn alignment() -> usize { 410 2 411 } 412 } 413 414 unsafe impl Identify for FileSystemVolumeLabel { 415 const GUID: Guid = uefi_raw::protocol::file_system::FileSystemVolumeLabel::ID; 416 } 417 418 impl InfoInternal for FileSystemVolumeLabel { name_offset() -> usize419 fn name_offset() -> usize { 420 0 421 } 422 } 423 424 impl FileProtocolInfo for FileSystemVolumeLabel {} 425 426 #[cfg(test)] 427 mod tests { 428 use super::*; 429 use crate::runtime::{Daylight, Time, TimeParams}; 430 use crate::CString16; 431 use alloc::vec; 432 validate_layout<T: InfoInternal + ?Sized>(info: &T, name: &[Char16])433 fn validate_layout<T: InfoInternal + ?Sized>(info: &T, name: &[Char16]) { 434 // Check the hardcoded struct alignment. 435 assert_eq!(mem::align_of_val(info), T::alignment()); 436 // Check the hardcoded name slice offset. 437 assert_eq!( 438 unsafe { (name.as_ptr() as *const u8).offset_from(info as *const _ as *const u8) }, 439 T::name_offset() as isize 440 ); 441 } 442 443 #[test] test_file_info()444 fn test_file_info() { 445 let mut storage = vec![0; 128]; 446 447 let file_size = 123; 448 let physical_size = 456; 449 let tp = TimeParams { 450 year: 1970, 451 month: 1, 452 day: 1, 453 hour: 0, 454 minute: 0, 455 second: 0, 456 nanosecond: 0, 457 time_zone: None, 458 daylight: Daylight::IN_DAYLIGHT, 459 }; 460 let create_time = Time::new(tp).unwrap(); 461 let last_access_time = Time::new(TimeParams { year: 1971, ..tp }).unwrap(); 462 let modification_time = Time::new(TimeParams { year: 1972, ..tp }).unwrap(); 463 let attribute = FileAttribute::READ_ONLY; 464 let name = CString16::try_from("test_name").unwrap(); 465 let info = FileInfo::new( 466 &mut storage, 467 file_size, 468 physical_size, 469 create_time, 470 last_access_time, 471 modification_time, 472 attribute, 473 &name, 474 ) 475 .unwrap(); 476 477 validate_layout(info, &info.file_name); 478 479 // Header size: 80 bytes 480 // + Name size (including trailing null): 20 bytes 481 // = 100 482 // Round size up to match FileInfo alignment of 8: 104 483 assert_eq!(info.size, 104); 484 assert_eq!(info.size, mem::size_of_val(info) as u64); 485 486 assert_eq!(info.file_size(), file_size); 487 assert_eq!(info.physical_size(), physical_size); 488 assert_eq!(info.create_time(), &create_time); 489 assert_eq!(info.last_access_time(), &last_access_time); 490 assert_eq!(info.modification_time(), &modification_time); 491 assert_eq!(info.attribute(), attribute); 492 assert_eq!(info.file_name(), name); 493 } 494 495 #[test] test_file_system_info()496 fn test_file_system_info() { 497 let mut storage = vec![0; 128]; 498 499 let read_only = true; 500 let volume_size = 123; 501 let free_space = 456; 502 let block_size = 789; 503 let name = CString16::try_from("test_name2").unwrap(); 504 let info = FileSystemInfo::new( 505 &mut storage, 506 read_only, 507 volume_size, 508 free_space, 509 block_size, 510 &name, 511 ) 512 .unwrap(); 513 514 validate_layout(info, &info.volume_label); 515 516 // Header size: 36 bytes 517 // + Name size (including trailing null): 22 bytes 518 // = 58 519 // Round size up to match FileSystemInfo alignment of 8: 64 520 assert_eq!(info.size, 64); 521 assert_eq!(info.size, mem::size_of_val(info) as u64); 522 523 assert_eq!(info.read_only, read_only); 524 assert_eq!(info.volume_size, volume_size); 525 assert_eq!(info.free_space, free_space); 526 assert_eq!(info.block_size, block_size); 527 assert_eq!(info.volume_label(), name); 528 } 529 530 #[test] test_file_system_volume_label()531 fn test_file_system_volume_label() { 532 let mut storage = vec![0; 128]; 533 534 let name = CString16::try_from("test_name").unwrap(); 535 let info = FileSystemVolumeLabel::new(&mut storage, &name).unwrap(); 536 537 validate_layout(info, &info.volume_label); 538 539 assert_eq!(info.volume_label(), name); 540 } 541 } 542