io_window/
lib.rs

1//! This library contains `IoWindow`, an I/O adapter for a stream of bytes that
2//! limits operations within a byte range.
3//!
4//! An `IoWindow` is conceptually similar to a mutable slice, applied to
5//! a reader or writer. Given a byte range `start..end`, position 0 of the
6//! `IoWindow` is position `start` of the underlying object; the end position of
7//! the `IoWindow` is position `start + end` of the underlying object; and the
8//! length of the `IoWindow` is `end - start`.
9//!
10//! ```
11//! # use std::io::prelude::*;
12//! use io_window::IoWindow;
13//!
14//! # fn main() -> std::io::Result<()> {
15//! let stream = std::io::Cursor::new([0; 8]);
16//! let mut window = IoWindow::new(stream, 2..6)?;
17//! assert_eq!(window.write(&[42; 16])?, 4);
18//! assert_eq!(
19//!     window.into_inner().into_inner(),
20//!     [0, 0, 42, 42, 42, 42, 0, 0]
21//! );
22//! # Ok(())
23//! # }
24//! ```
25//!
26//! One use of this library is operating within a partition of a disk image.
27//! For instance, if you have a filesystem implementation that uses a
28//! `Read + Write + Seek` object, you can use `IoWindow` to avoid needing to
29//! copy a disk image's partition into memory or another file, or reaching for a
30//! memory-mapped buffer.
31//!
32//! ```
33//! # use std::fs::File;
34//! # use io_window::IoWindow;
35//! const MEBIBYTE: u64 = 1024 * 1024;
36//!
37//! # fn main() -> std::io::Result<()> {
38//! # let disk = "/dev/null";
39//! let file = File::open(disk)?;
40//! let mut partition = IoWindow::new(file, MEBIBYTE..(64 * MEBIBYTE))?;
41//! # Ok(())
42//! # }
43//! ```
44//!
45//! It's also possible to provide a range with an unbounded end. If you were
46//! working with a file with a header that you needed the ability to modify and
47//! append to, you could use a range like `1024..` to create an `IoWindow` from
48//! position 1024 to the end of the file.
49
50#![warn(clippy::pedantic)]
51
52use std::io::{Read, Seek, SeekFrom, Write};
53use std::ops::{Bound, RangeBounds};
54
55/// Seekable I/O adapter that limits operations to a byte range.
56///
57/// For more, see the [crate documentation](self).
58#[derive(Debug, Default, Clone, PartialEq, Eq)]
59pub struct IoWindow<T: Seek> {
60    inner: T,
61    start: u64,
62    end: Option<u64>,
63}
64
65impl<T: Seek> IoWindow<T> {
66    /// Adapts an object to limit I/O operations within a byte range.
67    ///
68    /// If the current position is earlier than the start of the range, the
69    /// object is seeked to that start.
70    ///
71    /// A range with an unbounded start (for example, `..1024`) is treated as
72    /// starting at `0`.
73    ///
74    /// A range with an unbounded end (for example, `1024..`) means
75    /// the window ends at the end of the stream. Writing past the end
76    /// of the window is supported if the underlying object can grow
77    /// its length (some examples include [`File`](std::fs::File) and
78    /// [`Cursor<Vec<u8>>`][std::io::Cursor]).
79    ///
80    /// # Errors
81    ///
82    /// Returns an error if determining the current position or seeking fails.
83    pub fn new(mut inner: T, range: impl RangeBounds<u64>) -> std::io::Result<IoWindow<T>> {
84        let start = match range.start_bound() {
85            Bound::Included(pos) => *pos,
86            Bound::Excluded(pos) => pos.checked_add(1).ok_or(BadRange)?,
87            Bound::Unbounded => 0,
88        };
89        let end = match range.end_bound() {
90            Bound::Included(pos) => Some(pos.checked_add(1).ok_or(BadRange)?),
91            Bound::Excluded(pos) => Some(*pos),
92            Bound::Unbounded => None,
93        };
94        if inner.stream_position()? < start {
95            inner.seek(SeekFrom::Start(start))?;
96        }
97        Ok(IoWindow { inner, start, end })
98    }
99
100    /// Consumes this adapter, returning the underlying object.
101    pub fn into_inner(self) -> T {
102        self.inner
103    }
104
105    /// Gets a reference to the underlying object.
106    pub fn get_ref(&self) -> &T {
107        &self.inner
108    }
109
110    /// Gets a mutable reference to the underlying object.
111    ///
112    /// It is a logic error to seek the object earlier than the start of the
113    /// window. If you're not absolutely certain that code using this mutable
114    /// reference upholds this requirement, call [`Seek::rewind`] on this
115    /// adapter immediately after using the mutable reference.
116    pub fn get_mut(&mut self) -> &mut T {
117        &mut self.inner
118    }
119
120    /// Given a buffer of size `len`, return either `len` or the number of bytes
121    /// remaining in our window if it's smaller.
122    fn reduce_buf_len(&mut self, len: usize) -> std::io::Result<usize> {
123        Ok(if let Some(end) = self.end {
124            if let Some(remaining) = end.checked_sub(self.inner.stream_position()?) {
125                // `usize::try_from` only fails here if `remaining` is larger
126                // than `usize::MAX`; if that's the case, it must be larger
127                // then `len`.
128                match usize::try_from(remaining) {
129                    Ok(remaining) => len.min(remaining),
130                    Err(_) => len,
131                }
132            } else {
133                // If the position is beyond the end, there are no bytes
134                // remaining.
135                0
136            }
137        } else {
138            len
139        })
140    }
141}
142
143impl<T: Read + Seek> Read for IoWindow<T> {
144    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
145        let len = self.reduce_buf_len(buf.len())?;
146        self.inner.read(&mut buf[..len])
147    }
148}
149
150impl<T: Write + Seek> Write for IoWindow<T> {
151    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
152        let len = self.reduce_buf_len(buf.len())?;
153        self.inner.write(&buf[..len])
154    }
155
156    fn flush(&mut self) -> std::io::Result<()> {
157        self.inner.flush()
158    }
159}
160
161impl<T: Seek> Seek for IoWindow<T> {
162    fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
163        let adjusted = match pos {
164            SeekFrom::Start(pos) => SeekFrom::Start(self.start.checked_add(pos).ok_or(BadSeek)?),
165            SeekFrom::End(pos) => {
166                if let Some(end) = self.end {
167                    SeekFrom::Start(checked_add_signed(end, pos).ok_or(BadSeek)?)
168                } else {
169                    SeekFrom::End(pos)
170                }
171            }
172            SeekFrom::Current(0) => SeekFrom::Current(0),
173            SeekFrom::Current(pos) => SeekFrom::Start(
174                checked_add_signed(self.inner.stream_position()?, pos).ok_or(BadSeek)?,
175            ),
176        };
177        if let SeekFrom::Start(start) = adjusted {
178            if start < self.start {
179                Err(BadSeek)?;
180            }
181        }
182        Ok(self.inner.seek(adjusted)? - self.start)
183    }
184}
185
186#[inline]
187fn checked_add_signed(lhs: u64, rhs: i64) -> Option<u64> {
188    if rhs.is_negative() {
189        lhs.checked_sub(rhs.unsigned_abs())
190    } else {
191        lhs.checked_add(rhs.unsigned_abs())
192    }
193}
194
195macro_rules! err_shortcut {
196    ($ident:ident, $str:literal) => {
197        struct $ident;
198
199        impl From<$ident> for std::io::Error {
200            #[inline]
201            fn from(_: $ident) -> std::io::Error {
202                std::io::Error::new(std::io::ErrorKind::InvalidInput, $str)
203            }
204        }
205    };
206}
207err_shortcut!(BadRange, "overflowing range bound");
208err_shortcut!(
209    BadSeek,
210    "invalid seek to a negative or overflowing position"
211);
212
213#[cfg(test)]
214mod tests {
215    use std::io::{Cursor, ErrorKind, Read, Seek, SeekFrom, Write};
216
217    use super::IoWindow;
218
219    #[test]
220    fn range() -> std::io::Result<()> {
221        let v = Cursor::new(vec![0; 512]);
222        let mut window = IoWindow::new(v, 128..256)?;
223        assert_eq!(window.stream_position()?, 0);
224        assert_eq!(window.get_mut().stream_position()?, 128);
225
226        macro_rules! t {
227            ($seekfrom:expr, $windowpos:expr, $innerpos:expr) => {{
228                assert_eq!(window.seek($seekfrom)?, $windowpos);
229                assert_eq!(window.stream_position()?, $windowpos);
230                assert_eq!(window.inner.stream_position()?, $innerpos);
231            }};
232        }
233
234        // seeks within 128..256 work as expected)
235        t!(SeekFrom::Start(0), 0, 128);
236        t!(SeekFrom::Start(32), 32, 160);
237        t!(SeekFrom::End(0), 128, 256);
238        t!(SeekFrom::End(-32), 96, 224);
239        t!(SeekFrom::Current(-32), 64, 192);
240
241        // reads/writes work as expected
242        let mut buf = [0; 16];
243        t!(SeekFrom::Start(0), 0, 128);
244        assert_eq!(window.write(b"meow meow meow")?, 14);
245        t!(SeekFrom::Current(0), 14, 142);
246        assert!(window
247            .inner
248            .get_ref()
249            .iter()
250            .eq([0; 128].iter().chain(b"meow meow meow").chain(&[0; 370])));
251        t!(SeekFrom::Current(-4), 10, 138);
252        assert_eq!(window.read(&mut buf[..4])?, 4);
253        assert_eq!(&buf[..4], b"meow");
254
255        // reads/writes at the end of the range don't go out of the range
256        t!(SeekFrom::End(-4), 124, 252);
257        assert_eq!(window.write(b"meow meow meow")?, 4);
258        t!(SeekFrom::Current(0), 128, 256);
259        assert_eq!(&window.inner.get_ref()[256..], &[0; 256]);
260        t!(SeekFrom::End(-8), 120, 248);
261        assert_eq!(window.read(&mut buf[..])?, 8);
262        t!(SeekFrom::Current(0), 128, 256);
263        assert_eq!(&buf[..8], b"\0\0\0\0meow");
264
265        // seeking to a negative position relative to `start` should fail, and
266        // the position should not change
267        assert!(window.seek(SeekFrom::Current(-160)).is_err());
268        t!(SeekFrom::Current(0), 128, 256);
269        assert!(window.seek(SeekFrom::End(-160)).is_err());
270        t!(SeekFrom::Current(0), 128, 256);
271
272        // like Cursor, seeking beyond the end of the stream should work, but
273        // both reads and writes return Ok(0)
274        t!(SeekFrom::End(64), 192, 320);
275        assert_eq!(window.read(&mut [0; 64])?, 0);
276        t!(SeekFrom::Current(0), 192, 320);
277        assert_eq!(window.write(&[0; 64])?, 0);
278        t!(SeekFrom::Current(0), 192, 320);
279
280        let v = window.into_inner().into_inner();
281        assert_eq!(v.len(), 512);
282        assert_eq!(v.capacity(), 512);
283
284        Ok(())
285    }
286
287    #[test]
288    fn range_unbounded() -> std::io::Result<()> {
289        let v = Cursor::new(Vec::new());
290        let mut window = IoWindow::new(v, 128..)?;
291
292        assert_eq!(window.inner.stream_position()?, 128);
293        assert_eq!(window.inner.get_ref().len(), 0);
294        window.write_all(b"meow")?;
295        assert_eq!(window.inner.get_ref().len(), 132);
296
297        window.seek(SeekFrom::Start(0))?;
298        let mut buf = [0; 8];
299        assert_eq!(window.read(&mut buf[..])?, 4);
300        assert_eq!(&buf[..4], b"meow");
301
302        Ok(())
303    }
304
305    #[test]
306    fn zero_range() -> std::io::Result<()> {
307        let mut window = IoWindow::new(Cursor::new(Vec::new()), 0..0)?;
308        assert_eq!(window.write(&[0; 4])?, 0);
309        assert_eq!(window.read(&mut [0; 4])?, 0);
310        Ok(())
311    }
312
313    #[test]
314    fn wrapped() -> std::io::Result<()> {
315        let inner = IoWindow::new(Cursor::new([0; 512]), 128..256)?;
316        let mut window = IoWindow::new(inner, 32..64)?;
317        assert_eq!(window.write(&[42; 128])?, 32);
318        assert_eq!(window.stream_position()?, 32);
319        let mut inner = window.into_inner();
320        assert_eq!(inner.stream_position()?, 64);
321        let mut cursor = inner.into_inner();
322        assert_eq!(cursor.stream_position()?, 192);
323        assert!(cursor
324            .get_ref()
325            .iter()
326            .eq([0; 160].iter().chain(&[42; 32]).chain(&[0; 320])));
327        Ok(())
328    }
329
330    #[test]
331    fn copy() -> std::io::Result<()> {
332        let from = b"meow meow meow meow";
333        let mut to = IoWindow::new(Cursor::new([0; 32]), 0..24)?;
334        std::io::copy(&mut &from[..], &mut to)?;
335
336        let mut to = IoWindow::new(Cursor::new([0; 32]), 0..8)?;
337        assert_eq!(
338            std::io::copy(&mut &from[..], &mut to).unwrap_err().kind(),
339            ErrorKind::WriteZero
340        );
341
342        Ok(())
343    }
344}