2 +----------------------------------------------------------------------+
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"
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"
46 const char* Repo::kMagicProduct
=
47 "facebook.com HipHop Virtual Machine bytecode repository";
48 const char* Repo::kDbs
[RepoIdCount
] = { "main", // Central.
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");
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
);
71 void Repo::shutdown() {
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.
89 if (s_nRepos
> 0 || AsyncFuncImpl::count()) {
90 XboxServer::Restart();
94 folly::SingletonVault::singleton()->destroyInstances();
98 void Repo::postfork(pid_t pid
) {
99 folly::SingletonVault::singleton()->reenableInstances();
100 XboxServer::Restart();
103 new (&s_lock
) SimpleMutex();
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) {
127 Repo::~Repo() noexcept
{
132 std::string
Repo::s_cliFile
;
133 void Repo::setCliFile(const std::string
& cliFile
) {
134 assertx(s_cliFile
.empty());
135 assertx(t_dh
.isNull());
139 size_t Repo::stringLengthLimit() const {
140 static const size_t limit
= sqlite3_limit(m_dbc
, SQLITE_LIMIT_LENGTH
, -1);
144 bool Repo::hasGlobalData() {
145 for (int repoId
= RepoIdCount
- 1; repoId
>= 0; --repoId
) {
146 if (repoName(repoId
).empty()) {
147 // The repo wasn't loadable
151 RepoStmt
stmt(*this);
152 const auto& tbl
= table(repoId
, "GlobalData");
155 "SELECT count(*) FROM {};", tbl
158 auto txn
= RepoTxn
{begin()};
159 RepoTxnQuery
query(txn
, stmt
);
167 query
.getInt(0, val
);
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
192 RepoStmt
stmt(*this);
193 const auto& tbl
= table(repoId
, "GlobalData");
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
);
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
);
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
);
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
);
247 } catch (RepoExc
& e
) {
248 failures
.push_back(repoName(repoId
) + ": " + e
.msg());
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
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
);
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");
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());
314 void Repo::saveGlobalData(GlobalData
&& newData
) {
315 s_globalData
= std::move(newData
);
317 auto const repoId
= repoIdForNewUnit(UnitOrigin::File
);
318 RepoStmt
stmt(*this);
321 "INSERT INTO {} VALUES(@key, @data);", table(repoId
, "GlobalData")
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);
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);
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);
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
);
369 std::unique_ptr
<Unit
> Repo::loadUnit(const folly::StringPiece name
,
371 const Native::FuncTable
& nativeFuncs
) {
372 ARRPROV_USE_RUNTIME_LOCATION();
373 if (m_dbc
== nullptr) return nullptr;
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) {
385 auto const repoId
= repoIdForNewUnit(UnitOrigin::File
);
386 auto txn
= RepoTxn
{begin()};
387 m_removeFileHash
[repoId
].remove(txn
, name
);
391 std::vector
<std::pair
<std::string
,SHA1
>>
392 Repo::enumerateUnits(int repoId
, bool warn
) {
393 std::vector
<std::pair
<std::string
,SHA1
>> ret
;
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()) {
408 query
.getStdString(0, path
);
409 query
.getSha1(1, sha1
);
411 ret
.emplace_back(path
, sha1
);
415 } catch (RepoExc
& e
) {
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
426 void Repo::InsertFileHashStmt::insert(RepoTxn
& txn
, const StringData
* path
,
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
);
440 RepoStatus
Repo::GetFileHashStmt::get(const char *path
, SHA1
& sha1
) {
442 auto txn
= RepoTxn
{m_repo
.begin()};
444 auto selectQuery
= folly::sformat(
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
));
457 return RepoStatus::error
;
459 query
.getSha1(0, sha1
);
461 return RepoStatus::success
;
462 } catch (RepoExc
& re
) {
463 return RepoStatus::error
;
467 void Repo::RemoveFileHashStmt::remove(RepoTxn
& txn
, const std::string
& path
) {
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
);
479 RepoStatus
Repo::GetUnitPathStmt::get(int64_t unitSn
, String
& path
) {
481 auto txn
= RepoTxn
{m_repo
.begin()};
483 auto selectQuery
= folly::sformat(
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
);
495 return RepoStatus::error
;
497 StringData
* spath
; query
.getStaticString(0, spath
);
498 path
= String(spath
);
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
;
511 for (repoId
= RepoIdCount
- 1; repoId
>= 0; --repoId
) {
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
) {
526 auto txn
= RepoTxn
{m_repo
.begin()};
528 auto selectQuery
= folly::sformat(
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
));
540 return RepoStatus::error
;
542 int unitSn_
; /**/ query
.getInt(0, unitSn_
);
545 return RepoStatus::success
;
546 } catch (RepoExc
& re
) {
547 return RepoStatus::error
;
551 RepoStatus
Repo::findUnit(const char* path
, const std::string
& root
,
553 if (m_dbc
== nullptr) {
554 return RepoStatus::error
;
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
,
581 return tracing::Props
{}
587 if (m_dbc
== nullptr) {
588 return RepoStatus::error
;
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
,
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
;
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
) {
630 auto txn
= RepoTxn
{begin()};
631 RepoStatus err
= insertSha1(unitOrigin
, ue
, txn
);
632 if (err
== RepoStatus::success
) {
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(),
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
);
658 RepoTxn
Repo::begin() {
661 return RepoTxn
{*this};
664 // Verify start state.
665 always_assert(m_txDepth
== 0);
666 always_assert(!m_rollback
);
668 // Bypass RepoQuery, in order to avoid triggering exceptions.
669 int rc
= sqlite3_step(m_rollbackStmt
.get());
678 bool rollbackFailed
= false;
680 RepoQuery
query(m_rollbackStmt
);
682 } catch (RepoExc
& re
) {
683 rollbackFailed
= true;
685 always_assert(rollbackFailed
);
688 RepoQuery
query(m_beginStmt
);
692 return RepoTxn(*this);
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
700 assertx(m_txDepth
> 0);
706 RepoQuery
query(m_commitStmt
);
709 // We're in the outermost transaction - so clear the rollback flag.
711 RepoQuery
query(m_rollbackStmt
);
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.
733 void Repo::rollback() {
735 // NOTE: A try/catch isn't necessary - txPop() handles rollback as a nothrow.
739 void Repo::commit() {
743 RepoStatus
Repo::insertUnit(UnitEmitter
* ue
, UnitOrigin unitOrigin
,
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;
757 [&] { return tracing::Props
{}.add("filename", ue
->m_filepath
); }
761 commitSha1(unitOrigin
, ue
);
762 ue
->commit(unitOrigin
);
763 } catch (const std::exception
& e
) {
764 TRACE(0, "unexpected exception in commitUnit: %s\n",
770 void Repo::connect() {
773 if (!RuntimeOption::RepoEvalMode
.compare("local")) {
774 m_evalRepoId
= (m_localWritable
) ? RepoIdLocal
: RepoIdCentral
;
775 } else if (!RuntimeOption::RepoEvalMode
.compare("central")) {
776 m_evalRepoId
= RepoIdCentral
;
778 assertx(!RuntimeOption::RepoEvalMode
.compare("readonly"));
779 m_evalRepoId
= RepoIdInvalid
;
781 TRACE(1, "Repo.Eval.Mode=%s\n",
782 (m_evalRepoId
== RepoIdLocal
)
784 : (m_evalRepoId
== RepoIdCentral
)
789 void Repo::disconnect() noexcept
{
790 if (m_dbc
!= nullptr) {
791 sqlite3_close_v2(m_dbc
);
793 m_localReadable
= false;
794 m_localWritable
= false;
795 m_evalRepoId
= RepoIdInvalid
;
799 void Repo::initCentral() {
802 assertx(m_dbc
== nullptr);
803 auto tryPath
= [this, &error
](const char* path
) {
805 if (openCentral(path
, subErr
) == RepoStatus::error
) {
806 folly::format(&error
, " {}\n", subErr
.empty() ? path
: subErr
);
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()) {
820 always_assert_flog(false, "{}", error
);
823 // Try Repo.Central.Path
824 if (!RuntimeOption::RepoCentralPath
.empty() &&
825 tryPath(RuntimeOption::RepoCentralPath
.c_str())) {
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
)) {
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())) {
849 // Try the equivalent of "$HOME/.hhvm.hhbc", but look up the home directory
850 // in the password database.
852 auto buf
= PasswdBuffer
{};
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())) {
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()))
884 * Convert the permission bits from the given stat struct to an ls-style
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'},
894 ret
.reserve(sizeof(bits
) / sizeof(bits
[0]));
896 for (auto pair
: bits
) {
897 ret
+= (s
.st_mode
& pair
.first
) ? pair
.second
: '-';
903 * Return the name of the user with the given id.
905 std::string
uidToName(uid_t uid
) {
907 auto buf
= PasswdBuffer
{};
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";
915 return "<unsupported>";
920 * Return the uid of the user with the given name.
922 uid_t
nameToUid(const std::string
& name
) {
924 auto buf
= PasswdBuffer
{};
927 auto err
= getpwnam_r(name
.c_str(), &buf
.ent
, buf
.data
.get(), buf
.size
, &pw
);
928 if (err
!= 0 || pw
== nullptr) return -1;
936 * Return the name of the group with the given id.
938 std::string
gidToName(gid_t gid
) {
940 auto buf
= GroupBuffer
{};
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";
948 return "<unsupported>";
953 * Return the gid of the group with the given name.
955 gid_t
nameToGid(const std::string
& name
) {
957 auto buf
= GroupBuffer
{};
960 auto err
= getgrnam_r(name
.c_str(), &buf
.ent
, buf
.data
.get(), buf
.size
, &grp
);
961 if (err
!= 0 || grp
== nullptr) return -1;
968 void setCentralRepoFileMode(const std::string
& path
) {
969 // These runtime options are all best-effort, so we don't care if any of the
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
);
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
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",
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
));
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("--")) {
1053 if (!RuntimeOption::RepoLocalMode
.compare("rw")) {
1056 assertx(!RuntimeOption::RepoLocalMode
.compare("r-"));
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()) {
1068 attachLocal(cliRepo
.c_str(), isWritable
);
1070 attachLocal("hhvm.hhbc", isWritable
);
1076 void Repo::attachLocal(const char* path
, bool isWritable
) {
1077 std::string repoPath
= path
;
1078 replacePlaceholders(repoPath
);
1080 // Make sure the repo exists before attaching it, in order to avoid
1081 // creating a read-only repo.
1083 if (!strchr(repoPath
.c_str(), ':') &&
1084 stat(repoPath
.c_str(), &buf
) != 0) {
1089 auto attachQuery
= folly::sformat(
1090 "ATTACH DATABASE '{}' as {};", repoPath
, dbName(RepoIdLocal
));
1092 pragmas(RepoIdLocal
);
1093 } catch (RepoExc
& re
) {
1094 // Failed to run pragmas on local DB - ignored
1099 if (initSchema(RepoIdLocal
, isWritable
, error
) == RepoStatus::error
) {
1100 FTRACE(1, "Local repo {} failed to init schema: {}\n", repoPath
, error
);
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
);
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
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
);
1140 // Verify that the pragma had the desired effect.
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
);
1157 query
.getText(0, 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
);
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
1179 if (!strcmp(name
, "journal_mode") &&
1180 !strcmp(newval
.c_str(), "memory") &&
1181 strcmp(val
, "off")) {
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
;
1200 // createSchema() successfully wrote to the database, so no further
1201 // verification is necessary.
1202 return RepoStatus::success
;
1206 isWritable
= writable(repoId
);
1208 return RepoStatus::success
;
1211 bool Repo::schemaExists(int repoId
) {
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
);
1224 const char* text
; /**/ query
.getText(0, text
);
1225 if (strcmp(kMagicProduct
, text
)) {
1229 } catch (RepoExc
& re
) {
1235 RepoStatus
Repo::createSchema(int repoId
, std::string
& errorMsg
) {
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"),
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
);
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;
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.
1295 auto txn
= RepoTxn
{repo
.begin()};
1297 for (auto& ue
: ues
) {
1298 if (repo
.insertUnit(ue
.get(), UnitOrigin::File
, txn
) ==
1299 RepoStatus::error
) {
1309 // Commit units individually if an error occurred during batch commit.
1311 for (auto& ue
: ues
) {
1312 repo
.commitUnit(ue
.get(), UnitOrigin::File
);
1317 //////////////////////////////////////////////////////////////////////