git: b3127a2dc25a - main - libutil++: New library containing C++ utility classes for use in base

From: John Baldwin <jhb_at_FreeBSD.org>
Date: Mon, 04 Aug 2025 19:46:35 UTC
The branch main has been updated by jhb:

URL: https://cgit.FreeBSD.org/src/commit/?id=b3127a2dc25ac63cae8e33e6f3dbd3580644fe52

commit b3127a2dc25ac63cae8e33e6f3dbd3580644fe52
Author:     John Baldwin <jhb@FreeBSD.org>
AuthorDate: 2025-08-04 19:38:06 +0000
Commit:     John Baldwin <jhb@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 <jhb@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 <jhb@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 <jhb@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 <jhb@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 <jhb@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 <jhb@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 <jhb@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