about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/stdio/vsnprintf.c63
1 files changed, 38 insertions, 25 deletions
diff --git a/src/stdio/vsnprintf.c b/src/stdio/vsnprintf.c
index be2c44eb..b3510a63 100644
--- a/src/stdio/vsnprintf.c
+++ b/src/stdio/vsnprintf.c
@@ -4,39 +4,52 @@
 #include <errno.h>
 #include <stdint.h>
 
+struct cookie {
+	char *s;
+	size_t n;
+};
+
+#define MIN(a, b) ((a) < (b) ? (a) : (b))
+
 static size_t sn_write(FILE *f, const unsigned char *s, size_t l)
 {
-	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 */
+	struct cookie *c = f->cookie;
+	size_t k = MIN(c->n, f->wpos - f->wbase);
+	if (k) {
+		memcpy(c->s, f->wbase, k);
+		c->s += k;
+		c->n -= k;
+	}
+	k = MIN(c->n, l);
+	if (k) {
+		memcpy(c->s, s, k);
+		c->s += k;
+		c->n -= k;
+	}
+	*c->s = 0;
+	f->wpos = f->wbase = f->buf;
+	/* pretend to succeed, even if we discarded extra data */
 	return l;
 }
 
 int vsnprintf(char *restrict s, size_t n, const char *restrict fmt, va_list ap)
 {
-	int r;
-	char b;
-	FILE f = { .lbf = EOF, .write = sn_write, .lock = -1 };
+	unsigned char buf[1];
+	char dummy[1];
+	struct cookie c = { .s = n ? s : dummy, .n = n ? n-1 : 0 };
+	FILE f = {
+		.lbf = EOF,
+		.write = sn_write,
+		.lock = -1,
+		.buf = buf,
+		.cookie = &c,
+	};
 
-	if (n-1 > INT_MAX-1) {
-		if (n) {
-			errno = EOVERFLOW;
-			return -1;
-		}
-		s = &b;
-		n = 1;
+	if (n > INT_MAX) {
+		errno = EOVERFLOW;
+		return -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);
-
-	/* Null-terminate, overwriting last char if dest buffer is full */
-	if (n) f.wpos[-(f.wpos == f.wend)] = 0;
-	return r;
+	*c.s = 0;
+	return vfprintf(&f, fmt, ap);
 }