ZFILE(3)
π ZFILE(3) π β straight outta the CZMQ Manual, bestie π
β¨ NAME β¨
zfile β the class that hands you methods to work with files in a portable fashion. No cap, that's the whole job description. π
β¨ SYNOPSIS β¨
okay so first things first, read the vibe check at the top of the header, because it's giving very important:
// This is a stable class, and may not change except for emergencies. It
// is provided in stable builds.
// If file exists, populates properties. CZMQ supports portable symbolic
// links, which are files with the extension ".ln". A symbolic link is a
// text file containing one line, the filename of a target file. Reading
// data from the symbolic link actually reads from the target file. Path
// may be NULL, in which case it is not used.
CZMQ_EXPORT zfile_t *
zfile_new (const char *path, const char *name);
Translation: if the file's already there, it populates the properties. And yes, CZMQ does portable symbolic links β those are files with the extension ".ln", and inside is just one line of text: the filename of a target file. Read from the symlink and you're actually reading from the target. And path can be NULL, in which case it justβ¦ isn't used. Simple as. β¨
Next up, the teardown:
// Destroy a file item
CZMQ_EXPORT void
zfile_destroy (zfile_t **self_p);
Yep. Destroy the file item. It's giving closure, literally. π
// Duplicate a file item, returns a newly constructed item. If the file
// is null, or memory was exhausted, returns null.
// Caller owns return value and must destroy it when done.
CZMQ_EXPORT zfile_t *
zfile_dup (zfile_t *self);
Dup it π― β you get a brand new item back. If the file is null, or memory was exhausted, you get null. And heads up bestie: the caller owns the return value and MUST destroy it when done. That's your baby now.
// Return file name, remove path if provided
CZMQ_EXPORT const char *
zfile_filename (zfile_t *self, const char *path);
Gives you the file name, and it strips the path if you gave it one. π§
// Refresh file properties from disk; this is not done automatically
// on access methods, otherwise it is not possible to compare directory
// snapshots.
CZMQ_EXPORT void
zfile_restat (zfile_t *self);
refresh those properties from disk! And no, it does NOT happen automatically on the access methods β because if it did, you couldn't compare directory snapshots. iykyk. π
// Return when the file was last modified. If you want this to reflect the
// current situation, call zfile_restat before checking this property.
CZMQ_EXPORT time_t
zfile_modified (zfile_t *self);
Last modified time, babes. Want it to reflect the current situation? Call zfile_restat first. Fr fr.
// Return the last-known size of the file. If you want this to reflect the
// current situation, call zfile_restat before checking this property.
CZMQ_EXPORT off_t
zfile_cursize (zfile_t *self);
The last-known size. Same deal β zfile_restat first if you want the truth, the whole truth, nothing but. π
// Return true if the file is a directory. If you want this to reflect
// any external changes, call zfile_restat before checking this property.
CZMQ_EXPORT bool
zfile_is_directory (zfile_t *self);
// Return true if the file is a regular file. If you want this to reflect
// any external changes, call zfile_restat before checking this property.
CZMQ_EXPORT bool
zfile_is_regular (zfile_t *self);
// Return true if the file is readable by this process. If you want this to
// reflect any external changes, call zfile_restat before checking this
// property.
CZMQ_EXPORT bool
zfile_is_readable (zfile_t *self);
// Return true if the file is writeable by this process. If you want this
// to reflect any external changes, call zfile_restat before checking this
// property.
CZMQ_EXPORT bool
zfile_is_writeable (zfile_t *self);
Four lil vibe checks in a row: is it a directory? π is it a regular file? π is it readable by this process? π is it writeable by this process? βοΈ All of them say the same thing β want it to reflect external changes? zfile_restat. You know the drill by now, bestie.
// Check if file has stopped changing and can be safely processed.
// Updates the file statistics from disk at every call.
CZMQ_EXPORT bool
zfile_is_stable (zfile_t *self);
Has the file stopped changing and can it be safely processed? π¦ And unlike the others, this one updates the file statistics from disk at every single call. So it's always current. Iconic behaviour honestly.
// Return true if the file was changed on disk since the zfile_t object
// was created, or the last zfile_restat() call made on it.
CZMQ_EXPORT bool
zfile_has_changed (zfile_t *self);
True if the file changed on disk since the zfile_t object was created β or since the last zfile_restat() call on it. Either/or. π
// Remove the file from disk
CZMQ_EXPORT void
zfile_remove (zfile_t *self);
Bye bye file, off the disk you go. π«Ά
// Open file for reading
// Returns 0 if OK, -1 if not found or not accessible
CZMQ_EXPORT int
zfile_input (zfile_t *self);
Open it for reading. Returns 0 if OK, -1 if not found or not accessible. That's it, that's the contract. π
// Open file for writing, creating directory if needed
// File is created if necessary; chunks can be written to file at any
// location. Returns 0 if OK, -1 if error.
CZMQ_EXPORT int
zfile_output (zfile_t *self);
Open it for writing β and it creates the directory if needed, slay. The file gets created if necessary, and you can write chunks to the file at any location. Returns 0 if OK, -1 if error.
// Read chunk from file at specified position. If this was the last chunk,
// sets the eof property. Returns a null chunk in case of error.
// Caller owns return value and must destroy it when done.
CZMQ_EXPORT zchunk_t *
zfile_read (zfile_t *self, size_t bytes, off_t offset);
Reads a chunk from the file at the position you specify. If it was the last chunk, it sets the eof property. Error? You get a null chunk. And again β caller owns the return value, must destroy it when done. Don't leave it orphaned, that's not the vibe. π
// Returns true if zfile_read() just read the last chunk in the file.
CZMQ_EXPORT bool
zfile_eof (zfile_t *self);
True if zfile_read() just read the last chunk. Main character energy for the final chunk. β
// Write chunk to file at specified position
// Return 0 if OK, else -1
CZMQ_EXPORT int
zfile_write (zfile_t *self, zchunk_t *chunk, off_t offset);
Write that chunk to the file at the specified position. 0 if OK, else -1. Periodt.
// Read next line of text from file. Returns a pointer to the text line,
// or NULL if there was nothing more to read from the file.
CZMQ_EXPORT const char *
zfile_readln (zfile_t *self);
Next line of text, coming right up. π You get a pointer to the text line, or NULL if there was nothing more to read. That's the whole story.
// Close file, if open
CZMQ_EXPORT void
zfile_close (zfile_t *self);
Close the file β if it's open. If it's not, whatever, no drama. πΈ
// Return file handle, if opened
CZMQ_EXPORT FILE *
zfile_handle (zfile_t *self);
Hands you the file handle, if it was opened. π
// Calculate SHA1 digest for file, using zdigest class.
CZMQ_EXPORT const char *
zfile_digest (zfile_t *self);
Calculates the SHA1 digest for the file, using the zdigest class. Cryptographic queen behaviour. π
// Self test of this class.
CZMQ_EXPORT void
zfile_test (bool verbose);
Self test of this class. Run it, love it, trust it. β¨
Now here comes the draft section, gated behind a build flag:
#ifdef CZMQ_BUILD_DRAFT_API
// *** Draft method, for development use, may change without warning ***
// Create new temporary file for writing via tmpfile. File is automatically
// deleted on destroy
CZMQ_EXPORT zfile_t *
zfile_tmp (void);
#endif // CZMQ_BUILD_DRAFT_API
Careful bestie!!zfile_tmpis a draft method β for development use only, and it may change without warning. It creates a new temporary file for writing viatmpfile, and the file is automatically deleted on destroy. It's giving chaotic but useful. π
And now⦠the deprecated pile. These methods are deprecated, and have now moved to the zsys class:
// These methods are deprecated, and now moved to zsys class.
CZMQ_EXPORT bool
zfile_exists (const char *filename);
CZMQ_EXPORT ssize_t
zfile_size (const char *filename);
CZMQ_EXPORT mode_t
zfile_mode (const char *filename);
CZMQ_EXPORT int
zfile_delete (const char *filename);
CZMQ_EXPORT bool
zfile_stable (const char *filename);
CZMQ_EXPORT int
zfile_mkdir (const char *pathname);
CZMQ_EXPORT int
zfile_rmdir (const char *pathname);
CZMQ_EXPORT void
zfile_mode_private (void);
CZMQ_EXPORT void
zfile_mode_default (void);
Deprecated alert! π¨zfile_exists,zfile_size,zfile_mode,zfile_delete,zfile_stable,zfile_mkdir,zfile_rmdir,zfile_mode_privateandzfile_mode_defaultall live inzsysnow. Not saying you *can't*β¦ just saying π
And one more line, straight from the source, we do not edit it, we just report it:
Please add '@interface' section in './../src/zfile.c'.
(it's literally in the page, bestie, we don't make the rules π« )
β¨ DESCRIPTION β¨
okay so let's actually talk about zfile. π¬
The zfile class provides methods to work with disk files. A file object gives you the modified date, the current size, and the type of the file. And here's the cute part β you can create a file object for a filename that doesn't even exist yet. Manifesting, literally. β¨
To read or write data from the file, you use the input and output methods, and then you read and write chunks. The output method is the flexible one β it lets you both read and write chunks, at any offset. No limits, no restrictions. π
Finally, this class provides portable symbolic links. Here's the tea: if a filename ends in ".ln", the first line of text in the file gets read, and that gets used as the underlying file for read/write operations. Which means you can manipulate (e.g.) copy symbolic links without copying the perhaps very large files they point to. That's the whole point and it ate. π₯
One more thing before we go β this class is a new API, and it's deprecating the old zfile class (which still exists, but is implemented in zsys now). So likeβ¦ the old one is still at the party, it's just not the one running the aux cord anymore. π§
to be continued, bestieβ¦ π¦
π EXAMPLE π
okay bestie, THIS is the part everyone screenshots. it's straight from the zfile_test method, and it's basically the entire life cycle of a zfile_t in one long slay. grab a snack π
first up: the setup. where everything lives, and the four path strings we're gonna build:
const char *SELFTEST_DIR_RW = "src/selftest-rw";
const char *testbasedir = "this";
const char *testsubdir = "is/a/test";
const char *testfile = "bilbo";
const char *testlink = "bilbo.ln";
char *basedirpath = NULL; // subdir in a test, under SELFTEST_DIR_RW
char *dirpath = NULL; // subdir in a test, under basedirpath
char *filepath = NULL; // pathname to testfile in a test, in dirpath
char *linkpath = NULL; // pathname to testlink in a test, in dirpath
basedirpath = zsys_sprintf ("%s/%s", SELFTEST_DIR_RW, testbasedir);
assert (basedirpath);
dirpath = zsys_sprintf ("%s/%s", basedirpath, testsubdir);
assert (dirpath);
filepath = zsys_sprintf ("%s/%s", dirpath, testfile);
assert (filepath);
linkpath = zsys_sprintf ("%s/%s", dirpath, testlink);
assert (linkpath);
then the little subtest that's specifically about passing NULL as the current directory. no SELFTEST_DIR_RW in sight, and testfile has to have zero slashes in it. the comments spill the tea on why it cleans up manually: normally tests tidy themselves in zfile_destroy(), but if a selftest run dies (say, on an assert()), the workspace stays dirty. so we clean up front. icon behaviour honestly:
// This subtest is specifically for NULL as current directory, so
// no SELFTEST_DIR_RW here; testfile should have no slashes inside.
// Normally tests clean up in zfile_destroy(), but if a selftest run
// dies e.g. on assert(), workspace remains dirty. Better clean it up.
if (zfile_exists (testfile) ) {
if (verbose)
zsys_debug ("zfile_test() has to remove ./%s that should not have been here", testfile);
zfile_delete (testfile);
}
zfile_t *file = zfile_new (NULL, testfile);
assert (file);
assert (streq (zfile_filename (file, "."), testfile));
assert (zfile_is_readable (file) == false);
zfile_destroy (&file);
now we make a real test file in some random subdirectory. note the verbose gates on the debug spam β very demure:
// Create a test file in some random subdirectory
if (verbose)
zsys_debug ("zfile_test() at timestamp %" PRIi64 ": "
"Creating new zfile %s",
zclock_time(), filepath );
if (zfile_exists (filepath) ) {
if (verbose)
zsys_debug ("zfile_test() has to remove %s that should not have been here", filepath);
zfile_delete (filepath);
}
file = zfile_new (dirpath, testfile);
assert (file);
int rc = zfile_output (file);
assert (rc == 0);
zchunk_t *chunk = zchunk_new (NULL, 100);
assert (chunk);
zchunk_fill (chunk, 0, 100);
next: write 100 bytes at position 1,000,000. yes, a million. we're skipping ahead like it's nothing, and the file ends up 1000100 bytes long π
// Write 100 bytes at position 1,000,000 in the file
if (verbose)
zsys_debug ("zfile_test() at timestamp %" PRIi64 ": "
"Writing 100 bytes at position 1,000,000 in the file",
zclock_time() );
rc = zfile_write (file, chunk, 1000000);
if (verbose)
zsys_debug ("zfile_test() at timestamp %" PRIi64 ": "
"Wrote 100 bytes at position 1,000,000 in the file, result code %d",
zclock_time(), rc );
assert (rc == 0);
zchunk_destroy (&chunk);
zfile_close (file);
assert (zfile_is_readable (file));
assert (zfile_cursize (file) == 1000100);
if (verbose)
zsys_debug ("zfile_test() at timestamp %" PRIi64 ": "
"Testing if file is NOT stable (is younger than 1 sec)",
zclock_time() );
assert (!zfile_is_stable (file));
if (verbose)
zsys_debug ("zfile_test() at timestamp %" PRIi64 ": "
"Passed the lag-dependent tests",
zclock_time() );
assert (zfile_digest (file));
notice the lag-dependent tests β the "is it stable yet" checks are timing-sensitive, which is exactly why the draft-API path sleeps a different amount than the non-draft path. respect the lag, bestie.
and here's the chaos part: we truncate the file from outside, behind zfile's back. rude. then we check it noticed:
// Now truncate file from outside
int handle = open (filepath, O_WRONLY | O_TRUNC | O_BINARY, 0);
assert (handle >= 0);
rc = write (handle, "Hello, World\n", 13);
assert (rc == 13);
close (handle);
assert (zfile_has_changed (file));
#ifdef CZMQ_BUILD_DRAFT_API
zclock_sleep ((int)zsys_file_stable_age_msec() + 50);
#else
zclock_sleep (5050);
#endif
assert (zfile_has_changed (file));
assert (!zfile_is_stable (file));
zfile_restat (file);
assert (zfile_is_stable (file));
assert (streq (zfile_digest (file), "4AB299C8AD6ED14F31923DD94F8B5F5CB89DFB54"));
then we read it back, and read lines, and the second zfile_readln giving us NULL is the little end-of-file bow on top β¨
// Check we can read from file
rc = zfile_input (file);
assert (rc == 0);
chunk = zfile_read (file, 1000100, 0);
assert (chunk);
assert (zchunk_size (chunk) == 13);
zchunk_destroy (&chunk);
zfile_close (file);
// Check we can read lines from file
rc = zfile_input (file);
assert (rc == 0);
const char *line = zfile_readln (file);
assert (streq (line, "Hello, World"));
line = zfile_readln (file);
assert (line == NULL);
zfile_close (file);
symbolic links time π¦ we write a link file that just contains the path to the real file, then read through it and get the same 13 bytes:
// Try some fun with symbolic links
zfile_t *link = zfile_new (dirpath, testlink);
assert (link);
rc = zfile_output (link);
assert (rc == 0);
fprintf (zfile_handle (link), "%s\n", filepath);
zfile_destroy (&link);
link = zfile_new (dirpath, testlink);
assert (link);
rc = zfile_input (link);
assert (rc == 0);
chunk = zfile_read (link, 1000100, 0);
assert (chunk);
assert (zchunk_size (chunk) == 13);
zchunk_destroy (&chunk);
zfile_destroy (&link);
then we nuke the directory with zdir, watch zdir_cursize go from 26 to 0, and confirm the file is no longer readable β zfile_input now hands us -1:
// Remove file and directory
zdir_t *dir = zdir_new (basedirpath, NULL);
assert (dir);
assert (zdir_cursize (dir) == 26);
zdir_remove (dir, true);
assert (zdir_cursize (dir) == 0);
zdir_destroy (&dir);
// Check we can no longer read from file
assert (zfile_is_readable (file));
zfile_restat (file);
assert (!zfile_is_readable (file));
rc = zfile_input (file);
assert (rc == -1);
zfile_destroy (&file);
// This set of tests is done, free the strings for reuse
zstr_free (&basedirpath);
zstr_free (&dirpath);
zstr_free (&filepath);
zstr_free (&linkpath);
const char *eof_checkfile = "eof_checkfile";
filepath = zsys_sprintf ("%s/%s", SELFTEST_DIR_RW, eof_checkfile);
assert (filepath);
if (zfile_exists (filepath) ) {
if (verbose)
zsys_debug ("zfile_test() has to remove %s that should not have been here", filepath);
zfile_delete (filepath);
}
zstr_free (&filepath);
file = zfile_new (SELFTEST_DIR_RW, eof_checkfile);
assert (file);
round two, electric boogaloo: the eof_checkfile era. write 9 bytes, read more than exists (hello zfile_eof), read a normal chunk, then read from a non-zero offset until the end. the exact "12345" and "6789" splits are giving textbook π§
// 1. Write something first
rc = zfile_output (file);
assert (rc == 0);
chunk = zchunk_new ("123456789", 9);
assert (chunk);
rc = zfile_write (file, chunk, 0);
assert (rc == 0);
zchunk_destroy (&chunk);
zfile_close (file);
assert (zfile_cursize (file) == 9);
// 2. Read the written something
rc = zfile_input (file);
assert (rc != -1);
// try to read more bytes than there is in the file
chunk = zfile_read (file, 1000, 0);
assert (zfile_eof(file));
assert (zchunk_streq (chunk, "123456789"));
zchunk_destroy (&chunk);
// reading is ok
chunk = zfile_read (file, 5, 0);
assert (!zfile_eof(file));
assert (zchunk_streq (chunk, "12345"));
zchunk_destroy (&chunk);
// read from non zero offset until the end
chunk = zfile_read (file, 5, 5);
assert (zfile_eof(file));
assert (zchunk_streq (chunk, "6789"));
zchunk_destroy (&chunk);
zfile_remove (file);
zfile_close (file);
zfile_destroy (&file);
and last but not least, the draft-API-only temp file bit, plus the Windows shutdown. note that zfile_tmp() gives you a file that gets cleaned up when the zfile_t is destroyed β zsys_file_exists says false after, no cap:
#ifdef CZMQ_BUILD_DRAFT_API
zfile_t *tempfile = zfile_tmp ();
assert (tempfile);
assert (zfile_filename (tempfile, NULL));
assert (zsys_file_exists (zfile_filename (tempfile, NULL)));
zchunk_t *tchunk = zchunk_new ("HELLO", 6);
assert (zfile_write (tempfile, tchunk, 0) == 0);
zchunk_destroy (&tchunk);
char *filename = strdup (zfile_filename (tempfile, NULL));
zfile_destroy (&tempfile);
assert (!zsys_file_exists (filename));
zstr_free (&filename);
#endif // CZMQ_BUILD_DRAFT_API
#if defined (__WINDOWS__)
zsys_shutdown();
#endif
π AUTHORS π
the czmq manual was written by the authors in the AUTHORS file. collective slay, fr fr π«Ά
π RESOURCES π
Main web site:
(that's the whole line bestie, the page lists the label and nothing else β not me inventing a URL π)
report bugs to the email <zeromq-dev@lists.zeromq.org[1]> π¬
π COPYRIGHT π
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.
tl;dr: MPL v2.0, be normal about it π
π¦ NOTES π¦
zeromq-dev@lists.zeromq.orgmailto:zeromq-dev@lists.zeromq.org
okay that's the whole page bestie, you're officially a zfile scholar πβ¨ this one comes from CZMQ 4.2.1, dated 01/16/2026, and it lives in the manual as ZFILE(3). go forth and restat responsibly, mwah π
π¦ 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