Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
hrydgard
GitHub Repository: hrydgard/ppsspp
Path: blob/master/Common/File/VFS/ZipFileReader.cpp
3187 views
1
#include <cctype>
2
#include <set>
3
#include <algorithm> // for sort
4
#include <cstdio>
5
#include <cstring>
6
7
#ifdef SHARED_LIBZIP
8
#include <zip.h>
9
#else
10
#include "ext/libzip/zip.h"
11
#endif
12
13
#include "Common/Common.h"
14
#include "Common/Log.h"
15
#include "Common/File/VFS/ZipFileReader.h"
16
#include "Common/StringUtils.h"
17
18
ZipFileReader *ZipFileReader::Create(const Path &zipFile, const char *inZipPath, bool logErrors) {
19
int error = 0;
20
zip *zip_file;
21
if (zipFile.Type() == PathType::CONTENT_URI) {
22
int fd = File::OpenFD(zipFile, File::OPEN_READ);
23
if (!fd) {
24
if (logErrors) {
25
ERROR_LOG(Log::IO, "Failed to open FD for '%s' as zip file", zipFile.c_str());
26
}
27
return nullptr;
28
}
29
zip_file = zip_fdopen(fd, 0, &error);
30
} else {
31
zip_file = zip_open(zipFile.c_str(), 0, &error);
32
}
33
34
if (!zip_file) {
35
if (logErrors) {
36
ERROR_LOG(Log::IO, "Failed to open %s as a zip file", zipFile.c_str());
37
}
38
return nullptr;
39
}
40
41
// The inZipPath is supposed to be a folder, and internally in this class, we suffix
42
// folder paths with '/', matching how the zip library works.
43
std::string path = inZipPath;
44
if (!path.empty() && path.back() != '/') {
45
path.push_back('/');
46
}
47
return new ZipFileReader(zip_file, zipFile, path);
48
}
49
50
ZipFileReader::~ZipFileReader() {
51
std::lock_guard<std::mutex> guard(lock_);
52
zip_close(zip_file_);
53
}
54
55
uint8_t *ZipFileReader::ReadFile(const char *path, size_t *size) {
56
std::string temp_path = inZipPath_ + path;
57
58
std::lock_guard<std::mutex> guard(lock_);
59
// Figure out the file size first.
60
struct zip_stat zstat;
61
int retval = zip_stat(zip_file_, temp_path.c_str(), ZIP_FL_NOCASE | ZIP_FL_UNCHANGED, &zstat);
62
if (retval != 0) {
63
ERROR_LOG(Log::IO, "Error opening %s from ZIP", temp_path.c_str());
64
return 0;
65
}
66
zip_file *file = zip_fopen_index(zip_file_, zstat.index, ZIP_FL_NOCASE | ZIP_FL_UNCHANGED);
67
if (!file) {
68
ERROR_LOG(Log::IO, "Error opening %s from ZIP", temp_path.c_str());
69
return 0;
70
}
71
uint8_t *contents = new uint8_t[zstat.size + 1];
72
zip_fread(file, contents, zstat.size);
73
zip_fclose(file);
74
contents[zstat.size] = 0;
75
76
*size = zstat.size;
77
return contents;
78
}
79
80
bool ZipFileReader::GetFileListing(const char *orig_path, std::vector<File::FileInfo> *listing, const char *filter = 0) {
81
std::string path = std::string(inZipPath_) + orig_path;
82
if (!path.empty() && path.back() != '/') {
83
path.push_back('/');
84
}
85
86
std::set<std::string> filters;
87
std::string tmp;
88
if (filter) {
89
while (*filter) {
90
if (*filter == ':') {
91
filters.emplace("." + tmp);
92
tmp.clear();
93
} else {
94
tmp.push_back(*filter);
95
}
96
filter++;
97
}
98
}
99
100
if (tmp.size())
101
filters.emplace("." + tmp);
102
103
// We just loop through the whole ZIP file and deduce what files are in this directory, and what subdirectories there are.
104
std::set<std::string> files;
105
std::set<std::string> directories;
106
bool success = GetZipListings(path, files, directories);
107
if (!success) {
108
// This means that no file prefix matched the path.
109
return false;
110
}
111
112
listing->clear();
113
114
// INFO_LOG(Log::IO, "Zip: Listing '%s'", orig_path);
115
116
const std::string relativePath = path.substr(inZipPath_.size());
117
118
listing->reserve(directories.size() + files.size());
119
for (const auto &dir : directories) {
120
File::FileInfo info;
121
info.name = dir;
122
123
// Remove the "inzip" part of the fullname.
124
info.fullName = Path(relativePath + dir);
125
info.exists = true;
126
info.isWritable = false;
127
info.isDirectory = true;
128
// INFO_LOG(Log::IO, "Found file: %s (%s)", info.name.c_str(), info.fullName.c_str());
129
listing->push_back(info);
130
}
131
132
for (const auto &fiter : files) {
133
File::FileInfo info;
134
info.name = fiter;
135
info.fullName = Path(relativePath + fiter);
136
info.exists = true;
137
info.isWritable = false;
138
info.isDirectory = false;
139
std::string ext = info.fullName.GetFileExtension();
140
if (filter) {
141
if (filters.find(ext) == filters.end()) {
142
continue;
143
}
144
}
145
// INFO_LOG(Log::IO, "Found dir: %s (%s)", info.name.c_str(), info.fullName.c_str());
146
listing->push_back(info);
147
}
148
149
std::sort(listing->begin(), listing->end());
150
return true;
151
}
152
153
// path here is from the root, so inZipPath needs to already be added.
154
bool ZipFileReader::GetZipListings(const std::string &path, std::set<std::string> &files, std::set<std::string> &directories) {
155
_dbg_assert_(path.empty() || path.back() == '/');
156
157
std::lock_guard<std::mutex> guard(lock_);
158
int numFiles = zip_get_num_files(zip_file_);
159
bool anyPrefixMatched = false;
160
for (int i = 0; i < numFiles; i++) {
161
const char* name = zip_get_name(zip_file_, i, 0);
162
if (!name)
163
continue; // shouldn't happen, I think
164
if (startsWith(name, path)) {
165
if (strlen(name) == path.size()) {
166
// Don't want to return the same folder.
167
continue;
168
}
169
const char *slashPos = strchr(name + path.size(), '/');
170
if (slashPos != 0) {
171
anyPrefixMatched = true;
172
// A directory. Let's pick off the only part we care about.
173
size_t offset = path.size();
174
std::string dirName = std::string(name + offset, slashPos - (name + offset));
175
// We might get a lot of these if the tree is deep. The std::set deduplicates.
176
directories.insert(dirName);
177
} else {
178
anyPrefixMatched = true;
179
// It's a file.
180
const char *fn = name + path.size();
181
files.emplace(fn);
182
}
183
}
184
}
185
return anyPrefixMatched;
186
}
187
188
bool ZipFileReader::GetFileInfo(const char *path, File::FileInfo *info) {
189
struct zip_stat zstat;
190
std::string temp_path = inZipPath_ + path;
191
192
// Clear some things to start.
193
info->isDirectory = false;
194
info->isWritable = false;
195
info->size = 0;
196
197
{
198
std::lock_guard<std::mutex> guard(lock_);
199
if (0 != zip_stat(zip_file_, temp_path.c_str(), ZIP_FL_NOCASE | ZIP_FL_UNCHANGED, &zstat)) {
200
// ZIP files do not have real directories, so we'll end up here if we
201
// try to stat one. For now that's fine.
202
info->exists = false;
203
return false;
204
}
205
}
206
207
// Zips usually don't contain directory entries, but they may.
208
if ((zstat.valid & ZIP_STAT_NAME) != 0 && zstat.name) {
209
info->isDirectory = zstat.name[strlen(zstat.name) - 1] == '/';
210
}
211
if ((zstat.valid & ZIP_STAT_SIZE) != 0) {
212
info->size = zstat.size;
213
}
214
215
info->fullName = Path(path);
216
info->exists = true;
217
return true;
218
}
219
220
class ZipFileReaderFileReference : public VFSFileReference {
221
public:
222
int zi;
223
};
224
225
class ZipFileReaderOpenFile : public VFSOpenFile {
226
public:
227
~ZipFileReaderOpenFile() {
228
// Needs to be closed properly and unlocked.
229
_dbg_assert_(zf == nullptr);
230
}
231
ZipFileReaderFileReference *reference;
232
zip_file_t *zf = nullptr;
233
};
234
235
VFSFileReference *ZipFileReader::GetFile(const char *path) {
236
int zi = zip_name_locate(zip_file_, path, ZIP_FL_NOCASE); // this is EXPENSIVE
237
if (zi < 0) {
238
// Not found.
239
return nullptr;
240
}
241
ZipFileReaderFileReference *ref = new ZipFileReaderFileReference();
242
ref->zi = zi;
243
return ref;
244
}
245
246
bool ZipFileReader::GetFileInfo(VFSFileReference *vfsReference, File::FileInfo *fileInfo) {
247
ZipFileReaderFileReference *reference = (ZipFileReaderFileReference *)vfsReference;
248
// If you crash here, you called this while having the lock held by having the file open.
249
// Don't do that, check the info before you open the file.
250
zip_stat_t zstat;
251
if (zip_stat_index(zip_file_, reference->zi, 0, &zstat) != 0)
252
return false;
253
*fileInfo = File::FileInfo{};
254
fileInfo->size = 0;
255
if (zstat.valid & ZIP_STAT_SIZE)
256
fileInfo->size = zstat.size;
257
return zstat.size;
258
}
259
260
void ZipFileReader::ReleaseFile(VFSFileReference *vfsReference) {
261
ZipFileReaderFileReference *reference = (ZipFileReaderFileReference *)vfsReference;
262
// Don't do anything other than deleting it.
263
delete reference;
264
}
265
266
VFSOpenFile *ZipFileReader::OpenFileForRead(VFSFileReference *vfsReference, size_t *size) {
267
ZipFileReaderFileReference *reference = (ZipFileReaderFileReference *)vfsReference;
268
ZipFileReaderOpenFile *openFile = new ZipFileReaderOpenFile();
269
openFile->reference = reference;
270
*size = 0;
271
// We only allow one file to be open for read concurrently. It's possible that this can be improved,
272
// especially if we only access by index like this.
273
lock_.lock();
274
zip_stat_t zstat;
275
if (zip_stat_index(zip_file_, reference->zi, 0, &zstat) != 0) {
276
lock_.unlock();
277
delete openFile;
278
return nullptr;
279
}
280
281
openFile->zf = zip_fopen_index(zip_file_, reference->zi, 0);
282
if (!openFile->zf) {
283
WARN_LOG(Log::G3D, "File with index %d not found in zip", reference->zi);
284
lock_.unlock();
285
delete openFile;
286
return nullptr;
287
}
288
289
*size = zstat.size;
290
// Intentionally leaving the mutex locked, will be closed in CloseFile.
291
return openFile;
292
}
293
294
void ZipFileReader::Rewind(VFSOpenFile *vfsOpenFile) {
295
ZipFileReaderOpenFile *file = (ZipFileReaderOpenFile *)vfsOpenFile;
296
_assert_(file);
297
// Unless the zip file is compressed, can't seek directly, so we re-open.
298
// This version of libzip doesn't even have zip_file_is_seekable(), should probably upgrade.
299
zip_fclose(file->zf);
300
file->zf = zip_fopen_index(zip_file_, file->reference->zi, 0);
301
_dbg_assert_(file->zf != nullptr);
302
}
303
304
size_t ZipFileReader::Read(VFSOpenFile *vfsOpenFile, void *buffer, size_t length) {
305
ZipFileReaderOpenFile *file = (ZipFileReaderOpenFile *)vfsOpenFile;
306
_assert_(file);
307
_dbg_assert_(file->zf != nullptr);
308
return zip_fread(file->zf, buffer, length);
309
}
310
311
void ZipFileReader::CloseFile(VFSOpenFile *vfsOpenFile) {
312
ZipFileReaderOpenFile *file = (ZipFileReaderOpenFile *)vfsOpenFile;
313
_assert_(file);
314
_dbg_assert_(file->zf != nullptr);
315
zip_fclose(file->zf);
316
file->zf = nullptr;
317
vfsOpenFile = nullptr;
318
lock_.unlock();
319
delete file;
320
}
321
322
bool ReadSingleFileFromZip(Path zipFile, const char *path, std::string *data, std::mutex *mutex) {
323
zip *zip = nullptr;
324
int error = 0;
325
if (zipFile.Type() == PathType::CONTENT_URI) {
326
int fd = File::OpenFD(zipFile, File::OPEN_READ);
327
if (!fd) {
328
return false;
329
}
330
zip = zip_fdopen(fd, 0, &error);
331
} else {
332
zip = zip_open(zipFile.c_str(), 0, &error);
333
}
334
335
if (!zip) {
336
return false;
337
}
338
339
struct zip_stat zstat;
340
if (zip_stat(zip, path, ZIP_FL_NOCASE | ZIP_FL_UNCHANGED, &zstat) != 0) {
341
zip_close(zip);
342
return false;
343
}
344
zip_file *file = zip_fopen_index(zip, zstat.index, ZIP_FL_UNCHANGED);
345
if (!file) {
346
zip_close(zip);
347
return false;
348
}
349
if (mutex) {
350
mutex->lock();
351
}
352
data->resize(zstat.size);
353
if (zip_fread(file, data->data(), zstat.size) != zstat.size) {
354
if (mutex) {
355
mutex->unlock();
356
}
357
data->resize(0);
358
zip_fclose(file);
359
zip_close(zip);
360
return false;
361
}
362
if (mutex) {
363
mutex->unlock();
364
}
365
zip_fclose(file);
366
zip_close(zip);
367
return true;
368
}
369
370