blob: f5e2d6b0a18c180165b30bfd78f52265fa85de44 [file] [log] [blame]
// 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<Mutex<HashMap<RawFd, Option<OwnedFd>>>> = 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::<RawFd>().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<OwnedFd, Error> {
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<RawFd>,
}
impl Fixture {
fn setup(num_fds: usize) -> Result<Self> {
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<RawFd> {
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(())
}
}