cap_primitives/rustix/fs/
metadata_ext.rs1#![allow(clippy::useless_conversion)]
2
3use crate::fs::{ImplFileTypeExt, ImplPermissionsExt, Metadata};
4use crate::time::{Duration, SystemClock, SystemTime};
5#[cfg(target_os = "linux")]
6use rustix::fs::{makedev, Statx, StatxFlags};
7use rustix::fs::{RawMode, Stat};
8use std::{fs, io};
9
10#[derive(Debug, Clone)]
11pub(crate) struct ImplMetadataExt {
12 dev: u64,
13 ino: u64,
14 #[cfg(not(target_os = "wasi"))]
15 mode: u32,
16 nlink: u64,
17 #[cfg(not(target_os = "wasi"))]
18 uid: u32,
19 #[cfg(not(target_os = "wasi"))]
20 gid: u32,
21 #[cfg(not(target_os = "wasi"))]
22 rdev: u64,
23 size: u64,
24 #[cfg(not(target_os = "wasi"))]
25 atime: i64,
26 #[cfg(not(target_os = "wasi"))]
27 atime_nsec: i64,
28 #[cfg(not(target_os = "wasi"))]
29 mtime: i64,
30 #[cfg(not(target_os = "wasi"))]
31 mtime_nsec: i64,
32 #[cfg(not(target_os = "wasi"))]
33 ctime: i64,
34 #[cfg(not(target_os = "wasi"))]
35 ctime_nsec: i64,
36 #[cfg(not(target_os = "wasi"))]
37 blksize: u64,
38 #[cfg(not(target_os = "wasi"))]
39 blocks: u64,
40 #[cfg(target_os = "wasi")]
41 atim: u64,
42 #[cfg(target_os = "wasi")]
43 mtim: u64,
44 #[cfg(target_os = "wasi")]
45 ctim: u64,
46}
47
48impl ImplMetadataExt {
49 #[inline]
52 #[allow(clippy::unnecessary_wraps)]
53 pub(crate) fn from(_file: &fs::File, std: &fs::Metadata) -> io::Result<Self> {
54 Ok(Self::from_just_metadata(std))
56 }
57
58 #[inline]
61 pub(crate) fn from_just_metadata(std: &fs::Metadata) -> Self {
62 use rustix::fs::MetadataExt;
63 Self {
64 dev: std.dev(),
65 ino: std.ino(),
66 #[cfg(not(target_os = "wasi"))]
67 mode: std.mode(),
68 nlink: std.nlink(),
69 #[cfg(not(target_os = "wasi"))]
70 uid: std.uid(),
71 #[cfg(not(target_os = "wasi"))]
72 gid: std.gid(),
73 #[cfg(not(target_os = "wasi"))]
74 rdev: std.rdev(),
75 size: std.size(),
76 #[cfg(not(target_os = "wasi"))]
77 atime: std.atime(),
78 #[cfg(not(target_os = "wasi"))]
79 atime_nsec: std.atime_nsec(),
80 #[cfg(not(target_os = "wasi"))]
81 mtime: std.mtime(),
82 #[cfg(not(target_os = "wasi"))]
83 mtime_nsec: std.mtime_nsec(),
84 #[cfg(not(target_os = "wasi"))]
85 ctime: std.ctime(),
86 #[cfg(not(target_os = "wasi"))]
87 ctime_nsec: std.ctime_nsec(),
88 #[cfg(not(target_os = "wasi"))]
89 blksize: std.blksize(),
90 #[cfg(not(target_os = "wasi"))]
91 blocks: std.blocks(),
92 #[cfg(target_os = "wasi")]
93 atim: std.atim(),
94 #[cfg(target_os = "wasi")]
95 mtim: std.mtim(),
96 #[cfg(target_os = "wasi")]
97 ctim: std.ctim(),
98 }
99 }
100
101 #[inline]
103 #[allow(unused_comparisons)] pub(crate) fn from_rustix(stat: Stat) -> Metadata {
105 Metadata {
106 file_type: ImplFileTypeExt::from_raw_mode(stat.st_mode as RawMode),
107 len: u64::try_from(stat.st_size).unwrap(),
108 #[cfg(not(target_os = "wasi"))]
109 permissions: ImplPermissionsExt::from_raw_mode(stat.st_mode as RawMode),
110 #[cfg(target_os = "wasi")]
111 permissions: ImplPermissionsExt::default(),
112
113 #[cfg(not(target_os = "wasi"))]
114 modified: system_time_from_rustix(
115 stat.st_mtime.try_into().unwrap(),
116 stat.st_mtime_nsec as _,
117 ),
118 #[cfg(not(target_os = "wasi"))]
119 accessed: system_time_from_rustix(
120 stat.st_atime.try_into().unwrap(),
121 stat.st_atime_nsec as _,
122 ),
123
124 #[cfg(target_os = "wasi")]
125 modified: system_time_from_rustix(stat.st_mtim.tv_sec, stat.st_mtim.tv_nsec as _),
126 #[cfg(target_os = "wasi")]
127 accessed: system_time_from_rustix(stat.st_atim.tv_sec, stat.st_atim.tv_nsec as _),
128
129 #[cfg(any(
130 target_os = "freebsd",
131 target_os = "openbsd",
132 target_os = "netbsd",
133 target_os = "macos",
134 target_os = "ios",
135 target_os = "tvos",
136 target_os = "watchos",
137 target_os = "visionos",
138 ))]
139 created: system_time_from_rustix(
140 stat.st_birthtime.try_into().unwrap(),
141 stat.st_birthtime_nsec as _,
142 ),
143
144 #[cfg(not(any(
146 target_os = "freebsd",
147 target_os = "openbsd",
148 target_os = "macos",
149 target_os = "ios",
150 target_os = "tvos",
151 target_os = "watchos",
152 target_os = "visionos",
153 target_os = "netbsd"
154 )))]
155 created: None,
156
157 ext: Self {
158 dev: if stat.st_dev < 0 {
168 i64::try_from(stat.st_dev).unwrap() as u64
169 } else {
170 u64::try_from(stat.st_dev).unwrap()
171 },
172 ino: stat.st_ino.into(),
173 #[cfg(not(target_os = "wasi"))]
174 mode: u32::from(stat.st_mode),
175 nlink: u64::from(stat.st_nlink),
176 #[cfg(not(target_os = "wasi"))]
177 uid: stat.st_uid,
178 #[cfg(not(target_os = "wasi"))]
179 gid: stat.st_gid,
180 #[cfg(not(target_os = "wasi"))]
181 rdev: u64::try_from(stat.st_rdev).unwrap(),
182 size: u64::try_from(stat.st_size).unwrap(),
183 #[cfg(not(target_os = "wasi"))]
184 atime: i64::try_from(stat.st_atime).unwrap(),
185 #[cfg(not(target_os = "wasi"))]
186 atime_nsec: stat.st_atime_nsec as _,
187 #[cfg(not(target_os = "wasi"))]
188 mtime: i64::try_from(stat.st_mtime).unwrap(),
189 #[cfg(not(target_os = "wasi"))]
190 mtime_nsec: stat.st_mtime_nsec as _,
191 #[cfg(not(target_os = "wasi"))]
192 ctime: i64::try_from(stat.st_ctime).unwrap(),
193 #[cfg(not(target_os = "wasi"))]
194 ctime_nsec: stat.st_ctime_nsec as _,
195 #[cfg(not(target_os = "wasi"))]
196 blksize: u64::try_from(stat.st_blksize).unwrap(),
197 #[cfg(not(target_os = "wasi"))]
198 blocks: u64::try_from(stat.st_blocks).unwrap(),
199 #[cfg(target_os = "wasi")]
200 atim: u64::try_from(
201 stat.st_atim.tv_sec as u64 * 1000000000 + stat.st_atim.tv_nsec as u64,
202 )
203 .unwrap(),
204 #[cfg(target_os = "wasi")]
205 mtim: u64::try_from(
206 stat.st_mtim.tv_sec as u64 * 1000000000 + stat.st_mtim.tv_nsec as u64,
207 )
208 .unwrap(),
209 #[cfg(target_os = "wasi")]
210 ctim: u64::try_from(
211 stat.st_ctim.tv_sec as u64 * 1000000000 + stat.st_ctim.tv_nsec as u64,
212 )
213 .unwrap(),
214 },
215 }
216 }
217
218 #[cfg(target_os = "linux")]
220 #[inline]
221 pub(crate) fn from_rustix_statx(statx: Statx) -> Metadata {
222 Metadata {
223 file_type: ImplFileTypeExt::from_raw_mode(RawMode::from(statx.stx_mode)),
224 len: u64::try_from(statx.stx_size).unwrap(),
225 permissions: ImplPermissionsExt::from_raw_mode(RawMode::from(statx.stx_mode)),
226 modified: if statx.stx_mask & StatxFlags::MTIME.bits() != 0 {
227 system_time_from_rustix(statx.stx_mtime.tv_sec, statx.stx_mtime.tv_nsec as _)
228 } else {
229 None
230 },
231 accessed: if statx.stx_mask & StatxFlags::ATIME.bits() != 0 {
232 system_time_from_rustix(statx.stx_atime.tv_sec, statx.stx_atime.tv_nsec as _)
233 } else {
234 None
235 },
236 created: if statx.stx_mask & StatxFlags::BTIME.bits() != 0 {
237 system_time_from_rustix(statx.stx_btime.tv_sec, statx.stx_btime.tv_nsec as _)
238 } else {
239 None
240 },
241
242 ext: Self {
243 dev: makedev(statx.stx_dev_major, statx.stx_dev_minor),
244 ino: statx.stx_ino.into(),
245 mode: u32::from(statx.stx_mode),
246 nlink: u64::from(statx.stx_nlink),
247 uid: statx.stx_uid,
248 gid: statx.stx_gid,
249 rdev: makedev(statx.stx_rdev_major, statx.stx_rdev_minor),
250 size: statx.stx_size,
251 atime: i64::from(statx.stx_atime.tv_sec),
252 atime_nsec: statx.stx_atime.tv_nsec as _,
253 mtime: i64::from(statx.stx_mtime.tv_sec),
254 mtime_nsec: statx.stx_mtime.tv_nsec as _,
255 ctime: i64::from(statx.stx_ctime.tv_sec),
256 ctime_nsec: statx.stx_ctime.tv_nsec as _,
257 blksize: u64::from(statx.stx_blksize),
258 blocks: statx.stx_blocks,
259 },
260 }
261 }
262
263 pub(crate) const fn is_same_file(&self, other: &Self) -> bool {
266 self.dev == other.dev && self.ino == other.ino
267 }
268}
269
270#[allow(clippy::similar_names)]
271fn system_time_from_rustix(sec: i64, nsec: u64) -> Option<SystemTime> {
272 if sec >= 0 {
273 SystemClock::UNIX_EPOCH.checked_add(Duration::new(u64::try_from(sec).unwrap(), nsec as _))
274 } else {
275 SystemClock::UNIX_EPOCH
276 .checked_sub(Duration::new(u64::try_from(-sec).unwrap(), 0))
277 .map(|t| t.checked_add(Duration::new(0, nsec as u32)))
278 .flatten()
279 }
280}
281
282impl crate::fs::MetadataExt for ImplMetadataExt {
283 #[inline]
284 fn dev(&self) -> u64 {
285 self.dev
286 }
287
288 #[inline]
289 fn ino(&self) -> u64 {
290 self.ino
291 }
292
293 #[cfg(not(target_os = "wasi"))]
294 #[inline]
295 fn mode(&self) -> u32 {
296 self.mode
297 }
298
299 #[inline]
300 fn nlink(&self) -> u64 {
301 self.nlink
302 }
303
304 #[cfg(not(target_os = "wasi"))]
305 #[inline]
306 fn uid(&self) -> u32 {
307 self.uid
308 }
309
310 #[cfg(not(target_os = "wasi"))]
311 #[inline]
312 fn gid(&self) -> u32 {
313 self.gid
314 }
315
316 #[cfg(not(target_os = "wasi"))]
317 #[inline]
318 fn rdev(&self) -> u64 {
319 self.rdev
320 }
321
322 #[inline]
323 fn size(&self) -> u64 {
324 self.size
325 }
326
327 #[cfg(not(target_os = "wasi"))]
328 #[inline]
329 fn atime(&self) -> i64 {
330 self.atime
331 }
332
333 #[cfg(not(target_os = "wasi"))]
334 #[inline]
335 fn atime_nsec(&self) -> i64 {
336 self.atime_nsec
337 }
338
339 #[cfg(not(target_os = "wasi"))]
340 #[inline]
341 fn mtime(&self) -> i64 {
342 self.mtime
343 }
344
345 #[cfg(not(target_os = "wasi"))]
346 #[inline]
347 fn mtime_nsec(&self) -> i64 {
348 self.mtime_nsec
349 }
350
351 #[cfg(not(target_os = "wasi"))]
352 #[inline]
353 fn ctime(&self) -> i64 {
354 self.ctime
355 }
356
357 #[cfg(not(target_os = "wasi"))]
358 #[inline]
359 fn ctime_nsec(&self) -> i64 {
360 self.ctime_nsec
361 }
362
363 #[cfg(not(target_os = "wasi"))]
364 #[inline]
365 fn blksize(&self) -> u64 {
366 self.blksize
367 }
368
369 #[cfg(not(target_os = "wasi"))]
370 #[inline]
371 fn blocks(&self) -> u64 {
372 self.blocks
373 }
374
375 #[cfg(target_os = "wasi")]
376 fn atim(&self) -> u64 {
377 self.atim
378 }
379
380 #[cfg(target_os = "wasi")]
381 fn mtim(&self) -> u64 {
382 self.mtim
383 }
384
385 #[cfg(target_os = "wasi")]
386 fn ctim(&self) -> u64 {
387 self.ctim
388 }
389}
390
391#[test]
394fn negative_time() {
395 let system_time = system_time_from_rustix(-1, 1).unwrap();
396 let d = SystemClock::UNIX_EPOCH.duration_since(system_time).unwrap();
397 assert_eq!(d.as_secs(), 0);
398 if !cfg!(emulate_second_only_system) {
399 assert_eq!(d.subsec_nanos(), 999999999);
400 }
401}