mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 686805 part 4 - Make the linker load libraries with on-demand decompression when they are seekable compressed streams. r=tglek,r=sewardj
This commit is contained in:
parent
5af592a8a5
commit
d8fa15fe94
@ -326,6 +326,7 @@ private:
|
|||||||
void (*func)(void);
|
void (*func)(void);
|
||||||
} f;
|
} f;
|
||||||
f.ptr = ptr;
|
f.ptr = ptr;
|
||||||
|
debug("%s: Calling function @%p", GetPath(), ptr);
|
||||||
f.func();
|
f.func();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -226,17 +226,19 @@ ElfLoader::Load(const char *path, int flags, LibHandle *parent)
|
|||||||
zip = zips.GetZip(zip_path);
|
zip = zips.GetZip(zip_path);
|
||||||
Zip::Stream s;
|
Zip::Stream s;
|
||||||
if (zip && zip->GetStream(subpath, &s)) {
|
if (zip && zip->GetStream(subpath, &s)) {
|
||||||
if (s.GetType() == Zip::Stream::DEFLATE) {
|
/* When the MOZ_LINKER_EXTRACT environment variable is set to "1",
|
||||||
/* When the MOZ_LINKER_EXTRACT environment variable is set to "1",
|
* compressed libraries are going to be (temporarily) extracted as
|
||||||
* compressed libraries are going to be (temporarily) extracted as
|
* files, in the directory pointed by the MOZ_LINKER_CACHE
|
||||||
* files, in the directory pointed by the MOZ_LINKER_CACHE
|
* environment variable. */
|
||||||
* environment variable. */
|
const char *extract = getenv("MOZ_LINKER_EXTRACT");
|
||||||
const char *extract = getenv("MOZ_LINKER_EXTRACT");
|
if (extract && !strncmp(extract, "1", 2 /* Including '\0' */))
|
||||||
if (extract && !strncmp(extract, "1", 2 /* Including '\0' */))
|
mappable = MappableExtractFile::Create(name, &s);
|
||||||
mappable = MappableExtractFile::Create(name, &s);
|
if (!mappable) {
|
||||||
/* The above may fail in some cases. */
|
if (s.GetType() == Zip::Stream::DEFLATE) {
|
||||||
if (!mappable)
|
|
||||||
mappable = MappableDeflate::Create(name, zip, &s);
|
mappable = MappableDeflate::Create(name, zip, &s);
|
||||||
|
} else if (s.GetType() == Zip::Stream::STORE) {
|
||||||
|
mappable = MappableSeekableZStream::Create(name, zip, &s);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,6 +34,7 @@ CPPSRCS += \
|
|||||||
ElfLoader.cpp \
|
ElfLoader.cpp \
|
||||||
CustomElf.cpp \
|
CustomElf.cpp \
|
||||||
Mappable.cpp \
|
Mappable.cpp \
|
||||||
|
SeekableZStream.cpp \
|
||||||
$(NULL)
|
$(NULL)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
#include <linux/ashmem.h>
|
#include <linux/ashmem.h>
|
||||||
#endif
|
#endif
|
||||||
#include "ElfLoader.h"
|
#include "ElfLoader.h"
|
||||||
|
#include "SeekableZStream.h"
|
||||||
#include "Logging.h"
|
#include "Logging.h"
|
||||||
|
|
||||||
#ifndef PAGE_SIZE
|
#ifndef PAGE_SIZE
|
||||||
@ -80,35 +81,61 @@ MappableExtractFile::Create(const char *name, Zip::Stream *stream)
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
AutoUnlinkFile file = path.forget();
|
AutoUnlinkFile file = path.forget();
|
||||||
if (ftruncate(fd, stream->GetUncompressedSize()) == -1) {
|
if (stream->GetType() == Zip::Stream::DEFLATE) {
|
||||||
log("Couldn't ftruncate %s to decompress library", file.get());
|
if (ftruncate(fd, stream->GetUncompressedSize()) == -1) {
|
||||||
return NULL;
|
log("Couldn't ftruncate %s to decompress library", file.get());
|
||||||
}
|
return NULL;
|
||||||
/* Map the temporary file for use as inflate buffer */
|
}
|
||||||
MappedPtr buffer(::mmap(NULL, stream->GetUncompressedSize(), PROT_WRITE,
|
/* Map the temporary file for use as inflate buffer */
|
||||||
MAP_SHARED, fd, 0), stream->GetUncompressedSize());
|
MappedPtr buffer(::mmap(NULL, stream->GetUncompressedSize(), PROT_WRITE,
|
||||||
if (buffer == MAP_FAILED) {
|
MAP_SHARED, fd, 0), stream->GetUncompressedSize());
|
||||||
log("Couldn't map %s to decompress library", file.get());
|
if (buffer == MAP_FAILED) {
|
||||||
return NULL;
|
log("Couldn't map %s to decompress library", file.get());
|
||||||
}
|
return NULL;
|
||||||
z_stream zStream = stream->GetZStream(buffer);
|
}
|
||||||
|
|
||||||
/* Decompress */
|
z_stream zStream = stream->GetZStream(buffer);
|
||||||
if (inflateInit2(&zStream, -MAX_WBITS) != Z_OK) {
|
|
||||||
log("inflateInit failed: %s", zStream.msg);
|
/* Decompress */
|
||||||
return NULL;
|
if (inflateInit2(&zStream, -MAX_WBITS) != Z_OK) {
|
||||||
}
|
log("inflateInit failed: %s", zStream.msg);
|
||||||
if (inflate(&zStream, Z_FINISH) != Z_STREAM_END) {
|
return NULL;
|
||||||
log("inflate failed: %s", zStream.msg);
|
}
|
||||||
return NULL;
|
if (inflate(&zStream, Z_FINISH) != Z_STREAM_END) {
|
||||||
}
|
log("inflate failed: %s", zStream.msg);
|
||||||
if (inflateEnd(&zStream) != Z_OK) {
|
return NULL;
|
||||||
log("inflateEnd failed: %s", zStream.msg);
|
}
|
||||||
return NULL;
|
if (inflateEnd(&zStream) != Z_OK) {
|
||||||
}
|
log("inflateEnd failed: %s", zStream.msg);
|
||||||
if (zStream.total_out != stream->GetUncompressedSize()) {
|
return NULL;
|
||||||
log("File not fully uncompressed! %ld / %d", zStream.total_out,
|
}
|
||||||
static_cast<unsigned int>(stream->GetUncompressedSize()));
|
if (zStream.total_out != stream->GetUncompressedSize()) {
|
||||||
|
log("File not fully uncompressed! %ld / %d", zStream.total_out,
|
||||||
|
static_cast<unsigned int>(stream->GetUncompressedSize()));
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
} else if (stream->GetType() == Zip::Stream::STORE) {
|
||||||
|
SeekableZStream zStream;
|
||||||
|
if (!zStream.Init(stream->GetBuffer())) {
|
||||||
|
log("Couldn't initialize SeekableZStream for %s", name);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (ftruncate(fd, zStream.GetUncompressedSize()) == -1) {
|
||||||
|
log("Couldn't ftruncate %s to decompress library", file.get());
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
MappedPtr buffer(::mmap(NULL, zStream.GetUncompressedSize(), PROT_WRITE,
|
||||||
|
MAP_SHARED, fd, 0), zStream.GetUncompressedSize());
|
||||||
|
if (buffer == MAP_FAILED) {
|
||||||
|
log("Couldn't map %s to decompress library", file.get());
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!zStream.Decompress(buffer, 0, zStream.GetUncompressedSize())) {
|
||||||
|
log("%s: failed to decompress", name);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -296,3 +323,178 @@ MappableDeflate::finalize()
|
|||||||
/* Remove reference to Zip archive */
|
/* Remove reference to Zip archive */
|
||||||
zip = NULL;
|
zip = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MappableSeekableZStream *
|
||||||
|
MappableSeekableZStream::Create(const char *name, Zip *zip,
|
||||||
|
Zip::Stream *stream)
|
||||||
|
{
|
||||||
|
MOZ_ASSERT(stream->GetType() == Zip::Stream::STORE);
|
||||||
|
AutoDeletePtr<MappableSeekableZStream> mappable =
|
||||||
|
new MappableSeekableZStream(zip);
|
||||||
|
|
||||||
|
if (pthread_mutex_init(&mappable->mutex, NULL))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
if (!mappable->zStream.Init(stream->GetBuffer()))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
mappable->buffer = _MappableBuffer::Create(name,
|
||||||
|
mappable->zStream.GetUncompressedSize());
|
||||||
|
if (!mappable->buffer)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
mappable->chunkAvail = new unsigned char[mappable->zStream.GetChunksNum()];
|
||||||
|
memset(mappable->chunkAvail, 0, mappable->zStream.GetChunksNum());
|
||||||
|
|
||||||
|
return mappable.forget();
|
||||||
|
}
|
||||||
|
|
||||||
|
MappableSeekableZStream::MappableSeekableZStream(Zip *zip)
|
||||||
|
: zip(zip) { }
|
||||||
|
|
||||||
|
MappableSeekableZStream::~MappableSeekableZStream()
|
||||||
|
{
|
||||||
|
pthread_mutex_destroy(&mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
void *
|
||||||
|
MappableSeekableZStream::mmap(const void *addr, size_t length, int prot,
|
||||||
|
int flags, off_t offset)
|
||||||
|
{
|
||||||
|
/* Map with PROT_NONE so that accessing the mapping would segfault, and
|
||||||
|
* bring us to ensure() */
|
||||||
|
void *res = buffer->mmap(addr, length, PROT_NONE, flags, offset);
|
||||||
|
if (res == MAP_FAILED)
|
||||||
|
return MAP_FAILED;
|
||||||
|
|
||||||
|
/* Store the mapping, ordered by offset and length */
|
||||||
|
std::vector<LazyMap>::reverse_iterator it;
|
||||||
|
for (it = lazyMaps.rbegin(); it < lazyMaps.rend(); ++it) {
|
||||||
|
if ((it->offset < offset) ||
|
||||||
|
((it->offset == offset) && (it->length < length)))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
LazyMap map = { res, length, prot, offset };
|
||||||
|
lazyMaps.insert(it.base(), map);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
MappableSeekableZStream::munmap(void *addr, size_t length)
|
||||||
|
{
|
||||||
|
std::vector<LazyMap>::iterator it;
|
||||||
|
for (it = lazyMaps.begin(); it < lazyMaps.end(); ++it)
|
||||||
|
if ((it->addr = addr) && (it->length == length)) {
|
||||||
|
lazyMaps.erase(it);
|
||||||
|
::munmap(addr, length);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
MOZ_NOT_REACHED("munmap called with unknown mapping");
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
MappableSeekableZStream::finalize() { }
|
||||||
|
|
||||||
|
class AutoLock {
|
||||||
|
public:
|
||||||
|
AutoLock(pthread_mutex_t *mutex): mutex(mutex)
|
||||||
|
{
|
||||||
|
if (pthread_mutex_lock(mutex))
|
||||||
|
MOZ_NOT_REACHED("pthread_mutex_lock failed");
|
||||||
|
}
|
||||||
|
~AutoLock()
|
||||||
|
{
|
||||||
|
if (pthread_mutex_unlock(mutex))
|
||||||
|
MOZ_NOT_REACHED("pthread_mutex_unlock failed");
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
pthread_mutex_t *mutex;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool
|
||||||
|
MappableSeekableZStream::ensure(const void *addr)
|
||||||
|
{
|
||||||
|
debug("ensure @%p", addr);
|
||||||
|
void *addrPage = reinterpret_cast<void *>
|
||||||
|
(reinterpret_cast<uintptr_t>(addr) & PAGE_MASK);
|
||||||
|
/* Find the mapping corresponding to the given page */
|
||||||
|
std::vector<LazyMap>::iterator map;
|
||||||
|
for (map = lazyMaps.begin(); map < lazyMaps.end(); ++map) {
|
||||||
|
if (map->Contains(addrPage))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (map == lazyMaps.end())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
/* Find corresponding chunk */
|
||||||
|
off_t mapOffset = map->offsetOf(addrPage);
|
||||||
|
size_t chunk = mapOffset / zStream.GetChunkSize();
|
||||||
|
|
||||||
|
/* In the typical case, we just need to decompress the chunk entirely. But
|
||||||
|
* when the current mapping ends in the middle of the chunk, we want to
|
||||||
|
* stop there. However, if another mapping needs the last part of the
|
||||||
|
* chunk, we still need to continue. As mappings are ordered by offset
|
||||||
|
* and length, we don't need to scan the entire list of mappings.
|
||||||
|
* It is safe to run through lazyMaps here because the linker is never
|
||||||
|
* going to call mmap (which adds lazyMaps) while this function is
|
||||||
|
* called. */
|
||||||
|
size_t length = zStream.GetChunkSize(chunk);
|
||||||
|
size_t chunkStart = chunk * zStream.GetChunkSize();
|
||||||
|
size_t chunkEnd = chunkStart + length;
|
||||||
|
std::vector<LazyMap>::iterator it;
|
||||||
|
for (it = map; it < lazyMaps.end(); ++it) {
|
||||||
|
if (chunkEnd <= it->endOffset())
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if ((it == lazyMaps.end()) || (chunkEnd > it->endOffset())) {
|
||||||
|
/* The mapping "it" points at now is past the interesting one */
|
||||||
|
--it;
|
||||||
|
length = it->endOffset() - chunkStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
AutoLock lock(&mutex);
|
||||||
|
|
||||||
|
/* The very first page is mapped and accessed separately of the rest, and
|
||||||
|
* as such, only the first page of the first chunk is decompressed this way.
|
||||||
|
* When we fault in the remaining pages of that chunk, we want to decompress
|
||||||
|
* the complete chunk again. Short of doing that, we would end up with
|
||||||
|
* no data between PAGE_SIZE and chunkSize, which would effectively corrupt
|
||||||
|
* symbol resolution in the underlying library. */
|
||||||
|
if (chunkAvail[chunk] < (length + PAGE_SIZE - 1) / PAGE_SIZE) {
|
||||||
|
if (!zStream.DecompressChunk(*buffer + chunkStart, chunk, length))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
#if defined(ANDROID) && defined(__arm__)
|
||||||
|
if (map->prot & PROT_EXEC) {
|
||||||
|
/* We just extracted data that may be executed in the future.
|
||||||
|
* We thus need to ensure Instruction and Data cache coherency. */
|
||||||
|
debug("cacheflush(%p, %p)", *buffer + chunkStart, *buffer + (chunkStart + length));
|
||||||
|
cacheflush(reinterpret_cast<uintptr_t>(*buffer + chunkStart),
|
||||||
|
reinterpret_cast<uintptr_t>(*buffer + (chunkStart + length)), 0);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
chunkAvail[chunk] = (length + PAGE_SIZE - 1) / PAGE_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Flip the chunk mapping protection to the recorded flags. We could
|
||||||
|
* also flip the protection for other mappings of the same chunk,
|
||||||
|
* but it's easier to skip that and let further segfaults call
|
||||||
|
* ensure again. */
|
||||||
|
const void *chunkAddr = reinterpret_cast<const void *>
|
||||||
|
(reinterpret_cast<uintptr_t>(addrPage)
|
||||||
|
- mapOffset % zStream.GetChunkSize());
|
||||||
|
const void *chunkEndAddr = reinterpret_cast<const void *>
|
||||||
|
(reinterpret_cast<uintptr_t>(chunkAddr) + length);
|
||||||
|
|
||||||
|
const void *start = std::max(map->addr, chunkAddr);
|
||||||
|
const void *end = std::min(map->end(), chunkEndAddr);
|
||||||
|
length = reinterpret_cast<uintptr_t>(end)
|
||||||
|
- reinterpret_cast<uintptr_t>(start);
|
||||||
|
|
||||||
|
debug("mprotect @%p, 0x%x, 0x%x", start, length, map->prot);
|
||||||
|
if (mprotect(const_cast<void *>(start), length, map->prot) == 0)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
log("mprotect failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
@ -6,7 +6,9 @@
|
|||||||
#define Mappable_h
|
#define Mappable_h
|
||||||
|
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
|
#include <pthread.h>
|
||||||
#include "Zip.h"
|
#include "Zip.h"
|
||||||
|
#include "SeekableZStream.h"
|
||||||
#include "mozilla/RefPtr.h"
|
#include "mozilla/RefPtr.h"
|
||||||
#include "zlib.h"
|
#include "zlib.h"
|
||||||
|
|
||||||
@ -155,4 +157,84 @@ private:
|
|||||||
z_stream zStream;
|
z_stream zStream;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mappable implementation for seekable zStreams.
|
||||||
|
* Inflates the mapped bits in a temporary buffer, on demand.
|
||||||
|
*/
|
||||||
|
class MappableSeekableZStream: public Mappable
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
~MappableSeekableZStream();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a MappableSeekableZStream instance for the given Zip stream. The
|
||||||
|
* name argument is used for an appropriately named temporary file, and the
|
||||||
|
* Zip instance is given for the MappableSeekableZStream to keep a reference
|
||||||
|
* of it.
|
||||||
|
*/
|
||||||
|
static MappableSeekableZStream *Create(const char *name, Zip *zip,
|
||||||
|
Zip::Stream *stream);
|
||||||
|
|
||||||
|
/* Inherited from Mappable */
|
||||||
|
virtual void *mmap(const void *addr, size_t length, int prot, int flags, off_t offset);
|
||||||
|
virtual void munmap(void *addr, size_t length);
|
||||||
|
virtual void finalize();
|
||||||
|
virtual bool ensure(const void *addr);
|
||||||
|
|
||||||
|
private:
|
||||||
|
MappableSeekableZStream(Zip *zip);
|
||||||
|
|
||||||
|
/* Zip reference */
|
||||||
|
mozilla::RefPtr<Zip> zip;
|
||||||
|
|
||||||
|
/* Decompression buffer */
|
||||||
|
AutoDeletePtr<_MappableBuffer> buffer;
|
||||||
|
|
||||||
|
/* Seekable ZStream */
|
||||||
|
SeekableZStream zStream;
|
||||||
|
|
||||||
|
/* Keep track of mappings performed with MappableSeekableZStream::mmap so
|
||||||
|
* that they can be realized by MappableSeekableZStream::ensure.
|
||||||
|
* Values stored in the struct are those passed to mmap */
|
||||||
|
struct LazyMap
|
||||||
|
{
|
||||||
|
const void *addr;
|
||||||
|
size_t length;
|
||||||
|
int prot;
|
||||||
|
off_t offset;
|
||||||
|
|
||||||
|
/* Returns addr + length, as a pointer */
|
||||||
|
const void *end() const {
|
||||||
|
return reinterpret_cast<const void *>
|
||||||
|
(reinterpret_cast<const unsigned char *>(addr) + length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Returns offset + length */
|
||||||
|
const off_t endOffset() const {
|
||||||
|
return offset + length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Returns the offset corresponding to the given address */
|
||||||
|
const off_t offsetOf(const void *ptr) const {
|
||||||
|
return reinterpret_cast<uintptr_t>(ptr)
|
||||||
|
- reinterpret_cast<uintptr_t>(addr) + offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Returns whether the given address is in the LazyMap range */
|
||||||
|
const bool Contains(const void *ptr) const {
|
||||||
|
return (ptr >= addr) && (ptr < end());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/* List of all mappings */
|
||||||
|
std::vector<LazyMap> lazyMaps;
|
||||||
|
|
||||||
|
/* Array keeping track of which chunks have already been decompressed.
|
||||||
|
* Each value is the number of pages decompressed for the given chunk. */
|
||||||
|
AutoDeleteArray<unsigned char> chunkAvail;
|
||||||
|
|
||||||
|
/* Mutex protecting decompression */
|
||||||
|
pthread_mutex_t mutex;
|
||||||
|
};
|
||||||
|
|
||||||
#endif /* Mappable_h */
|
#endif /* Mappable_h */
|
||||||
|
100
mozglue/linker/SeekableZStream.cpp
Normal file
100
mozglue/linker/SeekableZStream.cpp
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
|
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include "SeekableZStream.h"
|
||||||
|
#include "Logging.h"
|
||||||
|
|
||||||
|
#ifndef PAGE_SIZE
|
||||||
|
#define PAGE_SIZE 4096
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef PAGE_MASK
|
||||||
|
#define PAGE_MASK (~ (PAGE_SIZE - 1))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool
|
||||||
|
SeekableZStream::Init(const void *buf)
|
||||||
|
{
|
||||||
|
const SeekableZStreamHeader *header = SeekableZStreamHeader::validate(buf);
|
||||||
|
if (!header) {
|
||||||
|
log("Not a seekable zstream");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer = reinterpret_cast<const unsigned char *>(buf);
|
||||||
|
totalSize = header->totalSize;
|
||||||
|
chunkSize = header->chunkSize;
|
||||||
|
lastChunkSize = header->lastChunkSize;
|
||||||
|
offsetTable.Init(&header[1], header->nChunks);
|
||||||
|
|
||||||
|
/* Sanity check */
|
||||||
|
if ((chunkSize == 0) ||
|
||||||
|
(chunkSize % PAGE_SIZE) ||
|
||||||
|
(chunkSize > 8 * PAGE_SIZE) ||
|
||||||
|
(offsetTable.numElements() < 1) ||
|
||||||
|
(lastChunkSize == 0) ||
|
||||||
|
(lastChunkSize > chunkSize)) {
|
||||||
|
log("Malformed or broken seekable zstream");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
SeekableZStream::Decompress(void *where, size_t chunk, size_t length)
|
||||||
|
{
|
||||||
|
while (length) {
|
||||||
|
size_t len = std::min(length, static_cast<size_t>(chunkSize));
|
||||||
|
if (!DecompressChunk(where, chunk, len))
|
||||||
|
return false;
|
||||||
|
where = reinterpret_cast<unsigned char *>(where) + len;
|
||||||
|
length -= len;
|
||||||
|
chunk++;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
SeekableZStream::DecompressChunk(void *where, size_t chunk, size_t length)
|
||||||
|
{
|
||||||
|
if (chunk >= offsetTable.numElements()) {
|
||||||
|
log("DecompressChunk: chunk #%ld out of range [0-%ld)",
|
||||||
|
chunk, offsetTable.numElements());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isLastChunk = (chunk == offsetTable.numElements() - 1);
|
||||||
|
|
||||||
|
size_t chunkLen = isLastChunk ? lastChunkSize : chunkSize;
|
||||||
|
|
||||||
|
if (length == 0 || length > chunkLen)
|
||||||
|
length = chunkLen;
|
||||||
|
|
||||||
|
debug("DecompressChunk #%ld @%p (%ld/%ld)", chunk, where, length, chunkLen);
|
||||||
|
z_stream zStream;
|
||||||
|
memset(&zStream, 0, sizeof(zStream));
|
||||||
|
zStream.avail_in = (isLastChunk ? totalSize : uint32_t(offsetTable[chunk + 1]))
|
||||||
|
- uint32_t(offsetTable[chunk]);
|
||||||
|
zStream.next_in = const_cast<Bytef *>(buffer + uint32_t(offsetTable[chunk]));
|
||||||
|
zStream.avail_out = length;
|
||||||
|
zStream.next_out = reinterpret_cast<Bytef *>(where);
|
||||||
|
|
||||||
|
/* Decompress chunk */
|
||||||
|
if (inflateInit(&zStream) != Z_OK) {
|
||||||
|
log("inflateInit failed: %s", zStream.msg);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (inflate(&zStream, (length == chunkLen) ? Z_FINISH : Z_SYNC_FLUSH)
|
||||||
|
!= (length == chunkLen) ? Z_STREAM_END : Z_OK) {
|
||||||
|
log("inflate failed: %s", zStream.msg);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (inflateEnd(&zStream) != Z_OK) {
|
||||||
|
log("inflateEnd failed: %s", zStream.msg);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
@ -45,4 +45,58 @@ struct SeekableZStreamHeader: public Zip::SignedEntity<SeekableZStreamHeader>
|
|||||||
MOZ_STATIC_ASSERT(sizeof(SeekableZStreamHeader) == 5 * 4,
|
MOZ_STATIC_ASSERT(sizeof(SeekableZStreamHeader) == 5 * 4,
|
||||||
"SeekableZStreamHeader should be 5 32-bits words");
|
"SeekableZStreamHeader should be 5 32-bits words");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper class used to decompress Seekable ZStreams.
|
||||||
|
*/
|
||||||
|
class SeekableZStream {
|
||||||
|
public:
|
||||||
|
/* Initialize from the given buffer. Returns whether initialization
|
||||||
|
* succeeded (true) or failed (false). */
|
||||||
|
bool Init(const void *buf);
|
||||||
|
|
||||||
|
/* Decompresses starting from the given chunk. The decompressed data is
|
||||||
|
* stored at the given location. The given length, in bytes, indicates
|
||||||
|
* how much data to decompress. If length is 0, then exactly one chunk
|
||||||
|
* is decompressed.
|
||||||
|
* Returns whether decompression succeeded (true) or failed (false). */
|
||||||
|
bool Decompress(void *where, size_t chunk, size_t length = 0);
|
||||||
|
|
||||||
|
/* Decompresses the given chunk at the given address. If a length is given,
|
||||||
|
* only decompresses that amount of data instead of the entire chunk.
|
||||||
|
* Returns whether decompression succeeded (true) or failed (false). */
|
||||||
|
bool DecompressChunk(void *where, size_t chunk, size_t length = 0);
|
||||||
|
|
||||||
|
/* Returns the uncompressed size of the complete zstream */
|
||||||
|
const size_t GetUncompressedSize() const
|
||||||
|
{
|
||||||
|
return (offsetTable.numElements() - 1) * chunkSize + lastChunkSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Returns the chunk size of the given chunk */
|
||||||
|
const size_t GetChunkSize(size_t chunk = 0) const {
|
||||||
|
return (chunk == offsetTable.numElements() - 1) ? lastChunkSize : chunkSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Returns the number of chunks */
|
||||||
|
const size_t GetChunksNum() const {
|
||||||
|
return offsetTable.numElements();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
/* RAW Seekable SZtream buffer */
|
||||||
|
const unsigned char *buffer;
|
||||||
|
|
||||||
|
/* Total size of the stream, including the 4 magic bytes. */
|
||||||
|
uint32_t totalSize;
|
||||||
|
|
||||||
|
/* Chunk size */
|
||||||
|
uint32_t chunkSize;
|
||||||
|
|
||||||
|
/* Size of last chunk (> 0, <= Chunk size) */
|
||||||
|
uint32_t lastChunkSize;
|
||||||
|
|
||||||
|
/* Offsets table */
|
||||||
|
Array<le_uint32> offsetTable;
|
||||||
|
};
|
||||||
|
|
||||||
#endif /* SeekableZStream_h */
|
#endif /* SeekableZStream_h */
|
||||||
|
Loading…
Reference in New Issue
Block a user