about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/exit/exit.c2
-rw-r--r--src/internal/stdio_impl.h34
-rw-r--r--src/stdio/__overflow.c52
-rw-r--r--src/stdio/__stdio_read.c18
-rw-r--r--src/stdio/__stdio_seek.c4
-rw-r--r--src/stdio/__stdio_write.c29
-rw-r--r--src/stdio/__stdout_write.c10
-rw-r--r--src/stdio/__toread.c14
-rw-r--r--src/stdio/__towrite.c21
-rw-r--r--src/stdio/__uflow.c8
-rw-r--r--src/stdio/__underflow.c38
-rw-r--r--src/stdio/fflush.c18
-rw-r--r--src/stdio/fgetc.c4
-rw-r--r--src/stdio/fgets.c21
-rw-r--r--src/stdio/fgetwc.c4
-rw-r--r--src/stdio/fputc.c5
-rw-r--r--src/stdio/fputwc.c3
-rw-r--r--src/stdio/fread.c23
-rw-r--r--src/stdio/fseek.c20
-rw-r--r--src/stdio/fwrite.c44
-rw-r--r--src/stdio/getc.c6
-rw-r--r--src/stdio/getc_unlocked.c4
-rw-r--r--src/stdio/getchar_unlocked.c2
-rw-r--r--src/stdio/getdelim.c30
-rw-r--r--src/stdio/putc.c8
-rw-r--r--src/stdio/putc_unlocked.c4
-rw-r--r--src/stdio/putchar_unlocked.c3
-rw-r--r--src/stdio/setvbuf.c4
-rw-r--r--src/stdio/stderr.c4
-rw-r--r--src/stdio/stdout.c2
-rw-r--r--src/stdio/ungetc.c16
-rw-r--r--src/stdio/ungetwc.c17
-rw-r--r--src/stdio/vdprintf.c6
-rw-r--r--src/stdio/vfprintf.c2
-rw-r--r--src/stdio/vfscanf.c13
-rw-r--r--src/stdio/vsnprintf.c42
-rw-r--r--src/stdio/vswprintf.c4
37 files changed, 258 insertions, 281 deletions
diff --git a/src/exit/exit.c b/src/exit/exit.c
index d0c1bfc1..bfdb3923 100644
--- a/src/exit/exit.c
+++ b/src/exit/exit.c
@@ -17,7 +17,7 @@ void exit(int code)
 
 	/* Only do atexit & stdio flush if they were actually used */
 	if (__funcs_on_exit) __funcs_on_exit();
-	if (__fflush_on_exit) __fflush_on_exit(0);
+	if (__fflush_on_exit) __fflush_on_exit((void *)0);
 
 	/* Destructor s**t is kept separate from atexit to avoid bloat */
 	if (libc.fini) libc.fini();
diff --git a/src/internal/stdio_impl.h b/src/internal/stdio_impl.h
index b977df68..90a8214b 100644
--- a/src/internal/stdio_impl.h
+++ b/src/internal/stdio_impl.h
@@ -18,10 +18,11 @@
 #include <sys/wait.h>
 #include <math.h>
 #include <float.h>
+#include <sys/uio.h>
 #include "syscall.h"
 #include "libc.h"
 
-#define UNGET 4
+#define UNGET 8
 
 #define FLOCK(f) ((libc.lockfile && (f)->lock>=0) ? (libc.lockfile((f)),0) : 0)
 #define FUNLOCK(f) ((f)->lockcount && (--(f)->lockcount || ((f)->lock=0)))
@@ -31,14 +32,18 @@
 #define F_NOWR 8
 #define F_EOF 16
 #define F_ERR 32
+#define F_SVB 64
 
 struct __FILE_s {
 	unsigned flags;
-	unsigned char *rpos, *rstop;
-	unsigned char *rend, *wend;
-	unsigned char *wpos, *wstop;
+	unsigned char *rpos, *rend;
+	int (*close)(FILE *);
+	unsigned char *wend, *wpos;
+	unsigned char *mustbezero_1;
 	unsigned char *wbase;
-	unsigned char *dummy01[3];
+	size_t (*read)(FILE *, unsigned char *, size_t);
+	size_t (*write)(FILE *, const unsigned char *, size_t);
+	off_t (*seek)(FILE *, off_t, int);
 	unsigned char *buf;
 	size_t buf_size;
 	FILE *prev, *next;
@@ -46,26 +51,25 @@ struct __FILE_s {
 	int pipe_pid;
 	long dummy2;
 	short dummy3;
-	char dummy4;
+	signed char mode;
 	signed char lbf;
 	int lock;
 	int lockcount;
 	void *cookie;
 	off_t off;
 	int (*flush)(FILE *);
-	void **wide_data; /* must be NULL */
-	size_t (*read)(FILE *, unsigned char *, size_t);
-	size_t (*write)(FILE *, const unsigned char *, size_t);
-	off_t (*seek)(FILE *, off_t, int);
-	int mode;
-	int (*close)(FILE *);
+	void *mustbezero_2;
 };
 
 size_t __stdio_read(FILE *, unsigned char *, size_t);
 size_t __stdio_write(FILE *, const unsigned char *, size_t);
+size_t __stdout_write(FILE *, const unsigned char *, size_t);
 off_t __stdio_seek(FILE *, off_t, int);
 int __stdio_close(FILE *);
 
+int __toread(FILE *);
+int __towrite(FILE *);
+
 int __overflow(FILE *, int);
 int __oflow(FILE *);
 int __uflow(FILE *);
@@ -87,6 +91,12 @@ FILE *__fdopen(int, const char *);
 #define feof(f) ((f)->flags & F_EOF)
 #define ferror(f) ((f)->flags & F_ERR)
 
+#define getc_unlocked(f) \
+	( ((f)->rpos < (f)->rend) ? *(f)->rpos++ : __uflow((f)) )
+
+#define putc_unlocked(c, f) ( ((c)!=(f)->lbf && (f)->wpos<(f)->wend) \
+	? *(f)->wpos++ = (c) : __overflow((f),(c)) )
+
 /* Caller-allocated FILE * operations */
 FILE *__fopen_rb_ca(const char *, FILE *, unsigned char *, size_t);
 int __fclose_ca(FILE *);
diff --git a/src/stdio/__overflow.c b/src/stdio/__overflow.c
index e35104de..3bb37923 100644
--- a/src/stdio/__overflow.c
+++ b/src/stdio/__overflow.c
@@ -1,52 +1,10 @@
 #include "stdio_impl.h"
 
-static int overflow(FILE *f, int c)
+int __overflow(FILE *f, int _c)
 {
-	/* Initialize if we're not already writing */
-	if (!f->wend) {
-		/* Fail if we're in error state or unwritable. */
-		if (f->flags & (F_ERR|F_NOWR)) return EOF;
-
-		/* Set byte orientation -1,0=>-1; 1=>1 */
-		f->mode |= f->mode-1;
-
-		/* Clear read buffer (easier than summoning nasal demons) */
-		f->rpos = f->rend = f->rstop = 0;
-
-		/* Activate write through the buffer */
-		f->wpos = f->wbase = f->buf;
-		f->wend = f->buf + f->buf_size;
-		f->wstop = (f->lbf < 0) ? f->wend - 1 : 0;
-	}
-
-	/* Buffer can always hold at least 1 byte... */
-	if (c != EOF) {
-		*f->wpos++ = c;
-		if (f->wpos <= f->wstop && c != f->lbf) return c;
-	}
-	/* ...since if the next call fails, buffer is empty. */
-	if (f->write(f, f->wbase, f->wpos - f->wbase) < 0) {
-		f->flags |= F_ERR;
-		f->wpos = f->wbase = f->wend = f->wstop = 0;
-		return EOF;
-	}
-
-	/* Buffer is empty so reset position to beginning */
-	f->wpos = f->wbase;
-
+	unsigned char c = _c;
+	if (!f->wend && __towrite(f)) return EOF;
+	if (f->wpos < f->wend && c != f->lbf) return *f->wpos++ = c;
+	if (f->write(f, &c, 1)!=1) return EOF;
 	return c;
 }
-
-int __overflow(FILE *f, int c)
-{
-	return overflow(f, c & 0xff);
-}
-
-int __oflow(FILE *f)
-{
-	overflow(f, EOF);
-	return (f->flags & F_ERR) ? EOF : 0;
-}
-
-/* Link flush-on-exit code iff any stdio write functions are linked. */
-int (*const __fflush_on_exit)(FILE *) = fflush;
diff --git a/src/stdio/__stdio_read.c b/src/stdio/__stdio_read.c
index d9bb3269..a2e4cd62 100644
--- a/src/stdio/__stdio_read.c
+++ b/src/stdio/__stdio_read.c
@@ -2,5 +2,21 @@
 
 size_t __stdio_read(FILE *f, unsigned char *buf, size_t len)
 {
-	return syscall(SYS_read, f->fd, buf, len);
+	struct iovec iov[2] = {
+		{ .iov_base = buf, .iov_len = len },
+		{ .iov_base = f->buf, .iov_len = f->buf_size }
+	};
+	ssize_t cnt;
+
+	cnt = syscall(SYS_readv, f->fd, iov, 2);
+	if (cnt <= 0) {
+		f->flags |= F_EOF ^ ((F_ERR^F_EOF) & cnt);
+		f->rpos = f->rend = 0;
+		return cnt;
+	}
+	if (cnt <= len) return cnt;
+	cnt -= len;
+	f->rpos = f->buf;
+	f->rend = f->buf + cnt;
+	return len;
 }
diff --git a/src/stdio/__stdio_seek.c b/src/stdio/__stdio_seek.c
index c205ab82..35ae788c 100644
--- a/src/stdio/__stdio_seek.c
+++ b/src/stdio/__stdio_seek.c
@@ -2,6 +2,7 @@
 
 static off_t retneg1(FILE *f, off_t off, int whence)
 {
+	errno = ESPIPE;
 	return -1;
 }
 
@@ -15,7 +16,6 @@ off_t __stdio_seek(FILE *f, off_t off, int whence)
 	ret = syscall(SYS_lseek, f->fd, off, whence);
 #endif
 	/* Detect unseekable files and optimize future failures out */
-	if (ret < 0 && off == 0 && whence == SEEK_CUR)
-		f->seek = retneg1;
+	if (ret < 0 && errno == ESPIPE) f->seek = retneg1;
 	return ret;
 }
diff --git a/src/stdio/__stdio_write.c b/src/stdio/__stdio_write.c
index d4264eff..63d9c858 100644
--- a/src/stdio/__stdio_write.c
+++ b/src/stdio/__stdio_write.c
@@ -2,8 +2,29 @@
 
 size_t __stdio_write(FILE *f, const unsigned char *buf, size_t len)
 {
-	const unsigned char *stop = buf+len;
-	ssize_t cnt = 1;
-	for (; buf<stop && (cnt=syscall(SYS_write, f->fd, buf, len))>0; buf+=cnt);
-	return len-(stop-buf);
+	struct iovec iovs[2] = {
+		{ .iov_base = f->wbase, .iov_len = f->wpos-f->wbase },
+		{ .iov_base = (void *)buf, .iov_len = len }
+	};
+	struct iovec *iov = iovs;
+	size_t rem = iov[0].iov_len + iov[1].iov_len;
+	int iovcnt = 2;
+	ssize_t cnt;
+	f->wpos = f->wbase;
+	for (;;) {
+		cnt = syscall(SYS_writev, f->fd, iov, iovcnt);
+		if (cnt == rem) return len;
+		if (cnt < 0) {
+			f->wpos = f->wbase = f->wend = 0;
+			f->flags |= F_ERR;
+			return iovcnt == 2 ? 0 : len-iov[0].iov_len;
+		}
+		rem -= cnt;
+		if (cnt > iov[0].iov_len) {
+			cnt -= iov[0].iov_len;
+			iov++; iovcnt--;
+		}
+		iov[0].iov_base = (char *)iov[0].iov_base + cnt;
+		iov[0].iov_len -= cnt;
+	}
 }
diff --git a/src/stdio/__stdout_write.c b/src/stdio/__stdout_write.c
new file mode 100644
index 00000000..4683ffc3
--- /dev/null
+++ b/src/stdio/__stdout_write.c
@@ -0,0 +1,10 @@
+#include "stdio_impl.h"
+
+size_t __stdout_write(FILE *f, const unsigned char *buf, size_t len)
+{
+	struct termios tio;
+	f->write = __stdio_write;
+	if (!(f->flags & F_SVB) && syscall(SYS_ioctl, f->fd, TCGETS, &tio))
+		f->lbf = -1;
+	return __stdio_write(f, buf, len);
+}
diff --git a/src/stdio/__toread.c b/src/stdio/__toread.c
new file mode 100644
index 00000000..f00cc467
--- /dev/null
+++ b/src/stdio/__toread.c
@@ -0,0 +1,14 @@
+#include <stdio_impl.h>
+
+int __toread(FILE *f)
+{
+	f->mode |= f->mode-1;
+	if (f->wpos > f->buf) f->write(f, 0, 0);
+	f->wpos = f->wbase = f->wend = 0;
+	if (f->flags & (F_EOF|F_NORD)) {
+		if (f->flags & F_NORD) f->flags |= F_ERR;
+		return EOF;
+	}
+	f->rpos = f->rend = f->buf;
+	return 0;
+}
diff --git a/src/stdio/__towrite.c b/src/stdio/__towrite.c
new file mode 100644
index 00000000..b4587419
--- /dev/null
+++ b/src/stdio/__towrite.c
@@ -0,0 +1,21 @@
+#include "stdio_impl.h"
+
+int __towrite(FILE *f)
+{
+	f->mode |= f->mode-1;
+	if (f->flags & (F_NOWR)) {
+		f->flags |= F_ERR;
+		return EOF;
+	}
+	/* Clear read buffer (easier than summoning nasal demons) */
+	f->rpos = f->rend = 0;
+
+	/* Activate write through the buffer. */
+	f->wpos = f->wbase = f->buf;
+	f->wend = f->buf + f->buf_size;
+
+	return 0;
+}
+
+/* Link flush-on-exit code iff any stdio write functions are linked. */
+int (*const __fflush_on_exit)(FILE *) = fflush;
diff --git a/src/stdio/__uflow.c b/src/stdio/__uflow.c
index 5a51d610..544dda98 100644
--- a/src/stdio/__uflow.c
+++ b/src/stdio/__uflow.c
@@ -1,7 +1,11 @@
 #include "stdio_impl.h"
 
+/* This function will never be called if there is already data
+ * buffered for reading. Thus we can get by with very few branches. */
+
 int __uflow(FILE *f)
 {
-	if (__underflow(f) < 0) return EOF;
-	else return *f->rpos++;
+	unsigned char c = EOF;
+	if (f->rend || !__toread(f)) f->read(f, &c, 1);
+	return c;
 }
diff --git a/src/stdio/__underflow.c b/src/stdio/__underflow.c
deleted file mode 100644
index b769f4e4..00000000
--- a/src/stdio/__underflow.c
+++ /dev/null
@@ -1,38 +0,0 @@
-#include "stdio_impl.h"
-
-int __underflow(FILE *f)
-{
-	ssize_t cnt;
-
-	/* Read from buffer (Do we ever get called when this is true??) */
-	if (f->rpos < f->rstop) return *f->rpos;
-
-	/* Initialize if we're not already reading */
-	if (!f->rstop) {
-		/* Fail immediately if unreadable, eof, or error state. */
-		if (f->flags & (F_EOF|F_ERR|F_NORD)) return EOF;
-
-		/* Set byte orientation -1,0=>-1; 1=>1 */
-		f->mode |= f->mode-1;
-
-		/* Flush any unwritten output; fail on error. */
-		if (f->wpos > f->buf && __oflow(f)) return EOF;
-
-		/* Disallow writes to buffer. */
-		f->wstop = 0;
-	}
-
-	/* Perform the underlying read operation */
-	if ((cnt=f->read(f, f->buf, f->buf_size)) + 1 <= 1) {
-		/* Set flags and leave read mode */
-		f->flags |= F_EOF | (cnt & F_ERR);
-		f->rpos = f->rend = f->rstop = 0;
-		return EOF;
-	}
-
-	/* Setup buffer pointers for reading from buffer */
-	f->rpos = f->buf;
-	f->rend = f->rstop = f->buf + cnt;
-
-	return *f->rpos;
-}
diff --git a/src/stdio/fflush.c b/src/stdio/fflush.c
index cf3f5b0e..cdbd39bc 100644
--- a/src/stdio/fflush.c
+++ b/src/stdio/fflush.c
@@ -2,17 +2,23 @@
 
 static int __fflush_unlocked(FILE *f)
 {
-	/* If writing, flush output. */
-	if (f->wpos > f->buf && __oflow(f)) return -1;
+	/* If writing, flush output */
+	if (f->wpos > f->wbase) {
+		f->write(f, 0, 0);
+		if (!f->wpos) return EOF;
+	}
 
 	/* If reading, sync position, per POSIX */
 	if (f->rpos < f->rend) f->seek(f, f->rpos-f->rend, SEEK_CUR);
-	f->rpos = f->rend;
+
+	/* Clear read and write modes */
+	f->wpos = f->wbase = f->wend = 0;
+	f->rpos = f->rend = 0;
 
 	/* Hook for special behavior on flush */
 	if (f->flush) f->flush(f);
 
-	return (f->flags & F_ERR) ? EOF : 0;
+	return 0;
 }
 
 /* stdout.c will override this if linked */
@@ -36,9 +42,9 @@ int fflush(FILE *f)
 	OFLLOCK();
 	for (f=ofl_head; f; f=next) {
 		FLOCK(f);
-		OFLUNLOCK();
+		//OFLUNLOCK();
 		r |= __fflush_unlocked(f);
-		OFLLOCK();
+		//OFLLOCK();
 		next = f->next;
 		FUNLOCK(f);
 	}
diff --git a/src/stdio/fgetc.c b/src/stdio/fgetc.c
index 3a7f1e30..da638684 100644
--- a/src/stdio/fgetc.c
+++ b/src/stdio/fgetc.c
@@ -4,7 +4,9 @@ int fgetc(FILE *f)
 {
 	int c;
 	FLOCK(f);
-	c = f->rpos < f->rstop ? *f->rpos++ : __uflow(f);
+	c = getc_unlocked(f);
 	FUNLOCK(f);
 	return c;
 }
+
+weak_alias(fgetc, getc);
diff --git a/src/stdio/fgets.c b/src/stdio/fgets.c
index 7939303e..3135a69a 100644
--- a/src/stdio/fgets.c
+++ b/src/stdio/fgets.c
@@ -7,12 +7,17 @@ char *fgets(char *s, int n, FILE *f)
 	char *p = s;
 	unsigned char *z;
 	size_t k;
+	int c;
 
-	if (!n--) return 0;
+	if (n--<=1) {
+		if (n) return 0;
+		*s = 0;
+		return s;
+	}
 
 	FLOCK(f);
 
-	while (n && !feof(f)) {
+	while (n) {
 		z = memchr(f->rpos, '\n', f->rend - f->rpos);
 		k = z ? z - f->rpos + 1 : f->rend - f->rpos;
 		k = MIN(k, n);
@@ -20,15 +25,19 @@ char *fgets(char *s, int n, FILE *f)
 		f->rpos += k;
 		p += k;
 		n -= k;
-		if (z) break;
-		__underflow(f);
+		if (z || !n) break;
+		if ((c = getc_unlocked(f)) < 0) {
+			if (p==s || !feof(f)) s = 0;
+			break;
+		}
+		n--;
+		if ((*p++ = c) == '\n') break;
 	}
 	*p = 0;
-	if (ferror(f)) p = s;
 
 	FUNLOCK(f);
 
-	return (p == s) ? 0 : s;
+	return s;
 }
 
 weak_alias(fgets, fgets_unlocked);
diff --git a/src/stdio/fgetwc.c b/src/stdio/fgetwc.c
index 77b30fd1..5e420594 100644
--- a/src/stdio/fgetwc.c
+++ b/src/stdio/fgetwc.c
@@ -23,9 +23,9 @@ wint_t __fgetwc_unlocked(FILE *f)
 		}
 	} else l = -2;
 
-	/* Convert character byte-by-byte from __uflow */
+	/* Convert character byte-by-byte */
 	while (l == -2) {
-		b = c = __uflow(f);
+		b = c = getc_unlocked(f);
 		if (c < 0) {
 			if (!mbsinit(&st)) errno = EILSEQ;
 			return WEOF;
diff --git a/src/stdio/fputc.c b/src/stdio/fputc.c
index ec859385..98d0a20a 100644
--- a/src/stdio/fputc.c
+++ b/src/stdio/fputc.c
@@ -3,8 +3,9 @@
 int fputc(int c, FILE *f)
 {
 	FLOCK(f);
-	if (c != f->lbf && f->wpos + 1 < f->wend) *f->wpos++ = c;
-	else c = __overflow(f, c);
+	c = putc_unlocked(c, f);
 	FUNLOCK(f);
 	return c;
 }
+
+weak_alias(fputc, putc);
diff --git a/src/stdio/fputwc.c b/src/stdio/fputwc.c
index ec49b5c6..292a53fb 100644
--- a/src/stdio/fputwc.c
+++ b/src/stdio/fputwc.c
@@ -8,8 +8,7 @@ wint_t __fputwc_unlocked(wchar_t c, FILE *f)
 	f->mode |= f->mode+1;
 
 	if (isascii(c)) {
-		if (c != f->lbf && f->wpos + 1 < f->wend) *f->wpos++ = c;
-		else c = __overflow(f, c);
+		c = putc_unlocked(c, f);
 	} else if (f->wpos + MB_LEN_MAX < f->wend) {
 		l = wctomb((void *)f->wpos, c);
 		if (l < 0) c = WEOF;
diff --git a/src/stdio/fread.c b/src/stdio/fread.c
index 0fa0b2aa..8105fe99 100644
--- a/src/stdio/fread.c
+++ b/src/stdio/fread.c
@@ -12,38 +12,31 @@ size_t fread(void *destv, size_t size, size_t nmemb, FILE *f)
 
 	FLOCK(f);
 
-	for (;;) {
+	if (f->rend - f->rpos > 0) {
 		/* First exhaust the buffer. */
 		k = MIN(f->rend - f->rpos, l);
 		memcpy(dest, f->rpos, k);
 		f->rpos += k;
 		dest += k;
 		l -= k;
-
-		/* Stop on EOF or errors */
-		if (f->flags & (F_EOF|F_ERR|F_NORD)) goto eof;
-
-		/* Done? Or going unbuffered? */
-		if (!l || l > f->buf_size/2) break;
-
-		/* Otherwise, refill & read thru buffer. */
-		__underflow(f);
+	}
+	
+	if (!l) {
+		FUNLOCK(f);
+		return nmemb;
 	}
 
 	/* Read the remainder directly */
 	for (; l; l-=k, dest+=k) {
 		k = f->read(f, dest, l);
 		if (k+1<=1) {
-			f->flags |= F_EOF | (F_ERR & k);
-			goto eof;
+			FUNLOCK(f);
+			return (len-l)/size;
 		}
 	}
 
 	FUNLOCK(f);
 	return nmemb;
-eof:
-	FUNLOCK(f);
-	return (len-l)/size;
 }
 
 weak_alias(fread, fread_unlocked);
diff --git a/src/stdio/fseek.c b/src/stdio/fseek.c
index bfaad375..8d9da440 100644
--- a/src/stdio/fseek.c
+++ b/src/stdio/fseek.c
@@ -5,17 +5,25 @@ int __fseeko_unlocked(FILE *f, off_t off, int whence)
 	/* Adjust relative offset for unread data in buffer, if any. */
 	if (whence == SEEK_CUR) off -= f->rend - f->rpos;
 
-	/* If writing, flush output. */
-	if (f->wpos > f->buf && __oflow(f)) return -1;
+	/* Flush write buffer, and report error on failure. */
+	if (f->wpos > f->wbase) {
+		f->write(f, 0, 0);
+		if (!f->wpos) return -1;
+	}
 
-	/* Perform the underlying seek operation. */
-	if (f->seek(f, off, whence) < 0) return -1;
+	/* Leave writing mode */
+	f->wpos = f->wbase = f->wend = 0;
+
+	/* Perform the underlying seek. */
+	if (f->seek(f, off, whence) < 0) {
+		f->flags |= F_ERR;
+		return -1;
+	}
 
 	/* If seek succeeded, file is seekable and we discard read buffer. */
-	f->rpos = f->rend = f->rstop = 0;
+	f->rpos = f->rend = 0;
 	f->flags &= ~F_EOF;
 	
-	FUNLOCK(f);	
 	return 0;
 }
 
diff --git a/src/stdio/fwrite.c b/src/stdio/fwrite.c
index 23974fe1..02908c4b 100644
--- a/src/stdio/fwrite.c
+++ b/src/stdio/fwrite.c
@@ -2,50 +2,36 @@
 
 size_t __fwritex(const unsigned char *s, size_t l, FILE *f)
 {
-	size_t i = 0;
-	size_t k = f->wend - f->wpos;
+	size_t i=0;
+
+	if (!f->wend && __towrite(f)) return 0;
+
+	if (l > f->wend - f->wpos) return f->write(f, s, l);
 
-	/* Handle line-buffered mode by breaking into 2 parts */
 	if (f->lbf >= 0) {
 		/* Match /^(.*\n|)/ */
 		for (i=l; i && s[i-1] != '\n'; i--);
 		if (i) {
-			f->lbf = EOF;
-			__fwritex(s, i, f);
-			f->lbf = '\n';
-			__oflow(f);
-			return ferror(f) ? 0 : i + __fwritex(s+i, l-i, f);
+			if (f->write(f, s, i) < i)
+				return i;
+			s += i;
+			l -= i;
 		}
 	}
 
-	/* Buffer initial segment */
-	if (k > l) k = l;
-	memcpy(f->wpos, s, k);
-	f->wpos += k;
-	if (f->wpos < f->wend) return l;
-
-	/* If there's work left to do, flush buffer */
-	__oflow(f);
-	if (ferror(f)) return 0;
-
-	/* If the remainder will not fit in buffer, write it directly */
-	if (l - k >= f->wend - f->wpos)
-		return k + f->write(f, s+k, l-k);
-
-	/* Otherwise, buffer the remainder */
-	memcpy(f->wpos, s+k, l-k);
-	f->wpos += l-k;
-	return l;
+	memcpy(f->wpos, s, l);
+	f->wpos += l;
+	return l+i;
 }
 
 size_t fwrite(const void *src, size_t size, size_t nmemb, FILE *f)
 {
-	size_t l = size*nmemb;
+	size_t k, l = size*nmemb;
 	if (!l) return l;
 	FLOCK(f);
-	l = __fwritex(src, l, f);
+	k = __fwritex(src, l, f);
 	FUNLOCK(f);
-	return l/size;
+	return k==l ? nmemb : l/size;
 }
 
 weak_alias(fwrite, fwrite_unlocked);
diff --git a/src/stdio/getc.c b/src/stdio/getc.c
deleted file mode 100644
index b739b0a5..00000000
--- a/src/stdio/getc.c
+++ /dev/null
@@ -1,6 +0,0 @@
-#include "stdio_impl.h"
-
-int getc(FILE *f)
-{
-	return fgetc(f);
-}
diff --git a/src/stdio/getc_unlocked.c b/src/stdio/getc_unlocked.c
index 629223ea..203a1081 100644
--- a/src/stdio/getc_unlocked.c
+++ b/src/stdio/getc_unlocked.c
@@ -1,8 +1,8 @@
 #include "stdio_impl.h"
 
-int getc_unlocked(FILE *f)
+int (getc_unlocked)(FILE *f)
 {
-	return f->rpos < f->rstop ? *f->rpos++ : __uflow(f);
+	return getc_unlocked(f);
 }
 
 weak_alias (getc_unlocked, fgetc_unlocked);
diff --git a/src/stdio/getchar_unlocked.c b/src/stdio/getchar_unlocked.c
index 299cb958..355ac318 100644
--- a/src/stdio/getchar_unlocked.c
+++ b/src/stdio/getchar_unlocked.c
@@ -2,5 +2,5 @@
 
 int getchar_unlocked(void)
 {
-	return stdin->rpos < stdin->rstop ? *stdin->rpos++ : __uflow(stdin);
+	return getc_unlocked(stdin);
 }
diff --git a/src/stdio/getdelim.c b/src/stdio/getdelim.c
index f770d20b..20d345d1 100644
--- a/src/stdio/getdelim.c
+++ b/src/stdio/getdelim.c
@@ -8,6 +8,7 @@ ssize_t getdelim(char **s, size_t *n, int delim, FILE *f)
 	unsigned char *z;
 	size_t k;
 	size_t i=0;
+	int c;
 
 	if (!n || !s) {
 		errno = EINVAL;
@@ -18,16 +19,16 @@ ssize_t getdelim(char **s, size_t *n, int delim, FILE *f)
 
 	FLOCK(f);
 
-	while (!feof(f)) {
+	for (;;) {
 		z = memchr(f->rpos, delim, f->rend - f->rpos);
 		k = z ? z - f->rpos + 1 : f->rend - f->rpos;
 		if (i+k >= *n) {
-			if (k >= SIZE_MAX-i) goto oom;
-			*n = i+k+1;
-			if (*n < SIZE_MAX/2) *n *= 2;
+			if (k >= SIZE_MAX/2-i) goto oom;
+			*n = i+k+2;
+			if (*n < SIZE_MAX/4) *n *= 2;
 			tmp = realloc(*s, *n);
 			if (!tmp) {
-				*n = i+k+1;
+				*n = i+k+2;
 				tmp = realloc(*s, *n);
 				if (!tmp) goto oom;
 			}
@@ -37,23 +38,22 @@ ssize_t getdelim(char **s, size_t *n, int delim, FILE *f)
 		f->rpos += k;
 		i += k;
 		if (z) break;
-		__underflow(f);
+		if ((c = getc_unlocked(f)) == EOF) {
+			if (!i || !feof(f)) {
+				FUNLOCK(f);
+				return -1;
+			}
+			break;
+		}
+		if (((*s)[i++] = c) == delim) break;
 	}
 	(*s)[i] = 0;
-	if (feof(f) || ferror(f)) {
-		FUNLOCK(f);
-		return -1;
-	}
 
 	FUNLOCK(f);
 
-	if (i > SSIZE_MAX) {
-		errno = EOVERFLOW;
-		return -1;
-	}
-
 	return i;
 oom:
+	FUNLOCK(f);
 	errno = ENOMEM;
 	return -1;
 }
diff --git a/src/stdio/putc.c b/src/stdio/putc.c
deleted file mode 100644
index 3c9dc11e..00000000
--- a/src/stdio/putc.c
+++ /dev/null
@@ -1,8 +0,0 @@
-#include "stdio_impl.h"
-
-int putc(int c, FILE *f)
-{
-	return fputc(c, f);
-}
-
-weak_alias(putc, _IO_putc);
diff --git a/src/stdio/putc_unlocked.c b/src/stdio/putc_unlocked.c
index f01da717..b47876c9 100644
--- a/src/stdio/putc_unlocked.c
+++ b/src/stdio/putc_unlocked.c
@@ -1,8 +1,8 @@
 #include "stdio_impl.h"
 
-int putc_unlocked(int c, FILE *f)
+int (putc_unlocked)(int c, FILE *f)
 {
-	return f->wpos < f->wstop ? (*f->wpos++ = c) : __overflow(f, c);
+	return putc_unlocked(c, f);
 }
 
 weak_alias(putc_unlocked, fputc_unlocked);
diff --git a/src/stdio/putchar_unlocked.c b/src/stdio/putchar_unlocked.c
index 72d47d15..8b5d0603 100644
--- a/src/stdio/putchar_unlocked.c
+++ b/src/stdio/putchar_unlocked.c
@@ -2,6 +2,5 @@
 
 int putchar_unlocked(int c)
 {
-	return stdout->wpos < stdout->wstop ?
-		(*stdout->wpos++ = c) : __overflow(stdout, c);
+	return putc_unlocked(c, stdout);
 }
diff --git a/src/stdio/setvbuf.c b/src/stdio/setvbuf.c
index 2985d3f1..6dea0ebf 100644
--- a/src/stdio/setvbuf.c
+++ b/src/stdio/setvbuf.c
@@ -14,9 +14,11 @@ int setvbuf(FILE *f, char *buf, int type, size_t size)
 	f->lbf = EOF;
 
 	if (type == _IONBF)
-		f->buf_size = 1;
+		f->buf_size = 0;
 	else if (type == _IOLBF)
 		f->lbf = '\n';
 
+	f->flags |= F_SVB;
+
 	return 0;
 }
diff --git a/src/stdio/stderr.c b/src/stdio/stderr.c
index 4a79d4e7..3bdaffbc 100644
--- a/src/stdio/stderr.c
+++ b/src/stdio/stderr.c
@@ -1,9 +1,9 @@
 #include "stdio_impl.h"
 
-static unsigned char buf[1+UNGET];
+static unsigned char buf[UNGET];
 static FILE f = {
 	.buf = buf+UNGET,
-	.buf_size = 1,
+	.buf_size = 0,
 	.fd = 2,
 	.flags = F_PERM | F_NORD,
 	.write = __stdio_write,
diff --git a/src/stdio/stdout.c b/src/stdio/stdout.c
index bf6eea6c..552d729e 100644
--- a/src/stdio/stdout.c
+++ b/src/stdio/stdout.c
@@ -7,7 +7,7 @@ static FILE f = {
 	.fd = 1,
 	.flags = F_PERM | F_NORD,
 	.lbf = '\n',
-	.write = __stdio_write,
+	.write = __stdout_write,
 	.seek = __stdio_seek,
 	.close = __stdio_close,
 };
diff --git a/src/stdio/ungetc.c b/src/stdio/ungetc.c
index 07181684..7f56f8d5 100644
--- a/src/stdio/ungetc.c
+++ b/src/stdio/ungetc.c
@@ -6,25 +6,11 @@ int ungetc(int c, FILE *f)
 
 	FLOCK(f);
 
-	/* Fail if unreadable or writing and unable to flush */
-	if ((f->flags & (F_ERR|F_NORD)) || (f->wpos && __oflow(f))) {
+	if ((!f->rend && __toread(f)) || f->rpos <= f->buf - UNGET) {
 		FUNLOCK(f);
 		return EOF;
 	}
 
-	/* Clear write mode */
-	f->wbase = f->wpos = f->wstop = f->wend = 0;
-
-	/* Put the file in read mode */
-	if (!f->rpos) f->rpos = f->rend = f->buf;
-
-	/* If unget buffer is already full, fail. */
-	if (f->rpos <= f->buf - UNGET) {
-		FUNLOCK(f);
-		return EOF;
-	}
-
-	/* Put a byte back into the buffer */
 	*--f->rpos = c;
 	f->flags &= ~F_EOF;
 
diff --git a/src/stdio/ungetwc.c b/src/stdio/ungetwc.c
index 6871d034..5282fee1 100644
--- a/src/stdio/ungetwc.c
+++ b/src/stdio/ungetwc.c
@@ -15,29 +15,14 @@ wint_t ungetwc(wint_t c, FILE *f)
 
 	f->mode |= f->mode+1;
 
-	/* Fail if unreadable or writing and unable to flush */
-	if ((f->flags & (F_ERR|F_NORD)) || (f->wpos && __oflow(f))) {
+	if ((!f->rend && __toread(f)) || f->rpos < f->buf - UNGET + l) {
 		FUNLOCK(f);
 		return EOF;
 	}
 
-	/* Clear write mode */
-	f->wpos = f->wstop = f->wend = 0;
-
-	/* Put the file in read mode */
-	if (!f->rpos) f->rpos = f->rend = f->buf;
-
-	/* If unget buffer is nonempty, fail. */
-	if (f->rpos < f->buf) {
-		FUNLOCK(f);
-		return WEOF;
-	}
-
-	/* Put character back into the buffer */
 	if (isascii(c)) *--f->rpos = c;
 	else memcpy(f->rpos -= l, mbc, l);
 
-	/* Clear EOF */
 	f->flags &= ~F_EOF;
 
 	FUNLOCK(f);
diff --git a/src/stdio/vdprintf.c b/src/stdio/vdprintf.c
index 68562e05..faf9536a 100644
--- a/src/stdio/vdprintf.c
+++ b/src/stdio/vdprintf.c
@@ -11,9 +11,9 @@ int vdprintf(int fd, const char *fmt, va_list ap)
 	unsigned char buf[BUFSIZ];
 	FILE f = {
 		.fd = fd, .lbf = EOF, .write = wrap_write,
-		.buf = buf+UNGET, .buf_size = sizeof buf - UNGET
+		.buf = buf+UNGET, .buf_size = sizeof buf - UNGET,
+		.lock = -1
 	};
 	r = vfprintf(&f, fmt, ap);
-	__oflow(&f);
-	return r;
+	return fflush(&f) ? EOF : r;
 }
diff --git a/src/stdio/vfprintf.c b/src/stdio/vfprintf.c
index b6bb3bcf..57878c03 100644
--- a/src/stdio/vfprintf.c
+++ b/src/stdio/vfprintf.c
@@ -434,7 +434,7 @@ static int printf_core(FILE *f, const char *fmt, va_list *ap, union arg *nl_arg,
 		/* Update output count, end loop when fmt is exhausted */
 		if (cnt >= 0) {
 			if (l > INT_MAX - cnt) {
-				if (!ferror(f)) errno = EOVERFLOW;
+				errno = EOVERFLOW;
 				cnt = -1;
 			} else cnt += l;
 		}
diff --git a/src/stdio/vfscanf.c b/src/stdio/vfscanf.c
index 69f45081..414c2a3d 100644
--- a/src/stdio/vfscanf.c
+++ b/src/stdio/vfscanf.c
@@ -9,7 +9,7 @@
 static void f_read(rctx_t *r)
 {
 	FILE *f = r->opaque;
-	if ((r->c = __uflow(f)) >= 0) r->l++;
+	if ((r->c = getc_unlocked(f)) >= 0) r->l++;
 }
 
 int vfscanf(FILE *f, const char *fmt, va_list ap)
@@ -28,15 +28,8 @@ int vfscanf(FILE *f, const char *fmt, va_list ap)
 
 	result = __scanf(&r, fmt2, ap);
 
-	if (r.u && r.c >= 0) {
-		/* This code takes care of the case where the caller performs
-		 * a nonmatching scanf to leave a character in the unscan
-		 * buffer, followed by an unget, followed by a scanf that
-		 * matches zero characters. In this case the final 'unread'
-		 * character must be returned to the unget buffer rather than
-		 * the unscan buffer. */
-		 f->rpos--;
-	}
+	if (r.u && r.c >= 0)
+		ungetc(r.c, f);
 
 	FUNLOCK(f);
 	return result;
diff --git a/src/stdio/vsnprintf.c b/src/stdio/vsnprintf.c
index 1f316ca4..ba17bd7d 100644
--- a/src/stdio/vsnprintf.c
+++ b/src/stdio/vsnprintf.c
@@ -2,33 +2,37 @@
 
 static size_t sn_write(FILE *f, const unsigned char *s, size_t l)
 {
-	/* pretend to succeed, but discard data */
+	size_t k = f->wend - f->wpos;
+	if (k > l) k = l;
+	memcpy(f->wpos, s, k);
+	f->wpos += k;
+	/* pretend to succeed, but discard extra data */
 	return l;
 }
 
 int vsnprintf(char *s, size_t n, const char *fmt, va_list ap)
 {
 	int r;
-	FILE f;
-	unsigned char buf[1];
+	char b;
+	FILE f = { .lbf = EOF, .write = sn_write, .lock = -1 };
 
-	memset(&f, 0, sizeof(FILE));
-	f.lbf = EOF;
-	f.write = sn_write;
-	f.buf_size = 1;
-	f.buf = buf;
-	f.lock = -1;
-	if (n > INT_MAX) {
-		errno = EOVERFLOW;
-		return -1;
-	} else if (n > 0) {
-		if (n > (char *)0+SIZE_MAX-s) n = (char *)0+SIZE_MAX-s;
-		f.wpos = (void *)s;
-		f.wbase = f.wend = (void *)(s+n-1);
-		f.wstop = f.wend - 1;
+	if (n-1 > INT_MAX-1) {
+		if (n) {
+			errno = EOVERFLOW;
+			return -1;
+		}
+		s = &b;
+		n = 1;
 	}
+
+	/* Ensure pointers don't wrap if "infinite" n is passed in */
+	if (n > (char *)0+SIZE_MAX-s-1) n = (char *)0+SIZE_MAX-s-1;
+	f.buf_size = n;
+	f.buf = f.wpos = (void *)s;
+	f.wbase = f.wend = (void *)(s+n);
 	r = vfprintf(&f, fmt, ap);
-	/* wpos points just after last byte written, or to s+n-1 (wbase) */
-	*f.wpos = 0;
+
+	/* Null-terminate, overwriting last char if dest buffer is full */
+	if (n) f.wpos[-(f.wpos == f.wend)] = 0;
 	return r;
 }
diff --git a/src/stdio/vswprintf.c b/src/stdio/vswprintf.c
index 2d9f2002..8e8f80ce 100644
--- a/src/stdio/vswprintf.c
+++ b/src/stdio/vswprintf.c
@@ -10,6 +10,8 @@ static size_t sw_write(FILE *f, const unsigned char *s, size_t l)
 	size_t l0 = l;
 	int i = 0;
 	struct cookie *c = f->cookie;
+	if (s!=f->wbase && sw_write(f, f->wbase, f->wpos-f->wbase)==-1)
+		return -1;
 	while (c->l && l && (i=mbtowc(c->ws, (void *)s, l))>=0) {
 		s+=i;
 		l-=i;
@@ -41,6 +43,6 @@ int vswprintf(wchar_t *s, size_t n, const wchar_t *fmt, va_list ap)
 		return -1;
 	}
 	r = vfwprintf(&f, fmt, ap);
-	__oflow(&f);
+	sw_write(&f, 0, 0);
 	return r>=n ? -1 : r;
 }