DBAction.cpp (8839B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "mozilla/dom/cache/DBAction.h" 8 9 #include "mozIStorageConnection.h" 10 #include "mozIStorageService.h" 11 #include "mozStorageCID.h" 12 #include "mozilla/AppShutdown.h" 13 #include "mozilla/Assertions.h" 14 #include "mozilla/GeckoTrace.h" 15 #include "mozilla/dom/cache/Connection.h" 16 #include "mozilla/dom/cache/DBSchema.h" 17 #include "mozilla/dom/cache/FileUtils.h" 18 #include "mozilla/dom/cache/QuotaClient.h" 19 #include "mozilla/dom/quota/PersistenceType.h" 20 #include "mozilla/dom/quota/ResultExtensions.h" 21 #include "mozilla/net/nsFileProtocolHandler.h" 22 #include "nsIFile.h" 23 #include "nsIFileURL.h" 24 #include "nsIURI.h" 25 #include "nsIURIMutator.h" 26 27 namespace mozilla::dom::cache { 28 29 using mozilla::dom::quota::CloneFileAndAppend; 30 using mozilla::dom::quota::IsDatabaseCorruptionError; 31 32 namespace { 33 34 nsresult WipeDatabase(const CacheDirectoryMetadata& aDirectoryMetadata, 35 nsIFile& aDBFile) { 36 QM_TRY_INSPECT(const auto& dbDir, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 37 nsCOMPtr<nsIFile>, aDBFile, GetParent)); 38 39 QM_TRY(MOZ_TO_RESULT(RemoveNsIFile(aDirectoryMetadata, aDBFile))); 40 41 // Note, the -wal journal file will be automatically deleted by sqlite when 42 // the new database is created. No need to explicitly delete it here. 43 44 // Delete the morgue as well. 45 QM_TRY(MOZ_TO_RESULT(BodyDeleteDir(aDirectoryMetadata, *dbDir))); 46 47 QM_TRY(MOZ_TO_RESULT(WipePaddingFile(aDirectoryMetadata, dbDir))); 48 49 return NS_OK; 50 } 51 52 } // namespace 53 54 DBAction::DBAction(Mode aMode) : mMode(aMode) {} 55 56 DBAction::~DBAction() = default; 57 58 void DBAction::RunOnTarget( 59 SafeRefPtr<Resolver> aResolver, 60 const Maybe<CacheDirectoryMetadata>& aDirectoryMetadata, 61 Data* aOptionalData, const Maybe<CipherKey>& aMaybeCipherKey) { 62 MOZ_ASSERT(!NS_IsMainThread()); 63 MOZ_DIAGNOSTIC_ASSERT(aResolver); 64 MOZ_DIAGNOSTIC_ASSERT(aDirectoryMetadata); 65 MOZ_DIAGNOSTIC_ASSERT(aDirectoryMetadata->mDir); 66 67 if (IsCanceled() || AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownQM)) { 68 aResolver->Resolve(NS_ERROR_ABORT); 69 return; 70 } 71 72 const auto resolveErr = [&aResolver](const nsresult rv) { 73 aResolver->Resolve(rv); 74 }; 75 76 QM_TRY_INSPECT(const auto& dbDir, 77 CloneFileAndAppend(*(aDirectoryMetadata->mDir), u"cache"_ns), 78 QM_VOID, resolveErr); 79 80 nsCOMPtr<mozIStorageConnection> conn; 81 82 // Attempt to reuse the connection opened by a previous Action. 83 if (aOptionalData) { 84 conn = aOptionalData->GetConnection(); 85 } 86 87 // If there is no previous Action, then we must open one. 88 if (!conn) { 89 QM_TRY_UNWRAP(conn, 90 OpenConnection(*aDirectoryMetadata, *dbDir, aMaybeCipherKey), 91 QM_VOID, resolveErr); 92 MOZ_DIAGNOSTIC_ASSERT(conn); 93 94 // Save this connection in the shared Data object so later Actions can 95 // use it. This avoids opening a new connection for every Action. 96 if (aOptionalData) { 97 // Since we know this connection will be around for as long as the 98 // Cache is open, use our special wrapped connection class. This 99 // will let us perform certain operations once the Cache origin 100 // is closed. 101 nsCOMPtr<mozIStorageConnection> wrapped = new Connection(conn); 102 aOptionalData->SetConnection(wrapped); 103 } 104 } 105 106 RunWithDBOnTarget(std::move(aResolver), *aDirectoryMetadata, dbDir, conn); 107 } 108 109 Result<nsCOMPtr<mozIStorageConnection>, nsresult> DBAction::OpenConnection( 110 const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile& aDBDir, 111 const Maybe<CipherKey>& aMaybeCipherKey) { 112 MOZ_ASSERT(!NS_IsMainThread()); 113 MOZ_DIAGNOSTIC_ASSERT(aDirectoryMetadata.mDirectoryLockId >= 0); 114 115 QM_TRY_INSPECT(const bool& exists, 116 MOZ_TO_RESULT_INVOKE_MEMBER(aDBDir, Exists)); 117 118 if (!exists) { 119 QM_TRY(OkIf(mMode == Create), Err(NS_ERROR_FILE_NOT_FOUND)); 120 QM_TRY(MOZ_TO_RESULT(aDBDir.Create(nsIFile::DIRECTORY_TYPE, 0755))); 121 } 122 123 QM_TRY_INSPECT(const auto& dbFile, 124 CloneFileAndAppend(aDBDir, kCachesSQLiteFilename)); 125 126 QM_TRY_RETURN(OpenDBConnection(aDirectoryMetadata, *dbFile, aMaybeCipherKey)); 127 } 128 129 SyncDBAction::SyncDBAction(Mode aMode) : DBAction(aMode) {} 130 131 SyncDBAction::~SyncDBAction() = default; 132 133 void SyncDBAction::RunWithDBOnTarget( 134 SafeRefPtr<Resolver> aResolver, 135 const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile* aDBDir, 136 mozIStorageConnection* aConn) { 137 MOZ_ASSERT(!NS_IsMainThread()); 138 MOZ_DIAGNOSTIC_ASSERT(aResolver); 139 MOZ_DIAGNOSTIC_ASSERT(aDBDir); 140 MOZ_DIAGNOSTIC_ASSERT(aConn); 141 142 nsresult rv = RunSyncWithDBOnTarget(aDirectoryMetadata, aDBDir, aConn); 143 aResolver->Resolve(rv); 144 } 145 146 Result<nsCOMPtr<mozIStorageConnection>, nsresult> OpenDBConnection( 147 const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile& aDBFile, 148 const Maybe<CipherKey>& aMaybeCipherKey) { 149 GECKO_TRACE_SCOPE("dom::cache", "OpenDBConnection"); 150 151 MOZ_ASSERT(!NS_IsMainThread()); 152 MOZ_DIAGNOSTIC_ASSERT(aDirectoryMetadata.mDirectoryLockId >= -1); 153 MOZ_DIAGNOSTIC_ASSERT_IF(aDirectoryMetadata.mIsPrivate, aMaybeCipherKey); 154 155 // Use our default file:// protocol handler directly to construct the database 156 // URL. This avoids any problems if a plugin registers a custom file:// 157 // handler. If such a custom handler used javascript, then we would have a 158 // bad time running off the main thread here. 159 auto handler = MakeRefPtr<nsFileProtocolHandler>(); 160 QM_TRY(MOZ_TO_RESULT(handler->Init())); 161 162 QM_TRY_INSPECT(const auto& mutator, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 163 nsCOMPtr<nsIURIMutator>, handler, 164 NewFileURIMutator, &aDBFile)); 165 166 const nsCString directoryLockIdClause = 167 "&directoryLockId="_ns + 168 IntToCString(aDirectoryMetadata.mDirectoryLockId); 169 170 const auto keyClause = [&aMaybeCipherKey] { 171 nsAutoCString keyClause; 172 if (aMaybeCipherKey) { 173 keyClause.AssignLiteral("&key="); 174 for (uint8_t byte : CipherStrategy::SerializeKey(*aMaybeCipherKey)) { 175 keyClause.AppendPrintf("%02x", byte); 176 } 177 } 178 return keyClause; 179 }(); 180 181 nsCOMPtr<nsIFileURL> dbFileUrl; 182 QM_TRY(MOZ_TO_RESULT( 183 NS_MutateURI(mutator) 184 .SetQuery("cache=private"_ns + directoryLockIdClause + keyClause) 185 .Finalize(dbFileUrl))); 186 187 QM_TRY_INSPECT(const auto& storageService, 188 MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<mozIStorageService>, 189 MOZ_SELECT_OVERLOAD(do_GetService), 190 MOZ_STORAGE_SERVICE_CONTRACTID), 191 Err(NS_ERROR_UNEXPECTED)); 192 193 QM_TRY_UNWRAP( 194 auto conn, 195 QM_OR_ELSE_WARN_IF( 196 // Expression. 197 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 198 nsCOMPtr<mozIStorageConnection>, storageService, 199 OpenDatabaseWithFileURL, dbFileUrl, ""_ns, 200 mozIStorageService::CONNECTION_DEFAULT), 201 // Predicate. 202 IsDatabaseCorruptionError, 203 // Fallback. 204 ([&aDirectoryMetadata, &aDBFile, &storageService, 205 &dbFileUrl](const nsresult rv) 206 -> Result<nsCOMPtr<mozIStorageConnection>, nsresult> { 207 NS_WARNING("Cache database corrupted. Recreating empty database."); 208 209 // There is nothing else we can do to recover. Also, this data 210 // can be deleted by QuotaManager at any time anyways. 211 QM_TRY(MOZ_TO_RESULT(WipeDatabase(aDirectoryMetadata, aDBFile))); 212 213 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 214 nsCOMPtr<mozIStorageConnection>, storageService, 215 OpenDatabaseWithFileURL, dbFileUrl, ""_ns, 216 mozIStorageService::CONNECTION_DEFAULT)); 217 }))); 218 219 // Check the schema to make sure it is not too old. 220 QM_TRY_INSPECT(const int32_t& schemaVersion, 221 MOZ_TO_RESULT_INVOKE_MEMBER(conn, GetSchemaVersion)); 222 if (schemaVersion > 0 && schemaVersion < db::kFirstShippedSchemaVersion) { 223 // Close existing connection before wiping database. 224 conn = nullptr; 225 226 QM_TRY(MOZ_TO_RESULT(WipeDatabase(aDirectoryMetadata, aDBFile))); 227 228 QM_TRY_UNWRAP(conn, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 229 nsCOMPtr<mozIStorageConnection>, storageService, 230 OpenDatabaseWithFileURL, dbFileUrl, ""_ns, 231 mozIStorageService::CONNECTION_DEFAULT)); 232 } 233 234 QM_TRY(MOZ_TO_RESULT(db::InitializeConnection(*conn))); 235 236 return conn; 237 } 238 239 } // namespace mozilla::dom::cache