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