Skip to content

Commit 5a417e2

Browse files
committed
Implement Metadata:from_statx for Linux MetadataExt trait
* Implement Metadata:from_statx for Linux MetadataExt trait * Added STATX_MASK constant and included documentation for it * Refactored shared code from try_statx and Metadata::from_statx into a function with FileAttr::from_statx
1 parent 4429659 commit 5a417e2

3 files changed

Lines changed: 133 additions & 35 deletions

File tree

library/std/src/os/linux/fs.rs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,24 @@
77
use crate::fs::Metadata;
88
#[allow(deprecated)]
99
use crate::os::linux::raw;
10+
use crate::os::raw::{c_uint, c_void};
1011
use crate::sys::AsInner;
12+
use crate::sys::fs::cfg_has_statx;
13+
cfg_has_statx! {{
14+
use crate::sys::fs::FileAttr;
15+
use crate::sys::FromInner;
16+
} else {
17+
use crate::sys::unsupported;
18+
}}
19+
20+
/// This is the [`statx`] mask expected by [`Metadata::from_statx`], which sets both
21+
/// `STATX_BASIC_STATS` and `STATX_BTIME`. See the [Linux man page] for statx for more
22+
/// details.
23+
///
24+
/// [`statx`]: https://docs.rs/libc/latest/libc/struct.statx.html
25+
/// [Linux man page]: https://man7.org/linux/man-pages/man2/statx.2.html
26+
#[unstable(feature = "metadata_statx", issue = "156268")]
27+
pub const STATX_MASK: c_uint = libc::STATX_BASIC_STATS | libc::STATX_BTIME;
1128

1229
/// OS-specific extensions to [`fs::Metadata`].
1330
///
@@ -41,6 +58,53 @@ pub trait MetadataExt {
4158
#[allow(deprecated)]
4259
fn as_raw_stat(&self) -> &raw::stat;
4360

61+
/// Creates a [`Metadata`] from a const void pointer populated by the [`statx`] syscall.
62+
///
63+
/// Currently [`Metadata::from_statx`] is only supported on Linux platforms with a target
64+
/// environment of GNU.
65+
///
66+
/// # Safety
67+
///
68+
/// The caller must take care to provide a valid const void pointer containing information
69+
/// populated by the [`statx`] syscall. In particular, the provided pointer should contain
70+
/// statx information pertaining to the mask [`STATX_MASK`], so that there will be no
71+
/// uninitialized data encountered in constructing [`Metadata`].
72+
///
73+
/// Note that the relevant information is copied out of the structure and the pointer is
74+
/// not retained past the call.
75+
///
76+
/// [`Metadata`]: crate::fs::Metadata
77+
/// [`statx`]: https://docs.rs/libc/latest/libc/struct.statx.html
78+
///
79+
/// ```no_run
80+
/// #![feature(metadata_statx)]
81+
/// use libc::statx;
82+
/// use std::ffi::c_void;
83+
/// use std::fs::{write, Metadata};
84+
/// use std::io;
85+
/// use std::os::linux::fs::{MetadataExt, STATX_MASK};
86+
///
87+
/// fn main() -> io::Result<()> {
88+
/// write("hello.txt", "Hello World!")?;
89+
/// let mut buf = Box::<statx>::new_uninit();
90+
/// unsafe {
91+
/// libc::statx(
92+
/// libc::AT_FDCWD,
93+
/// "hello.txt".as_ptr().cast(),
94+
/// libc::AT_STATX_SYNC_AS_STAT,
95+
/// STATX_MASK,
96+
/// buf.as_mut_ptr().cast()
97+
/// );
98+
/// }
99+
/// let statxbuf: Box<statx> = unsafe { buf.assume_init() };
100+
/// let metadata = unsafe { Metadata::from_statx(&*statxbuf as *const statx as *const c_void) };
101+
/// assert_eq!(metadata.len(), 12); // "Hello World!" is 12 bytes
102+
/// Ok(())
103+
/// }
104+
/// ```
105+
#[unstable(feature = "metadata_statx", issue = "156268")]
106+
unsafe fn from_statx(statxbuf: *const c_void) -> Self;
107+
44108
/// Returns the device ID on which this file resides.
45109
///
46110
/// # Examples
@@ -337,6 +401,14 @@ impl MetadataExt for Metadata {
337401
&*(self.as_inner().as_inner() as *const libc::stat64 as *const raw::stat)
338402
}
339403
}
404+
cfg_has_statx! {{
405+
unsafe fn from_statx(statxbuf: *const c_void) -> Metadata {
406+
Metadata::from_inner(FileAttr::from_statx(*(statxbuf as *const libc::statx)))
407+
}} else {
408+
unsafe fn from_statx(statxbuf: *const c_void) -> Self {
409+
unsupported();
410+
}
411+
}}
340412
fn st_dev(&self) -> u64 {
341413
self.as_inner().as_inner().st_dev as u64
342414
}

library/std/src/sys/fs/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ cfg_select! {
1818
#[cfg(any(target_os = "linux", target_os = "android"))]
1919
pub(super) use unix::CachedFileMetadata;
2020
use crate::sys::helpers::run_path_with_cstr as with_native_path;
21+
#[cfg(all(target_os = "linux", target_env = "gnu"))]
22+
pub(crate) use unix::cfg_has_statx;
2123
}
2224
target_os = "windows" => {
2325
mod windows;

library/std/src/sys/fs/unix.rs

Lines changed: 59 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,9 @@ macro_rules! cfg_has_statx {
125125
};
126126
}
127127

128+
#[cfg(all(target_os = "linux", target_env = "gnu"))]
129+
pub(crate) use cfg_has_statx;
130+
128131
cfg_has_statx! {{
129132
#[derive(Clone)]
130133
pub struct FileAttr {
@@ -133,7 +136,7 @@ cfg_has_statx! {{
133136
}
134137

135138
#[derive(Clone)]
136-
struct StatxExtraFields {
139+
pub struct StatxExtraFields {
137140
// This is needed to check if btime is supported by the filesystem.
138141
stx_mask: u32,
139142
stx_btime: libc::statx_timestamp,
@@ -212,40 +215,7 @@ cfg_has_statx! {{
212215
STATX_SAVED_STATE.store(STATX_STATE::Present as u8, Ordering::Relaxed);
213216
}
214217

215-
// We cannot fill `stat64` exhaustively because of private padding fields.
216-
let mut stat: stat64 = mem::zeroed();
217-
// `c_ulong` on gnu-mips, `dev_t` otherwise
218-
stat.st_dev = libc::makedev(buf.stx_dev_major, buf.stx_dev_minor) as _;
219-
stat.st_ino = buf.stx_ino as libc::ino64_t;
220-
stat.st_nlink = buf.stx_nlink as libc::nlink_t;
221-
stat.st_mode = buf.stx_mode as libc::mode_t;
222-
stat.st_uid = buf.stx_uid as libc::uid_t;
223-
stat.st_gid = buf.stx_gid as libc::gid_t;
224-
stat.st_rdev = libc::makedev(buf.stx_rdev_major, buf.stx_rdev_minor) as _;
225-
stat.st_size = buf.stx_size as off64_t;
226-
stat.st_blksize = buf.stx_blksize as libc::blksize_t;
227-
stat.st_blocks = buf.stx_blocks as libc::blkcnt64_t;
228-
stat.st_atime = buf.stx_atime.tv_sec as libc::time_t;
229-
// `i64` on gnu-x86_64-x32, `c_ulong` otherwise.
230-
stat.st_atime_nsec = buf.stx_atime.tv_nsec as _;
231-
stat.st_mtime = buf.stx_mtime.tv_sec as libc::time_t;
232-
stat.st_mtime_nsec = buf.stx_mtime.tv_nsec as _;
233-
stat.st_ctime = buf.stx_ctime.tv_sec as libc::time_t;
234-
stat.st_ctime_nsec = buf.stx_ctime.tv_nsec as _;
235-
236-
let extra = StatxExtraFields {
237-
stx_mask: buf.stx_mask,
238-
stx_btime: buf.stx_btime,
239-
// Store full times to avoid 32-bit `time_t` truncation.
240-
#[cfg(target_pointer_width = "32")]
241-
stx_atime: buf.stx_atime,
242-
#[cfg(target_pointer_width = "32")]
243-
stx_ctime: buf.stx_ctime,
244-
#[cfg(target_pointer_width = "32")]
245-
stx_mtime: buf.stx_mtime,
246-
};
247-
248-
Some(Ok(FileAttr { stat, statx_extra_fields: Some(extra) }))
218+
Some(Ok(FileAttr::from_statx(buf)))
249219
}
250220

251221
} else {
@@ -525,11 +495,65 @@ pub struct DirBuilder {
525495
struct Mode(mode_t);
526496

527497
cfg_has_statx! {{
498+
impl StatxExtraFields {
499+
/// Constructs `StatxExtraFields` from a given `libc::statx` struct
500+
///
501+
/// SAFETY:
502+
/// The caller must take care to provide a `libc::statx` buffer that is
503+
/// populated by the statx syscall with the flags `STATX_BASIC_STATS`
504+
/// and `STATX_BTIME` enabled
505+
pub unsafe fn from_statx(buf: libc::statx) -> Self {
506+
StatxExtraFields {
507+
stx_mask: buf.stx_mask,
508+
stx_btime: buf.stx_btime,
509+
// Store full times to avoid 32-bit `time_t` truncation.
510+
#[cfg(target_pointer_width = "32")]
511+
stx_atime: buf.stx_atime,
512+
#[cfg(target_pointer_width = "32")]
513+
stx_ctime: buf.stx_ctime,
514+
#[cfg(target_pointer_width = "32")]
515+
stx_mtime: buf.stx_mtime,
516+
}
517+
}
518+
}
528519
impl FileAttr {
529520
fn from_stat64(stat: stat64) -> Self {
530521
Self { stat, statx_extra_fields: None }
531522
}
532523

524+
/// Constructs `FileAttr` from a given `libc::statx` struct
525+
///
526+
/// SAFETY:
527+
/// The caller must take care to provide a `libc::statx` buffer that is
528+
/// populated by the statx syscall with the flags `STATX_BASIC_STATS`
529+
/// and `STATX_BTIME` enabled
530+
pub unsafe fn from_statx(buf: libc::statx) -> Self {
531+
// We cannot fill `stat64` exhaustively because of private padding fields.
532+
let mut stat: libc::stat64 = mem::zeroed();
533+
// `c_ulong` on gnu-mips, `dev_t` otherwise
534+
stat.st_dev = libc::makedev((buf).stx_dev_major, (buf).stx_dev_minor) as _;
535+
stat.st_ino = buf.stx_ino as libc::ino64_t;
536+
stat.st_nlink = buf.stx_nlink as libc::nlink_t;
537+
stat.st_mode = buf.stx_mode as libc::mode_t;
538+
stat.st_uid = buf.stx_uid as libc::uid_t;
539+
stat.st_gid = buf.stx_gid as libc::gid_t;
540+
stat.st_rdev = libc::makedev(buf.stx_rdev_major, buf.stx_rdev_minor) as _;
541+
stat.st_size = buf.stx_size as libc::off64_t;
542+
stat.st_blksize = buf.stx_blksize as libc::blksize_t;
543+
stat.st_blocks = buf.stx_blocks as libc::blkcnt64_t;
544+
stat.st_atime = buf.stx_atime.tv_sec as libc::time_t;
545+
// `i64` on gnu-x86_64-x32, `c_ulong` otherwise.
546+
stat.st_atime_nsec = buf.stx_atime.tv_nsec as _;
547+
stat.st_mtime = buf.stx_mtime.tv_sec as libc::time_t;
548+
stat.st_mtime_nsec = buf.stx_mtime.tv_nsec as _;
549+
stat.st_ctime = buf.stx_ctime.tv_sec as libc::time_t;
550+
stat.st_ctime_nsec = buf.stx_ctime.tv_nsec as _;
551+
552+
let statx_extra_fields = Some(StatxExtraFields::from_statx(buf));
553+
554+
Self {stat, statx_extra_fields}
555+
}
556+
533557
#[cfg(target_pointer_width = "32")]
534558
pub fn stx_mtime(&self) -> Option<&libc::statx_timestamp> {
535559
if let Some(ext) = &self.statx_extra_fields {

0 commit comments

Comments
 (0)