mozglue/linker/Zip.cpp

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/mozglue/linker/Zip.cpp	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,213 @@
     1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this file,
     1.6 + * You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +
     1.8 +#include <sys/mman.h>
     1.9 +#include <sys/stat.h>
    1.10 +#include <fcntl.h>
    1.11 +#include <errno.h>
    1.12 +#include <unistd.h>
    1.13 +#include <cstdlib>
    1.14 +#include <algorithm>
    1.15 +#include "Logging.h"
    1.16 +#include "Zip.h"
    1.17 +
    1.18 +mozilla::TemporaryRef<Zip>
    1.19 +Zip::Create(const char *filename)
    1.20 +{
    1.21 +  /* Open and map the file in memory */
    1.22 +  AutoCloseFD fd(open(filename, O_RDONLY));
    1.23 +  if (fd == -1) {
    1.24 +    LOG("Error opening %s: %s", filename, strerror(errno));
    1.25 +    return nullptr;
    1.26 +  }
    1.27 +  struct stat st;
    1.28 +  if (fstat(fd, &st) == -1) {
    1.29 +    LOG("Error stating %s: %s", filename, strerror(errno));
    1.30 +    return nullptr;
    1.31 +  }
    1.32 +  size_t size = st.st_size;
    1.33 +  if (size <= sizeof(CentralDirectoryEnd)) {
    1.34 +    LOG("Error reading %s: too short", filename);
    1.35 +    return nullptr;
    1.36 +  }
    1.37 +  void *mapped = mmap(nullptr, size, PROT_READ, MAP_SHARED, fd, 0);
    1.38 +  if (mapped == MAP_FAILED) {
    1.39 +    LOG("Error mmapping %s: %s", filename, strerror(errno));
    1.40 +    return nullptr;
    1.41 +  }
    1.42 +  DEBUG_LOG("Mapped %s @%p", filename, mapped);
    1.43 +
    1.44 +  return Create(filename, mapped, size);
    1.45 +}
    1.46 +
    1.47 +mozilla::TemporaryRef<Zip>
    1.48 +Zip::Create(const char *filename, void *mapped, size_t size)
    1.49 +{
    1.50 +  mozilla::RefPtr<Zip> zip = new Zip(filename, mapped, size);
    1.51 +
    1.52 +  // If neither the first Local File entry nor central directory entries
    1.53 +  // have been found, the zip was invalid.
    1.54 +  if (!zip->nextFile && !zip->entries) {
    1.55 +    LOG("%s - Invalid zip", filename);
    1.56 +    return nullptr;
    1.57 +  }
    1.58 +
    1.59 +  ZipCollection::Singleton.Register(zip);
    1.60 +  return zip;
    1.61 +}
    1.62 +
    1.63 +Zip::Zip(const char *filename, void *mapped, size_t size)
    1.64 +: name(filename ? strdup(filename) : nullptr)
    1.65 +, mapped(mapped)
    1.66 +, size(size)
    1.67 +, nextFile(LocalFile::validate(mapped)) // first Local File entry
    1.68 +, nextDir(nullptr)
    1.69 +, entries(nullptr)
    1.70 +{
    1.71 +  // If the first local file entry couldn't be found (which can happen
    1.72 +  // with optimized jars), check the first central directory entry.
    1.73 +  if (!nextFile)
    1.74 +    GetFirstEntry();
    1.75 +}
    1.76 +
    1.77 +Zip::~Zip()
    1.78 +{
    1.79 +  ZipCollection::Forget(this);
    1.80 +  if (name) {
    1.81 +    munmap(mapped, size);
    1.82 +    DEBUG_LOG("Unmapped %s @%p", name, mapped);
    1.83 +    free(name);
    1.84 +  }
    1.85 +}
    1.86 +
    1.87 +bool
    1.88 +Zip::GetStream(const char *path, Zip::Stream *out) const
    1.89 +{
    1.90 +  DEBUG_LOG("%s - GetFile %s", name, path);
    1.91 +  /* Fast path: if the Local File header on store matches, we can return the
    1.92 +   * corresponding stream right away.
    1.93 +   * However, the Local File header may not contain enough information, in
    1.94 +   * which case the 3rd bit on the generalFlag is set. Unfortunately, this
    1.95 +   * bit is also set in some archives even when we do have the data (most
    1.96 +   * notably the android packages as built by the Mozilla build system).
    1.97 +   * So instead of testing the generalFlag bit, only use the fast path when
    1.98 +   * we haven't read the central directory entries yet, and when the
    1.99 +   * compressed size as defined in the header is not filled (which is a
   1.100 +   * normal condition for the bit to be set). */
   1.101 +  if (nextFile && nextFile->GetName().Equals(path) &&
   1.102 +      !entries && (nextFile->compressedSize != 0)) {
   1.103 +    DEBUG_LOG("%s - %s was next file: fast path", name, path);
   1.104 +    /* Fill Stream info from Local File header content */
   1.105 +    const char *data = reinterpret_cast<const char *>(nextFile->GetData());
   1.106 +    out->compressedBuf = data;
   1.107 +    out->compressedSize = nextFile->compressedSize;
   1.108 +    out->uncompressedSize = nextFile->uncompressedSize;
   1.109 +    out->type = static_cast<Stream::Type>(uint16_t(nextFile->compression));
   1.110 +
   1.111 +    /* Find the next Local File header. It is usually simply following the
   1.112 +     * compressed stream, but in cases where the 3rd bit of the generalFlag
   1.113 +     * is set, there is a Data Descriptor header before. */
   1.114 +    data += nextFile->compressedSize;
   1.115 +    if ((nextFile->generalFlag & 0x8) && DataDescriptor::validate(data)) {
   1.116 +      data += sizeof(DataDescriptor);
   1.117 +    }
   1.118 +    nextFile = LocalFile::validate(data);
   1.119 +    return true;
   1.120 +  }
   1.121 +
   1.122 +  /* If the directory entry we have in store doesn't match, scan the Central
   1.123 +   * Directory for the entry corresponding to the given path */
   1.124 +  if (!nextDir || !nextDir->GetName().Equals(path)) {
   1.125 +    const DirectoryEntry *entry = GetFirstEntry();
   1.126 +    DEBUG_LOG("%s - Scan directory entries in search for %s", name, path);
   1.127 +    while (entry && !entry->GetName().Equals(path)) {
   1.128 +      entry = entry->GetNext();
   1.129 +    }
   1.130 +    nextDir = entry;
   1.131 +  }
   1.132 +  if (!nextDir) {
   1.133 +    DEBUG_LOG("%s - Couldn't find %s", name, path);
   1.134 +    return false;
   1.135 +  }
   1.136 +
   1.137 +  /* Find the Local File header corresponding to the Directory entry that
   1.138 +   * was found. */
   1.139 +  nextFile = LocalFile::validate(static_cast<const char *>(mapped)
   1.140 +                             + nextDir->offset);
   1.141 +  if (!nextFile) {
   1.142 +    LOG("%s - Couldn't find the Local File header for %s", name, path);
   1.143 +    return false;
   1.144 +  }
   1.145 +
   1.146 +  /* Fill Stream info from Directory entry content */
   1.147 +  const char *data = reinterpret_cast<const char *>(nextFile->GetData());
   1.148 +  out->compressedBuf = data;
   1.149 +  out->compressedSize = nextDir->compressedSize;
   1.150 +  out->uncompressedSize = nextDir->uncompressedSize;
   1.151 +  out->type = static_cast<Stream::Type>(uint16_t(nextDir->compression));
   1.152 +
   1.153 +  /* Store the next directory entry */
   1.154 +  nextDir = nextDir->GetNext();
   1.155 +  nextFile = nullptr;
   1.156 +  return true;
   1.157 +}
   1.158 +
   1.159 +const Zip::DirectoryEntry *
   1.160 +Zip::GetFirstEntry() const
   1.161 +{
   1.162 +  if (entries)
   1.163 +    return entries;
   1.164 +
   1.165 +  const CentralDirectoryEnd *end = nullptr;
   1.166 +  const char *_end = static_cast<const char *>(mapped) + size
   1.167 +                     - sizeof(CentralDirectoryEnd);
   1.168 +
   1.169 +  /* Scan for the Central Directory End */
   1.170 +  for (; _end > mapped && !end; _end--)
   1.171 +    end = CentralDirectoryEnd::validate(_end);
   1.172 +  if (!end) {
   1.173 +    LOG("%s - Couldn't find end of central directory record", name);
   1.174 +    return nullptr;
   1.175 +  }
   1.176 +
   1.177 +  entries = DirectoryEntry::validate(static_cast<const char *>(mapped)
   1.178 +                                 + end->offset);
   1.179 +  if (!entries) {
   1.180 +    LOG("%s - Couldn't find central directory record", name);
   1.181 +  }
   1.182 +  return entries;
   1.183 +}
   1.184 +
   1.185 +ZipCollection ZipCollection::Singleton;
   1.186 +
   1.187 +mozilla::TemporaryRef<Zip>
   1.188 +ZipCollection::GetZip(const char *path)
   1.189 +{
   1.190 +  /* Search the list of Zips we already have for a match */
   1.191 +  for (std::vector<Zip *>::iterator it = Singleton.zips.begin();
   1.192 +       it < Singleton.zips.end(); ++it) {
   1.193 +    if ((*it)->GetName() && (strcmp((*it)->GetName(), path) == 0))
   1.194 +      return *it;
   1.195 +  }
   1.196 +  return Zip::Create(path);
   1.197 +}
   1.198 +
   1.199 +void
   1.200 +ZipCollection::Register(Zip *zip)
   1.201 +{
   1.202 +  Singleton.zips.push_back(zip);
   1.203 +}
   1.204 +
   1.205 +void
   1.206 +ZipCollection::Forget(Zip *zip)
   1.207 +{
   1.208 +  DEBUG_LOG("ZipCollection::Forget(\"%s\")", zip->GetName());
   1.209 +  std::vector<Zip *>::iterator it = std::find(Singleton.zips.begin(),
   1.210 +                                              Singleton.zips.end(), zip);
   1.211 +  if (*it == zip) {
   1.212 +    Singleton.zips.erase(it);
   1.213 +  } else {
   1.214 +    DEBUG_LOG("ZipCollection::Forget: didn't find \"%s\" in bookkeeping", zip->GetName());
   1.215 +  }
   1.216 +}

mercurial