ZCONFIG(3)
π¦ zconfig β CZMQ Manual, bestie edition π¦
okay so we're doing ZCONFIG(3) from the CZMQ Manual, and it's a long one so it comes in 3 parts. this is part 1. grab a snack. π
β¨ NAME β¨
zconfig β a whole entire class for working with config files written in rfc.zeromq.org/spec:4/ZPL. yes, that's the vibe. π
π SYNOPSIS π
first things first, heads up: this is a stable class, and it may not change except for emergencies. it's provided in stable builds. iconic, no notes. it also has draft methods, which may change over time β and those are not in stable releases by default. to get them you use --enable-drafts to enable. π
// This is a stable class, and may not change except for emergencies. It
// is provided in stable builds.
// This class has draft methods, which may change over time. They are not
// in stable releases, by default. Use --enable-drafts to enable.
//
typedef int (zconfig_fct) (
zconfig_t *self, void *arg, int level);
that's our callback type right there β a zconfig_fct takes a zconfig_t *self, a void *arg, and an int level, and gives you back an int. slay.
now. the functions. the way there are SO many and they're all so good. π
// Create new config item
CZMQ_EXPORT zconfig_t *
zconfig_new (const char *name, zconfig_t *parent);
// Load a config tree from a specified ZPL text file; returns a zconfig_t
// reference for the root, if the file exists and is readable. Returns NULL
// if the file does not exist.
CZMQ_EXPORT zconfig_t *
zconfig_load (const char *filename);
// Equivalent to zconfig_load, taking a format string instead of a fixed
// filename.
CZMQ_EXPORT zconfig_t *
zconfig_loadf (const char *format, ...) CHECK_PRINTF (1);
// Destroy a config item and all its children
CZMQ_EXPORT void
zconfig_destroy (zconfig_t **self_p);
// Return name of config item
CZMQ_EXPORT char *
zconfig_name (zconfig_t *self);
// Return value of config item
CZMQ_EXPORT char *
zconfig_value (zconfig_t *self);
// Insert or update configuration key with value
CZMQ_EXPORT void
zconfig_put (zconfig_t *self, const char *path, const char *value);
// Equivalent to zconfig_put, accepting a format specifier and variable
// argument list, instead of a single string value.
CZMQ_EXPORT void
zconfig_putf (zconfig_t *self, const char *path, const char *format, ...) CHECK_PRINTF (3);
// Get value for config item into a string value; leading slash is optional
// and ignored.
CZMQ_EXPORT char *
zconfig_get (zconfig_t *self, const char *path, const char *default_value);
// Set config item name, name may be NULL
CZMQ_EXPORT void
zconfig_set_name (zconfig_t *self, const char *name);
// Set new value for config item. The new value may be a string, a printf
// format, or NULL. Note that if string may possibly contain '%', or if it
// comes from an insecure source, you must use '%s' as the format, followed
// by the string.
CZMQ_EXPORT void
zconfig_set_value (zconfig_t *self, const char *format, ...) CHECK_PRINTF (2);
// Find our first child, if any
CZMQ_EXPORT zconfig_t *
zconfig_child (zconfig_t *self);
// Find our first sibling, if any
CZMQ_EXPORT zconfig_t *
zconfig_next (zconfig_t *self);
// Find a config item along a path; leading slash is optional and ignored.
CZMQ_EXPORT zconfig_t *
zconfig_locate (zconfig_t *self, const char *path);
// Locate the last config item at a specified depth
CZMQ_EXPORT zconfig_t *
zconfig_at_depth (zconfig_t *self, int level);
// Execute a callback for each config item in the tree; returns zero if
// successful, else -1.
CZMQ_EXPORT int
zconfig_execute (zconfig_t *self, zconfig_fct handler, void *arg);
// Add comment to config item before saving to disk. You can add as many
// comment lines as you like. If you use a null format, all comments are
// deleted.
CZMQ_EXPORT void
zconfig_set_comment (zconfig_t *self, const char *format, ...) CHECK_PRINTF (2);
// Return comments of config item, as zlist.
CZMQ_EXPORT zlist_t *
zconfig_comments (zconfig_t *self);
// Save a config tree to a specified ZPL text file, where a filename
// "-" means dump to standard output.
CZMQ_EXPORT int
zconfig_save (zconfig_t *self, const char *filename);
// Equivalent to zconfig_save, taking a format string instead of a fixed
// filename.
CZMQ_EXPORT int
zconfig_savef (zconfig_t *self, const char *format, ...) CHECK_PRINTF (2);
// Report filename used during zconfig_load, or NULL if none
CZMQ_EXPORT const char *
zconfig_filename (zconfig_t *self);
// Reload config tree from same file that it was previously loaded from.
// Returns 0 if OK, -1 if there was an error (and then does not change
// existing data).
CZMQ_EXPORT int
zconfig_reload (zconfig_t **self_p);
// Load a config tree from a memory chunk
CZMQ_EXPORT zconfig_t *
zconfig_chunk_load (zchunk_t *chunk);
// Save a config tree to a new memory chunk
CZMQ_EXPORT zchunk_t *
zconfig_chunk_save (zconfig_t *self);
// Load a config tree from a null-terminated string
// Caller owns return value and must destroy it when done.
CZMQ_EXPORT zconfig_t *
zconfig_str_load (const char *string);
// Save a config tree to a new null terminated string
// Caller owns return value and must destroy it when done.
CZMQ_EXPORT char *
zconfig_str_save (zconfig_t *self);
// Return true if a configuration tree was loaded from a file and that
// file has changed in since the tree was loaded.
CZMQ_EXPORT bool
zconfig_has_changed (zconfig_t *self);
// Print the config file to open stream
CZMQ_EXPORT void
zconfig_fprint (zconfig_t *self, FILE *file);
// Print properties of object
CZMQ_EXPORT void
zconfig_print (zconfig_t *self);
// Self test of this class
CZMQ_EXPORT void
zconfig_test (bool verbose);
okay and NOW the draft girls π these only show up if CZMQ_BUILD_DRAFT_API is defined, and they come with a warning, so listen up:
#ifdef CZMQ_BUILD_DRAFT_API
// *** Draft method, for development use, may change without warning ***
// Create copy of zconfig, caller MUST free the value
// Create copy of config, as new zconfig object. Returns a fresh zconfig_t
// object. If config is null, or memory was exhausted, returns null.
// Caller owns return value and must destroy it when done.
CZMQ_EXPORT zconfig_t *
zconfig_dup (zconfig_t *self);
// *** Draft method, for development use, may change without warning ***
// Destroy subtree (all children)
CZMQ_EXPORT void
zconfig_remove_subtree (zconfig_t *self);
// *** Draft method, for development use, may change without warning ***
// Destroy node and subtree (all children)
CZMQ_EXPORT void
zconfig_remove (zconfig_t **self_p);
#endif // CZMQ_BUILD_DRAFT_API
β οΈ those three are literally marked "for development use, may change without warning". do not build your whole personality on them. π
and then, straight from the source, there's this little note:
Please add '@interface' section in './../src/zconfig.c'.
not me being told to add a section π anyway.
πΈ DESCRIPTION πΈ
so what does it DO, bestie? it lets applications load, work with, and save configuration files. it implements rfc.zeromq.org/spec:4/ZPL, which is a simple structured text format for configuration files. cute, structured, efficient. we love her. π«Ά
here's an example ZPL stream AND its corresponding config structure, side by side, because we're visual learners:
context
iothreads = 1
verbose = 1 # Ask for a trace
main
type = zqueue # ZMQ_DEVICE type
frontend
option
hwm = 1000
swap = 25000000 # 25MB
bind = 'inproc://addr1'
bind = 'ipc://addr2'
backend
bind = inproc://addr3
root Down = child
| Across = next
v
context-->main
| |
| v
| type=queue-->frontend-->backend
| | |
| | v
| | bind=inproc://addr3
| v
| option-->bind=inproc://addr1-->bind=ipc://addr2
| |
| v
| hwm=1000-->swap=25000000
v
iothreads=1-->verbose=false
read the tree like a map, bestie: going down means it's a child, going across means it's the next sibling. that's the whole mental model and honestly? it's giving flowchart. π¦
π₯ EXAMPLE π₯
this one's straight from the zconfig_test method. real code, real assertions, real energy:
const char *SELFTEST_DIR_RW = "src/selftest-rw";
const char *testbasedir = ".test_zconfig";
const char *testfile = "test.cfg";
char *basedirpath = NULL; // subdir in a test, under SELFTEST_DIR_RW
char *filepath = NULL; // pathname to testfile in a test, in dirpath
basedirpath = zsys_sprintf ("%s/%s", SELFTEST_DIR_RW, testbasedir);
assert (basedirpath);
filepath = zsys_sprintf ("%s/%s", basedirpath, testfile);
assert (filepath);
// Make sure old aborted tests do not hinder us
zdir_t *dir = zdir_new (basedirpath, NULL);
if (dir) {
zdir_remove (dir, true);
zdir_destroy (&dir);
}
zsys_file_delete (filepath);
zsys_dir_delete (basedirpath);
// Create temporary directory for test files
zsys_dir_create (basedirpath);
zconfig_t *root = zconfig_new ("root", NULL);
assert (root);
zconfig_t *section, *item;
section = zconfig_new ("headers", root);
assert (section);
item = zconfig_new ("email", section);
assert (item);
zconfig_set_value (item, "some@random.com");
item = zconfig_new ("name", section);
assert (item);
zconfig_set_value (item, "Justin Kayce");
zconfig_putf (root, "/curve/secret-key", "%s", "Top Secret");
zconfig_set_comment (root, " CURVE certificate");
zconfig_set_comment (root, " -----------------");
assert (zconfig_comments (root));
zconfig_save (root, filepath);
zconfig_destroy (&root);
root = zconfig_load (filepath);
if (verbose)
zconfig_save (root, "-");
assert (streq (zconfig_filename (root), filepath));
char *email = zconfig_get (root, "/headers/email", NULL);
assert (email);
assert (streq (email, "some@random.com"));
char *passwd = zconfig_get (root, "/curve/secret-key", NULL);
assert (passwd);
assert (streq (passwd, "Top Secret"));
zconfig_savef (root, "%s/%s", basedirpath, testfile);
assert (!zconfig_has_changed (root));
int rc = zconfig_reload (&root);
assert (rc == 0);
assert (!zconfig_has_changed (root));
zconfig_destroy (&root);
// Test chunk load/save
root = zconfig_new ("root", NULL);
assert (root);
section = zconfig_new ("section", root);
assert (section);
item = zconfig_new ("value", section);
assert (item);
zconfig_set_value (item, "somevalue");
zconfig_t *search = zconfig_locate (root, "section/value");
assert (search == item);
zchunk_t *chunk = zconfig_chunk_save (root);
assert (strlen ((char *) zchunk_data (chunk)) == 32);
char *string = zconfig_str_save (root);
assert (string);
assert (streq (string, (char *) zchunk_data (chunk)));
freen (string);
assert (chunk);
zconfig_destroy (&root);
root = zconfig_chunk_load (chunk);
assert (root);
char *value = zconfig_get (root, "/section/value", NULL);
assert (value);
assert (streq (value, "somevalue"));
obsessed with this test honestly. look at her β create the dir, clean up old mess, build a tree, slap a comment on it, save, load, zconfig_save (root, "-") when verbose is on to dump to standard output, check zconfig_filename matches, pull values back out with zconfig_get, savef it, confirm !zconfig_has_changed (root), reload with rc == 0, and then the chunk/string round-trip. and strlen ((char *) zchunk_data (chunk)) == 32?? we're counting bytes out here. π
and that's part 1, bestie β part 2 is right behind me, stay tuned π
okay so we're literally in the middle of the test code and it is NOT done being iconic yet. this chunk is the "can we please not write to a folder that doesn't exist" vibe check, and then it flexes on zconfig_str_load by loading a whole config out of a string like it's nothing π
// Test config can't be saved to a file in a path that doesn't
// exist or isn't writable
rc = zconfig_savef (root, "%s/path/that/doesnt/exist/%s", basedirpath, testfile);
assert (rc == -1);
zconfig_destroy (&root);
zchunk_destroy (&chunk);
// Test str_load
zconfig_t *config = zconfig_str_load (
"malamute\n"
" endpoint = ipc://@/malamute\n"
" producer = STREAM\n"
" consumer\n"
" STREAM2 = .*\n"
" STREAM3 = HAM\n"
"server\n"
" verbose = true\n"
);
assert (config);
assert (streq (zconfig_get (config, "malamute/endpoint", NULL), "ipc://@/malamute"));
assert (streq (zconfig_get (config, "malamute/producer", NULL), "STREAM"));
assert (zconfig_locate (config, "malamute/consumer"));
zconfig_t *c = zconfig_child (zconfig_locate (config, "malamute/consumer"));
assert (c);
assert (streq (zconfig_name (c), "STREAM2"));
assert (streq (zconfig_value (c), ".*"));
c = zconfig_next (c);
assert (c);
assert (streq (zconfig_name (c), "STREAM3"));
assert (streq (zconfig_value (c), "HAM"));
c = zconfig_next (c);
assert (!c);
assert (streq (zconfig_get (config, "server/verbose", NULL), "true"));
zconfig_t *dup = zconfig_dup (config);
assert (dup);
assert (streq (zconfig_get (dup, "server/verbose", NULL), "true"));
zconfig_destroy (&dup);
zconfig_destroy (&config);
so the savef call aims at a path with %s/path/that/doesnt/exist/%s and the assert is like "yeah bestie, that's a -1, we expected that". then zconfig_str_load slurps a config straight out of a string β malamute, server, the whole cast π β and every zconfig_get / zconfig_locate / zconfig_child / zconfig_next check comes back slaying. zconfig_dup makes a whole copy and the copy still knows server/verbose is true, then both get zconfig_destroy'd. no leaks, no drama.
π§Ή the subtree removal era
next up: what actually happens when you zconfig_remove_subtree something. read the asserts closely, they're the whole plot π
// Test subtree removal
{
zconfig_t *root = zconfig_str_load (
"context\n"
" iothreads = 1\n"
" verbose = 1 # Ask for a trace\n"
"main\n"
" type = zqueue # ZMQ_DEVICE type\n"
" frontend\n"
" option\n"
" hwm = 1000\n"
" swap = 25000000 # 25MB\n"
" bind = 'inproc://addr1'\n"
" bind = 'ipc://addr2'\n"
" backend\n"
" bind = inproc://addr3\n"
);
// no subtree
zconfig_t *to_delete = zconfig_locate (root, "context/iothreads");
assert (to_delete);
zconfig_remove_subtree (to_delete);
zconfig_t *check = zconfig_locate (root, "context/iothreads");
assert (check);
assert (streq (zconfig_value (check), "1"));
check = zconfig_locate (root, "context/verbose");
assert (check);
assert (streq (zconfig_value (check), "1"));
// existing subtree
to_delete = zconfig_locate (root, "main/frontend/option");
assert (to_delete);
zconfig_remove_subtree (to_delete);
check = zconfig_locate (root, "main/frontend/option/hwm");
assert (check == NULL);
check = zconfig_locate (root, "main/frontend/option/swap");
assert (check == NULL);
check = zconfig_locate (root, "main/frontend/option");
assert (check);
assert (streq (zconfig_value (check), ""));
check = zconfig_next (check);
assert (check);
assert (streq (zconfig_name (check), "bind"));
assert (streq (zconfig_value (check), "inproc://addr1"));
check = zconfig_next (check);
assert (check);
assert (streq (zconfig_name (check), "bind"));
assert (streq (zconfig_value (check), "ipc://addr2"));
assert (zconfig_next (check) == NULL);
to_delete = zconfig_locate (root, "main/frontend");
assert (to_delete);
zconfig_remove_subtree (to_delete);
check = zconfig_locate (root, "main/frontend/option/hwm");
assert (check == NULL);
check = zconfig_locate (root, "main/frontend/option/swap");
assert (check == NULL);
check = zconfig_locate (root, "main/frontend/option");
assert (check == NULL);
check = zconfig_locate (root, "main/frontend/bind");
assert (check == NULL);
check = zconfig_locate (root, "main/frontend");
assert (check);
assert (streq (zconfig_value (check), ""));
assert (zconfig_child (check) == NULL);
check = zconfig_next (check);
assert (check);
assert (streq (zconfig_name (check), "backend"));
assert (streq (zconfig_value (check), ""));
to_delete = zconfig_locate (root, "main");
assert (to_delete);
zconfig_remove_subtree (to_delete);
check = zconfig_locate (root, "main/type");
assert (check == NULL);
check = zconfig_locate (root, "main/frontend");
assert (check == NULL);
check = zconfig_locate (root, "main/backend");
assert (check == NULL);
check = zconfig_locate (root, "main");
assert (check);
// root
zconfig_remove_subtree (root);
assert (root);
assert (zconfig_child (root) == NULL);
check = zconfig_locate (root, "main");
assert (check == NULL);
check = zconfig_locate (root, "context");
assert (check == NULL);
zconfig_destroy (&root);
}
ngl the pattern here is so satisfying:context/iothreadshas no kids, so afterzconfig_remove_subtreeit's still there and still1. butmain/frontend/optionhashwmandswapβ those two get yeeted (== NULL), whileoptionitself survives with an empty value and itsbindsiblings still lined up. same story one level up. and when you call it onroot,rootstays alive but loses every child. no cap. π
ποΈ removing nodes vs. removing subtrees
now we're comparing zconfig_remove against zconfig_remove_subtree, and the pointer-passing is the moment of the whole test β zconfig_remove (&to_delete). also the code literally tells you that calling it on root is equivalent to zconfig_destroy (&root). periodt.
// Test node and subtree removal
{
zconfig_t *root = zconfig_str_load (
"A1 = abc\n"
" x\n"
" 1\n"
" 2\n"
" y = 1 # Ask for a trace\n"
"A2\n"
" B1 = zqueue # ZMQ_DEVICE type\n"
" B2\n"
" C1\n"
" hwm = 1000\n"
" swap = 25000000 # 25MB\n"
" C2 = 50\n"
" C3\n"
" bind = addr3\n"
" B3\n"
" bind = inproc://addr4\n"
" B4 = Ignac\n"
" z = 5\n"
"A3\n"
"A4\n"
);
zconfig_t *to_delete = zconfig_locate (root, "A2/B2/C3");
assert (to_delete);
zconfig_remove (&to_delete);
zconfig_t *check = zconfig_locate (root, "A2/B2/C2");
assert (check);
assert (streq (zconfig_value (check), "50"));
assert (zconfig_next (check) == NULL);
assert (zconfig_locate (root, "A2/B2/C3/bind") == NULL);
assert (zconfig_locate (root, "A2/B2/C3") == NULL);
to_delete = zconfig_locate (root, "A2/B2");
assert (to_delete);
zconfig_remove (&to_delete);
check = zconfig_locate (root, "A2");
assert (check);
check = zconfig_child (check);
assert (check);
assert (streq (zconfig_name (check), "B1"));
assert (streq (zconfig_value (check), "zqueue"));
check = zconfig_next (check);
assert (check);
assert (streq (zconfig_name (check), "B3"));
assert (streq (zconfig_value (check), ""));
assert (zconfig_locate (root, "A2/B2/C1") == NULL);
assert (zconfig_locate (root, "A2/B2/C2") == NULL);
assert (zconfig_locate (root, "A2/B2") == NULL);
assert (zconfig_locate (root, "A2/B4"));
to_delete = zconfig_locate (root, "A2/B1");
assert (to_delete);
zconfig_remove (&to_delete);
check = zconfig_locate (root, "A2");
assert (check);
check = zconfig_child (check);
assert (check);
assert (streq (zconfig_name (check), "B3"));
assert (streq (zconfig_value (check), ""));
check = zconfig_next (check);
assert (check);
assert (streq (zconfig_name (check), "B4"));
assert (streq (zconfig_value (check), "Ignac"));
assert (zconfig_next (check) == NULL);
assert (zconfig_locate (root, "A2/B1") == NULL);
assert (zconfig_locate (root, "A2/B2") == NULL);
to_delete = zconfig_locate (root, "A2/B3");
assert (to_delete);
zconfig_remove (&to_delete);
check = zconfig_locate (root, "A2");
assert (check);
check = zconfig_child (check);
assert (check);
assert (streq (zconfig_name (check), "B4"));
assert (streq (zconfig_value (check), "Ignac"));
assert (zconfig_next (check) == NULL);
to_delete = zconfig_locate (root, "A2");
assert (to_delete);
zconfig_remove (&to_delete);
check = zconfig_locate (root, "A1");
assert (check);
check = zconfig_next (check);
assert (check);
assert (streq (zconfig_name (check), "A3"));
assert (zconfig_locate (root, "A2/B4") == NULL);
assert (zconfig_locate (root, "A2") == NULL);
to_delete = zconfig_locate (root, "A1");
assert (to_delete);
zconfig_remove (&to_delete);
check = zconfig_child (root);
assert (check);
assert (streq (zconfig_name (check), "A3"));
assert (zconfig_locate (root, "A1/x/1") == NULL);
assert (zconfig_locate (root, "A1/x") == NULL);
assert (zconfig_locate (root, "A1/y") == NULL);
assert (zconfig_locate (root, "A3"));
assert (zconfig_locate (root, "A4"));
// called on root should be equivalent to zconfig_destroy (&root)
zconfig_remove (&root);
}
okay so watch how zconfig_remove takes the address of the pointer and nukes the whole node (kids included, unlike subtree removal which keeps the node). every time a B gets removed, the assert chain walks A2's remaining children and confirms the survivors are still in the right order, and the deleted names == NULL. the final line is the iconique one:
// called on root should be equivalent to zconfig_destroy (&root)
zconfig_remove (&root);
yes bestie, zconfig_remove (&root) on the root = zconfig_destroy (&root). that's the whole flex. π
π§Ό cleanup era (the tests leave no trace)
// Delete all test files
dir = zdir_new (basedirpath, NULL);
assert (dir);
zdir_remove (dir, true);
zdir_destroy (&dir);
zstr_free (&basedirpath);
zstr_free (&filepath);
#if defined (__WINDOWS__)
zsys_shutdown();
#endif
and scene: zdir_new opens basedirpath, zdir_remove (dir, true) wipes it, zdir_destroy (&dir) closes it, and both strings get zstr_free'd. on __WINDOWS__ there's a zsys_shutdown() tucked behind the #if. leaving the playground spotless is self-care actually πΈ
π«Ά AUTHORS π«Ά
The czmq manual was written by the authors in the AUTHORS file. yeah, the whole manual β this one included β is a group project and they're all in that file, go say hi π
β¨ RESOURCES β¨
Main web site:
- (that's the line, bestie β the page points you at the main web site and doesn't paste a URL here)
Report bugs to the email zeromq-dev@lists.zeromq.org[1]
and no we are not done, there's more page left so don't you dare close the tab π¦
π COPYRIGHT π
okay so legal bit, but it's important bestie, don't skip it π
Copyright (c) the Contributors as noted in the AUTHORS file. This file is part of CZMQ, the high-level C binding for 0MQ: http://czmq.zeromq.org. This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. LICENSE included with the czmq distribution.
fr fr, if you're shipping this anywhere, go read the LICENSE file that comes with the czmq distribution. it's giving "adulting" but it's literally the whole vibe of keeping this project alive π«Ά
π NOTES π
- zeromq-dev@lists.zeromq.org mailto:zeromq-dev@lists.zeromq.org
that's the mailing list, babes β hit it up if you wanna talk to the actual humans behind all this β¨
and that's a wrap on ZCONFIG(3), bestie π you now know zconfig better than half the people who've been linking against it for years, no cap π
this lil' glow-up was brought to you by CZMQ 4.2.1, dated 01/16/2026, and it lives under the name ZCONFIG(3) β so if you ever need to find it again, that's the magic incantation π β¨
go config something iconic π¦π periodt
π¦ where this page comes from
- fedora 44: czmq-devel 4.2.1-24.fc44 Β· MPL-2.0
- fedora 44: czmq-devel 4.2.1-24.fc44 Β· MPL-2.0
- ubuntu resolute: libczmq-dev 4.2.1-3build1 Β· MPL-2.0
- ubuntu stonking: libczmq-dev 4.2.1-3build1 Β· MPL-2.0
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms