From 5419c79601a17be2264f430d56b6c5cd91a18fcc Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Sat, 22 Jan 2022 15:43:00 -0700 Subject: [PATCH 1/2] Add fspacectl on FreeBSD --- CHANGELOG.md | 2 + Cargo.toml | 2 +- src/fcntl.rs | 133 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 136 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ae85e31a6..523bf30419 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,8 @@ This project adheres to [Semantic Versioning](https://semver.org/). (#[1628](https://github.com/nix-rust/nix/pull/1628)) - Added `MAP_FIXED_NOREPLACE` on Linux. (#[1636](https://github.com/nix-rust/nix/pull/1636)) +- Added `fspacectl` on FreeBSD + (#[1640](https://github.com/nix-rust/nix/pull/1640)) ### Changed diff --git a/Cargo.toml b/Cargo.toml index dbe05683dd..c0cd0d5348 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,7 @@ targets = [ ] [dependencies] -libc = { git = "https://github.com/rust-lang/libc", rev = "e470e3b6a1f940e0024d40d3b79fc73fe29c7f17", features = [ "extra_traits" ] } +libc = { git = "https://github.com/rust-lang/libc", rev = "7600416f1ca896b501d58b0f44f1869d566359d6", features = [ "extra_traits" ] } bitflags = "1.1" cfg-if = "1.0" diff --git a/src/fcntl.rs b/src/fcntl.rs index c02a81a9a6..99a473d369 100644 --- a/src/fcntl.rs +++ b/src/fcntl.rs @@ -697,6 +697,139 @@ pub fn fallocate( Errno::result(res).map(drop) } +/// Argument to [`fspacectl`] describing the range to zero. The first member is +/// the file offset, and the second is the length of the region. +#[cfg(any(target_os = "freebsd"))] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct SpacectlRange(pub libc::off_t, pub libc::off_t); + +#[cfg(any(target_os = "freebsd"))] +impl SpacectlRange { + #[inline] + pub fn is_empty(&self) -> bool { + self.1 == 0 + } + + #[inline] + pub fn len(&self) -> libc::off_t { + self.1 + } + + #[inline] + pub fn offset(&self) -> libc::off_t { + self.0 + } +} + +/// Punch holes in a file. +/// +/// `fspacectl` instructs the file system to deallocate a portion of a file. +/// After a successful operation, this region of the file will return all zeroes +/// if read. If the file system supports deallocation, then it may free the +/// underlying storage, too. +/// +/// # Arguments +/// +/// - `fd` - File to operate on +/// - `range.0` - File offset at which to begin deallocation +/// - `range.1` - Length of the region to deallocate +/// +/// # Returns +/// +/// The operation may deallocate less than the entire requested region. On +/// success, it returns the region that still remains to be deallocated. The +/// caller should loop until the returned region is empty. +/// +/// # Example +/// +/// ``` +/// # use std::io::Write; +/// # use std::os::unix::fs::FileExt; +/// # use std::os::unix::io::AsRawFd; +/// # use nix::fcntl::*; +/// # use tempfile::tempfile; +/// const INITIAL: &[u8] = b"0123456789abcdef"; +/// let mut f = tempfile().unwrap(); +/// f.write_all(INITIAL).unwrap(); +/// let mut range = SpacectlRange(3, 6); +/// while (!range.is_empty()) { +/// let r = fspacectl(f.as_raw_fd(), range); +/// # if r == Err(nix::Error::ENOSYS) { +/// # // not supported until FreeBSD 14.0 +/// # return; +/// # } +/// range = r.unwrap(); +/// } +/// let mut buf = vec![0; INITIAL.len()]; +/// f.read_exact_at(&mut buf, 0).unwrap(); +/// assert_eq!(buf, b"012\0\0\0\0\0\09abcdef"); +/// ``` +#[cfg(target_os = "freebsd")] +pub fn fspacectl(fd: RawFd, range: SpacectlRange) -> Result { + let mut rqsr = libc::spacectl_range{r_offset: range.0, r_len: range.1}; + let res = unsafe { libc::fspacectl( + fd, + libc::SPACECTL_DEALLOC, // Only one command is supported ATM + &rqsr, + 0, // No flags are currently supported + &mut rqsr + )}; + Errno::result(res).map(|_| SpacectlRange(rqsr.r_offset, rqsr.r_len)) +} + +/// Like [`fspacectl`], but will never return incomplete. +/// +/// # Arguments +/// +/// - `fd` - File to operate on +/// - `offset` - File offset at which to begin deallocation +/// - `len` - Length of the region to deallocate +/// +/// # Returns +/// +/// Returns `()` on success. On failure, the region may or may not be partially +/// deallocated. +/// +/// # Example +/// +/// ``` +/// # use std::io::Write; +/// # use std::os::unix::fs::FileExt; +/// # use std::os::unix::io::AsRawFd; +/// # use nix::fcntl::*; +/// # use tempfile::tempfile; +/// const INITIAL: &[u8] = b"0123456789abcdef"; +/// let mut f = tempfile().unwrap(); +/// f.write_all(INITIAL).unwrap(); +/// let r = fspacectl_all(f.as_raw_fd(), 3, 6); +/// # if r == Err(nix::Error::ENOSYS) { +/// # // not supported until FreeBSD 14.0 +/// # return; +/// # } +/// r.unwrap(); +/// let mut buf = vec![0; INITIAL.len()]; +/// f.read_exact_at(&mut buf, 0).unwrap(); +/// assert_eq!(buf, b"012\0\0\0\0\0\09abcdef"); +/// ``` +#[cfg(target_os = "freebsd")] +pub fn fspacectl_all(fd: RawFd, offset: libc::off_t, len: libc::off_t) + -> Result<()> +{ + let mut rqsr = libc::spacectl_range{r_offset: offset, r_len: len}; + while rqsr.r_len > 0 { + let res = unsafe { libc::fspacectl( + fd, + libc::SPACECTL_DEALLOC, // Only one command is supported ATM + &rqsr, + 0, // No flags are currently supported + &mut rqsr + )}; + if let Err(e) = Errno::result(res) { + return Err(e); + } + } + Ok(()) +} #[cfg(any( target_os = "linux", From e2ce5efece04811d96336a9f896bbe7d50e6af86 Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Sun, 23 Jan 2022 08:37:56 -0700 Subject: [PATCH 2/2] Disable the fspacectl tests They fail to link prior to FreeBSD 14.0, which we don't use in CI. So mark them as no_run. The only alternative I see would be to add a build script. --- src/fcntl.rs | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/src/fcntl.rs b/src/fcntl.rs index 99a473d369..6c713608ee 100644 --- a/src/fcntl.rs +++ b/src/fcntl.rs @@ -742,7 +742,8 @@ impl SpacectlRange { /// /// # Example /// -/// ``` +// no_run because it fails to link until FreeBSD 14.0 +/// ```no_run /// # use std::io::Write; /// # use std::os::unix::fs::FileExt; /// # use std::os::unix::io::AsRawFd; @@ -753,12 +754,7 @@ impl SpacectlRange { /// f.write_all(INITIAL).unwrap(); /// let mut range = SpacectlRange(3, 6); /// while (!range.is_empty()) { -/// let r = fspacectl(f.as_raw_fd(), range); -/// # if r == Err(nix::Error::ENOSYS) { -/// # // not supported until FreeBSD 14.0 -/// # return; -/// # } -/// range = r.unwrap(); +/// range = fspacectl(f.as_raw_fd(), range).unwrap(); /// } /// let mut buf = vec![0; INITIAL.len()]; /// f.read_exact_at(&mut buf, 0).unwrap(); @@ -792,7 +788,8 @@ pub fn fspacectl(fd: RawFd, range: SpacectlRange) -> Result { /// /// # Example /// -/// ``` +// no_run because it fails to link until FreeBSD 14.0 +/// ```no_run /// # use std::io::Write; /// # use std::os::unix::fs::FileExt; /// # use std::os::unix::io::AsRawFd; @@ -801,12 +798,7 @@ pub fn fspacectl(fd: RawFd, range: SpacectlRange) -> Result { /// const INITIAL: &[u8] = b"0123456789abcdef"; /// let mut f = tempfile().unwrap(); /// f.write_all(INITIAL).unwrap(); -/// let r = fspacectl_all(f.as_raw_fd(), 3, 6); -/// # if r == Err(nix::Error::ENOSYS) { -/// # // not supported until FreeBSD 14.0 -/// # return; -/// # } -/// r.unwrap(); +/// fspacectl_all(f.as_raw_fd(), 3, 6).unwrap(); /// let mut buf = vec![0; INITIAL.len()]; /// f.read_exact_at(&mut buf, 0).unwrap(); /// assert_eq!(buf, b"012\0\0\0\0\0\09abcdef");