Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add statx #1660

Draft
wants to merge 7 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/fcntl.rs
Expand Up @@ -889,3 +889,6 @@ pub fn posix_fallocate(fd: RawFd, offset: libc::off_t, len: libc::off_t) -> Resu
}
}
}

#[cfg(all(feature="fs", any(target_os = "android", target_os = "linux")))]
Copy link
Member

@SteveLauC SteveLauC Oct 5, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#[cfg(all(
    any(
        target_os = "android",
        all(target_os = "linux", not(target_env = "musl")),
    ),
    feature = "fs"
))]

Since this is not available on musl, this configuration should work

pub mod statx;
vi marked this conversation as resolved.
Show resolved Hide resolved
350 changes: 350 additions & 0 deletions src/fcntl/statx.rs
@@ -0,0 +1,350 @@
use std::{mem, os::unix::prelude::RawFd};

use libc::{self, c_int, c_uint, mode_t};

use crate::{errno::Errno, NixPath, Result, sys::stat::{SFlag, Mode}, unistd::{Uid, Gid}, sys::time::TimeSpec};

libc_bitflags!(
/// Configuration options for statx.
pub struct Flags: c_int {
/// Allow empty `pathname`, to receive status of the specified file descriptor
/// itself (not a directory entry) instead.
AT_EMPTY_PATH;

/// Do not automount the last component of the path being queried.
AT_NO_AUTOMOUNT;

/// Do not follow symlink in the last component of the path being queried.
AT_SYMLINK_NOFOLLOW;

/// Do whatever usual, non-`x` stat does. The default.
AT_STATX_SYNC_AS_STAT;

/// Force returned attributes to be up to date (synchonized).
/// May trigger data writeback.
/// May involve additional roudtrips for networked filesystems.
AT_STATX_FORCE_SYNC;

/// Don't synchronize, use cached data if possible. This may lead to the
/// returned data to be approximate.
AT_STATX_DONT_SYNC;
}
);

libc_bitflags!(
/// Configuration options for statx.
pub struct Mask: c_uint {
/// Want stx_mode & S_IFMT
STATX_TYPE;
/// Want stx_mode & ~S_IFMT
STATX_MODE;
/// Want stx_nlink
STATX_NLINK;
/// Want stx_uid
STATX_UID;
/// Want stx_gid
STATX_GID;
/// Want stx_atime
STATX_ATIME;
/// Want stx_mtime
STATX_MTIME;
/// Want stx_ctime
STATX_CTIME;
/// Want stx_ino
STATX_INO;
/// Want stx_size
STATX_SIZE;
/// Want stx_blocks
STATX_BLOCKS;
/// [All of the above]
STATX_BASIC_STATS;
/// Want stx_btime
STATX_BTIME;
/// Want stx_mnt_id (since Linux 5.8);
STATX_MNT_ID;
/// [All currently available fields]
STATX_ALL;
}
);

/// Attempt to retrieve stats of `pathname`. If `pathname` is relative, `dirfd`
/// is used as starting directory for lookups. If `dirfs` is None, current
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// is used as starting directory for lookups. If `dirfs` is None, current
/// is used as starting directory for lookups. If `dirfd` is None, current

/// directory is used.
/// `pathname` may be empty string. But instead of specifying empty string
/// literal, you are adviced to use zero-terminated `CStr` to avoid extra allocation
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about adding some examples to show usage? That would be especially helpful for the "empty pathname" case.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea. Shall doc examples be marked as testable code (and assert something about typical external files e.g. /dev/null)?

/// `mask` determines what stats entries should be filled in, but kernel
/// can actually fill more or less than requested.
/// `statx` is not atomic: if attributes are being changed at the time of
/// `statx` is called, the returned attributes set may have items from
/// different moments of time.
pub fn statx<P: ?Sized + NixPath>(
dirfd: Option<RawFd>,
pathname: &P,
flags: Flags,
mask: Mask,
) -> Result<Statx> {
let dirfd = dirfd.unwrap_or(libc::AT_FDCWD);

let mut dst = mem::MaybeUninit::uninit();
let res = pathname.with_nix_path(|cstr| unsafe {
libc::statx(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This wrapper function is only exposed on GNU/Linux as glibc added it.

What about making this statx module temporarily exclusive to GNU/Linux? When the missing stuff is added, we can easily add support for other platforms by changing a few lines of code.

dirfd,
cstr.as_ptr(),
flags.bits() as libc::c_int,
mask.bits() as libc::c_uint,
dst.as_mut_ptr(),
)
})?;

Errno::result(res)?;

Ok(Statx {
inner: unsafe { dst.assume_init() },
})
}

#[derive(Debug,Copy,Clone)]
pub struct Statx {
pub inner: libc::statx,
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since it has only one member, it can be a NewType.

Suggested change
pub struct Statx {
pub inner: libc::statx,
}
#[repr(transparent)]
pub struct Statx(libc::statx);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done (inner tuple field is still pub, unlike in the suggested change).


impl From<libc::statx_timestamp> for TimeSpec {
fn from(x: libc::statx_timestamp) -> Self {
TimeSpec::from_timespec(libc::timespec {
tv_sec: x.tv_sec,
tv_nsec: x.tv_nsec.into(),
})
}
}

impl Statx {
/// Retrieve file type, if it has been returned by kernel
pub fn filetype(&self) -> Option<SFlag> {
if Mask::STATX_TYPE.bits() & self.inner.stx_mask > 0 {
Some(SFlag::from_bits_truncate(self.inner.stx_mode as mode_t & SFlag::S_IFMT.bits()))
} else {
None
}
}

/// Retrieve file mode, if it has been returned by kernel.
pub fn mode(&self) -> Option<Mode> {
if Mask::STATX_MODE.bits() & self.inner.stx_mask > 0 {
Some(Mode::from_bits_truncate(self.inner.stx_mode as mode_t))
} else {
None
}
}

/// Retrieve number of hard links, if it has been returned by kernel.
pub fn nlinks(&self) -> Option<u32> {
if Mask::STATX_NLINK.bits() & self.inner.stx_mask > 0 {
Some(self.inner.stx_nlink)
} else {
None
}
}

/// Retrieve uid (owner, user ID), if it has been returned by kernel.
pub fn uid(&self) -> Option<Uid> {
if Mask::STATX_UID.bits() & self.inner.stx_mask > 0 {
Some(Uid::from_raw(self.inner.stx_uid))
} else {
None
}
}

/// Retrieve gid (group ID), if it has been returned by kernel.
pub fn gid(&self) -> Option<Gid> {
if Mask::STATX_GID.bits() & self.inner.stx_mask > 0 {
Some(Gid::from_raw(self.inner.stx_uid))
} else {
None
}
}

/// Retrieve file access time, if it has been returned by kernel
pub fn atime(&self) -> Option<TimeSpec> {
if Mask::STATX_ATIME.bits() & self.inner.stx_mask > 0 {
Some(self.inner.stx_atime.into())
} else {
None
}
}

/// Retrieve file modification time, if it has been returned by kernel
pub fn mtime(&self) -> Option<TimeSpec> {
if Mask::STATX_MTIME.bits() & self.inner.stx_mask > 0 {
Some(self.inner.stx_mtime.into())
} else {
None
}
}

/// Retrieve file attribute change time, if it has been returned by kernel
pub fn ctime(&self) -> Option<TimeSpec> {
if Mask::STATX_CTIME.bits() & self.inner.stx_mask > 0 {
Some(self.inner.stx_ctime.into())
} else {
None
}
}

/// Retrieve file birth (creation) time, if it has been returned by kernel
pub fn btime(&self) -> Option<TimeSpec> {
if Mask::STATX_BTIME.bits() & self.inner.stx_mask > 0 {
Some(self.inner.stx_btime.into())
} else {
None
}
}

/// Retrieve inode number, if it has been returned by kernel
pub fn ino(&self) -> Option<u64> {
if Mask::STATX_INO.bits() & self.inner.stx_mask > 0 {
Some(self.inner.stx_ino)
} else {
None
}
}

/// Retrieve file size, in bytes, if it has been returned by kernel
pub fn size(&self) -> Option<u64> {
if Mask::STATX_SIZE.bits() & self.inner.stx_mask > 0 {
Some(self.inner.stx_size)
} else {
None
}
}

/// Retrieve file size as a number of blocks, if it has been returned by kernel
pub fn blocks(&self) -> Option<u64> {
if Mask::STATX_BLOCKS.bits() & self.inner.stx_mask > 0 {
Some(self.inner.stx_blocks)
} else {
None
}
}

/// Retrieve size of the block (used in `blocks` function) in bytes
pub fn blksize(&self) -> Option<u32> {
// I'm not sure what exact mask bit should be used to check presence of block size.
// Actual Linux kernel seems return most of STATX_BASIC_STATS
// in one go, regarless of which things were asked for.
if Mask::STATX_BASIC_STATS.bits() & self.inner.stx_mask == Mask::STATX_BASIC_STATS.bits() {
Some(self.inner.stx_blksize)
} else {
None
}
}

/// Retrieve mount ID, if it has been returned by kernel
pub fn mnt_id(&self) -> Option<u64> {
if Mask::STATX_MNT_ID.bits() & self.inner.stx_mask > 0 {
Some(self.inner.stx_mnt_id)
} else {
None
}
}

/// Retrieve device major and minor numbers (first and second elements of the
/// tuple respectively) of the filesystem where the file resides at, if it has been returned by kernel.
pub fn dev_major_minor(&self) -> Option<(u32, u32)> {
// I'm not sure what exact mask bit should be used to check presence of this information.
// Actual Linux kernel seems return most of STATX_BASIC_STATS in one go,
// regarless of which things were asked for.
if Mask::STATX_BASIC_STATS.bits() & self.inner.stx_mask == Mask::STATX_BASIC_STATS.bits() {
Some((self.inner.stx_dev_major, self.inner.stx_dev_minor))
} else {
None
}
}

/// Retrieve pointed-to device major and minor numbers (first and second
/// elements of the tuple respectively) of this character or block device
/// file, if it has been returned by kernel.
/// Note that this function does not check for the file type.
/// It would return `Some` even for regular files.
pub fn rdev_major_minor(&self) -> Option<(u32, u32)> {
vi marked this conversation as resolved.
Show resolved Hide resolved
// I'm not sure what exact mask bit should be used to check presence of this information.
// Actual Linux kernel seems return most of STATX_BASIC_STATS
// in one go, regarless of which things were asked for.
if Mask::STATX_BASIC_STATS.bits() & self.inner.stx_mask == Mask::STATX_BASIC_STATS.bits() {
Some((self.inner.stx_rdev_major, self.inner.stx_rdev_minor))
} else {
None
}
}

/// Determine if the file is compressed. None means kernel does not
/// indicate this attrbiute is supported by the filesystem
pub fn compressed(&self) -> Option<bool> {
if self.inner.stx_attributes_mask & libc::STATX_ATTR_COMPRESSED as u64 > 0 {
Some(self.inner.stx_attributes & libc::STATX_ATTR_COMPRESSED as u64 > 0)
} else {
None
}
}

/// Determine if the file is immutable. None means kernel does not indicate this
/// attrbiute is supported by the filesystem
pub fn immutable(&self) -> Option<bool> {
if self.inner.stx_attributes_mask & libc::STATX_ATTR_IMMUTABLE as u64 > 0 {
Some(self.inner.stx_attributes & libc::STATX_ATTR_IMMUTABLE as u64 > 0)
} else {
None
}
}

/// Determine if the file is append-only. None means kernel does not indicate
/// this attrbiute is supported by the filesystem
pub fn append_only(&self) -> Option<bool> {
if self.inner.stx_attributes_mask & libc::STATX_ATTR_APPEND as u64 > 0 {
Some(self.inner.stx_attributes & libc::STATX_ATTR_APPEND as u64 > 0)
} else {
None
}
}

/// Determine if the file is not a candidate for a backup. None means kernel
/// does not indicate this attrbiute is supported by the filesystem
pub fn nodump(&self) -> Option<bool> {
if self.inner.stx_attributes_mask & libc::STATX_ATTR_NODUMP as u64 > 0 {
Some(self.inner.stx_attributes & libc::STATX_ATTR_NODUMP as u64 > 0)
} else {
None
}
}

/// Determine if the file requires a key to be encrypted(?).
/// None means kernel does not indicate this attrbiute is supported by the filesystem
pub fn encrypted(&self) -> Option<bool> {
if self.inner.stx_attributes_mask & libc::STATX_ATTR_ENCRYPTED as u64 > 0 {
Some(self.inner.stx_attributes & libc::STATX_ATTR_ENCRYPTED as u64 > 0)
} else {
None
}
}

/*
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are these two methods commented out?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because of current version of libc crate does not expose e.g. STATX_ATTR_VERITY. See my question at #1649 (comment).
Android target's libc lacks even more consts in libc.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I submitted a PR to add the missing constants on GNU/Linux

/// Determine if the file has fs-verify enabled. None means kernel does not
/// indicate this attrbiute is supported by the filesystem
pub fn verify_enabled(&self) -> Option<bool> {
if self.inner.stx_attributes_mask & libc::STATX_ATTR_VERITY as u64 > 0 {
Some(self.inner.stx_attributes & libc::STATX_ATTR_VERITY as u64 > 0)
} else {
None
}
}

/// Determine if the file is in CPU direct access state. None means kernel
/// does not indicate this attrbiute is supported by the filesystem.
pub fn dax(&self) -> Option<bool> {
if self.inner.stx_attributes_mask & libc::STATX_ATTR_DAX as u64 > 0 {
Some(self.inner.stx_attributes & libc::STATX_ATTR_DAX as u64 > 0)
} else {
None
}
}
*/
}