Index: sys/kern/subr_sbuf.c =================================================================== --- sys/kern/subr_sbuf.c +++ sys/kern/subr_sbuf.c @@ -70,6 +70,7 @@ #define SBUF_ISDYNAMIC(s) ((s)->s_flags & SBUF_DYNAMIC) #define SBUF_ISDYNSTRUCT(s) ((s)->s_flags & SBUF_DYNSTRUCT) #define SBUF_ISFINISHED(s) ((s)->s_flags & SBUF_FINISHED) +#define SBUF_HASTRAILINGNL(s) ((s)->s_flags & SBUF_TRAILINGNL) #define SBUF_HASROOM(s) ((s)->s_len < (s)->s_size - 1) #define SBUF_FREESPACE(s) ((s)->s_size - ((s)->s_len + 1)) #define SBUF_CANEXTEND(s) ((s)->s_flags & SBUF_AUTOEXTEND) @@ -225,6 +226,7 @@ s->s_flags |= flags; s->s_size = length; s->s_buf = buf; + SBUF_SETFLAG(s, SBUF_TRAILINGNL); /* * Allocate DYNAMIC, i.e., heap data buffer backing the sbuf, if no @@ -384,6 +386,12 @@ KASSERT(s->s_len > 0, ("Shouldn't drain empty sbuf %p", s)); KASSERT(s->s_error == 0, ("Called %s with error on %p", __func__, s)); + + if (s->s_buf[s->s_len - 1] == '\n') + SBUF_SETFLAG(s, SBUF_TRAILINGNL); + else + SBUF_CLEARFLAG(s, SBUF_TRAILINGNL); + if (SBUF_DODRAINTOEOR(s) && s->s_rec_off == 0) return (s->s_error = EDEADLK); len = s->s_drain_func(s->s_drain_arg, s->s_buf, @@ -716,6 +724,34 @@ return (0); } +/* + * Append a trailing newline to a non-empty sbuf, if one is not already + * present. Handles sbufs with drain functions correctly. + */ +int +sbuf_nl_terminate(struct sbuf *s) +{ + + assert_sbuf_integrity(s); + assert_sbuf_state(s, 0); + + /* + * We track NL-termination status of fully drained buffers with the + * SBUF_TRAILINGNL flag in sbuf_drain(). + * + * If the buffer isn't empty, we can just refer to it. + */ + if (__predict_false(s->s_len == 0)) { + if (__predict_true(!SBUF_HASTRAILINGNL(s))) + sbuf_put_byte(s, '\n'); + } else if (__predict_true(s->s_buf[s->s_len - 1] != '\n')) + sbuf_put_byte(s, '\n'); + + if (s->s_error != 0) + return (-1); + return (0); +} + /* * Trim whitespace characters from end of an sbuf. */ Index: sys/sys/sbuf.h =================================================================== --- sys/sys/sbuf.h +++ sys/sys/sbuf.h @@ -58,6 +58,7 @@ #define SBUF_FINISHED 0x00020000 /* set by sbuf_finish() */ #define SBUF_DYNSTRUCT 0x00080000 /* sbuf must be freed */ #define SBUF_INSECTION 0x00100000 /* set by sbuf_start_section() */ +#define SBUF_TRAILINGNL 0x00200000 /* drained contents ended in \n */ int s_flags; /* flags */ ssize_t s_sect_len; /* current length of section */ ssize_t s_rec_off; /* current record start offset */ @@ -103,6 +104,7 @@ __printflike(2, 3); int sbuf_vprintf(struct sbuf *, const char *, __va_list) __printflike(2, 0); +int sbuf_nl_terminate(struct sbuf *); int sbuf_putc(struct sbuf *, int); void sbuf_set_drain(struct sbuf *, sbuf_drain_func *, void *); int sbuf_trim(struct sbuf *);