Skip to content

Commit e0f2991

Browse files
committed
Implement io::Seek for io::BufReader<R> where R: io::Seek
Seeking the `BufReader` discards the internal buffer (and adjusts the offset appropriately when seeking with `SeekFrom::Current(_)`).
1 parent 1fd89b6 commit e0f2991

File tree

1 file changed

+108
-2
lines changed

1 file changed

+108
-2
lines changed

src/libstd/io/buffered.rs

Lines changed: 108 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ use io::prelude::*;
1818
use cmp;
1919
use error;
2020
use fmt;
21-
use io::{self, DEFAULT_BUF_SIZE, Error, ErrorKind};
21+
use io::{self, DEFAULT_BUF_SIZE, Error, ErrorKind, SeekFrom};
2222
use ptr;
2323
use iter;
2424

@@ -120,6 +120,51 @@ impl<R> fmt::Debug for BufReader<R> where R: fmt::Debug {
120120
}
121121
}
122122

123+
impl<R: Seek> Seek for BufReader<R> {
124+
/// Seek to an offset, in bytes, in the underlying reader.
125+
///
126+
/// The position used for seeking with `SeekFrom::Current(_)` is the
127+
/// position the underlying reader would be at if the `BufReader` had no
128+
/// internal buffer.
129+
///
130+
/// Seeking always discards the internal buffer, even if the seek position
131+
/// would otherwise fall within it. This guarantees that calling
132+
/// `.unwrap()` immediately after a seek yields the underlying reader at
133+
/// the same position.
134+
///
135+
/// See `std::io::Seek` for more details.
136+
///
137+
/// Note: In the edge case where you're seeking with `SeekFrom::Current(n)`
138+
/// where `n` minus the internal buffer length underflows an `i64`, two
139+
/// seeks will be performed instead of one. If the second seek returns
140+
/// `Err`, the underlying reader will be left at the same position it would
141+
/// have if you seeked to `SeekFrom::Current(0)`.
142+
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
143+
let result: u64;
144+
if let SeekFrom::Current(n) = pos {
145+
let remainder = (self.cap - self.pos) as i64;
146+
// it should be safe to assume that remainder fits within an i64 as the alternative
147+
// means we managed to allocate 8 ebibytes and that's absurd.
148+
// But it's not out of the realm of possibility for some weird underlying reader to
149+
// support seeking by i64::min_value() so we need to handle underflow when subtracting
150+
// remainder.
151+
if let Some(offset) = n.checked_sub(remainder) {
152+
result = try!(self.inner.seek(SeekFrom::Current(offset)));
153+
} else {
154+
// seek backwards by our remainder, and then by the offset
155+
try!(self.inner.seek(SeekFrom::Current(-remainder)));
156+
self.pos = self.cap; // empty the buffer
157+
result = try!(self.inner.seek(SeekFrom::Current(n)));
158+
}
159+
} else {
160+
// Seeking with Start/End doesn't care about our buffer length.
161+
result = try!(self.inner.seek(pos));
162+
}
163+
self.pos = self.cap; // empty the buffer
164+
Ok(result)
165+
}
166+
}
167+
123168
/// Wraps a Writer and buffers output to it
124169
///
125170
/// It can be excessively inefficient to work directly with a `Write`. For
@@ -478,7 +523,7 @@ impl<S: Write> fmt::Debug for BufStream<S> where S: fmt::Debug {
478523
mod tests {
479524
use prelude::v1::*;
480525
use io::prelude::*;
481-
use io::{self, BufReader, BufWriter, BufStream, Cursor, LineWriter};
526+
use io::{self, BufReader, BufWriter, BufStream, Cursor, LineWriter, SeekFrom};
482527
use test;
483528

484529
/// A dummy reader intended at testing short-reads propagation.
@@ -533,6 +578,67 @@ mod tests {
533578
assert_eq!(reader.read(&mut buf).unwrap(), 0);
534579
}
535580

581+
#[test]
582+
fn test_buffered_reader_seek() {
583+
let inner: &[u8] = &[5, 6, 7, 0, 1, 2, 3, 4];
584+
let mut reader = BufReader::with_capacity(2, io::Cursor::new(inner));
585+
586+
assert_eq!(reader.seek(SeekFrom::Start(3)).ok(), Some(3));
587+
assert_eq!(reader.fill_buf().ok(), Some(&[0, 1][..]));
588+
assert_eq!(reader.seek(SeekFrom::Current(0)).ok(), Some(3));
589+
assert_eq!(reader.fill_buf().ok(), Some(&[0, 1][..]));
590+
assert_eq!(reader.seek(SeekFrom::Current(1)).ok(), Some(4));
591+
assert_eq!(reader.fill_buf().ok(), Some(&[1, 2][..]));
592+
reader.consume(1);
593+
assert_eq!(reader.seek(SeekFrom::Current(-2)).ok(), Some(3));
594+
}
595+
596+
#[test]
597+
fn test_buffered_reader_seek_underflow() {
598+
// gimmick reader that yields its position modulo 256 for each byte
599+
struct PositionReader {
600+
pos: u64
601+
}
602+
impl Read for PositionReader {
603+
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
604+
let len = buf.len();
605+
for x in buf {
606+
*x = self.pos as u8;
607+
self.pos = self.pos.wrapping_add(1);
608+
}
609+
Ok(len)
610+
}
611+
}
612+
impl Seek for PositionReader {
613+
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
614+
match pos {
615+
SeekFrom::Start(n) => {
616+
self.pos = n;
617+
}
618+
SeekFrom::Current(n) => {
619+
self.pos = self.pos.wrapping_add(n as u64);
620+
}
621+
SeekFrom::End(n) => {
622+
self.pos = u64::max_value().wrapping_add(n as u64);
623+
}
624+
}
625+
Ok(self.pos)
626+
}
627+
}
628+
629+
let mut reader = BufReader::with_capacity(5, PositionReader { pos: 0 });
630+
assert_eq!(reader.fill_buf().ok(), Some(&[0, 1, 2, 3, 4][..]));
631+
assert_eq!(reader.seek(SeekFrom::End(-5)).ok(), Some(u64::max_value()-5));
632+
assert_eq!(reader.fill_buf().ok().map(|s| s.len()), Some(5));
633+
// the following seek will require two underlying seeks
634+
let expected = 9223372036854775802;
635+
assert_eq!(reader.seek(SeekFrom::Current(i64::min_value())).ok(), Some(expected));
636+
assert_eq!(reader.fill_buf().ok().map(|s| s.len()), Some(5));
637+
// seeking to 0 should empty the buffer.
638+
assert_eq!(reader.seek(SeekFrom::Current(0)).ok(), Some(expected));
639+
assert_eq!(reader.get_ref().pos, expected);
640+
}
641+
536642
#[test]
537643
fn test_buffered_writer() {
538644
let inner = Vec::new();

0 commit comments

Comments
 (0)