Compare commits
7 Commits
3653a3e3f8
...
79eb0cc9a8
Author | SHA1 | Date | |
---|---|---|---|
79eb0cc9a8 | |||
a797eee481 | |||
dc3b98cd6f | |||
cb671d3c3a | |||
69856c86ca | |||
08a991d60c | |||
8bcf0bf855 |
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,5 +1,6 @@
|
||||||
build/
|
build/
|
||||||
slcl
|
slcl
|
||||||
|
thumbnail/thumbnail
|
||||||
*.o
|
*.o
|
||||||
*.d
|
*.d
|
||||||
./Makefile
|
./Makefile
|
||||||
|
|
|
@ -4,6 +4,7 @@ add_executable(${PROJECT_NAME}
|
||||||
auth.c
|
auth.c
|
||||||
base64.c
|
base64.c
|
||||||
cftw.c
|
cftw.c
|
||||||
|
crealpath.c
|
||||||
hex.c
|
hex.c
|
||||||
jwt.c
|
jwt.c
|
||||||
main.c
|
main.c
|
||||||
|
|
33
README.md
33
README.md
|
@ -23,6 +23,9 @@ portability, minimalism, simplicity and efficiency.
|
||||||
- Uses [`libweb`](https://gitea.privatedns.org/xavi/libweb), a tiny web framework.
|
- Uses [`libweb`](https://gitea.privatedns.org/xavi/libweb), a tiny web framework.
|
||||||
- A simple JSON file as the credentials database.
|
- A simple JSON file as the credentials database.
|
||||||
- No JavaScript.
|
- No JavaScript.
|
||||||
|
- Thumbnail generation can be optionally provided via a
|
||||||
|
[separate application](thumbnail/README-md) and enabled by `slcl` via a command
|
||||||
|
line option. Inter-process communication is achieved via a named pipe.
|
||||||
|
|
||||||
### TLS
|
### TLS
|
||||||
|
|
||||||
|
@ -123,16 +126,32 @@ anything users put into them.
|
||||||
|
|
||||||
**Note:** `slcl` creates the given directory if it does not exist.
|
**Note:** `slcl` creates the given directory if it does not exist.
|
||||||
|
|
||||||
|
[Generated thumbnails](thumbnail/README.md) are stored into another directory,
|
||||||
|
namely `thumbnails`, which is automatically created with directory mode bits
|
||||||
|
set to `0700`.
|
||||||
|
|
||||||
|
```
|
||||||
|
.
|
||||||
|
├── db.json
|
||||||
|
├── public
|
||||||
|
├── thumbnails
|
||||||
|
└── user
|
||||||
|
```
|
||||||
|
|
||||||
A more complete example:
|
A more complete example:
|
||||||
|
|
||||||
```
|
```
|
||||||
.
|
.
|
||||||
├── db.json
|
├── db.json
|
||||||
├── public
|
├── public
|
||||||
│ └── 44e03ab1bc3b0eff1567c76619186596 -> user/alice/file.txt
|
│ └── 44e03ab1bc3b0eff1567c76619186596 -> user/alice/file.jpg
|
||||||
|
├── thumbnails
|
||||||
|
│ ├── alice
|
||||||
|
│ │ └── file.jpg
|
||||||
|
│ └── john
|
||||||
└── user
|
└── user
|
||||||
├── alice
|
├── alice
|
||||||
│ └── file.txt
|
│ └── file.jpg
|
||||||
└── john
|
└── john
|
||||||
└── file2.txt
|
└── file2.txt
|
||||||
```
|
```
|
||||||
|
@ -199,6 +218,16 @@ temporary directory to the database, which might be an expensive operation.
|
||||||
Therefore, in order to avoid expensive copies, define a custom temporary
|
Therefore, in order to avoid expensive copies, define a custom temporary
|
||||||
directory that resides on the same filesystem.
|
directory that resides on the same filesystem.
|
||||||
|
|
||||||
|
#### Thumbnail generation and rendering
|
||||||
|
|
||||||
|
Optionally, `slcl` displays thumbnails when listing a directory, if available
|
||||||
|
from the `thumbnails/` directory. In order to update these thumbnails when
|
||||||
|
files are added/removed to/from the database, a separate application must be
|
||||||
|
executed. See its [`README.md`](thumbnail/README.md) for further reference.
|
||||||
|
|
||||||
|
`slcl` provides the `-F` command line option to enable the use of a named pipe
|
||||||
|
that shall be used by the [thumbnail generation tool](thumbnail/).
|
||||||
|
|
||||||
## Why this project?
|
## Why this project?
|
||||||
|
|
||||||
Previously, I had been recommended Nextcloud as an alternative to proprietary
|
Previously, I had been recommended Nextcloud as an alternative to proprietary
|
||||||
|
|
48
auth.c
48
auth.c
|
@ -1,4 +1,5 @@
|
||||||
#include "auth.h"
|
#include "auth.h"
|
||||||
|
#include "crealpath.h"
|
||||||
#include "hex.h"
|
#include "hex.h"
|
||||||
#include "jwt.h"
|
#include "jwt.h"
|
||||||
#include <libweb/http.h>
|
#include <libweb/http.h>
|
||||||
|
@ -511,42 +512,6 @@ static int init_db(struct auth *const a)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static char *resolve_cwd(void)
|
|
||||||
{
|
|
||||||
size_t len = 1;
|
|
||||||
char *p = NULL;
|
|
||||||
|
|
||||||
for (;;)
|
|
||||||
{
|
|
||||||
char *const pp = realloc(p, len);
|
|
||||||
|
|
||||||
if (!pp)
|
|
||||||
{
|
|
||||||
fprintf(stderr, "%s: realloc(3): %s\n", __func__, strerror(errno));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
p = pp;
|
|
||||||
|
|
||||||
if (!getcwd(pp, len))
|
|
||||||
{
|
|
||||||
if (errno != ERANGE)
|
|
||||||
{
|
|
||||||
fprintf(stderr, "%s: getcwd(3): %s\n",
|
|
||||||
__func__, strerror(errno));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
len++;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
|
|
||||||
free(p);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct auth *auth_alloc(const char *const dir)
|
struct auth *auth_alloc(const char *const dir)
|
||||||
{
|
{
|
||||||
struct auth *const a = malloc(sizeof *a), *ret = NULL;
|
struct auth *const a = malloc(sizeof *a), *ret = NULL;
|
||||||
|
@ -563,21 +528,16 @@ struct auth *auth_alloc(const char *const dir)
|
||||||
dynstr_init(&a->db);
|
dynstr_init(&a->db);
|
||||||
dynstr_init(&a->dir);
|
dynstr_init(&a->dir);
|
||||||
|
|
||||||
if (*dir != '/' && !(abspath = resolve_cwd()))
|
if (!(abspath = crealpath(dir)))
|
||||||
{
|
{
|
||||||
fprintf(stderr, "%s: resolve_cwd failed\n", __func__);
|
fprintf(stderr, "%s: crealpath failed\n", __func__);
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
else if (abspath && dynstr_append(&a->dir, "%s", abspath))
|
else if (dynstr_append(&a->dir, "%s", abspath))
|
||||||
{
|
{
|
||||||
fprintf(stderr, "%s: dynstr_append abspath failed\n", __func__);
|
fprintf(stderr, "%s: dynstr_append abspath failed\n", __func__);
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
else if (dynstr_append(&a->dir, "%s%s", abspath ? "/" : "", dir))
|
|
||||||
{
|
|
||||||
fprintf(stderr, "%s: dynstr_append dir failed\n", __func__);
|
|
||||||
goto end;
|
|
||||||
}
|
|
||||||
else if (dynstr_append(&a->db, "%s/db.json", a->dir.str))
|
else if (dynstr_append(&a->db, "%s/db.json", a->dir.str))
|
||||||
{
|
{
|
||||||
fprintf(stderr, "%s: dynstr_append db failed\n", __func__);
|
fprintf(stderr, "%s: dynstr_append db failed\n", __func__);
|
||||||
|
|
1
configure
vendored
1
configure
vendored
|
@ -96,6 +96,7 @@ OBJECTS = \
|
||||||
auth.o \
|
auth.o \
|
||||||
base64.o \
|
base64.o \
|
||||||
cftw.o \
|
cftw.o \
|
||||||
|
crealpath.o \
|
||||||
hex.o \
|
hex.o \
|
||||||
jwt.o \
|
jwt.o \
|
||||||
main.o \
|
main.o \
|
||||||
|
|
174
crealpath.c
Normal file
174
crealpath.c
Normal file
|
@ -0,0 +1,174 @@
|
||||||
|
#define _POSIX_C_SOURCE 200809L
|
||||||
|
|
||||||
|
#include "crealpath.h"
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
static char *alloc_cwd(void)
|
||||||
|
{
|
||||||
|
size_t len = 1;
|
||||||
|
char *p = NULL;
|
||||||
|
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
char *const pp = realloc(p, len);
|
||||||
|
|
||||||
|
if (!pp)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s: realloc(3): %s\n", __func__, strerror(errno));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
p = pp;
|
||||||
|
|
||||||
|
if (!getcwd(pp, len))
|
||||||
|
{
|
||||||
|
if (errno != ERANGE)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s: getcwd(3): %s\n",
|
||||||
|
__func__, strerror(errno));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
len++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
free(p);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *get_parent(char *p)
|
||||||
|
{
|
||||||
|
char *last;
|
||||||
|
size_t len;
|
||||||
|
|
||||||
|
if (!p || !(last = strrchr(p, '/')) || strlen(last) < strlen("/a"))
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s: parent folder expected\n", __func__);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
len = last == p ? strlen("/") : last - p;
|
||||||
|
|
||||||
|
if (!(p = realloc(p, len + 1)))
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s: realloc(3): %s\n", __func__, strerror(errno));
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
p[len] = '\0';
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *get_component(const char **const path)
|
||||||
|
{
|
||||||
|
char *ret;
|
||||||
|
|
||||||
|
while (**path == '/')
|
||||||
|
(*path)++;
|
||||||
|
|
||||||
|
const char *const next = strchr(*path, '/');
|
||||||
|
const size_t len = next ? next - *path : strlen(*path);
|
||||||
|
|
||||||
|
if (!(ret = malloc(len + 1)))
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s: malloc(3): %s\n", __func__, strerror(errno));
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(ret, *path, len);
|
||||||
|
ret[len] = '\0';
|
||||||
|
*path += len;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *resolve_component(const char **const path, char *p)
|
||||||
|
{
|
||||||
|
char *const c = get_component(path);
|
||||||
|
|
||||||
|
if (!*c)
|
||||||
|
{
|
||||||
|
if (!p && !(p = strdup("/")))
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s: strdup(3): %s\n", __func__, strerror(errno));
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
else if (!strcmp(c, "..") && !(p = get_parent(p)))
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s: get_parent failed\n", __func__);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
else if (strcmp(c, "."))
|
||||||
|
{
|
||||||
|
const size_t len = p ? strlen(p) : 0, clen = strlen(c);
|
||||||
|
|
||||||
|
if (!(p = realloc(p, len + strlen("/") + clen + 1)))
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s: realloc(3): %s\n",
|
||||||
|
__func__, strerror(errno));
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
p[len] = '/';
|
||||||
|
memcpy(&p[len + 1], c, clen);
|
||||||
|
p[len + 1 + clen] = '\0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
end:
|
||||||
|
free(c);
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *resolve(const char *path, char *p)
|
||||||
|
{
|
||||||
|
while (*path)
|
||||||
|
if (!(p = resolve_component(&path, p)))
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s: resolve_component failed\n", __func__);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *crealpath(const char *const path)
|
||||||
|
{
|
||||||
|
char *p = NULL, *ret = NULL;
|
||||||
|
|
||||||
|
if (!*path)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s: expected non-empty path\n", __func__);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
else if (*path != '/' && !(p = alloc_cwd()))
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s: alloc_cwd failed\n", __func__);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
else if (!(p = resolve(path, p)))
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s: resolve failed\n", __func__);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = p;
|
||||||
|
|
||||||
|
end:
|
||||||
|
if (!ret)
|
||||||
|
free(p);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
8
crealpath.h
Normal file
8
crealpath.h
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
#ifndef CREALPATH_H
|
||||||
|
#define CREALPATH_H
|
||||||
|
|
||||||
|
/* Custom implementation of a GNU's realpath(1)-like function that
|
||||||
|
* does not require GNU extensions. */
|
||||||
|
char *crealpath(const char *path);
|
||||||
|
|
||||||
|
#endif
|
423
main.c
423
main.c
|
@ -1,6 +1,7 @@
|
||||||
#define _POSIX_C_SOURCE 200809L
|
#define _POSIX_C_SOURCE 200809L
|
||||||
|
|
||||||
#include "auth.h"
|
#include "auth.h"
|
||||||
|
#include "crealpath.h"
|
||||||
#include "cftw.h"
|
#include "cftw.h"
|
||||||
#include "hex.h"
|
#include "hex.h"
|
||||||
#include "page.h"
|
#include "page.h"
|
||||||
|
@ -32,6 +33,14 @@ struct form
|
||||||
char *key, *value;
|
char *key, *value;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct user_args
|
||||||
|
{
|
||||||
|
struct auth *a;
|
||||||
|
struct dynstr d;
|
||||||
|
int fd;
|
||||||
|
bool fifo;
|
||||||
|
};
|
||||||
|
|
||||||
static int redirect(struct http_response *const r)
|
static int redirect(struct http_response *const r)
|
||||||
{
|
{
|
||||||
*r = (const struct http_response)
|
*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,
|
static int serve_index(const struct http_payload *const p,
|
||||||
struct http_response *const r, void *const user)
|
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 page_login(r);
|
||||||
|
|
||||||
return redirect(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)
|
struct http_response *const r, void *const user)
|
||||||
{
|
{
|
||||||
int ret = -1;
|
int ret = -1;
|
||||||
struct auth *const a = user;
|
const struct user_args *const ua = user;
|
||||||
const char *const dir = auth_dir(a);
|
const char *const dir = auth_dir(ua->a);
|
||||||
struct dynstr d;
|
struct dynstr d;
|
||||||
|
|
||||||
dynstr_init(&d);
|
dynstr_init(&d);
|
||||||
|
@ -292,7 +301,7 @@ static int login(const struct http_payload *const pl,
|
||||||
int ret = -1;
|
int ret = -1;
|
||||||
size_t n = 0;
|
size_t n = 0;
|
||||||
struct form *forms = NULL;
|
struct form *forms = NULL;
|
||||||
struct auth *const a = user;
|
const struct user_args *const ua = user;
|
||||||
char *cookie = NULL;
|
char *cookie = NULL;
|
||||||
|
|
||||||
if ((ret = get_forms(pl, &forms, &n)))
|
if ((ret = get_forms(pl, &forms, &n)))
|
||||||
|
@ -302,7 +311,7 @@ static int login(const struct http_payload *const pl,
|
||||||
|
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
else if ((ret = check_credentials(a, forms, n, &cookie)))
|
else if ((ret = check_credentials(ua->a, forms, n, &cookie)))
|
||||||
{
|
{
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
fprintf(stderr, "%s: check_credentials failed\n", __func__);
|
fprintf(stderr, "%s: check_credentials failed\n", __func__);
|
||||||
|
@ -339,9 +348,9 @@ end:
|
||||||
static int logout(const struct http_payload *const p,
|
static int logout(const struct http_payload *const p,
|
||||||
struct http_response *const r, void *const user)
|
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 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)
|
if (res < 0)
|
||||||
{
|
{
|
||||||
|
@ -395,16 +404,22 @@ static bool path_isrel(const char *const path)
|
||||||
{
|
{
|
||||||
if (!strcmp(path, "..")
|
if (!strcmp(path, "..")
|
||||||
|| !strcmp(path, ".")
|
|| !strcmp(path, ".")
|
||||||
|| !strcmp(path, "./")
|
|| !strncmp(path, "./", strlen("./"))
|
||||||
|| !strcmp(path, "../")
|
|| !strncmp(path, "../", strlen("../"))
|
||||||
|
|| strstr(path, "/./")
|
||||||
|| strstr(path, "/../"))
|
|| strstr(path, "/../"))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
static const char suffix[] = "/..";
|
static const char *const suffixes[] = {"/.", "/.."};
|
||||||
const size_t n = strlen(path), sn = strlen(suffix);
|
|
||||||
|
|
||||||
if (n >= sn && !strcmp(path + n - sn, suffix))
|
for (size_t i = 0; i < sizeof suffixes / sizeof *suffixes; i++)
|
||||||
return true;
|
{
|
||||||
|
const char *const suffix = suffixes[i];
|
||||||
|
const size_t n = strlen(path), sn = strlen(suffix);
|
||||||
|
|
||||||
|
if (n >= sn && !strcmp(path + n - sn, suffix))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -428,8 +443,8 @@ static int getpublic(const struct http_payload *const p,
|
||||||
struct http_response *const r, void *const user)
|
struct http_response *const r, void *const user)
|
||||||
{
|
{
|
||||||
int ret = -1;
|
int ret = -1;
|
||||||
struct auth *const a = user;
|
const struct user_args *const ua = user;
|
||||||
const char *const adir = auth_dir(a),
|
const char *const adir = auth_dir(ua->a),
|
||||||
*const file = p->resource + strlen("/public/");
|
*const file = p->resource + strlen("/public/");
|
||||||
struct dynstr d;
|
struct dynstr d;
|
||||||
|
|
||||||
|
@ -710,12 +725,17 @@ static int search(const struct http_payload *const p,
|
||||||
struct http_response *const r, void *const user)
|
struct http_response *const r, void *const user)
|
||||||
{
|
{
|
||||||
int ret = -1;
|
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);
|
const char *const username = p->cookie.field, *const root = auth_dir(a);
|
||||||
struct page_search s = {0};
|
|
||||||
int (*f)(struct http_response *);
|
int (*f)(struct http_response *);
|
||||||
char *dir = NULL;
|
char *dir = NULL;
|
||||||
struct dynstr userd, d, res;
|
struct dynstr userd, d, res;
|
||||||
|
struct page_search s =
|
||||||
|
{
|
||||||
|
.username = username,
|
||||||
|
.adir = root
|
||||||
|
};
|
||||||
|
|
||||||
dynstr_init(&userd);
|
dynstr_init(&userd);
|
||||||
dynstr_init(&d);
|
dynstr_init(&d);
|
||||||
|
@ -771,7 +791,8 @@ end:
|
||||||
static int share(const struct http_payload *const p,
|
static int share(const struct http_payload *const p,
|
||||||
struct http_response *const r, void *const user)
|
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))
|
if (auth_cookie(a, &p->cookie))
|
||||||
{
|
{
|
||||||
|
@ -937,7 +958,8 @@ static int check_length(const unsigned long long len,
|
||||||
static int getnode(const struct http_payload *const p,
|
static int getnode(const struct http_payload *const p,
|
||||||
struct http_response *const r, void *const user)
|
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))
|
if (auth_cookie(a, &p->cookie))
|
||||||
{
|
{
|
||||||
|
@ -999,6 +1021,8 @@ static int getnode(const struct http_payload *const p,
|
||||||
.r = r,
|
.r = r,
|
||||||
.args = p->args,
|
.args = p->args,
|
||||||
.n_args = p->n_args,
|
.n_args = p->n_args,
|
||||||
|
.username = username,
|
||||||
|
.adir = adir,
|
||||||
.dir = dir.str,
|
.dir = dir.str,
|
||||||
.root = root.str,
|
.root = root.str,
|
||||||
.res = d.str,
|
.res = d.str,
|
||||||
|
@ -1019,7 +1043,8 @@ end:
|
||||||
static int getnode_head(const struct http_payload *const p,
|
static int getnode_head(const struct http_payload *const p,
|
||||||
struct http_response *const r, void *const user)
|
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))
|
if (auth_cookie(a, &p->cookie))
|
||||||
{
|
{
|
||||||
|
@ -1158,6 +1183,93 @@ static int rename_or_move(const char *const old, const char *const new)
|
||||||
return res;
|
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)
|
static int check_upload_dir(const char *const dir)
|
||||||
{
|
{
|
||||||
struct stat sb;
|
struct stat sb;
|
||||||
|
@ -1189,10 +1301,12 @@ static int check_upload_dir(const char *const dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
static int upload_file(const struct http_post_file *const f,
|
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;
|
int ret = -1;
|
||||||
struct dynstr dird, d;
|
struct dynstr dird, d;
|
||||||
|
char *abspath = NULL;
|
||||||
|
|
||||||
dynstr_init(&dird);
|
dynstr_init(&dird);
|
||||||
dynstr_init(&d);
|
dynstr_init(&d);
|
||||||
|
@ -1224,10 +1338,24 @@ static int upload_file(const struct http_post_file *const f,
|
||||||
fprintf(stderr, "%s: rename_or_move failed\n", __func__);
|
fprintf(stderr, "%s: rename_or_move failed\n", __func__);
|
||||||
goto end;
|
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;
|
ret = 0;
|
||||||
|
|
||||||
end:
|
end:
|
||||||
|
free(abspath);
|
||||||
dynstr_free(&dird);
|
dynstr_free(&dird);
|
||||||
dynstr_free(&d);
|
dynstr_free(&d);
|
||||||
return ret;
|
return ret;
|
||||||
|
@ -1285,8 +1413,9 @@ static const char *get_upload_dir(const struct http_post *const po)
|
||||||
}
|
}
|
||||||
|
|
||||||
static int upload_files(const struct http_payload *const p,
|
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 struct http_post *const po = &p->u.post;
|
||||||
const char *const root = auth_dir(a), *const user = p->cookie.field,
|
const char *const root = auth_dir(a), *const user = p->cookie.field,
|
||||||
*const dir = get_upload_dir(po);
|
*const dir = get_upload_dir(po);
|
||||||
|
@ -1328,7 +1457,7 @@ static int upload_files(const struct http_payload *const p,
|
||||||
|
|
||||||
for (size_t i = 0; i < po->nfiles; i++)
|
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)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
|
@ -1345,9 +1474,9 @@ static int upload_files(const struct http_payload *const p,
|
||||||
static int upload(const struct http_payload *const p,
|
static int upload(const struct http_payload *const p,
|
||||||
struct http_response *const r, void *const user)
|
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__);
|
fprintf(stderr, "%s: auth_cookie failed\n", __func__);
|
||||||
return page_forbidden(r);
|
return page_forbidden(r);
|
||||||
|
@ -1362,14 +1491,15 @@ static int upload(const struct http_payload *const p,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return upload_files(p, r, a);
|
return upload_files(p, r, ua);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int createdir(const struct http_payload *const p,
|
static int createdir(const struct http_payload *const p,
|
||||||
struct http_response *const r, void *const user)
|
struct http_response *const r, void *const user)
|
||||||
{
|
{
|
||||||
int ret = -1;
|
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 dynstr d, userd;
|
||||||
struct form *forms = NULL;
|
struct form *forms = NULL;
|
||||||
size_t n = 0;
|
size_t n = 0;
|
||||||
|
@ -1573,14 +1703,14 @@ static int confirm_rm(const struct http_payload *const p,
|
||||||
struct http_response *const r, void *const user)
|
struct http_response *const r, void *const user)
|
||||||
{
|
{
|
||||||
int ret = -1;
|
int ret = -1;
|
||||||
struct auth *const a = user;
|
const struct user_args *const ua = user;
|
||||||
struct form *forms = NULL;
|
struct form *forms = NULL;
|
||||||
const char **items = NULL;
|
const char **items = NULL;
|
||||||
size_t n = 0;
|
size_t n = 0;
|
||||||
int (*f)(struct http_response *);
|
int (*f)(struct http_response *);
|
||||||
struct page_rm rm = {0};
|
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__);
|
fprintf(stderr, "%s: auth_cookie failed\n", __func__);
|
||||||
ret = page_forbidden(r);
|
ret = page_forbidden(r);
|
||||||
|
@ -1644,36 +1774,82 @@ static const char *find_rm_dir(const struct form *const forms, const size_t n,
|
||||||
return dir;
|
return dir;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int rm_dir_contents(const char *const fpath,
|
static int fifo_notify_remove(int *const fd, const char *const path,
|
||||||
const struct stat *const sb, bool *const done, void *const user)
|
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",
|
fprintf(stderr, "%s: fifo_writeall header failed\n", __func__);
|
||||||
__func__, fpath, strerror(errno));
|
|
||||||
return -1;
|
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",
|
fprintf(stderr, "%s: fifo_writeall body failed\n", __func__);
|
||||||
__func__, fpath, strerror(errno));
|
return -1;
|
||||||
|
}
|
||||||
|
else if (fifo_writeall(fd, path, lf, strlen(lf)))
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s: fifo_writeall lf failed\n", __func__);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
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;
|
int ret = -1;
|
||||||
DIR *const d = opendir(path);
|
DIR *const d = opendir(path);
|
||||||
|
char *abspath = NULL;
|
||||||
|
|
||||||
if (!d)
|
if (!d)
|
||||||
{
|
{
|
||||||
fprintf(stderr, "%s: opendir(3): %s\n", __func__, strerror(errno));
|
fprintf(stderr, "%s: opendir(3): %s\n", __func__, strerror(errno));
|
||||||
goto end;
|
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__);
|
fprintf(stderr, "%s: rm_dir_contents failed\n", __func__);
|
||||||
goto end;
|
goto end;
|
||||||
|
@ -1684,6 +1860,19 @@ static int rmdir_r(const char *const path)
|
||||||
__func__, path, strerror(errno));
|
__func__, path, strerror(errno));
|
||||||
goto end;
|
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;
|
ret = 0;
|
||||||
|
|
||||||
|
@ -1694,10 +1883,12 @@ end:
|
||||||
ret = -1;
|
ret = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
free(abspath);
|
||||||
return ret;
|
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;
|
int ret = -1;
|
||||||
struct stat sb;
|
struct stat sb;
|
||||||
|
@ -1722,15 +1913,23 @@ static int rm_item(const char *const root, const char *const item)
|
||||||
|
|
||||||
goto end;
|
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__);
|
fprintf(stderr, "%s: rmdir_r failed\n", __func__);
|
||||||
goto end;
|
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));
|
if (remove(d.str))
|
||||||
goto end;
|
{
|
||||||
|
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;
|
ret = 0;
|
||||||
|
@ -1740,8 +1939,8 @@ end:
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int do_rm(const struct form *const forms, const size_t n,
|
static int do_rm(struct user_args *const ua, const struct form *const forms,
|
||||||
const char *const dir)
|
const size_t n, const char *const dir)
|
||||||
{
|
{
|
||||||
for (size_t i = 0; i < n; i++)
|
for (size_t i = 0; i < n; i++)
|
||||||
{
|
{
|
||||||
|
@ -1756,7 +1955,7 @@ static int do_rm(const struct form *const forms, const size_t n,
|
||||||
fprintf(stderr, "%s: invalid path %s\n", __func__, path);
|
fprintf(stderr, "%s: invalid path %s\n", __func__, path);
|
||||||
return 1;
|
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__);
|
fprintf(stderr, "%s: rm_item failed\n", __func__);
|
||||||
return -1;
|
return -1;
|
||||||
|
@ -1771,7 +1970,8 @@ static int rm(const struct http_payload *const p,
|
||||||
struct http_response *const r, void *const user)
|
struct http_response *const r, void *const user)
|
||||||
{
|
{
|
||||||
int ret = -1;
|
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;
|
struct form *forms = NULL;
|
||||||
size_t n = 0;
|
size_t n = 0;
|
||||||
const struct http_cookie *const c = &p->cookie;
|
const struct http_cookie *const c = &p->cookie;
|
||||||
|
@ -1822,7 +2022,7 @@ static int rm(const struct http_payload *const p,
|
||||||
fprintf(stderr, "%s: dynstr_append failed\n", __func__);
|
fprintf(stderr, "%s: dynstr_append failed\n", __func__);
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
else if ((ret = do_rm(forms, n, userdir.str)))
|
else if ((ret = do_rm(ua, forms, n, userdir.str)))
|
||||||
{
|
{
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
fprintf(stderr, "%s: do_rm failed\n", __func__);
|
fprintf(stderr, "%s: do_rm failed\n", __func__);
|
||||||
|
@ -1857,14 +2057,63 @@ end:
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int getthumbnail(const struct http_payload *const p,
|
||||||
|
struct http_response *const r, void *const user)
|
||||||
|
{
|
||||||
|
int ret = -1;
|
||||||
|
const struct user_args *const ua = user;
|
||||||
|
const struct auth *const a = ua->a;
|
||||||
|
struct dynstr d, dir;
|
||||||
|
|
||||||
|
dynstr_init(&d);
|
||||||
|
dynstr_init(&dir);
|
||||||
|
|
||||||
|
if (auth_cookie(a, &p->cookie))
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s: auth_cookie failed\n", __func__);
|
||||||
|
ret = page_forbidden(r);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *const adir = auth_dir(a);
|
||||||
|
|
||||||
|
if (!adir)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s: auth_dir failed\n", __func__);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
else if (dynstr_append(&d, "%s%s", adir, p->resource))
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s: dynstr_append failed\n", __func__);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
else if (dynstr_dup(&dir, &d))
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s: dynstr_dup failed\n", __func__);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
else if (page_thumbnail(r, d.str))
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s: page_public failed\n", __func__);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = 0;
|
||||||
|
|
||||||
|
end:
|
||||||
|
dynstr_free(&d);
|
||||||
|
dynstr_free(&dir);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
static void usage(char *const argv[])
|
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[],
|
static int parse_args(const int argc, char *const argv[],
|
||||||
const char **const dir, unsigned short *const port,
|
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");
|
const char *const envtmp = getenv("TMPDIR");
|
||||||
int opt;
|
int opt;
|
||||||
|
@ -1873,10 +2122,14 @@ static int parse_args(const int argc, char *const argv[],
|
||||||
*port = 0;
|
*port = 0;
|
||||||
*tmpdir = envtmp ? envtmp : "/tmp";
|
*tmpdir = envtmp ? envtmp : "/tmp";
|
||||||
|
|
||||||
while ((opt = getopt(argc, argv, "t:p:")) != -1)
|
while ((opt = getopt(argc, argv, "Ft:p:")) != -1)
|
||||||
{
|
{
|
||||||
switch (opt)
|
switch (opt)
|
||||||
{
|
{
|
||||||
|
case 'F':
|
||||||
|
*fifo = true;
|
||||||
|
break;
|
||||||
|
|
||||||
case 't':
|
case 't':
|
||||||
*tmpdir = optarg;
|
*tmpdir = optarg;
|
||||||
break;
|
break;
|
||||||
|
@ -2085,7 +2338,8 @@ static int add_urls(struct handler *const h, void *const user)
|
||||||
{.url = "/upload", .op = HTTP_OP_POST, .f = upload},
|
{.url = "/upload", .op = HTTP_OP_POST, .f = upload},
|
||||||
{.url = "/mkdir", .op = HTTP_OP_POST, .f = createdir},
|
{.url = "/mkdir", .op = HTTP_OP_POST, .f = createdir},
|
||||||
{.url = "/confirm/rm", .op = HTTP_OP_POST, .f = confirm_rm},
|
{.url = "/confirm/rm", .op = HTTP_OP_POST, .f = confirm_rm},
|
||||||
{.url = "/rm", .op = HTTP_OP_POST, .f = rm}
|
{.url = "/rm", .op = HTTP_OP_POST, .f = rm},
|
||||||
|
{.url = "/thumbnails/*", .op = HTTP_OP_GET, .f = getthumbnail}
|
||||||
};
|
};
|
||||||
|
|
||||||
for (size_t i = 0; i < sizeof urls / sizeof *urls; i++)
|
for (size_t i = 0; i < sizeof urls / sizeof *urls; i++)
|
||||||
|
@ -2102,25 +2356,68 @@ static int add_urls(struct handler *const h, void *const user)
|
||||||
return 0;
|
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 main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
int ret = EXIT_FAILURE;
|
int ret = EXIT_FAILURE;
|
||||||
struct handler *h = NULL;
|
struct handler *h = NULL;
|
||||||
struct auth *a = NULL;
|
|
||||||
const char *dir, *tmpdir;
|
const char *dir, *tmpdir;
|
||||||
unsigned short port;
|
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)
|
|| init_dirs(dir)
|
||||||
|| ensure_style(dir)
|
|| ensure_style(dir)
|
||||||
|| !(a = auth_alloc(dir)))
|
|| !(ua.a = auth_alloc(dir))
|
||||||
|
|| (ua.fifo && ensure_fifo(dir, &ua)))
|
||||||
goto end;
|
goto end;
|
||||||
|
|
||||||
const struct handler_cfg cfg =
|
const struct handler_cfg cfg =
|
||||||
{
|
{
|
||||||
.length = check_length,
|
.length = check_length,
|
||||||
.tmpdir = tmpdir,
|
.tmpdir = tmpdir,
|
||||||
.user = a,
|
.user = ua.a,
|
||||||
.post =
|
.post =
|
||||||
{
|
{
|
||||||
/* Arbitrary limit. */
|
/* Arbitrary limit. */
|
||||||
|
@ -2133,7 +2430,7 @@ int main(int argc, char *argv[])
|
||||||
unsigned short outport;
|
unsigned short outport;
|
||||||
|
|
||||||
if (!(h = handler_alloc(&cfg))
|
if (!(h = handler_alloc(&cfg))
|
||||||
|| add_urls(h, a)
|
|| add_urls(h, &ua)
|
||||||
|| handler_listen(h, port, &outport))
|
|| handler_listen(h, port, &outport))
|
||||||
goto end;
|
goto end;
|
||||||
|
|
||||||
|
@ -2148,7 +2445,13 @@ int main(int argc, char *argv[])
|
||||||
ret = EXIT_SUCCESS;
|
ret = EXIT_SUCCESS;
|
||||||
|
|
||||||
end:
|
end:
|
||||||
auth_free(a);
|
|
||||||
handler_free(h);
|
handler_free(h);
|
||||||
|
|
||||||
|
if (user_args_free(&ua))
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s: user_args_free failed\n", __func__);
|
||||||
|
ret = EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
158
page.c
158
page.c
|
@ -101,6 +101,67 @@ end:
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int prepare_thumbnail(struct html_node *const n,
|
||||||
|
const struct stat *const sb, const struct page_resource *const pr,
|
||||||
|
const char *const name, const char *const sep)
|
||||||
|
{
|
||||||
|
int ret = -1;
|
||||||
|
struct html_node *img;
|
||||||
|
struct dynstr abs, rel;
|
||||||
|
struct stat tsb;
|
||||||
|
|
||||||
|
dynstr_init(&abs);
|
||||||
|
dynstr_init(&rel);
|
||||||
|
|
||||||
|
if (!S_ISREG(sb->st_mode))
|
||||||
|
{
|
||||||
|
ret = 0;
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
else if (!pr->adir || !pr->username)
|
||||||
|
{
|
||||||
|
ret = 0;
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
else if (dynstr_append(&rel, "/thumbnails/%s%s%s%s",
|
||||||
|
pr->username, pr->dir + strlen("/user"), sep, name))
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s: dynstr_append rel failed\n", __func__);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
else if (dynstr_append(&abs, "%s%s", pr->adir, rel.str))
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s: dynstr_append rel failed\n", __func__);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
else if (stat(abs.str, &tsb))
|
||||||
|
{
|
||||||
|
if (errno == ENOENT)
|
||||||
|
ret = 0;
|
||||||
|
else
|
||||||
|
fprintf(stderr, "%s: stat(2): %s\n", __func__, strerror(errno));
|
||||||
|
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
else if (!(img = html_node_add_child(n, "img")))
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s: html_node_add_child failed\n", __func__);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
else if (html_node_add_attr(img, "src", rel.str))
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s: html_node_add_attr src failed\n", __func__);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = 0;
|
||||||
|
|
||||||
|
end:
|
||||||
|
dynstr_free(&abs);
|
||||||
|
dynstr_free(&rel);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
static int prepare_name(struct html_node *const n, struct stat *const sb,
|
static int prepare_name(struct html_node *const n, struct stat *const sb,
|
||||||
const char *const dir, const char *const name)
|
const char *const dir, const char *const name)
|
||||||
{
|
{
|
||||||
|
@ -360,19 +421,19 @@ end:
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int add_element(struct html_node *const n, const char *const dir,
|
static int add_element(struct html_node *const n,
|
||||||
const char *const res, const char *const name, const bool chbx,
|
const struct page_resource *const pr, const char *const name,
|
||||||
struct element_stats *const st)
|
const bool chbx, struct element_stats *const st)
|
||||||
{
|
{
|
||||||
int ret = -1;
|
int ret = -1;
|
||||||
enum {RM_CHECKBOX, NAME, SIZE, DATE, SHARE, PREVIEW, COLUMNS};
|
enum {RM_CHECKBOX, THUMBNAIL, NAME, SIZE, DATE, SHARE, PREVIEW, COLUMNS};
|
||||||
struct html_node *tr, *td[COLUMNS];
|
struct html_node *tr, *td[COLUMNS];
|
||||||
struct dynstr path;
|
struct dynstr path;
|
||||||
const char *const sep = res[strlen(res) - 1] != '/' ? "/" : "";
|
const char *const sep = pr->res[strlen(pr->res) - 1] != '/' ? "/" : "";
|
||||||
|
|
||||||
dynstr_init(&path);
|
dynstr_init(&path);
|
||||||
|
|
||||||
if (dynstr_append(&path, "%s%s%s", res, sep, name))
|
if (dynstr_append(&path, "%s%s%s", pr->res, sep, name))
|
||||||
{
|
{
|
||||||
fprintf(stderr, "%s: dynstr_append failed\n", __func__);
|
fprintf(stderr, "%s: dynstr_append failed\n", __func__);
|
||||||
goto end;
|
goto end;
|
||||||
|
@ -414,7 +475,12 @@ static int add_element(struct html_node *const n, const char *const dir,
|
||||||
fprintf(stderr, "%s: prepare_rm_checkbox failed\n", __func__);
|
fprintf(stderr, "%s: prepare_rm_checkbox failed\n", __func__);
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
else if (prepare_name(td[NAME], &sb, dir, name))
|
else if (prepare_thumbnail(td[THUMBNAIL], &sb, pr, name, sep))
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s: prepare_thumbnail failed\n", __func__);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
else if (prepare_name(td[NAME], &sb, pr->dir, name))
|
||||||
{
|
{
|
||||||
fprintf(stderr, "%s: prepare_name failed\n", __func__);
|
fprintf(stderr, "%s: prepare_name failed\n", __func__);
|
||||||
goto end;
|
goto end;
|
||||||
|
@ -429,12 +495,12 @@ static int add_element(struct html_node *const n, const char *const dir,
|
||||||
fprintf(stderr, "%s: prepare_date failed\n", __func__);
|
fprintf(stderr, "%s: prepare_date failed\n", __func__);
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
else if (prepare_share(td[SHARE], &sb, dir, name))
|
else if (prepare_share(td[SHARE], &sb, pr->dir, name))
|
||||||
{
|
{
|
||||||
fprintf(stderr, "%s: prepare_date failed\n", __func__);
|
fprintf(stderr, "%s: prepare_date failed\n", __func__);
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
else if (prepare_preview(td[PREVIEW], &sb, dir, name))
|
else if (prepare_preview(td[PREVIEW], &sb, pr->dir, name))
|
||||||
{
|
{
|
||||||
fprintf(stderr, "%s: prepare_date failed\n", __func__);
|
fprintf(stderr, "%s: prepare_date failed\n", __func__);
|
||||||
goto end;
|
goto end;
|
||||||
|
@ -1151,13 +1217,12 @@ end:
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int add_elements(const char *const root, const char *const res,
|
static int add_elements(const struct page_resource *const pr,
|
||||||
const char *const dir, struct html_node *const table,
|
struct html_node *const table, struct element_stats *const st)
|
||||||
struct element_stats *const st)
|
|
||||||
{
|
{
|
||||||
int ret = -1;
|
int ret = -1;
|
||||||
struct dirent **pde = NULL;
|
struct dirent **pde = NULL;
|
||||||
const int n = scandir(res, &pde, NULL, alphasort);
|
const int n = scandir(pr->res, &pde, NULL, alphasort);
|
||||||
|
|
||||||
if (n < 0)
|
if (n < 0)
|
||||||
{
|
{
|
||||||
|
@ -1173,9 +1238,9 @@ static int add_elements(const char *const root, const char *const res,
|
||||||
const char *const name = de->d_name;
|
const char *const name = de->d_name;
|
||||||
|
|
||||||
if (!strcmp(name, ".")
|
if (!strcmp(name, ".")
|
||||||
|| (!strcmp(name, "..") && !strcmp(root, res)))
|
|| (!strcmp(name, "..") && !strcmp(pr->root, pr->res)))
|
||||||
continue;
|
continue;
|
||||||
else if (add_element(table, dir, res, name, true, st))
|
else if (add_element(table, pr, name, true, st))
|
||||||
{
|
{
|
||||||
fprintf(stderr, "%s: add_element failed\n", __func__);
|
fprintf(stderr, "%s: add_element failed\n", __func__);
|
||||||
goto end;
|
goto end;
|
||||||
|
@ -1242,7 +1307,7 @@ static int list_dir(const struct page_resource *const pr)
|
||||||
fprintf(stderr, "%s: resource_layout failed\n", __func__);
|
fprintf(stderr, "%s: resource_layout failed\n", __func__);
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
else if (add_elements(pr->root, pr->res, pr->dir, table, &st))
|
else if (add_elements(pr, table, &st))
|
||||||
{
|
{
|
||||||
fprintf(stderr, "%s: read_elements failed\n", __func__);
|
fprintf(stderr, "%s: read_elements failed\n", __func__);
|
||||||
goto end;
|
goto end;
|
||||||
|
@ -1891,8 +1956,15 @@ static int add_search_results(struct html_node *const n,
|
||||||
for (size_t i = 0; i < s->n; i++)
|
for (size_t i = 0; i < s->n; i++)
|
||||||
{
|
{
|
||||||
const struct page_search_result *const r = &s->results[i];
|
const struct page_search_result *const r = &s->results[i];
|
||||||
|
const struct page_resource pr =
|
||||||
|
{
|
||||||
|
.dir = "/user/",
|
||||||
|
.adir = s->adir,
|
||||||
|
.username = s->username,
|
||||||
|
.res = s->root
|
||||||
|
};
|
||||||
|
|
||||||
if (add_element(table, "/user/", s->root, r->name, false, NULL))
|
if (add_element(table, &pr, r->name, false, NULL))
|
||||||
{
|
{
|
||||||
fprintf(stderr, "%s: add_element failed\n", __func__);
|
fprintf(stderr, "%s: add_element failed\n", __func__);
|
||||||
return -1;
|
return -1;
|
||||||
|
@ -2308,3 +2380,55 @@ int page_head_resource(struct http_response *const r, const char *const res)
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int page_thumbnail(struct http_response *const r, const char *const res)
|
||||||
|
{
|
||||||
|
int ret = -1;
|
||||||
|
const int fd = open(res, O_RDONLY);
|
||||||
|
bool fdopened = false;
|
||||||
|
struct stat sb;
|
||||||
|
|
||||||
|
if (fd < 0)
|
||||||
|
{
|
||||||
|
if (errno == ENOENT)
|
||||||
|
ret = page_not_found(r);
|
||||||
|
else
|
||||||
|
fprintf(stderr, "%s: open(2) %s: %s\n",
|
||||||
|
__func__, res, strerror(errno));
|
||||||
|
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
else if (fstat(fd, &sb))
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s: fstat(2) %s: %s\n",
|
||||||
|
__func__, res, strerror(errno));
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mode_t m = sb.st_mode;
|
||||||
|
|
||||||
|
if (!S_ISREG(m))
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s: only regular files are supported\n", __func__);
|
||||||
|
ret = 1;
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
else if (serve_file(r, &sb, res, true, fd, &fdopened))
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s: serve_file failed\n", __func__);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = 0;
|
||||||
|
|
||||||
|
end:
|
||||||
|
|
||||||
|
if (!fdopened && fd >= 0 && close(fd))
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s: close(2) %s: %s\n",
|
||||||
|
__func__, res, strerror(errno));
|
||||||
|
ret = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
5
page.h
5
page.h
|
@ -13,7 +13,7 @@ struct page_quota
|
||||||
struct page_resource
|
struct page_resource
|
||||||
{
|
{
|
||||||
struct http_response *r;
|
struct http_response *r;
|
||||||
const char *dir, *root, *res;
|
const char *adir, *username, *dir, *root, *res;
|
||||||
const struct page_quota *q;
|
const struct page_quota *q;
|
||||||
const struct http_arg *args;
|
const struct http_arg *args;
|
||||||
size_t n_args;
|
size_t n_args;
|
||||||
|
@ -26,7 +26,7 @@ struct page_search
|
||||||
char *name;
|
char *name;
|
||||||
} *results;
|
} *results;
|
||||||
|
|
||||||
const char *root;
|
const char *root, *adir, *username;
|
||||||
size_t n;
|
size_t n;
|
||||||
bool limit_exceeded;
|
bool limit_exceeded;
|
||||||
};
|
};
|
||||||
|
@ -50,5 +50,6 @@ int page_quota_exceeded(struct http_response *r, unsigned long long len,
|
||||||
unsigned long long quota);
|
unsigned long long quota);
|
||||||
int page_search(struct http_response *r, const struct page_search *s);
|
int page_search(struct http_response *r, const struct page_search *s);
|
||||||
int page_rm(struct http_response *r, const struct page_rm *rm);
|
int page_rm(struct http_response *r, const struct page_rm *rm);
|
||||||
|
int page_thumbnail(struct http_response *r, const char *res);
|
||||||
|
|
||||||
#endif /* PAGE_H */
|
#endif /* PAGE_H */
|
||||||
|
|
10
thumbnail/CMakeLists.txt
Normal file
10
thumbnail/CMakeLists.txt
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
cmake_minimum_required(VERSION 3.13)
|
||||||
|
project(thumbnail)
|
||||||
|
add_executable(${PROJECT_NAME} cftw.c crealpath.c main.c)
|
||||||
|
add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/../libweb/dynstr
|
||||||
|
${CMAKE_BINARY_DIR}/dynstr)
|
||||||
|
target_include_directories(${PROJECT_NAME} PRIVATE ..)
|
||||||
|
target_compile_definitions(${PROJECT_NAME} PRIVATE _FILE_OFFSET_BITS=64)
|
||||||
|
include(FindPkgConfig)
|
||||||
|
pkg_check_modules(ImageMagick REQUIRED IMPORTED_TARGET ImageMagick)
|
||||||
|
target_link_libraries(${PROJECT_NAME} PRIVATE dynstr PkgConfig::ImageMagick)
|
29
thumbnail/Makefile
Normal file
29
thumbnail/Makefile
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
.POSIX:
|
||||||
|
|
||||||
|
PROJECT = thumbnail
|
||||||
|
O = -Og
|
||||||
|
CDEFS = -D_FILE_OFFSET_BITS=64 # Required for large file support on 32-bit.
|
||||||
|
CFLAGS = $(O) $(CDEFS) -g -Wall -I../libweb/dynstr/include -I. \
|
||||||
|
$$(pkg-config --cflags ImageMagick) -MD -MF $(@:.o=.d)
|
||||||
|
LDFLAGS = $(LIBS)
|
||||||
|
DEPS = $(OBJECTS:.o=.d)
|
||||||
|
DYNSTR = ../libweb/dynstr/libdynstr.a
|
||||||
|
DYNSTR_LIBS = -L../libweb/dynstr -ldynstr
|
||||||
|
LIBS = $$(pkg-config --libs ImageMagick) $(DYNSTR_LIBS)
|
||||||
|
OBJECTS = \
|
||||||
|
crealpath.o \
|
||||||
|
main.o \
|
||||||
|
cftw.o
|
||||||
|
|
||||||
|
all: $(PROJECT)
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f $(OBJECTS) $(DEPS)
|
||||||
|
|
||||||
|
$(PROJECT): $(OBJECTS) $(DYNSTR)
|
||||||
|
$(CC) $(OBJECTS) $(LDFLAGS) $(LIBS) -o $@
|
||||||
|
|
||||||
|
$(DYNSTR):
|
||||||
|
+cd ../dynstr && $(MAKE)
|
||||||
|
|
||||||
|
-include $(DEPS)
|
66
thumbnail/README.md
Normal file
66
thumbnail/README.md
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
# Thumbnail generation tool for `slcl`
|
||||||
|
|
||||||
|
This directory defines a separation application to be used in conjunction with
|
||||||
|
`slcl`, aimed to update the thumbnail database automatically whenever files
|
||||||
|
are added/removed to/from the database.
|
||||||
|
|
||||||
|
### Root permissions
|
||||||
|
|
||||||
|
This application requires no `root` permissions. So, in order to avoid the
|
||||||
|
risk for security bugs, **please do not run this application as `root`**.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- A POSIX environment.
|
||||||
|
- [`dynstr`](https://gitea.privatedns.org/xavi92/dynstr)
|
||||||
|
(provided as a `git` submodule).
|
||||||
|
- MagickCore
|
||||||
|
- CMake (optional).
|
||||||
|
|
||||||
|
### Ubuntu / Debian
|
||||||
|
|
||||||
|
#### Mandatory packages
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo apt install build-essential libmagickcore-6.q16-dev
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Optional packages
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo apt install cmake
|
||||||
|
```
|
||||||
|
|
||||||
|
## How to use
|
||||||
|
### Build
|
||||||
|
|
||||||
|
Similarly to `slcl`, two build environments are provided - feel free to choose
|
||||||
|
any of them:
|
||||||
|
|
||||||
|
- A mostly POSIX-compliant [`Makefile`](Makefile).
|
||||||
|
- A [`CMakeLists.txt`](CMakeLists.txt).
|
||||||
|
|
||||||
|
`thumbnail` can be built using the standard build process:
|
||||||
|
|
||||||
|
#### Make
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ make
|
||||||
|
```
|
||||||
|
|
||||||
|
#### CMake
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ mkdir build/
|
||||||
|
$ cmake ..
|
||||||
|
$ cmake --build .
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
This tool only consumes one argument: the directory containing the instance
|
||||||
|
database, which must be the same used by `slcl`. For example:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
thumbnail ~/my-db
|
||||||
|
```
|
1
thumbnail/cftw.c
Symbolic link
1
thumbnail/cftw.c
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../cftw.c
|
1
thumbnail/cftw.h
Symbolic link
1
thumbnail/cftw.h
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../cftw.h
|
1
thumbnail/crealpath.c
Symbolic link
1
thumbnail/crealpath.c
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../crealpath.c
|
1
thumbnail/crealpath.h
Symbolic link
1
thumbnail/crealpath.h
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../crealpath.h
|
758
thumbnail/main.c
Normal file
758
thumbnail/main.c
Normal file
|
@ -0,0 +1,758 @@
|
||||||
|
#define _POSIX_C_SOURCE 200809L
|
||||||
|
|
||||||
|
#include "crealpath.h"
|
||||||
|
#include <cftw.h>
|
||||||
|
#include <dynstr.h>
|
||||||
|
#include <magick/api.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <poll.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <strings.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
static int print_error(const char *const func, const char *const ifunc,
|
||||||
|
const ExceptionInfo *const exc)
|
||||||
|
{
|
||||||
|
const char *const reason = exc->reason ? exc->reason : "n/a",
|
||||||
|
*const description = exc->description ?
|
||||||
|
exc->description : "n/a";
|
||||||
|
|
||||||
|
return fprintf(stderr, "%s: %s failed: reason: %s, description: %s\n",
|
||||||
|
func, ifunc, reason, description);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int create_thumbnail(const char *const src, const char *const dst,
|
||||||
|
const unsigned long size)
|
||||||
|
{
|
||||||
|
int ret = -1;
|
||||||
|
const size_t slen = strlen(src), dlen = strlen(dst);
|
||||||
|
ImageInfo *info = NULL;
|
||||||
|
Image *i = NULL, *t = NULL;
|
||||||
|
ExceptionInfo *const exc = AcquireExceptionInfo();
|
||||||
|
/* Ensure permissions for files created by MagickCore.*/
|
||||||
|
const mode_t mode = umask(~(S_IRUSR | S_IWUSR));
|
||||||
|
|
||||||
|
if (slen >= sizeof i->filename)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s: src maximum length exceeded (%zu, max %zu)\n",
|
||||||
|
__func__, slen, sizeof i->filename - 1);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
else if (dlen >= sizeof t->filename)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s: dst maximum length exceeded (%zu, max %zu)\n",
|
||||||
|
__func__, slen, sizeof t->filename - 1);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
else if (!(info = CloneImageInfo(NULL)))
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s: CloneImageInfo failed\n", __func__);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
strcpy(info->filename, src);
|
||||||
|
info->adjoin = MagickTrue;
|
||||||
|
|
||||||
|
if (!(i = ReadImage(info, exc)))
|
||||||
|
{
|
||||||
|
switch (exc->severity)
|
||||||
|
{
|
||||||
|
case DelegateError:
|
||||||
|
case CorruptImageError:
|
||||||
|
case FileOpenError:
|
||||||
|
case MissingDelegateError:
|
||||||
|
ret = 1;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
print_error(__func__, "ReadImage", exc);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!i->rows || !i->columns)
|
||||||
|
{
|
||||||
|
ret = 1;
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned long x, y;
|
||||||
|
|
||||||
|
if (i->columns > i->rows)
|
||||||
|
{
|
||||||
|
x = i->columns > size ? size : i->columns;
|
||||||
|
y = x * i->rows / i->columns;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
y = i->rows > size ? size : i->rows;
|
||||||
|
x = y * i->columns / i->rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(t = ResizeImage(i, x, y, PointFilter, 1, exc)))
|
||||||
|
{
|
||||||
|
print_error(__func__, "ResizeImage", exc);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
else if (WriteImages(info, t, dst, exc) != MagickTrue)
|
||||||
|
{
|
||||||
|
print_error(__func__, "WriteImages", exc);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("Created %s\n", dst);
|
||||||
|
ret = 0;
|
||||||
|
|
||||||
|
end:
|
||||||
|
umask(mode);
|
||||||
|
|
||||||
|
if (info)
|
||||||
|
DestroyImageInfo(info);
|
||||||
|
|
||||||
|
if (i)
|
||||||
|
DestroyImage(i);
|
||||||
|
|
||||||
|
if (t)
|
||||||
|
DestroyImage(t);
|
||||||
|
|
||||||
|
if (exc)
|
||||||
|
DestroyExceptionInfo(exc);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *fifo_getline(const int fd)
|
||||||
|
{
|
||||||
|
char *ret = NULL;
|
||||||
|
size_t n = 0;
|
||||||
|
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
char c;
|
||||||
|
ssize_t sz;
|
||||||
|
|
||||||
|
again:
|
||||||
|
sz = read(fd, &c, sizeof c);
|
||||||
|
|
||||||
|
if (sz < 0)
|
||||||
|
{
|
||||||
|
if (errno == EAGAIN || errno == EINTR)
|
||||||
|
goto again;
|
||||||
|
else
|
||||||
|
fprintf(stderr, "%s: read(2): %s\n", __func__, strerror(errno));
|
||||||
|
|
||||||
|
goto failure;
|
||||||
|
}
|
||||||
|
else if (c == '\n')
|
||||||
|
break;
|
||||||
|
|
||||||
|
char *const aux = realloc(ret, n + 1);
|
||||||
|
|
||||||
|
if (!aux)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s: realloc(3): %s\n",
|
||||||
|
__func__, strerror(errno));
|
||||||
|
goto failure;
|
||||||
|
}
|
||||||
|
|
||||||
|
aux[n++] = c;
|
||||||
|
ret = aux;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *const aux = realloc(ret, n + 1);
|
||||||
|
|
||||||
|
if (!aux)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s: realloc(3): %s\n", __func__, strerror(errno));
|
||||||
|
goto failure;
|
||||||
|
}
|
||||||
|
|
||||||
|
aux[n++] = '\0';
|
||||||
|
ret = aux;
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
failure:
|
||||||
|
free(ret);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ensure_dir(const char *const path, const size_t len)
|
||||||
|
{
|
||||||
|
int ret = -1;
|
||||||
|
struct dynstr d;
|
||||||
|
struct stat sb;
|
||||||
|
|
||||||
|
dynstr_init(&d);
|
||||||
|
|
||||||
|
if (dynstr_append(&d, "%.*s", (int)len, path))
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s: dynstr_append failed\n", __func__);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
else if (stat(d.str, &sb))
|
||||||
|
{
|
||||||
|
if (errno != ENOENT && errno != ENOTDIR)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s: stat(2) %s: %s\n",
|
||||||
|
__func__, d.str, strerror(errno));
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
else if (mkdir(d.str, S_IRWXU))
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s: mkdir(2): %s\n", __func__, strerror(errno));
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = 0;
|
||||||
|
|
||||||
|
end:
|
||||||
|
dynstr_free(&d);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ensure_dirs(const char *const path)
|
||||||
|
{
|
||||||
|
for (const char *p = path; p; p = strchr(p, '/'))
|
||||||
|
{
|
||||||
|
const char *const next = p + 1;
|
||||||
|
|
||||||
|
if (*next == '/')
|
||||||
|
{
|
||||||
|
p = next;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (!*next)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s: expected file path: %s\n", __func__, path);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
else if (!(p = strchr(next, '/')))
|
||||||
|
break;
|
||||||
|
else if (ensure_dir(path, p - path))
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s: ensure_dir failed\n", __func__);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
p = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool extension_allowed(const char *const path)
|
||||||
|
{
|
||||||
|
static const char *const extensions[] =
|
||||||
|
{
|
||||||
|
".jpg", ".jpeg", ".png", ".bmp"
|
||||||
|
};
|
||||||
|
|
||||||
|
for (size_t i = 0; i < sizeof extensions / sizeof *extensions; i++)
|
||||||
|
{
|
||||||
|
const char *const ext = extensions[i];
|
||||||
|
const size_t len = strlen(path), elen = strlen(ext);
|
||||||
|
|
||||||
|
if (len < elen)
|
||||||
|
continue;
|
||||||
|
else if (!strcasecmp(path + len - elen, ext))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int process_add(const char *const abspath, const char *const relpath,
|
||||||
|
const char *const user, const char *const thumbnail,
|
||||||
|
const unsigned long size)
|
||||||
|
{
|
||||||
|
int ret = -1;
|
||||||
|
struct dynstr d;
|
||||||
|
|
||||||
|
dynstr_init(&d);
|
||||||
|
|
||||||
|
if (!extension_allowed(abspath))
|
||||||
|
;
|
||||||
|
else if (dynstr_append(&d, "%s%s", thumbnail, relpath))
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s: dynstr_append failed\n", __func__);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
else if ((ret = ensure_dirs(d.str)))
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s: ensure_dirs failed\n", __func__);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
else if ((ret = create_thumbnail(abspath, d.str, size)))
|
||||||
|
{
|
||||||
|
if (ret < 0)
|
||||||
|
fprintf(stderr, "%s: create_thumbnail failed\n", __func__);
|
||||||
|
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = 0;
|
||||||
|
|
||||||
|
end:
|
||||||
|
dynstr_free(&d);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int process_rm(const char *const abspath, const char *const relpath,
|
||||||
|
const char *const user, const char *const thumbnail,
|
||||||
|
const unsigned long size)
|
||||||
|
{
|
||||||
|
int ret = -1;
|
||||||
|
struct dynstr d;
|
||||||
|
|
||||||
|
dynstr_init(&d);
|
||||||
|
|
||||||
|
if (dynstr_append(&d, "%s%s", thumbnail, relpath))
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s: dynstr_append failed\n", __func__);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
else if (remove(d.str) && errno != ENOENT && errno != ENOTDIR)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s: remove(3) %s: %s\n",
|
||||||
|
__func__, d.str, strerror(errno));
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
printf("Removed %s\n", d.str);
|
||||||
|
|
||||||
|
ret = 0;
|
||||||
|
|
||||||
|
end:
|
||||||
|
dynstr_free(&d);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int process_line(const char *const line, const char *const user,
|
||||||
|
const char *const thumbnail, const unsigned long size)
|
||||||
|
{
|
||||||
|
int ret = -1;
|
||||||
|
const char *p = line;
|
||||||
|
static const char template[] = "+ f";
|
||||||
|
const size_t len = strlen(p);
|
||||||
|
|
||||||
|
if (len < strlen(template))
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s: invalid input: %s\n", __func__, p);
|
||||||
|
ret = 1;
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
int (*f)(const char *, const char *, const char *, const char *,
|
||||||
|
unsigned long) = NULL;
|
||||||
|
|
||||||
|
switch (*p)
|
||||||
|
{
|
||||||
|
case '+':
|
||||||
|
f = process_add;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '-':
|
||||||
|
f = process_rm;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
fprintf(stderr, "%s: unexpected operator %c\n", __func__, *p);
|
||||||
|
ret = 1;
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isspace(*++p))
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s: expected whitespace: %s\n", __func__, line);
|
||||||
|
ret = 1;
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (isspace(*p))
|
||||||
|
p++;
|
||||||
|
|
||||||
|
if (!*p)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s: expected path: %s\n", __func__, line);
|
||||||
|
ret = 1;
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *const path = p, *rel;
|
||||||
|
|
||||||
|
if (strstr(path, user) != path)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s: expected user directory (%s): %s\n",
|
||||||
|
__func__, user, line);
|
||||||
|
ret = 1;
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
rel = path + strlen(user);
|
||||||
|
|
||||||
|
if ((ret = f(path, rel, user, thumbnail, size)) < 0)
|
||||||
|
fprintf(stderr, "%s: callback failed\n", __func__);
|
||||||
|
|
||||||
|
end:
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int process_input(const int fd, const char *const user,
|
||||||
|
const char *const thumbnail, const unsigned long size)
|
||||||
|
{
|
||||||
|
int ret = -1;
|
||||||
|
char *const line = fifo_getline(fd);
|
||||||
|
|
||||||
|
if (!line)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s: fifo_getline failed\n", __func__);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
else if (*line && (ret = process_line(line, user, thumbnail, size)))
|
||||||
|
{
|
||||||
|
if (ret < 0)
|
||||||
|
fprintf(stderr, "%s: process_line failed\n", __func__);
|
||||||
|
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = 0;
|
||||||
|
|
||||||
|
end:
|
||||||
|
free(line);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static volatile sig_atomic_t do_exit;
|
||||||
|
|
||||||
|
static int process(const int fd, const char *const user,
|
||||||
|
const char *const thumbnail, const unsigned long size)
|
||||||
|
{
|
||||||
|
while (!do_exit)
|
||||||
|
{
|
||||||
|
struct pollfd fds =
|
||||||
|
{
|
||||||
|
.fd = fd,
|
||||||
|
.events = POLLIN
|
||||||
|
};
|
||||||
|
|
||||||
|
int ret, res;
|
||||||
|
|
||||||
|
again:
|
||||||
|
res = poll(&fds, 1, -1);
|
||||||
|
|
||||||
|
if (res < 0)
|
||||||
|
{
|
||||||
|
switch (errno)
|
||||||
|
{
|
||||||
|
case EAGAIN:
|
||||||
|
/* Fall through. */
|
||||||
|
case EINTR:
|
||||||
|
if (do_exit)
|
||||||
|
goto end;
|
||||||
|
|
||||||
|
goto again;
|
||||||
|
|
||||||
|
default:
|
||||||
|
fprintf(stderr, "%s: poll(2): %s\n",
|
||||||
|
__func__, strerror(errno));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!res)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s: poll(2) returned zero\n", __func__);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
else if (fds.revents & POLLHUP)
|
||||||
|
{
|
||||||
|
printf("FIFO closed by external process\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if ((ret = process_input(fd, user, thumbnail, size)) < 0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s: process_input failed\n", __func__);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
end:
|
||||||
|
printf("Exiting...\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct args
|
||||||
|
{
|
||||||
|
const char *user, *thumbnail;
|
||||||
|
unsigned long size;
|
||||||
|
bool force;
|
||||||
|
};
|
||||||
|
|
||||||
|
static int do_generate(const char *fpath,
|
||||||
|
const struct stat *sb, bool *done, void *user)
|
||||||
|
{
|
||||||
|
int ret = -1;
|
||||||
|
const struct args *const a = user;
|
||||||
|
const char *const relpath = fpath + strlen(a->user);
|
||||||
|
bool generate = false;
|
||||||
|
struct dynstr d;
|
||||||
|
struct stat tsb;
|
||||||
|
|
||||||
|
dynstr_init(&d);
|
||||||
|
|
||||||
|
if (do_exit)
|
||||||
|
*done = true;
|
||||||
|
else if (!S_ISREG(sb->st_mode) || !extension_allowed(fpath))
|
||||||
|
;
|
||||||
|
else if (dynstr_append(&d, "%s%s", a->thumbnail, relpath))
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s: dynstr_append failed\n", __func__);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
else if (stat(d.str, &tsb))
|
||||||
|
{
|
||||||
|
if (errno != ENOENT && errno != ENOTDIR)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s: stat(2) %s: %s\n",
|
||||||
|
__func__, d.str, strerror(errno));
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
generate = true;
|
||||||
|
}
|
||||||
|
else if (a->force)
|
||||||
|
generate = true;
|
||||||
|
|
||||||
|
if (generate)
|
||||||
|
{
|
||||||
|
if (ensure_dirs(d.str))
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s: ensure_dirs failed\n", __func__);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
else if ((ret = create_thumbnail(fpath, d.str, a->size)) < 0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s: create_thumbnail faled\n", __func__);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = 0;
|
||||||
|
|
||||||
|
end:
|
||||||
|
dynstr_free(&d);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int generate_existing(const char *const user,
|
||||||
|
const char *const thumbnail, const unsigned long size, const bool force)
|
||||||
|
{
|
||||||
|
struct args a =
|
||||||
|
{
|
||||||
|
.user = user,
|
||||||
|
.thumbnail = thumbnail,
|
||||||
|
.size = size,
|
||||||
|
.force = force
|
||||||
|
};
|
||||||
|
|
||||||
|
printf("Scanning existing files...\n");
|
||||||
|
|
||||||
|
if (cftw(user, do_generate, &a))
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s: cftw failed\n", __func__);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("Finished scanning\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handle_signal(const int signum)
|
||||||
|
{
|
||||||
|
switch (signum)
|
||||||
|
{
|
||||||
|
case SIGINT:
|
||||||
|
/* Fall through. */
|
||||||
|
case SIGTERM:
|
||||||
|
do_exit = 1;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int init_signals(void)
|
||||||
|
{
|
||||||
|
struct sigaction sa =
|
||||||
|
{
|
||||||
|
.sa_handler = handle_signal,
|
||||||
|
.sa_flags = SA_RESTART
|
||||||
|
};
|
||||||
|
|
||||||
|
sigemptyset(&sa.sa_mask);
|
||||||
|
|
||||||
|
if (sigaction(SIGINT, &sa, NULL))
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s: sigaction(2) SIGINT: %s\n",
|
||||||
|
__func__, strerror(errno));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
else if (sigaction(SIGTERM, &sa, NULL))
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s: sigaction(2) SIGTERM: %s\n",
|
||||||
|
__func__, strerror(errno));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
else if (sigaction(SIGPIPE, &sa, NULL))
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s: sigaction(2) SIGPIPE: %s\n",
|
||||||
|
__func__, strerror(errno));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void usage(char *const argv[])
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s [-f] [-s size] dir\n", *argv);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int parse_args(const int argc, char *const argv[],
|
||||||
|
const char **const dir, unsigned long *const size, bool *const force)
|
||||||
|
{
|
||||||
|
int opt;
|
||||||
|
|
||||||
|
/* Default values. */
|
||||||
|
*size = 96;
|
||||||
|
*force = false;
|
||||||
|
|
||||||
|
while ((opt = getopt(argc, argv, "fs:")) != -1)
|
||||||
|
{
|
||||||
|
switch (opt)
|
||||||
|
{
|
||||||
|
case 'f':
|
||||||
|
*force = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 's':
|
||||||
|
{
|
||||||
|
char *endptr;
|
||||||
|
|
||||||
|
*size = strtoul(optarg, &endptr, 10);
|
||||||
|
|
||||||
|
if (*endptr)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s: invalid size %s\n", __func__, optarg);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
usage(argv);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (optind >= argc)
|
||||||
|
{
|
||||||
|
usage(argv);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
*dir = argv[optind];
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
int ret = EXIT_FAILURE, fd = - 1;
|
||||||
|
char *rpath = NULL;
|
||||||
|
struct dynstr fifo, thumbnail, user;
|
||||||
|
const char *dir;
|
||||||
|
unsigned long size;
|
||||||
|
bool force;
|
||||||
|
|
||||||
|
MagickCoreGenesis(*argv, MagickTrue);
|
||||||
|
dynstr_init(&fifo);
|
||||||
|
dynstr_init(&thumbnail);
|
||||||
|
dynstr_init(&user);
|
||||||
|
|
||||||
|
if (parse_args(argc, argv, &dir, &size, &force))
|
||||||
|
{
|
||||||
|
usage(argv);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
else if (init_signals())
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s: init_signals failed\n", __func__);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
else if (!(rpath = crealpath(dir)))
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s: realpath(3): %s\n", __func__, strerror(errno));
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
else if (dynstr_append(&thumbnail, "%s/thumbnails", rpath))
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s: dynstr_append thumbnail failed\n", __func__);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
else if (dynstr_append(&user, "%s/user", rpath))
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s: dynstr_append user failed\n", __func__);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
else if (dynstr_append(&fifo, "%s/slcl.fifo", rpath))
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s: dynstr_append fifo failed\n", __func__);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
else if ((fd = open(fifo.str, O_RDONLY | O_NONBLOCK)) < 0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s: open(2) %s: %s\n",
|
||||||
|
__func__, fifo.str, strerror(errno));
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
else if (generate_existing(user.str, thumbnail.str, size, force))
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s: force_generate failed\n", __func__);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
else if (process(fd, user.str, thumbnail.str, size))
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s: process failed\n", __func__);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = EXIT_SUCCESS;
|
||||||
|
|
||||||
|
end:
|
||||||
|
free(rpath);
|
||||||
|
dynstr_free(&fifo);
|
||||||
|
dynstr_free(&thumbnail);
|
||||||
|
dynstr_free(&user);
|
||||||
|
|
||||||
|
if (fd >= 0 && close(fd))
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s: close(2): %s\n", __func__, strerror(errno));
|
||||||
|
ret = EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
MagickCoreTerminus();
|
||||||
|
return ret;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user