#define _POSIX_C_SOURCE 200809L #include "crealpath.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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; }