VFS: Initial write support for zip files

This commit is contained in:
Vicki Pfau 2020-12-08 19:30:30 -08:00
parent d802ffa745
commit 67f8197493
2 changed files with 163 additions and 67 deletions

View File

@ -101,12 +101,12 @@ struct VDir* VDirOpenArchive(const char* path) {
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;

View File

@ -5,6 +5,7 @@
* 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 VDirEntryZip {
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 @@ enum {
};
#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 @@ static voidpf _vfmzOpen(voidpf opaque, const char* filename, int mode) {
}
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 @@ static uLong _vfmzRead(voidpf opaque, voidpf stream, void* buf, uLong size) {
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 @@ struct VDir* VDirOpenZip(const char* path, int 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 @@ struct VDir* VDirOpenZip(const char* path, int flags) {
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 @@ struct VDir* VDirOpenZip(const char* path, int flags) {
#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 @@ ssize_t _vfzRead(struct VFile* vf, void* buffer, size_t 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;
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 @@ struct VFile* _vdzOpenFile(struct VDir* vd, const char* path, int 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;
if (!vdz->write) {
return 0;
}
} else {
if (zip_stat(vdz->z, path, 0, &s) < 0) {
return 0;
}
zf = zip_fopen(vdz->z, path, 0);
if (!zf) {
return 0;
}
}
struct zip_stat s;
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;
}
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 @@ static enum VFSType _vdezType(struct VDirEntry* vde) {
#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 @@ bool _vfzClose(struct VFile* vf) {
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 @@ off_t _vfzSeek(struct VFile* vf, off_t offset, int whence) {
}
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 @@ off_t _vfzSeek(struct VFile* vf, off_t offset, int whence) {
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 @@ void* _vfzMap(struct VFile* vf, size_t size, int flags) {
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 @@ ssize_t _vfzSize(struct VFile* vf) {
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 @@ bool _vdzClose(struct VDir* vd) {
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;