summaryrefslogtreecommitdiff
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);
}