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        #[cfg(not(musl_v1_2_3))]
200        {
201            let mtime = self.0.stat.st_mtime;
202            let mtime_nsec = self.0.stat.st_mtime_nsec as u32;
203
204            Ok(SystemTime::UNIX_EPOCH + std::time::Duration::new(mtime as u64, mtime_nsec))
205        }
206        #[cfg(musl_v1_2_3)]
207        {
208            let mtime = self.0.stat.st_mtim;
209            let mtime_nsec = mtime.tv_nsec as u32;
210
211            Ok(SystemTime::UNIX_EPOCH + std::time::Duration::new(mtime.tv_sec as u64, mtime_nsec))
212        }
213    }
214
215    /// Returns the last access time listed in this metadata.
216    ///
217    /// # Examples
218    ///
219    /// ```no_run
220    /// use monoio::fs;
221    ///
222    /// #[monoio::main]
223    /// async fn main() -> std::io::Result<()> {
224    ///     let metadata = fs::metadata("foo.txt").await?;
225    ///
226    ///     println!("{:?}", metadata.accessed());
227    ///     Ok(())
228    /// }
229    /// ```
230    pub fn accessed(&self) -> std::io::Result<SystemTime> {
231        #[cfg(not(musl_v1_2_3))]
232        {
233            let atime = self.0.stat.st_atime;
234            let atime_nsec = self.0.stat.st_atime_nsec as u32;
235
236            Ok(SystemTime::UNIX_EPOCH + std::time::Duration::new(atime as u64, atime_nsec))
237        }
238        #[cfg(musl_v1_2_3)]
239        {
240            let atime = self.0.stat.st_atim;
241            let atime_nsec = atime.tv_nsec as u32;
242
243            Ok(SystemTime::UNIX_EPOCH + std::time::Duration::new(atime.tv_sec as u64, atime_nsec))
244        }
245    }
246
247    /// Returns the creation time listed in this metadata.
248    ///
249    /// # Examples
250    ///
251    /// ```no_run
252    /// use monoio::fs;
253    ///
254    /// #[monoio::main]
255    /// async fn main() -> std::io::Result<()> {
256    ///     let metadata = fs::metadata("foo.txt").await?;
257    ///
258    ///     println!("{:?}", metadata.created());
259    ///     Ok(())
260    /// }
261    /// ```
262    #[cfg(target_os = "linux")]
263    pub fn created(&self) -> std::io::Result<SystemTime> {
264        if let Some(extra) = self.0.statx_extra_fields.as_ref() {
265            return if extra.stx_mask & libc::STATX_BTIME != 0 {
266                let btime = extra.stx_btime.tv_sec;
267                let btime_nsec = extra.stx_btime.tv_nsec;
268
269                Ok(SystemTime::UNIX_EPOCH + std::time::Duration::new(btime as u64, btime_nsec))
270            } else {
271                Err(std::io::Error::other("Creation time is not available"))
272            };
273        }
274
275        Err(std::io::Error::other("Creation time is not available"))
276    }
277
278    /// Returns the permissions of the file this metadata is for.
279    ///
280    /// # Examples
281    ///
282    /// ```no_run
283    /// use monoio::fs;
284    ///
285    /// #[monoio::main]
286    /// async fn main() -> std::io::Result<()> {
287    ///     let metadata = fs::metadata("foo.txt").await?;
288    ///
289    ///     println!("{:?}", metadata.permissions());
290    ///     Ok(())
291    /// }
292    /// ```
293    #[cfg(unix)]
294    pub fn permissions(&self) -> Permissions {
295        use super::permissions::Permissions;
296
297        Permissions(self.0.perm())
298    }
299
300    /// Returns the file type for this metadata.
301    ///
302    /// # Examples
303    ///
304    /// ```no_run
305    /// use monoio::fs;
306    ///
307    /// #[monoio::main]
308    /// async fn main() -> std::io::Result<()> {
309    ///     let metadata = fs::metadata("foo.txt").await?;
310    ///
311    ///     println!("{:?}", metadata.file_type());
312    ///     Ok(())
313    /// }
314    /// ```
315    #[cfg(unix)]
316    pub fn file_type(&self) -> FileType {
317        self.0.file_type()
318    }
319}
320
321impl std::fmt::Debug for Metadata {
322    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
323        let mut debug = f.debug_struct("Metadata");
324        // debug.field("file_type", &self.file_type());
325        debug.field("permissions", &self.permissions());
326        debug.field("len", &self.len());
327        if let Ok(modified) = self.modified() {
328            debug.field("modified", &modified);
329        }
330        if let Ok(accessed) = self.accessed() {
331            debug.field("accessed", &accessed);
332        }
333        #[cfg(target_os = "linux")]
334        if let Ok(created) = self.created() {
335            debug.field("created", &created);
336        }
337        debug.finish_non_exhaustive()
338    }
339}
340
341#[cfg(all(target_os = "linux", not(target_pointer_width = "32")))]
342impl MetadataExt for Metadata {
343    fn dev(&self) -> u64 {
344        self.0.stat.st_dev
345    }
346
347    fn ino(&self) -> u64 {
348        self.0.stat.st_ino
349    }
350
351    fn mode(&self) -> u32 {
352        self.0.stat.st_mode
353    }
354
355    #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
356    fn nlink(&self) -> u64 {
357        self.0.stat.st_nlink.into()
358    }
359
360    /// longarch64 need the `into` convert.
361    #[allow(clippy::useless_conversion)]
362    #[cfg(not(any(target_arch = "aarch64", target_arch = "riscv64")))]
363    fn nlink(&self) -> u64 {
364        self.0.stat.st_nlink.into()
365    }
366
367    fn uid(&self) -> u32 {
368        self.0.stat.st_uid
369    }
370
371    fn gid(&self) -> u32 {
372        self.0.stat.st_gid
373    }
374
375    fn rdev(&self) -> u64 {
376        self.0.stat.st_rdev
377    }
378
379    fn size(&self) -> u64 {
380        self.0.stat.st_size as u64
381    }
382
383    fn atime(&self) -> i64 {
384        #[cfg(not(musl_v1_2_3))]
385        let atime = self.0.stat.st_atime;
386        #[cfg(musl_v1_2_3)]
387        let atime = self.0.stat.st_atim.tv_sec;
388
389        atime
390    }
391
392    fn atime_nsec(&self) -> i64 {
393        #[cfg(not(musl_v1_2_3))]
394        let atime_nsec = self.0.stat.st_atime_nsec;
395        #[cfg(musl_v1_2_3)]
396        let atime_nsec = self.0.stat.st_atim.tv_nsec;
397
398        atime_nsec
399    }
400
401    fn mtime(&self) -> i64 {
402        #[cfg(not(musl_v1_2_3))]
403        let mtime = self.0.stat.st_mtime;
404        #[cfg(musl_v1_2_3)]
405        let mtime = self.0.stat.st_mtim.tv_sec;
406
407        mtime
408    }
409
410    fn mtime_nsec(&self) -> i64 {
411        #[cfg(not(musl_v1_2_3))]
412        let mtime_nsec = self.0.stat.st_mtime_nsec;
413        #[cfg(musl_v1_2_3)]
414        let mtime_nsec = self.0.stat.st_mtim.tv_nsec;
415
416        mtime_nsec
417    }
418
419    fn ctime(&self) -> i64 {
420        #[cfg(not(musl_v1_2_3))]
421        let ctime = self.0.stat.st_ctime;
422        #[cfg(musl_v1_2_3)]
423        let ctime = self.0.stat.st_ctim.tv_sec;
424
425        ctime
426    }
427
428    fn ctime_nsec(&self) -> i64 {
429        #[cfg(not(musl_v1_2_3))]
430        let ctime_nsec = self.0.stat.st_ctime_nsec;
431        #[cfg(musl_v1_2_3)]
432        let ctime_nsec = self.0.stat.st_ctim.tv_nsec;
433
434        ctime_nsec
435    }
436
437    fn blksize(&self) -> u64 {
438        self.0.stat.st_blksize as u64
439    }
440
441    fn blocks(&self) -> u64 {
442        self.0.stat.st_blocks as u64
443    }
444}
445
446#[cfg(all(
447    any(target_os = "macos", target_os = "freebsd"),
448    not(target_pointer_width = "32")
449))]
450impl MetadataExt for Metadata {
451    fn dev(&self) -> u64 {
452        self.0.stat.st_dev as u64
453    }
454
455    fn ino(&self) -> u64 {
456        self.0.stat.st_ino.into()
457    }
458
459    fn mode(&self) -> u32 {
460        self.0.stat.st_mode as u32
461    }
462
463    fn nlink(&self) -> u64 {
464        self.0.stat.st_nlink.into()
465    }
466
467    fn uid(&self) -> u32 {
468        self.0.stat.st_uid
469    }
470
471    fn gid(&self) -> u32 {
472        self.0.stat.st_gid
473    }
474
475    fn rdev(&self) -> u64 {
476        self.0.stat.st_rdev as u64
477    }
478
479    fn size(&self) -> u64 {
480        self.0.stat.st_size as u64
481    }
482
483    fn atime(&self) -> i64 {
484        self.0.stat.st_atime
485    }
486
487    fn atime_nsec(&self) -> i64 {
488        self.0.stat.st_atime_nsec
489    }
490
491    fn mtime(&self) -> i64 {
492        self.0.stat.st_mtime
493    }
494
495    fn mtime_nsec(&self) -> i64 {
496        self.0.stat.st_mtime_nsec
497    }
498
499    fn ctime(&self) -> i64 {
500        self.0.stat.st_ctime
501    }
502
503    fn ctime_nsec(&self) -> i64 {
504        self.0.stat.st_ctime_nsec
505    }
506
507    fn blksize(&self) -> u64 {
508        self.0.stat.st_blksize as u64
509    }
510
511    fn blocks(&self) -> u64 {
512        self.0.stat.st_blocks as u64
513    }
514}
515
516#[cfg(all(unix, target_pointer_width = "32"))]
517impl MetadataExt for Metadata {
518    fn dev(&self) -> u64 {
519        self.0.stat.st_dev.into()
520    }
521
522    fn ino(&self) -> u64 {
523        self.0.stat.st_ino.into()
524    }
525
526    fn mode(&self) -> u32 {
527        self.0.stat.st_mode
528    }
529
530    fn nlink(&self) -> u64 {
531        self.0.stat.st_nlink.into()
532    }
533
534    fn uid(&self) -> u32 {
535        self.0.stat.st_uid
536    }
537
538    fn gid(&self) -> u32 {
539        self.0.stat.st_gid
540    }
541
542    fn rdev(&self) -> u64 {
543        self.0.stat.st_rdev.into()
544    }
545
546    fn size(&self) -> u64 {
547        self.0.stat.st_size as u64
548    }
549
550    fn atime(&self) -> i64 {
551        #[cfg(not(musl_v1_2_3))]
552        let atime = self.0.stat.st_atime;
553        #[cfg(musl_v1_2_3)]
554        let atime = self.0.stat.st_atim.tv_sec;
555
556        atime.into()
557    }
558
559    fn atime_nsec(&self) -> i64 {
560        #[cfg(not(musl_v1_2_3))]
561        let atime_nsec = self.0.stat.st_atime_nsec;
562        #[cfg(musl_v1_2_3)]
563        let atime_nsec = self.0.stat.st_atim.tv_nsec;
564
565        atime_nsec.into()
566    }
567
568    fn mtime(&self) -> i64 {
569        #[cfg(not(musl_v1_2_3))]
570        let mtime = self.0.stat.st_mtime;
571        #[cfg(musl_v1_2_3)]
572        let mtime = self.0.stat.st_mtim.tv_sec;
573
574        mtime.into()
575    }
576
577    fn mtime_nsec(&self) -> i64 {
578        #[cfg(not(musl_v1_2_3))]
579        let mtime_nsec = self.0.stat.st_mtime_nsec;
580        #[cfg(musl_v1_2_3)]
581        let mtime_nsec = self.0.stat.st_mtim.tv_nsec;
582
583        mtime_nsec.into()
584    }
585
586    fn ctime(&self) -> i64 {
587        #[cfg(not(musl_v1_2_3))]
588        let ctime = self.0.stat.st_ctime;
589        #[cfg(musl_v1_2_3)]
590        let ctime = self.0.stat.st_ctim.tv_sec;
591
592        ctime.into()
593    }
594
595    fn ctime_nsec(&self) -> i64 {
596        #[cfg(not(musl_v1_2_3))]
597        let ctime_nsec = self.0.stat.st_ctime_nsec;
598        #[cfg(musl_v1_2_3)]
599        let ctime_nsec = self.0.stat.st_ctim.tv_nsec;
600
601        ctime_nsec.into()
602    }
603
604    fn blksize(&self) -> u64 {
605        self.0.stat.st_blksize as u64
606    }
607
608    fn blocks(&self) -> u64 {
609        self.0.stat.st_blocks as u64
610    }
611}