Add libsafe_ownedfd crate
It provides a safe conversion function (returns Result) from RawFd to
OwnedFd.
Bug: 243500154
Test: build
Test: atest libsafe_ownedfd.test
Change-Id: Ia31add2c41df4ff9a8d45217d194d8547810fa75
diff --git a/libs/libsafe_ownedfd/Android.bp b/libs/libsafe_ownedfd/Android.bp
new file mode 100644
index 0000000..1f14578
--- /dev/null
+++ b/libs/libsafe_ownedfd/Android.bp
@@ -0,0 +1,37 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_defaults {
+ name: "libsafe_ownedfd.defaults",
+ crate_name: "safe_ownedfd",
+ srcs: ["src/lib.rs"],
+ edition: "2021",
+ rustlibs: [
+ "libnix",
+ "libthiserror",
+ ],
+}
+
+rust_library {
+ name: "libsafe_ownedfd",
+ defaults: ["libsafe_ownedfd.defaults"],
+ apex_available: [
+ "com.android.compos",
+ "com.android.virt",
+ ],
+}
+
+rust_test {
+ name: "libsafe_ownedfd.test",
+ defaults: ["libsafe_ownedfd.defaults"],
+ rustlibs: [
+ "libanyhow",
+ "libtempfile",
+ ],
+ host_supported: true,
+ test_suites: ["general-tests"],
+ test_options: {
+ unit_test: true,
+ },
+}
diff --git a/libs/libsafe_ownedfd/src/lib.rs b/libs/libsafe_ownedfd/src/lib.rs
new file mode 100644
index 0000000..52ae180
--- /dev/null
+++ b/libs/libsafe_ownedfd/src/lib.rs
@@ -0,0 +1,127 @@
+// 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 a safer conversion from `RawFd` to `OwnedFd`
+
+use nix::fcntl::{fcntl, FdFlag, F_DUPFD, F_GETFD, F_SETFD};
+use nix::libc;
+use nix::unistd::close;
+use std::os::fd::FromRawFd;
+use std::os::fd::OwnedFd;
+use std::os::fd::RawFd;
+use std::sync::Mutex;
+use thiserror::Error;
+
+/// Errors that can occur while taking an ownership of `RawFd`
+#[derive(Debug, PartialEq, Error)]
+pub enum Error {
+ /// RawFd is not a valid file descriptor
+ #[error("{0} is not a file descriptor")]
+ Invalid(RawFd),
+
+ /// RawFd is either stdio, stdout, or stderr
+ #[error("standard IO descriptors cannot be owned")]
+ StdioNotAllowed,
+
+ /// Generic UNIX error
+ #[error("UNIX error")]
+ Errno(#[from] nix::errno::Errno),
+}
+
+static LOCK: Mutex<()> = Mutex::new(());
+
+/// Takes the ownership of `RawFd` and converts it to `OwnedFd`. It is important to know that
+/// `RawFd` is closed when this function successfully returns. The raw file descriptor of the
+/// returned `OwnedFd` is different from `RawFd`. The returned file descriptor is CLOEXEC set.
+pub fn take_fd_ownership(raw_fd: RawFd) -> Result<OwnedFd, Error> {
+ fcntl(raw_fd, F_GETFD).map_err(|_| Error::Invalid(raw_fd))?;
+
+ if [libc::STDIN_FILENO, libc::STDOUT_FILENO, libc::STDERR_FILENO].contains(&raw_fd) {
+ return Err(Error::StdioNotAllowed);
+ }
+
+ // sync is needed otherwise we can create multiple OwnedFds out of the same RawFd
+ let lock = LOCK.lock().unwrap();
+ let new_fd = fcntl(raw_fd, F_DUPFD(raw_fd))?;
+ close(raw_fd)?;
+ drop(lock);
+
+ // This is not essential, but let's follow the common practice in the Rust ecosystem
+ fcntl(new_fd, F_SETFD(FdFlag::FD_CLOEXEC)).map_err(Error::Errno)?;
+
+ // SAFETY: In this function, we have checked that RawFd is actually an open file descriptor and
+ // this is the first time to claim its ownership because we just created it by duping.
+ Ok(unsafe { OwnedFd::from_raw_fd(new_fd) })
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use anyhow::Result;
+ use nix::fcntl::{fcntl, FdFlag, F_GETFD, F_SETFD};
+ use std::os::fd::AsRawFd;
+ use std::os::fd::IntoRawFd;
+ use tempfile::tempfile;
+
+ #[test]
+ fn good_fd() -> Result<()> {
+ let raw_fd = tempfile()?.into_raw_fd();
+ assert!(take_fd_ownership(raw_fd).is_ok());
+ Ok(())
+ }
+
+ #[test]
+ fn invalid_fd() -> Result<()> {
+ let raw_fd = 12345; // randomly chosen
+ assert_eq!(take_fd_ownership(raw_fd).unwrap_err(), Error::Invalid(raw_fd));
+ Ok(())
+ }
+
+ #[test]
+ fn original_fd_closed() -> Result<()> {
+ let raw_fd = tempfile()?.into_raw_fd();
+ let owned_fd = take_fd_ownership(raw_fd)?;
+ assert_ne!(raw_fd, owned_fd.as_raw_fd());
+ assert!(fcntl(raw_fd, F_GETFD).is_err());
+ Ok(())
+ }
+
+ #[test]
+ fn cannot_use_same_rawfd_multiple_times() -> Result<()> {
+ let raw_fd = tempfile()?.into_raw_fd();
+
+ let owned_fd = take_fd_ownership(raw_fd); // once
+ let owned_fd2 = take_fd_ownership(raw_fd); // twice
+
+ assert!(owned_fd.is_ok());
+ assert!(owned_fd2.is_err());
+ Ok(())
+ }
+
+ #[test]
+ fn cloexec() -> Result<()> {
+ let raw_fd = tempfile()?.into_raw_fd();
+
+ // intentionally clear cloexec to see if it is set by take_fd_ownership
+ fcntl(raw_fd, F_SETFD(FdFlag::empty()))?;
+ let flags = fcntl(raw_fd, F_GETFD)?;
+ assert_eq!(flags, FdFlag::empty().bits());
+
+ let owned_fd = take_fd_ownership(raw_fd)?;
+ let flags = fcntl(owned_fd.as_raw_fd(), F_GETFD)?;
+ assert_eq!(flags, FdFlag::FD_CLOEXEC.bits());
+ drop(owned_fd);
+ Ok(())
+ }
+}