pathrs/handle.rs
1// SPDX-License-Identifier: MPL-2.0 OR LGPL-3.0-or-later
2/*
3 * libpathrs: safe path resolution on Linux
4 * Copyright (C) 2019-2025 SUSE LLC
5 * Copyright (C) 2026 Aleksa Sarai <cyphar@cyphar.com>
6 *
7 * == MPL-2.0 ==
8 *
9 * This Source Code Form is subject to the terms of the Mozilla Public
10 * License, v. 2.0. If a copy of the MPL was not distributed with this
11 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
12 *
13 * Alternatively, this Source Code Form may also (at your option) be used
14 * under the terms of the GNU Lesser General Public License Version 3, as
15 * described below:
16 *
17 * == LGPL-3.0-or-later ==
18 *
19 * This program is free software: you can redistribute it and/or modify it
20 * under the terms of the GNU Lesser General Public License as published by
21 * the Free Software Foundation, either version 3 of the License, or (at
22 * your option) any later version.
23 *
24 * This program is distributed in the hope that it will be useful, but
25 * WITHOUT ANY WARRANTY; without even the implied warranty of
26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
27 * Public License for more details.
28 *
29 * You should have received a copy of the GNU Lesser General Public License
30 * along with this program. If not, see <https://www.gnu.org/licenses/>.
31 */
32
33#![forbid(unsafe_code)]
34
35use crate::{
36 error::{Error, ErrorImpl},
37 flags::OpenFlags,
38 procfs::ProcfsHandle,
39 utils::FdExt,
40};
41
42use std::{
43 fs::File,
44 os::unix::io::{AsFd, BorrowedFd, OwnedFd},
45};
46
47/// A handle to an existing inode within a [`Root`].
48///
49/// This handle references an already-resolved path which can be used for the
50/// purpose of "re-opening" the handle and get an actual [`File`] which can be
51/// used for ordinary operations.
52///
53/// # Safety
54///
55/// It is critical for the safety of this library that **at no point** do you
56/// use interfaces like [`libc::openat`] directly on the [`OwnedFd`] you can
57/// extract from this [`Handle`]. **You must always do operations through a
58/// valid [`Root`].**
59///
60/// [`RawFd`]: std::os::unix::io::RawFd
61/// [`Root`]: crate::Root
62#[derive(Debug)]
63pub struct Handle {
64 inner: OwnedFd,
65}
66
67impl Handle {
68 /// Wrap an [`OwnedFd`] into a [`Handle`].
69 #[inline]
70 pub fn from_fd(fd: impl Into<OwnedFd>) -> Self {
71 Self { inner: fd.into() }
72 }
73
74 /// Borrow this [`Handle`] as a [`HandleRef`].
75 // XXX: We can't use Borrow/Deref for this because HandleRef takes a
76 // lifetime rather than being a pure reference. Ideally we would use
77 // Deref but it seems that won't be possible in standard Rust for a
78 // long time, if ever...
79 #[inline]
80 pub fn as_ref(&self) -> HandleRef<'_> {
81 HandleRef {
82 inner: self.as_fd(),
83 }
84 }
85
86 /// Create a copy of an existing [`Handle`].
87 ///
88 /// The new handle is completely independent from the original, but
89 /// references the same underlying file.
90 #[inline]
91 pub fn try_clone(&self) -> Result<Self, Error> {
92 self.as_ref().try_clone()
93 }
94
95 /// "Upgrade" the handle to a usable [`File`] handle.
96 ///
97 /// This new [`File`] handle is suitable for reading and writing. This does
98 /// not consume the original handle (allowing for it to be used many times).
99 ///
100 /// The [`File`] handle will be opened with `O_NOCTTY` and `O_CLOEXEC` set,
101 /// regardless of whether those flags are present in the `flags` argument.
102 /// You can correct these yourself if these defaults are not ideal for you:
103 ///
104 /// 1. `fcntl(fd, F_SETFD, 0)` will let you unset `O_CLOEXEC`.
105 /// 2. `ioctl(fd, TIOCSCTTY, 0)` will set the fd as the controlling terminal
106 /// (if you don't have one already, and the fd references a TTY).
107 ///
108 /// [`Root::create`]: crate::Root::create
109 #[doc(alias = "pathrs_reopen")]
110 #[inline]
111 pub fn reopen(&self, flags: impl Into<OpenFlags>) -> Result<File, Error> {
112 self.as_ref().reopen(flags)
113 }
114}
115
116impl From<OwnedFd> for Handle {
117 /// Shorthand for [`Handle::from_fd`].
118 fn from(fd: OwnedFd) -> Self {
119 Self::from_fd(fd)
120 }
121}
122
123impl From<Handle> for OwnedFd {
124 /// Unwrap a [`Handle`] to reveal the underlying [`OwnedFd`].
125 ///
126 /// **Note**: This method is primarily intended to allow for file descriptor
127 /// passing or otherwise transmitting file descriptor information. If you
128 /// want to get a [`File`] handle for general use, please use
129 /// [`Handle::reopen`] instead.
130 #[inline]
131 fn from(handle: Handle) -> Self {
132 handle.inner
133 }
134}
135
136impl AsFd for Handle {
137 /// Access the underlying file descriptor for a [`Handle`].
138 ///
139 /// **Note**: This method is primarily intended to allow for tests and other
140 /// code to check the status of the underlying [`OwnedFd`] without having to
141 /// use [`OwnedFd::from`]. It is not safe to use this [`BorrowedFd`]
142 /// directly to do filesystem operations. Please use the provided
143 /// [`HandleRef`] methods.
144 #[inline]
145 fn as_fd(&self) -> BorrowedFd<'_> {
146 self.inner.as_fd()
147 }
148}
149
150/// Borrowed version of [`Handle`].
151///
152/// Unlike [`Handle`], when [`HandleRef`] is dropped the underlying file
153/// descriptor is *not* closed. This is mainly useful for programs and libraries
154/// that have to do operations on [`&File`][File]s and [`BorrowedFd`]s passed
155/// from elsewhere.
156///
157/// [File]: std::fs::File
158// TODO: Is there any way we can restructure this to use Deref so that we don't
159// need to copy all of the methods into Handle? Probably not... Maybe GATs
160// will eventually support this but we'd still need a GAT-friendly Deref.
161#[derive(Copy, Clone, Debug)]
162pub struct HandleRef<'fd> {
163 inner: BorrowedFd<'fd>,
164}
165
166impl HandleRef<'_> {
167 /// Wrap a [`BorrowedFd`] into a [`HandleRef`].
168 pub fn from_fd(inner: BorrowedFd<'_>) -> HandleRef<'_> {
169 HandleRef { inner }
170 }
171
172 /// Create a copy of a [`HandleRef`].
173 ///
174 /// Note that (unlike [`BorrowedFd::clone`]) this method creates a full copy
175 /// of the underlying file descriptor and thus is more equivalent to
176 /// [`BorrowedFd::try_clone_to_owned`].
177 ///
178 /// To create a shallow copy of a [`HandleRef`], you can use
179 /// [`Clone::clone`] (or just [`Copy`]).
180 // TODO: We might need to call this something other than try_clone(), since
181 // it's a little too easy to confuse with Clone::clone() but we also
182 // really want to have Copy.
183 pub fn try_clone(&self) -> Result<Handle, Error> {
184 self.as_fd()
185 .try_clone_to_owned()
186 .map_err(|err| {
187 ErrorImpl::OsError {
188 operation: "clone underlying handle file".into(),
189 source: err,
190 }
191 .into()
192 })
193 .map(Handle::from_fd)
194 }
195
196 /// "Upgrade" the handle to a usable [`File`] handle.
197 ///
198 /// This new [`File`] handle is suitable for reading and writing. This does
199 /// not consume the original handle (allowing for it to be used many times).
200 ///
201 /// The [`File`] handle will be opened with `O_NOCTTY` and `O_CLOEXEC` set,
202 /// regardless of whether those flags are present in the `flags` argument.
203 /// You can correct these yourself if these defaults are not ideal for you:
204 ///
205 /// 1. `fcntl(fd, F_SETFD, 0)` will let you unset `O_CLOEXEC`.
206 /// 2. `ioctl(fd, TIOCSCTTY, 0)` will set the fd as the controlling terminal
207 /// (if you don't have one already, and the fd references a TTY).
208 ///
209 /// [`Root::create`]: crate::Root::create
210 #[doc(alias = "pathrs_reopen")]
211 pub fn reopen(&self, flags: impl Into<OpenFlags>) -> Result<File, Error> {
212 self.inner
213 .reopen(&ProcfsHandle::new()?, flags.into())
214 .map(File::from)
215 }
216
217 // TODO: All the different stat* interfaces?
218
219 // TODO: bind(). This might be safe to do (set the socket path to
220 // /proc/self/fd/...) but I'm a bit sad it'd be separate from
221 // Handle::reopen().
222}
223
224impl<'fd> From<BorrowedFd<'fd>> for HandleRef<'fd> {
225 /// Shorthand for [`HandleRef::from_fd`].
226 fn from(fd: BorrowedFd<'fd>) -> Self {
227 Self::from_fd(fd)
228 }
229}
230
231impl AsFd for HandleRef<'_> {
232 /// Access the underlying file descriptor for a [`HandleRef`].
233 ///
234 /// **Note**: This method is primarily intended to allow for tests and other
235 /// code to check the status of the underlying file descriptor. It is not
236 /// safe to use this [`BorrowedFd`] directly to do filesystem operations.
237 /// Please use the provided [`HandleRef`] methods.
238 #[inline]
239 fn as_fd(&self) -> BorrowedFd<'_> {
240 self.inner.as_fd()
241 }
242}
243
244#[cfg(test)]
245mod tests {
246 use crate::{Handle, HandleRef, Root};
247
248 use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, OwnedFd};
249
250 use anyhow::Error;
251 use pretty_assertions::assert_eq;
252
253 #[test]
254 fn from_fd() -> Result<(), Error> {
255 let handle = Root::open(".")?.resolve(".")?;
256 let handle_ref1 = handle.as_ref();
257 let handle_ref2 = HandleRef::from_fd(handle.as_fd());
258
259 assert_eq!(
260 handle.as_fd().as_raw_fd(),
261 handle_ref1.as_fd().as_raw_fd(),
262 "Handle::as_ref should have the same underlying fd"
263 );
264 assert_eq!(
265 handle.as_fd().as_raw_fd(),
266 handle_ref2.as_fd().as_raw_fd(),
267 "HandleRef::from_fd should have the same underlying fd"
268 );
269
270 Ok(())
271 }
272
273 #[test]
274 fn into_from_ownedfd() -> Result<(), Error> {
275 let handle = Root::open(".")?.resolve(".")?;
276 let handle_fd = handle.as_fd().as_raw_fd();
277
278 let owned: OwnedFd = handle.into();
279 let owned_fd = owned.as_fd().as_raw_fd();
280
281 let handle2: Handle = owned.into();
282 let handle2_fd = handle2.as_fd().as_raw_fd();
283
284 assert_eq!(
285 handle_fd, owned_fd,
286 "OwnedFd::from(handle) should have same underlying fd",
287 );
288 assert_eq!(
289 handle_fd, handle2_fd,
290 "Handle -> OwnedFd -> Handle roundtrip should have same underlying fd",
291 );
292
293 Ok(())
294 }
295
296 #[test]
297 fn from_borrowedfd() -> Result<(), Error> {
298 let handle = Root::open(".")?.resolve(".")?;
299 let borrowed_fd: BorrowedFd<'_> = handle.as_fd();
300 let handle_ref: HandleRef<'_> = borrowed_fd.into();
301
302 assert_eq!(
303 handle.as_fd().as_raw_fd(),
304 borrowed_fd.as_fd().as_raw_fd(),
305 "BorrowedFd::from(HandleRef) should have the same underlying fd"
306 );
307 assert_eq!(
308 handle.as_fd().as_raw_fd(),
309 handle_ref.as_fd().as_raw_fd(),
310 "HandleRef::from(BorrowedFd) should have the same underlying fd"
311 );
312
313 Ok(())
314 }
315}