/* -*- 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 "mozStorageService.h" #include "mozStorageConnection.h" #include "prinit.h" #include "nsAutoPtr.h" #include "nsCollationCID.h" #include "nsEmbedCID.h" #include "nsThreadUtils.h" #include "mozStoragePrivateHelpers.h" #include "nsILocale.h" #include "nsILocaleService.h" #include "nsIXPConnect.h" #include "nsIObserverService.h" #include "nsIPropertyBag2.h" #include "mozilla/Services.h" #include "mozilla/Preferences.h" #include "mozilla/LateWriteChecks.h" #include "mozIStorageCompletionCallback.h" #include "mozIStoragePendingStatement.h" #include "sqlite3.h" #ifdef SQLITE_OS_WIN // "windows.h" was included and it can #define lots of things we care about... #undef CompareString #endif #include "nsIPromptService.h" #ifdef MOZ_STORAGE_MEMORY # include "mozmemory.h" # ifdef MOZ_DMD # include "DMD.h" # endif #endif //////////////////////////////////////////////////////////////////////////////// //// Defines #define PREF_TS_SYNCHRONOUS "toolkit.storage.synchronous" #define PREF_TS_SYNCHRONOUS_DEFAULT 1 #define PREF_TS_PAGESIZE "toolkit.storage.pageSize" // This value must be kept in sync with the value of SQLITE_DEFAULT_PAGE_SIZE in // db/sqlite3/src/Makefile.in. #define PREF_TS_PAGESIZE_DEFAULT 32768 namespace mozilla { namespace storage { //////////////////////////////////////////////////////////////////////////////// //// Memory Reporting #ifdef MOZ_DMD static mozilla::Atomic 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. */ nsresult 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); nsresult rv = aHandleReport->Callback(EmptyCString(), path, nsIMemoryReporter::KIND_HEAP, nsIMemoryReporter::UNITS_BYTES, int64_t(val), aDesc, aData); NS_ENSURE_SUCCESS(rv, rv); *aTotal += val; return NS_OK; } // 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) { nsresult rv; size_t totalConnSize = 0; { nsTArray > connections; getConnections(connections); for (uint32_t i = 0; i < connections.Length(); i++) { RefPtr &conn = connections[i]; // Someone may have closed the Connection, in which case we skip it. bool isReady; (void)conn->GetConnectionReady(&isReady); if (!isReady) { 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); NS_NAMED_LITERAL_CSTRING(stmtDesc, "Memory (approximate) used by all prepared statements used by " "connections to this database."); rv = ReportConn(aHandleReport, aData, conn, pathHead, NS_LITERAL_CSTRING("stmt"), stmtDesc, SQLITE_DBSTATUS_STMT_USED, &totalConnSize); NS_ENSURE_SUCCESS(rv, rv); NS_NAMED_LITERAL_CSTRING(cacheDesc, "Memory (approximate) used by all pager caches used by connections " "to this database."); rv = ReportConn(aHandleReport, aData, conn, pathHead, NS_LITERAL_CSTRING("cache"), cacheDesc, SQLITE_DBSTATUS_CACHE_USED, &totalConnSize); NS_ENSURE_SUCCESS(rv, rv); NS_NAMED_LITERAL_CSTRING(schemaDesc, "Memory (approximate) used to store the schema for all databases " "associated with connections to this database."); rv = ReportConn(aHandleReport, aData, conn, pathHead, NS_LITERAL_CSTRING("schema"), schemaDesc, SQLITE_DBSTATUS_SCHEMA_USED, &totalConnSize); NS_ENSURE_SUCCESS(rv, rv); } #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; rv = aHandleReport->Callback( EmptyCString(), NS_LITERAL_CSTRING("explicit/storage/sqlite/other"), KIND_HEAP, UNITS_BYTES, other, NS_LITERAL_CSTRING("All unclassified sqlite memory."), aData); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } //////////////////////////////////////////////////////////////////////////////// //// Service NS_IMPL_ISUPPORTS( Service, mozIStorageService, nsIObserver, nsIMemoryReporter ) Service *Service::gService = nullptr; Service * Service::getSingleton() { if (gService) { NS_ADDREF(gService); return gService; } // Ensure that we are using the same version of SQLite that we compiled with // or newer. Our configure check ensures we are using a new enough version // at compile time. if (SQLITE_VERSION_NUMBER > ::sqlite3_libversion_number()) { nsCOMPtr ps(do_GetService(NS_PROMPTSERVICE_CONTRACTID)); if (ps) { nsAutoString title, message; title.AppendLiteral("SQLite Version Error"); message.AppendLiteral("The application has been updated, but your version " "of SQLite is too old and the application cannot " "run."); (void)ps->Alert(nullptr, title.get(), message.get()); } ::PR_Abort(); } // The first reference to the storage service must be obtained on the // main thread. NS_ENSURE_TRUE(NS_IsMainThread(), nullptr); gService = new Service(); if (gService) { NS_ADDREF(gService); if (NS_FAILED(gService->initialize())) NS_RELEASE(gService); } return gService; } nsIXPConnect *Service::sXPConnect = nullptr; // static already_AddRefed Service::getXPConnect() { NS_PRECONDITION(NS_IsMainThread(), "Must only get XPConnect on the main thread!"); NS_PRECONDITION(gService, "Can not get XPConnect without an instance of our service!"); // If we've been shutdown, sXPConnect will be null. To prevent leaks, we do // not cache the service after this point. nsCOMPtr xpc(sXPConnect); if (!xpc) xpc = do_GetService(nsIXPConnect::GetCID()); NS_ASSERTION(xpc, "Could not get XPConnect!"); return xpc.forget(); } int32_t Service::sSynchronousPref; // static int32_t Service::getSynchronousPref() { return sSynchronousPref; } int32_t Service::sDefaultPageSize = PREF_TS_PAGESIZE_DEFAULT; Service::Service() : mMutex("Service::mMutex") , mSqliteVFS(nullptr) , mRegistrationMutex("Service::mRegistrationMutex") , mConnections() { } Service::~Service() { mozilla::UnregisterWeakMemoryReporter(this); mozilla::UnregisterStorageSQLiteDistinguishedAmount(); int rc = sqlite3_vfs_unregister(mSqliteVFS); if (rc != SQLITE_OK) NS_WARNING("Failed to unregister sqlite vfs wrapper."); // Shutdown the sqlite3 API. Warn if shutdown did not turn out okay, but // there is nothing actionable we can do in that case. rc = ::sqlite3_shutdown(); if (rc != SQLITE_OK) NS_WARNING("sqlite3 did not shutdown cleanly."); DebugOnly shutdownObserved = !sXPConnect; NS_ASSERTION(shutdownObserved, "Shutdown was not observed!"); gService = nullptr; delete mSqliteVFS; mSqliteVFS = 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 kungFuDeathGrip(this); { mRegistrationMutex.AssertNotCurrentThreadOwns(); MutexAutoLock mutex(mRegistrationMutex); for (uint32_t i = 0 ; i < mConnections.Length(); ++i) { if (mConnections[i] == aConnection) { nsCOMPtr thread = mConnections[i]->threadOpenedOn; // Ensure the connection is released on its opening thread. Note, we // must use .forget().take() so that we can manually cast to an // unambiguous nsISupports type. NS_ProxyRelease(thread, static_cast(mConnections[i].forget().take())); mConnections.RemoveElementAt(i); return; } } MOZ_ASSERT_UNREACHABLE("Attempt to unregister unknown storage connection!"); } } void Service::getConnections(/* inout */ nsTArray >& aConnections) { mRegistrationMutex.AssertNotCurrentThreadOwns(); MutexAutoLock mutex(mRegistrationMutex); aConnections.Clear(); aConnections.AppendElements(mConnections); } void Service::minimizeMemory() { nsTArray > connections; getConnections(connections); for (uint32_t i = 0; i < connections.Length(); i++) { RefPtr conn = connections[i]; if (!conn->connectionReady()) continue; NS_NAMED_LITERAL_CSTRING(shrinkPragma, "PRAGMA shrink_memory"); nsCOMPtr syncConn = do_QueryInterface( NS_ISUPPORTS_CAST(mozIStorageAsyncConnection*, conn)); bool onOpenedThread = false; if (!syncConn) { // This is a mozIStorageAsyncConnection, it can only be used on the main // thread, so we can do a straight API call. nsCOMPtr ps; DebugOnly rv = conn->ExecuteSimpleSQLAsync(shrinkPragma, nullptr, getter_AddRefs(ps)); MOZ_ASSERT(NS_SUCCEEDED(rv), "Should have purged sqlite caches"); } else if (NS_SUCCEEDED(conn->threadOpenedOn->IsOnCurrentThread(&onOpenedThread)) && onOpenedThread) { // We are on the opener thread, so we can just proceed. conn->ExecuteSimpleSQL(shrinkPragma); } else { // We are on the wrong thread, the query should be executed on the // opener thread, so we must dispatch to it. nsCOMPtr event = NS_NewRunnableMethodWithArg( conn, &Connection::ExecuteSimpleSQL, shrinkPragma); conn->threadOpenedOn->Dispatch(event, NS_DISPATCH_NORMAL); } } } void Service::shutdown() { NS_IF_RELEASE(sXPConnect); } sqlite3_vfs *ConstructTelemetryVFS(); #ifdef MOZ_STORAGE_MEMORY namespace { // By default, SQLite tracks the size of all its heap blocks by adding an extra // 8 bytes at the start of the block to hold the size. Unfortunately, this // causes a lot of 2^N-sized allocations to be rounded up by jemalloc // allocator, wasting memory. For example, a request for 1024 bytes has 8 // bytes added, becoming a request for 1032 bytes, and jemalloc rounds this up // to 2048 bytes, wasting 1012 bytes. (See bug 676189 for more details.) // // So we register jemalloc as the malloc implementation, which avoids this // 8-byte overhead, and thus a lot of waste. This requires us to provide a // function, sqliteMemRoundup(), which computes the actual size that will be // allocated for a given request. SQLite uses this function before all // allocations, and may be able to use any excess bytes caused by the rounding. // // Note: the wrappers for malloc, realloc and moz_malloc_usable_size are // necessary because the sqlite_mem_methods type signatures differ slightly // from the standard ones -- they use int instead of size_t. But we don't need // a wrapper for free. #ifdef MOZ_DMD // sqlite does its own memory accounting, and we use its numbers in our memory // reporters. But we don't want sqlite's heap blocks to show up in DMD's // output as unreported, so we mark them as reported when they're allocated and // mark them as unreported when they are freed. // // In other words, we are marking all sqlite heap blocks as reported even // though we're not reporting them ourselves. Instead we're trusting that // sqlite is fully and correctly accounting for all of its heap blocks via its // own memory accounting. Well, we don't have to trust it entirely, because // it's easy to keep track (while doing this DMD-specific marking) of exactly // how much memory SQLite is using. And we can compare that against what // SQLite reports it is using. MOZ_DEFINE_MALLOC_SIZE_OF_ON_ALLOC(SqliteMallocSizeOfOnAlloc) MOZ_DEFINE_MALLOC_SIZE_OF_ON_FREE(SqliteMallocSizeOfOnFree) #endif static void *sqliteMemMalloc(int n) { void* p = ::malloc(n); #ifdef MOZ_DMD gSqliteMemoryUsed += SqliteMallocSizeOfOnAlloc(p); #endif return p; } static void sqliteMemFree(void *p) { #ifdef MOZ_DMD gSqliteMemoryUsed -= SqliteMallocSizeOfOnFree(p); #endif ::free(p); } static void *sqliteMemRealloc(void *p, int n) { #ifdef MOZ_DMD gSqliteMemoryUsed -= SqliteMallocSizeOfOnFree(p); void *pnew = ::realloc(p, n); if (pnew) { gSqliteMemoryUsed += SqliteMallocSizeOfOnAlloc(pnew); } else { // realloc failed; undo the SqliteMallocSizeOfOnFree from above gSqliteMemoryUsed += SqliteMallocSizeOfOnAlloc(p); } return pnew; #else return ::realloc(p, n); #endif } static int sqliteMemSize(void *p) { return ::moz_malloc_usable_size(p); } static int sqliteMemRoundup(int n) { #if(0) n = malloc_good_size(n); // jemalloc can return blocks of size 2 and 4, but SQLite requires that all // allocations be 8-aligned. So we round up sub-8 requests to 8. This // wastes a small amount of memory but is obviously safe. return n <= 8 ? 8 : n; #else // 10.4 has no good_size in its zones, despite having malloc_good_size. // jemalloc *should* take care of this for us, but just in case, let's // round this up to the nearest 16-byte boundary for AltiVec which also // satisfies the above constraint. return (n & 15) ? (n+16-(n&15)) : n; #endif } static int sqliteMemInit(void *p) { return 0; } static void sqliteMemShutdown(void *p) { } const sqlite3_mem_methods memMethods = { &sqliteMemMalloc, &sqliteMemFree, &sqliteMemRealloc, &sqliteMemSize, &sqliteMemRoundup, &sqliteMemInit, &sqliteMemShutdown, nullptr }; } // namespace #endif // MOZ_STORAGE_MEMORY static const char* sObserverTopics[] = { "memory-pressure", "xpcom-shutdown", "xpcom-shutdown-threads" }; nsresult Service::initialize() { MOZ_ASSERT(NS_IsMainThread(), "Must be initialized on the main thread"); int rc; #ifdef MOZ_STORAGE_MEMORY rc = ::sqlite3_config(SQLITE_CONFIG_MALLOC, &memMethods); if (rc != SQLITE_OK) return convertResultCode(rc); #endif // TODO (bug 1191405): do not preallocate the connections caches until we // have figured the impact on our consumers and memory. sqlite3_config(SQLITE_CONFIG_PAGECACHE, NULL, 0, 0); // Explicitly initialize sqlite3. Although this is implicitly called by // various sqlite3 functions (and the sqlite3_open calls in our case), // the documentation suggests calling this directly. So we do. rc = ::sqlite3_initialize(); if (rc != SQLITE_OK) return convertResultCode(rc); mSqliteVFS = ConstructTelemetryVFS(); if (mSqliteVFS) { rc = sqlite3_vfs_register(mSqliteVFS, 1); if (rc != SQLITE_OK) return convertResultCode(rc); } else { NS_WARNING("Failed to register telemetry VFS"); } // Register for xpcom-shutdown so we can cleanup after ourselves. The // observer service can only be used on the main thread. nsCOMPtr 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; } } // We cache XPConnect for our language helpers. XPConnect can only be // used on the main thread. (void)CallGetService(nsIXPConnect::GetCID(), &sXPConnect); // We need to obtain the toolkit.storage.synchronous preferences on the main // thread because the preference service can only be accessed there. This // is cached in the service for all future Open[Unshared]Database calls. sSynchronousPref = Preferences::GetInt(PREF_TS_SYNCHRONOUS, PREF_TS_SYNCHRONOUS_DEFAULT); // We need to obtain the toolkit.storage.pageSize preferences on the main // thread because the preference service can only be accessed there. This // is cached in the service for all future Open[Unshared]Database calls. sDefaultPageSize = Preferences::GetInt(PREF_TS_PAGESIZE, PREF_TS_PAGESIZE_DEFAULT); mozilla::RegisterWeakMemoryReporter(this); mozilla::RegisterStorageSQLiteDistinguishedAmount(StorageSQLiteDistinguishedAmount); return NS_OK; } int Service::localeCompareStrings(const nsAString &aStr1, const nsAString &aStr2, int32_t aComparisonStrength) { // The implementation of nsICollation.CompareString() is platform-dependent. // On Linux it's not thread-safe. It may not be on Windows and OS X either, // but it's more difficult to tell. We therefore synchronize this method. MutexAutoLock mutex(mMutex); nsICollation *coll = getLocaleCollation(); if (!coll) { NS_ERROR("Storage service has no collation"); return 0; } int32_t res; nsresult rv = coll->CompareString(aComparisonStrength, aStr1, aStr2, &res); if (NS_FAILED(rv)) { NS_ERROR("Collation compare string failed"); return 0; } return res; } nsICollation * Service::getLocaleCollation() { mMutex.AssertCurrentThreadOwns(); if (mLocaleCollation) return mLocaleCollation; nsCOMPtr svc(do_GetService(NS_LOCALESERVICE_CONTRACTID)); if (!svc) { NS_WARNING("Could not get locale service"); return nullptr; } nsCOMPtr appLocale; nsresult rv = svc->GetApplicationLocale(getter_AddRefs(appLocale)); if (NS_FAILED(rv)) { NS_WARNING("Could not get application locale"); return nullptr; } nsCOMPtr collFact = do_CreateInstance(NS_COLLATIONFACTORY_CONTRACTID); if (!collFact) { NS_WARNING("Could not create collation factory"); return nullptr; } rv = collFact->CreateCollation(appLocale, getter_AddRefs(mLocaleCollation)); if (NS_FAILED(rv)) { NS_WARNING("Could not create collation"); return nullptr; } return mLocaleCollation; } //////////////////////////////////////////////////////////////////////////////// //// mozIStorageService NS_IMETHODIMP Service::OpenSpecialDatabase(const char *aStorageKey, mozIStorageConnection **_connection) { nsresult rv; nsCOMPtr storageFile; if (::strcmp(aStorageKey, "memory") == 0) { // just fall through with nullptr storageFile, this will cause the storage // connection to use a memory DB. } else { return NS_ERROR_INVALID_ARG; } RefPtr msc = new Connection(this, SQLITE_OPEN_READWRITE, false); rv = storageFile ? msc->initialize(storageFile) : msc->initialize(); NS_ENSURE_SUCCESS(rv, rv); msc.forget(_connection); return NS_OK; } namespace { class AsyncInitDatabase final : public nsRunnable { public: AsyncInitDatabase(Connection* aConnection, nsIFile* aStorageFile, int32_t aGrowthIncrement, mozIStorageCompletionCallback* aCallback) : mConnection(aConnection) , mStorageFile(aStorageFile) , mGrowthIncrement(aGrowthIncrement) , mCallback(aCallback) { MOZ_ASSERT(NS_IsMainThread()); } NS_IMETHOD Run() { MOZ_ASSERT(!NS_IsMainThread()); nsresult rv = mStorageFile ? mConnection->initialize(mStorageFile) : mConnection->initialize(); if (NS_FAILED(rv)) { nsCOMPtr closeRunnable = NS_NewRunnableMethodWithArg( mConnection.get(), &Connection::AsyncClose, nullptr); MOZ_ASSERT(closeRunnable); MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(closeRunnable))); return DispatchResult(rv, nullptr); } if (mGrowthIncrement >= 0) { // Ignore errors. In the future, we might wish to log them. (void)mConnection->SetGrowthIncrement(mGrowthIncrement, EmptyCString()); } return DispatchResult(NS_OK, NS_ISUPPORTS_CAST(mozIStorageAsyncConnection*, mConnection)); } private: nsresult DispatchResult(nsresult aStatus, nsISupports* aValue) { RefPtr event = new CallbackComplete(aStatus, aValue, mCallback.forget()); return NS_DispatchToMainThread(event); } ~AsyncInitDatabase() { nsCOMPtr thread; DebugOnly rv = NS_GetMainThread(getter_AddRefs(thread)); MOZ_ASSERT(NS_SUCCEEDED(rv)); (void)NS_ProxyRelease(thread, mStorageFile); // Handle ambiguous nsISupports inheritance. Connection *rawConnection = nullptr; mConnection.swap(rawConnection); (void)NS_ProxyRelease(thread, NS_ISUPPORTS_CAST(mozIStorageConnection *, rawConnection)); // 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. mozIStorageCompletionCallback *rawCallback = nullptr; mCallback.swap(rawCallback); (void)NS_ProxyRelease(thread, rawCallback); } RefPtr mConnection; nsCOMPtr mStorageFile; int32_t mGrowthIncrement; RefPtr mCallback; }; } // namespace NS_IMETHODIMP Service::OpenAsyncDatabase(nsIVariant *aDatabaseStore, nsIPropertyBag2 *aOptions, mozIStorageCompletionCallback *aCallback) { if (!NS_IsMainThread()) { return NS_ERROR_NOT_SAME_THREAD; } NS_ENSURE_ARG(aDatabaseStore); NS_ENSURE_ARG(aCallback); nsCOMPtr storageFile; int flags = SQLITE_OPEN_READWRITE; nsCOMPtr 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; } rv = storageFile->Clone(getter_AddRefs(storageFile)); MOZ_ASSERT(NS_SUCCEEDED(rv)); // Ensure that SQLITE_OPEN_CREATE is passed in for compatibility reasons. flags |= SQLITE_OPEN_CREATE; // Extract and apply the shared-cache option. bool shared = false; if (aOptions) { rv = aOptions->GetPropertyAsBool(NS_LITERAL_STRING("shared"), &shared); if (NS_FAILED(rv) && rv != NS_ERROR_NOT_AVAILABLE) { return NS_ERROR_INVALID_ARG; } } 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.EqualsLiteral("memory")) { return NS_ERROR_INVALID_ARG; } // Just fall through with nullptr storageFile, this will cause the storage // connection to use a memory DB. } int32_t growthIncrement = -1; if (aOptions && storageFile) { rv = aOptions->GetPropertyAsInt32(NS_LITERAL_STRING("growthIncrement"), &growthIncrement); if (NS_FAILED(rv) && rv != NS_ERROR_NOT_AVAILABLE) { return NS_ERROR_INVALID_ARG; } } // Create connection on this thread, but initialize it on its helper thread. RefPtr msc = new Connection(this, flags, true); nsCOMPtr target = msc->getAsyncExecutionTarget(); MOZ_ASSERT(target, "Cannot initialize a connection that has been closed already"); RefPtr asyncInit = new AsyncInitDatabase(msc, storageFile, growthIncrement, aCallback); return target->Dispatch(asyncInit, nsIEventTarget::DISPATCH_NORMAL); } NS_IMETHODIMP Service::OpenDatabase(nsIFile *aDatabaseFile, mozIStorageConnection **_connection) { NS_ENSURE_ARG(aDatabaseFile); // Always ensure that SQLITE_OPEN_CREATE is passed in for compatibility // reasons. int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_SHAREDCACHE | SQLITE_OPEN_CREATE; RefPtr msc = new Connection(this, flags, false); nsresult rv = msc->initialize(aDatabaseFile); NS_ENSURE_SUCCESS(rv, rv); msc.forget(_connection); return NS_OK; } NS_IMETHODIMP Service::OpenUnsharedDatabase(nsIFile *aDatabaseFile, mozIStorageConnection **_connection) { NS_ENSURE_ARG(aDatabaseFile); // Always ensure that SQLITE_OPEN_CREATE is passed in for compatibility // reasons. int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_PRIVATECACHE | SQLITE_OPEN_CREATE; RefPtr msc = new Connection(this, flags, false); nsresult rv = msc->initialize(aDatabaseFile); NS_ENSURE_SUCCESS(rv, rv); msc.forget(_connection); return NS_OK; } NS_IMETHODIMP Service::OpenDatabaseWithFileURL(nsIFileURL *aFileURL, mozIStorageConnection **_connection) { NS_ENSURE_ARG(aFileURL); // Always ensure that SQLITE_OPEN_CREATE is passed in for compatibility // reasons. int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_SHAREDCACHE | SQLITE_OPEN_CREATE | SQLITE_OPEN_URI; RefPtr msc = new Connection(this, flags, false); nsresult rv = msc->initialize(aFileURL); 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 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 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") == 0) { shutdown(); } else if (strcmp(aTopic, "xpcom-shutdown-threads") == 0) { nsCOMPtr os = mozilla::services::GetObserverService(); for (size_t i = 0; i < ArrayLength(sObserverTopics); ++i) { (void)os->RemoveObserver(this, sObserverTopics[i]); } bool anyOpen = false; do { nsTArray > connections; getConnections(connections); anyOpen = false; for (uint32_t i = 0; i < connections.Length(); i++) { RefPtr &conn = connections[i]; if (conn->isClosing()) { anyOpen = true; break; } } if (anyOpen) { nsCOMPtr thread = do_GetCurrentThread(); NS_ProcessNextEvent(thread); } } while (anyOpen); if (gShutdownChecks == SCM_CRASH) { nsTArray > connections; getConnections(connections); for (uint32_t i = 0, n = connections.Length(); i < n; i++) { if (!connections[i]->isClosed()) { // This seems to happen with uBlock origin ... fprintf(stderr, "FATAL ERROR: Storage connections not closed! Bad add-on! %s:%i\n", __FILE__, __LINE__); #if __ppc__ // Drop into the debugger. __asm__("trap\n"); #endif MOZ_CRASH(); } } } } return NS_OK; } } // namespace storage } // namespace mozilla