diff options
Diffstat (limited to 'cftw.c')
| -rw-r--r-- | cftw.c | 252 |
1 files changed, 197 insertions, 55 deletions
@@ -7,91 +7,233 @@ #include <errno.h> #include <stdio.h> #include <stdint.h> +#include <stdlib.h> #include <string.h> -static int do_cftw(const char *const dirpath, int (*const fn)(const char *, - const struct stat *, bool *, void *), bool *const done, void *const user) +struct cftw_entry { - int ret = -1; + DIR *d; + char *dirpath; + struct cftw_entry *child; +}; + +struct cftw +{ + bool done; + char *dirpath; + int (*fn)(const char *, const struct stat *, bool *, void *); + void *user; + struct cftw_entry *root; +}; + +static int free_entry(struct cftw_entry *const e) +{ + int ret = 0; + + if (e->d && closedir(e->d)) + { + fprintf(stderr, "%s: closedir(2) %s: %s\n", __func__, e->dirpath, + strerror(errno)); + ret = -1; + } + + free(e->dirpath); + free(e); + return ret; +} + +void cftw_free(struct cftw *const c) +{ + if (!c) + return; + + for (struct cftw_entry *e = c->root; e;) + { + struct cftw_entry *const child = e->child; + + free_entry(e); + e = child; + } + + free(c); +} + +static struct cftw_entry *entry(const char *const dirpath) +{ + struct cftw_entry *ret = NULL; + char *dirdup = NULL; DIR *const d = opendir(dirpath); if (!d) { - fprintf(stderr, "%s: opendir(2): %s\n", __func__, strerror(errno)); - goto end; + fprintf(stderr, "%s: opendir(2) %s: %s\n", __func__, dirpath, + strerror(errno)); + goto failure; + } + else if (!(dirdup = strdup(dirpath))) + { + fprintf(stderr, "%s: strdup(3): %s\n", __func__, strerror(errno)); + goto failure; + } + else if (!(ret = malloc(sizeof *ret))) + { + fprintf(stderr, "%s: malloc(3): %s\n", __func__, strerror(errno)); + goto failure; } - for (;;) + *ret = (const struct cftw_entry) { - errno = 0; - struct dirent *const de = readdir(d); + .dirpath = dirdup, + .d = d + }; - if (errno) - { - fprintf(stderr, "%s: readdir(3): %s\n", __func__, strerror(errno)); - goto end; - } - else if (!de) - break; + return ret; + +failure: - const char *const path = de->d_name; + if (d && closedir(d)) + fprintf(stderr, "%s: closedir(2) %s: %s\n", __func__, dirpath, + strerror(errno)); - if (!strcmp(path, ".") || !strcmp(path, "..")) - continue; + free(dirdup); + free(ret); + return NULL; +} - const char *const sep = dirpath[strlen(dirpath) - 1] == '/' ? "" : "/"; - struct stat sb; - struct dynstr d; +static enum cftw_state run(struct cftw *const c, struct cftw_entry *const e) +{ + int error; + struct dirent *de; + struct dynstr d; - dynstr_init(&d); + dynstr_init(&d); + errno = 0; + de = readdir(e->d); - if (dynstr_append(&d, "%s%s%s", dirpath, sep, path)) - { - fprintf(stderr, "%s: dynstr_append failed\n", __func__); - return -1; - } + if (errno) + { + fprintf(stderr, "%s: readdir(3): %s\n", __func__, strerror(errno)); + goto failure; + } + else if (!de) + return CFTW_OK; - const int r = stat(d.str, &sb); + const char *const path = de->d_name, *const dirpath = e->dirpath; - if (r) - fprintf(stderr, "%s: stat(2) %s: %s\n", - __func__, path, strerror(errno)); - else if (S_ISDIR(sb.st_mode)) - { - if ((ret = do_cftw(d.str, fn, done, user))) - ; - else if ((ret = fn(d.str, &sb, done, user))) - ; - } - else if (S_ISREG(sb.st_mode)) - ret = fn(d.str, &sb, done, user); - else - fprintf(stderr, "%s: unexpected st_mode %ju\n", - __func__, (uintmax_t)sb.st_mode); + if (!strcmp(path, ".") || !strcmp(path, "..")) + return CFTW_AGAIN; - dynstr_free(&d); + const char *const sep = dirpath[strlen(dirpath) - 1] == '/' ? "" : "/"; + struct stat sb; - if (ret || *done) - goto end; + if (dynstr_append(&d, "%s%s%s", dirpath, sep, path)) + { + fprintf(stderr, "%s: dynstr_append failed\n", __func__); + goto failure; } - ret = 0; + const int r = stat(d.str, &sb); -end: + if (r) + { + fprintf(stderr, "%s: stat(2) %s: %s\n", __func__, path, + strerror(errno)); + goto failure; + } + else if (S_ISDIR(sb.st_mode)) + { + if (!(e->child = entry(d.str))) + goto failure; - if (d && closedir(d)) + error = c->fn(d.str, &sb, &c->done, c->user); + } + else if (S_ISREG(sb.st_mode)) + error = c->fn(d.str, &sb, &c->done, c->user); + else { - fprintf(stderr, "%s: closedir(2): %s\n", __func__, strerror(errno)); - ret = -1; + fprintf(stderr, "%s: unexpected st_mode %ju\n", + __func__, (uintmax_t)sb.st_mode); + goto failure; } - return ret; + dynstr_free(&d); + + if (error) + return CFTW_FATAL; + else if (c->done) + return CFTW_OK; + + return CFTW_AGAIN; + +failure: + dynstr_free(&d); + return CFTW_FATAL; +} + +static enum cftw_state step(struct cftw *const c, struct cftw_entry *const e) +{ + if (e->child) + { + switch (step(c, e->child)) + { + case CFTW_AGAIN: + return CFTW_AGAIN; + + case CFTW_FATAL: + free_entry(e->child); + return CFTW_FATAL; + + case CFTW_OK: + if (free_entry(e->child)) + return CFTW_FATAL; + + e->child = NULL; + break; + } + + return CFTW_AGAIN; + } + + return run(c, e); } -int cftw(const char *const dirpath, int (*const fn)(const char *, +enum cftw_state cftw_step(struct cftw *c) +{ + return step(c, c->root); +} + +struct cftw *cftw(const char *const dirpath, int (*const fn)(const char *, const struct stat *, bool *, void *), void *const user) { - bool done = false; + struct cftw *ret = NULL; + char *const dirdup = strdup(dirpath); + + if (!dirdup) + { + fprintf(stderr, "%s: strdup(3): %s\n", __func__, strerror(errno)); + goto failure; + } + else if (!(ret = malloc(sizeof *ret))) + { + fprintf(stderr, "%s: malloc(3): %s\n", __func__, strerror(errno)); + goto failure; + } + + *ret = (const struct cftw) + { + .dirpath = dirdup, + .fn = fn, + .user = user, + .root = entry(dirpath) + }; + + if (!ret->root) + goto failure; + + return ret; - return do_cftw(dirpath, fn, &done, user); +failure: + free(dirdup); + free(ret); + return NULL; } |
