@@ -20,6 +20,10 @@ use libc::{stat64, fstat64, lstat64, off64_t, ftruncate64, lseek64, dirent64, re
2020use libc:: fstatat64;
2121#[ cfg( any( target_os = "linux" , target_os = "emscripten" , target_os = "android" ) ) ]
2222use libc:: dirfd;
23+ // We only use struct `statx`, not the function `statx`.
24+ // Instead, use `syscall` to check if it is available at runtime.
25+ #[ cfg( target_os = "linux" ) ]
26+ use libc:: { statx, makedev} ;
2327#[ cfg( target_os = "android" ) ]
2428use libc:: { stat as stat64, fstat as fstat64, fstatat as fstatat64, lstat as lstat64, lseek64,
2529 dirent as dirent64, open as open64} ;
@@ -44,6 +48,82 @@ pub struct File(FileDesc);
4448#[ derive( Clone ) ]
4549pub struct FileAttr {
4650 stat : stat64 ,
51+ #[ cfg( target_os = "linux" ) ]
52+ statx_extra_fields : Option < StatxExtraFields > ,
53+ }
54+
55+ #[ derive( Clone ) ]
56+ struct StatxExtraFields {
57+ // This is needed to check if btime is supported by the filesystem.
58+ stx_mask : u32 ,
59+ stx_btime : libc:: statx_timestamp ,
60+ }
61+
62+ // We prefer `statx` if available, which contains file creation time.
63+ #[ cfg( target_os = "linux" ) ]
64+ unsafe fn try_statx (
65+ fd : c_int ,
66+ path : * const libc:: c_char ,
67+ flags : i32 ,
68+ mask : u32 ,
69+ ) -> Option < io:: Result < FileAttr > > {
70+ use crate :: sync:: atomic:: { AtomicBool , Ordering } ;
71+
72+ // Linux kernel prior to 4.11 or glibc prior to glibc 2.28 don't support `statx`
73+ // We store the availability in a global to avoid unnecessary syscalls
74+ static HAS_STATX : AtomicBool = AtomicBool :: new ( true ) ;
75+ syscall ! {
76+ fn statx(
77+ fd: c_int,
78+ pathname: * const libc:: c_char,
79+ flags: c_int,
80+ mask: libc:: c_uint,
81+ statxbuf: * mut statx
82+ ) -> c_int
83+ }
84+
85+ if !HAS_STATX . load ( Ordering :: Relaxed ) {
86+ return None ;
87+ }
88+
89+ let mut buf: statx = mem:: zeroed ( ) ;
90+ let ret = cvt ( statx ( fd, path, flags, mask, & mut buf) ) ;
91+ match ret {
92+ Err ( err) => match err. raw_os_error ( ) {
93+ Some ( libc:: ENOSYS ) => {
94+ HAS_STATX . store ( false , Ordering :: Relaxed ) ;
95+ return None ;
96+ }
97+ _ => return Some ( Err ( err) ) ,
98+ }
99+ Ok ( _) => {
100+ // We cannot fill `stat64` exhaustively because of private padding fields.
101+ let mut stat: stat64 = mem:: zeroed ( ) ;
102+ stat. st_dev = makedev ( buf. stx_dev_major , buf. stx_dev_minor ) ;
103+ stat. st_ino = buf. stx_ino ;
104+ stat. st_nlink = buf. stx_nlink as u64 ;
105+ stat. st_mode = buf. stx_mode as u32 ;
106+ stat. st_uid = buf. stx_uid ;
107+ stat. st_gid = buf. stx_gid ;
108+ stat. st_rdev = makedev ( buf. stx_rdev_major , buf. stx_rdev_minor ) ;
109+ stat. st_size = buf. stx_size as i64 ;
110+ stat. st_blksize = buf. stx_blksize as i64 ;
111+ stat. st_blocks = buf. stx_blocks as i64 ;
112+ stat. st_atime = buf. stx_atime . tv_sec ;
113+ stat. st_atime_nsec = buf. stx_atime . tv_nsec as i64 ;
114+ stat. st_mtime = buf. stx_mtime . tv_sec ;
115+ stat. st_mtime_nsec = buf. stx_mtime . tv_nsec as i64 ;
116+ stat. st_ctime = buf. stx_ctime . tv_sec ;
117+ stat. st_ctime_nsec = buf. stx_ctime . tv_nsec as i64 ;
118+
119+ let extra = StatxExtraFields {
120+ stx_mask : buf. stx_mask ,
121+ stx_btime : buf. stx_btime ,
122+ } ;
123+
124+ Some ( Ok ( FileAttr { stat, statx_extra_fields : Some ( extra) } ) )
125+ }
126+ }
47127}
48128
49129// all DirEntry's will have a reference to this struct
@@ -148,6 +228,26 @@ impl FileAttr {
148228 } ) )
149229 }
150230
231+ #[ cfg( target_os = "linux" ) ]
232+ pub fn created ( & self ) -> io:: Result < SystemTime > {
233+ match & self . statx_extra_fields {
234+ Some ( ext) if ( ext. stx_mask & libc:: STATX_BTIME ) != 0 => {
235+ Ok ( SystemTime :: from ( libc:: timespec {
236+ tv_sec : ext. stx_btime . tv_sec as libc:: time_t ,
237+ tv_nsec : ext. stx_btime . tv_nsec as libc:: c_long ,
238+ } ) )
239+ }
240+ Some ( _) => Err ( io:: Error :: new (
241+ io:: ErrorKind :: Other ,
242+ "creation time is not available for the filesystam" ,
243+ ) ) ,
244+ None => Err ( io:: Error :: new (
245+ io:: ErrorKind :: Other ,
246+ "creation time is not available on this platform currently" ,
247+ ) ) ,
248+ }
249+ }
250+
151251 #[ cfg( any( target_os = "freebsd" ,
152252 target_os = "openbsd" ,
153253 target_os = "macos" ,
@@ -159,7 +259,8 @@ impl FileAttr {
159259 } ) )
160260 }
161261
162- #[ cfg( not( any( target_os = "freebsd" ,
262+ #[ cfg( not( any( target_os = "linux" ,
263+ target_os = "freebsd" ,
163264 target_os = "openbsd" ,
164265 target_os = "macos" ,
165266 target_os = "ios" ) ) ) ]
@@ -304,7 +405,23 @@ impl DirEntry {
304405 OsStr :: from_bytes ( self . name_bytes ( ) ) . to_os_string ( )
305406 }
306407
307- #[ cfg( any( target_os = "linux" , target_os = "emscripten" , target_os = "android" ) ) ]
408+ #[ cfg( target_os = "linux" ) ]
409+ pub fn metadata ( & self ) -> io:: Result < FileAttr > {
410+ let dir_fd = cvt ( unsafe { dirfd ( self . dir . inner . dirp . 0 ) } ) ?;
411+ let pathname = self . entry . d_name . as_ptr ( ) ;
412+ unsafe { try_statx (
413+ dir_fd,
414+ pathname,
415+ libc:: AT_SYMLINK_NOFOLLOW | libc:: AT_STATX_SYNC_AS_STAT ,
416+ libc:: STATX_ALL ,
417+ ) } . unwrap_or_else ( || {
418+ let mut stat = unsafe { mem:: zeroed ( ) } ;
419+ cvt ( unsafe { fstatat64 ( dir_fd, pathname, & mut stat, libc:: AT_SYMLINK_NOFOLLOW ) } ) ?;
420+ Ok ( FileAttr { stat, statx_extra_fields : None } )
421+ } )
422+ }
423+
424+ #[ cfg( any( target_os = "emscripten" , target_os = "android" ) ) ]
308425 pub fn metadata ( & self ) -> io:: Result < FileAttr > {
309426 let fd = cvt ( unsafe { dirfd ( self . dir . inner . dirp . 0 ) } ) ?;
310427 let mut stat: stat64 = unsafe { mem:: zeroed ( ) } ;
@@ -516,6 +633,22 @@ impl File {
516633 Ok ( File ( fd) )
517634 }
518635
636+ #[ cfg( target_os = "linux" ) ]
637+ pub fn file_attr ( & self ) -> io:: Result < FileAttr > {
638+ let fd = self . 0 . raw ( ) ;
639+ unsafe { try_statx (
640+ fd,
641+ b"\0 " as * const _ as * const libc:: c_char ,
642+ libc:: AT_EMPTY_PATH | libc:: AT_STATX_SYNC_AS_STAT ,
643+ libc:: STATX_ALL ,
644+ ) } . unwrap_or_else ( || {
645+ let mut stat = unsafe { mem:: zeroed ( ) } ;
646+ cvt ( unsafe { fstat64 ( fd, & mut stat) } ) ?;
647+ Ok ( FileAttr { stat, statx_extra_fields : None } )
648+ } )
649+ }
650+
651+ #[ cfg( not( target_os = "linux" ) ) ]
519652 pub fn file_attr ( & self ) -> io:: Result < FileAttr > {
520653 let mut stat: stat64 = unsafe { mem:: zeroed ( ) } ;
521654 cvt ( unsafe {
@@ -796,6 +929,23 @@ pub fn link(src: &Path, dst: &Path) -> io::Result<()> {
796929 Ok ( ( ) )
797930}
798931
932+ #[ cfg( target_os = "linux" ) ]
933+ pub fn stat ( p : & Path ) -> io:: Result < FileAttr > {
934+ let p = cstr ( p) ?;
935+ let p = p. as_ptr ( ) ;
936+ unsafe { try_statx (
937+ libc:: AT_FDCWD ,
938+ p,
939+ libc:: AT_STATX_SYNC_AS_STAT ,
940+ libc:: STATX_ALL ,
941+ ) } . unwrap_or_else ( || {
942+ let mut stat = unsafe { mem:: zeroed ( ) } ;
943+ cvt ( unsafe { stat64 ( p, & mut stat) } ) ?;
944+ Ok ( FileAttr { stat, statx_extra_fields : None } )
945+ } )
946+ }
947+
948+ #[ cfg( not( target_os = "linux" ) ) ]
799949pub fn stat ( p : & Path ) -> io:: Result < FileAttr > {
800950 let p = cstr ( p) ?;
801951 let mut stat: stat64 = unsafe { mem:: zeroed ( ) } ;
@@ -805,6 +955,23 @@ pub fn stat(p: &Path) -> io::Result<FileAttr> {
805955 Ok ( FileAttr { stat } )
806956}
807957
958+ #[ cfg( target_os = "linux" ) ]
959+ pub fn lstat ( p : & Path ) -> io:: Result < FileAttr > {
960+ let p = cstr ( p) ?;
961+ let p = p. as_ptr ( ) ;
962+ unsafe { try_statx (
963+ libc:: AT_FDCWD ,
964+ p,
965+ libc:: AT_SYMLINK_NOFOLLOW | libc:: AT_STATX_SYNC_AS_STAT ,
966+ libc:: STATX_ALL ,
967+ ) } . unwrap_or_else ( || {
968+ let mut stat = unsafe { mem:: zeroed ( ) } ;
969+ cvt ( unsafe { lstat64 ( p, & mut stat) } ) ?;
970+ Ok ( FileAttr { stat, statx_extra_fields : None } )
971+ } )
972+ }
973+
974+ #[ cfg( not( target_os = "linux" ) ) ]
808975pub fn lstat ( p : & Path ) -> io:: Result < FileAttr > {
809976 let p = cstr ( p) ?;
810977 let mut stat: stat64 = unsafe { mem:: zeroed ( ) } ;
0 commit comments