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/hhbbc/options.h"
24 #include "hphp/runtime/vm/blob-helper.h"
25 #include "hphp/runtime/vm/repo-global-data.h"
26 #include "hphp/runtime/server/xbox-server.h"
28 #include "hphp/util/assertions.h"
29 #include "hphp/util/async-func.h"
30 #include "hphp/util/build-info.h"
31 #include "hphp/util/hugetlb.h"
32 #include "hphp/util/logger.h"
33 #include "hphp/util/process.h"
34 #include "hphp/util/trace.h"
44 const char* Repo::kMagicProduct
=
45 "facebook.com HipHop Virtual Machine bytecode repository";
46 const char* Repo::kDbs
[RepoIdCount
] = { "main", // Central.
48 Repo::GlobalData
Repo::s_globalData
;
50 void initialize_repo() {
51 if (!sqlite3_threadsafe()) {
52 TRACE(0, "SQLite was compiled without thread support; aborting\n");
55 if (sqlite3_config(SQLITE_CONFIG_MULTITHREAD
) != SQLITE_OK
) {
56 TRACE(1, "Failed to set default SQLite multi-threading mode\n");
58 if (sqlite3_config(SQLITE_CONFIG_MEMSTATUS
, 0) != SQLITE_OK
) {
59 TRACE(1, "Failed to disable SQLite memory statistics\n");
63 THREAD_LOCAL(Repo
, t_dh
);
69 void Repo::shutdown() {
73 static SimpleMutex s_lock
;
74 static std::atomic
<unsigned> s_nRepos
;
76 bool Repo::prefork() {
77 if (num_1g_pages() > 0) {
78 // We put data on the 1G huge pages, and we don't want to do COW upon
79 // fork(). If you need to fork(), configure HHVM not to use 1G pages.
87 if (s_nRepos
> 0 || AsyncFuncImpl::count()) {
88 XboxServer::Restart();
92 folly::SingletonVault::singleton()->destroyInstances();
96 void Repo::postfork(pid_t pid
) {
97 folly::SingletonVault::singleton()->reenableInstances();
98 XboxServer::Restart();
101 new (&s_lock
) SimpleMutex();
109 m_insertFileHash
{InsertFileHashStmt(*this, 0),
110 InsertFileHashStmt(*this, 1)},
111 m_getFileHash
{GetFileHashStmt(*this, 0), GetFileHashStmt(*this, 1)},
112 m_dbc(nullptr), m_localReadable(false), m_localWritable(false),
113 m_evalRepoId(-1), m_txDepth(0), m_rollback(false), m_beginStmt(*this),
114 m_rollbackStmt(*this), m_commitStmt(*this), m_urp(*this), m_pcrp(*this),
115 m_rrp(*this), m_frp(*this), m_lsrp(*this) {
121 Repo::~Repo() noexcept
{
126 std::string
Repo::s_cliFile
;
127 void Repo::setCliFile(const std::string
& cliFile
) {
128 assertx(s_cliFile
.empty());
129 assertx(t_dh
.isNull());
133 size_t Repo::stringLengthLimit() const {
134 static const size_t limit
= sqlite3_limit(m_dbc
, SQLITE_LIMIT_LENGTH
, -1);
138 bool Repo::hasGlobalData() {
139 for (int repoId
= RepoIdCount
- 1; repoId
>= 0; --repoId
) {
140 if (repoName(repoId
).empty()) {
141 // The repo wasn't loadable
145 RepoStmt
stmt(*this);
146 const auto& tbl
= table(repoId
, "GlobalData");
149 "SELECT count(*) FROM {};", tbl
152 auto txn
= RepoTxn
{begin()};
153 RepoTxnQuery
query(txn
, stmt
);
161 query
.getInt(0, val
);
168 void Repo::loadGlobalData(bool readArrayTable
/* = true */) {
169 if (readArrayTable
) m_lsrp
.load();
171 if (!RuntimeOption::RepoAuthoritative
) return;
173 std::vector
<std::string
> failures
;
176 * This should probably just go to the Local repo always, except
177 * that our unit test suite is currently running RepoAuthoritative
178 * tests with the compiled repo as the Central repo.
180 for (int repoId
= RepoIdCount
- 1; repoId
>= 0; --repoId
) {
181 if (repoName(repoId
).empty()) {
182 // The repo wasn't loadable
186 RepoStmt
stmt(*this);
187 const auto& tbl
= table(repoId
, "GlobalData");
190 "SELECT count(*), data FROM {};", tbl
193 auto txn
= RepoTxn
{begin()};
194 RepoTxnQuery
query(txn
, stmt
);
197 throw RepoExc("Can't find table %s", tbl
.c_str());
200 query
.getInt(0, val
);
202 throw RepoExc("No rows in %s. Did you forget to compile that file with "
203 "this HHVM version?", tbl
.c_str());
205 BlobDecoder decoder
= query
.getBlob(1, true);
206 decoder(s_globalData
);
207 FTRACE(1, "GlobalData loaded from '{}':\n", repoName(repoId
));
208 FTRACE(1, "{}", show(s_globalData
));
209 if (readArrayTable
) {
210 auto& arrayTypeTable
= globalArrayTypeTable();
211 decoder(arrayTypeTable
);
212 decoder(s_globalData
.APCProfile
);
213 decoder(s_globalData
.ConstantFunctions
);
214 decoder
.assertDone();
217 } catch (RepoExc
& e
) {
218 failures
.push_back(repoName(repoId
) + ": " + e
.msg());
222 // TODO: this should probably read out the other elements of the global data
223 // which control Option or RuntimeOption values -- the others are read out
224 // in an inconsistent and ad-hoc manner. But I don't understand their uses
225 // and interactions well enough to feel comfortable fixing now.
226 RuntimeOption::EvalPromoteEmptyObject
= s_globalData
.PromoteEmptyObject
;
227 RuntimeOption::EnableIntrinsicsExtension
=
228 s_globalData
.EnableIntrinsicsExtension
;
229 HHBBC::options
.ElideAutoloadInvokes
= s_globalData
.ElideAutoloadInvokes
;
230 RuntimeOption::EnableHipHopSyntax
= s_globalData
.EnableHipHopSyntax
;
231 RuntimeOption::EvalUseHHBBC
= s_globalData
.UsedHHBBC
;
232 RuntimeOption::PHP7_Builtins
= s_globalData
.PHP7_Builtins
;
233 RuntimeOption::PHP7_IntSemantics
= s_globalData
.PHP7_IntSemantics
;
234 RuntimeOption::PHP7_NoHexNumerics
= s_globalData
.PHP7_NoHexNumerics
;
235 RuntimeOption::PHP7_ScalarTypes
= s_globalData
.PHP7_ScalarTypes
;
236 RuntimeOption::PHP7_Substr
= s_globalData
.PHP7_Substr
;
237 RuntimeOption::EvalReffinessInvariance
= s_globalData
.ReffinessInvariance
;
238 RuntimeOption::EvalCheckPropTypeHints
= s_globalData
.CheckPropTypeHints
;
239 RuntimeOption::EvalHackArrDVArrs
= s_globalData
.HackArrDVArrs
;
240 RuntimeOption::EnableArgsInBacktraces
= s_globalData
.EnableArgsInBacktraces
;
241 RuntimeOption::EvalAbortBuildOnVerifyError
=
242 s_globalData
.AbortBuildOnVerifyError
;
243 if (s_globalData
.HardReturnTypeHints
) {
244 RuntimeOption::EvalCheckReturnTypeHints
= 3;
246 if (s_globalData
.ThisTypeHintLevel
== 3) {
247 RuntimeOption::EvalThisTypeHintLevel
= s_globalData
.ThisTypeHintLevel
;
249 RuntimeOption::ConstantFunctions
.clear();
250 for (auto const& elm
: s_globalData
.ConstantFunctions
) {
251 RuntimeOption::ConstantFunctions
.insert(elm
);
257 if (failures
.empty()) {
258 std::fprintf(stderr
, "No repo was loadable. Check all the possible repo "
259 "locations (Repo.Central.Path, HHVM_REPO_CENTRAL_PATH, and "
260 "$HOME/.hhvm.hhbc) to make sure one of them is a valid "
261 "sqlite3 HHVM repo built with this exact HHVM version.\n");
263 // We should always have a global data section in RepoAuthoritative
264 // mode, or the repo is messed up.
265 std::fprintf(stderr
, "Failed to load Repo::GlobalData:\n");
266 for (auto& f
: failures
) {
267 std::fprintf(stderr
, " %s\n", f
.c_str());
271 assertx(Process::IsInMainThread());
275 void Repo::saveGlobalData(GlobalData newData
) {
276 s_globalData
= newData
;
278 auto const repoId
= repoIdForNewUnit(UnitOrigin::File
);
279 RepoStmt
stmt(*this);
282 "INSERT INTO {} VALUES(@data);", table(repoId
, "GlobalData")
285 auto txn
= RepoTxn
{begin()};
286 RepoTxnQuery
query(txn
, stmt
);
287 BlobEncoder encoder
{true};
288 encoder(s_globalData
);
289 encoder(globalArrayTypeTable());
290 encoder(s_globalData
.APCProfile
);
291 encoder(s_globalData
.ConstantFunctions
);
292 query
.bindBlob("@data", encoder
, /* static */ true);
295 // TODO(#3521039): we could just put the litstr table in the same
296 // blob as the above and delete LitstrRepoProxy.
297 LitstrTable::get().forEachLitstr(
298 [this, &txn
, repoId
](int i
, const StringData
* name
) {
299 lsrp().insertLitstr(repoId
).insert(txn
, i
, name
);
305 std::unique_ptr
<Unit
> Repo::loadUnit(const std::string
& name
, const SHA1
& sha1
,
306 const Native::FuncTable
& nativeFuncs
) {
307 if (m_dbc
== nullptr) {
310 return m_urp
.load(name
, sha1
, nativeFuncs
);
313 std::vector
<std::pair
<std::string
,SHA1
>>
314 Repo::enumerateUnits(int repoId
, bool warn
) {
315 std::vector
<std::pair
<std::string
,SHA1
>> ret
;
318 RepoStmt
stmt(*this);
319 stmt
.prepare(folly::sformat(
320 "SELECT path, sha1 FROM {};",
321 table(repoId
, "FileSha1"))
323 auto txn
= RepoTxn
{begin()};
324 RepoTxnQuery
query(txn
, stmt
);
326 for (query
.step(); query
.row(); query
.step()) {
330 query
.getStdString(0, path
);
331 query
.getSha1(1, sha1
);
333 ret
.emplace_back(path
, sha1
);
337 } catch (RepoExc
& e
) {
339 fprintf(stderr
, "failed to enumerate units: %s\n", e
.what());
341 // Ugh - the error is dropped. At least we might have printed an error to
348 void Repo::InsertFileHashStmt::insert(RepoTxn
& txn
, const StringData
* path
,
351 auto insertQuery
= folly::sformat(
352 "INSERT INTO {} VALUES(@path, @sha1);",
353 m_repo
.table(m_repoId
, "FileSha1"));
354 txn
.prepare(*this, insertQuery
);
356 RepoTxnQuery
query(txn
, *this);
357 query
.bindStaticString("@path", path
);
358 query
.bindSha1("@sha1", sha1
);
362 RepoStatus
Repo::GetFileHashStmt::get(const char *path
, SHA1
& sha1
) {
364 auto txn
= RepoTxn
{m_repo
.begin()};
366 auto selectQuery
= folly::sformat(
368 "FROM {} AS f, {} AS u "
369 "WHERE path == @path AND f.sha1 == u.sha1 "
370 "ORDER BY unitSn DESC LIMIT 1;",
371 m_repo
.table(m_repoId
, "FileSha1"),
372 m_repo
.table(m_repoId
, "Unit"));
373 txn
.prepare(*this, selectQuery
);
375 RepoTxnQuery
query(txn
, *this);
376 query
.bindText("@path", path
, strlen(path
));
379 return RepoStatus::error
;
381 query
.getSha1(0, sha1
);
383 return RepoStatus::success
;
384 } catch (RepoExc
& re
) {
385 return RepoStatus::error
;
389 RepoStatus
Repo::findFile(const char *path
, const std::string
&root
,
391 if (m_dbc
== nullptr) {
392 return RepoStatus::error
;
395 for (repoId
= RepoIdCount
- 1; repoId
>= 0; --repoId
) {
396 if (*path
== '/' && !root
.empty() &&
397 !strncmp(root
.c_str(), path
, root
.size()) &&
398 (m_getFileHash
[repoId
].get(path
+ root
.size(), sha1
) ==
399 RepoStatus::success
)) {
400 TRACE(3, "Repo loaded file hash for '%s' from '%s'\n",
401 path
+ root
.size(), repoName(repoId
).c_str());
402 return RepoStatus::success
;
404 if (m_getFileHash
[repoId
].get(path
, sha1
) == RepoStatus::success
) {
405 TRACE(3, "Repo loaded file hash for '%s' from '%s'\n",
406 path
, repoName(repoId
).c_str());
407 return RepoStatus::success
;
410 TRACE(3, "Repo file hash: error loading '%s'\n", path
);
411 return RepoStatus::error
;
414 RepoStatus
Repo::insertSha1(UnitOrigin unitOrigin
, UnitEmitter
* ue
,
416 const StringData
* path
= ue
->m_filepath
;
417 const SHA1
& sha1
= ue
->sha1();
418 int repoId
= repoIdForNewUnit(unitOrigin
);
419 if (repoId
== RepoIdInvalid
) {
420 return RepoStatus::error
;
423 m_insertFileHash
[repoId
].insert(txn
, path
, sha1
);
424 return RepoStatus::success
;
425 } catch (RepoExc
& re
) {
426 TRACE(3, "Failed to commit sha1 for '%s' to '%s': %s\n",
427 path
->data(), repoName(repoId
).c_str(), re
.msg().c_str());
428 return RepoStatus::error
;
432 void Repo::commitSha1(UnitOrigin unitOrigin
, UnitEmitter
* ue
) {
434 auto txn
= RepoTxn
{begin()};
435 RepoStatus err
= insertSha1(unitOrigin
, ue
, txn
);
436 if (err
== RepoStatus::success
) {
439 } catch (RepoExc
& re
) {
440 int repoId
= repoIdForNewUnit(unitOrigin
);
441 if (repoId
!= RepoIdInvalid
) {
442 TRACE(3, "Failed to commit sha1 for '%s' to '%s': %s\n",
443 ue
->m_filepath
->data(), repoName(repoId
).c_str(),
449 std::string
Repo::table(int repoId
, const char* tablePrefix
) {
450 return folly::sformat(
451 "{}.{}_{}", dbName(repoId
), tablePrefix
, repoSchemaId());
454 void Repo::exec(const std::string
& sQuery
) {
455 RepoStmt
stmt(*this);
456 stmt
.prepare(sQuery
);
457 RepoQuery
query(stmt
);
461 RepoTxn
Repo::begin() {
464 return RepoTxn
{*this};
467 // Verify start state.
468 always_assert(m_txDepth
== 0);
469 always_assert(!m_rollback
);
471 // Bypass RepoQuery, in order to avoid triggering exceptions.
472 int rc
= sqlite3_step(m_rollbackStmt
.get());
481 bool rollbackFailed
= false;
483 RepoQuery
query(m_rollbackStmt
);
485 } catch (RepoExc
& re
) {
486 rollbackFailed
= true;
488 always_assert(rollbackFailed
);
491 RepoQuery
query(m_beginStmt
);
495 return RepoTxn(*this);
499 // We mix the concept of rollback with a normal commit so that if we try to
500 // rollback an inner transaction we eventually end up rolling back the outer
501 // transaction instead (Sqlite doesn't support rolling back partial
503 assertx(m_txDepth
> 0);
509 RepoQuery
query(m_commitStmt
);
512 // We're in the outermost transaction - so clear the rollback flag.
514 RepoQuery
query(m_rollbackStmt
);
517 } catch (RepoExc
& ex
) {
519 * Having a rollback fail is actually a normal, expected case,
520 * so just swallow this.
522 * In particular, according to the docs, if we got an I/O error
523 * while doing a commit, the rollback will often fail with "no
524 * transaction in progress", because the commit will have
525 * automatically been rolled back. Recommended practice is
526 * still to execute a rollback statement and ignore the error.
528 TRACE(4, "repo rollback failure: %s\n", ex
.what());
531 // Decrement depth after query execution, in case an exception occurs during
532 // commit. This allows for subsequent rollback of the failed commit.
536 void Repo::rollback() {
538 // NOTE: A try/catch isn't necessary - txPop() handles rollback as a nothrow.
542 void Repo::commit() {
546 RepoStatus
Repo::insertUnit(UnitEmitter
* ue
, UnitOrigin unitOrigin
,
548 if (insertSha1(unitOrigin
, ue
, txn
) == RepoStatus::error
||
549 ue
->insert(unitOrigin
, txn
) == RepoStatus::error
) {
550 return RepoStatus::error
;
552 return RepoStatus::success
;
555 void Repo::commitUnit(UnitEmitter
* ue
, UnitOrigin unitOrigin
) {
556 if (!RuntimeOption::RepoCommit
|| ue
->m_ICE
) return;
559 commitSha1(unitOrigin
, ue
);
560 ue
->commit(unitOrigin
);
561 } catch (const std::exception
& e
) {
562 TRACE(0, "unexpected exception in commitUnit: %s\n",
568 void Repo::connect() {
571 if (!RuntimeOption::RepoEvalMode
.compare("local")) {
572 m_evalRepoId
= (m_localWritable
) ? RepoIdLocal
: RepoIdCentral
;
573 } else if (!RuntimeOption::RepoEvalMode
.compare("central")) {
574 m_evalRepoId
= RepoIdCentral
;
576 assertx(!RuntimeOption::RepoEvalMode
.compare("readonly"));
577 m_evalRepoId
= RepoIdInvalid
;
579 TRACE(1, "Repo.Eval.Mode=%s\n",
580 (m_evalRepoId
== RepoIdLocal
)
582 : (m_evalRepoId
== RepoIdCentral
)
587 void Repo::disconnect() noexcept
{
588 if (m_dbc
!= nullptr) {
589 sqlite3_close(m_dbc
);
591 m_localReadable
= false;
592 m_localWritable
= false;
593 m_evalRepoId
= RepoIdInvalid
;
597 void Repo::initCentral() {
600 assertx(m_dbc
== nullptr);
601 auto tryPath
= [this, &error
](const char* path
) {
603 if (openCentral(path
, subErr
) == RepoStatus::error
) {
604 folly::format(&error
, " {}\n", subErr
.empty() ? path
: subErr
);
610 auto fail_no_repo
= [&error
] {
611 error
= "Failed to initialize central HHBC repository:\n" + error
;
612 // Database initialization failed; this is an unrecoverable state.
613 Logger::Error(error
);
615 if (Process::IsInMainThread()) {
618 always_assert_flog(false, "{}", error
);
621 // Try Repo.Central.Path
622 if (!RuntimeOption::RepoCentralPath
.empty() &&
623 tryPath(RuntimeOption::RepoCentralPath
.c_str())) {
627 // Try HHVM_REPO_CENTRAL_PATH
628 const char* HHVM_REPO_CENTRAL_PATH
= getenv("HHVM_REPO_CENTRAL_PATH");
629 if (HHVM_REPO_CENTRAL_PATH
!= nullptr &&
630 tryPath(HHVM_REPO_CENTRAL_PATH
)) {
634 if (!RuntimeOption::RepoAllowFallbackPath
) fail_no_repo();
636 // Try "$HOME/.hhvm.hhbc".
637 char* HOME
= getenv("HOME");
638 if (HOME
!= nullptr) {
639 std::string centralPath
= HOME
;
640 centralPath
+= "/.hhvm.hhbc";
641 if (tryPath(centralPath
.c_str())) {
647 // Try the equivalent of "$HOME/.hhvm.hhbc", but look up the home directory
648 // in the password database.
652 long bufsize
= sysconf(_SC_GETPW_R_SIZE_MAX
);
654 auto buf
= new char[bufsize
];
655 SCOPE_EXIT
{ delete[] buf
; };
656 if (!getpwuid_r(getuid(), &pwbuf
, buf
, size_t(bufsize
), &pwbufp
)
657 && (HOME
== nullptr || strcmp(HOME
, pwbufp
->pw_dir
))) {
658 std::string centralPath
= pwbufp
->pw_dir
;
659 centralPath
+= "/.hhvm.hhbc";
660 if (tryPath(centralPath
.c_str())) {
667 // Try "$HOMEDRIVE$HOMEPATH/.hhvm.hhbc"
668 char* HOMEDRIVE
= getenv("HOMEDRIVE");
669 if (HOMEDRIVE
!= nullptr) {
670 char* HOMEPATH
= getenv("HOMEPATH");
671 if (HOMEPATH
!= nullptr) {
672 std::string centralPath
= HOMEDRIVE
;
673 centralPath
+= HOMEPATH
;
674 centralPath
+= "\\.hhvm.hhbc";
675 if (tryPath(centralPath
.c_str()))
686 * Convert the permission bits from the given stat struct to an ls-style
689 std::string
showPermissions(const struct stat
& s
) {
690 static const std::pair
<int, char> bits
[] = {
691 {S_IRUSR
, 'r'}, {S_IWUSR
, 'w'}, {S_IXUSR
, 'x'},
692 {S_IRGRP
, 'r'}, {S_IWGRP
, 'w'}, {S_IXGRP
, 'x'},
693 {S_IROTH
, 'r'}, {S_IWOTH
, 'w'}, {S_IXOTH
, 'x'},
696 ret
.reserve(sizeof(bits
) / sizeof(bits
[0]));
698 for (auto pair
: bits
) {
699 ret
+= (s
.st_mode
& pair
.first
) ? pair
.second
: '-';
704 struct PasswdBuffer
{
705 explicit PasswdBuffer(int name
)
706 : size
{sysconf(name
)}
708 if (size
== -1) size
= 1024;
709 data
= std::make_unique
<char[]>(size
);
713 std::unique_ptr
<char[]> data
;
717 * Return the name of the user with the given id.
719 std::string
uidToName(uid_t uid
) {
721 auto buffer
= PasswdBuffer
{_SC_GETPW_R_SIZE_MAX
};
725 auto err
= getpwuid_r(uid
, &pw
, buffer
.data
.get(), buffer
.size
, &result
);
726 if (err
!= 0) return folly::errnoStr(errno
).toStdString();
727 if (result
== nullptr) return "user does not exist";
730 return "<unsupported>";
735 * Return the uid of the user with the given name.
737 uid_t
nameToUid(const std::string
& name
) {
739 auto buffer
= PasswdBuffer
{_SC_GETPW_R_SIZE_MAX
};
743 auto err
= getpwnam_r(
744 name
.c_str(), &pw
, buffer
.data
.get(), buffer
.size
, &result
746 if (err
!= 0 || result
== nullptr) return -1;
754 * Return the name of the group with the given id.
756 std::string
gidToName(gid_t gid
) {
758 auto buffer
= PasswdBuffer
{_SC_GETGR_R_SIZE_MAX
};
762 auto err
= getgrgid_r(gid
, &grp
, buffer
.data
.get(), buffer
.size
, &result
);
763 if (err
!= 0) return folly::errnoStr(errno
).toStdString();
764 if (result
== nullptr) return "group does not exist";
767 return "<unsupported>";
772 * Return the gid of the group with the given name.
774 gid_t
nameToGid(const std::string
& name
) {
776 auto buffer
= PasswdBuffer
{_SC_GETGR_R_SIZE_MAX
};
780 auto err
= getgrnam_r(
781 name
.c_str(), &grp
, buffer
.data
.get(), buffer
.size
, &result
783 if (err
!= 0 || result
== nullptr) return -1;
790 void setCentralRepoFileMode(const std::string
& path
) {
791 // These runtime options are all best-effort, so we don't care if any of the
794 if (auto const mode
= RuntimeOption::RepoCentralFileMode
) {
795 chmod(path
.c_str(), mode
);
798 if (!RuntimeOption::RepoCentralFileUser
.empty()) {
799 auto const uid
= nameToUid(RuntimeOption::RepoCentralFileUser
);
800 chown(path
.c_str(), uid
, -1);
803 if (!RuntimeOption::RepoCentralFileGroup
.empty()) {
804 auto const gid
= nameToGid(RuntimeOption::RepoCentralFileGroup
);
805 chown(path
.c_str(), -1, gid
);
810 RepoStatus
Repo::openCentral(const char* rawPath
, std::string
& errorMsg
) {
811 std::string repoPath
= insertSchema(rawPath
);
812 // SQLITE_OPEN_NOMUTEX specifies that the connection be opened such
813 // that no mutexes are used to protect the database connection from other
814 // threads. However, multiple connections can still be used concurrently,
815 // because SQLite as a whole is thread-safe.
816 if (int err
= sqlite3_open_v2(repoPath
.c_str(), &m_dbc
,
817 SQLITE_OPEN_NOMUTEX
|
818 SQLITE_OPEN_READWRITE
|
819 SQLITE_OPEN_CREATE
, nullptr)) {
820 TRACE(1, "Repo::%s() failed to open candidate central repo '%s'\n",
821 __func__
, repoPath
.c_str());
822 errorMsg
= folly::format("Failed to open {}: {} - {}",
823 repoPath
, err
, sqlite3_errmsg(m_dbc
)).str();
824 return RepoStatus::error
;
827 if (RuntimeOption::RepoBusyTimeoutMS
) {
828 sqlite3_busy_timeout(m_dbc
, RuntimeOption::RepoBusyTimeoutMS
);
831 m_beginStmt
.prepare("BEGIN TRANSACTION;");
832 m_rollbackStmt
.prepare("ROLLBACK;");
833 m_commitStmt
.prepare("COMMIT;");
834 pragmas(RepoIdCentral
);
835 } catch (RepoExc
& re
) {
836 TRACE(1, "Repo::%s() failed to initialize connection to canditate repo"
837 " '%s': %s\n", __func__
, repoPath
.c_str(), re
.what());
838 errorMsg
= folly::format("Failed to initialize connection to {}: {}",
839 repoPath
, re
.what()).str();
840 return RepoStatus::error
;
843 // sqlite3_open_v2() will silently open in read-only mode if file permissions
844 // prevent writing. Therefore, tell initSchema() to verify that the database
846 bool centralWritable
= true;
847 if (initSchema(RepoIdCentral
, centralWritable
, errorMsg
) == RepoStatus::error
848 || !centralWritable
) {
849 TRACE(1, "Repo::initSchema() failed for candidate central repo '%s'\n",
851 struct stat repoStat
;
853 if (stat(repoPath
.c_str(), &repoStat
) == 0) {
854 statStr
= folly::sformat("{} {}:{}",
855 showPermissions(repoStat
),
856 uidToName(repoStat
.st_uid
),
857 gidToName(repoStat
.st_gid
));
859 statStr
= folly::errnoStr(errno
).toStdString();
861 errorMsg
= folly::format("Failed to initialize schema in {}({}): {}",
862 repoPath
, statStr
, errorMsg
).str();
863 return RepoStatus::error
;
865 m_centralRepo
= repoPath
;
866 setCentralRepoFileMode(repoPath
);
867 TRACE(1, "Central repo: '%s'\n", m_centralRepo
.c_str());
868 return RepoStatus::success
;
871 void Repo::initLocal() {
872 if (RuntimeOption::RepoLocalMode
.compare("--")) {
874 if (!RuntimeOption::RepoLocalMode
.compare("rw")) {
877 assertx(!RuntimeOption::RepoLocalMode
.compare("r-"));
881 if (!RuntimeOption::RepoLocalPath
.empty()) {
882 attachLocal(RuntimeOption::RepoLocalPath
.c_str(), isWritable
);
883 } else if (RuntimeOption::RepoAllowFallbackPath
) {
884 if (!RuntimeOption::ServerExecutionMode()) {
885 std::string cliRepo
= s_cliFile
;
886 if (!cliRepo
.empty()) {
889 attachLocal(cliRepo
.c_str(), isWritable
);
891 attachLocal("hhvm.hhbc", isWritable
);
897 void Repo::attachLocal(const char* path
, bool isWritable
) {
898 std::string repoPath
= insertSchema(path
);
900 // Make sure the repo exists before attaching it, in order to avoid
901 // creating a read-only repo.
903 if (!strchr(repoPath
.c_str(), ':') &&
904 stat(repoPath
.c_str(), &buf
) != 0) {
909 auto attachQuery
= folly::sformat(
910 "ATTACH DATABASE '{}' as {};", repoPath
, dbName(RepoIdLocal
));
912 pragmas(RepoIdLocal
);
913 } catch (RepoExc
& re
) {
914 // Failed to run pragmas on local DB - ignored
919 if (initSchema(RepoIdLocal
, isWritable
, error
) == RepoStatus::error
) {
920 FTRACE(1, "Local repo {} failed to init schema: {}\n", repoPath
, error
);
923 m_localRepo
= repoPath
;
924 m_localReadable
= true;
925 m_localWritable
= isWritable
;
926 TRACE(1, "Local repo: '%s' (read%s)\n",
927 m_localRepo
.c_str(), m_localWritable
? "-write" : "-only");
930 void Repo::pragmas(int repoId
) {
931 // Valid synchronous values: 0 (OFF), 1 (NORMAL), 2 (FULL).
932 static const int synchronous
= 0;
933 setIntPragma(repoId
, "synchronous", synchronous
);
934 setIntPragma(repoId
, "cache_size", 20);
935 // Valid journal_mode values: delete, truncate, persist, memory, wal, off.
936 setTextPragma(repoId
, "journal_mode", RuntimeOption::RepoJournal
.c_str());
939 void Repo::getIntPragma(int repoId
, const char* name
, int& val
) {
940 auto pragmaQuery
= folly::sformat("PRAGMA {}.{};", dbName(repoId
), name
);
941 RepoStmt
stmt(*this);
942 stmt
.prepare(pragmaQuery
);
943 RepoQuery
query(stmt
);
945 query
.getInt(0, val
);
948 void Repo::setIntPragma(int repoId
, const char* name
, int val
) {
949 // Read first to see if a write can be avoided
951 getIntPragma(repoId
, name
, oldval
);
952 if (val
== oldval
) return;
954 // Pragma writes must be executed outside transactions, since they may change
955 // transaction behavior.
956 auto pragmaQuery
= folly::sformat(
957 "PRAGMA {}.{} = {};", dbName(repoId
), name
, val
);
960 // Verify that the pragma had the desired effect.
962 getIntPragma(repoId
, name
, newval
);
964 throw RepoExc("Unexpected PRAGMA %s.%s value: %d\n",
965 dbName(repoId
), name
, newval
);
970 void Repo::getTextPragma(int repoId
, const char* name
, std::string
& val
) {
971 auto pragmaQuery
= folly::sformat("PRAGMA {}.{};", dbName(repoId
), name
);
972 RepoStmt
stmt(*this);
973 stmt
.prepare(pragmaQuery
);
974 RepoQuery
query(stmt
);
981 void Repo::setTextPragma(int repoId
, const char* name
, const char* val
) {
982 // Read first to see if a write can be avoided
983 std::string oldval
= "?";
984 getTextPragma(repoId
, name
, oldval
);
985 if (!strcmp(oldval
.c_str(), val
)) return;
987 // Pragma writes must be executed outside transactions, since they may change
988 // transaction behavior.
989 auto pragmaQuery
= folly::sformat(
990 "PRAGMA {}.{} = {};", dbName(repoId
), name
, val
);
993 // Verify that the pragma had the desired effect.
994 std::string newval
= "?";
995 getTextPragma(repoId
, name
, newval
);
996 if (strcmp(newval
.c_str(), val
)) {
997 throw RepoExc("Unexpected PRAGMA %s.%s value: %s\n",
998 dbName(repoId
), name
, newval
.c_str());
1003 RepoStatus
Repo::initSchema(int repoId
, bool& isWritable
,
1004 std::string
& errorMsg
) {
1005 if (!schemaExists(repoId
)) {
1006 if (createSchema(repoId
, errorMsg
) == RepoStatus::error
) {
1007 // Check whether this failure is due to losing the schema
1008 // initialization race with another process.
1009 if (!schemaExists(repoId
)) {
1010 return RepoStatus::error
;
1013 // createSchema() successfully wrote to the database, so no further
1014 // verification is necessary.
1015 return RepoStatus::success
;
1019 isWritable
= writable(repoId
);
1021 return RepoStatus::success
;
1024 bool Repo::schemaExists(int repoId
) {
1026 auto txn
= RepoTxn
{begin()};
1027 auto selectQuery
= folly::sformat(
1028 "SELECT product FROM {};", table(repoId
, "magic"));
1029 RepoStmt
stmt(*this);
1030 // If the DB is 'new' and hasn't been initialized yet then we expect this
1031 // prepare() to fail.
1032 stmt
.prepare(selectQuery
);
1033 // This SHOULDN'T fail - we create the table under a transaction - so if it
1034 // exists then it should have our magic value.
1035 RepoTxnQuery
query(txn
, stmt
);
1037 const char* text
; /**/ query
.getText(0, text
);
1038 if (strcmp(kMagicProduct
, text
)) {
1042 } catch (RepoExc
& re
) {
1048 RepoStatus
Repo::createSchema(int repoId
, std::string
& errorMsg
) {
1050 auto txn
= RepoTxn
{begin()};
1052 auto createQuery
= folly::sformat(
1053 "CREATE TABLE {} (product TEXT);", table(repoId
, "magic"));
1054 txn
.exec(createQuery
);
1056 auto insertQuery
= folly::sformat(
1057 "INSERT INTO {} VALUES('{}');", table(repoId
, "magic"), kMagicProduct
);
1058 txn
.exec(insertQuery
);
1061 auto createQuery
= folly::sformat(
1062 "CREATE TABLE {} (path TEXT, sha1 BLOB, UNIQUE(path, sha1));",
1063 table(repoId
, "FileSha1"));
1064 txn
.exec(createQuery
);
1066 txn
.exec(folly::sformat("CREATE TABLE {} (data BLOB);",
1067 table(repoId
, "GlobalData")));
1068 m_urp
.createSchema(repoId
, txn
);
1069 m_pcrp
.createSchema(repoId
, txn
);
1070 m_rrp
.createSchema(repoId
, txn
);
1071 m_frp
.createSchema(repoId
, txn
);
1072 m_lsrp
.createSchema(repoId
, txn
);
1075 } catch (RepoExc
& re
) {
1076 errorMsg
= re
.what();
1077 return RepoStatus::error
;
1079 return RepoStatus::success
;
1082 bool Repo::writable(int repoId
) {
1083 switch (sqlite3_db_readonly(m_dbc
, dbName(repoId
))) {
1084 case 0: return true;
1085 case 1: return false;
1086 case -1: return false;
1089 always_assert(false);
1092 //////////////////////////////////////////////////////////////////////
1094 void batchCommit(const std::vector
<std::unique_ptr
<UnitEmitter
>>& ues
) {
1095 auto& repo
= Repo::get();
1097 // Attempt batch commit. This can legitimately fail due to multiple input
1098 // files having identical contents.
1101 auto txn
= RepoTxn
{repo
.begin()};
1103 for (auto& ue
: ues
) {
1104 if (repo
.insertUnit(ue
.get(), UnitOrigin::File
, txn
) ==
1105 RepoStatus::error
) {
1115 // Commit units individually if an error occurred during batch commit.
1117 for (auto& ue
: ues
) {
1118 repo
.commitUnit(ue
.get(), UnitOrigin::File
);
1123 //////////////////////////////////////////////////////////////////////