diff --git a/lib/libsbuf/Symbol.map b/lib/libsbuf/Symbol.map --- a/lib/libsbuf/Symbol.map +++ b/lib/libsbuf/Symbol.map @@ -42,3 +42,7 @@ FBSD_1.6 { sbuf_printf_drain; }; + +FBSD_1.8 { + sbuf_prepend; +}; diff --git a/lib/libsbuf/tests/sbuf_string_test.c b/lib/libsbuf/tests/sbuf_string_test.c --- a/lib/libsbuf/tests/sbuf_string_test.c +++ b/lib/libsbuf/tests/sbuf_string_test.c @@ -210,6 +210,54 @@ sbuf_delete(sb); } +ATF_TC_WITHOUT_HEAD(sbuf_prepend_test); +ATF_TC_BODY(sbuf_prepend_test, tc) +{ + struct sbuf *sb; + size_t test_sbuf_len; + const char subsystem_part[] = "subsystem=kernel"; + const char type_part[] = " type=none"; + const char data_part[] = " data=foo"; + const char full_string[] = "subsystem=kernel type=none data=foo"; + + sb = sbuf_new_auto(); + ATF_REQUIRE_MSG(sb != NULL, "sbuf_new_auto failed: %s", + strerror(errno)); + + ATF_CHECK_MSG(sbuf_cpy(sb, data_part) == 0, "sbuf_cat failed"); + + test_sbuf_len = sbuf_len(sb); + ATF_REQUIRE_MSG(test_sbuf_len == (ssize_t)strlen(data_part), + "sbuf_len(..) => %zd (actual) != %zu (expected)", test_sbuf_len, + sizeof(data_part) - 1); + + ATF_CHECK_MSG(sbuf_prepend(sb, type_part) == 0, "sbuf_prepend failed"); + + test_sbuf_len = sbuf_len(sb); + ATF_REQUIRE_MSG(test_sbuf_len == (ssize_t)(strlen(type_part) + + strlen(data_part)), + "sbuf_len(..) => %zd (actual) != %zu (expected)", test_sbuf_len, + sizeof(type_part) + sizeof(data_part) - 2); + + ATF_CHECK_MSG(sbuf_prepend(sb, subsystem_part) == 0, "sbuf_prepend failed"); + + test_sbuf_len = sbuf_len(sb); + ATF_REQUIRE_MSG(test_sbuf_len == (ssize_t)(strlen(subsystem_part) + + strlen(type_part) + strlen(data_part)), + "sbuf_len(..) => %zd (actual) != %zu (expected)", test_sbuf_len, + sizeof(subsystem_part) + sizeof(type_part) + sizeof(data_part) - 3); + + ATF_REQUIRE_MSG(sbuf_finish(sb) == 0, "sbuf_finish failed: %s", + strerror(errno)); + + ATF_REQUIRE_STREQ_MSG(sbuf_data(sb), full_string, + "sbuf (\"%s\") != test string (\"%s\")", sbuf_data(sb), + full_string); + + sbuf_delete(sb); +} + + ATF_TC_WITHOUT_HEAD(sbuf_putc_test); ATF_TC_BODY(sbuf_putc_test, tc) { @@ -282,6 +330,7 @@ ATF_TP_ADD_TC(tp, sbuf_bcpy_test); ATF_TP_ADD_TC(tp, sbuf_cat_test); ATF_TP_ADD_TC(tp, sbuf_cpy_test); + ATF_TP_ADD_TC(tp, sbuf_prepend_test); ATF_TP_ADD_TC(tp, sbuf_putc_test); ATF_TP_ADD_TC(tp, sbuf_trim_test); diff --git a/share/man/man9/Makefile b/share/man/man9/Makefile --- a/share/man/man9/Makefile +++ b/share/man/man9/Makefile @@ -1984,6 +1984,7 @@ sbuf.9 sbuf_new_auto.9 \ sbuf.9 sbuf_new_for_sysctl.9 \ sbuf.9 sbuf_nl_terminate.9 \ + sbuf.9 sbuf_prepend.9 \ sbuf.9 sbuf_printf.9 \ sbuf.9 sbuf_printf_drain.9 \ sbuf.9 sbuf_putbuf.9 \ diff --git a/share/man/man9/sbuf.9 b/share/man/man9/sbuf.9 --- a/share/man/man9/sbuf.9 +++ b/share/man/man9/sbuf.9 @@ -23,7 +23,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd October 3, 2023 +.Dd April 21, 2025 .Dt SBUF 9 .Os .Sh NAME @@ -43,6 +43,7 @@ .Nm sbuf_copyin , .Nm sbuf_cpy , .Nm sbuf_nl_terminate , +.Nm sbuf_prepend , .Nm sbuf_printf , .Nm sbuf_vprintf , .Nm sbuf_putc , @@ -131,6 +132,11 @@ .Ft int .Fn sbuf_nl_terminate "struct sbuf *" .Ft int +.Fo sbuf_prepend +.Fa "struct sbuf *s" +.Fa "const char *str" +.Fc +.Ft int .Fo sbuf_printf .Fa "struct sbuf *s" .Fa "const char *fmt" "..." @@ -405,6 +411,12 @@ to the .Fa sbuf at the current position. +The +.Fn sbuf_prepend +function prepends the NUL-terminated string +.Fa str +to the beginning of the +.Fa sbuf . .Pp The .Fn sbuf_set_drain diff --git a/sys/kern/subr_sbuf.c b/sys/kern/subr_sbuf.c --- a/sys/kern/subr_sbuf.c +++ b/sys/kern/subr_sbuf.c @@ -432,6 +432,59 @@ return (0); } +/* + * Prepend bytes 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. + */ +static void +sbuf_prepend_bytes(struct sbuf *s, const char *buf, size_t len) +{ + size_t n, pos; + + assert_sbuf_integrity(s); + assert_sbuf_state(s, 0); + + if (s->s_error != 0) + return; + + /* + * We can't prepend to sbufs with a drain function or sections at the + * moment. + */ + if (s->s_drain_func != NULL || SBUF_ISSECTION(s)) { + s->s_error = ENXIO; + return; + } + + /* + * We'll maintain the loop that sbuf_put_bytes() does since we can only + * allocate up to INT_MAX at a time. + */ + pos = 0; + while (len > 0) { + if (SBUF_FREESPACE(s) <= 0) { + if (sbuf_extend(s, len > INT_MAX ? INT_MAX : len) < 0) { + s->s_error = ENOMEM; + return; + } + } + n = SBUF_FREESPACE(s); + if (len < n) + n = len; + + /* Move anything currently in the way forward */ + memmove(&s->s_buf[n + pos], &s->s_buf[pos], s->s_len - pos); + + /* Append to anything previously prepended. */ + memcpy(&s->s_buf[pos], buf, n); + s->s_len += n; + pos += n; + len -= n; + buf += n; + } +} + /* * Append bytes to an sbuf. This is the core function for appending * to an sbuf and is the main place that deals with extending the @@ -571,6 +624,21 @@ return (0); } +/* + * Prepend a string to an sbuf + */ +int +sbuf_prepend(struct sbuf *s, const char *str) +{ + size_t n; + + n = strlen(str); + sbuf_prepend_bytes(s, str, n); + if (s->s_error != 0) + return (-1); + return (0); +} + #ifdef _KERNEL /* * Append a string from userland to an sbuf. diff --git a/sys/sys/sbuf.h b/sys/sys/sbuf.h --- a/sys/sys/sbuf.h +++ b/sys/sys/sbuf.h @@ -85,6 +85,7 @@ int sbuf_bcat(struct sbuf *, const void *, size_t); int sbuf_bcpy(struct sbuf *, const void *, size_t); int sbuf_cat(struct sbuf *, const char *); +int sbuf_prepend(struct sbuf *, const char *); int sbuf_cpy(struct sbuf *, const char *); int sbuf_printf(struct sbuf *, const char *, ...) __printflike(2, 3);