cap_primitives/fs/
maybe_owned_file.rs

1use crate::fs::{open_unchecked, OpenOptions};
2use maybe_owned::MaybeOwned;
3use std::ops::Deref;
4use std::path::Component;
5use std::{fmt, fs, io, mem};
6#[cfg(racy_asserts)]
7use {crate::fs::file_path, std::path::PathBuf};
8
9/// Several places in the code need to be able to handle either owned or
10/// borrowed [`std::fs::File]`s. Cloning a `File` to let them always have an
11/// owned `File` is expensive and fallible, so use this `struct` to hold either
12/// one, and implement [`Deref`] to allow them to be handled in a uniform way.
13///
14/// This is similar to [`Cow`], except without the copy-on-write part ;-).
15/// `Cow` requires a `Clone` implementation, which `File` doesn't have, and
16/// most users of this type don't need copy-on-write behavior.
17///
18/// And, this type has the special `descend_to`, which just does an assignment,
19/// but also some useful assertion checks.
20///
21/// [`Deref`]: std::ops::Deref
22/// [`Cow`]: std::borrow::Cow
23pub(super) struct MaybeOwnedFile<'borrow> {
24    inner: MaybeOwned<'borrow, fs::File>,
25
26    #[cfg(racy_asserts)]
27    path: Option<PathBuf>,
28}
29
30impl<'borrow> MaybeOwnedFile<'borrow> {
31    /// Constructs a new `MaybeOwnedFile` which is not owned.
32    pub(super) fn borrowed(file: &'borrow fs::File) -> Self {
33        #[cfg(racy_asserts)]
34        let path = file_path(file);
35
36        Self {
37            inner: MaybeOwned::Borrowed(file),
38
39            #[cfg(racy_asserts)]
40            path,
41        }
42    }
43
44    /// Constructs a new `MaybeOwnedFile` which is owned.
45    pub(super) fn owned(file: fs::File) -> Self {
46        #[cfg(racy_asserts)]
47        let path = file_path(&file);
48
49        Self {
50            inner: MaybeOwned::Owned(file),
51
52            #[cfg(racy_asserts)]
53            path,
54        }
55    }
56
57    /// Like `borrowed` but does not do path checks.
58    #[allow(dead_code)]
59    pub(super) const fn borrowed_noassert(file: &'borrow fs::File) -> Self {
60        Self {
61            inner: MaybeOwned::Borrowed(file),
62
63            #[cfg(racy_asserts)]
64            path: None,
65        }
66    }
67
68    /// Like `owned` but does not do path checks.
69    #[allow(dead_code)]
70    pub(super) const fn owned_noassert(file: fs::File) -> Self {
71        Self {
72            inner: MaybeOwned::Owned(file),
73
74            #[cfg(racy_asserts)]
75            path: None,
76        }
77    }
78
79    /// Set this `MaybeOwnedFile` to a new owned file which is from a subtree
80    /// of the current file. Return a `MaybeOwnedFile` representing the
81    /// previous state.
82    pub(super) fn descend_to(&mut self, to: MaybeOwnedFile<'borrow>) -> Self {
83        #[cfg(racy_asserts)]
84        let path = self.path.clone();
85
86        #[cfg(racy_asserts)]
87        if let Some(to_path) = file_path(&to) {
88            if let Some(current_path) = &self.path {
89                assert!(
90                    to_path.starts_with(current_path),
91                    "attempted to descend from {:?} to {:?}",
92                    to_path.display(),
93                    current_path.display()
94                );
95            }
96            self.path = Some(to_path);
97        }
98
99        Self {
100            inner: mem::replace(&mut self.inner, to.inner),
101
102            #[cfg(racy_asserts)]
103            path,
104        }
105    }
106
107    /// Produce an owned `File`. This uses `open` on "." if needed to convert a
108    /// borrowed `File` to an owned one.
109    #[cfg_attr(windows, allow(dead_code))]
110    pub(super) fn into_file(self, options: &OpenOptions) -> io::Result<fs::File> {
111        match self.inner {
112            MaybeOwned::Owned(file) => Ok(file),
113            MaybeOwned::Borrowed(file) => {
114                // The only situation in which we'd be asked to produce an owned
115                // `File` is when there's a need to open "." within a directory
116                // to obtain a new handle.
117                open_unchecked(file, Component::CurDir.as_ref(), options).map_err(Into::into)
118            }
119        }
120    }
121
122    /// Assuming `self` holds an owned `File`, return it.
123    #[cfg_attr(any(windows, target_os = "freebsd"), allow(dead_code))]
124    pub(super) fn unwrap_owned(self) -> fs::File {
125        match self.inner {
126            MaybeOwned::Owned(file) => file,
127            MaybeOwned::Borrowed(_) => panic!("expected owned file"),
128        }
129    }
130}
131
132impl<'borrow> Deref for MaybeOwnedFile<'borrow> {
133    type Target = fs::File;
134
135    #[inline]
136    fn deref(&self) -> &Self::Target {
137        self.inner.as_ref()
138    }
139}
140
141impl<'borrow> fmt::Debug for MaybeOwnedFile<'borrow> {
142    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
143        self.deref().fmt(f)
144    }
145}