Add libinherited_fd crate

It provides a way to obtain `OwnedFd` for file descriptors that are
inherited from the parent process.

Bug: 243500154
Test: build
Test: atest libinherited_fd.test
Change-Id: I9aba4cc125ada207c5f62e4b6279e2225a171f8a
diff --git a/libs/libinherited_fd/Android.bp b/libs/libinherited_fd/Android.bp
new file mode 100644
index 0000000..28ec2e5
--- /dev/null
+++ b/libs/libinherited_fd/Android.bp
@@ -0,0 +1,44 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_defaults {
+    name: "libinherited_fd.defaults",
+    crate_name: "inherited_fd",
+    srcs: ["src/lib.rs"],
+    edition: "2021",
+    rustlibs: [
+        "libnix",
+        "libonce_cell",
+        "libthiserror",
+    ],
+}
+
+rust_library {
+    name: "libinherited_fd",
+    defaults: ["libinherited_fd.defaults"],
+    apex_available: [
+        "com.android.compos",
+        "com.android.virt",
+    ],
+}
+
+rust_test {
+    name: "libinherited_fd.test",
+    defaults: ["libinherited_fd.defaults"],
+    rustlibs: [
+        "libanyhow",
+        "libtempfile",
+    ],
+    host_supported: true,
+    test_suites: ["general-tests"],
+    test_options: {
+        unit_test: true,
+    },
+    // this is to run each test function in a separate process.
+    // note that they still run in parallel.
+    flags: [
+        "-C panic=abort",
+        "-Z panic_abort_tests",
+    ],
+}
diff --git a/libs/libinherited_fd/src/lib.rs b/libs/libinherited_fd/src/lib.rs
new file mode 100644
index 0000000..709bac6
--- /dev/null
+++ b/libs/libinherited_fd/src/lib.rs
@@ -0,0 +1,269 @@
+// 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();
+
+    match fds.get_mut(&raw_fd) {
+        None => Err(Error::FileDescriptorNotInherited(raw_fd)),
+        Some(None) => Err(Error::OwnershipTaken(raw_fd)),
+        Some(owned_fd) => {
+            let owned_fd = owned_fd.take().unwrap();
+            fcntl(raw_fd, F_SETFD(FdFlag::FD_CLOEXEC)).or(Err(Error::FailCloseOnExec(raw_fd)))?;
+            Ok(owned_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(())
+    }
+}