xref: /aosp_15_r20/system/librustutils/inherited_fd.rs (revision e51878c104ea269309bae357ae559a9fff179380)
1 // Copyright 2024, The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 //! Library for safely obtaining `OwnedFd` for inherited file descriptors.
16 
17 use nix::fcntl::{fcntl, FdFlag, F_SETFD};
18 use nix::libc;
19 use std::collections::HashMap;
20 use std::fs::canonicalize;
21 use std::fs::read_dir;
22 use std::os::fd::FromRawFd;
23 use std::os::fd::OwnedFd;
24 use std::os::fd::RawFd;
25 use std::sync::Mutex;
26 use std::sync::OnceLock;
27 use thiserror::Error;
28 
29 /// Errors that can occur while taking an ownership of `RawFd`
30 #[derive(Debug, PartialEq, Error)]
31 pub enum Error {
32     /// init_once() not called
33     #[error("init_once() not called")]
34     NotInitialized,
35 
36     /// Ownership already taken
37     #[error("Ownership of FD {0} is already taken")]
38     OwnershipTaken(RawFd),
39 
40     /// Not an inherited file descriptor
41     #[error("FD {0} is either invalid file descriptor or not an inherited one")]
42     FileDescriptorNotInherited(RawFd),
43 
44     /// Failed to set CLOEXEC
45     #[error("Failed to set CLOEXEC on FD {0}")]
46     FailCloseOnExec(RawFd),
47 }
48 
49 static INHERITED_FDS: OnceLock<Mutex<HashMap<RawFd, Option<OwnedFd>>>> = OnceLock::new();
50 
51 /// Take ownership of all open file descriptors in this process, which later can be obtained by
52 /// calling `take_fd_ownership`.
53 ///
54 /// # Safety
55 /// This function has to be called very early in the program before the ownership of any file
56 /// descriptors (except stdin/out/err) is taken.
init_once() -> Result<(), std::io::Error>57 pub unsafe fn init_once() -> Result<(), std::io::Error> {
58     let mut fds = HashMap::new();
59 
60     let fd_path = canonicalize("/proc/self/fd")?;
61 
62     for entry in read_dir(&fd_path)? {
63         let entry = entry?;
64 
65         // Files in /prod/self/fd are guaranteed to be numbers. So parsing is always successful.
66         let file_name = entry.file_name();
67         let raw_fd = file_name.to_str().unwrap().parse::<RawFd>().unwrap();
68 
69         // We don't take ownership of the stdio FDs as the Rust runtime owns them.
70         if [libc::STDIN_FILENO, libc::STDOUT_FILENO, libc::STDERR_FILENO].contains(&raw_fd) {
71             continue;
72         }
73 
74         // Exceptional case: /proc/self/fd/* may be a dir fd created by read_dir just above. Since
75         // the file descriptor is owned by read_dir (and thus closed by it), we shouldn't take
76         // ownership to it.
77         if entry.path().read_link()? == fd_path {
78             continue;
79         }
80 
81         // SAFETY: /proc/self/fd/* are file descriptors that are open. If `init_once()` was called
82         // at the very beginning of the program execution (as requested by the safety requirement
83         // of this function), this is the first time to claim the ownership of these file
84         // descriptors.
85         let owned_fd = unsafe { OwnedFd::from_raw_fd(raw_fd) };
86         fds.insert(raw_fd, Some(owned_fd));
87     }
88 
89     INHERITED_FDS
90         .set(Mutex::new(fds))
91         .or(Err(std::io::Error::other("Inherited fds were already initialized")))
92 }
93 
94 /// Take the ownership of the given `RawFd` and returns `OwnedFd` for it. The returned FD is set
95 /// CLOEXEC. `Error` is returned when the ownership was already taken (by a prior call to this
96 /// function with the same `RawFd`) or `RawFd` is not an inherited file descriptor.
take_fd_ownership(raw_fd: RawFd) -> Result<OwnedFd, Error>97 pub fn take_fd_ownership(raw_fd: RawFd) -> Result<OwnedFd, Error> {
98     let mut fds = INHERITED_FDS.get().ok_or(Error::NotInitialized)?.lock().unwrap();
99 
100     if let Some(value) = fds.get_mut(&raw_fd) {
101         if let Some(owned_fd) = value.take() {
102             fcntl(raw_fd, F_SETFD(FdFlag::FD_CLOEXEC)).or(Err(Error::FailCloseOnExec(raw_fd)))?;
103             Ok(owned_fd)
104         } else {
105             Err(Error::OwnershipTaken(raw_fd))
106         }
107     } else {
108         Err(Error::FileDescriptorNotInherited(raw_fd))
109     }
110 }
111 
112 #[cfg(test)]
113 mod test {
114     use super::*;
115     use anyhow::Result;
116     use nix::fcntl::{fcntl, FdFlag, F_GETFD, F_SETFD};
117     use nix::unistd::close;
118     use std::os::fd::{AsRawFd, IntoRawFd};
119     use tempfile::tempfile;
120 
121     struct Fixture {
122         fds: Vec<RawFd>,
123     }
124 
125     impl Fixture {
setup(num_fds: usize) -> Result<Self>126         fn setup(num_fds: usize) -> Result<Self> {
127             let mut fds = Vec::new();
128             for _ in 0..num_fds {
129                 fds.push(tempfile()?.into_raw_fd());
130             }
131             Ok(Fixture { fds })
132         }
133 
open_new_file(&mut self) -> Result<RawFd>134         fn open_new_file(&mut self) -> Result<RawFd> {
135             let raw_fd = tempfile()?.into_raw_fd();
136             self.fds.push(raw_fd);
137             Ok(raw_fd)
138         }
139     }
140 
141     impl Drop for Fixture {
drop(&mut self)142         fn drop(&mut self) {
143             self.fds.iter().for_each(|fd| {
144                 let _ = close(*fd);
145             });
146         }
147     }
148 
is_fd_opened(raw_fd: RawFd) -> bool149     fn is_fd_opened(raw_fd: RawFd) -> bool {
150         fcntl(raw_fd, F_GETFD).is_ok()
151     }
152 
153     #[test]
happy_case() -> Result<()>154     fn happy_case() -> Result<()> {
155         let fixture = Fixture::setup(2)?;
156         let f0 = fixture.fds[0];
157         let f1 = fixture.fds[1];
158 
159         // SAFETY: assume files opened by Fixture are inherited ones
160         unsafe {
161             init_once()?;
162         }
163 
164         let f0_owned = take_fd_ownership(f0)?;
165         let f1_owned = take_fd_ownership(f1)?;
166         assert_eq!(f0, f0_owned.as_raw_fd());
167         assert_eq!(f1, f1_owned.as_raw_fd());
168 
169         drop(f0_owned);
170         drop(f1_owned);
171         assert!(!is_fd_opened(f0));
172         assert!(!is_fd_opened(f1));
173         Ok(())
174     }
175 
176     #[test]
access_non_inherited_fd() -> Result<()>177     fn access_non_inherited_fd() -> Result<()> {
178         let mut fixture = Fixture::setup(2)?;
179 
180         // SAFETY: assume files opened by Fixture are inherited ones
181         unsafe {
182             init_once()?;
183         }
184 
185         let f = fixture.open_new_file()?;
186         assert_eq!(Some(Error::FileDescriptorNotInherited(f)), take_fd_ownership(f).err());
187         Ok(())
188     }
189 
190     #[test]
call_init_once_multiple_times() -> Result<()>191     fn call_init_once_multiple_times() -> Result<()> {
192         let _ = Fixture::setup(2)?;
193 
194         // SAFETY: assume files opened by Fixture are inherited ones
195         unsafe {
196             init_once()?;
197         }
198 
199         // SAFETY: for testing
200         let res = unsafe { init_once() };
201         assert!(res.is_err());
202         Ok(())
203     }
204 
205     #[test]
access_without_init_once() -> Result<()>206     fn access_without_init_once() -> Result<()> {
207         let fixture = Fixture::setup(2)?;
208 
209         let f = fixture.fds[0];
210         assert_eq!(Some(Error::NotInitialized), take_fd_ownership(f).err());
211         Ok(())
212     }
213 
214     #[test]
double_ownership() -> Result<()>215     fn double_ownership() -> Result<()> {
216         let fixture = Fixture::setup(2)?;
217         let f = fixture.fds[0];
218 
219         // SAFETY: assume files opened by Fixture are inherited ones
220         unsafe {
221             init_once()?;
222         }
223 
224         let f_owned = take_fd_ownership(f)?;
225         let f_double_owned = take_fd_ownership(f);
226         assert_eq!(Some(Error::OwnershipTaken(f)), f_double_owned.err());
227 
228         // just to highlight that f_owned is kept alive when the second call to take_fd_ownership
229         // is made.
230         drop(f_owned);
231         Ok(())
232     }
233 
234     #[test]
take_drop_retake() -> Result<()>235     fn take_drop_retake() -> Result<()> {
236         let fixture = Fixture::setup(2)?;
237         let f = fixture.fds[0];
238 
239         // SAFETY: assume files opened by Fixture are inherited ones
240         unsafe {
241             init_once()?;
242         }
243 
244         let f_owned = take_fd_ownership(f)?;
245         drop(f_owned);
246 
247         let f_double_owned = take_fd_ownership(f);
248         assert_eq!(Some(Error::OwnershipTaken(f)), f_double_owned.err());
249         Ok(())
250     }
251 
252     #[test]
cloexec() -> Result<()>253     fn cloexec() -> Result<()> {
254         let fixture = Fixture::setup(2)?;
255         let f = fixture.fds[0];
256 
257         // SAFETY: assume files opened by Fixture are inherited ones
258         unsafe {
259             init_once()?;
260         }
261 
262         // Intentionally cleaar cloexec to see if it is set by take_fd_ownership
263         fcntl(f, F_SETFD(FdFlag::empty()))?;
264 
265         let f_owned = take_fd_ownership(f)?;
266         let flags = fcntl(f_owned.as_raw_fd(), F_GETFD)?;
267         assert_eq!(flags, FdFlag::FD_CLOEXEC.bits());
268         Ok(())
269     }
270 }
271