Dir
Walk one directory with POSIX dirent. Names come back one at a time. lf_path_join joins two path components with a single / and does not allocate.
Concurrent use is unsupported. The walker skips . and .. only. It does not skip other dotfiles, and it does not recurse. Callers that also want to reject other dotfiles do that on the name.
Usage#
Zero the handle before lf_dir_open if a failed open should be safe to close. The caller owns LfDir. lf_dir_close releases the directory stream and the current name. It does not free the handle.
LfDir dir;
memset(&dir, 0, sizeof dir);
if (lf_dir_open(&dir, "assets") == 0) {
const char *name;
while (lf_dir_next(&dir, &name) == 0) {
char path[256];
if (lf_path_join(path, sizeof path, "assets", name) == 0) {
/* path is "assets/<name>" */
}
}
lf_dir_close(&dir);
}
name points at storage owned by dir. It stays valid until a later successful lf_dir_next or lf_dir_close. A return of 1 or -1 leaves the previous name in place. Do not free it. The string is the directory entry, not a joined path.
Structs#
LfDir#
The handle is complete so the caller can declare LfDir dir with no create function. dir and name are private. Do not read or write them.
typedef struct LfDir {
void *dir; /* DIR *, private */
char *name; /* private, owned by the LfDir */
} LfDir;
dir holds the open DIR *. name holds the current entry copy. The header does not include dirent.h.
Functions#
lf_dir_open#
Opens path as a directory. Returns 0 on success and -1 on error: d is NULL, path is NULL, path is not a directory, or the OS reports an error.
On failure, *d is unchanged when d is non-NULL. Nothing is written before opendir succeeds, so a zeroed handle that fails to open stays zeroed and lf_dir_close on it is safe. Close of an uninitialized handle is not safe.
On success, dir is the open stream and name is NULL. lf_dir_open does not close a stream already stored in the handle. Close a live handle before opening another path; otherwise the previous stream and name leak.
int lf_dir_open(LfDir *d, const char *path);lf_dir_close#
Closes the DIR and frees the name copy. Does not free the handle. NULL is safe. A zeroed handle is safe (dir and name are NULL). After close, both fields are NULL, so a second close is safe.
void lf_dir_close(LfDir *d);lf_dir_next#
Returns 0 after writing an entry name, 1 when the directory is exhausted, and -1 on error. Skips . and ... Does not skip other dotfiles. Does not recurse. The name is the directory entry only.
On 0, *name receives the new entry. That pointer stays valid until a later successful lf_dir_next or lf_dir_close on this handle. On 1 or -1, *name is unchanged and the previous name stays valid. A NULL d, a NULL out pointer, or a handle that is not open returns -1 and does not write through name.
The owned copy is replaced only when the return value is 0. lf_dir_open does not free a name already stored in the handle. lf_dir_next sets errno to 0 before readdir and may change it again. The status is the return value; callers do not need to read errno.
int lf_dir_next(LfDir *d, const char **name);lf_path_join#
Writes a and b joined by a single / into dest. Returns 0 on success. Returns -1 if dest is NULL, a or b is NULL, b is empty, or the result does not fit in dest_cap including the NUL. On failure, the bytes in dest are unchanged. The function does not allocate. It checks the size before writing.
If a already ends with /, no second slash is written: "a/" and "b" become "a/b". "a" and "b" become "a/b".
An empty a is not an error. The result is b with no extra slash. An empty b is an error.
a and b are NUL-terminated. dest must not overlap either string. The function does not normalize ., .., or slashes that are not at the join point.
int lf_path_join(char *dest, size_t dest_cap, const char *a, const char *b);