summaryrefslogtreecommitdiffstats
path: root/sys/kern/subr_sbuf.c
diff options
context:
space:
mode:
authormdf <mdf@FreeBSD.org>2010-09-09 17:49:18 +0000
committermdf <mdf@FreeBSD.org>2010-09-09 17:49:18 +0000
commit73d2d3f18e68d936aadff54bb3b15b6dcc61e805 (patch)
tree6a93387cb5edf1079ae335b9e16743977276216d /sys/kern/subr_sbuf.c
parentc35a7592ebf347e53345d160cdb932a094861810 (diff)
downloadFreeBSD-src-73d2d3f18e68d936aadff54bb3b15b6dcc61e805.zip
FreeBSD-src-73d2d3f18e68d936aadff54bb3b15b6dcc61e805.tar.gz
Add drain functionality to sbufs. The drain is a function that is
called when the sbuf internal buffer is filled. For kernel sbufs with a drain, the internal buffer will never be expanded. For userland sbufs with a drain, the internal buffer may still be expanded by sbuf_[v]printf(3). Sbufs now have three basic uses: 1) static string manipulation. Overflow is marked. 2) dynamic string manipulation. Overflow triggers string growth. 3) drained string manipulation. Overflow triggers draining. In all cases the manipulation is 'safe' in that overflow is detected and managed. Reviewed by: phk (the previous version)
Diffstat (limited to 'sys/kern/subr_sbuf.c')
-rw-r--r--sys/kern/subr_sbuf.c105
1 files changed, 99 insertions, 6 deletions
diff --git a/sys/kern/subr_sbuf.c b/sys/kern/subr_sbuf.c
index bca8705..d7b57a5 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);
OpenPOWER on IntegriCloud