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