Rename modify to ziptool and install it.
diff --git a/src/ziptool.c b/src/ziptool.c
new file mode 100644
index 0000000..f647adc
--- /dev/null
+++ b/src/ziptool.c
@@ -0,0 +1,1034 @@
+/*
+  ziptool.c -- tool for modifying zip archive in multiple ways
+  Copyright (C) 2012-2016 Dieter Baron and Thomas Klausner
+
+  This file is part of libzip, a library to manipulate ZIP archives.
+  The authors can be contacted at <libzip@nih.at>
+
+  Redistribution and use in source and binary forms, with or without
+  modification, are permitted provided that the following conditions
+  are met:
+  1. Redistributions of source code must retain the above copyright
+     notice, this list of conditions and the following disclaimer.
+  2. Redistributions in binary form must reproduce the above copyright
+     notice, this list of conditions and the following disclaimer in
+     the documentation and/or other materials provided with the
+     distribution.
+  3. The names of the authors may not be used to endorse or promote
+     products derived from this software without specific prior
+     written permission.
+
+  THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS
+  OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+  ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
+  DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+  GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+  IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+  OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+  IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#include "config.h"
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#ifdef _WIN32
+/* WIN32 needs <fcntl.h> for _O_BINARY */
+#include <fcntl.h>
+#endif
+
+#ifndef HAVE_GETOPT
+#include "getopt.h"
+#endif
+
+#include "zip.h"
+#include "compat.h"
+
+zip_source_t *source_hole_create(const char *, int flags, zip_error_t *);
+
+typedef enum {
+    SOURCE_TYPE_NONE,
+    SOURCE_TYPE_IN_MEMORY,
+    SOURCE_TYPE_HOLE
+} source_type_t;
+
+typedef struct dispatch_table_s {
+    const char *cmdline_name;
+    int argument_count;
+    const char *arg_names;
+    const char *description;
+    int (*function)(int argc, char *argv[]);
+} dispatch_table_t;
+
+static zip_flags_t get_flags(const char *arg);
+static zip_int32_t get_compression_method(const char *arg);
+static void hexdump(const zip_uint8_t *data, zip_uint16_t len);
+static zip_t *read_to_memory(const char *archive, int flags, int *err, zip_source_t **srcp);
+static zip_source_t *source_nul(zip_t *za, zip_uint64_t length);
+
+zip_t *za, *z_in[16];
+unsigned int z_in_count;
+zip_flags_t stat_flags;
+
+static int
+add(int argc, char *argv[]) {
+    zip_source_t *zs;
+
+    if ((zs=zip_source_buffer(za, argv[1], strlen(argv[1]), 0)) == NULL) {
+	fprintf(stderr, "can't create zip_source from buffer: %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
+add_dir(int argc, char *argv[]) {
+    /* add directory */
+    if (zip_add_dir(za, argv[0]) < 0) {
+	fprintf(stderr, "can't add directory '%s': %s\n", argv[0], zip_strerror(za));
+	return -1;
+    }
+    return 0;
+}
+
+static int
+add_file(int argc, char *argv[]) {
+    zip_source_t *zs;
+    zip_uint64_t start = strtoull(argv[2], NULL, 10);
+    zip_int64_t len = strtoll(argv[3], NULL, 10);
+
+    if (strcmp(argv[1], "/dev/stdin") == 0) {
+	if ((zs=zip_source_filep(za, stdin, start, len)) == NULL) {
+	    fprintf(stderr, "can't create zip_source from stdin: %s\n", zip_strerror(za));
+	    return -1;
+	}
+    } else {
+	if ((zs=zip_source_file(za, argv[1], start, len)) == NULL) {
+	    fprintf(stderr, "can't create zip_source from file: %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
+add_from_zip(int argc, char *argv[]) {
+    zip_uint64_t idx, start;
+    zip_int64_t len;
+    int err;
+    zip_source_t *zs;
+    /* add from another zip file */
+    idx = strtoull(argv[2], NULL, 10);
+    start = strtoull(argv[3], NULL, 10);
+    len = strtoll(argv[4], NULL, 10);
+    if ((z_in[z_in_count]=zip_open(argv[1], ZIP_CHECKCONS, &err)) == NULL) {
+	zip_error_t error;
+	zip_error_init_with_code(&error, err);
+	fprintf(stderr, "can't open zip archive '%s': %s\n", argv[1], zip_error_strerror(&error));
+	zip_error_fini(&error);
+	return -1;
+    }
+    if ((zs=zip_source_zip(za, z_in[z_in_count], idx, 0, start, len)) == NULL) {
+	fprintf(stderr, "error creating file source from '%s' index '%" PRIu64 "': %s\n", argv[1], idx, zip_strerror(za));
+	zip_close(z_in[z_in_count]);
+	return -1;
+    }
+    if (zip_add(za, argv[0], zs) == -1) {
+	fprintf(stderr, "can't add file '%s': %s\n", argv[0], zip_strerror(za));
+	zip_source_free(zs);
+	zip_close(z_in[z_in_count]);
+	return -1;
+    }
+    z_in_count++;
+    return 0;
+}
+
+static int
+add_nul(int argc, 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
+cat(int argc, char *argv[]) {
+    /* output file contents to stdout */
+    zip_uint64_t idx;
+    zip_int64_t n;
+    zip_file_t *zf;
+    char buf[8192];
+    int err;
+    idx = strtoull(argv[0], NULL, 10);
+
+#ifdef _WIN32
+    /* Need to set stdout to binary mode for Windows */
+    setmode(fileno(stdout), _O_BINARY);
+#endif
+    if ((zf=zip_fopen_index(za, idx, 0)) == NULL) {
+	fprintf(stderr, "can't open file at index '%" PRIu64 "': %s\n", idx, zip_strerror(za));
+	return -1;
+    }
+    while ((n=zip_fread(zf, buf, sizeof(buf))) > 0) {
+	if (fwrite(buf, (size_t)n, 1, stdout) != 1) {
+	    zip_fclose(zf);
+	    fprintf(stderr, "can't write file contents to stdout: %s\n", strerror(errno));
+	    return -1;
+	}
+    }
+    if (n == -1) {
+	zip_fclose(zf);
+	fprintf(stderr, "can't read file at index '%" PRIu64 "': %s\n", idx, zip_file_strerror(zf));
+	return -1;
+    }
+    if ((err = zip_fclose(zf)) != 0) {
+	zip_error_t error;
+
+	zip_error_init_with_code(&error, err);
+	fprintf(stderr, "can't close file at index '%" PRIu64 "': %s\n", idx, zip_error_strerror(&error));
+	return -1;
+    }
+
+    return 0;
+}
+
+static int
+count_extra(int argc, char *argv[]) {
+    zip_int16_t count;
+    zip_uint64_t idx;
+    zip_flags_t ceflags = 0;
+    idx = strtoull(argv[0], NULL, 10);
+    ceflags = get_flags(argv[1]);
+    if ((count=zip_file_extra_fields_count(za, idx, ceflags)) < 0) {
+	fprintf(stderr, "can't get extra field count for file at index '%" PRIu64 "': %s\n", idx, zip_strerror(za));
+	return -1;
+    } else {
+	printf("Extra field count: %d\n", count);
+    }
+    return 0;
+}
+
+static int
+count_extra_by_id(int argc, char *argv[]) {
+    zip_int16_t count;
+    zip_uint16_t eid;
+    zip_flags_t ceflags = 0;
+    zip_uint64_t idx;
+    idx = strtoull(argv[0], NULL, 10);
+    eid = (zip_uint16_t)strtoull(argv[1], NULL, 10);
+    ceflags = get_flags(argv[2]);
+    if ((count=zip_file_extra_fields_count_by_id(za, idx, eid, ceflags)) < 0) {
+	fprintf(stderr, "can't get extra field count for file at index '%" PRIu64 "' and for id `%d': %s\n", idx, eid, zip_strerror(za));
+	return -1;
+    } else {
+	printf("Extra field count: %d\n", count);
+    }
+    return 0;
+}
+
+static int
+delete(int argc, char *argv[]) {
+    zip_uint64_t idx;
+    idx = strtoull(argv[0], NULL, 10);
+    if (zip_delete(za, idx) < 0) {
+	fprintf(stderr, "can't delete file at index '%" PRIu64 "': %s\n", idx, zip_strerror(za));
+	return -1;
+    }
+    return 0;
+}
+
+static int
+delete_extra(int argc, char *argv[]) {
+    zip_flags_t geflags;
+    zip_uint16_t eid;
+    zip_uint64_t idx;
+    idx = strtoull(argv[0], NULL, 10);
+    eid = (zip_uint16_t)strtoull(argv[1], NULL, 10);
+    geflags = get_flags(argv[2]);
+    if ((zip_file_extra_field_delete(za, idx, eid, geflags)) < 0) {
+	fprintf(stderr, "can't delete extra field data for file at index '%" PRIu64 "', extra field id `%d': %s\n", idx, eid, zip_strerror(za));
+	return -1;
+    }
+    return 0;
+}
+
+static int
+delete_extra_by_id(int argc, char *argv[]) {
+    zip_flags_t geflags;
+    zip_uint16_t eid, eidx;
+    zip_uint64_t idx;
+    idx = strtoull(argv[0], NULL, 10);
+    eid = (zip_uint16_t)strtoull(argv[1], NULL, 10);
+    eidx = (zip_uint16_t)strtoull(argv[2], NULL, 10);
+    geflags = get_flags(argv[3]);
+    if ((zip_file_extra_field_delete_by_id(za, idx, eid, eidx, geflags)) < 0) {
+	fprintf(stderr, "can't delete extra field data for file at index '%" PRIu64 "', extra field id `%d', extra field idx `%d': %s\n", idx, eid, eidx, zip_strerror(za));
+	return -1;
+    }
+    return 0;
+}
+
+static int
+get_archive_comment(int argc, char *argv[]) {
+    const char *comment;
+    int len;
+    /* get archive comment */
+    if ((comment=zip_get_archive_comment(za, &len, 0)) == NULL)
+	printf("No archive comment\n");
+    else
+	printf("Archive comment: %.*s\n", len, comment);
+    return 0;
+}
+
+static int
+get_extra(int argc, char *argv[]) {
+    zip_flags_t geflags;
+    zip_uint16_t id, eidx, eflen;
+    const zip_uint8_t *efdata;
+    zip_uint64_t idx;
+    /* get extra field data */
+    idx = strtoull(argv[0], NULL, 10);
+    eidx = (zip_uint16_t)strtoull(argv[1], NULL, 10);
+    geflags = get_flags(argv[2]);
+    if ((efdata=zip_file_extra_field_get(za, idx, eidx, &id, &eflen, geflags)) == NULL) {
+	fprintf(stderr, "can't get extra field data for file at index %" PRIu64 ", extra field %d, flags %u: %s\n", idx, eidx, geflags, zip_strerror(za));
+	return -1;
+    }
+    printf("Extra field 0x%04x: len %d", id, eflen);
+    if (eflen > 0) {
+	printf(", data ");
+	hexdump(efdata, eflen);
+    }
+    printf("\n");
+    return 0;
+}
+
+static int
+get_extra_by_id(int argc, char *argv[]) {
+    zip_flags_t geflags;
+    zip_uint16_t eid, eidx, eflen;
+    const zip_uint8_t *efdata;
+    zip_uint64_t idx;
+    idx = strtoull(argv[0], NULL, 10);
+    eid = (zip_uint16_t)strtoull(argv[1], NULL, 10);
+    eidx = (zip_uint16_t)strtoull(argv[2], NULL, 10);
+    geflags = get_flags(argv[3]);
+    if ((efdata=zip_file_extra_field_get_by_id(za, idx, eid, eidx, &eflen, geflags)) == NULL) {
+	fprintf(stderr, "can't get extra field data for file at index %" PRIu64 ", extra field id %d, ef index %d, flags %u: %s\n", idx, eid, eidx, geflags, zip_strerror(za));
+	return -1;
+    }
+    printf("Extra field 0x%04x: len %d", eid, eflen);
+    if (eflen > 0) {
+	printf(", data ");
+	hexdump(efdata, eflen);
+    }
+    printf("\n");
+    return 0;
+}
+
+static int
+get_file_comment(int argc, char *argv[]) {
+    const char *comment;
+    int len;
+    zip_uint64_t idx;
+    /* get file comment */
+    idx = strtoull(argv[0], NULL, 10);
+    if ((comment=zip_get_file_comment(za, idx, &len, 0)) == NULL) {
+	fprintf(stderr, "can't get comment for '%s': %s\n", zip_get_name(za, idx, 0), zip_strerror(za));
+	return -1;
+    } else if (len == 0)
+	printf("No comment for '%s'\n", zip_get_name(za, idx, 0));
+    else
+	printf("File comment for '%s': %.*s\n", zip_get_name(za, idx, 0), len, comment);
+    return 0;
+}
+
+static int
+get_num_entries(int argc, char *argv[]) {
+    zip_int64_t count;
+    zip_flags_t flags;
+    /* get number of entries in archive */
+    flags = get_flags(argv[0]);
+    count = zip_get_num_entries(za, flags);
+    printf("%" PRId64 " entr%s in archive\n", count, count == 1 ? "y" : "ies");
+    return 0;
+}
+
+static int
+name_locate(int argc, char *argv[]) {
+    zip_flags_t flags;
+    zip_int64_t idx;
+    flags = get_flags(argv[1]);
+
+    if ((idx=zip_name_locate(za, argv[0], flags)) < 0) {
+	fprintf(stderr, "can't find entry with name '%s' using flags '%s'\n", argv[0], argv[1]);
+    } else {
+	printf("name '%s' using flags '%s' found at index %" PRId64 "\n", argv[0], argv[1], idx);
+    }
+
+    return 0;
+}
+
+static int
+zrename(int argc, char *argv[]) {
+    zip_uint64_t idx;
+    idx = strtoull(argv[0], NULL, 10);
+    if (zip_rename(za, idx, argv[1]) < 0) {
+	fprintf(stderr, "can't rename file at index '%" PRIu64 "' to `%s': %s\n", idx, argv[1], zip_strerror(za));
+	return -1;
+    }
+    return 0;
+}
+
+static int
+replace_file_contents(int argc, char *argv[]) {
+    /* replace file contents with data from command line */
+    const char *content;
+    zip_source_t *s;
+    zip_uint64_t idx;
+    idx = strtoull(argv[0], NULL, 10);
+    content = argv[1];
+    if ((s=zip_source_buffer(za, content, strlen(content), 0)) == NULL ||
+	zip_file_replace(za, idx, s, 0) < 0) {
+	zip_source_free(s);
+	fprintf(stderr, "error replacing file data: %s\n", zip_strerror(za));
+	return -1;
+    }
+    return 0;
+}
+
+static int
+set_extra(int argc, char *argv[]) {
+    zip_flags_t geflags;
+    zip_uint16_t eid, eidx;
+    const zip_uint8_t *efdata;
+    zip_uint64_t idx;
+    idx = strtoull(argv[0], NULL, 10);
+    eid = (zip_uint16_t)strtoull(argv[1], NULL, 10);
+    eidx = (zip_uint16_t)strtoull(argv[2], NULL, 10);
+    geflags = get_flags(argv[3]);
+    efdata = (zip_uint8_t *)argv[4];
+    if ((zip_file_extra_field_set(za, idx, eid, eidx, efdata, (zip_uint16_t)strlen((const char *)efdata), geflags)) < 0) {
+	fprintf(stderr, "can't set extra field data for file at index '%" PRIu64 "', extra field id `%d', index `%d': %s\n", idx, eid, eidx, zip_strerror(za));
+	return -1;
+    }
+    return 0;
+}
+
+static int
+set_archive_comment(int argc, char *argv[]) {
+    if (zip_set_archive_comment(za, argv[0], (zip_uint16_t)strlen(argv[0])) < 0) {
+	fprintf(stderr, "can't set archive comment to `%s': %s\n", argv[0], zip_strerror(za));
+	return -1;
+    }
+    return 0;
+}
+
+static int
+set_file_comment(int argc, char *argv[]) {
+    zip_uint64_t idx;
+    idx = strtoull(argv[0], NULL, 10);
+    if (zip_file_set_comment(za, idx, argv[1], (zip_uint16_t)strlen(argv[1]), 0) < 0) {
+	fprintf(stderr, "can't set file comment at index '%" PRIu64 "' to `%s': %s\n", idx, argv[1], zip_strerror(za));
+	return -1;
+    }
+    return 0;
+}
+
+static int
+set_file_compression(int argc, char *argv[]) {
+    zip_int32_t method;
+    zip_uint32_t flags;
+    zip_uint64_t idx;
+    idx = strtoull(argv[0], NULL, 10);
+    method = get_compression_method(argv[1]);
+    flags = (zip_uint32_t)strtoull(argv[2], NULL, 10);
+    if (zip_set_file_compression(za, idx, method, flags) < 0) {
+	fprintf(stderr, "can't set file compression method at index '%" PRIu64 "' to `%s', flags `%d': %s\n", idx, argv[1], flags, zip_strerror(za));
+	return -1;
+    }
+    return 0;
+}
+
+static int
+set_file_mtime(int argc, char *argv[]) {
+    /* set file last modification time (mtime) */
+    time_t mtime;
+    zip_uint64_t idx;
+    idx = strtoull(argv[0], NULL, 10);
+    mtime = (time_t)strtoull(argv[1], NULL, 10);
+    if (zip_file_set_mtime(za, idx, mtime, 0) < 0) {
+	fprintf(stderr, "can't set file mtime at index '%" PRIu64 "' to `%ld': %s\n", idx, mtime, zip_strerror(za));
+	return -1;
+    }
+    return 0;
+}
+
+static int
+set_password(int argc, char *argv[]) {
+    /* set default password */
+    if (zip_set_default_password(za, argv[0]) < 0) {
+	fprintf(stderr, "can't set default password to `%s'", argv[0]);
+	return -1;
+    }
+    return 0;
+}
+
+static int
+zstat(int argc, char *argv[]) {
+    zip_uint64_t idx;
+    char buf[100];
+    struct zip_stat sb;
+    idx = strtoull(argv[0], NULL, 10);
+
+    if (zip_stat_index(za, idx, stat_flags, &sb) < 0) {
+	fprintf(stderr, "zip_stat_index failed on '%" PRIu64 "' failed: %s\n", idx, zip_strerror(za));
+	return -1;
+    }
+
+    if (sb.valid & ZIP_STAT_NAME)
+	printf("name: '%s'\n", sb.name);
+    if (sb.valid & ZIP_STAT_INDEX)
+	printf("index: '%"PRIu64"'\n", sb.index);
+    if (sb.valid & ZIP_STAT_SIZE)
+	printf("size: '%"PRIu64"'\n", sb.size);
+    if (sb.valid & ZIP_STAT_COMP_SIZE)
+	printf("compressed size: '%"PRIu64"'\n", sb.comp_size);
+    if (sb.valid & ZIP_STAT_MTIME) {
+	struct tm *tpm;
+	tpm = localtime(&sb.mtime);
+	strftime(buf, sizeof(buf), "%a %b %d %Y %H:%M:%S", tpm);
+	printf("mtime: '%s'\n", buf);
+    }
+    if (sb.valid & ZIP_STAT_CRC)
+	printf("crc: '%0x'\n", sb.crc);
+    if (sb.valid & ZIP_STAT_COMP_METHOD)
+	printf("compression method: '%d'\n", sb.comp_method);
+    if (sb.valid & ZIP_STAT_ENCRYPTION_METHOD)
+	printf("encryption method: '%d'\n", sb.encryption_method);
+    if (sb.valid & ZIP_STAT_FLAGS)
+	printf("flags: '%ld'\n", (long)sb.flags);
+    printf("\n");
+
+    return 0;
+}
+
+static int
+unchange_all(int argc, 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
+zin_close(int argc, char *argv[]) {
+    zip_uint64_t idx;
+
+    idx = strtoull(argv[0], NULL, 10);
+    if (idx >= z_in_count) {
+	fprintf(stderr, "invalid argument '%" PRIu64 "', only %d 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_flags_t
+get_flags(const char *arg)
+{
+    zip_flags_t flags = 0;
+    if (strchr(arg, 'C') != NULL)
+	flags |= ZIP_FL_NOCASE;
+    if (strchr(arg, 'c') != NULL)
+	flags |= ZIP_FL_CENTRAL;
+    if (strchr(arg, 'd') != NULL)
+	flags |= ZIP_FL_NODIR;
+    if (strchr(arg, 'l') != NULL)
+	flags |= ZIP_FL_LOCAL;
+    if (strchr(arg, 'u') != NULL)
+	flags |= ZIP_FL_UNCHANGED;
+    return flags;
+}
+
+static zip_int32_t
+get_compression_method(const char *arg)
+{
+    if (strcmp(arg, "default") == 0)
+        return ZIP_CM_DEFAULT;
+    else if (strcmp(arg, "store") == 0)
+        return ZIP_CM_STORE;
+    else if (strcmp(arg, "deflate") ==0)
+        return ZIP_CM_DEFLATE;
+    else if (strcmp(arg, "unknown") ==0)
+        return 99;
+    return 0; /* TODO: error handling */
+}
+
+static void
+hexdump(const zip_uint8_t *data, zip_uint16_t len)
+{
+    zip_uint16_t i;
+
+    if (len <= 0)
+	return;
+
+    printf("0x");
+
+    for (i=0; i<len; i++)
+	printf("%02x", data[i]);
+
+    return;
+}
+
+
+static zip_t *
+read_hole(const char *archive, int flags, int *err)
+{
+    zip_error_t error;
+    zip_source_t *src = NULL;
+    zip_t *zs = NULL;
+
+    zip_error_init(&error);
+
+    if ((src = source_hole_create(archive, flags, &error)) == NULL
+        || (zs = zip_open_from_source(src, flags, &error)) == NULL) {
+        zip_source_free(src);
+        *err = zip_error_code_zip(&error);
+        errno = zip_error_code_system(&error);
+    }
+
+    return zs;
+}
+
+
+static zip_t *
+read_to_memory(const char *archive, int flags, int *err, zip_source_t **srcp)
+{
+    struct stat st;
+    zip_source_t *src;
+    zip_t *zb;
+    zip_error_t error;
+
+    if (stat(archive, &st) < 0) {
+	if (errno == ENOENT) {
+	    src = zip_source_buffer_create(NULL, 0, 0, &error);
+	}
+	else {
+	    *err = ZIP_ER_OPEN;
+	    return NULL;
+	}
+    }
+    else {
+	char *buf;
+	FILE *fp;
+	if ((buf=malloc((size_t)st.st_size)) == NULL) {
+	    *err = ZIP_ER_MEMORY;
+	    return NULL;
+	}
+	if ((fp=fopen(archive, "r")) == NULL) {
+	    free(buf);
+	    *err = ZIP_ER_READ;
+	    return NULL;
+	}
+	if (fread(buf, (size_t)st.st_size, 1, fp) < 1) {
+	    free(buf);
+	    fclose(fp);
+	    *err = ZIP_ER_READ;
+	    return NULL;
+	}
+	fclose(fp);
+	src = zip_source_buffer_create(buf, (zip_uint64_t)st.st_size, 1, &error);
+	if (src == NULL) {
+	    free(buf);
+	}
+    }
+    if (src == NULL) {
+	*err = zip_error_code_zip(&error);
+	errno = zip_error_code_system(&error);
+	return NULL;
+    }
+    zb = zip_open_from_source(src, flags, &error);
+    if (zb == NULL) {
+	*err = zip_error_code_zip(&error);
+	errno = zip_error_code_system(&error);
+	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 (remove(archive) < 0 && errno != ENOENT) {
+		fprintf(stderr, "remove 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;
+}
+
+dispatch_table_t dispatch_table[] = {
+    { "add", 2, "name content", "add file called name using content", add },
+    { "add_dir", 1, "name", "add directory", add_dir },
+    { "add_file", 4, "name file_to_add offset len", "add file to archive, len bytes starting from offset", add_file },
+    { "add_from_zip", 5, "name archivename index offset len", "add file from another archive, len bytes starting from offset", add_from_zip },
+    { "add_nul", 2, "name length", "add NUL bytes", add_nul },
+    { "cat", 1, "index", "output file contents to stdout", cat },
+    { "count_extra", 2, "index flags", "show number of extra fields for archive entry", count_extra },
+    { "count_extra_by_id", 3, "index extra_id flags", "show number of extra fields of type extra_id for archive entry", count_extra_by_id },
+    { "delete", 1, "index", "remove entry", delete },
+    { "delete_extra", 3, "index extra_idx flags", "remove extra field", delete_extra },
+    { "delete_extra_by_id", 4, "index extra_id extra_index flags", "remove extra field of type extra_id", delete_extra_by_id },
+    { "get_archive_comment", 0, "", "show archive comment", get_archive_comment },
+    { "get_extra", 3, "index extra_index flags", "show extra field", get_extra },
+    { "get_extra_by_id", 4, "index extra_id extra_index flags", "show extra field of type extra_id", get_extra_by_id },
+    { "get_file_comment", 1, "index", "get file comment", get_file_comment },
+    { "get_num_entries", 1, "flags", "get number of entries in archive", get_num_entries },
+    { "name_locate", 2, "name flags", "find entry in archive", name_locate },
+    { "rename", 2, "index name", "rename entry", zrename },
+    { "replace_file_contents", 2, "index data", "replace entry with data", replace_file_contents },
+    { "set_archive_comment", 1, "comment", "set archive comment", set_archive_comment },
+    { "set_extra", 5, "index extra_id extra_index flags value", "set extra field", set_extra },
+    { "set_file_comment", 2, "index comment", "set file comment", set_file_comment },
+    { "set_file_compression", 3, "index method flags", "set file compression method", set_file_compression },
+    { "set_file_mtime", 2, "index timestamp", "set file modification time", set_file_mtime },
+    { "set_password", 1, "set default password", "set default password for encryption", set_password },
+    { "stat", 1, "index", "print information about entry", zstat },
+    { "unchange_all", 0, "", "revert all changes", unchange_all },
+    { "zin_close", 1, "index", "close input zip_source (for internal tests)", zin_close }
+};
+
+static int
+dispatch(int argc, char *argv[])
+{
+    unsigned int i;
+    for (i=0; i<sizeof(dispatch_table)/sizeof(dispatch_table_t); i++) {
+	if (strcmp(dispatch_table[i].cmdline_name, argv[0]) == 0) {
+	    argc--;
+	    argv++;
+	    /* 1 for the command, argument_count for the arguments */
+	    if (argc < dispatch_table[i].argument_count) {
+		fprintf(stderr, "not enough arguments for command '%s': %d available, %d needed\n", dispatch_table[i].cmdline_name, argc, dispatch_table[i].argument_count);
+		return -1;
+	    }
+	    if (dispatch_table[i].function(argc, argv) == 0)
+		return 1 + dispatch_table[i].argument_count;
+	    return -1;
+	}
+    }
+
+    fprintf(stderr, "unknown command '%s'\n", argv[0]);
+    return -1;
+}
+
+
+static void
+usage(const char *progname)
+{
+    unsigned int i;
+    fprintf(stderr, "usage: %s [-cemnt] archive command1 [args] [command2 [args] ...]\n\n"
+	    "Supported options are:\n"
+	    "\t-c\tcheck consistency\n"
+	    "\t-e\terror if archive already exists (only useful with -n)\n"
+	    "\t-g\tguess file name encoding (for stat)\n"
+            "\t-H\twrite files with holes compactly\n"
+	    "\t-m\tread archive into memory, and modify there; write out at end\n"
+	    "\t-n\tcreate archive if it doesn't exist (default)\n"
+	    "\t-r\tprint raw file name encoding without translation (for stat)\n"
+	    "\t-s\tfollow file name convention strictly (for stat)\n"
+	    "\t-t\tdisregard current archive contents, if any\n", progname);
+    fprintf(stderr, "\nSupported commands and arguments are:\n");
+    for (i=0; i<sizeof(dispatch_table)/sizeof(dispatch_table_t); i++) {
+	fprintf(stderr, "\t%s %s -- %s\n", dispatch_table[i].cmdline_name, dispatch_table[i].arg_names, dispatch_table[i].description);
+    }
+    fprintf(stderr, "\nSupported flags are:\n"
+	    "\tC\tZIP_FL_NOCASE\n"
+	    "\tc\tZIP_FL_CENTRAL\n"
+	    "\td\tZIP_FL_NODIR\n"
+	    "\tl\tZIP_FL_LOCAL\n"
+	    "\tu\tZIP_FL_UNCHANGED\n");
+    fprintf(stderr, "\nThe index is zero-based.\n");
+    exit(1);
+}
+
+int
+main(int argc, char *argv[])
+{
+    const char *archive;
+    zip_source_t *memory_src;
+    unsigned int i;
+    int c, arg, err, flags;
+    const char *prg;
+    source_type_t source_type = SOURCE_TYPE_NONE;
+
+    flags = 0;
+    prg = argv[0];
+
+    if (argc < 2)
+	usage(prg);
+
+    while ((c=getopt(argc, argv, "cegHmnrst")) != -1) {
+	switch (c) {
+	case 'c':
+	    flags |= ZIP_CHECKCONS;
+	    break;
+	case 'e':
+	    flags |= ZIP_EXCL;
+	    break;
+	case 'g':
+	    stat_flags = ZIP_FL_ENC_GUESS;
+	    break;
+        case 'H':
+            source_type = SOURCE_TYPE_HOLE;
+            break;
+	case 'm':
+            source_type = SOURCE_TYPE_IN_MEMORY;
+            break;
+	case 'n':
+	    flags |= ZIP_CREATE;
+	    break;
+	case 'r':
+	    stat_flags = ZIP_FL_ENC_RAW;
+	    break;
+	case 's':
+	    stat_flags = ZIP_FL_ENC_STRICT;
+	    break;
+	case 't':
+	    flags |= ZIP_TRUNCATE;
+	    break;
+
+	default:
+	    usage(prg);
+	}
+    }
+
+    arg = optind;
+
+    archive = argv[arg++];
+
+    if (flags == 0)
+	flags = ZIP_CREATE;
+
+    switch (source_type) {
+        case SOURCE_TYPE_NONE:
+            za = zip_open(archive, flags, &err);
+            break;
+
+        case SOURCE_TYPE_IN_MEMORY:
+            za = read_to_memory(archive, flags, &err, &memory_src);
+            break;
+
+        case SOURCE_TYPE_HOLE: {
+            za = read_hole(archive, flags, &err);
+            break;
+        }
+    }
+    if (za == NULL) {
+	zip_error_t error;
+	zip_error_init_with_code(&error, err);
+	fprintf(stderr, "can't open zip archive '%s': %s\n", archive, zip_error_strerror(&error));
+	zip_error_fini(&error);
+	return 1;
+    }
+
+    err = 0;
+    while (arg < argc) {
+	int ret;
+	ret = dispatch(argc-arg, argv+arg);
+	if (ret > 0) {
+	    arg += ret;
+	} else {
+	    err = 1;
+	    break;
+	}
+    }
+
+    if (zip_close(za) == -1) {
+	fprintf(stderr, "can't close zip archive '%s': %s\n", archive, zip_strerror(za));
+	return 1;
+    }
+    if (source_type == SOURCE_TYPE_IN_MEMORY) {
+	if (write_memory_src_to_file(archive, memory_src) < 0) {
+	    err = 1;
+	}
+	zip_source_free(memory_src);
+    }
+
+    for (i=0; i<z_in_count; i++) {
+	if (zip_close(z_in[i]) < 0) {
+	    err = 1;
+	}
+    }
+
+    return err;
+}