diff options
| author | Lexi Winter <lexi@hemlock.eden.le-fay.org> | 2025-03-24 11:36:55 +0000 |
|---|---|---|
| committer | Lexi Winter <lexi@hemlock.eden.le-fay.org> | 2025-03-24 20:31:21 +0000 |
| commit | 0909c8089b470f26fb50a5788b921854129b2671 (patch) | |
| tree | 247aa79ac1236f8c52185ed9df1cf644a6b853d6 | |
| download | libnvxx-0909c8089b470f26fb50a5788b921854129b2671.tar.gz libnvxx-0909c8089b470f26fb50a5788b921854129b2671.tar.bz2 | |
libnvxx: a C++ wrapper around libnv
| -rw-r--r-- | .gitignore | 12 | ||||
| -rw-r--r-- | LICENSE | 19 | ||||
| -rw-r--r-- | Makefile | 24 | ||||
| -rw-r--r-- | README | 80 | ||||
| -rw-r--r-- | libnvxx/Makefile | 37 | ||||
| -rw-r--r-- | libnvxx/nvxx.3 | 205 | ||||
| -rw-r--r-- | libnvxx/nvxx.cc | 944 | ||||
| -rw-r--r-- | libnvxx/nvxx.h | 32 | ||||
| -rw-r--r-- | libnvxx/nvxx_base.h | 550 | ||||
| -rw-r--r-- | libnvxx/tests/Makefile | 30 | ||||
| -rw-r--r-- | libnvxx/tests/nvxx_basic.cc | 571 |
11 files changed, 2504 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3d00a1e --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +*.swp +*.o +*.a +*.so +*.so.* +*.pico +*.core +*.full +*.debug +.depend* +Kyuafile +/libnvxx/tests/nvxx_basic @@ -0,0 +1,19 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or distribute +this software, either in source code form or as a compiled binary, for any +purpose, commercial or non-commercial, and by any means. + +In jurisdictions that recognize copyright laws, the author or authors of +this software dedicate any and all copyright interest in the software to the +public domain. We make this dedication for the benefit of the public at +large and to the detriment of our heirs and successors. We intend this +dedication to be an overt act of relinquishment in perpetuity of all present +and future rights to this software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..cbc6d1e --- /dev/null +++ b/Makefile @@ -0,0 +1,24 @@ +# This is free and unencumbered software released into the public domain. +# +# Anyone is free to copy, modify, publish, use, compile, sell, or distribute +# this software, either in source code form or as a compiled binary, for any +# purpose, commercial or non-commercial, and by any means. +# +# In jurisdictions that recognize copyright laws, the author or authors of +# this software dedicate any and all copyright interest in the software to the +# public domain. We make this dedication for the benefit of the public at +# large and to the detriment of our heirs and successors. We intend this +# dedication to be an overt act of relinquishment in perpetuity of all present +# and future rights to this software under copyright law. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +PREFIX?= /usr/local +SUBDIR= libnvxx + +.include <bsd.subdir.mk> @@ -0,0 +1,80 @@ +libnvxx is a lightweight C++ wrapper around FreeBSD's libnv. it allows you to +interoperate with C code that uses libnv, which is becoming fairly common on +FreeBSD. + +priority is given to a clean and idiomatic C++ API for the library. this means +in some cases the API is less efficient than it could be; for example, it +sometimes requires data copies which are, strictly speaking, not necessary. +this is not considered a problem in practice, since the overhead is still very +low, and using libnv in a tight loop would be fairly unusual. + +to build and install the library: + +% make +% su +# make install +# kyua test --kyuafile /usr/local/tests/nvxx/Kyuafile + +to use the library: + +#include <nvxx.h> and link with -lnvxx. if you link statically, you also need +to link with -lnv. + +the basic API is very similar to the C libnv API: + + auto nvl = bsd::nv_list(); + nvl.add_number("the answer", 42); + assert(nvl.exists_number("the answer")); + std::print("{0}", nvl.get_number("the answer")); + +infrequently asked questions: + +Q: what version of FreeBSD does libnvxx require? +A: libnvxx is developed and tested on FreeBSD 15.0-CURRENT. it will probably + work on earlier versions, as long as the C++ compiler is sufficiently + capable. + +Q: what version of C++ does libnvxx require? +A: libnvxx requires C++23 (or later). + +Q: but isn't FreeBSD's implementation of C++23 rather incomplete? +A: yes. however, libnvxx only uses the parts which are implemented in the + version of LLVM that FreeBSD ships in base. + +Q: doesn't the public API only require C++20? +A: this may be the case, i haven't verified it. if so, patches to support + C++20 for the public API would probably be accepted. + +Q: why is the type called bsd::nv_list instead of bsd::nvlist? +A: because <sys/nv_namespace.h> does "#define nvlist FreeBSD_nvlist", which + would cause issues with symbol names in the ABI. + +Q: i found a bug and i have a patch that fixes it. +A: that's not a question. + +Q: i found a bug and i have a patch that fixes it? +A: please open a pull request on the GitHub repository. + +Q: what if i found a bug but i don't have a patch? +A: in that case please open an issue on the GitHub repository, preferably with + a minimal test case. + +Q: why wrap libnv instead of creating a C++ version from scratch? +A: the primary use-case of libnv in C++ is to interoperate with existing C APIs + and protocols which use libnv. this requires using libnv in order to, for + example, pass nvlists between C and C++ code. + + unlike in C, there is little reason to use libnv in C++ native code for data + storage, since we already have a rich template library for that. + +Q: why does the library abort on invalid operations instead of throwing an + exception? +A: because this is how libnv works and there's no way to override it. + +Q: do you intend to submit this for inclusion in the FreeBSD base system? +A: eventually, yes, but not until there are some actual users of it to justify + importing it. + +Q: is this why you're using <bsd.lib.mk> instead of something more sensible + like CMake? +A: precisely! diff --git a/libnvxx/Makefile b/libnvxx/Makefile new file mode 100644 index 0000000..d201992 --- /dev/null +++ b/libnvxx/Makefile @@ -0,0 +1,37 @@ +# This is free and unencumbered software released into the public domain. +# +# Anyone is free to copy, modify, publish, use, compile, sell, or distribute +# this software, either in source code form or as a compiled binary, for any +# purpose, commercial or non-commercial, and by any means. +# +# In jurisdictions that recognize copyright laws, the author or authors of +# this software dedicate any and all copyright interest in the software to the +# public domain. We make this dedication for the benefit of the public at +# large and to the detriment of our heirs and successors. We intend this +# dedication to be an overt act of relinquishment in perpetuity of all present +# and future rights to this software under copyright law. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +.include <bsd.opts.mk> + +PREFIX?= /usr/local +LIB_CXX= nvxx +LIBDIR= ${PREFIX}/lib +INCLUDEDIR= ${PREFIX}/include +SHLIB_MAJOR= 1 +INCS= nvxx.h nvxx_base.h +SRCS= nvxx.cc +CXXSTD= c++23 +CXXFLAGS+= -W -Wall -Wextra -Werror +LDADD= -lnv + +HAS_TESTS= +SUBDIR.${MK_TESTS}+= tests + +.include <bsd.lib.mk> diff --git a/libnvxx/nvxx.3 b/libnvxx/nvxx.3 new file mode 100644 index 0000000..555b8eb --- /dev/null +++ b/libnvxx/nvxx.3 @@ -0,0 +1,205 @@ +.\" This is free and unencumbered software released into the public domain. +.\" +.\" Anyone is free to copy, modify, publish, use, compile, sell, or distribute +.\" this software, either in source code form or as a compiled binary, for any +.\" purpose, commercial or non-commercial, and by any means. +.\" +.\" In jurisdictions that recognize copyright laws, the author or authors of +.\" this software dedicate any and all copyright interest in the software to the +.\" public domain. We make this dedication for the benefit of the public at +.\" large and to the detriment of our heirs and successors. We intend this +.\" dedication to be an overt act of relinquishment in perpetuity of all present +.\" and future rights to this software under copyright law. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +.\" IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +.\" FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +.\" AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +.\" ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +.\" WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +.Dd March 24, 2025 +.Dt NVXX 3 +.Os +.Sh NAME +.Nm nvxx +.Nd C++ interface for +.Xr nv 9 +.Sh LIBRARY +.Lb libnvxx +.Sh SYNOPSIS +.In nvxx.h +.\" unfortunately, mdoc is not really set up for documenting C++ classes. +.Bd -literal +namespace bsd { + +// exposition only +template<typename T> +using container-type = ...; + +// exposition only +struct const_nv_list : <unspecified> { + const_nv_list(); + explicit const_nv_list(::nvlist_t const *); + const_nv_list(const_nv_list const &); + + const_nv_list &operator=(const_nv_list const &); + const_nv_list &operator=(nv_list const &); + + ::nvlist_t const *ptr() const; + + void dump(int fd) const; + void fdump(std::FILE *fp) const; + + std::size_t packed_size() const; + std::vector<std::byte> pack() const; + + std::error_code error() const; + + explicit operator bool() const; + + bool empty() const; + int flags() const; + bool in_array() const; + + void send(int fd) const; + + bool exists(std::string_view key) const; + bool exists_null(std::string_view key) const; + bool exists_bool(std::string_view key) const; + bool exists_number(std::string_view key) const; + bool exists_string(std::string_view key) const; + bool exists_binary(std::string_view key) const; + bool exists_nvlist(std::string_view key) const; + bool exists_descriptor(std::string_view key) const; + + auto get_bool(std::string_view key) const -> bool; + auto get_number(std::string_view key) const -> std::uint64_t; + auto get_string(std::string_view key) const -> std::string_view; + auto get_descriptor(std::string_view key) const -> int; + auto get_nvlist(std::string_view key) const -> const_nv_list; + + // exposition only + auto get_binary(std::string_view key) const -> container-type<std::byte const>; + + bool exists_bool_array(std::string_view key) const; + bool exists_number_array(std::string_view key) const; + bool exists_string_array(std::string_view key) const; + bool exists_descriptor_array(std::string_view key) const; + bool exists_nvlist_array(std::string_view key) const; + + // exposition only + auto get_bool_array(std::string_view key) const -> container-type<bool const>; + auto get_number_array(std::string_view key) const -> container-type<std::uint64_t const>; + auto get_string_array(std::string_view key) const -> container-type<std::string_view>; + auto get_descriptor_array(std::string_view key) const -> container-type<int const>; + auto get_nvlist_array(std::string_view key) const -> container-type<const_nv_list>; +}; + +// exposition only +struct nv_list : <unspecified> { + explicit nv_list(int flags = 0); + explicit nv_list(::nvlist_t *); + nv_list(nv_list const &); + nv_list(nv_list &&); + + nv_list &operator=(nv_list const &); + nv_list &operator=(nv_list &&); + + ::nvlist_t *ptr(); + ::nvlist_t const *ptr() const; + + ::nvlist_t *release() &&; + + void set_error(int error); + + void free(std::string_view name); + + operator const_nv_list() const; + + void add_null(std::string_view key); + void add_bool(std::string_view key, bool value); + void add_number(std::string_view key, std::uint64_t); + void add_string(std::string_view key, std::string_view); + void add_descriptor(std::string_view key, int); + void add_nvlist(std::string_view key, const_nv_list const &); + void add_binary(std::string_view key, std::span<std::byte const>); + + void move_string(std::string_view key, char *); + void move_descriptor(std::string_view key, int); + void move_binary(std::string_view key, std::span<std::byte>); + void move_nvlist(std::string_view key, nv_list &&); + void move_nvlist(std::string_view key, ::nvlist_t *); + + void free_null(std::string_view key); + void free_bool(std::string_view key); + void free_number(std::string_view key); + void free_string(std::string_view key); + void free_descriptor(std::string_view key); + void free_binary(std::string_view key); + void free_nvlist(std::string_view key); + + auto take_bool(std::string_view key) -> bool; + auto take_number(std::string_view key) -> std::uint64_t; + auto take_string(std::string_view key) -> std::string; + auto take_descriptor(std::string_view key) -> int; + auto take_nvlist(std::string_view key) -> nv_list; + + void move_bool_array(std::string_view key, std::span<bool>); + void move_number_array(std::string_view key, std::span<std::uint64_t>); + void move_string_array(std::string_view key, std::span<char *>); + void move_descriptor_array(std::string_view key, std::span<int>); + void move_nvlist_array(std::string_view key, std::span<::nvlist_t *>); + + void append_bool_array(std::string_view key, bool); + void append_number_array(std::string_view key, std::uint64_t); + void append_string_array(std::string_view key, std::string_view); + void append_descriptor_array(std::string_view key, int); + void append_nvlist_array(std::string_view key, const_nv_list const &); + + void free_bool_array(std::string_view key); + void free_number_array(std::string_view key); + void free_string_array(std::string_view key); + void free_nvlist_array(std::string_view key); + void free_descriptor_array(std::string_view key); + + void add_bool_array(std::string_view key, std::span<bool const>); + void add_number_array(std::string_view key, std::span<std::uint64_t const>); + void add_string_array(std::string_view key, std::span<std::string_view const>); + void add_descriptor_array(std::string_view key, std::span<int const>); + void add_nvlist_array(std::string_view key, std::span<const_nv_list const>); + void add_nvlist_array(std::string_view key, std::span<nv_list const>); + + // exposition only + + template<std::ranges::range Range> + void add_bool_range(std::string_view key, Range &&); + + template<std::ranges::range Range> + void add_number_range(std::string_view key, Range &&); + + template<std::ranges::range Range> + void add_binary_range(std::string_view key, Range &&); + + template<std::ranges::range Range> + void add_string_range(std::string_view key, _Range &&); + + // exposition only + auto take_bool_array(std::string_view key) -> container-type<bool>; + auto take_number_array(std::string_view key) -> container-type<std::uint64_t>; + auto take_string_array(std::string_view key) -> container-type<std::string>; + auto take_nvlist_array(std::string_view key) -> container-type<nv_list>; + auto take_descriptor_array(std::string_view __key) -> container-type<int>; + auto take_binary(std::string_view key) -> container-type<std::byte>; +}; + +} // namespace bsd +.Ed +.Sh DESCRIPTION +The +.Nm +library provides a C++ wrapper around the +.Xr nv 9 +C library. +The library is ABI compatible with the C library, in the case that it can both +consume and produce pointers of type +.Vt nvlist_t . diff --git a/libnvxx/nvxx.cc b/libnvxx/nvxx.cc new file mode 100644 index 0000000..1cd5f84 --- /dev/null +++ b/libnvxx/nvxx.cc @@ -0,0 +1,944 @@ +/* + * This is free and unencumbered software released into the public domain. + * + * Anyone is free to copy, modify, publish, use, compile, sell, or distribute + * this software, either in source code form or as a compiled binary, for any + * purpose, commercial or non-commercial, and by any means. + * + * In jurisdictions that recognize copyright laws, the author or authors of + * this software dedicate any and all copyright interest in the software to the + * public domain. We make this dedication for the benefit of the public at + * large and to the detriment of our heirs and successors. We intend this + * dedication to be an overt act of relinquishment in perpetuity of all present + * and future rights to this software under copyright law. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include <cerrno> +#include <cassert> + +#include "nvxx.h" + +namespace { + +/* + * Some useful helper types. + */ + +template<typename T> +struct ptr_guard { + ptr_guard(T *ptr_) : ptr(ptr_) {} + + ~ptr_guard() { + std::free(ptr); + } + + T *ptr; +}; + +template<typename T> +auto construct = std::views::transform([] (auto &&value) { + return T(std::forward<decltype(value)>(value)); +}); + +} + +namespace bsd { + +/* + * const_nv_list + */ + +const_nv_list::const_nv_list() noexcept + : __nv_list_base(nullptr, nvlist_owning::non_owning) +{ +} + +// const_cast is safe here since a non-owning nvlist is never modified. +const_nv_list::const_nv_list(::nvlist_t const *nvl) noexcept + : __nv_list_base(const_cast<::nvlist_t *>(nvl), + nvlist_owning::non_owning) +{ +} + +const_nv_list::const_nv_list(const_nv_list const &other) noexcept + : __nv_list_base(other.__m_nv, nvlist_owning::non_owning) +{ +} + +const_nv_list & +const_nv_list::operator=(const_nv_list const &other) noexcept +{ + if (this != &other) { + /* This is not a leak since we never own the nvlist_t. */ + __m_nv = other.__m_nv; + } + + return *this; +} + +const_nv_list & +const_nv_list::operator=(nv_list const &other) noexcept +{ + /* This is not a leak since we never own the nvlist_t. */ + __m_nv = other.__m_nv; + return *this; +} + +::nvlist_t const * +const_nv_list::ptr() const +{ + return __m_nv; +} + +/* + * nv_list + */ + +nv_list::nv_list(int flags) + : __nv_list_base(flags) +{ + if (__m_nv == nullptr) + throw std::system_error( + std::error_code(errno, std::system_category())); +} + +nv_list::nv_list(::nvlist_t *nvl) noexcept + : __nv_list_base(nvl, nvlist_owning::owning) +{ +} + +nv_list::nv_list(nv_list const &other) + : __nv_list_base(::nvlist_clone(other.__m_nv), + nvlist_owning::owning) +{ + if (__m_nv == nullptr) + throw std::system_error( + std::error_code(errno, std::system_category())); +} + +nv_list::nv_list(nv_list &&other) noexcept + : __nv_list_base(std::exchange(other.__m_nv, nullptr), + nvlist_owning::owning) +{ +} + +nv_list & +nv_list::operator=(nv_list const &other) +{ + if (this != &other) { + auto *clone = nvlist_clone(other.__m_nv); + if (clone == nullptr) + throw std::system_error( + std::error_code(errno, + std::system_category())); + __free_nv(); + __m_nv = clone; + __m_owning = nvlist_owning::owning; + } + + return *this; +} + +nv_list & +nv_list::operator=(nv_list &&other) noexcept +{ + if (this != &other) { + __m_nv = std::exchange(other.__m_nv, nullptr); + __m_owning = nvlist_owning::owning; + } + + return *this; +} + +::nvlist_t * +nv_list::ptr() +{ + return __m_nv; +} + +::nvlist_t const * +nv_list::ptr() const +{ + return __m_nv; +} + +::nvlist_t * +nv_list::release() && +{ + return std::exchange(__m_nv, nullptr); +} + +nv_list +nv_list::unpack(std::span<std::byte> data, int flags) +{ + if (auto nv = ::nvlist_unpack(std::ranges::data(data), + std::ranges::size(data), + flags); + nv != nullptr) { + return nv_list(nv); + } + + throw std::system_error( + std::error_code(errno, std::system_category())); +} + +nv_list +nv_list::recv(int fd, int flags) +{ + if (auto *nv = ::nvlist_recv(fd, flags); nv != nullptr) + return nv_list(nv); + + throw std::system_error( + std::error_code(errno, std::system_category())); +} + +nv_list +nv_list::xfer(int fd, nv_list &&nvl, int flags) +{ + if (auto *nv = ::nvlist_xfer(fd, nvl.__m_nv, flags); + nv != nullptr) { + // nvlist_xfer destroys the original list + nvl.__m_nv = nullptr; + return nv_list(nv); + } + + throw std::system_error( + std::error_code(errno, std::system_category())); +} + +} // namespace bsd + +namespace bsd::__detail { + +/* + * __nv_list_base + */ + +__nv_list_base::__nv_list_base(int flags) + : __m_nv(::nvlist_create(flags)) + , __m_owning(nvlist_owning::owning) +{ + if (__m_nv == nullptr) + throw std::system_error( + std::error_code(errno, std::system_category())); +} + +__nv_list_base::__nv_list_base(nvlist_t *nv, nvlist_owning owning) + : __m_nv(nv) + , __m_owning(owning) +{ + assert(nv); +} + +__nv_list_base::~__nv_list_base() +{ + __free_nv(); +} + +void +__nv_list_base::__free_nv() noexcept +{ + if ((__m_nv != nullptr) && + (__m_owning == nvlist_owning::owning)) + ::nvlist_destroy(__m_nv); +} + +/* + * __const_nv_list + */ + +std::error_code +__const_nv_list::error() const noexcept +{ + if (auto const err = nvlist_error(__m_nv); err != 0) + return std::make_error_code(std::errc(err)); + return {}; +} + +bool +__const_nv_list::exists(std::string_view key) const +{ + return ::nvlist_exists(__m_nv, std::string(key).c_str()); +} + +bool +__const_nv_list::empty() const noexcept +{ + return ::nvlist_empty(__m_nv); +} + +int +__const_nv_list::flags() const noexcept +{ + return ::nvlist_flags(__m_nv); +} + +bool +__const_nv_list::in_array() const noexcept +{ + return ::nvlist_in_array(__m_nv); +} + +__const_nv_list::operator bool() const noexcept +{ + return ::nvlist_error(__m_nv) == 0; +} + +void +__const_nv_list::send(int fd) const +{ + if (::nvlist_send(fd, __m_nv) == 0) + return; + + throw std::system_error(error()); +} + +void +__const_nv_list::dump(int fd) const noexcept +{ + ::nvlist_dump(__m_nv, fd); +} + +void +__const_nv_list::fdump(std::FILE *__fp) const noexcept +{ + ::nvlist_fdump(__m_nv, __fp); +} + +std::size_t +__const_nv_list::packed_size() const noexcept +{ + return ::nvlist_size(__m_nv); +} + +std::vector<std::byte> +__const_nv_list::pack() const +{ + auto size = std::size_t{}; + + if (auto *data = nvlist_pack(__m_nv, &size); data != nullptr) { + auto bytes = ptr_guard(static_cast<std::byte *>(data)); + return {bytes.ptr, bytes.ptr + size}; + } + + throw std::system_error(error()); +} + +/* + * __nv_list + */ + + +void +__nv_list::set_error(int error) noexcept +{ + ::nvlist_set_error(__m_nv, error); +} + +__nv_list::operator const_nv_list() const +{ + return const_nv_list(__m_nv); +} + +void +__nv_list::free(std::string_view key) +{ + ::nvlist_free(__m_nv, std::string(key).c_str()); +} + +/* + * null operations + */ + +bool +__const_nv_list::exists_null(std::string_view key) const +{ + return ::nvlist_exists_null(__m_nv, std::string(key).c_str()); +} + +void +__nv_list::add_null(std::string_view key) +{ + ::nvlist_add_null(__m_nv, std::string(key).c_str()); +} + +void +__nv_list::free_null(std::string_view key) +{ + ::nvlist_free_null(__m_nv, std::string(key).c_str()); +} + +/* + * bool operations + */ + +void +__nv_list::add_bool(std::string_view key, bool value) +{ + ::nvlist_add_bool(__m_nv, std::string(key).c_str(), value); +} + +bool +__const_nv_list::exists_bool(std::string_view key) const +{ + return ::nvlist_exists_bool(__m_nv, std::string(key).c_str()); +} + +bool +__const_nv_list::get_bool(std::string_view key) const +{ + return ::nvlist_get_bool(__m_nv, std::string(key).c_str()); +} + +bool +__nv_list::take_bool(std::string_view key) +{ + return ::nvlist_take_bool(__m_nv, std::string(key).c_str()); +} + +void +__nv_list::free_bool(std::string_view key) +{ + ::nvlist_free_bool(__m_nv, std::string(key).c_str()); +} + +bool +__const_nv_list::exists_bool_array(std::string_view key) const +{ + return ::nvlist_exists_bool_array(__m_nv, std::string(key).c_str()); +} + +std::span<bool const> +__const_nv_list::get_bool_array(std::string_view key) const +{ + auto nitems = std::size_t{}; + auto *data = ::nvlist_get_bool_array(__m_nv, + std::string(key).c_str(), + &nitems); + return {data, nitems}; +} + +std::vector<bool> +__nv_list::take_bool_array(std::string_view key) +{ + auto nitems = std::size_t{}; + auto ptr = ptr_guard(::nvlist_take_bool_array(__m_nv, + std::string(key).c_str(), + &nitems)); + return std::vector<bool>(ptr.ptr, ptr.ptr + nitems); +} + +void +__nv_list::add_bool_array(std::string_view key, + std::span<bool const> value) +{ + ::nvlist_add_bool_array(__m_nv, + std::string(key).c_str(), + std::ranges::data(value), + std::ranges::size(value)); +} + +void +__nv_list::move_bool_array(std::string_view key, std::span<bool> value) +{ + ::nvlist_move_bool_array(__m_nv, std::string(key).c_str(), + std::ranges::data(value), + std::ranges::size(value)); +} + +void +__nv_list::append_bool_array(std::string_view key, bool value) +{ + ::nvlist_append_bool_array(__m_nv, std::string(key).c_str(), value); +} + +void +__nv_list::free_bool_array(std::string_view key) +{ + ::nvlist_free_bool_array(__m_nv, std::string(key).c_str()); +} + +/* + * number operations + */ + +void +__nv_list::add_number(std::string_view key, std::uint64_t value) +{ + ::nvlist_add_number(__m_nv, std::string(key).c_str(), value); +} + +bool +__const_nv_list::exists_number(std::string_view key) const +{ + return ::nvlist_exists_number(__m_nv, std::string(key).c_str()); +} + +std::uint64_t +__const_nv_list::get_number(std::string_view key) const +{ + return ::nvlist_get_number(__m_nv, std::string(key).c_str()); +} + +std::uint64_t +__nv_list::take_number(std::string_view key) +{ + return ::nvlist_take_number(__m_nv, std::string(key).c_str()); +} + +void +__nv_list::free_number(std::string_view key) +{ + ::nvlist_free_number(__m_nv, std::string(key).c_str()); +} + +bool +__const_nv_list::exists_number_array(std::string_view key) const +{ + return ::nvlist_exists_number_array(__m_nv, std::string(key).c_str()); +} + +std::span<std::uint64_t const> +__const_nv_list::get_number_array(std::string_view key) const +{ + auto nitems = std::size_t{}; + auto *data = ::nvlist_get_number_array( + __m_nv, std::string(key).c_str(), &nitems); + return {data, nitems}; +} + +std::vector<std::uint64_t> +__nv_list::take_number_array(std::string_view key) +{ + auto nitems = std::size_t{}; + auto ptr = ptr_guard( + ::nvlist_take_number_array(__m_nv, + std::string(key).c_str(), + &nitems)); + return {ptr.ptr, ptr.ptr + nitems}; +} + +void +__nv_list::add_number_array(std::string_view key, + std::span<std::uint64_t const> value) +{ + ::nvlist_add_number_array(__m_nv, std::string(key).c_str(), + std::ranges::data(value), + std::ranges::size(value)); +} + +void +__nv_list::move_number_array(std::string_view key, + std::span<std::uint64_t> value) +{ + ::nvlist_move_number_array(__m_nv, std::string(key).c_str(), + std::ranges::data(value), + std::ranges::size(value)); +} + +void +__nv_list::append_number_array(std::string_view key, std::uint64_t value) +{ + ::nvlist_append_number_array(__m_nv, std::string(key).c_str(), value); +} + +void +__nv_list::free_number_array(std::string_view key) +{ + ::nvlist_free_number_array(__m_nv, std::string(key).c_str()); +} + +/* + * string operations + */ + +void +__nv_list::add_string(std::string_view key, std::string_view value) +{ + ::nvlist_add_string(__m_nv, + std::string(key).c_str(), + std::string(value).c_str()); +} + +void +__nv_list::move_string(std::string_view key, char *value) +{ + ::nvlist_move_string(__m_nv, std::string(key).c_str(), value); +} + +bool +__const_nv_list::exists_string(std::string_view key) const +{ + return nvlist_exists_string(__m_nv, std::string(key).c_str()); +} + +std::string_view +__const_nv_list::get_string(std::string_view key) const +{ + return nvlist_get_string(__m_nv, std::string(key).c_str()); +} + +std::string +__nv_list::take_string(std::string_view key) +{ + return nvlist_take_string(__m_nv, std::string(key).c_str()); +} + +void +__nv_list::free_string(std::string_view key) +{ + nvlist_free_string(__m_nv, std::string(key).c_str()); +} + +void +__nv_list::add_string_array(std::string_view key, + std::span<std::string_view const> value) +{ + // nvlist_add_string_array expects an array of NUL-terminated + // C strings. + + auto strings = value + | construct<std::string>() + | std::ranges::to<std::vector>(); + + auto ptrs = strings + | std::views::transform(&std::string::c_str) + | std::ranges::to<std::vector>(); + + nvlist_add_string_array(__m_nv, std::string(key).c_str(), + ptrs.data(), ptrs.size()); +} + +void +__nv_list::move_string_array(std::string_view key, + std::span<char *> value) +{ + ::nvlist_move_string_array(__m_nv, std::string(key).c_str(), + std::ranges::data(value), + std::ranges::size(value)); +} + +void +__nv_list::append_string_array(std::string_view key, std::string_view value) +{ + ::nvlist_append_string_array(__m_nv, + std::string(key).c_str(), + std::string(value).c_str()); +} + +bool +__const_nv_list::exists_string_array(std::string_view key) const +{ + return nvlist_exists_string_array(__m_nv, std::string(key).c_str()); +} + +std::vector<std::string_view> +__const_nv_list::get_string_array(std::string_view key) const +{ + auto nitems = std::size_t{}; + auto *data = nvlist_get_string_array(__m_nv, std::string(key).c_str(), + &nitems); + return std::span(data, data + nitems) + | construct<std::string_view>() + | std::ranges::to<std::vector>(); +} + +std::vector<std::string> +__nv_list::take_string_array(std::string_view key) +{ + auto nitems = std::size_t{}; + auto *data = nvlist_take_string_array(__m_nv, std::string(key).c_str(), + &nitems); + return std::span(data, data + nitems) + | construct<std::string>() + | std::ranges::to<std::vector>(); +} + +void +__nv_list::free_string_array(std::string_view key) +{ + ::nvlist_free_string_array(__m_nv, std::string(key).c_str()); +} + +/* + * nv_list operations + */ + +void +__nv_list::add_nvlist(std::string_view key, const_nv_list const &other) +{ + nvlist_add_nvlist(__m_nv, std::string(key).c_str(), other.__m_nv); +} + +void +__nv_list::move_nvlist(std::string_view key, nv_list &&value) +{ + ::nvlist_move_nvlist(__m_nv, std::string(key).c_str(), + std::exchange(value.__m_nv, nullptr)); +} + +void +__nv_list::move_nvlist(std::string_view key, ::nvlist_t *value) +{ + ::nvlist_move_nvlist(__m_nv, std::string(key).c_str(), value); +} + +bool +__const_nv_list::exists_nvlist(std::string_view key) const +{ + return nvlist_exists_nvlist(__m_nv, std::string(key).c_str()); +} + +const_nv_list +__const_nv_list::get_nvlist(std::string_view key) const +{ + auto nvl = nvlist_get_nvlist(__m_nv, std::string(key).c_str()); + return const_nv_list(nvl); +} + +nv_list +__nv_list::take_nvlist(std::string_view key) +{ + auto nvl = nvlist_take_nvlist(__m_nv, std::string(key).c_str()); + return nv_list(nvl); +} + +void +__nv_list::free_nvlist(std::string_view key) +{ + nvlist_free_nvlist(__m_nv, std::string(key).c_str()); +} + +bool +__const_nv_list::exists_nvlist_array(std::string_view key) const +{ + return nvlist_exists_nvlist_array(__m_nv, std::string(key).c_str()); +} + +void +__nv_list::add_nvlist_array(std::string_view key, + std::span<const_nv_list const> value) +{ + auto ptrs = value + | std::views::transform(&const_nv_list::__m_nv) + | std::ranges::to<std::vector>(); + + ::nvlist_add_nvlist_array(__m_nv, std::string(key).c_str(), + ptrs.data(), ptrs.size()); +} + +void +__nv_list::add_nvlist_array(std::string_view key, + std::span<nv_list const> value) +{ + auto ptrs = value + | std::views::transform(&nv_list::__m_nv) + | std::ranges::to<std::vector>(); + + nvlist_add_nvlist_array(__m_nv, std::string(key).c_str(), + ptrs.data(), ptrs.size()); +} + +void +__nv_list::move_nvlist_array(std::string_view key, + std::span<::nvlist_t *> value) +{ + ::nvlist_move_nvlist_array(__m_nv, std::string(key).c_str(), + std::ranges::data(value), + std::ranges::size(value)); +} + +void +__nv_list::append_nvlist_array(std::string_view key, + const_nv_list const &value) +{ + ::nvlist_append_nvlist_array(__m_nv, std::string(key).c_str(), + value.__m_nv); +} + +std::vector<const_nv_list> +__const_nv_list::get_nvlist_array(std::string_view key) const +{ + auto nitems = std::size_t{}; + auto *data = nvlist_get_nvlist_array(__m_nv, std::string(key).c_str(), + &nitems); + return {std::from_range, + std::span(data, nitems) | construct<const_nv_list>()}; +} + +std::vector<nv_list> +__nv_list::take_nvlist_array(std::string_view key) +{ + auto nitems = std::size_t{}; + auto ptr = ptr_guard( + ::nvlist_take_nvlist_array(__m_nv, + std::string(key).c_str(), + &nitems)); + return {std::from_range, + std::span(ptr.ptr, nitems) | construct<nv_list>()}; +} + +void +__nv_list::free_nvlist_array(std::string_view key) +{ + ::nvlist_free_nvlist_array(__m_nv, std::string(key).c_str()); +} + +/* + * descriptor operations + */ + +int +__const_nv_list::get_descriptor(std::string_view key) const +{ + return ::nvlist_get_descriptor(__m_nv, std::string(key).c_str()); +} + +int +__nv_list::take_descriptor(std::string_view key) +{ + return ::nvlist_take_descriptor(__m_nv, std::string(key).c_str()); +} + +void +__nv_list::add_descriptor(std::string_view key, int value) +{ + ::nvlist_add_descriptor(__m_nv, std::string(key).c_str(), value); +} + +void +__nv_list::move_descriptor(std::string_view key, int value) +{ + ::nvlist_move_descriptor(__m_nv, std::string(key).c_str(), value); +} + +void +__nv_list::free_descriptor(std::string_view key) +{ + ::nvlist_free_descriptor(__m_nv, std::string(key).c_str()); +} + +void +__nv_list::append_descriptor_array(std::string_view key, int value) +{ + ::nvlist_append_descriptor_array(__m_nv, + std::string(key).c_str(), + value); +} + +void +__nv_list::add_descriptor_array(std::string_view key, + std::span<int const> value) +{ + ::nvlist_add_descriptor_array(__m_nv, std::string(key).c_str(), + std::ranges::data(value), + std::ranges::size(value)); +} + +void +__nv_list::move_descriptor_array(std::string_view key, std::span<int> value) +{ + ::nvlist_add_descriptor_array(__m_nv, std::string(key).c_str(), + std::ranges::data(value), + std::ranges::size(value)); +} + +std::span<int const> +__const_nv_list::get_descriptor_array(std::string_view key) const +{ + auto nitems = std::size_t{}; + auto *data = ::nvlist_get_descriptor_array( + __m_nv, std::string(key).c_str(), &nitems); + return {data, nitems}; +} + +void +__nv_list::free_descriptor_array(std::string_view key) +{ + ::nvlist_free_descriptor_array(__m_nv, std::string(key).c_str()); +} + +bool +__const_nv_list::exists_descriptor(std::string_view key) const +{ + return ::nvlist_exists_descriptor(__m_nv, std::string(key).c_str()); +} + +bool +__const_nv_list::exists_descriptor_array(std::string_view key) const +{ + return ::nvlist_exists_descriptor_array(__m_nv, + std::string(key).c_str()); +} + +std::vector<int> +__nv_list::take_descriptor_array(std::string_view key) +{ + auto nitems = std::size_t{}; + auto ptr = ptr_guard( + ::nvlist_take_descriptor_array(__m_nv, + std::string(key).c_str(), + &nitems)); + return {ptr.ptr, ptr.ptr + nitems}; +} + +/* + * binary operations + */ + +bool +__const_nv_list::exists_binary(std::string_view key) const +{ + return ::nvlist_exists_binary(__m_nv, std::string(key).c_str()); +} + +void +__nv_list::add_binary(std::string_view key, std::span<std::byte const> value) +{ + ::nvlist_add_binary(__m_nv, std::string(key).c_str(), + std::ranges::data(value), + std::ranges::size(value)); +} + +void +__nv_list::move_binary(std::string_view key, std::span<std::byte> value) +{ + ::nvlist_move_binary(__m_nv, std::string(key).c_str(), + std::ranges::data(value), + std::ranges::size(value)); +} + +void +__nv_list::free_binary(std::string_view key) +{ + ::nvlist_free_binary(__m_nv, std::string(key).c_str()); +} + +std::span<std::byte const> +__const_nv_list::get_binary(std::string_view key) const +{ + auto size = std::size_t{}; + auto *data = nvlist_get_binary(__m_nv, std::string(key).c_str(), + &size); + return {static_cast<std::byte const *>(data), size}; +} + +std::vector<std::byte> +__nv_list::take_binary(std::string_view key) +{ + auto size = std::size_t{}; + auto *data = ::nvlist_take_binary(__m_nv, + std::string(key).c_str(), + &size); + auto ptr = ptr_guard(static_cast<std::byte *>(data)); + return {ptr.ptr, ptr.ptr + size}; +} + +} // namespace bsd::__detail diff --git a/libnvxx/nvxx.h b/libnvxx/nvxx.h new file mode 100644 index 0000000..7c938ce --- /dev/null +++ b/libnvxx/nvxx.h @@ -0,0 +1,32 @@ +/* + * This is free and unencumbered software released into the public domain. + * + * Anyone is free to copy, modify, publish, use, compile, sell, or distribute + * this software, either in source code form or as a compiled binary, for any + * purpose, commercial or non-commercial, and by any means. + * + * In jurisdictions that recognize copyright laws, the author or authors of + * this software dedicate any and all copyright interest in the software to the + * public domain. We make this dedication for the benefit of the public at + * large and to the detriment of our heirs and successors. We intend this + * dedication to be an overt act of relinquishment in perpetuity of all present + * and future rights to this software under copyright law. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef _NVXX_H_INCLUDED +#define _NVXX_H_INCLUDED + +/* + * nvl: lightweight wrapper around nv(9). + */ + +#include "nvxx_base.h" + +#endif /* !_NVXX_H_INCLUDED */ diff --git a/libnvxx/nvxx_base.h b/libnvxx/nvxx_base.h new file mode 100644 index 0000000..298d5d6 --- /dev/null +++ b/libnvxx/nvxx_base.h @@ -0,0 +1,550 @@ +/* + * This is free and unencumbered software released into the public domain. + * + * Anyone is free to copy, modify, publish, use, compile, sell, or distribute + * this software, either in source code form or as a compiled binary, for any + * purpose, commercial or non-commercial, and by any means. + * + * In jurisdictions that recognize copyright laws, the author or authors of + * this software dedicate any and all copyright interest in the software to the + * public domain. We make this dedication for the benefit of the public at + * large and to the detriment of our heirs and successors. We intend this + * dedication to be an overt act of relinquishment in perpetuity of all present + * and future rights to this software under copyright law. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef _NVXX_BASE_H_INCLUDED +#define _NVXX_BASE_H_INCLUDED + +#include <sys/nv.h> + +#include <expected> +#include <ranges> +#include <span> +#include <system_error> +#include <vector> +#include <stdexcept> + +namespace bsd { + +struct nv_list; +struct const_nv_list; + +enum struct nvlist_owning { + owning, + non_owning +}; + +namespace __detail { + +struct __nv_list_base { +protected: + friend struct bsd::const_nv_list; + + __nv_list_base(int __flags = 0); + __nv_list_base(::nvlist_t *, + nvlist_owning = nvlist_owning::owning); + + __nv_list_base(__nv_list_base const &) = delete; + __nv_list_base(__nv_list_base &&) noexcept = delete; + + __nv_list_base &operator=(__nv_list_base const &) = delete; + __nv_list_base &operator=(__nv_list_base &&) noexcept = delete; + + ~__nv_list_base(); + void __free_nv() noexcept; + + ::nvlist_t *__m_nv{}; + nvlist_owning __m_owning; +}; + +struct __const_nv_list : virtual __nv_list_base { + friend struct __nv_list; + + /* + * Write the contents of this nvlist to __fd or __fp in a + * human-readable format suitable for debugging. + */ + void dump(int __fd) const noexcept; + void fdump(std::FILE *__fp) const noexcept; + + /* + * Return the number of bytes that would be required to call pack() on + * this nvlist. This is equivalent to nvlist_size(), but we rename it + * to packed_size() to avoid confusion with the usual container size() + * function (which returns the number of elements). + */ + [[nodiscard]] std::size_t packed_size() const noexcept; + + /* + * Return a byte array representing the contents of this nvlist; the + * array can later be passed to nv_list::unpack() to reproduce the + * nvlist. + */ + [[nodiscard]] std::vector<std::byte> pack() const; + + /* + * Return the error code associated with this nvlist, if any, by + * calling nvlist_error(). + */ + [[nodiscard]] std::error_code error() const noexcept; + + /* + * Return true if this nvlist is in a non-error state, otherwise false. + */ + [[nodiscard]] explicit operator bool() const noexcept; + + /* + * Returns true if this nvlist is empty (contains no values). + */ + [[nodiscard]] bool empty() const noexcept; + + /* + * Return the flags used to create this nv_list. + */ + [[nodiscard]] int flags() const noexcept; + + /* + * Returns true if this nv_list is part of an array contained inside + * another nv_list. + */ + [[nodiscard]] bool in_array() const noexcept; + + /* + * Pack this nvlist and write it to the given file descriptor. On + * error, throws std::system_error. + */ + void send(int __fd) const; + + /* + * if a key of any type with the given name exists, return true. + */ + [[nodiscard]] bool exists(std::string_view __key) const; + + // TODO: exists_type() + + /* exists */ + + [[nodiscard]] bool exists_null(std::string_view __key) const; + [[nodiscard]] bool exists_bool(std::string_view __key) const; + [[nodiscard]] bool exists_number(std::string_view __key) const; + [[nodiscard]] bool exists_string(std::string_view __key) const; + [[nodiscard]] bool exists_nvlist(std::string_view __key) const; + [[nodiscard]] bool exists_descriptor(std::string_view __key) const; + [[nodiscard]] bool exists_binary(std::string_view __key) const; + + [[nodiscard]] bool exists_bool_array(std::string_view __key) const; + [[nodiscard]] bool exists_number_array(std::string_view __key) const; + [[nodiscard]] bool exists_string_array(std::string_view __key) const; + [[nodiscard]] bool exists_nvlist_array(std::string_view __key) const; + [[nodiscard]] bool exists_descriptor_array( + std::string_view __key) const; + + /* get */ + + [[nodiscard]] auto + get_bool(std::string_view __key) const -> bool; + + [[nodiscard]] auto + get_number(std::string_view __key) const -> std::uint64_t; + + [[nodiscard]] auto + get_string(std::string_view __key) const -> std::string_view; + + [[nodiscard]] auto + get_nvlist(std::string_view __key) const -> const_nv_list; + + [[nodiscard]] auto + get_descriptor(std::string_view __key) const -> int; + + [[nodiscard]] auto + get_binary(std::string_view key) const -> std::span<std::byte const>; + + + [[nodiscard]] auto + get_bool_array(std::string_view __key) const -> std::span<bool const>; + + [[nodiscard]] auto + get_number_array(std::string_view __key) const + -> std::span<std::uint64_t const>; + + [[nodiscard]] auto + get_string_array(std::string_view __key) const + -> std::vector<std::string_view>; + + [[nodiscard]] auto + get_nvlist_array(std::string_view name) const + -> std::vector<const_nv_list>; + + [[nodiscard]] auto + get_descriptor_array(std::string_view __key) const + -> std::span<int const>; +}; + +struct __nv_list : virtual __nv_list_base { + friend struct const_nv_list; + + /* + * Set the error code on this nvlist to the given value. + */ + void set_error(int __error) noexcept; + + /* + * Convert this nv_list into a const_nv_list. This is a shallow copy + * which does not clone the underlying nvlist_t; therefore, the + * lifetime of the const_nv_list ends when this nv_list is destroyed. + */ + operator const_nv_list() const; + + /* add */ + + void add_null(std::string_view __key); + void add_bool(std::string_view __key, bool); + void add_number(std::string_view __key, std::uint64_t); + void add_string(std::string_view __key, std::string_view); + void add_nvlist(std::string_view __key, const_nv_list const &); + void add_descriptor(std::string_view __key, int); + void add_binary(std::string_view __key, std::span<std::byte const>); + + void add_bool_array(std::string_view __key, + std::span<bool const>); + void add_number_array(std::string_view __key, + std::span<std::uint64_t const>); + void add_string_array(std::string_view __key, + std::span<std::string_view const>); + void add_nvlist_array(std::string_view __key, + std::span<const_nv_list const>); + void add_nvlist_array(std::string_view __key, + std::span<nv_list const>); + void add_descriptor_array(std::string_view __key, + std::span<int const>); + + /* free */ + + void free(std::string_view __key); + // TODO: free_type() + void free_null(std::string_view __key); + void free_bool(std::string_view __key); + void free_number(std::string_view __key); + void free_string(std::string_view __key); + void free_nvlist(std::string_view __key); + void free_descriptor(std::string_view __key); + void free_binary(std::string_view __key); + + void free_bool_array(std::string_view __key); + void free_number_array(std::string_view __key); + void free_string_array(std::string_view __key); + void free_nvlist_array(std::string_view __key); + void free_descriptor_array(std::string_view __key); + + /* take */ + + [[nodiscard]] auto take_bool(std::string_view __key) -> bool; + [[nodiscard]] auto take_number(std::string_view __key) + -> std::uint64_t; + [[nodiscard]] auto take_string(std::string_view __key) -> std::string; + [[nodiscard]] auto take_nvlist(std::string_view __key) -> nv_list; + [[nodiscard]] auto take_descriptor(std::string_view __key) -> int; + [[nodiscard]] auto take_binary(std::string_view __key) + -> std::vector<std::byte>; + + [[nodiscard]] auto + take_bool_array(std::string_view __key) -> std::vector<bool>; + + [[nodiscard]] auto + take_number_array(std::string_view __key) + -> std::vector<std::uint64_t>; + + [[nodiscard]] auto + take_string_array(std::string_view __key) -> std::vector<std::string>; + + [[nodiscard]] auto + take_nvlist_array(std::string_view __key) -> std::vector<nv_list>; + + [[nodiscard]] auto + take_descriptor_array(std::string_view __key) -> std::vector<int>; + + /* move */ + + void move_string(std::string_view __key, char *); + void move_nvlist(std::string_view __key, nv_list &&); + void move_nvlist(std::string_view __key, ::nvlist_t *); + void move_descriptor(std::string_view __key, int); + void move_binary(std::string_view __key, std::span<std::byte>); + + void move_bool_array(std::string_view __key, std::span<bool>); + void move_number_array(std::string_view __key, std::span<std::uint64_t>); + void move_string_array(std::string_view __key, std::span<char *>); + void move_nvlist_array(std::string_view __key, std::span<::nvlist_t *>); + void move_descriptor_array(std::string_view __key, std::span<int>); + + /* append */ + + void append_bool_array(std::string_view __key, bool); + void append_number_array(std::string_view __key, std::uint64_t); + void append_string_array(std::string_view __key, std::string_view); + void append_nvlist_array(std::string_view __key, const_nv_list const &); + void append_descriptor_array(std::string_view __key, int); + + /* add_bool_range */ + template<std::ranges::contiguous_range _Range> + requires std::is_same_v<bool, + std::remove_cvref_t< + std::ranges::range_value_t<_Range>>> + void add_bool_range(std::string_view __key, _Range &&__value) + { + add_bool_array(__key, std::span(__value)); + } + + template<std::ranges::range _Range> + requires (!std::ranges::contiguous_range<_Range> + && std::is_same_v<bool, + std::remove_cvref_t< + std::ranges::range_value_t<_Range>>>) + void add_bool_range(std::string_view __key, _Range &&__value) + { + /* + * since vector<bool> is not a contiguous_range, + * we need to do two copies here. + */ + + auto __v = std::vector(std::from_range, __value); + auto __p = std::make_unique<bool[]>(__v.size()); + std::ranges::copy(__v, __p.get()); + add_bool_array(__key, std::span(__p.get(), __v.size())); + } + + /* add_number_range */ + + template<std::ranges::contiguous_range _Range> + requires std::is_same_v<std::uint64_t, + std::remove_cvref_t< + std::ranges::range_value_t<_Range>>> + void add_number_range(std::string_view __key, _Range &&__value) + { + add_number_array(__key, std::span(__value)); + } + + template<std::ranges::range _Range> + requires ( + !std::ranges::contiguous_range<_Range> + && std::is_same_v<std::uint64_t, + std::remove_cvref_t< + std::ranges::range_value_t<_Range>>>) + void add_number_range(std::string_view __key, _Range &&__value) + { + auto __arr = std::vector(std::from_range, __value); + add_number_range(__key, __arr); + } + + + /* add_string_range */ + + template<std::ranges::contiguous_range _Range> + requires std::is_same_v<std::string_view, + std::remove_cvref_t< + std::ranges::range_value_t<_Range>>> + void add_string_range(std::string_view __key, _Range &&__value) + { + add_string_array(__key, std::span(__value)); + } + + template<std::ranges::range _Range> + requires (!std::ranges::contiguous_range<_Range> + && std::is_same_v<std::string_view, + std::remove_cvref_t< + std::ranges::range_value_t<_Range>>>) + void add_string_range(std::string_view __key, _Range &&__value) + { + auto __arr = std::vector(std::from_range, __value); + add_string_range(__key, __arr); + } + + /* add_binary_range */ + + template<std::ranges::contiguous_range _Range> + requires std::is_same_v<std::byte, + std::remove_cvref_t< + std::ranges::range_value_t<_Range>>> + void add_binary_range(std::string const &__name, _Range &&__value) + { + auto __span = std::span(__value); + add_binary(__name, __span); + } + + template<std::ranges::range _Range> + requires ( + !std::ranges::contiguous_range<_Range> + && std::is_same_v<std::byte, + std::remove_cvref_t< + std::ranges::range_value_t<_Range>>>) + void add_binary_range(std::string_view __key, _Range &&__value) + { + auto __arr = std::vector(std::from_range, __value); + add_number_range(__key, __arr); + } +}; + +} // namespace bsd::__detail + +/* + * const_nv_list is an immutable, non-owning reference to an nvlist. + * it will not free the nvlist_t on destruction. + */ +struct const_nv_list final + : virtual __detail::__nv_list_base + , __detail::__const_nv_list +{ + /* + * Default constructing a const_nv_list leaves it in the empty state; + * it can be assigned to or destructed but no other operations are + * valid. + */ + const_nv_list() noexcept; + + /* + * Create an nv_list object that refers to an existing nvlist_t. The + * const_nv_list is non-owning, i.e. it will not free the nvlist_t on + * destruction. If the nvlist_t is null, the const_nv_list will be + * empty. + */ + explicit const_nv_list(::nvlist_t const *) noexcept; + + /* + * Copy the nvlist pointer from an existing const_nv_list. This does + * not clone the nvlist itself; both nvlists will have the same + * lifetime. If the other nvlist is empty, this nvlist will also be + * empty. + */ + const_nv_list(const_nv_list const &) noexcept; + + /* + * Cause this const_nv_list to refer to the same nvlist as the RHS. + * This does not clone the nvlist; both nvlists will have the same + * lifetime. If the RHS nvlist is empty, this nvlist will also be + * empty. + */ + const_nv_list &operator=(const_nv_list const &) noexcept; + const_nv_list &operator=(nv_list const &) noexcept; + + /* + * Return the nvlist pointer stored by this const_nv_list. Since + * const_nv_list is non-owning, the lifetime of the pointer is + * unspecified. + */ + ::nvlist_t const *ptr() const; +}; + +/* + * nv_list is a mutable, owning reference to an nvlist. it will free the + * nvlist_t on destruction, invalidating any const_nv_lists created from it. + */ +struct nv_list final + : virtual __detail::__nv_list_base + , __detail::__const_nv_list + , __detail::__nv_list +{ + /* + * Create a new, empty nv_list. On failure, throws std::system_error. + * The flags argument is passed to nvlist_create(). + */ + explicit nv_list(int __flags = 0); + + /* + * Create an nv_list object that refers to an existing nvlist_t. + */ + explicit nv_list(::nvlist_t *) noexcept; + + /* + * Create an nv_list by copying an existing nv_list object with + * nvlist_clone(). On failure, throws std::system_error. + */ + nv_list(nv_list const &); + + /* + * Create an nv_list by moving from an existing nv_list. The + * moved-from nvlist is left in an undefined state and must not be + * accessed other than to destruct it. + */ + nv_list(nv_list &&) noexcept; + + /* + * Replace the wrapped nv_list with a copy of the RHS nv_list using + * nvlist_clone(). On failure, throws std::system_error. + */ + nv_list &operator=(nv_list const &); + + /* + * Replace the wrapped nv_list by moving from another nv_list. The + * moved-from nv_list is left in an undefined state and must not be + * accessed other than to destruct it. + */ + nv_list &operator=(nv_list &&) noexcept; + + /* + * Return the pointer stored by this nv_list, without releasing it. + * The pointer may be used to modify the nvlist, but must not be + * freed (e.g., by passing it to a function like nvlist_xfer()). + */ + ::nvlist_t *ptr(); + ::nvlist_t const *ptr() const; + + /* + * Release the pointer held by this nv_list and return it. The nv_list + * will be left in a moved-from state and must not be used other than + * to destruct it. + */ + ::nvlist_t *release() &&; + + /* + * Create and return an nv_list by calling nvlist_unpack() on the + * provided buffer. The __flags argument is passed to nvlist_unpack(). + * On failure, throws std::system_error. + */ + [[nodiscard]] static nv_list + unpack(std::span<std::byte> __data, int __flags = 0); + + /* + * As the previous function, but the nv_list is unpacked from the + * provided range, which must be a contiguous_range of value type + * std::byte. + */ + template<std::ranges::contiguous_range _Range> + requires std::is_same_v<std::byte, + std::remove_cvref_t< + std::ranges::range_value_t<_Range>>> + [[nodiscard]] static nv_list + unpack(_Range const &__data, int __flags = 0) + { + return unpack(std::span<std::byte>(__data), __flags); + } + + /* + * Receive an nv_list from a file descriptor by calling nvlist_recv(), + * to which flags is passed. On failure, throws std::system_error. + */ + [[nodiscard]] static auto recv(int __fd, int __flags) -> nv_list; + + /* + * Send an nv_list over a file descriptor and receive another nv_list + * in response, which is returned, by calling nvlist_xfer(). On + * failure, throws std::system_error. + * + * The source nv_list is moved-from and is left in an undefined state. + * The returned nv_list is owning. + */ + [[nodiscard]] static nv_list xfer(int __fd, + nv_list &&__source, + int __flags); +}; + +} // namespace bsd + +#endif /* !_NVXX_BASE_H_INCLUDED */ diff --git a/libnvxx/tests/Makefile b/libnvxx/tests/Makefile new file mode 100644 index 0000000..29d0351 --- /dev/null +++ b/libnvxx/tests/Makefile @@ -0,0 +1,30 @@ +# This is free and unencumbered software released into the public domain. +# +# Anyone is free to copy, modify, publish, use, compile, sell, or distribute +# this software, either in source code form or as a compiled binary, for any +# purpose, commercial or non-commercial, and by any means. +# +# In jurisdictions that recognize copyright laws, the author or authors of +# this software dedicate any and all copyright interest in the software to the +# public domain. We make this dedication for the benefit of the public at +# large and to the detriment of our heirs and successors. We intend this +# dedication to be an overt act of relinquishment in perpetuity of all present +# and future rights to this software under copyright law. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +PREFIX?= /usr/local +TESTSDIR?= ${PREFIX}/tests/nvxx +ATF_TESTS_CXX= nvxx_basic +CXXSTD= c++23 +# Note that we can't use -Werror here because it breaks ATF. +CXXFLAGS+= -W -Wall -Wextra +CFLAGS+= -I${.CURDIR:H} +LDFLAGS+= -lprivateatf-c++ -L${.OBJDIR:H} -lnvxx + +.include <bsd.test.mk> diff --git a/libnvxx/tests/nvxx_basic.cc b/libnvxx/tests/nvxx_basic.cc new file mode 100644 index 0000000..1cf2bfd --- /dev/null +++ b/libnvxx/tests/nvxx_basic.cc @@ -0,0 +1,571 @@ +/* + * This is free and unencumbered software released into the public domain. + * + * Anyone is free to copy, modify, publish, use, compile, sell, or distribute + * this software, either in source code form or as a compiled binary, for any + * purpose, commercial or non-commercial, and by any means. + * + * In jurisdictions that recognize copyright laws, the author or authors of + * this software dedicate any and all copyright interest in the software to the + * public domain. We make this dedication for the benefit of the public at + * large and to the detriment of our heirs and successors. We intend this + * dedication to be an overt act of relinquishment in perpetuity of all present + * and future rights to this software under copyright law. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include <algorithm> +#include <ranges> +#include <list> +#include <vector> +#include <span> +#include <string> +#include <string_view> + +#include <atf-c++.hpp> + +#include "nvxx.h" + +#define TEST_CASE(name) \ + ATF_TEST_CASE_WITHOUT_HEAD(name) \ + ATF_TEST_CASE_BODY(name) + +/* + * test the default ctor + */ + +TEST_CASE(nvxx_ctor_default) +{ + auto nvl = bsd::nv_list(); +} + +/* + * test the NV_FLAG_IGNORE_CASE flag. + */ + +TEST_CASE(nvxx_ignore_case) +{ + auto nvl = bsd::nv_list(NV_FLAG_IGNORE_CASE); + nvl.add_number("TEST number", 42u); + ATF_REQUIRE_EQ(true, nvl.exists_number("TesT nUMBEr")); + + auto n = nvl.take_number("test NuMbEr"); + ATF_REQUIRE_EQ(42u, n); + ATF_REQUIRE_EQ(false, nvl.exists_number("TesT nUMBEr")); +} + +/* + * null tests + */ + +TEST_CASE(nvxx_add_null) +{ + auto nvl = bsd::nv_list(); + nvl.add_null("test"); + ATF_REQUIRE_EQ(true, nvl.exists_null("test")); + ATF_REQUIRE_EQ(true, nvl.exists("test")); +} + +/* + * bool tests + */ + +TEST_CASE(nvxx_add_bool) +{ + using namespace std::literals; + auto constexpr key = "test_bool"sv; + auto constexpr value = true; + + auto nvl = bsd::nv_list(); + nvl.add_bool(key, value); + + ATF_REQUIRE_EQ(true, nvl.exists(key)); + ATF_REQUIRE_EQ(true, nvl.exists_bool(key)); + ATF_REQUIRE_EQ(value, nvl.get_bool(key)); +} + +TEST_CASE(nvxx_take_bool) +{ + using namespace std::literals; + auto constexpr key = "test_bool"sv; + auto constexpr value = true; + + auto nvl = bsd::nv_list(); + nvl.add_bool(key, value); + auto b = nvl.take_bool(key); + ATF_REQUIRE_EQ(value, b); + ATF_REQUIRE_EQ(false, nvl.exists(key)); +} + +TEST_CASE(nvxx_free_bool) +{ + using namespace std::literals; + auto constexpr key = "test_bool"sv; + auto constexpr value = true; + + auto nvl = bsd::nv_list(); + nvl.add_bool(key, value); + nvl.free_bool(key); + ATF_REQUIRE_EQ(false, nvl.exists(key)); +} + +TEST_CASE(nvxx_add_bool_array) +{ + using namespace std::literals; + auto constexpr key = "test_bool"sv; + + auto data = std::array<bool, 2>{true, false}; + + auto nvl = bsd::nv_list(); + nvl.add_bool_array(key, std::span(data)); + + ATF_REQUIRE_EQ(true, nvl.exists(key)); + ATF_REQUIRE_EQ(true, nvl.exists_bool_array(key)); + + auto data2 = nvl.get_bool_array(key); + ATF_REQUIRE_EQ(2, data2.size()); + ATF_REQUIRE_EQ(true, data2[0]); + ATF_REQUIRE_EQ(false, data2[1]); + + auto data3 = nvl.take_bool_array(key); + ATF_REQUIRE_EQ(2, data3.size()); + ATF_REQUIRE_EQ(true, data3[0]); + ATF_REQUIRE_EQ(false, data3[1]); + ATF_REQUIRE_EQ(false, nvl.exists(key)); +} + +TEST_CASE(nvxx_add_bool_range) +{ + using namespace std::literals; + auto constexpr key = "test_bool"sv; + + auto data = std::list{true, false}; + + auto nvl = bsd::nv_list(); + nvl.add_bool_range(key, data); + + ATF_REQUIRE_EQ(true, nvl.exists(key)); + ATF_REQUIRE_EQ(true, nvl.exists_bool_array(key)); + + auto data2 = nvl.get_bool_array(key); + ATF_REQUIRE_EQ(2, data2.size()); + ATF_REQUIRE_EQ(true, data2[0]); + ATF_REQUIRE_EQ(false, data2[1]); +} + +TEST_CASE(nvxx_add_bool_contig_range) +{ + using namespace std::literals; + auto constexpr key = "test_bool"sv; + + auto data = std::vector{true, false}; + + auto nvl = bsd::nv_list(); + nvl.add_bool_range(key, data); + + ATF_REQUIRE_EQ(true, nvl.exists(key)); + ATF_REQUIRE_EQ(true, nvl.exists_bool_array(key)); + + auto data2 = nvl.get_bool_array(key); + ATF_REQUIRE_EQ(2, data2.size()); + ATF_REQUIRE_EQ(true, data2[0]); + ATF_REQUIRE_EQ(false, data2[1]); +} + +/* + * number tests + */ + +// a literal operator to create std::uint64_ts +constexpr std::uint64_t operator"" _u64(unsigned long long v) { + return static_cast<std::uint64_t>(v); +} + +TEST_CASE(nvxx_add_number) +{ + using namespace std::literals; + auto constexpr key = "test_number"sv; + auto constexpr value = 42_u64; + + auto nvl = bsd::nv_list(); + + nvl.add_number(key, value); + ATF_REQUIRE_EQ(true, nvl.exists(key)); + ATF_REQUIRE_EQ(true, nvl.exists_number(key)); + ATF_REQUIRE_EQ(value, nvl.get_number(key)); +} + +TEST_CASE(nvxx_take_number) +{ + using namespace std::literals; + auto constexpr key = "test_number"sv; + auto constexpr value = 42_u64; + + auto nvl = bsd::nv_list(); + + nvl.add_number(key, value); + auto n = nvl.take_number(key); + ATF_REQUIRE_EQ(value, n); + ATF_REQUIRE_EQ(false, nvl.exists(key)); +} + +TEST_CASE(nvxx_free_number) +{ + using namespace std::literals; + auto constexpr key = "test_number"sv; + auto constexpr value = 42_u64; + + auto nvl = bsd::nv_list(); + + nvl.add_number(key, value); + nvl.free_number(key); + ATF_REQUIRE_EQ(false, nvl.exists(key)); +} + +TEST_CASE(nvxx_add_number_array) +{ + using namespace std::literals; + auto constexpr key = "test_number"sv; + auto constexpr size = 16u; + + auto data = std::array<std::uint64_t, size>{}; + std::ranges::copy(std::ranges::iota_view{0u, size}, data.begin()); + + auto nvl = bsd::nv_list(); + nvl.add_number_array(key, std::span(data)); + + ATF_REQUIRE_EQ(true, nvl.exists(key)); + ATF_REQUIRE_EQ(true, nvl.exists_number_array(key)); + + auto data2 = nvl.get_number_array(key); + ATF_REQUIRE_EQ(true, std::ranges::equal(data, data2)); + + auto data3 = nvl.take_number_array(key); + ATF_REQUIRE_EQ(true, std::ranges::equal(data, data3)); + ATF_REQUIRE_EQ(false, nvl.exists(key)); +} + +TEST_CASE(nvxx_add_number_range) +{ + using namespace std::literals; + auto constexpr key = "test_number"sv; + auto constexpr size = 16u; + + auto data = std::list(std::from_range, + std::ranges::iota_view{0_u64, size}); + + auto nvl = bsd::nv_list(); + nvl.add_number_range(key, data); + + ATF_REQUIRE_EQ(true, nvl.exists(key)); + ATF_REQUIRE_EQ(true, nvl.exists_number_array(key)); + + auto data2 = nvl.get_number_array(key); + ATF_REQUIRE_EQ(true, std::ranges::equal(data, data2)); +} + +TEST_CASE(nvxx_add_number_contig_range) +{ + using namespace std::literals; + auto constexpr key = "test_number"sv; + auto constexpr size = 16u; + + auto data = std::vector(std::from_range, + std::ranges::iota_view{0_u64, size}); + + auto nvl = bsd::nv_list(); + nvl.add_number_range(key, data); + + ATF_REQUIRE_EQ(true, nvl.exists(key)); + ATF_REQUIRE_EQ(true, nvl.exists_number_array(key)); + + auto data2 = nvl.get_number_array(key); + ATF_REQUIRE_EQ(true, std::ranges::equal(data, data2)); +} + +/* + * string tests + */ + +TEST_CASE(nvxx_add_string) +{ + using namespace std::literals; + auto constexpr key = "test_string"sv; + auto constexpr value = "testing value"sv; + + auto nvl = bsd::nv_list(); + nvl.add_string(key, value); + + ATF_REQUIRE_EQ(true, nvl.exists(key)); + ATF_REQUIRE_EQ(true, nvl.exists_string(key)); + ATF_REQUIRE_EQ(value, nvl.get_string(key)); +} + +TEST_CASE(nvxx_take_string) +{ + using namespace std::literals; + auto constexpr key = "test_string"sv; + auto constexpr value = "testing value"sv; + + auto nvl = bsd::nv_list(); + nvl.add_string(key, value); + auto s = nvl.take_string(key); + ATF_REQUIRE_EQ(value, s); + ATF_REQUIRE_EQ(false, nvl.exists(key)); +} + +TEST_CASE(nvxx_free_string) +{ + using namespace std::literals; + auto constexpr key = "test_string"sv; + auto constexpr value = "testing value"sv; + + auto nvl = bsd::nv_list(); + nvl.add_string(key, value); + nvl.free_string(key); + ATF_REQUIRE_EQ(false, nvl.exists(key)); +} + +TEST_CASE(nvxx_add_string_array) +{ + using namespace std::literals; + auto constexpr key = "test_string"sv; + + auto data = std::array<std::string_view, 2>{"one"sv, "two"sv}; + + auto nvl = bsd::nv_list(); + nvl.add_string_array(key, std::span(data)); + + ATF_REQUIRE_EQ(true, nvl.exists(key)); + ATF_REQUIRE_EQ(true, nvl.exists_string_array(key)); + + auto data2 = nvl.get_string_array(key); + ATF_REQUIRE_EQ(true, std::ranges::equal(data, data2)); + + auto data3 = nvl.take_string_array(key); + ATF_REQUIRE_EQ(true, std::ranges::equal(data, data3)); + ATF_REQUIRE_EQ(false, nvl.exists(key)); +} + +TEST_CASE(nvxx_add_string_range) +{ + using namespace std::literals; + auto constexpr key = "test_string"sv; + + auto data = std::list<std::string_view>{"one"sv, "two"sv}; + + auto nvl = bsd::nv_list(); + nvl.add_string_range(key, data); + + ATF_REQUIRE_EQ(true, nvl.exists(key)); + ATF_REQUIRE_EQ(true, nvl.exists_string_array(key)); + + auto data2 = nvl.get_string_array(key); + ATF_REQUIRE_EQ(true, std::ranges::equal(data, data2)); + + auto data3 = nvl.take_string_array(key); + ATF_REQUIRE_EQ(true, std::ranges::equal(data, data3)); + ATF_REQUIRE_EQ(false, nvl.exists(key)); +} + +TEST_CASE(nvxx_add_string_contig_range) +{ + using namespace std::literals; + auto constexpr key = "test_string"sv; + + auto data = std::vector<std::string_view>{"one"sv, "two"sv}; + + auto nvl = bsd::nv_list(); + nvl.add_string_range(key, data); + + ATF_REQUIRE_EQ(true, nvl.exists(key)); + ATF_REQUIRE_EQ(true, nvl.exists_string_array(key)); + + auto data2 = nvl.get_string_array(key); + ATF_REQUIRE_EQ(true, std::ranges::equal(data, data2)); + + auto data3 = nvl.take_string_array(key); + ATF_REQUIRE_EQ(true, std::ranges::equal(data, data3)); + ATF_REQUIRE_EQ(false, nvl.exists(key)); +} + +/* + * nv_list tests + */ + +TEST_CASE(nvxx_add_nvlist) +{ + auto nvl = bsd::nv_list(), nvl2 = bsd::nv_list(); + + nvl2.add_number("test_number", 42); + nvl.add_nvlist("test_nvlist", nvl2); + + ATF_REQUIRE_EQ(true, nvl.exists("test_nvlist")); + ATF_REQUIRE_EQ(true, nvl.exists_nvlist("test_nvlist")); + ATF_REQUIRE_EQ(42, nvl.get_nvlist("test_nvlist") + .get_number("test_number")); +} + +TEST_CASE(nvxx_take_nvlist) +{ + auto nvl = bsd::nv_list(), nvl2 = bsd::nv_list(); + + nvl2.add_number("test_number", 42); + nvl.add_nvlist("test_nvlist", nvl2); + + auto nvl3 = nvl.take_nvlist("test_nvlist"); + ATF_REQUIRE_EQ(false, nvl.exists("test_nvlist")); + ATF_REQUIRE_EQ(42, nvl3.get_number("test_number")); +} + +TEST_CASE(nvxx_free_nvlist) +{ + auto nvl = bsd::nv_list(), nvl2 = bsd::nv_list(); + + nvl2.add_number("test_number", 42); + nvl.add_nvlist("test_nvlist", nvl2); + + nvl.free_nvlist("test_nvlist"); + ATF_REQUIRE_EQ(false, nvl.exists("test_nvlist")); +} + +TEST_CASE(nvxx_add_nvlist_array) +{ + using namespace std::literals; + auto constexpr key = "nvls"sv; + + auto nvls = std::vector<bsd::nv_list>(); + + { + auto nvl_ = bsd::nv_list(); + nvl_.add_number("one", 1); + nvls.push_back(nvl_); + } + + { + auto nvl_ = bsd::nv_list(); + nvl_.add_number("two", 2); + nvls.push_back(nvl_); + } + + auto nvl = bsd::nv_list(); + nvl.add_nvlist_array(key, std::span(nvls)); + + ATF_REQUIRE_EQ(true, nvl.exists(key)); + ATF_REQUIRE_EQ(true, nvl.exists_nvlist_array(key)); + + auto nvls2 = nvl.get_nvlist_array(key); + + auto n1 = nvls2[0].get_number("one"); + ATF_REQUIRE_EQ(n1, 1); + + auto n2 = nvls2[1].get_number("two"); + ATF_REQUIRE_EQ(n2, 2); +} + +TEST_CASE(nvxx_add_descriptor) +{ + int fds[2] = {}; + auto ret = ::pipe(&fds[0]); + ATF_REQUIRE_EQ(0, ret); + + auto guard = std::unique_ptr<int[], + decltype([](auto fds) { + ::close(fds[0]); + ::close(fds[1]); + })>(fds); + + auto nvl = bsd::nv_list(); + nvl.add_descriptor("test_descriptor", fds[0]); + ATF_REQUIRE_EQ(true, nvl.exists("test_descriptor")); + ATF_REQUIRE_EQ(true, nvl.exists_descriptor("test_descriptor")); + + auto fd = nvl.get_descriptor("test_descriptor"); + ret = ::write(fd, "1234", 4); + ATF_REQUIRE_EQ(4, ret); + + auto buf = std::array<char, 4>{}; + ret = ::read(fds[1], &buf[0], 4); + ATF_REQUIRE_EQ(4, ret); + + ATF_REQUIRE_EQ('1', buf[0]); + ATF_REQUIRE_EQ('2', buf[1]); + ATF_REQUIRE_EQ('3', buf[2]); + ATF_REQUIRE_EQ('4', buf[3]); +} + +TEST_CASE(nvxx_add_binary) +{ + auto nvl = bsd::nv_list(); + auto data = std::array<std::byte, 16>{}; + + for (auto i = 0u; i < data.size(); ++i) + data[i] = static_cast<std::byte>(i); + + nvl.add_binary("test_binary", std::span(data)); + + ATF_REQUIRE_EQ(true, nvl.exists("test_binary")); + ATF_REQUIRE_EQ(true, nvl.exists_binary("test_binary")); + auto data2 = nvl.get_binary("test_binary"); + ATF_REQUIRE_EQ(true, std::ranges::equal(data, data2)); +} + +TEST_CASE(nvxx_add_binary_range) +{ + auto nvl = bsd::nv_list(); + auto data = std::array<std::byte, 16>{}; + + for (auto i = 0u; i < data.size(); ++i) + data[i] = static_cast<std::byte>(i); + + nvl.add_binary_range("test_binary", data); + + ATF_REQUIRE_EQ(true, nvl.exists("test_binary")); + ATF_REQUIRE_EQ(true, nvl.exists_binary("test_binary")); + auto data2 = nvl.get_binary("test_binary"); + ATF_REQUIRE_EQ(true, std::ranges::equal(data, data2)); +} + +ATF_INIT_TEST_CASES(tcs) +{ + ATF_ADD_TEST_CASE(tcs, nvxx_ctor_default); + ATF_ADD_TEST_CASE(tcs, nvxx_ignore_case); + + ATF_ADD_TEST_CASE(tcs, nvxx_add_null); + + ATF_ADD_TEST_CASE(tcs, nvxx_add_bool); + ATF_ADD_TEST_CASE(tcs, nvxx_take_bool); + ATF_ADD_TEST_CASE(tcs, nvxx_free_bool); + ATF_ADD_TEST_CASE(tcs, nvxx_add_bool_array); + ATF_ADD_TEST_CASE(tcs, nvxx_add_bool_range); + ATF_ADD_TEST_CASE(tcs, nvxx_add_bool_contig_range); + + ATF_ADD_TEST_CASE(tcs, nvxx_add_number); + ATF_ADD_TEST_CASE(tcs, nvxx_take_number); + ATF_ADD_TEST_CASE(tcs, nvxx_free_number); + ATF_ADD_TEST_CASE(tcs, nvxx_add_number_array); + ATF_ADD_TEST_CASE(tcs, nvxx_add_number_range); + ATF_ADD_TEST_CASE(tcs, nvxx_add_number_contig_range); + + ATF_ADD_TEST_CASE(tcs, nvxx_add_string); + ATF_ADD_TEST_CASE(tcs, nvxx_take_string); + ATF_ADD_TEST_CASE(tcs, nvxx_free_string); + ATF_ADD_TEST_CASE(tcs, nvxx_add_string_array); + ATF_ADD_TEST_CASE(tcs, nvxx_add_string_range); + ATF_ADD_TEST_CASE(tcs, nvxx_add_string_contig_range); + + ATF_ADD_TEST_CASE(tcs, nvxx_add_nvlist); + ATF_ADD_TEST_CASE(tcs, nvxx_take_nvlist); + ATF_ADD_TEST_CASE(tcs, nvxx_free_nvlist); + ATF_ADD_TEST_CASE(tcs, nvxx_add_nvlist_array); + + ATF_ADD_TEST_CASE(tcs, nvxx_add_descriptor); + ATF_ADD_TEST_CASE(tcs, nvxx_add_binary); + ATF_ADD_TEST_CASE(tcs, nvxx_add_binary_range); +} |
