Index: uspace/lib/libc/generic/io/io.c
===================================================================
--- uspace/lib/libc/generic/io/io.c	(revision a000878c0dee83a08f032207ffd800c201fca95b)
+++ uspace/lib/libc/generic/io/io.c	(revision 39e8406404efea86e341847c0720d5d65bed7959)
@@ -46,4 +46,5 @@
 #include <adt/list.h>
 
+static void _ffillbuf(FILE *stream);
 static void _fflushbuf(FILE *stream);
 
@@ -57,5 +58,7 @@
 	.buf = NULL,
 	.buf_size = 0,
-	.buf_head = NULL
+	.buf_head = NULL,
+	.buf_tail = NULL,
+	.buf_state = _bs_empty
 };
 
@@ -69,5 +72,7 @@
 	.buf = NULL,
 	.buf_size = BUFSIZ,
-	.buf_head = NULL
+	.buf_head = NULL,
+	.buf_tail = NULL,
+	.buf_state = _bs_empty
 };
 
@@ -81,5 +86,7 @@
 	.buf = NULL,
 	.buf_size = 0,
-	.buf_head = NULL
+	.buf_head = NULL,
+	.buf_tail = NULL,
+	.buf_state = _bs_empty
 };
 
@@ -179,4 +186,6 @@
 	stream->buf_size = size;
 	stream->buf_head = stream->buf;
+	stream->buf_tail = stream->buf;
+	stream->buf_state = _bs_empty;
 }
 
@@ -210,4 +219,5 @@
 	
 	stream->buf_head = stream->buf;
+	stream->buf_tail = stream->buf;
 	return 0;
 }
@@ -243,4 +253,5 @@
 	stream->klog = false;
 	stream->phone = -1;
+	stream->need_sync = false;
 	_setvbuf(stream);
 	
@@ -264,4 +275,5 @@
 	stream->klog = false;
 	stream->phone = -1;
+	stream->need_sync = false;
 	_setvbuf(stream);
 	
@@ -295,4 +307,5 @@
 	stream->klog = false;
 	stream->phone = -1;
+	stream->need_sync = false;
 	_setvbuf(stream);
 	
@@ -331,5 +344,5 @@
 }
 
-/** Read from a stream.
+/** Read from a stream (unbuffered).
  *
  * @param buf    Destination buffer.
@@ -337,7 +350,6 @@
  * @param nmemb  Number of records to read.
  * @param stream Pointer to the stream.
- *
- */
-size_t fread(void *buf, size_t size, size_t nmemb, FILE *stream)
+ */
+static size_t _fread(void *buf, size_t size, size_t nmemb, FILE *stream)
 {
 	size_t left, done;
@@ -345,7 +357,4 @@
 	if (size == 0 || nmemb == 0)
 		return 0;
-
-	/* Make sure no data is pending write. */
-	_fflushbuf(stream);
 
 	left = size * nmemb;
@@ -368,4 +377,11 @@
 }
 
+/** Write to a stream (unbuffered).
+ *
+ * @param buf    Source buffer.
+ * @param size   Size of each record.
+ * @param nmemb  Number of records to write.
+ * @param stream Pointer to the stream.
+ */
 static size_t _fwrite(const void *buf, size_t size, size_t nmemb, FILE *stream)
 {
@@ -394,23 +410,125 @@
 		}
 	}
+
+	if (done > 0)
+		stream->need_sync = true;
 	
 	return (done / size);
 }
 
-/** Drain stream buffer, do not sync stream. */
+/** Read some data in stream buffer. */
+static void _ffillbuf(FILE *stream)
+{
+	ssize_t rc;
+
+	stream->buf_head = stream->buf_tail = stream->buf;
+
+	rc = read(stream->fd, stream->buf, stream->buf_size);
+	if (rc < 0) {
+		stream->error = true;
+		return;
+	}
+
+	if (rc == 0) {
+		stream->eof = true;
+		return;
+	}
+
+	stream->buf_head += rc;
+	stream->buf_state = _bs_read;
+}
+
+/** Write out stream buffer, do not sync stream. */
 static void _fflushbuf(FILE *stream)
 {
 	size_t bytes_used;
-	
+
 	if ((!stream->buf) || (stream->btype == _IONBF) || (stream->error))
 		return;
-	
-	bytes_used = stream->buf_head - stream->buf;
+
+	bytes_used = stream->buf_head - stream->buf_tail;
 	if (bytes_used == 0)
 		return;
-	
-	(void) _fwrite(stream->buf, 1, bytes_used, stream);
+
+	/* If buffer has prefetched read data, we need to seek back. */
+	if (stream->buf_state == _bs_read)
+		lseek(stream->fd, - (ssize_t) bytes_used, SEEK_CUR);
+
+	/* If buffer has unwritten data, we need to write them out. */
+	if (stream->buf_state == _bs_write)
+		(void) _fwrite(stream->buf_tail, 1, bytes_used, stream);
+
 	stream->buf_head = stream->buf;
-}
+	stream->buf_tail = stream->buf;
+	stream->buf_state = _bs_empty;
+}
+
+/** Read from a stream.
+ *
+ * @param dest   Destination buffer.
+ * @param size   Size of each record.
+ * @param nmemb  Number of records to read.
+ * @param stream Pointer to the stream.
+ *
+ */
+size_t fread(void *dest, size_t size, size_t nmemb, FILE *stream)
+{
+	uint8_t *dp;
+	size_t bytes_left;
+	size_t now;
+	size_t data_avail;
+	size_t total_read;
+	size_t i;
+
+	if (size == 0 || nmemb == 0)
+		return 0;
+
+	/* If not buffered stream, read in directly. */
+	if (stream->btype == _IONBF) {
+		now = _fread(dest, size, nmemb, stream);
+		return now;
+	}
+
+	/* Make sure no data is pending write. */
+	if (stream->buf_state == _bs_write)
+		_fflushbuf(stream);
+
+	/* Perform lazy allocation of stream buffer. */
+	if (stream->buf == NULL) {
+		if (_fallocbuf(stream) != 0)
+			return 0; /* Errno set by _fallocbuf(). */
+	}
+
+	bytes_left = size * nmemb;
+	total_read = 0;
+	dp = (uint8_t *) dest;
+
+	while ((!stream->error) && (!stream->eof) && (bytes_left > 0)) {
+		if (stream->buf_head == stream->buf_tail)
+			_ffillbuf(stream);
+
+		if (stream->error || stream->eof)
+			break;
+
+		data_avail = stream->buf_head - stream->buf_tail;
+
+		if (bytes_left > data_avail)
+			now = data_avail;
+		else
+			now = bytes_left;
+
+		for (i = 0; i < now; i++) {
+			dp[i] = stream->buf_tail[i];
+		}
+
+		dp += now;
+		stream->buf_tail += now;
+		bytes_left -= now;
+		total_read += now;
+	}
+
+	return (total_read / size);
+}
+
 
 /** Write to a stream.
@@ -442,5 +560,10 @@
 		return now;
 	}
-	
+
+	/* Make sure buffer contains no prefetched data. */
+	if (stream->buf_state == _bs_read)
+		_fflushbuf(stream);
+
+
 	/* Perform lazy allocation of stream buffer. */
 	if (stream->buf == NULL) {
@@ -482,4 +605,7 @@
 	}
 	
+	if (total_written > 0)
+		stream->buf_state = _bs_write;
+
 	if (need_flush)
 		fflush(stream);
@@ -570,12 +696,15 @@
 int fseek(FILE *stream, off64_t offset, int whence)
 {
-	off64_t rc = lseek(stream->fd, offset, whence);
+	off64_t rc;
+
+	_fflushbuf(stream);
+
+	rc = lseek(stream->fd, offset, whence);
 	if (rc == (off64_t) (-1)) {
 		/* errno has been set by lseek64. */
 		return -1;
 	}
-	
+
 	stream->eof = false;
-	
 	return 0;
 }
@@ -600,6 +729,12 @@
 	}
 	
-	if (stream->fd >= 0)
+	if (stream->fd >= 0 && stream->need_sync) {
+		/**
+		 * Better than syncing always, but probably still not the
+		 * right thing to do.
+		 */
+		stream->need_sync = false;
 		return fsync(stream->fd);
+	}
 	
 	return ENOENT;
Index: uspace/lib/libc/include/stdio.h
===================================================================
--- uspace/lib/libc/include/stdio.h	(revision a000878c0dee83a08f032207ffd800c201fca95b)
+++ uspace/lib/libc/include/stdio.h	(revision 39e8406404efea86e341847c0720d5d65bed7959)
@@ -75,4 +75,15 @@
 };
 
+enum _buffer_state {
+	/** Buffer is empty */
+	_bs_empty,
+
+	/** Buffer contains data to be written */
+	_bs_write,
+
+	/** Buffer contains prefetched data for reading */
+	_bs_read
+};
+
 typedef struct {
 	/** Linked list pointer. */
@@ -94,12 +105,27 @@
 	int phone;
 
+	/**
+	 * Non-zero if the stream needs sync on fflush(). XXX change
+	 * console semantics so that sync is not needed.
+	 */
+	int need_sync;
+
 	/** Buffering type */
 	enum _buffer_type btype;
+
 	/** Buffer */
 	uint8_t *buf;
+
 	/** Buffer size */
 	size_t buf_size;
+
+	/** Buffer state */
+	enum _buffer_state buf_state;
+
 	/** Buffer I/O pointer */
 	uint8_t *buf_head;
+
+	/** Points to end of occupied space when in read mode. */
+	uint8_t *buf_tail;
 } FILE;
 
