Bug 1883861 - Part 1: Move visitMemoryBarrier into the common CodeGenerator file...
[gecko.git] / dom / cache / QuotaClient.cpp
blob64bb5013851bbe7cd80efebbc5ba20823dbb65fd
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/. */
7 #include "QuotaClientImpl.h"
9 #include "DBAction.h"
10 #include "FileUtilsImpl.h"
11 #include "mozilla/DebugOnly.h"
12 #include "mozilla/ResultExtensions.h"
13 #include "mozilla/Telemetry.h"
14 #include "mozilla/Unused.h"
15 #include "mozilla/dom/cache/DBSchema.h"
16 #include "mozilla/dom/cache/Manager.h"
17 #include "mozilla/dom/quota/PersistenceType.h"
18 #include "mozilla/dom/quota/QuotaCommon.h"
19 #include "mozilla/dom/quota/QuotaManager.h"
20 #include "mozilla/dom/quota/UsageInfo.h"
21 #include "mozilla/ipc/BackgroundParent.h"
22 #include "nsIFile.h"
23 #include "nsThreadUtils.h"
25 namespace mozilla::dom::cache {
27 using mozilla::dom::quota::AssertIsOnIOThread;
28 using mozilla::dom::quota::Client;
29 using mozilla::dom::quota::CloneFileAndAppend;
30 using mozilla::dom::quota::DatabaseUsageType;
31 using mozilla::dom::quota::GetDirEntryKind;
32 using mozilla::dom::quota::nsIFileKind;
33 using mozilla::dom::quota::OriginMetadata;
34 using mozilla::dom::quota::PrincipalMetadata;
35 using mozilla::dom::quota::QuotaManager;
36 using mozilla::dom::quota::UsageInfo;
37 using mozilla::ipc::AssertIsOnBackgroundThread;
39 namespace {
41 template <typename StepFunc>
42 Result<UsageInfo, nsresult> ReduceUsageInfo(nsIFile& aDir,
43 const Atomic<bool>& aCanceled,
44 const StepFunc& aStepFunc) {
45 QM_TRY_RETURN(quota::ReduceEachFileAtomicCancelable(
46 aDir, aCanceled, UsageInfo{},
47 [&aStepFunc](UsageInfo usageInfo, const nsCOMPtr<nsIFile>& bodyDir)
48 -> Result<UsageInfo, nsresult> {
49 QM_TRY(OkIf(!QuotaManager::IsShuttingDown()).mapErr([](const auto&) {
50 return NS_ERROR_ABORT;
51 }));
53 QM_TRY_INSPECT(const auto& stepUsageInfo, aStepFunc(bodyDir));
55 return usageInfo + stepUsageInfo;
56 }));
59 Result<int64_t, nsresult> GetPaddingSizeFromDB(
60 nsIFile& aDir, nsIFile& aDBFile, const OriginMetadata& aOriginMetadata,
61 const Maybe<CipherKey>& aMaybeCipherKey) {
62 CacheDirectoryMetadata directoryMetadata(aOriginMetadata);
63 // directoryMetadata.mDirectoryLockId must be -1 (which is default for new
64 // CacheDirectoryMetadata) because this method should only be called from
65 // QuotaClient::InitOrigin when the temporary storage hasn't been initialized
66 // yet. At that time, the in-memory objects (e.g. OriginInfo) are only being
67 // created so it doesn't make sense to tunnel quota information to QuotaVFS
68 // to get corresponding QuotaObject instance for the SQLite file).
69 MOZ_DIAGNOSTIC_ASSERT(directoryMetadata.mDirectoryLockId == -1);
71 #ifdef DEBUG
73 QM_TRY_INSPECT(const bool& exists,
74 MOZ_TO_RESULT_INVOKE_MEMBER(aDBFile, Exists));
75 MOZ_ASSERT(exists);
77 #endif
79 QM_TRY_INSPECT(const auto& conn,
80 OpenDBConnection(directoryMetadata, aDBFile, aMaybeCipherKey));
82 // Make sure that the database has the latest schema before we try to read
83 // from it. We have to do this because GetPaddingSizeFromDB is called
84 // by InitOrigin. And it means that SetupAction::RunSyncWithDBOnTarget hasn't
85 // checked the schema for the given origin yet).
86 QM_TRY(MOZ_TO_RESULT(db::CreateOrMigrateSchema(aDir, *conn)));
88 QM_TRY_RETURN(DirectoryPaddingRestore(aDir, *conn,
89 /* aMustRestore */ false));
92 Result<int64_t, nsresult> GetTotalDiskUsageFromDB(
93 nsIFile& aDir, nsIFile& aDBFile, const OriginMetadata& aOriginMetadata,
94 const Maybe<CipherKey>& aMaybeCipherKey) {
95 CacheDirectoryMetadata directoryMetadata(aOriginMetadata);
96 // directoryMetadata.mDirectoryLockId must be -1 (which is default for new
97 // CacheDirectoryMetadata) because this method should only be called from
98 // QuotaClient::InitOrigin when the temporary storage hasn't been initialized
99 // yet. At that time, the in-memory objects (e.g. OriginInfo) are only being
100 // created so it doesn't make sense to tunnel quota information to QuotaVFS
101 // to get corresponding QuotaObject instance for the SQLite file).
102 MOZ_DIAGNOSTIC_ASSERT(directoryMetadata.mDirectoryLockId == -1);
104 #ifdef DEBUG
106 QM_TRY_INSPECT(const bool& exists,
107 MOZ_TO_RESULT_INVOKE_MEMBER(aDBFile, Exists));
108 MOZ_ASSERT(exists);
110 #endif
112 QM_TRY_INSPECT(const auto& conn,
113 OpenDBConnection(directoryMetadata, aDBFile, aMaybeCipherKey));
115 // Make sure that the database has the latest schema before we try to read
116 // from it. We have to do this because GetTotalDiskUsageFromDB is called
117 // by InitOrigin. And it means that SetupAction::RunSyncWithDBOnTarget hasn't
118 // checked the schema for the given origin yet).
119 QM_TRY(MOZ_TO_RESULT(db::CreateOrMigrateSchema(aDir, *conn)));
121 QM_TRY_RETURN(db::GetTotalDiskUsage(*conn));
124 } // namespace
126 const nsLiteralString kCachesSQLiteFilename = u"caches.sqlite"_ns;
127 const nsLiteralString kMorgueDirectoryFilename = u"morgue"_ns;
129 CacheQuotaClient::CacheQuotaClient() {
130 AssertIsOnBackgroundThread();
131 MOZ_DIAGNOSTIC_ASSERT(!sInstance);
132 sInstance = this;
135 // static
136 CacheQuotaClient* CacheQuotaClient::Get() {
137 MOZ_DIAGNOSTIC_ASSERT(sInstance);
138 return sInstance;
141 CacheQuotaClient::Type CacheQuotaClient::GetType() { return DOMCACHE; }
143 Result<UsageInfo, nsresult> CacheQuotaClient::InitOrigin(
144 PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata,
145 const AtomicBool& aCanceled) {
146 AssertIsOnIOThread();
147 MOZ_ASSERT(aOriginMetadata.mPersistenceType == aPersistenceType);
149 QuotaManager* const qm = QuotaManager::Get();
150 MOZ_DIAGNOSTIC_ASSERT(qm);
152 QM_TRY_INSPECT(const auto& dir, qm->GetOriginDirectory(aOriginMetadata));
154 QM_TRY(MOZ_TO_RESULT(
155 dir->Append(NS_LITERAL_STRING_FROM_CSTRING(DOMCACHE_DIRECTORY_NAME))));
157 QM_TRY_INSPECT(
158 const auto& cachesSQLiteFile,
159 ([dir]() -> Result<nsCOMPtr<nsIFile>, nsresult> {
160 QM_TRY_INSPECT(const auto& cachesSQLite,
161 CloneFileAndAppend(*dir, kCachesSQLiteFilename));
163 // IsDirectory is used to check if caches.sqlite exists or not. Another
164 // benefit of this is that we can test the failed cases by creating a
165 // directory named "caches.sqlite".
166 QM_TRY_INSPECT(const auto& dirEntryKind,
167 GetDirEntryKind(*cachesSQLite));
168 if (dirEntryKind == nsIFileKind::DoesNotExist) {
169 // We only ensure padding files and morgue directory get removed like
170 // WipeDatabase in DBAction.cpp. The -wal journal file will be
171 // automatically deleted by sqlite when the new database is created.
172 // XXX Ideally, we would delete the -wal journal file as well (here
173 // and also in WipeDatabase).
174 // XXX We should have something like WipeDatabaseNoQuota for this.
175 // XXX Long term, we might even think about removing entire origin
176 // directory because missing caches.sqlite while other files exist can
177 // be interpreted as database corruption.
178 QM_TRY(MOZ_TO_RESULT(mozilla::dom::cache::DirectoryPaddingDeleteFile(
179 *dir, DirPaddingFile::TMP_FILE)));
181 QM_TRY(MOZ_TO_RESULT(mozilla::dom::cache::DirectoryPaddingDeleteFile(
182 *dir, DirPaddingFile::FILE)));
184 QM_TRY_INSPECT(const auto& morgueDir,
185 CloneFileAndAppend(*dir, kMorgueDirectoryFilename));
187 QM_TRY(MOZ_TO_RESULT(mozilla::dom::cache::RemoveNsIFileRecursively(
188 Nothing(), *morgueDir,
189 /* aTrackQuota */ false)));
191 return nsCOMPtr<nsIFile>{nullptr};
194 QM_TRY(OkIf(dirEntryKind == nsIFileKind::ExistsAsFile),
195 Err(NS_ERROR_FAILURE));
197 return cachesSQLite;
198 }()));
200 // If the caches.sqlite doesn't exist, then padding files and morgue directory
201 // should have been removed if they existed. We ignore the rest of known files
202 // because we assume that they will be removed when a new database is created.
203 // XXX Ensure the -wel file is removed if the caches.sqlite doesn't exist.
204 QM_TRY(OkIf(!!cachesSQLiteFile), UsageInfo{});
206 const auto maybeCipherKey = [this, &aOriginMetadata] {
207 Maybe<CipherKey> maybeCipherKey;
208 auto cipherKeyManager = GetOrCreateCipherKeyManager(aOriginMetadata);
209 if (cipherKeyManager) {
210 maybeCipherKey = Some(cipherKeyManager->Ensure());
212 return maybeCipherKey;
213 }();
215 QM_TRY_INSPECT(
216 const auto& paddingSize,
217 ([dir, cachesSQLiteFile, &aOriginMetadata,
218 &maybeCipherKey]() -> Result<int64_t, nsresult> {
219 if (!DirectoryPaddingFileExists(*dir, DirPaddingFile::TMP_FILE)) {
220 QM_WARNONLY_TRY_UNWRAP(const auto maybePaddingSize,
221 DirectoryPaddingGet(*dir));
222 if (maybePaddingSize) {
223 return maybePaddingSize.ref();
227 // If the temporary file still exists or failing to get the padding size
228 // from the padding file, then we need to get the padding size from the
229 // database and restore the padding file.
230 QM_TRY_RETURN(GetPaddingSizeFromDB(*dir, *cachesSQLiteFile,
231 aOriginMetadata, maybeCipherKey));
232 }()));
234 QM_TRY_INSPECT(const auto& totalDiskUsage,
235 GetTotalDiskUsageFromDB(*dir, *cachesSQLiteFile,
236 aOriginMetadata, maybeCipherKey));
238 QM_TRY_INSPECT(
239 const auto& innerUsageInfo,
240 ReduceUsageInfo(
241 *dir, aCanceled,
242 [](const nsCOMPtr<nsIFile>& file) -> Result<UsageInfo, nsresult> {
243 QM_TRY_INSPECT(const auto& leafName,
244 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoString, file,
245 GetLeafName));
247 QM_TRY_INSPECT(const auto& dirEntryKind, GetDirEntryKind(*file));
249 switch (dirEntryKind) {
250 case nsIFileKind::ExistsAsDirectory:
251 if (!leafName.EqualsLiteral("morgue")) {
252 NS_WARNING("Unknown Cache directory found!");
255 break;
257 case nsIFileKind::ExistsAsFile:
258 // Ignore transient sqlite files and marker files
259 if (leafName.EqualsLiteral("caches.sqlite-journal") ||
260 leafName.EqualsLiteral("caches.sqlite-shm") ||
261 StringBeginsWith(leafName, u"caches.sqlite-mj"_ns) ||
262 leafName.EqualsLiteral("context_open.marker")) {
263 break;
266 if (leafName.Equals(kCachesSQLiteFilename) ||
267 leafName.EqualsLiteral("caches.sqlite-wal")) {
268 QM_TRY_INSPECT(
269 const int64_t& fileSize,
270 MOZ_TO_RESULT_INVOKE_MEMBER(file, GetFileSize));
271 MOZ_DIAGNOSTIC_ASSERT(fileSize >= 0);
273 return UsageInfo{DatabaseUsageType(Some(fileSize))};
276 // Ignore directory padding file
277 if (leafName.EqualsLiteral(PADDING_FILE_NAME) ||
278 leafName.EqualsLiteral(PADDING_TMP_FILE_NAME)) {
279 break;
282 NS_WARNING("Unknown Cache file found!");
284 break;
286 case nsIFileKind::DoesNotExist:
287 // Ignore files that got removed externally while iterating.
288 break;
291 return UsageInfo{};
292 }));
294 // FIXME: Separate file usage and database usage in OriginInfo so that the
295 // workaround for treating padding file size as database usage can be removed.
296 return UsageInfo{DatabaseUsageType(Some(paddingSize))} +
297 UsageInfo{DatabaseUsageType(Some(totalDiskUsage))} + innerUsageInfo;
300 nsresult CacheQuotaClient::InitOriginWithoutTracking(
301 PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata,
302 const AtomicBool& aCanceled) {
303 AssertIsOnIOThread();
305 // This is called when a storage/permanent/${origin}/cache directory exists.
306 // Even though this shouldn't happen with a "good" profile, we shouldn't
307 // return an error here, since that would cause origin initialization to fail.
308 // We just warn and otherwise ignore that.
309 UNKNOWN_FILE_WARNING(NS_LITERAL_STRING_FROM_CSTRING(DOMCACHE_DIRECTORY_NAME));
310 return NS_OK;
313 Result<UsageInfo, nsresult> CacheQuotaClient::GetUsageForOrigin(
314 PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata,
315 const AtomicBool& aCanceled) {
316 AssertIsOnIOThread();
318 // We can't open the database at this point, since it can be already used by
319 // the Cache IO thread. Use the cached value instead.
321 QuotaManager* quotaManager = QuotaManager::Get();
322 MOZ_ASSERT(quotaManager);
324 return quotaManager->GetUsageForClient(aOriginMetadata.mPersistenceType,
325 aOriginMetadata, Client::DOMCACHE);
328 void CacheQuotaClient::OnOriginClearCompleted(PersistenceType aPersistenceType,
329 const nsACString& aOrigin) {
330 AssertIsOnIOThread();
332 if (aPersistenceType == quota::PERSISTENCE_TYPE_PRIVATE) {
333 if (auto entry = mCipherKeyManagers.Lookup(aOrigin)) {
334 entry.Data()->Invalidate();
335 entry.Remove();
340 void CacheQuotaClient::OnRepositoryClearCompleted(
341 PersistenceType aPersistenceType) {
342 AssertIsOnIOThread();
344 if (aPersistenceType == quota::PERSISTENCE_TYPE_PRIVATE) {
345 for (const auto& cipherKeyManager : mCipherKeyManagers.Values()) {
346 cipherKeyManager->Invalidate();
349 mCipherKeyManagers.Clear();
353 void CacheQuotaClient::ReleaseIOThreadObjects() {
354 // Nothing to do here as the Context handles cleaning everything up
355 // automatically.
358 void CacheQuotaClient::AbortOperationsForLocks(
359 const DirectoryLockIdTable& aDirectoryLockIds) {
360 AssertIsOnBackgroundThread();
362 Manager::Abort(aDirectoryLockIds);
365 void CacheQuotaClient::AbortOperationsForProcess(
366 ContentParentId aContentParentId) {
367 // The Cache and Context can be shared by multiple client processes. They
368 // are not exclusively owned by a single process.
370 // As far as I can tell this is used by QuotaManager to abort operations
371 // when a particular process goes away. We definitely don't want this
372 // since we are shared. Also, the Cache actor code already properly
373 // handles asynchronous actor destruction when the child process dies.
375 // Therefore, do nothing here.
378 void CacheQuotaClient::AbortAllOperations() {
379 AssertIsOnBackgroundThread();
381 Manager::AbortAll();
384 void CacheQuotaClient::StartIdleMaintenance() {}
386 void CacheQuotaClient::StopIdleMaintenance() {}
388 void CacheQuotaClient::InitiateShutdown() {
389 AssertIsOnBackgroundThread();
391 Manager::InitiateShutdown();
394 bool CacheQuotaClient::IsShutdownCompleted() const {
395 AssertIsOnBackgroundThread();
397 return Manager::IsShutdownAllComplete();
400 void CacheQuotaClient::ForceKillActors() {
401 // Currently we don't implement killing actors (are there any to kill here?).
404 nsCString CacheQuotaClient::GetShutdownStatus() const {
405 AssertIsOnBackgroundThread();
407 return Manager::GetShutdownStatus();
410 void CacheQuotaClient::FinalizeShutdown() {
411 // Nothing to do here.
414 nsresult CacheQuotaClient::UpgradeStorageFrom2_0To2_1(nsIFile* aDirectory) {
415 AssertIsOnIOThread();
416 MOZ_DIAGNOSTIC_ASSERT(aDirectory);
418 QM_TRY(MOZ_TO_RESULT(DirectoryPaddingInit(*aDirectory)));
420 return NS_OK;
423 nsresult CacheQuotaClient::RestorePaddingFileInternal(
424 nsIFile* aBaseDir, mozIStorageConnection* aConn) {
425 MOZ_ASSERT(!NS_IsMainThread());
426 MOZ_DIAGNOSTIC_ASSERT(aBaseDir);
427 MOZ_DIAGNOSTIC_ASSERT(aConn);
429 QM_TRY_INSPECT(const int64_t& dummyPaddingSize,
430 DirectoryPaddingRestore(*aBaseDir, *aConn,
431 /* aMustRestore */ true));
432 Unused << dummyPaddingSize;
434 return NS_OK;
437 nsresult CacheQuotaClient::WipePaddingFileInternal(
438 const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile* aBaseDir) {
439 MOZ_ASSERT(!NS_IsMainThread());
440 MOZ_DIAGNOSTIC_ASSERT(aBaseDir);
442 MOZ_ASSERT(DirectoryPaddingFileExists(*aBaseDir, DirPaddingFile::FILE));
444 QM_TRY_INSPECT(
445 const int64_t& paddingSize, ([&aBaseDir]() -> Result<int64_t, nsresult> {
446 const bool temporaryPaddingFileExist =
447 DirectoryPaddingFileExists(*aBaseDir, DirPaddingFile::TMP_FILE);
449 Maybe<int64_t> directoryPaddingGetResult;
450 if (!temporaryPaddingFileExist) {
451 QM_TRY_UNWRAP(directoryPaddingGetResult,
452 ([&aBaseDir]() -> Result<Maybe<int64_t>, nsresult> {
453 QM_TRY_RETURN(
454 DirectoryPaddingGet(*aBaseDir).map(Some<int64_t>),
455 Maybe<int64_t>{});
456 }()));
459 if (temporaryPaddingFileExist || !directoryPaddingGetResult) {
460 // XXXtt: Maybe have a method in the QuotaManager to clean the usage
461 // under the quota client and the origin. There is nothing we can do
462 // to recover the file.
463 NS_WARNING("Cannnot read padding size from file!");
464 return 0;
467 return *directoryPaddingGetResult;
468 }()));
470 if (paddingSize > 0) {
471 DecreaseUsageForDirectoryMetadata(aDirectoryMetadata, paddingSize);
474 QM_TRY(MOZ_TO_RESULT(
475 DirectoryPaddingDeleteFile(*aBaseDir, DirPaddingFile::FILE)));
477 // Remove temporary file if we have one.
478 QM_TRY(MOZ_TO_RESULT(
479 DirectoryPaddingDeleteFile(*aBaseDir, DirPaddingFile::TMP_FILE)));
481 QM_TRY(MOZ_TO_RESULT(DirectoryPaddingInit(*aBaseDir)));
483 return NS_OK;
486 RefPtr<CipherKeyManager> CacheQuotaClient::GetOrCreateCipherKeyManager(
487 const PrincipalMetadata& aMetadata) {
488 AssertIsOnIOThread();
490 auto privateOrigin = aMetadata.mIsPrivate;
491 if (!privateOrigin) {
492 return nullptr;
495 const auto& origin = aMetadata.mOrigin;
496 return mCipherKeyManagers.LookupOrInsertWith(
497 origin, [] { return new CipherKeyManager("CacheCipherKeyManager"); });
500 CacheQuotaClient::~CacheQuotaClient() {
501 AssertIsOnBackgroundThread();
502 MOZ_DIAGNOSTIC_ASSERT(sInstance == this);
504 sInstance = nullptr;
507 // static
508 CacheQuotaClient* CacheQuotaClient::sInstance = nullptr;
510 // static
511 already_AddRefed<quota::Client> CreateQuotaClient() {
512 AssertIsOnBackgroundThread();
514 RefPtr<CacheQuotaClient> ref = new CacheQuotaClient();
515 return ref.forget();
518 // static
519 nsresult RestorePaddingFile(nsIFile* aBaseDir, mozIStorageConnection* aConn) {
520 MOZ_ASSERT(!NS_IsMainThread());
521 MOZ_DIAGNOSTIC_ASSERT(aBaseDir);
522 MOZ_DIAGNOSTIC_ASSERT(aConn);
524 RefPtr<CacheQuotaClient> cacheQuotaClient = CacheQuotaClient::Get();
525 MOZ_DIAGNOSTIC_ASSERT(cacheQuotaClient);
527 QM_TRY(MOZ_TO_RESULT(
528 cacheQuotaClient->RestorePaddingFileInternal(aBaseDir, aConn)));
530 return NS_OK;
533 // static
534 nsresult WipePaddingFile(const CacheDirectoryMetadata& aDirectoryMetadata,
535 nsIFile* aBaseDir) {
536 MOZ_ASSERT(!NS_IsMainThread());
537 MOZ_DIAGNOSTIC_ASSERT(aBaseDir);
539 RefPtr<CacheQuotaClient> cacheQuotaClient = CacheQuotaClient::Get();
540 MOZ_DIAGNOSTIC_ASSERT(cacheQuotaClient);
542 QM_TRY(MOZ_TO_RESULT(
543 cacheQuotaClient->WipePaddingFileInternal(aDirectoryMetadata, aBaseDir)));
545 return NS_OK;
548 } // namespace mozilla::dom::cache