@@ -41,11 +41,136 @@ pub use crate::sys_common::fs::remove_dir_all;
41
41
42
42
pub struct File ( FileDesc ) ;
43
43
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
+ } ;
47
84
}
48
85
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
+
49
174
// all DirEntry's will have a reference to this struct
50
175
struct InnerReadDir {
51
176
dirp : Dir ,
@@ -97,6 +222,20 @@ pub struct FileType { mode: mode_t }
97
222
#[ derive( Debug ) ]
98
223
pub struct DirBuilder { mode : mode_t }
99
224
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
+
100
239
impl FileAttr {
101
240
pub fn size ( & self ) -> u64 { self . stat . st_size as u64 }
102
241
pub fn perm ( & self ) -> FilePermissions {
@@ -164,6 +303,22 @@ impl FileAttr {
164
303
target_os = "macos" ,
165
304
target_os = "ios" ) ) ) ]
166
305
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
+
167
322
Err ( io:: Error :: new ( io:: ErrorKind :: Other ,
168
323
"creation time is not available on this platform \
169
324
currently") )
@@ -306,12 +461,25 @@ impl DirEntry {
306
461
307
462
#[ cfg( any( target_os = "linux" , target_os = "emscripten" , target_os = "android" ) ) ]
308
463
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
+
310
478
let mut stat: stat64 = unsafe { mem:: zeroed ( ) } ;
311
479
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 )
313
481
} ) ?;
314
- Ok ( FileAttr { stat } )
482
+ Ok ( FileAttr :: from_stat64 ( stat) )
315
483
}
316
484
317
485
#[ cfg( not( any( target_os = "linux" , target_os = "emscripten" , target_os = "android" ) ) ) ]
@@ -517,11 +685,24 @@ impl File {
517
685
}
518
686
519
687
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
+
520
701
let mut stat: stat64 = unsafe { mem:: zeroed ( ) } ;
521
702
cvt ( unsafe {
522
- fstat64 ( self . 0 . raw ( ) , & mut stat)
703
+ fstat64 ( fd , & mut stat)
523
704
} ) ?;
524
- Ok ( FileAttr { stat } )
705
+ Ok ( FileAttr :: from_stat64 ( stat) )
525
706
}
526
707
527
708
pub fn fsync ( & self ) -> io:: Result < ( ) > {
@@ -798,20 +979,44 @@ pub fn link(src: &Path, dst: &Path) -> io::Result<()> {
798
979
799
980
pub fn stat ( p : & Path ) -> io:: Result < FileAttr > {
800
981
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
+
801
994
let mut stat: stat64 = unsafe { mem:: zeroed ( ) } ;
802
995
cvt ( unsafe {
803
996
stat64 ( p. as_ptr ( ) , & mut stat)
804
997
} ) ?;
805
- Ok ( FileAttr { stat } )
998
+ Ok ( FileAttr :: from_stat64 ( stat) )
806
999
}
807
1000
808
1001
pub fn lstat ( p : & Path ) -> io:: Result < FileAttr > {
809
1002
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
+
810
1015
let mut stat: stat64 = unsafe { mem:: zeroed ( ) } ;
811
1016
cvt ( unsafe {
812
1017
lstat64 ( p. as_ptr ( ) , & mut stat)
813
1018
} ) ?;
814
- Ok ( FileAttr { stat } )
1019
+ Ok ( FileAttr :: from_stat64 ( stat) )
815
1020
}
816
1021
817
1022
pub fn canonicalize ( p : & Path ) -> io:: Result < PathBuf > {
0 commit comments