diff --git a/src/Makefile.am b/src/Makefile.am
index 1c533da..f56eddc 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -320,8 +320,8 @@ TEST_SH = \
 	$(NULL)
 EXTRA_DIST += $(TEST_SH)
 
-check_PROGRAMS = dumpkeys reflect-text-view reflect-vte mev table xticker vteconv
-TESTS = table vteconv $(TEST_SH)
+check_PROGRAMS = dumpkeys reflect-text-view reflect-vte mev table xticker vteconv vtestream-file
+TESTS = table vteconv vtestream-file $(TEST_SH)
 
 reflect_text_view_CPPFLAGS = -DUSE_TEXT_VIEW $(AM_CPPFLAGS)
 reflect_text_view_CFLAGS = $(VTE_CFLAGS) $(AM_CFLAGS)
@@ -392,6 +392,24 @@ table_LDADD = \
 	$(GLIB_LIBS) \
 	$(GOBJECT_LIBS)
 
+vtestream_file_SOURCES = \
+	vtestream-base.h \
+	vtestream-file.h \
+	vtestream.c \
+	vtestream.h \
+	vteutils.c \
+	vteutils.h \
+	$(NULL)
+vtestream_file_CPPFLAGS = \
+	-DVTESTREAM_MAIN \
+	$(AM_CPPFLAGS)
+vtestream_file_CFLAGS = \
+	$(GLIB_CFLAGS) \
+	$(AM_CFLAGS)
+vtestream_file_LDADD = \
+	$(GLIB_LIBS) \
+	$(GOBJECT_LIBS)
+
 vteconv_SOURCES = buffer.h debug.c debug.h vteconv.c vteconv.h
 vteconv_CPPFLAGS = -DVTECONV_MAIN $(AM_CPPFLAGS)
 vteconv_CFLAGS = $(VTE_CFLAGS) $(AM_CFLAGS)
diff --git a/src/vtestream-base.h b/src/vtestream-base.h
index ebe3eec..8b8ac6d 100644
--- a/src/vtestream-base.h
+++ b/src/vtestream-base.h
@@ -37,6 +37,7 @@ typedef struct _VteStreamClass {
 	gboolean (*read) (VteStream *stream, gsize offset, char *data, gsize len);
 	void (*truncate) (VteStream *stream, gsize offset);
 	void (*advance_tail) (VteStream *stream, gsize offset);
+	gsize (*tail) (VteStream *stream);
 	gsize (*head) (VteStream *stream);
 } VteStreamClass;
 
@@ -87,6 +88,12 @@ _vte_stream_advance_tail (VteStream *stream, gsize offset)
 }
 
 gsize
+_vte_stream_tail (VteStream *stream)
+{
+	return VTE_STREAM_GET_CLASS (stream)->tail (stream);
+}
+
+gsize
 _vte_stream_head (VteStream *stream)
 {
 	return VTE_STREAM_GET_CLASS (stream)->head (stream);
diff --git a/src/vtestream-file.h b/src/vtestream-file.h
index 5c709b4..93ed6ea 100644
--- a/src/vtestream-file.h
+++ b/src/vtestream-file.h
@@ -20,31 +20,80 @@
  * Google Author(s): Behdad Esfahbod
  */
 
-#include <errno.h>
-
-#include "vteutils.h"
-
-
 /*
- * We provide two file implementations: one based on raw
- * syscalls, and one based on stdio.  The syscall one has
- * got more than enough testing in the past.  The stdio
- * is newer, but has a default cache, which greatly reduces
- * number of syscalls.
+ * The data structure implemented here remembers the last certain amount of
+ * data written, with the size dynamically changing. Basic operations include
+ * appending data (advancing the head), forgetting old data (advancing the
+ * tail), and random access read. In these cases the logical head and tail
+ * offsets can only increase. Rare special operations that don't influence the
+ * overall design are popping data from the head, and resetting the stream.
+ *
+ * The mapping from logical to physical offsets can be in one of 4 states:
+ * 1. One continuous region;
+ * 2. Two continuous regions, the second one preceding the first;
+ * 3. Three continuous regions, the second one preceding the first, and the
+ *    third being at the end;
+ * 4. Two continuous regions, the first one preceding the second.
+ *
+ * Initially, data is simply written to the file, we're in state 1:
+ * (A) abcd
+ *
+ * Later the tail starts to advance too, but we're still in state 1:
+ * (B) ___de
+ *
+ * At some point, based on heuristics, we wrap to the beginning, into state 2:
+ * (C) f__de
+ *
+ * The trivial case: If tail reaches EOF before head would bite tail, we're
+ * back at state 1 and we can even truncate the file:
+ * (D) fg___
+ * (E) fg
+ *
+ * Let's write some more data and advance the tail to get back to a state
+ * equivalent to (C).
+ * (F) k__ij
+ *
+ * If head would bite tail, we continue appending at EOF, entering state 3:
+ * (G) klmijno
+ *
+ * As tail keeps advancing, we're still in state 3:
+ * (H) klm_jnop
+ *
+ * When tail finishes with the middle segment, we enter state 4:
+ * (I) klm__nop
+ *
+ * Further advancing tail keeps us at state 4:
+ * (J) __m__nopqr
+ *
+ * When tail finishes with the first segment, we return to state 1:
+ * (K) _____nopqr
+ *
+ * Depending on the aforementioned heuristics, we might stay in state 1:
+ * (L) _____nopqrs
  *
- * Change the following conditional to 0 for syscalls, and
- * to 1 for stdio.  Please test both branches before
- * making changes to _file_t semantics or implementation.
+ * But probably we soon return to state 2:
+ * (M) ______opqrs
+ * (N) tu____opqrs
+ *
+ * and so on...
  */
 
-#if 0
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
 
+#include "vteutils.h"
 
-/*
- * File implementation using Unix syscalls.
- */
 
-#include <unistd.h>
+#ifndef VTESTREAM_MAIN
+#define VTE_FILE_STREAM_BLOCKSIZE 4096
+#else
+#define VTE_FILE_STREAM_BLOCKSIZE 4  /* Small block size for unit testing */
+#endif
+
+#define ALIGN(x) ((x) & ~(VTE_FILE_STREAM_BLOCKSIZE - 1))
+#define MOD(x)   ((x) &  (VTE_FILE_STREAM_BLOCKSIZE - 1))
+
 
 #ifndef HAVE_PREAD
 #define pread _pread
@@ -185,161 +234,338 @@ _file_write (_file_t *f, const char *data, gsize len, gsize offset)
 }
 
 
-#else
-
-
 /*
- * File implementation using stdio.
+ * VteUnbufferedFileStream: A file-based stream, implementing the tricky file
+ * layout described above; without any buffering.
  */
 
-#include <stdio.h>
+typedef struct _VteUnbufferedFileStream {
+	VteStream parent;
 
-/* Use unlocked versions if available. */
-#ifdef HAVE_FWRITE_UNLOCKED
-#	define fwrite	fwrite_unlocked
-#	define fread	fread_unlocked
-#	define fflush	fflush_unlocked
-#endif
+	_file_t file;
+	int state;
+	struct {
+		gsize st_tail;  /* Stream's logical tail offset. */
+		gsize st_head;  /* Stream's logical head offset. */
+		gsize fd_tail;  /* FD's physical tail offset. */
+		gsize fd_head;  /* FD's physical head offset. Redundant, nevermind. */
+	} segment[3];           /* At most 3 segments, [0] at the tail. */
+	gsize tail, head;       /* These are redundant too, for convenience. */
+} VteUnbufferedFileStream;
+#define VTE_UNBUFFERED_FILE_STREAM_SEGMENTS(s) ((s)->state == 4 ? 2 : (s)->state)
 
-typedef struct
-{
-	FILE *fp;
-	gsize pos;
-} _file_t;
+typedef VteStreamClass VteUnbufferedFileStreamClass;
 
-static inline void
-_file_init (_file_t *f)
+static GType _vte_unbuffered_file_stream_get_type (void);
+#define VTE_TYPE_UNBUFFERED_FILE_STREAM _vte_unbuffered_file_stream_get_type ()
+
+G_DEFINE_TYPE (VteUnbufferedFileStream, _vte_unbuffered_file_stream, VTE_TYPE_STREAM)
+
+VteStream *
+_vte_unbuffered_file_stream_new (void)
 {
-	f->fp = NULL;
-	f->pos = -1;
+	return (VteStream *) g_object_new (VTE_TYPE_UNBUFFERED_FILE_STREAM, NULL);
 }
 
-static inline void
-_file_open (_file_t *f, int fd)
+static void
+_vte_unbuffered_file_stream_init (VteUnbufferedFileStream *stream)
 {
-	f->fp = fdopen (fd, "w+");
-	f->pos = 0;
+	_file_init (&stream->file);
+	stream->state = 1;
 }
 
-static inline gboolean
-_file_isopen (_file_t *f)
+static void
+_vte_unbuffered_file_stream_finalize (GObject *object)
 {
-	return f->fp != NULL;
+	VteUnbufferedFileStream *stream = (VteUnbufferedFileStream *) object;
+
+	_file_close (&stream->file);
+
+	G_OBJECT_CLASS (_vte_unbuffered_file_stream_parent_class)->finalize(object);
 }
 
 static inline void
-_file_close (_file_t *f)
+_vte_unbuffered_file_stream_ensure_file (VteUnbufferedFileStream *stream)
 {
-	if (G_UNLIKELY (!_file_isopen (f)))
+	int fd;
+
+	if (G_LIKELY (_file_isopen (&stream->file)))
 		return;
 
-	fclose (f->fp);
-}
+        fd = _vte_mkstemp ();
+        if (fd == -1)
+                return;
 
+        _file_open (&stream->file, fd);
+}
 
-static gboolean
-_file_try_truncate (_file_t *f, gsize offset)
+static void
+_vte_unbuffered_file_stream_reset (VteStream *astream, gsize offset)
 {
-	if (G_UNLIKELY (!_file_isopen (f)))
-		return TRUE;
+	VteUnbufferedFileStream *stream = (VteUnbufferedFileStream *) astream;
 
-	/* We don't have to truncate.  The bytestream works
-	 * without a working truncate (hence the "try").
-	 * Here's a fallback implementation.  Things should
-	 * work with or without this.
+	/* Due to the buffering above this layer, small streams often get reset.
+	 * Calling ftruncate all the time is expensive, so do some heuristics here
+	 * not to truncate the file if it's quite small.
 	 */
-	if (1)
-	{
-		int ret;
-
-		fflush (f->fp);
+	int segment_with_highest_file_offset = stream->state == 3 ? 2 : stream->state == 4 ? 1 : 0;
+	if (stream->segment[segment_with_highest_file_offset].fd_head >= 16 * VTE_FILE_STREAM_BLOCKSIZE)
+		_file_reset (&stream->file);
 
-		do {
-			ret = ftruncate (fileno (f->fp), offset);
-		} while (ret == -1 && errno == EINTR);
-
-		return !ret;
-	}
-
-	return FALSE;
+	stream->segment[0].st_tail = stream->segment[0].st_head = stream->tail = stream->head = offset;
+	stream->segment[0].fd_tail = stream->segment[0].fd_head = 0;
+	stream->state = 1;
 }
 
+/*
+ * When appending, the following state transfers can occur (even more of them
+ * if the data written is large enough):
+ * 1->2, 2->3.
+ */
 static void
-_file_reset (_file_t *f)
+_vte_unbuffered_file_stream_append (VteStream *astream, const char *data, gsize len)
 {
-	int fd;
+	VteUnbufferedFileStream *stream = (VteUnbufferedFileStream *) astream;
+	int last_segment;
 
-	if (_file_try_truncate (f, 0))
-		return;
+	_vte_unbuffered_file_stream_ensure_file (stream);
 
-	if (G_UNLIKELY (!_file_isopen (f)))
-		return;
+	g_assert(len > 0);
 
-	/* Reopen file to truncate it. */
+	/* Heuristics on when to start the second segment. The only crucial thing is that fd_tail needs to be greater than 0. */
+	if (G_UNLIKELY (stream->state == 1 && 2 * stream->segment[0].fd_tail > stream->segment[0].fd_head)) {
+		stream->segment[1].st_tail = stream->segment[1].st_head = stream->segment[0].st_head;
+		stream->segment[1].fd_tail = stream->segment[1].fd_head = 0;
+		stream->state = 2;
+	}
 
-	fd = dup (fileno (f->fp));
+	if (stream->state == 2) {
+		gsize l;
+		l = MIN(len, stream->segment[0].fd_tail - stream->segment[1].fd_head);
+		_file_write (&stream->file, data, l, stream->segment[1].fd_head);
+		data += l; len -= l;
+		stream->segment[1].st_head += l; stream->segment[1].fd_head += l;
+		if (len == 0) {
+			stream->head = stream->segment[1].st_head;
+			return;
+		}
+		stream->segment[2].st_tail = stream->segment[2].st_head = stream->segment[1].st_head;
+		stream->segment[2].fd_tail = stream->segment[2].fd_head = stream->segment[0].fd_head;
+		stream->state = 3;
+	}
 
-	_file_close (f);
-	_file_open (f, fd);
+	last_segment = VTE_UNBUFFERED_FILE_STREAM_SEGMENTS(stream) - 1;
+	_file_write (&stream->file, data, len, stream->segment[last_segment].fd_head);
+	stream->segment[last_segment].st_head += len; stream->segment[last_segment].fd_head += len;
+	stream->head = stream->segment[last_segment].st_head;
 }
 
 static gboolean
-_file_seek (_file_t *f, gsize offset)
+_vte_unbuffered_file_stream_read (VteStream *astream, gsize offset, char *data, gsize len)
 {
-	if (f->pos != offset)
-	{
-		if (-1 == fseek (f->fp, offset, SEEK_SET))
+	VteUnbufferedFileStream *stream = (VteUnbufferedFileStream *) astream;
+	gsize l;
+
+	int segments = VTE_UNBUFFERED_FILE_STREAM_SEGMENTS(stream);
+	int i;
+
+	/* Out of bounds request.
+	 * FIXME: Need to detect when offset is extremely large
+	 * (actuall a negative value stored in unsigned gsize),
+	 * and the read attempt wraps around to a sane offset:
+	 * https://bugzilla.gnome.org/show_bug.cgi?id=740347#c3
+	 */
+	if (G_UNLIKELY (offset < stream->tail || offset + len > stream->head || offset + len < offset)) {
+		/* If completely out of bounds, the caller expects a FALSE. */
+		if (offset + len <= stream->tail || offset >= stream->head)
 			return FALSE;
-		f->pos = offset;
+		/* Partial out of bounds requests never happen. */
+		g_assert_not_reached();
 	}
-	return TRUE;
+
+	if (len == 0)
+		return TRUE;
+	for (i = 0; i < segments; i++) {
+		if (offset >= stream->segment[i].st_head)
+			continue;
+		l = MIN(stream->segment[i].st_head - offset, len);
+		l = _file_read (&stream->file, data, l, offset + stream->segment[i].fd_tail - stream->segment[i].st_tail);
+		offset += l; data += l; len -= l;
+		if (len == 0)
+			return TRUE;
+	}
+
+	return FALSE;
 }
 
-static gsize
-_file_read (_file_t *f, char *data, gsize len, gsize offset)
+/*
+ * When truncating, the following state transfers can occur (even more of them
+ * if the truncated amount is large enough):
+ * 4->1, 3->2, 2->1.
+ */
+static void
+_vte_unbuffered_file_stream_truncate (VteStream *astream, gsize offset)
 {
-	gsize ret;
+	VteUnbufferedFileStream *stream = (VteUnbufferedFileStream *) astream;
+	int last_segment;
 
-	if (G_UNLIKELY (!_file_isopen (f)))
-		return 0;
+	g_assert (offset >= stream->tail);
+	g_assert (offset <= stream->head);
 
-	if (!_file_seek (f, offset))
-		return 0;
+	if (G_UNLIKELY (offset == stream->tail)) {
+		_vte_unbuffered_file_stream_reset (astream, offset);
+		return;
+	}
 
-	ret = fread (data, 1, len, f->fp);
-	f->pos += ret;
-	return ret;
+	while (TRUE) {
+		last_segment = VTE_UNBUFFERED_FILE_STREAM_SEGMENTS(stream) - 1;
+		if (offset >= stream->segment[last_segment].st_head)
+			return;
+		if (offset > stream->segment[last_segment].st_tail) {
+			/* Truncate some (but not all) bytes from the last segment. */
+			gsize diff = stream->segment[last_segment].st_head - offset;
+			stream->segment[last_segment].st_head -= diff;
+			stream->segment[last_segment].fd_head -= diff;
+			stream->head = stream->segment[last_segment].st_head;
+			if (stream->state != 2) {
+				_file_try_truncate(&stream->file, stream->segment[last_segment].fd_head);
+			}
+			return;
+		} else {
+			/* Discard the whole last segment. */
+			switch (stream->state) {
+			case 1:
+				g_assert_not_reached();
+				break;
+			case 2:
+				stream->state = 1;
+				break;
+			case 3:
+				stream->state = 2;
+				_file_try_truncate(&stream->file, stream->segment[0].fd_head);
+				break;
+			case 4:
+				stream->state = 1;
+				_file_try_truncate(&stream->file, stream->segment[0].fd_head);
+				break;
+			default:
+				g_assert_not_reached();
+				break;
+			}
+		}
+		last_segment = VTE_UNBUFFERED_FILE_STREAM_SEGMENTS(stream) - 1;
+		stream->head = stream->segment[last_segment].st_head;
+	}
 }
 
+/*
+ * When advancing the tail, the following state transfers can occur (even more
+ * of them if the amount discarded is large enough):
+ * 2->1, 3->4, 4->1.
+ */
 static void
-_file_write (_file_t *f, const char *data, gsize len, gsize offset)
+_vte_unbuffered_file_stream_advance_tail (VteStream *astream, gsize offset)
 {
-	gsize ret;
+	VteUnbufferedFileStream *stream = (VteUnbufferedFileStream *) astream;
 
-	g_assert (_file_isopen (f) || !len);
+	g_assert (offset >= stream->tail);
+	g_assert (offset <= stream->head);
 
-	if (!_file_seek (f, offset))
+	if (G_UNLIKELY (offset == stream->head)) {
+		_vte_unbuffered_file_stream_reset (astream, offset);
 		return;
+	}
 
-	ret = fwrite (data, 1, len, f->fp);
-	f->pos += ret;
+	while (offset > stream->segment[0].st_tail) {
+		if (offset < stream->segment[0].st_head) {
+			/* Drop some (but not all) bytes from the first segment. */
+			stream->segment[0].fd_tail += offset - stream->segment[0].st_tail;
+			stream->segment[0].st_tail = stream->tail = offset;
+			return;
+		} else {
+			/* Drop the entire first segment. */
+			switch (stream->state) {
+			case 1:
+				g_assert_not_reached();
+				break;
+			case 2:
+				stream->segment[0] = stream->segment[1];
+				_file_try_truncate (&stream->file, stream->segment[0].fd_head);
+				stream->state = 1;
+				break;
+			case 3:
+				stream->segment[0] = stream->segment[1];
+				stream->segment[1] = stream->segment[2];
+				stream->state = 4;
+				break;
+			case 4:
+				stream->segment[0] = stream->segment[1];
+				stream->state = 1;
+				break;
+			default:
+				g_assert_not_reached();
+				break;
+			}
+		}
+	}
+	g_assert(stream->segment[0].st_tail == offset);
+	stream->tail = offset;
 }
 
+static gsize
+_vte_unbuffered_file_stream_tail (VteStream *astream)
+{
+	VteUnbufferedFileStream *stream = (VteUnbufferedFileStream *) astream;
+
+	return stream->tail;
+}
 
-#endif
+static gsize
+_vte_unbuffered_file_stream_head (VteStream *astream)
+{
+	VteUnbufferedFileStream *stream = (VteUnbufferedFileStream *) astream;
+
+	return stream->head;
+}
+
+static void
+_vte_unbuffered_file_stream_class_init (VteUnbufferedFileStreamClass *klass)
+{
+	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+	gobject_class->finalize = _vte_unbuffered_file_stream_finalize;
+
+	klass->reset = _vte_unbuffered_file_stream_reset;
+	klass->append = _vte_unbuffered_file_stream_append;
+	klass->read = _vte_unbuffered_file_stream_read;
+	klass->truncate = _vte_unbuffered_file_stream_truncate;
+	klass->advance_tail = _vte_unbuffered_file_stream_advance_tail;
+	klass->tail = _vte_unbuffered_file_stream_tail;
+	klass->head = _vte_unbuffered_file_stream_head;
+}
 
 
 /*
- * VteFileStream: A file-based stream
+ * VteFileStream: Implement buffering/caching on top of VteUnbufferedFileStream.
+ * This offers the same API as the non-caching counterpart. These methods always
+ * use aligned 4kB blocks when talking to the underlying non-caching methods.
  */
 
 typedef struct _VteFileStream {
-	VteStream parent;
+	VteUnbufferedFileStream parent;
 
-	/* The first file/offset is for the write head, second is for last page */
-	_file_t file[2];
-	gsize offset[2];
-	gsize head;
+	gsize blocksize;
+
+	char *rbuf;
+	/* Offset of the cached record, always a multiple of block size.
+	 * Use a value of 1 (or anything that's not a multiple of block size)
+	 * to denote if no record is cached. */
+	gsize rbuf_offset;
+
+	char *wbuf;
+	gsize wbuf_len;
+
+	gsize head, tail;
 } VteFileStream;
 
 typedef VteStreamClass VteFileStreamClass;
@@ -349,13 +575,6 @@ static GType _vte_file_stream_get_type (void);
 
 G_DEFINE_TYPE (VteFileStream, _vte_file_stream, VTE_TYPE_STREAM)
 
-static void
-_vte_file_stream_init (VteFileStream *stream)
-{
-	_file_init (&stream->file[0]);
-	_file_init (&stream->file[1]);
-}
-
 VteStream *
 _vte_file_stream_new (void)
 {
@@ -363,40 +582,37 @@ _vte_file_stream_new (void)
 }
 
 static void
-_vte_file_stream_finalize (GObject *object)
+_vte_file_stream_init (VteFileStream *stream)
 {
-	VteFileStream *stream = (VteFileStream *) object;
-
-	_file_close (&stream->file[0]);
-	_file_close (&stream->file[1]);
+	_vte_unbuffered_file_stream_init ((VteUnbufferedFileStream *) stream);
 
-	G_OBJECT_CLASS (_vte_file_stream_parent_class)->finalize(object);
+	stream->rbuf = g_malloc(VTE_FILE_STREAM_BLOCKSIZE);
+	stream->wbuf = g_malloc(VTE_FILE_STREAM_BLOCKSIZE);
+	stream->rbuf_offset = 1;  /* Invalidate the read cache. */
 }
 
-static inline void
-_vte_file_stream_ensure_file0 (VteFileStream *stream)
+static void
+_vte_file_stream_finalize (GObject *object)
 {
-	int fd;
-
-	if (G_LIKELY (_file_isopen (&stream->file[0])))
-		return;
+	VteFileStream *stream = (VteFileStream *) object;
 
-        fd = _vte_mkstemp ();
-        if (fd == -1)
-                return;
+	g_free(stream->rbuf);
+	g_free(stream->wbuf);
 
-        _file_open (&stream->file[0], fd);
+	G_OBJECT_CLASS (_vte_file_stream_parent_class)->finalize(object);
 }
 
 static void
 _vte_file_stream_reset (VteStream *astream, gsize offset)
 {
 	VteFileStream *stream = (VteFileStream *) astream;
+	gsize offset_aligned = ALIGN(offset);
 
-	_file_reset (&stream->file[0]);
-	_file_reset (&stream->file[1]);
-
-	stream->head = stream->offset[0] = stream->offset[1] = offset;
+	_vte_unbuffered_file_stream_reset (astream, offset_aligned);
+	stream->tail = stream->head = offset;
+	memset(stream->wbuf, 0, MOD(offset));
+	stream->wbuf_len = MOD(offset);
+	stream->rbuf_offset = 1;  /* Invalidate the read cache. */
 }
 
 static void
@@ -404,38 +620,55 @@ _vte_file_stream_append (VteStream *astream, const char *data, gsize len)
 {
 	VteFileStream *stream = (VteFileStream *) astream;
 
-	_vte_file_stream_ensure_file0 (stream);
+	g_assert (len > 0);
 
-	_file_write (&stream->file[0], data, len, stream->head - stream->offset[0]);
 	stream->head += len;
+	while (len) {
+		gsize l = MIN(VTE_FILE_STREAM_BLOCKSIZE - stream->wbuf_len, len);
+		memcpy(stream->wbuf + stream->wbuf_len, data, l);
+		stream->wbuf_len += l; data += l; len -= l;
+		if (stream->wbuf_len == VTE_FILE_STREAM_BLOCKSIZE) {
+			_vte_unbuffered_file_stream_append (astream, stream->wbuf, VTE_FILE_STREAM_BLOCKSIZE);
+			stream->wbuf_len = 0;
+		}
+	}
 }
 
 static gboolean
 _vte_file_stream_read (VteStream *astream, gsize offset, char *data, gsize len)
 {
 	VteFileStream *stream = (VteFileStream *) astream;
-	gsize l;
-
-	if (G_UNLIKELY (offset < stream->offset[1]))
-		return FALSE;
 
-	if (offset < stream->offset[0]) {
-		l = _file_read (&stream->file[1], data, len, offset - stream->offset[1]);
-		offset += l; data += l; len -= l; if (!len) return TRUE;
+	/* Out of bounds request.
+	 * FIXME: Need to detect when offset is extremely large
+	 * (actuall a negative value stored in unsigned gsize),
+	 * and the read attempt wraps around to a sane offset:
+	 * https://bugzilla.gnome.org/show_bug.cgi?id=740347#c3
+	 */
+	if (G_UNLIKELY (offset < stream->tail || offset + len > stream->head || offset + len < offset)) {
+		/* If completely out of bounds, the caller expects a FALSE. */
+		if (offset + len <= stream->tail || offset >= stream->head)
+			return FALSE;
+		/* Partial out of bounds requests never happen. */
+		g_assert_not_reached();
 	}
 
-	l = _file_read (&stream->file[0], data, len, offset - stream->offset[0]);
-	offset += l; data += l; len -= l; if (!len) return TRUE;
-
-	return FALSE;
-}
-
-static void
-_vte_file_stream_swap_fds (VteFileStream *stream)
-{
-	_file_t f;
-
-	f = stream->file[0]; stream->file[0] = stream->file[1]; stream->file[1] = f;
+	while (len && offset < stream->parent.head) {
+		gsize l = MIN(VTE_FILE_STREAM_BLOCKSIZE - MOD(offset), len);
+		gsize offset_aligned = ALIGN(offset);
+		if (offset_aligned != stream->rbuf_offset) {
+			if (!_vte_unbuffered_file_stream_read (astream, offset_aligned, stream->rbuf, VTE_FILE_STREAM_BLOCKSIZE))
+				return FALSE;
+			stream->rbuf_offset = offset_aligned;
+		}
+		memcpy(data, stream->rbuf + MOD(offset), l);
+		offset += l; data += l; len -= l;
+	}
+	if (len) {
+		g_assert(MOD(offset) + len <= stream->wbuf_len);
+		memcpy(data, stream->wbuf + MOD(offset), len);
+	}
+	return TRUE;
 }
 
 static void
@@ -443,19 +676,28 @@ _vte_file_stream_truncate (VteStream *astream, gsize offset)
 {
 	VteFileStream *stream = (VteFileStream *) astream;
 
-	if (G_UNLIKELY (offset < stream->offset[1])) {
-		_file_reset (&stream->file[1]);
-		stream->offset[1] = offset;
-	}
+	g_assert (offset >= stream->tail);
+	g_assert (offset <= stream->head);
+
+	if (offset < stream->parent.head) {
+		/* Truncating goes back to the part that we've written to the
+		 * file. For simplicity (since this is a very rare, user
+		 * initiated event - the window is made taller) go for the
+		 * simplest local hack here that allows to leave the rest of
+		 * the code intact, that is, read back the new partial last
+		 * block to the write cache. */
+		gsize offset_aligned = ALIGN(offset);
+		if (!_vte_unbuffered_file_stream_read (astream, offset_aligned, stream->wbuf, VTE_FILE_STREAM_BLOCKSIZE)) {
+			/* what now? */
+			memset(stream->wbuf, 0, VTE_FILE_STREAM_BLOCKSIZE);
+		}
+		_vte_unbuffered_file_stream_truncate (astream, offset_aligned);
 
-	if (G_UNLIKELY (offset < stream->offset[0])) {
-		_file_reset (&stream->file[0]);
-		stream->offset[0] = stream->offset[1];
-		_vte_file_stream_swap_fds (stream);
-	} else {
-		_file_try_truncate (&stream->file[0], offset - stream->offset[0]);
+		if (stream->rbuf_offset >= offset_aligned) {
+			stream->rbuf_offset = 1;  /* Invalidate the read cache. */
+		}
 	}
-
+	stream->wbuf_len = MOD(offset);
 	stream->head = offset;
 }
 
@@ -464,14 +706,23 @@ _vte_file_stream_advance_tail (VteStream *astream, gsize offset)
 {
 	VteFileStream *stream = (VteFileStream *) astream;
 
-	g_assert(offset <= stream->head);
+	g_assert (offset >= stream->tail);
+	g_assert (offset <= stream->head);
 
-	if (offset >= stream->offset[0]) {
-		stream->offset[1] = stream->offset[0];
-		stream->offset[0] = stream->head;
-		_vte_file_stream_swap_fds (stream);
-		_file_reset (&stream->file[0]);
-	}
+	stream->tail = offset;
+
+	offset = ALIGN(offset);
+	if (offset < stream->parent.segment[0].st_tail) return;
+
+	_vte_unbuffered_file_stream_advance_tail (astream, offset);
+}
+
+static gsize
+_vte_file_stream_tail (VteStream *astream)
+{
+	VteFileStream *stream = (VteFileStream *) astream;
+
+	return stream->tail;
 }
 
 static gsize
@@ -482,6 +733,7 @@ _vte_file_stream_head (VteStream *astream)
 	return stream->head;
 }
 
+
 static void
 _vte_file_stream_class_init (VteFileStreamClass *klass)
 {
@@ -494,5 +746,538 @@ _vte_file_stream_class_init (VteFileStreamClass *klass)
 	klass->read = _vte_file_stream_read;
 	klass->truncate = _vte_file_stream_truncate;
 	klass->advance_tail = _vte_file_stream_advance_tail;
+	klass->tail = _vte_file_stream_tail;
 	klass->head = _vte_file_stream_head;
 }
+
+
+#ifdef VTESTREAM_MAIN
+
+static void
+assert_file_contents_n (int fd, const char *s, int n)
+{
+	char buf[100];
+	ssize_t filesize = pread(fd, buf, 100, 0);
+	g_assert (filesize == n);
+	g_assert (memcmp(buf, s, n) == 0);
+}
+
+static void
+assert_file_contents (int fd, const char *s)
+{
+	assert_file_contents_n (fd, s, strlen(s));
+}
+
+static void
+assert_file_contents_starts_with_n (int fd, const char *s, int n)
+{
+	char buf[100];
+	ssize_t filesize = pread(fd, buf, 100, 0);
+	g_assert (filesize >= n);
+	g_assert (memcmp(buf, s, n) == 0);
+}
+
+static void
+assert_file_contents_starts_with (int fd, const char *s)
+{
+	assert_file_contents_starts_with_n (fd, s, strlen(s));
+}
+
+static void
+assert_stream_contents_n (VteStream *astream, const char *s, gsize n)
+{
+	char buf[100];
+	gsize tail = _vte_stream_tail (astream);
+	gsize head = _vte_stream_head (astream);
+	g_assert (head - tail == n);
+	g_assert (_vte_stream_read (astream, tail, buf, n));
+	g_assert (memcmp(buf, s, n) == 0);
+}
+
+static void
+assert_stream_contents (VteStream *astream, const char *s)
+{
+	assert_stream_contents_n (astream, s, strlen(s));
+}
+
+/* Unbuffered: Test the example at the top of this file. */
+static void
+test_unbuffered_example (void)
+{
+	VteStream *as = _vte_unbuffered_file_stream_new();
+	VteUnbufferedFileStream *s = (VteUnbufferedFileStream *) as;
+
+	g_assert (s->state == 1 && s->tail == 0 && s->head == 0);
+	assert_stream_contents (as, "");
+
+	/* (A) */
+	_vte_stream_append (as, "abcd", 4);
+	assert_file_contents (s->file.fd, "abcd");
+	assert_stream_contents (as, "abcd");
+
+	/* (B) */
+	_vte_stream_append (as, "e", 1);
+	_vte_stream_advance_tail (as, 3);
+	g_assert (s->state == 1 && s->tail == 3 && s->head == 5);
+	assert_file_contents (s->file.fd, "abcde");
+	assert_stream_contents (as, "de");
+
+	/* (C) */
+	/* This relies on the exact current behavior of the heuristics to switch to state 2. */
+	_vte_stream_append (as, "f", 1);
+	g_assert (s->state == 2 && s->tail == 3 && s->head == 6);
+	assert_file_contents (s->file.fd, "fbcde");
+	assert_stream_contents (as, "def");
+
+	/* (D)-(E) */
+	_vte_stream_append (as, "g", 1);
+	_vte_stream_advance_tail (as, 4);
+	g_assert (s->state == 2 && s->tail == 4 && s->head == 7);
+	assert_file_contents (s->file.fd, "fgcde");
+	assert_stream_contents (as, "efg");
+
+	_vte_stream_advance_tail (as, 5);
+	g_assert (s->state == 1 && s->tail == 5 && s->head == 7);
+	assert_file_contents (s->file.fd, "fg");
+	assert_stream_contents (as, "fg");
+
+	/* (F) */
+	_vte_stream_append (as, "hij", 3);
+	_vte_stream_advance_tail (as, 8);
+	g_assert (s->state == 1 && s->tail == 8 && s->head == 10);
+	assert_file_contents (s->file.fd, "fghij");
+	assert_stream_contents (as, "ij");
+
+	_vte_stream_append (as, "k", 1);
+	g_assert (s->state == 2 && s->tail == 8 && s->head == 11);
+	assert_file_contents (s->file.fd, "kghij");
+	assert_stream_contents (as, "ijk");
+
+	/* (G) */
+	_vte_stream_append (as, "lmno", 4);
+	g_assert (s->state == 3 && s->tail == 8 && s->head == 15);
+	assert_file_contents (s->file.fd, "klmijno");
+	assert_stream_contents (as, "ijklmno");
+
+	/* (H) */
+	_vte_stream_append (as, "p", 1);
+	_vte_stream_advance_tail (as, 9);
+	g_assert (s->state == 3 && s->tail == 9 && s->head == 16);
+	assert_file_contents (s->file.fd, "klmijnop");
+	assert_stream_contents (as, "jklmnop");
+
+	/* (I) */
+	_vte_stream_advance_tail (as, 10);
+	g_assert (s->state == 4 && s->tail == 10 && s->head == 16);
+	assert_file_contents (s->file.fd, "klmijnop");
+	assert_stream_contents (as, "klmnop");
+
+	/* (J) */
+	_vte_stream_append (as, "qr", 2);
+	_vte_stream_advance_tail (as, 12);
+	g_assert (s->state == 4 && s->tail == 12 && s->head == 18);
+	assert_file_contents (s->file.fd, "klmijnopqr");
+	assert_stream_contents (as, "mnopqr");
+
+	/* (K) */
+	_vte_stream_advance_tail (as, 13);
+	g_assert (s->state == 1 && s->tail == 13 && s->head == 18);
+	assert_file_contents (s->file.fd, "klmijnopqr");
+	assert_stream_contents (as, "nopqr");
+
+	/* (K) */
+	_vte_stream_advance_tail (as, 13);
+	g_assert (s->state == 1 && s->tail == 13 && s->head == 18);
+	assert_file_contents (s->file.fd, "klmijnopqr");
+	assert_stream_contents (as, "nopqr");
+
+	/* (L) */
+	_vte_stream_append (as, "s", 1);
+	g_assert (s->state == 1 && s->tail == 13 && s->head == 19);
+	assert_file_contents (s->file.fd, "klmijnopqrs");
+	assert_stream_contents (as, "nopqrs");
+
+	/* (M)-(N) */
+	_vte_stream_advance_tail (as, 14);
+	_vte_stream_append (as, "tu", 2);
+	g_assert (s->state == 2 && s->tail == 14 && s->head == 21);
+	assert_file_contents (s->file.fd, "tumijnopqrs");
+	assert_stream_contents (as, "opqrstu");
+
+	g_object_unref (as);
+}
+
+/* Unbuffered: Test append() 1->2, 2->3, 1->3, and truncate() 3->2, 2->1, 3->1 state changes */
+static void
+test_unbuffered_append_truncate_123 (void)
+{
+	VteStream *as = _vte_unbuffered_file_stream_new();
+	VteUnbufferedFileStream *s = (VteUnbufferedFileStream *) as;
+
+	/* Setup */
+	_vte_stream_append (as, "abcde", 5);
+	_vte_stream_advance_tail (as, 3);
+	g_assert (s->state == 1 && s->tail == 3 && s->head == 5);
+	assert_file_contents (s->file.fd, "abcde");
+	assert_stream_contents (as, "de");
+
+	/* Append 1 byte, state change 1 -> 2 */
+	_vte_stream_append (as, "f", 1);
+	g_assert (s->state == 2 && s->tail == 3 && s->head == 6);
+	assert_file_contents (s->file.fd, "fbcde");
+	assert_stream_contents (as, "def");
+
+	/* Append, stay in state 2 */
+	_vte_stream_append (as, "gh", 2);
+	g_assert (s->state == 2 && s->tail == 3 && s->head == 8);
+	assert_file_contents (s->file.fd, "fghde");
+	assert_stream_contents (as, "defgh");
+
+	/* Append 1 byte, state change 2 -> 3 */
+	_vte_stream_append (as, "i", 1);
+	g_assert (s->state == 3 && s->tail == 3 && s->head == 9);
+	assert_file_contents (s->file.fd, "fghdei");
+	assert_stream_contents (as, "defghi");
+
+	/* Append, stay in state 3 */
+	_vte_stream_append (as, "jk", 2);
+	g_assert (s->state == 3 && s->tail == 3 && s->head == 11);
+	assert_file_contents (s->file.fd, "fghdeijk");
+	assert_stream_contents (as, "defghijk");
+
+	/* Truncate, stay in state 3 */
+	_vte_stream_truncate (as, 9);
+	g_assert (s->state == 3 && s->tail == 3 && s->head == 9);
+	assert_file_contents (s->file.fd, "fghdei");
+	assert_stream_contents (as, "defghi");
+
+	/* Truncate 1 byte, state change 3 -> 2 */
+	_vte_stream_truncate (as, 8);
+	g_assert (s->state == 2 && s->tail == 3 && s->head == 8);
+	assert_file_contents (s->file.fd, "fghde");
+	assert_stream_contents (as, "defgh");
+
+	/* Truncate, stay in state 2 */
+	_vte_stream_truncate (as, 6);
+	g_assert (s->state == 2 && s->tail == 3 && s->head == 6);
+	assert_file_contents (s->file.fd, "fghde");
+	assert_stream_contents (as, "def");
+
+	/* Truncate 1 byte, state change 2 -> 1 */
+	_vte_stream_truncate (as, 5);
+	g_assert (s->state == 1 && s->tail == 3 && s->head == 5);
+	assert_file_contents (s->file.fd, "fghde");
+	assert_stream_contents (as, "de");
+
+	/* Append just as much to fill the gap, state change 1 -> 2 */
+	_vte_stream_append (as, "lmn", 3);
+	g_assert (s->state == 2 && s->tail == 3 && s->head == 8);
+	assert_file_contents (s->file.fd, "lmnde");
+	assert_stream_contents (as, "delmn");
+
+	/* Truncate, state change 2 -> 1 */
+	_vte_stream_truncate (as, 5);
+	g_assert (s->state == 1 && s->tail == 3 && s->head == 5);
+	assert_file_contents (s->file.fd, "lmnde");
+	assert_stream_contents (as, "de");
+
+	/* Append, state chagnge 1 -> 2 -> 3 */
+	_vte_stream_append (as, "opqr", 4);
+	g_assert (s->state == 3 && s->tail == 3 && s->head == 9);
+	assert_file_contents (s->file.fd, "opqder");
+	assert_stream_contents (as, "deopqr");
+
+	/* Truncate, state change 3 -> 2 -> 1 */
+	_vte_stream_truncate (as, 5);
+	g_assert (s->state == 1 && s->tail == 3 && s->head == 5);
+	assert_file_contents (s->file.fd, "opqde");
+	assert_stream_contents (as, "de");
+
+	g_object_unref (as);
+}
+
+/* Unbuffered: Test truncate() 4->1 state change */
+static void
+test_unbuffered_truncate_41 (void)
+{
+	VteStream *as = _vte_unbuffered_file_stream_new();
+	VteUnbufferedFileStream *s = (VteUnbufferedFileStream *) as;
+
+	/* Setup */
+	_vte_stream_append (as, "abcde", 5);
+	_vte_stream_advance_tail (as, 3);
+	_vte_stream_append (as, "fghij", 5);
+	_vte_stream_advance_tail (as, 6);
+	g_assert (s->state == 4 && s->tail == 6 && s->head == 10);
+	assert_file_contents (s->file.fd, "fghdeij");
+	assert_stream_contents (as, "ghij");
+
+	/* Truncate, stay in state 4 */
+	_vte_stream_truncate (as, 9);
+	g_assert (s->state == 4 && s->tail == 6 && s->head == 9);
+	assert_file_contents (s->file.fd, "fghdei");
+	assert_stream_contents (as, "ghi");
+
+	/* Truncate, state change 4 -> 1 */
+	_vte_stream_truncate (as, 8);
+	g_assert (s->state == 1 && s->tail == 6 && s->head == 8);
+	assert_file_contents (s->file.fd, "fgh");
+	assert_stream_contents (as, "gh");
+
+	g_object_unref (as);
+}
+
+/* Unbuffered: Test advance_tail() 2->1 state change */
+static void
+test_unbuffered_advancetail_21 (void)
+{
+	VteStream *as = _vte_unbuffered_file_stream_new();
+	VteUnbufferedFileStream *s = (VteUnbufferedFileStream *) as;
+
+	/* Setup */
+	_vte_stream_append (as, "abcde", 5);
+	_vte_stream_advance_tail (as, 3);
+	_vte_stream_append (as, "fg", 2);
+	g_assert (s->state == 2 && s->tail == 3 && s->head == 7);
+	assert_file_contents (s->file.fd, "fgcde");
+	assert_stream_contents (as, "defg");
+
+	/* Advance_tail, stay in state 2 */
+	_vte_stream_advance_tail (as, 4);
+	g_assert (s->state == 2 && s->tail == 4 && s->head == 7);
+	assert_file_contents (s->file.fd, "fgcde");
+	assert_stream_contents (as, "efg");
+
+	/* Advance_tail, state change 2 -> 1 */
+	_vte_stream_advance_tail (as, 5);
+	g_assert (s->state == 1 && s->tail == 5 && s->head == 7);
+	assert_file_contents (s->file.fd, "fg");
+	assert_stream_contents (as, "fg");
+
+	g_object_unref (as);
+}
+
+/* Unbuffered: Test advance_tail() 3->4, 4->1 state changes */
+static void
+test_unbuffered_advancetail_341 (void)
+{
+	VteStream *as = _vte_unbuffered_file_stream_new();
+	VteUnbufferedFileStream *s = (VteUnbufferedFileStream *) as;
+
+	/* Setup */
+	_vte_stream_append (as, "abcde", 5);
+	_vte_stream_advance_tail (as, 3);
+	_vte_stream_append (as, "fghij", 5);
+	g_assert (s->state == 3 && s->tail == 3 && s->head == 10);
+	assert_file_contents (s->file.fd, "fghdeij");
+	assert_stream_contents (as, "defghij");
+
+	/* Advance_tail, stay in state 3 */
+	_vte_stream_advance_tail (as, 4);
+	g_assert (s->state == 3 && s->tail == 4 && s->head == 10);
+	assert_file_contents (s->file.fd, "fghdeij");
+	assert_stream_contents (as, "efghij");
+
+	/* Advance_tail, state change 3 -> 4 */
+	_vte_stream_advance_tail (as, 5);
+	g_assert (s->state == 4 && s->tail == 5 && s->head == 10);
+	assert_file_contents (s->file.fd, "fghdeij");
+	assert_stream_contents (as, "fghij");
+
+	/* Advance_tail, stay in state 4 */
+	_vte_stream_advance_tail (as, 7);
+	g_assert (s->state == 4 && s->tail == 7 && s->head == 10);
+	assert_file_contents (s->file.fd, "fghdeij");
+	assert_stream_contents (as, "hij");
+
+	/* Advance_tail, state change 4 -> 1 */
+	_vte_stream_advance_tail (as, 8);
+	g_assert (s->state == 1 && s->tail == 8 && s->head == 10);
+	assert_file_contents (s->file.fd, "fghdeij");
+	assert_stream_contents (as, "ij");
+
+	g_object_unref (as);
+}
+
+/* Unbuffered: Test advance_tail() 3->1 state changes */
+static void
+test_unbuffered_advancetail_31 (void)
+{
+	VteStream *as = _vte_unbuffered_file_stream_new();
+	VteUnbufferedFileStream *s = (VteUnbufferedFileStream *) as;
+
+	/* Setup */
+	_vte_stream_append (as, "abcde", 5);
+	_vte_stream_advance_tail (as, 3);
+	_vte_stream_append (as, "fghij", 5);
+	g_assert (s->state == 3 && s->tail == 3 && s->head == 10);
+	assert_file_contents (s->file.fd, "fghdeij");
+	assert_stream_contents (as, "defghij");
+
+	/* Advance_tail, state change 3 -> 4 -> 1*/
+	_vte_stream_advance_tail (as, 8);
+	g_assert (s->state == 1 && s->tail == 8 && s->head == 10);
+	assert_file_contents (s->file.fd, "fghdeij");
+	assert_stream_contents (as, "ij");
+
+	g_object_unref (as);
+}
+
+static void
+test_unbuffered_reset (void)
+{
+	VteStream *as = _vte_unbuffered_file_stream_new();
+	VteUnbufferedFileStream *s = (VteUnbufferedFileStream *) as;
+
+	/* Setup */
+	_vte_stream_append (as, "abcde", 5);
+	_vte_stream_advance_tail (as, 3);
+	_vte_stream_append (as, "fg", 2);
+	g_assert (s->state == 2 && s->tail == 3 && s->head == 7);
+
+	/* Reset */
+	_vte_stream_reset (as, 6);
+	_vte_stream_append (as, "fgh", 3);
+	g_assert (s->state == 1 && s->tail == 6 && s->head == 9);
+	assert_file_contents_starts_with (s->file.fd, "fgh");
+	assert_stream_contents (as, "fgh");
+
+	g_object_unref (as);
+}
+
+static void
+test_buffer (void)
+{
+	char buf[4];
+
+	VteStream *as = _vte_file_stream_new();
+	VteFileStream *s = (VteFileStream *) as;
+
+	/* Append */
+	_vte_stream_append (as, "abc", 3);
+	g_assert (s->head == 3);
+	g_assert (s->parent.head == 0);
+	g_assert (s->parent.file.fd == -1);
+
+	_vte_stream_append (as, "d", 1);
+	g_assert (s->head == 4);
+	g_assert (s->parent.head == 4);
+	assert_file_contents (s->parent.file.fd, "abcd");
+	assert_stream_contents (as, "abcd");
+
+	_vte_stream_append (as, "efghij", 6);
+	g_assert (s->head == 10);
+	g_assert (s->parent.head == 8);
+	assert_file_contents (s->parent.file.fd, "abcdefgh");
+	assert_stream_contents (as, "abcdefghij");
+
+	_vte_stream_append (as, "klm", 3);
+	g_assert (s->head == 13);
+	g_assert (s->parent.head == 12);
+	assert_file_contents (s->parent.file.fd, "abcdefghijkl");
+	assert_stream_contents (as, "abcdefghijklm");
+
+	/* Truncate */
+	_vte_stream_truncate (as, 12);
+	g_assert (s->head == 12);
+	g_assert (s->parent.head == 12);
+	assert_file_contents (s->parent.file.fd, "abcdefghijkl");
+	assert_stream_contents (as, "abcdefghijkl");
+
+	_vte_stream_truncate (as, 9);
+	g_assert (s->head == 9);
+	g_assert (s->parent.head == 8);
+	assert_file_contents (s->parent.file.fd, "abcdefgh");
+	assert_stream_contents (as, "abcdefghi");
+
+	/* Test that the read cache is invalidated on truncate */
+	_vte_stream_read (as, 4, buf, 4);
+	g_assert (s->rbuf_offset == 4);
+	_vte_stream_truncate (as, 7);
+	g_assert (s->rbuf_offset == 1);
+	_vte_stream_append (as, "mn", 2);
+	_vte_stream_read (as, 4, buf, 4);
+	g_assert (s->rbuf_offset == 4);
+	g_assert (strncmp (buf, "efgm", 4) == 0);
+
+	/* Advance_tail */
+	_vte_stream_advance_tail (as, 1);
+	g_assert (s->tail == 1);
+	g_assert (s->parent.tail == 0);
+	assert_file_contents (s->parent.file.fd, "abcdefgm");
+	assert_stream_contents (as, "bcdefgmn");
+
+	_vte_stream_advance_tail (as, 3);
+	g_assert (s->tail == 3);
+	g_assert (s->parent.tail == 0);
+	assert_file_contents (s->parent.file.fd, "abcdefgm");
+	assert_stream_contents (as, "defgmn");
+
+	_vte_stream_advance_tail (as, 4);
+	g_assert (s->tail == 4);
+	g_assert (s->parent.tail == 4);
+	assert_file_contents (s->parent.file.fd, "abcdefgm");
+	assert_stream_contents (as, "efgmn");
+
+	/* Tail and head within the same block (causes a reset in the underlying layer) */
+	_vte_stream_advance_tail (as, 8);
+	g_assert (s->tail == 8);
+	g_assert (s->parent.tail == 8);
+	assert_stream_contents (as, "n");
+
+	_vte_stream_advance_tail (as, 9);
+	g_assert (s->tail == 9);
+	g_assert (s->parent.tail == 8);
+	assert_stream_contents (as, "");
+
+	_vte_stream_append (as, "op", 2);
+	g_assert (s->head == 11);
+	g_assert (s->parent.head == 8);
+	assert_stream_contents (as, "op");
+
+	_vte_stream_advance_tail (as, 10);
+	g_assert (s->tail == 10);
+	g_assert (s->parent.tail == 8);
+	assert_stream_contents (as, "p");
+
+	_vte_stream_append (as, "q", 1);
+	g_assert (s->head == 12);
+	g_assert (s->parent.head == 12);
+	assert_stream_contents (as, "pq");
+
+	_vte_stream_append (as, "rs", 2);
+	g_assert (s->head == 14);
+	g_assert (s->parent.head == 12);
+	assert_file_contents_starts_with (s->parent.file.fd, "nopq");
+	assert_stream_contents (as, "pqrs");
+
+	/* Reset */
+	_vte_stream_reset (as, 13);
+	_vte_stream_append (as, "tuvw", 4);
+	g_assert (s->tail == 13);
+	g_assert (s->parent.tail == 12);
+	g_assert (s->head == 17);
+	g_assert (s->parent.head == 16);
+	assert_file_contents_starts_with_n (s->parent.file.fd, "\0tuv", 4);
+	assert_stream_contents (as, "tuvw");
+
+	g_object_unref (as);
+}
+
+int
+main (int argc, char **argv)
+{
+	test_unbuffered_example();
+	test_unbuffered_append_truncate_123();
+	test_unbuffered_truncate_41();
+	test_unbuffered_advancetail_21();
+	test_unbuffered_advancetail_341();
+	test_unbuffered_advancetail_31();
+	test_unbuffered_reset();
+
+	test_buffer();
+
+	return 0;
+}
+
+#endif /* VTESTREAM_MAIN */
diff --git a/src/vtestream.h b/src/vtestream.h
index 4cf6862..d1b4172 100644
--- a/src/vtestream.h
+++ b/src/vtestream.h
@@ -33,11 +33,14 @@ void _vte_stream_append (VteStream *stream, const char *data, gsize len);
 gboolean _vte_stream_read (VteStream *stream, gsize offset, char *data, gsize len);
 void _vte_stream_truncate (VteStream *stream, gsize offset);
 void _vte_stream_advance_tail (VteStream *stream, gsize offset);
+gsize _vte_stream_tail (VteStream *stream);
 gsize _vte_stream_head (VteStream *stream);
 
 /* Various streams */
 
 VteStream *
+_vte_unbuffered_file_stream_new (void);
+VteStream *
 _vte_file_stream_new (void);
 
 G_END_DECLS
