Bug 1751497 - adjust wpt test-verify and test-coverage tasks to be fission only....
[gecko.git] / mozglue / linker / Zip.cpp
blob7ecc6b9a7426aad8cd8fc54ee740e4323fdce86a
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
5 #include <sys/mman.h>
6 #include <sys/stat.h>
7 #include <fcntl.h>
8 #include <errno.h>
9 #include <unistd.h>
10 #include <cstdlib>
11 #include <algorithm>
12 #include "Logging.h"
13 #include "Zip.h"
15 already_AddRefed<Zip> Zip::Create(const char* filename) {
16 /* Open and map the file in memory */
17 AutoCloseFD fd(open(filename, O_RDONLY));
18 if (fd == -1) {
19 ERROR("Error opening %s: %s", filename, strerror(errno));
20 return nullptr;
22 struct stat st;
23 if (fstat(fd, &st) == -1) {
24 ERROR("Error stating %s: %s", filename, strerror(errno));
25 return nullptr;
27 size_t size = st.st_size;
28 if (size <= sizeof(CentralDirectoryEnd)) {
29 ERROR("Error reading %s: too short", filename);
30 return nullptr;
32 void* mapped = mmap(nullptr, size, PROT_READ, MAP_SHARED, fd, 0);
33 if (mapped == MAP_FAILED) {
34 ERROR("Error mmapping %s: %s", filename, strerror(errno));
35 return nullptr;
37 DEBUG_LOG("Mapped %s @%p", filename, mapped);
39 return Create(filename, mapped, size);
42 already_AddRefed<Zip> Zip::Create(const char* filename, void* mapped,
43 size_t size) {
44 RefPtr<Zip> zip = new Zip(filename, mapped, size);
46 // If neither the first Local File entry nor central directory entries
47 // have been found, the zip was invalid.
48 if (!zip->nextFile && !zip->entries) {
49 ERROR("%s - Invalid zip", filename);
50 return nullptr;
53 ZipCollection::Singleton.Register(zip);
54 return zip.forget();
57 Zip::Zip(const char* filename, void* mapped, size_t size)
58 : name(filename ? strdup(filename) : nullptr),
59 mapped(mapped),
60 size(size),
61 nextFile(LocalFile::validate(mapped)) // first Local File entry
63 nextDir(nullptr),
64 entries(nullptr) {
65 pthread_mutex_init(&mutex, nullptr);
66 // If the first local file entry couldn't be found (which can happen
67 // with optimized jars), check the first central directory entry.
68 if (!nextFile) GetFirstEntry();
71 Zip::~Zip() {
72 if (name) {
73 munmap(mapped, size);
74 DEBUG_LOG("Unmapped %s @%p", name, mapped);
75 free(name);
77 pthread_mutex_destroy(&mutex);
80 bool Zip::GetStream(const char* path, Zip::Stream* out) const {
81 AutoLock lock(&mutex);
83 DEBUG_LOG("%s - GetFile %s", name, path);
84 /* Fast path: if the Local File header on store matches, we can return the
85 * corresponding stream right away.
86 * However, the Local File header may not contain enough information, in
87 * which case the 3rd bit on the generalFlag is set. Unfortunately, this
88 * bit is also set in some archives even when we do have the data (most
89 * notably the android packages as built by the Mozilla build system).
90 * So instead of testing the generalFlag bit, only use the fast path when
91 * we haven't read the central directory entries yet, and when the
92 * compressed size as defined in the header is not filled (which is a
93 * normal condition for the bit to be set). */
94 if (nextFile && nextFile->GetName().Equals(path) && !entries &&
95 (nextFile->compressedSize != 0)) {
96 DEBUG_LOG("%s - %s was next file: fast path", name, path);
97 /* Fill Stream info from Local File header content */
98 const char* data = reinterpret_cast<const char*>(nextFile->GetData());
99 out->compressedBuf = data;
100 out->compressedSize = nextFile->compressedSize;
101 out->uncompressedSize = nextFile->uncompressedSize;
102 out->CRC32 = nextFile->CRC32;
103 out->type = static_cast<Stream::Type>(uint16_t(nextFile->compression));
105 /* Find the next Local File header. It is usually simply following the
106 * compressed stream, but in cases where the 3rd bit of the generalFlag
107 * is set, there is a Data Descriptor header before. */
108 data += nextFile->compressedSize;
109 if ((nextFile->generalFlag & 0x8) && DataDescriptor::validate(data)) {
110 data += sizeof(DataDescriptor);
112 nextFile = LocalFile::validate(data);
113 return true;
116 /* If the directory entry we have in store doesn't match, scan the Central
117 * Directory for the entry corresponding to the given path */
118 if (!nextDir || !nextDir->GetName().Equals(path)) {
119 const DirectoryEntry* entry = GetFirstEntry();
120 DEBUG_LOG("%s - Scan directory entries in search for %s", name, path);
121 while (entry && !entry->GetName().Equals(path)) {
122 entry = entry->GetNext();
124 nextDir = entry;
126 if (!nextDir) {
127 DEBUG_LOG("%s - Couldn't find %s", name, path);
128 return false;
131 /* Find the Local File header corresponding to the Directory entry that
132 * was found. */
133 nextFile =
134 LocalFile::validate(static_cast<const char*>(mapped) + nextDir->offset);
135 if (!nextFile) {
136 ERROR("%s - Couldn't find the Local File header for %s", name, path);
137 return false;
140 /* Fill Stream info from Directory entry content */
141 const char* data = reinterpret_cast<const char*>(nextFile->GetData());
142 out->compressedBuf = data;
143 out->compressedSize = nextDir->compressedSize;
144 out->uncompressedSize = nextDir->uncompressedSize;
145 out->CRC32 = nextDir->CRC32;
146 out->type = static_cast<Stream::Type>(uint16_t(nextDir->compression));
148 /* Store the next directory entry */
149 nextDir = nextDir->GetNext();
150 nextFile = nullptr;
151 return true;
154 const Zip::DirectoryEntry* Zip::GetFirstEntry() const {
155 if (entries) return entries;
157 const CentralDirectoryEnd* end = nullptr;
158 const char* _end =
159 static_cast<const char*>(mapped) + size - sizeof(CentralDirectoryEnd);
161 /* Scan for the Central Directory End */
162 for (; _end > mapped && !end; _end--)
163 end = CentralDirectoryEnd::validate(_end);
164 if (!end) {
165 ERROR("%s - Couldn't find end of central directory record", name);
166 return nullptr;
169 entries =
170 DirectoryEntry::validate(static_cast<const char*>(mapped) + end->offset);
171 if (!entries) {
172 ERROR("%s - Couldn't find central directory record", name);
174 return entries;
177 bool Zip::VerifyCRCs() const {
178 AutoLock lock(&mutex);
180 for (const DirectoryEntry* entry = GetFirstEntry(); entry;
181 entry = entry->GetNext()) {
182 const LocalFile* file =
183 LocalFile::validate(static_cast<const char*>(mapped) + entry->offset);
184 uint32_t crc = crc32(0, nullptr, 0);
186 DEBUG_LOG("%.*s: crc=%08x", int(entry->filenameSize),
187 reinterpret_cast<const char*>(entry) + sizeof(*entry),
188 uint32_t(entry->CRC32));
190 if (entry->compression == Stream::Type::STORE) {
191 crc = crc32(crc, static_cast<const uint8_t*>(file->GetData()),
192 entry->compressedSize);
193 DEBUG_LOG(" STORE size=%d crc=%08x", int(entry->compressedSize), crc);
195 } else if (entry->compression == Stream::Type::DEFLATE) {
196 z_stream zstream;
197 Bytef buffer[1024];
198 zstream.avail_in = entry->compressedSize;
199 zstream.next_in =
200 reinterpret_cast<Bytef*>(const_cast<void*>(file->GetData()));
201 zstream.zalloc = nullptr;
202 zstream.zfree = nullptr;
203 zstream.opaque = nullptr;
205 if (inflateInit2(&zstream, -MAX_WBITS) != Z_OK) {
206 return false;
209 for (;;) {
210 zstream.avail_out = sizeof(buffer);
211 zstream.next_out = buffer;
213 int ret = inflate(&zstream, Z_SYNC_FLUSH);
214 crc = crc32(crc, buffer, sizeof(buffer) - zstream.avail_out);
216 if (ret == Z_STREAM_END) {
217 break;
218 } else if (ret != Z_OK) {
219 return false;
223 inflateEnd(&zstream);
224 DEBUG_LOG(" DEFLATE size=%d crc=%08x", int(zstream.total_out), crc);
226 } else {
227 MOZ_ASSERT_UNREACHABLE("Unexpected stream type");
228 continue;
231 if (entry->CRC32 != crc) {
232 return false;
236 return true;
239 ZipCollection ZipCollection::Singleton;
241 static pthread_mutex_t sZipCollectionMutex = PTHREAD_MUTEX_INITIALIZER;
243 already_AddRefed<Zip> ZipCollection::GetZip(const char* path) {
245 AutoLock lock(&sZipCollectionMutex);
246 /* Search the list of Zips we already have for a match */
247 for (const auto& zip : Singleton.zips) {
248 if (zip->GetName() && (strcmp(zip->GetName(), path) == 0)) {
249 return RefPtr<Zip>(zip).forget();
253 return Zip::Create(path);
256 void ZipCollection::Register(Zip* zip) {
257 AutoLock lock(&sZipCollectionMutex);
258 DEBUG_LOG("ZipCollection::Register(\"%s\")", zip->GetName());
259 Singleton.zips.push_back(zip);
262 void ZipCollection::Forget(const Zip* zip) {
263 AutoLock lock(&sZipCollectionMutex);
264 if (zip->refCount() > 1) {
265 // Someone has acquired a reference before we had acquired the lock,
266 // ignore this request.
267 return;
269 DEBUG_LOG("ZipCollection::Forget(\"%s\")", zip->GetName());
270 const auto it = std::find(Singleton.zips.begin(), Singleton.zips.end(), zip);
271 if (*it == zip) {
272 Singleton.zips.erase(it);
273 } else {
274 DEBUG_LOG("ZipCollection::Forget: didn't find \"%s\" in bookkeeping",
275 zip->GetName());