Wed, 31 Dec 2014 07:22:50 +0100
Correct previous dual key logic pending first delivery installment.
michael@0 | 1 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
michael@0 | 3 | * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 4 | |
michael@0 | 5 | #include <algorithm> |
michael@0 | 6 | #include <map> |
michael@0 | 7 | #include <sys/stat.h> |
michael@0 | 8 | #include <string> |
michael@0 | 9 | #include <sstream> |
michael@0 | 10 | #include <cstring> |
michael@0 | 11 | #include <cstdlib> |
michael@0 | 12 | #include <zlib.h> |
michael@0 | 13 | #include <fcntl.h> |
michael@0 | 14 | #include <errno.h> |
michael@0 | 15 | #include "mozilla/Assertions.h" |
michael@0 | 16 | #include "mozilla/Scoped.h" |
michael@0 | 17 | #include "SeekableZStream.h" |
michael@0 | 18 | #include "Utils.h" |
michael@0 | 19 | #include "Logging.h" |
michael@0 | 20 | |
michael@0 | 21 | Logging Logging::Singleton; |
michael@0 | 22 | |
michael@0 | 23 | const char *filterName[] = { |
michael@0 | 24 | "none", |
michael@0 | 25 | "thumb", |
michael@0 | 26 | "arm", |
michael@0 | 27 | "x86", |
michael@0 | 28 | "auto" |
michael@0 | 29 | }; |
michael@0 | 30 | |
michael@0 | 31 | /* Maximum supported size for chunkSize */ |
michael@0 | 32 | static const size_t maxChunkSize = |
michael@0 | 33 | 1 << (8 * std::min(sizeof(((SeekableZStreamHeader *)nullptr)->chunkSize), |
michael@0 | 34 | sizeof(((SeekableZStreamHeader *)nullptr)->lastChunkSize)) - 1); |
michael@0 | 35 | |
michael@0 | 36 | class Buffer: public MappedPtr |
michael@0 | 37 | { |
michael@0 | 38 | public: |
michael@0 | 39 | virtual ~Buffer() { } |
michael@0 | 40 | |
michael@0 | 41 | virtual bool Resize(size_t size) |
michael@0 | 42 | { |
michael@0 | 43 | MemoryRange buf = mmap(nullptr, size, PROT_READ | PROT_WRITE, |
michael@0 | 44 | MAP_PRIVATE | MAP_ANON, -1, 0); |
michael@0 | 45 | if (buf == MAP_FAILED) |
michael@0 | 46 | return false; |
michael@0 | 47 | if (*this != MAP_FAILED) |
michael@0 | 48 | memcpy(buf, *this, std::min(size, GetLength())); |
michael@0 | 49 | Assign(buf); |
michael@0 | 50 | return true; |
michael@0 | 51 | } |
michael@0 | 52 | |
michael@0 | 53 | bool Fill(Buffer &other) |
michael@0 | 54 | { |
michael@0 | 55 | size_t size = other.GetLength(); |
michael@0 | 56 | if (!size || !Resize(size)) |
michael@0 | 57 | return false; |
michael@0 | 58 | memcpy(static_cast<void *>(*this), static_cast<void *>(other), size); |
michael@0 | 59 | return true; |
michael@0 | 60 | } |
michael@0 | 61 | }; |
michael@0 | 62 | |
michael@0 | 63 | class FileBuffer: public Buffer |
michael@0 | 64 | { |
michael@0 | 65 | public: |
michael@0 | 66 | bool Init(const char *name, bool writable_ = false) |
michael@0 | 67 | { |
michael@0 | 68 | fd = open(name, writable_ ? O_RDWR | O_CREAT | O_TRUNC : O_RDONLY, 0666); |
michael@0 | 69 | if (fd == -1) |
michael@0 | 70 | return false; |
michael@0 | 71 | writable = writable_; |
michael@0 | 72 | return true; |
michael@0 | 73 | } |
michael@0 | 74 | |
michael@0 | 75 | virtual bool Resize(size_t size) |
michael@0 | 76 | { |
michael@0 | 77 | if (writable) { |
michael@0 | 78 | if (ftruncate(fd, size) == -1) |
michael@0 | 79 | return false; |
michael@0 | 80 | } |
michael@0 | 81 | Assign(MemoryRange::mmap(nullptr, size, |
michael@0 | 82 | PROT_READ | (writable ? PROT_WRITE : 0), |
michael@0 | 83 | writable ? MAP_SHARED : MAP_PRIVATE, fd, 0)); |
michael@0 | 84 | return this != MAP_FAILED; |
michael@0 | 85 | } |
michael@0 | 86 | |
michael@0 | 87 | int getFd() |
michael@0 | 88 | { |
michael@0 | 89 | return fd; |
michael@0 | 90 | } |
michael@0 | 91 | |
michael@0 | 92 | private: |
michael@0 | 93 | AutoCloseFD fd; |
michael@0 | 94 | bool writable; |
michael@0 | 95 | }; |
michael@0 | 96 | |
michael@0 | 97 | class FilteredBuffer: public Buffer |
michael@0 | 98 | { |
michael@0 | 99 | public: |
michael@0 | 100 | void Filter(Buffer &other, SeekableZStream::FilterId filter, size_t chunkSize) |
michael@0 | 101 | { |
michael@0 | 102 | SeekableZStream::ZStreamFilter filterCB = |
michael@0 | 103 | SeekableZStream::GetFilter(filter); |
michael@0 | 104 | MOZ_ASSERT(filterCB); |
michael@0 | 105 | Fill(other); |
michael@0 | 106 | size_t size = other.GetLength(); |
michael@0 | 107 | Bytef *data = reinterpret_cast<Bytef *>(static_cast<void *>(*this)); |
michael@0 | 108 | size_t avail = 0; |
michael@0 | 109 | /* Filter needs to be applied in chunks. */ |
michael@0 | 110 | while (size) { |
michael@0 | 111 | avail = std::min(size, chunkSize); |
michael@0 | 112 | filterCB(data - static_cast<unsigned char *>(static_cast<void *>(*this)), |
michael@0 | 113 | SeekableZStream::FILTER, data, avail); |
michael@0 | 114 | size -= avail; |
michael@0 | 115 | data += avail; |
michael@0 | 116 | } |
michael@0 | 117 | } |
michael@0 | 118 | }; |
michael@0 | 119 | |
michael@0 | 120 | template <typename T> |
michael@0 | 121 | class Dictionary: public Buffer |
michael@0 | 122 | { |
michael@0 | 123 | typedef T piece; |
michael@0 | 124 | typedef std::pair<piece, int> stat_pair; |
michael@0 | 125 | |
michael@0 | 126 | static bool stat_cmp(stat_pair a, stat_pair b) |
michael@0 | 127 | { |
michael@0 | 128 | return a.second < b.second; |
michael@0 | 129 | } |
michael@0 | 130 | |
michael@0 | 131 | public: |
michael@0 | 132 | Dictionary(Buffer &inBuf, size_t size) |
michael@0 | 133 | { |
michael@0 | 134 | if (!size || !Resize(size)) |
michael@0 | 135 | return; |
michael@0 | 136 | DEBUG_LOG("Creating dictionary"); |
michael@0 | 137 | piece *origBufPieces = reinterpret_cast<piece *>( |
michael@0 | 138 | static_cast<void *>(inBuf)); |
michael@0 | 139 | std::map<piece, int> stats; |
michael@0 | 140 | for (unsigned int i = 0; i < inBuf.GetLength() / sizeof(piece); i++) { |
michael@0 | 141 | stats[origBufPieces[i]]++; |
michael@0 | 142 | } |
michael@0 | 143 | std::vector<stat_pair> statsVec(stats.begin(), stats.end()); |
michael@0 | 144 | std::sort(statsVec.begin(), statsVec.end(), stat_cmp); |
michael@0 | 145 | |
michael@0 | 146 | piece *dictPieces = reinterpret_cast<piece *>( |
michael@0 | 147 | static_cast<void *>(*this)); |
michael@0 | 148 | typename std::vector<stat_pair>::reverse_iterator it = statsVec.rbegin(); |
michael@0 | 149 | for (int i = size / sizeof(piece); i > 0 && it < statsVec.rend(); |
michael@0 | 150 | i--, ++it) { |
michael@0 | 151 | dictPieces[i - 1] = it->first; |
michael@0 | 152 | } |
michael@0 | 153 | } |
michael@0 | 154 | }; |
michael@0 | 155 | |
michael@0 | 156 | class SzipAction |
michael@0 | 157 | { |
michael@0 | 158 | public: |
michael@0 | 159 | virtual int run(const char *name, Buffer &origBuf, |
michael@0 | 160 | const char *outName, Buffer &outBuf) = 0; |
michael@0 | 161 | |
michael@0 | 162 | virtual ~SzipAction() {} |
michael@0 | 163 | }; |
michael@0 | 164 | |
michael@0 | 165 | class SzipDecompress: public SzipAction |
michael@0 | 166 | { |
michael@0 | 167 | public: |
michael@0 | 168 | int run(const char *name, Buffer &origBuf, |
michael@0 | 169 | const char *outName, Buffer &outBuf); |
michael@0 | 170 | }; |
michael@0 | 171 | |
michael@0 | 172 | |
michael@0 | 173 | class SzipCompress: public SzipAction |
michael@0 | 174 | { |
michael@0 | 175 | public: |
michael@0 | 176 | int run(const char *name, Buffer &origBuf, |
michael@0 | 177 | const char *outName, Buffer &outBuf); |
michael@0 | 178 | |
michael@0 | 179 | SzipCompress(size_t aChunkSize, SeekableZStream::FilterId aFilter, |
michael@0 | 180 | size_t aDictSize) |
michael@0 | 181 | : chunkSize(aChunkSize ? aChunkSize : 16384) |
michael@0 | 182 | , filter(aFilter) |
michael@0 | 183 | , dictSize(aDictSize) |
michael@0 | 184 | {} |
michael@0 | 185 | |
michael@0 | 186 | const static signed char winSizeLog = 15; |
michael@0 | 187 | const static size_t winSize = 1 << winSizeLog; |
michael@0 | 188 | |
michael@0 | 189 | const static SeekableZStream::FilterId DEFAULT_FILTER = |
michael@0 | 190 | #if defined(TARGET_THUMB) |
michael@0 | 191 | SeekableZStream::BCJ_THUMB; |
michael@0 | 192 | #elif defined(TARGET_ARM) |
michael@0 | 193 | SeekableZStream::BCJ_ARM; |
michael@0 | 194 | #elif defined(TARGET_X86) |
michael@0 | 195 | SeekableZStream::BCJ_X86; |
michael@0 | 196 | #else |
michael@0 | 197 | SeekableZStream::NONE; |
michael@0 | 198 | #endif |
michael@0 | 199 | |
michael@0 | 200 | private: |
michael@0 | 201 | |
michael@0 | 202 | int do_compress(Buffer &origBuf, Buffer &outBuf, const unsigned char *aDict, |
michael@0 | 203 | size_t aDictSize, SeekableZStream::FilterId aFilter); |
michael@0 | 204 | |
michael@0 | 205 | size_t chunkSize; |
michael@0 | 206 | SeekableZStream::FilterId filter; |
michael@0 | 207 | size_t dictSize; |
michael@0 | 208 | }; |
michael@0 | 209 | |
michael@0 | 210 | /* Decompress a seekable compressed stream */ |
michael@0 | 211 | int SzipDecompress::run(const char *name, Buffer &origBuf, |
michael@0 | 212 | const char *outName, Buffer &outBuf) |
michael@0 | 213 | { |
michael@0 | 214 | size_t origSize = origBuf.GetLength(); |
michael@0 | 215 | if (origSize < sizeof(SeekableZStreamHeader)) { |
michael@0 | 216 | LOG("%s is not compressed", name); |
michael@0 | 217 | return 0; |
michael@0 | 218 | } |
michael@0 | 219 | |
michael@0 | 220 | SeekableZStream zstream; |
michael@0 | 221 | if (!zstream.Init(origBuf, origSize)) |
michael@0 | 222 | return 0; |
michael@0 | 223 | |
michael@0 | 224 | size_t size = zstream.GetUncompressedSize(); |
michael@0 | 225 | |
michael@0 | 226 | /* Give enough room for the uncompressed data */ |
michael@0 | 227 | if (!outBuf.Resize(size)) { |
michael@0 | 228 | LOG("Error resizing %s: %s", outName, strerror(errno)); |
michael@0 | 229 | return 1; |
michael@0 | 230 | } |
michael@0 | 231 | |
michael@0 | 232 | if (!zstream.Decompress(outBuf, 0, size)) |
michael@0 | 233 | return 1; |
michael@0 | 234 | |
michael@0 | 235 | return 0; |
michael@0 | 236 | } |
michael@0 | 237 | |
michael@0 | 238 | /* Generate a seekable compressed stream. */ |
michael@0 | 239 | int SzipCompress::run(const char *name, Buffer &origBuf, |
michael@0 | 240 | const char *outName, Buffer &outBuf) |
michael@0 | 241 | { |
michael@0 | 242 | size_t origSize = origBuf.GetLength(); |
michael@0 | 243 | if (origSize == 0) { |
michael@0 | 244 | LOG("Won't compress %s: it's empty", name); |
michael@0 | 245 | return 1; |
michael@0 | 246 | } |
michael@0 | 247 | if (SeekableZStreamHeader::validate(origBuf)) { |
michael@0 | 248 | LOG("Skipping %s: it's already a szip", name); |
michael@0 | 249 | return 0; |
michael@0 | 250 | } |
michael@0 | 251 | bool compressed = false; |
michael@0 | 252 | LOG("Size = %" PRIuSize, origSize); |
michael@0 | 253 | |
michael@0 | 254 | /* Allocate a buffer the size of the uncompressed data: we don't want |
michael@0 | 255 | * a compressed file larger than that anyways. */ |
michael@0 | 256 | if (!outBuf.Resize(origSize)) { |
michael@0 | 257 | LOG("Couldn't allocate output buffer: %s", strerror(errno)); |
michael@0 | 258 | return 1; |
michael@0 | 259 | } |
michael@0 | 260 | |
michael@0 | 261 | /* Find the most appropriate filter */ |
michael@0 | 262 | SeekableZStream::FilterId firstFilter, lastFilter; |
michael@0 | 263 | bool scanFilters; |
michael@0 | 264 | if (filter == SeekableZStream::FILTER_MAX) { |
michael@0 | 265 | firstFilter = SeekableZStream::NONE; |
michael@0 | 266 | lastFilter = SeekableZStream::FILTER_MAX; |
michael@0 | 267 | scanFilters = true; |
michael@0 | 268 | } else { |
michael@0 | 269 | firstFilter = lastFilter = filter; |
michael@0 | 270 | ++lastFilter; |
michael@0 | 271 | scanFilters = false; |
michael@0 | 272 | } |
michael@0 | 273 | |
michael@0 | 274 | mozilla::ScopedDeletePtr<Buffer> filteredBuf; |
michael@0 | 275 | Buffer *origData; |
michael@0 | 276 | for (SeekableZStream::FilterId f = firstFilter; f < lastFilter; ++f) { |
michael@0 | 277 | FilteredBuffer *filteredTmp = nullptr; |
michael@0 | 278 | Buffer tmpBuf; |
michael@0 | 279 | if (f != SeekableZStream::NONE) { |
michael@0 | 280 | DEBUG_LOG("Applying filter \"%s\"", filterName[f]); |
michael@0 | 281 | filteredTmp = new FilteredBuffer(); |
michael@0 | 282 | filteredTmp->Filter(origBuf, f, chunkSize); |
michael@0 | 283 | origData = filteredTmp; |
michael@0 | 284 | } else { |
michael@0 | 285 | origData = &origBuf; |
michael@0 | 286 | } |
michael@0 | 287 | if (dictSize && !scanFilters) { |
michael@0 | 288 | filteredBuf = filteredTmp; |
michael@0 | 289 | break; |
michael@0 | 290 | } |
michael@0 | 291 | DEBUG_LOG("Compressing with no dictionary"); |
michael@0 | 292 | if (do_compress(*origData, tmpBuf, nullptr, 0, f) == 0) { |
michael@0 | 293 | if (tmpBuf.GetLength() < outBuf.GetLength()) { |
michael@0 | 294 | outBuf.Fill(tmpBuf); |
michael@0 | 295 | compressed = true; |
michael@0 | 296 | filter = f; |
michael@0 | 297 | filteredBuf = filteredTmp; |
michael@0 | 298 | continue; |
michael@0 | 299 | } |
michael@0 | 300 | } |
michael@0 | 301 | delete filteredTmp; |
michael@0 | 302 | } |
michael@0 | 303 | |
michael@0 | 304 | origData = filteredBuf ? filteredBuf : &origBuf; |
michael@0 | 305 | |
michael@0 | 306 | if (dictSize) { |
michael@0 | 307 | Dictionary<uint64_t> dict(*origData, dictSize ? SzipCompress::winSize : 0); |
michael@0 | 308 | |
michael@0 | 309 | /* Find the most appropriate dictionary size */ |
michael@0 | 310 | size_t firstDictSize, lastDictSize; |
michael@0 | 311 | if (dictSize == (size_t) -1) { |
michael@0 | 312 | /* If we scanned for filters, we effectively already tried dictSize=0 */ |
michael@0 | 313 | firstDictSize = scanFilters ? 4096 : 0; |
michael@0 | 314 | lastDictSize = SzipCompress::winSize; |
michael@0 | 315 | } else { |
michael@0 | 316 | firstDictSize = lastDictSize = dictSize; |
michael@0 | 317 | } |
michael@0 | 318 | |
michael@0 | 319 | Buffer tmpBuf; |
michael@0 | 320 | for (size_t d = firstDictSize; d <= lastDictSize; d += 4096) { |
michael@0 | 321 | DEBUG_LOG("Compressing with dictionary of size %" PRIuSize, d); |
michael@0 | 322 | if (do_compress(*origData, tmpBuf, static_cast<unsigned char *>(dict) |
michael@0 | 323 | + SzipCompress::winSize - d, d, filter)) |
michael@0 | 324 | continue; |
michael@0 | 325 | if (!compressed || tmpBuf.GetLength() < outBuf.GetLength()) { |
michael@0 | 326 | outBuf.Fill(tmpBuf); |
michael@0 | 327 | compressed = true; |
michael@0 | 328 | dictSize = d; |
michael@0 | 329 | } |
michael@0 | 330 | } |
michael@0 | 331 | } |
michael@0 | 332 | |
michael@0 | 333 | if (!compressed) { |
michael@0 | 334 | outBuf.Fill(origBuf); |
michael@0 | 335 | LOG("Not compressed"); |
michael@0 | 336 | return 0; |
michael@0 | 337 | } |
michael@0 | 338 | |
michael@0 | 339 | if (dictSize == (size_t) -1) |
michael@0 | 340 | dictSize = 0; |
michael@0 | 341 | |
michael@0 | 342 | DEBUG_LOG("Used filter \"%s\" and dictionary size of %" PRIuSize, |
michael@0 | 343 | filterName[filter], dictSize); |
michael@0 | 344 | LOG("Compressed size is %" PRIuSize, outBuf.GetLength()); |
michael@0 | 345 | |
michael@0 | 346 | /* Sanity check */ |
michael@0 | 347 | Buffer tmpBuf; |
michael@0 | 348 | SzipDecompress decompress; |
michael@0 | 349 | if (decompress.run("buffer", outBuf, "buffer", tmpBuf)) |
michael@0 | 350 | return 1; |
michael@0 | 351 | |
michael@0 | 352 | size_t size = tmpBuf.GetLength(); |
michael@0 | 353 | if (size != origSize) { |
michael@0 | 354 | LOG("Compression error: %" PRIuSize " != %" PRIuSize, size, origSize); |
michael@0 | 355 | return 1; |
michael@0 | 356 | } |
michael@0 | 357 | if (memcmp(static_cast<void *>(origBuf), static_cast<void *>(tmpBuf), size)) { |
michael@0 | 358 | LOG("Compression error: content mismatch"); |
michael@0 | 359 | return 1; |
michael@0 | 360 | } |
michael@0 | 361 | return 0; |
michael@0 | 362 | } |
michael@0 | 363 | |
michael@0 | 364 | int SzipCompress::do_compress(Buffer &origBuf, Buffer &outBuf, |
michael@0 | 365 | const unsigned char *aDict, size_t aDictSize, |
michael@0 | 366 | SeekableZStream::FilterId aFilter) |
michael@0 | 367 | { |
michael@0 | 368 | size_t origSize = origBuf.GetLength(); |
michael@0 | 369 | MOZ_ASSERT(origSize != 0); |
michael@0 | 370 | |
michael@0 | 371 | /* Expected total number of chunks */ |
michael@0 | 372 | size_t nChunks = ((origSize + chunkSize - 1) / chunkSize); |
michael@0 | 373 | |
michael@0 | 374 | /* The first chunk is going to be stored after the header, the dictionary |
michael@0 | 375 | * and the offset table */ |
michael@0 | 376 | size_t offset = sizeof(SeekableZStreamHeader) + aDictSize |
michael@0 | 377 | + nChunks * sizeof(uint32_t); |
michael@0 | 378 | |
michael@0 | 379 | if (offset >= origSize) |
michael@0 | 380 | return 1; |
michael@0 | 381 | |
michael@0 | 382 | /* Allocate a buffer the size of the uncompressed data: we don't want |
michael@0 | 383 | * a compressed file larger than that anyways. */ |
michael@0 | 384 | if (!outBuf.Resize(origSize)) { |
michael@0 | 385 | LOG("Couldn't allocate output buffer: %s", strerror(errno)); |
michael@0 | 386 | return 1; |
michael@0 | 387 | } |
michael@0 | 388 | |
michael@0 | 389 | SeekableZStreamHeader *header = new (outBuf) SeekableZStreamHeader; |
michael@0 | 390 | unsigned char *dictionary = static_cast<unsigned char *>( |
michael@0 | 391 | outBuf + sizeof(SeekableZStreamHeader)); |
michael@0 | 392 | le_uint32 *entry = |
michael@0 | 393 | reinterpret_cast<le_uint32 *>(dictionary + aDictSize); |
michael@0 | 394 | |
michael@0 | 395 | /* Initialize header */ |
michael@0 | 396 | header->chunkSize = chunkSize; |
michael@0 | 397 | header->dictSize = aDictSize; |
michael@0 | 398 | header->totalSize = offset; |
michael@0 | 399 | header->windowBits = -SzipCompress::winSizeLog; // Raw stream, |
michael@0 | 400 | // window size of 32k. |
michael@0 | 401 | header->filter = aFilter; |
michael@0 | 402 | if (aDictSize) |
michael@0 | 403 | memcpy(dictionary, aDict, aDictSize); |
michael@0 | 404 | |
michael@0 | 405 | /* Initialize zlib structure */ |
michael@0 | 406 | z_stream zStream; |
michael@0 | 407 | memset(&zStream, 0, sizeof(zStream)); |
michael@0 | 408 | zStream.avail_out = origSize - offset; |
michael@0 | 409 | zStream.next_out = static_cast<Bytef*>(outBuf) + offset; |
michael@0 | 410 | |
michael@0 | 411 | size_t avail = 0; |
michael@0 | 412 | size_t size = origSize; |
michael@0 | 413 | unsigned char *data = reinterpret_cast<unsigned char *>( |
michael@0 | 414 | static_cast<void *>(origBuf)); |
michael@0 | 415 | while (size) { |
michael@0 | 416 | avail = std::min(size, chunkSize); |
michael@0 | 417 | |
michael@0 | 418 | /* Compress chunk */ |
michael@0 | 419 | int ret = deflateInit2(&zStream, 9, Z_DEFLATED, header->windowBits, |
michael@0 | 420 | MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY); |
michael@0 | 421 | if (aDictSize) |
michael@0 | 422 | deflateSetDictionary(&zStream, dictionary, aDictSize); |
michael@0 | 423 | MOZ_ASSERT(ret == Z_OK); |
michael@0 | 424 | zStream.avail_in = avail; |
michael@0 | 425 | zStream.next_in = data; |
michael@0 | 426 | ret = deflate(&zStream, Z_FINISH); |
michael@0 | 427 | MOZ_ASSERT(ret == Z_STREAM_END); |
michael@0 | 428 | ret = deflateEnd(&zStream); |
michael@0 | 429 | MOZ_ASSERT(ret == Z_OK); |
michael@0 | 430 | if (zStream.avail_out <= 0) |
michael@0 | 431 | return 1; |
michael@0 | 432 | |
michael@0 | 433 | size_t len = origSize - offset - zStream.avail_out; |
michael@0 | 434 | |
michael@0 | 435 | /* Adjust headers */ |
michael@0 | 436 | header->totalSize += len; |
michael@0 | 437 | *entry++ = offset; |
michael@0 | 438 | header->nChunks++; |
michael@0 | 439 | |
michael@0 | 440 | /* Prepare for next iteration */ |
michael@0 | 441 | size -= avail; |
michael@0 | 442 | data += avail; |
michael@0 | 443 | offset += len; |
michael@0 | 444 | } |
michael@0 | 445 | header->lastChunkSize = avail; |
michael@0 | 446 | MOZ_ASSERT(header->totalSize == offset); |
michael@0 | 447 | MOZ_ASSERT(header->nChunks == nChunks); |
michael@0 | 448 | |
michael@0 | 449 | if (!outBuf.Resize(offset)) { |
michael@0 | 450 | LOG("Error truncating output: %s", strerror(errno)); |
michael@0 | 451 | return 1; |
michael@0 | 452 | } |
michael@0 | 453 | |
michael@0 | 454 | return 0; |
michael@0 | 455 | |
michael@0 | 456 | } |
michael@0 | 457 | |
michael@0 | 458 | bool GetSize(const char *str, size_t *out) |
michael@0 | 459 | { |
michael@0 | 460 | char *end; |
michael@0 | 461 | MOZ_ASSERT(out); |
michael@0 | 462 | errno = 0; |
michael@0 | 463 | *out = strtol(str, &end, 10); |
michael@0 | 464 | return (!errno && !*end); |
michael@0 | 465 | } |
michael@0 | 466 | |
michael@0 | 467 | int main(int argc, char* argv[]) |
michael@0 | 468 | { |
michael@0 | 469 | mozilla::ScopedDeletePtr<SzipAction> action; |
michael@0 | 470 | char **firstArg; |
michael@0 | 471 | bool compress = true; |
michael@0 | 472 | size_t chunkSize = 0; |
michael@0 | 473 | SeekableZStream::FilterId filter = SzipCompress::DEFAULT_FILTER; |
michael@0 | 474 | size_t dictSize = (size_t) 0; |
michael@0 | 475 | |
michael@0 | 476 | Logging::Init(); |
michael@0 | 477 | |
michael@0 | 478 | for (firstArg = &argv[1]; argc > 2; argc--, firstArg++) { |
michael@0 | 479 | if (!firstArg[0] || firstArg[0][0] != '-') |
michael@0 | 480 | break; |
michael@0 | 481 | if (strcmp(firstArg[0], "-d") == 0) { |
michael@0 | 482 | compress = false; |
michael@0 | 483 | } else if (strcmp(firstArg[0], "-c") == 0) { |
michael@0 | 484 | firstArg++; |
michael@0 | 485 | argc--; |
michael@0 | 486 | if (!firstArg[0]) |
michael@0 | 487 | break; |
michael@0 | 488 | if (!GetSize(firstArg[0], &chunkSize) || !chunkSize || |
michael@0 | 489 | (chunkSize % 4096) || (chunkSize > maxChunkSize)) { |
michael@0 | 490 | LOG("Invalid chunk size"); |
michael@0 | 491 | return 1; |
michael@0 | 492 | } |
michael@0 | 493 | } else if (strcmp(firstArg[0], "-f") == 0) { |
michael@0 | 494 | firstArg++; |
michael@0 | 495 | argc--; |
michael@0 | 496 | if (!firstArg[0]) |
michael@0 | 497 | break; |
michael@0 | 498 | bool matched = false; |
michael@0 | 499 | for (unsigned int i = 0; i < sizeof(filterName) / sizeof(char *); ++i) { |
michael@0 | 500 | if (strcmp(firstArg[0], filterName[i]) == 0) { |
michael@0 | 501 | filter = static_cast<SeekableZStream::FilterId>(i); |
michael@0 | 502 | matched = true; |
michael@0 | 503 | break; |
michael@0 | 504 | } |
michael@0 | 505 | } |
michael@0 | 506 | if (!matched) { |
michael@0 | 507 | LOG("Invalid filter"); |
michael@0 | 508 | return 1; |
michael@0 | 509 | } |
michael@0 | 510 | } else if (strcmp(firstArg[0], "-D") == 0) { |
michael@0 | 511 | firstArg++; |
michael@0 | 512 | argc--; |
michael@0 | 513 | if (!firstArg[0]) |
michael@0 | 514 | break; |
michael@0 | 515 | if (strcmp(firstArg[0], "auto") == 0) { |
michael@0 | 516 | dictSize = -1; |
michael@0 | 517 | } else if (!GetSize(firstArg[0], &dictSize) || (dictSize >= 1 << 16)) { |
michael@0 | 518 | LOG("Invalid dictionary size"); |
michael@0 | 519 | return 1; |
michael@0 | 520 | } |
michael@0 | 521 | } |
michael@0 | 522 | } |
michael@0 | 523 | |
michael@0 | 524 | if (argc != 2 || !firstArg[0]) { |
michael@0 | 525 | LOG("usage: %s [-d] [-c CHUNKSIZE] [-f FILTER] [-D DICTSIZE] file", |
michael@0 | 526 | argv[0]); |
michael@0 | 527 | return 1; |
michael@0 | 528 | } |
michael@0 | 529 | |
michael@0 | 530 | if (compress) { |
michael@0 | 531 | action = new SzipCompress(chunkSize, filter, dictSize); |
michael@0 | 532 | } else { |
michael@0 | 533 | if (chunkSize) { |
michael@0 | 534 | LOG("-c is incompatible with -d"); |
michael@0 | 535 | return 1; |
michael@0 | 536 | } |
michael@0 | 537 | if (dictSize) { |
michael@0 | 538 | LOG("-D is incompatible with -d"); |
michael@0 | 539 | return 1; |
michael@0 | 540 | } |
michael@0 | 541 | action = new SzipDecompress(); |
michael@0 | 542 | } |
michael@0 | 543 | |
michael@0 | 544 | std::stringstream tmpOutStream; |
michael@0 | 545 | tmpOutStream << firstArg[0] << ".sz." << getpid(); |
michael@0 | 546 | std::string tmpOut(tmpOutStream.str()); |
michael@0 | 547 | int ret; |
michael@0 | 548 | struct stat st; |
michael@0 | 549 | { |
michael@0 | 550 | FileBuffer origBuf; |
michael@0 | 551 | if (!origBuf.Init(firstArg[0])) { |
michael@0 | 552 | LOG("Couldn't open %s: %s", firstArg[0], strerror(errno)); |
michael@0 | 553 | return 1; |
michael@0 | 554 | } |
michael@0 | 555 | |
michael@0 | 556 | ret = fstat(origBuf.getFd(), &st); |
michael@0 | 557 | if (ret == -1) { |
michael@0 | 558 | LOG("Couldn't stat %s: %s", firstArg[0], strerror(errno)); |
michael@0 | 559 | return 1; |
michael@0 | 560 | } |
michael@0 | 561 | |
michael@0 | 562 | size_t origSize = st.st_size; |
michael@0 | 563 | |
michael@0 | 564 | /* Mmap the original file */ |
michael@0 | 565 | if (!origBuf.Resize(origSize)) { |
michael@0 | 566 | LOG("Couldn't mmap %s: %s", firstArg[0], strerror(errno)); |
michael@0 | 567 | return 1; |
michael@0 | 568 | } |
michael@0 | 569 | |
michael@0 | 570 | /* Create the compressed file */ |
michael@0 | 571 | FileBuffer outBuf; |
michael@0 | 572 | if (!outBuf.Init(tmpOut.c_str(), true)) { |
michael@0 | 573 | LOG("Couldn't open %s: %s", tmpOut.c_str(), strerror(errno)); |
michael@0 | 574 | return 1; |
michael@0 | 575 | } |
michael@0 | 576 | |
michael@0 | 577 | ret = action->run(firstArg[0], origBuf, tmpOut.c_str(), outBuf); |
michael@0 | 578 | if ((ret == 0) && (fstat(outBuf.getFd(), &st) == -1)) { |
michael@0 | 579 | st.st_size = 0; |
michael@0 | 580 | } |
michael@0 | 581 | } |
michael@0 | 582 | |
michael@0 | 583 | if ((ret == 0) && st.st_size) { |
michael@0 | 584 | rename(tmpOut.c_str(), firstArg[0]); |
michael@0 | 585 | } else { |
michael@0 | 586 | unlink(tmpOut.c_str()); |
michael@0 | 587 | } |
michael@0 | 588 | return ret; |
michael@0 | 589 | } |