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 -and marks it as finished, which means that it may no longer be -modified using +is flushed. +If there is no attached drain, +.Fn sbuf_finish +NUL-terminates the +.Fa sbuf . +In either case it marks the +.Fa sbuf +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 +#include #include #include #include @@ -40,6 +41,7 @@ __FBSDID("$FreeBSD$"); #include #else /* _KERNEL */ #include +#include #include #include #include @@ -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; } @@ -271,6 +274,54 @@ sbuf_setpos(struct sbuf *s, int pos) return (0); } +/* + * 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 @@ -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 +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 *);