The branch main has been updated by jhb: URL: https://cgit.FreeBSD.org/src/commit/?id=b3127a2dc25ac63cae8e33e6f3dbd3580644fe52
commit b3127a2dc25ac63cae8e33e6f3dbd3580644fe52 Author: John Baldwin <j...@freebsd.org> AuthorDate: 2025-08-04 19:38:06 +0000 Commit: John Baldwin <j...@freebsd.org> CommitDate: 2025-08-04 19:38:06 +0000 libutil++: New library containing C++ utility classes for use in base - freebsd::FILE_up is a wrapper class for std::unique_ptr<> for FILE objects which uses a custom deleter that calls fclose(). - freebsd::malloc_up<T> is a wrapper class for std::unique_ptr<> which uses a custom deleter that calls free(). It is useful for pointers allocated by malloc() such as strdup(). - The freebsd::stringf() functions return a std::string constructed using a printf format string. The current implementation of freebsd::stringf() uses fwopen() where the write function appends to a std::string. Sponsored by: Chelsio Communications Pull Request: https://github.com/freebsd/freebsd-src/pull/1794 --- contrib/mandoc/lib.in | 1 + lib/Makefile | 1 + lib/libutil++/Makefile | 16 +++++++++++ lib/libutil++/freebsd::FILE_up.3 | 41 ++++++++++++++++++++++++++ lib/libutil++/freebsd::malloc_up.3 | 50 ++++++++++++++++++++++++++++++++ lib/libutil++/freebsd::stringf.3 | 48 +++++++++++++++++++++++++++++++ lib/libutil++/libutil++.hh | 55 +++++++++++++++++++++++++++++++++++ lib/libutil++/stringf.cc | 57 +++++++++++++++++++++++++++++++++++++ lib/libutil++/tests/Makefile | 9 ++++++ lib/libutil++/tests/stringf_test.cc | 52 +++++++++++++++++++++++++++++++++ lib/libutil++/tests/up_test.cc | 33 +++++++++++++++++++++ share/mk/src.libnames.mk | 4 +++ 12 files changed, 367 insertions(+) diff --git a/contrib/mandoc/lib.in b/contrib/mandoc/lib.in index 6b17aab5b27b..134614aa6478 100644 --- a/contrib/mandoc/lib.in +++ b/contrib/mandoc/lib.in @@ -131,6 +131,7 @@ LINE("libugidfw", "File System Firewall Interface Library (libugidfw, \\-lugidfw LINE("libulog", "User Login Record Library (libulog, \\-lulog)") LINE("libusbhid", "USB Human Interface Devices Library (libusbhid, \\-lusbhid)") LINE("libutil", "System Utilities Library (libutil, \\-lutil)") +LINE("libutil++", "C++ Utilities Library (libutil++, \\-lutil++)") LINE("libvgl", "Video Graphics Library (libvgl, \\-lvgl)") LINE("libx86_64", "x86_64 Architecture Library (libx86_64, \\-lx86_64)") LINE("libxo", "Text, XML, JSON, and HTML Output Emission Library (libxo, \\-lxo)") diff --git a/lib/Makefile b/lib/Makefile index e0aafcad60d4..e5139b312a75 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -106,6 +106,7 @@ SUBDIR= ${SUBDIR_BOOTSTRAP} \ libugidfw \ libulog \ libutil \ + libutil++ \ ${_libvgl} \ libwrap \ libxo \ diff --git a/lib/libutil++/Makefile b/lib/libutil++/Makefile new file mode 100644 index 000000000000..ec83a9ad35f9 --- /dev/null +++ b/lib/libutil++/Makefile @@ -0,0 +1,16 @@ +PACKAGE= lib${LIB} +LIB_CXX= util++ +INTERNALLIB= true +SHLIB_MAJOR= 1 +SRCS= stringf.cc + +MAN+= freebsd::FILE_up.3 \ + freebsd::malloc_up.3 \ + freebsd::stringf.3 + +.include <src.opts.mk> + +HAS_TESTS= +SUBDIR.${MK_TESTS}+= tests + +.include <bsd.lib.mk> diff --git a/lib/libutil++/freebsd::FILE_up.3 b/lib/libutil++/freebsd::FILE_up.3 new file mode 100644 index 000000000000..ea63b1233b43 --- /dev/null +++ b/lib/libutil++/freebsd::FILE_up.3 @@ -0,0 +1,41 @@ +.\" +.\" SPDX-License-Identifier: BSD-2-Clause +.\" +.\" Copyright (c) 2025 Chelsio Communications, Inc. +.\" Written by: John Baldwin <j...@freebsd.org> +.\" +.Dd July 31, 2025 +.Dt FREEBSD::FILE_UP 3 +.Os +.Sh NAME +.Nm freebsd::FILE_up +.Nd std::unique_ptr specialization for stdio FILE objects +.Sh LIBRARY +.Lb libutil++ +.Sh SYNOPSIS +.In libutil++.hh +.Ft using FILE_up = std::unique_ptr<FILE, fclose_deleter>; +.Sh DESCRIPTION +This class is a specialization of +.Vt std::unique_ptr +for stdio +.Vt FILE +objects. +When a +.Vt FILE +object managed by an instance of this class is disposed, +.Xr fclose 3 +is invoked to dispose of the +.Vt FILE +object. +.Sh EXAMPLES +.Bd -literal -offset indent +freebsd::FILE_up fp(fopen("foo.txt", "w")); +if (!fp) + err(1, "fopen"); +fprintf(fp.get(), "hello\n"); +// `fp' is implicitly closed on destruction +.Ed +.Sh SEE ALSO +.Xr fclose 3 , +.Xr fopen 3 diff --git a/lib/libutil++/freebsd::malloc_up.3 b/lib/libutil++/freebsd::malloc_up.3 new file mode 100644 index 000000000000..b18e7854213a --- /dev/null +++ b/lib/libutil++/freebsd::malloc_up.3 @@ -0,0 +1,50 @@ +.\" +.\" SPDX-License-Identifier: BSD-2-Clause +.\" +.\" Copyright (c) 2025 Chelsio Communications, Inc. +.\" Written by: John Baldwin <j...@freebsd.org> +.\" +.Dd July 31, 2025 +.Dt FREEBSD::MALLOC_UP 3 +.Os +.Sh NAME +.Nm freebsd::malloc_up +.Nd std::unique_ptr specialization for objects allocated via malloc +.Sh LIBRARY +.Lb libutil++ +.Sh SYNOPSIS +.In libutil++.hh +.Ft using malloc_up = std::unique_ptr<T, free_deleter<T>>; +.Sh DESCRIPTION +This class is a specialization of +.Vt std::unique_ptr +which invokes +.Xr free 3 +instead of +.Fn delete +when an object is disposed. +While explicit calls to +.Xr malloc 3 +should be avoided in C++ code, +this class can be useful to manage an object allocated by an existing API +which uses +.Xr malloc 3 +internally such as +.Xr scandir 3 . +Note that the type of the underlying object must be used as the first +template argument similar to std::unique_ptr. +.Sh EXAMPLES +This example uses +.Xr strdup 3 +for simplicity, +but new C++ code should generally not use +.Xr strdup 3 : +.Bd -literal -offset indent +freebsd::malloc_up<char> my_string(strdup("foo")); +// `mystring' is implicitly freed on destruction +.Ed +.Sh SEE ALSO +.Xr free 3 , +.Xr malloc 3 , +.Xr scandir 3 , +.Xr strdup 3 diff --git a/lib/libutil++/freebsd::stringf.3 b/lib/libutil++/freebsd::stringf.3 new file mode 100644 index 000000000000..341fedef4343 --- /dev/null +++ b/lib/libutil++/freebsd::stringf.3 @@ -0,0 +1,48 @@ +.\" +.\" SPDX-License-Identifier: BSD-2-Clause +.\" +.\" Copyright (c) 2025 Chelsio Communications, Inc. +.\" Written by: John Baldwin <j...@freebsd.org> +.\" +.Dd July 31, 2025 +.Dt FREEBSD::STRINGF 3 +.Os +.Sh NAME +.Nm freebsd::stringf +.Nd build a std::string using stdio formatting +.Sh LIBRARY +.Lb libutil++ +.Sh SYNOPSIS +.In libutil++.hh +.Ft std::string +.Fn freebsd::stringf "const char *fmt" "..." +.Ft std::string +.Fn freebsd::stringf "const char *fmt" "va_list ap" +.Sh DESCRIPTION +These functions construct a +.Vt std::string +object containing a formatted string. +The output of the string is dictated by the +.Fa fmt +argument and additional arguments using the same conventions documented in +.Xr printf 3 . +The first form provides functionality similar to +.Xr asprintf 3 +and the second form is similar to +.Xr vasprintf 3 . +.Sh RETURN VALUES +These functions return a std::string object. +.Sh EXCEPTIONS +These functions may throw one of the following exceptions: +.Bl -tag -width Er +.It Bq Er std::bad_alloc +Failed to allocate memory. +.It Bq Er std::length_error +The result would exceeed the maximum possible string size. +.El +.Sh EXAMPLES +.Bd -literal -offset indent +std::string s = freebsd::stringf("hello %s", "world"); +.Ed +.Sh SEE ALSO +.Xr asprintf 3 diff --git a/lib/libutil++/libutil++.hh b/lib/libutil++/libutil++.hh new file mode 100644 index 000000000000..93cc2d9e6650 --- /dev/null +++ b/lib/libutil++/libutil++.hh @@ -0,0 +1,55 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2025 Chelsio Communications, Inc. + * Written by: John Baldwin <j...@freebsd.org> + */ + +#ifndef __LIBUTILPP_HH__ +#define __LIBUTILPP_HH__ + +#include <cstdarg> +#include <cstdio> +#include <cstdlib> +#include <memory> + +namespace freebsd { + /* + * FILE_up is a std::unique_ptr<> for FILE objects which uses + * fclose() to destroy the wrapped pointer. + */ + struct fclose_deleter { + void operator() (std::FILE *fp) const + { + std::fclose(fp); + } + }; + + using FILE_up = std::unique_ptr<std::FILE, fclose_deleter>; + + /* + * malloc_up<T> is a std::unique_ptr<> which uses free() to + * destroy the wrapped pointer. This can be used to wrap + * pointers allocated implicitly by malloc() such as those + * returned by strdup(). + */ + template <class T> + struct free_deleter { + void operator() (T *p) const + { + std::free(p); + } + }; + + template <class T> + using malloc_up = std::unique_ptr<T, free_deleter<T>>; + + /* + * Returns a std::string containing the same output as + * sprintf(). Throws std::bad_alloc if an error occurs. + */ + std::string stringf(const char *fmt, ...) __printflike(1, 2); + std::string stringf(const char *fmt, std::va_list ap); +} + +#endif /* !__LIBUTILPP_HH__ */ diff --git a/lib/libutil++/stringf.cc b/lib/libutil++/stringf.cc new file mode 100644 index 000000000000..8c24167d70ac --- /dev/null +++ b/lib/libutil++/stringf.cc @@ -0,0 +1,57 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2025 Chelsio Communications, Inc. + * Written by: John Baldwin <j...@freebsd.org> + */ + +#include <cstdarg> +#include <cstdio> +#include <string> + +#include "libutil++.hh" + +static int +stringf_write(void *cookie, const char *buf, int len) +{ + std::string *str = reinterpret_cast<std::string *>(cookie); + try { + str->append(buf, len); + } catch (std::bad_alloc) { + errno = ENOMEM; + return (-1); + } catch (std::length_error) { + errno = EFBIG; + return (-1); + } + return (len); +} + +std::string +freebsd::stringf(const char *fmt, va_list ap) +{ + std::string str; + freebsd::FILE_up fp(fwopen(reinterpret_cast<void *>(&str), + stringf_write)); + + vfprintf(fp.get(), fmt, ap); + + if (ferror(fp.get())) + throw std::bad_alloc(); + fp.reset(nullptr); + + return str; +} + +std::string +freebsd::stringf(const char *fmt, ...) +{ + std::va_list ap; + std::string str; + + va_start(ap, fmt); + str = freebsd::stringf(fmt, ap); + va_end(ap); + + return str; +} diff --git a/lib/libutil++/tests/Makefile b/lib/libutil++/tests/Makefile new file mode 100644 index 000000000000..81b7be4f5660 --- /dev/null +++ b/lib/libutil++/tests/Makefile @@ -0,0 +1,9 @@ +PACKAGE= tests + +ATF_TESTS_CXX+= stringf_test +ATF_TESTS_CXX+= up_test + +CFLAGS+= -I${SRCTOP}/lib/libutil++ +LIBADD+= util++ + +.include <bsd.test.mk> diff --git a/lib/libutil++/tests/stringf_test.cc b/lib/libutil++/tests/stringf_test.cc new file mode 100644 index 000000000000..5b8ef4ad54a9 --- /dev/null +++ b/lib/libutil++/tests/stringf_test.cc @@ -0,0 +1,52 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2025 Chelsio Communications, Inc. + * Written by: John Baldwin <j...@freebsd.org> + */ + +#include <atf-c++.hpp> +#include <stdarg.h> +#include <stdio.h> + +#include <libutil++.hh> + +ATF_TEST_CASE_WITHOUT_HEAD(basic); +ATF_TEST_CASE_BODY(basic) +{ + ATF_REQUIRE_EQ("foo", freebsd::stringf("foo")); + ATF_REQUIRE_EQ("bar", freebsd::stringf("%s", "bar")); + ATF_REQUIRE_EQ("42", freebsd::stringf("%u", 42)); + ATF_REQUIRE_EQ("0xdeadbeef", freebsd::stringf("%#x", 0xdeadbeef)); + ATF_REQUIRE_EQ("", freebsd::stringf("")); + ATF_REQUIRE_EQ("this is a test", freebsd::stringf("this %s test", + "is a")); +} + +static std::string +stringv(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + std::string str = freebsd::stringf(fmt, ap); + va_end(ap); + return (str); +} + +ATF_TEST_CASE_WITHOUT_HEAD(va_list); +ATF_TEST_CASE_BODY(va_list) +{ + ATF_REQUIRE_EQ("foo", stringv("foo")); + ATF_REQUIRE_EQ("bar", stringv("%s", "bar")); + ATF_REQUIRE_EQ("42", stringv("%u", 42)); + ATF_REQUIRE_EQ("0xdeadbeef", stringv("%#x", 0xdeadbeef)); + ATF_REQUIRE_EQ("", stringv("")); + ATF_REQUIRE_EQ("this is a test", stringv("this %s test", "is a")); +} + +ATF_INIT_TEST_CASES(tcs) +{ + ATF_ADD_TEST_CASE(tcs, basic); + ATF_ADD_TEST_CASE(tcs, va_list); +} diff --git a/lib/libutil++/tests/up_test.cc b/lib/libutil++/tests/up_test.cc new file mode 100644 index 000000000000..3f344054c334 --- /dev/null +++ b/lib/libutil++/tests/up_test.cc @@ -0,0 +1,33 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2025 Chelsio Communications, Inc. + * Written by: John Baldwin <j...@freebsd.org> + */ + +#include <atf-c++.hpp> +#include <libutil.h> + +#include <libutil++.hh> + +ATF_TEST_CASE_WITHOUT_HEAD(FILE_up); +ATF_TEST_CASE_BODY(FILE_up) +{ + FILE *fp = fopen("/dev/null", "r"); + ATF_REQUIRE(fp != NULL); + ATF_REQUIRE(fileno(fp) != -1); + + freebsd::FILE_up f(fp); + ATF_REQUIRE_EQ(fileno(fp), fileno(f.get())); + + f.reset(); + ATF_REQUIRE_EQ(f.get(), nullptr); + + ATF_REQUIRE_EQ(-1, fileno(fp)); + ATF_REQUIRE_EQ(EBADF, errno); +} + +ATF_INIT_TEST_CASES(tcs) +{ + ATF_ADD_TEST_CASE(tcs, FILE_up); +} diff --git a/share/mk/src.libnames.mk b/share/mk/src.libnames.mk index 0fe352dffaf4..283a99496b9f 100644 --- a/share/mk/src.libnames.mk +++ b/share/mk/src.libnames.mk @@ -78,6 +78,7 @@ _INTERNALLIBS= \ smdb \ smutil \ telnet \ + util++ \ vers \ wpaap \ wpacommon \ @@ -694,6 +695,9 @@ LIBPKGECC?= ${LIBPKGECCDIR}/libpkgecc${PIE_SUFFIX}.a LIBPMCSTATDIR= ${_LIB_OBJTOP}/lib/libpmcstat LIBPMCSTAT?= ${LIBPMCSTATDIR}/libpmcstat${PIE_SUFFIX}.a +LIBUTIL++DIR= ${_LIB_OBJTOP}/lib/libutil++ +LIBUTIL++?= ${LIBUTIL++DIR}/libutil++${PIE_SUFFIX}.a + LIBWPAAPDIR= ${_LIB_OBJTOP}/usr.sbin/wpa/src/ap LIBWPAAP?= ${LIBWPAAPDIR}/libwpaap${PIE_SUFFIX}.a