1 | use super::FileAttribute; |
2 | use crate::data_types::Align; |
3 | use crate::table::runtime::Time; |
4 | use crate::{guid, CStr16, Char16, Guid, Identify}; |
5 | use core::ffi::c_void; |
6 | use core::{mem, ptr}; |
7 | use 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. |
13 | pub 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. |
20 | pub 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. |
36 | trait 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 | |
106 | impl<T> FromUefi for T |
107 | where |
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)] |
120 | pub 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)] |
146 | pub 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 | |
157 | impl 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 | |
234 | impl Align for FileInfo { |
235 | fn alignment() -> usize { |
236 | 8 |
237 | } |
238 | } |
239 | |
240 | unsafe impl Identify for FileInfo { |
241 | const GUID: Guid = guid!("09576e92-6d3f-11d2-8e39-00a0c969723b" ); |
242 | } |
243 | |
244 | impl InfoInternal for FileInfo { |
245 | fn name_offset() -> usize { |
246 | 80 |
247 | } |
248 | } |
249 | |
250 | impl 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)] |
260 | pub 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 | |
269 | impl 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 | |
329 | impl Align for FileSystemInfo { |
330 | fn alignment() -> usize { |
331 | 8 |
332 | } |
333 | } |
334 | |
335 | unsafe impl Identify for FileSystemInfo { |
336 | const GUID: Guid = guid!("09576e93-6d3f-11d2-8e39-00a0c969723b" ); |
337 | } |
338 | |
339 | impl InfoInternal for FileSystemInfo { |
340 | fn name_offset() -> usize { |
341 | 36 |
342 | } |
343 | } |
344 | |
345 | impl 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)] |
352 | pub struct FileSystemVolumeLabel { |
353 | volume_label: [Char16], |
354 | } |
355 | |
356 | impl 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 | |
380 | impl Align for FileSystemVolumeLabel { |
381 | fn alignment() -> usize { |
382 | 2 |
383 | } |
384 | } |
385 | |
386 | unsafe impl Identify for FileSystemVolumeLabel { |
387 | const GUID: Guid = guid!("db47d7d3-fe81-11d3-9a35-0090273fc14d" ); |
388 | } |
389 | |
390 | impl InfoInternal for FileSystemVolumeLabel { |
391 | fn name_offset() -> usize { |
392 | 0 |
393 | } |
394 | } |
395 | |
396 | impl FileProtocolInfo for FileSystemVolumeLabel {} |
397 | |
398 | #[cfg (test)] |
399 | mod 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 | |