text: /*
     +----------------------------------------------------------------------+
     | HipHop for PHP                                                       |
     +----------------------------------------------------------------------+
     | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com)  |
     +----------------------------------------------------------------------+
     | This source file is subject to version 3.01 of the PHP license,      |
     | that is bundled with this package in the file LICENSE, and is        |
     | available through the world-wide-web at the following url:           |
     | http://www.php.net/license/3_01.txt                                  |
     | If you did not receive a copy of the PHP license and are unable to   |
     | obtain it through the world-wide-web, please send a note to          |
     | license@php.net so we can mail you a copy immediately.               |
     +----------------------------------------------------------------------+
  */
  
  #include "hphp/util/cache/cache-manager.h"
  
  #include <fcntl.h>
  #include <sys/stat.h>
  #include <sys/types.h>
  
  #include <cstdint>
  #include <map>
  #include <memory>
  #include <set>
  #include <string>
  #include <utility>
  #include <vector>
  
  #include "hphp/util/cache/cache-data.h"
  #include "hphp/util/cache/cache-saver.h"
  #include "hphp/util/cache/magic-numbers.h"
  #include "hphp/util/cache/mmap-file.h"
  #include "hphp/util/logger.h"
  #include "hphp/util/text-util.h"
  
  #include <folly/portability/SysMman.h>
  
  namespace HPHP {
  
  std::function<void(bool, const std::string&)> CacheManager::s_logger;
  
  CacheManager::CacheManager() {}
  CacheManager::~CacheManager() {}
  
  bool CacheManager::getFileContents(const std::string& name, const char** data,
                                     uint64_t* data_len,
                                     bool* compressed) const {
    const auto it = m_cache_map.find(name);
  
    if (it == m_cache_map.end()) {
      return false;
    }
  
    if (s_logger && !it->second->dataFetched()) {
      s_logger(false, name);
    }
  
    const CacheData& cd = *it->second;
    return cd.getDataPointer(data, data_len, compressed);
  }
  
  bool CacheManager::getDecompressed(const std::string& name,
                                     std::string* data) const {
    const auto it = m_cache_map.find(name);
  
    if (it == m_cache_map.end()) {
      return false;
    }
  
    const CacheData& cd = *it->second;
    return cd.getDecompressedData(data);
  }
  
  bool CacheManager::isCompressed(const std::string& name) const {
    const auto it = m_cache_map.find(name);
  
    if (it == m_cache_map.end()) {
      return false;
    }
  
    const CacheData& cd = *it->second;
    return cd.isCompressed();
  }
  
  bool CacheManager::addFileContents(const std::string& name,
                                     const std::string& path) {
    if (m_cache_map.count(name)) {
      return false;
    }
  
    std::unique_ptr<CacheData> cd(new CacheData);
  
    if (!cd->loadFromFile(name, m_entry_counter++, path)) {
      return false;
    }
  
    m_cache_map[name] = std::move(cd);
  
    addDirectories(name);
  
    return true;
  }
  
  bool CacheManager::addEmptyEntry(const std::string& name) {
    if (m_cache_map.count(name)) {
      return false;
    }
  
    std::unique_ptr<CacheData> cd(new CacheData);
    cd->createEmpty(name, m_entry_counter++);
  
    m_cache_map[name] = std::move(cd);
  
    addDirectories(name);
  
    return true;
  }
  
  template<class F>
  bool CacheManager::existsHelper(const std::string& name, F fn) const {
    auto it = m_cache_map.find(name);
    if (it == m_cache_map.end()) return false;
  
    auto cd = it->second.get();
  
    if (!fn(cd)) return false;
  
    if (s_logger && !cd->existChecked()) {
      s_logger(true, name);
    }
  
    return true;
  }
  
  bool CacheManager::entryExists(const std::string& name) const {
    return existsHelper(name, [] (CacheData*) { return true; });
  }
  
  bool CacheManager::fileExists(const std::string& name) const {
    return existsHelper(name, [] (CacheData* cd) {
        return cd->isRegularFile();
      });
  }
  
  bool CacheManager::dirExists(const std::string& name) const {
    return existsHelper(name, [] (CacheData* cd) {
        return cd->isDirectory();
      });
  }
  
  bool CacheManager::emptyEntryExists(const std::string& name) const {
    return existsHelper(name, [] (CacheData* cd) {
        return cd->isEmpty();
      });
  }
  
  bool CacheManager::getUncompressedFileSize(const std::string& name,
                                             uint64_t* size) const {
    const auto it = m_cache_map.find(name);
  
    if (it == m_cache_map.end()) {
      return false;
    }
  
    const CacheData& cd = *it->second;
  
    if (!cd.isRegularFile()) {
      return false;
    }
  
    if (!cd.isCompressed()) {
      *size = cd.fileSize();
      return true;
    }
  
    std::string data;
  
    if (!cd.getDecompressedData(&data)) {
      return false;
    }
  
    *size = data.length();
    return true;
  }
  
  bool CacheManager::loadCache(const std::string& path) {
    m_mmap_file.reset(new MmapFile(path));
  
    if (!m_mmap_file->init()) {
      Logger::Error("Unable to mmap cache file " + path);
      return false;
    }
  
    uint64_t magic;
    if (!m_mmap_file->readUInt64(&magic)) {
      Logger::Error("Unable to read magic number from " + path);
      return false;
    }
  
    if (magic != kCacheFileMagic) {
      Logger::Error("Bad magic number in " + path);
      return false;
    }
  
    uint64_t dirlen;
    if (!m_mmap_file->readUInt64(&dirlen)) {
      return false;
    }
  
    for (uint64_t i = 0; i < dirlen; ++i) {
      std::unique_ptr<CacheData> cd(new CacheData);
  
      std::string name;
      if (!cd->loadFromMmap(m_mmap_file.get(), &name)) {
        return false;
      }
  
      m_cache_map[name] = std::move(cd);
    }
  
    uint64_t dirterm;
    if (!m_mmap_file->readUInt64(&dirterm)) {
      return false;
    }
  
    if (dirterm != kDirectoryTerminatorMagic) {
      Logger::Error("Bad end-of-directory magic number in " + path);
      return false;
    }
  
    return true;
  }
  
  bool CacheManager::saveCache(const std::string& path) const {
    CacheSaver cs(path);
  
    if (!cs.init(kCacheFileMagic, m_cache_map.size())) {
      Logger::Error("Unable to initialize CacheSaver");
      return false;
    }
  
    for (const auto& it : m_cache_map) {
      const std::string& name = it.first;
      const CacheData* cd = it.second.get();
  
      if (!cd->save(&cs)) {
        Logger::Error("Failed to save CacheData for " + name);
        return false;
      }
    }
  
    if (!cs.endDirectory(kDirectoryTerminatorMagic)) {
      Logger::Error("Unable to write directory terminator");
      return false;
    }
  
    if (!cs.writeFiles()) {
      Logger::Error("Unable to write files");
      return false;
    }
  
    if (!cs.rewriteDirectory(m_cache_map.size())) {
      Logger::Error("Unable to rewrite directory");
      return false;
    }
  
    if (!cs.finish()) {
      Logger::Error("CacheSaver finish failed");
      return false;
    }
  
    return true;
  }
  
  void CacheManager::getEntryNames(std::set<std::string>* names) const {
    std::set<std::string> temp;
  
    for (const auto& it : m_cache_map) {
      temp.insert(it.first);
    }
  
    *names = temp;
  }
  
  // --- Private functions.
  
  void CacheManager::addDirectories(const std::string& path) {
  
    if (path.empty()) {
      return;
    }
  
    size_t start = 0;
    for (size_t i = 1; i < path.length(); ++i) {
      if (path[i] == '/') {
        auto pathdir = path.substr(start, i);
        std::unique_ptr<CacheData> cd(new CacheData);
        cd->createDirectory(pathdir, m_entry_counter++);
        m_cache_map[pathdir] = std::move(cd);
      }
    }
  }
  
  VFileType CacheManager::getFileType(const std::string& name) const {
    auto file = m_cache_map.find(name);
  
    if (file == m_cache_map.end()) return VFileType::NotFound;
    if (file->second->isDirectory()) return VFileType::Directory;
    return VFileType::PlainFile;
  }
  
  std::vector<std::string>
  CacheManager::readDirectory(const std::string& name) const {
    auto start = m_cache_map.find(name);
    if (start == m_cache_map.end() || !start->second->isDirectory()) {
      return std::vector<std::string>{};
    }
  
    std::set<std::string> ret;
    for (; start != m_cache_map.end() && start->first.find(name) == 0; ++start) {
      // Remove Prefix
      folly::StringPiece sp = folly::StringPiece(start->first, name.size());
  
      // Removing leading '/'
      while (sp.size() && sp.front() == '/') sp.pop_front();
  
      // Skip subpaths
      if (!sp.size() || sp.find_first_of('/') != std::string::npos) continue;
  
      ret.emplace(sp.str());
    }
  
    return std::vector<std::string>(ret.begin(), ret.end());
  }
  
  void CacheManager::dump() const {
    for (const auto& it : m_cache_map ) {
      printf("- %s\n", it.first.c_str());
      it.second->dump();
    }
  }
  
  }  // namespace HPHP
  