@@ -41,11 +41,136 @@ pub use crate::sys_common::fs::remove_dir_all;
4141
4242pub struct File ( FileDesc ) ;
4343
44- #[ derive( Clone ) ]
45- pub struct FileAttr {
46- stat : stat64 ,
44+ // FIXME: This should be available on Linux with all `target_arch` and `target_env`.
45+ // https://github.com/rust-lang/libc/issues/1545
46+ macro_rules! cfg_has_statx {
47+ ( { $( $then_tt: tt) * } else { $( $else_tt: tt) * } ) => {
48+ cfg_if:: cfg_if! {
49+ if #[ cfg( all( target_os = "linux" , target_env = "gnu" , any(
50+ target_arch = "x86" ,
51+ target_arch = "arm" ,
52+ // target_arch = "mips",
53+ target_arch = "powerpc" ,
54+ target_arch = "x86_64" ,
55+ // target_arch = "aarch64",
56+ target_arch = "powerpc64" ,
57+ // target_arch = "mips64",
58+ // target_arch = "s390x",
59+ target_arch = "sparc64" ,
60+ ) ) ) ] {
61+ $( $then_tt) *
62+ } else {
63+ $( $else_tt) *
64+ }
65+ }
66+ } ;
67+ ( $( $block_inner: tt) * ) => {
68+ #[ cfg( all( target_os = "linux" , target_env = "gnu" , any(
69+ target_arch = "x86" ,
70+ target_arch = "arm" ,
71+ // target_arch = "mips",
72+ target_arch = "powerpc" ,
73+ target_arch = "x86_64" ,
74+ // target_arch = "aarch64",
75+ target_arch = "powerpc64" ,
76+ // target_arch = "mips64",
77+ // target_arch = "s390x",
78+ target_arch = "sparc64" ,
79+ ) ) ) ]
80+ {
81+ $( $block_inner) *
82+ }
83+ } ;
4784}
4885
86+ cfg_has_statx ! { {
87+ #[ derive( Clone ) ]
88+ pub struct FileAttr {
89+ stat: stat64,
90+ statx_extra_fields: Option <StatxExtraFields >,
91+ }
92+
93+ #[ derive( Clone ) ]
94+ struct StatxExtraFields {
95+ // This is needed to check if btime is supported by the filesystem.
96+ stx_mask: u32 ,
97+ stx_btime: libc:: statx_timestamp,
98+ }
99+
100+ // We prefer `statx` on Linux if available, which contains file creation time.
101+ // Default `stat64` contains no creation time.
102+ unsafe fn try_statx(
103+ fd: c_int,
104+ path: * const libc:: c_char,
105+ flags: i32 ,
106+ mask: u32 ,
107+ ) -> Option <io:: Result <FileAttr >> {
108+ use crate :: sync:: atomic:: { AtomicBool , Ordering } ;
109+
110+ // Linux kernel prior to 4.11 or glibc prior to glibc 2.28 don't support `statx`
111+ // We store the availability in a global to avoid unnecessary syscalls
112+ static HAS_STATX : AtomicBool = AtomicBool :: new( true ) ;
113+ syscall! {
114+ fn statx(
115+ fd: c_int,
116+ pathname: * const libc:: c_char,
117+ flags: c_int,
118+ mask: libc:: c_uint,
119+ statxbuf: * mut libc:: statx
120+ ) -> c_int
121+ }
122+
123+ if !HAS_STATX . load( Ordering :: Relaxed ) {
124+ return None ;
125+ }
126+
127+ let mut buf: libc:: statx = mem:: zeroed( ) ;
128+ let ret = cvt( statx( fd, path, flags, mask, & mut buf) ) ;
129+ match ret {
130+ Err ( err) => match err. raw_os_error( ) {
131+ Some ( libc:: ENOSYS ) => {
132+ HAS_STATX . store( false , Ordering :: Relaxed ) ;
133+ return None ;
134+ }
135+ _ => return Some ( Err ( err) ) ,
136+ }
137+ Ok ( _) => {
138+ // We cannot fill `stat64` exhaustively because of private padding fields.
139+ let mut stat: stat64 = mem:: zeroed( ) ;
140+ stat. st_dev = libc:: makedev( buf. stx_dev_major, buf. stx_dev_minor) ;
141+ stat. st_ino = buf. stx_ino as libc:: ino64_t;
142+ stat. st_nlink = buf. stx_nlink as libc:: nlink_t;
143+ stat. st_mode = buf. stx_mode as libc:: mode_t;
144+ stat. st_uid = buf. stx_uid as libc:: uid_t;
145+ stat. st_gid = buf. stx_gid as libc:: gid_t;
146+ stat. st_rdev = libc:: makedev( buf. stx_rdev_major, buf. stx_rdev_minor) ;
147+ stat. st_size = buf. stx_size as off64_t;
148+ stat. st_blksize = buf. stx_blksize as libc:: blksize_t;
149+ stat. st_blocks = buf. stx_blocks as libc:: blkcnt64_t;
150+ stat. st_atime = buf. stx_atime. tv_sec as libc:: time_t;
151+ stat. st_atime_nsec = buf. stx_atime. tv_nsec as libc:: c_long;
152+ stat. st_mtime = buf. stx_mtime. tv_sec as libc:: time_t;
153+ stat. st_mtime_nsec = buf. stx_mtime. tv_nsec as libc:: c_long;
154+ stat. st_ctime = buf. stx_ctime. tv_sec as libc:: time_t;
155+ stat. st_ctime_nsec = buf. stx_ctime. tv_nsec as libc:: c_long;
156+
157+ let extra = StatxExtraFields {
158+ stx_mask: buf. stx_mask,
159+ stx_btime: buf. stx_btime,
160+ } ;
161+
162+ Some ( Ok ( FileAttr { stat, statx_extra_fields: Some ( extra) } ) )
163+ }
164+ }
165+ }
166+
167+ } else {
168+ #[ derive( Clone ) ]
169+ pub struct FileAttr {
170+ stat: stat64,
171+ }
172+ } }
173+
49174// all DirEntry's will have a reference to this struct
50175struct InnerReadDir {
51176 dirp : Dir ,
@@ -97,6 +222,20 @@ pub struct FileType { mode: mode_t }
97222#[ derive( Debug ) ]
98223pub struct DirBuilder { mode : mode_t }
99224
225+ cfg_has_statx ! { {
226+ impl FileAttr {
227+ fn from_stat64( stat: stat64) -> Self {
228+ Self { stat, statx_extra_fields: None }
229+ }
230+ }
231+ } else {
232+ impl FileAttr {
233+ fn from_stat64( stat: stat64) -> Self {
234+ Self { stat }
235+ }
236+ }
237+ } }
238+
100239impl FileAttr {
101240 pub fn size ( & self ) -> u64 { self . stat . st_size as u64 }
102241 pub fn perm ( & self ) -> FilePermissions {
@@ -164,6 +303,22 @@ impl FileAttr {
164303 target_os = "macos" ,
165304 target_os = "ios" ) ) ) ]
166305 pub fn created ( & self ) -> io:: Result < SystemTime > {
306+ cfg_has_statx ! {
307+ if let Some ( ext) = & self . statx_extra_fields {
308+ return if ( ext. stx_mask & libc:: STATX_BTIME ) != 0 {
309+ Ok ( SystemTime :: from( libc:: timespec {
310+ tv_sec: ext. stx_btime. tv_sec as libc:: time_t,
311+ tv_nsec: ext. stx_btime. tv_nsec as libc:: c_long,
312+ } ) )
313+ } else {
314+ Err ( io:: Error :: new(
315+ io:: ErrorKind :: Other ,
316+ "creation time is not available for the filesystem" ,
317+ ) )
318+ } ;
319+ }
320+ }
321+
167322 Err ( io:: Error :: new ( io:: ErrorKind :: Other ,
168323 "creation time is not available on this platform \
169324 currently") )
@@ -306,12 +461,25 @@ impl DirEntry {
306461
307462 #[ cfg( any( target_os = "linux" , target_os = "emscripten" , target_os = "android" ) ) ]
308463 pub fn metadata ( & self ) -> io:: Result < FileAttr > {
309- let fd = cvt ( unsafe { dirfd ( self . dir . inner . dirp . 0 ) } ) ?;
464+ let fd = cvt ( unsafe { dirfd ( self . dir . inner . dirp . 0 ) } ) ?;
465+ let name = self . entry . d_name . as_ptr ( ) ;
466+
467+ cfg_has_statx ! {
468+ if let Some ( ret) = unsafe { try_statx(
469+ fd,
470+ name,
471+ libc:: AT_SYMLINK_NOFOLLOW | libc:: AT_STATX_SYNC_AS_STAT ,
472+ libc:: STATX_ALL ,
473+ ) } {
474+ return ret;
475+ }
476+ }
477+
310478 let mut stat: stat64 = unsafe { mem:: zeroed ( ) } ;
311479 cvt ( unsafe {
312- fstatat64 ( fd, self . entry . d_name . as_ptr ( ) , & mut stat, libc:: AT_SYMLINK_NOFOLLOW )
480+ fstatat64 ( fd, name , & mut stat, libc:: AT_SYMLINK_NOFOLLOW )
313481 } ) ?;
314- Ok ( FileAttr { stat } )
482+ Ok ( FileAttr :: from_stat64 ( stat) )
315483 }
316484
317485 #[ cfg( not( any( target_os = "linux" , target_os = "emscripten" , target_os = "android" ) ) ) ]
@@ -517,11 +685,24 @@ impl File {
517685 }
518686
519687 pub fn file_attr ( & self ) -> io:: Result < FileAttr > {
688+ let fd = self . 0 . raw ( ) ;
689+
690+ cfg_has_statx ! {
691+ if let Some ( ret) = unsafe { try_statx(
692+ fd,
693+ b"\0 " as * const _ as * const libc:: c_char,
694+ libc:: AT_EMPTY_PATH | libc:: AT_STATX_SYNC_AS_STAT ,
695+ libc:: STATX_ALL ,
696+ ) } {
697+ return ret;
698+ }
699+ }
700+
520701 let mut stat: stat64 = unsafe { mem:: zeroed ( ) } ;
521702 cvt ( unsafe {
522- fstat64 ( self . 0 . raw ( ) , & mut stat)
703+ fstat64 ( fd , & mut stat)
523704 } ) ?;
524- Ok ( FileAttr { stat } )
705+ Ok ( FileAttr :: from_stat64 ( stat) )
525706 }
526707
527708 pub fn fsync ( & self ) -> io:: Result < ( ) > {
@@ -798,20 +979,44 @@ pub fn link(src: &Path, dst: &Path) -> io::Result<()> {
798979
799980pub fn stat ( p : & Path ) -> io:: Result < FileAttr > {
800981 let p = cstr ( p) ?;
982+
983+ cfg_has_statx ! {
984+ if let Some ( ret) = unsafe { try_statx(
985+ libc:: AT_FDCWD ,
986+ p. as_ptr( ) ,
987+ libc:: AT_STATX_SYNC_AS_STAT ,
988+ libc:: STATX_ALL ,
989+ ) } {
990+ return ret;
991+ }
992+ }
993+
801994 let mut stat: stat64 = unsafe { mem:: zeroed ( ) } ;
802995 cvt ( unsafe {
803996 stat64 ( p. as_ptr ( ) , & mut stat)
804997 } ) ?;
805- Ok ( FileAttr { stat } )
998+ Ok ( FileAttr :: from_stat64 ( stat) )
806999}
8071000
8081001pub fn lstat ( p : & Path ) -> io:: Result < FileAttr > {
8091002 let p = cstr ( p) ?;
1003+
1004+ cfg_has_statx ! {
1005+ if let Some ( ret) = unsafe { try_statx(
1006+ libc:: AT_FDCWD ,
1007+ p. as_ptr( ) ,
1008+ libc:: AT_SYMLINK_NOFOLLOW | libc:: AT_STATX_SYNC_AS_STAT ,
1009+ libc:: STATX_ALL ,
1010+ ) } {
1011+ return ret;
1012+ }
1013+ }
1014+
8101015 let mut stat: stat64 = unsafe { mem:: zeroed ( ) } ;
8111016 cvt ( unsafe {
8121017 lstat64 ( p. as_ptr ( ) , & mut stat)
8131018 } ) ?;
814- Ok ( FileAttr { stat } )
1019+ Ok ( FileAttr :: from_stat64 ( stat) )
8151020}
8161021
8171022pub fn canonicalize ( p : & Path ) -> io:: Result < PathBuf > {
0 commit comments