aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorXavier Del Campo Romero <xavi92@disroot.org>2025-10-06 15:53:11 +0200
committerXavier Del Campo Romero <xavi92@disroot.org>2025-10-06 16:28:59 +0200
commitfda1fed7c88549030523350c0a3f337e49bbf868 (patch)
treef7c62b6b294cbfe032d529903638620cd5453ec3
parenteab87d6828f21d01dfd09bc01bf094ca5e176358 (diff)
Fix missing refactors related to cftw
Commit 4fa1b3e8 missed to update other calls to cftw that were still relying on the older interface, causing unexpected errors. As a side effect, user quotas are now calculated asynchronously i.e., without blocking other clients. While the same improvement was planned for the /rm endpoint, it proved too challenging to implement for a first refactor: on one hand, /rm takes one or more key-value pairs involving the top-level directories and/or files to remove. On the other hand, every directory must be traversed recursively as rmdir(2) must be used on empty directories. While certainly possible, it was considered to keep a synchronous behaviour for do_rm for the sake of simplicity.
m---------libweb0
-rw-r--r--main.c271
2 files changed, 200 insertions, 71 deletions
diff --git a/libweb b/libweb
-Subproject 5a6f30440b66fe6713acb9d979dc3e6624e4c36
+Subproject 4918cf87d3cf5d3dd8425fe10d97a06282472df
diff --git a/main.c b/main.c
index 95a5927..ee29be0 100644
--- a/main.c
+++ b/main.c
@@ -46,7 +46,6 @@ struct user_args
struct dynstr d;
int fd;
bool fifo;
- void *state;
};
struct search
@@ -56,6 +55,15 @@ struct search
struct page_search search;
};
+typedef int (*quota_fn)(const struct http_payload *, struct http_response *,
+ void *, unsigned long long);
+
+struct quota
+{
+ struct cftw *cftw;
+ unsigned long long cur, max;
+};
+
static struct handler *handler;
static int redirect(struct http_response *const r)
@@ -716,10 +724,9 @@ static void free_search(struct search *const s)
}
static int search_step(const struct http_payload *const p,
- struct http_response *const r, void *const user)
+ struct http_response *const r, void *const user, void *const state)
{
- const struct user_args *const ua = user;
- struct search *const s = ua->state;
+ struct search *const s = state;
switch (cftw_step(s->cftw))
{
@@ -735,6 +742,7 @@ static int search_step(const struct http_payload *const p,
break;
case CFTW_FATAL:
+ fprintf(stderr, "%s: cftw_step failed\n", __func__);
goto failure;
case CFTW_AGAIN:
@@ -811,9 +819,13 @@ static int search(const struct http_payload *const p,
goto end;
}
- *r = (const struct http_response){.step = search_step};
+ *r = (const struct http_response)
+ {
+ .step.payload = search_step,
+ .step_args = s
+ };
+
s->search.root = s->root.str;
- args->state = s;
ret = 0;
end:
@@ -927,16 +939,27 @@ static int add_length(const char *const fpath, const struct stat *const sb,
if (!S_ISREG(sb->st_mode))
return 0;
- unsigned long long *const l = user;
+ struct quota *const q = user;
- *l += sb->st_size;
+ q->cur += sb->st_size;
return 0;
}
-static int quota_current(const struct auth *const a,
- const char *const username, unsigned long long *const cur)
+static void free_quota(struct quota *const q)
{
- int ret = -1;
+ if (!q)
+ return;
+
+ cftw_free(q->cftw);
+ free(q);
+}
+
+static struct quota *quota_current(struct user_args *const ua,
+ const char *const username, const unsigned long long max)
+{
+ struct quota *ret = NULL, *q = NULL;
+ struct cftw *c = NULL;
+ const struct auth *const a = ua->a;
const char *const adir = auth_dir(a);
struct dynstr d;
@@ -952,45 +975,82 @@ static int quota_current(const struct auth *const a,
fprintf(stderr, "%s: dynstr_append failed\n", __func__);
goto end;
}
-
- *cur = 0;
-
- if (cftw(d.str, add_length, cur))
+ else if (!(q = malloc(sizeof *q)))
+ {
+ fprintf(stderr, "%s: malloc(3): %s\n", __func__, strerror(errno));
+ goto end;
+ }
+ else if (!(c = cftw(d.str, add_length, q)))
{
- fprintf(stderr, "%s: cftw: %s\n", __func__, strerror(errno));
+ fprintf(stderr, "%s: cftw failed\n", __func__);
goto end;
}
- ret = 0;
+ *q = (const struct quota)
+ {
+ .cftw = c,
+ .max = max
+ };
+
+ ret = q;
end:
+
+ if (!ret)
+ {
+ free(q);
+ cftw_free(c);
+ }
+
dynstr_free(&d);
return ret;
}
-static int check_quota(const struct auth *const a, const char *const username,
- const unsigned long long len, const unsigned long long quota)
+static int check_quota(const unsigned long long len,
+ const struct quota *const q)
{
- unsigned long long total;
+ return q->cur + len > q->max ? 1 : 0;
+}
+
+static int check_length_step(const unsigned long long len,
+ const struct http_cookie *const c, struct http_response *const r,
+ void *const user, void *const step_args)
+{
+ int ret = 0;
+ struct quota *const q = step_args;
- if (quota_current(a, username, &total))
+ switch (cftw_step(q->cftw))
{
- fprintf(stderr, "%s: quota_current failed\n", __func__);
- return -1;
+ case CFTW_OK:
+ ret = check_quota(len, q);
+ free_quota(q);
+ r->step.length = NULL;
+ break;
+
+ case CFTW_FATAL:
+ fprintf(stderr, "%s: cftw_step failed\n", __func__);
+ goto failure;
+
+ case CFTW_AGAIN:
+ break;
}
- return total + len > quota ? 1 : 0;
+ return ret;
+
+failure:
+ free_quota(q);
+ return -1;
}
static int check_length(const unsigned long long len,
const struct http_cookie *const c, struct http_response *const r,
void *const user)
{
- const struct user_args *const ua = user;
+ struct user_args *const ua = user;
const struct auth *const a = ua->a;
const char *const username = c->field;
bool has_quota;
- unsigned long long quota;
+ unsigned long long max;
if (auth_cookie(a, c))
{
@@ -1001,51 +1061,43 @@ static int check_length(const unsigned long long len,
return 1;
}
- else if (auth_quota(a, username, &has_quota, &quota))
+ else if (auth_quota(a, username, &has_quota, &max))
{
fprintf(stderr, "%s: auth_quota failed\n", __func__);
return -1;
}
else if (has_quota)
{
- int res = check_quota(a, username, len, quota);
+ struct quota *const q = quota_current(ua, username, max);
- if (res < 0)
- fprintf(stderr, "%s: check_quota failed\n", __func__);
- else if (res > 0 && page_quota_exceeded(r, len, quota) < 0)
+ if (!q)
+ {
+ fprintf(stderr, "%s: quota_current failed\n", __func__);
return -1;
+ }
- return res;
+ *r = (const struct http_response)
+ {
+ .step.length = check_length_step,
+ .step_args = q
+ };
}
return 0;
}
-static int getnode(const struct http_payload *const p,
- struct http_response *const r, void *const user)
+static int send_resource(const struct http_payload *const p,
+ struct http_response *const r, void *const user,
+ const struct page_quota *const q)
{
+ int ret = -1;
const struct user_args *const ua = user;
const struct auth *const a = ua->a;
-
- if (auth_cookie(a, &p->cookie))
- {
- fprintf(stderr, "%s: auth_cookie failed\n", __func__);
- return page_forbidden(r);
- }
-
- const char *const username = p->cookie.field,
- *const resource = p->resource + strlen("/user/");
-
- if (path_invalid(resource))
- {
- fprintf(stderr, "%s: illegal relative path %s\n", __func__, resource);
- return page_forbidden(r);
- }
-
- int ret = -1;
- struct dynstr dir, root, d;
const char *const adir = auth_dir(a),
+ *const username = p->cookie.field,
+ *const resource = p->resource + strlen("/user/"),
*const sep = p->resource[strlen(p->resource) - 1] != '/' ? "/" : "";
+ struct dynstr dir, root, d;
dynstr_init(&dir);
dynstr_init(&d);
@@ -1068,20 +1120,6 @@ static int getnode(const struct http_payload *const p,
goto end;
}
- bool available;
- unsigned long long cur, max;
-
- if (auth_quota(a, username, &available, &max))
- {
- fprintf(stderr, "%s: quota_available failed\n", __func__);
- goto end;
- }
- else if (available && quota_current(a, username, &cur))
- {
- fprintf(stderr, "%s: quota_current failed\n", __func__);
- goto end;
- }
-
const struct page_resource pr =
{
.r = r,
@@ -1092,9 +1130,7 @@ static int getnode(const struct http_payload *const p,
.dir = dir.str,
.root = root.str,
.res = d.str,
- .q = available ?
- &(const struct page_quota) {.cur = cur, .max = max }
- : NULL
+ .q = q
};
ret = page_resource(&pr);
@@ -1106,6 +1142,87 @@ end:
return ret;
}
+static int getnode_step(const struct http_payload *const p,
+ struct http_response *const r, void *const user, void *const step_args)
+{
+ int ret = 0;
+ struct quota *const q = step_args;
+
+ switch (cftw_step(q->cftw))
+ {
+ case CFTW_OK:
+ {
+ const struct page_quota pq = {.cur = q->cur, .max = q->max};
+
+ if ((ret = send_resource(p, r, user, &pq)))
+ fprintf(stderr, "%s: send_resource failed\n", __func__);
+
+ free_quota(q);
+ }
+ break;
+
+ case CFTW_FATAL:
+ fprintf(stderr, "%s: cftw_step failed\n", __func__);
+ goto failure;
+
+ case CFTW_AGAIN:
+ break;
+ }
+
+ return ret;
+
+failure:
+ free_quota(q);
+ return -1;
+}
+
+static int getnode(const struct http_payload *const p,
+ struct http_response *const r, void *const user)
+{
+ struct user_args *const ua = user;
+ const struct auth *const a = ua->a;
+ const char *const resource = p->resource + strlen("/user/"),
+ *const username = p->cookie.field;
+ bool available;
+ unsigned long long max;
+
+ if (auth_cookie(a, &p->cookie))
+ {
+ fprintf(stderr, "%s: auth_cookie failed\n", __func__);
+ return page_forbidden(r);
+ }
+ else if (path_invalid(resource))
+ {
+ fprintf(stderr, "%s: illegal relative path %s\n", __func__, resource);
+ return page_forbidden(r);
+ }
+ else if (auth_quota(a, username, &available, &max))
+ {
+ fprintf(stderr, "%s: quota_available failed\n", __func__);
+ return -1;
+ }
+ else if (available)
+ {
+ struct quota *const q = quota_current(ua, username, max);
+
+ if (!q)
+ {
+ fprintf(stderr, "%s: quota_current failed\n", __func__);
+ return -1;
+ }
+
+ *r = (const struct http_response)
+ {
+ .step.payload = getnode_step,
+ .step_args = q
+ };
+ }
+ else
+ return send_resource(p, r, user, NULL);
+
+ return 0;
+}
+
static int getnode_head(const struct http_payload *const p,
struct http_response *const r, void *const user)
{
@@ -1907,6 +2024,8 @@ end:
static int rmdir_r(struct user_args *const ua, const char *const path)
{
int ret = -1;
+ struct cftw *c = NULL;
+ enum cftw_state state;
DIR *const d = opendir(path);
char *abspath = NULL;
@@ -1915,9 +2034,18 @@ static int rmdir_r(struct user_args *const ua, const char *const path)
fprintf(stderr, "%s: opendir(3): %s\n", __func__, strerror(errno));
goto end;
}
- else if (cftw(path, rm_dir_contents, ua))
+ else if (!(c = cftw(path, rm_dir_contents, ua)))
+ {
+ fprintf(stderr, "%s: cftw failed\n", __func__);
+ goto end;
+ }
+
+ while ((state = cftw_step(c)) == CFTW_AGAIN)
+ ;
+
+ if (state)
{
- fprintf(stderr, "%s: rm_dir_contents failed\n", __func__);
+ fprintf(stderr, "%s: cftw_step failed\n", __func__);
goto end;
}
else if (rmdir(path))
@@ -1950,6 +2078,7 @@ end:
}
free(abspath);
+ cftw_free(c);
return ret;
}