Compare commits

...

3 Commits

Author SHA1 Message Date
Xavier Del Campo Romero dba703e9e5
README.md: Update according to thumbnail generation 2023-11-24 02:06:18 +01:00
Xavier Del Campo Romero 5416c4e5b7
Display thumbnails, if available 2023-11-24 02:06:18 +01:00
Xavier Del Campo Romero 18bd8d678f
Add thumbnail generation tool
This new application runs separately from slcl and communicates with it
via a named pipe. When files are added/removed to/from the user
directory, slcl shall write to the named pipe.

Then, this new tool shall process incoming entries and generate
or remove thumbnails accordingly. Such thumbnails are stored into a new
directory inside the database directory, namely thumbnails/, which
replicates the same structure as user/.
2023-11-24 02:06:17 +01:00
13 changed files with 1076 additions and 23 deletions

1
.gitignore vendored
View File

@ -1,5 +1,6 @@
build/
slcl
thumbnail/thumbnail
*.o
*.d
./Makefile

View File

@ -23,6 +23,9 @@ portability, minimalism, simplicity and efficiency.
- Uses [`libweb`](https://gitea.privatedns.org/xavi/libweb), a tiny web framework.
- A simple JSON file as the credentials database.
- 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
@ -123,16 +126,32 @@ anything users put into them.
**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:
```
.
├── db.json
├── public
│   └── 44e03ab1bc3b0eff1567c76619186596 -> user/alice/file.txt
│   └── 44e03ab1bc3b0eff1567c76619186596 -> user/alice/file.jpg
├── thumbnails
│ ├── alice
│ │   └── file.jpg
│ └── john
└── user
├── alice
│   └── file.txt
│   └── file.jpg
└── john
└── 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
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?
Previously, I had been recommended Nextcloud as an alternative to proprietary

60
main.c
View File

@ -710,10 +710,14 @@ static int search(const struct http_payload *const p,
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 *);
char *dir = NULL;
struct dynstr userd, d, res;
struct page_search s =
{
.username = username,
.adir = root
};
dynstr_init(&userd);
dynstr_init(&d);
@ -990,6 +994,8 @@ static int getnode(const struct http_payload *const p,
.r = r,
.args = p->args,
.n_args = p->n_args,
.username = username,
.adir = adir,
.dir = dir.str,
.root = root.str,
.res = d.str,
@ -2011,6 +2017,55 @@ end:
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[])
{
fprintf(stderr, "%s [-F] [-t tmpdir] [-p port] dir\n", *argv);
@ -2243,7 +2298,8 @@ static int add_urls(struct handler *const h, void *const user)
{.url = "/upload", .op = HTTP_OP_POST, .f = upload},
{.url = "/mkdir", .op = HTTP_OP_POST, .f = createdir},
{.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++)

133
page.c
View File

@ -101,6 +101,67 @@ end:
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,
const char *const dir, const char *const name)
{
@ -338,19 +399,19 @@ end:
return ret;
}
static int add_element(struct html_node *const n, const char *const dir,
const char *const res, const char *const name, const bool chbx,
struct element_stats *const st)
static int add_element(struct html_node *const n,
const struct page_resource *const pr, const char *const name,
const bool chbx, struct element_stats *const st)
{
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 dynstr path;
const char *const sep = res[strlen(res) - 1] != '/' ? "/" : "";
const char *const sep = pr->res[strlen(pr->res) - 1] != '/' ? "/" : "";
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__);
goto end;
@ -392,7 +453,12 @@ static int add_element(struct html_node *const n, const char *const dir,
fprintf(stderr, "%s: prepare_rm_checkbox failed\n", __func__);
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__);
goto end;
@ -407,12 +473,12 @@ static int add_element(struct html_node *const n, const char *const dir,
fprintf(stderr, "%s: prepare_date failed\n", __func__);
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__);
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__);
goto end;
@ -1129,13 +1195,12 @@ end:
return ret;
}
static int add_elements(const char *const root, const char *const res,
const char *const dir, struct html_node *const table,
struct element_stats *const st)
static int add_elements(const struct page_resource *const pr,
struct html_node *const table, struct element_stats *const st)
{
int ret = -1;
struct dirent **pde = NULL;
const int n = scandir(res, &pde, NULL, alphasort);
const int n = scandir(pr->res, &pde, NULL, alphasort);
if (n < 0)
{
@ -1151,9 +1216,9 @@ static int add_elements(const char *const root, const char *const res,
const char *const name = de->d_name;
if (!strcmp(name, ".")
|| (!strcmp(name, "..") && !strcmp(root, res)))
|| (!strcmp(name, "..") && !strcmp(pr->root, pr->res)))
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__);
goto end;
@ -1220,7 +1285,7 @@ static int list_dir(const struct page_resource *const pr)
fprintf(stderr, "%s: resource_layout failed\n", __func__);
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__);
goto end;
@ -1815,8 +1880,15 @@ static int add_search_results(struct html_node *const n,
for (size_t i = 0; i < s->n; 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__);
return -1;
@ -2232,3 +2304,30 @@ int page_head_resource(struct http_response *const r, const char *const res)
return 0;
}
int page_thumbnail(struct http_response *const r, const char *const res)
{
struct stat sb;
if (stat(res, &sb))
{
if (errno == ENOENT)
return page_not_found(r);
else
{
fprintf(stderr, "%s: stat(2) %s: %s\n",
__func__, res, strerror(errno));
return -1;
}
}
const mode_t m = sb.st_mode;
if (!S_ISREG(m))
{
fprintf(stderr, "%s: only regular files are supported\n", __func__);
return 1;
}
return serve_file(r, &sb, res, true);
}

5
page.h
View File

@ -13,7 +13,7 @@ struct page_quota
struct page_resource
{
struct http_response *r;
const char *dir, *root, *res;
const char *adir, *username, *dir, *root, *res;
const struct page_quota *q;
const struct http_arg *args;
size_t n_args;
@ -26,7 +26,7 @@ struct page_search
char *name;
} *results;
const char *root;
const char *root, *adir, *username;
size_t n;
bool limit_exceeded;
};
@ -50,5 +50,6 @@ int page_quota_exceeded(struct http_response *r, unsigned long long len,
unsigned long long quota);
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_thumbnail(struct http_response *r, const char *res);
#endif /* PAGE_H */

10
thumbnail/CMakeLists.txt Normal file
View 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
View 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
View 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
View File

@ -0,0 +1 @@
../cftw.c

1
thumbnail/cftw.h Symbolic link
View File

@ -0,0 +1 @@
../cftw.h

1
thumbnail/crealpath.c Symbolic link
View File

@ -0,0 +1 @@
../crealpath.c

1
thumbnail/crealpath.h Symbolic link
View File

@ -0,0 +1 @@
../crealpath.h

758
thumbnail/main.c Normal file
View 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;
}