diff --git a/Cargo.toml b/Cargo.toml index 0665795ed2..36dd6394a4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ targets = [ ] [dependencies] -libc = { version = "0.2.160", features = ["extra_traits"] } +libc = { version = "0.2.162", features = ["extra_traits"] } bitflags = "2.3.3" cfg-if = "1.0" pin-utils = { version = "0.1.0", optional = true } diff --git a/changelog/2525.added.md b/changelog/2525.added.md new file mode 100644 index 0000000000..89cd93f2e8 --- /dev/null +++ b/changelog/2525.added.md @@ -0,0 +1 @@ +Add `close_range` in unistd for Linux/glibc and FreeBSD diff --git a/src/unistd.rs b/src/unistd.rs index 4e35cb5b32..d2baa0ce2c 100644 --- a/src/unistd.rs +++ b/src/unistd.rs @@ -3992,3 +3992,61 @@ pub fn chflags(path: &P, flags: FileFlag) -> Result<()> { Errno::result(res).map(drop) } } + +#[cfg(any( + all(target_os = "linux", target_env = "gnu"), + target_os = "freebsd" +))] +#[cfg(feature = "fs")] +libc_bitflags! { + /// Options for close_range() + #[cfg_attr(docsrs, doc(cfg(feature = "fs")))] + pub struct CloseRangeFlags : c_int { + #[cfg(all(target_os = "linux", target_env = "gnu"))] + /// Unshare the file descriptors table, then close the file descriptors specified in the + /// range + CLOSE_RANGE_UNSHARE as c_int; + /// Set the close-on-exec flag on the file descriptors range instead of closing them + CLOSE_RANGE_CLOEXEC as c_int; + } +} + +feature! { +#![feature = "fs"] + +/// Close all the file descriptor from a given range. +/// An optional flag can be applied to modify its behavior. +/// +/// # Safety +/// +/// This function as there are risks of double closes on the file descriptors. +#[cfg(any( + all(target_os = "linux", target_env = "gnu"), + target_os = "freebsd" +))] +pub unsafe fn close_range(fdbegin: F, fdlast: F, flags: CloseRangeFlags) -> Result> { + let raw = unsafe { + Errno::clear(); + + cfg_if! { + if #[cfg(all(target_os = "linux", target_env = "gnu"))] { + libc::syscall(libc::SYS_close_range, fdbegin.as_raw_fd() as u32, fdlast.as_raw_fd() as u32, flags.bits()) + } else { + libc::close_range(fdbegin.as_raw_fd() as u32, fdlast.as_raw_fd() as u32, flags.bits()) + } + } + }; + if raw == -1 { + if Errno::last_raw() == 0 { + Ok(None) + } else { + Err(Errno::last()) + } + } else { + #[cfg(all(target_os = "linux", target_env = "gnu", target_pointer_width = "64"))] + let raw = raw as i32; + Ok(Some(raw)) + } + +} +} diff --git a/test/test_unistd.rs b/test/test_unistd.rs index 5cb019bd95..f3c961cfef 100644 --- a/test/test_unistd.rs +++ b/test/test_unistd.rs @@ -1391,3 +1391,36 @@ fn test_group_from() { assert_eq!(group.gid, group_id); assert_eq!(group.name, "wheel"); } + +#[test] +#[cfg(any( + all(target_os = "linux", target_env = "gnu"), + target_os = "freebsd" +))] +#[cfg_attr(qemu, ignore)] +fn test_close_range() { + use tempfile::NamedTempFile; + const CONTENTS: &[u8] = b"abcdef123456"; + let mut tempfile: [NamedTempFile; 3] = [ + NamedTempFile::new().unwrap(), + NamedTempFile::new().unwrap(), + NamedTempFile::new().unwrap(), + ]; + + for tf in &mut tempfile { + let _ = tf.write_all(CONTENTS); + } + let areclosed = unsafe { + close_range( + tempfile[0].as_file().as_fd(), + tempfile[2].as_file().as_fd(), + CloseRangeFlags::CLOSE_RANGE_CLOEXEC, + ) + }; + assert_eq!( + areclosed + .expect("close_range failed") + .expect("invalid flag"), + 0 + ); +}