Skip to content

Commit

Permalink
Add statx
Browse files Browse the repository at this point in the history
Resolves nix-rust#1649.
  • Loading branch information
vi committed Feb 9, 2022
1 parent 0bd56d9 commit 9302265
Show file tree
Hide file tree
Showing 2 changed files with 374 additions and 0 deletions.
3 changes: 3 additions & 0 deletions src/fcntl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -889,3 +889,6 @@ pub fn posix_fallocate(fd: RawFd, offset: libc::off_t, len: libc::off_t) -> Resu
}
}
}

#[cfg(any(target_os = "android", target_os = "linux"))]
pub mod statx;
371 changes: 371 additions & 0 deletions src/fcntl/statx.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,371 @@
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}};

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;
}
);

/*
libc_bitflags!(
pub struct FileAttributes: c_int {
/// The file is compressed by the filesystem and may take extra resources to access.
STATX_ATTR_COMPRESSED;
/// The file cannot be modified: it cannot be deleted or renamed, no hard links can be created to this file and no data can be written to it.
STATX_ATTR_IMMUTABLE;
/// The file can only be opened in append mode for writing. Random access writing is not permitted.
STATX_ATTR_APPEND;
/// File is not a candidate for backup when a backup program such as `dump` is run.
STATX_ATTR_NODUMP;
/// A key is required for the file to be encrypted by the filesystem.
STATX_ATTR_ENCRYPTED;
// /// (since Linux 5.5)
// /// The file has fs-verity enabled. It cannot be written to, and all reads from it will be verified
// /// against a cryptographic hash that covers the entire file
// /// (e.g., via a Merkle tree).
// STATX_ATTR_VERITY;
// /// (since Linux 5.8)
// /// The file is in the DAX (cpu direct access) state.
// STATX_ATTR_DAX;
}
);
*/

/// Attempt to retrieve stats of `pathname`. If `pathname` is relative, `dirfd` is used as starting directory for lookups. If `dirfs` is None, current directory is used.
/// `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(
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() },
})
}

/// Get stats of specified file descriptor (not nesessarily directory) without resolving the path.
/// Automaitcally sets the `AT_EMPTY_PATH` flag.
pub fn statx_without_path(dirfd: RawFd, mut flags: Flags, mask: Mask) -> Result<Statx> {
flags.set(Flags::AT_EMPTY_PATH, true);
let mut dst = mem::MaybeUninit::uninit();
let res = unsafe {
libc::statx(
dirfd,
b"\0" as *const u8 as *const libc::c_char,
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,
}

impl Statx {
/// Retrieve file type, if it has been returned by kernel
pub fn r#type(&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_NLINK.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_NLINK.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<libc::statx_timestamp> {
if Mask::STATX_ATIME.bits() & self.inner.stx_mask > 0 {
Some(self.inner.stx_atime)
} else {
None
}
}

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

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

/// Retrieve file birth (creation) time, if it has been returned by kernel
pub fn btime(&self) -> Option<libc::statx_timestamp> {
if Mask::STATX_BTIME.bits() & self.inner.stx_mask > 0 {
Some(self.inner.stx_btime)
} 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 file size as a number of blocks, if it has been returned by kernel
pub fn blksize(&self) -> Option<u64> {
// 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 are asked for.
if Mask::STATX_BASIC_STATS.bits() & self.inner.stx_mask == Mask::STATX_BASIC_STATS.bits() {
Some(self.inner.stx_blocks)
} 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 are 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)> {
// 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 are 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
}
}

/// 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
}
}

/*
/// 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
}
}
*/
}

0 comments on commit 9302265

Please sign in to comment.