Optimize struct element initialization
[hiphop-php.git] / hphp / runtime / vm / repo.cpp
blobb04437038a9f47a097173ea17dde7f6fa3bd51f4
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 RepoGlobalData Repo::s_globalData;
51 bool Repo::s_deleteLocalOnFailure;
53 void initialize_repo() {
54 if (!sqlite3_threadsafe()) {
55 TRACE(0, "SQLite was compiled without thread support; aborting\n");
56 abort();
58 if (sqlite3_config(SQLITE_CONFIG_MULTITHREAD) != SQLITE_OK) {
59 TRACE(1, "Failed to set default SQLite multi-threading mode\n");
61 if (sqlite3_config(SQLITE_CONFIG_MEMSTATUS, 0) != SQLITE_OK) {
62 TRACE(1, "Failed to disable SQLite memory statistics\n");
66 THREAD_LOCAL(Repo, t_dh);
68 Repo& Repo::get() {
69 return *t_dh.get();
72 void Repo::shutdown() {
73 t_dh.destroy();
76 static SimpleMutex s_lock;
77 static std::atomic<unsigned> s_nRepos;
79 bool Repo::prefork() {
80 if (num_1g_pages() > 0 || RuntimeOption::EvalFileBackedColdArena) {
81 // We put data on shared pages, which won't work with fork().
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_localWritable(false), m_centralWritable(false),
119 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), m_numOpenRepos(0) {
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 assertx(limit > 8);
143 // In addition to restricting the size of text and blob fields, during an
144 // INSERT or SELECT operation an entire row is encoded as blob, therefore,
145 // SQLITE_LIMIT_LENGTH controls the maximum total size of a row. We currently
146 // store litstrs in a table with an INTEGER key and a TEXT litstr, the size of
147 // the litstr must leave room for an integer, which can require between 1 and
148 // 8 bytes.
149 return limit - 8;
152 bool Repo::hasGlobalData() {
153 for (int repoId = numOpenRepos() - 1; repoId >= 0; --repoId) {
154 RepoStmt stmt(*this);
155 const auto& tbl = table(repoId, "GlobalData");
156 stmt.prepare(
157 folly::sformat(
158 "SELECT count(*) FROM {};", tbl
161 auto txn = RepoTxn{begin()};
162 RepoTxnQuery query(txn, stmt);
163 query.step();
165 if (!query.row()) {
166 return false;
169 int val;
170 query.getInt(0, val);
171 return val != 0;
174 return false;
177 void Repo::loadGlobalData(bool readGlobalTables /* = true */) {
178 if (readGlobalTables) m_lsrp.load(RuntimeOption::RepoLitstrLazyLoad);
180 if (!RuntimeOption::RepoAuthoritative) return;
182 std::vector<std::string> failures;
185 * This should probably just go to the Local repo always, except
186 * that our unit test suite is currently running RepoAuthoritative
187 * tests with the compiled repo as the Central repo.
189 for (int repoId = numOpenRepos() - 1; repoId >= 0; --repoId) {
190 try {
191 RepoStmt stmt(*this);
192 const auto& tbl = table(repoId, "GlobalData");
193 stmt.prepare(
194 folly::sformat(
195 "SELECT data FROM {} WHERE key = @key;", tbl
198 auto txn = RepoTxn{begin()};
200 RepoTxnQuery query(txn, stmt);
201 auto key = std::string("config");
202 query.bindStdString("@key", key);
203 query.step();
204 if (!query.row()) {
205 throw RepoExc("Can't find key = 'config' in %s", tbl.c_str());
207 BlobDecoder decoder = query.getBlob(0, true);
208 decoder(s_globalData);
209 FTRACE(1, "GlobalData loaded from '{}':\n", repoName(repoId));
210 FTRACE(1, "{}", show(s_globalData));
213 if (readGlobalTables) {
215 RepoTxnQuery query(txn, stmt);
216 auto key = std::string("arraytable");
217 query.bindStdString("@key", key);
218 query.step();
219 if (!query.row()) {
220 throw RepoExc("Can't find key = 'arraytable' in %s", tbl.c_str());
222 BlobDecoder decoder = query.getBlob(0, true);
223 auto& arrayTypeTable = globalArrayTypeTable();
224 decoder(arrayTypeTable);
225 decoder(s_globalData.ConstantFunctions);
226 decoder.assertDone();
230 RepoTxnQuery query(txn, stmt);
231 auto key = std::string("autoloadmap");
232 query.bindStdString("@key", key);
233 query.step();
234 if (!query.row()) {
235 throw RepoExc("Can't find key = 'autoloadmap' in %s", tbl.c_str());
237 BlobDecoder decoder = query.getBlob(0, true);
238 s_globalData.AutoloadMap = RepoAutoloadMapBuilder::serde(decoder);
242 txn.commit();
243 } catch (RepoExc& e) {
244 failures.push_back(repoName(repoId) + ": " + e.msg());
245 continue;
248 // TODO: this should probably read out the other elements of the global data
249 // which control Option or RuntimeOption values -- the others are read out
250 // in an inconsistent and ad-hoc manner. But I don't understand their uses
251 // and interactions well enough to feel comfortable fixing now.
252 RuntimeOption::EnableIntrinsicsExtension =
253 s_globalData.EnableIntrinsicsExtension;
254 RuntimeOption::PHP7_Builtins = s_globalData.PHP7_Builtins;
255 RuntimeOption::PHP7_NoHexNumerics = s_globalData.PHP7_NoHexNumerics;
256 RuntimeOption::PHP7_Substr = s_globalData.PHP7_Substr;
257 RuntimeOption::EvalCheckPropTypeHints = s_globalData.CheckPropTypeHints;
258 RuntimeOption::EvalHackArrDVArrs = true; // TODO(kshaunak): Clean up.
260 always_assert(!RO::EvalArrayProvenance);
261 always_assert(!RO::EvalLogArrayProvenance);
263 RuntimeOption::EnableArgsInBacktraces = s_globalData.EnableArgsInBacktraces;
264 RuntimeOption::EvalAbortBuildOnVerifyError =
265 s_globalData.AbortBuildOnVerifyError;
266 RuntimeOption::StrictArrayFillKeys = s_globalData.StrictArrayFillKeys;
267 if (s_globalData.HardGenericsUB) {
268 RuntimeOption::EvalEnforceGenericsUB = 2;
271 RuntimeOption::EvalIsCompatibleClsMethType =
272 s_globalData.IsCompatibleClsMethType;
273 RuntimeOption::EvalEmitClassPointers = s_globalData.EmitClassPointers;
274 RuntimeOption::EvalEmitClsMethPointers = s_globalData.EmitClsMethPointers;
275 RO::EvalForbidDynamicCallsWithAttr =
276 s_globalData.ForbidDynamicCallsWithAttr;
278 RO::EvalHackArrCompatIsVecDictNotices =
279 s_globalData.HackArrCompatIsVecDictNotices;
281 RO::EvalRaiseClassConversionWarning =
282 s_globalData.RaiseClassConversionWarning;
283 RO::EvalClassPassesClassname =
284 s_globalData.ClassPassesClassname;
285 RO::EvalClassnameNotices =
286 s_globalData.ClassnameNotices;
287 RO::EvalRaiseClsMethConversionWarning =
288 s_globalData.RaiseClsMethConversionWarning;
290 RO::EvalNoticeOnCoerceForStrConcat =
291 s_globalData.NoticeOnCoerceForStrConcat;
293 RO::EvalNoticeOnCoerceForBitOp =
294 s_globalData.NoticeOnCoerceForBitOp;
296 RuntimeOption::ConstantFunctions.clear();
297 for (auto const& elm : s_globalData.ConstantFunctions) {
298 RuntimeOption::ConstantFunctions.insert(elm);
300 return;
303 if (failures.empty()) {
304 std::fprintf(stderr, "No repo was loadable. Check all the possible repo "
305 "locations (Repo.Central.Path, HHVM_REPO_CENTRAL_PATH, and "
306 "$HOME/.hhvm.hhbc) to make sure one of them is a valid "
307 "sqlite3 HHVM repo built with this exact HHVM version.\n");
308 } else {
309 // We should always have a global data section in RepoAuthoritative
310 // mode, or the repo is messed up.
311 std::fprintf(stderr, "Failed to load RepoGlobalData:\n");
312 for (auto& f : failures) {
313 std::fprintf(stderr, " %s\n", f.c_str());
317 assertx(Process::IsInMainThread());
318 exit(1);
321 void Repo::saveGlobalData(RepoGlobalData&& newData,
322 const RepoAutoloadMapBuilder& autoloadMapBuilder) {
323 s_globalData = std::move(newData);
325 auto const repoId = repoIdForNewUnit(UnitOrigin::File);
326 RepoStmt stmt(*this);
327 stmt.prepare(
328 folly::format(
329 "INSERT INTO {} VALUES(@key, @data);", table(repoId, "GlobalData")
330 ).str()
332 auto txn = RepoTxn{begin()};
334 RepoTxnQuery query(txn, stmt);
335 auto key = std::string("config");
336 query.bindStdString("@key", key);
337 BlobEncoder encoder{true};
338 encoder(s_globalData);
339 query.bindBlob("@data", encoder, /* static */ true);
340 query.exec();
344 RepoTxnQuery query(txn, stmt);
345 auto key = std::string("arraytable");
346 query.bindStdString("@key", key);
347 BlobEncoder encoder{true};
348 encoder(globalArrayTypeTable());
349 encoder(s_globalData.ConstantFunctions);
350 query.bindBlob("@data", encoder, /* static */ true);
351 query.exec();
355 RepoTxnQuery query(txn, stmt);
356 auto key = std::string("autoloadmap");
357 query.bindStdString("@key", key);
358 BlobEncoder encoder{true};
359 autoloadMapBuilder.serde(encoder);
360 query.bindBlob("@data", encoder, /* static */ true);
361 query.exec();
364 // TODO(#3521039): we could just put the litstr table in the same
365 // blob as the above and delete LitstrRepoProxy.
366 LitstrTable::get().setReading();
367 LitstrRepoProxy::InsertLitstrStmt insertStmt{*this, repoId};
368 LitstrTable::get().forEachLitstr(
369 [this, &txn, &insertStmt](int i, const StringData* name) {
370 insertStmt.insert(txn, i, name);
373 txn.commit();
376 std::unique_ptr<Unit> Repo::loadUnit(const folly::StringPiece name,
377 const SHA1& sha1,
378 const Native::FuncTable& nativeFuncs) {
379 if (m_dbc == nullptr) return nullptr;
380 tracing::Block _{
381 "repo-load-unit", [&] { return tracing::Props{}.add("name", name); }
383 return m_urp.load(name, sha1, nativeFuncs);
386 void Repo::forgetUnit(const std::string& name) {
387 if (m_dbc == nullptr) {
388 return;
391 auto const repoId = repoIdForNewUnit(UnitOrigin::File);
392 auto txn = RepoTxn{begin()};
393 m_removeFileHash[repoId].remove(txn, name);
394 txn.commit();
397 std::vector<std::pair<std::string,SHA1>>
398 Repo::enumerateUnits(int repoId, bool warn) {
399 std::vector<std::pair<std::string,SHA1>> ret;
401 try {
402 RepoStmt stmt(*this);
403 stmt.prepare(folly::sformat(
404 "SELECT path, sha1 FROM {};",
405 table(repoId, "FileSha1"))
407 auto txn = RepoTxn{begin()};
408 RepoTxnQuery query(txn, stmt);
410 for (query.step(); query.row(); query.step()) {
411 std::string path;
412 SHA1 sha1;
414 query.getStdString(0, path);
415 query.getSha1(1, sha1);
417 ret.emplace_back(path, sha1);
420 txn.commit();
421 } catch (RepoExc& e) {
422 if (warn) {
423 fprintf(stderr, "failed to enumerate units: %s\n", e.what());
425 // Ugh - the error is dropped. At least we might have printed an error to
426 // stderr.
429 return ret;
432 void Repo::InsertFileHashStmt::insert(RepoTxn& txn, const StringData* path,
433 const SHA1& sha1) {
434 if (!prepared()) {
435 auto insertQuery = folly::sformat(
436 "INSERT INTO {} VALUES(@path, @sha1);",
437 m_repo.table(m_repoId, "FileSha1"));
438 txn.prepare(*this, insertQuery);
440 RepoTxnQuery query(txn, *this);
441 query.bindStaticString("@path", path);
442 query.bindSha1("@sha1", sha1);
443 query.exec();
446 RepoStatus Repo::GetFileHashStmt::get(const char *path, SHA1& sha1) {
447 try {
448 auto txn = RepoTxn{m_repo.begin()};
449 if (!prepared()) {
450 auto selectQuery = folly::sformat(
451 "SELECT f.sha1 "
452 "FROM {} AS f, {} AS u "
453 "WHERE path == @path AND f.sha1 == u.sha1 "
454 "ORDER BY unitSn DESC LIMIT 1;",
455 m_repo.table(m_repoId, "FileSha1"),
456 m_repo.table(m_repoId, "Unit"));
457 txn.prepare(*this, selectQuery);
459 RepoTxnQuery query(txn, *this);
460 query.bindText("@path", path, strlen(path));
461 query.step();
462 if (!query.row()) {
463 return RepoStatus::error;
465 query.getSha1(0, sha1);
466 txn.commit();
467 return RepoStatus::success;
468 } catch (RepoExc& re) {
469 return RepoStatus::error;
473 void Repo::RemoveFileHashStmt::remove(RepoTxn& txn, const std::string& path) {
474 if (!prepared()) {
475 auto insertQuery = folly::sformat(
476 "DELETE FROM {} WHERE path == @path;",
477 m_repo.table(m_repoId, "FileSha1"));
478 txn.prepare(*this, insertQuery);
480 RepoTxnQuery query(txn, *this);
481 query.bindStdString("@path", path);
482 query.exec();
485 std::optional<String> Repo::GetUnitPathStmt::get(int64_t unitSn) {
486 auto txn = RepoTxn{m_repo.begin()};
487 if (!prepared()) {
488 auto selectQuery = folly::sformat(
489 "SELECT f.path "
490 "FROM {} AS u, {} AS f "
491 "WHERE u.unitSn == @unitSn AND f.sha1 == u.sha1",
492 m_repo.table(m_repoId, "Unit"),
493 m_repo.table(m_repoId, "FileSha1"));
494 txn.prepare(*this, selectQuery);
496 RepoTxnQuery query(txn, *this);
497 query.bindInt64("@unitSn", unitSn);
498 query.step();
499 if (!query.row()) {
500 return {};
503 StringData* spath; query.getStaticString(0, spath);
504 auto path = String(spath);
505 txn.commit();
506 return path;
509 std::optional<String> Repo::findPath(int64_t unitSn, const std::string& root) {
510 if (m_dbc == nullptr) {
511 return {};
514 int repoId;
515 for (repoId = numOpenRepos() - 1; repoId >= 0; --repoId) {
516 auto maybeRelPath = m_getUnitPath[repoId].get(unitSn);
517 if (maybeRelPath.has_value()) {
518 auto relPath = maybeRelPath.value();
519 String path;
520 if (relPath[0] == '/') {
521 path = relPath;
522 } else {
523 path = root + relPath;
525 TRACE(3, "Repo loaded file path for '%ld' from '%s'\n",
526 unitSn, repoName(repoId).c_str());
527 return path;
530 TRACE(3, "Repo file path: error loading '%ld'\n", unitSn);
531 return {};
534 RepoStatus Repo::GetUnitStmt::get(const char* path, int64_t& unitSn) {
535 try {
536 auto txn = RepoTxn{m_repo.begin()};
537 if (!prepared()) {
538 auto selectQuery = folly::sformat(
539 "SELECT u.unitSn "
540 "FROM {} AS f, {} AS u "
541 "WHERE f.path == @path AND f.sha1 == u.sha1",
542 m_repo.table(m_repoId, "FileSha1"),
543 m_repo.table(m_repoId, "Unit"));
544 txn.prepare(*this, selectQuery);
546 RepoTxnQuery query(txn, *this);
547 query.bindText("@path", path, strlen(path));
548 query.step();
549 if (!query.row()) {
550 return RepoStatus::error;
552 int unitSn_; /**/ query.getInt(0, unitSn_);
553 unitSn = unitSn_;
554 txn.commit();
555 return RepoStatus::success;
556 } catch (RepoExc& re) {
557 return RepoStatus::error;
561 RepoStatus Repo::findUnit(const char* path, const std::string& root,
562 int64_t& unitSn) {
563 if (m_dbc == nullptr) {
564 return RepoStatus::error;
566 int repoId;
567 for (repoId = numOpenRepos() - 1; repoId >= 0; --repoId) {
568 if (*path == '/' && !root.empty() &&
569 !strncmp(root.c_str(), path, root.size()) &&
570 (m_getUnit[repoId].get(path + root.size(), unitSn) ==
571 RepoStatus::success)) {
572 TRACE(3, "Repo loaded unit for '%s' from '%s'\n",
573 path + root.size(), repoName(repoId).c_str());
574 return RepoStatus::success;
576 if (m_getUnit[repoId].get(path, unitSn) == RepoStatus::success) {
577 TRACE(3, "Repo loaded unit for '%s' from '%s'\n",
578 path, repoName(repoId).c_str());
579 return RepoStatus::success;
582 TRACE(3, "Repo unit: error loading '%s'\n", path);
583 return RepoStatus::error;
586 RepoStatus Repo::findFile(const char *path, const std::string& root,
587 SHA1& sha1) {
588 tracing::Block _{
589 "repo-find-file",
590 [&] {
591 return tracing::Props{}
592 .add("path", path)
593 .add("root", root);
597 if (m_dbc == nullptr) {
598 return RepoStatus::error;
600 int repoId;
601 for (repoId = numOpenRepos() - 1; repoId >= 0; --repoId) {
602 if (*path == '/' && !root.empty() &&
603 !strncmp(root.c_str(), path, root.size()) &&
604 (m_getFileHash[repoId].get(path + root.size(), sha1) ==
605 RepoStatus::success)) {
606 TRACE(3, "Repo loaded file hash for '%s' from '%s'\n",
607 path + root.size(), repoName(repoId).c_str());
608 return RepoStatus::success;
610 if (m_getFileHash[repoId].get(path, sha1) == RepoStatus::success) {
611 TRACE(3, "Repo loaded file hash for '%s' from '%s'\n",
612 path, repoName(repoId).c_str());
613 return RepoStatus::success;
616 TRACE(3, "Repo file hash: error loading '%s'\n", path);
617 return RepoStatus::error;
620 RepoStatus Repo::insertSha1(UnitOrigin unitOrigin, UnitEmitter* ue,
621 RepoTxn& txn) {
622 const StringData* path = ue->m_filepath;
623 const SHA1& sha1 = ue->sha1();
624 int repoId = repoIdForNewUnit(unitOrigin);
625 if (repoId == RepoIdInvalid) {
626 return RepoStatus::error;
628 try {
629 m_insertFileHash[repoId].insert(txn, path, sha1);
630 return RepoStatus::success;
631 } catch (RepoExc& re) {
632 TRACE(3, "Failed to commit sha1 for '%s' to '%s': %s\n",
633 path->data(), repoName(repoId).c_str(), re.msg().c_str());
634 return RepoStatus::error;
638 void Repo::commitSha1(UnitOrigin unitOrigin, UnitEmitter* ue) {
639 try {
640 auto txn = RepoTxn{begin()};
641 RepoStatus err = insertSha1(unitOrigin, ue, txn);
642 if (err == RepoStatus::success) {
643 txn.commit();
645 } catch (RepoExc& re) {
646 tracing::addPointNoTrace("sha1-commit-exn");
647 int repoId = repoIdForNewUnit(unitOrigin);
648 if (repoId != RepoIdInvalid) {
649 TRACE(3, "Failed to commit sha1 for '%s' to '%s': %s\n",
650 ue->m_filepath->data(), repoName(repoId).c_str(),
651 re.msg().c_str());
656 std::string Repo::table(int repoId, const char* tablePrefix) {
657 return folly::sformat(
658 "{}.{}_{}", dbName(repoId), tablePrefix, repoSchemaId());
661 void Repo::exec(const std::string& sQuery) {
662 RepoStmt stmt(*this);
663 stmt.prepare(sQuery);
664 RepoQuery query(stmt);
665 query.exec();
668 RepoTxn Repo::begin() {
669 if (m_txDepth > 0) {
670 m_txDepth++;
671 return RepoTxn{*this};
673 if (debug) {
674 // Verify start state.
675 always_assert(m_txDepth == 0);
676 always_assert(!m_rollback);
677 if (true) {
678 // Bypass RepoQuery, in order to avoid triggering exceptions.
679 int rc = sqlite3_step(m_rollbackStmt.get());
680 switch (rc) {
681 case SQLITE_DONE:
682 case SQLITE_ROW:
683 not_reached();
684 default:
685 break;
687 } else {
688 bool rollbackFailed = false;
689 try {
690 RepoQuery query(m_rollbackStmt);
691 query.exec();
692 } catch (RepoExc& re) {
693 rollbackFailed = true;
695 always_assert(rollbackFailed);
698 RepoQuery query(m_beginStmt);
699 query.exec();
700 m_txDepth++;
702 return RepoTxn(*this);
705 void Repo::txPop() {
706 // We mix the concept of rollback with a normal commit so that if we try to
707 // rollback an inner transaction we eventually end up rolling back the outer
708 // transaction instead (Sqlite doesn't support rolling back partial
709 // transactions).
710 assertx(m_txDepth > 0);
711 if (m_txDepth > 1) {
712 m_txDepth--;
713 return;
715 if (!m_rollback) {
716 RepoQuery query(m_commitStmt);
717 query.exec();
718 } else {
719 // We're in the outermost transaction - so clear the rollback flag.
720 m_rollback = false;
721 RepoQuery query(m_rollbackStmt);
722 try {
723 query.exec();
724 } catch (RepoExc& ex) {
726 * Having a rollback fail is actually a normal, expected case,
727 * so just swallow this.
729 * In particular, according to the docs, if we got an I/O error
730 * while doing a commit, the rollback will often fail with "no
731 * transaction in progress", because the commit will have
732 * automatically been rolled back. Recommended practice is
733 * still to execute a rollback statement and ignore the error.
735 TRACE(4, "repo rollback failure: %s\n", ex.what());
738 // Decrement depth after query execution, in case an exception occurs during
739 // commit. This allows for subsequent rollback of the failed commit.
740 m_txDepth--;
743 void Repo::rollback() {
744 m_rollback = true;
745 // NOTE: A try/catch isn't necessary - txPop() handles rollback as a nothrow.
746 txPop();
749 void Repo::commit() {
750 txPop();
753 RepoStatus Repo::insertUnit(UnitEmitter* ue, UnitOrigin unitOrigin,
754 RepoTxn& txn, bool usePreAllocatedUnitSn) {
755 if (insertSha1(unitOrigin, ue, txn) == RepoStatus::error ||
756 ue->insert(unitOrigin, txn, usePreAllocatedUnitSn) == RepoStatus::error) {
757 return RepoStatus::error;
759 return RepoStatus::success;
762 void Repo::commitUnit(UnitEmitter* ue, UnitOrigin unitOrigin,
763 bool usePreAllocatedUnitSn) {
764 if (!RuntimeOption::RepoCommit || ue->m_ICE) return;
766 tracing::Block _{
767 "repo-commit-unit",
768 [&] { return tracing::Props{}.add("filename", ue->m_filepath); }
771 try {
772 commitSha1(unitOrigin, ue);
773 ue->commit(unitOrigin, usePreAllocatedUnitSn);
774 } catch (const std::exception& e) {
775 TRACE(0, "unexpected exception in commitUnit: %s\n",
776 e.what());
777 assertx(false);
781 void Repo::connect() {
782 m_numOpenRepos = 0;
783 if (initCentral()) {
784 m_numOpenRepos++;
786 if (initLocal()) {
787 m_numOpenRepos++;
791 void Repo::disconnect() noexcept {
792 if (m_dbc != nullptr) {
793 sqlite3_close_v2(m_dbc);
794 m_dbc = nullptr;
795 m_localWritable = false;
796 m_centralWritable = false;
798 m_numOpenRepos = 0;
801 bool Repo::initCentral() {
802 std::string error;
804 if (RuntimeOption::RepoCentralMode == RepoMode::Closed &&
805 RuntimeOption::RepoLocalMode == RepoMode::Closed) {
806 return false;
809 assertx(m_dbc == nullptr);
810 auto tryPath = [this, &error](const char* path) {
811 std::string subErr;
812 if (openCentral(path, subErr) == RepoStatus::error) {
813 folly::format(&error, " {}\n", subErr.empty() ? path : subErr);
814 return false;
816 return true;
819 auto fail_no_repo = [&error] {
820 error = "Failed to initialize central HHBC repository:\n" + error;
821 // Database initialization failed; this is an unrecoverable state.
822 Logger::Error(error);
824 if (Process::IsInMainThread()) {
825 exit(1);
827 always_assert_flog(false, "{}", error);
830 // Try Repo.Central.Path
831 if (!RuntimeOption::RepoCentralPath.empty() &&
832 tryPath(RuntimeOption::RepoCentralPath.c_str())) {
833 return true;
836 // Try HHVM_REPO_CENTRAL_PATH
837 const char* HHVM_REPO_CENTRAL_PATH = getenv("HHVM_REPO_CENTRAL_PATH");
838 if (HHVM_REPO_CENTRAL_PATH != nullptr &&
839 tryPath(HHVM_REPO_CENTRAL_PATH)) {
840 return true;
843 if (!RuntimeOption::RepoAllowFallbackPath) fail_no_repo();
845 // Try "$HOME/.hhvm.hhbc".
846 char* HOME = getenv("HOME");
847 if (HOME != nullptr) {
848 std::string centralPath = HOME;
849 centralPath += "/.hhvm.hhbc";
850 if (tryPath(centralPath.c_str())) {
851 return true;
855 #ifndef _WIN32
856 // Try the equivalent of "$HOME/.hhvm.hhbc", but look up the home directory
857 // in the password database.
859 auto buf = PasswdBuffer{};
860 passwd* pw;
861 auto err = getpwuid_r(getuid(), &buf.ent, buf.data.get(), buf.size, &pw);
862 if (err == 0 && pw != nullptr &&
863 (HOME == nullptr || strcmp(HOME, pw->pw_dir))) {
864 std::string centralPath = pw->pw_dir;
865 centralPath += "/.hhvm.hhbc";
866 if (tryPath(centralPath.c_str())) {
867 return true;
871 #else // _WIN32
872 // Try "$HOMEDRIVE$HOMEPATH/.hhvm.hhbc"
873 char* HOMEDRIVE = getenv("HOMEDRIVE");
874 if (HOMEDRIVE != nullptr) {
875 char* HOMEPATH = getenv("HOMEPATH");
876 if (HOMEPATH != nullptr) {
877 std::string centralPath = HOMEDRIVE;
878 centralPath += HOMEPATH;
879 centralPath += "\\.hhvm.hhbc";
880 if (tryPath(centralPath.c_str()))
881 return true;
884 #endif
886 fail_no_repo();
887 return false;
890 namespace {
892 * Convert the permission bits from the given stat struct to an ls-style
893 * rwxrwxrwx format.
895 std::string showPermissions(const struct stat& s) {
896 static const std::pair<int, char> bits[] = {
897 {S_IRUSR, 'r'}, {S_IWUSR, 'w'}, {S_IXUSR, 'x'},
898 {S_IRGRP, 'r'}, {S_IWGRP, 'w'}, {S_IXGRP, 'x'},
899 {S_IROTH, 'r'}, {S_IWOTH, 'w'}, {S_IXOTH, 'x'},
901 std::string ret;
902 ret.reserve(sizeof(bits) / sizeof(bits[0]));
904 for (auto pair : bits) {
905 ret += (s.st_mode & pair.first) ? pair.second : '-';
907 return ret;
911 * Return the name of the user with the given id.
913 std::string uidToName(uid_t uid) {
914 #ifndef _WIN32
915 auto buf = PasswdBuffer{};
916 passwd* pw;
918 auto err = getpwuid_r(uid, &buf.ent, buf.data.get(), buf.size, &pw);
919 if (err != 0) return folly::errnoStr(errno);
920 if (pw == nullptr) return "user does not exist";
921 return pw->pw_name;
922 #else
923 return "<unsupported>";
924 #endif
928 * Return the uid of the user with the given name.
930 uid_t nameToUid(const std::string& name) {
931 #ifndef _WIN32
932 auto buf = PasswdBuffer{};
933 passwd* pw;
935 auto err = getpwnam_r(name.c_str(), &buf.ent, buf.data.get(), buf.size, &pw);
936 if (err != 0 || pw == nullptr) return -1;
937 return pw->pw_uid;
938 #else
939 return -1;
940 #endif
944 * Return the name of the group with the given id.
946 std::string gidToName(gid_t gid) {
947 #ifndef _WIN32
948 auto buf = GroupBuffer{};
949 group* grp;
951 auto err = getgrgid_r(gid, &buf.ent, buf.data.get(), buf.size, &grp);
952 if (err != 0) return folly::errnoStr(errno);
953 if (grp == nullptr) return "group does not exist";
954 return grp->gr_name;
955 #else
956 return "<unsupported>";
957 #endif
961 * Return the gid of the group with the given name.
963 gid_t nameToGid(const std::string& name) {
964 #ifndef _WIN32
965 auto buf = GroupBuffer{};
966 group* grp;
968 auto err = getgrnam_r(name.c_str(), &buf.ent, buf.data.get(), buf.size, &grp);
969 if (err != 0 || grp == nullptr) return -1;
970 return grp->gr_gid;
971 #else
972 return -1;
973 #endif
976 void setCentralRepoFileMode(const std::string& path) {
977 // These runtime options are all best-effort, so we don't care if any of the
978 // operations fail.
980 if (auto const mode = RuntimeOption::RepoCentralFileMode) {
981 chmod(path.c_str(), mode);
984 if (!RuntimeOption::RepoCentralFileUser.empty()) {
985 auto const uid = nameToUid(RuntimeOption::RepoCentralFileUser);
986 chown(path.c_str(), uid, -1);
989 if (!RuntimeOption::RepoCentralFileGroup.empty()) {
990 auto const gid = nameToGid(RuntimeOption::RepoCentralFileGroup);
991 chown(path.c_str(), -1, gid);
996 namespace {
997 std::mutex s_sqliteCreateMutex;
1000 RepoStatus Repo::openCentral(const char* rawPath, std::string& errorMsg) {
1001 std::string repoPath = rawPath;
1002 replacePlaceholders(repoPath);
1003 // SQLITE_OPEN_NOMUTEX specifies that the connection be opened such
1004 // that no mutexes are used to protect the database connection from other
1005 // threads. However, multiple connections can still be used concurrently,
1006 // because SQLite as a whole is thread-safe.
1007 // Because we attach the local repo to the central one if either of them
1008 // is readwrite we need to open the central one with readwrite.
1009 int flags = SQLITE_OPEN_NOMUTEX;
1010 if (RuntimeOption::RepoCentralMode == RepoMode::ReadWrite ||
1011 RuntimeOption::RepoLocalMode == RepoMode::ReadWrite) {
1012 flags |= SQLITE_OPEN_READWRITE;
1013 if (RuntimeOption::RepoCentralMode == RepoMode::ReadWrite) {
1014 flags |= SQLITE_OPEN_CREATE;
1016 } else {
1017 flags |= SQLITE_OPEN_READONLY;
1021 // sqlite can't handle concurrent SQLITE_OPEN_CREATEs
1022 std::lock_guard<std::mutex> l(s_sqliteCreateMutex);
1023 if (int err = sqlite3_open_v2(repoPath.c_str(), &m_dbc, flags, nullptr)) {
1024 TRACE(1, "Repo::%s() failed to open candidate central repo '%s'\n",
1025 __func__, repoPath.c_str());
1026 errorMsg = folly::format("Failed to open {}: {} - {}",
1027 repoPath, err, sqlite3_errmsg(m_dbc)).str();
1028 return RepoStatus::error;
1032 if (RuntimeOption::RepoBusyTimeoutMS) {
1033 sqlite3_busy_timeout(m_dbc, RuntimeOption::RepoBusyTimeoutMS);
1035 try {
1036 m_beginStmt.prepare("BEGIN TRANSACTION;");
1037 m_rollbackStmt.prepare("ROLLBACK;");
1038 m_commitStmt.prepare("COMMIT;");
1039 pragmas(RepoIdCentral);
1040 } catch (RepoExc& re) {
1041 TRACE(1, "Repo::%s() failed to initialize connection to canditate repo"
1042 " '%s': %s\n", __func__, repoPath.c_str(), re.what());
1043 errorMsg = folly::format("Failed to initialize connection to {}: {}",
1044 repoPath, re.what()).str();
1045 return RepoStatus::error;
1048 // sqlite3_open_v2() will silently open in read-only mode if file permissions
1049 // prevent writing. Therefore, tell initSchema() to verify that the database
1050 // is writable.
1051 bool shouldBeWritable = RuntimeOption::RepoCentralMode == RepoMode::ReadWrite;
1052 bool centralWritable = shouldBeWritable;
1053 if (initSchema(RepoIdCentral, centralWritable, errorMsg) == RepoStatus::error
1054 || (!centralWritable && shouldBeWritable)) {
1055 TRACE(1, "Repo::initSchema() failed for candidate central repo '%s'\n",
1056 repoPath.c_str());
1057 struct stat repoStat;
1058 std::string statStr;
1059 if (stat(repoPath.c_str(), &repoStat) == 0) {
1060 statStr = folly::sformat("{} {}:{}",
1061 showPermissions(repoStat),
1062 uidToName(repoStat.st_uid),
1063 gidToName(repoStat.st_gid));
1064 } else {
1065 statStr = folly::errnoStr(errno);
1067 errorMsg = folly::format("Failed to initialize schema in {}({}): {}",
1068 repoPath, statStr, errorMsg).str();
1069 return RepoStatus::error;
1071 m_centralRepo = repoPath;
1072 m_centralWritable = centralWritable;
1073 setCentralRepoFileMode(repoPath);
1074 TRACE(1, "Central repo: '%s'\n", m_centralRepo.c_str());
1075 return RepoStatus::success;
1078 bool Repo::initLocal() {
1079 if (RuntimeOption::RepoLocalMode != RepoMode::Closed) {
1080 bool isWritable;
1081 if (RuntimeOption::RepoLocalMode == RepoMode::ReadWrite) {
1082 isWritable = true;
1083 } else {
1084 assertx(RuntimeOption::RepoLocalMode == RepoMode::ReadOnly);
1085 isWritable = false;
1088 if (!RuntimeOption::RepoLocalPath.empty()) {
1089 if (attachLocal(RuntimeOption::RepoLocalPath.c_str(), isWritable)) {
1090 return true;
1092 if (!s_deleteLocalOnFailure) {
1093 return false;
1096 unlink(RuntimeOption::RepoLocalPath.c_str());
1097 s_deleteLocalOnFailure = false;
1098 Logger::Warning("Deleting local repo because it was corrupt");
1099 return attachLocal(RuntimeOption::RepoLocalPath.c_str(), true);
1100 } else if (RuntimeOption::RepoAllowFallbackPath) {
1101 if (!RuntimeOption::ServerExecutionMode()) {
1102 std::string cliRepo = s_cliFile;
1103 if (!cliRepo.empty()) {
1104 cliRepo += ".hhbc";
1106 return attachLocal(cliRepo.c_str(), isWritable);
1107 } else {
1108 return attachLocal("hhvm.hhbc", isWritable);
1113 return false;
1116 bool Repo::attachLocal(const char* path, bool isWritable) {
1117 std::string repoPath = path;
1118 replacePlaceholders(repoPath);
1119 if (!isWritable) {
1120 // Make sure the repo exists before attaching it, in order to avoid
1121 // creating a read-only repo.
1122 struct stat buf;
1123 if (!strchr(repoPath.c_str(), ':') &&
1124 stat(repoPath.c_str(), &buf) != 0) {
1125 return false;
1128 try {
1129 m_localWritable = isWritable;
1130 auto attachQuery = folly::sformat(
1131 "ATTACH DATABASE '{}' as {};", repoPath, dbName(RepoIdLocal));
1132 exec(attachQuery);
1133 pragmas(RepoIdLocal);
1134 } catch (RepoExc& re) {
1135 // Failed to run pragmas on local DB - ignored
1136 m_localWritable = false;
1137 return false;
1140 std::string error;
1141 if (initSchema(RepoIdLocal, isWritable, error) == RepoStatus::error) {
1142 FTRACE(1, "Local repo {} failed to init schema: {}\n", repoPath, error);
1143 return false;
1145 m_localRepo = repoPath;
1146 TRACE(1, "Local repo: '%s' (read%s)\n",
1147 m_localRepo.c_str(), m_localWritable ? "-write" : "-only");
1148 return true;
1151 void Repo::pragmas(int repoId) {
1152 // Valid synchronous values: 0 (OFF), 1 (NORMAL), 2 (FULL).
1153 static const int synchronous = 0;
1154 setIntPragma(repoId, "synchronous", synchronous);
1155 setIntPragma(repoId, "cache_size", 20);
1156 // Valid journal_mode values: delete, truncate, persist, memory, wal, off.
1157 setTextPragma(repoId, "journal_mode", RuntimeOption::RepoJournal.c_str());
1160 void Repo::getIntPragma(int repoId, const char* name, int& val) {
1161 auto pragmaQuery = folly::sformat("PRAGMA {}.{};", dbName(repoId), name);
1162 RepoStmt stmt(*this);
1163 stmt.prepare(pragmaQuery);
1164 RepoQuery query(stmt);
1165 query.step();
1166 query.getInt(0, val);
1169 void Repo::setIntPragma(int repoId, const char* name, int val) {
1170 // Read first to see if a write can be avoided
1171 int oldval = -1;
1172 getIntPragma(repoId, name, oldval);
1173 if (val == oldval) return;
1175 // Pragma writes must be executed outside transactions, since they may change
1176 // transaction behavior.
1177 auto pragmaQuery = folly::sformat(
1178 "PRAGMA {}.{} = {};", dbName(repoId), name, val);
1179 exec(pragmaQuery);
1180 if (debug) {
1181 // Verify that the pragma had the desired effect.
1182 int newval = -1;
1183 getIntPragma(repoId, name, newval);
1184 if (newval != val) {
1185 throw RepoExc("Unexpected PRAGMA %s.%s value: %d\n",
1186 dbName(repoId), name, newval);
1191 void Repo::getTextPragma(int repoId, const char* name, std::string& val) {
1192 auto pragmaQuery = folly::sformat("PRAGMA {}.{};", dbName(repoId), name);
1193 RepoStmt stmt(*this);
1194 stmt.prepare(pragmaQuery);
1195 RepoQuery query(stmt);
1196 const char* s;
1197 query.step();
1198 query.getText(0, s);
1199 val = s;
1202 void Repo::setTextPragma(int repoId, const char* name, const char* val) {
1203 // Read first to see if a write can be avoided
1204 std::string oldval = "?";
1205 getTextPragma(repoId, name, oldval);
1206 if (!strcmp(oldval.c_str(), val)) return;
1207 // Pragma writes must be executed outside transactions, since they may change
1208 // transaction behavior.
1209 auto pragmaQuery = folly::sformat(
1210 "PRAGMA {}.{} = {};", dbName(repoId), name, val);
1211 exec(pragmaQuery);
1212 if (debug) {
1213 // Verify that the pragma had the desired effect.
1214 std::string newval = "?";
1215 getTextPragma(repoId, name, newval);
1216 if (strcmp(newval.c_str(), val)) {
1217 // If the db is in memory, journal mode will stick at "memory"
1218 // unless its turned off. Ignore attempts to change it to
1219 // something else.
1220 if (!strcmp(name, "journal_mode") &&
1221 !strcmp(newval.c_str(), "memory") &&
1222 strcmp(val, "off")) {
1223 return;
1225 throw RepoExc("Unexpected PRAGMA %s.%s value: %s (should be %s)\n",
1226 dbName(repoId), name, newval.c_str(), val);
1231 RepoStatus Repo::initSchema(int repoId, bool& isWritable,
1232 std::string& errorMsg) {
1233 if (!schemaExists(repoId)) {
1234 if (createSchema(repoId, errorMsg) == RepoStatus::error) {
1235 // Check whether this failure is due to losing the schema
1236 // initialization race with another process.
1237 if (!schemaExists(repoId)) {
1238 return RepoStatus::error;
1240 } else {
1241 // createSchema() successfully wrote to the database, so no further
1242 // verification is necessary.
1243 return RepoStatus::success;
1246 if (isWritable) {
1247 isWritable = writable(repoId);
1249 return RepoStatus::success;
1252 bool Repo::schemaExists(int repoId) {
1253 try {
1254 auto txn = RepoTxn{begin()};
1255 auto selectQuery = folly::sformat(
1256 "SELECT product FROM {};", table(repoId, "magic"));
1257 RepoStmt stmt(*this);
1258 // If the DB is 'new' and hasn't been initialized yet then we expect this
1259 // prepare() to fail.
1260 stmt.prepare(selectQuery);
1261 // This SHOULDN'T fail - we create the table under a transaction - so if it
1262 // exists then it should have our magic value.
1263 RepoTxnQuery query(txn, stmt);
1264 query.step();
1265 const char* text; /**/ query.getText(0, text);
1266 if (strcmp(kMagicProduct, text)) {
1267 return false;
1269 txn.commit();
1270 } catch (RepoExc& re) {
1271 return false;
1273 return true;
1276 RepoStatus Repo::createSchema(int repoId, std::string& errorMsg) {
1277 try {
1278 auto txn = RepoTxn{begin()};
1280 auto createQuery = folly::sformat(
1281 "CREATE TABLE {} (product TEXT);", table(repoId, "magic"));
1282 txn.exec(createQuery);
1284 auto insertQuery = folly::sformat(
1285 "INSERT INTO {} VALUES('{}');", table(repoId, "magic"), kMagicProduct);
1286 txn.exec(insertQuery);
1289 auto createQuery = folly::sformat(
1290 "CREATE TABLE {} (path TEXT, sha1 BLOB, UNIQUE(path, sha1));",
1291 table(repoId, "FileSha1"));
1292 txn.exec(createQuery);
1294 auto indexQuery = folly::sformat(
1295 "CREATE INDEX {}_sha1_index ON {}_{} (sha1);",
1296 table(repoId, "FileSha1"),
1297 "FileSha1",
1298 repoSchemaId());
1299 txn.exec(indexQuery);
1301 txn.exec(folly::sformat("CREATE TABLE {} (key TEXT, data BLOB);",
1302 table(repoId, "GlobalData")));
1303 m_urp.createSchema(repoId, txn);
1304 m_pcrp.createSchema(repoId, txn);
1305 m_rrp.createSchema(repoId, txn);
1306 m_frp.createSchema(repoId, txn);
1307 m_lsrp.createSchema(repoId, txn);
1309 txn.commit();
1310 } catch (RepoExc& re) {
1311 errorMsg = re.what();
1312 return RepoStatus::error;
1314 return RepoStatus::success;
1317 bool Repo::writable(int repoId) {
1318 switch (sqlite3_db_readonly(m_dbc, dbName(repoId))) {
1319 case 0: return true;
1320 case 1: return false;
1321 case -1: return false;
1322 default: break;
1324 always_assert(false);
1327 //////////////////////////////////////////////////////////////////////
1329 bool batchCommitWithoutRetry(const std::vector<std::unique_ptr<UnitEmitter>>& ues,
1330 bool usePreAllocatedUnitSn) {
1331 auto& repo = Repo::get();
1333 bool err = false;
1335 auto txn = RepoTxn{repo.begin()};
1337 for (auto& ue : ues) {
1338 assertx(!usePreAllocatedUnitSn || ue->m_sn != -1);
1339 if (repo.insertUnit(ue.get(), UnitOrigin::File, txn, usePreAllocatedUnitSn) ==
1340 RepoStatus::error) {
1341 err = true;
1342 break;
1345 if (!err) {
1346 txn.commit();
1350 return err;
1353 void batchCommit(const std::vector<std::unique_ptr<UnitEmitter>>& ues,
1354 bool usePreAllocatedUnitSn) {
1355 auto& repo = Repo::get();
1357 // Attempt batch commit. This can legitimately fail due to multiple input
1358 // files having identical contents.
1359 bool err = batchCommitWithoutRetry(ues, usePreAllocatedUnitSn);
1361 // Commit units individually if an error occurred during batch commit.
1362 if (err) {
1363 for (auto& ue : ues) {
1364 repo.commitUnit(ue.get(), UnitOrigin::File, usePreAllocatedUnitSn);
1369 //////////////////////////////////////////////////////////////////////