759 lines
16 KiB
C
759 lines
16 KiB
C
#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;
|
|
}
|