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