CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
hrydgard

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

GitHub Repository: hrydgard/ppsspp
Path: blob/master/Common/File/VFS/ZipFileReader.cpp
Views: 1401
1
#include <algorithm>
2
#include <ctype.h>
3
#include <set>
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, 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
zip_stat(zip_file_, temp_path.c_str(), ZIP_FL_NOCASE | ZIP_FL_UNCHANGED, &zstat);
62
zip_file *file = zip_fopen(zip_file_, temp_path.c_str(), ZIP_FL_NOCASE | ZIP_FL_UNCHANGED);
63
if (!file) {
64
ERROR_LOG(Log::IO, "Error opening %s from ZIP", temp_path.c_str());
65
return 0;
66
}
67
uint8_t *contents = new uint8_t[zstat.size + 1];
68
zip_fread(file, contents, zstat.size);
69
zip_fclose(file);
70
contents[zstat.size] = 0;
71
72
*size = zstat.size;
73
return contents;
74
}
75
76
bool ZipFileReader::GetFileListing(const char *orig_path, std::vector<File::FileInfo> *listing, const char *filter = 0) {
77
std::string path = std::string(inZipPath_) + orig_path;
78
if (!path.empty() && path.back() != '/') {
79
path.push_back('/');
80
}
81
82
std::set<std::string> filters;
83
std::string tmp;
84
if (filter) {
85
while (*filter) {
86
if (*filter == ':') {
87
filters.emplace("." + tmp);
88
tmp.clear();
89
} else {
90
tmp.push_back(*filter);
91
}
92
filter++;
93
}
94
}
95
96
if (tmp.size())
97
filters.emplace("." + tmp);
98
99
// We just loop through the whole ZIP file and deduce what files are in this directory, and what subdirectories there are.
100
std::set<std::string> files;
101
std::set<std::string> directories;
102
bool success = GetZipListings(path, files, directories);
103
if (!success) {
104
// This means that no file prefix matched the path.
105
return false;
106
}
107
108
listing->clear();
109
110
// INFO_LOG(Log::System, "Zip: Listing '%s'", orig_path);
111
112
listing->reserve(directories.size() + files.size());
113
for (const auto &dir : directories) {
114
File::FileInfo info;
115
info.name = dir;
116
117
// Remove the "inzip" part of the fullname.
118
std::string relativePath = std::string(path).substr(inZipPath_.size());
119
info.fullName = Path(relativePath + dir);
120
info.exists = true;
121
info.isWritable = false;
122
info.isDirectory = true;
123
// INFO_LOG(Log::System, "Found file: %s (%s)", info.name.c_str(), info.fullName.c_str());
124
listing->push_back(info);
125
}
126
127
for (const auto &fiter : files) {
128
std::string fpath = path;
129
File::FileInfo info;
130
info.name = fiter;
131
std::string relativePath = std::string(path).substr(inZipPath_.size());
132
info.fullName = Path(relativePath + fiter);
133
info.exists = true;
134
info.isWritable = false;
135
info.isDirectory = false;
136
std::string ext = info.fullName.GetFileExtension();
137
if (filter) {
138
if (filters.find(ext) == filters.end()) {
139
continue;
140
}
141
}
142
// INFO_LOG(Log::System, "Found dir: %s (%s)", info.name.c_str(), info.fullName.c_str());
143
listing->push_back(info);
144
}
145
146
std::sort(listing->begin(), listing->end());
147
return true;
148
}
149
150
// path here is from the root, so inZipPath needs to already be added.
151
bool ZipFileReader::GetZipListings(const std::string &path, std::set<std::string> &files, std::set<std::string> &directories) {
152
_dbg_assert_(path.empty() || path.back() == '/');
153
154
std::lock_guard<std::mutex> guard(lock_);
155
int numFiles = zip_get_num_files(zip_file_);
156
bool anyPrefixMatched = false;
157
for (int i = 0; i < numFiles; i++) {
158
const char* name = zip_get_name(zip_file_, i, 0);
159
if (!name)
160
continue; // shouldn't happen, I think
161
if (startsWith(name, path)) {
162
if (strlen(name) == path.size()) {
163
// Don't want to return the same folder.
164
continue;
165
}
166
const char *slashPos = strchr(name + path.size(), '/');
167
if (slashPos != 0) {
168
anyPrefixMatched = true;
169
// A directory. Let's pick off the only part we care about.
170
size_t offset = path.size();
171
std::string dirName = std::string(name + offset, slashPos - (name + offset));
172
// We might get a lot of these if the tree is deep. The std::set deduplicates.
173
directories.insert(dirName);
174
} else {
175
anyPrefixMatched = true;
176
// It's a file.
177
const char *fn = name + path.size();
178
files.emplace(fn);
179
}
180
}
181
}
182
return anyPrefixMatched;
183
}
184
185
bool ZipFileReader::GetFileInfo(const char *path, File::FileInfo *info) {
186
struct zip_stat zstat;
187
std::string temp_path = inZipPath_ + path;
188
189
// Clear some things to start.
190
info->isDirectory = false;
191
info->isWritable = false;
192
info->size = 0;
193
194
{
195
std::lock_guard<std::mutex> guard(lock_);
196
if (0 != zip_stat(zip_file_, temp_path.c_str(), ZIP_FL_NOCASE | ZIP_FL_UNCHANGED, &zstat)) {
197
// ZIP files do not have real directories, so we'll end up here if we
198
// try to stat one. For now that's fine.
199
info->exists = false;
200
return false;
201
}
202
}
203
204
// Zips usually don't contain directory entries, but they may.
205
if ((zstat.valid & ZIP_STAT_NAME) != 0 && zstat.name) {
206
info->isDirectory = zstat.name[strlen(zstat.name) - 1] == '/';
207
}
208
if ((zstat.valid & ZIP_STAT_SIZE) != 0) {
209
info->size = zstat.size;
210
}
211
212
info->fullName = Path(path);
213
info->exists = true;
214
return true;
215
}
216
217
class ZipFileReaderFileReference : public VFSFileReference {
218
public:
219
int zi;
220
};
221
222
class ZipFileReaderOpenFile : public VFSOpenFile {
223
public:
224
~ZipFileReaderOpenFile() {
225
// Needs to be closed properly and unlocked.
226
_dbg_assert_(zf == nullptr);
227
}
228
ZipFileReaderFileReference *reference;
229
zip_file_t *zf = nullptr;
230
};
231
232
VFSFileReference *ZipFileReader::GetFile(const char *path) {
233
std::lock_guard<std::mutex> guard(lock_);
234
int zi = zip_name_locate(zip_file_, path, ZIP_FL_NOCASE);
235
if (zi < 0) {
236
// Not found.
237
return nullptr;
238
}
239
ZipFileReaderFileReference *ref = new ZipFileReaderFileReference();
240
ref->zi = zi;
241
return ref;
242
}
243
244
bool ZipFileReader::GetFileInfo(VFSFileReference *vfsReference, File::FileInfo *fileInfo) {
245
ZipFileReaderFileReference *reference = (ZipFileReaderFileReference *)vfsReference;
246
// If you crash here, you called this while having the lock held by having the file open.
247
// Don't do that, check the info before you open the file.
248
std::lock_guard<std::mutex> guard(lock_);
249
zip_stat_t zstat;
250
if (zip_stat_index(zip_file_, reference->zi, 0, &zstat) != 0)
251
return false;
252
*fileInfo = File::FileInfo{};
253
fileInfo->size = 0;
254
if (zstat.valid & ZIP_STAT_SIZE)
255
fileInfo->size = zstat.size;
256
return zstat.size;
257
}
258
259
void ZipFileReader::ReleaseFile(VFSFileReference *vfsReference) {
260
ZipFileReaderFileReference *reference = (ZipFileReaderFileReference *)vfsReference;
261
// Don't do anything other than deleting it.
262
delete reference;
263
}
264
265
VFSOpenFile *ZipFileReader::OpenFileForRead(VFSFileReference *vfsReference, size_t *size) {
266
ZipFileReaderFileReference *reference = (ZipFileReaderFileReference *)vfsReference;
267
ZipFileReaderOpenFile *openFile = new ZipFileReaderOpenFile();
268
openFile->reference = reference;
269
*size = 0;
270
// We only allow one file to be open for read concurrently. It's possible that this can be improved,
271
// especially if we only access by index like this.
272
lock_.lock();
273
zip_stat_t zstat;
274
if (zip_stat_index(zip_file_, reference->zi, 0, &zstat) != 0) {
275
lock_.unlock();
276
delete openFile;
277
return nullptr;
278
}
279
280
openFile->zf = zip_fopen_index(zip_file_, reference->zi, 0);
281
if (!openFile->zf) {
282
WARN_LOG(Log::G3D, "File with index %d not found in zip", reference->zi);
283
lock_.unlock();
284
delete openFile;
285
return nullptr;
286
}
287
288
*size = zstat.size;
289
// Intentionally leaving the mutex locked, will be closed in CloseFile.
290
return openFile;
291
}
292
293
void ZipFileReader::Rewind(VFSOpenFile *vfsOpenFile) {
294
ZipFileReaderOpenFile *openFile = (ZipFileReaderOpenFile *)vfsOpenFile;
295
// Close and re-open.
296
zip_fclose(openFile->zf);
297
openFile->zf = zip_fopen_index(zip_file_, openFile->reference->zi, 0);
298
}
299
300
size_t ZipFileReader::Read(VFSOpenFile *vfsOpenFile, void *buffer, size_t length) {
301
ZipFileReaderOpenFile *file = (ZipFileReaderOpenFile *)vfsOpenFile;
302
return zip_fread(file->zf, buffer, length);
303
}
304
305
void ZipFileReader::CloseFile(VFSOpenFile *vfsOpenFile) {
306
ZipFileReaderOpenFile *file = (ZipFileReaderOpenFile *)vfsOpenFile;
307
_dbg_assert_(file->zf != nullptr);
308
zip_fclose(file->zf);
309
file->zf = nullptr;
310
lock_.unlock();
311
delete file;
312
}
313
314