diff options
Diffstat (limited to 'storage/mozStorageService.cpp')
-rw-r--r-- | storage/mozStorageService.cpp | 982 |
1 files changed, 982 insertions, 0 deletions
diff --git a/storage/mozStorageService.cpp b/storage/mozStorageService.cpp new file mode 100644 index 000000000..4f288ad42 --- /dev/null +++ b/storage/mozStorageService.cpp @@ -0,0 +1,982 @@ +/* -*- 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 "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<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(EmptyCString(), 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. + 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."); + ReportConn(aHandleReport, aData, conn, pathHead, + NS_LITERAL_CSTRING("stmt"), stmtDesc, + SQLITE_DBSTATUS_STMT_USED, &totalConnSize); + + NS_NAMED_LITERAL_CSTRING(cacheDesc, + "Memory (approximate) used by all pager caches used by connections " + "to this database."); + ReportConn(aHandleReport, aData, conn, pathHead, + NS_LITERAL_CSTRING("cache"), cacheDesc, + SQLITE_DBSTATUS_CACHE_USED_SHARED, &totalConnSize); + + NS_NAMED_LITERAL_CSTRING(schemaDesc, + "Memory (approximate) used to store the schema for all databases " + "associated with connections to this database."); + ReportConn(aHandleReport, aData, conn, pathHead, + NS_LITERAL_CSTRING("schema"), 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; + +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<nsIPromptService> ps(do_GetService(NS_PROMPTSERVICE_CONTRACTID)); + if (ps) { + nsAutoString title, message; + title.AppendLiteral("SQLite Version Error"); + message.AppendLiteral("The application has been updated, but the SQLite " + "library wasn't updated properly and the application " + "cannot run. Please try to launch the application again. " + "If that should still fail, please try reinstalling " + "it, or visit https://support.mozilla.org/."); + (void)ps->Alert(nullptr, title.get(), message.get()); + } + MOZ_CRASH("SQLite Version Error"); + } + + // 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<nsIXPConnect> +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<nsIXPConnect> 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<bool> 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<Service> kungFuDeathGrip(this); + { + mRegistrationMutex.AssertNotCurrentThreadOwns(); + MutexAutoLock mutex(mRegistrationMutex); + + for (uint32_t i = 0 ; i < mConnections.Length(); ++i) { + if (mConnections[i] == aConnection) { + nsCOMPtr<nsIThread> 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, mConnections[i].forget()); + + mConnections.RemoveElementAt(i); + return; + } + } + + MOZ_ASSERT_UNREACHABLE("Attempt to unregister unknown storage connection!"); + } +} + +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]; + if (!conn->connectionReady()) + continue; + + NS_NAMED_LITERAL_CSTRING(shrinkPragma, "PRAGMA shrink_memory"); + nsCOMPtr<mozIStorageConnection> 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<mozIStoragePendingStatement> ps; + DebugOnly<nsresult> 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<nsIRunnable> event = + NewRunnableMethod<const nsCString>( + 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) +{ + 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; +} + +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<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; + } + } + + // 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<nsILocaleService> svc(do_GetService(NS_LOCALESERVICE_CONTRACTID)); + if (!svc) { + NS_WARNING("Could not get locale service"); + return nullptr; + } + + nsCOMPtr<nsILocale> appLocale; + nsresult rv = svc->GetApplicationLocale(getter_AddRefs(appLocale)); + if (NS_FAILED(rv)) { + NS_WARNING("Could not get application locale"); + return nullptr; + } + + nsCOMPtr<nsICollationFactory> 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<nsIFile> 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<Connection> 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 Runnable +{ +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() override + { + MOZ_ASSERT(!NS_IsMainThread()); + nsresult rv = mStorageFile ? mConnection->initialize(mStorageFile) + : mConnection->initialize(); + if (NS_FAILED(rv)) { + nsCOMPtr<nsIRunnable> closeRunnable = + NewRunnableMethod<mozIStorageCompletionCallback*>( + mConnection.get(), + &Connection::AsyncClose, + nullptr); + MOZ_ASSERT(closeRunnable); + MOZ_ALWAYS_SUCCEEDS(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<CallbackComplete> event = + new CallbackComplete(aStatus, + aValue, + mCallback.forget()); + return NS_DispatchToMainThread(event); + } + + ~AsyncInitDatabase() + { + NS_ReleaseOnMainThread(mStorageFile.forget()); + NS_ReleaseOnMainThread(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(mCallback.forget()); + } + + RefPtr<Connection> mConnection; + nsCOMPtr<nsIFile> mStorageFile; + int32_t mGrowthIncrement; + RefPtr<mozIStorageCompletionCallback> 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); + + nsresult rv; + bool shared = false; + bool readOnly = false; + bool ignoreLockingMode = false; + int32_t growthIncrement = -1; + +#define FAIL_IF_SET_BUT_INVALID(rv)\ + if (NS_FAILED(rv) && rv != NS_ERROR_NOT_AVAILABLE) { \ + return NS_ERROR_INVALID_ARG; \ + } + + // Deal with options first: + if (aOptions) { + rv = aOptions->GetPropertyAsBool(NS_LITERAL_STRING("readOnly"), &readOnly); + FAIL_IF_SET_BUT_INVALID(rv); + + rv = aOptions->GetPropertyAsBool(NS_LITERAL_STRING("ignoreLockingMode"), + &ignoreLockingMode); + FAIL_IF_SET_BUT_INVALID(rv); + // Specifying ignoreLockingMode will force use of the readOnly flag: + if (ignoreLockingMode) { + readOnly = true; + } + + rv = aOptions->GetPropertyAsBool(NS_LITERAL_STRING("shared"), &shared); + FAIL_IF_SET_BUT_INVALID(rv); + + // NB: we re-set to -1 if we don't have a storage file later on. + rv = aOptions->GetPropertyAsInt32(NS_LITERAL_STRING("growthIncrement"), + &growthIncrement); + FAIL_IF_SET_BUT_INVALID(rv); + } + int flags = readOnly ? SQLITE_OPEN_READONLY : SQLITE_OPEN_READWRITE; + + nsCOMPtr<nsIFile> storageFile; + nsCOMPtr<nsISupports> dbStore; + 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)); + + 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.EqualsLiteral("memory")) { + return NS_ERROR_INVALID_ARG; + } + + // Just fall through with nullptr storageFile, this will cause the storage + // connection to use a memory DB. + } + + if (!storageFile && growthIncrement >= 0) { + return NS_ERROR_INVALID_ARG; + } + + // Create connection on this thread, but initialize it on its helper thread. + RefPtr<Connection> msc = new Connection(this, flags, 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, + 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<Connection> 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<Connection> 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<Connection> 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<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") == 0) { + shutdown(); + } else if (strcmp(aTopic, "xpcom-shutdown-threads") == 0) { + nsCOMPtr<nsIObserverService> os = + mozilla::services::GetObserverService(); + + for (size_t i = 0; i < ArrayLength(sObserverTopics); ++i) { + (void)os->RemoveObserver(this, sObserverTopics[i]); + } + + bool anyOpen = false; + do { + nsTArray<RefPtr<Connection> > connections; + getConnections(connections); + anyOpen = false; + for (uint32_t i = 0; i < connections.Length(); i++) { + RefPtr<Connection> &conn = connections[i]; + if (conn->isClosing()) { + anyOpen = true; + break; + } + } + if (anyOpen) { + nsCOMPtr<nsIThread> thread = do_GetCurrentThread(); + NS_ProcessNextEvent(thread); + } + } while (anyOpen); + + if (gShutdownChecks == SCM_CRASH) { + nsTArray<RefPtr<Connection> > connections; + getConnections(connections); + for (uint32_t i = 0, n = connections.Length(); i < n; i++) { + if (!connections[i]->isClosed()) { + MOZ_CRASH(); + } + } + } + } + + return NS_OK; +} + +} // namespace storage +} // namespace mozilla |