2012-02-22 07:12:15 +00:00
|
|
|
/* 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 <sys/stat.h>
|
|
|
|
#include <cstring>
|
|
|
|
#include <zlib.h>
|
|
|
|
#include <fcntl.h>
|
|
|
|
#include <errno.h>
|
|
|
|
#include "mozilla/Assertions.h"
|
2012-07-30 18:17:53 +00:00
|
|
|
#include "mozilla/Scoped.h"
|
2012-02-22 07:12:15 +00:00
|
|
|
#include "SeekableZStream.h"
|
|
|
|
#include "Utils.h"
|
|
|
|
#include "Logging.h"
|
|
|
|
|
2013-03-06 06:29:33 +00:00
|
|
|
class Buffer: public MappedPtr
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
virtual bool Resize(size_t size)
|
|
|
|
{
|
|
|
|
void *buf = mmap(NULL, size, PROT_READ | PROT_WRITE,
|
|
|
|
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
|
|
|
if (buf == MAP_FAILED)
|
|
|
|
return false;
|
|
|
|
if (*this != MAP_FAILED)
|
|
|
|
memcpy(buf, *this, std::min(size, GetLength()));
|
|
|
|
Assign(buf, size);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
class FileBuffer: public Buffer
|
2013-03-06 06:29:13 +00:00
|
|
|
{
|
|
|
|
public:
|
|
|
|
bool Init(const char *name, bool writable_ = false)
|
|
|
|
{
|
|
|
|
fd = open(name, writable_ ? O_RDWR | O_CREAT | O_TRUNC : O_RDONLY, 0666);
|
|
|
|
if (fd == -1)
|
|
|
|
return false;
|
|
|
|
writable = writable_;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2013-03-06 06:29:33 +00:00
|
|
|
virtual bool Resize(size_t size)
|
2013-03-06 06:29:13 +00:00
|
|
|
{
|
|
|
|
if (writable) {
|
|
|
|
if (ftruncate(fd, size) == -1)
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
Assign(mmap(NULL, size, PROT_READ | (writable ? PROT_WRITE : 0),
|
|
|
|
writable ? MAP_SHARED : MAP_PRIVATE, fd, 0), size);
|
|
|
|
return this != MAP_FAILED;
|
|
|
|
}
|
|
|
|
|
|
|
|
int getFd()
|
|
|
|
{
|
|
|
|
return fd;
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
AutoCloseFD fd;
|
|
|
|
bool writable;
|
|
|
|
};
|
|
|
|
|
2012-02-22 07:12:15 +00:00
|
|
|
static const size_t CHUNK = 16384;
|
|
|
|
|
2013-03-06 06:29:22 +00:00
|
|
|
/* Decompress a seekable compressed stream */
|
2013-03-06 06:29:33 +00:00
|
|
|
int do_decompress(const char *name, Buffer &origBuf,
|
|
|
|
const char *outName, Buffer &outBuf)
|
2012-02-22 07:12:15 +00:00
|
|
|
{
|
2013-03-06 06:29:22 +00:00
|
|
|
size_t origSize = origBuf.GetLength();
|
|
|
|
if (origSize < sizeof(SeekableZStreamHeader)) {
|
|
|
|
log("%s is not a seekable zstream", name);
|
2012-02-22 07:12:15 +00:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2013-03-06 06:29:22 +00:00
|
|
|
SeekableZStream zstream;
|
|
|
|
if (!zstream.Init(origBuf, origSize))
|
2012-02-22 07:12:15 +00:00
|
|
|
return 1;
|
|
|
|
|
2013-03-06 06:29:22 +00:00
|
|
|
size_t size = zstream.GetUncompressedSize();
|
2012-02-22 07:12:15 +00:00
|
|
|
|
2013-03-06 06:29:22 +00:00
|
|
|
/* Give enough room for the uncompressed data */
|
|
|
|
if (!outBuf.Resize(size)) {
|
|
|
|
log("Error resizing %s: %s", outName, strerror(errno));
|
2012-02-22 07:12:15 +00:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2013-03-06 06:29:22 +00:00
|
|
|
if (!zstream.Decompress(outBuf, 0, size))
|
2012-02-22 07:12:15 +00:00
|
|
|
return 1;
|
|
|
|
|
2013-03-06 06:29:22 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Generate a seekable compressed stream. */
|
2013-03-06 06:29:33 +00:00
|
|
|
int do_compress(const char *name, Buffer &origBuf,
|
|
|
|
const char *outName, Buffer &outBuf)
|
2013-03-06 06:29:22 +00:00
|
|
|
{
|
|
|
|
size_t origSize = origBuf.GetLength();
|
|
|
|
if (origSize == 0) {
|
|
|
|
log("Won't compress %s: it's empty", name);
|
2012-02-22 07:12:15 +00:00
|
|
|
return 1;
|
|
|
|
}
|
2013-03-06 06:29:22 +00:00
|
|
|
log("Size = %" PRIuSize, origSize);
|
2012-02-22 07:12:15 +00:00
|
|
|
|
|
|
|
/* Expected total number of chunks */
|
|
|
|
size_t nChunks = ((origSize + CHUNK - 1) / CHUNK);
|
|
|
|
|
|
|
|
/* The first chunk is going to be stored after the header and the offset
|
|
|
|
* table */
|
|
|
|
size_t offset = sizeof(SeekableZStreamHeader) + nChunks * sizeof(uint32_t);
|
|
|
|
|
|
|
|
/* Give enough room for the header and the offset table, and map them */
|
2013-03-06 06:29:13 +00:00
|
|
|
if (!outBuf.Resize(origSize)) {
|
2013-03-06 06:29:22 +00:00
|
|
|
log("Couldn't mmap %s: %s", outName, strerror(errno));
|
2012-02-22 07:12:15 +00:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2013-03-06 06:29:13 +00:00
|
|
|
SeekableZStreamHeader *header = new (outBuf) SeekableZStreamHeader;
|
2012-02-22 07:12:15 +00:00
|
|
|
le_uint32 *entry = reinterpret_cast<le_uint32 *>(&header[1]);
|
|
|
|
|
|
|
|
/* Initialize header */
|
|
|
|
header->chunkSize = CHUNK;
|
|
|
|
header->totalSize = offset;
|
2013-03-06 06:29:41 +00:00
|
|
|
header->windowBits = -15; // Raw stream, window size of 32k.
|
2012-02-22 07:12:15 +00:00
|
|
|
|
|
|
|
/* Initialize zlib structure */
|
|
|
|
z_stream zStream;
|
|
|
|
memset(&zStream, 0, sizeof(zStream));
|
2013-03-06 06:29:13 +00:00
|
|
|
zStream.avail_out = origSize - offset;
|
|
|
|
zStream.next_out = static_cast<Bytef*>(outBuf) + offset;
|
2012-02-22 07:12:15 +00:00
|
|
|
|
|
|
|
Bytef *origData = static_cast<Bytef*>(origBuf);
|
|
|
|
size_t avail = 0;
|
2013-03-06 06:29:13 +00:00
|
|
|
size_t size = origSize;
|
|
|
|
while (size) {
|
|
|
|
avail = std::min(size, CHUNK);
|
2012-02-22 07:12:15 +00:00
|
|
|
|
|
|
|
/* Compress chunk */
|
2013-03-06 06:29:41 +00:00
|
|
|
int ret = deflateInit2(&zStream, 9, Z_DEFLATED, header->windowBits,
|
|
|
|
MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY);
|
2012-02-22 07:12:15 +00:00
|
|
|
MOZ_ASSERT(ret == Z_OK);
|
|
|
|
zStream.avail_in = avail;
|
|
|
|
zStream.next_in = origData;
|
|
|
|
ret = deflate(&zStream, Z_FINISH);
|
|
|
|
MOZ_ASSERT(ret == Z_STREAM_END);
|
|
|
|
ret = deflateEnd(&zStream);
|
|
|
|
MOZ_ASSERT(ret == Z_OK);
|
|
|
|
MOZ_ASSERT(zStream.avail_out > 0);
|
|
|
|
|
2013-03-06 06:29:13 +00:00
|
|
|
size_t len = origSize - offset - zStream.avail_out;
|
2012-02-22 07:12:15 +00:00
|
|
|
|
|
|
|
/* Adjust headers */
|
|
|
|
header->totalSize += len;
|
|
|
|
*entry++ = offset;
|
|
|
|
header->nChunks++;
|
|
|
|
|
|
|
|
/* Prepare for next iteration */
|
2013-03-06 06:29:13 +00:00
|
|
|
size -= avail;
|
2012-02-22 07:12:15 +00:00
|
|
|
origData += avail;
|
|
|
|
offset += len;
|
|
|
|
}
|
|
|
|
header->lastChunkSize = avail;
|
2013-03-06 06:29:13 +00:00
|
|
|
if (!outBuf.Resize(offset)) {
|
2013-03-06 06:29:22 +00:00
|
|
|
log("Error resizing %s: %s", outName, strerror(errno));
|
2013-03-06 06:29:13 +00:00
|
|
|
return 1;
|
|
|
|
}
|
2012-02-22 07:12:15 +00:00
|
|
|
|
|
|
|
MOZ_ASSERT(header->nChunks == nChunks);
|
2013-03-06 06:29:22 +00:00
|
|
|
log("Compressed size is %" PRIuSize, offset);
|
2012-02-22 07:12:15 +00:00
|
|
|
|
2013-03-06 06:29:33 +00:00
|
|
|
/* Sanity check */
|
|
|
|
Buffer tmpBuf;
|
|
|
|
if (do_decompress("buffer", outBuf, "buffer", tmpBuf))
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
size = tmpBuf.GetLength();
|
|
|
|
if (size != origSize) {
|
|
|
|
log("Compression error: %" PRIuSize " != %" PRIuSize, size, origSize);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
if (memcmp(static_cast<void *>(origBuf), static_cast<void *>(tmpBuf), size)) {
|
|
|
|
log("Compression error: content mismatch");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2012-02-22 07:12:15 +00:00
|
|
|
return 0;
|
|
|
|
}
|
2013-03-06 06:29:22 +00:00
|
|
|
|
|
|
|
int main(int argc, char* argv[])
|
|
|
|
{
|
2013-03-06 06:29:33 +00:00
|
|
|
int (*func)(const char *, Buffer &, const char *, Buffer &) = do_compress;
|
2013-03-06 06:29:22 +00:00
|
|
|
char **firstArg = &argv[1];
|
|
|
|
|
|
|
|
if ((argc > 1) && strcmp(argv[1], "-d") == 0) {
|
|
|
|
func = do_decompress;
|
|
|
|
firstArg++;
|
|
|
|
argc--;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (argc != 3 || !firstArg[0] || !firstArg[1] ||
|
|
|
|
(strcmp(firstArg[0], firstArg[1]) == 0)) {
|
|
|
|
log("usage: %s [-d] in_file out_file", argv[0]);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
FileBuffer origBuf;
|
|
|
|
if (!origBuf.Init(firstArg[0])) {
|
|
|
|
log("Couldn't open %s: %s", firstArg[0], strerror(errno));
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct stat st;
|
|
|
|
int ret = fstat(origBuf.getFd(), &st);
|
|
|
|
if (ret == -1) {
|
|
|
|
log("Couldn't stat %s: %s", firstArg[0], strerror(errno));
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t origSize = st.st_size;
|
|
|
|
|
|
|
|
/* Mmap the original file */
|
|
|
|
if (!origBuf.Resize(origSize)) {
|
|
|
|
log("Couldn't mmap %s: %s", firstArg[0], strerror(errno));
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Create the compressed file */
|
|
|
|
FileBuffer outBuf;
|
|
|
|
if (!outBuf.Init(firstArg[1], true)) {
|
|
|
|
log("Couldn't open %s: %s", firstArg[1], strerror(errno));
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return func(firstArg[0], origBuf, firstArg[1], outBuf);
|
|
|
|
}
|