aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--share/man/man9/Makefile1
-rw-r--r--share/man/man9/sbuf.9106
-rw-r--r--sys/kern/subr_sbuf.c105
-rw-r--r--sys/sys/sbuf.h10
4 files changed, 207 insertions, 15 deletions
diff --git a/share/man/man9/Makefile b/share/man/man9/Makefile
index e6d8881efce0..6abafe19e09f 100644
--- a/share/man/man9/Makefile
+++ b/share/man/man9/Makefile
@@ -1031,6 +1031,7 @@ MLINKS+=sbuf.9 sbuf_bcat.9 \
sbuf.9 sbuf_overflowed.9 \
sbuf.9 sbuf_printf.9 \
sbuf.9 sbuf_putc.9 \
+ sbuf.9 sbuf_set_drain.9 \
sbuf.9 sbuf_setpos.9 \
sbuf.9 sbuf_trim.9 \
sbuf.9 sbuf_vprintf.9
diff --git a/share/man/man9/sbuf.9 b/share/man/man9/sbuf.9
index abe5831f56b8..7389064544fa 100644
--- a/share/man/man9/sbuf.9
+++ b/share/man/man9/sbuf.9
@@ -43,6 +43,7 @@
.Nm sbuf_printf ,
.Nm sbuf_vprintf ,
.Nm sbuf_putc ,
+.Nm sbuf_set_drain ,
.Nm sbuf_trim ,
.Nm sbuf_overflowed ,
.Nm sbuf_finish ,
@@ -54,6 +55,8 @@
.Sh SYNOPSIS
.In sys/types.h
.In sys/sbuf.h
+.Ft typedef\ int ( sbuf_drain_func ) ( void\ *arg, const\ char\ *data, int\ len ) ;
+.Pp
.Ft struct sbuf *
.Fn sbuf_new "struct sbuf *s" "char *buf" "int length" "int flags"
.Ft struct sbuf *
@@ -80,11 +83,13 @@
.Fn sbuf_vprintf "struct sbuf *s" "const char *fmt" "va_list ap"
.Ft int
.Fn sbuf_putc "struct sbuf *s" "int c"
+.Ft void
+.Fn sbuf_set_drain "struct sbuf *s" "sbuf_drain_func *func" "void *arg"
.Ft int
.Fn sbuf_trim "struct sbuf *s"
.Ft int
.Fn sbuf_overflowed "struct sbuf *s"
-.Ft void
+.Ft int
.Fn sbuf_finish "struct sbuf *s"
.Ft char *
.Fn sbuf_data "struct sbuf *s"
@@ -224,6 +229,51 @@ to the
at the current position.
.Pp
The
+.Fn sbuf_set_drain
+function sets a drain function
+.Fa func
+for the
+.Fa sbuf ,
+and records a pointer
+.Fa arg
+to be passed to the drain on callback.
+The drain function cannot be changed while
+.Fa sbuf_len
+is non-zero.
+.Pp
+The registered drain function
+.Vt sbuf_drain_func
+will be called with the argument
+.Fa arg
+provided to
+.Fn sbuf_set_drain ,
+a pointer
+.Fa data
+to a byte string that is the contents of the sbuf, and the length
+.Fa len
+of the data.
+If the drain function exists, it will be called when the sbuf internal
+buffer is full, or on behalf of
+.Fn sbuf_finish .
+The drain function may drain some or all of the data, but must drain
+at least 1 byte.
+The return value from the drain function, if positive, indicates how
+many bytes were drained.
+If negative, the return value indicates the negative error code which
+will be returned from this or a later call to
+.Fn sbuf_finish .
+The returned drained length cannot be zero.
+To do unbuffered draining, initialize the sbuf with a two-byte buffer.
+The drain will be called for every byte added to the sbuf.
+The
+.Fn sbuf_bcopyin ,
+.Fn sbuf_copyin ,
+.Fn sbuf_trim ,
+and
+.Fn sbuf_data
+functions cannot be used on an sbuf with a drain.
+.Pp
+The
.Fn sbuf_copyin
function copies a NUL-terminated string from the specified userland
address into the
@@ -289,10 +339,17 @@ overflowed.
.Pp
The
.Fn sbuf_finish
-function NUL-terminates the
+function will call the attached drain function if one exists until all
+the data in the
+.Fa sbuf
+is flushed.
+If there is no attached drain,
+.Fn sbuf_finish
+NUL-terminates the
+.Fa sbuf .
+In either case it marks the
.Fa sbuf
-and marks it as finished, which means that it may no longer be
-modified using
+as finished, which means that it may no longer be modified using
.Fn sbuf_setpos ,
.Fn sbuf_cat ,
.Fn sbuf_cpy ,
@@ -305,12 +362,21 @@ is used to reset the sbuf.
.Pp
The
.Fn sbuf_data
-and
-.Fn sbuf_len
-functions return the actual string and its length, respectively;
+function returns the actual string;
.Fn sbuf_data
only works on a finished
.Fa sbuf .
+The
+.Fn sbuf_len function returns the length of the string.
+For an
+.Fa sbuf
+with an attached drain,
+.Fn sbuf_len
+returns the length of the un-drained data.
+.Fn sbuf_done
+returns non-zero if the
+.Fa sbuf
+is finished.
.Fn sbuf_done
returns non-zero if the
.Fa sbuf
@@ -329,6 +395,22 @@ size of its storage buffer using
.Fn sbuf_setpos ,
or it is reinitialized to a sufficiently short string using
.Fn sbuf_cpy .
+.Pp
+Drains in user-space will not always function as indicated.
+While the drain function will be called immediately on overflow from
+the
+.Fa sbuf_putc ,
+.Fa sbuf_bcat ,
+.Fa sbuf_cat
+functions,
+.Fa sbuf_printf
+and
+.Fa sbuf_vprintf
+currently have no way to determine whether there will be an overflow
+until after it occurs, and cannot do a partial expansion of the format
+string.
+Thus when using libsbuf the buffer may be extended to allow completion
+of a single printf call, even though a drain is attached.
.Sh RETURN VALUES
The
.Fn sbuf_new
@@ -372,6 +454,14 @@ The
function
returns \-1 if copying string from userland failed, and number of bytes
copied otherwise.
+The
+.Fn sbuf_finish
+function returns ENOMEM if the sbuf overflowed before being finished,
+or returns the error code from the drain if one is attached.
+When used as
+.Xr sbuf_finish 3 ,
+.Fn sbuf_finish
+will return \-1 and set errno on error instead.
.Sh SEE ALSO
.Xr printf 3 ,
.Xr strcat 3 ,
@@ -396,6 +486,8 @@ Additional improvements were suggested by
.An Justin T. Gibbs Aq gibbs@FreeBSD.org .
Auto-extend support added by
.An Kelly Yancey Aq kbyanc@FreeBSD.org .
+Drain functionality added by
+.An Matthew Fleming Aq mdf@FreeBSD.org .
.Pp
This manual page was written by
.An Dag-Erling Sm\(/orgrav Aq des@FreeBSD.org .
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 *);