diff options
Diffstat (limited to 'storage/mozStorageService.cpp')
-rw-r--r-- | storage/mozStorageService.cpp | 762 |
1 files changed, 762 insertions, 0 deletions
diff --git a/storage/mozStorageService.cpp b/storage/mozStorageService.cpp new file mode 100644 index 0000000000..d73a1680ac --- /dev/null +++ b/storage/mozStorageService.cpp @@ -0,0 +1,762 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/Attributes.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/SpinEventLoopUntil.h" + +#include "mozStorageService.h" +#include "mozStorageConnection.h" +#include "nsComponentManagerUtils.h" +#include "nsEmbedCID.h" +#include "nsExceptionHandler.h" +#include "nsThreadUtils.h" +#include "mozStoragePrivateHelpers.h" +#include "nsIObserverService.h" +#include "nsIPropertyBag2.h" +#include "mozilla/Services.h" +#include "mozilla/LateWriteChecks.h" +#include "mozIStorageCompletionCallback.h" +#include "mozIStoragePendingStatement.h" +#include "mozilla/StaticPrefs_storage.h" +#include "mozilla/intl/Collator.h" +#include "mozilla/intl/LocaleService.h" + +#include "sqlite3.h" +#include "mozilla/AutoSQLiteLifetime.h" + +#ifdef XP_WIN +// "windows.h" was included and it can #define lots of things we care about... +# undef CompareString +#endif + +using mozilla::intl::Collator; + +namespace mozilla { +namespace storage { + +//////////////////////////////////////////////////////////////////////////////// +//// Memory Reporting + +#ifdef MOZ_DMD +mozilla::Atomic<size_t> gSqliteMemoryUsed; +#endif + +static int64_t StorageSQLiteDistinguishedAmount() { + return ::sqlite3_memory_used(); +} + +/** + * Passes a single SQLite memory statistic to a memory reporter callback. + * + * @param aHandleReport + * The callback. + * @param aData + * The data for the callback. + * @param aConn + * The SQLite connection. + * @param aPathHead + * Head of the path for the memory report. + * @param aKind + * The memory report statistic kind, one of "stmt", "cache" or + * "schema". + * @param aDesc + * The memory report description. + * @param aOption + * The SQLite constant for getting the measurement. + * @param aTotal + * The accumulator for the measurement. + */ +static void ReportConn(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, Connection* aConn, + const nsACString& aPathHead, const nsACString& aKind, + const nsACString& aDesc, int32_t aOption, + size_t* aTotal) { + nsCString path(aPathHead); + path.Append(aKind); + path.AppendLiteral("-used"); + + int32_t val = aConn->getSqliteRuntimeStatus(aOption); + aHandleReport->Callback(""_ns, path, nsIMemoryReporter::KIND_HEAP, + nsIMemoryReporter::UNITS_BYTES, int64_t(val), aDesc, + aData); + *aTotal += val; +} + +// Warning: To get a Connection's measurements requires holding its lock. +// There may be a delay getting the lock if another thread is accessing the +// Connection. This isn't very nice if CollectReports is called from the main +// thread! But at the time of writing this function is only called when +// about:memory is loaded (not, for example, when telemetry pings occur) and +// any delays in that case aren't so bad. +NS_IMETHODIMP +Service::CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) { + size_t totalConnSize = 0; + { + nsTArray<RefPtr<Connection>> connections; + getConnections(connections); + + for (uint32_t i = 0; i < connections.Length(); i++) { + RefPtr<Connection>& conn = connections[i]; + + // Someone may have closed the Connection, in which case we skip it. + // Note that we have consumers of the synchronous API that are off the + // main-thread, like the DOM Cache and IndexedDB, and as such we must be + // sure that we have a connection. + MutexAutoLock lockedAsyncScope(conn->sharedAsyncExecutionMutex); + if (!conn->connectionReady()) { + continue; + } + + nsCString pathHead("explicit/storage/sqlite/"); + // This filename isn't privacy-sensitive, and so is never anonymized. + pathHead.Append(conn->getFilename()); + pathHead.Append('/'); + + SQLiteMutexAutoLock lockedScope(conn->sharedDBMutex); + + constexpr auto stmtDesc = + "Memory (approximate) used by all prepared statements used by " + "connections to this database."_ns; + ReportConn(aHandleReport, aData, conn, pathHead, "stmt"_ns, stmtDesc, + SQLITE_DBSTATUS_STMT_USED, &totalConnSize); + + constexpr auto cacheDesc = + "Memory (approximate) used by all pager caches used by connections " + "to this database."_ns; + ReportConn(aHandleReport, aData, conn, pathHead, "cache"_ns, cacheDesc, + SQLITE_DBSTATUS_CACHE_USED_SHARED, &totalConnSize); + + constexpr auto schemaDesc = + "Memory (approximate) used to store the schema for all databases " + "associated with connections to this database."_ns; + ReportConn(aHandleReport, aData, conn, pathHead, "schema"_ns, schemaDesc, + SQLITE_DBSTATUS_SCHEMA_USED, &totalConnSize); + } + +#ifdef MOZ_DMD + if (::sqlite3_memory_used() != int64_t(gSqliteMemoryUsed)) { + NS_WARNING( + "memory consumption reported by SQLite doesn't match " + "our measurements"); + } +#endif + } + + int64_t other = ::sqlite3_memory_used() - totalConnSize; + + MOZ_COLLECT_REPORT("explicit/storage/sqlite/other", KIND_HEAP, UNITS_BYTES, + other, "All unclassified sqlite memory."); + + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// +//// Service + +NS_IMPL_ISUPPORTS(Service, mozIStorageService, nsIObserver, nsIMemoryReporter) + +Service* Service::gService = nullptr; + +already_AddRefed<Service> Service::getSingleton() { + if (gService) { + return do_AddRef(gService); + } + + // The first reference to the storage service must be obtained on the + // main thread. + NS_ENSURE_TRUE(NS_IsMainThread(), nullptr); + RefPtr<Service> service = new Service(); + if (NS_SUCCEEDED(service->initialize())) { + // Note: This is cleared in the Service destructor. + gService = service.get(); + return service.forget(); + } + + return nullptr; +} + +int Service::AutoVFSRegistration::Init(UniquePtr<sqlite3_vfs> aVFS) { + MOZ_ASSERT(!mVFS); + if (aVFS) { + mVFS = std::move(aVFS); + return sqlite3_vfs_register(mVFS.get(), 0); + } + NS_WARNING("Failed to register VFS"); + return SQLITE_OK; +} + +Service::AutoVFSRegistration::~AutoVFSRegistration() { + if (mVFS) { + int rc = sqlite3_vfs_unregister(mVFS.get()); + if (rc != SQLITE_OK) { + NS_WARNING("Failed to unregister sqlite vfs wrapper."); + } + } +} + +Service::Service() + : mMutex("Service::mMutex"), + mRegistrationMutex("Service::mRegistrationMutex"), + mConnections() {} + +Service::~Service() { + mozilla::UnregisterWeakMemoryReporter(this); + mozilla::UnregisterStorageSQLiteDistinguishedAmount(); + + gService = nullptr; +} + +void Service::registerConnection(Connection* aConnection) { + mRegistrationMutex.AssertNotCurrentThreadOwns(); + MutexAutoLock mutex(mRegistrationMutex); + (void)mConnections.AppendElement(aConnection); +} + +void Service::unregisterConnection(Connection* aConnection) { + // If this is the last Connection it might be the only thing keeping Service + // alive. So ensure that Service is destroyed only after the Connection is + // cleanly unregistered and destroyed. + RefPtr<Service> kungFuDeathGrip(this); + RefPtr<Connection> forgettingRef; + { + mRegistrationMutex.AssertNotCurrentThreadOwns(); + MutexAutoLock mutex(mRegistrationMutex); + + for (uint32_t i = 0; i < mConnections.Length(); ++i) { + if (mConnections[i] == aConnection) { + // Because dropping the final reference can potentially result in + // spinning a nested event loop if the connection was not properly + // shutdown, we want to do that outside this loop so that we can finish + // mutating the array and drop our mutex. + forgettingRef = std::move(mConnections[i]); + mConnections.RemoveElementAt(i); + break; + } + } + } + + MOZ_ASSERT(forgettingRef, + "Attempt to unregister unknown storage connection!"); + + // Do not proxy the release anywhere, just let this reference drop here. (We + // previously did proxy the release, but that was because we invoked Close() + // in the destructor and Close() likes to complain if it's not invoked on the + // opener event target, so it was essential that the last reference be dropped + // on the opener event target. We now enqueue Close() inside our caller, + // Release(), so it doesn't actually matter what thread our reference drops + // on.) +} + +void Service::getConnections( + /* inout */ nsTArray<RefPtr<Connection>>& aConnections) { + mRegistrationMutex.AssertNotCurrentThreadOwns(); + MutexAutoLock mutex(mRegistrationMutex); + aConnections.Clear(); + aConnections.AppendElements(mConnections); +} + +void Service::minimizeMemory() { + nsTArray<RefPtr<Connection>> connections; + getConnections(connections); + + for (uint32_t i = 0; i < connections.Length(); i++) { + RefPtr<Connection> conn = connections[i]; + // For non-main-thread owning/opening threads, we may be racing against them + // closing their connection or their thread. That's okay, see below. + if (!conn->connectionReady()) { + continue; + } + + constexpr auto shrinkPragma = "PRAGMA shrink_memory"_ns; + + if (!conn->operationSupported(Connection::SYNCHRONOUS)) { + // This is a mozIStorageAsyncConnection, it can only be used on the main + // thread, so we can do a straight API call. + nsCOMPtr<mozIStoragePendingStatement> ps; + DebugOnly<nsresult> rv = conn->ExecuteSimpleSQLAsync( + shrinkPragma, nullptr, getter_AddRefs(ps)); + MOZ_ASSERT(NS_SUCCEEDED(rv), "Should have purged sqlite caches"); + } else if (IsOnCurrentSerialEventTarget(conn->eventTargetOpenedOn)) { + if (conn->isAsyncExecutionThreadAvailable()) { + nsCOMPtr<mozIStoragePendingStatement> ps; + DebugOnly<nsresult> rv = conn->ExecuteSimpleSQLAsync( + shrinkPragma, nullptr, getter_AddRefs(ps)); + MOZ_ASSERT(NS_SUCCEEDED(rv), "Should have purged sqlite caches"); + } else { + conn->ExecuteSimpleSQL(shrinkPragma); + } + } else { + // We are on the wrong event target, the query should be executed on the + // opener event target, so we must dispatch to it. + // It's possible the connection is already closed or will be closed by the + // time our runnable runs. ExecuteSimpleSQL will safely return with a + // failure in that case. If the event target is shutting down or shut + // down, the dispatch will fail and that's okay. + nsCOMPtr<nsIRunnable> event = NewRunnableMethod<const nsCString>( + "Connection::ExecuteSimpleSQL", conn, &Connection::ExecuteSimpleSQL, + shrinkPragma); + Unused << conn->eventTargetOpenedOn->Dispatch(event, NS_DISPATCH_NORMAL); + } + } +} + +UniquePtr<sqlite3_vfs> ConstructBaseVFS(bool); +const char* GetBaseVFSName(bool); + +UniquePtr<sqlite3_vfs> ConstructQuotaVFS(const char* aBaseVFSName); +const char* GetQuotaVFSName(); + +UniquePtr<sqlite3_vfs> ConstructObfuscatingVFS(const char* aBaseVFSName); + +UniquePtr<sqlite3_vfs> ConstructReadOnlyNoLockVFS(); + +static const char* sObserverTopics[] = {"memory-pressure", + "xpcom-shutdown-threads"}; + +nsresult Service::initialize() { + MOZ_ASSERT(NS_IsMainThread(), "Must be initialized on the main thread"); + + int rc = AutoSQLiteLifetime::getInitResult(); + if (rc != SQLITE_OK) { + return convertResultCode(rc); + } + + /** + * The virtual file system hierarchy + * + * obfsvfs + * | + * | + * | + * quotavfs + * / \ + * / \ + * / \ + * / \ + * / \ + * base-vfs-excl base-vfs + * / \ / \ + * / \ / \ + * / \ / \ + * unix-excl win32 unix win32 + */ + + rc = mBaseSqliteVFS.Init(ConstructBaseVFS(false)); + if (rc != SQLITE_OK) { + return convertResultCode(rc); + } + + rc = mBaseExclSqliteVFS.Init(ConstructBaseVFS(true)); + if (rc != SQLITE_OK) { + return convertResultCode(rc); + } + + rc = mQuotaSqliteVFS.Init(ConstructQuotaVFS( + GetBaseVFSName(StaticPrefs::storage_sqlite_exclusiveLock_enabled()))); + if (rc != SQLITE_OK) { + return convertResultCode(rc); + } + + rc = mObfuscatingSqliteVFS.Init(ConstructObfuscatingVFS(GetQuotaVFSName())); + if (rc != SQLITE_OK) { + return convertResultCode(rc); + } + + rc = mReadOnlyNoLockSqliteVFS.Init(ConstructReadOnlyNoLockVFS()); + if (rc != SQLITE_OK) { + return convertResultCode(rc); + } + + nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); + NS_ENSURE_TRUE(os, NS_ERROR_FAILURE); + + for (size_t i = 0; i < ArrayLength(sObserverTopics); ++i) { + nsresult rv = os->AddObserver(this, sObserverTopics[i], false); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + mozilla::RegisterWeakMemoryReporter(this); + mozilla::RegisterStorageSQLiteDistinguishedAmount( + StorageSQLiteDistinguishedAmount); + + return NS_OK; +} + +int Service::localeCompareStrings(const nsAString& aStr1, + const nsAString& aStr2, + Collator::Sensitivity aSensitivity) { + // The mozilla::intl::Collator is not thread safe, since the Collator::Options + // can be changed. + MutexAutoLock mutex(mMutex); + + Collator* collator = getCollator(); + if (!collator) { + NS_ERROR("Storage service has no collation"); + return 0; + } + + if (aSensitivity != mLastSensitivity) { + Collator::Options options{}; + options.sensitivity = aSensitivity; + auto result = mCollator->SetOptions(options); + + if (result.isErr()) { + NS_WARNING("Could not configure the mozilla::intl::Collation."); + return 0; + } + mLastSensitivity = aSensitivity; + } + + return collator->CompareStrings(aStr1, aStr2); +} + +Collator* Service::getCollator() { + mMutex.AssertCurrentThreadOwns(); + + if (mCollator) { + return mCollator.get(); + } + + auto result = mozilla::intl::LocaleService::TryCreateComponent<Collator>(); + if (result.isErr()) { + NS_WARNING("Could not create mozilla::intl::Collation."); + return nullptr; + } + + mCollator = result.unwrap(); + + // Sort in a case-insensitive way, where "base" letters are considered + // equal, e.g: a = á, a = A, a ≠ b. + Collator::Options options{}; + options.sensitivity = Collator::Sensitivity::Base; + auto optResult = mCollator->SetOptions(options); + + if (optResult.isErr()) { + NS_WARNING("Could not configure the mozilla::intl::Collation."); + mCollator = nullptr; + return nullptr; + } + + return mCollator.get(); +} + +//////////////////////////////////////////////////////////////////////////////// +//// mozIStorageService + +NS_IMETHODIMP +Service::OpenSpecialDatabase(const nsACString& aStorageKey, + const nsACString& aName, uint32_t aConnectionFlags, + mozIStorageConnection** _connection) { + if (!aStorageKey.Equals(kMozStorageMemoryStorageKey)) { + return NS_ERROR_INVALID_ARG; + } + + const bool interruptible = + aConnectionFlags & mozIStorageService::CONNECTION_INTERRUPTIBLE; + + int flags = SQLITE_OPEN_READWRITE; + + if (!aName.IsEmpty()) { + flags |= SQLITE_OPEN_URI; + } + + RefPtr<Connection> msc = + new Connection(this, flags, Connection::SYNCHRONOUS, interruptible); + + const nsresult rv = msc->initialize(aStorageKey, aName); + NS_ENSURE_SUCCESS(rv, rv); + + msc.forget(_connection); + return NS_OK; +} + +namespace { + +class AsyncInitDatabase final : public Runnable { + public: + AsyncInitDatabase(Connection* aConnection, nsIFile* aStorageFile, + int32_t aGrowthIncrement, + mozIStorageCompletionCallback* aCallback) + : Runnable("storage::AsyncInitDatabase"), + mConnection(aConnection), + mStorageFile(aStorageFile), + mGrowthIncrement(aGrowthIncrement), + mCallback(aCallback) { + MOZ_ASSERT(NS_IsMainThread()); + } + + NS_IMETHOD Run() override { + MOZ_ASSERT(!NS_IsMainThread()); + nsresult rv = mConnection->initializeOnAsyncThread(mStorageFile); + if (NS_FAILED(rv)) { + return DispatchResult(rv, nullptr); + } + + if (mGrowthIncrement >= 0) { + // Ignore errors. In the future, we might wish to log them. + (void)mConnection->SetGrowthIncrement(mGrowthIncrement, ""_ns); + } + + return DispatchResult( + NS_OK, NS_ISUPPORTS_CAST(mozIStorageAsyncConnection*, mConnection)); + } + + private: + nsresult DispatchResult(nsresult aStatus, nsISupports* aValue) { + RefPtr<CallbackComplete> event = + new CallbackComplete(aStatus, aValue, mCallback.forget()); + return NS_DispatchToMainThread(event); + } + + ~AsyncInitDatabase() { + NS_ReleaseOnMainThread("AsyncInitDatabase::mStorageFile", + mStorageFile.forget()); + NS_ReleaseOnMainThread("AsyncInitDatabase::mConnection", + mConnection.forget()); + + // Generally, the callback will be released by CallbackComplete. + // However, if for some reason Run() is not executed, we still + // need to ensure that it is released here. + NS_ReleaseOnMainThread("AsyncInitDatabase::mCallback", mCallback.forget()); + } + + RefPtr<Connection> mConnection; + nsCOMPtr<nsIFile> mStorageFile; + int32_t mGrowthIncrement; + RefPtr<mozIStorageCompletionCallback> mCallback; +}; + +} // namespace + +NS_IMETHODIMP +Service::OpenAsyncDatabase(nsIVariant* aDatabaseStore, uint32_t aOpenFlags, + uint32_t /* aConnectionFlags */, + mozIStorageCompletionCallback* aCallback) { + if (!NS_IsMainThread()) { + return NS_ERROR_NOT_SAME_THREAD; + } + NS_ENSURE_ARG(aDatabaseStore); + NS_ENSURE_ARG(aCallback); + + const bool shared = aOpenFlags & mozIStorageService::OPEN_SHARED; + const bool ignoreLockingMode = + aOpenFlags & mozIStorageService::OPEN_IGNORE_LOCKING_MODE; + // Specifying ignoreLockingMode will force use of the readOnly flag: + const bool readOnly = + ignoreLockingMode || (aOpenFlags & mozIStorageService::OPEN_READONLY); + int flags = readOnly ? SQLITE_OPEN_READONLY : SQLITE_OPEN_READWRITE; + + nsCOMPtr<nsIFile> storageFile; + nsCOMPtr<nsISupports> dbStore; + nsresult rv = aDatabaseStore->GetAsISupports(getter_AddRefs(dbStore)); + if (NS_SUCCEEDED(rv)) { + // Generally, aDatabaseStore holds the database nsIFile. + storageFile = do_QueryInterface(dbStore, &rv); + if (NS_FAILED(rv)) { + return NS_ERROR_INVALID_ARG; + } + + nsCOMPtr<nsIFile> cloned; + rv = storageFile->Clone(getter_AddRefs(cloned)); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + storageFile = std::move(cloned); + + if (!readOnly) { + // Ensure that SQLITE_OPEN_CREATE is passed in for compatibility reasons. + flags |= SQLITE_OPEN_CREATE; + } + + // Apply the shared-cache option. + flags |= shared ? SQLITE_OPEN_SHAREDCACHE : SQLITE_OPEN_PRIVATECACHE; + } else { + // Sometimes, however, it's a special database name. + nsAutoCString keyString; + rv = aDatabaseStore->GetAsACString(keyString); + if (NS_FAILED(rv) || !keyString.Equals(kMozStorageMemoryStorageKey)) { + return NS_ERROR_INVALID_ARG; + } + + // Just fall through with nullptr storageFile, this will cause the storage + // connection to use a memory DB. + } + + // Create connection on this thread, but initialize it on its helper thread. + RefPtr<Connection> msc = + new Connection(this, flags, Connection::ASYNCHRONOUS, + /* interruptible */ true, ignoreLockingMode); + nsCOMPtr<nsIEventTarget> target = msc->getAsyncExecutionTarget(); + MOZ_ASSERT(target, + "Cannot initialize a connection that has been closed already"); + + RefPtr<AsyncInitDatabase> asyncInit = new AsyncInitDatabase( + msc, storageFile, /* growthIncrement */ -1, aCallback); + return target->Dispatch(asyncInit, nsIEventTarget::DISPATCH_NORMAL); +} + +NS_IMETHODIMP +Service::OpenDatabase(nsIFile* aDatabaseFile, uint32_t aConnectionFlags, + mozIStorageConnection** _connection) { + NS_ENSURE_ARG(aDatabaseFile); + + const bool interruptible = + aConnectionFlags & mozIStorageService::CONNECTION_INTERRUPTIBLE; + + // Always ensure that SQLITE_OPEN_CREATE is passed in for compatibility + // reasons. + const int flags = + SQLITE_OPEN_READWRITE | SQLITE_OPEN_SHAREDCACHE | SQLITE_OPEN_CREATE; + RefPtr<Connection> msc = + new Connection(this, flags, Connection::SYNCHRONOUS, interruptible); + + const nsresult rv = msc->initialize(aDatabaseFile); + NS_ENSURE_SUCCESS(rv, rv); + + msc.forget(_connection); + return NS_OK; +} + +NS_IMETHODIMP +Service::OpenUnsharedDatabase(nsIFile* aDatabaseFile, uint32_t aConnectionFlags, + mozIStorageConnection** _connection) { + NS_ENSURE_ARG(aDatabaseFile); + + const bool interruptible = + aConnectionFlags & mozIStorageService::CONNECTION_INTERRUPTIBLE; + + // Always ensure that SQLITE_OPEN_CREATE is passed in for compatibility + // reasons. + const int flags = + SQLITE_OPEN_READWRITE | SQLITE_OPEN_PRIVATECACHE | SQLITE_OPEN_CREATE; + RefPtr<Connection> msc = + new Connection(this, flags, Connection::SYNCHRONOUS, interruptible); + + const nsresult rv = msc->initialize(aDatabaseFile); + NS_ENSURE_SUCCESS(rv, rv); + + msc.forget(_connection); + return NS_OK; +} + +NS_IMETHODIMP +Service::OpenDatabaseWithFileURL(nsIFileURL* aFileURL, + const nsACString& aTelemetryFilename, + uint32_t aConnectionFlags, + mozIStorageConnection** _connection) { + NS_ENSURE_ARG(aFileURL); + + const bool interruptible = + aConnectionFlags & mozIStorageService::CONNECTION_INTERRUPTIBLE; + + // Always ensure that SQLITE_OPEN_CREATE is passed in for compatibility + // reasons. + const int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_SHAREDCACHE | + SQLITE_OPEN_CREATE | SQLITE_OPEN_URI; + RefPtr<Connection> msc = + new Connection(this, flags, Connection::SYNCHRONOUS, interruptible); + + const nsresult rv = msc->initialize(aFileURL, aTelemetryFilename); + NS_ENSURE_SUCCESS(rv, rv); + + msc.forget(_connection); + return NS_OK; +} + +NS_IMETHODIMP +Service::BackupDatabaseFile(nsIFile* aDBFile, const nsAString& aBackupFileName, + nsIFile* aBackupParentDirectory, nsIFile** backup) { + nsresult rv; + nsCOMPtr<nsIFile> parentDir = aBackupParentDirectory; + if (!parentDir) { + // This argument is optional, and defaults to the same parent directory + // as the current file. + rv = aDBFile->GetParent(getter_AddRefs(parentDir)); + NS_ENSURE_SUCCESS(rv, rv); + } + + nsCOMPtr<nsIFile> backupDB; + rv = parentDir->Clone(getter_AddRefs(backupDB)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = backupDB->Append(aBackupFileName); + NS_ENSURE_SUCCESS(rv, rv); + + rv = backupDB->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString fileName; + rv = backupDB->GetLeafName(fileName); + NS_ENSURE_SUCCESS(rv, rv); + + rv = backupDB->Remove(false); + NS_ENSURE_SUCCESS(rv, rv); + + backupDB.forget(backup); + + return aDBFile->CopyTo(parentDir, fileName); +} + +//////////////////////////////////////////////////////////////////////////////// +//// nsIObserver + +NS_IMETHODIMP +Service::Observe(nsISupports*, const char* aTopic, const char16_t*) { + if (strcmp(aTopic, "memory-pressure") == 0) { + minimizeMemory(); + } else if (strcmp(aTopic, "xpcom-shutdown-threads") == 0) { + // The Service is kept alive by our strong observer references and + // references held by Connection instances. Since we're about to remove the + // former and then wait for the latter ones to go away, it behooves us to + // hold a strong reference to ourselves so our calls to getConnections() do + // not happen on a deleted object. + RefPtr<Service> kungFuDeathGrip = this; + + nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); + + for (size_t i = 0; i < ArrayLength(sObserverTopics); ++i) { + (void)os->RemoveObserver(this, sObserverTopics[i]); + } + + SpinEventLoopUntil("storage::Service::Observe(xpcom-shutdown-threads)"_ns, + [&]() -> bool { + // We must wait until all the closing connections are + // closed. + nsTArray<RefPtr<Connection>> connections; + getConnections(connections); + for (auto& conn : connections) { + if (conn->isClosing()) { + return false; + } + } + return true; + }); + +#ifdef DEBUG + nsTArray<RefPtr<Connection>> connections; + getConnections(connections); + for (uint32_t i = 0, n = connections.Length(); i < n; i++) { + if (!connections[i]->isClosed()) { + // getFilename is only the leaf name for the database file, + // so it shouldn't contain privacy-sensitive information. + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::StorageConnectionNotClosed, + connections[i]->getFilename()); + printf_stderr("Storage connection not closed: %s", + connections[i]->getFilename().get()); + MOZ_CRASH(); + } + } +#endif + } + + return NS_OK; +} + +} // namespace storage +} // namespace mozilla |