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 "FileUtilsImpl.h"
8
9 #include "DBSchema.h"
10 #include "mozilla/dom/InternalResponse.h"
11 #include "mozilla/dom/quota/FileStreams.h"
12 #include "mozilla/dom/quota/QuotaManager.h"
13 #include "mozilla/dom/quota/QuotaObject.h"
14 #include "mozilla/ScopeExit.h"
15 #include "mozilla/SnappyCompressOutputStream.h"
16 #include "mozilla/Unused.h"
17 #include "nsIObjectInputStream.h"
18 #include "nsIObjectOutputStream.h"
19 #include "nsIFile.h"
20 #include "nsIUUIDGenerator.h"
21 #include "nsNetCID.h"
22 #include "nsNetUtil.h"
23 #include "nsServiceManagerUtils.h"
24 #include "nsString.h"
25 #include "nsThreadUtils.h"
26
27 namespace mozilla::dom::cache {
28
29 using mozilla::dom::quota::Client;
30 using mozilla::dom::quota::CloneFileAndAppend;
31 using mozilla::dom::quota::FileInputStream;
32 using mozilla::dom::quota::FileOutputStream;
33 using mozilla::dom::quota::GetDirEntryKind;
34 using mozilla::dom::quota::nsIFileKind;
35 using mozilla::dom::quota::PERSISTENCE_TYPE_DEFAULT;
36 using mozilla::dom::quota::QuotaManager;
37 using mozilla::dom::quota::QuotaObject;
38
39 namespace {
40
41 // Const variable for generate padding size.
42 // XXX This will be tweaked to something more meaningful in Bug 1383656.
43 const int64_t kRoundUpNumber = 20480;
44
45 enum BodyFileType { BODY_FILE_FINAL, BODY_FILE_TMP };
46
47 Result<NotNull<nsCOMPtr<nsIFile>>, nsresult> BodyIdToFile(nsIFile& aBaseDir,
48 const nsID& aId,
49 BodyFileType aType);
50
51 int64_t RoundUp(int64_t aX, int64_t aY);
52
53 // The alogrithm for generating padding refers to the mitigation approach in
54 // https://github.com/whatwg/storage/issues/31.
55 // First, generate a random number between 0 and 100kB.
56 // Next, round up the sum of random number and response size to the nearest
57 // 20kB.
58 // Finally, the virtual padding size will be the result minus the response size.
59 int64_t BodyGeneratePadding(int64_t aBodyFileSize, uint32_t aPaddingInfo);
60
61 nsresult DirectoryPaddingWrite(nsIFile& aBaseDir,
62 DirPaddingFile aPaddingFileType,
63 int64_t aPaddingSize);
64
65 const auto kMorgueDirectory = u"morgue"_ns;
66
IsFileNotFoundError(const nsresult aRv)67 bool IsFileNotFoundError(const nsresult aRv) {
68 return aRv == NS_ERROR_FILE_NOT_FOUND ||
69 aRv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST;
70 }
71
BodyGetCacheDir(nsIFile & aBaseDir,const nsID & aId)72 Result<NotNull<nsCOMPtr<nsIFile>>, nsresult> BodyGetCacheDir(nsIFile& aBaseDir,
73 const nsID& aId) {
74 QM_TRY_UNWRAP(auto cacheDir, CloneFileAndAppend(aBaseDir, kMorgueDirectory));
75
76 // Some file systems have poor performance when there are too many files
77 // in a single directory. Mitigate this issue by spreading the body
78 // files out into sub-directories. We use the last byte of the ID for
79 // the name of the sub-directory.
80 QM_TRY(cacheDir->Append(IntToString(aId.m3[7])));
81
82 // Callers call this function without checking if the directory already
83 // exists (idempotent usage). QM_OR_ELSE_WARN_IF is not used here since we
84 // just want to log NS_ERROR_FILE_ALREADY_EXISTS result and not spam the
85 // reports.
86 QM_TRY(QM_OR_ELSE_LOG_VERBOSE_IF(
87 // Expression.
88 ToResult(cacheDir->Create(nsIFile::DIRECTORY_TYPE, 0755)),
89 // Predicate.
90 IsSpecificError<NS_ERROR_FILE_ALREADY_EXISTS>,
91 // Fallback.
92 ErrToDefaultOk<>));
93
94 return WrapNotNullUnchecked(std::move(cacheDir));
95 }
96
97 } // namespace
98
BodyCreateDir(nsIFile & aBaseDir)99 nsresult BodyCreateDir(nsIFile& aBaseDir) {
100 QM_TRY_INSPECT(const auto& bodyDir,
101 CloneFileAndAppend(aBaseDir, kMorgueDirectory));
102
103 // Callers call this function without checking if the directory already
104 // exists (idempotent usage). QM_OR_ELSE_WARN_IF is not used here since we
105 // just want to log NS_ERROR_FILE_ALREADY_EXISTS result and not spam the
106 // reports.
107 QM_TRY(QM_OR_ELSE_LOG_VERBOSE_IF(
108 // Expression.
109 ToResult(bodyDir->Create(nsIFile::DIRECTORY_TYPE, 0755)),
110 // Predicate.
111 IsSpecificError<NS_ERROR_FILE_ALREADY_EXISTS>,
112 // Fallback.
113 ErrToDefaultOk<>));
114
115 return NS_OK;
116 }
117
BodyDeleteDir(const QuotaInfo & aQuotaInfo,nsIFile & aBaseDir)118 nsresult BodyDeleteDir(const QuotaInfo& aQuotaInfo, nsIFile& aBaseDir) {
119 QM_TRY_INSPECT(const auto& bodyDir,
120 CloneFileAndAppend(aBaseDir, kMorgueDirectory));
121
122 QM_TRY(RemoveNsIFileRecursively(aQuotaInfo, *bodyDir));
123
124 return NS_OK;
125 }
126
BodyStartWriteStream(const QuotaInfo & aQuotaInfo,nsIFile & aBaseDir,nsIInputStream & aSource,void * aClosure,nsAsyncCopyCallbackFun aCallback)127 Result<std::pair<nsID, nsCOMPtr<nsISupports>>, nsresult> BodyStartWriteStream(
128 const QuotaInfo& aQuotaInfo, nsIFile& aBaseDir, nsIInputStream& aSource,
129 void* aClosure, nsAsyncCopyCallbackFun aCallback) {
130 MOZ_DIAGNOSTIC_ASSERT(aClosure);
131 MOZ_DIAGNOSTIC_ASSERT(aCallback);
132
133 QM_TRY_INSPECT(const auto& idGen, ToResultGet<nsCOMPtr<nsIUUIDGenerator>>(
134 MOZ_SELECT_OVERLOAD(do_GetService),
135 "@mozilla.org/uuid-generator;1"));
136
137 nsID id;
138 QM_TRY(idGen->GenerateUUIDInPlace(&id));
139
140 QM_TRY_INSPECT(const auto& finalFile,
141 BodyIdToFile(aBaseDir, id, BODY_FILE_FINAL));
142
143 {
144 QM_TRY_INSPECT(const bool& exists,
145 MOZ_TO_RESULT_INVOKE(*finalFile, Exists));
146
147 QM_TRY(OkIf(!exists), Err(NS_ERROR_FILE_ALREADY_EXISTS));
148 }
149
150 QM_TRY_INSPECT(const auto& tmpFile,
151 BodyIdToFile(aBaseDir, id, BODY_FILE_TMP));
152
153 QM_TRY_INSPECT(const auto& fileStream,
154 CreateFileOutputStream(PERSISTENCE_TYPE_DEFAULT, aQuotaInfo,
155 Client::DOMCACHE, tmpFile.get()));
156
157 const auto compressed =
158 MakeRefPtr<SnappyCompressOutputStream>(fileStream.get());
159
160 const nsCOMPtr<nsIEventTarget> target =
161 do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
162
163 nsCOMPtr<nsISupports> copyContext;
164 QM_TRY(NS_AsyncCopy(&aSource, compressed, target,
165 NS_ASYNCCOPY_VIA_WRITESEGMENTS, compressed->BlockSize(),
166 aCallback, aClosure, true,
167 true, // close streams
168 getter_AddRefs(copyContext)));
169
170 return std::make_pair(id, std::move(copyContext));
171 }
172
BodyCancelWrite(nsISupports & aCopyContext)173 void BodyCancelWrite(nsISupports& aCopyContext) {
174 QM_WARNONLY_TRY(NS_CancelAsyncCopy(&aCopyContext, NS_ERROR_ABORT));
175
176 // TODO The partially written file must be cleaned up after the async copy
177 // makes its callback.
178 }
179
BodyFinalizeWrite(nsIFile & aBaseDir,const nsID & aId)180 nsresult BodyFinalizeWrite(nsIFile& aBaseDir, const nsID& aId) {
181 QM_TRY_INSPECT(const auto& tmpFile,
182 BodyIdToFile(aBaseDir, aId, BODY_FILE_TMP));
183
184 QM_TRY_INSPECT(const auto& finalFile,
185 BodyIdToFile(aBaseDir, aId, BODY_FILE_FINAL));
186
187 nsAutoString finalFileName;
188 QM_TRY(finalFile->GetLeafName(finalFileName));
189
190 // It's fine to not notify the QuotaManager that the path has been changed,
191 // because its path will be updated and its size will be recalculated when
192 // opening file next time.
193 QM_TRY(tmpFile->RenameTo(nullptr, finalFileName));
194
195 return NS_OK;
196 }
197
BodyOpen(const QuotaInfo & aQuotaInfo,nsIFile & aBaseDir,const nsID & aId)198 Result<NotNull<nsCOMPtr<nsIInputStream>>, nsresult> BodyOpen(
199 const QuotaInfo& aQuotaInfo, nsIFile& aBaseDir, const nsID& aId) {
200 QM_TRY_INSPECT(const auto& finalFile,
201 BodyIdToFile(aBaseDir, aId, BODY_FILE_FINAL));
202
203 QM_TRY_RETURN(CreateFileInputStream(PERSISTENCE_TYPE_DEFAULT, aQuotaInfo,
204 Client::DOMCACHE, finalFile.get())
205 .map([](NotNull<RefPtr<FileInputStream>>&& stream) {
206 return WrapNotNullUnchecked(
207 nsCOMPtr<nsIInputStream>{stream.get()});
208 }));
209 }
210
BodyMaybeUpdatePaddingSize(const QuotaInfo & aQuotaInfo,nsIFile & aBaseDir,const nsID & aId,const uint32_t aPaddingInfo,int64_t * aPaddingSizeInOut)211 nsresult BodyMaybeUpdatePaddingSize(const QuotaInfo& aQuotaInfo,
212 nsIFile& aBaseDir, const nsID& aId,
213 const uint32_t aPaddingInfo,
214 int64_t* aPaddingSizeInOut) {
215 MOZ_DIAGNOSTIC_ASSERT(aPaddingSizeInOut);
216
217 QM_TRY_INSPECT(const auto& bodyFile,
218 BodyIdToFile(aBaseDir, aId, BODY_FILE_TMP));
219
220 QuotaManager* quotaManager = QuotaManager::Get();
221 MOZ_DIAGNOSTIC_ASSERT(quotaManager);
222
223 int64_t fileSize = 0;
224 RefPtr<QuotaObject> quotaObject = quotaManager->GetQuotaObject(
225 PERSISTENCE_TYPE_DEFAULT, aQuotaInfo, Client::DOMCACHE, bodyFile.get(),
226 -1, &fileSize);
227 MOZ_DIAGNOSTIC_ASSERT(quotaObject);
228 MOZ_DIAGNOSTIC_ASSERT(fileSize >= 0);
229 // XXXtt: bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1422815
230 if (!quotaObject) {
231 return NS_ERROR_UNEXPECTED;
232 }
233
234 if (*aPaddingSizeInOut == InternalResponse::UNKNOWN_PADDING_SIZE) {
235 *aPaddingSizeInOut = BodyGeneratePadding(fileSize, aPaddingInfo);
236 }
237
238 MOZ_DIAGNOSTIC_ASSERT(*aPaddingSizeInOut >= 0);
239
240 if (!quotaObject->IncreaseSize(*aPaddingSizeInOut)) {
241 return NS_ERROR_FILE_NO_DEVICE_SPACE;
242 }
243
244 return NS_OK;
245 }
246
BodyDeleteFiles(const QuotaInfo & aQuotaInfo,nsIFile & aBaseDir,const nsTArray<nsID> & aIdList)247 nsresult BodyDeleteFiles(const QuotaInfo& aQuotaInfo, nsIFile& aBaseDir,
248 const nsTArray<nsID>& aIdList) {
249 for (const auto id : aIdList) {
250 QM_TRY_INSPECT(const auto& bodyDir, BodyGetCacheDir(aBaseDir, id));
251
252 const auto removeFileForId =
253 [&aQuotaInfo, &id](
254 nsIFile& bodyFile,
255 const nsACString& leafName) -> Result<bool, nsresult> {
256 nsID fileId;
257 QM_TRY(OkIf(fileId.Parse(leafName.BeginReading())), true,
258 ([&aQuotaInfo, &bodyFile](const auto) {
259 DebugOnly<nsresult> result =
260 RemoveNsIFile(aQuotaInfo, bodyFile, /* aTrackQuota */ false);
261 MOZ_ASSERT(NS_SUCCEEDED(result));
262 }));
263
264 if (id.Equals(fileId)) {
265 DebugOnly<nsresult> result = RemoveNsIFile(aQuotaInfo, bodyFile);
266 MOZ_ASSERT(NS_SUCCEEDED(result));
267 return true;
268 }
269
270 return false;
271 };
272 QM_TRY(BodyTraverseFiles(aQuotaInfo, *bodyDir, removeFileForId,
273 /* aCanRemoveFiles */ false,
274 /* aTrackQuota */ true));
275 }
276
277 return NS_OK;
278 }
279
280 namespace {
281
BodyIdToFile(nsIFile & aBaseDir,const nsID & aId,const BodyFileType aType)282 Result<NotNull<nsCOMPtr<nsIFile>>, nsresult> BodyIdToFile(
283 nsIFile& aBaseDir, const nsID& aId, const BodyFileType aType) {
284 QM_TRY_UNWRAP(auto bodyFile, BodyGetCacheDir(aBaseDir, aId));
285
286 char idString[NSID_LENGTH];
287 aId.ToProvidedString(idString);
288
289 NS_ConvertASCIItoUTF16 fileName(idString);
290
291 if (aType == BODY_FILE_FINAL) {
292 fileName.AppendLiteral(".final");
293 } else {
294 fileName.AppendLiteral(".tmp");
295 }
296
297 QM_TRY(bodyFile->Append(fileName));
298
299 return bodyFile;
300 }
301
RoundUp(const int64_t aX,const int64_t aY)302 int64_t RoundUp(const int64_t aX, const int64_t aY) {
303 MOZ_DIAGNOSTIC_ASSERT(aX >= 0);
304 MOZ_DIAGNOSTIC_ASSERT(aY > 0);
305
306 MOZ_DIAGNOSTIC_ASSERT(INT64_MAX - ((aX - 1) / aY) * aY >= aY);
307 return aY + ((aX - 1) / aY) * aY;
308 }
309
BodyGeneratePadding(const int64_t aBodyFileSize,const uint32_t aPaddingInfo)310 int64_t BodyGeneratePadding(const int64_t aBodyFileSize,
311 const uint32_t aPaddingInfo) {
312 // Generate padding
313 int64_t randomSize = static_cast<int64_t>(aPaddingInfo);
314 MOZ_DIAGNOSTIC_ASSERT(INT64_MAX - aBodyFileSize >= randomSize);
315 randomSize += aBodyFileSize;
316
317 return RoundUp(randomSize, kRoundUpNumber) - aBodyFileSize;
318 }
319
DirectoryPaddingWrite(nsIFile & aBaseDir,DirPaddingFile aPaddingFileType,int64_t aPaddingSize)320 nsresult DirectoryPaddingWrite(nsIFile& aBaseDir,
321 DirPaddingFile aPaddingFileType,
322 int64_t aPaddingSize) {
323 MOZ_DIAGNOSTIC_ASSERT(aPaddingSize >= 0);
324
325 QM_TRY_INSPECT(
326 const auto& file,
327 CloneFileAndAppend(aBaseDir, aPaddingFileType == DirPaddingFile::TMP_FILE
328 ? nsLiteralString(PADDING_TMP_FILE_NAME)
329 : nsLiteralString(PADDING_FILE_NAME)));
330
331 QM_TRY_INSPECT(const auto& outputStream, NS_NewLocalFileOutputStream(file));
332
333 nsCOMPtr<nsIObjectOutputStream> objectStream =
334 NS_NewObjectOutputStream(outputStream);
335
336 QM_TRY(objectStream->Write64(aPaddingSize));
337
338 return NS_OK;
339 }
340
341 } // namespace
342
BodyDeleteOrphanedFiles(const QuotaInfo & aQuotaInfo,nsIFile & aBaseDir,const nsTArray<nsID> & aKnownBodyIdList)343 nsresult BodyDeleteOrphanedFiles(const QuotaInfo& aQuotaInfo, nsIFile& aBaseDir,
344 const nsTArray<nsID>& aKnownBodyIdList) {
345 // body files are stored in a directory structure like:
346 //
347 // /morgue/01/{01fdddb2-884d-4c3d-95ba-0c8062f6c325}.final
348 // /morgue/02/{02fdddb2-884d-4c3d-95ba-0c8062f6c325}.tmp
349
350 QM_TRY_INSPECT(const auto& dir,
351 CloneFileAndAppend(aBaseDir, kMorgueDirectory));
352
353 // Iterate over all the intermediate morgue subdirs
354 QM_TRY(quota::CollectEachFile(
355 *dir,
356 [&aQuotaInfo, &aKnownBodyIdList](
357 const nsCOMPtr<nsIFile>& subdir) -> Result<Ok, nsresult> {
358 QM_TRY_INSPECT(const auto& dirEntryKind, GetDirEntryKind(*subdir));
359
360 switch (dirEntryKind) {
361 case nsIFileKind::ExistsAsDirectory: {
362 const auto removeOrphanedFiles =
363 [&aQuotaInfo, &aKnownBodyIdList](
364 nsIFile& bodyFile,
365 const nsACString& leafName) -> Result<bool, nsresult> {
366 // Finally, parse the uuid out of the name. If it fails to parse,
367 // then ignore the file.
368 auto cleanup = MakeScopeExit([&aQuotaInfo, &bodyFile] {
369 DebugOnly<nsresult> result =
370 RemoveNsIFile(aQuotaInfo, bodyFile);
371 MOZ_ASSERT(NS_SUCCEEDED(result));
372 });
373
374 nsID id;
375 QM_TRY(OkIf(id.Parse(leafName.BeginReading())), true);
376
377 if (!aKnownBodyIdList.Contains(id)) {
378 return true;
379 }
380
381 cleanup.release();
382
383 return false;
384 };
385
386 // QM_OR_ELSE_WARN_IF is not used here since we just want to log
387 // NS_ERROR_FILE_FS_CORRUPTED result and not spam the reports (even
388 // a warning in the reports is not desired).
389 QM_TRY(QM_OR_ELSE_LOG_VERBOSE_IF(
390 // Expression.
391 ToResult(BodyTraverseFiles(aQuotaInfo, *subdir,
392 removeOrphanedFiles,
393 /* aCanRemoveFiles */ true,
394 /* aTrackQuota */ true)),
395 // Predicate.
396 IsSpecificError<NS_ERROR_FILE_FS_CORRUPTED>,
397 // Fallback. We treat NS_ERROR_FILE_FS_CORRUPTED as if the
398 // directory did not exist at all.
399 ErrToDefaultOk<>));
400 break;
401 }
402
403 case nsIFileKind::ExistsAsFile: {
404 // If a file got in here somehow, try to remove it and move on
405 DebugOnly<nsresult> result =
406 RemoveNsIFile(aQuotaInfo, *subdir, /* aTrackQuota */ false);
407 MOZ_ASSERT(NS_SUCCEEDED(result));
408 break;
409 }
410
411 case nsIFileKind::DoesNotExist:
412 // Ignore files that got removed externally while iterating.
413 break;
414 }
415
416 return Ok{};
417 }));
418
419 return NS_OK;
420 }
421
422 namespace {
423
GetMarkerFileHandle(const QuotaInfo & aQuotaInfo)424 Result<nsCOMPtr<nsIFile>, nsresult> GetMarkerFileHandle(
425 const QuotaInfo& aQuotaInfo) {
426 QM_TRY_UNWRAP(auto marker, CloneFileAndAppend(*aQuotaInfo.mDir, u"cache"_ns));
427
428 QM_TRY(marker->Append(u"context_open.marker"_ns));
429
430 return marker;
431 }
432
433 } // namespace
434
CreateMarkerFile(const QuotaInfo & aQuotaInfo)435 nsresult CreateMarkerFile(const QuotaInfo& aQuotaInfo) {
436 QM_TRY_INSPECT(const auto& marker, GetMarkerFileHandle(aQuotaInfo));
437
438 // Callers call this function without checking if the file already exists
439 // (idempotent usage). QM_OR_ELSE_WARN_IF is not used here since we just want
440 // to log NS_ERROR_FILE_ALREADY_EXISTS result and not spam the reports.
441 //
442 // TODO: In theory if this file exists, then Context::~Context should have
443 // cleaned it up, but obviously we can crash and not clean it up, which is
444 // the whole point of the marker file. In that case, we'll realize the marker
445 // file exists in SetupAction::RunSyncWithDBOnTarget and do some cleanup, but
446 // we won't delete the marker file, so if we see this marker file, it is part
447 // of our standard operating procedure to redundantly try and create the
448 // marker here. We currently treat this as idempotent usage, but we could
449 // make sure to delete the marker file when handling the existing marker
450 // file in SetupAction::RunSyncWithDBOnTarget and change
451 // QM_OR_ELSE_LOG_VERBOSE_IF to QM_OR_ELSE_WARN_IF in the end.
452 QM_TRY(QM_OR_ELSE_LOG_VERBOSE_IF(
453 // Expression.
454 ToResult(marker->Create(nsIFile::NORMAL_FILE_TYPE, 0644)),
455 // Predicate.
456 IsSpecificError<NS_ERROR_FILE_ALREADY_EXISTS>,
457 // Fallback.
458 ErrToDefaultOk<>));
459
460 // Note, we don't need to fsync here. We only care about actually
461 // writing the marker if later modifications to the Cache are
462 // actually flushed to the disk. If the OS crashes before the marker
463 // is written then we are ensured no other changes to the Cache were
464 // flushed either.
465
466 return NS_OK;
467 }
468
DeleteMarkerFile(const QuotaInfo & aQuotaInfo)469 nsresult DeleteMarkerFile(const QuotaInfo& aQuotaInfo) {
470 QM_TRY_INSPECT(const auto& marker, GetMarkerFileHandle(aQuotaInfo));
471
472 DebugOnly<nsresult> result =
473 RemoveNsIFile(aQuotaInfo, *marker, /* aTrackQuota */ false);
474 MOZ_ASSERT(NS_SUCCEEDED(result));
475
476 // Again, no fsync is necessary. If the OS crashes before the file
477 // removal is flushed, then the Cache will search for stale data on
478 // startup. This will cause the next Cache access to be a bit slow, but
479 // it seems appropriate after an OS crash.
480
481 return NS_OK;
482 }
483
MarkerFileExists(const QuotaInfo & aQuotaInfo)484 bool MarkerFileExists(const QuotaInfo& aQuotaInfo) {
485 QM_TRY_INSPECT(const auto& marker, GetMarkerFileHandle(aQuotaInfo), false);
486
487 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE(marker, Exists), false);
488 }
489
RemoveNsIFileRecursively(const QuotaInfo & aQuotaInfo,nsIFile & aFile,const bool aTrackQuota)490 nsresult RemoveNsIFileRecursively(const QuotaInfo& aQuotaInfo, nsIFile& aFile,
491 const bool aTrackQuota) {
492 QM_TRY_INSPECT(const auto& dirEntryKind, GetDirEntryKind(aFile));
493
494 switch (dirEntryKind) {
495 case nsIFileKind::ExistsAsDirectory:
496 // Unfortunately, we need to traverse all the entries and delete files one
497 // by
498 // one to update their usages to the QuotaManager.
499 QM_TRY(quota::CollectEachFile(
500 aFile,
501 [&aQuotaInfo, &aTrackQuota](
502 const nsCOMPtr<nsIFile>& file) -> Result<Ok, nsresult> {
503 QM_TRY(RemoveNsIFileRecursively(aQuotaInfo, *file, aTrackQuota));
504
505 return Ok{};
506 }));
507
508 // In the end, remove the folder
509 QM_TRY(aFile.Remove(/* recursive */ false));
510
511 break;
512
513 case nsIFileKind::ExistsAsFile:
514 return RemoveNsIFile(aQuotaInfo, aFile, aTrackQuota);
515
516 case nsIFileKind::DoesNotExist:
517 // Ignore files that got removed externally while iterating.
518 break;
519 }
520
521 return NS_OK;
522 }
523
RemoveNsIFile(const QuotaInfo & aQuotaInfo,nsIFile & aFile,const bool aTrackQuota)524 nsresult RemoveNsIFile(const QuotaInfo& aQuotaInfo, nsIFile& aFile,
525 const bool aTrackQuota) {
526 int64_t fileSize = 0;
527 if (aTrackQuota) {
528 QM_TRY_INSPECT(
529 const auto& maybeFileSize,
530 QM_OR_ELSE_WARN_IF(
531 // Expression.
532 MOZ_TO_RESULT_INVOKE(aFile, GetFileSize).map(Some<int64_t>),
533 // Predicate.
534 IsFileNotFoundError,
535 // Fallback.
536 ErrToDefaultOk<Maybe<int64_t>>));
537
538 if (!maybeFileSize) {
539 return NS_OK;
540 }
541
542 fileSize = *maybeFileSize;
543 }
544
545 QM_TRY(QM_OR_ELSE_WARN_IF(
546 // Expression.
547 ToResult(aFile.Remove(/* recursive */ false)),
548 // Predicate.
549 IsFileNotFoundError,
550 // Fallback.
551 ErrToDefaultOk<>));
552
553 if (fileSize > 0) {
554 MOZ_ASSERT(aTrackQuota);
555 DecreaseUsageForQuotaInfo(aQuotaInfo, fileSize);
556 }
557
558 return NS_OK;
559 }
560
DecreaseUsageForQuotaInfo(const QuotaInfo & aQuotaInfo,const int64_t aUpdatingSize)561 void DecreaseUsageForQuotaInfo(const QuotaInfo& aQuotaInfo,
562 const int64_t aUpdatingSize) {
563 MOZ_DIAGNOSTIC_ASSERT(aUpdatingSize > 0);
564
565 QuotaManager* quotaManager = QuotaManager::Get();
566 MOZ_DIAGNOSTIC_ASSERT(quotaManager);
567
568 quotaManager->DecreaseUsageForClient(
569 quota::ClientMetadata{aQuotaInfo, Client::DOMCACHE}, aUpdatingSize);
570 }
571
DirectoryPaddingFileExists(nsIFile & aBaseDir,DirPaddingFile aPaddingFileType)572 bool DirectoryPaddingFileExists(nsIFile& aBaseDir,
573 DirPaddingFile aPaddingFileType) {
574 QM_TRY_INSPECT(
575 const auto& file,
576 CloneFileAndAppend(aBaseDir, aPaddingFileType == DirPaddingFile::TMP_FILE
577 ? nsLiteralString(PADDING_TMP_FILE_NAME)
578 : nsLiteralString(PADDING_FILE_NAME)),
579 false);
580
581 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE(file, Exists), false);
582 }
583
DirectoryPaddingGet(nsIFile & aBaseDir)584 Result<int64_t, nsresult> DirectoryPaddingGet(nsIFile& aBaseDir) {
585 MOZ_DIAGNOSTIC_ASSERT(
586 !DirectoryPaddingFileExists(aBaseDir, DirPaddingFile::TMP_FILE));
587
588 QM_TRY_INSPECT(
589 const auto& file,
590 CloneFileAndAppend(aBaseDir, nsLiteralString(PADDING_FILE_NAME)));
591
592 QM_TRY_UNWRAP(auto stream, NS_NewLocalFileInputStream(file));
593
594 QM_TRY_INSPECT(const auto& bufferedStream,
595 NS_NewBufferedInputStream(stream.forget(), 512));
596
597 const nsCOMPtr<nsIObjectInputStream> objectStream =
598 NS_NewObjectInputStream(bufferedStream);
599
600 QM_TRY_RETURN(
601 MOZ_TO_RESULT_INVOKE(objectStream, Read64).map([](const uint64_t val) {
602 return int64_t(val);
603 }));
604 }
605
DirectoryPaddingInit(nsIFile & aBaseDir)606 nsresult DirectoryPaddingInit(nsIFile& aBaseDir) {
607 QM_TRY(DirectoryPaddingWrite(aBaseDir, DirPaddingFile::FILE, 0));
608
609 return NS_OK;
610 }
611
UpdateDirectoryPaddingFile(nsIFile & aBaseDir,mozIStorageConnection & aConn,const int64_t aIncreaseSize,const int64_t aDecreaseSize,const bool aTemporaryFileExist)612 nsresult UpdateDirectoryPaddingFile(nsIFile& aBaseDir,
613 mozIStorageConnection& aConn,
614 const int64_t aIncreaseSize,
615 const int64_t aDecreaseSize,
616 const bool aTemporaryFileExist) {
617 MOZ_DIAGNOSTIC_ASSERT(aIncreaseSize >= 0);
618 MOZ_DIAGNOSTIC_ASSERT(aDecreaseSize >= 0);
619
620 const auto directoryPaddingGetResult =
621 aTemporaryFileExist ? Maybe<int64_t>{} : [&aBaseDir] {
622 QM_TRY_RETURN(QM_OR_ELSE_WARN_IF(
623 // Expression.
624 DirectoryPaddingGet(aBaseDir).map(Some<int64_t>),
625 // Predicate.
626 IsFileNotFoundError,
627 // Fallback.
628 ErrToDefaultOk<Maybe<int64_t>>),
629 Maybe<int64_t>{});
630 }();
631
632 QM_TRY_INSPECT(
633 const int64_t& currentPaddingSize,
634 ([directoryPaddingGetResult, &aBaseDir, &aConn, aIncreaseSize,
635 aDecreaseSize]() -> Result<int64_t, nsresult> {
636 if (!directoryPaddingGetResult) {
637 // Fail to read padding size from the dir padding file, so try to
638 // restore.
639
640 // Not delete the temporary padding file here, because we're going
641 // to overwrite it below anyway.
642 QM_TRY(DirectoryPaddingDeleteFile(aBaseDir, DirPaddingFile::FILE));
643
644 // We don't need to add the aIncreaseSize or aDecreaseSize here,
645 // because it's already encompassed within the database.
646 QM_TRY_RETURN(db::FindOverallPaddingSize(aConn));
647 }
648
649 int64_t currentPaddingSize = directoryPaddingGetResult.value();
650 bool shouldRevise = false;
651
652 if (aIncreaseSize > 0) {
653 if (INT64_MAX - currentPaddingSize < aDecreaseSize) {
654 shouldRevise = true;
655 } else {
656 currentPaddingSize += aIncreaseSize;
657 }
658 }
659
660 if (aDecreaseSize > 0) {
661 if (currentPaddingSize < aDecreaseSize) {
662 shouldRevise = true;
663 } else if (!shouldRevise) {
664 currentPaddingSize -= aDecreaseSize;
665 }
666 }
667
668 if (shouldRevise) {
669 // If somehow runing into this condition, the tracking padding size is
670 // incorrect.
671 // Delete padding file to indicate the padding size is incorrect for
672 // avoiding error happening in the following lines.
673 QM_TRY(DirectoryPaddingDeleteFile(aBaseDir, DirPaddingFile::FILE));
674
675 QM_TRY_UNWRAP(currentPaddingSize, db::FindOverallPaddingSize(aConn));
676
677 // XXXtt: we should have an easy way to update (increase or
678 // recalulate) padding size in the QM. For now, only correct the
679 // padding size in padding file and make QM be able to get the correct
680 // size in the next QM initialization. We still want to catch this in
681 // the debug build.
682 MOZ_ASSERT(false, "The padding size is unsync with QM");
683 }
684
685 #ifdef DEBUG
686 const int64_t lastPaddingSize = currentPaddingSize;
687 QM_TRY_UNWRAP(currentPaddingSize, db::FindOverallPaddingSize(aConn));
688
689 MOZ_DIAGNOSTIC_ASSERT(currentPaddingSize == lastPaddingSize);
690 #endif // DEBUG
691
692 return currentPaddingSize;
693 }()));
694
695 MOZ_DIAGNOSTIC_ASSERT(currentPaddingSize >= 0);
696
697 QM_TRY(DirectoryPaddingWrite(aBaseDir, DirPaddingFile::TMP_FILE,
698 currentPaddingSize));
699
700 return NS_OK;
701 }
702
DirectoryPaddingFinalizeWrite(nsIFile & aBaseDir)703 nsresult DirectoryPaddingFinalizeWrite(nsIFile& aBaseDir) {
704 MOZ_DIAGNOSTIC_ASSERT(
705 DirectoryPaddingFileExists(aBaseDir, DirPaddingFile::TMP_FILE));
706
707 QM_TRY_INSPECT(
708 const auto& file,
709 CloneFileAndAppend(aBaseDir, nsLiteralString(PADDING_TMP_FILE_NAME)));
710
711 QM_TRY(file->RenameTo(nullptr, nsLiteralString(PADDING_FILE_NAME)));
712
713 return NS_OK;
714 }
715
DirectoryPaddingRestore(nsIFile & aBaseDir,mozIStorageConnection & aConn,const bool aMustRestore)716 Result<int64_t, nsresult> DirectoryPaddingRestore(nsIFile& aBaseDir,
717 mozIStorageConnection& aConn,
718 const bool aMustRestore) {
719 // The content of padding file is untrusted, so remove it here.
720 QM_TRY(DirectoryPaddingDeleteFile(aBaseDir, DirPaddingFile::FILE));
721
722 QM_TRY_INSPECT(const int64_t& paddingSize, db::FindOverallPaddingSize(aConn));
723 MOZ_DIAGNOSTIC_ASSERT(paddingSize >= 0);
724
725 QM_TRY(DirectoryPaddingWrite(aBaseDir, DirPaddingFile::FILE, paddingSize),
726 (aMustRestore ? Err(tryTempError)
727 : Result<int64_t, nsresult>{paddingSize}));
728
729 QM_TRY(DirectoryPaddingDeleteFile(aBaseDir, DirPaddingFile::TMP_FILE));
730
731 return paddingSize;
732 }
733
DirectoryPaddingDeleteFile(nsIFile & aBaseDir,DirPaddingFile aPaddingFileType)734 nsresult DirectoryPaddingDeleteFile(nsIFile& aBaseDir,
735 DirPaddingFile aPaddingFileType) {
736 QM_TRY_INSPECT(
737 const auto& file,
738 CloneFileAndAppend(aBaseDir, aPaddingFileType == DirPaddingFile::TMP_FILE
739 ? nsLiteralString(PADDING_TMP_FILE_NAME)
740 : nsLiteralString(PADDING_FILE_NAME)));
741
742 QM_TRY(QM_OR_ELSE_WARN_IF(
743 // Expression.
744 ToResult(file->Remove(/* recursive */ false)),
745 // Predicate.
746 IsFileNotFoundError,
747 // Fallback.
748 ErrToDefaultOk<>));
749
750 return NS_OK;
751 }
752 } // namespace mozilla::dom::cache
753