Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
171 changes: 170 additions & 1 deletion src/fs/ioctl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,18 +96,27 @@ unsafe impl ioctl::Ioctl for Ficlone<'_> {

#[cfg(linux_raw_dep)]
bitflags! {
/// `FS_*` constants for use with [`ioctl_getflags`].
/// `FS_*` constants for use with [`ioctl_getflags`] and [`ioctl_setflags`].
///
/// [`ioctl_getflags`]: crate::fs::ioctl::ioctl_getflags
/// [`ioctl_setflags`]: crate::fs::ioctl::ioctl_setflags
///
/// Not every flag returned by [`ioctl_getflags`] can be changed with
/// [`ioctl_setflags`]. The kernel validates which changes the filesystem
/// and calling process permit.
#[repr(transparent)]
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub struct IFlags: ffi::c_uint {
/// `FS_APPEND_FL`
const APPEND = linux_raw_sys::general::FS_APPEND_FL;
/// `FS_CASEFOLD_FL`
const CASEFOLD = linux_raw_sys::general::FS_CASEFOLD_FL;
/// `FS_COMPR_FL`
const COMPRESSED = linux_raw_sys::general::FS_COMPR_FL;
/// `FS_DIRSYNC_FL`
const DIRSYNC = linux_raw_sys::general::FS_DIRSYNC_FL;
/// `FS_ENCRYPT_FL`
const ENCRYPTED = linux_raw_sys::general::FS_ENCRYPT_FL;
/// `FS_IMMUTABLE_FL`
const IMMUTABLE = linux_raw_sys::general::FS_IMMUTABLE_FL;
/// `FS_JOURNAL_DATA_FL`
Expand All @@ -130,6 +139,8 @@ bitflags! {
const TOPDIR = linux_raw_sys::general::FS_TOPDIR_FL;
/// `FS_UNRM_FL`
const UNRM = linux_raw_sys::general::FS_UNRM_FL;
/// `FS_VERITY_FL`
const VERITY = linux_raw_sys::general::FS_VERITY_FL;
}
}

Expand Down Expand Up @@ -167,3 +178,161 @@ pub fn ioctl_setflags<Fd: AsFd>(fd: Fd, flags: IFlags) -> io::Result<()> {
ioctl::ioctl(fd, ctl)
}
}

#[cfg(linux_raw_dep)]
bitflags! {
/// `FS_XFLAG_*` constants for [`Fsxattr::fsx_xflags`].
///
/// Values returned by [`ioctl_fsgetxattr`] retain any bits that are not
/// represented by a named constant.
#[repr(transparent)]
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub struct FsxattrFlags: u32 {
/// `FS_XFLAG_REALTIME`
const REALTIME = linux_raw_sys::general::FS_XFLAG_REALTIME;
/// `FS_XFLAG_PREALLOC`
const PREALLOC = linux_raw_sys::general::FS_XFLAG_PREALLOC;
/// `FS_XFLAG_IMMUTABLE`
const IMMUTABLE = linux_raw_sys::general::FS_XFLAG_IMMUTABLE;
/// `FS_XFLAG_APPEND`
const APPEND = linux_raw_sys::general::FS_XFLAG_APPEND;
/// `FS_XFLAG_SYNC`
const SYNC = linux_raw_sys::general::FS_XFLAG_SYNC;
/// `FS_XFLAG_NOATIME`
const NOATIME = linux_raw_sys::general::FS_XFLAG_NOATIME;
/// `FS_XFLAG_NODUMP`
const NODUMP = linux_raw_sys::general::FS_XFLAG_NODUMP;
/// `FS_XFLAG_RTINHERIT`
const RTINHERIT = linux_raw_sys::general::FS_XFLAG_RTINHERIT;
/// `FS_XFLAG_PROJINHERIT`
const PROJINHERIT = linux_raw_sys::general::FS_XFLAG_PROJINHERIT;
/// `FS_XFLAG_NOSYMLINKS`
const NOSYMLINKS = linux_raw_sys::general::FS_XFLAG_NOSYMLINKS;
/// `FS_XFLAG_EXTSIZE`
const EXTSIZE = linux_raw_sys::general::FS_XFLAG_EXTSIZE;
/// `FS_XFLAG_EXTSZINHERIT`
const EXTSZINHERIT = linux_raw_sys::general::FS_XFLAG_EXTSZINHERIT;
/// `FS_XFLAG_NODEFRAG`
const NODEFRAG = linux_raw_sys::general::FS_XFLAG_NODEFRAG;
/// `FS_XFLAG_FILESTREAM`
const FILESTREAM = linux_raw_sys::general::FS_XFLAG_FILESTREAM;
/// `FS_XFLAG_DAX`
const DAX = linux_raw_sys::general::FS_XFLAG_DAX;
/// `FS_XFLAG_COWEXTSIZE`
const COWEXTSIZE = linux_raw_sys::general::FS_XFLAG_COWEXTSIZE;
/// `FS_XFLAG_HASATTR`
const HASATTR = linux_raw_sys::general::FS_XFLAG_HASATTR;

/// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
const _ = !0;
}
}

/// Filesystem attributes returned by [`ioctl_fsgetxattr`].
#[cfg(linux_raw_dep)]
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
#[non_exhaustive]
pub struct Fsxattr {
/// Extended filesystem flags.
pub fsx_xflags: FsxattrFlags,
/// Preferred extent size.
pub fsx_extsize: u32,
/// Number of extents.
pub fsx_nextents: u32,
/// Project identifier.
pub fsx_projid: u32,
/// Preferred copy-on-write extent size.
pub fsx_cowextsize: u32,
}

#[cfg(linux_raw_dep)]
const FS_IOC_FSGETXATTR: ioctl::Opcode =
ioctl::opcode::read::<linux_raw_sys::general::fsxattr>(b'X', 31);

#[cfg(linux_raw_dep)]
#[inline]
fn fsxattr_from_raw(raw: linux_raw_sys::general::fsxattr) -> Fsxattr {
Fsxattr {
fsx_xflags: FsxattrFlags::from_bits_retain(raw.fsx_xflags),
fsx_extsize: raw.fsx_extsize,
fsx_nextents: raw.fsx_nextents,
fsx_projid: raw.fsx_projid,
fsx_cowextsize: raw.fsx_cowextsize,
}
}

/// `ioctl(fd, FS_IOC_FSGETXATTR)`—Returns extended filesystem attributes.
#[cfg(linux_raw_dep)]
#[inline]
#[doc(alias = "FS_IOC_FSGETXATTR")]
pub fn ioctl_fsgetxattr<Fd: AsFd>(fd: Fd) -> io::Result<Fsxattr> {
// SAFETY: `fsxattr` consists entirely of integers and bytes, so all-zero
// is a valid value. This also initializes the reserved padding before the
// kernel may read it.
let mut raw: linux_raw_sys::general::fsxattr = unsafe { core::mem::zeroed() };

// SAFETY: `FS_IOC_FSGETXATTR` is `_IOR('X', 31, struct fsxattr)`. `raw`
// has the exact kernel layout and is fully initialized, and `Updater`
// permits the kernel to read and write it.
unsafe {
let ctl =
ioctl::Updater::<{ FS_IOC_FSGETXATTR }, linux_raw_sys::general::fsxattr>::new(&mut raw);
ioctl::ioctl(fd, ctl)?;
}

Ok(fsxattr_from_raw(raw))
}

#[cfg(all(test, linux_raw_dep))]
mod tests {
use super::*;

#[test]
fn test_fsgetxattr_from_raw_fields_and_unknown_bits() {
const UNKNOWN_XFLAG: u32 = 0x0000_0004;

let attrs = fsxattr_from_raw(linux_raw_sys::general::fsxattr {
fsx_xflags: FsxattrFlags::APPEND.bits() | UNKNOWN_XFLAG,
fsx_extsize: 11,
fsx_nextents: 22,
fsx_projid: 33,
fsx_cowextsize: 44,
fsx_pad: [0; 8],
});

assert!(attrs.fsx_xflags.contains(FsxattrFlags::APPEND));
assert_eq!(
(attrs.fsx_xflags & !FsxattrFlags::APPEND).bits(),
UNKNOWN_XFLAG
);
assert_eq!(attrs.fsx_extsize, 11);
assert_eq!(attrs.fsx_nextents, 22);
assert_eq!(attrs.fsx_projid, 33);
assert_eq!(attrs.fsx_cowextsize, 44);
}

#[cfg(not(any(
target_arch = "hexagon",
target_arch = "sparc",
target_arch = "sparc64"
)))]
#[test]
fn test_fsgetxattr_opcode_matches_linux_raw_sys() {
assert_eq!(
FS_IOC_FSGETXATTR,
linux_raw_sys::ioctl::FS_IOC_FSGETXATTR as ioctl::Opcode
);
}

#[cfg(any(target_arch = "sparc", target_arch = "sparc64"))]
#[test]
fn test_fsgetxattr_opcode_matches_sparc_uapi() {
assert_eq!(FS_IOC_FSGETXATTR, 0x401c_581f);
}

#[cfg(target_arch = "hexagon")]
#[test]
fn test_fsgetxattr_opcode_matches_hexagon_uapi() {
assert_eq!(FS_IOC_FSGETXATTR, 0x801c_581f_u32 as ioctl::Opcode);
}
}
132 changes: 132 additions & 0 deletions tests/fs/ioctl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,135 @@ fn test_ioctl_ficlone() {
Err(err) => panic!("{:?}", err),
}
}

#[cfg(linux_raw_dep)]
#[repr(C)]
#[derive(Debug, Default)]
struct RawFsxattr {
fsx_xflags: u32,
fsx_extsize: u32,
fsx_nextents: u32,
fsx_projid: u32,
fsx_cowextsize: u32,
fsx_pad: [u8; 8],
}

#[cfg(linux_raw_dep)]
fn raw_fsgetxattr(file: &std::fs::File) -> Result<RawFsxattr, rustix::io::Errno> {
use std::os::unix::io::AsRawFd;

let mut raw = RawFsxattr::default();
let request = rustix::ioctl::opcode::read::<RawFsxattr>(b'X', 31);
let result = unsafe { libc::ioctl(file.as_raw_fd(), request as _, &mut raw) };
if result == -1 {
Err(rustix::io::Errno::from_raw_os_error(libc_errno::errno().0))
} else {
assert_eq!(result, 0);
Ok(raw)
}
}

#[cfg(linux_raw_dep)]
#[test]
fn test_fsgetxattr_flag_names_and_unknown_bits() {
use rustix::fs::{FsxattrFlags, IFlags};

const UNKNOWN_XFLAG: u32 = 0x0000_0004;

let xflags = FsxattrFlags::from_bits_retain(FsxattrFlags::APPEND.bits() | UNKNOWN_XFLAG);
assert!(xflags.contains(FsxattrFlags::APPEND));
assert_eq!((xflags & !FsxattrFlags::APPEND).bits(), UNKNOWN_XFLAG);

assert_eq!(
IFlags::ENCRYPTED.bits(),
linux_raw_sys::general::FS_ENCRYPT_FL
);
assert_eq!(IFlags::VERITY.bits(), linux_raw_sys::general::FS_VERITY_FL);
assert_eq!(
IFlags::CASEFOLD.bits(),
linux_raw_sys::general::FS_CASEFOLD_FL
);
}

#[cfg(linux_raw_dep)]
#[test]
fn test_fsgetxattr_raw_layout() {
use core::mem::{align_of, size_of};
use linux_raw_sys::general::fsxattr as LinuxFsxattr;
use memoffset::offset_of;

assert_eq!(size_of::<RawFsxattr>(), 28);
assert_eq!(align_of::<RawFsxattr>(), 4);
assert_eq!(size_of::<LinuxFsxattr>(), size_of::<RawFsxattr>());
assert_eq!(align_of::<LinuxFsxattr>(), align_of::<RawFsxattr>());

assert_eq!(offset_of!(RawFsxattr, fsx_xflags), 0);
assert_eq!(offset_of!(RawFsxattr, fsx_extsize), 4);
assert_eq!(offset_of!(RawFsxattr, fsx_nextents), 8);
assert_eq!(offset_of!(RawFsxattr, fsx_projid), 12);
assert_eq!(offset_of!(RawFsxattr, fsx_cowextsize), 16);
assert_eq!(offset_of!(RawFsxattr, fsx_pad), 20);

assert_eq!(
offset_of!(LinuxFsxattr, fsx_xflags),
offset_of!(RawFsxattr, fsx_xflags)
);
assert_eq!(
offset_of!(LinuxFsxattr, fsx_extsize),
offset_of!(RawFsxattr, fsx_extsize)
);
assert_eq!(
offset_of!(LinuxFsxattr, fsx_nextents),
offset_of!(RawFsxattr, fsx_nextents)
);
assert_eq!(
offset_of!(LinuxFsxattr, fsx_projid),
offset_of!(RawFsxattr, fsx_projid)
);
assert_eq!(
offset_of!(LinuxFsxattr, fsx_cowextsize),
offset_of!(RawFsxattr, fsx_cowextsize)
);
assert_eq!(
offset_of!(LinuxFsxattr, fsx_pad),
offset_of!(RawFsxattr, fsx_pad)
);
}

#[cfg(linux_raw_dep)]
#[test]
fn test_ioctl_fsgetxattr_matches_raw_ioctl() {
use rustix::io;

let file = tempfile::tempfile().unwrap();
let attrs = match rustix::fs::ioctl_fsgetxattr(&file) {
Ok(attrs) => attrs,
Err(err) if matches!(err, io::Errno::NOTTY | io::Errno::OPNOTSUPP) => {
assert_eq!(raw_fsgetxattr(&file).unwrap_err(), err);
eprintln!("skipped: the temporary filesystem does not support FS_IOC_FSGETXATTR");
return;
}
Err(err) => panic!("FS_IOC_FSGETXATTR failed on the temporary file: {err:?}"),
};
let raw = raw_fsgetxattr(&file).unwrap();

assert_eq!(attrs.fsx_xflags.bits(), raw.fsx_xflags);
assert_eq!(attrs.fsx_extsize, raw.fsx_extsize);
assert_eq!(attrs.fsx_nextents, raw.fsx_nextents);
assert_eq!(attrs.fsx_projid, raw.fsx_projid);
assert_eq!(attrs.fsx_cowextsize, raw.fsx_cowextsize);
assert_eq!(raw.fsx_pad, [0; 8]);
}

#[cfg(linux_raw_dep)]
#[test]
fn test_ioctl_fsgetxattr_unsupported_fd_errno() {
use rustix::io;

let null = std::fs::File::open("/dev/null").unwrap();
let safe_errno = rustix::fs::ioctl_fsgetxattr(&null).unwrap_err();
let raw_errno = raw_fsgetxattr(&null).unwrap_err();

assert_eq!(safe_errno, raw_errno);
assert_eq!(safe_errno, io::Errno::NOTTY);
}
Loading