aboutsummaryrefslogtreecommitdiff
path: root/cftw.c
diff options
context:
space:
mode:
Diffstat (limited to 'cftw.c')
-rw-r--r--cftw.c252
1 files changed, 197 insertions, 55 deletions
diff --git a/cftw.c b/cftw.c
index 4b8b013..87684fa 100644
--- a/cftw.c
+++ b/cftw.c
@@ -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;
}