diff options
| author | Xavier Del Campo Romero <xavi92@disroot.org> | 2025-10-06 15:53:11 +0200 |
|---|---|---|
| committer | Xavier Del Campo Romero <xavi92@disroot.org> | 2025-10-06 16:28:59 +0200 |
| commit | fda1fed7c88549030523350c0a3f337e49bbf868 (patch) | |
| tree | f7c62b6b294cbfe032d529903638620cd5453ec3 | |
| parent | eab87d6828f21d01dfd09bc01bf094ca5e176358 (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--------- | libweb | 0 | ||||
| -rw-r--r-- | main.c | 271 |
2 files changed, 200 insertions, 71 deletions
diff --git a/libweb b/libweb -Subproject 5a6f30440b66fe6713acb9d979dc3e6624e4c36 +Subproject 4918cf87d3cf5d3dd8425fe10d97a06282472df @@ -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, "a)) + 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; } |
