| #include "zip.h" |
| |
| #include <sys/stat.h> |
| |
| #define ZIP_MIN(a, b) ((a) < (b) ? (a) : (b)) |
| |
| #define FOR_REGRESS |
| |
| typedef enum { SOURCE_TYPE_NONE, SOURCE_TYPE_IN_MEMORY, SOURCE_TYPE_HOLE } source_type_t; |
| |
| source_type_t source_type = SOURCE_TYPE_NONE; |
| zip_uint64_t fragment_size = 0; |
| |
| static int add_nul(char *argv[]); |
| static int cancel(char *argv[]); |
| static int unchange_one(char *argv[]); |
| static int unchange_all(char *argv[]); |
| static int zin_close(char *argv[]); |
| |
| #define OPTIONS_REGRESS "F:Hm" |
| |
| #define USAGE_REGRESS " [-Hm] [-F fragment-size]" |
| |
| #define GETOPT_REGRESS \ |
| case 'H': \ |
| source_type = SOURCE_TYPE_HOLE; \ |
| break; \ |
| case 'm': \ |
| source_type = SOURCE_TYPE_IN_MEMORY; \ |
| break; \ |
| case 'F': \ |
| fragment_size = strtoull(optarg, NULL, 10); \ |
| break; |
| |
| /* clang-format off */ |
| |
| #define DISPATCH_REGRESS \ |
| {"add_nul", 2, "name length", "add NUL bytes", add_nul}, \ |
| {"cancel", 1, "limit", "cancel writing archive when limit% have been written (calls print_progress)", cancel}, \ |
| {"unchange", 1, "index", "revert changes for entry", unchange_one}, \ |
| {"unchange_all", 0, "", "revert all changes", unchange_all}, \ |
| { "zin_close", 1, "index", "close input zip_source (for internal tests)", zin_close } |
| |
| /* clang-format on */ |
| |
| |
| zip_t *ziptool_open(const char *archive, int flags, zip_error_t *error, zip_uint64_t offset, zip_uint64_t len); |
| |
| |
| #include "ziptool.c" |
| |
| |
| zip_source_t *memory_src = NULL; |
| |
| zip_source_t *source_hole_create(const char *, int flags, zip_error_t *); |
| |
| static zip_t *read_to_memory(const char *archive, int flags, zip_error_t *error, zip_source_t **srcp); |
| static zip_source_t *source_nul(zip_t *za, zip_uint64_t length); |
| |
| |
| static int |
| add_nul(char *argv[]) { |
| zip_source_t *zs; |
| zip_uint64_t length = strtoull(argv[1], NULL, 10); |
| |
| if ((zs = source_nul(za, length)) == NULL) { |
| fprintf(stderr, "can't create zip_source for length: %s\n", zip_strerror(za)); |
| return -1; |
| } |
| |
| if (zip_add(za, argv[0], zs) == -1) { |
| zip_source_free(zs); |
| fprintf(stderr, "can't add file '%s': %s\n", argv[0], zip_strerror(za)); |
| return -1; |
| } |
| return 0; |
| } |
| |
| static int |
| unchange_all(char *argv[]) { |
| if (zip_unchange_all(za) < 0) { |
| fprintf(stderr, "can't revert changes to archive: %s\n", zip_strerror(za)); |
| return -1; |
| } |
| return 0; |
| } |
| |
| |
| static int |
| unchange_one(char *argv[]) { |
| zip_uint64_t idx; |
| |
| idx = strtoull(argv[0], NULL, 10); |
| |
| if (zip_unchange(za, idx) < 0) { |
| fprintf(stderr, "can't revert changes for entry %" PRIu64 ": %s", idx, zip_strerror(za)); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| |
| static int |
| cancel_callback(zip_t *archive, void *ud) { |
| if (progress_userdata.percentage >= progress_userdata.limit) { |
| return -1; |
| } |
| return 0; |
| } |
| |
| static int |
| cancel(char *argv[]) { |
| zip_int64_t percent; |
| percent = strtoll(argv[0], NULL, 10); |
| if (percent > 100 || percent < 0) { |
| fprintf(stderr, "invalid percentage '%" PRId64 "' for cancel (valid: 0 <= x <= 100)\n", percent); |
| return -1; |
| } |
| progress_userdata.limit = ((double)percent) / 100; |
| |
| zip_register_cancel_callback_with_state(za, cancel_callback, NULL, NULL); |
| |
| /* needs the percentage updates from print_progress */ |
| print_progress(argv); |
| return 0; |
| } |
| |
| static int |
| zin_close(char *argv[]) { |
| zip_uint64_t idx; |
| |
| idx = strtoull(argv[0], NULL, 10); |
| if (idx >= z_in_count) { |
| fprintf(stderr, "invalid argument '%" PRIu64 "', only %u zip sources open\n", idx, z_in_count); |
| return -1; |
| } |
| if (zip_close(z_in[idx]) < 0) { |
| fprintf(stderr, "can't close source archive: %s\n", zip_strerror(z_in[idx])); |
| return -1; |
| } |
| z_in[idx] = z_in[z_in_count]; |
| z_in_count--; |
| |
| return 0; |
| } |
| |
| |
| static zip_t * |
| read_hole(const char *archive, int flags, zip_error_t *error) { |
| zip_source_t *src = NULL; |
| zip_t *zs = NULL; |
| |
| if (strcmp(archive, "/dev/stdin") == 0) { |
| zip_error_set(error, ZIP_ER_OPNOTSUPP, 0); |
| return NULL; |
| } |
| |
| if ((src = source_hole_create(archive, flags, error)) == NULL || (zs = zip_open_from_source(src, flags, error)) == NULL) { |
| zip_source_free(src); |
| } |
| |
| return zs; |
| } |
| |
| |
| static zip_t * |
| read_to_memory(const char *archive, int flags, zip_error_t *error, zip_source_t **srcp) { |
| zip_source_t *src; |
| zip_t *zb; |
| FILE *fp; |
| |
| if (strcmp(archive, "/dev/stdin") == 0) { |
| zip_error_set(error, ZIP_ER_OPNOTSUPP, 0); |
| return NULL; |
| } |
| |
| if ((fp = fopen(archive, "rb")) == NULL) { |
| if (errno == ENOENT) { |
| src = zip_source_buffer_create(NULL, 0, 0, error); |
| } |
| else { |
| zip_error_set(error, ZIP_ER_OPEN, errno); |
| return NULL; |
| } |
| } |
| else { |
| struct stat st; |
| |
| if (fstat(fileno(fp), &st) < 0) { |
| fclose(fp); |
| zip_error_set(error, ZIP_ER_OPEN, errno); |
| return NULL; |
| } |
| if (fragment_size == 0) { |
| char *buf; |
| if ((buf = malloc((size_t)st.st_size)) == NULL) { |
| fclose(fp); |
| zip_error_set(error, ZIP_ER_MEMORY, 0); |
| return NULL; |
| } |
| if (fread(buf, (size_t)st.st_size, 1, fp) < 1) { |
| free(buf); |
| fclose(fp); |
| zip_error_set(error, ZIP_ER_READ, errno); |
| return NULL; |
| } |
| src = zip_source_buffer_create(buf, (zip_uint64_t)st.st_size, 1, error); |
| if (src == NULL) { |
| free(buf); |
| } |
| } |
| else { |
| zip_uint64_t nfragments, i, left; |
| zip_buffer_fragment_t *fragments; |
| |
| nfragments = ((size_t)st.st_size + fragment_size - 1) / fragment_size; |
| if ((fragments = malloc(sizeof(fragments[0]) * nfragments)) == NULL) { |
| fclose(fp); |
| zip_error_set(error, ZIP_ER_MEMORY, 0); |
| return NULL; |
| } |
| for (i = 0; i < nfragments; i++) { |
| left = ZIP_MIN(fragment_size, (size_t)st.st_size - i * fragment_size); |
| if ((fragments[i].data = malloc(left)) == NULL) { |
| #ifndef __clang_analyzer__ |
| /* fragments is initialized up to i - 1*/ |
| while (--i > 0) { |
| free(fragments[i].data); |
| } |
| #endif |
| free(fragments); |
| fclose(fp); |
| zip_error_set(error, ZIP_ER_MEMORY, 0); |
| return NULL; |
| } |
| fragments[i].length = left; |
| if (fread(fragments[i].data, left, 1, fp) < 1) { |
| #ifndef __clang_analyzer__ |
| /* fragments is initialized up to i - 1*/ |
| while (--i > 0) { |
| free(fragments[i].data); |
| } |
| #endif |
| free(fragments); |
| fclose(fp); |
| zip_error_set(error, ZIP_ER_READ, errno); |
| return NULL; |
| } |
| } |
| src = zip_source_buffer_fragment_create(fragments, nfragments, 1, error); |
| if (src == NULL) { |
| for (i = 0; i < nfragments; i++) { |
| free(fragments[i].data); |
| } |
| free(fragments); |
| fclose(fp); |
| return NULL; |
| } |
| free(fragments); |
| } |
| fclose(fp); |
| } |
| if (src == NULL) { |
| return NULL; |
| } |
| zb = zip_open_from_source(src, flags, error); |
| if (zb == NULL) { |
| zip_source_free(src); |
| return NULL; |
| } |
| zip_source_keep(src); |
| *srcp = src; |
| return zb; |
| } |
| |
| |
| typedef struct source_nul { |
| zip_error_t error; |
| zip_uint64_t length; |
| zip_uint64_t offset; |
| } source_nul_t; |
| |
| static zip_int64_t |
| source_nul_cb(void *ud, void *data, zip_uint64_t length, zip_source_cmd_t command) { |
| source_nul_t *ctx = (source_nul_t *)ud; |
| |
| switch (command) { |
| case ZIP_SOURCE_CLOSE: |
| return 0; |
| |
| case ZIP_SOURCE_ERROR: |
| return zip_error_to_data(&ctx->error, data, length); |
| |
| case ZIP_SOURCE_FREE: |
| free(ctx); |
| return 0; |
| |
| case ZIP_SOURCE_OPEN: |
| ctx->offset = 0; |
| return 0; |
| |
| case ZIP_SOURCE_READ: |
| if (length > ZIP_INT64_MAX) { |
| zip_error_set(&ctx->error, ZIP_ER_INVAL, 0); |
| return -1; |
| } |
| |
| if (length > ctx->length - ctx->offset) { |
| length = ctx->length - ctx->offset; |
| } |
| |
| memset(data, 0, length); |
| ctx->offset += length; |
| return (zip_int64_t)length; |
| |
| case ZIP_SOURCE_STAT: { |
| zip_stat_t *st = ZIP_SOURCE_GET_ARGS(zip_stat_t, data, length, &ctx->error); |
| |
| if (st == NULL) { |
| return -1; |
| } |
| |
| st->valid |= ZIP_STAT_SIZE; |
| st->size = ctx->length; |
| |
| return 0; |
| } |
| |
| case ZIP_SOURCE_SUPPORTS: |
| return zip_source_make_command_bitmap(ZIP_SOURCE_CLOSE, ZIP_SOURCE_ERROR, ZIP_SOURCE_FREE, ZIP_SOURCE_OPEN, ZIP_SOURCE_READ, ZIP_SOURCE_STAT, -1); |
| |
| default: |
| zip_error_set(&ctx->error, ZIP_ER_OPNOTSUPP, 0); |
| return -1; |
| } |
| } |
| |
| static zip_source_t * |
| source_nul(zip_t *zs, zip_uint64_t length) { |
| source_nul_t *ctx; |
| zip_source_t *src; |
| |
| if ((ctx = (source_nul_t *)malloc(sizeof(*ctx))) == NULL) { |
| zip_error_set(zip_get_error(zs), ZIP_ER_MEMORY, 0); |
| return NULL; |
| } |
| |
| zip_error_init(&ctx->error); |
| ctx->length = length; |
| ctx->offset = 0; |
| |
| if ((src = zip_source_function(zs, source_nul_cb, ctx)) == NULL) { |
| free(ctx); |
| return NULL; |
| } |
| |
| return src; |
| } |
| |
| |
| static int |
| write_memory_src_to_file(const char *archive, zip_source_t *src) { |
| zip_stat_t zst; |
| char *buf; |
| FILE *fp; |
| |
| if (zip_source_stat(src, &zst) < 0) { |
| fprintf(stderr, "zip_source_stat on buffer failed: %s\n", zip_error_strerror(zip_source_error(src))); |
| return -1; |
| } |
| if (zip_source_open(src) < 0) { |
| if (zip_error_code_zip(zip_source_error(src)) == ZIP_ER_DELETED) { |
| if (unlink(archive) < 0 && errno != ENOENT) { |
| fprintf(stderr, "unlink failed: %s\n", strerror(errno)); |
| return -1; |
| } |
| return 0; |
| } |
| fprintf(stderr, "zip_source_open on buffer failed: %s\n", zip_error_strerror(zip_source_error(src))); |
| return -1; |
| } |
| if ((buf = malloc(zst.size)) == NULL) { |
| fprintf(stderr, "malloc failed: %s\n", strerror(errno)); |
| zip_source_close(src); |
| return -1; |
| } |
| if (zip_source_read(src, buf, zst.size) < (zip_int64_t)zst.size) { |
| fprintf(stderr, "zip_source_read on buffer failed: %s\n", zip_error_strerror(zip_source_error(src))); |
| zip_source_close(src); |
| free(buf); |
| return -1; |
| } |
| zip_source_close(src); |
| if ((fp = fopen(archive, "wb")) == NULL) { |
| fprintf(stderr, "fopen failed: %s\n", strerror(errno)); |
| free(buf); |
| return -1; |
| } |
| if (fwrite(buf, zst.size, 1, fp) < 1) { |
| fprintf(stderr, "fwrite failed: %s\n", strerror(errno)); |
| free(buf); |
| fclose(fp); |
| return -1; |
| } |
| free(buf); |
| if (fclose(fp) != 0) { |
| fprintf(stderr, "fclose failed: %s\n", strerror(errno)); |
| return -1; |
| } |
| return 0; |
| } |
| |
| |
| zip_t * |
| ziptool_open(const char *archive, int flags, zip_error_t *error, zip_uint64_t offset, zip_uint64_t len) { |
| switch (source_type) { |
| case SOURCE_TYPE_NONE: |
| za = read_from_file(archive, flags, error, offset, len); |
| break; |
| |
| case SOURCE_TYPE_IN_MEMORY: |
| za = read_to_memory(archive, flags, error, &memory_src); |
| break; |
| |
| case SOURCE_TYPE_HOLE: |
| za = read_hole(archive, flags, error); |
| break; |
| } |
| |
| return za; |
| } |
| |
| |
| int |
| ziptool_post_close(const char *archive) { |
| if (source_type == SOURCE_TYPE_IN_MEMORY) { |
| if (write_memory_src_to_file(archive, memory_src) < 0) { |
| return -1; |
| } |
| zip_source_free(memory_src); |
| } |
| |
| return 0; |
| } |