main.c: Add -m command line option to open a named pipe

This write-only named pipe is meant to inform other processes about
files that have been added/removed to/from the user/ directory.
The syntax is line-oriented and is described below.

For added files:
+<space ...><path><LF>

For removed files:
-<space ...><path><LF>

Paths shall always be absolute.

Examples:

+ /home/test/db/user/alice/a picture.jpg
- /home/test/db/user/bob/essay.txt

As reported above, this feature has been made completely optional so as
not to introduce breaking changes. A new command line option, namely -m,
is required to enable this feature.
This commit is contained in:
Xavier Del Campo Romero 2023-07-24 23:25:46 +02:00
parent 5adbc376a7
commit 8033237bf5
Signed by: xavi
GPG Key ID: 84FF3612A9BF43F2

345
main.c
View File

@ -1,6 +1,7 @@
#define _POSIX_C_SOURCE 200809L
#include "auth.h"
#include "crealpath.h"
#include "cftw.h"
#include "hex.h"
#include "page.h"
@ -32,6 +33,14 @@ struct form
char *key, *value;
};
struct user_args
{
struct auth *a;
struct dynstr d;
int fd;
bool fifo;
};
static int redirect(struct http_response *const r)
{
*r = (const struct http_response)
@ -51,9 +60,9 @@ static int redirect(struct http_response *const r)
static int serve_index(const struct http_payload *const p,
struct http_response *const r, void *const user)
{
struct auth *const a = user;
const struct user_args *const ua = user;
if (auth_cookie(a, &p->cookie))
if (auth_cookie(ua->a, &p->cookie))
return page_login(r);
return redirect(r);
@ -63,8 +72,8 @@ static int serve_style(const struct http_payload *const p,
struct http_response *const r, void *const user)
{
int ret = -1;
struct auth *const a = user;
const char *const dir = auth_dir(a);
const struct user_args *const ua = user;
const char *const dir = auth_dir(ua->a);
struct dynstr d;
dynstr_init(&d);
@ -292,7 +301,7 @@ static int login(const struct http_payload *const pl,
int ret = -1;
size_t n = 0;
struct form *forms = NULL;
struct auth *const a = user;
const struct user_args *const ua = user;
char *cookie = NULL;
if ((ret = get_forms(pl, &forms, &n)))
@ -302,7 +311,7 @@ static int login(const struct http_payload *const pl,
goto end;
}
else if ((ret = check_credentials(a, forms, n, &cookie)))
else if ((ret = check_credentials(ua->a, forms, n, &cookie)))
{
if (ret < 0)
fprintf(stderr, "%s: check_credentials failed\n", __func__);
@ -339,9 +348,9 @@ end:
static int logout(const struct http_payload *const p,
struct http_response *const r, void *const user)
{
struct auth *const a = user;
const struct user_args *const ua = user;
const struct http_cookie *const c = &p->cookie;
const int res = auth_cookie(a, c);
const int res = auth_cookie(ua->a, c);
if (res < 0)
{
@ -428,8 +437,8 @@ static int getpublic(const struct http_payload *const p,
struct http_response *const r, void *const user)
{
int ret = -1;
struct auth *const a = user;
const char *const adir = auth_dir(a);
const struct user_args *const ua = user;
const char *const adir = auth_dir(ua->a);
struct dynstr d;
dynstr_init(&d);
@ -702,7 +711,8 @@ static int search(const struct http_payload *const p,
struct http_response *const r, void *const user)
{
int ret = -1;
const struct auth *const a = user;
const struct user_args *const args = user;
const struct auth *const a = args->a;
const char *const username = p->cookie.field, *const root = auth_dir(a);
struct page_search s = {0};
int (*f)(struct http_response *);
@ -763,7 +773,8 @@ end:
static int share(const struct http_payload *const p,
struct http_response *const r, void *const user)
{
struct auth *const a = user;
const struct user_args *const ua = user;
const struct auth *const a = ua->a;
if (auth_cookie(a, &p->cookie))
{
@ -929,7 +940,8 @@ static int check_length(const unsigned long long len,
static int getnode(const struct http_payload *const p,
struct http_response *const r, void *const user)
{
struct auth *const a = user;
const struct user_args *const ua = user;
const struct auth *const a = ua->a;
if (auth_cookie(a, &p->cookie))
{
@ -1011,7 +1023,8 @@ end:
static int getnode_head(const struct http_payload *const p,
struct http_response *const r, void *const user)
{
struct auth *const a = user;
const struct user_args *const args = user;
const struct auth *const a = args->a;
if (auth_cookie(a, &p->cookie))
{
@ -1150,6 +1163,93 @@ static int rename_or_move(const char *const old, const char *const new)
return res;
}
static int fifo_writeall(int *const fd, const char *const path,
const void *buf, size_t n)
{
if (*fd < 0 && (*fd = open(path, O_WRONLY | O_NONBLOCK)) < 0)
{
if (errno == ENXIO)
return 0;
else
{
fprintf(stderr, "%s: open(2): %s\n", __func__, strerror(errno));
return -1;
}
}
int ret = -1;
while (n)
{
ssize_t res;
again:
if ((res = write(*fd, buf, n)) < 0)
{
switch (errno)
{
case EAGAIN:
/* Fall through. */
case EINTR:
goto again;
case EPIPE:
{
ret = 0;
if (close(*fd))
{
fprintf(stderr, "%s: close(2): %s\n",
__func__, strerror(errno));
ret = -1;
}
else
*fd = -1;
goto end;
}
default:
fprintf(stderr, "%s: write(2): %s\n",
__func__, strerror(errno));
goto end;
}
}
n -= res;
}
ret = 0;
end:
return ret;
}
static int fifo_notify_add(int *const fd, const char *const path,
const char *const body)
{
static const char header[] = "+ ", lf[] = "\n";
if (fifo_writeall(fd, path, header, strlen(header)))
{
fprintf(stderr, "%s: fifo_writeall header failed\n", __func__);
return -1;
}
else if (fifo_writeall(fd, path, body, strlen(body)))
{
fprintf(stderr, "%s: fifo_writeall body failed\n", __func__);
return -1;
}
else if (fifo_writeall(fd, path, lf, strlen(lf)))
{
fprintf(stderr, "%s: fifo_writeall lf failed\n", __func__);
return -1;
}
return 0;
}
static int check_upload_dir(const char *const dir)
{
struct stat sb;
@ -1181,10 +1281,12 @@ static int check_upload_dir(const char *const dir)
}
static int upload_file(const struct http_post_file *const f,
const char *const user, const char *const root, const char *const dir)
const char *const user, const char *const root, const char *const dir,
struct user_args *const ua)
{
int ret = -1;
struct dynstr dird, d;
char *abspath = NULL;
dynstr_init(&dird);
dynstr_init(&d);
@ -1216,10 +1318,24 @@ static int upload_file(const struct http_post_file *const f,
fprintf(stderr, "%s: rename_or_move failed\n", __func__);
goto end;
}
else if (ua->fifo)
{
if (!(abspath = crealpath(d.str)))
{
fprintf(stderr, "%s: crealpath failed\n", __func__);
goto end;
}
else if (fifo_notify_add(&ua->fd, ua->d.str, abspath))
{
fprintf(stderr, "%s: fifo_notify_add failed\n", __func__);
goto end;
}
}
ret = 0;
end:
free(abspath);
dynstr_free(&dird);
dynstr_free(&d);
return ret;
@ -1277,8 +1393,9 @@ static const char *get_upload_dir(const struct http_post *const po)
}
static int upload_files(const struct http_payload *const p,
struct http_response *const r, const struct auth *const a)
struct http_response *const r, struct user_args *const ua)
{
const struct auth *const a = ua->a;
const struct http_post *const po = &p->u.post;
const char *const root = auth_dir(a), *const user = p->cookie.field,
*const dir = get_upload_dir(po);
@ -1320,7 +1437,7 @@ static int upload_files(const struct http_payload *const p,
for (size_t i = 0; i < po->nfiles; i++)
{
const int ret = upload_file(&po->files[i], user, root, dir);
const int ret = upload_file(&po->files[i], user, root, dir, ua);
if (ret < 0)
{
@ -1337,9 +1454,9 @@ static int upload_files(const struct http_payload *const p,
static int upload(const struct http_payload *const p,
struct http_response *const r, void *const user)
{
const struct auth *const a = user;
struct user_args *const ua = user;
if (auth_cookie(a, &p->cookie))
if (auth_cookie(ua->a, &p->cookie))
{
fprintf(stderr, "%s: auth_cookie failed\n", __func__);
return page_forbidden(r);
@ -1354,14 +1471,15 @@ static int upload(const struct http_payload *const p,
return 0;
}
return upload_files(p, r, a);
return upload_files(p, r, ua);
}
static int createdir(const struct http_payload *const p,
struct http_response *const r, void *const user)
{
int ret = -1;
struct auth *const a = user;
const struct user_args *const ua = user;
const struct auth *const a = ua->a;
struct dynstr d, userd;
struct form *forms = NULL;
size_t n = 0;
@ -1565,14 +1683,14 @@ static int confirm_rm(const struct http_payload *const p,
struct http_response *const r, void *const user)
{
int ret = -1;
struct auth *const a = user;
const struct user_args *const ua = user;
struct form *forms = NULL;
const char **items = NULL;
size_t n = 0;
int (*f)(struct http_response *);
struct page_rm rm = {0};
if (auth_cookie(a, &p->cookie))
if (auth_cookie(ua->a, &p->cookie))
{
fprintf(stderr, "%s: auth_cookie failed\n", __func__);
ret = page_forbidden(r);
@ -1636,36 +1754,82 @@ static const char *find_rm_dir(const struct form *const forms, const size_t n,
return dir;
}
static int rm_dir_contents(const char *const fpath,
const struct stat *const sb, bool *const done, void *const user)
static int fifo_notify_remove(int *const fd, const char *const path,
const char *const body)
{
if (S_ISDIR(sb->st_mode) && rmdir(fpath))
static const char header[] = "- ", lf[] = "\n";
if (fifo_writeall(fd, path, header, strlen(header)))
{
fprintf(stderr, "%s: rmdir(2) %s: %s\n",
__func__, fpath, strerror(errno));
fprintf(stderr, "%s: fifo_writeall header failed\n", __func__);
return -1;
}
else if (S_ISREG(sb->st_mode) && remove(fpath))
else if (fifo_writeall(fd, path, body, strlen(body)))
{
fprintf(stderr, "%s: remove(3) %s: %s\n",
__func__, fpath, strerror(errno));
fprintf(stderr, "%s: fifo_writeall body failed\n", __func__);
return -1;
}
else if (fifo_writeall(fd, path, lf, strlen(lf)))
{
fprintf(stderr, "%s: fifo_writeall lf failed\n", __func__);
return -1;
}
return 0;
}
static int rmdir_r(const char *const path)
static int rm_dir_contents(const char *const fpath,
const struct stat *const sb, bool *const done, void *const user)
{
int ret = -1;
struct user_args *const ua = user;
char *abspath = NULL;
if (S_ISDIR(sb->st_mode) && rmdir(fpath))
{
fprintf(stderr, "%s: rmdir(2) %s: %s\n",
__func__, fpath, strerror(errno));
goto end;
}
else if (S_ISREG(sb->st_mode) && remove(fpath))
{
fprintf(stderr, "%s: remove(3) %s: %s\n",
__func__, fpath, strerror(errno));
goto end;
}
else if (ua->fifo)
{
if (!(abspath = crealpath(fpath)))
{
fprintf(stderr, "%s: crealpath failed\n", __func__);
goto end;
}
else if (fifo_notify_remove(&ua->fd, ua->d.str, abspath))
{
fprintf(stderr, "%s: fifo_notify_remove failed\n", __func__);
goto end;
}
}
ret = 0;
end:
free(abspath);
return ret;
}
static int rmdir_r(struct user_args *const ua, const char *const path)
{
int ret = -1;
DIR *const d = opendir(path);
char *abspath = NULL;
if (!d)
{
fprintf(stderr, "%s: opendir(3): %s\n", __func__, strerror(errno));
goto end;
}
else if (cftw(path, rm_dir_contents, NULL))
else if (cftw(path, rm_dir_contents, ua))
{
fprintf(stderr, "%s: rm_dir_contents failed\n", __func__);
goto end;
@ -1676,6 +1840,19 @@ static int rmdir_r(const char *const path)
__func__, path, strerror(errno));
goto end;
}
else if (ua->fifo)
{
if (!(abspath = crealpath(path)))
{
fprintf(stderr, "%s: crealpath failed\n", __func__);
goto end;
}
else if (fifo_notify_remove(&ua->fd, ua->d.str, abspath))
{
fprintf(stderr, "%s: fifo_notify_remove failed\n", __func__);
goto end;
}
}
ret = 0;
@ -1686,10 +1863,12 @@ end:
ret = -1;
}
free(abspath);
return ret;
}
static int rm_item(const char *const root, const char *const item)
static int rm_item(struct user_args *const ua, const char *const root,
const char *const item)
{
int ret = -1;
struct stat sb;
@ -1714,15 +1893,23 @@ static int rm_item(const char *const root, const char *const item)
goto end;
}
else if (S_ISDIR(sb.st_mode) && rmdir_r(d.str))
else if (S_ISDIR(sb.st_mode) && rmdir_r(ua, d.str))
{
fprintf(stderr, "%s: rmdir_r failed\n", __func__);
goto end;
}
else if (S_ISREG(sb.st_mode) && remove(d.str))
else if (S_ISREG(sb.st_mode))
{
fprintf(stderr, "%s: remove(3): %s\n", __func__, strerror(errno));
goto end;
if (remove(d.str))
{
fprintf(stderr, "%s: remove(3): %s\n", __func__, strerror(errno));
goto end;
}
else if (ua->fifo && fifo_notify_remove(&ua->fd, ua->d.str, d.str))
{
fprintf(stderr, "%s: fifo_notify_remove failed\n", __func__);
goto end;
}
}
ret = 0;
@ -1732,8 +1919,8 @@ end:
return ret;
}
static int do_rm(const struct form *const forms, const size_t n,
const char *const dir)
static int do_rm(struct user_args *const ua, const struct form *const forms,
const size_t n, const char *const dir)
{
for (size_t i = 0; i < n; i++)
{
@ -1748,7 +1935,7 @@ static int do_rm(const struct form *const forms, const size_t n,
fprintf(stderr, "%s: invalid path %s\n", __func__, path);
return 1;
}
else if (rm_item(dir, f->value))
else if (rm_item(ua, dir, f->value))
{
fprintf(stderr, "%s: rm_item failed\n", __func__);
return -1;
@ -1763,7 +1950,8 @@ static int rm(const struct http_payload *const p,
struct http_response *const r, void *const user)
{
int ret = -1;
struct auth *const a = user;
struct user_args *const ua = user;
const struct auth *const a = ua->a;
struct form *forms = NULL;
size_t n = 0;
const struct http_cookie *const c = &p->cookie;
@ -1814,7 +2002,7 @@ static int rm(const struct http_payload *const p,
fprintf(stderr, "%s: dynstr_append failed\n", __func__);
goto end;
}
else if ((ret = do_rm(forms, n, userdir.str)))
else if ((ret = do_rm(ua, forms, n, userdir.str)))
{
if (ret < 0)
fprintf(stderr, "%s: do_rm failed\n", __func__);
@ -1851,12 +2039,12 @@ end:
static void usage(char *const argv[])
{
fprintf(stderr, "%s [-t tmpdir] [-p port] dir\n", *argv);
fprintf(stderr, "%s [-F] [-t tmpdir] [-p port] dir\n", *argv);
}
static int parse_args(const int argc, char *const argv[],
const char **const dir, unsigned short *const port,
const char **const tmpdir)
const char **const tmpdir, bool *const fifo)
{
const char *const envtmp = getenv("TMPDIR");
int opt;
@ -1865,10 +2053,14 @@ static int parse_args(const int argc, char *const argv[],
*port = 0;
*tmpdir = envtmp ? envtmp : "/tmp";
while ((opt = getopt(argc, argv, "t:p:")) != -1)
while ((opt = getopt(argc, argv, "Ft:p:")) != -1)
{
switch (opt)
{
case 'F':
*fifo = true;
break;
case 't':
*tmpdir = optarg;
break;
@ -2094,25 +2286,68 @@ static int add_urls(struct handler *const h, void *const user)
return 0;
}
static int ensure_fifo(const char *const dir, struct user_args *const ua)
{
int ret = -1;
if (dynstr_append(&ua->d, "%s/slcl.fifo", dir))
{
fprintf(stderr, "%s: dynstr_append failed\n", __func__);
goto end;
}
else if (mkfifo(ua->d.str, S_IRUSR | S_IWUSR))
{
fprintf(stderr, "%s: mkfifo(3): %s\n", __func__, strerror(errno));
goto end;
}
ret = 0;
end:
return ret;
}
static int user_args_free(struct user_args *const ua)
{
int ret = 0;
if (ua)
{
if (ua->d.str && remove(ua->d.str))
{
fprintf(stderr, "%s: remove(3): %s\n", __func__, strerror(errno));
ret = -1;
}
auth_free(ua->a);
dynstr_free(&ua->d);
}
return ret;
}
int main(int argc, char *argv[])
{
int ret = EXIT_FAILURE;
struct handler *h = NULL;
struct auth *a = NULL;
const char *dir, *tmpdir;
unsigned short port;
struct user_args ua = {.fd = -1};
if (parse_args(argc, argv, &dir, &port, &tmpdir)
dynstr_init(&ua.d);
if (parse_args(argc, argv, &dir, &port, &tmpdir, &ua.fifo)
|| init_dirs(dir)
|| ensure_style(dir)
|| !(a = auth_alloc(dir)))
|| !(ua.a = auth_alloc(dir))
|| (ua.fifo && ensure_fifo(dir, &ua)))
goto end;
const struct handler_cfg cfg =
{
.length = check_length,
.tmpdir = tmpdir,
.user = a,
.user = ua.a,
.post =
{
/* Arbitrary limit. */
@ -2125,7 +2360,7 @@ int main(int argc, char *argv[])
unsigned short outport;
if (!(h = handler_alloc(&cfg))
|| add_urls(h, a)
|| add_urls(h, &ua)
|| handler_listen(h, port, &outport))
goto end;
@ -2140,7 +2375,13 @@ int main(int argc, char *argv[])
ret = EXIT_SUCCESS;
end:
auth_free(a);
handler_free(h);
if (user_args_free(&ua))
{
fprintf(stderr, "%s: user_args_free failed\n", __func__);
ret = EXIT_FAILURE;
}
return ret;
}