Example #1
0
PhpFile *FileRepository::checkoutFile(const std::string &rname, const struct stat &s) {
  PhpFile *ret = NULL;
  Lock lock(s_lock);
  string name;

  if (rname[0] == '/') {
    name = rname;
  } else if (RuntimeOption::SourceRoot.empty()) {
    name = Process::GetCurrentDirectory() + "/" + rname;
  } else {
    name = RuntimeOption::SourceRoot + "/" + rname;
  }

  hphp_hash_map<string, PhpFile*, string_hash>::iterator it =
    m_files.find(name);
  if (it == m_files.end()) {
    ret = readFile(name, s);
    if (ret) {
      m_files[name] = ret;
    }
  } else {
    if (it->second->isChanged(s)) {
      ret = readFile(name, s);
      if (ret) {
        it->second->decRef();
        it->second = ret;
      }
    } else {
      ret = it->second;
    }
  }
  if (ret) ret->incRef();
  return ret;
}
PhpFile *FileRepository::readFile(const string &name,
                                  const struct stat &s,
                                  bool &created) {
  created = false;
  vector<StaticStatementPtr> sts;
  Block::VariableIndices variableIndices;
  if (s.st_size > StringData::LenMask) {
    throw FatalErrorException(0, "file %s is too big", name.c_str());
  }
  int fileSize = s.st_size;
  int fd = open(name.c_str(), O_RDONLY);
  if (!fd) return NULL; // ignore file open exception
  char *input = (char *)malloc(fileSize + 1);
  if (!input) return NULL;
  int nbytes = read(fd, input, fileSize);
  close(fd);
  input[fileSize] = 0;
  String strHelper(input, fileSize, AttachString);
  if (nbytes != fileSize) return NULL;

  string md5;
  string relPath;
  string srcRoot;
  if (RuntimeOption::SandboxCheckMd5) {
    md5 = StringUtil::MD5(strHelper).c_str();
    srcRoot = SourceRootInfo::GetCurrentSourceRoot();
    if (srcRoot.empty()) srcRoot = RuntimeOption::SourceRoot;
    int srcRootLen = srcRoot.size();
    if (srcRootLen) {
      if (!strncmp(name.c_str(), srcRoot.c_str(), srcRootLen)) {
        relPath = string(name.c_str() + srcRootLen);
      }
    }
    hphp_hash_map<string, PhpFile*, string_hash>::iterator it =
      s_md5Files.find(md5);
    if (it != s_md5Files.end()) {
      PhpFile *f = it->second;
      if (!relPath.empty() && relPath == f->getRelPath()) {
        return it->second;
      }
    }
  }
  StatementPtr stmt =
    Parser::ParseString(input, name.c_str(), sts, variableIndices);
  if (stmt) {
    created = true;
    PhpFile *p = new PhpFile(stmt, sts, variableIndices,
                             name, srcRoot, relPath, md5);
    return p;
  }
  return NULL;
}
String FileRepository::translateFileName(StringData *file) {
  ParsedFilesMap::const_accessor acc;
  if (!s_files.find(acc, file)) return file;
  string srcRoot(SourceRootInfo::GetCurrentSourceRoot());
  if (srcRoot.empty()) return file;
  PhpFile *f = acc->second->getPhpFile();
  const string &parsedSrcRoot = f->getSrcRoot();
  if (srcRoot == parsedSrcRoot) return file;
  int len = parsedSrcRoot.size();
  if (len > 0 && file->size() > len &&
      strncmp(file->data(), parsedSrcRoot.c_str(), len) == 0) {
    return srcRoot + (file->data() + len);
  }
  return file;
}
String FileRepository::translateFileName(const string &file) {
  if (!RuntimeOption::SandboxCheckMd5) return file;
  hphp_hash_map<string, PhpFileWrapper*, string_hash>::const_iterator iter =
    s_files.find(file);
  if (iter == s_files.end()) return file;
  string srcRoot(SourceRootInfo::GetCurrentSourceRoot());
  if (srcRoot.empty()) srcRoot = RuntimeOption::SourceRoot;
  if (srcRoot.empty()) return file;
  PhpFile *f = iter->second->getPhpFile();
  string parsedSrcRoot = f->getSrcRoot();
  if (srcRoot == parsedSrcRoot) return file;
  unsigned int len = parsedSrcRoot.size();
  if (len > 0 && file.size() > len &&
      strncmp(file.data(), parsedSrcRoot.c_str(), len) == 0) {
    return srcRoot + file.substr(len);
  }
  return file;
}
void FileRepository::setFileInfo(const StringData *name,
                                 const string& md5,
                                 FileInfo &fileInfo,
                                 bool fromRepo) {
  int md5len;
  char* md5str;
  // Incorporate the path into the md5 that is used as the key for file
  // repository lookups.  This assures that even if two PHP files have
  // identical content, separate units exist for them (so that
  // Unit::filepath() and Unit::dirpath() work correctly).
  string s = md5 + '\0' + name->data();
  md5str = string_md5(s.c_str(), s.size(), false, md5len);
  fileInfo.m_md5 = string(md5str, md5len);
  free(md5str);

  if (fromRepo) {
    fileInfo.m_unitMd5 = md5;
  } else {
    fileInfo.m_unitMd5 = unitMd5(md5);
  }

  fileInfo.m_srcRoot = SourceRootInfo::GetCurrentSourceRoot();
  int srcRootLen = fileInfo.m_srcRoot.size();
  if (srcRootLen) {
    if (!strncmp(name->data(), fileInfo.m_srcRoot.c_str(), srcRootLen)) {
      fileInfo.m_relPath = string(name->data() + srcRootLen);
    }
  }

  ReadLock lock(s_md5Lock);
  Md5FileMap::iterator it = s_md5Files.find(fileInfo.m_md5);
  if (it != s_md5Files.end()) {
    PhpFile *f = it->second;
    if (!fileInfo.m_relPath.empty() &&
        fileInfo.m_relPath == f->getRelPath()) {
      assert(fileInfo.m_md5 == f->getMd5());
      fileInfo.m_phpFile = f;
    }
  }
}
PhpFile *FileRepository::checkoutFile(const string &rname,
                                      const struct stat &s) {
  PhpFile *ret = NULL;
  Lock lock(s_lock);
  string name;

  if (rname[0] == '/') {
    name = rname;
  } else if (RuntimeOption::SourceRoot.empty()) {
    name = Process::GetCurrentDirectory() + "/" + rname;
  } else {
    name = RuntimeOption::SourceRoot + "/" + rname;
  }

  hphp_hash_map<string, PhpFileWrapper*, string_hash>::iterator it =
    s_files.find(name);
  if (it == s_files.end()) {
    bool created;
    ret = readFile(name, s, created);
    if (ret) {
      s_files[name] = new PhpFileWrapper(s, ret);
      ret->incRef();
      if (created) {
        if (RuntimeOption::SandboxCheckMd5) {
          s_md5Files[ret->getMd5()] = ret;
        }
      } else {
        if (RuntimeOption::SandboxCheckMd5) {
          ASSERT(s_md5Files.find(ret->getMd5())->second == ret);
        }
      }
    }
  } else if (it->second->isChanged(s)) {
      bool created;
      ret = readFile(name, s, created);
      if (ret) {
        PhpFile *f = it->second->getPhpFile();
        if (LIKELY(created)) {
          assert(ret != f);
        } else {
          if (RuntimeOption::SandboxCheckMd5) {
            ASSERT(s_md5Files.find(ret->getMd5())->second == ret);
          }
        }
        if (LIKELY(f != ret)) {
          string oldMd5 = f->getMd5();
          if (f->decRef() == 0) {
            onDelete(f);
          }
          if (RuntimeOption::SandboxCheckMd5) {
            s_md5Files[ret->getMd5()] = ret;
          }
          delete(it->second);
          it->second = new PhpFileWrapper(s, ret);
          ret->incRef();
        }
      }
  } else {
    ret = it->second->getPhpFile();
  }
  if (ret) {
    ret->incRef();
  }
  return ret;
}
PhpFile *FileRepository::checkoutFile(StringData *rname,
                                      const struct stat &s) {
  FileInfo fileInfo;
  PhpFile *ret = nullptr;
  String name(rname);
  bool isPlainFile = File::IsPlainFilePath(name);

  if (isPlainFile) {
    if (rname->data()[0] != '/') {
      name = String(SourceRootInfo::GetCurrentSourceRoot()) + name;
    }
    // Get the common fast path out of the way with as little locking
    // as possible: it's in the map and has not changed on disk
    ParsedFilesMap::const_accessor acc;
    if (s_files.find(acc, name.get()) && !acc->second->isChanged(s)) {
      TRACE(1, "FR fast path hit %s\n", rname->data());
      ret = acc->second->getPhpFile();
      return ret;
    }
  } else {
    // Do the read before we get the repo lock, since it will call into the
    // stream library and will needs its own locks.
    if (isAuthoritativeRepo()) {
      throw FatalErrorException(
        "including urls doesn't work in RepoAuthoritative mode"
      );
    }
    Stream::Wrapper* w = Stream::getWrapperFromURI(name);
    File* f = w->open(name, "r", 0, null_variant);
    if (!f) return nullptr;
    StringBuffer sb;
    sb.read(f);
    fileInfo.m_inputString = sb.detach();
    computeMd5(name.get(), fileInfo);
  }

  TRACE(1, "FR fast path miss: %s\n", rname->data());
  const StringData *n = makeStaticString(name.get());

  PhpFile* toKill = nullptr;
  SCOPE_EXIT {
    // run this after acc is destroyed (and its lock released)
    if (toKill) toKill->decRefAndDelete();
  };
  ParsedFilesMap::accessor acc;
  bool isNew = s_files.insert(acc, n);
  PhpFileWrapper* old = acc->second;
  SCOPE_EXIT {
    // run this just before acc is released
    if (old && old != acc->second) {
      if (old->getPhpFile() != acc->second->getPhpFile()) {
        toKill = old->getPhpFile();
      }
      delete old;
    }
    if (!acc->second) s_files.erase(acc);
  };

  assert(isNew || old); // We don't leave null entries around.
  bool isChanged = !isNew && old->isChanged(s);

  if (isNew || isChanged) {
    if (isPlainFile && !readFile(n, s, fileInfo)) {
      TRACE(1, "File disappeared between stat and FR::readNewFile: %s\n",
            rname->data());
      return nullptr;
    }
    ret = fileInfo.m_phpFile;
    if (isChanged && ret == old->getPhpFile()) {
      // The file changed but had the same contents.
      if (debug && md5Enabled()) {
        ReadLock lock(s_md5Lock);
        assert(s_md5Files.find(ret->getMd5())->second == ret);
      }
      return ret;
    }
  } else if (!isNew) {
    // Somebody might have loaded the file between when we dropped
    // our read lock and got the write lock
    ret = old->getPhpFile();
    return ret;
  }

  // If we get here the file was not in s_files or has changed on disk
  if (!ret) {
    // Try to read Unit from .hhbc repo.
    ret = readHhbc(n->data(), fileInfo);
  }
  if (!ret) {
    if (isAuthoritativeRepo()) {
      raise_error("Tried to parse %s in repo authoritative mode", n->data());
    }
    ret = parseFile(n->data(), fileInfo);
    if (!ret) return nullptr;
  }
  assert(ret != nullptr);

  if (isNew) {
    acc->second = new PhpFileWrapper(s, ret);
    ret->incRef();
    ret->setId(Transl::TargetCache::allocBit());
  } else {
    PhpFile *f = old->getPhpFile();
    if (f != ret) {
      ret->setId(f->getId());
      ret->incRef();
    }
    acc->second = new PhpFileWrapper(s, ret);
  }

  if (md5Enabled()) {
    WriteLock lock(s_md5Lock);
    s_md5Files[ret->getMd5()] = ret;
  }
  return ret;
}
PhpFile *FileRepository::checkoutFile(StringData *rname,
                                      const struct stat &s) {
  FileInfo fileInfo;
  PhpFile *ret = nullptr;
  String name(rname);
  if (rname->data()[0] != '/') {
    name = String(SourceRootInfo::GetCurrentSourceRoot()) + name;
  }

  {
    // Get the common fast path out of the way with as little locking
    // as possible: it's in the map and has not changed on disk
    ParsedFilesMap::const_accessor acc;
    if (s_files.find(acc, name.get()) && !acc->second->isChanged(s)) {
      TRACE(1, "FR fast path hit %s\n", rname->data());
      ret = acc->second->getPhpFile();
      ret->incRef();
      return ret;
    }
  }

  TRACE(1, "FR fast path miss: %s\n", rname->data());
  bool interceptsEnabled = s_interceptsEnabled;
  const StringData *n = StringData::GetStaticString(name.get());
  ParsedFilesMap::accessor acc;
  bool isNew = s_files.insert(acc, n);
  assert(isNew || acc->second); // We don't leave null entries around.
  bool isChanged = !isNew && acc->second->isChanged(s);

  if (isNew || isChanged) {
    if (!readFile(n, s, fileInfo)) {
      // Be sure to get rid of the new reference to it.
      s_files.erase(acc);
      TRACE(1, "File disappeared between stat and FR::readNewFile: %s\n",
            rname->data());
      return nullptr;
    }
    ret = fileInfo.m_phpFile;
    if (isChanged && ret == acc->second->getPhpFile()) {
      // The file changed but had the same contents.
      if (debug && md5Enabled()) {
        ReadLock lock(s_md5Lock);
        assert(s_md5Files.find(ret->getMd5())->second == ret);
      }
      ret->incRef();
      return ret;
    }
  } else if (!isNew) {
    // Somebody might have loaded the file between when we dropped
    // our read lock and got the write lock
    ret = acc->second->getPhpFile();
    ret->incRef();
    return ret;
  }

  // If we get here the file was not in s_files or has changed on disk
  if (!ret) {
    // Try to read Unit from .hhbc repo.
    ret = readHhbc(n->data(), fileInfo);
  }
  if (!ret) {
    if (isAuthoritativeRepo()) {
      raise_error("Tried to parse %s in repo authoritative mode", n->data());
    }
    ret = parseFile(n->data(), fileInfo);
    if (!ret) {
      s_files.erase(acc);
      return nullptr;
    }
  }
  assert(ret != nullptr);

  if (isNew) {
    acc->second = new PhpFileWrapper(s, ret);
    ret->incRef();
    ret->setId(VM::Transl::TargetCache::allocBit());
  } else {
    PhpFile *f = acc->second->getPhpFile();
    if (f != ret) {
      ret->setId(f->getId());
      tx64->invalidateFile(f); // f has changed
    }
    f->decRefAndDelete();
    delete acc->second;
    acc->second = new PhpFileWrapper(s, ret);
    ret->incRef();
  }

  if (md5Enabled()) {
    WriteLock lock(s_md5Lock);
    // make sure intercepts are enabled for the functions within the
    // new units
    // Since we have the write lock on s_md5lock, s_interceptsEnabled
    // can't change, and we are serialized wrt enableIntercepts
    // (i.e., this will execute either before or after
    // enableIntercepts).
    if (interceptsEnabled != s_interceptsEnabled) {
      // intercepts were enabled since the time we created the unit
      ret->unit()->enableIntercepts();
    }
    s_md5Files[ret->getMd5()] = ret;
  }
  ret->incRef();
  return ret;
}