src/util/vfs/vfs-zip.c (view raw)
1/* Copyright (c) 2013-2014 Jeffrey Pfau
2 *
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6#include <mgba-util/vfs.h>
7
8#include <mgba-util/string.h>
9
10#ifdef USE_LIBZIP
11#include <zip.h>
12
13struct VDirEntryZip {
14 struct VDirEntry d;
15 struct zip* z;
16 zip_int64_t index;
17};
18
19struct VDirZip {
20 struct VDir d;
21 struct zip* z;
22 struct VDirEntryZip dirent;
23};
24
25struct VFileZip {
26 struct VFile d;
27 struct zip_file* zf;
28 void* buffer;
29 size_t offset;
30 size_t bufferSize;
31 size_t readSize;
32 size_t fileSize;
33};
34
35enum {
36 BLOCK_SIZE = 1024
37};
38#else
39#ifdef USE_MINIZIP
40#include <minizip/unzip.h>
41#else
42#include "third-party/zlib/contrib/minizip/unzip.h"
43#endif
44#include <mgba-util/memory.h>
45
46struct VDirEntryZip {
47 struct VDirEntry d;
48 char name[PATH_MAX];
49 size_t fileSize;
50 unzFile z;
51};
52
53struct VDirZip {
54 struct VDir d;
55 unzFile z;
56 struct VDirEntryZip dirent;
57 bool atStart;
58};
59
60struct VFileZip {
61 struct VFile d;
62 unzFile z;
63 void* buffer;
64 size_t bufferSize;
65 size_t fileSize;
66};
67#endif
68
69static bool _vfzClose(struct VFile* vf);
70static off_t _vfzSeek(struct VFile* vf, off_t offset, int whence);
71static ssize_t _vfzRead(struct VFile* vf, void* buffer, size_t size);
72static ssize_t _vfzWrite(struct VFile* vf, const void* buffer, size_t size);
73static void* _vfzMap(struct VFile* vf, size_t size, int flags);
74static void _vfzUnmap(struct VFile* vf, void* memory, size_t size);
75static void _vfzTruncate(struct VFile* vf, size_t size);
76static ssize_t _vfzSize(struct VFile* vf);
77static bool _vfzSync(struct VFile* vf, void* buffer, size_t size);
78
79static bool _vdzClose(struct VDir* vd);
80static void _vdzRewind(struct VDir* vd);
81static struct VDirEntry* _vdzListNext(struct VDir* vd);
82static struct VFile* _vdzOpenFile(struct VDir* vd, const char* path, int mode);
83static struct VDir* _vdzOpenDir(struct VDir* vd, const char* path);
84static bool _vdzDeleteFile(struct VDir* vd, const char* path);
85
86static const char* _vdezName(struct VDirEntry* vde);
87static enum VFSType _vdezType(struct VDirEntry* vde);
88
89#ifndef USE_LIBZIP
90static voidpf _vfmzOpen(voidpf opaque, const char* filename, int mode) {
91 UNUSED(opaque);
92 int flags = 0;
93 switch (mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER) {
94 case ZLIB_FILEFUNC_MODE_READ:
95 flags = O_RDONLY;
96 break;
97 case ZLIB_FILEFUNC_MODE_WRITE:
98 flags = O_WRONLY;
99 break;
100 case ZLIB_FILEFUNC_MODE_READ | ZLIB_FILEFUNC_MODE_WRITE:
101 flags = O_RDWR;
102 break;
103 }
104 if (mode & ZLIB_FILEFUNC_MODE_CREATE) {
105 flags |= O_CREAT;
106 }
107 return VFileOpen(filename, flags);
108}
109
110static uLong _vfmzRead(voidpf opaque, voidpf stream, void* buf, uLong size) {
111 UNUSED(opaque);
112 struct VFile* vf = stream;
113 ssize_t r = vf->read(vf, buf, size);
114 if (r < 0) {
115 return 0;
116 }
117 return r;
118}
119
120int _vfmzClose(voidpf opaque, voidpf stream) {
121 UNUSED(opaque);
122 struct VFile* vf = stream;
123 return vf->close(vf);
124}
125
126int _vfmzError(voidpf opaque, voidpf stream) {
127 UNUSED(opaque);
128 struct VFile* vf = stream;
129 return vf->seek(vf, 0, SEEK_CUR) < 0;
130}
131
132long _vfmzTell(voidpf opaque, voidpf stream) {
133 UNUSED(opaque);
134 struct VFile* vf = stream;
135 return vf->seek(vf, 0, SEEK_CUR);
136}
137
138long _vfmzSeek(voidpf opaque, voidpf stream, uLong offset, int origin) {
139 UNUSED(opaque);
140 struct VFile* vf = stream;
141 return vf->seek(vf, offset, origin) < 0;
142}
143#endif
144
145struct VDir* VDirOpenZip(const char* path, int flags) {
146#ifndef USE_LIBZIP
147 UNUSED(flags);
148 zlib_filefunc_def ops = {
149 .zopen_file = _vfmzOpen,
150 .zread_file = _vfmzRead,
151 .zwrite_file = 0,
152 .ztell_file = _vfmzTell,
153 .zseek_file = _vfmzSeek,
154 .zclose_file = _vfmzClose,
155 .zerror_file = _vfmzError,
156 .opaque = 0
157 };
158 unzFile z = unzOpen2(path, &ops);
159 if (!z) {
160 return 0;
161 }
162#else
163 int zflags = 0;
164 if (flags & O_CREAT) {
165 zflags |= ZIP_CREATE;
166 }
167 if (flags & O_EXCL) {
168 zflags |= ZIP_EXCL;
169 }
170
171 struct zip* z = zip_open(path, zflags, 0);
172 if (!z) {
173 return 0;
174 }
175#endif
176 struct VDirZip* vd = malloc(sizeof(struct VDirZip));
177
178 vd->d.close = _vdzClose;
179 vd->d.rewind = _vdzRewind;
180 vd->d.listNext = _vdzListNext;
181 vd->d.openFile = _vdzOpenFile;
182 vd->d.openDir = _vdzOpenDir;
183 vd->d.deleteFile = _vdzDeleteFile;
184 vd->z = z;
185
186#ifndef USE_LIBZIP
187 vd->atStart = true;
188#endif
189
190 vd->dirent.d.name = _vdezName;
191 vd->dirent.d.type = _vdezType;
192#ifdef USE_LIBZIP
193 vd->dirent.index = -1;
194#endif
195 vd->dirent.z = z;
196
197 return &vd->d;
198}
199
200#ifdef USE_LIBZIP
201bool _vfzClose(struct VFile* vf) {
202 struct VFileZip* vfz = (struct VFileZip*) vf;
203 if (zip_fclose(vfz->zf) < 0) {
204 return false;
205 }
206 free(vfz->buffer);
207 free(vfz);
208 return true;
209}
210
211off_t _vfzSeek(struct VFile* vf, off_t offset, int whence) {
212 struct VFileZip* vfz = (struct VFileZip*) vf;
213
214 size_t position;
215 switch (whence) {
216 case SEEK_SET:
217 position = offset;
218 break;
219 case SEEK_CUR:
220 if (offset < 0 && ((vfz->offset < (size_t) -offset) || (offset == INT_MIN))) {
221 return -1;
222 }
223 position = vfz->offset + offset;
224 break;
225 case SEEK_END:
226 if (offset < 0 && ((vfz->fileSize < (size_t) -offset) || (offset == INT_MIN))) {
227 return -1;
228 }
229 position = vfz->fileSize + offset;
230 break;
231 default:
232 return -1;
233 }
234
235 if (position <= vfz->offset) {
236 vfz->offset = position;
237 return position;
238 }
239
240 if (position <= vfz->fileSize) {
241 ssize_t read = vf->read(vf, 0, position - vfz->offset);
242 if (read < 0) {
243 return -1;
244 }
245 return vfz->offset;
246 }
247
248 return -1;
249}
250
251ssize_t _vfzRead(struct VFile* vf, void* buffer, size_t size) {
252 struct VFileZip* vfz = (struct VFileZip*) vf;
253
254 size_t bytesRead = 0;
255 if (!vfz->buffer) {
256 vfz->bufferSize = BLOCK_SIZE;
257 vfz->buffer = malloc(BLOCK_SIZE);
258 }
259
260 while (bytesRead < size) {
261 if (vfz->offset < vfz->readSize) {
262 size_t diff = vfz->readSize - vfz->offset;
263 void* start = &((uint8_t*) vfz->buffer)[vfz->offset];
264 if (diff > size - bytesRead) {
265 diff = size - bytesRead;
266 }
267 if (buffer) {
268 void* bufferOffset = &((uint8_t*) buffer)[bytesRead];
269 memcpy(bufferOffset, start, diff);
270 }
271 vfz->offset += diff;
272 bytesRead += diff;
273 if (diff == size) {
274 break;
275 }
276 }
277 // offset == readSize
278 if (vfz->readSize == vfz->bufferSize) {
279 vfz->bufferSize *= 2;
280 if (vfz->bufferSize > vfz->fileSize) {
281 vfz->bufferSize = vfz->fileSize;
282 }
283 vfz->buffer = realloc(vfz->buffer, vfz->bufferSize);
284 }
285 if (vfz->readSize < vfz->bufferSize) {
286 void* start = &((uint8_t*) vfz->buffer)[vfz->readSize];
287 size_t toRead = vfz->bufferSize - vfz->readSize;
288 if (toRead > BLOCK_SIZE) {
289 toRead = BLOCK_SIZE;
290 }
291 ssize_t zipRead = zip_fread(vfz->zf, start, toRead);
292 if (zipRead < 0) {
293 if (bytesRead == 0) {
294 return -1;
295 }
296 break;
297 }
298 if (zipRead == 0) {
299 break;
300 }
301 vfz->readSize += zipRead;
302 } else {
303 break;
304 }
305 }
306 return bytesRead;
307}
308
309ssize_t _vfzWrite(struct VFile* vf, const void* buffer, size_t size) {
310 // TODO
311 UNUSED(vf);
312 UNUSED(buffer);
313 UNUSED(size);
314 return -1;
315}
316
317void* _vfzMap(struct VFile* vf, size_t size, int flags) {
318 struct VFileZip* vfz = (struct VFileZip*) vf;
319
320 UNUSED(flags);
321 if (size > vfz->readSize) {
322 vf->read(vf, 0, size - vfz->readSize);
323 }
324 return vfz->buffer;
325}
326
327void _vfzUnmap(struct VFile* vf, void* memory, size_t size) {
328 UNUSED(vf);
329 UNUSED(memory);
330 UNUSED(size);
331}
332
333void _vfzTruncate(struct VFile* vf, size_t size) {
334 // TODO
335 UNUSED(vf);
336 UNUSED(size);
337}
338
339ssize_t _vfzSize(struct VFile* vf) {
340 struct VFileZip* vfz = (struct VFileZip*) vf;
341 return vfz->fileSize;
342}
343
344bool _vdzClose(struct VDir* vd) {
345 struct VDirZip* vdz = (struct VDirZip*) vd;
346 if (zip_close(vdz->z) < 0) {
347 return false;
348 }
349 free(vdz);
350 return true;
351}
352
353void _vdzRewind(struct VDir* vd) {
354 struct VDirZip* vdz = (struct VDirZip*) vd;
355 vdz->dirent.index = -1;
356}
357
358struct VDirEntry* _vdzListNext(struct VDir* vd) {
359 struct VDirZip* vdz = (struct VDirZip*) vd;
360 zip_int64_t maxIndex = zip_get_num_entries(vdz->z, 0);
361 if (maxIndex <= vdz->dirent.index + 1) {
362 return 0;
363 }
364 ++vdz->dirent.index;
365 return &vdz->dirent.d;
366}
367
368struct VFile* _vdzOpenFile(struct VDir* vd, const char* path, int mode) {
369 UNUSED(mode);
370 // TODO: support truncating, appending and creating, and write
371 struct VDirZip* vdz = (struct VDirZip*) vd;
372
373 if ((mode & O_RDWR) == O_RDWR) {
374 // libzip doesn't allow for random access, so read/write is impossible without
375 // reading the entire file first. This approach will be supported eventually.
376 return 0;
377 }
378
379 if (mode & O_WRONLY) {
380 // Write support is not yet implemented.
381 return 0;
382 }
383
384 struct zip_stat s;
385 if (zip_stat(vdz->z, path, 0, &s) < 0) {
386 return 0;
387 }
388
389 struct zip_file* zf = zip_fopen(vdz->z, path, 0);
390 if (!zf) {
391 return 0;
392 }
393
394 struct VFileZip* vfz = malloc(sizeof(struct VFileZip));
395 vfz->zf = zf;
396 vfz->buffer = 0;
397 vfz->offset = 0;
398 vfz->bufferSize = 0;
399 vfz->readSize = 0;
400 vfz->fileSize = s.size;
401
402 vfz->d.close = _vfzClose;
403 vfz->d.seek = _vfzSeek;
404 vfz->d.read = _vfzRead;
405 vfz->d.readline = VFileReadline;
406 vfz->d.write = _vfzWrite;
407 vfz->d.map = _vfzMap;
408 vfz->d.unmap = _vfzUnmap;
409 vfz->d.truncate = _vfzTruncate;
410 vfz->d.size = _vfzSize;
411 vfz->d.sync = _vfzSync;
412
413 return &vfz->d;
414}
415
416struct VDir* _vdzOpenDir(struct VDir* vd, const char* path) {
417 UNUSED(vd);
418 UNUSED(path);
419 return 0;
420}
421
422bool _vdzDeleteFile(struct VDir* vd, const char* path) {
423 UNUSED(vd);
424 UNUSED(path);
425 // TODO
426 return false;
427}
428
429bool _vfzSync(struct VFile* vf, void* memory, size_t size) {
430 UNUSED(vf);
431 UNUSED(memory);
432 UNUSED(size);
433 return false;
434}
435
436const char* _vdezName(struct VDirEntry* vde) {
437 struct VDirEntryZip* vdez = (struct VDirEntryZip*) vde;
438 struct zip_stat s;
439 if (zip_stat_index(vdez->z, vdez->index, 0, &s) < 0) {
440 return 0;
441 }
442 return s.name;
443}
444
445static enum VFSType _vdezType(struct VDirEntry* vde) {
446 if (endswith(vde->name(vde), "/")) {
447 return VFS_DIRECTORY;
448 }
449 return VFS_FILE;
450}
451#else
452bool _vfzClose(struct VFile* vf) {
453 struct VFileZip* vfz = (struct VFileZip*) vf;
454 unzCloseCurrentFile(vfz->z);
455 if (vfz->buffer) {
456 mappedMemoryFree(vfz->buffer, vfz->bufferSize);
457 }
458 free(vfz);
459 return true;
460}
461
462off_t _vfzSeek(struct VFile* vf, off_t offset, int whence) {
463 struct VFileZip* vfz = (struct VFileZip*) vf;
464
465 int64_t currentPos = unztell64(vfz->z);
466 int64_t pos;
467 switch (whence) {
468 case SEEK_SET:
469 pos = 0;
470 break;
471 case SEEK_CUR:
472 pos = unztell64(vfz->z);
473 break;
474 case SEEK_END:
475 pos = vfz->fileSize;
476 break;
477 default:
478 return -1;
479 }
480
481 if (pos < 0 || pos + offset < 0) {
482 return -1;
483 }
484 pos += offset;
485 if (currentPos > pos) {
486 unzCloseCurrentFile(vfz->z);
487 unzOpenCurrentFile(vfz->z);
488 currentPos = 0;
489 }
490 while (currentPos < pos) {
491 char tempBuf[1024];
492 ssize_t toRead = sizeof(tempBuf);
493 if (toRead > pos - currentPos) {
494 toRead = pos - currentPos;
495 }
496 ssize_t read = vf->read(vf, tempBuf, toRead);
497 if (read < toRead) {
498 return -1;
499 }
500 currentPos += read;
501 }
502
503 return unztell64(vfz->z);
504}
505
506ssize_t _vfzRead(struct VFile* vf, void* buffer, size_t size) {
507 struct VFileZip* vfz = (struct VFileZip*) vf;
508 return unzReadCurrentFile(vfz->z, buffer, size);
509}
510
511ssize_t _vfzWrite(struct VFile* vf, const void* buffer, size_t size) {
512 // TODO
513 UNUSED(vf);
514 UNUSED(buffer);
515 UNUSED(size);
516 return -1;
517}
518
519void* _vfzMap(struct VFile* vf, size_t size, int flags) {
520 struct VFileZip* vfz = (struct VFileZip*) vf;
521
522 // TODO
523 UNUSED(flags);
524
525 off_t pos = vf->seek(vf, 0, SEEK_CUR);
526 if (pos < 0) {
527 return 0;
528 }
529
530 vfz->buffer = anonymousMemoryMap(size);
531 if (!vfz->buffer) {
532 return 0;
533 }
534
535 unzCloseCurrentFile(vfz->z);
536 unzOpenCurrentFile(vfz->z);
537 vf->read(vf, vfz->buffer, size);
538 unzCloseCurrentFile(vfz->z);
539 unzOpenCurrentFile(vfz->z);
540 vf->seek(vf, pos, SEEK_SET);
541
542 vfz->bufferSize = size;
543
544 return vfz->buffer;
545}
546
547void _vfzUnmap(struct VFile* vf, void* memory, size_t size) {
548 struct VFileZip* vfz = (struct VFileZip*) vf;
549
550 if (memory != vfz->buffer) {
551 return;
552 }
553
554 mappedMemoryFree(vfz->buffer, size);
555 vfz->buffer = 0;
556}
557
558void _vfzTruncate(struct VFile* vf, size_t size) {
559 // TODO
560 UNUSED(vf);
561 UNUSED(size);
562}
563
564ssize_t _vfzSize(struct VFile* vf) {
565 struct VFileZip* vfz = (struct VFileZip*) vf;
566 return vfz->fileSize;
567}
568
569bool _vdzClose(struct VDir* vd) {
570 struct VDirZip* vdz = (struct VDirZip*) vd;
571 if (unzClose(vdz->z) < 0) {
572 return false;
573 }
574 free(vdz);
575 return true;
576}
577
578void _vdzRewind(struct VDir* vd) {
579 struct VDirZip* vdz = (struct VDirZip*) vd;
580 vdz->atStart = unzGoToFirstFile(vdz->z) == UNZ_OK;
581}
582
583struct VDirEntry* _vdzListNext(struct VDir* vd) {
584 struct VDirZip* vdz = (struct VDirZip*) vd;
585 if (!vdz->atStart) {
586 if (unzGoToNextFile(vdz->z) == UNZ_END_OF_LIST_OF_FILE) {
587 return 0;
588 }
589 } else {
590 vdz->atStart = false;
591 }
592 unz_file_info64 info;
593 int status = unzGetCurrentFileInfo64(vdz->z, &info, vdz->dirent.name, sizeof(vdz->dirent.name), 0, 0, 0, 0);
594 if (status < 0) {
595 return 0;
596 }
597 vdz->dirent.fileSize = info.uncompressed_size;
598 return &vdz->dirent.d;
599}
600
601struct VFile* _vdzOpenFile(struct VDir* vd, const char* path, int mode) {
602 UNUSED(mode);
603 struct VDirZip* vdz = (struct VDirZip*) vd;
604
605 if ((mode & O_ACCMODE) != O_RDONLY) {
606 // minizip implementation only supports read
607 return 0;
608 }
609
610 if (unzLocateFile(vdz->z, path, 0) != UNZ_OK) {
611 return 0;
612 }
613
614 if (unzOpenCurrentFile(vdz->z) < 0) {
615 return 0;
616 }
617
618 unz_file_info64 info;
619 int status = unzGetCurrentFileInfo64(vdz->z, &info, 0, 0, 0, 0, 0, 0);
620 if (status < 0) {
621 return 0;
622 }
623
624 struct VFileZip* vfz = malloc(sizeof(struct VFileZip));
625 vfz->z = vdz->z;
626 vfz->buffer = 0;
627 vfz->bufferSize = 0;
628 vfz->fileSize = info.uncompressed_size;
629
630 vfz->d.close = _vfzClose;
631 vfz->d.seek = _vfzSeek;
632 vfz->d.read = _vfzRead;
633 vfz->d.readline = VFileReadline;
634 vfz->d.write = _vfzWrite;
635 vfz->d.map = _vfzMap;
636 vfz->d.unmap = _vfzUnmap;
637 vfz->d.truncate = _vfzTruncate;
638 vfz->d.size = _vfzSize;
639 vfz->d.sync = _vfzSync;
640
641 return &vfz->d;
642}
643
644struct VDir* _vdzOpenDir(struct VDir* vd, const char* path) {
645 UNUSED(vd);
646 UNUSED(path);
647 return 0;
648}
649
650bool _vdzDeleteFile(struct VDir* vd, const char* path) {
651 UNUSED(vd);
652 UNUSED(path);
653 // TODO
654 return false;
655}
656
657bool _vfzSync(struct VFile* vf, void* memory, size_t size) {
658 UNUSED(vf);
659 UNUSED(memory);
660 UNUSED(size);
661 return false;
662}
663
664const char* _vdezName(struct VDirEntry* vde) {
665 struct VDirEntryZip* vdez = (struct VDirEntryZip*) vde;
666 return vdez->name;
667}
668
669static enum VFSType _vdezType(struct VDirEntry* vde) {
670 struct VDirEntryZip* vdez = (struct VDirEntryZip*) vde;
671 if (endswith(vdez->name, "/")) {
672 return VFS_DIRECTORY;
673 }
674 return VFS_FILE;
675}
676#endif