cap_primitives/fs/
metadata.rs

1use crate::fs::{FileType, ImplFileTypeExt, ImplMetadataExt, Permissions};
2use crate::time::SystemTime;
3use std::{fs, io};
4
5/// Metadata information about a file.
6///
7/// This corresponds to [`std::fs::Metadata`].
8///
9/// <details>
10/// We need to define our own version because the libstd `Metadata` doesn't
11/// have a public constructor that we can use.
12/// </details>
13#[derive(Debug, Clone)]
14pub struct Metadata {
15    pub(crate) file_type: FileType,
16    pub(crate) len: u64,
17    pub(crate) permissions: Permissions,
18    pub(crate) modified: Option<SystemTime>,
19    pub(crate) accessed: Option<SystemTime>,
20    pub(crate) created: Option<SystemTime>,
21    pub(crate) ext: ImplMetadataExt,
22}
23
24#[allow(clippy::len_without_is_empty)]
25impl Metadata {
26    /// Constructs a new instance of `Self` from the given [`std::fs::File`].
27    #[inline]
28    pub fn from_file(file: &fs::File) -> io::Result<Self> {
29        let std = file.metadata()?;
30        let ext = ImplMetadataExt::from(file, &std)?;
31        let file_type = ImplFileTypeExt::from(file, &std)?;
32        Ok(Self::from_parts(std, ext, file_type))
33    }
34
35    /// Constructs a new instance of `Self` from the given
36    /// [`std::fs::Metadata`].
37    ///
38    /// As with the comments in [`std::fs::Metadata::volume_serial_number`] and
39    /// nearby functions, some fields of the resulting metadata will be `None`.
40    ///
41    /// [`std::fs::Metadata::volume_serial_number`]: https://doc.rust-lang.org/std/os/windows/fs/trait.MetadataExt.html#tymethod.volume_serial_number
42    #[inline]
43    pub fn from_just_metadata(std: fs::Metadata) -> Self {
44        let ext = ImplMetadataExt::from_just_metadata(&std);
45        let file_type = ImplFileTypeExt::from_just_metadata(&std);
46        Self::from_parts(std, ext, file_type)
47    }
48
49    #[inline]
50    fn from_parts(std: fs::Metadata, ext: ImplMetadataExt, file_type: FileType) -> Self {
51        Self {
52            file_type,
53            len: std.len(),
54            permissions: Permissions::from_std(std.permissions()),
55            modified: std.modified().ok().map(SystemTime::from_std),
56            accessed: std.accessed().ok().map(SystemTime::from_std),
57            created: std.created().ok().map(SystemTime::from_std),
58            ext,
59        }
60    }
61
62    /// Returns the file type for this metadata.
63    ///
64    /// This corresponds to [`std::fs::Metadata::file_type`].
65    #[inline]
66    pub const fn file_type(&self) -> FileType {
67        self.file_type
68    }
69
70    /// Returns `true` if this metadata is for a directory.
71    ///
72    /// This corresponds to [`std::fs::Metadata::is_dir`].
73    #[inline]
74    pub fn is_dir(&self) -> bool {
75        self.file_type.is_dir()
76    }
77
78    /// Returns `true` if this metadata is for a regular file.
79    ///
80    /// This corresponds to [`std::fs::Metadata::is_file`].
81    #[inline]
82    pub fn is_file(&self) -> bool {
83        self.file_type.is_file()
84    }
85
86    /// Returns `true` if this metadata is for a symbolic link.
87    ///
88    /// This corresponds to [`std::fs::Metadata::is_symlink`].
89    #[inline]
90    pub fn is_symlink(&self) -> bool {
91        self.file_type.is_symlink()
92    }
93
94    /// Returns the size of the file, in bytes, this metadata is for.
95    ///
96    /// This corresponds to [`std::fs::Metadata::len`].
97    #[inline]
98    pub const fn len(&self) -> u64 {
99        self.len
100    }
101
102    /// Returns the permissions of the file this metadata is for.
103    ///
104    /// This corresponds to [`std::fs::Metadata::permissions`].
105    #[inline]
106    pub fn permissions(&self) -> Permissions {
107        self.permissions.clone()
108    }
109
110    /// Returns the last modification time listed in this metadata.
111    ///
112    /// This corresponds to [`std::fs::Metadata::modified`].
113    #[inline]
114    pub fn modified(&self) -> io::Result<SystemTime> {
115        #[cfg(io_error_uncategorized)]
116        {
117            self.modified.ok_or_else(|| {
118                io::Error::new(
119                    io::ErrorKind::Unsupported,
120                    "modified time metadata not available on this platform",
121                )
122            })
123        }
124        #[cfg(not(io_error_uncategorized))]
125        {
126            self.modified.ok_or_else(|| {
127                io::Error::new(
128                    io::ErrorKind::Other,
129                    "modified time metadata not available on this platform",
130                )
131            })
132        }
133    }
134
135    /// Returns the last access time of this metadata.
136    ///
137    /// This corresponds to [`std::fs::Metadata::accessed`].
138    #[inline]
139    pub fn accessed(&self) -> io::Result<SystemTime> {
140        #[cfg(io_error_uncategorized)]
141        {
142            self.accessed.ok_or_else(|| {
143                io::Error::new(
144                    io::ErrorKind::Unsupported,
145                    "accessed time metadata not available on this platform",
146                )
147            })
148        }
149        #[cfg(not(io_error_uncategorized))]
150        {
151            self.accessed.ok_or_else(|| {
152                io::Error::new(
153                    io::ErrorKind::Other,
154                    "accessed time metadata not available on this platform",
155                )
156            })
157        }
158    }
159
160    /// Returns the creation time listed in this metadata.
161    ///
162    /// This corresponds to [`std::fs::Metadata::created`].
163    #[inline]
164    pub fn created(&self) -> io::Result<SystemTime> {
165        #[cfg(io_error_uncategorized)]
166        {
167            self.created.ok_or_else(|| {
168                io::Error::new(
169                    io::ErrorKind::Unsupported,
170                    "created time metadata not available on this platform",
171                )
172            })
173        }
174        #[cfg(not(io_error_uncategorized))]
175        {
176            self.created.ok_or_else(|| {
177                io::Error::new(
178                    io::ErrorKind::Other,
179                    "created time metadata not available on this platform",
180                )
181            })
182        }
183    }
184
185    /// Determine if `self` and `other` refer to the same inode on the same
186    /// device.
187    #[cfg(any(not(windows), windows_by_handle))]
188    pub(crate) fn is_same_file(&self, other: &Self) -> bool {
189        self.ext.is_same_file(&other.ext)
190    }
191
192    /// `MetadataExt` requires nightly to be implemented, but we sometimes
193    /// just need the file attributes.
194    #[cfg(windows)]
195    #[inline]
196    pub(crate) fn file_attributes(&self) -> u32 {
197        self.ext.file_attributes()
198    }
199}
200
201/// Unix-specific extensions for [`MetadataExt`].
202///
203/// This corresponds to [`std::os::unix::fs::MetadataExt`].
204#[cfg(any(unix, target_os = "vxworks"))]
205pub trait MetadataExt {
206    /// Returns the ID of the device containing the file.
207    fn dev(&self) -> u64;
208    /// Returns the inode number.
209    fn ino(&self) -> u64;
210    /// Returns the rights applied to this file.
211    fn mode(&self) -> u32;
212    /// Returns the number of hard links pointing to this file.
213    fn nlink(&self) -> u64;
214    /// Returns the user ID of the owner of this file.
215    fn uid(&self) -> u32;
216    /// Returns the group ID of the owner of this file.
217    fn gid(&self) -> u32;
218    /// Returns the device ID of this file (if it is a special one).
219    fn rdev(&self) -> u64;
220    /// Returns the total size of this file in bytes.
221    fn size(&self) -> u64;
222    /// Returns the last access time of the file, in seconds since Unix Epoch.
223    fn atime(&self) -> i64;
224    /// Returns the last access time of the file, in nanoseconds since [`atime`].
225    fn atime_nsec(&self) -> i64;
226    /// Returns the last modification time of the file, in seconds since Unix Epoch.
227    fn mtime(&self) -> i64;
228    /// Returns the last modification time of the file, in nanoseconds since [`mtime`].
229    fn mtime_nsec(&self) -> i64;
230    /// Returns the last status change time of the file, in seconds since Unix Epoch.
231    fn ctime(&self) -> i64;
232    /// Returns the last status change time of the file, in nanoseconds since [`ctime`].
233    fn ctime_nsec(&self) -> i64;
234    /// Returns the block size for filesystem I/O.
235    fn blksize(&self) -> u64;
236    /// Returns the number of blocks allocated to the file, in 512-byte units.
237    fn blocks(&self) -> u64;
238    #[cfg(target_os = "vxworks")]
239    fn attrib(&self) -> u8;
240}
241
242/// WASI-specific extensions for [`MetadataExt`].
243///
244/// This corresponds to [`std::os::wasi::fs::MetadataExt`].
245#[cfg(target_os = "wasi")]
246pub trait MetadataExt {
247    /// Returns the ID of the device containing the file.
248    fn dev(&self) -> u64;
249    /// Returns the inode number.
250    fn ino(&self) -> u64;
251    /// Returns the number of hard links pointing to this file.
252    fn nlink(&self) -> u64;
253}
254
255/// Windows-specific extensions to [`Metadata`].
256///
257/// This corresponds to [`std::os::windows::fs::MetadataExt`].
258#[cfg(windows)]
259pub trait MetadataExt {
260    /// Returns the value of the `dwFileAttributes` field of this metadata.
261    fn file_attributes(&self) -> u32;
262    /// Returns the value of the `ftCreationTime` field of this metadata.
263    fn creation_time(&self) -> u64;
264    /// Returns the value of the `ftLastAccessTime` field of this metadata.
265    fn last_access_time(&self) -> u64;
266    /// Returns the value of the `ftLastWriteTime` field of this metadata.
267    fn last_write_time(&self) -> u64;
268    /// Returns the value of the `nFileSize{High,Low}` fields of this metadata.
269    fn file_size(&self) -> u64;
270    /// Returns the value of the `dwVolumeSerialNumber` field of this metadata.
271    #[cfg(windows_by_handle)]
272    fn volume_serial_number(&self) -> Option<u32>;
273    /// Returns the value of the `nNumberOfLinks` field of this metadata.
274    #[cfg(windows_by_handle)]
275    fn number_of_links(&self) -> Option<u32>;
276    /// Returns the value of the `nFileIndex{Low,High}` fields of this metadata.
277    #[cfg(windows_by_handle)]
278    fn file_index(&self) -> Option<u64>;
279}
280
281#[cfg(unix)]
282impl MetadataExt for Metadata {
283    #[inline]
284    fn dev(&self) -> u64 {
285        crate::fs::MetadataExt::dev(&self.ext)
286    }
287
288    #[inline]
289    fn ino(&self) -> u64 {
290        crate::fs::MetadataExt::ino(&self.ext)
291    }
292
293    #[inline]
294    fn mode(&self) -> u32 {
295        crate::fs::MetadataExt::mode(&self.ext)
296    }
297
298    #[inline]
299    fn nlink(&self) -> u64 {
300        crate::fs::MetadataExt::nlink(&self.ext)
301    }
302
303    #[inline]
304    fn uid(&self) -> u32 {
305        crate::fs::MetadataExt::uid(&self.ext)
306    }
307
308    #[inline]
309    fn gid(&self) -> u32 {
310        crate::fs::MetadataExt::gid(&self.ext)
311    }
312
313    #[inline]
314    fn rdev(&self) -> u64 {
315        crate::fs::MetadataExt::rdev(&self.ext)
316    }
317
318    #[inline]
319    fn size(&self) -> u64 {
320        crate::fs::MetadataExt::size(&self.ext)
321    }
322
323    #[inline]
324    fn atime(&self) -> i64 {
325        crate::fs::MetadataExt::atime(&self.ext)
326    }
327
328    #[inline]
329    fn atime_nsec(&self) -> i64 {
330        crate::fs::MetadataExt::atime_nsec(&self.ext)
331    }
332
333    #[inline]
334    fn mtime(&self) -> i64 {
335        crate::fs::MetadataExt::mtime(&self.ext)
336    }
337
338    #[inline]
339    fn mtime_nsec(&self) -> i64 {
340        crate::fs::MetadataExt::mtime_nsec(&self.ext)
341    }
342
343    #[inline]
344    fn ctime(&self) -> i64 {
345        crate::fs::MetadataExt::ctime(&self.ext)
346    }
347
348    #[inline]
349    fn ctime_nsec(&self) -> i64 {
350        crate::fs::MetadataExt::ctime_nsec(&self.ext)
351    }
352
353    #[inline]
354    fn blksize(&self) -> u64 {
355        crate::fs::MetadataExt::blksize(&self.ext)
356    }
357
358    #[inline]
359    fn blocks(&self) -> u64 {
360        crate::fs::MetadataExt::blocks(&self.ext)
361    }
362}
363
364#[cfg(target_os = "wasi")]
365impl MetadataExt for Metadata {
366    #[inline]
367    fn dev(&self) -> u64 {
368        crate::fs::MetadataExt::dev(&self.ext)
369    }
370
371    #[inline]
372    fn ino(&self) -> u64 {
373        crate::fs::MetadataExt::ino(&self.ext)
374    }
375
376    #[inline]
377    fn nlink(&self) -> u64 {
378        crate::fs::MetadataExt::nlink(&self.ext)
379    }
380}
381
382#[cfg(target_os = "vxworks")]
383impl MetadataExt for Metadata {
384    #[inline]
385    fn dev(&self) -> u64 {
386        self.ext.dev()
387    }
388
389    #[inline]
390    fn ino(&self) -> u64 {
391        self.ext.ino()
392    }
393
394    #[inline]
395    fn mode(&self) -> u32 {
396        self.ext.mode()
397    }
398
399    #[inline]
400    fn nlink(&self) -> u64 {
401        self.ext.nlink()
402    }
403
404    #[inline]
405    fn uid(&self) -> u32 {
406        self.ext.uid()
407    }
408
409    #[inline]
410    fn gid(&self) -> u32 {
411        self.ext.gid()
412    }
413
414    #[inline]
415    fn rdev(&self) -> u64 {
416        self.ext.rdev()
417    }
418
419    #[inline]
420    fn size(&self) -> u64 {
421        self.ext.size()
422    }
423
424    #[inline]
425    fn atime(&self) -> i64 {
426        self.ext.atime()
427    }
428
429    #[inline]
430    fn atime_nsec(&self) -> i64 {
431        self.ext.atime_nsec()
432    }
433
434    #[inline]
435    fn mtime(&self) -> i64 {
436        self.ext.mtime()
437    }
438
439    #[inline]
440    fn mtime_nsec(&self) -> i64 {
441        self.ext.mtime_nsec()
442    }
443
444    #[inline]
445    fn ctime(&self) -> i64 {
446        self.ext.ctime()
447    }
448
449    #[inline]
450    fn ctime_nsec(&self) -> i64 {
451        self.ext.ctime_nsec()
452    }
453
454    #[inline]
455    fn blksize(&self) -> u64 {
456        self.ext.blksize()
457    }
458
459    #[inline]
460    fn blocks(&self) -> u64 {
461        self.ext.blocks()
462    }
463}
464
465#[cfg(windows)]
466impl MetadataExt for Metadata {
467    #[inline]
468    fn file_attributes(&self) -> u32 {
469        self.ext.file_attributes()
470    }
471
472    #[inline]
473    fn creation_time(&self) -> u64 {
474        self.ext.creation_time()
475    }
476
477    #[inline]
478    fn last_access_time(&self) -> u64 {
479        self.ext.last_access_time()
480    }
481
482    #[inline]
483    fn last_write_time(&self) -> u64 {
484        self.ext.last_write_time()
485    }
486
487    #[inline]
488    fn file_size(&self) -> u64 {
489        self.ext.file_size()
490    }
491
492    #[inline]
493    #[cfg(windows_by_handle)]
494    fn volume_serial_number(&self) -> Option<u32> {
495        self.ext.volume_serial_number()
496    }
497
498    #[inline]
499    #[cfg(windows_by_handle)]
500    fn number_of_links(&self) -> Option<u32> {
501        self.ext.number_of_links()
502    }
503
504    #[inline]
505    #[cfg(windows_by_handle)]
506    fn file_index(&self) -> Option<u64> {
507        self.ext.file_index()
508    }
509}
510
511/// Extension trait to allow `volume_serial_number` etc. to be exposed by
512/// the `cap-fs-ext` crate.
513///
514/// This is hidden from the main API since this functionality isn't present in
515/// `std`. Use `cap_fs_ext::MetadataExt` instead of calling this directly.
516#[cfg(windows)]
517#[doc(hidden)]
518pub trait _WindowsByHandle {
519    fn file_attributes(&self) -> u32;
520    fn volume_serial_number(&self) -> Option<u32>;
521    fn number_of_links(&self) -> Option<u32>;
522    fn file_index(&self) -> Option<u64>;
523}