aboutsummaryrefslogtreecommitdiff
path: root/sys
diff options
context:
space:
mode:
Diffstat (limited to 'sys')
-rw-r--r--sys/kern/subr_sbuf.c105
-rw-r--r--sys/sys/sbuf.h10
2 files changed, 107 insertions, 8 deletions
diff --git a/sys/kern/subr_sbuf.c b/sys/kern/subr_sbuf.c
index bca870531208..d7b57a5891a2 100644
--- a/sys/kern/subr_sbuf.c
+++ b/sys/kern/subr_sbuf.c
@@ -33,6 +33,7 @@ __FBSDID("$FreeBSD$");
#ifdef _KERNEL
#include <sys/ctype.h>
+#include <sys/errno.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/systm.h>
@@ -40,6 +41,7 @@ __FBSDID("$FreeBSD$");
#include <machine/stdarg.h>
#else /* _KERNEL */
#include <ctype.h>
+#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
@@ -246,6 +248,7 @@ sbuf_clear(struct sbuf *s)
SBUF_CLEARFLAG(s, SBUF_FINISHED);
SBUF_CLEARFLAG(s, SBUF_OVERFLOWED);
+ s->s_error = 0;
s->s_len = 0;
}
@@ -272,6 +275,54 @@ sbuf_setpos(struct sbuf *s, int pos)
}
/*
+ * Set up a drain function and argument on an sbuf to flush data to
+ * when the sbuf buffer overflows.
+ */
+void
+sbuf_set_drain(struct sbuf *s, sbuf_drain_func *func, void *ctx)
+{
+
+ assert_sbuf_state(s, 0);
+ assert_sbuf_integrity(s);
+ KASSERT(func == s->s_drain_func || s->s_len == 0,
+ ("Cannot change drain to %p on non-empty sbuf %p", func, s));
+ s->s_drain_func = func;
+ s->s_drain_arg = ctx;
+}
+
+/*
+ * Call the drain and process the return.
+ */
+static int
+sbuf_drain(struct sbuf *s)
+{
+ int len;
+
+ KASSERT(s->s_len > 0, ("Shouldn't drain empty sbuf %p", s));
+ len = s->s_drain_func(s->s_drain_arg, s->s_buf, s->s_len);
+ if (len < 0) {
+ s->s_error = -len;
+ SBUF_SETFLAG(s, SBUF_OVERFLOWED);
+ return (s->s_error);
+ }
+
+ KASSERT(len > 0, ("Drain must either error or work!"));
+ s->s_len -= len;
+ /*
+ * Fast path for the expected case where all the data was
+ * drained.
+ */
+ if (s->s_len == 0)
+ return (0);
+ /*
+ * Move the remaining characters to the beginning of the
+ * string.
+ */
+ memmove(s->s_buf, s->s_buf + len, s->s_len);
+ return (0);
+}
+
+/*
* Append a byte to an sbuf. This is the core function for appending
* to an sbuf and is the main place that deals with extending the
* buffer and marking overflow.
@@ -286,10 +337,16 @@ sbuf_put_byte(int c, struct sbuf *s)
if (SBUF_HASOVERFLOWED(s))
return;
if (SBUF_FREESPACE(s) <= 0) {
- if (sbuf_extend(s, 1) < 0) {
+ /*
+ * If there is a drain, use it, otherwise extend the
+ * buffer.
+ */
+ if (s->s_drain_func != NULL)
+ (void)sbuf_drain(s);
+ else if (sbuf_extend(s, 1) < 0)
SBUF_SETFLAG(s, SBUF_OVERFLOWED);
+ if (SBUF_HASOVERFLOWED(s))
return;
- }
}
s->s_buf[s->s_len++] = c;
}
@@ -338,6 +395,8 @@ sbuf_bcopyin(struct sbuf *s, const void *uaddr, size_t len)
assert_sbuf_integrity(s);
assert_sbuf_state(s, 0);
+ KASSERT(s->s_drain_func == NULL,
+ ("Nonsensical copyin to sbuf %p with a drain", s));
if (SBUF_HASOVERFLOWED(s))
return (-1);
@@ -402,6 +461,8 @@ sbuf_copyin(struct sbuf *s, const void *uaddr, size_t len)
assert_sbuf_integrity(s);
assert_sbuf_state(s, 0);
+ KASSERT(s->s_drain_func == NULL,
+ ("Nonsensical copyin to sbuf %p with a drain", s));
if (SBUF_HASOVERFLOWED(s))
return (-1);
@@ -466,7 +527,7 @@ int
sbuf_vprintf(struct sbuf *s, const char *fmt, va_list ap)
{
va_list ap_copy;
- int len;
+ int error, len;
assert_sbuf_integrity(s);
assert_sbuf_state(s, 0);
@@ -481,15 +542,28 @@ sbuf_vprintf(struct sbuf *s, const char *fmt, va_list ap)
* For the moment, there is no way to get vsnprintf(3) to hand
* back a character at a time, to push everything into
* sbuf_putc_func() as was done for the kernel.
+ *
+ * In userspace, while drains are useful, there's generally
+ * not a problem attempting to malloc(3) on out of space. So
+ * expand a userland sbuf if there is not enough room for the
+ * data produced by sbuf_[v]printf(3).
*/
+ error = 0;
do {
va_copy(ap_copy, ap);
len = vsnprintf(&s->s_buf[s->s_len], SBUF_FREESPACE(s) + 1,
fmt, ap_copy);
va_end(ap_copy);
- } while (len > SBUF_FREESPACE(s) &&
- sbuf_extend(s, len - SBUF_FREESPACE(s)) == 0);
+
+ if (SBUF_FREESPACE(s) >= len)
+ break;
+ /* Cannot print with the current available space. */
+ if (s->s_drain_func != NULL && s->s_len > 0)
+ error = sbuf_drain(s);
+ else
+ error = sbuf_extend(s, len - SBUF_FREESPACE(s));
+ } while (error == 0);
/*
* s->s_len is the length of the string, without the terminating nul.
@@ -552,6 +626,8 @@ sbuf_trim(struct sbuf *s)
assert_sbuf_integrity(s);
assert_sbuf_state(s, 0);
+ KASSERT(s->s_drain_func == NULL,
+ ("%s makes no sense on sbuf %p with drain", __func__, s));
if (SBUF_HASOVERFLOWED(s))
return (-1);
@@ -575,16 +651,29 @@ sbuf_overflowed(struct sbuf *s)
/*
* Finish off an sbuf.
*/
-void
+int
sbuf_finish(struct sbuf *s)
{
+ int error = 0;
assert_sbuf_integrity(s);
assert_sbuf_state(s, 0);
+ if (s->s_drain_func != NULL) {
+ error = s->s_error;
+ while (s->s_len > 0 && error == 0)
+ error = sbuf_drain(s);
+ } else if (SBUF_HASOVERFLOWED(s))
+ error = ENOMEM;
s->s_buf[s->s_len] = '\0';
SBUF_CLEARFLAG(s, SBUF_OVERFLOWED);
SBUF_SETFLAG(s, SBUF_FINISHED);
+#ifdef _KERNEL
+ return (error);
+#else
+ errno = error;
+ return (-1);
+#endif
}
/*
@@ -596,6 +685,8 @@ sbuf_data(struct sbuf *s)
assert_sbuf_integrity(s);
assert_sbuf_state(s, SBUF_FINISHED);
+ KASSERT(s->s_drain_func == NULL,
+ ("%s makes no sense on sbuf %p with drain", __func__, s));
return (s->s_buf);
}
@@ -609,6 +700,8 @@ sbuf_len(struct sbuf *s)
assert_sbuf_integrity(s);
/* don't care if it's finished or not */
+ KASSERT(s->s_drain_func == NULL,
+ ("%s makes no sense on sbuf %p with drain", __func__, s));
if (SBUF_HASOVERFLOWED(s))
return (-1);
diff --git a/sys/sys/sbuf.h b/sys/sys/sbuf.h
index fce24beeed13..be3d80c52406 100644
--- a/sys/sys/sbuf.h
+++ b/sys/sys/sbuf.h
@@ -33,12 +33,17 @@
#include <sys/_types.h>
+struct sbuf;
+typedef int (sbuf_drain_func)(void *, const char *, int);
+
/*
* Structure definition
*/
struct sbuf {
char *s_buf; /* storage buffer */
- void *s_unused; /* binary compatibility. */
+ sbuf_drain_func *s_drain_func; /* drain function */
+ void *s_drain_arg; /* user-supplied drain argument */
+ int s_error; /* current error code */
int s_size; /* size of storage buffer */
int s_len; /* current length of string */
#define SBUF_FIXEDLEN 0x00000000 /* fixed length buffer (default) */
@@ -69,9 +74,10 @@ int sbuf_printf(struct sbuf *, const char *, ...)
int sbuf_vprintf(struct sbuf *, const char *, __va_list)
__printflike(2, 0);
int sbuf_putc(struct sbuf *, int);
+void sbuf_set_drain(struct sbuf *, sbuf_drain_func *, void *);
int sbuf_trim(struct sbuf *);
int sbuf_overflowed(struct sbuf *);
-void sbuf_finish(struct sbuf *);
+int sbuf_finish(struct sbuf *);
char *sbuf_data(struct sbuf *);
int sbuf_len(struct sbuf *);
int sbuf_done(struct sbuf *);