#define _POSIX_C_SOURCE 200809L #include "zip.h" #include "cftw.h" #include #include #include #include #include #include #include #include #include #include #include #include struct zip { char *basedir; struct cftw *cftw; ZIPstream *stream; ZIPentry *entry; int fds[2], fd; off_t filesz, read, lread; int (*next)(void *, size_t, bool *, void *, struct zip *); }; static int recurse(void *, size_t, bool *, void *, struct zip *); static int read_file(void *, size_t, bool *, void *, struct zip *); static void free_zip(void *const p) { struct zip *const z = p; if (!z) return; else if (z->entry) zs_entryend(z->stream, z->entry, NULL); zs_free(z->stream); if ((z->fds[0] >= 0 && close(z->fds[0])) || (z->fds[1] >= 0 && close(z->fds[1])) || (z->fd >= 0 && close(z->fd))) fprintf(stderr, "%s: close(2): %s\n", __func__, strerror(errno)); cftw_free(z->cftw); free(z->basedir); free(z); } static int dump_final(void *const buf, const size_t n, bool *const done, void *const user, struct zip *const z) { const ssize_t r = read(z->fds[0], buf, n); if (r < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK) { *done = true; return 0; } else fprintf(stderr, "%s: read(2): %s\n", __func__, strerror(errno)); } return r; } static int dump_end(void *const buf, const size_t n, bool *const done, void *const user, struct zip *const z) { const ssize_t r = read(z->fds[0], buf, z->lread); if (r < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK) { if (close(z->fd)) { fprintf(stderr, "%s: close(2): %s\n", __func__, strerror(errno)); return -1; } z->fd = -1; z->next = recurse; return 0; } } return r; } static int dumpz(void *const buf, const size_t n, bool *const done, void *const user, struct zip *const z) { const ssize_t r = read(z->fds[0], buf, z->lread); if (r < 0) { if (errno != EAGAIN && errno != EWOULDBLOCK) { fprintf(stderr, "%s: read(2): %s\n", __func__, strerror(errno)); return -1; } else if (z->read == z->filesz) { if (!zs_entryend(z->stream, z->entry, NULL)) { fprintf(stderr, "%s: zs_entryend failed\n", __func__); return -1; } z->entry = NULL; z->next = dump_end; } else z->next = read_file; return 0; } return r; } static int read_file(void *const buf, const size_t n, bool *const done, void *const user, struct zip *const z) { const off_t pend = z->filesz - z->read; size_t rem = pend > n ? n : pend; ssize_t r; if ((r = read(z->fd, buf, rem)) < 0) { fprintf(stderr, "%s: read(2): %s\n", __func__, strerror(errno)); return -1; } else if (!zs_entrydata(z->stream, z->entry, buf, r, NULL)) { fprintf(stderr, "%s: zs_entrydata failed\n", __func__); return -1; } z->lread = r; z->read += r; z->next = dumpz; return 0; } static int setup_file(struct zip *const z, const char *const fpath, const time_t t) { struct stat sb; ZIPentry *entry = NULL; const int fd = open(fpath, O_RDONLY | O_NONBLOCK); /* zs_entrybegin is not const-correct. */ char *path = (char *)fpath + strlen(z->basedir); if (fd < 0) { fprintf(stderr, "%s: open(2) %s: %s\n", __func__, fpath, strerror(errno)); goto failure; } else if (fstat(fd, &sb)) { fprintf(stderr, "%s: fstat(2) %s: %s\n", __func__, fpath, strerror(errno)); goto failure; } else if (!(entry = zs_entrybegin(z->stream, path, t, ZS_DEFLATE, NULL))) { fprintf(stderr, "%s: zs_entrybegin failed\n", __func__); goto failure; } z->fd = fd; z->read = 0; z->entry = entry; z->filesz = sb.st_size; z->next = read_file; return 0; failure: if (fd && close(fd)) fprintf(stderr, "%s: close(2) %s: %s\n", __func__, fpath, strerror(errno)); return -1; } static int setup(const char *const fpath, const struct stat *sb, bool *const done, void *const user) { struct zip *const z = user; if (S_ISREG(sb->st_mode) && setup_file(z, fpath, sb->st_ctim.tv_sec)) { fprintf(stderr, "%s: setup_file failed\n", __func__); return -1; } return 0; } static int finalize(struct zip *const z) { if (zs_finish(z->stream, NULL)) { fprintf(stderr, "%s: zs_finish failed\n", __func__); return -1; } z->next = dump_final; return 0; } static int recurse(void *const buf, const size_t n, bool *const done, void *const user, struct zip *const z) { switch (cftw_step(z->cftw)) { case CFTW_AGAIN: break; case CFTW_FATAL: fprintf(stderr, "%s: cftw_step failed\n", __func__); return -1; case CFTW_OK: if (finalize(z)) { fprintf(stderr, "%s: finalize end\n", __func__); return -1; } break; } return 0; } static int step(void *const buf, const size_t n, bool *const done, void *const user, void *const args) { struct zip *const z = args; return z->next(buf, n, done, user, z); } int zip(const char *const dir, struct http_response *const r) { struct cftw *c = NULL; ZIPstream *stream = NULL; struct zip *z = NULL; char *basedir = NULL, *bndup = NULL, *bn = NULL; int fds[2] = {-1, -1}, flags; struct dynstr d; dynstr_init(&d); if (pipe(fds)) { fprintf(stderr, "%s: pipe(2): %s\n", __func__, strerror(errno)); goto failure; } else if ((flags = fcntl(fds[0], F_GETFL)) == -1 || fcntl(fds[0], F_SETFL, flags | O_NONBLOCK) == -1) { fprintf(stderr, "%s: fcntl(2): %s\n", __func__, strerror(errno)); goto failure; } else if (!(z = malloc(sizeof *z))) { fprintf(stderr, "%s: malloc(3): %s\n", __func__, strerror(errno)); goto failure; } else if (!(c = cftw(dir, setup, z))) { fprintf(stderr, "%s: cftw failed\n", __func__); goto failure; } else if (!(basedir = strdup(dir)) || !(bndup = strdup(dir))) { fprintf(stderr, "%s: strdup(3): %s\n", __func__, strerror(errno)); goto failure; } else if (!(bn = basename(bndup))) { fprintf(stderr, "%s: basename(3) failed\n", __func__); goto failure; } else if (!(stream = zs_init(fds[1], NULL))) { fprintf(stderr, "%s: zs_init failed\n", __func__); goto failure; } else if (dynstr_append(&d, "attachment; filename=\"%s.zip\"", bn)) { fprintf(stderr, "%s: dynstr_append failed\n", __func__); goto failure; } *z = (const struct zip) { .fds = {[0] = fds[0], [1] = fds[1]}, .basedir = basedir, .stream = stream, .cftw = c, .next = recurse }; *r = (const struct http_response) { .status = HTTP_STATUS_OK, .chunk = step, .args = z, .free = free_zip }; if (http_response_add_header(r, "Content-Type", "application/zip") || http_response_add_header(r, "Content-Disposition", d.str)) { fprintf(stderr, "%s: http_response_add_header failed\n", __func__); goto failure; } dynstr_free(&d); free(bndup); return 0; failure: if ((fds[0] >= 0 && close(fds[0])) || (fds[1] >= 0 && close(fds[1]))) fprintf(stderr, "%s: close(2): %s\n", __func__, strerror(errno)); dynstr_free(&d); zs_free(stream); cftw_free(c); free(basedir); free(bndup); free(z); return -1; }