mozStorageConnection.cpp (100262B)
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 "BaseVFS.h" 8 #include "ErrorList.h" 9 #include "nsError.h" 10 #include "nsThreadUtils.h" 11 #include "nsIFile.h" 12 #include "nsIFileURL.h" 13 #include "nsIXPConnect.h" 14 #include "mozilla/AppShutdown.h" 15 #include "mozilla/CheckedInt.h" 16 #include "mozilla/glean/StorageMetrics.h" 17 #include "mozilla/Telemetry.h" 18 #include "mozilla/Mutex.h" 19 #include "mozilla/CondVar.h" 20 #include "mozilla/Attributes.h" 21 #include "mozilla/ErrorNames.h" 22 #include "mozilla/dom/quota/QuotaObject.h" 23 #include "mozilla/ScopeExit.h" 24 #include "mozilla/SpinEventLoopUntil.h" 25 #include "mozilla/StaticPrefs_storage.h" 26 27 #include "mozIStorageCompletionCallback.h" 28 #include "mozIStorageFunction.h" 29 30 #include "mozStorageAsyncStatementExecution.h" 31 #include "mozStorageSQLFunctions.h" 32 #include "mozStorageConnection.h" 33 #include "mozStorageService.h" 34 #include "mozStorageStatement.h" 35 #include "mozStorageAsyncStatement.h" 36 #include "mozStorageArgValueArray.h" 37 #include "mozStoragePrivateHelpers.h" 38 #include "mozStorageStatementData.h" 39 #include "ObfuscatingVFS.h" 40 #include "QuotaVFS.h" 41 #include "StorageBaseStatementInternal.h" 42 #include "SQLCollations.h" 43 #include "FileSystemModule.h" 44 #include "mozStorageHelper.h" 45 #include "sqlite3_static_ext.h" 46 47 #include "mozilla/Assertions.h" 48 #include "mozilla/Logging.h" 49 #include "mozilla/Printf.h" 50 #include "mozilla/ProfilerLabels.h" 51 #include "mozilla/RefPtr.h" 52 #include "nsComponentManagerUtils.h" 53 #include "nsProxyRelease.h" 54 #include "nsStringFwd.h" 55 #include "nsURLHelper.h" 56 57 #define MIN_AVAILABLE_BYTES_PER_CHUNKED_GROWTH 524288000 // 500 MiB 58 59 // Maximum size of the pages cache per connection. 60 #define MAX_CACHE_SIZE_KIBIBYTES 2048 // 2 MiB 61 62 mozilla::LazyLogModule gStorageLog("mozStorage"); 63 64 // Checks that the protected code is running on the main-thread only if the 65 // connection was also opened on it. 66 #ifdef DEBUG 67 # define CHECK_MAINTHREAD_ABUSE() \ 68 do { \ 69 NS_WARNING_ASSERTION( \ 70 eventTargetOpenedOn == GetMainThreadSerialEventTarget() || \ 71 !NS_IsMainThread(), \ 72 "Using Storage synchronous API on main-thread, but " \ 73 "the connection was opened on another thread."); \ 74 } while (0) 75 #else 76 # define CHECK_MAINTHREAD_ABUSE() \ 77 do { /* Nothing */ \ 78 } while (0) 79 #endif 80 81 namespace mozilla::storage { 82 83 using mozilla::dom::quota::QuotaObject; 84 85 namespace { 86 87 int nsresultToSQLiteResult(nsresult aXPCOMResultCode) { 88 if (NS_SUCCEEDED(aXPCOMResultCode)) { 89 return SQLITE_OK; 90 } 91 92 switch (aXPCOMResultCode) { 93 case NS_ERROR_FILE_CORRUPTED: 94 return SQLITE_CORRUPT; 95 case NS_ERROR_FILE_ACCESS_DENIED: 96 return SQLITE_CANTOPEN; 97 case NS_ERROR_STORAGE_BUSY: 98 return SQLITE_BUSY; 99 case NS_ERROR_FILE_IS_LOCKED: 100 return SQLITE_LOCKED; 101 case NS_ERROR_FILE_READ_ONLY: 102 return SQLITE_READONLY; 103 case NS_ERROR_STORAGE_IOERR: 104 return SQLITE_IOERR; 105 case NS_ERROR_FILE_NO_DEVICE_SPACE: 106 return SQLITE_FULL; 107 case NS_ERROR_OUT_OF_MEMORY: 108 return SQLITE_NOMEM; 109 case NS_ERROR_UNEXPECTED: 110 return SQLITE_MISUSE; 111 case NS_ERROR_ABORT: 112 return SQLITE_ABORT; 113 case NS_ERROR_STORAGE_CONSTRAINT: 114 return SQLITE_CONSTRAINT; 115 default: 116 return SQLITE_ERROR; 117 } 118 119 MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Must return in switch above!"); 120 } 121 122 //////////////////////////////////////////////////////////////////////////////// 123 //// Variant Specialization Functions (variantToSQLiteT) 124 125 int sqlite3_T_int(sqlite3_context* aCtx, int aValue) { 126 ::sqlite3_result_int(aCtx, aValue); 127 return SQLITE_OK; 128 } 129 130 int sqlite3_T_int64(sqlite3_context* aCtx, sqlite3_int64 aValue) { 131 ::sqlite3_result_int64(aCtx, aValue); 132 return SQLITE_OK; 133 } 134 135 int sqlite3_T_double(sqlite3_context* aCtx, double aValue) { 136 ::sqlite3_result_double(aCtx, aValue); 137 return SQLITE_OK; 138 } 139 140 int sqlite3_T_text(sqlite3_context* aCtx, const nsCString& aValue) { 141 CheckedInt<int32_t> length(aValue.Length()); 142 if (!length.isValid()) { 143 return SQLITE_MISUSE; 144 } 145 ::sqlite3_result_text(aCtx, aValue.get(), length.value(), SQLITE_TRANSIENT); 146 return SQLITE_OK; 147 } 148 149 int sqlite3_T_text16(sqlite3_context* aCtx, const nsString& aValue) { 150 CheckedInt<int32_t> n_bytes = 151 CheckedInt<int32_t>(aValue.Length()) * sizeof(char16_t); 152 if (!n_bytes.isValid()) { 153 return SQLITE_MISUSE; 154 } 155 ::sqlite3_result_text16(aCtx, aValue.get(), n_bytes.value(), 156 SQLITE_TRANSIENT); 157 return SQLITE_OK; 158 } 159 160 int sqlite3_T_null(sqlite3_context* aCtx) { 161 ::sqlite3_result_null(aCtx); 162 return SQLITE_OK; 163 } 164 165 int sqlite3_T_blob(sqlite3_context* aCtx, const void* aData, int aSize) { 166 ::sqlite3_result_blob(aCtx, aData, aSize, free); 167 return SQLITE_OK; 168 } 169 170 int sqlite3_T_array(sqlite3_context* aCtx, const void* aData, int aSize, 171 int aType) { 172 // Not supported for now. 173 return SQLITE_MISUSE; 174 } 175 176 #include "variantToSQLiteT_impl.h" 177 178 //////////////////////////////////////////////////////////////////////////////// 179 //// Modules 180 181 struct Module { 182 const char* name; 183 int (*registerFunc)(sqlite3*, const char*); 184 }; 185 186 Module gModules[] = {{"filesystem", RegisterFileSystemModule}}; 187 188 //////////////////////////////////////////////////////////////////////////////// 189 //// Local Functions 190 191 int tracefunc(unsigned aReason, void* aClosure, void* aP, void* aX) { 192 switch (aReason) { 193 case SQLITE_TRACE_STMT: { 194 // aP is a pointer to the prepared statement. 195 sqlite3_stmt* stmt = static_cast<sqlite3_stmt*>(aP); 196 // aX is a pointer to a string containing the unexpanded SQL or a comment, 197 // starting with "--"" in case of a trigger. 198 char* expanded = static_cast<char*>(aX); 199 // Simulate what sqlite_trace was doing. 200 if (!::strncmp(expanded, "--", 2)) { 201 MOZ_LOG(gStorageLog, LogLevel::Debug, 202 ("TRACE_STMT on %p: '%s'", aClosure, expanded)); 203 } else { 204 char* sql = ::sqlite3_expanded_sql(stmt); 205 MOZ_LOG(gStorageLog, LogLevel::Debug, 206 ("TRACE_STMT on %p: '%s'", aClosure, sql)); 207 ::sqlite3_free(sql); 208 } 209 break; 210 } 211 case SQLITE_TRACE_PROFILE: { 212 // aX is pointer to a 64bit integer containing nanoseconds it took to 213 // execute the last command. 214 sqlite_int64 time = *(static_cast<sqlite_int64*>(aX)) / 1000000; 215 if (time > 0) { 216 MOZ_LOG(gStorageLog, LogLevel::Debug, 217 ("TRACE_TIME on %p: %lldms", aClosure, time)); 218 } 219 break; 220 } 221 } 222 return 0; 223 } 224 225 void basicFunctionHelper(sqlite3_context* aCtx, int aArgc, 226 sqlite3_value** aArgv) { 227 void* userData = ::sqlite3_user_data(aCtx); 228 229 mozIStorageFunction* func = static_cast<mozIStorageFunction*>(userData); 230 231 RefPtr<ArgValueArray> arguments(new ArgValueArray(aArgc, aArgv)); 232 if (!arguments) return; 233 234 nsCOMPtr<nsIVariant> result; 235 nsresult rv = func->OnFunctionCall(arguments, getter_AddRefs(result)); 236 if (NS_FAILED(rv)) { 237 nsAutoCString errorMessage; 238 GetErrorName(rv, errorMessage); 239 errorMessage.InsertLiteral("User function returned ", 0); 240 errorMessage.Append('!'); 241 242 NS_WARNING(errorMessage.get()); 243 244 ::sqlite3_result_error(aCtx, errorMessage.get(), -1); 245 ::sqlite3_result_error_code(aCtx, nsresultToSQLiteResult(rv)); 246 return; 247 } 248 int retcode = variantToSQLiteT(aCtx, result); 249 if (retcode != SQLITE_OK) { 250 NS_WARNING("User function returned invalid data type!"); 251 ::sqlite3_result_error(aCtx, "User function returned invalid data type", 252 -1); 253 } 254 } 255 256 RefPtr<QuotaObject> GetQuotaObject(sqlite3_file* aFile, bool obfuscatingVFS) { 257 return obfuscatingVFS 258 ? mozilla::storage::obfsvfs::GetQuotaObjectForFile(aFile) 259 : mozilla::storage::quotavfs::GetQuotaObjectForFile(aFile); 260 } 261 262 /** 263 * This code is heavily based on the sample at: 264 * http://www.sqlite.org/unlock_notify.html 265 */ 266 class UnlockNotification { 267 public: 268 UnlockNotification() 269 : mMutex("UnlockNotification mMutex"), 270 mCondVar(mMutex, "UnlockNotification condVar"), 271 mSignaled(false) {} 272 273 void Wait() { 274 MutexAutoLock lock(mMutex); 275 while (!mSignaled) { 276 (void)mCondVar.Wait(); 277 } 278 } 279 280 void Signal() { 281 MutexAutoLock lock(mMutex); 282 mSignaled = true; 283 (void)mCondVar.Notify(); 284 } 285 286 private: 287 Mutex mMutex MOZ_UNANNOTATED; 288 CondVar mCondVar; 289 bool mSignaled; 290 }; 291 292 void UnlockNotifyCallback(void** aArgs, int aArgsSize) { 293 for (int i = 0; i < aArgsSize; i++) { 294 UnlockNotification* notification = 295 static_cast<UnlockNotification*>(aArgs[i]); 296 notification->Signal(); 297 } 298 } 299 300 int WaitForUnlockNotify(sqlite3* aDatabase) { 301 UnlockNotification notification; 302 int srv = 303 ::sqlite3_unlock_notify(aDatabase, UnlockNotifyCallback, ¬ification); 304 MOZ_ASSERT(srv == SQLITE_LOCKED || srv == SQLITE_OK); 305 if (srv == SQLITE_OK) { 306 notification.Wait(); 307 } 308 309 return srv; 310 } 311 312 //////////////////////////////////////////////////////////////////////////////// 313 //// Local Classes 314 315 class AsyncCloseConnection final : public Runnable { 316 public: 317 AsyncCloseConnection(Connection* aConnection, sqlite3* aNativeConnection, 318 nsIRunnable* aCallbackEvent) 319 : Runnable("storage::AsyncCloseConnection"), 320 mConnection(aConnection), 321 mNativeConnection(aNativeConnection), 322 mCallbackEvent(aCallbackEvent) {} 323 324 NS_IMETHOD Run() override { 325 // Make sure we don't dispatch to the current thread. 326 MOZ_ASSERT(!IsOnCurrentSerialEventTarget(mConnection->eventTargetOpenedOn)); 327 328 nsCOMPtr<nsIRunnable> event = 329 NewRunnableMethod("storage::Connection::shutdownAsyncThread", 330 mConnection, &Connection::shutdownAsyncThread); 331 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(event)); 332 333 // Internal close. 334 (void)mConnection->internalClose(mNativeConnection); 335 336 // Callback 337 if (mCallbackEvent) { 338 nsCOMPtr<nsIThread> thread; 339 (void)NS_GetMainThread(getter_AddRefs(thread)); 340 (void)thread->Dispatch(mCallbackEvent, NS_DISPATCH_NORMAL); 341 } 342 343 return NS_OK; 344 } 345 346 ~AsyncCloseConnection() override { 347 NS_ReleaseOnMainThread("AsyncCloseConnection::mConnection", 348 mConnection.forget()); 349 NS_ReleaseOnMainThread("AsyncCloseConnection::mCallbackEvent", 350 mCallbackEvent.forget()); 351 } 352 353 private: 354 RefPtr<Connection> mConnection; 355 sqlite3* mNativeConnection; 356 nsCOMPtr<nsIRunnable> mCallbackEvent; 357 }; 358 359 /** 360 * An event used to initialize the clone of a connection. 361 * 362 * Must be executed on the clone's async execution thread. 363 */ 364 class AsyncInitializeClone final : public Runnable { 365 public: 366 /** 367 * @param aConnection The connection being cloned. 368 * @param aClone The clone. 369 * @param aReadOnly If |true|, the clone is read only. 370 * @param aCallback A callback to trigger once initialization 371 * is complete. This event will be called on 372 * aClone->eventTargetOpenedOn. 373 */ 374 AsyncInitializeClone(Connection* aConnection, Connection* aClone, 375 const bool aReadOnly, 376 mozIStorageCompletionCallback* aCallback) 377 : Runnable("storage::AsyncInitializeClone"), 378 mConnection(aConnection), 379 mClone(aClone), 380 mReadOnly(aReadOnly), 381 mCallback(aCallback) { 382 MOZ_ASSERT(NS_IsMainThread()); 383 } 384 385 NS_IMETHOD Run() override { 386 MOZ_ASSERT(!NS_IsMainThread()); 387 nsresult rv = mConnection->initializeClone(mClone, mReadOnly); 388 if (NS_FAILED(rv)) { 389 return Dispatch(rv, nullptr); 390 } 391 return Dispatch(NS_OK, 392 NS_ISUPPORTS_CAST(mozIStorageAsyncConnection*, mClone)); 393 } 394 395 private: 396 nsresult Dispatch(nsresult aResult, nsISupports* aValue) { 397 RefPtr<CallbackComplete> event = 398 new CallbackComplete(aResult, aValue, mCallback.forget()); 399 return mClone->eventTargetOpenedOn->Dispatch(event, NS_DISPATCH_NORMAL); 400 } 401 402 ~AsyncInitializeClone() override { 403 nsCOMPtr<nsIThread> thread; 404 DebugOnly<nsresult> rv = NS_GetMainThread(getter_AddRefs(thread)); 405 MOZ_ASSERT(NS_SUCCEEDED(rv)); 406 407 // Handle ambiguous nsISupports inheritance. 408 NS_ProxyRelease("AsyncInitializeClone::mConnection", thread, 409 mConnection.forget()); 410 NS_ProxyRelease("AsyncInitializeClone::mClone", thread, mClone.forget()); 411 412 // Generally, the callback will be released by CallbackComplete. 413 // However, if for some reason Run() is not executed, we still 414 // need to ensure that it is released here. 415 NS_ProxyRelease("AsyncInitializeClone::mCallback", thread, 416 mCallback.forget()); 417 } 418 419 RefPtr<Connection> mConnection; 420 RefPtr<Connection> mClone; 421 const bool mReadOnly; 422 nsCOMPtr<mozIStorageCompletionCallback> mCallback; 423 }; 424 425 /** 426 * A listener for async connection closing. 427 */ 428 class CloseListener final : public mozIStorageCompletionCallback { 429 public: 430 NS_DECL_ISUPPORTS 431 CloseListener() : mClosed(false) {} 432 433 NS_IMETHOD Complete(nsresult, nsISupports*) override { 434 mClosed = true; 435 return NS_OK; 436 } 437 438 bool mClosed; 439 440 private: 441 ~CloseListener() = default; 442 }; 443 444 NS_IMPL_ISUPPORTS(CloseListener, mozIStorageCompletionCallback) 445 446 class AsyncVacuumEvent final : public Runnable { 447 public: 448 AsyncVacuumEvent(Connection* aConnection, 449 mozIStorageCompletionCallback* aCallback, 450 bool aUseIncremental, int32_t aSetPageSize) 451 : Runnable("storage::AsyncVacuum"), 452 mConnection(aConnection), 453 mCallback(aCallback), 454 mUseIncremental(aUseIncremental), 455 mSetPageSize(aSetPageSize), 456 mStatus(NS_ERROR_UNEXPECTED) {} 457 458 NS_IMETHOD Run() override { 459 // This is initially dispatched to the helper thread, then re-dispatched 460 // to the opener thread, where it will callback. 461 if (IsOnCurrentSerialEventTarget(mConnection->eventTargetOpenedOn)) { 462 // Send the completion event. 463 if (mCallback) { 464 (void)mCallback->Complete(mStatus, nullptr); 465 } 466 return NS_OK; 467 } 468 469 // Ensure to invoke the callback regardless of errors. 470 auto guard = MakeScopeExit([&]() { 471 mConnection->mIsStatementOnHelperThreadInterruptible = false; 472 (void)mConnection->eventTargetOpenedOn->Dispatch(this, 473 NS_DISPATCH_NORMAL); 474 }); 475 476 // Get list of attached databases. 477 nsCOMPtr<mozIStorageStatement> stmt; 478 nsresult rv = mConnection->CreateStatement(MOZ_STORAGE_UNIQUIFY_QUERY_STR 479 "PRAGMA database_list"_ns, 480 getter_AddRefs(stmt)); 481 NS_ENSURE_SUCCESS(rv, rv); 482 // We must accumulate names and loop through them later, otherwise VACUUM 483 // will see an ongoing statement and bail out. 484 nsTArray<nsCString> schemaNames; 485 bool hasResult = false; 486 while (stmt && NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) { 487 nsAutoCString name; 488 rv = stmt->GetUTF8String(1, name); 489 if (NS_SUCCEEDED(rv) && !name.EqualsLiteral("temp")) { 490 schemaNames.AppendElement(name); 491 } 492 } 493 mStatus = NS_OK; 494 // Mark this vacuum as an interruptible operation, so it can be interrupted 495 // if the connection closes during shutdown. 496 mConnection->mIsStatementOnHelperThreadInterruptible = true; 497 for (const nsCString& schemaName : schemaNames) { 498 rv = this->Vacuum(schemaName); 499 if (NS_FAILED(rv)) { 500 // This is sub-optimal since it's only keeping the last error reason, 501 // but it will do for now. 502 mStatus = rv; 503 } 504 } 505 return mStatus; 506 } 507 508 nsresult Vacuum(const nsACString& aSchemaName) { 509 // Abort if we're in shutdown. 510 if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) { 511 return NS_ERROR_ABORT; 512 } 513 int32_t removablePages = mConnection->RemovablePagesInFreeList(aSchemaName); 514 if (!removablePages) { 515 // There's no empty pages to remove, so skip this vacuum for now. 516 return NS_OK; 517 } 518 nsresult rv; 519 bool needsFullVacuum = true; 520 521 if (mSetPageSize) { 522 nsAutoCString query(MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA "); 523 query.Append(aSchemaName); 524 query.AppendLiteral(".page_size = "); 525 query.AppendInt(mSetPageSize); 526 nsCOMPtr<mozIStorageStatement> stmt; 527 rv = mConnection->ExecuteSimpleSQL(query); 528 NS_ENSURE_SUCCESS(rv, rv); 529 } 530 531 // Check auto_vacuum. 532 { 533 nsAutoCString query(MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA "); 534 query.Append(aSchemaName); 535 query.AppendLiteral(".auto_vacuum"); 536 nsCOMPtr<mozIStorageStatement> stmt; 537 rv = mConnection->CreateStatement(query, getter_AddRefs(stmt)); 538 NS_ENSURE_SUCCESS(rv, rv); 539 bool hasResult = false; 540 bool changeAutoVacuum = false; 541 if (stmt && NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) { 542 bool isIncrementalVacuum = stmt->AsInt32(0) == 2; 543 changeAutoVacuum = isIncrementalVacuum != mUseIncremental; 544 if (isIncrementalVacuum && !changeAutoVacuum) { 545 needsFullVacuum = false; 546 } 547 } 548 // Changing auto_vacuum is only supported on the main schema. 549 if (aSchemaName.EqualsLiteral("main") && changeAutoVacuum) { 550 nsAutoCString query(MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA "); 551 query.Append(aSchemaName); 552 query.AppendLiteral(".auto_vacuum = "); 553 query.AppendInt(mUseIncremental ? 2 : 0); 554 rv = mConnection->ExecuteSimpleSQL(query); 555 NS_ENSURE_SUCCESS(rv, rv); 556 } 557 } 558 559 if (needsFullVacuum) { 560 nsAutoCString query(MOZ_STORAGE_UNIQUIFY_QUERY_STR "VACUUM "); 561 query.Append(aSchemaName); 562 rv = mConnection->ExecuteSimpleSQL(query); 563 // TODO (Bug 1818039): Report failed vacuum telemetry. 564 NS_ENSURE_SUCCESS(rv, rv); 565 } else { 566 nsAutoCString query(MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA "); 567 query.Append(aSchemaName); 568 query.AppendLiteral(".incremental_vacuum("); 569 query.AppendInt(removablePages); 570 query.AppendLiteral(")"); 571 rv = mConnection->ExecuteSimpleSQL(query); 572 NS_ENSURE_SUCCESS(rv, rv); 573 } 574 575 return NS_OK; 576 } 577 578 ~AsyncVacuumEvent() override { 579 NS_ReleaseOnMainThread("AsyncVacuum::mConnection", mConnection.forget()); 580 NS_ReleaseOnMainThread("AsyncVacuum::mCallback", mCallback.forget()); 581 } 582 583 private: 584 RefPtr<Connection> mConnection; 585 nsCOMPtr<mozIStorageCompletionCallback> mCallback; 586 bool mUseIncremental; 587 int32_t mSetPageSize; 588 Atomic<nsresult> mStatus; 589 }; 590 591 /** 592 * A runnable to perform an SQLite database backup when there may be one or more 593 * open connections on that database. 594 */ 595 class AsyncBackupDatabaseFile final : public Runnable, public nsITimerCallback { 596 public: 597 NS_DECL_ISUPPORTS_INHERITED 598 599 /** 600 * @param aConnection The connection to the database being backed up. 601 * @param aNativeConnection The native connection to the database being backed 602 * up. 603 * @param aDestinationFile The destination file for the created backup. 604 * @param aCallback A callback to trigger once the backup process has 605 * completed. The callback will be supplied with an nsresult 606 * indicating whether or not the backup was successfully 607 * created. This callback will be called on the 608 * mConnection->eventTargetOpenedOn thread. 609 * @throws 610 */ 611 AsyncBackupDatabaseFile(Connection* aConnection, sqlite3* aNativeConnection, 612 nsIFile* aDestinationFile, 613 mozIStorageCompletionCallback* aCallback, 614 int32_t aPagesPerStep, uint32_t aStepDelayMs) 615 : Runnable("storage::AsyncBackupDatabaseFile"), 616 mConnection(aConnection), 617 mNativeConnection(aNativeConnection), 618 mDestinationFile(aDestinationFile), 619 mCallback(aCallback), 620 mPagesPerStep(aPagesPerStep), 621 mStepDelayMs(aStepDelayMs), 622 mBackupFile(nullptr), 623 mBackupHandle(nullptr) { 624 MOZ_ASSERT(NS_IsMainThread()); 625 } 626 627 NS_IMETHOD Run() override { 628 MOZ_ASSERT(!NS_IsMainThread()); 629 630 nsAutoString path; 631 nsresult rv = mDestinationFile->GetPath(path); 632 if (NS_FAILED(rv)) { 633 return Dispatch(rv, nullptr); 634 } 635 // Put a .tmp on the end of the destination while the backup is underway. 636 // This extension will be stripped off after the backup successfully 637 // completes. 638 path.AppendLiteral(".tmp"); 639 640 int srv = ::sqlite3_open(NS_ConvertUTF16toUTF8(path).get(), &mBackupFile); 641 if (srv != SQLITE_OK) { 642 ::sqlite3_close(mBackupFile); 643 mBackupFile = nullptr; 644 return Dispatch(NS_ERROR_FAILURE, nullptr); 645 } 646 647 static const char* mainDBName = "main"; 648 649 mBackupHandle = ::sqlite3_backup_init(mBackupFile, mainDBName, 650 mNativeConnection, mainDBName); 651 if (!mBackupHandle) { 652 MOZ_ALWAYS_TRUE(::sqlite3_close(mBackupFile) == SQLITE_OK); 653 return Dispatch(NS_ERROR_FAILURE, nullptr); 654 } 655 656 return DoStep(); 657 } 658 659 NS_IMETHOD 660 Notify(nsITimer* aTimer) override { return DoStep(); } 661 662 private: 663 nsresult DoStep() { 664 #define DISPATCH_AND_RETURN_IF_FAILED(rv) \ 665 if (NS_FAILED(rv)) { \ 666 return Dispatch(rv, nullptr); \ 667 } 668 669 // This guard is used to close the backup database in the event of 670 // some failure throughout this process. We release the exit guard 671 // only if we complete the backup successfully, or defer to another 672 // later call to DoStep. 673 auto guard = MakeScopeExit([&]() { 674 MOZ_ALWAYS_TRUE(::sqlite3_close(mBackupFile) == SQLITE_OK); 675 mBackupFile = nullptr; 676 }); 677 678 MOZ_ASSERT(!NS_IsMainThread()); 679 nsAutoString originalPath; 680 nsresult rv = mDestinationFile->GetPath(originalPath); 681 DISPATCH_AND_RETURN_IF_FAILED(rv); 682 683 nsAutoString tempPath = originalPath; 684 tempPath.AppendLiteral(".tmp"); 685 686 nsCOMPtr<nsIFile> file; 687 rv = NS_NewLocalFile(tempPath, getter_AddRefs(file)); 688 DISPATCH_AND_RETURN_IF_FAILED(rv); 689 690 int srv = ::sqlite3_backup_step(mBackupHandle, mPagesPerStep); 691 if (srv == SQLITE_OK || srv == SQLITE_BUSY || srv == SQLITE_LOCKED) { 692 // We're continuing the backup later. Release the guard to avoid closing 693 // the database. 694 guard.release(); 695 // Queue up the next step 696 return NS_NewTimerWithCallback(getter_AddRefs(mTimer), this, mStepDelayMs, 697 nsITimer::TYPE_ONE_SHOT, 698 GetCurrentSerialEventTarget()); 699 } 700 #ifdef DEBUG 701 if (srv != SQLITE_DONE) { 702 nsCString warnMsg; 703 warnMsg.AppendLiteral( 704 "The SQLite database copy could not be completed due to an error: "); 705 warnMsg.Append(::sqlite3_errmsg(mBackupFile)); 706 NS_WARNING(warnMsg.get()); 707 } 708 #endif 709 710 (void)::sqlite3_backup_finish(mBackupHandle); 711 MOZ_ALWAYS_TRUE(::sqlite3_close(mBackupFile) == SQLITE_OK); 712 mBackupFile = nullptr; 713 714 // The database is already closed, so we can release this guard now. 715 guard.release(); 716 717 if (srv != SQLITE_DONE) { 718 NS_WARNING("Failed to create database copy."); 719 720 // The partially created database file is not useful. Let's remove it. 721 rv = file->Remove(false); 722 if (NS_FAILED(rv)) { 723 NS_WARNING( 724 "Removing a partially backed up SQLite database file failed."); 725 } 726 727 return Dispatch(convertResultCode(srv), nullptr); 728 } 729 730 // Now that we've successfully created the copy, we'll strip off the .tmp 731 // extension. 732 733 nsAutoString leafName; 734 rv = mDestinationFile->GetLeafName(leafName); 735 DISPATCH_AND_RETURN_IF_FAILED(rv); 736 737 rv = file->RenameTo(nullptr, leafName); 738 DISPATCH_AND_RETURN_IF_FAILED(rv); 739 740 #undef DISPATCH_AND_RETURN_IF_FAILED 741 return Dispatch(NS_OK, nullptr); 742 } 743 744 nsresult Dispatch(nsresult aResult, nsISupports* aValue) { 745 RefPtr<CallbackComplete> event = 746 new CallbackComplete(aResult, aValue, mCallback.forget()); 747 return mConnection->eventTargetOpenedOn->Dispatch(event, 748 NS_DISPATCH_NORMAL); 749 } 750 751 ~AsyncBackupDatabaseFile() override { 752 nsresult rv; 753 nsCOMPtr<nsIThread> thread = 754 do_QueryInterface(mConnection->eventTargetOpenedOn, &rv); 755 MOZ_ASSERT(NS_SUCCEEDED(rv)); 756 757 // Handle ambiguous nsISupports inheritance. 758 NS_ProxyRelease("AsyncBackupDatabaseFile::mConnection", thread, 759 mConnection.forget()); 760 NS_ProxyRelease("AsyncBackupDatabaseFile::mDestinationFile", thread, 761 mDestinationFile.forget()); 762 763 // Generally, the callback will be released by CallbackComplete. 764 // However, if for some reason Run() is not executed, we still 765 // need to ensure that it is released here. 766 NS_ProxyRelease("AsyncInitializeClone::mCallback", thread, 767 mCallback.forget()); 768 } 769 770 RefPtr<Connection> mConnection; 771 sqlite3* mNativeConnection; 772 nsCOMPtr<nsITimer> mTimer; 773 nsCOMPtr<nsIFile> mDestinationFile; 774 nsCOMPtr<mozIStorageCompletionCallback> mCallback; 775 int32_t mPagesPerStep; 776 uint32_t mStepDelayMs; 777 sqlite3* mBackupFile; 778 sqlite3_backup* mBackupHandle; 779 }; 780 781 NS_IMPL_ISUPPORTS_INHERITED(AsyncBackupDatabaseFile, Runnable, nsITimerCallback) 782 783 } // namespace 784 785 //////////////////////////////////////////////////////////////////////////////// 786 //// Connection 787 788 Connection::Connection(Service* aService, int aFlags, 789 ConnectionOperation aSupportedOperations, 790 const nsCString& aTelemetryFilename, bool aInterruptible, 791 bool aIgnoreLockingMode, bool aOpenNotExclusive) 792 : sharedAsyncExecutionMutex("Connection::sharedAsyncExecutionMutex"), 793 sharedDBMutex("Connection::sharedDBMutex"), 794 eventTargetOpenedOn(WrapNotNull(GetCurrentSerialEventTarget())), 795 mIsStatementOnHelperThreadInterruptible(false), 796 mDBConn(nullptr), 797 mDefaultTransactionType(mozIStorageConnection::TRANSACTION_DEFERRED), 798 mDestroying(false), 799 mProgressHandler(nullptr), 800 mStorageService(aService), 801 mFlags(aFlags), 802 mTransactionNestingLevel(0), 803 mSupportedOperations(aSupportedOperations), 804 mInterruptible(aSupportedOperations == Connection::ASYNCHRONOUS || 805 aInterruptible), 806 mIgnoreLockingMode(aIgnoreLockingMode), 807 mOpenNotExclusive(aOpenNotExclusive), 808 mAsyncExecutionThreadShuttingDown(false), 809 mConnectionClosed(false), 810 mGrowthChunkSize(0) { 811 MOZ_ASSERT(!mIgnoreLockingMode || mFlags & SQLITE_OPEN_READONLY, 812 "Can't ignore locking for a non-readonly connection!"); 813 mStorageService->registerConnection(this); 814 MOZ_ASSERT(!aTelemetryFilename.IsEmpty(), 815 "A telemetry filename should have been passed-in."); 816 mTelemetryFilename.Assign(aTelemetryFilename); 817 } 818 819 Connection::~Connection() { 820 // Failsafe Close() occurs in our custom Release method because of 821 // complications related to Close() potentially invoking AsyncClose() which 822 // will increment our refcount. 823 MOZ_ASSERT(!mAsyncExecutionThread, 824 "The async thread has not been shutdown properly!"); 825 } 826 827 NS_IMPL_ADDREF(Connection) 828 829 NS_INTERFACE_MAP_BEGIN(Connection) 830 NS_INTERFACE_MAP_ENTRY(mozIStorageAsyncConnection) 831 NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor) 832 NS_INTERFACE_MAP_ENTRY(mozIStorageConnection) 833 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, mozIStorageConnection) 834 NS_INTERFACE_MAP_END 835 836 // This is identical to what NS_IMPL_RELEASE provides, but with the 837 // extra |1 == count| case. 838 NS_IMETHODIMP_(MozExternalRefCountType) Connection::Release(void) { 839 MOZ_ASSERT(0 != mRefCnt, "dup release"); 840 nsrefcnt count = --mRefCnt; 841 NS_LOG_RELEASE(this, count, "Connection"); 842 if (1 == count) { 843 // If the refcount went to 1, the single reference must be from 844 // gService->mConnections (in class |Service|). And the code calling 845 // Release is either: 846 // - The "user" code that had created the connection, releasing on any 847 // thread. 848 // - One of Service's getConnections() callers had acquired a strong 849 // reference to the Connection that out-lived the last "user" reference, 850 // and now that just got dropped. Note that this reference could be 851 // getting dropped on the main thread or Connection->eventTargetOpenedOn 852 // (because of the NewRunnableMethod used by minimizeMemory). 853 // 854 // Either way, we should now perform our failsafe Close() and unregister. 855 // However, we only want to do this once, and the reality is that our 856 // refcount could go back up above 1 and down again at any time if we are 857 // off the main thread and getConnections() gets called on the main thread, 858 // so we use an atomic here to do this exactly once. 859 if (mDestroying.compareExchange(false, true)) { 860 // Close the connection, dispatching to the opening event target if we're 861 // not on that event target already and that event target is still 862 // accepting runnables. We do this because it's possible we're on the main 863 // thread because of getConnections(), and we REALLY don't want to 864 // transfer I/O to the main thread if we can avoid it. 865 if (IsOnCurrentSerialEventTarget(eventTargetOpenedOn)) { 866 // This could cause SpinningSynchronousClose() to be invoked and AddRef 867 // triggered for AsyncCloseConnection's strong ref if the conn was ever 868 // use for async purposes. (Main-thread only, though.) 869 (void)synchronousClose(); 870 } else { 871 nsCOMPtr<nsIRunnable> event = 872 NewRunnableMethod("storage::Connection::synchronousClose", this, 873 &Connection::synchronousClose); 874 if (NS_FAILED(eventTargetOpenedOn->Dispatch(event.forget(), 875 NS_DISPATCH_NORMAL))) { 876 // The event target was dead and so we've just leaked our runnable. 877 // This should not happen because our non-main-thread consumers should 878 // be explicitly closing their connections, not relying on us to close 879 // them for them. (It's okay to let a statement go out of scope for 880 // automatic cleanup, but not a Connection.) 881 MOZ_ASSERT(false, 882 "Leaked Connection::synchronousClose(), ownership fail."); 883 (void)synchronousClose(); 884 } 885 } 886 887 // This will drop its strong reference right here, right now. 888 mStorageService->unregisterConnection(this); 889 } 890 } else if (0 == count) { 891 mRefCnt = 1; /* stabilize */ 892 #if 0 /* enable this to find non-threadsafe destructors: */ 893 NS_ASSERT_OWNINGTHREAD(Connection); 894 #endif 895 delete (this); 896 return 0; 897 } 898 return count; 899 } 900 901 int32_t Connection::getSqliteRuntimeStatus(int32_t aStatusOption, 902 int32_t* aMaxValue) { 903 MOZ_ASSERT(connectionReady(), "A connection must exist at this point"); 904 int curr = 0, max = 0; 905 DebugOnly<int> rc = 906 ::sqlite3_db_status(mDBConn, aStatusOption, &curr, &max, 0); 907 MOZ_ASSERT(NS_SUCCEEDED(convertResultCode(rc))); 908 if (aMaxValue) *aMaxValue = max; 909 return curr; 910 } 911 912 nsIEventTarget* Connection::getAsyncExecutionTarget() { 913 NS_ENSURE_TRUE(IsOnCurrentSerialEventTarget(eventTargetOpenedOn), nullptr); 914 915 // Don't return the asynchronous event target if we are shutting down. 916 if (mAsyncExecutionThreadShuttingDown) { 917 return nullptr; 918 } 919 920 // Create the async event target if there's none yet. 921 if (!mAsyncExecutionThread) { 922 // Names start with "sqldb:" followed by a recognizable name, like the 923 // database file name, or a specially crafted name like "memory". 924 // This name will be surfaced on https://crash-stats.mozilla.org, so any 925 // sensitive part of the file name (e.g. an URL origin) should be replaced 926 // by passing an explicit telemetryName to openDatabaseWithFileURL. 927 nsAutoCString name("sqldb:"_ns); 928 name.Append(mTelemetryFilename); 929 static nsThreadPoolNaming naming; 930 nsresult rv = NS_NewNamedThread(naming.GetNextThreadName(name), 931 getter_AddRefs(mAsyncExecutionThread)); 932 if (NS_FAILED(rv)) { 933 NS_WARNING("Failed to create async thread."); 934 return nullptr; 935 } 936 } 937 938 return mAsyncExecutionThread; 939 } 940 941 void Connection::RecordOpenStatus(nsresult rv) { 942 nsCString histogramKey = mTelemetryFilename; 943 944 if (histogramKey.IsEmpty()) { 945 histogramKey.AssignLiteral("unknown"); 946 } 947 948 if (NS_SUCCEEDED(rv)) { 949 return; 950 } 951 952 switch (rv) { 953 case NS_ERROR_FILE_CORRUPTED: 954 mozilla::glean::sqlite_store::open.Get(histogramKey, "corrupt"_ns).Add(); 955 break; 956 case NS_ERROR_STORAGE_IOERR: 957 mozilla::glean::sqlite_store::open.Get(histogramKey, "diskio"_ns).Add(); 958 break; 959 case NS_ERROR_FILE_ACCESS_DENIED: 960 case NS_ERROR_FILE_IS_LOCKED: 961 case NS_ERROR_FILE_READ_ONLY: 962 mozilla::glean::sqlite_store::open.Get(histogramKey, "access"_ns).Add(); 963 break; 964 case NS_ERROR_FILE_NO_DEVICE_SPACE: 965 mozilla::glean::sqlite_store::open.Get(histogramKey, "diskspace"_ns) 966 .Add(); 967 break; 968 default: 969 mozilla::glean::sqlite_store::open.Get(histogramKey, "failure"_ns).Add(); 970 } 971 } 972 973 void Connection::RecordQueryStatus(int srv) { 974 nsCString histogramKey = mTelemetryFilename; 975 976 if (histogramKey.IsEmpty()) { 977 histogramKey.AssignLiteral("unknown"); 978 } 979 980 switch (srv) { 981 case SQLITE_OK: 982 case SQLITE_ROW: 983 case SQLITE_DONE: 984 985 // Note that these are returned when we intentionally cancel a statement so 986 // they aren't indicating a failure. 987 case SQLITE_ABORT: 988 case SQLITE_INTERRUPT: 989 break; 990 case SQLITE_CORRUPT: 991 case SQLITE_NOTADB: 992 mozilla::glean::sqlite_store::query.Get(histogramKey, "corrupt"_ns).Add(); 993 break; 994 case SQLITE_PERM: 995 case SQLITE_CANTOPEN: 996 case SQLITE_LOCKED: 997 case SQLITE_READONLY: 998 mozilla::glean::sqlite_store::query.Get(histogramKey, "access"_ns).Add(); 999 break; 1000 case SQLITE_IOERR: 1001 case SQLITE_NOLFS: 1002 mozilla::glean::sqlite_store::query.Get(histogramKey, "diskio"_ns).Add(); 1003 break; 1004 case SQLITE_FULL: 1005 case SQLITE_TOOBIG: 1006 mozilla::glean::sqlite_store::query.Get(histogramKey, "diskspace"_ns) 1007 .Add(); 1008 break; 1009 case SQLITE_CONSTRAINT: 1010 case SQLITE_RANGE: 1011 case SQLITE_MISMATCH: 1012 case SQLITE_MISUSE: 1013 mozilla::glean::sqlite_store::query.Get(histogramKey, "misuse"_ns).Add(); 1014 break; 1015 case SQLITE_BUSY: 1016 mozilla::glean::sqlite_store::query.Get(histogramKey, "busy"_ns).Add(); 1017 break; 1018 default: 1019 mozilla::glean::sqlite_store::query.Get(histogramKey, "failure"_ns).Add(); 1020 } 1021 } 1022 1023 nsresult Connection::initialize(const nsACString& aStorageKey, 1024 const nsACString& aName) { 1025 MOZ_ASSERT(aStorageKey.Equals(kMozStorageMemoryStorageKey)); 1026 NS_ASSERTION(!connectionReady(), 1027 "Initialize called on already opened database!"); 1028 MOZ_ASSERT(!mIgnoreLockingMode, "Can't ignore locking on an in-memory db."); 1029 AUTO_PROFILER_LABEL("Connection::initialize", OTHER); 1030 1031 mStorageKey = aStorageKey; 1032 mName = aName; 1033 1034 // in memory database requested, sqlite uses a magic file name 1035 1036 const nsAutoCString path = 1037 mName.IsEmpty() ? nsAutoCString(":memory:"_ns) 1038 : "file:"_ns + mName + "?mode=memory&cache=shared"_ns; 1039 1040 int srv = ::sqlite3_open_v2(path.get(), &mDBConn, mFlags, 1041 basevfs::GetVFSName(true)); 1042 if (srv != SQLITE_OK) { 1043 ::sqlite3_close(mDBConn); 1044 mDBConn = nullptr; 1045 nsresult rv = convertResultCode(srv); 1046 RecordOpenStatus(rv); 1047 return rv; 1048 } 1049 1050 #ifdef MOZ_SQLITE_FTS3_TOKENIZER 1051 srv = 1052 ::sqlite3_db_config(mDBConn, SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER, 1, 0); 1053 MOZ_ASSERT(srv == SQLITE_OK, 1054 "SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER should be enabled"); 1055 #endif 1056 1057 // Do not set mDatabaseFile or mFileURL here since this is a "memory" 1058 // database. 1059 1060 nsresult rv = initializeInternal(); 1061 RecordOpenStatus(rv); 1062 NS_ENSURE_SUCCESS(rv, rv); 1063 1064 return NS_OK; 1065 } 1066 1067 nsresult Connection::initialize(nsIFile* aDatabaseFile) { 1068 NS_ASSERTION(aDatabaseFile, "Passed null file!"); 1069 NS_ASSERTION(!connectionReady(), 1070 "Initialize called on already opened database!"); 1071 AUTO_PROFILER_LABEL("Connection::initialize", OTHER); 1072 1073 // Do not set mFileURL here since this is database does not have an associated 1074 // URL. 1075 mDatabaseFile = aDatabaseFile; 1076 1077 nsAutoString path; 1078 nsresult rv = aDatabaseFile->GetPath(path); 1079 NS_ENSURE_SUCCESS(rv, rv); 1080 1081 bool exclusive = 1082 StaticPrefs::storage_sqlite_exclusiveLock_enabled() && !mOpenNotExclusive; 1083 int srv; 1084 if (mIgnoreLockingMode) { 1085 exclusive = false; 1086 srv = ::sqlite3_open_v2(NS_ConvertUTF16toUTF8(path).get(), &mDBConn, mFlags, 1087 "readonly-immutable-nolock"); 1088 } else { 1089 srv = ::sqlite3_open_v2(NS_ConvertUTF16toUTF8(path).get(), &mDBConn, mFlags, 1090 basevfs::GetVFSName(exclusive)); 1091 if (exclusive && (srv == SQLITE_LOCKED || srv == SQLITE_BUSY)) { 1092 ::sqlite3_close(mDBConn); 1093 // Retry without trying to get an exclusive lock. 1094 exclusive = false; 1095 srv = ::sqlite3_open_v2(NS_ConvertUTF16toUTF8(path).get(), &mDBConn, 1096 mFlags, basevfs::GetVFSName(false)); 1097 } 1098 } 1099 if (srv != SQLITE_OK) { 1100 ::sqlite3_close(mDBConn); 1101 mDBConn = nullptr; 1102 rv = convertResultCode(srv); 1103 RecordOpenStatus(rv); 1104 return rv; 1105 } 1106 1107 rv = initializeInternal(); 1108 if (exclusive && 1109 (rv == NS_ERROR_STORAGE_BUSY || rv == NS_ERROR_FILE_IS_LOCKED)) { 1110 // Usually SQLite will fail to acquire an exclusive lock on opening, but in 1111 // some cases it may successfully open the database and then lock on the 1112 // first query execution. When initializeInternal fails it closes the 1113 // connection, so we can try to restart it in non-exclusive mode. 1114 srv = ::sqlite3_open_v2(NS_ConvertUTF16toUTF8(path).get(), &mDBConn, mFlags, 1115 basevfs::GetVFSName(false)); 1116 if (srv == SQLITE_OK) { 1117 rv = initializeInternal(); 1118 } else { 1119 ::sqlite3_close(mDBConn); 1120 mDBConn = nullptr; 1121 } 1122 } 1123 1124 RecordOpenStatus(rv); 1125 NS_ENSURE_SUCCESS(rv, rv); 1126 1127 return NS_OK; 1128 } 1129 1130 nsresult Connection::initialize(nsIFileURL* aFileURL) { 1131 NS_ASSERTION(aFileURL, "Passed null file URL!"); 1132 NS_ASSERTION(!connectionReady(), 1133 "Initialize called on already opened database!"); 1134 AUTO_PROFILER_LABEL("Connection::initialize", OTHER); 1135 1136 nsCOMPtr<nsIFile> databaseFile; 1137 nsresult rv = aFileURL->GetFile(getter_AddRefs(databaseFile)); 1138 NS_ENSURE_SUCCESS(rv, rv); 1139 1140 // Set both mDatabaseFile and mFileURL here. 1141 mFileURL = aFileURL; 1142 mDatabaseFile = databaseFile; 1143 1144 nsAutoCString spec; 1145 rv = aFileURL->GetSpec(spec); 1146 NS_ENSURE_SUCCESS(rv, rv); 1147 1148 // If there is a key specified, we need to use the obfuscating VFS. 1149 nsAutoCString query; 1150 rv = aFileURL->GetQuery(query); 1151 NS_ENSURE_SUCCESS(rv, rv); 1152 1153 bool hasKey = false; 1154 bool hasDirectoryLockId = false; 1155 1156 MOZ_ALWAYS_TRUE( 1157 URLParams::Parse(query, true, 1158 [&hasKey, &hasDirectoryLockId]( 1159 const nsACString& aName, const nsACString& aValue) { 1160 if (aName.EqualsLiteral("key")) { 1161 hasKey = true; 1162 return true; 1163 } 1164 if (aName.EqualsLiteral("directoryLockId")) { 1165 hasDirectoryLockId = true; 1166 return true; 1167 } 1168 return true; 1169 })); 1170 1171 bool exclusive = 1172 StaticPrefs::storage_sqlite_exclusiveLock_enabled() && !mOpenNotExclusive; 1173 1174 const char* const vfs = hasKey ? obfsvfs::GetVFSName() 1175 : hasDirectoryLockId ? quotavfs::GetVFSName() 1176 : basevfs::GetVFSName(exclusive); 1177 1178 int srv = ::sqlite3_open_v2(spec.get(), &mDBConn, mFlags, vfs); 1179 if (srv != SQLITE_OK) { 1180 ::sqlite3_close(mDBConn); 1181 mDBConn = nullptr; 1182 rv = convertResultCode(srv); 1183 RecordOpenStatus(rv); 1184 return rv; 1185 } 1186 1187 rv = initializeInternal(); 1188 RecordOpenStatus(rv); 1189 NS_ENSURE_SUCCESS(rv, rv); 1190 1191 return NS_OK; 1192 } 1193 1194 nsresult Connection::initializeInternal() { 1195 MOZ_ASSERT(mDBConn); 1196 auto guard = MakeScopeExit([&]() { initializeFailed(); }); 1197 1198 mConnectionClosed = false; 1199 1200 #ifdef MOZ_SQLITE_FTS3_TOKENIZER 1201 DebugOnly<int> srv2 = 1202 ::sqlite3_db_config(mDBConn, SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER, 1, 0); 1203 MOZ_ASSERT(srv2 == SQLITE_OK, 1204 "SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER should be enabled"); 1205 #endif 1206 1207 // Properly wrap the database handle's mutex. 1208 sharedDBMutex.initWithMutex(sqlite3_db_mutex(mDBConn)); 1209 1210 // SQLite tracing can slow down queries (especially long queries) 1211 // significantly. Don't trace unless the user is actively monitoring SQLite. 1212 if (MOZ_LOG_TEST(gStorageLog, LogLevel::Debug)) { 1213 ::sqlite3_trace_v2(mDBConn, SQLITE_TRACE_STMT | SQLITE_TRACE_PROFILE, 1214 tracefunc, this); 1215 1216 MOZ_LOG( 1217 gStorageLog, LogLevel::Debug, 1218 ("Opening connection to '%s' (%p)", mTelemetryFilename.get(), this)); 1219 } 1220 1221 int64_t pageSize = Service::kDefaultPageSize; 1222 1223 // Set page_size to the preferred default value. This is effective only if 1224 // the database has just been created, otherwise, if the database does not 1225 // use WAL journal mode, a VACUUM operation will updated its page_size. 1226 nsAutoCString pageSizeQuery(MOZ_STORAGE_UNIQUIFY_QUERY_STR 1227 "PRAGMA page_size = "); 1228 pageSizeQuery.AppendInt(pageSize); 1229 int srv = executeSql(mDBConn, pageSizeQuery.get()); 1230 if (srv != SQLITE_OK) { 1231 return convertResultCode(srv); 1232 } 1233 1234 // Setting the cache_size forces the database open, verifying if it is valid 1235 // or corrupt. So this is executed regardless it being actually needed. 1236 // The cache_size is calculated from the actual page_size, to save memory. 1237 nsAutoCString cacheSizeQuery(MOZ_STORAGE_UNIQUIFY_QUERY_STR 1238 "PRAGMA cache_size = "); 1239 cacheSizeQuery.AppendInt(-MAX_CACHE_SIZE_KIBIBYTES); 1240 srv = executeSql(mDBConn, cacheSizeQuery.get()); 1241 if (srv != SQLITE_OK) { 1242 return convertResultCode(srv); 1243 } 1244 1245 // Register our built-in SQL functions. 1246 srv = registerFunctions(mDBConn); 1247 if (srv != SQLITE_OK) { 1248 return convertResultCode(srv); 1249 } 1250 1251 // Register our built-in SQL collating sequences. 1252 srv = registerCollations(mDBConn, mStorageService); 1253 if (srv != SQLITE_OK) { 1254 return convertResultCode(srv); 1255 } 1256 1257 // Set the default synchronous value. Each consumer can switch this 1258 // accordingly to their needs. 1259 #if defined(ANDROID) 1260 // Android prefers synchronous = OFF for performance reasons. 1261 (void)ExecuteSimpleSQL("PRAGMA synchronous = OFF;"_ns); 1262 #else 1263 // Normal is the suggested value for WAL journals. 1264 (void)ExecuteSimpleSQL("PRAGMA synchronous = NORMAL;"_ns); 1265 #endif 1266 1267 // Initialization succeeded, we can stop guarding for failures. 1268 guard.release(); 1269 return NS_OK; 1270 } 1271 1272 nsresult Connection::initializeOnAsyncThread(nsIFile* aStorageFile) { 1273 MOZ_ASSERT(!IsOnCurrentSerialEventTarget(eventTargetOpenedOn)); 1274 nsresult rv = aStorageFile 1275 ? initialize(aStorageFile) 1276 : initialize(kMozStorageMemoryStorageKey, VoidCString()); 1277 if (NS_FAILED(rv)) { 1278 // Shutdown the async thread, since initialization failed. 1279 MutexAutoLock lockedScope(sharedAsyncExecutionMutex); 1280 mAsyncExecutionThreadShuttingDown = true; 1281 nsCOMPtr<nsIRunnable> event = 1282 NewRunnableMethod("Connection::shutdownAsyncThread", this, 1283 &Connection::shutdownAsyncThread); 1284 (void)NS_DispatchToMainThread(event); 1285 } 1286 return rv; 1287 } 1288 1289 void Connection::initializeFailed() { 1290 { 1291 MutexAutoLock lockedScope(sharedAsyncExecutionMutex); 1292 mConnectionClosed = true; 1293 } 1294 MOZ_ALWAYS_TRUE(::sqlite3_close(mDBConn) == SQLITE_OK); 1295 mDBConn = nullptr; 1296 sharedDBMutex.destroy(); 1297 } 1298 1299 nsresult Connection::databaseElementExists( 1300 enum DatabaseElementType aElementType, const nsACString& aElementName, 1301 bool* _exists) { 1302 if (!connectionReady()) { 1303 return NS_ERROR_NOT_AVAILABLE; 1304 } 1305 nsresult rv = ensureOperationSupported(SYNCHRONOUS); 1306 if (NS_FAILED(rv)) { 1307 return rv; 1308 } 1309 1310 // When constructing the query, make sure to SELECT the correct db's 1311 // sqlite_master if the user is prefixing the element with a specific db. ex: 1312 // sample.test 1313 nsCString query("SELECT name FROM (SELECT * FROM "); 1314 nsDependentCSubstring element; 1315 int32_t ind = aElementName.FindChar('.'); 1316 if (ind == kNotFound) { 1317 element.Assign(aElementName); 1318 } else { 1319 nsDependentCSubstring db(Substring(aElementName, 0, ind + 1)); 1320 element.Assign(Substring(aElementName, ind + 1, aElementName.Length())); 1321 query.Append(db); 1322 } 1323 query.AppendLiteral( 1324 "sqlite_master UNION ALL SELECT * FROM sqlite_temp_master) WHERE type = " 1325 "'"); 1326 1327 switch (aElementType) { 1328 case INDEX: 1329 query.AppendLiteral("index"); 1330 break; 1331 case TABLE: 1332 query.AppendLiteral("table"); 1333 break; 1334 } 1335 query.AppendLiteral("' AND name ='"); 1336 query.Append(element); 1337 query.Append('\''); 1338 1339 sqlite3_stmt* stmt; 1340 int srv = prepareStatement(mDBConn, query, &stmt); 1341 if (srv != SQLITE_OK) { 1342 RecordQueryStatus(srv); 1343 return convertResultCode(srv); 1344 } 1345 1346 srv = stepStatement(mDBConn, stmt); 1347 // we just care about the return value from step 1348 (void)::sqlite3_finalize(stmt); 1349 1350 RecordQueryStatus(srv); 1351 1352 if (srv == SQLITE_ROW) { 1353 *_exists = true; 1354 return NS_OK; 1355 } 1356 if (srv == SQLITE_DONE) { 1357 *_exists = false; 1358 return NS_OK; 1359 } 1360 1361 return convertResultCode(srv); 1362 } 1363 1364 bool Connection::findFunctionByInstance(mozIStorageFunction* aInstance) { 1365 sharedDBMutex.assertCurrentThreadOwns(); 1366 1367 for (const auto& data : mFunctions.Values()) { 1368 if (data.function == aInstance) { 1369 return true; 1370 } 1371 } 1372 return false; 1373 } 1374 1375 /* static */ 1376 int Connection::sProgressHelper(void* aArg) { 1377 Connection* _this = static_cast<Connection*>(aArg); 1378 return _this->progressHandler(); 1379 } 1380 1381 int Connection::progressHandler() { 1382 sharedDBMutex.assertCurrentThreadOwns(); 1383 if (mProgressHandler) { 1384 bool result; 1385 nsresult rv = mProgressHandler->OnProgress(this, &result); 1386 if (NS_FAILED(rv)) return 0; // Don't break request 1387 return result ? 1 : 0; 1388 } 1389 return 0; 1390 } 1391 1392 nsresult Connection::setClosedState() { 1393 // Flag that we are shutting down the async thread, so that 1394 // getAsyncExecutionTarget knows not to expose/create the async thread. 1395 MutexAutoLock lockedScope(sharedAsyncExecutionMutex); 1396 NS_ENSURE_FALSE(mAsyncExecutionThreadShuttingDown, NS_ERROR_UNEXPECTED); 1397 1398 mAsyncExecutionThreadShuttingDown = true; 1399 1400 // Set the property to null before closing the connection, otherwise the 1401 // other functions in the module may try to use the connection after it is 1402 // closed. 1403 mDBConn = nullptr; 1404 1405 return NS_OK; 1406 } 1407 1408 bool Connection::operationSupported(ConnectionOperation aOperationType) { 1409 if (aOperationType == ASYNCHRONOUS) { 1410 // Async operations are supported for all connections, on any thread. 1411 return true; 1412 } 1413 // Sync operations are supported for sync connections (on any thread), and 1414 // async connections on a background thread. 1415 MOZ_ASSERT(aOperationType == SYNCHRONOUS); 1416 return mSupportedOperations == SYNCHRONOUS || !NS_IsMainThread(); 1417 } 1418 1419 nsresult Connection::ensureOperationSupported( 1420 ConnectionOperation aOperationType) { 1421 if (NS_WARN_IF(!operationSupported(aOperationType))) { 1422 #ifdef DEBUG 1423 if (NS_IsMainThread()) { 1424 nsCOMPtr<nsIXPConnect> xpc = nsIXPConnect::XPConnect(); 1425 (void)xpc->DebugDumpJSStack(false, false, false); 1426 } 1427 #endif 1428 MOZ_ASSERT(false, 1429 "Don't use async connections synchronously on the main thread"); 1430 return NS_ERROR_NOT_AVAILABLE; 1431 } 1432 return NS_OK; 1433 } 1434 1435 bool Connection::isConnectionReadyOnThisThread() { 1436 MOZ_ASSERT_IF(connectionReady(), !mConnectionClosed); 1437 if (mAsyncExecutionThread && mAsyncExecutionThread->IsOnCurrentThread()) { 1438 return true; 1439 } 1440 return connectionReady(); 1441 } 1442 1443 bool Connection::isClosing() { 1444 MutexAutoLock lockedScope(sharedAsyncExecutionMutex); 1445 return mAsyncExecutionThreadShuttingDown && !mConnectionClosed; 1446 } 1447 1448 bool Connection::isClosed() { 1449 MutexAutoLock lockedScope(sharedAsyncExecutionMutex); 1450 return mConnectionClosed; 1451 } 1452 1453 bool Connection::isClosed(MutexAutoLock& lock) { return mConnectionClosed; } 1454 1455 bool Connection::isAsyncExecutionThreadAvailable() { 1456 MOZ_ASSERT(IsOnCurrentSerialEventTarget(eventTargetOpenedOn)); 1457 return mAsyncExecutionThread && !mAsyncExecutionThreadShuttingDown; 1458 } 1459 1460 void Connection::shutdownAsyncThread() { 1461 MOZ_ASSERT(IsOnCurrentSerialEventTarget(eventTargetOpenedOn)); 1462 MOZ_ASSERT(mAsyncExecutionThread); 1463 MOZ_ASSERT(mAsyncExecutionThreadShuttingDown); 1464 1465 MOZ_ALWAYS_SUCCEEDS(mAsyncExecutionThread->Shutdown()); 1466 mAsyncExecutionThread = nullptr; 1467 } 1468 1469 nsresult Connection::internalClose(sqlite3* aNativeConnection) { 1470 #ifdef DEBUG 1471 { // Make sure we have marked our async thread as shutting down. 1472 MutexAutoLock lockedScope(sharedAsyncExecutionMutex); 1473 MOZ_ASSERT(mAsyncExecutionThreadShuttingDown, 1474 "Did not call setClosedState!"); 1475 MOZ_ASSERT(!isClosed(lockedScope), "Unexpected closed state"); 1476 } 1477 #endif // DEBUG 1478 1479 if (MOZ_LOG_TEST(gStorageLog, LogLevel::Debug)) { 1480 nsAutoCString leafName(":memory"); 1481 if (mDatabaseFile) (void)mDatabaseFile->GetNativeLeafName(leafName); 1482 MOZ_LOG(gStorageLog, LogLevel::Debug, 1483 ("Closing connection to '%s'", leafName.get())); 1484 } 1485 1486 // At this stage, we may still have statements that need to be 1487 // finalized. Attempt to close the database connection. This will 1488 // always disconnect any virtual tables and cleanly finalize their 1489 // internal statements. Once this is done, closing may fail due to 1490 // unfinalized client statements, in which case we need to finalize 1491 // these statements and close again. 1492 { 1493 MutexAutoLock lockedScope(sharedAsyncExecutionMutex); 1494 mConnectionClosed = true; 1495 } 1496 1497 // Nothing else needs to be done if we don't have a connection here. 1498 if (!aNativeConnection) return NS_OK; 1499 1500 int srv = ::sqlite3_close(aNativeConnection); 1501 1502 if (srv == SQLITE_BUSY) { 1503 { 1504 // Nothing else should change the connection or statements status until we 1505 // are done here. 1506 SQLiteMutexAutoLock lockedScope(sharedDBMutex); 1507 // We still have non-finalized statements. Finalize them. 1508 sqlite3_stmt* stmt = nullptr; 1509 while ((stmt = ::sqlite3_next_stmt(aNativeConnection, stmt))) { 1510 MOZ_LOG(gStorageLog, LogLevel::Debug, 1511 ("Auto-finalizing SQL statement '%s' (%p)", ::sqlite3_sql(stmt), 1512 stmt)); 1513 1514 #ifdef DEBUG 1515 SmprintfPointer msg = ::mozilla::Smprintf( 1516 "SQL statement '%s' (%p) should have been finalized before closing " 1517 "the connection", 1518 ::sqlite3_sql(stmt), stmt); 1519 NS_WARNING(msg.get()); 1520 #endif // DEBUG 1521 1522 srv = ::sqlite3_finalize(stmt); 1523 1524 #ifdef DEBUG 1525 if (srv != SQLITE_OK) { 1526 SmprintfPointer msg = ::mozilla::Smprintf( 1527 "Could not finalize SQL statement (%p)", stmt); 1528 NS_WARNING(msg.get()); 1529 } 1530 #endif // DEBUG 1531 1532 // Ensure that the loop continues properly, whether closing has 1533 // succeeded or not. 1534 if (srv == SQLITE_OK) { 1535 stmt = nullptr; 1536 } 1537 } 1538 // Scope exiting will unlock the mutex before we invoke sqlite3_close() 1539 // again, since Sqlite will try to acquire it. 1540 } 1541 1542 // Now that all statements have been finalized, we 1543 // should be able to close. 1544 srv = ::sqlite3_close(aNativeConnection); 1545 MOZ_ASSERT(false, 1546 "Had to forcibly close the database connection because not all " 1547 "the statements have been finalized."); 1548 } 1549 1550 if (srv == SQLITE_OK) { 1551 sharedDBMutex.destroy(); 1552 } else { 1553 MOZ_ASSERT(false, 1554 "sqlite3_close failed. There are probably outstanding " 1555 "statements that are listed above!"); 1556 } 1557 1558 return convertResultCode(srv); 1559 } 1560 1561 nsCString Connection::getFilename() { return mTelemetryFilename; } 1562 1563 int Connection::stepStatement(sqlite3* aNativeConnection, 1564 sqlite3_stmt* aStatement) { 1565 MOZ_ASSERT(aStatement); 1566 1567 AUTO_PROFILER_LABEL_DYNAMIC_CSTR("Connection::stepStatement", OTHER, 1568 ::sqlite3_sql(aStatement)); 1569 1570 bool checkedMainThread = false; 1571 TimeStamp startTime = TimeStamp::Now(); 1572 1573 // The connection may have been closed if the executing statement has been 1574 // created and cached after a call to asyncClose() but before the actual 1575 // sqlite3_close(). This usually happens when other tasks using cached 1576 // statements are asynchronously scheduled for execution and any of them ends 1577 // up after asyncClose. See bug 728653 for details. 1578 if (!isConnectionReadyOnThisThread()) return SQLITE_MISUSE; 1579 1580 (void)::sqlite3_extended_result_codes(aNativeConnection, 1); 1581 1582 int srv; 1583 while ((srv = ::sqlite3_step(aStatement)) == SQLITE_LOCKED_SHAREDCACHE) { 1584 if (!checkedMainThread) { 1585 checkedMainThread = true; 1586 if (::NS_IsMainThread()) { 1587 NS_WARNING("We won't allow blocking on the main thread!"); 1588 break; 1589 } 1590 } 1591 1592 srv = WaitForUnlockNotify(aNativeConnection); 1593 if (srv != SQLITE_OK) { 1594 break; 1595 } 1596 1597 ::sqlite3_reset(aStatement); 1598 } 1599 1600 // Report very slow SQL statements to Telemetry 1601 TimeDuration duration = TimeStamp::Now() - startTime; 1602 const uint32_t threshold = NS_IsMainThread() 1603 ? Telemetry::kSlowSQLThresholdForMainThread 1604 : Telemetry::kSlowSQLThresholdForHelperThreads; 1605 if (duration.ToMilliseconds() >= threshold) { 1606 nsDependentCString statementString(::sqlite3_sql(aStatement)); 1607 Telemetry::RecordSlowSQLStatement( 1608 statementString, mTelemetryFilename, 1609 static_cast<uint32_t>(duration.ToMilliseconds())); 1610 } 1611 1612 (void)::sqlite3_extended_result_codes(aNativeConnection, 0); 1613 // Drop off the extended result bits of the result code. 1614 return srv & 0xFF; 1615 } 1616 1617 int Connection::prepareStatement(sqlite3* aNativeConnection, 1618 const nsCString& aSQL, sqlite3_stmt** _stmt) { 1619 // We should not even try to prepare statements after the connection has 1620 // been closed. 1621 if (!isConnectionReadyOnThisThread()) return SQLITE_MISUSE; 1622 1623 bool checkedMainThread = false; 1624 1625 (void)::sqlite3_extended_result_codes(aNativeConnection, 1); 1626 1627 int srv; 1628 while ((srv = ::sqlite3_prepare_v2(aNativeConnection, aSQL.get(), -1, _stmt, 1629 nullptr)) == SQLITE_LOCKED_SHAREDCACHE) { 1630 if (!checkedMainThread) { 1631 checkedMainThread = true; 1632 if (::NS_IsMainThread()) { 1633 NS_WARNING("We won't allow blocking on the main thread!"); 1634 break; 1635 } 1636 } 1637 1638 srv = WaitForUnlockNotify(aNativeConnection); 1639 if (srv != SQLITE_OK) { 1640 break; 1641 } 1642 } 1643 1644 if (srv != SQLITE_OK) { 1645 nsCString warnMsg; 1646 warnMsg.AppendLiteral("The SQL statement '"); 1647 warnMsg.Append(aSQL); 1648 warnMsg.AppendLiteral("' could not be compiled due to an error: "); 1649 warnMsg.Append(::sqlite3_errmsg(aNativeConnection)); 1650 1651 #ifdef DEBUG 1652 NS_WARNING(warnMsg.get()); 1653 #endif 1654 MOZ_LOG(gStorageLog, LogLevel::Error, ("%s", warnMsg.get())); 1655 } 1656 1657 (void)::sqlite3_extended_result_codes(aNativeConnection, 0); 1658 // Drop off the extended result bits of the result code. 1659 int rc = srv & 0xFF; 1660 // sqlite will return OK on a comment only string and set _stmt to nullptr. 1661 // The callers of this function are used to only checking the return value, 1662 // so it is safer to return an error code. 1663 if (rc == SQLITE_OK && *_stmt == nullptr) { 1664 return SQLITE_MISUSE; 1665 } 1666 1667 return rc; 1668 } 1669 1670 int Connection::executeSql(sqlite3* aNativeConnection, const char* aSqlString) { 1671 if (!isConnectionReadyOnThisThread()) return SQLITE_MISUSE; 1672 1673 AUTO_PROFILER_LABEL_DYNAMIC_CSTR("Connection::executeSql", OTHER, aSqlString); 1674 1675 TimeStamp startTime = TimeStamp::Now(); 1676 int srv = 1677 ::sqlite3_exec(aNativeConnection, aSqlString, nullptr, nullptr, nullptr); 1678 RecordQueryStatus(srv); 1679 1680 // Report very slow SQL statements to Telemetry 1681 TimeDuration duration = TimeStamp::Now() - startTime; 1682 const uint32_t threshold = NS_IsMainThread() 1683 ? Telemetry::kSlowSQLThresholdForMainThread 1684 : Telemetry::kSlowSQLThresholdForHelperThreads; 1685 if (duration.ToMilliseconds() >= threshold) { 1686 nsDependentCString statementString(aSqlString); 1687 Telemetry::RecordSlowSQLStatement( 1688 statementString, mTelemetryFilename, 1689 static_cast<uint32_t>(duration.ToMilliseconds())); 1690 } 1691 1692 return srv; 1693 } 1694 1695 //////////////////////////////////////////////////////////////////////////////// 1696 //// nsIInterfaceRequestor 1697 1698 NS_IMETHODIMP 1699 Connection::GetInterface(const nsIID& aIID, void** _result) { 1700 if (aIID.Equals(NS_GET_IID(nsIEventTarget))) { 1701 nsIEventTarget* background = getAsyncExecutionTarget(); 1702 NS_IF_ADDREF(background); 1703 *_result = background; 1704 return NS_OK; 1705 } 1706 return NS_ERROR_NO_INTERFACE; 1707 } 1708 1709 //////////////////////////////////////////////////////////////////////////////// 1710 //// mozIStorageConnection 1711 1712 NS_IMETHODIMP 1713 Connection::Close() { 1714 nsresult rv = ensureOperationSupported(SYNCHRONOUS); 1715 if (NS_FAILED(rv)) { 1716 return rv; 1717 } 1718 return synchronousClose(); 1719 } 1720 1721 nsresult Connection::synchronousClose() { 1722 if (!connectionReady()) { 1723 return NS_ERROR_NOT_INITIALIZED; 1724 } 1725 1726 #ifdef DEBUG 1727 // Since we're accessing mAsyncExecutionThread, we need to be on the opener 1728 // event target. We make this check outside of debug code below in 1729 // setClosedState, but this is here to be explicit. 1730 MOZ_ASSERT(IsOnCurrentSerialEventTarget(eventTargetOpenedOn)); 1731 #endif // DEBUG 1732 1733 // Make sure we have not executed any asynchronous statements. 1734 // If this fails, the mDBConn may be left open, resulting in a leak. 1735 // We'll try to finalize the pending statements and close the connection. 1736 if (isAsyncExecutionThreadAvailable()) { 1737 #ifdef DEBUG 1738 if (NS_IsMainThread()) { 1739 nsCOMPtr<nsIXPConnect> xpc = nsIXPConnect::XPConnect(); 1740 (void)xpc->DebugDumpJSStack(false, false, false); 1741 } 1742 #endif 1743 MOZ_ASSERT(false, 1744 "Close() was invoked on a connection that executed asynchronous " 1745 "statements. " 1746 "Should have used asyncClose()."); 1747 // Try to close the database regardless, to free up resources. 1748 (void)SpinningSynchronousClose(); 1749 return NS_ERROR_UNEXPECTED; 1750 } 1751 1752 // setClosedState nullifies our connection pointer, so we take a raw pointer 1753 // off it, to pass it through the close procedure. 1754 sqlite3* nativeConn = mDBConn; 1755 nsresult rv = setClosedState(); 1756 NS_ENSURE_SUCCESS(rv, rv); 1757 1758 return internalClose(nativeConn); 1759 } 1760 1761 NS_IMETHODIMP 1762 Connection::SpinningSynchronousClose() { 1763 nsresult rv = ensureOperationSupported(SYNCHRONOUS); 1764 if (NS_FAILED(rv)) { 1765 return rv; 1766 } 1767 if (!IsOnCurrentSerialEventTarget(eventTargetOpenedOn)) { 1768 return NS_ERROR_NOT_SAME_THREAD; 1769 } 1770 1771 // As currently implemented, we can't spin to wait for an existing AsyncClose. 1772 // Our only existing caller will never have called close; assert if misused 1773 // so that no new callers assume this works after an AsyncClose. 1774 MOZ_DIAGNOSTIC_ASSERT(connectionReady()); 1775 if (!connectionReady()) { 1776 return NS_ERROR_UNEXPECTED; 1777 } 1778 1779 RefPtr<CloseListener> listener = new CloseListener(); 1780 rv = AsyncClose(listener); 1781 NS_ENSURE_SUCCESS(rv, rv); 1782 MOZ_ALWAYS_TRUE( 1783 SpinEventLoopUntil("storage::Connection::SpinningSynchronousClose"_ns, 1784 [&]() { return listener->mClosed; })); 1785 MOZ_ASSERT(isClosed(), "The connection should be closed at this point"); 1786 1787 return rv; 1788 } 1789 1790 NS_IMETHODIMP 1791 Connection::AsyncClose(mozIStorageCompletionCallback* aCallback) { 1792 NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_NOT_SAME_THREAD); 1793 // Check if AsyncClose or Close were already invoked. 1794 if (!connectionReady()) { 1795 return NS_ERROR_NOT_INITIALIZED; 1796 } 1797 nsresult rv = ensureOperationSupported(ASYNCHRONOUS); 1798 if (NS_FAILED(rv)) { 1799 return rv; 1800 } 1801 1802 // The two relevant factors at this point are whether we have a database 1803 // connection and whether we have an async execution thread. Here's what the 1804 // states mean and how we handle them: 1805 // 1806 // - (mDBConn && asyncThread): The expected case where we are either an 1807 // async connection or a sync connection that has been used asynchronously. 1808 // Either way the caller must call us and not Close(). Nothing surprising 1809 // about this. We'll dispatch AsyncCloseConnection to the already-existing 1810 // async thread. 1811 // 1812 // - (mDBConn && !asyncThread): A somewhat unusual case where the caller 1813 // opened the connection synchronously and was planning to use it 1814 // asynchronously, but never got around to using it asynchronously before 1815 // needing to shutdown. This has been observed to happen for the cookie 1816 // service in a case where Firefox shuts itself down almost immediately 1817 // after startup (for unknown reasons). In the Firefox shutdown case, 1818 // we may also fail to create a new async execution thread if one does not 1819 // already exist. (nsThreadManager will refuse to create new threads when 1820 // it has already been told to shutdown.) As such, we need to handle a 1821 // failure to create the async execution thread by falling back to 1822 // synchronous Close() and also dispatching the completion callback because 1823 // at least Places likes to spin a nested event loop that depends on the 1824 // callback being invoked. 1825 // 1826 // Note that we have considered not trying to spin up the async execution 1827 // thread in this case if it does not already exist, but the overhead of 1828 // thread startup (if successful) is significantly less expensive than the 1829 // worst-case potential I/O hit of synchronously closing a database when we 1830 // could close it asynchronously. 1831 // 1832 // - (!mDBConn && asyncThread): This happens in some but not all cases where 1833 // OpenAsyncDatabase encountered a problem opening the database. If it 1834 // happened in all cases AsyncInitDatabase would just shut down the thread 1835 // directly and we would avoid this case. But it doesn't, so for simplicity 1836 // and consistency AsyncCloseConnection knows how to handle this and we 1837 // act like this was the (mDBConn && asyncThread) case in this method. 1838 // 1839 // - (!mDBConn && !asyncThread): The database was never successfully opened or 1840 // Close() or AsyncClose() has already been called (at least) once. This is 1841 // undeniably a misuse case by the caller. We could optimize for this 1842 // case by adding an additional check of mAsyncExecutionThread without using 1843 // getAsyncExecutionTarget() to avoid wastefully creating a thread just to 1844 // shut it down. But this complicates the method for broken caller code 1845 // whereas we're still correct and safe without the special-case. 1846 nsIEventTarget* asyncThread = getAsyncExecutionTarget(); 1847 1848 // Create our callback event if we were given a callback. This will 1849 // eventually be dispatched in all cases, even if we fall back to Close() and 1850 // the database wasn't open and we return an error. The rationale is that 1851 // no existing consumer checks our return value and several of them like to 1852 // spin nested event loops until the callback fires. Given that, it seems 1853 // preferable for us to dispatch the callback in all cases. (Except the 1854 // wrong thread misuse case we bailed on up above. But that's okay because 1855 // that is statically wrong whereas these edge cases are dynamic.) 1856 nsCOMPtr<nsIRunnable> completeEvent; 1857 if (aCallback) { 1858 completeEvent = newCompletionEvent(aCallback); 1859 } 1860 1861 if (!asyncThread) { 1862 // We were unable to create an async thread, so we need to fall back to 1863 // using normal Close(). Since there is no async thread, Close() will 1864 // not complain about that. (Close() may, however, complain if the 1865 // connection is closed, but that's okay.) 1866 if (completeEvent) { 1867 // Closing the database is more important than returning an error code 1868 // about a failure to dispatch, especially because all existing native 1869 // callers ignore our return value. 1870 (void)NS_DispatchToMainThread(completeEvent.forget()); 1871 } 1872 MOZ_ALWAYS_SUCCEEDS(synchronousClose()); 1873 // Return a success inconditionally here, since Close() is unlikely to fail 1874 // and we want to reassure the consumer that its callback will be invoked. 1875 return NS_OK; 1876 } 1877 1878 // If we're closing the connection during shutdown, and there is an 1879 // interruptible statement running on the helper thread, issue a 1880 // sqlite3_interrupt() to avoid crashing when that statement takes a long 1881 // time (for example a vacuum). 1882 if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed) && 1883 mInterruptible && mIsStatementOnHelperThreadInterruptible) { 1884 MOZ_ASSERT(!isClosing(), "Must not be closing, see Interrupt()"); 1885 DebugOnly<nsresult> rv2 = Interrupt(); 1886 MOZ_ASSERT(NS_SUCCEEDED(rv2)); 1887 } 1888 1889 // setClosedState nullifies our connection pointer, so we take a raw pointer 1890 // off it, to pass it through the close procedure. 1891 sqlite3* nativeConn = mDBConn; 1892 rv = setClosedState(); 1893 NS_ENSURE_SUCCESS(rv, rv); 1894 1895 // Create and dispatch our close event to the background thread. 1896 nsCOMPtr<nsIRunnable> closeEvent = 1897 new AsyncCloseConnection(this, nativeConn, completeEvent); 1898 rv = asyncThread->Dispatch(closeEvent, NS_DISPATCH_NORMAL); 1899 NS_ENSURE_SUCCESS(rv, rv); 1900 1901 return NS_OK; 1902 } 1903 1904 NS_IMETHODIMP 1905 Connection::AsyncClone(bool aReadOnly, 1906 mozIStorageCompletionCallback* aCallback) { 1907 AUTO_PROFILER_LABEL("Connection::AsyncClone", OTHER); 1908 1909 NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_NOT_SAME_THREAD); 1910 if (!connectionReady()) { 1911 return NS_ERROR_NOT_INITIALIZED; 1912 } 1913 nsresult rv = ensureOperationSupported(ASYNCHRONOUS); 1914 if (NS_FAILED(rv)) { 1915 return rv; 1916 } 1917 if (!mDatabaseFile) return NS_ERROR_UNEXPECTED; 1918 1919 int flags = mFlags; 1920 if (aReadOnly) { 1921 // Turn off SQLITE_OPEN_READWRITE, and set SQLITE_OPEN_READONLY. 1922 flags = (~SQLITE_OPEN_READWRITE & flags) | SQLITE_OPEN_READONLY; 1923 // Turn off SQLITE_OPEN_CREATE. 1924 flags = (~SQLITE_OPEN_CREATE & flags); 1925 } 1926 1927 // The cloned connection will still implement the synchronous API, but throw 1928 // if any synchronous methods are called on the main thread. 1929 RefPtr<Connection> clone = 1930 new Connection(mStorageService, flags, ASYNCHRONOUS, mTelemetryFilename, 1931 mInterruptible, mIgnoreLockingMode, mOpenNotExclusive); 1932 1933 RefPtr<AsyncInitializeClone> initEvent = 1934 new AsyncInitializeClone(this, clone, aReadOnly, aCallback); 1935 // Dispatch to our async thread, since the originating connection must remain 1936 // valid and open for the whole cloning process. This also ensures we are 1937 // properly serialized with a `close` operation, rather than race with it. 1938 nsCOMPtr<nsIEventTarget> target = getAsyncExecutionTarget(); 1939 if (!target) { 1940 return NS_ERROR_UNEXPECTED; 1941 } 1942 return target->Dispatch(initEvent, NS_DISPATCH_NORMAL); 1943 } 1944 1945 nsresult Connection::initializeClone(Connection* aClone, bool aReadOnly) { 1946 nsresult rv; 1947 if (!mStorageKey.IsEmpty()) { 1948 rv = aClone->initialize(mStorageKey, mName); 1949 } else if (mFileURL) { 1950 rv = aClone->initialize(mFileURL); 1951 } else { 1952 rv = aClone->initialize(mDatabaseFile); 1953 } 1954 if (NS_FAILED(rv)) { 1955 return rv; 1956 } 1957 1958 auto guard = MakeScopeExit([&]() { aClone->initializeFailed(); }); 1959 1960 rv = aClone->SetDefaultTransactionType(mDefaultTransactionType); 1961 NS_ENSURE_SUCCESS(rv, rv); 1962 1963 // Re-attach on-disk databases that were attached to the original connection. 1964 { 1965 nsCOMPtr<mozIStorageStatement> stmt; 1966 rv = CreateStatement("PRAGMA database_list"_ns, getter_AddRefs(stmt)); 1967 NS_ENSURE_SUCCESS(rv, rv); 1968 bool hasResult = false; 1969 while (stmt && NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) { 1970 nsAutoCString name; 1971 rv = stmt->GetUTF8String(1, name); 1972 if (NS_SUCCEEDED(rv) && !name.EqualsLiteral("main") && 1973 !name.EqualsLiteral("temp")) { 1974 nsCString path; 1975 rv = stmt->GetUTF8String(2, path); 1976 if (NS_SUCCEEDED(rv) && !path.IsEmpty()) { 1977 nsCOMPtr<mozIStorageStatement> attachStmt; 1978 rv = aClone->CreateStatement("ATTACH DATABASE :path AS "_ns + name, 1979 getter_AddRefs(attachStmt)); 1980 NS_ENSURE_SUCCESS(rv, rv); 1981 rv = attachStmt->BindUTF8StringByName("path"_ns, path); 1982 NS_ENSURE_SUCCESS(rv, rv); 1983 rv = attachStmt->Execute(); 1984 NS_ENSURE_SUCCESS(rv, rv); 1985 } 1986 } 1987 } 1988 } 1989 1990 // Copy over pragmas from the original connection. 1991 // LIMITATION WARNING! Many of these pragmas are actually scoped to the 1992 // schema ("main" and any other attached databases), and this implmentation 1993 // fails to propagate them. This is being addressed on trunk. 1994 static const char* pragmas[] = { 1995 "cache_size", "temp_store", "foreign_keys", "journal_size_limit", 1996 "synchronous", "wal_autocheckpoint", "busy_timeout"}; 1997 for (auto& pragma : pragmas) { 1998 // Read-only connections just need cache_size and temp_store pragmas. 1999 if (aReadOnly && ::strcmp(pragma, "cache_size") != 0 && 2000 ::strcmp(pragma, "temp_store") != 0) { 2001 continue; 2002 } 2003 2004 nsAutoCString pragmaQuery("PRAGMA "); 2005 pragmaQuery.Append(pragma); 2006 nsCOMPtr<mozIStorageStatement> stmt; 2007 rv = CreateStatement(pragmaQuery, getter_AddRefs(stmt)); 2008 NS_ENSURE_SUCCESS(rv, rv); 2009 bool hasResult = false; 2010 if (stmt && NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) { 2011 pragmaQuery.AppendLiteral(" = "); 2012 pragmaQuery.AppendInt(stmt->AsInt32(0)); 2013 rv = aClone->ExecuteSimpleSQL(pragmaQuery); 2014 NS_ENSURE_SUCCESS(rv, rv); 2015 } 2016 } 2017 2018 // Copy over temporary tables, triggers, and views from the original 2019 // connections. Entities in `sqlite_temp_master` are only visible to the 2020 // connection that created them. 2021 if (!aReadOnly) { 2022 rv = aClone->ExecuteSimpleSQL("BEGIN TRANSACTION"_ns); 2023 NS_ENSURE_SUCCESS(rv, rv); 2024 2025 nsCOMPtr<mozIStorageStatement> stmt; 2026 rv = CreateStatement(nsLiteralCString("SELECT sql FROM sqlite_temp_master " 2027 "WHERE type IN ('table', 'view', " 2028 "'index', 'trigger')"), 2029 getter_AddRefs(stmt)); 2030 // Propagate errors, because failing to copy triggers might cause schema 2031 // coherency issues when writing to the database from the cloned connection. 2032 NS_ENSURE_SUCCESS(rv, rv); 2033 bool hasResult = false; 2034 while (stmt && NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) { 2035 nsAutoCString query; 2036 rv = stmt->GetUTF8String(0, query); 2037 NS_ENSURE_SUCCESS(rv, rv); 2038 2039 // The `CREATE` SQL statements in `sqlite_temp_master` omit the `TEMP` 2040 // keyword. We need to add it back, or we'll recreate temporary entities 2041 // as persistent ones. `sqlite_temp_master` also holds `CREATE INDEX` 2042 // statements, but those don't need `TEMP` keywords. 2043 if (StringBeginsWith(query, "CREATE TABLE "_ns) || 2044 StringBeginsWith(query, "CREATE TRIGGER "_ns) || 2045 StringBeginsWith(query, "CREATE VIEW "_ns)) { 2046 query.Replace(0, 6, "CREATE TEMP"); 2047 } 2048 2049 rv = aClone->ExecuteSimpleSQL(query); 2050 NS_ENSURE_SUCCESS(rv, rv); 2051 } 2052 2053 rv = aClone->ExecuteSimpleSQL("COMMIT"_ns); 2054 NS_ENSURE_SUCCESS(rv, rv); 2055 } 2056 2057 // Copy any functions that have been added to this connection. 2058 SQLiteMutexAutoLock lockedScope(sharedDBMutex); 2059 for (const auto& entry : mFunctions) { 2060 const nsACString& key = entry.GetKey(); 2061 Connection::FunctionInfo data = entry.GetData(); 2062 2063 rv = aClone->CreateFunction(key, data.numArgs, data.function); 2064 if (NS_FAILED(rv)) { 2065 NS_WARNING("Failed to copy function to cloned connection"); 2066 } 2067 } 2068 2069 // Load SQLite extensions that were on this connection. 2070 // Copy into an array rather than holding the mutex while we load extensions. 2071 nsTArray<nsCString> loadedExtensions; 2072 { 2073 MutexAutoLock lockedScope(sharedAsyncExecutionMutex); 2074 AppendToArray(loadedExtensions, mLoadedExtensions); 2075 } 2076 for (const auto& extension : loadedExtensions) { 2077 (void)aClone->LoadExtension(extension, nullptr); 2078 } 2079 2080 guard.release(); 2081 return NS_OK; 2082 } 2083 2084 NS_IMETHODIMP 2085 Connection::Clone(bool aReadOnly, mozIStorageConnection** _connection) { 2086 MOZ_ASSERT(IsOnCurrentSerialEventTarget(eventTargetOpenedOn)); 2087 2088 AUTO_PROFILER_LABEL("Connection::Clone", OTHER); 2089 2090 if (!connectionReady()) { 2091 return NS_ERROR_NOT_INITIALIZED; 2092 } 2093 nsresult rv = ensureOperationSupported(SYNCHRONOUS); 2094 if (NS_FAILED(rv)) { 2095 return rv; 2096 } 2097 2098 int flags = mFlags; 2099 if (aReadOnly) { 2100 // Turn off SQLITE_OPEN_READWRITE, and set SQLITE_OPEN_READONLY. 2101 flags = (~SQLITE_OPEN_READWRITE & flags) | SQLITE_OPEN_READONLY; 2102 // Turn off SQLITE_OPEN_CREATE. 2103 flags = (~SQLITE_OPEN_CREATE & flags); 2104 } 2105 2106 RefPtr<Connection> clone = 2107 new Connection(mStorageService, flags, mSupportedOperations, 2108 mTelemetryFilename, mInterruptible); 2109 2110 rv = initializeClone(clone, aReadOnly); 2111 if (NS_FAILED(rv)) { 2112 return rv; 2113 } 2114 2115 NS_IF_ADDREF(*_connection = clone); 2116 return NS_OK; 2117 } 2118 2119 NS_IMETHODIMP 2120 Connection::Interrupt() { 2121 MOZ_ASSERT(mInterruptible, "Interrupt method not allowed"); 2122 MOZ_ASSERT_IF(SYNCHRONOUS == mSupportedOperations, 2123 !IsOnCurrentSerialEventTarget(eventTargetOpenedOn)); 2124 MOZ_ASSERT_IF(ASYNCHRONOUS == mSupportedOperations, 2125 IsOnCurrentSerialEventTarget(eventTargetOpenedOn)); 2126 2127 if (!connectionReady()) { 2128 return NS_ERROR_NOT_INITIALIZED; 2129 } 2130 2131 if (isClosing()) { // Closing already in asynchronous case 2132 return NS_OK; 2133 } 2134 2135 { 2136 // As stated on https://www.sqlite.org/c3ref/interrupt.html, 2137 // it is not safe to call sqlite3_interrupt() when 2138 // database connection is closed or might close before 2139 // sqlite3_interrupt() returns. 2140 MutexAutoLock lockedScope(sharedAsyncExecutionMutex); 2141 if (!isClosed(lockedScope)) { 2142 MOZ_ASSERT(mDBConn); 2143 ::sqlite3_interrupt(mDBConn); 2144 } 2145 } 2146 2147 return NS_OK; 2148 } 2149 2150 NS_IMETHODIMP 2151 Connection::AsyncVacuum(mozIStorageCompletionCallback* aCallback, 2152 bool aUseIncremental, int32_t aSetPageSize) { 2153 NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_NOT_SAME_THREAD); 2154 // Abort if we're shutting down. 2155 if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) { 2156 return NS_ERROR_ABORT; 2157 } 2158 // Check if AsyncClose or Close were already invoked. 2159 if (!connectionReady()) { 2160 return NS_ERROR_NOT_INITIALIZED; 2161 } 2162 nsresult rv = ensureOperationSupported(ASYNCHRONOUS); 2163 if (NS_FAILED(rv)) { 2164 return rv; 2165 } 2166 nsIEventTarget* asyncThread = getAsyncExecutionTarget(); 2167 if (!asyncThread) { 2168 return NS_ERROR_NOT_INITIALIZED; 2169 } 2170 2171 // Create and dispatch our vacuum event to the background thread. 2172 nsCOMPtr<nsIRunnable> vacuumEvent = 2173 new AsyncVacuumEvent(this, aCallback, aUseIncremental, aSetPageSize); 2174 rv = asyncThread->Dispatch(vacuumEvent, NS_DISPATCH_NORMAL); 2175 NS_ENSURE_SUCCESS(rv, rv); 2176 2177 return NS_OK; 2178 } 2179 2180 NS_IMETHODIMP 2181 Connection::GetDefaultPageSize(int32_t* _defaultPageSize) { 2182 *_defaultPageSize = Service::kDefaultPageSize; 2183 return NS_OK; 2184 } 2185 2186 NS_IMETHODIMP 2187 Connection::GetConnectionReady(bool* _ready) { 2188 MOZ_ASSERT(IsOnCurrentSerialEventTarget(eventTargetOpenedOn)); 2189 *_ready = connectionReady(); 2190 return NS_OK; 2191 } 2192 2193 NS_IMETHODIMP 2194 Connection::GetDatabaseFile(nsIFile** _dbFile) { 2195 if (!connectionReady()) { 2196 return NS_ERROR_NOT_INITIALIZED; 2197 } 2198 nsresult rv = ensureOperationSupported(ASYNCHRONOUS); 2199 if (NS_FAILED(rv)) { 2200 return rv; 2201 } 2202 2203 NS_IF_ADDREF(*_dbFile = mDatabaseFile); 2204 2205 return NS_OK; 2206 } 2207 2208 NS_IMETHODIMP 2209 Connection::GetLastInsertRowID(int64_t* _id) { 2210 if (!connectionReady()) { 2211 return NS_ERROR_NOT_INITIALIZED; 2212 } 2213 nsresult rv = ensureOperationSupported(SYNCHRONOUS); 2214 if (NS_FAILED(rv)) { 2215 return rv; 2216 } 2217 2218 sqlite_int64 id = ::sqlite3_last_insert_rowid(mDBConn); 2219 *_id = id; 2220 2221 return NS_OK; 2222 } 2223 2224 NS_IMETHODIMP 2225 Connection::GetAffectedRows(int32_t* _rows) { 2226 if (!connectionReady()) { 2227 return NS_ERROR_NOT_INITIALIZED; 2228 } 2229 nsresult rv = ensureOperationSupported(SYNCHRONOUS); 2230 if (NS_FAILED(rv)) { 2231 return rv; 2232 } 2233 2234 *_rows = ::sqlite3_changes(mDBConn); 2235 2236 return NS_OK; 2237 } 2238 2239 NS_IMETHODIMP 2240 Connection::GetLastError(int32_t* _error) { 2241 if (!connectionReady()) { 2242 return NS_ERROR_NOT_INITIALIZED; 2243 } 2244 nsresult rv = ensureOperationSupported(SYNCHRONOUS); 2245 if (NS_FAILED(rv)) { 2246 return rv; 2247 } 2248 2249 *_error = ::sqlite3_errcode(mDBConn); 2250 2251 return NS_OK; 2252 } 2253 2254 NS_IMETHODIMP 2255 Connection::GetLastErrorString(nsACString& _errorString) { 2256 if (!connectionReady()) { 2257 return NS_ERROR_NOT_INITIALIZED; 2258 } 2259 nsresult rv = ensureOperationSupported(SYNCHRONOUS); 2260 if (NS_FAILED(rv)) { 2261 return rv; 2262 } 2263 2264 const char* serr = ::sqlite3_errmsg(mDBConn); 2265 _errorString.Assign(serr); 2266 2267 return NS_OK; 2268 } 2269 2270 NS_IMETHODIMP 2271 Connection::GetSchemaVersion(int32_t* _version) { 2272 if (!connectionReady()) { 2273 return NS_ERROR_NOT_INITIALIZED; 2274 } 2275 nsresult rv = ensureOperationSupported(SYNCHRONOUS); 2276 if (NS_FAILED(rv)) { 2277 return rv; 2278 } 2279 2280 nsCOMPtr<mozIStorageStatement> stmt; 2281 (void)CreateStatement("PRAGMA user_version"_ns, getter_AddRefs(stmt)); 2282 NS_ENSURE_TRUE(stmt, NS_ERROR_OUT_OF_MEMORY); 2283 2284 *_version = 0; 2285 bool hasResult; 2286 if (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) { 2287 *_version = stmt->AsInt32(0); 2288 } 2289 2290 return NS_OK; 2291 } 2292 2293 NS_IMETHODIMP 2294 Connection::SetSchemaVersion(int32_t aVersion) { 2295 if (!connectionReady()) { 2296 return NS_ERROR_NOT_INITIALIZED; 2297 } 2298 nsresult rv = ensureOperationSupported(SYNCHRONOUS); 2299 if (NS_FAILED(rv)) { 2300 return rv; 2301 } 2302 2303 nsAutoCString stmt("PRAGMA user_version = "_ns); 2304 stmt.AppendInt(aVersion); 2305 2306 return ExecuteSimpleSQL(stmt); 2307 } 2308 2309 NS_IMETHODIMP 2310 Connection::CreateStatement(const nsACString& aSQLStatement, 2311 mozIStorageStatement** _stmt) { 2312 NS_ENSURE_ARG_POINTER(_stmt); 2313 if (!connectionReady()) { 2314 return NS_ERROR_NOT_INITIALIZED; 2315 } 2316 nsresult rv = ensureOperationSupported(SYNCHRONOUS); 2317 if (NS_FAILED(rv)) { 2318 return rv; 2319 } 2320 2321 RefPtr<Statement> statement(new Statement()); 2322 NS_ENSURE_TRUE(statement, NS_ERROR_OUT_OF_MEMORY); 2323 2324 rv = statement->initialize(this, mDBConn, aSQLStatement); 2325 NS_ENSURE_SUCCESS(rv, rv); 2326 2327 Statement* rawPtr; 2328 statement.forget(&rawPtr); 2329 *_stmt = rawPtr; 2330 return NS_OK; 2331 } 2332 2333 NS_IMETHODIMP 2334 Connection::CreateAsyncStatement(const nsACString& aSQLStatement, 2335 mozIStorageAsyncStatement** _stmt) { 2336 NS_ENSURE_ARG_POINTER(_stmt); 2337 if (!connectionReady()) { 2338 return NS_ERROR_NOT_INITIALIZED; 2339 } 2340 nsresult rv = ensureOperationSupported(ASYNCHRONOUS); 2341 if (NS_FAILED(rv)) { 2342 return rv; 2343 } 2344 2345 RefPtr<AsyncStatement> statement(new AsyncStatement()); 2346 NS_ENSURE_TRUE(statement, NS_ERROR_OUT_OF_MEMORY); 2347 2348 rv = statement->initialize(this, mDBConn, aSQLStatement); 2349 NS_ENSURE_SUCCESS(rv, rv); 2350 2351 AsyncStatement* rawPtr; 2352 statement.forget(&rawPtr); 2353 *_stmt = rawPtr; 2354 return NS_OK; 2355 } 2356 2357 NS_IMETHODIMP 2358 Connection::ExecuteSimpleSQL(const nsACString& aSQLStatement) { 2359 CHECK_MAINTHREAD_ABUSE(); 2360 if (!connectionReady()) { 2361 return NS_ERROR_NOT_INITIALIZED; 2362 } 2363 nsresult rv = ensureOperationSupported(SYNCHRONOUS); 2364 if (NS_FAILED(rv)) { 2365 return rv; 2366 } 2367 2368 int srv = executeSql(mDBConn, PromiseFlatCString(aSQLStatement).get()); 2369 return convertResultCode(srv); 2370 } 2371 2372 NS_IMETHODIMP 2373 Connection::ExecuteAsync( 2374 const nsTArray<RefPtr<mozIStorageBaseStatement>>& aStatements, 2375 mozIStorageStatementCallback* aCallback, 2376 mozIStoragePendingStatement** _handle) { 2377 nsTArray<StatementData> stmts(aStatements.Length()); 2378 for (uint32_t i = 0; i < aStatements.Length(); i++) { 2379 nsCOMPtr<StorageBaseStatementInternal> stmt = 2380 do_QueryInterface(aStatements[i]); 2381 NS_ENSURE_STATE(stmt); 2382 2383 // Obtain our StatementData. 2384 StatementData data; 2385 nsresult rv = stmt->getAsynchronousStatementData(data); 2386 NS_ENSURE_SUCCESS(rv, rv); 2387 2388 NS_ASSERTION(stmt->getOwner() == this, 2389 "Statement must be from this database connection!"); 2390 2391 // Now append it to our array. 2392 stmts.AppendElement(data); 2393 } 2394 2395 // Dispatch to the background 2396 return AsyncExecuteStatements::execute(std::move(stmts), this, mDBConn, 2397 aCallback, _handle); 2398 } 2399 2400 NS_IMETHODIMP 2401 Connection::ExecuteSimpleSQLAsync(const nsACString& aSQLStatement, 2402 mozIStorageStatementCallback* aCallback, 2403 mozIStoragePendingStatement** _handle) { 2404 NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_NOT_SAME_THREAD); 2405 2406 nsCOMPtr<mozIStorageAsyncStatement> stmt; 2407 nsresult rv = CreateAsyncStatement(aSQLStatement, getter_AddRefs(stmt)); 2408 if (NS_FAILED(rv)) { 2409 return rv; 2410 } 2411 2412 nsCOMPtr<mozIStoragePendingStatement> pendingStatement; 2413 rv = stmt->ExecuteAsync(aCallback, getter_AddRefs(pendingStatement)); 2414 if (NS_FAILED(rv)) { 2415 return rv; 2416 } 2417 2418 pendingStatement.forget(_handle); 2419 return rv; 2420 } 2421 2422 NS_IMETHODIMP 2423 Connection::TableExists(const nsACString& aTableName, bool* _exists) { 2424 return databaseElementExists(TABLE, aTableName, _exists); 2425 } 2426 2427 NS_IMETHODIMP 2428 Connection::IndexExists(const nsACString& aIndexName, bool* _exists) { 2429 return databaseElementExists(INDEX, aIndexName, _exists); 2430 } 2431 2432 NS_IMETHODIMP 2433 Connection::GetTransactionInProgress(bool* _inProgress) { 2434 if (!connectionReady()) { 2435 return NS_ERROR_NOT_INITIALIZED; 2436 } 2437 nsresult rv = ensureOperationSupported(ASYNCHRONOUS); 2438 if (NS_FAILED(rv)) { 2439 return rv; 2440 } 2441 2442 SQLiteMutexAutoLock lockedScope(sharedDBMutex); 2443 *_inProgress = transactionInProgress(lockedScope); 2444 return NS_OK; 2445 } 2446 2447 NS_IMETHODIMP 2448 Connection::GetDefaultTransactionType(int32_t* _type) { 2449 *_type = mDefaultTransactionType; 2450 return NS_OK; 2451 } 2452 2453 NS_IMETHODIMP 2454 Connection::SetDefaultTransactionType(int32_t aType) { 2455 NS_ENSURE_ARG_RANGE(aType, TRANSACTION_DEFERRED, TRANSACTION_EXCLUSIVE); 2456 mDefaultTransactionType = aType; 2457 return NS_OK; 2458 } 2459 2460 NS_IMETHODIMP 2461 Connection::GetVariableLimit(int32_t* _limit) { 2462 if (!connectionReady()) { 2463 return NS_ERROR_NOT_INITIALIZED; 2464 } 2465 int limit = ::sqlite3_limit(mDBConn, SQLITE_LIMIT_VARIABLE_NUMBER, -1); 2466 if (limit < 0) { 2467 return NS_ERROR_UNEXPECTED; 2468 } 2469 *_limit = limit; 2470 return NS_OK; 2471 } 2472 2473 NS_IMETHODIMP 2474 Connection::SetVariableLimit(int32_t limit) { 2475 if (!connectionReady()) { 2476 return NS_ERROR_NOT_INITIALIZED; 2477 } 2478 int oldLimit = ::sqlite3_limit(mDBConn, SQLITE_LIMIT_VARIABLE_NUMBER, limit); 2479 if (oldLimit < 0) { 2480 return NS_ERROR_UNEXPECTED; 2481 } 2482 return NS_OK; 2483 } 2484 2485 NS_IMETHODIMP 2486 Connection::BeginTransaction() { 2487 if (!connectionReady()) { 2488 return NS_ERROR_NOT_INITIALIZED; 2489 } 2490 nsresult rv = ensureOperationSupported(SYNCHRONOUS); 2491 if (NS_FAILED(rv)) { 2492 return rv; 2493 } 2494 2495 SQLiteMutexAutoLock lockedScope(sharedDBMutex); 2496 return beginTransactionInternal(lockedScope, mDBConn, 2497 mDefaultTransactionType); 2498 } 2499 2500 nsresult Connection::beginTransactionInternal( 2501 const SQLiteMutexAutoLock& aProofOfLock, sqlite3* aNativeConnection, 2502 int32_t aTransactionType) { 2503 if (transactionInProgress(aProofOfLock)) { 2504 return NS_ERROR_FAILURE; 2505 } 2506 nsresult rv; 2507 switch (aTransactionType) { 2508 case TRANSACTION_DEFERRED: 2509 rv = convertResultCode(executeSql(aNativeConnection, "BEGIN DEFERRED")); 2510 break; 2511 case TRANSACTION_IMMEDIATE: 2512 rv = convertResultCode(executeSql(aNativeConnection, "BEGIN IMMEDIATE")); 2513 break; 2514 case TRANSACTION_EXCLUSIVE: 2515 rv = convertResultCode(executeSql(aNativeConnection, "BEGIN EXCLUSIVE")); 2516 break; 2517 default: 2518 return NS_ERROR_ILLEGAL_VALUE; 2519 } 2520 return rv; 2521 } 2522 2523 NS_IMETHODIMP 2524 Connection::CommitTransaction() { 2525 if (!connectionReady()) { 2526 return NS_ERROR_NOT_INITIALIZED; 2527 } 2528 nsresult rv = ensureOperationSupported(SYNCHRONOUS); 2529 if (NS_FAILED(rv)) { 2530 return rv; 2531 } 2532 2533 SQLiteMutexAutoLock lockedScope(sharedDBMutex); 2534 return commitTransactionInternal(lockedScope, mDBConn); 2535 } 2536 2537 nsresult Connection::commitTransactionInternal( 2538 const SQLiteMutexAutoLock& aProofOfLock, sqlite3* aNativeConnection) { 2539 if (!transactionInProgress(aProofOfLock)) { 2540 return NS_ERROR_UNEXPECTED; 2541 } 2542 nsresult rv = 2543 convertResultCode(executeSql(aNativeConnection, "COMMIT TRANSACTION")); 2544 return rv; 2545 } 2546 2547 NS_IMETHODIMP 2548 Connection::RollbackTransaction() { 2549 if (!connectionReady()) { 2550 return NS_ERROR_NOT_INITIALIZED; 2551 } 2552 nsresult rv = ensureOperationSupported(SYNCHRONOUS); 2553 if (NS_FAILED(rv)) { 2554 return rv; 2555 } 2556 2557 SQLiteMutexAutoLock lockedScope(sharedDBMutex); 2558 return rollbackTransactionInternal(lockedScope, mDBConn); 2559 } 2560 2561 nsresult Connection::rollbackTransactionInternal( 2562 const SQLiteMutexAutoLock& aProofOfLock, sqlite3* aNativeConnection) { 2563 if (!transactionInProgress(aProofOfLock)) { 2564 return NS_ERROR_UNEXPECTED; 2565 } 2566 2567 nsresult rv = 2568 convertResultCode(executeSql(aNativeConnection, "ROLLBACK TRANSACTION")); 2569 return rv; 2570 } 2571 2572 NS_IMETHODIMP 2573 Connection::CreateTable(const char* aTableName, const char* aTableSchema) { 2574 if (!connectionReady()) { 2575 return NS_ERROR_NOT_INITIALIZED; 2576 } 2577 nsresult rv = ensureOperationSupported(SYNCHRONOUS); 2578 if (NS_FAILED(rv)) { 2579 return rv; 2580 } 2581 2582 SmprintfPointer buf = 2583 ::mozilla::Smprintf("CREATE TABLE %s (%s)", aTableName, aTableSchema); 2584 if (!buf) return NS_ERROR_OUT_OF_MEMORY; 2585 2586 int srv = executeSql(mDBConn, buf.get()); 2587 2588 return convertResultCode(srv); 2589 } 2590 2591 NS_IMETHODIMP 2592 Connection::CreateFunction(const nsACString& aFunctionName, 2593 int32_t aNumArguments, 2594 mozIStorageFunction* aFunction) { 2595 if (!connectionReady()) { 2596 return NS_ERROR_NOT_INITIALIZED; 2597 } 2598 nsresult rv = ensureOperationSupported(ASYNCHRONOUS); 2599 if (NS_FAILED(rv)) { 2600 return rv; 2601 } 2602 2603 // Check to see if this function is already defined. We only check the name 2604 // because a function can be defined with the same body but different names. 2605 SQLiteMutexAutoLock lockedScope(sharedDBMutex); 2606 NS_ENSURE_FALSE(mFunctions.Contains(aFunctionName), NS_ERROR_FAILURE); 2607 2608 int srv = ::sqlite3_create_function( 2609 mDBConn, nsPromiseFlatCString(aFunctionName).get(), aNumArguments, 2610 SQLITE_ANY, aFunction, basicFunctionHelper, nullptr, nullptr); 2611 if (srv != SQLITE_OK) return convertResultCode(srv); 2612 2613 FunctionInfo info = {aFunction, aNumArguments}; 2614 mFunctions.InsertOrUpdate(aFunctionName, info); 2615 2616 return NS_OK; 2617 } 2618 2619 NS_IMETHODIMP 2620 Connection::RemoveFunction(const nsACString& aFunctionName) { 2621 if (!connectionReady()) { 2622 return NS_ERROR_NOT_INITIALIZED; 2623 } 2624 nsresult rv = ensureOperationSupported(ASYNCHRONOUS); 2625 if (NS_FAILED(rv)) { 2626 return rv; 2627 } 2628 2629 SQLiteMutexAutoLock lockedScope(sharedDBMutex); 2630 NS_ENSURE_TRUE(mFunctions.Get(aFunctionName, nullptr), NS_ERROR_FAILURE); 2631 2632 int srv = ::sqlite3_create_function( 2633 mDBConn, nsPromiseFlatCString(aFunctionName).get(), 0, SQLITE_ANY, 2634 nullptr, nullptr, nullptr, nullptr); 2635 if (srv != SQLITE_OK) return convertResultCode(srv); 2636 2637 mFunctions.Remove(aFunctionName); 2638 2639 return NS_OK; 2640 } 2641 2642 NS_IMETHODIMP 2643 Connection::SetProgressHandler(int32_t aGranularity, 2644 mozIStorageProgressHandler* aHandler, 2645 mozIStorageProgressHandler** _oldHandler) { 2646 if (!connectionReady()) { 2647 return NS_ERROR_NOT_INITIALIZED; 2648 } 2649 nsresult rv = ensureOperationSupported(ASYNCHRONOUS); 2650 if (NS_FAILED(rv)) { 2651 return rv; 2652 } 2653 2654 // Return previous one 2655 SQLiteMutexAutoLock lockedScope(sharedDBMutex); 2656 NS_IF_ADDREF(*_oldHandler = mProgressHandler); 2657 2658 if (!aHandler || aGranularity <= 0) { 2659 aHandler = nullptr; 2660 aGranularity = 0; 2661 } 2662 mProgressHandler = aHandler; 2663 ::sqlite3_progress_handler(mDBConn, aGranularity, sProgressHelper, this); 2664 2665 return NS_OK; 2666 } 2667 2668 NS_IMETHODIMP 2669 Connection::RemoveProgressHandler(mozIStorageProgressHandler** _oldHandler) { 2670 if (!connectionReady()) { 2671 return NS_ERROR_NOT_INITIALIZED; 2672 } 2673 nsresult rv = ensureOperationSupported(ASYNCHRONOUS); 2674 if (NS_FAILED(rv)) { 2675 return rv; 2676 } 2677 2678 // Return previous one 2679 SQLiteMutexAutoLock lockedScope(sharedDBMutex); 2680 NS_IF_ADDREF(*_oldHandler = mProgressHandler); 2681 2682 mProgressHandler = nullptr; 2683 ::sqlite3_progress_handler(mDBConn, 0, nullptr, nullptr); 2684 2685 return NS_OK; 2686 } 2687 2688 NS_IMETHODIMP 2689 Connection::SetGrowthIncrement(int32_t aChunkSize, 2690 const nsACString& aDatabaseName) { 2691 if (!connectionReady()) { 2692 return NS_ERROR_NOT_INITIALIZED; 2693 } 2694 nsresult rv = ensureOperationSupported(SYNCHRONOUS); 2695 if (NS_FAILED(rv)) { 2696 return rv; 2697 } 2698 2699 // Bug 597215: Disk space is extremely limited on Android 2700 // so don't preallocate space. This is also not effective 2701 // on log structured file systems used by Android devices 2702 #if !defined(ANDROID) && !defined(MOZ_PLATFORM_MAEMO) 2703 // Don't preallocate if less than 500MiB is available. 2704 int64_t bytesAvailable; 2705 rv = mDatabaseFile->GetDiskSpaceAvailable(&bytesAvailable); 2706 NS_ENSURE_SUCCESS(rv, rv); 2707 if (bytesAvailable < MIN_AVAILABLE_BYTES_PER_CHUNKED_GROWTH) { 2708 return NS_ERROR_FILE_TOO_BIG; 2709 } 2710 2711 int srv = ::sqlite3_file_control( 2712 mDBConn, 2713 aDatabaseName.Length() ? nsPromiseFlatCString(aDatabaseName).get() 2714 : nullptr, 2715 SQLITE_FCNTL_CHUNK_SIZE, &aChunkSize); 2716 if (srv == SQLITE_OK) { 2717 mGrowthChunkSize = aChunkSize; 2718 } 2719 #endif 2720 return NS_OK; 2721 } 2722 2723 int32_t Connection::RemovablePagesInFreeList(const nsACString& aSchemaName) { 2724 int32_t freeListPagesCount = 0; 2725 if (!isConnectionReadyOnThisThread()) { 2726 MOZ_ASSERT(false, "Database connection is not ready"); 2727 return freeListPagesCount; 2728 } 2729 { 2730 nsAutoCString query(MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA "); 2731 query.Append(aSchemaName); 2732 query.AppendLiteral(".freelist_count"); 2733 nsCOMPtr<mozIStorageStatement> stmt; 2734 DebugOnly<nsresult> rv = CreateStatement(query, getter_AddRefs(stmt)); 2735 MOZ_ASSERT(NS_SUCCEEDED(rv)); 2736 bool hasResult = false; 2737 if (stmt && NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) { 2738 freeListPagesCount = stmt->AsInt32(0); 2739 } 2740 } 2741 // If there's no chunk size set, any page is good to be removed. 2742 if (mGrowthChunkSize == 0 || freeListPagesCount == 0) { 2743 return freeListPagesCount; 2744 } 2745 int32_t pageSize; 2746 { 2747 nsAutoCString query(MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA "); 2748 query.Append(aSchemaName); 2749 query.AppendLiteral(".page_size"); 2750 nsCOMPtr<mozIStorageStatement> stmt; 2751 DebugOnly<nsresult> rv = CreateStatement(query, getter_AddRefs(stmt)); 2752 MOZ_ASSERT(NS_SUCCEEDED(rv)); 2753 bool hasResult = false; 2754 if (stmt && NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) { 2755 pageSize = stmt->AsInt32(0); 2756 } else { 2757 MOZ_ASSERT(false, "Couldn't get page_size"); 2758 return 0; 2759 } 2760 } 2761 return std::max(0, freeListPagesCount - (mGrowthChunkSize / pageSize)); 2762 } 2763 2764 NS_IMETHODIMP 2765 Connection::LoadExtension(const nsACString& aExtensionName, 2766 mozIStorageCompletionCallback* aCallback) { 2767 AUTO_PROFILER_LABEL("Connection::LoadExtension", OTHER); 2768 2769 // This is a static list of extensions we can load. 2770 // Please use lowercase ASCII names and keep this list alphabetically ordered. 2771 static constexpr nsLiteralCString sSupportedExtensions[] = { 2772 // clang-format off 2773 "fts5"_ns, 2774 #ifdef MOZ_SQLITE_VEC0_EXT 2775 "vec"_ns, 2776 #endif 2777 // clang-format on 2778 }; 2779 if (std::find(std::begin(sSupportedExtensions), 2780 std::end(sSupportedExtensions), 2781 aExtensionName) == std::end(sSupportedExtensions)) { 2782 return NS_ERROR_INVALID_ARG; 2783 } 2784 2785 if (!connectionReady()) { 2786 return NS_ERROR_NOT_INITIALIZED; 2787 } 2788 2789 int srv = ::sqlite3_db_config(mDBConn, SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION, 2790 1, nullptr); 2791 if (srv != SQLITE_OK) { 2792 return NS_ERROR_UNEXPECTED; 2793 } 2794 2795 // Track the loaded extension for later connection cloning operations. 2796 { 2797 MutexAutoLock lockedScope(sharedAsyncExecutionMutex); 2798 if (!mLoadedExtensions.EnsureInserted(aExtensionName)) { 2799 // Already loaded, bail out but issue a warning. 2800 NS_WARNING(nsPrintfCString( 2801 "Tried to register '%s' SQLite extension multiple times!", 2802 PromiseFlatCString(aExtensionName).get()) 2803 .get()); 2804 return NS_OK; 2805 } 2806 } 2807 2808 nsAutoCString entryPoint("sqlite3_"); 2809 entryPoint.Append(aExtensionName); 2810 entryPoint.AppendLiteral("_init"); 2811 2812 RefPtr<Runnable> loadTask = NS_NewRunnableFunction( 2813 "mozStorageConnection::LoadExtension", 2814 [this, self = RefPtr(this), entryPoint, 2815 callback = RefPtr(aCallback)]() mutable { 2816 MOZ_ASSERT( 2817 !NS_IsMainThread() || 2818 (operationSupported(Connection::SYNCHRONOUS) && 2819 eventTargetOpenedOn == GetMainThreadSerialEventTarget()), 2820 "Should happen on main-thread only for synchronous connections " 2821 "opened on the main thread"); 2822 #ifdef MOZ_FOLD_LIBS 2823 int srv = ::sqlite3_load_extension(mDBConn, 2824 MOZ_DLL_PREFIX "nss3" MOZ_DLL_SUFFIX, 2825 entryPoint.get(), nullptr); 2826 #else 2827 int srv = ::sqlite3_load_extension( 2828 mDBConn, MOZ_DLL_PREFIX "mozsqlite3" MOZ_DLL_SUFFIX, 2829 entryPoint.get(), nullptr); 2830 #endif 2831 if (!callback) { 2832 return; 2833 }; 2834 RefPtr<Runnable> callbackTask = NS_NewRunnableFunction( 2835 "mozStorageConnection::LoadExtension_callback", 2836 [callback = std::move(callback), srv]() { 2837 (void)callback->Complete(convertResultCode(srv), nullptr); 2838 }); 2839 if (IsOnCurrentSerialEventTarget(eventTargetOpenedOn)) { 2840 MOZ_ALWAYS_SUCCEEDS(callbackTask->Run()); 2841 } else { 2842 // Redispatch the callback to the calling thread. 2843 MOZ_ALWAYS_SUCCEEDS(eventTargetOpenedOn->Dispatch( 2844 callbackTask.forget(), NS_DISPATCH_NORMAL)); 2845 } 2846 }); 2847 2848 if (NS_IsMainThread() && !operationSupported(Connection::SYNCHRONOUS)) { 2849 // This is a main-thread call to an async-only connection, thus we should 2850 // load the library in the helper thread. 2851 nsIEventTarget* helperThread = getAsyncExecutionTarget(); 2852 if (!helperThread) { 2853 return NS_ERROR_NOT_INITIALIZED; 2854 } 2855 MOZ_ALWAYS_SUCCEEDS( 2856 helperThread->Dispatch(loadTask.forget(), NS_DISPATCH_NORMAL)); 2857 } else { 2858 // In any other case we just load the extension on the current thread. 2859 MOZ_ALWAYS_SUCCEEDS(loadTask->Run()); 2860 } 2861 return NS_OK; 2862 } 2863 2864 NS_IMETHODIMP 2865 Connection::EnableModule(const nsACString& aModuleName) { 2866 if (!connectionReady()) { 2867 return NS_ERROR_NOT_INITIALIZED; 2868 } 2869 nsresult rv = ensureOperationSupported(SYNCHRONOUS); 2870 if (NS_FAILED(rv)) { 2871 return rv; 2872 } 2873 2874 for (auto& gModule : gModules) { 2875 struct Module* m = &gModule; 2876 if (aModuleName.Equals(m->name)) { 2877 int srv = m->registerFunc(mDBConn, m->name); 2878 if (srv != SQLITE_OK) return convertResultCode(srv); 2879 2880 return NS_OK; 2881 } 2882 } 2883 2884 return NS_ERROR_FAILURE; 2885 } 2886 2887 NS_IMETHODIMP 2888 Connection::GetQuotaObjects(QuotaObject** aDatabaseQuotaObject, 2889 QuotaObject** aJournalQuotaObject) { 2890 MOZ_ASSERT(aDatabaseQuotaObject); 2891 MOZ_ASSERT(aJournalQuotaObject); 2892 2893 if (!connectionReady()) { 2894 return NS_ERROR_NOT_INITIALIZED; 2895 } 2896 nsresult rv = ensureOperationSupported(SYNCHRONOUS); 2897 if (NS_FAILED(rv)) { 2898 return rv; 2899 } 2900 2901 sqlite3_file* file; 2902 int srv = ::sqlite3_file_control(mDBConn, nullptr, SQLITE_FCNTL_FILE_POINTER, 2903 &file); 2904 if (srv != SQLITE_OK) { 2905 return convertResultCode(srv); 2906 } 2907 2908 sqlite3_vfs* vfs; 2909 srv = 2910 ::sqlite3_file_control(mDBConn, nullptr, SQLITE_FCNTL_VFS_POINTER, &vfs); 2911 if (srv != SQLITE_OK) { 2912 return convertResultCode(srv); 2913 } 2914 2915 bool obfusactingVFS = false; 2916 2917 { 2918 const nsDependentCString vfsName{vfs->zName}; 2919 2920 if (vfsName == obfsvfs::GetVFSName()) { 2921 obfusactingVFS = true; 2922 } else if (vfsName != quotavfs::GetVFSName()) { 2923 NS_WARNING("Got unexpected vfs"); 2924 return NS_ERROR_FAILURE; 2925 } 2926 } 2927 2928 RefPtr<QuotaObject> databaseQuotaObject = 2929 GetQuotaObject(file, obfusactingVFS); 2930 if (NS_WARN_IF(!databaseQuotaObject)) { 2931 return NS_ERROR_FAILURE; 2932 } 2933 2934 srv = ::sqlite3_file_control(mDBConn, nullptr, SQLITE_FCNTL_JOURNAL_POINTER, 2935 &file); 2936 if (srv != SQLITE_OK) { 2937 return convertResultCode(srv); 2938 } 2939 2940 RefPtr<QuotaObject> journalQuotaObject = GetQuotaObject(file, obfusactingVFS); 2941 if (NS_WARN_IF(!journalQuotaObject)) { 2942 return NS_ERROR_FAILURE; 2943 } 2944 2945 databaseQuotaObject.forget(aDatabaseQuotaObject); 2946 journalQuotaObject.forget(aJournalQuotaObject); 2947 return NS_OK; 2948 } 2949 2950 SQLiteMutex& Connection::GetSharedDBMutex() { return sharedDBMutex; } 2951 2952 uint32_t Connection::GetTransactionNestingLevel( 2953 const mozilla::storage::SQLiteMutexAutoLock& aProofOfLock) { 2954 return mTransactionNestingLevel; 2955 } 2956 2957 uint32_t Connection::IncreaseTransactionNestingLevel( 2958 const mozilla::storage::SQLiteMutexAutoLock& aProofOfLock) { 2959 return ++mTransactionNestingLevel; 2960 } 2961 2962 uint32_t Connection::DecreaseTransactionNestingLevel( 2963 const mozilla::storage::SQLiteMutexAutoLock& aProofOfLock) { 2964 return --mTransactionNestingLevel; 2965 } 2966 2967 NS_IMETHODIMP 2968 Connection::BackupToFileAsync(nsIFile* aDestinationFile, 2969 mozIStorageCompletionCallback* aCallback, 2970 uint32_t aPagesPerStep, uint32_t aStepDelayMs) { 2971 NS_ENSURE_ARG(aDestinationFile); 2972 NS_ENSURE_ARG(aCallback); 2973 NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_NOT_SAME_THREAD); 2974 2975 // Abort if we're shutting down. 2976 if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) { 2977 return NS_ERROR_ABORT; 2978 } 2979 // Check if AsyncClose or Close were already invoked. 2980 if (!connectionReady()) { 2981 return NS_ERROR_NOT_INITIALIZED; 2982 } 2983 nsresult rv = ensureOperationSupported(ASYNCHRONOUS); 2984 if (NS_FAILED(rv)) { 2985 return rv; 2986 } 2987 nsIEventTarget* asyncThread = getAsyncExecutionTarget(); 2988 if (!asyncThread) { 2989 return NS_ERROR_NOT_INITIALIZED; 2990 } 2991 2992 // The number of pages of the database to copy per step 2993 static constexpr int32_t DEFAULT_PAGES_PER_STEP = 5; 2994 // The number of milliseconds to wait between each step. 2995 static constexpr uint32_t DEFAULT_STEP_DELAY_MS = 250; 2996 2997 CheckedInt<int32_t> pagesPerStep(aPagesPerStep); 2998 if (!pagesPerStep.isValid()) { 2999 return NS_ERROR_INVALID_ARG; 3000 } 3001 3002 if (!pagesPerStep.value()) { 3003 pagesPerStep = DEFAULT_PAGES_PER_STEP; 3004 } 3005 3006 if (!aStepDelayMs) { 3007 aStepDelayMs = DEFAULT_STEP_DELAY_MS; 3008 } 3009 3010 // Create and dispatch our backup event to the execution thread. 3011 nsCOMPtr<nsIRunnable> backupEvent = 3012 new AsyncBackupDatabaseFile(this, mDBConn, aDestinationFile, aCallback, 3013 pagesPerStep.value(), aStepDelayMs); 3014 rv = asyncThread->Dispatch(backupEvent, NS_DISPATCH_NORMAL); 3015 return rv; 3016 } 3017 3018 } // namespace mozilla::storage