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)
downloadslcl-fda1fed7c88549030523350c0a3f337e49bbf868.tar.gz
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;
}