/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 |