#define _POSIX_C_SOURCE 200809L #include "cftw.h" #include #include #include #include #include #include #include #include struct cftw_entry { 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->dirpath); 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: %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; } *ret = (const struct cftw_entry) { .dirpath = dirdup, .d = d }; return ret; failure: if (d && closedir(d)) fprintf(stderr, "%s: closedir(2) %s: %s\n", __func__, dirpath, strerror(errno)); free(dirdup); free(ret); return NULL; } static enum cftw_state run(struct cftw *const c, struct cftw_entry *const e, struct cftw_entry *const root) { int error = 0; struct dirent *de; struct dynstr d; const char *const dirpath = e->dirpath; struct stat sb; dynstr_init(&d); errno = 0; de = readdir(e->d); if (errno) { fprintf(stderr, "%s: readdir(3): %s\n", __func__, strerror(errno)); goto failure; } else if (!de) { if (e == root) return CFTW_OK; else if (stat(dirpath, &sb)) { fprintf(stderr, "%s: stat(2) %s: %s\n", __func__, dirpath, strerror(errno)); goto failure; } return c->fn(dirpath, &sb, &c->done, c->user) ? CFTW_FATAL : CFTW_OK; } const char *const path = de->d_name; if (!strcmp(path, ".") || !strcmp(path, "..")) return CFTW_AGAIN; const char *const sep = dirpath[strlen(dirpath) - 1] == '/' ? "" : "/"; if (dynstr_append(&d, "%s%s%s", dirpath, sep, path)) { fprintf(stderr, "%s: dynstr_append failed\n", __func__); goto failure; } const int r = stat(d.str, &sb); 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; } else if (S_ISREG(sb.st_mode)) error = c->fn(d.str, &sb, &c->done, c->user); else { fprintf(stderr, "%s: unexpected st_mode %ju\n", __func__, (uintmax_t)sb.st_mode); goto failure; } 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, struct cftw_entry *const root) { if (e->child) { switch (step(c, e->child, root)) { 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, root); } enum cftw_state cftw_step(struct cftw *const c) { return step(c, c->root, c->root); } struct cftw *cftw(const char *const dirpath, int (*const fn)(const char *, const struct stat *, bool *, void *), void *const user) { 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; failure: free(dirdup); free(ret); return NULL; }