VFS: Initial write support for zip files
Vicki Pfau vi@endrift.com
Tue, 08 Dec 2020 19:30:30 -0800
2 files changed,
162 insertions(+),
66 deletions(-)
M
src/util/vfs.c
→
src/util/vfs.c
@@ -101,12 +101,12 @@ struct VDir* dir = 0;
UNUSED(path); #if defined(USE_LIBZIP) || defined(USE_ZLIB) if (!dir) { - dir = VDirOpenZip(path, 0); + dir = VDirOpenZip(path, O_RDONLY); } #endif #ifdef USE_LZMA if (!dir) { - dir = VDirOpen7z(path, 0); + dir = VDirOpen7z(path, O_RDONLY); } #endif return dir;
M
src/util/vfs/vfs-zip.c
→
src/util/vfs/vfs-zip.c
@@ -5,6 +5,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include <mgba-util/vfs.h> +#include <mgba-util/math.h> #include <mgba-util/string.h> #ifdef USE_LIBZIP@@ -19,17 +20,22 @@
struct VDirZip { struct VDir d; struct zip* z; + bool write; struct VDirEntryZip dirent; }; struct VFileZip { struct VFile d; + struct zip* z; struct zip_file* zf; void* buffer; size_t offset; size_t bufferSize; size_t readSize; + size_t writeSize; size_t fileSize; + char* name; + bool write; }; enum {@@ -37,8 +43,10 @@ BLOCK_SIZE = 1024
}; #else #ifdef USE_MINIZIP +#include <minizip/zip.h> #include <minizip/unzip.h> #else +#include "third-party/zlib/contrib/minizip/zip.h" #include "third-party/zlib/contrib/minizip/unzip.h" #endif #include <mgba-util/memory.h>@@ -47,19 +55,22 @@ struct VDirEntryZip {
struct VDirEntry d; char name[PATH_MAX]; size_t fileSize; - unzFile z; + unzFile uz; + zipFile z; }; struct VDirZip { struct VDir d; - unzFile z; + unzFile uz; + zipFile z; struct VDirEntryZip dirent; bool atStart; }; struct VFileZip { struct VFile d; - unzFile z; + unzFile uz; + zipFile z; void* buffer; size_t bufferSize; size_t fileSize;@@ -103,6 +114,9 @@ break;
} if (mode & ZLIB_FILEFUNC_MODE_CREATE) { flags |= O_CREAT; + if (!(mode & ZLIB_FILEFUNC_MODE_EXISTING)) { + flags |= O_TRUNC; + } } return VFileOpen(filename, flags); }@@ -117,6 +131,16 @@ }
return r; } +static uLong _vfmzWrite(voidpf opaque, voidpf stream, const void* buf, uLong size) { + UNUSED(opaque); + struct VFile* vf = stream; + ssize_t r = vf->write(vf, buf, size); + if (r < 0) { + return 0; + } + return r; +} + int _vfmzClose(voidpf opaque, voidpf stream) { UNUSED(opaque); struct VFile* vf = stream;@@ -148,25 +172,44 @@ UNUSED(flags);
zlib_filefunc_def ops = { .zopen_file = _vfmzOpen, .zread_file = _vfmzRead, - .zwrite_file = 0, + .zwrite_file = _vfmzWrite, .ztell_file = _vfmzTell, .zseek_file = _vfmzSeek, .zclose_file = _vfmzClose, .zerror_file = _vfmzError, .opaque = 0 }; - unzFile z = unzOpen2(path, &ops); - if (!z) { - return 0; + unzFile uz = NULL; + zipFile z = NULL; + + if ((flags & O_ACCMODE) == O_RDWR) { + return 0; // Read/write not supported + } + if (flags & O_WRONLY) { + z = zipOpen2(path, 0, NULL, &ops); + if (!z) { + return 0; + } + } else { + uz = unzOpen2(path, &ops); + if (!uz) { + return 0; + } } #else int zflags = 0; if (flags & O_CREAT) { zflags |= ZIP_CREATE; } + if (flags & O_TRUNC) { + zflags |= ZIP_TRUNCATE; + } if (flags & O_EXCL) { zflags |= ZIP_EXCL; } + if (!(flags & O_WRONLY)) { + zflags |= ZIP_RDONLY; + } struct zip* z = zip_open(path, zflags, 0); if (!z) {@@ -183,14 +226,19 @@ vd->d.openDir = _vdzOpenDir;
vd->d.deleteFile = _vdzDeleteFile; vd->z = z; -#ifndef USE_LIBZIP +#ifdef USE_LIBZIP + vd->write = !!(flags & O_WRONLY); +#else vd->atStart = true; + vd->uz = uz; #endif vd->dirent.d.name = _vdezName; vd->dirent.d.type = _vdezType; #ifdef USE_LIBZIP vd->dirent.index = -1; +#else + vd->dirent.uz = uz; #endif vd->dirent.z = z;@@ -200,10 +248,21 @@
#ifdef USE_LIBZIP bool _vfzClose(struct VFile* vf) { struct VFileZip* vfz = (struct VFileZip*) vf; - if (zip_fclose(vfz->zf) < 0) { + if (vfz->write) { + zip_source_t* source = zip_source_buffer(vfz->z, vfz->buffer, vfz->writeSize, 1); + vfz->buffer = NULL; + if (source && zip_file_add(vfz->z, vfz->name, source, ZIP_FL_OVERWRITE) < 0) { + zip_source_free(source); + return false; + } + free(vfz->name); + } + if (vfz->zf && zip_fclose(vfz->zf) < 0) { return false; } - free(vfz->buffer); + if (vfz->buffer) { + free(vfz->buffer); + } free(vfz); return true; }@@ -307,11 +366,29 @@ return bytesRead;
} ssize_t _vfzWrite(struct VFile* vf, const void* buffer, size_t size) { - // TODO - UNUSED(vf); - UNUSED(buffer); - UNUSED(size); - return -1; + struct VFileZip* vfz = (struct VFileZip*) vf; + + size_t bytesWritten = 0; + if (!vfz->buffer) { + vfz->bufferSize = toPow2(size); + vfz->buffer = malloc(vfz->bufferSize); + } else if (size > vfz->bufferSize || size > vfz->bufferSize - vfz->offset) { + vfz->bufferSize = toPow2(vfz->offset + size); + vfz->buffer = realloc(vfz->buffer, vfz->bufferSize); + } + + void* start = &((uint8_t*) vfz->buffer)[vfz->offset]; + if (buffer) { + memcpy(start, buffer, size); + } else { + memset(start, 0, size); + } + vfz->offset += size; + if (vfz->offset > vfz->writeSize) { + vfz->writeSize = vfz->offset; + } + bytesWritten += size; + return bytesWritten; } void* _vfzMap(struct VFile* vf, size_t size, int flags) {@@ -370,34 +447,37 @@ UNUSED(mode);
// TODO: support truncating, appending and creating, and write struct VDirZip* vdz = (struct VDirZip*) vd; - if ((mode & O_RDWR) == O_RDWR) { + if ((mode & O_ACCMODE) == O_RDWR) { // libzip doesn't allow for random access, so read/write is impossible without // reading the entire file first. This approach will be supported eventually. return 0; } + struct zip_file* zf = NULL; + struct zip_stat s = {0}; if (mode & O_WRONLY) { - // Write support is not yet implemented. - return 0; - } - - struct zip_stat s; - if (zip_stat(vdz->z, path, 0, &s) < 0) { - return 0; - } + if (!vdz->write) { + return 0; + } + } else { + if (zip_stat(vdz->z, path, 0, &s) < 0) { + return 0; + } - struct zip_file* zf = zip_fopen(vdz->z, path, 0); - if (!zf) { - return 0; + zf = zip_fopen(vdz->z, path, 0); + if (!zf) { + return 0; + } } - struct VFileZip* vfz = malloc(sizeof(struct VFileZip)); + struct VFileZip* vfz = calloc(1, sizeof(struct VFileZip)); vfz->zf = zf; - vfz->buffer = 0; - vfz->offset = 0; - vfz->bufferSize = 0; - vfz->readSize = 0; + vfz->z = vdz->z; vfz->fileSize = s.size; + if (mode & O_WRONLY) { + vfz->name = strdup(path); + vfz->write = true; + } vfz->d.close = _vfzClose; vfz->d.seek = _vfzSeek;@@ -451,7 +531,12 @@ }
#else bool _vfzClose(struct VFile* vf) { struct VFileZip* vfz = (struct VFileZip*) vf; - unzCloseCurrentFile(vfz->z); + if (vfz->uz) { + unzCloseCurrentFile(vfz->uz); + } + if (vfz->z) { + zipCloseFileInZip(vfz->z); + } if (vfz->buffer) { mappedMemoryFree(vfz->buffer, vfz->bufferSize); }@@ -461,15 +546,18 @@ }
off_t _vfzSeek(struct VFile* vf, off_t offset, int whence) { struct VFileZip* vfz = (struct VFileZip*) vf; + if (!vfz->uz) { + return -1; + } - int64_t currentPos = unztell64(vfz->z); + int64_t currentPos = unztell64(vfz->uz); int64_t pos; switch (whence) { case SEEK_SET: pos = 0; break; case SEEK_CUR: - pos = unztell64(vfz->z); + pos = unztell64(vfz->uz); break; case SEEK_END: pos = vfz->fileSize;@@ -483,8 +571,8 @@ return -1;
} pos += offset; if (currentPos > pos) { - unzCloseCurrentFile(vfz->z); - unzOpenCurrentFile(vfz->z); + unzCloseCurrentFile(vfz->uz); + unzOpenCurrentFile(vfz->uz); currentPos = 0; } while (currentPos < pos) {@@ -500,20 +588,17 @@ }
currentPos += read; } - return unztell64(vfz->z); + return unztell64(vfz->uz); } ssize_t _vfzRead(struct VFile* vf, void* buffer, size_t size) { struct VFileZip* vfz = (struct VFileZip*) vf; - return unzReadCurrentFile(vfz->z, buffer, size); + return unzReadCurrentFile(vfz->uz, buffer, size); } ssize_t _vfzWrite(struct VFile* vf, const void* buffer, size_t size) { - // TODO - UNUSED(vf); - UNUSED(buffer); - UNUSED(size); - return -1; + struct VFileZip* vfz = (struct VFileZip*) vf; + return zipWriteInFileInZip(vfz->z, buffer, size); } void* _vfzMap(struct VFile* vf, size_t size, int flags) {@@ -532,11 +617,11 @@ if (!vfz->buffer) {
return 0; } - unzCloseCurrentFile(vfz->z); - unzOpenCurrentFile(vfz->z); + unzCloseCurrentFile(vfz->uz); + unzOpenCurrentFile(vfz->uz); vf->read(vf, vfz->buffer, size); - unzCloseCurrentFile(vfz->z); - unzOpenCurrentFile(vfz->z); + unzCloseCurrentFile(vfz->uz); + unzOpenCurrentFile(vfz->uz); vf->seek(vf, pos, SEEK_SET); vfz->bufferSize = size;@@ -568,7 +653,10 @@ }
bool _vdzClose(struct VDir* vd) { struct VDirZip* vdz = (struct VDirZip*) vd; - if (unzClose(vdz->z) < 0) { + if (vdz->uz && unzClose(vdz->uz) < 0) { + return false; + } + if (vdz->z && zipClose(vdz->z, NULL) < 0) { return false; } free(vdz);@@ -577,20 +665,20 @@ }
void _vdzRewind(struct VDir* vd) { struct VDirZip* vdz = (struct VDirZip*) vd; - vdz->atStart = unzGoToFirstFile(vdz->z) == UNZ_OK; + vdz->atStart = unzGoToFirstFile(vdz->uz) == UNZ_OK; } struct VDirEntry* _vdzListNext(struct VDir* vd) { struct VDirZip* vdz = (struct VDirZip*) vd; if (!vdz->atStart) { - if (unzGoToNextFile(vdz->z) == UNZ_END_OF_LIST_OF_FILE) { + if (unzGoToNextFile(vdz->uz) == UNZ_END_OF_LIST_OF_FILE) { return 0; } } else { vdz->atStart = false; } unz_file_info64 info; - int status = unzGetCurrentFileInfo64(vdz->z, &info, vdz->dirent.name, sizeof(vdz->dirent.name), 0, 0, 0, 0); + int status = unzGetCurrentFileInfo64(vdz->uz, &info, vdz->dirent.name, sizeof(vdz->dirent.name), 0, 0, 0, 0); if (status < 0) { return 0; }@@ -602,26 +690,34 @@ struct VFile* _vdzOpenFile(struct VDir* vd, const char* path, int mode) {
UNUSED(mode); struct VDirZip* vdz = (struct VDirZip*) vd; - if ((mode & O_ACCMODE) != O_RDONLY) { - // minizip implementation only supports read + if ((mode & O_ACCMODE) == O_RDWR) { + // minizip implementation only supports read or write return 0; } - if (unzLocateFile(vdz->z, path, 0) != UNZ_OK) { - return 0; - } + unz_file_info64 info = {0}; + if (mode & O_RDONLY) { + if (unzLocateFile(vdz->uz, path, 0) != UNZ_OK) { + return 0; + } - if (unzOpenCurrentFile(vdz->z) < 0) { - return 0; - } + if (unzOpenCurrentFile(vdz->uz) < 0) { + return 0; + } - unz_file_info64 info; - int status = unzGetCurrentFileInfo64(vdz->z, &info, 0, 0, 0, 0, 0, 0); - if (status < 0) { - return 0; + int status = unzGetCurrentFileInfo64(vdz->uz, &info, 0, 0, 0, 0, 0, 0); + if (status < 0) { + return 0; + } + } + if (mode & O_WRONLY) { + if (zipOpenNewFileInZip(vdz->z, path, NULL, NULL, 0, NULL, 0, NULL, Z_DEFLATED, 3) < 0) { + return 0; + } } struct VFileZip* vfz = malloc(sizeof(struct VFileZip)); + vfz->uz = vdz->uz; vfz->z = vdz->z; vfz->buffer = 0; vfz->bufferSize = 0;