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; }