Skip to main content

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}