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
 

Reply via email to