Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/storage/mozStorageService.cpp
Line
Count
Source (jump to first uncovered line)
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 "nsExceptionHandler.h"
16
#include "nsThreadUtils.h"
17
#include "mozStoragePrivateHelpers.h"
18
#include "nsIXPConnect.h"
19
#include "nsIObserverService.h"
20
#include "nsIPropertyBag2.h"
21
#include "mozilla/Services.h"
22
#include "mozilla/Preferences.h"
23
#include "mozilla/LateWriteChecks.h"
24
#include "mozIStorageCompletionCallback.h"
25
#include "mozIStoragePendingStatement.h"
26
27
#include "sqlite3.h"
28
#include "mozilla/AutoSQLiteLifetime.h"
29
30
#ifdef XP_WIN
31
// "windows.h" was included and it can #define lots of things we care about...
32
#undef CompareString
33
#endif
34
35
#include "nsIPromptService.h"
36
37
////////////////////////////////////////////////////////////////////////////////
38
//// Defines
39
40
0
#define PREF_TS_SYNCHRONOUS "toolkit.storage.synchronous"
41
0
#define PREF_TS_SYNCHRONOUS_DEFAULT 1
42
43
0
#define PREF_TS_PAGESIZE "toolkit.storage.pageSize"
44
45
// This value must be kept in sync with the value of SQLITE_DEFAULT_PAGE_SIZE in
46
// db/sqlite3/src/Makefile.in.
47
0
#define PREF_TS_PAGESIZE_DEFAULT 32768
48
49
namespace mozilla {
50
namespace storage {
51
52
////////////////////////////////////////////////////////////////////////////////
53
//// Memory Reporting
54
55
#ifdef MOZ_DMD
56
static mozilla::Atomic<size_t> gSqliteMemoryUsed;
57
#endif
58
59
static int64_t
60
StorageSQLiteDistinguishedAmount()
61
0
{
62
0
  return ::sqlite3_memory_used();
63
0
}
64
65
/**
66
 * Passes a single SQLite memory statistic to a memory reporter callback.
67
 *
68
 * @param aHandleReport
69
 *        The callback.
70
 * @param aData
71
 *        The data for the callback.
72
 * @param aConn
73
 *        The SQLite connection.
74
 * @param aPathHead
75
 *        Head of the path for the memory report.
76
 * @param aKind
77
 *        The memory report statistic kind, one of "stmt", "cache" or
78
 *        "schema".
79
 * @param aDesc
80
 *        The memory report description.
81
 * @param aOption
82
 *        The SQLite constant for getting the measurement.
83
 * @param aTotal
84
 *        The accumulator for the measurement.
85
 */
86
static void
87
ReportConn(nsIHandleReportCallback *aHandleReport,
88
           nsISupports *aData,
89
           Connection *aConn,
90
           const nsACString &aPathHead,
91
           const nsACString &aKind,
92
           const nsACString &aDesc,
93
           int32_t aOption,
94
           size_t *aTotal)
95
0
{
96
0
  nsCString path(aPathHead);
97
0
  path.Append(aKind);
98
0
  path.AppendLiteral("-used");
99
0
100
0
  int32_t val = aConn->getSqliteRuntimeStatus(aOption);
101
0
  aHandleReport->Callback(EmptyCString(), path,
102
0
                          nsIMemoryReporter::KIND_HEAP,
103
0
                          nsIMemoryReporter::UNITS_BYTES,
104
0
                          int64_t(val), aDesc, aData);
105
0
  *aTotal += val;
106
0
}
107
108
// Warning: To get a Connection's measurements requires holding its lock.
109
// There may be a delay getting the lock if another thread is accessing the
110
// Connection.  This isn't very nice if CollectReports is called from the main
111
// thread!  But at the time of writing this function is only called when
112
// about:memory is loaded (not, for example, when telemetry pings occur) and
113
// any delays in that case aren't so bad.
114
NS_IMETHODIMP
115
Service::CollectReports(nsIHandleReportCallback *aHandleReport,
116
                        nsISupports *aData, bool aAnonymize)
117
0
{
118
0
  size_t totalConnSize = 0;
119
0
  {
120
0
    nsTArray<RefPtr<Connection> > connections;
121
0
    getConnections(connections);
122
0
123
0
    for (uint32_t i = 0; i < connections.Length(); i++) {
124
0
      RefPtr<Connection> &conn = connections[i];
125
0
126
0
      // Someone may have closed the Connection, in which case we skip it.
127
0
      // Note that we have consumers of the synchronous API that are off the
128
0
      // main-thread, like the DOM Cache and IndexedDB, and as such we must be
129
0
      // sure that we have a connection.
130
0
      MutexAutoLock lockedAsyncScope(conn->sharedAsyncExecutionMutex);
131
0
      if (!conn->connectionReady()) {
132
0
          continue;
133
0
      }
134
0
135
0
      nsCString pathHead("explicit/storage/sqlite/");
136
0
      // This filename isn't privacy-sensitive, and so is never anonymized.
137
0
      pathHead.Append(conn->getFilename());
138
0
      pathHead.Append('/');
139
0
140
0
      SQLiteMutexAutoLock lockedScope(conn->sharedDBMutex);
141
0
142
0
      NS_NAMED_LITERAL_CSTRING(stmtDesc,
143
0
        "Memory (approximate) used by all prepared statements used by "
144
0
        "connections to this database.");
145
0
      ReportConn(aHandleReport, aData, conn, pathHead,
146
0
                 NS_LITERAL_CSTRING("stmt"), stmtDesc,
147
0
                 SQLITE_DBSTATUS_STMT_USED, &totalConnSize);
148
0
149
0
      NS_NAMED_LITERAL_CSTRING(cacheDesc,
150
0
        "Memory (approximate) used by all pager caches used by connections "
151
0
        "to this database.");
152
0
      ReportConn(aHandleReport, aData, conn, pathHead,
153
0
                 NS_LITERAL_CSTRING("cache"), cacheDesc,
154
0
                 SQLITE_DBSTATUS_CACHE_USED_SHARED, &totalConnSize);
155
0
156
0
      NS_NAMED_LITERAL_CSTRING(schemaDesc,
157
0
        "Memory (approximate) used to store the schema for all databases "
158
0
        "associated with connections to this database.");
159
0
      ReportConn(aHandleReport, aData, conn, pathHead,
160
0
                 NS_LITERAL_CSTRING("schema"), schemaDesc,
161
0
                 SQLITE_DBSTATUS_SCHEMA_USED, &totalConnSize);
162
0
    }
163
0
164
#ifdef MOZ_DMD
165
    if (::sqlite3_memory_used() != int64_t(gSqliteMemoryUsed)) {
166
      NS_WARNING("memory consumption reported by SQLite doesn't match "
167
                 "our measurements");
168
    }
169
#endif
170
  }
171
0
172
0
  int64_t other = ::sqlite3_memory_used() - totalConnSize;
173
0
174
0
  MOZ_COLLECT_REPORT(
175
0
    "explicit/storage/sqlite/other", KIND_HEAP, UNITS_BYTES, other,
176
0
    "All unclassified sqlite memory.");
177
0
178
0
  return NS_OK;
179
0
}
180
181
////////////////////////////////////////////////////////////////////////////////
182
//// Service
183
184
NS_IMPL_ISUPPORTS(
185
  Service,
186
  mozIStorageService,
187
  nsIObserver,
188
  nsIMemoryReporter
189
)
190
191
Service *Service::gService = nullptr;
192
193
already_AddRefed<Service>
194
Service::getSingleton()
195
0
{
196
0
  if (gService) {
197
0
    return do_AddRef(gService);
198
0
  }
199
0
200
0
  // Ensure that we are using the same version of SQLite that we compiled with
201
0
  // or newer.  Our configure check ensures we are using a new enough version
202
0
  // at compile time.
203
0
  if (SQLITE_VERSION_NUMBER > ::sqlite3_libversion_number()) {
204
0
    nsCOMPtr<nsIPromptService> ps(do_GetService(NS_PROMPTSERVICE_CONTRACTID));
205
0
    if (ps) {
206
0
      nsAutoString title, message;
207
0
      title.AppendLiteral("SQLite Version Error");
208
0
      message.AppendLiteral("The application has been updated, but the SQLite "
209
0
                            "library wasn't updated properly and the application "
210
0
                            "cannot run. Please try to launch the application again. "
211
0
                            "If that should still fail, please try reinstalling "
212
0
                            "it, or visit https://support.mozilla.org/.");
213
0
      (void)ps->Alert(nullptr, title.get(), message.get());
214
0
    }
215
0
    MOZ_CRASH("SQLite Version Error");
216
0
  }
217
0
218
0
  // The first reference to the storage service must be obtained on the
219
0
  // main thread.
220
0
  NS_ENSURE_TRUE(NS_IsMainThread(), nullptr);
221
0
  RefPtr<Service> service = new Service();
222
0
  if (NS_SUCCEEDED(service->initialize())) {
223
0
    // Note: This is cleared in the Service destructor.
224
0
    gService = service.get();
225
0
    return service.forget();
226
0
  }
227
0
228
0
  return nullptr;
229
0
}
230
231
int32_t Service::sSynchronousPref;
232
233
// static
234
int32_t
235
Service::getSynchronousPref()
236
0
{
237
0
  return sSynchronousPref;
238
0
}
239
240
int32_t Service::sDefaultPageSize = PREF_TS_PAGESIZE_DEFAULT;
241
242
Service::Service()
243
: mMutex("Service::mMutex")
244
, mSqliteVFS(nullptr)
245
, mRegistrationMutex("Service::mRegistrationMutex")
246
, mConnections()
247
0
{
248
0
}
249
250
Service::~Service()
251
0
{
252
0
  mozilla::UnregisterWeakMemoryReporter(this);
253
0
  mozilla::UnregisterStorageSQLiteDistinguishedAmount();
254
0
255
0
  int rc = sqlite3_vfs_unregister(mSqliteVFS);
256
0
  if (rc != SQLITE_OK)
257
0
    NS_WARNING("Failed to unregister sqlite vfs wrapper.");
258
0
259
0
  gService = nullptr;
260
0
  delete mSqliteVFS;
261
0
  mSqliteVFS = nullptr;
262
0
}
263
264
void
265
Service::registerConnection(Connection *aConnection)
266
0
{
267
0
  mRegistrationMutex.AssertNotCurrentThreadOwns();
268
0
  MutexAutoLock mutex(mRegistrationMutex);
269
0
  (void)mConnections.AppendElement(aConnection);
270
0
}
271
272
void
273
Service::unregisterConnection(Connection *aConnection)
274
0
{
275
0
  // If this is the last Connection it might be the only thing keeping Service
276
0
  // alive.  So ensure that Service is destroyed only after the Connection is
277
0
  // cleanly unregistered and destroyed.
278
0
  RefPtr<Service> kungFuDeathGrip(this);
279
0
  RefPtr<Connection> forgettingRef;
280
0
  {
281
0
    mRegistrationMutex.AssertNotCurrentThreadOwns();
282
0
    MutexAutoLock mutex(mRegistrationMutex);
283
0
284
0
    for (uint32_t i = 0 ; i < mConnections.Length(); ++i) {
285
0
      if (mConnections[i] == aConnection) {
286
0
        // Because dropping the final reference can potentially result in
287
0
        // spinning a nested event loop if the connection was not properly
288
0
        // shutdown, we want to do that outside this loop so that we can finish
289
0
        // mutating the array and drop our mutex.
290
0
        forgettingRef = mConnections[i].forget();
291
0
        mConnections.RemoveElementAt(i);
292
0
        break;
293
0
      }
294
0
    }
295
0
  }
296
0
297
0
  MOZ_ASSERT(forgettingRef,
298
0
             "Attempt to unregister unknown storage connection!");
299
0
300
0
  // Do not proxy the release anywhere, just let this reference drop here.  (We
301
0
  // previously did proxy the release, but that was because we invoked Close()
302
0
  // in the destructor and Close() likes to complain if it's not invoked on the
303
0
  // opener thread, so it was essential that the last reference be dropped on
304
0
  // the opener thread.  We now enqueue Close() inside our caller, Release(), so
305
0
  // it doesn't actually matter what thread our reference drops on.)
306
0
}
307
308
void
309
Service::getConnections(/* inout */ nsTArray<RefPtr<Connection> >& aConnections)
310
0
{
311
0
  mRegistrationMutex.AssertNotCurrentThreadOwns();
312
0
  MutexAutoLock mutex(mRegistrationMutex);
313
0
  aConnections.Clear();
314
0
  aConnections.AppendElements(mConnections);
315
0
}
316
317
void
318
Service::minimizeMemory()
319
0
{
320
0
  nsTArray<RefPtr<Connection> > connections;
321
0
  getConnections(connections);
322
0
323
0
  for (uint32_t i = 0; i < connections.Length(); i++) {
324
0
    RefPtr<Connection> conn = connections[i];
325
0
    // For non-main-thread owning/opening threads, we may be racing against them
326
0
    // closing their connection or their thread.  That's okay, see below.
327
0
    if (!conn->connectionReady())
328
0
      continue;
329
0
330
0
    NS_NAMED_LITERAL_CSTRING(shrinkPragma, "PRAGMA shrink_memory");
331
0
    nsCOMPtr<mozIStorageConnection> syncConn = do_QueryInterface(
332
0
      NS_ISUPPORTS_CAST(mozIStorageAsyncConnection*, conn));
333
0
    bool onOpenedThread = false;
334
0
335
0
    if (!syncConn) {
336
0
      // This is a mozIStorageAsyncConnection, it can only be used on the main
337
0
      // thread, so we can do a straight API call.
338
0
      nsCOMPtr<mozIStoragePendingStatement> ps;
339
0
      DebugOnly<nsresult> rv =
340
0
        conn->ExecuteSimpleSQLAsync(shrinkPragma, nullptr, getter_AddRefs(ps));
341
0
      MOZ_ASSERT(NS_SUCCEEDED(rv), "Should have purged sqlite caches");
342
0
    } else if (NS_SUCCEEDED(conn->threadOpenedOn->IsOnCurrentThread(&onOpenedThread)) &&
343
0
               onOpenedThread) {
344
0
      if (conn->isAsyncExecutionThreadAvailable()) {
345
0
        nsCOMPtr<mozIStoragePendingStatement> ps;
346
0
        DebugOnly<nsresult> rv =
347
0
          conn->ExecuteSimpleSQLAsync(shrinkPragma, nullptr, getter_AddRefs(ps));
348
0
        MOZ_ASSERT(NS_SUCCEEDED(rv), "Should have purged sqlite caches");
349
0
      } else {
350
0
        conn->ExecuteSimpleSQL(shrinkPragma);
351
0
      }
352
0
    } else {
353
0
      // We are on the wrong thread, the query should be executed on the
354
0
      // opener thread, so we must dispatch to it.
355
0
      // It's possible the connection is already closed or will be closed by the
356
0
      // time our runnable runs.  ExecuteSimpleSQL will safely return with a
357
0
      // failure in that case.  If the thread is shutting down or shut down, the
358
0
      // dispatch will fail and that's okay.
359
0
      nsCOMPtr<nsIRunnable> event =
360
0
        NewRunnableMethod<const nsCString>(
361
0
          "Connection::ExecuteSimpleSQL",
362
0
          conn, &Connection::ExecuteSimpleSQL, shrinkPragma);
363
0
      Unused << conn->threadOpenedOn->Dispatch(event, NS_DISPATCH_NORMAL);
364
0
    }
365
0
  }
366
0
}
367
368
sqlite3_vfs *ConstructTelemetryVFS();
369
const char *GetVFSName();
370
371
static const char* sObserverTopics[] = {
372
  "memory-pressure",
373
  "xpcom-shutdown-threads"
374
};
375
376
nsresult
377
Service::initialize()
378
0
{
379
0
  MOZ_ASSERT(NS_IsMainThread(), "Must be initialized on the main thread");
380
0
381
0
  int rc = AutoSQLiteLifetime::getInitResult();
382
0
  if (rc != SQLITE_OK)
383
0
    return convertResultCode(rc);
384
0
385
0
  mSqliteVFS = ConstructTelemetryVFS();
386
0
  if (mSqliteVFS) {
387
0
    rc = sqlite3_vfs_register(mSqliteVFS, 0);
388
0
    if (rc != SQLITE_OK)
389
0
      return convertResultCode(rc);
390
0
  } else {
391
0
    NS_WARNING("Failed to register telemetry VFS");
392
0
  }
393
0
394
0
  nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
395
0
  NS_ENSURE_TRUE(os, NS_ERROR_FAILURE);
396
0
397
0
  for (size_t i = 0; i < ArrayLength(sObserverTopics); ++i) {
398
0
    nsresult rv = os->AddObserver(this, sObserverTopics[i], false);
399
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
400
0
      return rv;
401
0
    }
402
0
  }
403
0
404
0
  // We need to obtain the toolkit.storage.synchronous preferences on the main
405
0
  // thread because the preference service can only be accessed there.  This
406
0
  // is cached in the service for all future Open[Unshared]Database calls.
407
0
  sSynchronousPref =
408
0
    Preferences::GetInt(PREF_TS_SYNCHRONOUS, PREF_TS_SYNCHRONOUS_DEFAULT);
409
0
410
0
  // We need to obtain the toolkit.storage.pageSize preferences on the main
411
0
  // thread because the preference service can only be accessed there.  This
412
0
  // is cached in the service for all future Open[Unshared]Database calls.
413
0
  sDefaultPageSize =
414
0
      Preferences::GetInt(PREF_TS_PAGESIZE, PREF_TS_PAGESIZE_DEFAULT);
415
0
416
0
  mozilla::RegisterWeakMemoryReporter(this);
417
0
  mozilla::RegisterStorageSQLiteDistinguishedAmount(StorageSQLiteDistinguishedAmount);
418
0
419
0
  return NS_OK;
420
0
}
421
422
int
423
Service::localeCompareStrings(const nsAString &aStr1,
424
                              const nsAString &aStr2,
425
                              int32_t aComparisonStrength)
426
0
{
427
0
  // The implementation of nsICollation.CompareString() is platform-dependent.
428
0
  // On Linux it's not thread-safe.  It may not be on Windows and OS X either,
429
0
  // but it's more difficult to tell.  We therefore synchronize this method.
430
0
  MutexAutoLock mutex(mMutex);
431
0
432
0
  nsICollation *coll = getLocaleCollation();
433
0
  if (!coll) {
434
0
    NS_ERROR("Storage service has no collation");
435
0
    return 0;
436
0
  }
437
0
438
0
  int32_t res;
439
0
  nsresult rv = coll->CompareString(aComparisonStrength, aStr1, aStr2, &res);
440
0
  if (NS_FAILED(rv)) {
441
0
    NS_ERROR("Collation compare string failed");
442
0
    return 0;
443
0
  }
444
0
445
0
  return res;
446
0
}
447
448
nsICollation *
449
Service::getLocaleCollation()
450
0
{
451
0
  mMutex.AssertCurrentThreadOwns();
452
0
453
0
  if (mLocaleCollation)
454
0
    return mLocaleCollation;
455
0
456
0
  nsCOMPtr<nsICollationFactory> collFact =
457
0
    do_CreateInstance(NS_COLLATIONFACTORY_CONTRACTID);
458
0
  if (!collFact) {
459
0
    NS_WARNING("Could not create collation factory");
460
0
    return nullptr;
461
0
  }
462
0
463
0
  nsresult rv = collFact->CreateCollation(getter_AddRefs(mLocaleCollation));
464
0
  if (NS_FAILED(rv)) {
465
0
    NS_WARNING("Could not create collation");
466
0
    return nullptr;
467
0
  }
468
0
469
0
  return mLocaleCollation;
470
0
}
471
472
////////////////////////////////////////////////////////////////////////////////
473
//// mozIStorageService
474
475
476
NS_IMETHODIMP
477
Service::OpenSpecialDatabase(const char *aStorageKey,
478
                             mozIStorageConnection **_connection)
479
0
{
480
0
  nsresult rv;
481
0
482
0
  nsCOMPtr<nsIFile> storageFile;
483
0
  if (::strcmp(aStorageKey, "memory") == 0) {
484
0
    // just fall through with nullptr storageFile, this will cause the storage
485
0
    // connection to use a memory DB.
486
0
  }
487
0
  else {
488
0
    return NS_ERROR_INVALID_ARG;
489
0
  }
490
0
491
0
  RefPtr<Connection> msc = new Connection(this, SQLITE_OPEN_READWRITE, false);
492
0
493
0
  rv = storageFile ? msc->initialize(storageFile) : msc->initialize();
494
0
  NS_ENSURE_SUCCESS(rv, rv);
495
0
496
0
  msc.forget(_connection);
497
0
  return NS_OK;
498
0
499
0
}
500
501
namespace {
502
503
class AsyncInitDatabase final : public Runnable
504
{
505
public:
506
  AsyncInitDatabase(Connection* aConnection,
507
                    nsIFile* aStorageFile,
508
                    int32_t aGrowthIncrement,
509
                    mozIStorageCompletionCallback* aCallback)
510
    : Runnable("storage::AsyncInitDatabase")
511
    , mConnection(aConnection)
512
    , mStorageFile(aStorageFile)
513
    , mGrowthIncrement(aGrowthIncrement)
514
    , mCallback(aCallback)
515
0
  {
516
0
    MOZ_ASSERT(NS_IsMainThread());
517
0
  }
518
519
  NS_IMETHOD Run() override
520
0
  {
521
0
    MOZ_ASSERT(!NS_IsMainThread());
522
0
    nsresult rv = mConnection->initializeOnAsyncThread(mStorageFile);
523
0
    if (NS_FAILED(rv)) {
524
0
      return DispatchResult(rv, nullptr);
525
0
    }
526
0
527
0
    if (mGrowthIncrement >= 0) {
528
0
      // Ignore errors. In the future, we might wish to log them.
529
0
      (void)mConnection->SetGrowthIncrement(mGrowthIncrement, EmptyCString());
530
0
    }
531
0
532
0
    return DispatchResult(NS_OK, NS_ISUPPORTS_CAST(mozIStorageAsyncConnection*,
533
0
                          mConnection));
534
0
  }
535
536
private:
537
0
  nsresult DispatchResult(nsresult aStatus, nsISupports* aValue) {
538
0
    RefPtr<CallbackComplete> event =
539
0
      new CallbackComplete(aStatus,
540
0
                           aValue,
541
0
                           mCallback.forget());
542
0
    return NS_DispatchToMainThread(event);
543
0
  }
544
545
  ~AsyncInitDatabase()
546
0
  {
547
0
    NS_ReleaseOnMainThreadSystemGroup(
548
0
      "AsyncInitDatabase::mStorageFile", mStorageFile.forget());
549
0
    NS_ReleaseOnMainThreadSystemGroup(
550
0
      "AsyncInitDatabase::mConnection", mConnection.forget());
551
0
552
0
    // Generally, the callback will be released by CallbackComplete.
553
0
    // However, if for some reason Run() is not executed, we still
554
0
    // need to ensure that it is released here.
555
0
    NS_ReleaseOnMainThreadSystemGroup(
556
0
      "AsyncInitDatabase::mCallback", mCallback.forget());
557
0
  }
558
559
  RefPtr<Connection> mConnection;
560
  nsCOMPtr<nsIFile> mStorageFile;
561
  int32_t mGrowthIncrement;
562
  RefPtr<mozIStorageCompletionCallback> mCallback;
563
};
564
565
} // namespace
566
567
NS_IMETHODIMP
568
Service::OpenAsyncDatabase(nsIVariant *aDatabaseStore,
569
                           nsIPropertyBag2 *aOptions,
570
                           mozIStorageCompletionCallback *aCallback)
571
0
{
572
0
  if (!NS_IsMainThread()) {
573
0
    return NS_ERROR_NOT_SAME_THREAD;
574
0
  }
575
0
  NS_ENSURE_ARG(aDatabaseStore);
576
0
  NS_ENSURE_ARG(aCallback);
577
0
578
0
  nsresult rv;
579
0
  bool shared = false;
580
0
  bool readOnly = false;
581
0
  bool ignoreLockingMode = false;
582
0
  int32_t growthIncrement = -1;
583
0
584
0
#define FAIL_IF_SET_BUT_INVALID(rv)\
585
0
  if (NS_FAILED(rv) && rv != NS_ERROR_NOT_AVAILABLE) { \
586
0
    return NS_ERROR_INVALID_ARG; \
587
0
  }
588
0
589
0
  // Deal with options first:
590
0
  if (aOptions) {
591
0
    rv = aOptions->GetPropertyAsBool(NS_LITERAL_STRING("readOnly"), &readOnly);
592
0
    FAIL_IF_SET_BUT_INVALID(rv);
593
0
594
0
    rv = aOptions->GetPropertyAsBool(NS_LITERAL_STRING("ignoreLockingMode"),
595
0
                                     &ignoreLockingMode);
596
0
    FAIL_IF_SET_BUT_INVALID(rv);
597
0
    // Specifying ignoreLockingMode will force use of the readOnly flag:
598
0
    if (ignoreLockingMode) {
599
0
      readOnly = true;
600
0
    }
601
0
602
0
    rv = aOptions->GetPropertyAsBool(NS_LITERAL_STRING("shared"), &shared);
603
0
    FAIL_IF_SET_BUT_INVALID(rv);
604
0
605
0
    // NB: we re-set to -1 if we don't have a storage file later on.
606
0
    rv = aOptions->GetPropertyAsInt32(NS_LITERAL_STRING("growthIncrement"),
607
0
                                      &growthIncrement);
608
0
    FAIL_IF_SET_BUT_INVALID(rv);
609
0
  }
610
0
  int flags = readOnly ? SQLITE_OPEN_READONLY : SQLITE_OPEN_READWRITE;
611
0
612
0
  nsCOMPtr<nsIFile> storageFile;
613
0
  nsCOMPtr<nsISupports> dbStore;
614
0
  rv = aDatabaseStore->GetAsISupports(getter_AddRefs(dbStore));
615
0
  if (NS_SUCCEEDED(rv)) {
616
0
    // Generally, aDatabaseStore holds the database nsIFile.
617
0
    storageFile = do_QueryInterface(dbStore, &rv);
618
0
    if (NS_FAILED(rv)) {
619
0
      return NS_ERROR_INVALID_ARG;
620
0
    }
621
0
622
0
    rv = storageFile->Clone(getter_AddRefs(storageFile));
623
0
    MOZ_ASSERT(NS_SUCCEEDED(rv));
624
0
625
0
    if (!readOnly) {
626
0
      // Ensure that SQLITE_OPEN_CREATE is passed in for compatibility reasons.
627
0
      flags |= SQLITE_OPEN_CREATE;
628
0
    }
629
0
630
0
    // Apply the shared-cache option.
631
0
    flags |= shared ? SQLITE_OPEN_SHAREDCACHE : SQLITE_OPEN_PRIVATECACHE;
632
0
  } else {
633
0
    // Sometimes, however, it's a special database name.
634
0
    nsAutoCString keyString;
635
0
    rv = aDatabaseStore->GetAsACString(keyString);
636
0
    if (NS_FAILED(rv) || !keyString.EqualsLiteral("memory")) {
637
0
      return NS_ERROR_INVALID_ARG;
638
0
    }
639
0
640
0
    // Just fall through with nullptr storageFile, this will cause the storage
641
0
    // connection to use a memory DB.
642
0
  }
643
0
644
0
  if (!storageFile && growthIncrement >= 0) {
645
0
    return NS_ERROR_INVALID_ARG;
646
0
  }
647
0
648
0
  // Create connection on this thread, but initialize it on its helper thread.
649
0
  RefPtr<Connection> msc = new Connection(this, flags, true,
650
0
                                          ignoreLockingMode);
651
0
  nsCOMPtr<nsIEventTarget> target = msc->getAsyncExecutionTarget();
652
0
  MOZ_ASSERT(target, "Cannot initialize a connection that has been closed already");
653
0
654
0
  RefPtr<AsyncInitDatabase> asyncInit =
655
0
    new AsyncInitDatabase(msc,
656
0
                          storageFile,
657
0
                          growthIncrement,
658
0
                          aCallback);
659
0
  return target->Dispatch(asyncInit, nsIEventTarget::DISPATCH_NORMAL);
660
0
}
661
662
NS_IMETHODIMP
663
Service::OpenDatabase(nsIFile *aDatabaseFile,
664
                      mozIStorageConnection **_connection)
665
0
{
666
0
  NS_ENSURE_ARG(aDatabaseFile);
667
0
668
0
  // Always ensure that SQLITE_OPEN_CREATE is passed in for compatibility
669
0
  // reasons.
670
0
  int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_SHAREDCACHE |
671
0
              SQLITE_OPEN_CREATE;
672
0
  RefPtr<Connection> msc = new Connection(this, flags, false);
673
0
674
0
  nsresult rv = msc->initialize(aDatabaseFile);
675
0
  NS_ENSURE_SUCCESS(rv, rv);
676
0
677
0
  msc.forget(_connection);
678
0
  return NS_OK;
679
0
}
680
681
NS_IMETHODIMP
682
Service::OpenUnsharedDatabase(nsIFile *aDatabaseFile,
683
                              mozIStorageConnection **_connection)
684
0
{
685
0
  NS_ENSURE_ARG(aDatabaseFile);
686
0
687
0
  // Always ensure that SQLITE_OPEN_CREATE is passed in for compatibility
688
0
  // reasons.
689
0
  int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_PRIVATECACHE |
690
0
              SQLITE_OPEN_CREATE;
691
0
  RefPtr<Connection> msc = new Connection(this, flags, false);
692
0
693
0
  nsresult rv = msc->initialize(aDatabaseFile);
694
0
  NS_ENSURE_SUCCESS(rv, rv);
695
0
696
0
  msc.forget(_connection);
697
0
  return NS_OK;
698
0
}
699
700
NS_IMETHODIMP
701
Service::OpenDatabaseWithFileURL(nsIFileURL *aFileURL,
702
                                 mozIStorageConnection **_connection)
703
0
{
704
0
  NS_ENSURE_ARG(aFileURL);
705
0
706
0
  // Always ensure that SQLITE_OPEN_CREATE is passed in for compatibility
707
0
  // reasons.
708
0
  int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_SHAREDCACHE |
709
0
              SQLITE_OPEN_CREATE | SQLITE_OPEN_URI;
710
0
  RefPtr<Connection> msc = new Connection(this, flags, false);
711
0
712
0
  nsresult rv = msc->initialize(aFileURL);
713
0
  NS_ENSURE_SUCCESS(rv, rv);
714
0
715
0
  msc.forget(_connection);
716
0
  return NS_OK;
717
0
}
718
719
NS_IMETHODIMP
720
Service::BackupDatabaseFile(nsIFile *aDBFile,
721
                            const nsAString &aBackupFileName,
722
                            nsIFile *aBackupParentDirectory,
723
                            nsIFile **backup)
724
0
{
725
0
  nsresult rv;
726
0
  nsCOMPtr<nsIFile> parentDir = aBackupParentDirectory;
727
0
  if (!parentDir) {
728
0
    // This argument is optional, and defaults to the same parent directory
729
0
    // as the current file.
730
0
    rv = aDBFile->GetParent(getter_AddRefs(parentDir));
731
0
    NS_ENSURE_SUCCESS(rv, rv);
732
0
  }
733
0
734
0
  nsCOMPtr<nsIFile> backupDB;
735
0
  rv = parentDir->Clone(getter_AddRefs(backupDB));
736
0
  NS_ENSURE_SUCCESS(rv, rv);
737
0
738
0
  rv = backupDB->Append(aBackupFileName);
739
0
  NS_ENSURE_SUCCESS(rv, rv);
740
0
741
0
  rv = backupDB->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
742
0
  NS_ENSURE_SUCCESS(rv, rv);
743
0
744
0
  nsAutoString fileName;
745
0
  rv = backupDB->GetLeafName(fileName);
746
0
  NS_ENSURE_SUCCESS(rv, rv);
747
0
748
0
  rv = backupDB->Remove(false);
749
0
  NS_ENSURE_SUCCESS(rv, rv);
750
0
751
0
  backupDB.forget(backup);
752
0
753
0
  return aDBFile->CopyTo(parentDir, fileName);
754
0
}
755
756
////////////////////////////////////////////////////////////////////////////////
757
//// nsIObserver
758
759
NS_IMETHODIMP
760
Service::Observe(nsISupports *, const char *aTopic, const char16_t *)
761
0
{
762
0
  if (strcmp(aTopic, "memory-pressure") == 0) {
763
0
    minimizeMemory();
764
0
  } else if (strcmp(aTopic, "xpcom-shutdown-threads") == 0) {
765
0
    // The Service is kept alive by our strong observer references and
766
0
    // references held by Connection instances.  Since we're about to remove the
767
0
    // former and then wait for the latter ones to go away, it behooves us to
768
0
    // hold a strong reference to ourselves so our calls to getConnections() do
769
0
    // not happen on a deleted object.
770
0
    RefPtr<Service> kungFuDeathGrip = this;
771
0
772
0
    nsCOMPtr<nsIObserverService> os =
773
0
      mozilla::services::GetObserverService();
774
0
775
0
    for (size_t i = 0; i < ArrayLength(sObserverTopics); ++i) {
776
0
      (void)os->RemoveObserver(this, sObserverTopics[i]);
777
0
    }
778
0
779
0
    SpinEventLoopUntil([&]() -> bool {
780
0
      // We must wait until all the closing connections are closed.
781
0
      nsTArray<RefPtr<Connection>> connections;
782
0
      getConnections(connections);
783
0
      for (auto& conn : connections) {
784
0
        if (conn->isClosing()) {
785
0
          return false;
786
0
        }
787
0
      }
788
0
      return true;
789
0
    });
790
0
791
0
    if (gShutdownChecks == SCM_CRASH) {
792
0
      nsTArray<RefPtr<Connection> > connections;
793
0
      getConnections(connections);
794
0
      for (uint32_t i = 0, n = connections.Length(); i < n; i++) {
795
0
        if (!connections[i]->isClosed()) {
796
0
          // getFilename is only the leaf name for the database file,
797
0
          // so it shouldn't contain privacy-sensitive information.
798
0
          CrashReporter::AnnotateCrashReport(
799
0
            CrashReporter::Annotation::StorageConnectionNotClosed,
800
0
            connections[i]->getFilename());
801
#ifdef DEBUG
802
          printf_stderr("Storage connection not closed: %s",
803
                        connections[i]->getFilename().get());
804
#endif
805
0
          MOZ_CRASH();
806
0
        }
807
0
      }
808
0
    }
809
0
  }
810
0
811
0
  return NS_OK;
812
0
}
813
814
} // namespace storage
815
} // namespace mozilla