// Copyright 2024, The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //! Library for safely obtaining `OwnedFd` for inherited file descriptors. use nix::fcntl::{fcntl, FdFlag, F_SETFD}; use nix::libc; use std::collections::HashMap; use std::fs::canonicalize; use std::fs::read_dir; use std::os::fd::FromRawFd; use std::os::fd::OwnedFd; use std::os::fd::RawFd; use std::sync::Mutex; use std::sync::OnceLock; use thiserror::Error; /// Errors that can occur while taking an ownership of `RawFd` #[derive(Debug, PartialEq, Error)] pub enum Error { /// init_once() not called #[error("init_once() not called")] NotInitialized, /// Ownership already taken #[error("Ownership of FD {0} is already taken")] OwnershipTaken(RawFd), /// Not an inherited file descriptor #[error("FD {0} is either invalid file descriptor or not an inherited one")] FileDescriptorNotInherited(RawFd), /// Failed to set CLOEXEC #[error("Failed to set CLOEXEC on FD {0}")] FailCloseOnExec(RawFd), } static INHERITED_FDS: OnceLock>>> = OnceLock::new(); /// Take ownership of all open file descriptors in this process, which later can be obtained by /// calling `take_fd_ownership`. /// /// # Safety /// This function has to be called very early in the program before the ownership of any file /// descriptors (except stdin/out/err) is taken. pub unsafe fn init_once() -> Result<(), std::io::Error> { let mut fds = HashMap::new(); let fd_path = canonicalize("/proc/self/fd")?; for entry in read_dir(&fd_path)? { let entry = entry?; // Files in /prod/self/fd are guaranteed to be numbers. So parsing is always successful. let file_name = entry.file_name(); let raw_fd = file_name.to_str().unwrap().parse::().unwrap(); // We don't take ownership of the stdio FDs as the Rust runtime owns them. if [libc::STDIN_FILENO, libc::STDOUT_FILENO, libc::STDERR_FILENO].contains(&raw_fd) { continue; } // Exceptional case: /proc/self/fd/* may be a dir fd created by read_dir just above. Since // the file descriptor is owned by read_dir (and thus closed by it), we shouldn't take // ownership to it. if entry.path().read_link()? == fd_path { continue; } // SAFETY: /proc/self/fd/* are file descriptors that are open. If `init_once()` was called // at the very beginning of the program execution (as requested by the safety requirement // of this function), this is the first time to claim the ownership of these file // descriptors. let owned_fd = unsafe { OwnedFd::from_raw_fd(raw_fd) }; fds.insert(raw_fd, Some(owned_fd)); } INHERITED_FDS .set(Mutex::new(fds)) .or(Err(std::io::Error::other("Inherited fds were already initialized"))) } /// Take the ownership of the given `RawFd` and returns `OwnedFd` for it. The returned FD is set /// CLOEXEC. `Error` is returned when the ownership was already taken (by a prior call to this /// function with the same `RawFd`) or `RawFd` is not an inherited file descriptor. pub fn take_fd_ownership(raw_fd: RawFd) -> Result { let mut fds = INHERITED_FDS.get().ok_or(Error::NotInitialized)?.lock().unwrap(); if let Some(value) = fds.get_mut(&raw_fd) { if let Some(owned_fd) = value.take() { fcntl(raw_fd, F_SETFD(FdFlag::FD_CLOEXEC)).or(Err(Error::FailCloseOnExec(raw_fd)))?; Ok(owned_fd) } else { Err(Error::OwnershipTaken(raw_fd)) } } else { Err(Error::FileDescriptorNotInherited(raw_fd)) } } #[cfg(test)] mod test { use super::*; use anyhow::Result; use nix::fcntl::{fcntl, FdFlag, F_GETFD, F_SETFD}; use nix::unistd::close; use std::os::fd::{AsRawFd, IntoRawFd}; use tempfile::tempfile; struct Fixture { fds: Vec, } impl Fixture { fn setup(num_fds: usize) -> Result { let mut fds = Vec::new(); for _ in 0..num_fds { fds.push(tempfile()?.into_raw_fd()); } Ok(Fixture { fds }) } fn open_new_file(&mut self) -> Result { let raw_fd = tempfile()?.into_raw_fd(); self.fds.push(raw_fd); Ok(raw_fd) } } impl Drop for Fixture { fn drop(&mut self) { self.fds.iter().for_each(|fd| { let _ = close(*fd); }); } } fn is_fd_opened(raw_fd: RawFd) -> bool { fcntl(raw_fd, F_GETFD).is_ok() } #[test] fn happy_case() -> Result<()> { let fixture = Fixture::setup(2)?; let f0 = fixture.fds[0]; let f1 = fixture.fds[1]; // SAFETY: assume files opened by Fixture are inherited ones unsafe { init_once()?; } let f0_owned = take_fd_ownership(f0)?; let f1_owned = take_fd_ownership(f1)?; assert_eq!(f0, f0_owned.as_raw_fd()); assert_eq!(f1, f1_owned.as_raw_fd()); drop(f0_owned); drop(f1_owned); assert!(!is_fd_opened(f0)); assert!(!is_fd_opened(f1)); Ok(()) } #[test] fn access_non_inherited_fd() -> Result<()> { let mut fixture = Fixture::setup(2)?; // SAFETY: assume files opened by Fixture are inherited ones unsafe { init_once()?; } let f = fixture.open_new_file()?; assert_eq!(Some(Error::FileDescriptorNotInherited(f)), take_fd_ownership(f).err()); Ok(()) } #[test] fn call_init_once_multiple_times() -> Result<()> { let _ = Fixture::setup(2)?; // SAFETY: assume files opened by Fixture are inherited ones unsafe { init_once()?; } // SAFETY: for testing let res = unsafe { init_once() }; assert!(res.is_err()); Ok(()) } #[test] fn access_without_init_once() -> Result<()> { let fixture = Fixture::setup(2)?; let f = fixture.fds[0]; assert_eq!(Some(Error::NotInitialized), take_fd_ownership(f).err()); Ok(()) } #[test] fn double_ownership() -> Result<()> { let fixture = Fixture::setup(2)?; let f = fixture.fds[0]; // SAFETY: assume files opened by Fixture are inherited ones unsafe { init_once()?; } let f_owned = take_fd_ownership(f)?; let f_double_owned = take_fd_ownership(f); assert_eq!(Some(Error::OwnershipTaken(f)), f_double_owned.err()); // just to highlight that f_owned is kept alive when the second call to take_fd_ownership // is made. drop(f_owned); Ok(()) } #[test] fn take_drop_retake() -> Result<()> { let fixture = Fixture::setup(2)?; let f = fixture.fds[0]; // SAFETY: assume files opened by Fixture are inherited ones unsafe { init_once()?; } let f_owned = take_fd_ownership(f)?; drop(f_owned); let f_double_owned = take_fd_ownership(f); assert_eq!(Some(Error::OwnershipTaken(f)), f_double_owned.err()); Ok(()) } #[test] fn cloexec() -> Result<()> { let fixture = Fixture::setup(2)?; let f = fixture.fds[0]; // SAFETY: assume files opened by Fixture are inherited ones unsafe { init_once()?; } // Intentionally cleaar cloexec to see if it is set by take_fd_ownership fcntl(f, F_SETFD(FdFlag::empty()))?; let f_owned = take_fd_ownership(f)?; let flags = fcntl(f_owned.as_raw_fd(), F_GETFD)?; assert_eq!(flags, FdFlag::FD_CLOEXEC.bits()); Ok(()) } }