Factor implementation specific operations out of file source.

Add operations for read-only stdio (used on Unix and Windows) and
read/write stdio (used on Unix only).

TODO: Adapt Windows file sources to use new framework.
diff --git a/NEWS.md b/NEWS.md
index f3c0bad..def4540 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -5,6 +5,7 @@
 * Add `zip_compression_method_supported()`.
 * Add `zip_encryption_method_supported()`.
 * Add the `ZIP_SOURCE_GET_FILE_ATTRIBUTES` source command.
+* Refactor stdio file backend.
 
 1.6.1 [2020-02-03]
 ==================
diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt
index 7683aed..868e625 100644
--- a/lib/CMakeLists.txt
+++ b/lib/CMakeLists.txt
@@ -130,7 +130,8 @@
   zip_source_compress.c
   zip_source_crc.c
   zip_source_error.c
-  zip_source_filep.c
+  zip_source_file_common.c
+  zip_source_file_stdio.c
   zip_source_free.c
   zip_source_function.c
   zip_source_get_file_attributes.c
@@ -183,7 +184,7 @@
 ELSE(WIN32)
   SET(LIBZIP_OPSYS_FILES
     zip_mkstempm.c
-    zip_source_file.c
+    zip_source_file_stdio_write.c
     zip_random_unix.c
   )
 ENDIF(WIN32)
diff --git a/lib/zip_source_file.h b/lib/zip_source_file.h
new file mode 100644
index 0000000..dad54d2
--- /dev/null
+++ b/lib/zip_source_file.h
@@ -0,0 +1,83 @@
+/*
+  zip_source_file.h -- header for common file operations
+  Copyright (C) 2020 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.
+*/
+
+struct zip_source_file_stat {
+    zip_uint64_t size;    /* must be valid for regular files */
+    time_t mtime;
+    bool exists;
+    bool regular_file;
+    bool can_clone;
+};
+
+typedef struct zip_source_file_context zip_source_file_context_t;
+typedef struct zip_source_file_operations zip_source_file_operations_t;
+typedef struct zip_source_file_stat zip_source_file_stat_t;
+
+struct zip_source_file_context {
+    zip_error_t error; /* last error information */
+    zip_int64_t supports;
+
+    /* reading */
+    char *fname;                      /* name of file to read from */
+    void *f;                          /* file to read from */
+    zip_stat_t st;                    /* stat information passed in */
+    zip_file_attributes_t attributes; /* additional file attributes */
+    zip_error_t stat_error;           /* error returned for stat */
+    zip_uint64_t start;               /* start offset of data to read */
+    zip_uint64_t len;                 /* length of the file, 0 for up to EOF */
+    zip_uint64_t offset;              /* current offset relative to start (0 is beginning of part we read) */
+
+    /* writing */
+    char *tmpname;
+    void *fout;
+
+    zip_source_file_operations_t *ops;
+};
+
+struct zip_source_file_operations {
+    void (*close)(zip_source_file_context_t *ctx);
+    zip_int64_t (*commit_write)(zip_source_file_context_t *ctx);
+    zip_int64_t (*create_temp_output)(zip_source_file_context_t *ctx);
+    zip_int64_t (*create_temp_output_cloning)(zip_source_file_context_t *ctx, zip_uint64_t len);
+    bool (*open)(zip_source_file_context_t *ctx);
+    zip_int64_t (*read)(zip_source_file_context_t *ctx, void *buf, zip_uint64_t len);
+    zip_int64_t (*remove)(zip_source_file_context_t *ctx);
+    void (*rollback_write)(zip_source_file_context_t *ctx);
+    bool (*seek)(zip_source_file_context_t *ctx, void *f, zip_int64_t offset, int whence);
+    bool (*stat)(zip_source_file_context_t *ctx, zip_source_file_stat_t *st);
+    char *(*strdup)(const char *);
+    zip_int64_t (*tell)(zip_source_file_context_t *ctx, void *f);
+    zip_int64_t (*write)(zip_source_file_context_t *ctx, const void *data, zip_uint64_t len);
+};
+
+zip_source_t *zip_source_file_common_new(const char *fname, void *file, zip_uint64_t start, zip_int64_t len, const zip_stat_t *st, zip_source_file_operations_t *ops, zip_error_t *error);
diff --git a/lib/zip_source_file_common.c b/lib/zip_source_file_common.c
new file mode 100644
index 0000000..bc4ee6c
--- /dev/null
+++ b/lib/zip_source_file_common.c
@@ -0,0 +1,349 @@
+/*
+  zip_source_file_common.c -- create data source from file
+  Copyright (C) 1999-2019 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "zipint.h"
+
+#include "zip_source_file.h"
+
+static zip_int64_t read_file(void *state, void *data, zip_uint64_t len, zip_source_cmd_t cmd);
+
+static void
+zip_source_file_stat_init(zip_source_file_stat_t *st) {
+    st->size = 0;
+    st->mtime = time(NULL);
+    st->exists = false;
+    st->can_clone = false;
+    st->regular_file = false;
+}
+
+zip_source_t *
+zip_source_file_common_new(const char *fname, void *file, zip_uint64_t start, zip_int64_t len, const zip_stat_t *st, zip_source_file_operations_t *ops, zip_error_t *error) {
+    zip_source_file_context_t *ctx;
+    zip_source_t *zs;
+    zip_source_file_stat_t sb;
+    bool stat_valid;
+
+    if (ops == NULL) {
+	zip_error_set(error, ZIP_ER_INVAL, 0);
+	return NULL;
+    }
+
+    if (file == NULL && fname == NULL) {
+	zip_error_set(error, ZIP_ER_INVAL, 0);
+	return NULL;
+    }
+
+    if (len < 0) {
+	len = 0;
+    }
+
+    if (start > ZIP_INT64_MAX || start + (zip_uint64_t)len < start) {
+	zip_error_set(error, ZIP_ER_INVAL, 0);
+	return NULL;
+    }
+
+    if ((ctx = (zip_source_file_context_t *)malloc(sizeof(zip_source_file_context_t))) == NULL) {
+	zip_error_set(error, ZIP_ER_MEMORY, 0);
+	return NULL;
+    }
+
+    ctx->ops = ops;
+    ctx->fname = NULL;
+    if (fname) {
+	if ((ctx->fname = ops->strdup(fname)) == NULL) {
+	    zip_error_set(error, ZIP_ER_MEMORY, 0);
+	    free(ctx);
+	    return NULL;
+	}
+    }
+    ctx->f = file;
+    ctx->start = start;
+    ctx->len = (zip_uint64_t)len;
+    if (st) {
+	memcpy(&ctx->st, st, sizeof(ctx->st));
+	ctx->st.name = NULL;
+	ctx->st.valid &= ~ZIP_STAT_NAME;
+    }
+    else {
+	zip_stat_init(&ctx->st);
+    }
+
+    if (ctx->len > 0) {
+	ctx->st.size = ctx->len;
+	ctx->st.valid |= ZIP_STAT_SIZE;
+    }
+
+    zip_error_init(&ctx->stat_error);
+
+    ctx->tmpname = NULL;
+    ctx->fout = NULL;
+
+    zip_error_init(&ctx->error);
+    zip_file_attributes_init(&ctx->attributes);
+
+    ctx->supports = ZIP_SOURCE_SUPPORTS_READABLE | zip_source_make_command_bitmap(ZIP_SOURCE_SUPPORTS, ZIP_SOURCE_TELL, -1);
+
+    zip_source_file_stat_init(&sb);
+    stat_valid = ops->stat(ctx, &sb);
+
+    if (ctx->fname && !stat_valid) {
+	if (ctx->start == 0 && ctx->len == 0) {
+	    ctx->supports = ZIP_SOURCE_SUPPORTS_WRITABLE;
+	}
+    }
+
+    if (!stat_valid) {
+	zip_error_set(&ctx->stat_error, ZIP_ER_READ, errno);
+    }
+    else {
+	if ((ctx->st.valid & ZIP_STAT_MTIME) == 0) {
+	    ctx->st.mtime = sb.mtime;
+	    ctx->st.valid |= ZIP_STAT_MTIME;
+	}
+	if (sb.regular_file) {
+	    ctx->supports = ZIP_SOURCE_SUPPORTS_SEEKABLE;
+
+	    if (ctx->start + ctx->len > sb.size) {
+		zip_error_set(error, ZIP_ER_INVAL, 0);
+		free(ctx->fname);
+		free(ctx);
+		return NULL;
+	    }
+
+	    if (ctx->len == 0) {
+		ctx->len = sb.size - ctx->start;
+		ctx->st.size = ctx->len;
+		ctx->st.valid |= ZIP_STAT_SIZE;
+
+		/* when using a partial file, don't allow writing */
+		if (ctx->fname && start == 0 && ops->write != NULL) {
+		    ctx->supports = ZIP_SOURCE_SUPPORTS_WRITABLE;
+		}
+	    }
+	}
+
+	ctx->supports |= ZIP_SOURCE_MAKE_COMMAND_BITMASK(ZIP_SOURCE_GET_FILE_ATTRIBUTES);
+    }
+
+    ctx->supports |= ZIP_SOURCE_MAKE_COMMAND_BITMASK(ZIP_SOURCE_ACCEPT_EMPTY);
+    if (sb.can_clone) {
+	if (ctx->supports & ZIP_SOURCE_MAKE_COMMAND_BITMASK(ZIP_SOURCE_BEGIN_WRITE)) {
+	    ctx->supports |= ZIP_SOURCE_MAKE_COMMAND_BITMASK(ZIP_SOURCE_BEGIN_WRITE_CLONING);
+	}
+    }
+
+    if ((zs = zip_source_function_create(read_file, ctx, error)) == NULL) {
+	free(ctx->fname);
+	free(ctx);
+	return NULL;
+    }
+
+    return zs;
+}
+
+
+
+static zip_int64_t
+read_file(void *state, void *data, zip_uint64_t len, zip_source_cmd_t cmd) {
+    zip_source_file_context_t *ctx;
+    char *buf;
+
+    ctx = (zip_source_file_context_t *)state;
+    buf = (char *)data;
+
+    switch (cmd) {
+    case ZIP_SOURCE_ACCEPT_EMPTY:
+	return 0;
+
+    case ZIP_SOURCE_BEGIN_WRITE:
+	/* write support should not be set if fname is NULL */
+	if (ctx->fname == NULL) {
+	    zip_error_set(&ctx->error, ZIP_ER_INTERNAL, 0);
+	    return -1;
+	}
+	return ctx->ops->create_temp_output(ctx);
+
+    case ZIP_SOURCE_BEGIN_WRITE_CLONING:
+	/* write support should not be set if fname is NULL */
+	if (ctx->fname == NULL) {
+	    zip_error_set(&ctx->error, ZIP_ER_INTERNAL, 0);
+	    return -1;
+	}
+	return ctx->ops->create_temp_output_cloning(ctx, len);
+
+    case ZIP_SOURCE_CLOSE:
+	if (ctx->fname) {
+	    ctx->ops->close(ctx);
+	    ctx->f = NULL;
+	}
+	return 0;
+
+    case ZIP_SOURCE_COMMIT_WRITE: {
+	zip_int64_t ret = ctx->ops->commit_write(ctx);
+	ctx->fout = NULL;
+	if (ret == 0) {
+	    free(ctx->tmpname);
+	    ctx->tmpname = NULL;
+	}
+	return ret;
+    }
+
+    case ZIP_SOURCE_ERROR:
+	return zip_error_to_data(&ctx->error, data, len);
+
+    case ZIP_SOURCE_FREE:
+	free(ctx->fname);
+	free(ctx->tmpname);
+	if (ctx->f) {
+	    ctx->ops->close(ctx);
+	}
+	free(ctx);
+	return 0;
+
+    case ZIP_SOURCE_GET_FILE_ATTRIBUTES:
+	if (len < sizeof(ctx->attributes)) {
+            zip_error_set(&ctx->error, ZIP_ER_INVAL, 0);
+	    return -1;
+	}
+	memcpy(data, &ctx->attributes, sizeof(ctx->attributes));
+	return sizeof(ctx->attributes);
+
+    case ZIP_SOURCE_OPEN:
+	if (ctx->fname) {
+	    if (ctx->ops->open(ctx) == false) {
+		return -1;
+	    }
+	}
+
+	if (ctx->start > 0) {
+	    if (ctx->ops->seek(ctx, ctx->f, ctx->start, SEEK_SET) == false) {
+		/* TODO: skip by reading */
+		return -1;
+	    }
+	}
+	ctx->offset = 0;
+	return 0;
+
+    case ZIP_SOURCE_READ: {
+	zip_int64_t i;
+	zip_uint64_t n;
+
+	if (ctx->len > 0) {
+	    n = ZIP_MIN(ctx->len - ctx->offset, len);
+	}
+	else {
+	    n = len;
+	}
+
+	if ((i = ctx->ops->read(ctx, buf, n)) < 0) {
+	    zip_error_set(&ctx->error, ZIP_ER_READ, errno);
+	    return -1;
+	}
+	ctx->offset += i;
+
+	return i;
+    }
+
+    case ZIP_SOURCE_REMOVE:
+	return ctx->ops->remove(ctx);
+
+    case ZIP_SOURCE_ROLLBACK_WRITE:
+	ctx->ops->rollback_write(ctx);
+	ctx->fout = NULL;
+	free(ctx->tmpname);
+	ctx->tmpname = NULL;
+	return 0;
+
+    case ZIP_SOURCE_SEEK: {
+	zip_int64_t new_offset = zip_source_seek_compute_offset(ctx->offset, ctx->len, data, len, &ctx->error);
+
+	if (new_offset < 0) {
+	    return -1;
+	}
+
+	ctx->offset = (zip_uint64_t)new_offset;
+
+	if (ctx->ops->seek(ctx, ctx->f, ctx->offset + ctx->start, SEEK_SET) == false) {
+	    return -1;
+	}
+	return 0;
+    }
+
+    case ZIP_SOURCE_SEEK_WRITE: {
+	zip_source_args_seek_t *args;
+
+	args = ZIP_SOURCE_GET_ARGS(zip_source_args_seek_t, data, len, &ctx->error);
+	if (args == NULL) {
+	    return -1;
+	}
+
+	if (ctx->ops->seek(ctx, ctx->fout, args->offset, args->whence) == false) {
+	    return -1;
+	}
+	return 0;
+    }
+
+    case ZIP_SOURCE_STAT: {
+	if (len < sizeof(ctx->st))
+	    return -1;
+
+	if (zip_error_code_zip(&ctx->stat_error) != 0) {
+	    zip_error_set(&ctx->error, zip_error_code_zip(&ctx->stat_error), zip_error_code_system(&ctx->stat_error));
+	    return -1;
+	}
+
+	memcpy(data, &ctx->st, sizeof(ctx->st));
+	return sizeof(ctx->st);
+    }
+
+    case ZIP_SOURCE_SUPPORTS:
+	return ctx->supports;
+
+    case ZIP_SOURCE_TELL:
+	return (zip_int64_t)ctx->offset;
+
+    case ZIP_SOURCE_TELL_WRITE:
+	return ctx->ops->tell(ctx, ctx->fout);
+
+    case ZIP_SOURCE_WRITE:
+	return ctx->ops->write(ctx, data, len);
+
+    default:
+	zip_error_set(&ctx->error, ZIP_ER_OPNOTSUPP, 0);
+	return -1;
+    }
+}
diff --git a/lib/zip_source_file_stdio.c b/lib/zip_source_file_stdio.c
new file mode 100644
index 0000000..086715b
--- /dev/null
+++ b/lib/zip_source_file_stdio.c
@@ -0,0 +1,211 @@
+/*
+  zip_source_file_stdio.c -- Common file operations using stdio
+  Copyright (C) 2020 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 "zipint.h"
+
+#include "zip_source_file.h"
+#include "zip_source_file_stdio.h"
+
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+
+zip_source_file_operations_t ops_stdio_read = {
+    _zip_stdio_op_close,
+    NULL,
+    NULL,
+    NULL,
+    _zip_stdio_op_open,
+    _zip_stdio_op_read,
+    NULL,
+    NULL,
+    _zip_stdio_op_seek,
+    _zip_stdio_op_stat,
+    strdup,
+    _zip_stdio_op_tell,
+    NULL
+};
+
+
+ZIP_EXTERN zip_source_t *
+zip_source_filep(zip_t *za, FILE *file, zip_uint64_t start, zip_int64_t len) {
+    if (za == NULL) {
+	return NULL;
+    }
+
+    return zip_source_filep_create(file, start, len, &za->error);
+}
+
+
+ZIP_EXTERN zip_source_t *
+zip_source_filep_create(FILE *file, zip_uint64_t start, zip_int64_t length, zip_error_t *error) {
+    if (file == NULL || length < -1) {
+	zip_error_set(error, ZIP_ER_INVAL, 0);
+	return NULL;
+    }
+
+    return zip_source_file_common_new(NULL, file, start, length, NULL, &ops_stdio_read, error);
+}
+
+
+void
+_zip_stdio_op_close(zip_source_file_context_t *ctx) {
+    fclose((FILE *)ctx->f);
+}
+
+
+bool
+_zip_stdio_op_open(zip_source_file_context_t *ctx) {
+    if ((ctx->f = _zip_fopen_close_on_exec(ctx->fname, false)) == NULL) {
+	zip_error_set(&ctx->error, ZIP_ER_OPEN, errno);
+	return false;
+    }
+    return true;
+}
+
+
+zip_int64_t
+_zip_stdio_op_read(zip_source_file_context_t *ctx, void *buf, zip_uint64_t len) {
+    size_t i;
+    if (len > SIZE_MAX) {
+	len = SIZE_MAX;
+    }
+
+    if ((i = fread(buf, 1, (size_t)len, ctx->f)) == 0) {
+	if (ferror((FILE *)ctx->f)) {
+	    zip_error_set(&ctx->error, ZIP_ER_READ, errno);
+	    return -1;
+	}
+    }
+
+    return (zip_int64_t)i;
+}
+
+
+bool
+_zip_stdio_op_seek(zip_source_file_context_t *ctx, void *f, zip_int64_t offset, int whence) {
+    if (offset > ZIP_FSEEK_MAX || offset < ZIP_FSEEK_MIN) {
+	zip_error_set(&ctx->error, ZIP_ER_SEEK, EOVERFLOW);
+	return false;
+    }
+
+    if (fseeko((FILE *)f, (off_t)offset, whence) < 0) {
+	zip_error_set(&ctx->error, ZIP_ER_SEEK, errno);
+	return false;
+    }
+    return true;
+}
+
+
+bool
+_zip_stdio_op_stat(zip_source_file_context_t *ctx, zip_source_file_stat_t *st) {
+    struct stat sb;
+
+    int ret;
+
+    if (ctx->fname) {
+	ret = stat(ctx->fname, &sb);
+    }
+    else {
+	ret = fstat(fileno((FILE *)ctx->f), &sb);
+    }
+
+    if (ret < 0) {
+	zip_error_set(&ctx->error, ZIP_ER_READ, errno);
+	return false;
+    }
+
+    st->size = sb.st_size;
+    st->mtime = sb.st_mtime;
+
+    st->regular_file = S_ISREG(sb.st_mode);
+    st->exists = true;
+#ifdef CAN_CLONE
+    st->can_clone = true;
+#endif
+
+    /* We're using UNIX file API, even on Windows; thus, we supply external file attributes with Unix values. */
+    /* TODO: This could be improved on Windows by providing Windows-specific file attributes */
+    ctx->attributes.valid = ZIP_FILE_ATTRIBUTES_HOST_SYSTEM | ZIP_FILE_ATTRIBUTES_EXTERNAL_FILE_ATTRIBUTES;
+    ctx->attributes.host_system = ZIP_OPSYS_UNIX;
+    ctx->attributes.external_file_attributes = (((zip_uint32_t)sb.st_mode) << 16) | ((sb.st_mode & S_IWUSR) ? 0 : 1);
+
+    return true;
+}
+
+
+zip_int64_t
+_zip_stdio_op_tell(zip_source_file_context_t *ctx, void *f) {
+    off_t offset = ftello((FILE *)f);
+
+    if (offset < 0) {
+	zip_error_set(&ctx->error, ZIP_ER_SEEK, errno);
+    }
+
+    return offset;
+}
+
+
+
+
+
+/*
+ * fopen replacement that sets the close-on-exec flag
+ * some implementations support an fopen 'e' flag for that,
+ * but e.g. macOS doesn't.
+ */
+FILE *
+_zip_fopen_close_on_exec(const char *name, bool writeable) {
+    int fd;
+    int flags;
+    FILE *fp;
+
+    flags = O_CLOEXEC;
+    if (writeable) {
+	flags |= O_RDWR;
+    }
+    else {
+	flags |= O_RDONLY;
+    }
+
+    /* mode argument needed on Windows */
+    if ((fd = open(name, flags, 0666)) < 0) {
+	return NULL;
+    }
+    if ((fp = fdopen(fd, writeable ? "r+b" : "rb")) == NULL) {
+	return NULL;
+    }
+    return fp;
+}
+
diff --git a/lib/zip_source_file.c b/lib/zip_source_file_stdio.h
similarity index 64%
rename from lib/zip_source_file.c
rename to lib/zip_source_file_stdio.h
index 3fff4e3..0d70dcd 100644
--- a/lib/zip_source_file.c
+++ b/lib/zip_source_file_stdio.h
@@ -1,6 +1,6 @@
 /*
-  zip_source_file.c -- create data source from file
-  Copyright (C) 1999-2019 Dieter Baron and Thomas Klausner
+  zip_source_file_stdio.h -- common header for stdio file implementation
+  Copyright (C) 2020 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>
@@ -31,31 +31,26 @@
   IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
 
+#include "config.h"
 
 #include <stdio.h>
 
-#include "zipint.h"
-
-#ifdef _WIN32
-#error This file is incompatible with Windows, use zip_source_win32utf8.c instead.
-#error Something probably went wrong with configure/cmake.
+#ifdef HAVE_CLONEFILE
+#include <sys/attr.h>
+#include <sys/clonefile.h>
+#define CAN_CLONE
+#endif
+#ifdef HAVE_FICLONERANGE
+#include <linux/fs.h>
+#include <sys/ioctl.h>
+#define CAN_CLONE
 #endif
 
-ZIP_EXTERN zip_source_t *
-zip_source_file(zip_t *za, const char *fname, zip_uint64_t start, zip_int64_t len) {
-    if (za == NULL)
-	return NULL;
+void _zip_stdio_op_close(zip_source_file_context_t *ctx);
+bool _zip_stdio_op_open(zip_source_file_context_t *ctx);
+zip_int64_t _zip_stdio_op_read(zip_source_file_context_t *ctx, void *buf, zip_uint64_t len);
+bool _zip_stdio_op_seek(zip_source_file_context_t *ctx, void *f, zip_int64_t offset, int whence);
+bool _zip_stdio_op_stat(zip_source_file_context_t *ctx, zip_source_file_stat_t *st);
+zip_int64_t _zip_stdio_op_tell(zip_source_file_context_t *ctx, void *f);
 
-    return zip_source_file_create(fname, start, len, &za->error);
-}
-
-
-ZIP_EXTERN zip_source_t *
-zip_source_file_create(const char *fname, zip_uint64_t start, zip_int64_t length, zip_error_t *error) {
-    if (fname == NULL || length < -1) {
-	zip_error_set(error, ZIP_ER_INVAL, 0);
-	return NULL;
-    }
-
-    return _zip_source_file_or_p(fname, NULL, start, length, NULL, error);
-}
+FILE *_zip_fopen_close_on_exec(const char *name, bool writeable);
diff --git a/lib/zip_source_file_stdio_write.c b/lib/zip_source_file_stdio_write.c
new file mode 100644
index 0000000..c8560ff
--- /dev/null
+++ b/lib/zip_source_file_stdio_write.c
@@ -0,0 +1,279 @@
+/*
+  zip_source_file_stdio_write.c -- implementation of stdio backend with write support
+  Copyright (C) 2020 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 "zipint.h"
+
+#include "zip_source_file.h"
+#include "zip_source_file_stdio.h"
+
+#include <sys/stat.h>
+#include <stdlib.h>
+
+static zip_int64_t _zip_stdio_op_commit_write(zip_source_file_context_t *ctx);
+static zip_int64_t _zip_stdio_op_create_temp_output(zip_source_file_context_t *ctx);
+#ifdef CAN_CLONE
+static zip_int64_t _zip_stdio_op_create_temp_output_cloning(struct read_file *ctx, zip_uint64_t offset);
+#endif
+static zip_int64_t _zip_stdio_op_remove(zip_source_file_context_t *ctx);
+static void _zip_stdio_op_rollback_write(zip_source_file_context_t *ctx);
+static zip_int64_t _zip_stdio_op_write(zip_source_file_context_t *ctx, const void *data, zip_uint64_t len);
+
+zip_source_file_operations_t ops_stdio_write = {
+    _zip_stdio_op_close,
+    _zip_stdio_op_commit_write,
+    _zip_stdio_op_create_temp_output,
+#ifdef CAN_CLONE
+    _zip_stdio_op_create_temp_output_cloning,
+#else
+    NULL,
+#endif
+    _zip_stdio_op_open,
+    _zip_stdio_op_read,
+    _zip_stdio_op_remove,
+    _zip_stdio_op_rollback_write,
+    _zip_stdio_op_seek,
+    _zip_stdio_op_stat,
+    strdup,
+    _zip_stdio_op_tell,
+    _zip_stdio_op_write
+};
+
+ZIP_EXTERN zip_source_t *
+zip_source_file(zip_t *za, const char *fname, zip_uint64_t start, zip_int64_t len) {
+    if (za == NULL)
+	return NULL;
+
+    return zip_source_file_create(fname, start, len, &za->error);
+}
+
+
+ZIP_EXTERN zip_source_t *
+zip_source_file_create(const char *fname, zip_uint64_t start, zip_int64_t length, zip_error_t *error) {
+    if (fname == NULL || length < -1) {
+	zip_error_set(error, ZIP_ER_INVAL, 0);
+	return NULL;
+    }
+
+    return zip_source_file_common_new(fname, NULL, start, length, NULL, &ops_stdio_write, error);
+}
+
+
+static zip_int64_t
+_zip_stdio_op_commit_write(zip_source_file_context_t *ctx) {
+    if (fclose(ctx->fout) < 0) {
+	zip_error_set(&ctx->error, ZIP_ER_WRITE, errno);
+	return -1;
+    }
+    if (rename(ctx->tmpname, ctx->fname) < 0) {
+	zip_error_set(&ctx->error, ZIP_ER_RENAME, errno);
+	return -1;
+    }
+
+    return 0;
+}
+
+
+static zip_int64_t
+_zip_stdio_op_create_temp_output(zip_source_file_context_t *ctx) {
+    char *temp;
+    int tfd;
+    int mode;
+    FILE *tfp;
+    struct stat st;
+
+    if ((temp = (char *)malloc(strlen(ctx->fname) + 8)) == NULL) {
+	zip_error_set(&ctx->error, ZIP_ER_MEMORY, 0);
+	return -1;
+    }
+
+    if (stat(ctx->fname, &st) == 0) {
+	mode = st.st_mode;
+    }
+    else {
+	mode = -1;
+    }
+
+    sprintf(temp, "%s.XXXXXX", ctx->fname);
+
+    if ((tfd = _zip_mkstempm(temp, mode)) == -1) {
+	zip_error_set(&ctx->error, ZIP_ER_TMPOPEN, errno);
+	free(temp);
+	return -1;
+    }
+
+    if ((tfp = fdopen(tfd, "r+b")) == NULL) {
+	zip_error_set(&ctx->error, ZIP_ER_TMPOPEN, errno);
+	close(tfd);
+	(void)remove(temp);
+	free(temp);
+	return -1;
+    }
+
+    ctx->fout = tfp;
+    ctx->tmpname = temp;
+
+    return 0;
+}
+
+#ifdef CAN_CLONE
+static zip_int64_t
+_zip_stdio_op_create_temp_output_cloning(struct read_file *ctx, zip_uint64_t offset) {
+    char *temp;
+    FILE *tfp;
+
+    if (offset > ZIP_OFF_MAX) {
+	zip_error_set(&ctx->error, ZIP_ER_SEEK, E2BIG);
+	return -1;
+    }
+
+    if ((temp = (char *)malloc(strlen(ctx->fname) + 8)) == NULL) {
+	zip_error_set(&ctx->error, ZIP_ER_MEMORY, 0);
+	return -1;
+    }
+    sprintf(temp, "%s.XXXXXX", ctx->fname);
+
+#ifdef HAVE_CLONEFILE
+#ifndef __clang_analyzer__
+    /* we can't use mkstemp, since clonefile insists on creating the file */
+    if (mktemp(temp) == NULL) {
+	zip_error_set(&ctx->error, ZIP_ER_TMPOPEN, errno);
+	free(temp);
+	return -1;
+    }
+#endif
+
+    if (clonefile(ctx->fname, temp, 0) < 0) {
+	zip_error_set(&ctx->error, ZIP_ER_TMPOPEN, errno);
+	free(temp);
+	return -1;
+    }
+    if ((tfp = _zip_fopen_close_on_exec(temp, true)) == NULL) {
+	zip_error_set(&ctx->error, ZIP_ER_TMPOPEN, errno);
+	(void)remove(temp);
+	free(temp);
+	return -1;
+    }
+#else
+    {
+	int fd;
+	struct file_clone_range range;
+	struct stat st;
+
+	if (fstat(fileno(ctx->f), &st) < 0) {
+	    zip_error_set(&ctx->error, ZIP_ER_TMPOPEN, errno);
+	    return -1;
+	}
+
+	if ((fd = mkstemp(temp)) < 0) {
+	    zip_error_set(&ctx->error, ZIP_ER_TMPOPEN, errno);
+	    free(temp);
+	    return -1;
+	}
+
+	range.src_fd = fileno(ctx->f);
+	range.src_offset = 0;
+	range.src_length = ((offset + st.st_blksize - 1) / st.st_blksize) * st.st_blksize;
+	if (range.src_length > st.st_size) {
+	    range.src_length = 0;
+	}
+	range.dest_offset = 0;
+	if (ioctl(fd, FICLONERANGE, &range) < 0) {
+	    zip_error_set(&ctx->error, ZIP_ER_TMPOPEN, errno);
+	    (void)close(fd);
+	    (void)remove(temp);
+	    free(temp);
+	    return -1;
+	}
+
+	if ((tfp = fdopen(fd, "r+b")) == NULL) {
+	    zip_error_set(&ctx->error, ZIP_ER_TMPOPEN, errno);
+	    (void)close(fd);
+	    (void)remove(temp);
+	    free(temp);
+	    return -1;
+	}
+    }
+#endif
+
+    if (ftruncate(fileno(tfp), (off_t)offset) < 0) {
+	(void)fclose(tfp);
+	(void)remove(temp);
+	free(temp);
+	return -1;
+    }
+    if (fseeko(tfp, (off_t)offset, SEEK_SET) < 0) {
+	(void)fclose(tfp);
+	(void)remove(temp);
+	free(temp);
+	zip_error_set(&ctx->error, ZIP_ER_TMPOPEN, errno);
+    }
+
+    ctx->fout = tfp;
+    ctx->tmpname = temp;
+
+    return 0;
+}
+#endif
+
+static zip_int64_t
+_zip_stdio_op_remove(zip_source_file_context_t *ctx) {
+    if (remove(ctx->fname) < 0) {
+	zip_error_set(&ctx->error, ZIP_ER_REMOVE, errno);
+	return -1;
+    }
+    return 0;
+}
+
+
+static void
+_zip_stdio_op_rollback_write(zip_source_file_context_t *ctx) {
+    if (ctx->fout) {
+	fclose(ctx->fout);
+    }
+    (void)remove(ctx->tmpname);
+}
+
+static zip_int64_t
+_zip_stdio_op_write(zip_source_file_context_t *ctx, const void *data, zip_uint64_t len) {
+    off_t ret;
+
+    clearerr((FILE *)ctx->fout);
+    ret = fwrite(data, 1, len, (FILE *)ctx->fout);
+    if (ret != len || ferror((FILE *)ctx->fout)) {
+	zip_error_set(&ctx->error, ZIP_ER_WRITE, errno);
+	return -1;
+    }
+
+    return (zip_int64_t)ret;
+}
+
diff --git a/lib/zip_source_filep.c b/lib/zip_source_filep.c
deleted file mode 100644
index 9b38811..0000000
--- a/lib/zip_source_filep.c
+++ /dev/null
@@ -1,691 +0,0 @@
-/*
-  zip_source_filep.c -- create data source from FILE *
-  Copyright (C) 1999-2019 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 <fcntl.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/stat.h>
-
-#include "zipint.h"
-
-#ifdef HAVE_UNISTD_H
-#include <unistd.h>
-#endif
-
-#ifdef HAVE_CLONEFILE
-#include <sys/attr.h>
-#include <sys/clonefile.h>
-#define CAN_CLONE
-#endif
-#ifdef HAVE_FICLONERANGE
-#include <linux/fs.h>
-#include <sys/ioctl.h>
-#define CAN_CLONE
-#endif
-
-#ifdef _WIN32
-#  ifndef S_IWUSR
-#    define S_IWUSR _S_IWRITE
-#  endif
-#endif
-
-struct read_file {
-    zip_error_t error; /* last error information */
-    zip_int64_t supports;
-
-    /* reading */
-    char *fname;                      /* name of file to read from */
-    FILE *f;                          /* file to read from */
-    zip_stat_t st;                    /* stat information passed in */
-    zip_file_attributes_t attributes; /* additional file attributes */
-    zip_error_t stat_error;           /* error returned for stat */
-    zip_uint64_t start;               /* start offset of data to read */
-    zip_uint64_t end;                 /* end offset of data to read relative to start, 0 for up to EOF */
-    zip_uint64_t current;             /* current offset relative to start (0 is beginning of part we read) */
-
-    /* writing */
-    char *tmpname;
-    FILE *fout;
-};
-
-static zip_int64_t read_file(void *state, void *data, zip_uint64_t len, zip_source_cmd_t cmd);
-#ifdef CAN_CLONE
-static zip_int64_t create_temp_output_cloning(struct read_file *ctx, zip_uint64_t offset);
-#endif
-static FILE *_zip_fopen(const char *name, bool writeable);
-static int _zip_fseek_u(FILE *f, zip_uint64_t offset, int whence, zip_error_t *error);
-static int _zip_fseek(FILE *f, zip_int64_t offset, int whence, zip_error_t *error);
-
-
-ZIP_EXTERN zip_source_t *
-zip_source_filep(zip_t *za, FILE *file, zip_uint64_t start, zip_int64_t len) {
-    if (za == NULL)
-	return NULL;
-
-    return zip_source_filep_create(file, start, len, &za->error);
-}
-
-
-ZIP_EXTERN zip_source_t *
-zip_source_filep_create(FILE *file, zip_uint64_t start, zip_int64_t length, zip_error_t *error) {
-    if (file == NULL || length < -1) {
-	zip_error_set(error, ZIP_ER_INVAL, 0);
-	return NULL;
-    }
-
-    return _zip_source_file_or_p(NULL, file, start, length, NULL, error);
-}
-
-
-zip_source_t *
-_zip_source_file_or_p(const char *fname, FILE *file, zip_uint64_t start, zip_int64_t len, const zip_stat_t *st, zip_error_t *error) {
-    struct read_file *ctx;
-    zip_source_t *zs;
-    struct stat sb;
-    bool stat_valid;
-
-    if (file == NULL && fname == NULL) {
-	zip_error_set(error, ZIP_ER_INVAL, 0);
-	return NULL;
-    }
-
-    if (len < 0) {
-	len = 0;
-    }
-
-    if (start > ZIP_INT64_MAX || start + (zip_uint64_t)len < start) {
-	zip_error_set(error, ZIP_ER_INVAL, 0);
-	return NULL;
-    }
-
-    if ((ctx = (struct read_file *)malloc(sizeof(struct read_file))) == NULL) {
-	zip_error_set(error, ZIP_ER_MEMORY, 0);
-	return NULL;
-    }
-
-    ctx->fname = NULL;
-    if (fname) {
-	if ((ctx->fname = strdup(fname)) == NULL) {
-	    zip_error_set(error, ZIP_ER_MEMORY, 0);
-	    free(ctx);
-	    return NULL;
-	}
-    }
-    ctx->f = file;
-    ctx->start = start;
-    ctx->end = (zip_uint64_t)len;
-    if (st) {
-	memcpy(&ctx->st, st, sizeof(ctx->st));
-	ctx->st.name = NULL;
-	ctx->st.valid &= ~ZIP_STAT_NAME;
-    }
-    else {
-	zip_stat_init(&ctx->st);
-    }
-
-    if (ctx->end > 0) {
-	ctx->st.size = ctx->end;
-	ctx->st.valid |= ZIP_STAT_SIZE;
-    }
-
-    zip_error_init(&ctx->stat_error);
-
-    ctx->tmpname = NULL;
-    ctx->fout = NULL;
-
-    zip_error_init(&ctx->error);
-    zip_file_attributes_init(&ctx->attributes);
-
-    ctx->supports = ZIP_SOURCE_SUPPORTS_READABLE | zip_source_make_command_bitmap(ZIP_SOURCE_SUPPORTS, ZIP_SOURCE_TELL, -1);
-
-    if (ctx->fname) {
-	stat_valid = stat(ctx->fname, &sb) >= 0;
-
-	if (!stat_valid) {
-	    if (ctx->start == 0 && ctx->end == 0) {
-		ctx->supports = ZIP_SOURCE_SUPPORTS_WRITABLE;
-	    }
-	}
-    }
-    else {
-	stat_valid = fstat(fileno(ctx->f), &sb) >= 0;
-    }
-
-    if (!stat_valid) {
-	zip_error_set(&ctx->stat_error, ZIP_ER_READ, errno);
-    }
-    else {
-	if ((ctx->st.valid & ZIP_STAT_MTIME) == 0) {
-	    ctx->st.mtime = sb.st_mtime;
-	    ctx->st.valid |= ZIP_STAT_MTIME;
-	}
-	if (S_ISREG(sb.st_mode)) {
-	    ctx->supports = ZIP_SOURCE_SUPPORTS_SEEKABLE;
-
-	    if (ctx->start + ctx->end > (zip_uint64_t)sb.st_size) {
-		zip_error_set(error, ZIP_ER_INVAL, 0);
-		free(ctx->fname);
-		free(ctx);
-		return NULL;
-	    }
-
-	    if (ctx->end == 0) {
-		ctx->st.size = (zip_uint64_t)sb.st_size - ctx->start;
-		ctx->st.valid |= ZIP_STAT_SIZE;
-
-		if (ctx->fname && start == 0) {
-		    ctx->supports = ZIP_SOURCE_SUPPORTS_WRITABLE;
-		}
-	    }
-	}
-
-	/* We're using UNIX file API, even on Windows; thus, we supply external file attributes with Unix values. */
-	/* TODO: This could be improved on Windows by providing Windows-specific file attributes */
-	ctx->attributes.valid = ZIP_FILE_ATTRIBUTES_HOST_SYSTEM | ZIP_FILE_ATTRIBUTES_EXTERNAL_FILE_ATTRIBUTES;
-	ctx->attributes.host_system = ZIP_OPSYS_UNIX;
-	ctx->attributes.external_file_attributes = (((zip_uint32_t)sb.st_mode) << 16) | ((sb.st_mode & S_IWUSR) ? 0 : 1);
-
-	ctx->supports |= ZIP_SOURCE_MAKE_COMMAND_BITMASK(ZIP_SOURCE_GET_FILE_ATTRIBUTES);
-    }
-
-    ctx->supports |= ZIP_SOURCE_MAKE_COMMAND_BITMASK(ZIP_SOURCE_ACCEPT_EMPTY);
-#ifdef CAN_CLONE
-    if (ctx->supports & ZIP_SOURCE_MAKE_COMMAND_BITMASK(ZIP_SOURCE_BEGIN_WRITE)) {
-	ctx->supports |= ZIP_SOURCE_MAKE_COMMAND_BITMASK(ZIP_SOURCE_BEGIN_WRITE_CLONING);
-    }
-#endif
-
-    if ((zs = zip_source_function_create(read_file, ctx, error)) == NULL) {
-	free(ctx->fname);
-	free(ctx);
-	return NULL;
-    }
-
-    return zs;
-}
-
-
-#ifndef _WIN32
-static int
-create_temp_output(struct read_file *ctx) {
-    char *temp;
-    int tfd;
-    int mode;
-    FILE *tfp;
-    struct stat st;
-
-    if ((temp = (char *)malloc(strlen(ctx->fname) + 8)) == NULL) {
-	zip_error_set(&ctx->error, ZIP_ER_MEMORY, 0);
-	return -1;
-    }
-
-    if (stat(ctx->fname, &st) == 0) {
-	mode = st.st_mode;
-    }
-    else {
-	mode = -1;
-    }
-
-    sprintf(temp, "%s.XXXXXX", ctx->fname);
-
-    if ((tfd = _zip_mkstempm(temp, mode)) == -1) {
-	zip_error_set(&ctx->error, ZIP_ER_TMPOPEN, errno);
-	free(temp);
-	return -1;
-    }
-
-    if ((tfp = fdopen(tfd, "r+b")) == NULL) {
-	zip_error_set(&ctx->error, ZIP_ER_TMPOPEN, errno);
-	close(tfd);
-	(void)remove(temp);
-	free(temp);
-	return -1;
-    }
-
-    ctx->fout = tfp;
-    ctx->tmpname = temp;
-
-    return 0;
-}
-#endif
-
-#ifdef CAN_CLONE
-zip_int64_t static create_temp_output_cloning(struct read_file *ctx, zip_uint64_t offset) {
-    char *temp;
-    FILE *tfp;
-
-    if (offset > ZIP_OFF_MAX) {
-	zip_error_set(&ctx->error, ZIP_ER_SEEK, E2BIG);
-	return -1;
-    }
-
-    if ((temp = (char *)malloc(strlen(ctx->fname) + 8)) == NULL) {
-	zip_error_set(&ctx->error, ZIP_ER_MEMORY, 0);
-	return -1;
-    }
-    sprintf(temp, "%s.XXXXXX", ctx->fname);
-
-#ifdef HAVE_CLONEFILE
-#ifndef __clang_analyzer__
-    /* we can't use mkstemp, since clonefile insists on creating the file */
-    if (mktemp(temp) == NULL) {
-	zip_error_set(&ctx->error, ZIP_ER_TMPOPEN, errno);
-	free(temp);
-	return -1;
-    }
-#endif
-
-    if (clonefile(ctx->fname, temp, 0) < 0) {
-	zip_error_set(&ctx->error, ZIP_ER_TMPOPEN, errno);
-	free(temp);
-	return -1;
-    }
-    if ((tfp = _zip_fopen(temp, true)) == NULL) {
-	zip_error_set(&ctx->error, ZIP_ER_TMPOPEN, errno);
-	(void)remove(temp);
-	free(temp);
-	return -1;
-    }
-#else
-    {
-	int fd;
-	struct file_clone_range range;
-	struct stat st;
-
-	if (fstat(fileno(ctx->f), &st) < 0) {
-	    zip_error_set(&ctx->error, ZIP_ER_TMPOPEN, errno);
-	    return -1;
-	}
-
-	if ((fd = mkstemp(temp)) < 0) {
-	    zip_error_set(&ctx->error, ZIP_ER_TMPOPEN, errno);
-	    free(temp);
-	    return -1;
-	}
-
-	range.src_fd = fileno(ctx->f);
-	range.src_offset = 0;
-	range.src_length = ((offset + st.st_blksize - 1) / st.st_blksize) * st.st_blksize;
-	if (range.src_length > st.st_size) {
-	    range.src_length = 0;
-	}
-	range.dest_offset = 0;
-	if (ioctl(fd, FICLONERANGE, &range) < 0) {
-	    zip_error_set(&ctx->error, ZIP_ER_TMPOPEN, errno);
-	    (void)close(fd);
-	    (void)remove(temp);
-	    free(temp);
-	    return -1;
-	}
-
-	if ((tfp = fdopen(fd, "r+b")) == NULL) {
-	    zip_error_set(&ctx->error, ZIP_ER_TMPOPEN, errno);
-	    (void)close(fd);
-	    (void)remove(temp);
-	    free(temp);
-	    return -1;
-	}
-    }
-#endif
-
-    if (ftruncate(fileno(tfp), (off_t)offset) < 0) {
-	(void)fclose(tfp);
-	(void)remove(temp);
-	free(temp);
-	return -1;
-    }
-    if (fseeko(tfp, (off_t)offset, SEEK_SET) < 0) {
-	(void)fclose(tfp);
-	(void)remove(temp);
-	free(temp);
-	zip_error_set(&ctx->error, ZIP_ER_TMPOPEN, errno);
-    }
-
-    ctx->fout = tfp;
-    ctx->tmpname = temp;
-
-    return 0;
-}
-#endif
-
-
-static zip_int64_t
-read_file(void *state, void *data, zip_uint64_t len, zip_source_cmd_t cmd) {
-    struct read_file *ctx;
-    char *buf;
-    zip_uint64_t n;
-    size_t i;
-
-    ctx = (struct read_file *)state;
-    buf = (char *)data;
-
-    switch (cmd) {
-    case ZIP_SOURCE_ACCEPT_EMPTY:
-	return 0;
-
-    case ZIP_SOURCE_BEGIN_WRITE:
-#ifdef _WIN32
-	return -1;
-#else
-	if (ctx->fname == NULL) {
-	    zip_error_set(&ctx->error, ZIP_ER_OPNOTSUPP, 0);
-	    return -1;
-	}
-	return create_temp_output(ctx);
-#endif
-
-#ifdef CAN_CLONE
-    case ZIP_SOURCE_BEGIN_WRITE_CLONING:
-	if (ctx->fname == NULL) {
-	    zip_error_set(&ctx->error, ZIP_ER_OPNOTSUPP, 0);
-	    return -1;
-	}
-	return create_temp_output_cloning(ctx, len);
-#endif
-
-    case ZIP_SOURCE_CLOSE:
-	if (ctx->fname) {
-	    fclose(ctx->f);
-	    ctx->f = NULL;
-	}
-	return 0;
-
-    case ZIP_SOURCE_COMMIT_WRITE: {
-	if (fclose(ctx->fout) < 0) {
-	    ctx->fout = NULL;
-	    zip_error_set(&ctx->error, ZIP_ER_WRITE, errno);
-	}
-	ctx->fout = NULL;
-	if (rename(ctx->tmpname, ctx->fname) < 0) {
-	    zip_error_set(&ctx->error, ZIP_ER_RENAME, errno);
-	    return -1;
-	}
-	free(ctx->tmpname);
-	ctx->tmpname = NULL;
-	return 0;
-    }
-
-    case ZIP_SOURCE_ERROR:
-	return zip_error_to_data(&ctx->error, data, len);
-
-    case ZIP_SOURCE_FREE:
-	free(ctx->fname);
-	free(ctx->tmpname);
-	if (ctx->f)
-	    fclose(ctx->f);
-	free(ctx);
-	return 0;
-
-    case ZIP_SOURCE_GET_FILE_ATTRIBUTES:
-	if (len < sizeof(ctx->attributes)) {
-            zip_error_set(&ctx->error, ZIP_ER_INVAL, 0);
-	    return -1;
-	}
-	memcpy(data, &ctx->attributes, sizeof(ctx->attributes));
-	return sizeof(ctx->attributes);
-
-    case ZIP_SOURCE_OPEN:
-	if (ctx->fname) {
-	    if ((ctx->f = _zip_fopen(ctx->fname, false)) == NULL) {
-		zip_error_set(&ctx->error, ZIP_ER_OPEN, errno);
-		return -1;
-	    }
-	}
-
-	if (ctx->start > 0) {
-	    if (_zip_fseek_u(ctx->f, ctx->start, SEEK_SET, &ctx->error) < 0) {
-		/* TODO: skip by reading */
-		return -1;
-	    }
-	}
-	ctx->current = 0;
-	return 0;
-
-    case ZIP_SOURCE_READ:
-	if (ctx->end > 0) {
-	    n = ctx->end - ctx->current;
-	    if (n > len) {
-		n = len;
-	    }
-	}
-	else {
-	    n = len;
-	}
-
-	if (n > SIZE_MAX)
-	    n = SIZE_MAX;
-
-	if ((i = fread(buf, 1, (size_t)n, ctx->f)) == 0) {
-	    if (ferror(ctx->f)) {
-		zip_error_set(&ctx->error, ZIP_ER_READ, errno);
-		return -1;
-	    }
-	}
-	ctx->current += i;
-
-	return (zip_int64_t)i;
-
-    case ZIP_SOURCE_REMOVE:
-	if (remove(ctx->fname) < 0) {
-	    zip_error_set(&ctx->error, ZIP_ER_REMOVE, errno);
-	    return -1;
-	}
-	return 0;
-
-    case ZIP_SOURCE_ROLLBACK_WRITE:
-	if (ctx->fout) {
-	    fclose(ctx->fout);
-	    ctx->fout = NULL;
-	}
-	(void)remove(ctx->tmpname);
-	free(ctx->tmpname);
-	ctx->tmpname = NULL;
-	return 0;
-
-    case ZIP_SOURCE_SEEK: {
-	zip_int64_t new_current;
-	int need_seek;
-	zip_source_args_seek_t *args = ZIP_SOURCE_GET_ARGS(zip_source_args_seek_t, data, len, &ctx->error);
-
-	if (args == NULL)
-	    return -1;
-
-	need_seek = 1;
-
-	switch (args->whence) {
-	case SEEK_SET:
-	    new_current = args->offset;
-	    break;
-
-	case SEEK_END:
-	    if (ctx->end == 0) {
-		if (_zip_fseek(ctx->f, args->offset, SEEK_END, &ctx->error) < 0) {
-		    return -1;
-		}
-		if ((new_current = ftello(ctx->f)) < 0) {
-		    zip_error_set(&ctx->error, ZIP_ER_SEEK, errno);
-		    return -1;
-		}
-		if (new_current < (zip_int64_t)ctx->start) {
-                    new_current = 0;
-                }
-                else {
-                    new_current -= (zip_int64_t)ctx->start;
-                }
-		need_seek = 0;
-	    }
-	    else {
-		new_current = (zip_int64_t)ctx->end + args->offset;
-	    }
-	    break;
-
-	case SEEK_CUR:
-	    new_current = (zip_int64_t)ctx->current + args->offset;
-	    break;
-
-	default:
-	    zip_error_set(&ctx->error, ZIP_ER_INVAL, 0);
-	    return -1;
-	}
-
-	if (new_current < 0 || (ctx->end != 0 && (zip_uint64_t)new_current > ctx->end) || (zip_uint64_t)new_current + ctx->start < ctx->start) {
-	    zip_error_set(&ctx->error, ZIP_ER_INVAL, 0);
-	    return -1;
-	}
-
-	ctx->current = (zip_uint64_t)new_current;
-
-	if (need_seek) {
-	    if (_zip_fseek_u(ctx->f, ctx->current + ctx->start, SEEK_SET, &ctx->error) < 0) {
-		return -1;
-	    }
-	}
-	return 0;
-    }
-
-    case ZIP_SOURCE_SEEK_WRITE: {
-	zip_source_args_seek_t *args;
-
-	args = ZIP_SOURCE_GET_ARGS(zip_source_args_seek_t, data, len, &ctx->error);
-	if (args == NULL) {
-	    return -1;
-	}
-
-	if (_zip_fseek(ctx->fout, args->offset, args->whence, &ctx->error) < 0) {
-	    return -1;
-	}
-	return 0;
-    }
-
-    case ZIP_SOURCE_STAT: {
-	if (len < sizeof(ctx->st))
-	    return -1;
-
-	if (zip_error_code_zip(&ctx->stat_error) != 0) {
-	    zip_error_set(&ctx->error, zip_error_code_zip(&ctx->stat_error), zip_error_code_system(&ctx->stat_error));
-	    return -1;
-	}
-
-	memcpy(data, &ctx->st, sizeof(ctx->st));
-	return sizeof(ctx->st);
-    }
-
-    case ZIP_SOURCE_SUPPORTS:
-	return ctx->supports;
-
-    case ZIP_SOURCE_TELL:
-	return (zip_int64_t)ctx->current;
-
-    case ZIP_SOURCE_TELL_WRITE: {
-	off_t ret = ftello(ctx->fout);
-
-	if (ret < 0) {
-	    zip_error_set(&ctx->error, ZIP_ER_TELL, errno);
-	    return -1;
-	}
-	return ret;
-    }
-
-    case ZIP_SOURCE_WRITE: {
-	size_t ret;
-
-	clearerr(ctx->fout);
-	ret = fwrite(data, 1, len, ctx->fout);
-	if (ret != len || ferror(ctx->fout)) {
-	    zip_error_set(&ctx->error, ZIP_ER_WRITE, errno);
-	    return -1;
-	}
-
-	return (zip_int64_t)ret;
-    }
-
-    default:
-	zip_error_set(&ctx->error, ZIP_ER_OPNOTSUPP, 0);
-	return -1;
-    }
-}
-
-
-static int
-_zip_fseek_u(FILE *f, zip_uint64_t offset, int whence, zip_error_t *error) {
-    if (offset > ZIP_INT64_MAX) {
-	zip_error_set(error, ZIP_ER_SEEK, EOVERFLOW);
-	return -1;
-    }
-    return _zip_fseek(f, (zip_int64_t)offset, whence, error);
-}
-
-
-static int
-_zip_fseek(FILE *f, zip_int64_t offset, int whence, zip_error_t *error) {
-    if (offset > ZIP_FSEEK_MAX || offset < ZIP_FSEEK_MIN) {
-	zip_error_set(error, ZIP_ER_SEEK, EOVERFLOW);
-	return -1;
-    }
-    if (fseeko(f, (off_t)offset, whence) < 0) {
-	zip_error_set(error, ZIP_ER_SEEK, errno);
-	return -1;
-    }
-    return 0;
-}
-
-
-/*
- * fopen replacement that sets the close-on-exec flag
- * some implementations support an fopen 'e' flag for that,
- * but e.g. macOS doesn't.
- */
-static FILE *
-_zip_fopen(const char *name, bool writeable) {
-    int fd;
-    int flags;
-    FILE *fp;
-
-    flags = O_CLOEXEC;
-    if (writeable) {
-	flags |= O_RDWR;
-    }
-    else {
-	flags |= O_RDONLY;
-    }
-
-    /* mode argument needed on Windows */
-    if ((fd = open(name, flags, 0666)) < 0) {
-	return NULL;
-    }
-    if ((fp = fdopen(fd, writeable ? "r+b" : "rb")) == NULL) {
-	return NULL;
-    }
-    return fp;
-}