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/FileUtils.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/SnappyCompressOutputStream.h"
14 #include "mozilla/Unused.h"
15 #include "nsIObjectInputStream.h"
16 #include "nsIObjectOutputStream.h"
17 #include "nsIFile.h"
18 #include "nsIUUIDGenerator.h"
19 #include "nsNetCID.h"
20 #include "nsNetUtil.h"
21 #include "nsISimpleEnumerator.h"
22 #include "nsServiceManagerUtils.h"
23 #include "nsString.h"
24 #include "nsThreadUtils.h"
25
26 namespace mozilla {
27 namespace dom {
28 namespace cache {
29
30 using mozilla::dom::quota::FileInputStream;
31 using mozilla::dom::quota::FileOutputStream;
32 using mozilla::dom::quota::PERSISTENCE_TYPE_DEFAULT;
33 using mozilla::dom::quota::QuotaManager;
34 using mozilla::dom::quota::QuotaObject;
35
36 namespace {
37
38 // Const variable for generate padding size.
39 // XXX This will be tweaked to something more meaningful in Bug 1383656.
40 const int64_t kRoundUpNumber = 20480;
41
42 enum BodyFileType { BODY_FILE_FINAL, BODY_FILE_TMP };
43
44 nsresult BodyIdToFile(nsIFile* aBaseDir, const nsID& aId, BodyFileType aType,
45 nsIFile** aBodyFileOut);
46
47 int64_t RoundUp(const int64_t aX, const int64_t aY);
48
49 // The alogrithm for generating padding refers to the mitigation approach in
50 // https://github.com/whatwg/storage/issues/31.
51 // First, generate a random number between 0 and 100kB.
52 // Next, round up the sum of random number and response size to the nearest
53 // 20kB.
54 // Finally, the virtual padding size will be the result minus the response size.
55 int64_t BodyGeneratePadding(const int64_t aBodyFileSize,
56 const uint32_t aPaddingInfo);
57
58 nsresult LockedDirectoryPaddingWrite(nsIFile* aBaseDir,
59 DirPaddingFile aPaddingFileType,
60 int64_t aPaddingSize);
61
62 } // namespace
63
64 // static
BodyCreateDir(nsIFile * aBaseDir)65 nsresult BodyCreateDir(nsIFile* aBaseDir) {
66 MOZ_DIAGNOSTIC_ASSERT(aBaseDir);
67
68 nsCOMPtr<nsIFile> aBodyDir;
69 nsresult rv = aBaseDir->Clone(getter_AddRefs(aBodyDir));
70 if (NS_WARN_IF(NS_FAILED(rv))) {
71 return rv;
72 }
73
74 rv = aBodyDir->Append(NS_LITERAL_STRING("morgue"));
75 if (NS_WARN_IF(NS_FAILED(rv))) {
76 return rv;
77 }
78
79 rv = aBodyDir->Create(nsIFile::DIRECTORY_TYPE, 0755);
80 if (rv == NS_ERROR_FILE_ALREADY_EXISTS) {
81 return NS_OK;
82 }
83 if (NS_WARN_IF(NS_FAILED(rv))) {
84 return rv;
85 }
86
87 return rv;
88 }
89
90 // static
BodyDeleteDir(const QuotaInfo & aQuotaInfo,nsIFile * aBaseDir)91 nsresult BodyDeleteDir(const QuotaInfo& aQuotaInfo, nsIFile* aBaseDir) {
92 MOZ_DIAGNOSTIC_ASSERT(aBaseDir);
93
94 nsCOMPtr<nsIFile> aBodyDir;
95 nsresult rv = aBaseDir->Clone(getter_AddRefs(aBodyDir));
96 if (NS_WARN_IF(NS_FAILED(rv))) {
97 return rv;
98 }
99
100 rv = aBodyDir->Append(NS_LITERAL_STRING("morgue"));
101 if (NS_WARN_IF(NS_FAILED(rv))) {
102 return rv;
103 }
104
105 rv = RemoveNsIFileRecursively(aQuotaInfo, aBodyDir);
106 if (NS_WARN_IF(NS_FAILED(rv))) {
107 return rv;
108 }
109
110 return rv;
111 }
112
113 // static
BodyGetCacheDir(nsIFile * aBaseDir,const nsID & aId,nsIFile ** aCacheDirOut)114 nsresult BodyGetCacheDir(nsIFile* aBaseDir, const nsID& aId,
115 nsIFile** aCacheDirOut) {
116 MOZ_DIAGNOSTIC_ASSERT(aBaseDir);
117 MOZ_DIAGNOSTIC_ASSERT(aCacheDirOut);
118
119 *aCacheDirOut = nullptr;
120
121 nsresult rv = aBaseDir->Clone(aCacheDirOut);
122 if (NS_WARN_IF(NS_FAILED(rv))) {
123 return rv;
124 }
125 MOZ_DIAGNOSTIC_ASSERT(*aCacheDirOut);
126
127 rv = (*aCacheDirOut)->Append(NS_LITERAL_STRING("morgue"));
128 if (NS_WARN_IF(NS_FAILED(rv))) {
129 return rv;
130 }
131
132 // Some file systems have poor performance when there are too many files
133 // in a single directory. Mitigate this issue by spreading the body
134 // files out into sub-directories. We use the last byte of the ID for
135 // the name of the sub-directory.
136 nsAutoString subDirName;
137 subDirName.AppendInt(aId.m3[7]);
138 rv = (*aCacheDirOut)->Append(subDirName);
139 if (NS_WARN_IF(NS_FAILED(rv))) {
140 return rv;
141 }
142
143 rv = (*aCacheDirOut)->Create(nsIFile::DIRECTORY_TYPE, 0755);
144 if (rv == NS_ERROR_FILE_ALREADY_EXISTS) {
145 return NS_OK;
146 }
147 if (NS_WARN_IF(NS_FAILED(rv))) {
148 return rv;
149 }
150
151 return rv;
152 }
153
154 // static
BodyStartWriteStream(const QuotaInfo & aQuotaInfo,nsIFile * aBaseDir,nsIInputStream * aSource,void * aClosure,nsAsyncCopyCallbackFun aCallback,nsID * aIdOut,nsISupports ** aCopyContextOut)155 nsresult BodyStartWriteStream(const QuotaInfo& aQuotaInfo, nsIFile* aBaseDir,
156 nsIInputStream* aSource, void* aClosure,
157 nsAsyncCopyCallbackFun aCallback, nsID* aIdOut,
158 nsISupports** aCopyContextOut) {
159 MOZ_DIAGNOSTIC_ASSERT(aBaseDir);
160 MOZ_DIAGNOSTIC_ASSERT(aSource);
161 MOZ_DIAGNOSTIC_ASSERT(aClosure);
162 MOZ_DIAGNOSTIC_ASSERT(aCallback);
163 MOZ_DIAGNOSTIC_ASSERT(aIdOut);
164 MOZ_DIAGNOSTIC_ASSERT(aCopyContextOut);
165
166 nsresult rv;
167 nsCOMPtr<nsIUUIDGenerator> idGen =
168 do_GetService("@mozilla.org/uuid-generator;1", &rv);
169 if (NS_WARN_IF(NS_FAILED(rv))) {
170 return rv;
171 }
172
173 rv = idGen->GenerateUUIDInPlace(aIdOut);
174 if (NS_WARN_IF(NS_FAILED(rv))) {
175 return rv;
176 }
177
178 nsCOMPtr<nsIFile> finalFile;
179 rv = BodyIdToFile(aBaseDir, *aIdOut, BODY_FILE_FINAL,
180 getter_AddRefs(finalFile));
181 if (NS_WARN_IF(NS_FAILED(rv))) {
182 return rv;
183 }
184
185 bool exists;
186 rv = finalFile->Exists(&exists);
187 if (NS_WARN_IF(NS_FAILED(rv))) {
188 return rv;
189 }
190 if (NS_WARN_IF(exists)) {
191 return NS_ERROR_FILE_ALREADY_EXISTS;
192 }
193
194 nsCOMPtr<nsIFile> tmpFile;
195 rv = BodyIdToFile(aBaseDir, *aIdOut, BODY_FILE_TMP, getter_AddRefs(tmpFile));
196 if (NS_WARN_IF(NS_FAILED(rv))) {
197 return rv;
198 }
199
200 rv = tmpFile->Exists(&exists);
201 if (NS_WARN_IF(NS_FAILED(rv))) {
202 return rv;
203 }
204 if (NS_WARN_IF(exists)) {
205 return NS_ERROR_FILE_ALREADY_EXISTS;
206 }
207
208 nsCOMPtr<nsIOutputStream> fileStream = FileOutputStream::Create(
209 PERSISTENCE_TYPE_DEFAULT, aQuotaInfo.mGroup, aQuotaInfo.mOrigin, tmpFile);
210 if (NS_WARN_IF(!fileStream)) {
211 return NS_ERROR_UNEXPECTED;
212 }
213
214 RefPtr<SnappyCompressOutputStream> compressed =
215 new SnappyCompressOutputStream(fileStream);
216
217 nsCOMPtr<nsIEventTarget> target =
218 do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
219
220 rv = NS_AsyncCopy(aSource, compressed, target, NS_ASYNCCOPY_VIA_WRITESEGMENTS,
221 compressed->BlockSize(), aCallback, aClosure, true,
222 true, // close streams
223 aCopyContextOut);
224 if (NS_WARN_IF(NS_FAILED(rv))) {
225 return rv;
226 }
227
228 return rv;
229 }
230
231 // static
BodyCancelWrite(nsIFile * aBaseDir,nsISupports * aCopyContext)232 void BodyCancelWrite(nsIFile* aBaseDir, nsISupports* aCopyContext) {
233 MOZ_DIAGNOSTIC_ASSERT(aBaseDir);
234 MOZ_DIAGNOSTIC_ASSERT(aCopyContext);
235
236 nsresult rv = NS_CancelAsyncCopy(aCopyContext, NS_ERROR_ABORT);
237 Unused << NS_WARN_IF(NS_FAILED(rv));
238
239 // The partially written file must be cleaned up after the async copy
240 // makes its callback.
241 }
242
243 // static
BodyFinalizeWrite(nsIFile * aBaseDir,const nsID & aId)244 nsresult BodyFinalizeWrite(nsIFile* aBaseDir, const nsID& aId) {
245 MOZ_DIAGNOSTIC_ASSERT(aBaseDir);
246
247 nsCOMPtr<nsIFile> tmpFile;
248 nsresult rv =
249 BodyIdToFile(aBaseDir, aId, BODY_FILE_TMP, getter_AddRefs(tmpFile));
250 if (NS_WARN_IF(NS_FAILED(rv))) {
251 return rv;
252 }
253
254 nsCOMPtr<nsIFile> finalFile;
255 rv = BodyIdToFile(aBaseDir, aId, BODY_FILE_FINAL, getter_AddRefs(finalFile));
256 if (NS_WARN_IF(NS_FAILED(rv))) {
257 return rv;
258 }
259
260 nsAutoString finalFileName;
261 rv = finalFile->GetLeafName(finalFileName);
262 if (NS_WARN_IF(NS_FAILED(rv))) {
263 return rv;
264 }
265
266 // It's fine to not notify the QuotaManager that the path has been changed,
267 // because its path will be updated and its size will be recalculated when
268 // opening file next time.
269 rv = tmpFile->RenameTo(nullptr, finalFileName);
270 if (NS_WARN_IF(NS_FAILED(rv))) {
271 return rv;
272 }
273
274 return rv;
275 }
276
277 // static
BodyOpen(const QuotaInfo & aQuotaInfo,nsIFile * aBaseDir,const nsID & aId,nsIInputStream ** aStreamOut)278 nsresult BodyOpen(const QuotaInfo& aQuotaInfo, nsIFile* aBaseDir,
279 const nsID& aId, nsIInputStream** aStreamOut) {
280 MOZ_DIAGNOSTIC_ASSERT(aBaseDir);
281 MOZ_DIAGNOSTIC_ASSERT(aStreamOut);
282
283 nsCOMPtr<nsIFile> finalFile;
284 nsresult rv =
285 BodyIdToFile(aBaseDir, aId, BODY_FILE_FINAL, getter_AddRefs(finalFile));
286 if (NS_WARN_IF(NS_FAILED(rv))) {
287 return rv;
288 }
289
290 bool exists;
291 rv = finalFile->Exists(&exists);
292 if (NS_WARN_IF(NS_FAILED(rv))) {
293 return rv;
294 }
295 if (NS_WARN_IF(!exists)) {
296 return NS_ERROR_FILE_NOT_FOUND;
297 }
298
299 nsCOMPtr<nsIInputStream> fileStream =
300 FileInputStream::Create(PERSISTENCE_TYPE_DEFAULT, aQuotaInfo.mGroup,
301 aQuotaInfo.mOrigin, finalFile);
302 if (NS_WARN_IF(!fileStream)) {
303 return NS_ERROR_UNEXPECTED;
304 }
305
306 fileStream.forget(aStreamOut);
307
308 return rv;
309 }
310
311 // static
BodyMaybeUpdatePaddingSize(const QuotaInfo & aQuotaInfo,nsIFile * aBaseDir,const nsID & aId,const uint32_t aPaddingInfo,int64_t * aPaddingSizeOut)312 nsresult BodyMaybeUpdatePaddingSize(const QuotaInfo& aQuotaInfo,
313 nsIFile* aBaseDir, const nsID& aId,
314 const uint32_t aPaddingInfo,
315 int64_t* aPaddingSizeOut) {
316 MOZ_DIAGNOSTIC_ASSERT(aBaseDir);
317 MOZ_DIAGNOSTIC_ASSERT(aPaddingSizeOut);
318
319 nsCOMPtr<nsIFile> bodyFile;
320 nsresult rv =
321 BodyIdToFile(aBaseDir, aId, BODY_FILE_TMP, getter_AddRefs(bodyFile));
322 if (NS_WARN_IF(NS_FAILED(rv))) {
323 return rv;
324 }
325
326 MOZ_DIAGNOSTIC_ASSERT(bodyFile);
327
328 QuotaManager* quotaManager = QuotaManager::Get();
329 MOZ_DIAGNOSTIC_ASSERT(quotaManager);
330
331 int64_t fileSize = 0;
332 RefPtr<QuotaObject> quotaObject =
333 quotaManager->GetQuotaObject(PERSISTENCE_TYPE_DEFAULT, aQuotaInfo.mGroup,
334 aQuotaInfo.mOrigin, bodyFile, &fileSize);
335 MOZ_DIAGNOSTIC_ASSERT(quotaObject);
336 MOZ_DIAGNOSTIC_ASSERT(fileSize >= 0);
337 // XXXtt: bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1422815
338 if (!quotaObject) {
339 return NS_ERROR_UNEXPECTED;
340 }
341
342 if (*aPaddingSizeOut == InternalResponse::UNKNOWN_PADDING_SIZE) {
343 *aPaddingSizeOut = BodyGeneratePadding(fileSize, aPaddingInfo);
344 }
345
346 MOZ_DIAGNOSTIC_ASSERT(*aPaddingSizeOut >= 0);
347
348 if (!quotaObject->IncreaseSize(*aPaddingSizeOut)) {
349 return NS_ERROR_FILE_NO_DEVICE_SPACE;
350 }
351
352 return rv;
353 }
354
355 // static
BodyDeleteFiles(const QuotaInfo & aQuotaInfo,nsIFile * aBaseDir,const nsTArray<nsID> & aIdList)356 nsresult BodyDeleteFiles(const QuotaInfo& aQuotaInfo, nsIFile* aBaseDir,
357 const nsTArray<nsID>& aIdList) {
358 nsresult rv = NS_OK;
359
360 for (uint32_t i = 0; i < aIdList.Length(); ++i) {
361 nsCOMPtr<nsIFile> tmpFile;
362 rv = BodyIdToFile(aBaseDir, aIdList[i], BODY_FILE_TMP,
363 getter_AddRefs(tmpFile));
364 if (NS_WARN_IF(NS_FAILED(rv))) {
365 return rv;
366 }
367
368 rv = RemoveNsIFile(aQuotaInfo, tmpFile);
369 // Only treat file deletion as a hard failure in DEBUG builds. Users
370 // can unfortunately hit this on windows if anti-virus is scanning files,
371 // etc.
372 MOZ_ASSERT(NS_SUCCEEDED(rv));
373
374 nsCOMPtr<nsIFile> finalFile;
375 rv = BodyIdToFile(aBaseDir, aIdList[i], BODY_FILE_FINAL,
376 getter_AddRefs(finalFile));
377 if (NS_WARN_IF(NS_FAILED(rv))) {
378 return rv;
379 }
380
381 rv = RemoveNsIFile(aQuotaInfo, finalFile);
382 // Again, only treat removal as hard failure in debug build.
383 MOZ_ASSERT(NS_SUCCEEDED(rv));
384 }
385
386 return NS_OK;
387 }
388
389 namespace {
390
BodyIdToFile(nsIFile * aBaseDir,const nsID & aId,BodyFileType aType,nsIFile ** aBodyFileOut)391 nsresult BodyIdToFile(nsIFile* aBaseDir, const nsID& aId, BodyFileType aType,
392 nsIFile** aBodyFileOut) {
393 MOZ_DIAGNOSTIC_ASSERT(aBaseDir);
394 MOZ_DIAGNOSTIC_ASSERT(aBodyFileOut);
395
396 *aBodyFileOut = nullptr;
397
398 nsresult rv = BodyGetCacheDir(aBaseDir, aId, aBodyFileOut);
399 if (NS_WARN_IF(NS_FAILED(rv))) {
400 return rv;
401 }
402 MOZ_DIAGNOSTIC_ASSERT(*aBodyFileOut);
403
404 char idString[NSID_LENGTH];
405 aId.ToProvidedString(idString);
406
407 NS_ConvertASCIItoUTF16 fileName(idString);
408
409 if (aType == BODY_FILE_FINAL) {
410 fileName.AppendLiteral(".final");
411 } else {
412 fileName.AppendLiteral(".tmp");
413 }
414
415 rv = (*aBodyFileOut)->Append(fileName);
416 if (NS_WARN_IF(NS_FAILED(rv))) {
417 return rv;
418 }
419
420 return rv;
421 }
422
RoundUp(const int64_t aX,const int64_t aY)423 int64_t RoundUp(const int64_t aX, const int64_t aY) {
424 MOZ_DIAGNOSTIC_ASSERT(aX >= 0);
425 MOZ_DIAGNOSTIC_ASSERT(aY > 0);
426
427 MOZ_DIAGNOSTIC_ASSERT(INT64_MAX - ((aX - 1) / aY) * aY >= aY);
428 return aY + ((aX - 1) / aY) * aY;
429 }
430
BodyGeneratePadding(const int64_t aBodyFileSize,const uint32_t aPaddingInfo)431 int64_t BodyGeneratePadding(const int64_t aBodyFileSize,
432 const uint32_t aPaddingInfo) {
433 // Generate padding
434 int64_t randomSize = static_cast<int64_t>(aPaddingInfo);
435 MOZ_DIAGNOSTIC_ASSERT(INT64_MAX - aBodyFileSize >= randomSize);
436 randomSize += aBodyFileSize;
437
438 return RoundUp(randomSize, kRoundUpNumber) - aBodyFileSize;
439 }
440
LockedDirectoryPaddingWrite(nsIFile * aBaseDir,DirPaddingFile aPaddingFileType,int64_t aPaddingSize)441 nsresult LockedDirectoryPaddingWrite(nsIFile* aBaseDir,
442 DirPaddingFile aPaddingFileType,
443 int64_t aPaddingSize) {
444 MOZ_DIAGNOSTIC_ASSERT(aBaseDir);
445 MOZ_DIAGNOSTIC_ASSERT(aPaddingSize >= 0);
446
447 nsCOMPtr<nsIFile> file;
448 nsresult rv = aBaseDir->Clone(getter_AddRefs(file));
449 if (NS_WARN_IF(NS_FAILED(rv))) {
450 return rv;
451 }
452
453 if (aPaddingFileType == DirPaddingFile::TMP_FILE) {
454 rv = file->Append(NS_LITERAL_STRING(PADDING_TMP_FILE_NAME));
455 } else {
456 rv = file->Append(NS_LITERAL_STRING(PADDING_FILE_NAME));
457 }
458 if (NS_WARN_IF(NS_FAILED(rv))) {
459 return rv;
460 }
461
462 nsCOMPtr<nsIOutputStream> outputStream;
463 rv = NS_NewLocalFileOutputStream(getter_AddRefs(outputStream), file);
464 if (NS_WARN_IF(NS_FAILED(rv))) {
465 return rv;
466 }
467
468 nsCOMPtr<nsIObjectOutputStream> objectStream =
469 NS_NewObjectOutputStream(outputStream);
470
471 rv = objectStream->Write64(aPaddingSize);
472 if (NS_WARN_IF(NS_FAILED(rv))) {
473 return rv;
474 }
475
476 return rv;
477 }
478
479 } // namespace
480
BodyDeleteOrphanedFiles(const QuotaInfo & aQuotaInfo,nsIFile * aBaseDir,nsTArray<nsID> & aKnownBodyIdList)481 nsresult BodyDeleteOrphanedFiles(const QuotaInfo& aQuotaInfo, nsIFile* aBaseDir,
482 nsTArray<nsID>& aKnownBodyIdList) {
483 MOZ_DIAGNOSTIC_ASSERT(aBaseDir);
484
485 // body files are stored in a directory structure like:
486 //
487 // /morgue/01/{01fdddb2-884d-4c3d-95ba-0c8062f6c325}.final
488 // /morgue/02/{02fdddb2-884d-4c3d-95ba-0c8062f6c325}.tmp
489
490 nsCOMPtr<nsIFile> dir;
491 nsresult rv = aBaseDir->Clone(getter_AddRefs(dir));
492 if (NS_WARN_IF(NS_FAILED(rv))) {
493 return rv;
494 }
495
496 // Add the root morgue directory
497 rv = dir->Append(NS_LITERAL_STRING("morgue"));
498 if (NS_WARN_IF(NS_FAILED(rv))) {
499 return rv;
500 }
501
502 nsCOMPtr<nsISimpleEnumerator> entries;
503 rv = dir->GetDirectoryEntries(getter_AddRefs(entries));
504 if (NS_WARN_IF(NS_FAILED(rv))) {
505 return rv;
506 }
507
508 // Iterate over all the intermediate morgue subdirs
509 bool hasMore = false;
510 while (NS_SUCCEEDED(rv = entries->HasMoreElements(&hasMore)) && hasMore) {
511 nsCOMPtr<nsISupports> entry;
512 rv = entries->GetNext(getter_AddRefs(entry));
513 if (NS_WARN_IF(NS_FAILED(rv))) {
514 return rv;
515 }
516
517 nsCOMPtr<nsIFile> subdir = do_QueryInterface(entry);
518
519 bool isDir = false;
520 rv = subdir->IsDirectory(&isDir);
521 if (NS_WARN_IF(NS_FAILED(rv))) {
522 return rv;
523 }
524
525 // If a file got in here somehow, try to remove it and move on
526 if (NS_WARN_IF(!isDir)) {
527 rv = RemoveNsIFile(aQuotaInfo, subdir);
528 MOZ_ASSERT(NS_SUCCEEDED(rv));
529 continue;
530 }
531
532 nsCOMPtr<nsISimpleEnumerator> subEntries;
533 rv = subdir->GetDirectoryEntries(getter_AddRefs(subEntries));
534 if (NS_WARN_IF(NS_FAILED(rv))) {
535 return rv;
536 }
537
538 // Now iterate over all the files in the subdir
539 bool subHasMore = false;
540 while (NS_SUCCEEDED(rv = subEntries->HasMoreElements(&subHasMore)) &&
541 subHasMore) {
542 nsCOMPtr<nsISupports> subEntry;
543 rv = subEntries->GetNext(getter_AddRefs(subEntry));
544 if (NS_WARN_IF(NS_FAILED(rv))) {
545 return rv;
546 }
547
548 nsCOMPtr<nsIFile> file = do_QueryInterface(subEntry);
549
550 nsAutoCString leafName;
551 rv = file->GetNativeLeafName(leafName);
552 if (NS_WARN_IF(NS_FAILED(rv))) {
553 return rv;
554 }
555
556 // Delete all tmp files regardless of known bodies. These are
557 // all considered orphans.
558 if (StringEndsWith(leafName, NS_LITERAL_CSTRING(".tmp"))) {
559 // remove recursively in case its somehow a directory
560 rv = RemoveNsIFileRecursively(aQuotaInfo, file);
561 MOZ_ASSERT(NS_SUCCEEDED(rv));
562 continue;
563 }
564
565 nsCString suffix(NS_LITERAL_CSTRING(".final"));
566
567 // Otherwise, it must be a .final file. If its not, then just
568 // skip it.
569 if (NS_WARN_IF(!StringEndsWith(leafName, suffix) ||
570 leafName.Length() != NSID_LENGTH - 1 + suffix.Length())) {
571 continue;
572 }
573
574 // Finally, parse the uuid out of the name. If its fails to parse,
575 // the ignore the file.
576 nsID id;
577 if (NS_WARN_IF(!id.Parse(leafName.BeginReading()))) {
578 continue;
579 }
580
581 if (!aKnownBodyIdList.Contains(id)) {
582 // remove recursively in case its somehow a directory
583 rv = RemoveNsIFileRecursively(aQuotaInfo, file);
584 MOZ_ASSERT(NS_SUCCEEDED(rv));
585 }
586 }
587 }
588
589 return rv;
590 }
591
592 namespace {
593
GetMarkerFileHandle(const QuotaInfo & aQuotaInfo,nsIFile ** aFileOut)594 nsresult GetMarkerFileHandle(const QuotaInfo& aQuotaInfo, nsIFile** aFileOut) {
595 MOZ_DIAGNOSTIC_ASSERT(aFileOut);
596
597 nsCOMPtr<nsIFile> marker;
598 nsresult rv = aQuotaInfo.mDir->Clone(getter_AddRefs(marker));
599 if (NS_WARN_IF(NS_FAILED(rv))) {
600 return rv;
601 }
602
603 rv = marker->Append(NS_LITERAL_STRING("cache"));
604 if (NS_WARN_IF(NS_FAILED(rv))) {
605 return rv;
606 }
607
608 rv = marker->Append(NS_LITERAL_STRING("context_open.marker"));
609 if (NS_WARN_IF(NS_FAILED(rv))) {
610 return rv;
611 }
612
613 marker.forget(aFileOut);
614
615 return rv;
616 }
617
618 } // namespace
619
CreateMarkerFile(const QuotaInfo & aQuotaInfo)620 nsresult CreateMarkerFile(const QuotaInfo& aQuotaInfo) {
621 nsCOMPtr<nsIFile> marker;
622 nsresult rv = GetMarkerFileHandle(aQuotaInfo, getter_AddRefs(marker));
623 if (NS_WARN_IF(NS_FAILED(rv))) {
624 return rv;
625 }
626
627 rv = marker->Create(nsIFile::NORMAL_FILE_TYPE, 0644);
628 if (rv == NS_ERROR_FILE_ALREADY_EXISTS) {
629 rv = NS_OK;
630 }
631
632 // Note, we don't need to fsync here. We only care about actually
633 // writing the marker if later modifications to the Cache are
634 // actually flushed to the disk. If the OS crashes before the marker
635 // is written then we are ensured no other changes to the Cache were
636 // flushed either.
637
638 return rv;
639 }
640
DeleteMarkerFile(const QuotaInfo & aQuotaInfo)641 nsresult DeleteMarkerFile(const QuotaInfo& aQuotaInfo) {
642 nsCOMPtr<nsIFile> marker;
643 nsresult rv = GetMarkerFileHandle(aQuotaInfo, getter_AddRefs(marker));
644 if (NS_WARN_IF(NS_FAILED(rv))) {
645 return rv;
646 }
647
648 rv = RemoveNsIFile(aQuotaInfo, marker);
649 MOZ_ASSERT(NS_SUCCEEDED(rv));
650
651 // Again, no fsync is necessary. If the OS crashes before the file
652 // removal is flushed, then the Cache will search for stale data on
653 // startup. This will cause the next Cache access to be a bit slow, but
654 // it seems appropriate after an OS crash.
655
656 return NS_OK;
657 }
658
MarkerFileExists(const QuotaInfo & aQuotaInfo)659 bool MarkerFileExists(const QuotaInfo& aQuotaInfo) {
660 nsCOMPtr<nsIFile> marker;
661 nsresult rv = GetMarkerFileHandle(aQuotaInfo, getter_AddRefs(marker));
662 if (NS_WARN_IF(NS_FAILED(rv))) {
663 return false;
664 }
665
666 bool exists = false;
667 rv = marker->Exists(&exists);
668 if (NS_WARN_IF(NS_FAILED(rv))) {
669 return false;
670 }
671
672 return exists;
673 }
674
675 // static
RemoveNsIFileRecursively(const QuotaInfo & aQuotaInfo,nsIFile * aFile)676 nsresult RemoveNsIFileRecursively(const QuotaInfo& aQuotaInfo, nsIFile* aFile) {
677 MOZ_DIAGNOSTIC_ASSERT(aFile);
678
679 bool isDirectory = false;
680 nsresult rv = aFile->IsDirectory(&isDirectory);
681 if (rv == NS_ERROR_FILE_NOT_FOUND ||
682 rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) {
683 return NS_OK;
684 }
685 if (NS_WARN_IF(NS_FAILED(rv))) {
686 return rv;
687 }
688
689 if (!isDirectory) {
690 return RemoveNsIFile(aQuotaInfo, aFile);
691 }
692
693 // Unfortunately, we need to traverse all the entries and delete files one by
694 // one to update their usages to the QuotaManager.
695 nsCOMPtr<nsISimpleEnumerator> entries;
696 rv = aFile->GetDirectoryEntries(getter_AddRefs(entries));
697 if (NS_WARN_IF(NS_FAILED(rv))) {
698 return rv;
699 }
700
701 bool hasMore = false;
702 while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) && hasMore) {
703 nsCOMPtr<nsISupports> entry;
704 rv = entries->GetNext(getter_AddRefs(entry));
705 if (NS_WARN_IF(NS_FAILED(rv))) {
706 return rv;
707 }
708
709 nsCOMPtr<nsIFile> file = do_QueryInterface(entry);
710 MOZ_ASSERT(file);
711
712 rv = RemoveNsIFileRecursively(aQuotaInfo, file);
713 if (NS_WARN_IF(NS_FAILED(rv))) {
714 return rv;
715 }
716 }
717 if (NS_WARN_IF(NS_FAILED(rv))) {
718 return rv;
719 }
720
721 // In the end, remove the folder
722 rv = aFile->Remove(/* recursive */ false);
723 if (NS_WARN_IF(NS_FAILED(rv))) {
724 return rv;
725 }
726
727 return rv;
728 }
729
730 // static
RemoveNsIFile(const QuotaInfo & aQuotaInfo,nsIFile * aFile)731 nsresult RemoveNsIFile(const QuotaInfo& aQuotaInfo, nsIFile* aFile) {
732 MOZ_DIAGNOSTIC_ASSERT(aFile);
733
734 int64_t fileSize = 0;
735 nsresult rv = aFile->GetFileSize(&fileSize);
736 if (rv == NS_ERROR_FILE_NOT_FOUND ||
737 rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) {
738 return NS_OK;
739 }
740 if (NS_WARN_IF(NS_FAILED(rv))) {
741 return rv;
742 }
743
744 rv = aFile->Remove(/* recursive */ false);
745 if (NS_WARN_IF(NS_FAILED(rv))) {
746 return rv;
747 }
748
749 if (fileSize > 0) {
750 DecreaseUsageForQuotaInfo(aQuotaInfo, fileSize);
751 }
752
753 return rv;
754 }
755
756 // static
DecreaseUsageForQuotaInfo(const QuotaInfo & aQuotaInfo,const int64_t & aUpdatingSize)757 void DecreaseUsageForQuotaInfo(const QuotaInfo& aQuotaInfo,
758 const int64_t& aUpdatingSize) {
759 MOZ_DIAGNOSTIC_ASSERT(aUpdatingSize > 0);
760
761 QuotaManager* quotaManager = QuotaManager::Get();
762 MOZ_DIAGNOSTIC_ASSERT(quotaManager);
763
764 quotaManager->DecreaseUsageForOrigin(PERSISTENCE_TYPE_DEFAULT,
765 aQuotaInfo.mGroup, aQuotaInfo.mOrigin,
766 aUpdatingSize);
767 }
768
769 // static
DirectoryPaddingFileExists(nsIFile * aBaseDir,DirPaddingFile aPaddingFileType)770 bool DirectoryPaddingFileExists(nsIFile* aBaseDir,
771 DirPaddingFile aPaddingFileType) {
772 MOZ_DIAGNOSTIC_ASSERT(aBaseDir);
773
774 nsCOMPtr<nsIFile> file;
775 nsresult rv = aBaseDir->Clone(getter_AddRefs(file));
776 if (NS_WARN_IF(NS_FAILED(rv))) {
777 return false;
778 }
779
780 nsString fileName;
781 if (aPaddingFileType == DirPaddingFile::TMP_FILE) {
782 fileName = NS_LITERAL_STRING(PADDING_TMP_FILE_NAME);
783 } else {
784 fileName = NS_LITERAL_STRING(PADDING_FILE_NAME);
785 }
786
787 rv = file->Append(fileName);
788 if (NS_WARN_IF(NS_FAILED(rv))) {
789 return false;
790 }
791
792 bool exists = false;
793 rv = file->Exists(&exists);
794 if (NS_WARN_IF(NS_FAILED(rv))) {
795 return false;
796 }
797
798 return exists;
799 }
800
801 // static
LockedDirectoryPaddingGet(nsIFile * aBaseDir,int64_t * aPaddingSizeOut)802 nsresult LockedDirectoryPaddingGet(nsIFile* aBaseDir,
803 int64_t* aPaddingSizeOut) {
804 MOZ_DIAGNOSTIC_ASSERT(aBaseDir);
805 MOZ_DIAGNOSTIC_ASSERT(aPaddingSizeOut);
806 MOZ_DIAGNOSTIC_ASSERT(
807 !DirectoryPaddingFileExists(aBaseDir, DirPaddingFile::TMP_FILE));
808
809 nsCOMPtr<nsIFile> file;
810 nsresult rv = aBaseDir->Clone(getter_AddRefs(file));
811 if (NS_WARN_IF(NS_FAILED(rv))) {
812 return rv;
813 }
814
815 rv = file->Append(NS_LITERAL_STRING(PADDING_FILE_NAME));
816 if (NS_WARN_IF(NS_FAILED(rv))) {
817 return rv;
818 }
819
820 nsCOMPtr<nsIInputStream> stream;
821 rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), file);
822 if (NS_WARN_IF(NS_FAILED(rv))) {
823 return rv;
824 }
825
826 nsCOMPtr<nsIInputStream> bufferedStream;
827 rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedStream),
828 stream.forget(), 512);
829 if (NS_WARN_IF(NS_FAILED(rv))) {
830 return rv;
831 }
832
833 nsCOMPtr<nsIObjectInputStream> objectStream =
834 NS_NewObjectInputStream(bufferedStream);
835
836 uint64_t paddingSize = 0;
837 rv = objectStream->Read64(&paddingSize);
838 if (NS_WARN_IF(NS_FAILED(rv))) {
839 return rv;
840 }
841
842 *aPaddingSizeOut = paddingSize;
843
844 return rv;
845 }
846
847 // static
LockedDirectoryPaddingInit(nsIFile * aBaseDir)848 nsresult LockedDirectoryPaddingInit(nsIFile* aBaseDir) {
849 MOZ_DIAGNOSTIC_ASSERT(aBaseDir);
850
851 nsresult rv = LockedDirectoryPaddingWrite(aBaseDir, DirPaddingFile::FILE, 0);
852 Unused << NS_WARN_IF(NS_FAILED(rv));
853
854 return rv;
855 }
856
857 // static
LockedUpdateDirectoryPaddingFile(nsIFile * aBaseDir,mozIStorageConnection * aConn,const int64_t aIncreaseSize,const int64_t aDecreaseSize,const bool aTemporaryFileExist)858 nsresult LockedUpdateDirectoryPaddingFile(nsIFile* aBaseDir,
859 mozIStorageConnection* aConn,
860 const int64_t aIncreaseSize,
861 const int64_t aDecreaseSize,
862 const bool aTemporaryFileExist) {
863 MOZ_DIAGNOSTIC_ASSERT(aBaseDir);
864 MOZ_DIAGNOSTIC_ASSERT(aConn);
865 MOZ_DIAGNOSTIC_ASSERT(aIncreaseSize >= 0);
866 MOZ_DIAGNOSTIC_ASSERT(aDecreaseSize >= 0);
867
868 int64_t currentPaddingSize = 0;
869 nsresult rv = NS_OK;
870 if (aTemporaryFileExist ||
871 NS_WARN_IF(NS_FAILED(
872 rv = LockedDirectoryPaddingGet(aBaseDir, ¤tPaddingSize)))) {
873 // Fail to read padding size from the dir padding file, so try to restore.
874 if (rv != NS_ERROR_FILE_NOT_FOUND &&
875 rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) {
876 // Not delete the temporary padding file here, because we're going to
877 // overwrite it below anyway.
878 rv = LockedDirectoryPaddingDeleteFile(aBaseDir, DirPaddingFile::FILE);
879 if (NS_WARN_IF(NS_FAILED(rv))) {
880 return rv;
881 }
882 }
883
884 // We don't need to add the aIncreaseSize or aDecreaseSize here, because
885 // it's already encompassed within the database.
886 rv = db::FindOverallPaddingSize(aConn, ¤tPaddingSize);
887 if (NS_WARN_IF(NS_FAILED(rv))) {
888 return rv;
889 }
890 } else {
891 bool shouldRevise = false;
892 if (aIncreaseSize > 0) {
893 if (INT64_MAX - currentPaddingSize < aDecreaseSize) {
894 shouldRevise = true;
895 } else {
896 currentPaddingSize += aIncreaseSize;
897 }
898 }
899
900 if (aDecreaseSize > 0) {
901 if (currentPaddingSize < aDecreaseSize) {
902 shouldRevise = true;
903 } else if (!shouldRevise) {
904 currentPaddingSize -= aDecreaseSize;
905 }
906 }
907
908 if (shouldRevise) {
909 // If somehow runing into this condition, the tracking padding size is
910 // incorrect.
911 // Delete padding file to indicate the padding size is incorrect for
912 // avoiding error happening in the following lines.
913 rv = LockedDirectoryPaddingDeleteFile(aBaseDir, DirPaddingFile::FILE);
914 if (NS_WARN_IF(NS_FAILED(rv))) {
915 return rv;
916 }
917
918 int64_t paddingSizeFromDB = 0;
919 rv = db::FindOverallPaddingSize(aConn, &paddingSizeFromDB);
920 if (NS_WARN_IF(NS_FAILED(rv))) {
921 return rv;
922 }
923 currentPaddingSize = paddingSizeFromDB;
924
925 // XXXtt: we should have an easy way to update (increase or recalulate)
926 // padding size in the QM. For now, only correct the padding size in
927 // padding file and make QM be able to get the correct size in the next QM
928 // initialization.
929 // We still want to catch this in the debug build.
930 MOZ_ASSERT(false, "The padding size is unsync with QM");
931 }
932
933 #ifdef DEBUG
934 int64_t paddingSizeFromDB = 0;
935 rv = db::FindOverallPaddingSize(aConn, &paddingSizeFromDB);
936 if (NS_WARN_IF(NS_FAILED(rv))) {
937 return rv;
938 }
939
940 MOZ_DIAGNOSTIC_ASSERT(paddingSizeFromDB == currentPaddingSize);
941 #endif // DEBUG
942 }
943
944 MOZ_DIAGNOSTIC_ASSERT(currentPaddingSize >= 0);
945
946 rv = LockedDirectoryPaddingTemporaryWrite(aBaseDir, currentPaddingSize);
947 if (NS_WARN_IF(NS_FAILED(rv))) {
948 return rv;
949 }
950
951 return rv;
952 }
953
954 // static
LockedDirectoryPaddingTemporaryWrite(nsIFile * aBaseDir,int64_t aPaddingSize)955 nsresult LockedDirectoryPaddingTemporaryWrite(nsIFile* aBaseDir,
956 int64_t aPaddingSize) {
957 MOZ_DIAGNOSTIC_ASSERT(aBaseDir);
958 MOZ_DIAGNOSTIC_ASSERT(aPaddingSize >= 0);
959
960 nsresult rv = LockedDirectoryPaddingWrite(aBaseDir, DirPaddingFile::TMP_FILE,
961 aPaddingSize);
962 if (NS_WARN_IF(NS_FAILED(rv))) {
963 return rv;
964 }
965
966 return rv;
967 }
968
969 // static
LockedDirectoryPaddingFinalizeWrite(nsIFile * aBaseDir)970 nsresult LockedDirectoryPaddingFinalizeWrite(nsIFile* aBaseDir) {
971 MOZ_DIAGNOSTIC_ASSERT(aBaseDir);
972 MOZ_DIAGNOSTIC_ASSERT(
973 DirectoryPaddingFileExists(aBaseDir, DirPaddingFile::TMP_FILE));
974
975 nsCOMPtr<nsIFile> file;
976 nsresult rv = aBaseDir->Clone(getter_AddRefs(file));
977 if (NS_WARN_IF(NS_FAILED(rv))) {
978 return rv;
979 }
980
981 rv = file->Append(NS_LITERAL_STRING(PADDING_TMP_FILE_NAME));
982 if (NS_WARN_IF(NS_FAILED(rv))) {
983 return rv;
984 }
985
986 rv = file->RenameTo(nullptr, NS_LITERAL_STRING(PADDING_FILE_NAME));
987 if (NS_WARN_IF(NS_FAILED(rv))) {
988 return rv;
989 }
990
991 return rv;
992 }
993
994 // static
LockedDirectoryPaddingRestore(nsIFile * aBaseDir,mozIStorageConnection * aConn,bool aMustRestore,int64_t * aPaddingSizeOut)995 nsresult LockedDirectoryPaddingRestore(nsIFile* aBaseDir,
996 mozIStorageConnection* aConn,
997 bool aMustRestore,
998 int64_t* aPaddingSizeOut) {
999 MOZ_DIAGNOSTIC_ASSERT(aBaseDir);
1000 MOZ_DIAGNOSTIC_ASSERT(aConn);
1001 MOZ_DIAGNOSTIC_ASSERT(aPaddingSizeOut);
1002
1003 // The content of padding file is untrusted, so remove it here.
1004 nsresult rv =
1005 LockedDirectoryPaddingDeleteFile(aBaseDir, DirPaddingFile::FILE);
1006 if (NS_WARN_IF(NS_FAILED(rv))) {
1007 return rv;
1008 }
1009
1010 int64_t paddingSize = 0;
1011 rv = db::FindOverallPaddingSize(aConn, &paddingSize);
1012 if (NS_WARN_IF(NS_FAILED(rv))) {
1013 return rv;
1014 }
1015
1016 MOZ_DIAGNOSTIC_ASSERT(paddingSize >= 0);
1017 *aPaddingSizeOut = paddingSize;
1018
1019 rv = LockedDirectoryPaddingWrite(aBaseDir, DirPaddingFile::FILE, paddingSize);
1020 if (NS_WARN_IF(NS_FAILED(rv))) {
1021 // If we cannot write the correct padding size to file, just keep the
1022 // temporary file and let the padding size to be recalculate in the next
1023 // action
1024 return aMustRestore ? rv : NS_OK;
1025 }
1026
1027 rv = LockedDirectoryPaddingDeleteFile(aBaseDir, DirPaddingFile::TMP_FILE);
1028 Unused << NS_WARN_IF(NS_FAILED(rv));
1029
1030 return rv;
1031 }
1032
1033 // static
LockedDirectoryPaddingDeleteFile(nsIFile * aBaseDir,DirPaddingFile aPaddingFileType)1034 nsresult LockedDirectoryPaddingDeleteFile(nsIFile* aBaseDir,
1035 DirPaddingFile aPaddingFileType) {
1036 MOZ_DIAGNOSTIC_ASSERT(aBaseDir);
1037
1038 nsCOMPtr<nsIFile> file;
1039 nsresult rv = aBaseDir->Clone(getter_AddRefs(file));
1040 if (NS_WARN_IF(NS_FAILED(rv))) {
1041 return rv;
1042 }
1043
1044 if (aPaddingFileType == DirPaddingFile::TMP_FILE) {
1045 rv = file->Append(NS_LITERAL_STRING(PADDING_TMP_FILE_NAME));
1046 } else {
1047 rv = file->Append(NS_LITERAL_STRING(PADDING_FILE_NAME));
1048 }
1049 if (NS_WARN_IF(NS_FAILED(rv))) {
1050 return rv;
1051 }
1052
1053 rv = file->Remove(/* recursive */ false);
1054 if (rv == NS_ERROR_FILE_NOT_FOUND ||
1055 rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) {
1056 return NS_OK;
1057 }
1058 if (NS_WARN_IF(NS_FAILED(rv))) {
1059 return rv;
1060 }
1061
1062 return rv;
1063 }
1064 } // namespace cache
1065 } // namespace dom
1066 } // namespace mozilla
1067