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 : * ***** BEGIN LICENSE BLOCK *****
4 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 : *
6 : * The contents of this file are subject to the Mozilla Public License Version
7 : * 1.1 (the "License"); you may not use this file except in compliance with
8 : * the License. You may obtain a copy of the License at
9 : * http://www.mozilla.org/MPL/
10 : *
11 : * Software distributed under the License is distributed on an "AS IS" basis,
12 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 : * for the specific language governing rights and limitations under the
14 : * License.
15 : *
16 : * The Original Code is Oracle Corporation code.
17 : *
18 : * The Initial Developer of the Original Code is
19 : * Oracle Corporation
20 : * Portions created by the Initial Developer are Copyright (C) 2004
21 : * the Initial Developer. All Rights Reserved.
22 : *
23 : * Contributor(s):
24 : * Vladimir Vukicevic <vladimir.vukicevic@oracle.com>
25 : * Brett Wilson <brettw@gmail.com>
26 : * Shawn Wilsher <me@shawnwilsher.com>
27 : * Drew Willcoxon <adw@mozilla.com>
28 : *
29 : * Alternatively, the contents of this file may be used under the terms of
30 : * either the GNU General Public License Version 2 or later (the "GPL"), or
31 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
32 : * in which case the provisions of the GPL or the LGPL are applicable instead
33 : * of those above. If you wish to allow use of your version of this file only
34 : * under the terms of either the GPL or the LGPL, and not to allow others to
35 : * use your version of this file under the terms of the MPL, indicate your
36 : * decision by deleting the provisions above and replace them with the notice
37 : * and other provisions required by the GPL or the LGPL. If you do not delete
38 : * the provisions above, a recipient may use your version of this file under
39 : * the terms of any one of the MPL, the GPL or the LGPL.
40 : *
41 : * ***** END LICENSE BLOCK ***** */
42 :
43 : #include "mozStorageService.h"
44 : #include "mozStorageConnection.h"
45 : #include "prinit.h"
46 : #include "pratom.h"
47 : #include "nsAutoPtr.h"
48 : #include "nsCollationCID.h"
49 : #include "nsEmbedCID.h"
50 : #include "nsThreadUtils.h"
51 : #include "mozStoragePrivateHelpers.h"
52 : #include "nsILocale.h"
53 : #include "nsILocaleService.h"
54 : #include "nsIXPConnect.h"
55 : #include "nsIObserverService.h"
56 : #include "mozilla/Services.h"
57 : #include "mozilla/Preferences.h"
58 :
59 : #include "sqlite3.h"
60 : #include "test_quota.h"
61 : #include "test_quota.c"
62 :
63 : #ifdef SQLITE_OS_WIN
64 : // "windows.h" was included and it can #define lots of things we care about...
65 : #undef CompareString
66 : #endif
67 :
68 : #include "nsIPromptService.h"
69 : #include "nsIMemoryReporter.h"
70 :
71 : #include "mozilla/FunctionTimer.h"
72 : #include "mozilla/Util.h"
73 :
74 : namespace {
75 :
76 : class QuotaCallbackData
77 : {
78 : public:
79 50 : QuotaCallbackData(mozIStorageQuotaCallback *aCallback,
80 : nsISupports *aUserData)
81 50 : : callback(aCallback), userData(aUserData)
82 : {
83 50 : MOZ_COUNT_CTOR(QuotaCallbackData);
84 50 : }
85 :
86 50 : ~QuotaCallbackData()
87 50 : {
88 50 : MOZ_COUNT_DTOR(QuotaCallbackData);
89 50 : }
90 :
91 0 : static void Callback(const char *zFilename,
92 : sqlite3_int64 *piLimit,
93 : sqlite3_int64 iSize,
94 : void *pArg)
95 : {
96 0 : NS_ASSERTION(zFilename && strlen(zFilename), "Null or empty filename!");
97 0 : NS_ASSERTION(piLimit, "Null pointer!");
98 :
99 0 : QuotaCallbackData *data = static_cast<QuotaCallbackData*>(pArg);
100 0 : if (!data) {
101 : // No callback specified, return immediately.
102 0 : return;
103 : }
104 :
105 0 : NS_ASSERTION(data->callback, "Should never have a null callback!");
106 :
107 0 : nsDependentCString filename(zFilename);
108 :
109 : PRInt64 newLimit;
110 0 : if (NS_SUCCEEDED(data->callback->QuotaExceeded(filename, *piLimit,
111 : iSize, data->userData,
112 : &newLimit))) {
113 0 : *piLimit = newLimit;
114 : }
115 : }
116 :
117 50 : static void Destroy(void *aUserData)
118 : {
119 50 : delete static_cast<QuotaCallbackData*>(aUserData);
120 50 : }
121 :
122 : private:
123 : nsCOMPtr<mozIStorageQuotaCallback> callback;
124 : nsCOMPtr<nsISupports> userData;
125 : };
126 :
127 : } // anonymous namespace
128 :
129 : ////////////////////////////////////////////////////////////////////////////////
130 : //// Defines
131 :
132 : #define PREF_TS_SYNCHRONOUS "toolkit.storage.synchronous"
133 : #define PREF_TS_SYNCHRONOUS_DEFAULT 1
134 :
135 : namespace mozilla {
136 : namespace storage {
137 :
138 : ////////////////////////////////////////////////////////////////////////////////
139 : //// Memory Reporting
140 :
141 : static PRInt64
142 3 : GetStorageSQLiteMemoryUsed()
143 : {
144 3 : return ::sqlite3_memory_used();
145 : }
146 :
147 : // We don't need an "explicit" reporter for total SQLite memory usage, because
148 : // the multi-reporter provides reports that add up to the total. But it's
149 : // useful to have the total in the "Other Measurements" list in about:memory,
150 : // and more importantly, we also gather the total via telemetry.
151 821 : NS_MEMORY_REPORTER_IMPLEMENT(StorageSQLite,
152 : "storage-sqlite",
153 : KIND_OTHER,
154 : UNITS_BYTES,
155 : GetStorageSQLiteMemoryUsed,
156 6472 : "Memory used by SQLite.")
157 :
158 : class StorageSQLiteMultiReporter : public nsIMemoryMultiReporter
159 794 : {
160 : private:
161 : Service *mService; // a weakref because Service contains a strongref to this
162 : nsCString mStmtDesc;
163 : nsCString mCacheDesc;
164 : nsCString mSchemaDesc;
165 :
166 : public:
167 : NS_DECL_ISUPPORTS
168 :
169 803 : StorageSQLiteMultiReporter(Service *aService)
170 803 : : mService(aService)
171 : {
172 1606 : NS_NAMED_LITERAL_CSTRING(mStmtDesc,
173 : "Memory (approximate) used by all prepared statements used by "
174 : "connections to this database.");
175 :
176 1606 : NS_NAMED_LITERAL_CSTRING(mCacheDesc,
177 : "Memory (approximate) used by all pager caches used by connections "
178 : "to this database.");
179 :
180 803 : NS_NAMED_LITERAL_CSTRING(mSchemaDesc,
181 : "Memory (approximate) used to store the schema for all databases "
182 : "associated with connections to this database.");
183 803 : }
184 :
185 0 : NS_IMETHOD GetName(nsACString &aName)
186 : {
187 0 : aName.AssignLiteral("storage-sqlite");
188 0 : return NS_OK;
189 : }
190 :
191 : // Warning: To get a Connection's measurements requires holding its lock.
192 : // There may be a delay getting the lock if another thread is accessing the
193 : // Connection. This isn't very nice if CollectReports is called from the
194 : // main thread! But at the time of writing this function is only called when
195 : // about:memory is loaded (not, for example, when telemetry pings occur) and
196 : // any delays in that case aren't so bad.
197 3 : NS_IMETHOD CollectReports(nsIMemoryMultiReporterCallback *aCb,
198 : nsISupports *aClosure)
199 : {
200 : nsresult rv;
201 3 : size_t totalConnSize = 0;
202 : {
203 6 : nsTArray<nsRefPtr<Connection> > connections;
204 3 : mService->getConnections(connections);
205 :
206 12 : for (PRUint32 i = 0; i < connections.Length(); i++) {
207 9 : nsRefPtr<Connection> &conn = connections[i];
208 :
209 : // Someone may have closed the Connection, in which case we skip it.
210 : bool isReady;
211 9 : (void)conn->GetConnectionReady(&isReady);
212 9 : if (!isReady) {
213 0 : continue;
214 : }
215 :
216 18 : nsCString pathHead("explicit/storage/sqlite/");
217 9 : pathHead.Append(conn->getFilename());
218 9 : pathHead.AppendLiteral("/");
219 :
220 18 : SQLiteMutexAutoLock lockedScope(conn->sharedDBMutex);
221 :
222 : rv = reportConn(aCb, aClosure, *conn.get(), pathHead,
223 9 : NS_LITERAL_CSTRING("stmt"), mStmtDesc,
224 9 : SQLITE_DBSTATUS_STMT_USED, &totalConnSize);
225 9 : NS_ENSURE_SUCCESS(rv, rv);
226 :
227 : rv = reportConn(aCb, aClosure, *conn.get(), pathHead,
228 9 : NS_LITERAL_CSTRING("cache"), mCacheDesc,
229 9 : SQLITE_DBSTATUS_CACHE_USED, &totalConnSize);
230 9 : NS_ENSURE_SUCCESS(rv, rv);
231 :
232 : rv = reportConn(aCb, aClosure, *conn.get(), pathHead,
233 9 : NS_LITERAL_CSTRING("schema"), mSchemaDesc,
234 9 : SQLITE_DBSTATUS_SCHEMA_USED, &totalConnSize);
235 9 : NS_ENSURE_SUCCESS(rv, rv);
236 : }
237 : }
238 :
239 3 : PRInt64 other = ::sqlite3_memory_used() - totalConnSize;
240 :
241 3 : rv = aCb->Callback(NS_LITERAL_CSTRING(""),
242 3 : NS_LITERAL_CSTRING("explicit/storage/sqlite/other"),
243 : nsIMemoryReporter::KIND_HEAP,
244 : nsIMemoryReporter::UNITS_BYTES, other,
245 3 : NS_LITERAL_CSTRING("All unclassified sqlite memory."),
246 3 : aClosure);
247 3 : NS_ENSURE_SUCCESS(rv, rv);
248 :
249 3 : return NS_OK;
250 : }
251 :
252 3 : NS_IMETHOD GetExplicitNonHeap(PRInt64 *aAmount)
253 : {
254 : // This reporter doesn't do any non-heap measurements.
255 3 : *aAmount = 0;
256 3 : return NS_OK;
257 : }
258 :
259 : private:
260 : /**
261 : * Passes a single SQLite memory statistic to a memory multi-reporter
262 : * callback.
263 : *
264 : * @param aCallback
265 : * The callback.
266 : * @param aClosure
267 : * The closure for the callback.
268 : * @param aConn
269 : * The SQLite connection.
270 : * @param aPathHead
271 : * Head of the path for the memory report.
272 : * @param aKind
273 : * The memory report statistic kind, one of "stmt", "cache" or
274 : * "schema".
275 : * @param aDesc
276 : * The memory report description.
277 : * @param aOption
278 : * The SQLite constant for getting the measurement.
279 : * @param aTotal
280 : * The accumulator for the measurement.
281 : */
282 27 : nsresult reportConn(nsIMemoryMultiReporterCallback *aCb,
283 : nsISupports *aClosure,
284 : sqlite3 *aConn,
285 : const nsACString &aPathHead,
286 : const nsACString &aKind,
287 : const nsACString &aDesc,
288 : int aOption,
289 : size_t *aTotal)
290 : {
291 54 : nsCString path(aPathHead);
292 27 : path.Append(aKind);
293 27 : path.AppendLiteral("-used");
294 :
295 27 : int curr = 0, max = 0;
296 27 : int rc = ::sqlite3_db_status(aConn, aOption, &curr, &max, 0);
297 27 : nsresult rv = convertResultCode(rc);
298 27 : NS_ENSURE_SUCCESS(rv, rv);
299 :
300 27 : rv = aCb->Callback(NS_LITERAL_CSTRING(""), path,
301 : nsIMemoryReporter::KIND_HEAP,
302 : nsIMemoryReporter::UNITS_BYTES, PRInt64(curr),
303 27 : aDesc, aClosure);
304 27 : NS_ENSURE_SUCCESS(rv, rv);
305 27 : *aTotal += curr;
306 :
307 27 : return NS_OK;
308 : }
309 : };
310 :
311 6436 : NS_IMPL_THREADSAFE_ISUPPORTS1(
312 : StorageSQLiteMultiReporter,
313 : nsIMemoryMultiReporter
314 : )
315 :
316 : ////////////////////////////////////////////////////////////////////////////////
317 : //// Helpers
318 :
319 : class ServiceMainThreadInitializer : public nsRunnable
320 3212 : {
321 : public:
322 803 : ServiceMainThreadInitializer(Service *aService,
323 : nsIObserver *aObserver,
324 : nsIXPConnect **aXPConnectPtr,
325 : PRInt32 *aSynchronousPrefValPtr)
326 : : mService(aService)
327 : , mObserver(aObserver)
328 : , mXPConnectPtr(aXPConnectPtr)
329 803 : , mSynchronousPrefValPtr(aSynchronousPrefValPtr)
330 : {
331 803 : }
332 :
333 803 : NS_IMETHOD Run()
334 : {
335 803 : NS_PRECONDITION(NS_IsMainThread(), "Must be running on the main thread!");
336 :
337 : // NOTE: All code that can only run on the main thread and needs to be run
338 : // during initialization should be placed here. During the off-
339 : // chance that storage is initialized on a background thread, this
340 : // will ensure everything that isn't threadsafe is initialized in
341 : // the right place.
342 :
343 : // Register for xpcom-shutdown so we can cleanup after ourselves. The
344 : // observer service can only be used on the main thread.
345 : nsCOMPtr<nsIObserverService> os =
346 1606 : mozilla::services::GetObserverService();
347 803 : NS_ENSURE_TRUE(os, NS_ERROR_FAILURE);
348 803 : nsresult rv = os->AddObserver(mObserver, "xpcom-shutdown", false);
349 803 : NS_ENSURE_SUCCESS(rv, rv);
350 :
351 : // We cache XPConnect for our language helpers. XPConnect can only be
352 : // used on the main thread.
353 803 : (void)CallGetService(nsIXPConnect::GetCID(), mXPConnectPtr);
354 :
355 : // We need to obtain the toolkit.storage.synchronous preferences on the main
356 : // thread because the preference service can only be accessed there. This
357 : // is cached in the service for all future Open[Unshared]Database calls.
358 : PRInt32 synchronous =
359 803 : Preferences::GetInt(PREF_TS_SYNCHRONOUS, PREF_TS_SYNCHRONOUS_DEFAULT);
360 803 : ::PR_ATOMIC_SET(mSynchronousPrefValPtr, synchronous);
361 :
362 : // Create and register our SQLite memory reporters. Registration can only
363 : // happen on the main thread (otherwise you'll get cryptic crashes).
364 803 : mService->mStorageSQLiteReporter = new NS_MEMORY_REPORTER_NAME(StorageSQLite);
365 803 : mService->mStorageSQLiteMultiReporter = new StorageSQLiteMultiReporter(mService);
366 803 : (void)::NS_RegisterMemoryReporter(mService->mStorageSQLiteReporter);
367 803 : (void)::NS_RegisterMemoryMultiReporter(mService->mStorageSQLiteMultiReporter);
368 :
369 803 : return NS_OK;
370 : }
371 :
372 : private:
373 : Service *mService;
374 : nsIObserver *mObserver;
375 : nsIXPConnect **mXPConnectPtr;
376 : PRInt32 *mSynchronousPrefValPtr;
377 : };
378 :
379 : ////////////////////////////////////////////////////////////////////////////////
380 : //// Service
381 :
382 38856 : NS_IMPL_THREADSAFE_ISUPPORTS3(
383 : Service,
384 : mozIStorageService,
385 : nsIObserver,
386 : mozIStorageServiceQuotaManagement
387 : )
388 :
389 : Service *Service::gService = nsnull;
390 :
391 : Service *
392 803 : Service::getSingleton()
393 : {
394 803 : if (gService) {
395 0 : NS_ADDREF(gService);
396 0 : return gService;
397 : }
398 :
399 : // Ensure that we are using the same version of SQLite that we compiled with
400 : // or newer. Our configure check ensures we are using a new enough version
401 : // at compile time.
402 803 : if (SQLITE_VERSION_NUMBER > ::sqlite3_libversion_number()) {
403 0 : nsCOMPtr<nsIPromptService> ps(do_GetService(NS_PROMPTSERVICE_CONTRACTID));
404 0 : if (ps) {
405 0 : nsAutoString title, message;
406 0 : title.AppendASCII("SQLite Version Error");
407 : message.AppendASCII("The application has been updated, but your version "
408 : "of SQLite is too old and the application cannot "
409 0 : "run.");
410 0 : (void)ps->Alert(nsnull, title.get(), message.get());
411 : }
412 0 : ::PR_Abort();
413 : }
414 :
415 803 : gService = new Service();
416 803 : if (gService) {
417 803 : NS_ADDREF(gService);
418 803 : if (NS_FAILED(gService->initialize()))
419 0 : NS_RELEASE(gService);
420 : }
421 :
422 803 : return gService;
423 : }
424 :
425 : nsIXPConnect *Service::sXPConnect = nsnull;
426 :
427 : // static
428 : already_AddRefed<nsIXPConnect>
429 14668 : Service::getXPConnect()
430 : {
431 14668 : NS_PRECONDITION(NS_IsMainThread(),
432 : "Must only get XPConnect on the main thread!");
433 14668 : NS_PRECONDITION(gService,
434 : "Can not get XPConnect without an instance of our service!");
435 :
436 : // If we've been shutdown, sXPConnect will be null. To prevent leaks, we do
437 : // not cache the service after this point.
438 29336 : nsCOMPtr<nsIXPConnect> xpc(sXPConnect);
439 14668 : if (!xpc)
440 0 : xpc = do_GetService(nsIXPConnect::GetCID());
441 14668 : NS_ASSERTION(xpc, "Could not get XPConnect!");
442 14668 : return xpc.forget();
443 : }
444 :
445 : PRInt32 Service::sSynchronousPref;
446 :
447 : // static
448 : PRInt32
449 3267 : Service::getSynchronousPref()
450 : {
451 3267 : return sSynchronousPref;
452 : }
453 :
454 803 : Service::Service()
455 : : mMutex("Service::mMutex")
456 : , mSqliteVFS(nsnull)
457 : , mRegistrationMutex("Service::mRegistrationMutex")
458 : , mConnections()
459 : , mStorageSQLiteReporter(nsnull)
460 803 : , mStorageSQLiteMultiReporter(nsnull)
461 : {
462 803 : }
463 :
464 2382 : Service::~Service()
465 : {
466 794 : (void)::NS_UnregisterMemoryReporter(mStorageSQLiteReporter);
467 794 : (void)::NS_UnregisterMemoryMultiReporter(mStorageSQLiteMultiReporter);
468 :
469 794 : int rc = sqlite3_vfs_unregister(mSqliteVFS);
470 794 : if (rc != SQLITE_OK)
471 0 : NS_WARNING("Failed to unregister sqlite vfs wrapper.");
472 :
473 : // Shutdown the sqlite3 API. Warn if shutdown did not turn out okay, but
474 : // there is nothing actionable we can do in that case.
475 794 : rc = ::sqlite3_quota_shutdown();
476 794 : if (rc != SQLITE_OK)
477 0 : NS_WARNING("sqlite3 did not shutdown cleanly.");
478 :
479 794 : rc = ::sqlite3_shutdown();
480 794 : if (rc != SQLITE_OK)
481 0 : NS_WARNING("sqlite3 did not shutdown cleanly.");
482 :
483 1588 : DebugOnly<bool> shutdownObserved = !sXPConnect;
484 794 : NS_ASSERTION(shutdownObserved, "Shutdown was not observed!");
485 :
486 794 : gService = nsnull;
487 794 : delete mSqliteVFS;
488 794 : mSqliteVFS = nsnull;
489 3176 : }
490 :
491 : void
492 3281 : Service::registerConnection(Connection *aConnection)
493 : {
494 3281 : mRegistrationMutex.AssertNotCurrentThreadOwns();
495 6562 : MutexAutoLock mutex(mRegistrationMutex);
496 3281 : (void)mConnections.AppendElement(aConnection);
497 3281 : }
498 :
499 : void
500 3274 : Service::unregisterConnection(Connection *aConnection)
501 : {
502 : // If this is the last Connection it might be the only thing keeping Service
503 : // alive. So ensure that Service is destroyed only after the Connection is
504 : // cleanly unregistered and destroyed.
505 6548 : nsRefPtr<Service> kungFuDeathGrip(this);
506 : {
507 3274 : mRegistrationMutex.AssertNotCurrentThreadOwns();
508 6548 : MutexAutoLock mutex(mRegistrationMutex);
509 6548 : DebugOnly<bool> removed = mConnections.RemoveElement(aConnection);
510 : // Assert if we try to unregister a non-existent connection.
511 3274 : MOZ_ASSERT(removed);
512 : }
513 3274 : }
514 :
515 : void
516 3 : Service::getConnections(/* inout */ nsTArray<nsRefPtr<Connection> >& aConnections)
517 : {
518 3 : mRegistrationMutex.AssertNotCurrentThreadOwns();
519 6 : MutexAutoLock mutex(mRegistrationMutex);
520 3 : aConnections.Clear();
521 3 : aConnections.AppendElements(mConnections);
522 3 : }
523 :
524 : void
525 803 : Service::shutdown()
526 : {
527 803 : NS_IF_RELEASE(sXPConnect);
528 803 : }
529 :
530 : sqlite3_vfs *ConstructTelemetryVFS();
531 :
532 : #ifdef MOZ_MEMORY
533 :
534 : # if defined(XP_WIN) || defined(SOLARIS) || defined(ANDROID) || defined(XP_MACOSX)
535 : # include "jemalloc.h"
536 : # elif defined(XP_LINUX)
537 : // jemalloc is directly linked into firefox-bin; libxul doesn't link
538 : // with it. So if we tried to use je_malloc_usable_size_in_advance directly
539 : // here, it wouldn't be defined. Instead, we don't include the jemalloc header
540 : // and weakly link against je_malloc_usable_size_in_advance.
541 : extern "C" {
542 : extern size_t je_malloc_usable_size_in_advance(size_t size)
543 : NS_VISIBILITY_DEFAULT __attribute__((weak));
544 : }
545 : # endif // XP_LINUX
546 :
547 : namespace {
548 :
549 : // By default, SQLite tracks the size of all its heap blocks by adding an extra
550 : // 8 bytes at the start of the block to hold the size. Unfortunately, this
551 : // causes a lot of 2^N-sized allocations to be rounded up by jemalloc
552 : // allocator, wasting memory. For example, a request for 1024 bytes has 8
553 : // bytes added, becoming a request for 1032 bytes, and jemalloc rounds this up
554 : // to 2048 bytes, wasting 1012 bytes. (See bug 676189 for more details.)
555 : //
556 : // So we register jemalloc as the malloc implementation, which avoids this
557 : // 8-byte overhead, and thus a lot of waste. This requires us to provide a
558 : // function, sqliteMemRoundup(), which computes the actual size that will be
559 : // allocated for a given request. SQLite uses this function before all
560 : // allocations, and may be able to use any excess bytes caused by the rounding.
561 : //
562 : // Note: the wrappers for moz_malloc, moz_realloc and moz_malloc_usable_size
563 : // are necessary because the sqlite_mem_methods type signatures differ slightly
564 : // from the standard ones -- they use int instead of size_t. But we don't need
565 : // a wrapper for moz_free.
566 :
567 6078342 : static void *sqliteMemMalloc(int n)
568 : {
569 6078342 : return ::moz_malloc(n);
570 : }
571 :
572 269031 : static void *sqliteMemRealloc(void *p, int n)
573 : {
574 269031 : return ::moz_realloc(p, n);
575 : }
576 :
577 15842376 : static int sqliteMemSize(void *p)
578 : {
579 15842376 : return ::moz_malloc_usable_size(p);
580 : }
581 :
582 7116781 : static int sqliteMemRoundup(int n)
583 : {
584 7116781 : n = je_malloc_usable_size_in_advance(n);
585 :
586 : // jemalloc can return blocks of size 2 and 4, but SQLite requires that all
587 : // allocations be 8-aligned. So we round up sub-8 requests to 8. This
588 : // wastes a small amount of memory but is obviously safe.
589 7116780 : return n <= 8 ? 8 : n;
590 : }
591 :
592 803 : static int sqliteMemInit(void *p)
593 : {
594 803 : return 0;
595 : }
596 :
597 794 : static void sqliteMemShutdown(void *p)
598 : {
599 794 : }
600 :
601 : const sqlite3_mem_methods memMethods = {
602 : &sqliteMemMalloc,
603 : &moz_free,
604 : &sqliteMemRealloc,
605 : &sqliteMemSize,
606 : &sqliteMemRoundup,
607 : &sqliteMemInit,
608 : &sqliteMemShutdown,
609 : NULL
610 : };
611 :
612 : } // anonymous namespace
613 :
614 : #endif // MOZ_MEMORY
615 :
616 : nsresult
617 803 : Service::initialize()
618 : {
619 : NS_TIME_FUNCTION;
620 :
621 : int rc;
622 :
623 : #ifdef MOZ_MEMORY
624 803 : rc = ::sqlite3_config(SQLITE_CONFIG_MALLOC, &memMethods);
625 803 : if (rc != SQLITE_OK)
626 0 : return convertResultCode(rc);
627 : #endif
628 :
629 : // Explicitly initialize sqlite3. Although this is implicitly called by
630 : // various sqlite3 functions (and the sqlite3_open calls in our case),
631 : // the documentation suggests calling this directly. So we do.
632 803 : rc = ::sqlite3_initialize();
633 803 : if (rc != SQLITE_OK)
634 0 : return convertResultCode(rc);
635 :
636 803 : mSqliteVFS = ConstructTelemetryVFS();
637 803 : if (mSqliteVFS) {
638 803 : rc = sqlite3_vfs_register(mSqliteVFS, 1);
639 803 : if (rc != SQLITE_OK)
640 0 : return convertResultCode(rc);
641 : } else {
642 0 : NS_WARNING("Failed to register telemetry VFS");
643 : }
644 803 : rc = ::sqlite3_quota_initialize("telemetry-vfs", 0);
645 803 : if (rc != SQLITE_OK)
646 0 : return convertResultCode(rc);
647 :
648 : // Set the default value for the toolkit.storage.synchronous pref. It will be
649 : // updated with the user preference on the main thread.
650 803 : sSynchronousPref = PREF_TS_SYNCHRONOUS_DEFAULT;
651 :
652 : // Run the things that need to run on the main thread there.
653 : nsCOMPtr<nsIRunnable> event =
654 1606 : new ServiceMainThreadInitializer(this, this, &sXPConnect, &sSynchronousPref);
655 803 : if (event && ::NS_IsMainThread()) {
656 752 : (void)event->Run();
657 : }
658 : else {
659 51 : (void)::NS_DispatchToMainThread(event);
660 : }
661 :
662 803 : return NS_OK;
663 : }
664 :
665 : int
666 7098 : Service::localeCompareStrings(const nsAString &aStr1,
667 : const nsAString &aStr2,
668 : PRInt32 aComparisonStrength)
669 : {
670 : // The implementation of nsICollation.CompareString() is platform-dependent.
671 : // On Linux it's not thread-safe. It may not be on Windows and OS X either,
672 : // but it's more difficult to tell. We therefore synchronize this method.
673 14196 : MutexAutoLock mutex(mMutex);
674 :
675 7098 : nsICollation *coll = getLocaleCollation();
676 7098 : if (!coll) {
677 0 : NS_ERROR("Storage service has no collation");
678 0 : return 0;
679 : }
680 :
681 : PRInt32 res;
682 7098 : nsresult rv = coll->CompareString(aComparisonStrength, aStr1, aStr2, &res);
683 7098 : if (NS_FAILED(rv)) {
684 0 : NS_ERROR("Collation compare string failed");
685 0 : return 0;
686 : }
687 :
688 7098 : return res;
689 : }
690 :
691 : nsICollation *
692 7098 : Service::getLocaleCollation()
693 : {
694 7098 : mMutex.AssertCurrentThreadOwns();
695 :
696 7098 : if (mLocaleCollation)
697 7097 : return mLocaleCollation;
698 :
699 2 : nsCOMPtr<nsILocaleService> svc(do_GetService(NS_LOCALESERVICE_CONTRACTID));
700 1 : if (!svc) {
701 0 : NS_WARNING("Could not get locale service");
702 0 : return nsnull;
703 : }
704 :
705 2 : nsCOMPtr<nsILocale> appLocale;
706 1 : nsresult rv = svc->GetApplicationLocale(getter_AddRefs(appLocale));
707 1 : if (NS_FAILED(rv)) {
708 0 : NS_WARNING("Could not get application locale");
709 0 : return nsnull;
710 : }
711 :
712 : nsCOMPtr<nsICollationFactory> collFact =
713 2 : do_CreateInstance(NS_COLLATIONFACTORY_CONTRACTID);
714 1 : if (!collFact) {
715 0 : NS_WARNING("Could not create collation factory");
716 0 : return nsnull;
717 : }
718 :
719 1 : rv = collFact->CreateCollation(appLocale, getter_AddRefs(mLocaleCollation));
720 1 : if (NS_FAILED(rv)) {
721 0 : NS_WARNING("Could not create collation");
722 0 : return nsnull;
723 : }
724 :
725 1 : return mLocaleCollation;
726 : }
727 :
728 : ////////////////////////////////////////////////////////////////////////////////
729 : //// mozIStorageService
730 :
731 : #ifndef NS_APP_STORAGE_50_FILE
732 : #define NS_APP_STORAGE_50_FILE "UStor"
733 : #endif
734 :
735 : NS_IMETHODIMP
736 50 : Service::OpenSpecialDatabase(const char *aStorageKey,
737 : mozIStorageConnection **_connection)
738 : {
739 : nsresult rv;
740 :
741 100 : nsCOMPtr<nsIFile> storageFile;
742 50 : if (::strcmp(aStorageKey, "memory") == 0) {
743 : // just fall through with NULL storageFile, this will cause the storage
744 : // connection to use a memory DB.
745 : }
746 1 : else if (::strcmp(aStorageKey, "profile") == 0) {
747 :
748 : rv = NS_GetSpecialDirectory(NS_APP_STORAGE_50_FILE,
749 0 : getter_AddRefs(storageFile));
750 0 : NS_ENSURE_SUCCESS(rv, rv);
751 :
752 0 : nsString filename;
753 0 : storageFile->GetPath(filename);
754 0 : nsCString filename8 = NS_ConvertUTF16toUTF8(filename.get());
755 : // fall through to DB initialization
756 : }
757 : else {
758 1 : return NS_ERROR_INVALID_ARG;
759 : }
760 :
761 49 : Connection *msc = new Connection(this, SQLITE_OPEN_READWRITE);
762 49 : NS_ENSURE_TRUE(msc, NS_ERROR_OUT_OF_MEMORY);
763 :
764 49 : rv = msc->initialize(storageFile);
765 49 : NS_ENSURE_SUCCESS(rv, rv);
766 :
767 49 : NS_ADDREF(*_connection = msc);
768 49 : return NS_OK;
769 : }
770 :
771 : NS_IMETHODIMP
772 844 : Service::OpenDatabase(nsIFile *aDatabaseFile,
773 : mozIStorageConnection **_connection)
774 : {
775 844 : NS_ENSURE_ARG(aDatabaseFile);
776 :
777 : #ifdef NS_FUNCTION_TIMER
778 : nsCString leafname;
779 : (void)aDatabaseFile->GetNativeLeafName(leafname);
780 : NS_TIME_FUNCTION_FMT("mozIStorageService::OpenDatabase(%s)", leafname.get());
781 : #endif
782 :
783 : // Always ensure that SQLITE_OPEN_CREATE is passed in for compatibility
784 : // reasons.
785 : int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_SHAREDCACHE |
786 843 : SQLITE_OPEN_CREATE;
787 1686 : nsRefPtr<Connection> msc = new Connection(this, flags);
788 843 : NS_ENSURE_TRUE(msc, NS_ERROR_OUT_OF_MEMORY);
789 :
790 843 : nsresult rv = msc->initialize(aDatabaseFile);
791 843 : NS_ENSURE_SUCCESS(rv, rv);
792 :
793 838 : NS_ADDREF(*_connection = msc);
794 838 : return NS_OK;
795 : }
796 :
797 : NS_IMETHODIMP
798 1735 : Service::OpenUnsharedDatabase(nsIFile *aDatabaseFile,
799 : mozIStorageConnection **_connection)
800 : {
801 1735 : NS_ENSURE_ARG(aDatabaseFile);
802 :
803 : #ifdef NS_FUNCTION_TIMER
804 : nsCString leafname;
805 : (void)aDatabaseFile->GetNativeLeafName(leafname);
806 : NS_TIME_FUNCTION_FMT("mozIStorageService::OpenUnsharedDatabase(%s)",
807 : leafname.get());
808 : #endif
809 :
810 : // Always ensure that SQLITE_OPEN_CREATE is passed in for compatibility
811 : // reasons.
812 : int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_PRIVATECACHE |
813 1734 : SQLITE_OPEN_CREATE;
814 3468 : nsRefPtr<Connection> msc = new Connection(this, flags);
815 1734 : NS_ENSURE_TRUE(msc, NS_ERROR_OUT_OF_MEMORY);
816 :
817 1734 : nsresult rv = msc->initialize(aDatabaseFile);
818 1734 : NS_ENSURE_SUCCESS(rv, rv);
819 :
820 1725 : NS_ADDREF(*_connection = msc);
821 1725 : return NS_OK;
822 : }
823 :
824 : NS_IMETHODIMP
825 15 : Service::BackupDatabaseFile(nsIFile *aDBFile,
826 : const nsAString &aBackupFileName,
827 : nsIFile *aBackupParentDirectory,
828 : nsIFile **backup)
829 : {
830 : nsresult rv;
831 30 : nsCOMPtr<nsIFile> parentDir = aBackupParentDirectory;
832 15 : if (!parentDir) {
833 : // This argument is optional, and defaults to the same parent directory
834 : // as the current file.
835 8 : rv = aDBFile->GetParent(getter_AddRefs(parentDir));
836 8 : NS_ENSURE_SUCCESS(rv, rv);
837 : }
838 :
839 30 : nsCOMPtr<nsIFile> backupDB;
840 15 : rv = parentDir->Clone(getter_AddRefs(backupDB));
841 15 : NS_ENSURE_SUCCESS(rv, rv);
842 :
843 15 : rv = backupDB->Append(aBackupFileName);
844 15 : NS_ENSURE_SUCCESS(rv, rv);
845 :
846 15 : rv = backupDB->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
847 15 : NS_ENSURE_SUCCESS(rv, rv);
848 :
849 30 : nsAutoString fileName;
850 15 : rv = backupDB->GetLeafName(fileName);
851 15 : NS_ENSURE_SUCCESS(rv, rv);
852 :
853 15 : rv = backupDB->Remove(false);
854 15 : NS_ENSURE_SUCCESS(rv, rv);
855 :
856 15 : backupDB.forget(backup);
857 :
858 15 : return aDBFile->CopyTo(parentDir, fileName);
859 : }
860 :
861 : ////////////////////////////////////////////////////////////////////////////////
862 : //// nsIObserver
863 :
864 : NS_IMETHODIMP
865 803 : Service::Observe(nsISupports *, const char *aTopic, const PRUnichar *)
866 : {
867 803 : if (strcmp(aTopic, "xpcom-shutdown") == 0)
868 803 : shutdown();
869 803 : return NS_OK;
870 : }
871 :
872 : ////////////////////////////////////////////////////////////////////////////////
873 : //// mozIStorageServiceQuotaManagement
874 :
875 : NS_IMETHODIMP
876 605 : Service::OpenDatabaseWithVFS(nsIFile *aDatabaseFile,
877 : const nsACString &aVFSName,
878 : mozIStorageConnection **_connection)
879 : {
880 605 : NS_ENSURE_ARG(aDatabaseFile);
881 :
882 : #ifdef NS_FUNCTION_TIMER
883 : nsCString leafname;
884 : (void)aDatabaseFile->GetNativeLeafName(leafname);
885 : NS_TIME_FUNCTION_FMT("mozIStorageService::OpenDatabaseWithVFS(%s)",
886 : leafname.get());
887 : #endif
888 :
889 : // Always ensure that SQLITE_OPEN_CREATE is passed in for compatibility
890 : // reasons.
891 : int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_SHAREDCACHE |
892 605 : SQLITE_OPEN_CREATE;
893 1210 : nsRefPtr<Connection> msc = new Connection(this, flags);
894 605 : NS_ENSURE_TRUE(msc, NS_ERROR_OUT_OF_MEMORY);
895 :
896 : nsresult rv = msc->initialize(aDatabaseFile,
897 605 : PromiseFlatCString(aVFSName).get());
898 605 : NS_ENSURE_SUCCESS(rv, rv);
899 :
900 605 : NS_ADDREF(*_connection = msc);
901 605 : return NS_OK;
902 : }
903 :
904 : NS_IMETHODIMP
905 50 : Service::SetQuotaForFilenamePattern(const nsACString &aPattern,
906 : PRInt64 aSizeLimit,
907 : mozIStorageQuotaCallback *aCallback,
908 : nsISupports *aUserData)
909 : {
910 50 : NS_ENSURE_FALSE(aPattern.IsEmpty(), NS_ERROR_INVALID_ARG);
911 :
912 100 : nsAutoPtr<QuotaCallbackData> data;
913 50 : if (aSizeLimit && aCallback) {
914 50 : data = new QuotaCallbackData(aCallback, aUserData);
915 : }
916 :
917 50 : int rc = ::sqlite3_quota_set(PromiseFlatCString(aPattern).get(),
918 : aSizeLimit, QuotaCallbackData::Callback,
919 50 : data, QuotaCallbackData::Destroy);
920 50 : NS_ENSURE_TRUE(rc == SQLITE_OK, convertResultCode(rc));
921 :
922 50 : data.forget();
923 50 : return NS_OK;
924 : }
925 :
926 : NS_IMETHODIMP
927 0 : Service::UpdateQuotaInformationForFile(nsIFile *aFile)
928 : {
929 0 : NS_ENSURE_ARG_POINTER(aFile);
930 :
931 0 : nsCString path;
932 0 : nsresult rv = aFile->GetNativePath(path);
933 0 : NS_ENSURE_SUCCESS(rv, rv);
934 :
935 0 : int rc = ::sqlite3_quota_file(PromiseFlatCString(path).get());
936 0 : NS_ENSURE_TRUE(rc == SQLITE_OK, convertResultCode(rc));
937 :
938 0 : return NS_OK;
939 : }
940 :
941 : } // namespace storage
942 : } // namespace mozilla
|