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