NV(9)

โœจ NAME โœจ

okay so bestie, this is NV(9) โ€” straight out of the Kernel Developer's Manual, and it's the whole manual page for a library that does exactly one iconic thing: name/value pairs ๐ŸŽ€. no cap.

the squad of functions on this page is: nvlist_create, nvlist_destroy, nvlist_error, nvlist_set_error, nvlist_empty, nvlist_flags, nvlist_exists, nvlist_free, nvlist_clone, nvlist_dump, nvlist_fdump, nvlist_size, nvlist_pack, nvlist_unpack, nvlist_send, nvlist_recv, nvlist_xfer, nvlist_in_array, nvlist_next, nvlist_add, nvlist_move, nvlist_get, nvlist_take, and nvlist_append โ€” a library for name/value pairs ๐Ÿ’….


๐ŸŒธ LIBRARY ๐ŸŒธ

the library is literally just: library โ€œlibnvโ€. that's it. that's the whole vibe. ๐Ÿซถ


๐Ÿฆ‹ SYNOPSIS ๐Ÿฆ‹

first things first, you gotta pull it in:

#include <sys/nv.h>

and here's every single prototype, verbatim, no edits, no skips, because we don't do that here:

nvlist_t *
nvlist_create(int flags);

void
nvlist_destroy(nvlist_t *nvl);

int
nvlist_error(const nvlist_t *nvl);

void
nvlist_set_error(nvlist_t *nvl, int error);

bool
nvlist_empty(const nvlist_t *nvl);

int
nvlist_flags(const nvlist_t *nvl);

bool
nvlist_in_array(const nvlist_t *nvl);

nvlist_t *
nvlist_clone(const nvlist_t *nvl);

void
nvlist_dump(const nvlist_t *nvl, int fd);

void
nvlist_fdump(const nvlist_t *nvl, FILE *fp);

size_t
nvlist_size(const nvlist_t *nvl);

void *
nvlist_pack(const nvlist_t *nvl, size_t *sizep);

nvlist_t *
nvlist_unpack(const void *buf, size_t size, int flags);

int
nvlist_send(int sock, const nvlist_t *nvl);

nvlist_t *
nvlist_recv(int sock, int flags);

nvlist_t *
nvlist_xfer(int sock, nvlist_t *nvl, int flags);

const char *
nvlist_next(const nvlist_t *nvl, int *typep, void **cookiep);

bool
nvlist_exists(const nvlist_t *nvl, const char *name);

bool
nvlist_exists_type(const nvlist_t *nvl, const char *name, int type);

bool
nvlist_exists_null(const nvlist_t *nvl, const char *name);

bool
nvlist_exists_bool(const nvlist_t *nvl, const char *name);

bool
nvlist_exists_number(const nvlist_t *nvl, const char *name);

bool
nvlist_exists_string(const nvlist_t *nvl, const char *name);

bool
nvlist_exists_nvlist(const nvlist_t *nvl, const char *name);

bool
nvlist_exists_descriptor(const nvlist_t *nvl, const char *name);

bool
nvlist_exists_binary(const nvlist_t *nvl, const char *name);

bool
nvlist_exists_bool_array(const nvlist_t *nvl, const char *name);

bool
nvlist_exists_number_array(const nvlist_t *nvl, const char *name);

bool
nvlist_exists_string_array(const nvlist_t *nvl, const char *name);

bool
nvlist_exists_nvlist_array(const nvlist_t *nvl, const char *name);

bool
nvlist_exists_descriptor_array(const nvlist_t *nvl, const char *name);

void
nvlist_add_null(nvlist_t *nvl, const char *name);

void
nvlist_add_bool(nvlist_t *nvl, const char *name, bool value);

void
nvlist_add_number(nvlist_t *nvl, const char *name, uint64_t value);

void
nvlist_add_string(nvlist_t *nvl, const char *name, const char *value);

void
nvlist_add_stringf(nvlist_t *nvl, const char *name,
    const char *valuefmt, ...);

void
nvlist_add_stringv(nvlist_t *nvl, const char *name,
    const char *valuefmt, va_list valueap);

void
nvlist_add_nvlist(nvlist_t *nvl, const char *name,
    const nvlist_t *value);

void
nvlist_add_descriptor(nvlist_t *nvl, const char *name, int value);

void
nvlist_add_binary(nvlist_t *nvl, const char *name, const void *value,
    size_t size);

void
nvlist_add_bool_array(nvlist_t *nvl, const char *name,
    const bool *value, size_t nitems);

void
nvlist_add_number_array(nvlist_t *nvl, const char *name,
    const uint64_t *value, size_t nitems);

void
nvlist_add_string_array(nvlist_t *nvl, const char *name,
    const char * const * value, size_t nitems);

void
nvlist_add_nvlist_array(nvlist_t *nvl, const char *name,
    const nvlist_t * const * value, size_t nitems);

void
nvlist_add_descriptor_array(nvlist_t *nvl, const char *name,
    const int *value, size_t nitems);

void
nvlist_move_string(nvlist_t *nvl, const char *name, char *value);

void
nvlist_move_nvlist(nvlist_t *nvl, const char *name, nvlist_t *value);

void
nvlist_move_descriptor(nvlist_t *nvl, const char *name, int value);

void
nvlist_move_binary(nvlist_t *nvl, const char *name, void *value,
    size_t size);

void
nvlist_move_bool_array(nvlist_t *nvl, const char *name, bool *value,
    size_t nitems);

void
nvlist_move_number_array(nvlist_t *nvl, const char *name,
    uint64_t *value, size_t nitems);

void
nvlist_move_string_array(nvlist_t *nvl, const char *name, char **value,
    size_t nitems);

void
nvlist_move_nvlist_array(nvlist_t *nvl, const char *name,
    nvlist_t **value, size_t nitems);

void
nvlist_move_descriptor_array(nvlist_t *nvl, const char *name,
    int *value, size_t nitems);

bool
nvlist_get_bool(const nvlist_t *nvl, const char *name);

uint64_t
nvlist_get_number(const nvlist_t *nvl, const char *name);

const char *
nvlist_get_string(const nvlist_t *nvl, const char *name);

const nvlist_t *
nvlist_get_nvlist(const nvlist_t *nvl, const char *name);

int
nvlist_get_descriptor(const nvlist_t *nvl, const char *name);

const void *
nvlist_get_binary(const nvlist_t *nvl, const char *name, size_t *sizep);

const bool *
nvlist_get_bool_array(const nvlist_t *nvl, const char *name,
    size_t *nitems);

const uint64_t *
nvlist_get_number_array(const nvlist_t *nvl, const char *name,
    size_t *nitems);

const char * const *
nvlist_get_string_array(const nvlist_t *nvl, const char *name,
    size_t *nitems);

const nvlist_t * const *
nvlist_get_nvlist_array(const nvlist_t *nvl, const char *name,
    size_t *nitems);

const int *
nvlist_get_descriptor_array(const nvlist_t *nvl, const char *name,
    size_t *nitems);

const nvlist_t *
nvlist_get_parent(const nvlist_t *nvl, void **cookiep);

const nvlist_t *
nvlist_get_array_next(const nvlist_t *nvl);

const nvlist_t *
nvlist_get_pararr(const nvlist_t *nvl, void **cookiep);

bool
nvlist_take_bool(nvlist_t *nvl, const char *name);

uint64_t
nvlist_take_number(nvlist_t *nvl, const char *name);

char *
nvlist_take_string(nvlist_t *nvl, const char *name);

nvlist_t *
nvlist_take_nvlist(nvlist_t *nvl, const char *name);

int
nvlist_take_descriptor(nvlist_t *nvl, const char *name);

void *
nvlist_take_binary(nvlist_t *nvl, const char *name, size_t *sizep);

bool *
nvlist_take_bool_array(nvlist_t *nvl, const char *name, size_t *nitems);

uint64_t **
nvlist_take_number_array(nvlist_t *nvl, const char *name,
    size_t *nitems);

char **
nvlist_take_string_array(nvlist_t *nvl, const char *name,
    size_t *nitems);

nvlist_t **
nvlist_take_nvlist_array(nvlist_t *nvl, const char *name,
    size_t *nitems);

int *
nvlist_take_descriptor_array(nvlist_t *nvl, const char *name,
    size_t *nitems);

void
nvlist_append_bool_array(nvlist_t *nvl, const char *name,
    const bool value);

void
nvlist_append_number_array(nvlist_t *nvl, const char *name,
    const uint64_t value);

void
nvlist_append_string_array(nvlist_t *nvl, const char *name,
    const char * const value);

void
nvlist_append_nvlist_array(nvlist_t *nvl, const char *name,
    const nvlist_t * const value);

void
nvlist_append_descriptor_array(nvlist_t *nvl, const char *name,
    int value);

void
nvlist_free(nvlist_t *nvl, const char *name);

void
nvlist_free_type(nvlist_t *nvl, const char *name, int type);

void
nvlist_free_null(nvlist_t *nvl, const char *name);

void
nvlist_free_bool(nvlist_t *nvl, const char *name);

void
nvlist_free_number(nvlist_t *nvl, const char *name);

void
nvlist_free_string(nvlist_t *nvl, const char *name);

void
nvlist_free_nvlist(nvlist_t *nvl, const char *name);

void
nvlist_free_descriptor(nvlist_t *nvl, const char *name);

void
nvlist_free_binary(nvlist_t *nvl, const char *name);

void
nvlist_free_bool_array(nvlist_t *nvl, const char *name);

void
nvlist_free_number_array(nvlist_t *nvl, const char *name);

void
nvlist_free_string_array(nvlist_t *nvl, const char *name);

void
nvlist_free_nvlist_array(nvlist_t *nvl, const char *name);

void
nvlist_free_descriptor_array(nvlist_t *nvl, const char *name);

like. look at that list. it's giving thorough. ๐Ÿ˜ญ


๐Ÿ’– DESCRIPTION ๐Ÿ’–

so the libnv library is here to let you easily manage name value pairs, and also send and receive them over sockets. iconic behavior honestly. a group (list) of name value pairs is called an nvlist โ€” that's the word, learn it, love it ๐Ÿซถ.

the API supports these data types, and you will be respecting all of them:

  • null (NV_TYPE_NULL) โ€” there is no data associated with the name. it's just a name, vibing. the name is the whole personality.
  • bool (NV_TYPE_BOOL) โ€” the value can be either true or false. binary. two options. very decisive of it.
  • number (NV_TYPE_NUMBER) โ€” the value is a number stored as uint64_t.
  • string (NV_TYPE_STRING) โ€” the value is a C string.
  • nvlist (NV_TYPE_NVLIST) โ€” the value is a nested nvlist. nvlists inside nvlists, it's giving inception ๐ŸŒ€.
  • descriptor (NV_TYPE_DESCRIPTOR) โ€” the value is a file descriptor. โš ๏ธ note that file descriptors can be sent only over unix(4) domain sockets, so don't get cute with other socket types bestie, it will not work.
  • binary (NV_TYPE_BINARY) โ€” the value is a binary buffer.
  • bool array (NV_TYPE_BOOL_ARRAY) โ€” the value is an array of boolean values.
  • number array (NV_TYPE_NUMBER_ARRAY) โ€” the value is an array of numbers, each stored as uint64_t.
  • string array (NV_TYPE_STRING_ARRAY) โ€” the value is an array of C strings.
  • nvlist array (NV_TYPE_NVLIST_ARRAY) โ€” the value is an array of nvlists. heads up: when an nvlist is added to an array, it becomes part of the primary nvlist. traversing these arrays can be done using the nvlist_get_array_next() and nvlist_get_pararr() functions.
  • descriptor array (NV_TYPE_DESCRIPTOR_ARRAY) โ€” the value is an array of files descriptors. (yes, the plural is exactly like that in the original, we don't fix, we don't touch ๐Ÿ’…)

๐ŸŽ€ the opening move: nvlist_create() ๐ŸŽ€

the nvlist_create() function allocates memory and initializes an nvlist. new nvlist, who dis.

and bestie, you get to hand it a flag. here's what's on the menu:

  • NV_FLAG_IGNORE_CASE โ€” perform case-insensitive lookups of provided names. so "BESTIE" and "bestie" are the same girl.
  • NV_FLAG_NO_UNIQUE โ€” names in the nvlist do not have to be unique. chaos mode, enabled.

๐Ÿ’€ the cleanup: nvlist_destroy() ๐Ÿ’€

the nvlist_destroy() function destroys the given nvlist. a few things to know:

  • it does nothing if a NULL nvlist is provided โ€” safe, no crash, no drama.
  • it never modifies the errno global variable. not once. not ever. respectful queen ๐Ÿ‘‘.

๐Ÿ˜ญ the drama: nvlist_error() and nvlist_set_error() ๐Ÿ˜ญ

the nvlist_error() function returns any error value that the nvlist accumulated. and plot twist: if the given nvlist is NULL, the ENOMEM error will be returned. yeah. even NULL has an error. relatable.

the nvlist_set_error() function sets an nvlist to be in the error state. after that:

  • subsequent calls to nvlist_error() will return the given error value.
  • > this function cannot be used to clear the error state from an nvlist. you can set it, you cannot unset it. it's permanent. it's a tattoo.
  • and this function does nothing if the nvlist is already in the error state. once it's in its era, it stays in its era. ๐Ÿ™ˆ

nvlist_empty() โ€” bestie, this one's simple: it hands back true if the nvlist you gave it is empty, and false if it's not. That's it, that's the whole vibe. โš ๏ธ but the nvlist must not be in error state, okay?

nvlist_flags() โ€” returns the flags that were used to create the nvlist, the ones you passed to nvlist_create(). ๐Ÿ’…

nvlist_in_array() โ€” returns true if nvl is part of an array that's a member of another nvlist. Array-girlie behavior, we love to see it. ๐Ÿฆ‹

nvlist_clone() โ€” clones the nvlist you hand it, and the clone shares no resources with its origin. None. Zero. Which also means all the file descriptors hanging out inside the nvlist get duplicated with the dup(2) system call before they get placed in the clone. โœจ

nvlist_dump() โ€” dumps the nvlist content to the file descriptor fd, purely for debugging purposes. It's giving "peek behind the curtain" energy. ๐Ÿ”

nvlist_fdump() โ€” same debugging dump situation, but to the file stream fp instead. ๐ŸŽ€

nvlist_size() โ€” returns the size of the nvlist after converting it to a binary buffer with nvlist_pack(). So it's the packed size, not the vibe size. ๐Ÿ“

nvlist_pack() โ€” converts the nvlist into a binary buffer. It allocates the memory for that buffer, and you are the one who frees it, with free(3). If sizep isn't NULL, the size of the buffer gets stashed right there. It returns NULL if something goes wrong (allocation failure). And heads up: if the nvlist contains any file descriptors, NULL is what you get back. Also the nvlist must not be in error state. ๐Ÿ’€

nvlist_unpack() โ€” the reverse glow-up: turns the given buffer into an nvlist. The flags argument says what type of top level nvlist you're expecting, and those flags get set up with the nvlist_create() function. If the nvlist flags don't match the flags you passed to nvlist_unpack(), the nvlist will not be returned. And every nested nvlist should be checked with nvlist_flags(). Returns NULL on error. ๐Ÿซถ

nvlist_send() โ€” sends the nvlist over the socket given by the sock argument. ๐Ÿ“ฎ Important: an nvlist that contains file descriptors can only be sent over unix(4) domain sockets. Periodt.

nvlist_recv() โ€” receives an nvlist over the socket given by sock. The flags argument defines what type of top level nvlist is expected, set up with nvlist_create(). If the nvlist flags don't match the flags passed to nvlist_recv(), the nvlist won't be returned. Check every nested nvlist with nvlist_flags(). ๐Ÿ’Œ

nvlist_xfer() โ€” sends the nvlist over the socket sock and receives an nvlist over that same socket. Flags situation is identical to the other two: set up with nvlist_create(), must match what you pass to nvlist_xfer() or the nvlist isn't coming back, and every nested nvlist should be checked with nvlist_flags(). Oh and the nvlist you handed in? It's always destroyed. Gone. Consumed. ๐Ÿ”ฅ

nvlist_next() โ€” iterates over the nvlist, returning names and types of the subsequent elements. The cookiep argument is how it figures out which element comes next. Set *cookiep to NULL on the first call and then don't touch it after that. Returning NULL means there are no more elements on the nvlist. typep can be NULL. Elements may not be removed from the nvlist while you're traversing it โ€” do not do it, I'm serious. ๐Ÿ˜ญ The nvlist must not be in error state. Fun detail: nvlist_next() handles cookiep being set to NULL, and in that case the first element is returned, or NULL if the nvlist is empty. That behavior is literally there to make removing the first element from the list easier. Iconic design tbh. ๐Ÿ‘‘

nvlist_exists() โ€” returns true if an element of the given name exists (besides its type), false otherwise. nvlist must not be in error state.

nvlist_exists_type() โ€” returns true if an element with the given name and the given type exists, false otherwise. nvlist must not be in error state.

The type-specific existence squad โ€” nvlist_exists_null(), nvlist_exists_bool(), nvlist_exists_number(), nvlist_exists_string(), nvlist_exists_nvlist(), nvlist_exists_descriptor(), nvlist_exists_binary(), nvlist_exists_bool_array(), nvlist_exists_number_array(), nvlist_exists_string_array(), nvlist_exists_nvlist_array(), nvlist_exists_descriptor_array() โ€” all of these return true if an element with the given name and the given type (the type is determined by which function you called, iykyk) exists, false otherwise. nvlist must not be in error state. ๐Ÿ’–


The add squad โ€” nvlist_add_null(), nvlist_add_bool(), nvlist_add_number(), nvlist_add_string(), nvlist_add_stringf(), nvlist_add_stringv(), nvlist_add_nvlist(), nvlist_add_descriptor(), nvlist_add_binary(), nvlist_add_bool_array(), nvlist_add_number_array(), nvlist_add_string_array(), nvlist_add_nvlist_array(), nvlist_add_descriptor_array() โ€” these add an element to the given nvlist. Here's the tea:

  • Adding a string or a binary buffer? The function allocates memory and copies the data over. ๐Ÿซถ
  • Adding an nvlist? It gets cloned, and the clone is what gets added.
  • Adding a descriptor? It gets duplicated with the dup(2) system call, and the new descriptor is what gets added.
  • The array functions will fail if there are any NULL elements in the array, or if the array pointer itself is NULL.
  • If an error occurs while adding the new element, an internal error is set, and you can inspect it with nvlist_error(). ๐Ÿ™ˆ

The move squad โ€” nvlist_move_string(), nvlist_move_nvlist(), nvlist_move_descriptor(), nvlist_move_binary(), nvlist_move_bool_array(), nvlist_move_number_array(), nvlist_move_string_array(), nvlist_move_nvlist_array(), nvlist_move_descriptor_array() โ€” these add a new element to the nvlist too, but unlike the nvlist_add_<type>() functions they consume the resource you give them. โš ๏ธ For strings, descriptors, or nvlists, every element must be unique, or it could cause a double free. The array functions fail if there are any NULL elements or if the array pointer is NULL. And if an error occurs while adding, the resource is destroyed and an internal error is set, checkable with nvlist_error(). ๐Ÿ’€

The get squad โ€” nvlist_get_bool(), nvlist_get_number(), nvlist_get_string(), nvlist_get_nvlist(), nvlist_get_descriptor(), nvlist_get_binary(), nvlist_get_bool_array(), nvlist_get_number_array(), nvlist_get_string_array(), nvlist_get_nvlist_array(), nvlist_get_descriptor_array() โ€” these return the value that corresponds to the given key name. For strings, nvlists, descriptors, binary, or arrays, the returned resource should not be modified โ€” it still belongs to the nvlist, bestie, don't go messing with it. ๐Ÿšซ If an element with the given name doesn't exist, the program will be aborted. To dodge that whole situation, check that the name exists before you try to grab the value, or use the dnvlist(3) extension, which can hand you a default value when the element is missing. The nvlist must not be in error state. ๐Ÿ˜ญ

nvlist_get_parent() โ€” returns the parent nvlist of the nested nvlist. Family reunion. ๐Ÿซถ

nvlist_get_array_next() โ€” returns the next element from the array, or NULL if the nvlist isn't in an array or if it's the last element. Note: this only works if you added the nvlist array using nvlist_move_nvlist_array() or nvlist_add_nvlist_array(). ๐Ÿ”

nvlist_get_pararr() โ€” returns the next element in the array, or if that's not available, the parent of the nested nvlist. Two-for-one. โœจ

The take squad โ€” nvlist_take_bool(), nvlist_take_number(), nvlist_take_string(), nvlist_take_nvlist(), nvlist_take_descriptor(), nvlist_take_binary(), nvlist_take_bool_array(), nvlist_take_number_array(), nvlist_take_string_array(), nvlist_take_nvlist_array(), nvlist_take_descriptor_array() โ€” these return the value associated with the given name and remove the element from the nvlist. You're taking it, hence the name, slay. ๐ŸŽ€ Now the fine print, and it's a lot:

  • String and binary values? You're responsible for freeing the returned memory with free(3).
  • nvlist? You're responsible for destroying the returned nvlist with nvlist_destroy().
  • Descriptor? You're responsible for closing the returned descriptor with the close(2) system call.
  • If an element with the given name doesn't exist, the program will be aborted. To avoid that, check that the name exists before you try to take the value, or use the dnvlist(3) extension, which can give you a default value when the element is missing.
  • Array of strings or binary values? You're responsible for freeing every element of the array with free(3).
  • Array of nvlists? You're responsible for destroying every element of the array with nvlist_destroy().
  • Descriptors? You're responsible for closing every element of the array with the close(2) system call.
  • And in every case involving an array, you must also free the pointer to the array using free(3).

The nvlist must not be in error state. ๐Ÿ’…

The append squad โ€” nvlist_append_bool_array(), nvlist_append_number_array(), nvlist_append_string_array(), nvlist_append_nvlist_array(), nvlist_append_descriptor_array() โ€” these append an element to the existing array, using the same semantics as the add functions (i.e. the element gets copied when applicable). If the array for a given key doesn't exist, it gets created as if you'd used the nvlist_add_<type>_array() function. The internal error is set on append failure. ๐Ÿ”ฅ

nvlist_free() โ€” removes the element of the given name from the nvlist (besides its type) and frees all the resources associated with it. If an element of the given name doesn't exist, the program will be aborted. The nvlist must not be in error state. ๐Ÿงš

nvlist_free_type() โ€” removes the element of the given name and the given type from the nvlist and frees all resources associated with it. If an element of the given name and the given type doesn't exist, the program will be aborted. The nvlist must not be in error state. ๐Ÿ’€

The typed free squad โ€” nvlist_free_null(), nvlist_free_bool(), nvlist_free_number(), nvlist_free_string(), nvlist_free_nvlist(), nvlist_free_descriptor(), nvlist_free_binary(), nvlist_free_bool_array(), nvlist_free_number_array(), nvlist_free_string_array(), nvlist_free_nvlist_array(), nvlist_free_descriptor_array() โ€” these remove the element of the given name and the given type (the type is determined by the function name) from the nvlist and free all resources associated with it. If an element of the given name and the given type doesn't exist, the program will be aborted. The nvlist must not be in error state. ๐Ÿ˜ค


๐ŸŒˆ NOTES ๐ŸŒˆ

Okay so this part is genuinely so cool: the nvlist_pack() and nvlist_unpack() functions handle the byte-order conversions for you. Which means the binary buffer can be packed and unpacked by hosts with different endianness. No cap, it just works. โœจ๐Ÿฆ‹๐Ÿ’–

โœจ EXAMPLES โœจ

okay bestie this is the part where it all comes together ๐Ÿซถ these are the actual patterns you'll be copy-pasting into your own code, so pay attention, this is the good stuff.


๐Ÿ’Œ preparing an nvlist and sending it over a unix(4) domain socket

the following example demonstrates how to prepare an nvlist and send it over unix(4) domain socket. look at her go โœจ

nvlist_t *nvl;
int fd;

fd = open("/tmp/foo", O_RDONLY);
if (fd < 0)
        err(1, "open(\"/tmp/foo\") failed");

nvl = nvlist_create(0);
/*
 * There is no need to check if nvlist_create() succeeded,
 * as the nvlist_add_<type>() functions can cope.
 * If it failed, nvlist_send() will fail.
 */
nvlist_add_string(nvl, "filename", "/tmp/foo");
nvlist_add_number(nvl, "flags", O_RDONLY);
/*
 * We just want to send the descriptor, so we can give it
 * for the nvlist to consume (that's why we use nvlist_move
 * not nvlist_add).
 */
nvlist_move_descriptor(nvl, "fd", fd);
if (nvlist_send(sock, nvl) < 0) {
        nvlist_destroy(nvl);
        err(1, "nvlist_send() failed");
}
nvlist_destroy(nvl);

notice the little love note in there โ€” you don't need to check whether nvlist_create() succeeded, because the nvlist_add_<type>() functions can cope. and if it did fail, nvlist_send() will fail. the girlies at the API really said "we got you" ๐Ÿ’…

also lowkey iconic: we use nvlist_move_descriptor and not nvlist_add because we want the nvlist to consume the descriptor. move = she's yours now, bestie ๐ŸŽ€


๐Ÿฆ‹ receiving an nvlist and getting data out of it

nvlist_t *nvl;
const char *command;
char *filename;
int fd;

nvl = nvlist_recv(sock, 0);
if (nvl == NULL)
        err(1, "nvlist_recv() failed");

/* For command we take pointer to nvlist's buffer. */
command = nvlist_get_string(nvl, "command");
/*
 * For filename we remove it from the nvlist and take
 * ownership of the buffer.
 */
filename = nvlist_take_string(nvl, "filename");
/* The same for the descriptor. */
fd = nvlist_take_descriptor(nvl, "fd");

printf("command=%s filename=%s fd=%d0, command, filename, fd);

nvlist_destroy(nvl);
free(filename);
close(fd);
/* command was freed by nvlist_destroy() */

the tea here is the difference between get and take, and it's giving main character energy:

  • nvlist_get_string() โ€” you just get a pointer to the nvlist's own buffer. she still owns it, you're just borrowing. ๐Ÿ’–
  • nvlist_take_string() โ€” you remove it from the nvlist and take ownership of the buffer. it's yours now, you free it.
  • nvlist_take_descriptor() โ€” same deal, but for the descriptor.
fr fr, remember: command was freed by nvlist_destroy(), but filename is on you โ€” hence the free(filename) and close(fd). don't leave her hanging ๐Ÿ™ˆ

๐ŸŒธ iterating over an nvlist

nvlist_t *nvl;
const char *name;
void *cookie;
int type;

nvl = nvlist_recv(sock, 0);
if (nvl == NULL)
        err(1, "nvlist_recv() failed");

cookie = NULL;
while ((name = nvlist_next(nvl, &type, &cookie)) != NULL) {
        printf("%s=", name);
        switch (type) {
        case NV_TYPE_NUMBER:
                printf("%ju", (uintmax_t)nvlist_get_number(nvl, name));
                break;
        case NV_TYPE_STRING:
                printf("%s", nvlist_get_string(nvl, name));
                break;
        default:
                printf("N/A");
                break;
        }
        printf("\n");
}

see the cookie? that's your little bookmark ๐Ÿช โ€” set it to NULL to start, and nvlist_next() hands you the next name and type, updating the cookie as she goes. when she returns NULL, you're done, periodt.


๐Ÿงš iterating over every nested nvlist

nvlist_t *nvl;
const char *name;
void *cookie;
int type;

nvl = nvlist_recv(sock, 0);
if (nvl == NULL)
        err(1, "nvlist_recv() failed");

cookie = NULL;
do {
        while ((name = nvlist_next(nvl, &type, &cookie)) != NULL) {
                if (type == NV_TYPE_NVLIST) {
                        nvl = nvlist_get_nvlist(nvl, name);
                        cookie = NULL;
                }
        }
} while ((nvl = nvlist_get_parent(nvl, &cookie)) != NULL);

nested nvlists are like a whole friend group of nvlists ๐Ÿ’œ when you hit an NV_TYPE_NVLIST, you dive into it with nvlist_get_nvlist() and reset that cookie to NULL because you're in a brand new list. then nvlist_get_parent() walks you back up. it's a whole journey.


๐ŸŒˆ iterating over every nested nvlist and every nvlist element

nvlist_t *nvl;
const nvlist_t * const *array;
const char *name;
void *cookie;
int type;

nvl = nvlist_recv(sock, 0);
if (nvl == null)
        err(1, "nvlist_recv() failed");

cookie = null;
do {
        while ((name = nvlist_next(nvl, &type, &cookie)) != NULL) {
                if (type == NV_TYPE_NVLIST) {
                        nvl = nvlist_get_nvlist(nvl, name);
                        cookie = NULL;
                } else if (type == NV_TYPE_NVLIST_ARRAY) {
                        nvl = nvlist_get_nvlist_array(nvl, name, NULL)[0];
                        cookie = NULL;
                }
        }
} while ((nvl = nvlist_get_pararr(nvl, &cookie)) != NULL);

now we also handle NV_TYPE_NVLIST_ARRAY, because arrays of nvlists are absolutely valid and we are not ignoring them ๐Ÿ’… nvlist_get_pararr() walks back up through the parent array, no cap.


๐ŸŽ€ or alternatively:

nvlist_t *nvl, *tmp;
const nvlist_t * const *array;
const char *name;
void *cookie;
int type;

nvl = nvlist_recv(sock, 0);
if (nvl == null)
        err(1, "nvlist_recv() failed");

cooke = NULL;
tmp = nvl;
do {
        do {
                nvl = tmp;
                while ((name = nvlist_next(nvl, &type, &cookie)) != NULL) {
                        if (type == NV_TYPE_NVLIST) {
                                nvl = nvlist_get_nvlist(nvl,
                                    name);
                                cookie = NULL;
                        } else if (type == NV_TYPE_NVLIST_ARRAY) {
                                nvl = nvlist_get_nvlist_array(nvl, name,
                                    NULL)[0];
                                cookie = NULL;
                        }
                }
                cookie = NULL;
        } while ((tmp = nvlist_get_array_next(nvl)) != NULL);
} while ((tmp = nvlist_get_parent(nvl, &cookie)) != NULL);

two tmps, two nested loops, one absolutely unhinged but somehow gorgeous traversal strategy ๐Ÿ”ฅ the inner doโ€ฆwhile rides nvlist_get_array_next() through the array, and the outer one climbs home with nvlist_get_parent(). obsessed.


โญ SEE ALSO โญ

close(2), dup(2), open(2), err(3), free(3), printf(3), unix(4)


๐Ÿ“œ HISTORY ๐Ÿ“œ

the libnv library appeared in FreeBSD 11.0. she's a real one ๐Ÿ’–


๐Ÿ‘‘ AUTHORS ๐Ÿ‘‘

the libnv library was implemented by Pawel Jakub Dawidek <pawel@dawidek.net> under sponsorship from the FreeBSD Foundation. iconic behaviour, we love to see it ๐Ÿซถโœจ


and that's a wrap on NV(9) bestie!! this page came from the Debian packaging, dated June 19, 2018 โ€” so go forth, build your nvlists, and remember: move โ‰  add, cookies are your friends, and always free what you take ๐Ÿ’…๐Ÿ’– bye!!

๐Ÿ“ฆ where this page comes from