zfile.h is a cross-platform File I/O and Path Manipulation library for C. It abstracts away the messy differences between Windows (Win32 API) and POSIX systems, providing a unified, pragmatic API for file system operations.
It is designed to work seamlessly with zstr.h for string handling and includes a robust C++11 wrapper for RAII-style path manipulation and directory iteration.
It is part of the zdk suite.
- Cross-Platform: Unified API for Windows and Linux/macOS. Handles UTF-8 paths correctly on Windows (automatically converts to
wchar_t). - Atomic Saves:
zfile_save_atomicwrites to a temporary file and renames it, preventing data corruption during crashes. - Recursive Operations:
zfile_mkdir_recursivecreates full directory trees (likemkdir -p). - Buffered Reading: High-performance, zero-copy line reader (
ZFILE_FOR_EACH_LINE) for parsing large files. - Path Logic: Smart path joining, extension extraction, and normalization using
zstr. - C++ Support: Includes
z_file::path(operator/overloading) and range-based directory iterators. - Header Only: No linking required.
zfile.h depends on zstr.h and zcommon.h.
- Download
zfile.h, andzstr.h. You can also clone the repository and do:
make
# or
make get_dependencies- Include it in one C/C++ file with the implementation macros defined (
ZFILE_IMPLEMENTATIONandZSTR_IMPLEMENTATION).
#define ZFILE_IMPLEMENTATION
#include "zfile.h"If you use the clib package manager, you can install the z-libs/zdk package.
clib install z-libs/zdk
If you use the Zen Development Kit, it is included automatically by including <zdk/zfile.h> (or <zdk/zworld.h>).
To use the library in C, simply include the header. Access path manipulation functions to handle cross-platform separators, use the atomic save functions for safe file writing, and use the buffered reader macros for efficient parsing.
#include <stdio.h>
#define ZFILE_IMPLEMENTATION
#include "zfile.h"
int main(void)
{
// => Path manipulation.
// (Automatically handles '/' vs '\' separators).
zstr path = zfile_join("data", "config.json");
if (zfile_exists(zstr_cstr(&path)))
{
printf("Config found at: %s\n", zstr_cstr(&path));
}
// => Atomic write.
// Writes to "data/config.json.tmp_XXXX" then renames.
const char *data = "{\"version\": 1}";
if (zfile_save_atomic(zstr_cstr(&path), data, strlen(data)) == Z_OK)
{
printf("Saved successfully.\n");
}
// Buffered line reader (zero-copy views).
ZFILE_FOR_EACH_LINE("data/logs.txt", line)
{
// 'line' is a zstr_view (ptr + len), not a null-terminated string.
printf("Log: %.*s\n", (int)line.len, line.data);
}
zstr_free(&path);
return 0;
}The C++ wrapper lives in the z_file namespace. It provides a std::filesystem-like experience but is much lighter to compile. You can use z_file::path for RAII path manipulation and z_file::dir_iterable for range-based directory loops.
#include <iostream>
#define ZFILE_IMPLEMENTATION
#include "zfile.h"
int main()
{
// => Path wrapper (RAII).
z_file::path root = "assets";
z_file::path model = root / "models" / "hero.obj";
std::cout << "Loading: " << model.c_str() << "\n";
std::cout << "Extension: " << std::string(model.extension()) << "\n";
// => Directory iteration (Range-based).
if (root.exists() && root.is_dir())
{
for (auto entry : z_file::dir_iterable(root))
{
// entry.name is a char array.
// entry.type is ZDIR_FILE or ZDIR_DIR.
std::cout << "Found: " << entry.name << "\n";
}
}
return 0;
}| Function | Description |
|---|---|
zfile_join(base, child) |
Returns a new zstr joining two paths with the correct OS separator. |
zfile_ext(path) |
Returns a zstr_view of the file extension (e.g., .txt). |
zfile_base(path) |
Returns a zstr_view of the filename (basename). |
zfile_dir(path) |
Returns a zstr_view of the parent directory. |
zfile_normalize(zstr*) |
Modifies a zstr in-place, converting separators to the OS standard. |
zfile_tempname(pfx, ext) |
Generates a unique path in the system temp directory (e.g., /tmp/pfx_123.ext). |
| Function | Returns | Description |
|---|---|---|
zfile_exists(path) |
bool |
Returns true if the path exists. |
zfile_is_dir(path) |
bool |
Returns true if the path exists and is a directory. |
zfile_is_file(path) |
bool |
Returns true if the path exists and is a regular file. |
zfile_size(path) |
int64_t |
Returns file size in bytes, or -1 on error. |
zfile_read_all(path) |
zstr |
Reads entire file into memory. Returns empty string on failure. |
zfile_write_all(path, ...) |
int |
Writes buffer to file (overwrite). Returns 0 or error code. |
zfile_append(path, ...) |
int |
Appends buffer to file. |
zfile_save_atomic(path, ...) |
int |
Recommended. Writes to temp file, then renames. Safe against crashes. |
zfile_copy(src, dst) |
int |
Copies a file chunk-by-chunk. |
zfile_remove(path) |
int |
Deletes a file. |
zfile_rename(old, new) |
int |
Renames/Moves a file. |
| Function | Description |
|---|---|
zfile_mkdir_recursive(path) |
Creates a directory and all its parents (like mkdir -p). |
zdir_open(path) |
Opens a directory iterator (zdir_iter*). |
zdir_next(it, entry*) |
Advances iterator. Returns true if an entry was found. |
zdir_close(it) |
Frees the iterator. |
| Macro / Function | Description |
|---|---|
zfile_reader_open(path) |
Opens a file for buffered reading. Allocates internal buffer. |
zfile_reader_next_line(r, line*) |
Zero-copy parse. line points inside the buffer. Returns false on EOF. |
ZFILE_FOR_EACH_LINE(path, var) |
Macro: Automates open, loop, and close. var is declared as zstr_view. |
The C++ wrapper classes are lightweight and wrap zstr objects.
| Method | Description |
|---|---|
path(const char*) |
Constructor. |
operator/(other) |
Joins paths using the OS separator. Returns new path. |
exists() |
Returns true if path exists. |
is_dir() |
Returns true if path is a directory. |
extension() |
Returns z_str::view of the extension. |
string() |
Returns the underlying z_str::string. |
c_str() |
Returns const char*. |
Designed for range-based for loops.
| Method | Description |
|---|---|
dir_iterable(path) |
Constructor. |
begin(), end() |
Standard iterators. |
for (auto entry : z_file::dir_iterable("some/path"))
{
if (entry.type == ZDIR_FILE) { ... }
}If you prefer a cleaner API and don't have naming conflicts, define ZFILE_SHORT_NAMES before including the header.
| Short Name | Original |
|---|---|
file_exists |
zfile_exists |
path_join |
zfile_join |
file_read |
zfile_read_all |
file_write |
zfile_write_all |
dir_open |
zdir_open |
dir_next |
zdir_next |
file_foreach_line |
ZFILE_FOR_EACH_LINE |
zstr.h: Required for path string manipulation.zcommon.h: Required for standard types and definitions. Bundled.