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_tmp is a draft method β€” for development use only, and it may change without warning. It creates a new temporary file for writing via tmpfile, 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_private and zfile_mode_default all live in zsys now. 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 (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 πŸ¦‹

  1. zeromq-dev@lists.zeromq.org mailto: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