aboutsummaryrefslogtreecommitdiff
path: root/cftw.c
diff options
context:
space:
mode:
authorXavier Del Campo Romero <xavi92@disroot.org>2025-09-24 11:01:31 +0200
committerXavier Del Campo Romero <xavi92@disroot.org>2025-09-24 12:39:09 +0200
commit173528aef50a4b452acdd8ec9aff13f25c3e092c (patch)
treef3b79ae0f4eb067b97997b4c91a859157987c3bc /cftw.c
parentebb825d3c622f74f0c47a84e1e388b709dd06c7d (diff)
downloadslcl-173528aef50a4b452acdd8ec9aff13f25c3e092c.tar.gz
Make search non-blocking
Thanks to a new feature in libweb, it is now possible to generate HTTP responses asynchronously i.e., without blocking other clients if the response takes a long time to generate. This now allow users to search for files or directories without blocking other users, regardless how much time the search operation takes. This required cftw to deviate from the POSIX-like, blocking interface it had so far, and has been replaced now with a non-blocking interface, so that directories are inspected one entry at a time.
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;
}