storage/src/mozStorageService.cpp

Tue, 06 Jan 2015 21:39:09 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 06 Jan 2015 21:39:09 +0100
branch
TOR_BUG_9701
changeset 8
97036ab72558
permissions
-rw-r--r--

Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

     1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
     2  * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
     3  * This Source Code Form is subject to the terms of the Mozilla Public
     4  * License, v. 2.0. If a copy of the MPL was not distributed with this
     5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     7 #include "mozilla/Attributes.h"
     8 #include "mozilla/DebugOnly.h"
    10 #include "mozStorageService.h"
    11 #include "mozStorageConnection.h"
    12 #include "prinit.h"
    13 #include "nsAutoPtr.h"
    14 #include "nsCollationCID.h"
    15 #include "nsEmbedCID.h"
    16 #include "nsThreadUtils.h"
    17 #include "mozStoragePrivateHelpers.h"
    18 #include "nsILocale.h"
    19 #include "nsILocaleService.h"
    20 #include "nsIXPConnect.h"
    21 #include "nsIObserverService.h"
    22 #include "nsIPropertyBag2.h"
    23 #include "mozilla/Services.h"
    24 #include "mozilla/Preferences.h"
    25 #include "mozilla/LateWriteChecks.h"
    26 #include "mozIStorageCompletionCallback.h"
    27 #include "mozIStoragePendingStatement.h"
    29 #include "sqlite3.h"
    31 #ifdef SQLITE_OS_WIN
    32 // "windows.h" was included and it can #define lots of things we care about...
    33 #undef CompareString
    34 #endif
    36 #include "nsIPromptService.h"
    38 #ifdef MOZ_STORAGE_MEMORY
    39 #  include "mozmemory.h"
    40 #  ifdef MOZ_DMD
    41 #    include "DMD.h"
    42 #  endif
    43 #endif
    45 ////////////////////////////////////////////////////////////////////////////////
    46 //// Defines
    48 #define PREF_TS_SYNCHRONOUS "toolkit.storage.synchronous"
    49 #define PREF_TS_SYNCHRONOUS_DEFAULT 1
    51 #define PREF_TS_PAGESIZE "toolkit.storage.pageSize"
    53 // This value must be kept in sync with the value of SQLITE_DEFAULT_PAGE_SIZE in
    54 // db/sqlite3/src/Makefile.in.
    55 #define PREF_TS_PAGESIZE_DEFAULT 32768
    57 namespace mozilla {
    58 namespace storage {
    60 ////////////////////////////////////////////////////////////////////////////////
    61 //// Memory Reporting
    63 #ifdef MOZ_DMD
    64 static mozilla::Atomic<size_t> gSqliteMemoryUsed;
    65 #endif
    67 static int64_t
    68 StorageSQLiteDistinguishedAmount()
    69 {
    70   return ::sqlite3_memory_used();
    71 }
    73 /**
    74  * Passes a single SQLite memory statistic to a memory reporter callback.
    75  *
    76  * @param aHandleReport
    77  *        The callback.
    78  * @param aData
    79  *        The data for the callback.
    80  * @param aConn
    81  *        The SQLite connection.
    82  * @param aPathHead
    83  *        Head of the path for the memory report.
    84  * @param aKind
    85  *        The memory report statistic kind, one of "stmt", "cache" or
    86  *        "schema".
    87  * @param aDesc
    88  *        The memory report description.
    89  * @param aOption
    90  *        The SQLite constant for getting the measurement.
    91  * @param aTotal
    92  *        The accumulator for the measurement.
    93  */
    94 nsresult
    95 ReportConn(nsIHandleReportCallback *aHandleReport,
    96            nsISupports *aData,
    97            Connection *aConn,
    98            const nsACString &aPathHead,
    99            const nsACString &aKind,
   100            const nsACString &aDesc,
   101            int32_t aOption,
   102            size_t *aTotal)
   103 {
   104   nsCString path(aPathHead);
   105   path.Append(aKind);
   106   path.AppendLiteral("-used");
   108   int32_t val = aConn->getSqliteRuntimeStatus(aOption);
   109   nsresult rv = aHandleReport->Callback(EmptyCString(), path,
   110                                         nsIMemoryReporter::KIND_HEAP,
   111                                         nsIMemoryReporter::UNITS_BYTES,
   112                                         int64_t(val), aDesc, aData);
   113   NS_ENSURE_SUCCESS(rv, rv);
   114   *aTotal += val;
   116   return NS_OK;
   117 }
   119 // Warning: To get a Connection's measurements requires holding its lock.
   120 // There may be a delay getting the lock if another thread is accessing the
   121 // Connection.  This isn't very nice if CollectReports is called from the main
   122 // thread!  But at the time of writing this function is only called when
   123 // about:memory is loaded (not, for example, when telemetry pings occur) and
   124 // any delays in that case aren't so bad.
   125 NS_IMETHODIMP
   126 Service::CollectReports(nsIHandleReportCallback *aHandleReport,
   127                         nsISupports *aData)
   128 {
   129   nsresult rv;
   130   size_t totalConnSize = 0;
   131   {
   132     nsTArray<nsRefPtr<Connection> > connections;
   133     getConnections(connections);
   135     for (uint32_t i = 0; i < connections.Length(); i++) {
   136       nsRefPtr<Connection> &conn = connections[i];
   138       // Someone may have closed the Connection, in which case we skip it.
   139       bool isReady;
   140       (void)conn->GetConnectionReady(&isReady);
   141       if (!isReady) {
   142           continue;
   143       }
   145       nsCString pathHead("explicit/storage/sqlite/");
   146       pathHead.Append(conn->getFilename());
   147       pathHead.AppendLiteral("/");
   149       SQLiteMutexAutoLock lockedScope(conn->sharedDBMutex);
   151       NS_NAMED_LITERAL_CSTRING(stmtDesc,
   152         "Memory (approximate) used by all prepared statements used by "
   153         "connections to this database.");
   154       rv = ReportConn(aHandleReport, aData, conn, pathHead,
   155                       NS_LITERAL_CSTRING("stmt"), stmtDesc,
   156                       SQLITE_DBSTATUS_STMT_USED, &totalConnSize);
   157       NS_ENSURE_SUCCESS(rv, rv);
   159       NS_NAMED_LITERAL_CSTRING(cacheDesc,
   160         "Memory (approximate) used by all pager caches used by connections "
   161         "to this database.");
   162       rv = ReportConn(aHandleReport, aData, conn, pathHead,
   163                       NS_LITERAL_CSTRING("cache"), cacheDesc,
   164                       SQLITE_DBSTATUS_CACHE_USED, &totalConnSize);
   165       NS_ENSURE_SUCCESS(rv, rv);
   167       NS_NAMED_LITERAL_CSTRING(schemaDesc,
   168         "Memory (approximate) used to store the schema for all databases "
   169         "associated with connections to this database.");
   170       rv = ReportConn(aHandleReport, aData, conn, pathHead,
   171                       NS_LITERAL_CSTRING("schema"), schemaDesc,
   172                       SQLITE_DBSTATUS_SCHEMA_USED, &totalConnSize);
   173       NS_ENSURE_SUCCESS(rv, rv);
   174     }
   176 #ifdef MOZ_DMD
   177     if (::sqlite3_memory_used() != int64_t(gSqliteMemoryUsed)) {
   178       NS_WARNING("memory consumption reported by SQLite doesn't match "
   179                  "our measurements");
   180     }
   181 #endif
   182   }
   184   int64_t other = ::sqlite3_memory_used() - totalConnSize;
   186   rv = aHandleReport->Callback(
   187           EmptyCString(),
   188           NS_LITERAL_CSTRING("explicit/storage/sqlite/other"),
   189           KIND_HEAP, UNITS_BYTES, other,
   190           NS_LITERAL_CSTRING("All unclassified sqlite memory."),
   191           aData);
   192   NS_ENSURE_SUCCESS(rv, rv);
   194   return NS_OK;
   195 }
   197 ////////////////////////////////////////////////////////////////////////////////
   198 //// Service
   200 NS_IMPL_ISUPPORTS(
   201   Service,
   202   mozIStorageService,
   203   nsIObserver,
   204   nsIMemoryReporter
   205 )
   207 Service *Service::gService = nullptr;
   209 Service *
   210 Service::getSingleton()
   211 {
   212   if (gService) {
   213     NS_ADDREF(gService);
   214     return gService;
   215   }
   217   // Ensure that we are using the same version of SQLite that we compiled with
   218   // or newer.  Our configure check ensures we are using a new enough version
   219   // at compile time.
   220   if (SQLITE_VERSION_NUMBER > ::sqlite3_libversion_number()) {
   221     nsCOMPtr<nsIPromptService> ps(do_GetService(NS_PROMPTSERVICE_CONTRACTID));
   222     if (ps) {
   223       nsAutoString title, message;
   224       title.AppendLiteral("SQLite Version Error");
   225       message.AppendLiteral("The application has been updated, but your version "
   226                           "of SQLite is too old and the application cannot "
   227                           "run.");
   228       (void)ps->Alert(nullptr, title.get(), message.get());
   229     }
   230     ::PR_Abort();
   231   }
   233   // The first reference to the storage service must be obtained on the
   234   // main thread.
   235   NS_ENSURE_TRUE(NS_IsMainThread(), nullptr);
   236   gService = new Service();
   237   if (gService) {
   238     NS_ADDREF(gService);
   239     if (NS_FAILED(gService->initialize()))
   240       NS_RELEASE(gService);
   241   }
   243   return gService;
   244 }
   246 nsIXPConnect *Service::sXPConnect = nullptr;
   248 // static
   249 already_AddRefed<nsIXPConnect>
   250 Service::getXPConnect()
   251 {
   252   NS_PRECONDITION(NS_IsMainThread(),
   253                   "Must only get XPConnect on the main thread!");
   254   NS_PRECONDITION(gService,
   255                   "Can not get XPConnect without an instance of our service!");
   257   // If we've been shutdown, sXPConnect will be null.  To prevent leaks, we do
   258   // not cache the service after this point.
   259   nsCOMPtr<nsIXPConnect> xpc(sXPConnect);
   260   if (!xpc)
   261     xpc = do_GetService(nsIXPConnect::GetCID());
   262   NS_ASSERTION(xpc, "Could not get XPConnect!");
   263   return xpc.forget();
   264 }
   266 int32_t Service::sSynchronousPref;
   268 // static
   269 int32_t
   270 Service::getSynchronousPref()
   271 {
   272   return sSynchronousPref;
   273 }
   275 int32_t Service::sDefaultPageSize = PREF_TS_PAGESIZE_DEFAULT;
   277 Service::Service()
   278 : mMutex("Service::mMutex")
   279 , mSqliteVFS(nullptr)
   280 , mRegistrationMutex("Service::mRegistrationMutex")
   281 , mConnections()
   282 {
   283 }
   285 Service::~Service()
   286 {
   287   mozilla::UnregisterWeakMemoryReporter(this);
   288   mozilla::UnregisterStorageSQLiteDistinguishedAmount();
   290   int rc = sqlite3_vfs_unregister(mSqliteVFS);
   291   if (rc != SQLITE_OK)
   292     NS_WARNING("Failed to unregister sqlite vfs wrapper.");
   294   // Shutdown the sqlite3 API.  Warn if shutdown did not turn out okay, but
   295   // there is nothing actionable we can do in that case.
   296   rc = ::sqlite3_shutdown();
   297   if (rc != SQLITE_OK)
   298     NS_WARNING("sqlite3 did not shutdown cleanly.");
   300   DebugOnly<bool> shutdownObserved = !sXPConnect;
   301   NS_ASSERTION(shutdownObserved, "Shutdown was not observed!");
   303   gService = nullptr;
   304   delete mSqliteVFS;
   305   mSqliteVFS = nullptr;
   306 }
   308 void
   309 Service::registerConnection(Connection *aConnection)
   310 {
   311   mRegistrationMutex.AssertNotCurrentThreadOwns();
   312   MutexAutoLock mutex(mRegistrationMutex);
   313   (void)mConnections.AppendElement(aConnection);
   314 }
   316 void
   317 Service::unregisterConnection(Connection *aConnection)
   318 {
   319   // If this is the last Connection it might be the only thing keeping Service
   320   // alive.  So ensure that Service is destroyed only after the Connection is
   321   // cleanly unregistered and destroyed.
   322   nsRefPtr<Service> kungFuDeathGrip(this);
   323   {
   324     mRegistrationMutex.AssertNotCurrentThreadOwns();
   325     MutexAutoLock mutex(mRegistrationMutex);
   326     DebugOnly<bool> removed = mConnections.RemoveElement(aConnection);
   327     // Assert if we try to unregister a non-existent connection.
   328     MOZ_ASSERT(removed);
   329   }
   330 }
   332 void
   333 Service::getConnections(/* inout */ nsTArray<nsRefPtr<Connection> >& aConnections)
   334 {
   335   mRegistrationMutex.AssertNotCurrentThreadOwns();
   336   MutexAutoLock mutex(mRegistrationMutex);
   337   aConnections.Clear();
   338   aConnections.AppendElements(mConnections);
   339 }
   341 void
   342 Service::minimizeMemory()
   343 {
   344   nsTArray<nsRefPtr<Connection> > connections;
   345   getConnections(connections);
   347   for (uint32_t i = 0; i < connections.Length(); i++) {
   348     nsRefPtr<Connection> conn = connections[i];
   349     if (conn->connectionReady()) {
   350       NS_NAMED_LITERAL_CSTRING(shrinkPragma, "PRAGMA shrink_memory");
   351       nsCOMPtr<mozIStorageConnection> syncConn = do_QueryInterface(
   352         NS_ISUPPORTS_CAST(mozIStorageAsyncConnection*, conn));
   353       DebugOnly<nsresult> rv;
   355       if (!syncConn) {
   356         nsCOMPtr<mozIStoragePendingStatement> ps;
   357         rv = connections[i]->ExecuteSimpleSQLAsync(shrinkPragma, nullptr,
   358           getter_AddRefs(ps));
   359       } else {
   360         rv = connections[i]->ExecuteSimpleSQL(shrinkPragma);
   361       }
   363       MOZ_ASSERT(NS_SUCCEEDED(rv),
   364         "Should have been able to purge sqlite caches");
   365     }
   366   }
   367 }
   369 void
   370 Service::shutdown()
   371 {
   372   NS_IF_RELEASE(sXPConnect);
   373 }
   375 sqlite3_vfs *ConstructTelemetryVFS();
   377 #ifdef MOZ_STORAGE_MEMORY
   379 namespace {
   381 // By default, SQLite tracks the size of all its heap blocks by adding an extra
   382 // 8 bytes at the start of the block to hold the size.  Unfortunately, this
   383 // causes a lot of 2^N-sized allocations to be rounded up by jemalloc
   384 // allocator, wasting memory.  For example, a request for 1024 bytes has 8
   385 // bytes added, becoming a request for 1032 bytes, and jemalloc rounds this up
   386 // to 2048 bytes, wasting 1012 bytes.  (See bug 676189 for more details.)
   387 //
   388 // So we register jemalloc as the malloc implementation, which avoids this
   389 // 8-byte overhead, and thus a lot of waste.  This requires us to provide a
   390 // function, sqliteMemRoundup(), which computes the actual size that will be
   391 // allocated for a given request.  SQLite uses this function before all
   392 // allocations, and may be able to use any excess bytes caused by the rounding.
   393 //
   394 // Note: the wrappers for moz_malloc, moz_realloc and moz_malloc_usable_size
   395 // are necessary because the sqlite_mem_methods type signatures differ slightly
   396 // from the standard ones -- they use int instead of size_t.  But we don't need
   397 // a wrapper for moz_free.
   399 #ifdef MOZ_DMD
   401 // sqlite does its own memory accounting, and we use its numbers in our memory
   402 // reporters.  But we don't want sqlite's heap blocks to show up in DMD's
   403 // output as unreported, so we mark them as reported when they're allocated and
   404 // mark them as unreported when they are freed.
   405 //
   406 // In other words, we are marking all sqlite heap blocks as reported even
   407 // though we're not reporting them ourselves.  Instead we're trusting that
   408 // sqlite is fully and correctly accounting for all of its heap blocks via its
   409 // own memory accounting.  Well, we don't have to trust it entirely, because
   410 // it's easy to keep track (while doing this DMD-specific marking) of exactly
   411 // how much memory SQLite is using.  And we can compare that against what
   412 // SQLite reports it is using.
   414 MOZ_DEFINE_MALLOC_SIZE_OF_ON_ALLOC(SqliteMallocSizeOfOnAlloc)
   415 MOZ_DEFINE_MALLOC_SIZE_OF_ON_FREE(SqliteMallocSizeOfOnFree)
   417 #endif
   419 static void *sqliteMemMalloc(int n)
   420 {
   421   void* p = ::moz_malloc(n);
   422 #ifdef MOZ_DMD
   423   gSqliteMemoryUsed += SqliteMallocSizeOfOnAlloc(p);
   424 #endif
   425   return p;
   426 }
   428 static void sqliteMemFree(void *p)
   429 {
   430 #ifdef MOZ_DMD
   431   gSqliteMemoryUsed -= SqliteMallocSizeOfOnFree(p);
   432 #endif
   433   ::moz_free(p);
   434 }
   436 static void *sqliteMemRealloc(void *p, int n)
   437 {
   438 #ifdef MOZ_DMD
   439   gSqliteMemoryUsed -= SqliteMallocSizeOfOnFree(p);
   440   void *pnew = ::moz_realloc(p, n);
   441   if (pnew) {
   442     gSqliteMemoryUsed += SqliteMallocSizeOfOnAlloc(pnew);
   443   } else {
   444     // realloc failed;  undo the SqliteMallocSizeOfOnFree from above
   445     gSqliteMemoryUsed += SqliteMallocSizeOfOnAlloc(p);
   446   }
   447   return pnew;
   448 #else
   449   return ::moz_realloc(p, n);
   450 #endif
   451 }
   453 static int sqliteMemSize(void *p)
   454 {
   455   return ::moz_malloc_usable_size(p);
   456 }
   458 static int sqliteMemRoundup(int n)
   459 {
   460   n = malloc_good_size(n);
   462   // jemalloc can return blocks of size 2 and 4, but SQLite requires that all
   463   // allocations be 8-aligned.  So we round up sub-8 requests to 8.  This
   464   // wastes a small amount of memory but is obviously safe.
   465   return n <= 8 ? 8 : n;
   466 }
   468 static int sqliteMemInit(void *p)
   469 {
   470   return 0;
   471 }
   473 static void sqliteMemShutdown(void *p)
   474 {
   475 }
   477 const sqlite3_mem_methods memMethods = {
   478   &sqliteMemMalloc,
   479   &sqliteMemFree,
   480   &sqliteMemRealloc,
   481   &sqliteMemSize,
   482   &sqliteMemRoundup,
   483   &sqliteMemInit,
   484   &sqliteMemShutdown,
   485   nullptr
   486 };
   488 } // anonymous namespace
   490 #endif  // MOZ_STORAGE_MEMORY
   492 static const char* sObserverTopics[] = {
   493   "memory-pressure",
   494   "xpcom-shutdown",
   495   "xpcom-shutdown-threads"
   496 };
   498 nsresult
   499 Service::initialize()
   500 {
   501   MOZ_ASSERT(NS_IsMainThread(), "Must be initialized on the main thread");
   503   int rc;
   505 #ifdef MOZ_STORAGE_MEMORY
   506   rc = ::sqlite3_config(SQLITE_CONFIG_MALLOC, &memMethods);
   507   if (rc != SQLITE_OK)
   508     return convertResultCode(rc);
   509 #endif
   511   // Explicitly initialize sqlite3.  Although this is implicitly called by
   512   // various sqlite3 functions (and the sqlite3_open calls in our case),
   513   // the documentation suggests calling this directly.  So we do.
   514   rc = ::sqlite3_initialize();
   515   if (rc != SQLITE_OK)
   516     return convertResultCode(rc);
   518   mSqliteVFS = ConstructTelemetryVFS();
   519   if (mSqliteVFS) {
   520     rc = sqlite3_vfs_register(mSqliteVFS, 1);
   521     if (rc != SQLITE_OK)
   522       return convertResultCode(rc);
   523   } else {
   524     NS_WARNING("Failed to register telemetry VFS");
   525   }
   527   // Register for xpcom-shutdown so we can cleanup after ourselves.  The
   528   // observer service can only be used on the main thread.
   529   nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
   530   NS_ENSURE_TRUE(os, NS_ERROR_FAILURE);
   532   for (size_t i = 0; i < ArrayLength(sObserverTopics); ++i) {
   533     nsresult rv = os->AddObserver(this, sObserverTopics[i], false);
   534     if (NS_WARN_IF(NS_FAILED(rv))) {
   535       return rv;
   536     }
   537   }
   539   // We cache XPConnect for our language helpers.  XPConnect can only be
   540   // used on the main thread.
   541   (void)CallGetService(nsIXPConnect::GetCID(), &sXPConnect);
   543   // We need to obtain the toolkit.storage.synchronous preferences on the main
   544   // thread because the preference service can only be accessed there.  This
   545   // is cached in the service for all future Open[Unshared]Database calls.
   546   sSynchronousPref =
   547     Preferences::GetInt(PREF_TS_SYNCHRONOUS, PREF_TS_SYNCHRONOUS_DEFAULT);
   549   // We need to obtain the toolkit.storage.pageSize preferences on the main
   550   // thread because the preference service can only be accessed there.  This
   551   // is cached in the service for all future Open[Unshared]Database calls.
   552   sDefaultPageSize =
   553       Preferences::GetInt(PREF_TS_PAGESIZE, PREF_TS_PAGESIZE_DEFAULT);
   555   mozilla::RegisterWeakMemoryReporter(this);
   556   mozilla::RegisterStorageSQLiteDistinguishedAmount(StorageSQLiteDistinguishedAmount);
   558   return NS_OK;
   559 }
   561 int
   562 Service::localeCompareStrings(const nsAString &aStr1,
   563                               const nsAString &aStr2,
   564                               int32_t aComparisonStrength)
   565 {
   566   // The implementation of nsICollation.CompareString() is platform-dependent.
   567   // On Linux it's not thread-safe.  It may not be on Windows and OS X either,
   568   // but it's more difficult to tell.  We therefore synchronize this method.
   569   MutexAutoLock mutex(mMutex);
   571   nsICollation *coll = getLocaleCollation();
   572   if (!coll) {
   573     NS_ERROR("Storage service has no collation");
   574     return 0;
   575   }
   577   int32_t res;
   578   nsresult rv = coll->CompareString(aComparisonStrength, aStr1, aStr2, &res);
   579   if (NS_FAILED(rv)) {
   580     NS_ERROR("Collation compare string failed");
   581     return 0;
   582   }
   584   return res;
   585 }
   587 nsICollation *
   588 Service::getLocaleCollation()
   589 {
   590   mMutex.AssertCurrentThreadOwns();
   592   if (mLocaleCollation)
   593     return mLocaleCollation;
   595   nsCOMPtr<nsILocaleService> svc(do_GetService(NS_LOCALESERVICE_CONTRACTID));
   596   if (!svc) {
   597     NS_WARNING("Could not get locale service");
   598     return nullptr;
   599   }
   601   nsCOMPtr<nsILocale> appLocale;
   602   nsresult rv = svc->GetApplicationLocale(getter_AddRefs(appLocale));
   603   if (NS_FAILED(rv)) {
   604     NS_WARNING("Could not get application locale");
   605     return nullptr;
   606   }
   608   nsCOMPtr<nsICollationFactory> collFact =
   609     do_CreateInstance(NS_COLLATIONFACTORY_CONTRACTID);
   610   if (!collFact) {
   611     NS_WARNING("Could not create collation factory");
   612     return nullptr;
   613   }
   615   rv = collFact->CreateCollation(appLocale, getter_AddRefs(mLocaleCollation));
   616   if (NS_FAILED(rv)) {
   617     NS_WARNING("Could not create collation");
   618     return nullptr;
   619   }
   621   return mLocaleCollation;
   622 }
   624 ////////////////////////////////////////////////////////////////////////////////
   625 //// mozIStorageService
   628 NS_IMETHODIMP
   629 Service::OpenSpecialDatabase(const char *aStorageKey,
   630                              mozIStorageConnection **_connection)
   631 {
   632   nsresult rv;
   634   nsCOMPtr<nsIFile> storageFile;
   635   if (::strcmp(aStorageKey, "memory") == 0) {
   636     // just fall through with nullptr storageFile, this will cause the storage
   637     // connection to use a memory DB.
   638   }
   639   else {
   640     return NS_ERROR_INVALID_ARG;
   641   }
   643   nsRefPtr<Connection> msc = new Connection(this, SQLITE_OPEN_READWRITE, false);
   645   rv = storageFile ? msc->initialize(storageFile) : msc->initialize();
   646   NS_ENSURE_SUCCESS(rv, rv);
   648   msc.forget(_connection);
   649   return NS_OK;
   651 }
   653 namespace {
   655 class AsyncInitDatabase MOZ_FINAL : public nsRunnable
   656 {
   657 public:
   658   AsyncInitDatabase(Connection* aConnection,
   659                     nsIFile* aStorageFile,
   660                     int32_t aGrowthIncrement,
   661                     mozIStorageCompletionCallback* aCallback)
   662     : mConnection(aConnection)
   663     , mStorageFile(aStorageFile)
   664     , mGrowthIncrement(aGrowthIncrement)
   665     , mCallback(aCallback)
   666   {
   667     MOZ_ASSERT(NS_IsMainThread());
   668   }
   670   NS_IMETHOD Run()
   671   {
   672     MOZ_ASSERT(!NS_IsMainThread());
   673     nsresult rv = mStorageFile ? mConnection->initialize(mStorageFile)
   674                                : mConnection->initialize();
   675     if (NS_FAILED(rv)) {
   676       return DispatchResult(rv, nullptr);
   677     }
   679     if (mGrowthIncrement >= 0) {
   680       // Ignore errors. In the future, we might wish to log them.
   681       (void)mConnection->SetGrowthIncrement(mGrowthIncrement, EmptyCString());
   682     }
   684     return DispatchResult(NS_OK, NS_ISUPPORTS_CAST(mozIStorageAsyncConnection*,
   685                           mConnection));
   686   }
   688 private:
   689   nsresult DispatchResult(nsresult aStatus, nsISupports* aValue) {
   690     nsRefPtr<CallbackComplete> event =
   691       new CallbackComplete(aStatus,
   692                            aValue,
   693                            mCallback.forget());
   694     return NS_DispatchToMainThread(event);
   695   }
   697   ~AsyncInitDatabase()
   698   {
   699     nsCOMPtr<nsIThread> thread;
   700     DebugOnly<nsresult> rv = NS_GetMainThread(getter_AddRefs(thread));
   701     MOZ_ASSERT(NS_SUCCEEDED(rv));
   702     (void)NS_ProxyRelease(thread, mStorageFile);
   704     // Handle ambiguous nsISupports inheritance.
   705     Connection *rawConnection = nullptr;
   706     mConnection.swap(rawConnection);
   707     (void)NS_ProxyRelease(thread, NS_ISUPPORTS_CAST(mozIStorageConnection *,
   708                                                     rawConnection));
   710     // Generally, the callback will be released by CallbackComplete.
   711     // However, if for some reason Run() is not executed, we still
   712     // need to ensure that it is released here.
   713     mozIStorageCompletionCallback *rawCallback = nullptr;
   714     mCallback.swap(rawCallback);
   715     (void)NS_ProxyRelease(thread, rawCallback);
   716   }
   718   nsRefPtr<Connection> mConnection;
   719   nsCOMPtr<nsIFile> mStorageFile;
   720   int32_t mGrowthIncrement;
   721   nsRefPtr<mozIStorageCompletionCallback> mCallback;
   722 };
   724 } // anonymous namespace
   726 NS_IMETHODIMP
   727 Service::OpenAsyncDatabase(nsIVariant *aDatabaseStore,
   728                            nsIPropertyBag2 *aOptions,
   729                            mozIStorageCompletionCallback *aCallback)
   730 {
   731   if (!NS_IsMainThread()) {
   732     return NS_ERROR_NOT_SAME_THREAD;
   733   }
   734   NS_ENSURE_ARG(aDatabaseStore);
   735   NS_ENSURE_ARG(aCallback);
   737   nsCOMPtr<nsIFile> storageFile;
   738   int flags = SQLITE_OPEN_READWRITE;
   740   nsCOMPtr<nsISupports> dbStore;
   741   nsresult rv = aDatabaseStore->GetAsISupports(getter_AddRefs(dbStore));
   742   if (NS_SUCCEEDED(rv)) {
   743     // Generally, aDatabaseStore holds the database nsIFile.
   744     storageFile = do_QueryInterface(dbStore, &rv);
   745     if (NS_FAILED(rv)) {
   746       return NS_ERROR_INVALID_ARG;
   747     }
   749     rv = storageFile->Clone(getter_AddRefs(storageFile));
   750     MOZ_ASSERT(NS_SUCCEEDED(rv));
   752     // Ensure that SQLITE_OPEN_CREATE is passed in for compatibility reasons.
   753     flags |= SQLITE_OPEN_CREATE;
   755     // Extract and apply the shared-cache option.
   756     bool shared = false;
   757     if (aOptions) {
   758       rv = aOptions->GetPropertyAsBool(NS_LITERAL_STRING("shared"), &shared);
   759       if (NS_FAILED(rv) && rv != NS_ERROR_NOT_AVAILABLE) {
   760         return NS_ERROR_INVALID_ARG;
   761       }
   762     }
   763     flags |= shared ? SQLITE_OPEN_SHAREDCACHE : SQLITE_OPEN_PRIVATECACHE;
   764   } else {
   765     // Sometimes, however, it's a special database name.
   766     nsAutoCString keyString;
   767     rv = aDatabaseStore->GetAsACString(keyString);
   768     if (NS_FAILED(rv) || !keyString.EqualsLiteral("memory")) {
   769       return NS_ERROR_INVALID_ARG;
   770     }
   772     // Just fall through with nullptr storageFile, this will cause the storage
   773     // connection to use a memory DB.
   774   }
   776   int32_t growthIncrement = -1;
   777   if (aOptions && storageFile) {
   778     rv = aOptions->GetPropertyAsInt32(NS_LITERAL_STRING("growthIncrement"),
   779                                       &growthIncrement);
   780     if (NS_FAILED(rv) && rv != NS_ERROR_NOT_AVAILABLE) {
   781       return NS_ERROR_INVALID_ARG;
   782     }
   783   }
   785   // Create connection on this thread, but initialize it on its helper thread.
   786   nsRefPtr<Connection> msc = new Connection(this, flags, true);
   787   nsCOMPtr<nsIEventTarget> target = msc->getAsyncExecutionTarget();
   788   MOZ_ASSERT(target, "Cannot initialize a connection that has been closed already");
   790   nsRefPtr<AsyncInitDatabase> asyncInit =
   791     new AsyncInitDatabase(msc,
   792                           storageFile,
   793                           growthIncrement,
   794                           aCallback);
   795   return target->Dispatch(asyncInit, nsIEventTarget::DISPATCH_NORMAL);
   796 }
   798 NS_IMETHODIMP
   799 Service::OpenDatabase(nsIFile *aDatabaseFile,
   800                       mozIStorageConnection **_connection)
   801 {
   802   NS_ENSURE_ARG(aDatabaseFile);
   804   // Always ensure that SQLITE_OPEN_CREATE is passed in for compatibility
   805   // reasons.
   806   int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_SHAREDCACHE |
   807               SQLITE_OPEN_CREATE;
   808   nsRefPtr<Connection> msc = new Connection(this, flags, false);
   810   nsresult rv = msc->initialize(aDatabaseFile);
   811   NS_ENSURE_SUCCESS(rv, rv);
   813   msc.forget(_connection);
   814   return NS_OK;
   815 }
   817 NS_IMETHODIMP
   818 Service::OpenUnsharedDatabase(nsIFile *aDatabaseFile,
   819                               mozIStorageConnection **_connection)
   820 {
   821   NS_ENSURE_ARG(aDatabaseFile);
   823   // Always ensure that SQLITE_OPEN_CREATE is passed in for compatibility
   824   // reasons.
   825   int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_PRIVATECACHE |
   826               SQLITE_OPEN_CREATE;
   827   nsRefPtr<Connection> msc = new Connection(this, flags, false);
   829   nsresult rv = msc->initialize(aDatabaseFile);
   830   NS_ENSURE_SUCCESS(rv, rv);
   832   msc.forget(_connection);
   833   return NS_OK;
   834 }
   836 NS_IMETHODIMP
   837 Service::OpenDatabaseWithFileURL(nsIFileURL *aFileURL,
   838                                  mozIStorageConnection **_connection)
   839 {
   840   NS_ENSURE_ARG(aFileURL);
   842   // Always ensure that SQLITE_OPEN_CREATE is passed in for compatibility
   843   // reasons.
   844   int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_SHAREDCACHE |
   845               SQLITE_OPEN_CREATE | SQLITE_OPEN_URI;
   846   nsRefPtr<Connection> msc = new Connection(this, flags, false);
   848   nsresult rv = msc->initialize(aFileURL);
   849   NS_ENSURE_SUCCESS(rv, rv);
   851   msc.forget(_connection);
   852   return NS_OK;
   853 }
   855 NS_IMETHODIMP
   856 Service::BackupDatabaseFile(nsIFile *aDBFile,
   857                             const nsAString &aBackupFileName,
   858                             nsIFile *aBackupParentDirectory,
   859                             nsIFile **backup)
   860 {
   861   nsresult rv;
   862   nsCOMPtr<nsIFile> parentDir = aBackupParentDirectory;
   863   if (!parentDir) {
   864     // This argument is optional, and defaults to the same parent directory
   865     // as the current file.
   866     rv = aDBFile->GetParent(getter_AddRefs(parentDir));
   867     NS_ENSURE_SUCCESS(rv, rv);
   868   }
   870   nsCOMPtr<nsIFile> backupDB;
   871   rv = parentDir->Clone(getter_AddRefs(backupDB));
   872   NS_ENSURE_SUCCESS(rv, rv);
   874   rv = backupDB->Append(aBackupFileName);
   875   NS_ENSURE_SUCCESS(rv, rv);
   877   rv = backupDB->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
   878   NS_ENSURE_SUCCESS(rv, rv);
   880   nsAutoString fileName;
   881   rv = backupDB->GetLeafName(fileName);
   882   NS_ENSURE_SUCCESS(rv, rv);
   884   rv = backupDB->Remove(false);
   885   NS_ENSURE_SUCCESS(rv, rv);
   887   backupDB.forget(backup);
   889   return aDBFile->CopyTo(parentDir, fileName);
   890 }
   892 ////////////////////////////////////////////////////////////////////////////////
   893 //// nsIObserver
   895 NS_IMETHODIMP
   896 Service::Observe(nsISupports *, const char *aTopic, const char16_t *)
   897 {
   898   if (strcmp(aTopic, "memory-pressure") == 0) {
   899     minimizeMemory();
   900   } else if (strcmp(aTopic, "xpcom-shutdown") == 0) {
   901     shutdown();
   902   } else if (strcmp(aTopic, "xpcom-shutdown-threads") == 0) {
   903     nsCOMPtr<nsIObserverService> os =
   904       mozilla::services::GetObserverService();
   906     for (size_t i = 0; i < ArrayLength(sObserverTopics); ++i) {
   907       (void)os->RemoveObserver(this, sObserverTopics[i]);
   908     }
   910     bool anyOpen = false;
   911     do {
   912       nsTArray<nsRefPtr<Connection> > connections;
   913       getConnections(connections);
   914       anyOpen = false;
   915       for (uint32_t i = 0; i < connections.Length(); i++) {
   916         nsRefPtr<Connection> &conn = connections[i];
   917         if (conn->isClosing()) {
   918           anyOpen = true;
   919           break;
   920         }
   921       }
   922       if (anyOpen) {
   923         nsCOMPtr<nsIThread> thread = do_GetCurrentThread();
   924         NS_ProcessNextEvent(thread);
   925       }
   926     } while (anyOpen);
   928     if (gShutdownChecks == SCM_CRASH) {
   929       nsTArray<nsRefPtr<Connection> > connections;
   930       getConnections(connections);
   931       for (uint32_t i = 0, n = connections.Length(); i < n; i++) {
   932         if (!connections[i]->isClosed()) {
   933           MOZ_CRASH();
   934         }
   935       }
   936     }
   937   }
   939   return NS_OK;
   940 }
   942 } // namespace storage
   943 } // namespace mozilla

mercurial