symbolic_common/
byteview.rs

1//! A wrapper type providing direct memory access to binary data.
2//!
3//! See the [`ByteView`] struct for more documentation.
4//!
5//! [`ByteView`]: struct.ByteView.html
6
7use std::borrow::Cow;
8use std::fs::File;
9use std::io;
10use std::ops::Deref;
11use std::path::Path;
12use std::sync::Arc;
13
14use memmap2::Mmap;
15
16use crate::cell::StableDeref;
17
18/// The owner of data behind a ByteView.
19///
20/// This can either be an mmapped file, an owned buffer or a borrowed binary slice.
21#[derive(Debug)]
22enum ByteViewBacking<'a> {
23    Buf(Cow<'a, [u8]>),
24    Mmap(Mmap),
25}
26
27impl Deref for ByteViewBacking<'_> {
28    type Target = [u8];
29
30    fn deref(&self) -> &Self::Target {
31        match *self {
32            ByteViewBacking::Buf(ref buf) => buf,
33            ByteViewBacking::Mmap(ref mmap) => mmap,
34        }
35    }
36}
37
38/// A smart pointer for byte data.
39///
40/// This type can be used to uniformly access bytes that were created either from mmapping in a
41/// path, a vector or a borrowed slice. A `ByteView` dereferences into a `&[u8]` and guarantees
42/// random access to the underlying buffer or file.
43///
44/// A `ByteView` can be constructed from borrowed slices, vectors or memory mapped from the file
45/// system directly.
46///
47/// # Example
48///
49/// The most common way to use `ByteView` is to construct it from a file handle. This will own the
50/// underlying file handle until the `ByteView` is dropped:
51///
52/// ```
53/// use std::io::Write;
54/// use symbolic_common::ByteView;
55///
56/// fn main() -> Result<(), std::io::Error> {
57///     let mut file = tempfile::tempfile()?;
58///     file.write_all(b"1234");
59///
60///     let view = ByteView::map_file(file)?;
61///     assert_eq!(view.as_slice(), b"1234");
62///     Ok(())
63/// }
64/// ```
65#[derive(Clone, Debug)]
66pub struct ByteView<'a> {
67    backing: Arc<ByteViewBacking<'a>>,
68}
69
70impl<'a> ByteView<'a> {
71    fn with_backing(backing: ByteViewBacking<'a>) -> Self {
72        ByteView {
73            backing: Arc::new(backing),
74        }
75    }
76
77    /// Constructs a `ByteView` from a `Cow`.
78    ///
79    /// # Example
80    ///
81    /// ```
82    /// use std::borrow::Cow;
83    /// use symbolic_common::ByteView;
84    ///
85    /// let cow = Cow::Borrowed(&b"1234"[..]);
86    /// let view = ByteView::from_cow(cow);
87    /// ```
88    pub fn from_cow(cow: Cow<'a, [u8]>) -> Self {
89        ByteView::with_backing(ByteViewBacking::Buf(cow))
90    }
91
92    /// Constructs a `ByteView` from a byte slice.
93    ///
94    /// # Example
95    ///
96    /// ```
97    /// use symbolic_common::ByteView;
98    ///
99    /// let view = ByteView::from_slice(b"1234");
100    /// ```
101    pub fn from_slice(buffer: &'a [u8]) -> Self {
102        ByteView::from_cow(Cow::Borrowed(buffer))
103    }
104
105    /// Constructs a `ByteView` from a vector of bytes.
106    ///
107    /// # Example
108    ///
109    /// ```
110    /// use symbolic_common::ByteView;
111    ///
112    /// let vec = b"1234".to_vec();
113    /// let view = ByteView::from_vec(vec);
114    /// ```
115    pub fn from_vec(buffer: Vec<u8>) -> Self {
116        ByteView::from_cow(Cow::Owned(buffer))
117    }
118
119    /// Constructs a `ByteView` from an open file handle by memory mapping the file.
120    ///
121    /// See [`ByteView::map_file_ref`] for a non-consuming version of this constructor.
122    ///
123    /// # Example
124    ///
125    /// ```
126    /// use std::io::Write;
127    /// use symbolic_common::ByteView;
128    ///
129    /// fn main() -> Result<(), std::io::Error> {
130    ///     let mut file = tempfile::tempfile()?;
131    ///     let view = ByteView::map_file(file)?;
132    ///     Ok(())
133    /// }
134    /// ```
135    pub fn map_file(file: File) -> Result<Self, io::Error> {
136        Self::map_file_ref(&file)
137    }
138
139    /// Constructs a `ByteView` from an open file handle by memory mapping the file.
140    ///
141    /// The main difference with [`ByteView::map_file`] is that this takes the [`File`] by
142    /// reference rather than consuming it.
143    ///
144    /// # Example
145    ///
146    /// ```
147    /// use std::io::Write;
148    /// use symbolic_common::ByteView;
149    ///
150    /// fn main() -> Result<(), std::io::Error> {
151    ///     let mut file = tempfile::tempfile()?;
152    ///     let view = ByteView::map_file_ref(&file)?;
153    ///     Ok(())
154    /// }
155    /// ```
156    pub fn map_file_ref(file: &File) -> Result<Self, io::Error> {
157        let backing = match unsafe { Mmap::map(file) } {
158            Ok(mmap) => ByteViewBacking::Mmap(mmap),
159            Err(err) => {
160                // this is raised on empty mmaps which we want to ignore. The 1006 Windows error
161                // looks like "The volume for a file has been externally altered so that the opened
162                // file is no longer valid."
163                if err.kind() == io::ErrorKind::InvalidInput
164                    || (cfg!(windows) && err.raw_os_error() == Some(1006))
165                {
166                    ByteViewBacking::Buf(Cow::Borrowed(b""))
167                } else {
168                    return Err(err);
169                }
170            }
171        };
172
173        Ok(ByteView::with_backing(backing))
174    }
175
176    /// Constructs a `ByteView` from any `std::io::Reader`.
177    ///
178    /// **Note**: This currently consumes the entire reader and stores its data in an internal
179    /// buffer. Prefer [`open`] when reading from the file system or [`from_slice`] / [`from_vec`]
180    /// for in-memory operations. This behavior might change in the future.
181    ///
182    /// # Example
183    ///
184    /// ```
185    /// use std::io::Cursor;
186    /// use symbolic_common::ByteView;
187    ///
188    /// fn main() -> Result<(), std::io::Error> {
189    ///     let reader = Cursor::new(b"1234");
190    ///     let view = ByteView::read(reader)?;
191    ///     Ok(())
192    /// }
193    /// ```
194    ///
195    /// [`open`]: struct.ByteView.html#method.open
196    /// [`from_slice`]: struct.ByteView.html#method.from_slice
197    /// [`from_vec`]: struct.ByteView.html#method.from_vec
198    pub fn read<R: io::Read>(mut reader: R) -> Result<Self, io::Error> {
199        let mut buffer = vec![];
200        reader.read_to_end(&mut buffer)?;
201        Ok(ByteView::from_vec(buffer))
202    }
203
204    /// Constructs a `ByteView` from a file path by memory mapping the file.
205    ///
206    /// # Example
207    ///
208    /// ```no_run
209    /// use symbolic_common::ByteView;
210    ///
211    /// fn main() -> Result<(), std::io::Error> {
212    ///     let view = ByteView::open("test.txt")?;
213    ///     Ok(())
214    /// }
215    /// ```
216    pub fn open<P: AsRef<Path>>(path: P) -> Result<Self, io::Error> {
217        let file = File::open(path)?;
218        Self::map_file(file)
219    }
220
221    /// Returns a slice of the underlying data.
222    ///
223    ///
224    /// # Example
225    ///
226    /// ```
227    /// use symbolic_common::ByteView;
228    ///
229    /// let view = ByteView::from_slice(b"1234");
230    /// let data = view.as_slice();
231    /// ```
232    #[inline(always)]
233    pub fn as_slice(&self) -> &[u8] {
234        self.backing.deref()
235    }
236}
237
238impl AsRef<[u8]> for ByteView<'_> {
239    #[inline(always)]
240    fn as_ref(&self) -> &[u8] {
241        self.as_slice()
242    }
243}
244
245impl Deref for ByteView<'_> {
246    type Target = [u8];
247
248    #[inline(always)]
249    fn deref(&self) -> &Self::Target {
250        self.as_slice()
251    }
252}
253
254unsafe impl StableDeref for ByteView<'_> {}
255
256#[cfg(test)]
257mod tests {
258    use super::*;
259
260    use std::io::{Read, Seek, Write};
261
262    use similar_asserts::assert_eq;
263    use tempfile::NamedTempFile;
264
265    #[test]
266    fn test_open_empty_file() -> Result<(), std::io::Error> {
267        let tmp = NamedTempFile::new()?;
268
269        let view = ByteView::open(tmp.path())?;
270        assert_eq!(&*view, b"");
271
272        Ok(())
273    }
274
275    #[test]
276    fn test_open_file() -> Result<(), std::io::Error> {
277        let mut tmp = NamedTempFile::new()?;
278
279        tmp.write_all(b"1234")?;
280
281        let view = ByteView::open(tmp.path())?;
282        assert_eq!(&*view, b"1234");
283
284        Ok(())
285    }
286
287    #[test]
288    fn test_mmap_fd_reuse() -> Result<(), std::io::Error> {
289        let mut tmp = NamedTempFile::new()?;
290        tmp.write_all(b"1234")?;
291
292        let view = ByteView::map_file_ref(tmp.as_file())?;
293
294        // This deletes the file on disk.
295        let _path = tmp.path().to_path_buf();
296        let mut file = tmp.into_file();
297        #[cfg(not(windows))]
298        {
299            assert!(!_path.exists());
300        }
301
302        // Ensure we can still read from the the file after mmapping and deleting it on disk.
303        let mut buf = Vec::new();
304        file.rewind()?;
305        file.read_to_end(&mut buf)?;
306        assert_eq!(buf, b"1234");
307        drop(file);
308
309        // Ensure the byteview can still read the file as well.
310        assert_eq!(&*view, b"1234");
311
312        Ok(())
313    }
314}