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