monoio/fs/metadata/
mod.rs

1mod unix;
2mod windows;
3
4use std::{os::unix::fs::MetadataExt, path::Path, time::SystemTime};
5
6use super::{file_type::FileType, permissions::Permissions};
7use crate::driver::op::Op;
8
9/// Given a path, query the file system to get information about a file,
10/// directory, etc.
11///
12/// This function will traverse symbolic links to query information about the
13/// destination file.
14///
15/// # Platform-specific behavior
16///
17/// current implementation is only for Linux.
18///
19/// # Errors
20///
21/// This function will return an error in the following situations, but is not
22/// limited to just these cases:
23///
24/// * The user lacks permissions to perform `metadata` call on `path`.
25///     * execute(search) permission is required on all of the directories in path that lead to the
26///       file.
27/// * `path` does not exist.
28///
29/// # Examples
30///
31/// ```rust,no_run
32/// use monoio::fs;
33///
34/// #[monoio::main]
35/// async fn main() -> std::io::Result<()> {
36///     let attr = fs::metadata("/some/file/path.txt").await?;
37///     // inspect attr ...
38///     Ok(())
39/// }
40/// ```
41pub async fn metadata<P: AsRef<Path>>(path: P) -> std::io::Result<Metadata> {
42    #[cfg(target_os = "linux")]
43    let flags = libc::AT_STATX_SYNC_AS_STAT;
44
45    #[cfg(target_os = "linux")]
46    let op = Op::statx_using_path(path, flags)?;
47
48    #[cfg(any(target_os = "macos", target_os = "freebsd"))]
49    let op = Op::statx_using_path(path, true)?;
50
51    op.result().await.map(FileAttr::from).map(Metadata)
52}
53
54/// Query the metadata about a file without following symlinks.
55///
56/// # Platform-specific behavior
57///
58/// This function currently corresponds to the `lstat` function on linux
59///
60/// # Errors
61///
62/// This function will return an error in the following situations, but is not
63/// limited to just these cases:
64///
65/// * The user lacks permissions to perform `metadata` call on `path`.
66///     * execute(search) permission is required on all of the directories in path that lead to the
67///       file.
68/// * `path` does not exist.
69///
70/// # Examples
71/// ```rust,no_run
72/// use monoio::fs;
73///
74/// #[monoio::main]
75/// async fn main() -> std::io::Result<()> {
76///     let attr = fs::symlink_metadata("/some/file/path.txt").await?;
77///     // inspect attr ...
78///     Ok(())
79/// }
80/// ```
81pub async fn symlink_metadata<P: AsRef<Path>>(path: P) -> std::io::Result<Metadata> {
82    #[cfg(target_os = "linux")]
83    let flags = libc::AT_STATX_SYNC_AS_STAT | libc::AT_SYMLINK_NOFOLLOW;
84
85    #[cfg(target_os = "linux")]
86    let op = Op::statx_using_path(path, flags)?;
87
88    #[cfg(any(target_os = "macos", target_os = "freebsd"))]
89    let op = Op::statx_using_path(path, false)?;
90
91    op.result().await.map(FileAttr::from).map(Metadata)
92}
93
94#[cfg(unix)]
95pub(crate) use unix::FileAttr;
96
97/// Metadata information about a file.
98///
99/// This structure is returned from the [`metadata`] or
100/// [`symlink_metadata`] function or method and represents known
101/// metadata about a file such as its permissions, size, modification
102/// times, etc.
103#[cfg(unix)]
104pub struct Metadata(pub(crate) FileAttr);
105
106impl Metadata {
107    /// Returns `true` if this metadata is for a directory.
108    ///
109    /// # Examples
110    ///
111    /// ```no_run
112    /// use monoio::fs;
113    ///
114    /// #[monoio::main]
115    /// async fn main() -> std::io::Result<()> {
116    ///     let metadata = fs::metadata("path/to/dir").await?;
117    ///
118    ///     println!("{:?}", metadata.is_dir());
119    ///     Ok(())
120    /// }
121    /// ```
122    pub fn is_dir(&self) -> bool {
123        self.0.stat.st_mode & libc::S_IFMT == libc::S_IFDIR
124    }
125
126    /// Returns `true` if this metadata is for a regular file.
127    ///
128    /// # Examples
129    ///
130    /// ```no_run
131    /// use monoio::fs;
132    ///
133    /// #[monoio::main]
134    /// async fn main() -> std::io::Result<()> {
135    ///     let metadata = fs::metadata("foo.txt").await?;
136    ///
137    ///     println!("{:?}", metadata.is_file());
138    ///     Ok(())
139    /// }
140    /// ```
141    pub fn is_file(&self) -> bool {
142        self.0.stat.st_mode & libc::S_IFMT == libc::S_IFREG
143    }
144
145    /// Returns `true` if this metadata is for a symbolic link.
146    ///
147    /// # Examples
148    ///
149    /// ```no_run
150    /// use monoio::fs;
151    ///
152    /// #[monoio::main]
153    /// async fn main() -> std::io::Result<()> {
154    ///     let metadata = fs::metadata("foo.txt").await?;
155    ///
156    ///     println!("{:?}", metadata.is_symlink());
157    ///     Ok(())
158    /// }
159    /// ```
160    pub fn is_symlink(&self) -> bool {
161        self.0.stat.st_mode & libc::S_IFMT == libc::S_IFLNK
162    }
163
164    /// Returns the size of the file, in bytes, this metadata is for.
165    ///
166    /// # Examples
167    ///
168    /// ```no_run
169    /// use monoio::fs;
170    ///
171    /// #[monoio::main]
172    /// async fn main() -> std::io::Result<()> {
173    ///     let metadata = fs::metadata("foo.txt").await?;
174    ///
175    ///     println!("{:?}", metadata.len());
176    ///     Ok(())
177    /// }
178    /// ```
179    #[allow(clippy::len_without_is_empty)]
180    pub fn len(&self) -> u64 {
181        self.0.size()
182    }
183
184    /// Returns the last modification time listed in this metadata.
185    ///
186    /// # Examples
187    ///
188    /// ```no_run
189    /// use monoio::fs;
190    ///
191    /// #[monoio::main]
192    /// async fn main() -> std::io::Result<()> {
193    ///    let metadata = fs::metadata("foo.txt").await?;
194    ///
195    ///    println!("{:?}", metadata.modified());
196    ///    Ok(())
197    /// }
198    pub fn modified(&self) -> std::io::Result<SystemTime> {
199        let mtime = self.0.stat.st_mtime;
200        let mtime_nsec = self.0.stat.st_mtime_nsec as u32;
201
202        Ok(SystemTime::UNIX_EPOCH + std::time::Duration::new(mtime as u64, mtime_nsec))
203    }
204
205    /// Returns the last access time listed in this metadata.
206    ///
207    /// # Examples
208    ///
209    /// ```no_run
210    /// use monoio::fs;
211    ///
212    /// #[monoio::main]
213    /// async fn main() -> std::io::Result<()> {
214    ///     let metadata = fs::metadata("foo.txt").await?;
215    ///
216    ///     println!("{:?}", metadata.accessed());
217    ///     Ok(())
218    /// }
219    /// ```
220    pub fn accessed(&self) -> std::io::Result<SystemTime> {
221        let atime = self.0.stat.st_atime;
222        let atime_nsec = self.0.stat.st_atime_nsec as u32;
223
224        Ok(SystemTime::UNIX_EPOCH + std::time::Duration::new(atime as u64, atime_nsec))
225    }
226
227    /// Returns the creation time listed in this metadata.
228    ///
229    /// # Examples
230    ///
231    /// ```no_run
232    /// use monoio::fs;
233    ///
234    /// #[monoio::main]
235    /// async fn main() -> std::io::Result<()> {
236    ///     let metadata = fs::metadata("foo.txt").await?;
237    ///
238    ///     println!("{:?}", metadata.created());
239    ///     Ok(())
240    /// }
241    /// ```
242    #[cfg(target_os = "linux")]
243    pub fn created(&self) -> std::io::Result<SystemTime> {
244        if let Some(extra) = self.0.statx_extra_fields.as_ref() {
245            return if extra.stx_mask & libc::STATX_BTIME != 0 {
246                let btime = extra.stx_btime.tv_sec;
247                let btime_nsec = extra.stx_btime.tv_nsec;
248
249                Ok(SystemTime::UNIX_EPOCH + std::time::Duration::new(btime as u64, btime_nsec))
250            } else {
251                Err(std::io::Error::other("Creation time is not available"))
252            };
253        }
254
255        Err(std::io::Error::other("Creation time is not available"))
256    }
257
258    /// Returns the permissions of the file this metadata is for.
259    ///
260    /// # Examples
261    ///
262    /// ```no_run
263    /// use monoio::fs;
264    ///
265    /// #[monoio::main]
266    /// async fn main() -> std::io::Result<()> {
267    ///     let metadata = fs::metadata("foo.txt").await?;
268    ///
269    ///     println!("{:?}", metadata.permissions());
270    ///     Ok(())
271    /// }
272    /// ```
273    #[cfg(unix)]
274    pub fn permissions(&self) -> Permissions {
275        use super::permissions::Permissions;
276
277        Permissions(self.0.perm())
278    }
279
280    /// Returns the file type for this metadata.
281    ///
282    /// # Examples
283    ///
284    /// ```no_run
285    /// use monoio::fs;
286    ///
287    /// #[monoio::main]
288    /// async fn main() -> std::io::Result<()> {
289    ///     let metadata = fs::metadata("foo.txt").await?;
290    ///
291    ///     println!("{:?}", metadata.file_type());
292    ///     Ok(())
293    /// }
294    /// ```
295    #[cfg(unix)]
296    pub fn file_type(&self) -> FileType {
297        self.0.file_type()
298    }
299}
300
301impl std::fmt::Debug for Metadata {
302    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
303        let mut debug = f.debug_struct("Metadata");
304        // debug.field("file_type", &self.file_type());
305        debug.field("permissions", &self.permissions());
306        debug.field("len", &self.len());
307        if let Ok(modified) = self.modified() {
308            debug.field("modified", &modified);
309        }
310        if let Ok(accessed) = self.accessed() {
311            debug.field("accessed", &accessed);
312        }
313        #[cfg(target_os = "linux")]
314        if let Ok(created) = self.created() {
315            debug.field("created", &created);
316        }
317        debug.finish_non_exhaustive()
318    }
319}
320
321#[cfg(all(target_os = "linux", not(target_pointer_width = "32")))]
322impl MetadataExt for Metadata {
323    fn dev(&self) -> u64 {
324        self.0.stat.st_dev
325    }
326
327    fn ino(&self) -> u64 {
328        self.0.stat.st_ino
329    }
330
331    fn mode(&self) -> u32 {
332        self.0.stat.st_mode
333    }
334
335    #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
336    fn nlink(&self) -> u64 {
337        self.0.stat.st_nlink.into()
338    }
339
340    /// longarch64 need the `into` convert.
341    #[allow(clippy::useless_conversion)]
342    #[cfg(not(any(target_arch = "aarch64", target_arch = "riscv64")))]
343    fn nlink(&self) -> u64 {
344        self.0.stat.st_nlink.into()
345    }
346
347    fn uid(&self) -> u32 {
348        self.0.stat.st_uid
349    }
350
351    fn gid(&self) -> u32 {
352        self.0.stat.st_gid
353    }
354
355    fn rdev(&self) -> u64 {
356        self.0.stat.st_rdev
357    }
358
359    fn size(&self) -> u64 {
360        self.0.stat.st_size as u64
361    }
362
363    fn atime(&self) -> i64 {
364        self.0.stat.st_atime
365    }
366
367    fn atime_nsec(&self) -> i64 {
368        self.0.stat.st_atime_nsec
369    }
370
371    fn mtime(&self) -> i64 {
372        self.0.stat.st_mtime
373    }
374
375    fn mtime_nsec(&self) -> i64 {
376        self.0.stat.st_mtime_nsec
377    }
378
379    fn ctime(&self) -> i64 {
380        self.0.stat.st_ctime
381    }
382
383    fn ctime_nsec(&self) -> i64 {
384        self.0.stat.st_ctime_nsec
385    }
386
387    fn blksize(&self) -> u64 {
388        self.0.stat.st_blksize as u64
389    }
390
391    fn blocks(&self) -> u64 {
392        self.0.stat.st_blocks as u64
393    }
394}
395
396#[cfg(all(
397    any(target_os = "macos", target_os = "freebsd"),
398    not(target_pointer_width = "32")
399))]
400impl MetadataExt for Metadata {
401    fn dev(&self) -> u64 {
402        self.0.stat.st_dev as u64
403    }
404
405    fn ino(&self) -> u64 {
406        self.0.stat.st_ino.into()
407    }
408
409    fn mode(&self) -> u32 {
410        self.0.stat.st_mode as u32
411    }
412
413    fn nlink(&self) -> u64 {
414        self.0.stat.st_nlink.into()
415    }
416
417    fn uid(&self) -> u32 {
418        self.0.stat.st_uid
419    }
420
421    fn gid(&self) -> u32 {
422        self.0.stat.st_gid
423    }
424
425    fn rdev(&self) -> u64 {
426        self.0.stat.st_rdev as u64
427    }
428
429    fn size(&self) -> u64 {
430        self.0.stat.st_size as u64
431    }
432
433    fn atime(&self) -> i64 {
434        self.0.stat.st_atime
435    }
436
437    fn atime_nsec(&self) -> i64 {
438        self.0.stat.st_atime_nsec
439    }
440
441    fn mtime(&self) -> i64 {
442        self.0.stat.st_mtime
443    }
444
445    fn mtime_nsec(&self) -> i64 {
446        self.0.stat.st_mtime_nsec
447    }
448
449    fn ctime(&self) -> i64 {
450        self.0.stat.st_ctime
451    }
452
453    fn ctime_nsec(&self) -> i64 {
454        self.0.stat.st_ctime_nsec
455    }
456
457    fn blksize(&self) -> u64 {
458        self.0.stat.st_blksize as u64
459    }
460
461    fn blocks(&self) -> u64 {
462        self.0.stat.st_blocks as u64
463    }
464}
465
466#[cfg(all(unix, target_pointer_width = "32"))]
467impl MetadataExt for Metadata {
468    fn dev(&self) -> u64 {
469        self.0.stat.st_dev.into()
470    }
471
472    fn ino(&self) -> u64 {
473        self.0.stat.st_ino.into()
474    }
475
476    fn mode(&self) -> u32 {
477        self.0.stat.st_mode
478    }
479
480    fn nlink(&self) -> u64 {
481        self.0.stat.st_nlink.into()
482    }
483
484    fn uid(&self) -> u32 {
485        self.0.stat.st_uid
486    }
487
488    fn gid(&self) -> u32 {
489        self.0.stat.st_gid
490    }
491
492    fn rdev(&self) -> u64 {
493        self.0.stat.st_rdev.into()
494    }
495
496    fn size(&self) -> u64 {
497        self.0.stat.st_size as u64
498    }
499
500    fn atime(&self) -> i64 {
501        self.0.stat.st_atime.into()
502    }
503
504    fn atime_nsec(&self) -> i64 {
505        self.0.stat.st_atime_nsec.into()
506    }
507
508    fn mtime(&self) -> i64 {
509        self.0.stat.st_mtime.into()
510    }
511
512    fn mtime_nsec(&self) -> i64 {
513        self.0.stat.st_mtime_nsec.into()
514    }
515
516    fn ctime(&self) -> i64 {
517        self.0.stat.st_ctime.into()
518    }
519
520    fn ctime_nsec(&self) -> i64 {
521        self.0.stat.st_ctime_nsec.into()
522    }
523
524    fn blksize(&self) -> u64 {
525        self.0.stat.st_blksize as u64
526    }
527
528    fn blocks(&self) -> u64 {
529        self.0.stat.st_blocks as u64
530    }
531}