Remove redundant folly::toStdString calls
[hiphop-php.git] / hphp / runtime / vm / repo.cpp
blobf44377fb325a19d72f74b1d5459977b8d8c6cf5f
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
16 #include "hphp/runtime/vm/repo.h"
18 #include <sstream>
20 #include <folly/Format.h>
21 #include <folly/Singleton.h>
23 #include "hphp/runtime/base/repo-autoload-map.h"
24 #include "hphp/runtime/vm/blob-helper.h"
25 #include "hphp/runtime/vm/repo-autoload-map-builder.h"
26 #include "hphp/runtime/vm/repo-global-data.h"
27 #include "hphp/runtime/server/xbox-server.h"
29 #include "hphp/util/assertions.h"
30 #include "hphp/util/async-func.h"
31 #include "hphp/util/build-info.h"
32 #include "hphp/util/hugetlb.h"
33 #include "hphp/util/logger.h"
34 #include "hphp/util/process.h"
35 #include "hphp/util/trace.h"
36 #include "hphp/util/user-info.h"
38 #include <grp.h>
39 #include <pwd.h>
40 #include <sys/stat.h>
42 namespace HPHP {
44 TRACE_SET_MOD(hhbc);
46 const char* Repo::kMagicProduct =
47 "facebook.com HipHop Virtual Machine bytecode repository";
48 const char* Repo::kDbs[RepoIdCount] = { "main", // Central.
49 "local"}; // Local.
50 Repo::GlobalData Repo::s_globalData;
52 void initialize_repo() {
53 if (!sqlite3_threadsafe()) {
54 TRACE(0, "SQLite was compiled without thread support; aborting\n");
55 abort();
57 if (sqlite3_config(SQLITE_CONFIG_MULTITHREAD) != SQLITE_OK) {
58 TRACE(1, "Failed to set default SQLite multi-threading mode\n");
60 if (sqlite3_config(SQLITE_CONFIG_MEMSTATUS, 0) != SQLITE_OK) {
61 TRACE(1, "Failed to disable SQLite memory statistics\n");
65 THREAD_LOCAL(Repo, t_dh);
67 Repo& Repo::get() {
68 return *t_dh.get();
71 void Repo::shutdown() {
72 t_dh.destroy();
75 static SimpleMutex s_lock;
76 static std::atomic<unsigned> s_nRepos;
78 bool Repo::prefork() {
79 if (num_1g_pages() > 0) {
80 // We put data on the 1G huge pages, and we don't want to do COW upon
81 // fork(). If you need to fork(), configure HHVM not to use 1G pages.
82 return true;
84 if (!t_dh.isNull()) {
85 t_dh.destroy();
87 s_lock.lock();
88 XboxServer::Stop();
89 if (s_nRepos > 0 || AsyncFuncImpl::count()) {
90 XboxServer::Restart();
91 s_lock.unlock();
92 return true;
94 folly::SingletonVault::singleton()->destroyInstances();
95 return false;
98 void Repo::postfork(pid_t pid) {
99 folly::SingletonVault::singleton()->reenableInstances();
100 XboxServer::Restart();
101 if (pid == 0) {
102 Logger::ResetPid();
103 new (&s_lock) SimpleMutex();
104 } else {
105 s_lock.unlock();
109 Repo::Repo()
110 : RepoProxy(*this),
111 m_insertFileHash{InsertFileHashStmt(*this, 0),
112 InsertFileHashStmt(*this, 1)},
113 m_getFileHash{GetFileHashStmt(*this, 0), GetFileHashStmt(*this, 1)},
114 m_removeFileHash{RemoveFileHashStmt(*this, 0),
115 RemoveFileHashStmt(*this, 1)},
116 m_getUnitPath{GetUnitPathStmt(*this, 0), GetUnitPathStmt(*this, 1)},
117 m_getUnit{GetUnitStmt(*this, 0), GetUnitStmt(*this, 1)},
118 m_dbc(nullptr), m_localReadable(false), m_localWritable(false),
119 m_evalRepoId(-1), m_txDepth(0), m_rollback(false), m_beginStmt(*this),
120 m_rollbackStmt(*this), m_commitStmt(*this), m_urp(*this), m_pcrp(*this),
121 m_rrp(*this), m_frp(*this), m_lsrp(*this) {
123 ++s_nRepos;
124 connect();
127 Repo::~Repo() noexcept {
128 disconnect();
129 --s_nRepos;
132 std::string Repo::s_cliFile;
133 void Repo::setCliFile(const std::string& cliFile) {
134 assertx(s_cliFile.empty());
135 assertx(t_dh.isNull());
136 s_cliFile = cliFile;
139 size_t Repo::stringLengthLimit() const {
140 static const size_t limit = sqlite3_limit(m_dbc, SQLITE_LIMIT_LENGTH, -1);
141 return limit;
144 bool Repo::hasGlobalData() {
145 for (int repoId = RepoIdCount - 1; repoId >= 0; --repoId) {
146 if (repoName(repoId).empty()) {
147 // The repo wasn't loadable
148 continue;
151 RepoStmt stmt(*this);
152 const auto& tbl = table(repoId, "GlobalData");
153 stmt.prepare(
154 folly::sformat(
155 "SELECT count(*) FROM {};", tbl
158 auto txn = RepoTxn{begin()};
159 RepoTxnQuery query(txn, stmt);
160 query.step();
162 if (!query.row()) {
163 return false;
166 int val;
167 query.getInt(0, val);
168 return val != 0;
171 return false;
174 void Repo::loadGlobalData(bool readArrayTable /* = true */) {
175 if (readArrayTable) m_lsrp.load(RuntimeOption::RepoLitstrLazyLoad);
177 if (!RuntimeOption::RepoAuthoritative) return;
179 std::vector<std::string> failures;
182 * This should probably just go to the Local repo always, except
183 * that our unit test suite is currently running RepoAuthoritative
184 * tests with the compiled repo as the Central repo.
186 for (int repoId = RepoIdCount - 1; repoId >= 0; --repoId) {
187 if (repoName(repoId).empty()) {
188 // The repo wasn't loadable
189 continue;
191 try {
192 RepoStmt stmt(*this);
193 const auto& tbl = table(repoId, "GlobalData");
194 stmt.prepare(
195 folly::sformat(
196 "SELECT data FROM {} WHERE key = @key;", tbl
199 auto txn = RepoTxn{begin()};
201 RepoTxnQuery query(txn, stmt);
202 auto key = std::string("config");
203 query.bindStdString("@key", key);
204 query.step();
205 if (!query.row()) {
206 throw RepoExc("Can't find key = 'config' in %s", tbl.c_str());
208 BlobDecoder decoder = query.getBlob(0, true);
209 decoder(s_globalData);
210 FTRACE(1, "GlobalData loaded from '{}':\n", repoName(repoId));
211 FTRACE(1, "{}", show(s_globalData));
214 if (readArrayTable) {
216 RepoTxnQuery query(txn, stmt);
217 auto key = std::string("arraytable");
218 query.bindStdString("@key", key);
219 query.step();
220 if (!query.row()) {
221 throw RepoExc("Can't find key = 'arraytable' in %s", tbl.c_str());
223 BlobDecoder decoder = query.getBlob(0, true);
224 auto& arrayTypeTable = globalArrayTypeTable();
225 decoder(arrayTypeTable);
226 decoder(s_globalData.APCProfile);
227 decoder(s_globalData.ConstantFunctions);
228 decoder.assertDone();
232 if (RuntimeOption::EvalUseRepoAutoloadMap) {
234 RepoTxnQuery query(txn, stmt);
235 auto key = std::string("autoloadmap");
236 query.bindStdString("@key", key);
237 query.step();
238 if (!query.row()) {
239 throw RepoExc("Can't find key = 'autoloadmap' in %s", tbl.c_str());
241 BlobDecoder decoder = query.getBlob(0, true);
242 s_globalData.AutoloadMap = RepoAutoloadMapBuilder::serde(decoder);
246 txn.commit();
247 } catch (RepoExc& e) {
248 failures.push_back(repoName(repoId) + ": " + e.msg());
249 continue;
252 // TODO: this should probably read out the other elements of the global data
253 // which control Option or RuntimeOption values -- the others are read out
254 // in an inconsistent and ad-hoc manner. But I don't understand their uses
255 // and interactions well enough to feel comfortable fixing now.
256 RuntimeOption::EvalPromoteEmptyObject = s_globalData.PromoteEmptyObject;
257 RuntimeOption::EnableIntrinsicsExtension =
258 s_globalData.EnableIntrinsicsExtension;
259 RuntimeOption::PHP7_Builtins = s_globalData.PHP7_Builtins;
260 RuntimeOption::PHP7_NoHexNumerics = s_globalData.PHP7_NoHexNumerics;
261 RuntimeOption::PHP7_Substr = s_globalData.PHP7_Substr;
262 RuntimeOption::EvalCheckPropTypeHints = s_globalData.CheckPropTypeHints;
263 RuntimeOption::EvalHackArrDVArrs = s_globalData.HackArrDVArrs;
265 * We only should enable array provenance at runtime if it was enabled in
266 * the repo AND we have logging enabled--otherwise it's pointless to do the
267 * bookkeeping
269 * Also--just because array provenance wasn't enabled in the repo doesn't
270 * mean it can't be explicitly enabled at runtime
272 RuntimeOption::EvalArrayProvenance = RuntimeOption::EvalArrayProvenance ||
273 (s_globalData.ArrayProvenance && RuntimeOption::EvalLogArrayProvenance);
274 RuntimeOption::EnableArgsInBacktraces = s_globalData.EnableArgsInBacktraces;
275 RuntimeOption::EvalAbortBuildOnVerifyError =
276 s_globalData.AbortBuildOnVerifyError;
277 RuntimeOption::StrictArrayFillKeys = s_globalData.StrictArrayFillKeys;
278 if (s_globalData.HardReturnTypeHints) {
279 RuntimeOption::EvalCheckReturnTypeHints = 3;
281 if (s_globalData.HardGenericsUB) {
282 RuntimeOption::EvalEnforceGenericsUB = 2;
285 RuntimeOption::EvalIsCompatibleClsMethType =
286 s_globalData.IsCompatibleClsMethType;
287 RuntimeOption::EvalEmitClsMethPointers = s_globalData.EmitClsMethPointers;
289 RuntimeOption::ConstantFunctions.clear();
290 for (auto const& elm : s_globalData.ConstantFunctions) {
291 RuntimeOption::ConstantFunctions.insert(elm);
293 return;
296 if (failures.empty()) {
297 std::fprintf(stderr, "No repo was loadable. Check all the possible repo "
298 "locations (Repo.Central.Path, HHVM_REPO_CENTRAL_PATH, and "
299 "$HOME/.hhvm.hhbc) to make sure one of them is a valid "
300 "sqlite3 HHVM repo built with this exact HHVM version.\n");
301 } else {
302 // We should always have a global data section in RepoAuthoritative
303 // mode, or the repo is messed up.
304 std::fprintf(stderr, "Failed to load Repo::GlobalData:\n");
305 for (auto& f : failures) {
306 std::fprintf(stderr, " %s\n", f.c_str());
310 assertx(Process::IsInMainThread());
311 exit(1);
314 void Repo::saveGlobalData(GlobalData&& newData) {
315 s_globalData = std::move(newData);
317 auto const repoId = repoIdForNewUnit(UnitOrigin::File);
318 RepoStmt stmt(*this);
319 stmt.prepare(
320 folly::format(
321 "INSERT INTO {} VALUES(@key, @data);", table(repoId, "GlobalData")
322 ).str()
324 auto txn = RepoTxn{begin()};
326 RepoTxnQuery query(txn, stmt);
327 auto key = std::string("config");
328 query.bindStdString("@key", key);
329 BlobEncoder encoder{true};
330 encoder(s_globalData);
331 query.bindBlob("@data", encoder, /* static */ true);
332 query.exec();
336 RepoTxnQuery query(txn, stmt);
337 auto key = std::string("arraytable");
338 query.bindStdString("@key", key);
339 BlobEncoder encoder{true};
340 encoder(globalArrayTypeTable());
341 encoder(s_globalData.APCProfile);
342 encoder(s_globalData.ConstantFunctions);
343 query.bindBlob("@data", encoder, /* static */ true);
344 query.exec();
348 RepoTxnQuery query(txn, stmt);
349 auto key = std::string("autoloadmap");
350 query.bindStdString("@key", key);
351 BlobEncoder encoder{true};
352 RepoAutoloadMapBuilder::get().serde(encoder);
353 query.bindBlob("@data", encoder, /* static */ true);
354 query.exec();
357 // TODO(#3521039): we could just put the litstr table in the same
358 // blob as the above and delete LitstrRepoProxy.
359 LitstrTable::get().setReading();
360 LitstrRepoProxy::InsertLitstrStmt insertStmt{*this, repoId};
361 LitstrTable::get().forEachLitstr(
362 [this, &txn, &insertStmt](int i, const StringData* name) {
363 insertStmt.insert(txn, i, name);
366 txn.commit();
369 std::unique_ptr<Unit> Repo::loadUnit(const folly::StringPiece name,
370 const SHA1& sha1,
371 const Native::FuncTable& nativeFuncs) {
372 ARRPROV_USE_RUNTIME_LOCATION();
373 if (m_dbc == nullptr) return nullptr;
374 tracing::Block _{
375 "repo-load-unit", [&] { return tracing::Props{}.add("name", name); }
377 return m_urp.load(name, sha1, nativeFuncs);
380 void Repo::forgetUnit(const std::string& name) {
381 if (m_dbc == nullptr) {
382 return;
385 auto const repoId = repoIdForNewUnit(UnitOrigin::File);
386 auto txn = RepoTxn{begin()};
387 m_removeFileHash[repoId].remove(txn, name);
388 txn.commit();
391 std::vector<std::pair<std::string,SHA1>>
392 Repo::enumerateUnits(int repoId, bool warn) {
393 std::vector<std::pair<std::string,SHA1>> ret;
395 try {
396 RepoStmt stmt(*this);
397 stmt.prepare(folly::sformat(
398 "SELECT path, sha1 FROM {};",
399 table(repoId, "FileSha1"))
401 auto txn = RepoTxn{begin()};
402 RepoTxnQuery query(txn, stmt);
404 for (query.step(); query.row(); query.step()) {
405 std::string path;
406 SHA1 sha1;
408 query.getStdString(0, path);
409 query.getSha1(1, sha1);
411 ret.emplace_back(path, sha1);
414 txn.commit();
415 } catch (RepoExc& e) {
416 if (warn) {
417 fprintf(stderr, "failed to enumerate units: %s\n", e.what());
419 // Ugh - the error is dropped. At least we might have printed an error to
420 // stderr.
423 return ret;
426 void Repo::InsertFileHashStmt::insert(RepoTxn& txn, const StringData* path,
427 const SHA1& sha1) {
428 if (!prepared()) {
429 auto insertQuery = folly::sformat(
430 "INSERT INTO {} VALUES(@path, @sha1);",
431 m_repo.table(m_repoId, "FileSha1"));
432 txn.prepare(*this, insertQuery);
434 RepoTxnQuery query(txn, *this);
435 query.bindStaticString("@path", path);
436 query.bindSha1("@sha1", sha1);
437 query.exec();
440 RepoStatus Repo::GetFileHashStmt::get(const char *path, SHA1& sha1) {
441 try {
442 auto txn = RepoTxn{m_repo.begin()};
443 if (!prepared()) {
444 auto selectQuery = folly::sformat(
445 "SELECT f.sha1 "
446 "FROM {} AS f, {} AS u "
447 "WHERE path == @path AND f.sha1 == u.sha1 "
448 "ORDER BY unitSn DESC LIMIT 1;",
449 m_repo.table(m_repoId, "FileSha1"),
450 m_repo.table(m_repoId, "Unit"));
451 txn.prepare(*this, selectQuery);
453 RepoTxnQuery query(txn, *this);
454 query.bindText("@path", path, strlen(path));
455 query.step();
456 if (!query.row()) {
457 return RepoStatus::error;
459 query.getSha1(0, sha1);
460 txn.commit();
461 return RepoStatus::success;
462 } catch (RepoExc& re) {
463 return RepoStatus::error;
467 void Repo::RemoveFileHashStmt::remove(RepoTxn& txn, const std::string& path) {
468 if (!prepared()) {
469 auto insertQuery = folly::sformat(
470 "DELETE FROM {} WHERE path == @path;",
471 m_repo.table(m_repoId, "FileSha1"));
472 txn.prepare(*this, insertQuery);
474 RepoTxnQuery query(txn, *this);
475 query.bindStdString("@path", path);
476 query.exec();
479 RepoStatus Repo::GetUnitPathStmt::get(int64_t unitSn, String& path) {
480 try {
481 auto txn = RepoTxn{m_repo.begin()};
482 if (!prepared()) {
483 auto selectQuery = folly::sformat(
484 "SELECT f.path "
485 "FROM {} AS u, {} AS f "
486 "WHERE u.unitSn == @unitSn AND f.sha1 == u.sha1",
487 m_repo.table(m_repoId, "Unit"),
488 m_repo.table(m_repoId, "FileSha1"));
489 txn.prepare(*this, selectQuery);
491 RepoTxnQuery query(txn, *this);
492 query.bindInt64("@unitSn", unitSn);
493 query.step();
494 if (!query.row()) {
495 return RepoStatus::error;
497 StringData* spath; query.getStaticString(0, spath);
498 path = String(spath);
499 txn.commit();
500 return RepoStatus::success;
501 } catch (RepoExc& re) {
502 return RepoStatus::error;
506 RepoStatus Repo::findPath(int64_t unitSn, const std::string& root, String& path) {
507 if (m_dbc == nullptr) {
508 return RepoStatus::error;
510 int repoId;
511 for (repoId = RepoIdCount - 1; repoId >= 0; --repoId) {
512 String relPath;
513 if (m_getUnitPath[repoId].get(unitSn, relPath) == RepoStatus::success) {
514 path = root + relPath;
515 TRACE(3, "Repo loaded file path for '%ld' from '%s'\n",
516 unitSn, repoName(repoId).c_str());
517 return RepoStatus::success;
520 TRACE(3, "Repo file path: error loading '%ld'\n", unitSn);
521 return RepoStatus::error;
524 RepoStatus Repo::GetUnitStmt::get(const char* path, int64_t& unitSn) {
525 try {
526 auto txn = RepoTxn{m_repo.begin()};
527 if (!prepared()) {
528 auto selectQuery = folly::sformat(
529 "SELECT u.unitSn "
530 "FROM {} AS f, {} AS u "
531 "WHERE f.path == @path AND f.sha1 == u.sha1",
532 m_repo.table(m_repoId, "FileSha1"),
533 m_repo.table(m_repoId, "Unit"));
534 txn.prepare(*this, selectQuery);
536 RepoTxnQuery query(txn, *this);
537 query.bindText("@path", path, strlen(path));
538 query.step();
539 if (!query.row()) {
540 return RepoStatus::error;
542 int unitSn_; /**/ query.getInt(0, unitSn_);
543 unitSn = unitSn_;
544 txn.commit();
545 return RepoStatus::success;
546 } catch (RepoExc& re) {
547 return RepoStatus::error;
551 RepoStatus Repo::findUnit(const char* path, const std::string& root,
552 int64_t& unitSn) {
553 if (m_dbc == nullptr) {
554 return RepoStatus::error;
556 int repoId;
557 for (repoId = RepoIdCount - 1; repoId >= 0; --repoId) {
558 if (*path == '/' && !root.empty() &&
559 !strncmp(root.c_str(), path, root.size()) &&
560 (m_getUnit[repoId].get(path + root.size(), unitSn) ==
561 RepoStatus::success)) {
562 TRACE(3, "Repo loaded unit for '%s' from '%s'\n",
563 path + root.size(), repoName(repoId).c_str());
564 return RepoStatus::success;
566 if (m_getUnit[repoId].get(path, unitSn) == RepoStatus::success) {
567 TRACE(3, "Repo loaded unit for '%s' from '%s'\n",
568 path, repoName(repoId).c_str());
569 return RepoStatus::success;
572 TRACE(3, "Repo unit: error loading '%s'\n", path);
573 return RepoStatus::error;
576 RepoStatus Repo::findFile(const char *path, const std::string& root,
577 SHA1& sha1) {
578 tracing::Block _{
579 "repo-find-file",
580 [&] {
581 return tracing::Props{}
582 .add("path", path)
583 .add("root", root);
587 if (m_dbc == nullptr) {
588 return RepoStatus::error;
590 int repoId;
591 for (repoId = RepoIdCount - 1; repoId >= 0; --repoId) {
592 if (*path == '/' && !root.empty() &&
593 !strncmp(root.c_str(), path, root.size()) &&
594 (m_getFileHash[repoId].get(path + root.size(), sha1) ==
595 RepoStatus::success)) {
596 TRACE(3, "Repo loaded file hash for '%s' from '%s'\n",
597 path + root.size(), repoName(repoId).c_str());
598 return RepoStatus::success;
600 if (m_getFileHash[repoId].get(path, sha1) == RepoStatus::success) {
601 TRACE(3, "Repo loaded file hash for '%s' from '%s'\n",
602 path, repoName(repoId).c_str());
603 return RepoStatus::success;
606 TRACE(3, "Repo file hash: error loading '%s'\n", path);
607 return RepoStatus::error;
610 RepoStatus Repo::insertSha1(UnitOrigin unitOrigin, UnitEmitter* ue,
611 RepoTxn& txn) {
612 const StringData* path = ue->m_filepath;
613 const SHA1& sha1 = ue->sha1();
614 int repoId = repoIdForNewUnit(unitOrigin);
615 if (repoId == RepoIdInvalid) {
616 return RepoStatus::error;
618 try {
619 m_insertFileHash[repoId].insert(txn, path, sha1);
620 return RepoStatus::success;
621 } catch (RepoExc& re) {
622 TRACE(3, "Failed to commit sha1 for '%s' to '%s': %s\n",
623 path->data(), repoName(repoId).c_str(), re.msg().c_str());
624 return RepoStatus::error;
628 void Repo::commitSha1(UnitOrigin unitOrigin, UnitEmitter* ue) {
629 try {
630 auto txn = RepoTxn{begin()};
631 RepoStatus err = insertSha1(unitOrigin, ue, txn);
632 if (err == RepoStatus::success) {
633 txn.commit();
635 } catch (RepoExc& re) {
636 tracing::addPointNoTrace("sha1-commit-exn");
637 int repoId = repoIdForNewUnit(unitOrigin);
638 if (repoId != RepoIdInvalid) {
639 TRACE(3, "Failed to commit sha1 for '%s' to '%s': %s\n",
640 ue->m_filepath->data(), repoName(repoId).c_str(),
641 re.msg().c_str());
646 std::string Repo::table(int repoId, const char* tablePrefix) {
647 return folly::sformat(
648 "{}.{}_{}", dbName(repoId), tablePrefix, repoSchemaId());
651 void Repo::exec(const std::string& sQuery) {
652 RepoStmt stmt(*this);
653 stmt.prepare(sQuery);
654 RepoQuery query(stmt);
655 query.exec();
658 RepoTxn Repo::begin() {
659 if (m_txDepth > 0) {
660 m_txDepth++;
661 return RepoTxn{*this};
663 if (debug) {
664 // Verify start state.
665 always_assert(m_txDepth == 0);
666 always_assert(!m_rollback);
667 if (true) {
668 // Bypass RepoQuery, in order to avoid triggering exceptions.
669 int rc = sqlite3_step(m_rollbackStmt.get());
670 switch (rc) {
671 case SQLITE_DONE:
672 case SQLITE_ROW:
673 not_reached();
674 default:
675 break;
677 } else {
678 bool rollbackFailed = false;
679 try {
680 RepoQuery query(m_rollbackStmt);
681 query.exec();
682 } catch (RepoExc& re) {
683 rollbackFailed = true;
685 always_assert(rollbackFailed);
688 RepoQuery query(m_beginStmt);
689 query.exec();
690 m_txDepth++;
692 return RepoTxn(*this);
695 void Repo::txPop() {
696 // We mix the concept of rollback with a normal commit so that if we try to
697 // rollback an inner transaction we eventually end up rolling back the outer
698 // transaction instead (Sqlite doesn't support rolling back partial
699 // transactions).
700 assertx(m_txDepth > 0);
701 if (m_txDepth > 1) {
702 m_txDepth--;
703 return;
705 if (!m_rollback) {
706 RepoQuery query(m_commitStmt);
707 query.exec();
708 } else {
709 // We're in the outermost transaction - so clear the rollback flag.
710 m_rollback = false;
711 RepoQuery query(m_rollbackStmt);
712 try {
713 query.exec();
714 } catch (RepoExc& ex) {
716 * Having a rollback fail is actually a normal, expected case,
717 * so just swallow this.
719 * In particular, according to the docs, if we got an I/O error
720 * while doing a commit, the rollback will often fail with "no
721 * transaction in progress", because the commit will have
722 * automatically been rolled back. Recommended practice is
723 * still to execute a rollback statement and ignore the error.
725 TRACE(4, "repo rollback failure: %s\n", ex.what());
728 // Decrement depth after query execution, in case an exception occurs during
729 // commit. This allows for subsequent rollback of the failed commit.
730 m_txDepth--;
733 void Repo::rollback() {
734 m_rollback = true;
735 // NOTE: A try/catch isn't necessary - txPop() handles rollback as a nothrow.
736 txPop();
739 void Repo::commit() {
740 txPop();
743 RepoStatus Repo::insertUnit(UnitEmitter* ue, UnitOrigin unitOrigin,
744 RepoTxn& txn) {
745 if (insertSha1(unitOrigin, ue, txn) == RepoStatus::error ||
746 ue->insert(unitOrigin, txn) == RepoStatus::error) {
747 return RepoStatus::error;
749 return RepoStatus::success;
752 void Repo::commitUnit(UnitEmitter* ue, UnitOrigin unitOrigin) {
753 if (!RuntimeOption::RepoCommit || ue->m_ICE) return;
755 tracing::Block _{
756 "repo-commit-unit",
757 [&] { return tracing::Props{}.add("filename", ue->m_filepath); }
760 try {
761 commitSha1(unitOrigin, ue);
762 ue->commit(unitOrigin);
763 } catch (const std::exception& e) {
764 TRACE(0, "unexpected exception in commitUnit: %s\n",
765 e.what());
766 assertx(false);
770 void Repo::connect() {
771 initCentral();
772 initLocal();
773 if (!RuntimeOption::RepoEvalMode.compare("local")) {
774 m_evalRepoId = (m_localWritable) ? RepoIdLocal : RepoIdCentral;
775 } else if (!RuntimeOption::RepoEvalMode.compare("central")) {
776 m_evalRepoId = RepoIdCentral;
777 } else {
778 assertx(!RuntimeOption::RepoEvalMode.compare("readonly"));
779 m_evalRepoId = RepoIdInvalid;
781 TRACE(1, "Repo.Eval.Mode=%s\n",
782 (m_evalRepoId == RepoIdLocal)
783 ? "local"
784 : (m_evalRepoId == RepoIdCentral)
785 ? "central"
786 : "readonly");
789 void Repo::disconnect() noexcept {
790 if (m_dbc != nullptr) {
791 sqlite3_close_v2(m_dbc);
792 m_dbc = nullptr;
793 m_localReadable = false;
794 m_localWritable = false;
795 m_evalRepoId = RepoIdInvalid;
799 void Repo::initCentral() {
800 std::string error;
802 assertx(m_dbc == nullptr);
803 auto tryPath = [this, &error](const char* path) {
804 std::string subErr;
805 if (openCentral(path, subErr) == RepoStatus::error) {
806 folly::format(&error, " {}\n", subErr.empty() ? path : subErr);
807 return false;
809 return true;
812 auto fail_no_repo = [&error] {
813 error = "Failed to initialize central HHBC repository:\n" + error;
814 // Database initialization failed; this is an unrecoverable state.
815 Logger::Error(error);
817 if (Process::IsInMainThread()) {
818 exit(1);
820 always_assert_flog(false, "{}", error);
823 // Try Repo.Central.Path
824 if (!RuntimeOption::RepoCentralPath.empty() &&
825 tryPath(RuntimeOption::RepoCentralPath.c_str())) {
826 return;
829 // Try HHVM_REPO_CENTRAL_PATH
830 const char* HHVM_REPO_CENTRAL_PATH = getenv("HHVM_REPO_CENTRAL_PATH");
831 if (HHVM_REPO_CENTRAL_PATH != nullptr &&
832 tryPath(HHVM_REPO_CENTRAL_PATH)) {
833 return;
836 if (!RuntimeOption::RepoAllowFallbackPath) fail_no_repo();
838 // Try "$HOME/.hhvm.hhbc".
839 char* HOME = getenv("HOME");
840 if (HOME != nullptr) {
841 std::string centralPath = HOME;
842 centralPath += "/.hhvm.hhbc";
843 if (tryPath(centralPath.c_str())) {
844 return;
848 #ifndef _WIN32
849 // Try the equivalent of "$HOME/.hhvm.hhbc", but look up the home directory
850 // in the password database.
852 auto buf = PasswdBuffer{};
853 passwd* pw;
854 auto err = getpwuid_r(getuid(), &buf.ent, buf.data.get(), buf.size, &pw);
855 if (err == 0 && pw != nullptr &&
856 (HOME == nullptr || strcmp(HOME, pw->pw_dir))) {
857 std::string centralPath = pw->pw_dir;
858 centralPath += "/.hhvm.hhbc";
859 if (tryPath(centralPath.c_str())) {
860 return;
864 #else // _WIN32
865 // Try "$HOMEDRIVE$HOMEPATH/.hhvm.hhbc"
866 char* HOMEDRIVE = getenv("HOMEDRIVE");
867 if (HOMEDRIVE != nullptr) {
868 char* HOMEPATH = getenv("HOMEPATH");
869 if (HOMEPATH != nullptr) {
870 std::string centralPath = HOMEDRIVE;
871 centralPath += HOMEPATH;
872 centralPath += "\\.hhvm.hhbc";
873 if (tryPath(centralPath.c_str()))
874 return;
877 #endif
879 fail_no_repo();
882 namespace {
884 * Convert the permission bits from the given stat struct to an ls-style
885 * rwxrwxrwx format.
887 std::string showPermissions(const struct stat& s) {
888 static const std::pair<int, char> bits[] = {
889 {S_IRUSR, 'r'}, {S_IWUSR, 'w'}, {S_IXUSR, 'x'},
890 {S_IRGRP, 'r'}, {S_IWGRP, 'w'}, {S_IXGRP, 'x'},
891 {S_IROTH, 'r'}, {S_IWOTH, 'w'}, {S_IXOTH, 'x'},
893 std::string ret;
894 ret.reserve(sizeof(bits) / sizeof(bits[0]));
896 for (auto pair : bits) {
897 ret += (s.st_mode & pair.first) ? pair.second : '-';
899 return ret;
903 * Return the name of the user with the given id.
905 std::string uidToName(uid_t uid) {
906 #ifndef _WIN32
907 auto buf = PasswdBuffer{};
908 passwd* pw;
910 auto err = getpwuid_r(uid, &buf.ent, buf.data.get(), buf.size, &pw);
911 if (err != 0) return folly::errnoStr(errno);
912 if (pw == nullptr) return "user does not exist";
913 return pw->pw_name;
914 #else
915 return "<unsupported>";
916 #endif
920 * Return the uid of the user with the given name.
922 uid_t nameToUid(const std::string& name) {
923 #ifndef _WIN32
924 auto buf = PasswdBuffer{};
925 passwd* pw;
927 auto err = getpwnam_r(name.c_str(), &buf.ent, buf.data.get(), buf.size, &pw);
928 if (err != 0 || pw == nullptr) return -1;
929 return pw->pw_uid;
930 #else
931 return -1;
932 #endif
936 * Return the name of the group with the given id.
938 std::string gidToName(gid_t gid) {
939 #ifndef _WIN32
940 auto buf = GroupBuffer{};
941 group* grp;
943 auto err = getgrgid_r(gid, &buf.ent, buf.data.get(), buf.size, &grp);
944 if (err != 0) return folly::errnoStr(errno);
945 if (grp == nullptr) return "group does not exist";
946 return grp->gr_name;
947 #else
948 return "<unsupported>";
949 #endif
953 * Return the gid of the group with the given name.
955 gid_t nameToGid(const std::string& name) {
956 #ifndef _WIN32
957 auto buf = GroupBuffer{};
958 group* grp;
960 auto err = getgrnam_r(name.c_str(), &buf.ent, buf.data.get(), buf.size, &grp);
961 if (err != 0 || grp == nullptr) return -1;
962 return grp->gr_gid;
963 #else
964 return -1;
965 #endif
968 void setCentralRepoFileMode(const std::string& path) {
969 // These runtime options are all best-effort, so we don't care if any of the
970 // operations fail.
972 if (auto const mode = RuntimeOption::RepoCentralFileMode) {
973 chmod(path.c_str(), mode);
976 if (!RuntimeOption::RepoCentralFileUser.empty()) {
977 auto const uid = nameToUid(RuntimeOption::RepoCentralFileUser);
978 chown(path.c_str(), uid, -1);
981 if (!RuntimeOption::RepoCentralFileGroup.empty()) {
982 auto const gid = nameToGid(RuntimeOption::RepoCentralFileGroup);
983 chown(path.c_str(), -1, gid);
988 RepoStatus Repo::openCentral(const char* rawPath, std::string& errorMsg) {
989 std::string repoPath = rawPath;
990 replacePlaceholders(repoPath);
991 // SQLITE_OPEN_NOMUTEX specifies that the connection be opened such
992 // that no mutexes are used to protect the database connection from other
993 // threads. However, multiple connections can still be used concurrently,
994 // because SQLite as a whole is thread-safe.
995 if (int err = sqlite3_open_v2(repoPath.c_str(), &m_dbc,
996 SQLITE_OPEN_NOMUTEX |
997 SQLITE_OPEN_READWRITE |
998 SQLITE_OPEN_CREATE, nullptr)) {
999 TRACE(1, "Repo::%s() failed to open candidate central repo '%s'\n",
1000 __func__, repoPath.c_str());
1001 errorMsg = folly::format("Failed to open {}: {} - {}",
1002 repoPath, err, sqlite3_errmsg(m_dbc)).str();
1003 return RepoStatus::error;
1006 if (RuntimeOption::RepoBusyTimeoutMS) {
1007 sqlite3_busy_timeout(m_dbc, RuntimeOption::RepoBusyTimeoutMS);
1009 try {
1010 m_beginStmt.prepare("BEGIN TRANSACTION;");
1011 m_rollbackStmt.prepare("ROLLBACK;");
1012 m_commitStmt.prepare("COMMIT;");
1013 pragmas(RepoIdCentral);
1014 } catch (RepoExc& re) {
1015 TRACE(1, "Repo::%s() failed to initialize connection to canditate repo"
1016 " '%s': %s\n", __func__, repoPath.c_str(), re.what());
1017 errorMsg = folly::format("Failed to initialize connection to {}: {}",
1018 repoPath, re.what()).str();
1019 return RepoStatus::error;
1022 // sqlite3_open_v2() will silently open in read-only mode if file permissions
1023 // prevent writing. Therefore, tell initSchema() to verify that the database
1024 // is writable.
1025 bool centralWritable = true;
1026 if (initSchema(RepoIdCentral, centralWritable, errorMsg) == RepoStatus::error
1027 || !centralWritable) {
1028 TRACE(1, "Repo::initSchema() failed for candidate central repo '%s'\n",
1029 repoPath.c_str());
1030 struct stat repoStat;
1031 std::string statStr;
1032 if (stat(repoPath.c_str(), &repoStat) == 0) {
1033 statStr = folly::sformat("{} {}:{}",
1034 showPermissions(repoStat),
1035 uidToName(repoStat.st_uid),
1036 gidToName(repoStat.st_gid));
1037 } else {
1038 statStr = folly::errnoStr(errno);
1040 errorMsg = folly::format("Failed to initialize schema in {}({}): {}",
1041 repoPath, statStr, errorMsg).str();
1042 return RepoStatus::error;
1044 m_centralRepo = repoPath;
1045 setCentralRepoFileMode(repoPath);
1046 TRACE(1, "Central repo: '%s'\n", m_centralRepo.c_str());
1047 return RepoStatus::success;
1050 void Repo::initLocal() {
1051 if (RuntimeOption::RepoLocalMode.compare("--")) {
1052 bool isWritable;
1053 if (!RuntimeOption::RepoLocalMode.compare("rw")) {
1054 isWritable = true;
1055 } else {
1056 assertx(!RuntimeOption::RepoLocalMode.compare("r-"));
1057 isWritable = false;
1060 if (!RuntimeOption::RepoLocalPath.empty()) {
1061 attachLocal(RuntimeOption::RepoLocalPath.c_str(), isWritable);
1062 } else if (RuntimeOption::RepoAllowFallbackPath) {
1063 if (!RuntimeOption::ServerExecutionMode()) {
1064 std::string cliRepo = s_cliFile;
1065 if (!cliRepo.empty()) {
1066 cliRepo += ".hhbc";
1068 attachLocal(cliRepo.c_str(), isWritable);
1069 } else {
1070 attachLocal("hhvm.hhbc", isWritable);
1076 void Repo::attachLocal(const char* path, bool isWritable) {
1077 std::string repoPath = path;
1078 replacePlaceholders(repoPath);
1079 if (!isWritable) {
1080 // Make sure the repo exists before attaching it, in order to avoid
1081 // creating a read-only repo.
1082 struct stat buf;
1083 if (!strchr(repoPath.c_str(), ':') &&
1084 stat(repoPath.c_str(), &buf) != 0) {
1085 return;
1088 try {
1089 auto attachQuery = folly::sformat(
1090 "ATTACH DATABASE '{}' as {};", repoPath, dbName(RepoIdLocal));
1091 exec(attachQuery);
1092 pragmas(RepoIdLocal);
1093 } catch (RepoExc& re) {
1094 // Failed to run pragmas on local DB - ignored
1095 return;
1098 std::string error;
1099 if (initSchema(RepoIdLocal, isWritable, error) == RepoStatus::error) {
1100 FTRACE(1, "Local repo {} failed to init schema: {}\n", repoPath, error);
1101 return;
1103 m_localRepo = repoPath;
1104 m_localReadable = true;
1105 m_localWritable = isWritable;
1106 TRACE(1, "Local repo: '%s' (read%s)\n",
1107 m_localRepo.c_str(), m_localWritable ? "-write" : "-only");
1110 void Repo::pragmas(int repoId) {
1111 // Valid synchronous values: 0 (OFF), 1 (NORMAL), 2 (FULL).
1112 static const int synchronous = 0;
1113 setIntPragma(repoId, "synchronous", synchronous);
1114 setIntPragma(repoId, "cache_size", 20);
1115 // Valid journal_mode values: delete, truncate, persist, memory, wal, off.
1116 setTextPragma(repoId, "journal_mode", RuntimeOption::RepoJournal.c_str());
1119 void Repo::getIntPragma(int repoId, const char* name, int& val) {
1120 auto pragmaQuery = folly::sformat("PRAGMA {}.{};", dbName(repoId), name);
1121 RepoStmt stmt(*this);
1122 stmt.prepare(pragmaQuery);
1123 RepoQuery query(stmt);
1124 query.step();
1125 query.getInt(0, val);
1128 void Repo::setIntPragma(int repoId, const char* name, int val) {
1129 // Read first to see if a write can be avoided
1130 int oldval = -1;
1131 getIntPragma(repoId, name, oldval);
1132 if (val == oldval) return;
1134 // Pragma writes must be executed outside transactions, since they may change
1135 // transaction behavior.
1136 auto pragmaQuery = folly::sformat(
1137 "PRAGMA {}.{} = {};", dbName(repoId), name, val);
1138 exec(pragmaQuery);
1139 if (debug) {
1140 // Verify that the pragma had the desired effect.
1141 int newval = -1;
1142 getIntPragma(repoId, name, newval);
1143 if (newval != val) {
1144 throw RepoExc("Unexpected PRAGMA %s.%s value: %d\n",
1145 dbName(repoId), name, newval);
1150 void Repo::getTextPragma(int repoId, const char* name, std::string& val) {
1151 auto pragmaQuery = folly::sformat("PRAGMA {}.{};", dbName(repoId), name);
1152 RepoStmt stmt(*this);
1153 stmt.prepare(pragmaQuery);
1154 RepoQuery query(stmt);
1155 const char* s;
1156 query.step();
1157 query.getText(0, s);
1158 val = s;
1161 void Repo::setTextPragma(int repoId, const char* name, const char* val) {
1162 // Read first to see if a write can be avoided
1163 std::string oldval = "?";
1164 getTextPragma(repoId, name, oldval);
1165 if (!strcmp(oldval.c_str(), val)) return;
1166 // Pragma writes must be executed outside transactions, since they may change
1167 // transaction behavior.
1168 auto pragmaQuery = folly::sformat(
1169 "PRAGMA {}.{} = {};", dbName(repoId), name, val);
1170 exec(pragmaQuery);
1171 if (debug) {
1172 // Verify that the pragma had the desired effect.
1173 std::string newval = "?";
1174 getTextPragma(repoId, name, newval);
1175 if (strcmp(newval.c_str(), val)) {
1176 // If the db is in memory, journal mode will stick at "memory"
1177 // unless its turned off. Ignore attempts to change it to
1178 // something else.
1179 if (!strcmp(name, "journal_mode") &&
1180 !strcmp(newval.c_str(), "memory") &&
1181 strcmp(val, "off")) {
1182 return;
1184 throw RepoExc("Unexpected PRAGMA %s.%s value: %s (should be %s)\n",
1185 dbName(repoId), name, newval.c_str(), val);
1190 RepoStatus Repo::initSchema(int repoId, bool& isWritable,
1191 std::string& errorMsg) {
1192 if (!schemaExists(repoId)) {
1193 if (createSchema(repoId, errorMsg) == RepoStatus::error) {
1194 // Check whether this failure is due to losing the schema
1195 // initialization race with another process.
1196 if (!schemaExists(repoId)) {
1197 return RepoStatus::error;
1199 } else {
1200 // createSchema() successfully wrote to the database, so no further
1201 // verification is necessary.
1202 return RepoStatus::success;
1205 if (isWritable) {
1206 isWritable = writable(repoId);
1208 return RepoStatus::success;
1211 bool Repo::schemaExists(int repoId) {
1212 try {
1213 auto txn = RepoTxn{begin()};
1214 auto selectQuery = folly::sformat(
1215 "SELECT product FROM {};", table(repoId, "magic"));
1216 RepoStmt stmt(*this);
1217 // If the DB is 'new' and hasn't been initialized yet then we expect this
1218 // prepare() to fail.
1219 stmt.prepare(selectQuery);
1220 // This SHOULDN'T fail - we create the table under a transaction - so if it
1221 // exists then it should have our magic value.
1222 RepoTxnQuery query(txn, stmt);
1223 query.step();
1224 const char* text; /**/ query.getText(0, text);
1225 if (strcmp(kMagicProduct, text)) {
1226 return false;
1228 txn.commit();
1229 } catch (RepoExc& re) {
1230 return false;
1232 return true;
1235 RepoStatus Repo::createSchema(int repoId, std::string& errorMsg) {
1236 try {
1237 auto txn = RepoTxn{begin()};
1239 auto createQuery = folly::sformat(
1240 "CREATE TABLE {} (product TEXT);", table(repoId, "magic"));
1241 txn.exec(createQuery);
1243 auto insertQuery = folly::sformat(
1244 "INSERT INTO {} VALUES('{}');", table(repoId, "magic"), kMagicProduct);
1245 txn.exec(insertQuery);
1248 auto createQuery = folly::sformat(
1249 "CREATE TABLE {} (path TEXT, sha1 BLOB, UNIQUE(path, sha1));",
1250 table(repoId, "FileSha1"));
1251 txn.exec(createQuery);
1253 auto indexQuery = folly::sformat(
1254 "CREATE INDEX {}_sha1_index ON {}_{} (sha1);",
1255 table(repoId, "FileSha1"),
1256 "FileSha1",
1257 repoSchemaId());
1258 txn.exec(indexQuery);
1260 txn.exec(folly::sformat("CREATE TABLE {} (key TEXT, data BLOB);",
1261 table(repoId, "GlobalData")));
1262 m_urp.createSchema(repoId, txn);
1263 m_pcrp.createSchema(repoId, txn);
1264 m_rrp.createSchema(repoId, txn);
1265 m_frp.createSchema(repoId, txn);
1266 m_lsrp.createSchema(repoId, txn);
1268 txn.commit();
1269 } catch (RepoExc& re) {
1270 errorMsg = re.what();
1271 return RepoStatus::error;
1273 return RepoStatus::success;
1276 bool Repo::writable(int repoId) {
1277 switch (sqlite3_db_readonly(m_dbc, dbName(repoId))) {
1278 case 0: return true;
1279 case 1: return false;
1280 case -1: return false;
1281 default: break;
1283 always_assert(false);
1286 //////////////////////////////////////////////////////////////////////
1288 void batchCommit(const std::vector<std::unique_ptr<UnitEmitter>>& ues) {
1289 auto& repo = Repo::get();
1291 // Attempt batch commit. This can legitimately fail due to multiple input
1292 // files having identical contents.
1293 bool err = false;
1295 auto txn = RepoTxn{repo.begin()};
1297 for (auto& ue : ues) {
1298 if (repo.insertUnit(ue.get(), UnitOrigin::File, txn) ==
1299 RepoStatus::error) {
1300 err = true;
1301 break;
1304 if (!err) {
1305 txn.commit();
1309 // Commit units individually if an error occurred during batch commit.
1310 if (err) {
1311 for (auto& ue : ues) {
1312 repo.commitUnit(ue.get(), UnitOrigin::File);
1317 //////////////////////////////////////////////////////////////////////