Merge branch 'master' into stdio
diff --git a/usr/include/klibc/sysconfig.h b/usr/include/klibc/sysconfig.h
index a1c37fd..5fa9b60 100644
--- a/usr/include/klibc/sysconfig.h
+++ b/usr/include/klibc/sysconfig.h
@@ -107,6 +107,17 @@
 
 
 /*
+ * _KLIBC_BUFSIZ:
+ *	This is the size of an stdio buffer.  By default this is
+ *	_KLIBC_MALLOC_CHUNK_SIZE/4, which allows the three standard
+ *	streams to fit inside a malloc chunk.
+ */
+#ifndef _KLIBC_BUFSIZ
+# define _KLIBC_BUFSIZ (_KLIBC_MALLOC_CHUNK_SIZE >> 2)
+#endif
+
+
+/*
  * _KLIBC_SBRK_ALIGNMENT:
  *
  *	This is the minimum alignment for the memory returned by
diff --git a/usr/include/stdio.h b/usr/include/stdio.h
index 2db63be..ea77500 100644
--- a/usr/include/stdio.h
+++ b/usr/include/stdio.h
@@ -6,21 +6,44 @@
 #define _STDIO_H
 
 #include <klibc/extern.h>
+#include <klibc/sysconfig.h>
 #include <stdarg.h>
 #include <stddef.h>
 #include <unistd.h>
 
-/* This structure doesn't really exist, but it gives us something
-   to define FILE * with */
-struct _IO_file;
+/* Unidirectional buffer */
+struct _IO_buf {
+	char *buf;		/* Actual buffer */
+};
+
+/* Actual FILE structure */
+struct _IO_file {
+	struct _IO_file *prev, *next;
+	off_t filepos;		/* File position */
+	char *buf;		/* Buffer */
+	int offset;		/* Offset to data in buffer */
+	int bytes;		/* Data bytes in buffer */
+	int bufsiz;		/* Total size of buffer */
+	int fd;			/* Underlying file descriptor */
+	int flags;		/* Error, end of file */
+};
 typedef struct _IO_file FILE;
 
+enum _IO_file_flags {
+	_IO_FILE_FLAG_WRITE	=  1, /* Buffer has write data */
+	_IO_FILE_FLAG_READ	=  2, /* Buffer has read data */
+	_IO_FILE_FLAG_LINE_BUF  =  4, /* Line buffered */
+	_IO_FILE_FLAG_UNBUF     =  8, /* Unbuffered */
+	_IO_FILE_FLAG_EOF	= 16,
+	_IO_FILE_FLAG_ERR	= 32,
+};
+
 #ifndef EOF
 # define EOF (-1)
 #endif
 
 #ifndef BUFSIZ
-# define BUFSIZ 4096
+# define BUFSIZ _KLIBC_BUFSIZ
 #endif
 
 #define SEEK_SET 0
@@ -28,46 +51,24 @@
 #define SEEK_END 2
 
 /*
- * Convert between a FILE * and a file descriptor.  We don't actually
- * have any in-memory data, so we just abuse the pointer itself to
- * hold the data.  Note, however, that for file descriptors, -1 is
- * error and 0 is a valid value; for FILE *, NULL (0) is error and
- * non-NULL are valid.
+ * Convert between a FILE * and a file descriptor.
  */
-static __inline__ int fileno(FILE * __f)
+__extern int fileno(FILE *);
+
+__extern_inline int fileno(FILE * __f)
 {
-	/* This should really be intptr_t, but size_t should be the same size */
-	return (int)(size_t) __f - 1;
+	return __f->fd;
 }
 
-/* This is a macro so it can be used as initializer */
-#define __create_file(__fd) ((FILE *)(size_t)((__fd) + 1))
-
-#define stdin  __create_file(0)
-#define stdout __create_file(1)
-#define stderr __create_file(2)
+__extern FILE *stdin, *stdout, *stderr;
 
 __extern FILE *fopen(const char *, const char *);
-
-__static_inline FILE *fdopen(int __fd, const char *__m)
-{
-	(void)__m;
-	return __create_file(__fd);
-}
-__static_inline int fclose(FILE * __f)
-{
-	extern int close(int);
-	return close(fileno(__f));
-}
-__static_inline int fseek(FILE * __f, off_t __o, int __w)
-{
-	extern off_t lseek(int, off_t, int);
-	return (lseek(fileno(__f), __o, __w) == (off_t) - 1) ? -1 : 0;
-}
+__extern FILE *fdopen(int, const char *);
+__extern int fclose(FILE *);
+__extern int fseek(FILE *, off_t, int);
 __static_inline off_t ftell(FILE * __f)
 {
-	extern off_t lseek(int, off_t, int);
-	return lseek(fileno(__f), 0, SEEK_CUR);
+	return __f->filepos;
 }
 
 __extern int fputs(const char *, FILE *);
@@ -79,7 +80,10 @@
 __extern int fgetc(FILE *);
 __extern char *fgets(char *, int, FILE *);
 #define getc(f) fgetc(f)
+__extern int getc_unlocked(FILE *);
+#define getc_unlocked(f) fgetc(f)
 #define getchar() fgetc(stdin)
+__extern int ungetc(int, FILE *);
 
 __extern size_t _fread(void *, size_t, FILE *);
 __extern size_t _fwrite(const void *, size_t, FILE *);
@@ -109,19 +113,9 @@
 __extern int asprintf(char **, const char *, ...);
 __extern int vasprintf(char **, const char *, va_list);
 
-/* No buffering, so no flushing needed */
-__static_inline int fflush(FILE * __f)
-{
-	(void)__f;
-	return 0;
-}
-
-/* stream errors are not kept track of by klibc implementation */
-__static_inline int ferror(FILE * __f)
-{
-	(void)__f;
-	return 0;
-}
+__extern int ferror(FILE * );
+__extern int feof(FILE *);
+__extern int fflush(FILE *);
 
 __extern int sscanf(const char *, const char *, ...);
 __extern int vsscanf(const char *, const char *, va_list);
diff --git a/usr/klibc/Kbuild b/usr/klibc/Kbuild
index c4f9ae2..753ab85 100644
--- a/usr/klibc/Kbuild
+++ b/usr/klibc/Kbuild
@@ -19,8 +19,8 @@
 	  printf.o vprintf.o fprintf.o vfprintf.o perror.o \
 	  statfs.o fstatfs.o umount.o \
 	  creat.o open.o openat.o open_cloexec.o \
-	  fopen.o fread.o fread2.o fgetc.o fgets.o \
-	  fwrite.o fwrite2.o fputc.o fputs.o puts.o putchar.o \
+	  fread2.o fgetc.o fgets.o \
+	  fwrite2.o fputc.o fputs.o puts.o putchar.o \
 	  sleep.o usleep.o strtotimespec.o strtotimeval.o \
 	  raise.o abort.o assert.o alarm.o pause.o \
 	  __signal.o sysv_signal.o bsd_signal.o siglist.o sigabbrev.o \
@@ -57,7 +57,10 @@
 	  ctype/isxdigit.o ctype/tolower.o ctype/toupper.o \
 	  userdb/getgrgid.o userdb/getgrnam.o userdb/getpwnam.o \
 	  userdb/getpwuid.o userdb/root_group.o userdb/root_user.o \
-	  setmntent.o endmntent.o getmntent.o
+	  setmntent.o endmntent.o getmntent.o \
+	  stdio/fclose.o stdio/fopen.o stdio/fdopen.o stdio/fxopen.o \
+	  stdio/fread.o stdio/fwrite.o stdio/fflush.o \
+	  stdio/fseek.o stdio/ungetc.o stdio/feof.o stdio/ferror.o
 
 klib-$(CONFIG_KLIBC_ERRLIST) += errlist.o
 
diff --git a/usr/klibc/exit.c b/usr/klibc/exit.c
index 92f11c5..2368b07 100644
--- a/usr/klibc/exit.c
+++ b/usr/klibc/exit.c
@@ -6,6 +6,7 @@
 
 #include <stdlib.h>
 #include <unistd.h>
+#include <stdio.h>
 #include <sys/syscall.h>
 #include "atexit.h"
 
@@ -25,6 +26,7 @@
 	}
 
 	/* Handle any library destructors if we ever start using them... */
+	fflush(NULL);
 
 	_exit(rv);
 }
diff --git a/usr/klibc/fopen.c b/usr/klibc/fopen.c
deleted file mode 100644
index 6fa80d7..0000000
--- a/usr/klibc/fopen.c
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * fopen.c
- */
-
-#include <stdio.h>
-#include <unistd.h>
-#include <fcntl.h>
-
-/* This depends on O_RDONLY == 0, O_WRONLY == 1, O_RDWR == 2 */
-
-FILE *fopen(const char *file, const char *mode)
-{
-	int flags = O_RDONLY;
-	int plus = 0;
-
-	while (*mode) {
-		switch (*mode++) {
-		case 'r':
-			flags = O_RDONLY;
-			break;
-		case 'w':
-			flags = O_WRONLY | O_CREAT | O_TRUNC;
-			break;
-		case 'a':
-			flags = O_WRONLY | O_CREAT | O_APPEND;
-			break;
-		case '+':
-			plus = 1;
-			break;
-		}
-	}
-
-	if (plus) {
-		flags = (flags & ~(O_RDONLY | O_WRONLY)) | O_RDWR;
-	}
-
-	/* Note: __create_file(-1) == NULL, so this is safe */
-	return __create_file(open(file, flags, 0666));
-}
diff --git a/usr/klibc/fread.c b/usr/klibc/fread.c
deleted file mode 100644
index b9433aa..0000000
--- a/usr/klibc/fread.c
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * fread.c
- */
-
-#include <errno.h>
-#include <unistd.h>
-#include <stdio.h>
-
-size_t _fread(void *buf, size_t count, FILE *f)
-{
-	size_t bytes = 0;
-	ssize_t rv;
-	char *p = buf;
-
-	while (count) {
-		rv = read(fileno(f), p, count);
-		if (rv == -1) {
-			if (errno == EINTR) {
-				errno = 0;
-				continue;
-			} else
-				break;
-		} else if (rv == 0) {
-			break;
-		}
-
-		p += rv;
-		bytes += rv;
-		count -= rv;
-	}
-
-	return bytes;
-}
diff --git a/usr/klibc/fwrite.c b/usr/klibc/fwrite.c
deleted file mode 100644
index cba8de8..0000000
--- a/usr/klibc/fwrite.c
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * fwrite.c
- */
-
-#include <errno.h>
-#include <unistd.h>
-#include <stdio.h>
-
-size_t _fwrite(const void *buf, size_t count, FILE *f)
-{
-	size_t bytes = 0;
-	ssize_t rv;
-	const char *p = buf;
-
-	while (count) {
-		rv = write(fileno(f), p, count);
-		if (rv == -1) {
-			if (errno == EINTR) {
-				errno = 0;
-				continue;
-			} else
-				break;
-		} else if (rv == 0) {
-			break;
-		}
-
-		p += rv;
-		bytes += rv;
-		count -= rv;
-	}
-
-	return bytes;
-}
diff --git a/usr/klibc/libc_init.c b/usr/klibc/libc_init.c
index 55460ce..8d18820 100644
--- a/usr/klibc/libc_init.c
+++ b/usr/klibc/libc_init.c
@@ -39,6 +39,8 @@
 	uintptr_t v;
 };
 
+extern void __init_stdio(void);
+
 __noreturn __libc_init(uintptr_t * elfdata, void (*onexit) (void))
 {
 	int argc;
@@ -102,6 +104,8 @@
 #endif
 	__page_shift = page_shift;
 
+	__init_stdio();
+
 	environ = envp;
 	exit(MAIN(argc, argv, envp));
 }
diff --git a/usr/klibc/stdio/fclose.c b/usr/klibc/stdio/fclose.c
new file mode 100644
index 0000000..682385e
--- /dev/null
+++ b/usr/klibc/stdio/fclose.c
@@ -0,0 +1,23 @@
+/*
+ * fclose.c
+ */
+
+#include "stdioint.h"
+
+int fclose(FILE *f)
+{
+	int rv;
+
+	fflush(f);
+
+	rv = close(f->fd);
+
+	/* Remove from linked list */
+	f->next->prev = f->prev;
+	f->prev->next = f->next;
+
+	free(f->buf);
+	free(f);
+
+	return rv;
+}
diff --git a/usr/klibc/stdio/fdopen.c b/usr/klibc/stdio/fdopen.c
new file mode 100644
index 0000000..99f3a80
--- /dev/null
+++ b/usr/klibc/stdio/fdopen.c
@@ -0,0 +1,20 @@
+/*
+ * fdopen.c
+ */
+
+#include "stdioint.h"
+
+FILE *fdopen(int fd, const char *mode)
+{
+	int flags = __parse_open_mode(mode);
+	int oldflags;
+
+	if (fcntl(fd, F_GETFL, &oldflags))
+		return NULL;
+
+	oldflags = (oldflags & ~O_APPEND) | (flags & O_APPEND);
+	if (fcntl(fd, F_SETFL, &oldflags))
+		return NULL;
+
+	return __fxopen(fd, flags, 0);
+}
diff --git a/usr/klibc/stdio/feof.c b/usr/klibc/stdio/feof.c
new file mode 100644
index 0000000..01c26e1
--- /dev/null
+++ b/usr/klibc/stdio/feof.c
@@ -0,0 +1,6 @@
+#include <stdio.h>
+
+int feof(FILE *f)
+{
+	return f->flags & _IO_FILE_FLAG_EOF;
+}
diff --git a/usr/klibc/stdio/ferror.c b/usr/klibc/stdio/ferror.c
new file mode 100644
index 0000000..ab5322f
--- /dev/null
+++ b/usr/klibc/stdio/ferror.c
@@ -0,0 +1,6 @@
+#include <stdio.h>
+
+int ferror(FILE *f)
+{
+	return f->flags & _IO_FILE_FLAG_ERR;
+}
diff --git a/usr/klibc/stdio/fflush.c b/usr/klibc/stdio/fflush.c
new file mode 100644
index 0000000..0970c68
--- /dev/null
+++ b/usr/klibc/stdio/fflush.c
@@ -0,0 +1,45 @@
+/*
+ * fflush.c
+ */
+
+#include "stdioint.h"
+
+int fflush(FILE *f)
+{
+	ssize_t rv;
+	char *p;
+
+	if (!f) {
+		int err = 0;
+
+		for (f = __stdio_headnode.next; f != &__stdio_headnode;
+		     f = f->next)
+			err |= fflush(f);
+		return err;
+	}
+
+	if (!(f->flags & _IO_FILE_FLAG_WRITE))
+		return 0;
+
+	p = f->buf;
+	while (f->bytes) {
+		rv = write(f->fd, p, f->bytes);
+		if (rv == -1) {
+			if (errno == EINTR || errno == EAGAIN)
+				continue;
+			f->flags |= _IO_FILE_FLAG_ERR;
+			return EOF;
+		} else if (rv == 0) {
+			/* EOF on output? */
+			f->flags |= _IO_FILE_FLAG_EOF;
+			return EOF;
+		}
+
+		p += rv;
+		f->bytes -= rv;
+	}
+	f->offset = _IO_UNGET_SLOP;
+	f->flags &= ~_IO_FILE_FLAG_WRITE;
+
+	return 0;
+}
diff --git a/usr/klibc/stdio/fopen.c b/usr/klibc/stdio/fopen.c
new file mode 100644
index 0000000..475620f
--- /dev/null
+++ b/usr/klibc/stdio/fopen.c
@@ -0,0 +1,19 @@
+/*
+ * fopen.c
+ */
+
+#include "stdioint.h"
+
+extern int __parse_open_mode(const char *);
+
+FILE *fopen(const char *file, const char *mode)
+{
+	int flags = __parse_open_mode(mode);
+	int fd;
+
+	fd = open(file, flags, 0666);
+	if (fd < 0)
+		return NULL;
+
+	return __fxopen(fd, flags, 1);
+}
diff --git a/usr/klibc/stdio/fputc.c b/usr/klibc/stdio/fputc.c
new file mode 100644
index 0000000..8fd3452
--- /dev/null
+++ b/usr/klibc/stdio/fputc.c
@@ -0,0 +1,24 @@
+/*
+ * fputc.c
+ */
+
+#include "stdioint.h"
+
+int __fputc(int c, FILE *f)
+{
+	if (f->bytes >= f->bufsiz)
+		fflush(f);
+
+	*f->buf++ = c;
+	f->flags |= _IO_FILE_FLAG_WRITE;
+
+	if (f->flags & _IO_FILE_LINE_BUF && c == '\n')
+		fflush(f);
+}
+
+int fputc(int c, FILE *f)
+{
+	__fputc(c, f);
+	if (f->flags & _IO_FILE_UNBUF)
+		fflush(f);
+}
diff --git a/usr/klibc/stdio/fread.c b/usr/klibc/stdio/fread.c
new file mode 100644
index 0000000..b318dac
--- /dev/null
+++ b/usr/klibc/stdio/fread.c
@@ -0,0 +1,53 @@
+/*
+ * fread.c
+ */
+
+#include <string.h>
+#include "stdioint.h"
+
+size_t _fread(void *buf, size_t count, FILE *f)
+{
+	size_t bytes = 0;
+	size_t nb;
+	char *p = buf;
+	ssize_t rv;
+
+	/* Note: one could avoid double-buffering large reads. */
+
+	for (;;) {
+		nb = f->bytes;
+		nb = (count < nb) ? count : nb;
+		if (nb) {
+			memcpy(p, f->buf+f->offset, nb);
+			f->offset += nb;
+			f->bytes  -= nb;
+			p += nb;
+			count -= nb;
+			bytes += nb;
+			f->filepos += nb;
+			if (!f->bytes)
+				f->flags &= ~_IO_FILE_FLAG_READ;
+		}
+
+		if (!count)
+			break;	/* Done... */
+
+		/* If we get here, f->ibuf must be empty */
+		f->offset = _IO_UNGET_SLOP;
+
+		rv = read(f->fd, f->buf+_IO_UNGET_SLOP, BUFSIZ);
+		if (rv == -1) {
+			if (errno == EINTR || errno == EAGAIN)
+				continue;
+			f->flags |= _IO_FILE_FLAG_ERR;
+			return bytes;
+		} else if (rv == 0) {
+			f->flags |= _IO_FILE_FLAG_EOF;
+			return bytes;
+		}
+
+		f->bytes = rv;
+		f->flags |= _IO_FILE_FLAG_READ;
+	}
+	return bytes;
+}
diff --git a/usr/klibc/stdio/fseek.c b/usr/klibc/stdio/fseek.c
new file mode 100644
index 0000000..1a2ced3
--- /dev/null
+++ b/usr/klibc/stdio/fseek.c
@@ -0,0 +1,22 @@
+/*
+ * fseek.c
+ */
+
+#include "stdioint.h"
+
+__extern int fseek(FILE *f, off_t where, int whence)
+{
+	off_t rv;
+
+	fflush(f);
+
+	rv = lseek(f->fd, where, whence);
+	if (rv != -1) {
+		f->filepos = rv;
+		f->bytes = 0;
+		f->flags &= ~_IO_FILE_FLAG_READ;
+		return 0;
+	} else {
+		return -1;
+	}
+}
diff --git a/usr/klibc/stdio/fwrite.c b/usr/klibc/stdio/fwrite.c
new file mode 100644
index 0000000..7ad49d2
--- /dev/null
+++ b/usr/klibc/stdio/fwrite.c
@@ -0,0 +1,76 @@
+/*
+ * fwrite.c
+ */
+
+#include <string.h>
+#include "stdioint.h"
+
+static size_t fwrite_noflush(const void *buf, size_t count, FILE *f)
+{
+	size_t bytes = 0;
+	size_t nb;
+	const char *p = buf;
+
+	/* Note: one could avoid double-buffering large writes. */
+
+	for (;;) {
+		nb = f->bufsiz - f->bytes;
+		nb = (count < nb) ? count : nb;
+		if (nb) {
+			memcpy(f->buf+f->bytes, p, nb);
+			p += nb;
+			f->bytes += nb;
+			count -= nb;
+			bytes += nb;
+			f->filepos += nb;
+			f->flags |= _IO_FILE_FLAG_WRITE;
+		}
+
+		if (!count)
+			break;	/* Done... */
+
+		/* If we get here, the buffer must be full */
+		if (fflush(f))
+			break;
+	}
+	return bytes;
+}
+
+size_t _fwrite(const void *buf, size_t count, FILE *f)
+{
+	size_t bytes = 0;
+	size_t pf_len, pu_len;
+	const char *p = buf;
+
+	/* We divide the data into two chunks, flushed (pf)
+	   and unflushed (pu) depending on buffering mode
+	   and contents. */
+
+	if (f->flags & _IO_FILE_FLAG_UNBUF) {
+		pf_len = 0;
+		pu_len = count;
+	} else if (f->flags & _IO_FILE_FLAG_LINE_BUF) {
+		pf_len = count;
+		pu_len = 0;
+
+		while (pf_len && p[pf_len-1] != '\n') {
+			pf_len--;
+			pu_len++;
+		}
+	} else {
+		pf_len = 0;
+		pu_len = count;
+	}
+
+	if (pf_len) {
+		bytes = fwrite_noflush(p, pf_len, f);
+		p += bytes;
+		if (fflush(f) || bytes != pf_len)
+			return bytes;
+	}
+
+	if (pu_len)
+		bytes += fwrite_noflush(p, pu_len, f);
+
+	return bytes;
+}
diff --git a/usr/klibc/stdio/fxopen.c b/usr/klibc/stdio/fxopen.c
new file mode 100644
index 0000000..7c596ec
--- /dev/null
+++ b/usr/klibc/stdio/fxopen.c
@@ -0,0 +1,96 @@
+/*
+ * fxopen.c
+ *
+ * Common code between fopen(), fdopen() and the standard descriptors.
+ */
+
+#include "stdioint.h"
+
+FILE *stdin, *stdout, *stderr;
+
+/* Doubly-linked list of all stdio structures */
+struct _IO_file __stdio_headnode =
+{
+	.prev = &__stdio_headnode,
+	.next = &__stdio_headnode,
+};
+
+/* This depends on O_RDONLY == 0, O_WRONLY == 1, O_RDWR == 2 */
+int __parse_open_mode(const char *mode)
+{
+	int plus = 0;
+	int flags = O_RDONLY;
+
+	while (*mode) {
+		switch (*mode++) {
+		case 'r':
+			flags = O_RDONLY;
+			break;
+		case 'w':
+			flags = O_WRONLY | O_CREAT | O_TRUNC;
+			break;
+		case 'a':
+			flags = O_WRONLY | O_CREAT | O_APPEND;
+			break;
+		case '+':
+			plus = 1;
+			break;
+		}
+	}
+
+	if (plus)
+		flags = (flags & ~(O_RDONLY | O_WRONLY)) | O_RDWR;
+
+	return flags;
+}
+
+FILE *__fxopen(int fd, int flags, int close_on_err)
+{
+	FILE *f = NULL;
+
+	f = malloc(sizeof *f);
+	if (!f)
+		goto err;
+
+	f->fd = fd;
+	f->filepos = lseek(fd, 0, SEEK_CUR);
+
+	f->buf = malloc(BUFSIZ + _IO_UNGET_SLOP);
+	if (!f->buf)
+		goto err;
+
+	f->bufsiz = BUFSIZ;
+	f->offset = _IO_UNGET_SLOP;
+	f->bytes = 0;		/* No bytes in buffer */
+	f->flags = isatty(fd) ? _IO_FILE_FLAG_LINE_BUF : 0;
+
+	/* Insert into linked list */
+	f->prev = &__stdio_headnode;
+	f->next = __stdio_headnode.next;
+	f->next->prev = f;
+	__stdio_headnode.next = f;
+
+	return f;
+
+err:
+	if (f) {
+		free(f->buf);
+		free(f);
+	}
+	if (close_on_err)
+		close(fd);
+	errno = ENOMEM;
+	return NULL;
+}
+
+void __init_stdio(void)
+{
+	stdin  = __fxopen(0, O_RDONLY, 0);
+	stdin->flags = _IO_FILE_FLAG_LINE_BUF;
+
+	stdout = __fxopen(1, O_WRONLY|O_TRUNC, 0);
+	stdout->flags = _IO_FILE_FLAG_LINE_BUF;
+
+	stderr = __fxopen(2, O_WRONLY|O_TRUNC, 0);
+	stderr->flags = _IO_FILE_FLAG_UNBUF;
+}
diff --git a/usr/klibc/stdio/stdioint.h b/usr/klibc/stdio/stdioint.h
new file mode 100644
index 0000000..ad24589
--- /dev/null
+++ b/usr/klibc/stdio/stdioint.h
@@ -0,0 +1,25 @@
+/*
+ * stdioint.h
+ *
+ * stdio internals
+ */
+
+#ifndef USR_KLIBC_STDIO_STDIOINT_H
+#define USR_KLIBC_STDIO_STDIOINT_H
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+/* Assign this much extra to the input buffer in case of ungetc() */
+#define _IO_UNGET_SLOP	32
+
+__extern int __parse_open_mode(const char *mode);
+__extern FILE *__fxopen(int fd, int flags, int close_on_err);
+__extern int __fputc(int c, FILE *f);
+
+__extern struct _IO_file __stdio_headnode;
+
+#endif /* USR_KLIBC_STDIO_STDIOINT_H */
diff --git a/usr/klibc/stdio/ungetc.c b/usr/klibc/stdio/ungetc.c
new file mode 100644
index 0000000..b2a304c
--- /dev/null
+++ b/usr/klibc/stdio/ungetc.c
@@ -0,0 +1,16 @@
+/*
+ * ungetc.c
+ */
+
+#include "stdioint.h"
+
+int ungetc(int c, FILE *f)
+{
+	if (f->flags & _IO_FILE_FLAG_WRITE || f->offset <= 0)
+		return EOF;
+
+	f->buf[--f->offset] = c;
+	f->bytes++;
+	f->flags |= _IO_FILE_FLAG_READ;
+	return c;
+}
diff --git a/usr/klibc/version b/usr/klibc/version
index 5b5dc42..cd5ac03 100644
--- a/usr/klibc/version
+++ b/usr/klibc/version
@@ -1 +1 @@
-1.5.26
+2.0