Source code

Revision control

Other Tools

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 "nsServiceManagerUtils.h"
22
#include "nsString.h"
23
#include "nsThreadUtils.h"
24
25
namespace mozilla {
26
namespace dom {
27
namespace cache {
28
29
using mozilla::dom::quota::Client;
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
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
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
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
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
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 =
209
CreateFileOutputStream(PERSISTENCE_TYPE_DEFAULT, aQuotaInfo.mGroup,
210
aQuotaInfo.mOrigin, Client::DOMCACHE, tmpFile);
211
if (NS_WARN_IF(!fileStream)) {
212
return NS_ERROR_UNEXPECTED;
213
}
214
215
RefPtr<SnappyCompressOutputStream> compressed =
216
new SnappyCompressOutputStream(fileStream);
217
218
nsCOMPtr<nsIEventTarget> target =
219
do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
220
221
rv = NS_AsyncCopy(aSource, compressed, target, NS_ASYNCCOPY_VIA_WRITESEGMENTS,
222
compressed->BlockSize(), aCallback, aClosure, true,
223
true, // close streams
224
aCopyContextOut);
225
if (NS_WARN_IF(NS_FAILED(rv))) {
226
return rv;
227
}
228
229
return rv;
230
}
231
232
// static
233
void BodyCancelWrite(nsIFile* aBaseDir, nsISupports* aCopyContext) {
234
MOZ_DIAGNOSTIC_ASSERT(aBaseDir);
235
MOZ_DIAGNOSTIC_ASSERT(aCopyContext);
236
237
nsresult rv = NS_CancelAsyncCopy(aCopyContext, NS_ERROR_ABORT);
238
Unused << NS_WARN_IF(NS_FAILED(rv));
239
240
// The partially written file must be cleaned up after the async copy
241
// makes its callback.
242
}
243
244
// static
245
nsresult BodyFinalizeWrite(nsIFile* aBaseDir, const nsID& aId) {
246
MOZ_DIAGNOSTIC_ASSERT(aBaseDir);
247
248
nsCOMPtr<nsIFile> tmpFile;
249
nsresult rv =
250
BodyIdToFile(aBaseDir, aId, BODY_FILE_TMP, getter_AddRefs(tmpFile));
251
if (NS_WARN_IF(NS_FAILED(rv))) {
252
return rv;
253
}
254
255
nsCOMPtr<nsIFile> finalFile;
256
rv = BodyIdToFile(aBaseDir, aId, BODY_FILE_FINAL, getter_AddRefs(finalFile));
257
if (NS_WARN_IF(NS_FAILED(rv))) {
258
return rv;
259
}
260
261
nsAutoString finalFileName;
262
rv = finalFile->GetLeafName(finalFileName);
263
if (NS_WARN_IF(NS_FAILED(rv))) {
264
return rv;
265
}
266
267
// It's fine to not notify the QuotaManager that the path has been changed,
268
// because its path will be updated and its size will be recalculated when
269
// opening file next time.
270
rv = tmpFile->RenameTo(nullptr, finalFileName);
271
if (NS_WARN_IF(NS_FAILED(rv))) {
272
return rv;
273
}
274
275
return rv;
276
}
277
278
// static
279
nsresult BodyOpen(const QuotaInfo& aQuotaInfo, nsIFile* aBaseDir,
280
const nsID& aId, nsIInputStream** aStreamOut) {
281
MOZ_DIAGNOSTIC_ASSERT(aBaseDir);
282
MOZ_DIAGNOSTIC_ASSERT(aStreamOut);
283
284
nsCOMPtr<nsIFile> finalFile;
285
nsresult rv =
286
BodyIdToFile(aBaseDir, aId, BODY_FILE_FINAL, getter_AddRefs(finalFile));
287
if (NS_WARN_IF(NS_FAILED(rv))) {
288
return rv;
289
}
290
291
bool exists;
292
rv = finalFile->Exists(&exists);
293
if (NS_WARN_IF(NS_FAILED(rv))) {
294
return rv;
295
}
296
if (NS_WARN_IF(!exists)) {
297
return NS_ERROR_FILE_NOT_FOUND;
298
}
299
300
nsCOMPtr<nsIInputStream> fileStream =
301
CreateFileInputStream(PERSISTENCE_TYPE_DEFAULT, aQuotaInfo.mGroup,
302
aQuotaInfo.mOrigin, Client::DOMCACHE, finalFile);
303
if (NS_WARN_IF(!fileStream)) {
304
return NS_ERROR_UNEXPECTED;
305
}
306
307
fileStream.forget(aStreamOut);
308
309
return rv;
310
}
311
312
// static
313
nsresult BodyMaybeUpdatePaddingSize(const QuotaInfo& aQuotaInfo,
314
nsIFile* aBaseDir, const nsID& aId,
315
const uint32_t aPaddingInfo,
316
int64_t* aPaddingSizeOut) {
317
MOZ_DIAGNOSTIC_ASSERT(aBaseDir);
318
MOZ_DIAGNOSTIC_ASSERT(aPaddingSizeOut);
319
320
nsCOMPtr<nsIFile> bodyFile;
321
nsresult rv =
322
BodyIdToFile(aBaseDir, aId, BODY_FILE_TMP, getter_AddRefs(bodyFile));
323
if (NS_WARN_IF(NS_FAILED(rv))) {
324
return rv;
325
}
326
327
MOZ_DIAGNOSTIC_ASSERT(bodyFile);
328
329
QuotaManager* quotaManager = QuotaManager::Get();
330
MOZ_DIAGNOSTIC_ASSERT(quotaManager);
331
332
int64_t fileSize = 0;
333
RefPtr<QuotaObject> quotaObject = quotaManager->GetQuotaObject(
334
PERSISTENCE_TYPE_DEFAULT, aQuotaInfo.mGroup, aQuotaInfo.mOrigin,
335
Client::DOMCACHE, bodyFile, -1, &fileSize);
336
MOZ_DIAGNOSTIC_ASSERT(quotaObject);
337
MOZ_DIAGNOSTIC_ASSERT(fileSize >= 0);
339
if (!quotaObject) {
340
return NS_ERROR_UNEXPECTED;
341
}
342
343
if (*aPaddingSizeOut == InternalResponse::UNKNOWN_PADDING_SIZE) {
344
*aPaddingSizeOut = BodyGeneratePadding(fileSize, aPaddingInfo);
345
}
346
347
MOZ_DIAGNOSTIC_ASSERT(*aPaddingSizeOut >= 0);
348
349
if (!quotaObject->IncreaseSize(*aPaddingSizeOut)) {
350
return NS_ERROR_FILE_NO_DEVICE_SPACE;
351
}
352
353
return rv;
354
}
355
356
// static
357
nsresult BodyDeleteFiles(const QuotaInfo& aQuotaInfo, nsIFile* aBaseDir,
358
const nsTArray<nsID>& aIdList) {
359
nsresult rv = NS_OK;
360
for (const auto id : aIdList) {
361
nsCOMPtr<nsIFile> bodyDir;
362
rv = BodyGetCacheDir(aBaseDir, id, getter_AddRefs(bodyDir));
363
if (NS_WARN_IF(NS_FAILED(rv))) {
364
return rv;
365
}
366
367
const auto removeFileForId = [&aQuotaInfo, &id](nsIFile* bodyFile,
368
const nsACString& leafName,
369
bool& fileDeleted) {
370
MOZ_DIAGNOSTIC_ASSERT(bodyFile);
371
372
nsID fileId;
373
if (NS_WARN_IF(!fileId.Parse(leafName.BeginReading()))) {
374
DebugOnly<nsresult> result =
375
RemoveNsIFile(aQuotaInfo, bodyFile, /* aTrackQuota */ false);
376
MOZ_ASSERT(NS_SUCCEEDED(result));
377
fileDeleted = true;
378
return NS_OK;
379
}
380
381
if (id.Equals(fileId)) {
382
DebugOnly<nsresult> result = RemoveNsIFile(aQuotaInfo, bodyFile);
383
MOZ_ASSERT(NS_SUCCEEDED(result));
384
fileDeleted = true;
385
return NS_OK;
386
}
387
388
fileDeleted = false;
389
return NS_OK;
390
};
391
rv = BodyTraverseFiles(aQuotaInfo, bodyDir, removeFileForId,
392
/* aCanRemoveFiles */ false,
393
/* aTrackQuota */ true);
394
if (NS_WARN_IF(NS_FAILED(rv))) {
395
return rv;
396
}
397
}
398
399
return NS_OK;
400
}
401
402
namespace {
403
404
nsresult BodyIdToFile(nsIFile* aBaseDir, const nsID& aId, BodyFileType aType,
405
nsIFile** aBodyFileOut) {
406
MOZ_DIAGNOSTIC_ASSERT(aBaseDir);
407
MOZ_DIAGNOSTIC_ASSERT(aBodyFileOut);
408
409
*aBodyFileOut = nullptr;
410
411
nsresult rv = BodyGetCacheDir(aBaseDir, aId, aBodyFileOut);
412
if (NS_WARN_IF(NS_FAILED(rv))) {
413
return rv;
414
}
415
MOZ_DIAGNOSTIC_ASSERT(*aBodyFileOut);
416
417
char idString[NSID_LENGTH];
418
aId.ToProvidedString(idString);
419
420
NS_ConvertASCIItoUTF16 fileName(idString);
421
422
if (aType == BODY_FILE_FINAL) {
423
fileName.AppendLiteral(".final");
424
} else {
425
fileName.AppendLiteral(".tmp");
426
}
427
428
rv = (*aBodyFileOut)->Append(fileName);
429
if (NS_WARN_IF(NS_FAILED(rv))) {
430
return rv;
431
}
432
433
return rv;
434
}
435
436
int64_t RoundUp(const int64_t aX, const int64_t aY) {
437
MOZ_DIAGNOSTIC_ASSERT(aX >= 0);
438
MOZ_DIAGNOSTIC_ASSERT(aY > 0);
439
440
MOZ_DIAGNOSTIC_ASSERT(INT64_MAX - ((aX - 1) / aY) * aY >= aY);
441
return aY + ((aX - 1) / aY) * aY;
442
}
443
444
int64_t BodyGeneratePadding(const int64_t aBodyFileSize,
445
const uint32_t aPaddingInfo) {
446
// Generate padding
447
int64_t randomSize = static_cast<int64_t>(aPaddingInfo);
448
MOZ_DIAGNOSTIC_ASSERT(INT64_MAX - aBodyFileSize >= randomSize);
449
randomSize += aBodyFileSize;
450
451
return RoundUp(randomSize, kRoundUpNumber) - aBodyFileSize;
452
}
453
454
nsresult LockedDirectoryPaddingWrite(nsIFile* aBaseDir,
455
DirPaddingFile aPaddingFileType,
456
int64_t aPaddingSize) {
457
MOZ_DIAGNOSTIC_ASSERT(aBaseDir);
458
MOZ_DIAGNOSTIC_ASSERT(aPaddingSize >= 0);
459
460
nsCOMPtr<nsIFile> file;
461
nsresult rv = aBaseDir->Clone(getter_AddRefs(file));
462
if (NS_WARN_IF(NS_FAILED(rv))) {
463
return rv;
464
}
465
466
if (aPaddingFileType == DirPaddingFile::TMP_FILE) {
467
rv = file->Append(NS_LITERAL_STRING(PADDING_TMP_FILE_NAME));
468
} else {
469
rv = file->Append(NS_LITERAL_STRING(PADDING_FILE_NAME));
470
}
471
if (NS_WARN_IF(NS_FAILED(rv))) {
472
return rv;
473
}
474
475
nsCOMPtr<nsIOutputStream> outputStream;
476
rv = NS_NewLocalFileOutputStream(getter_AddRefs(outputStream), file);
477
if (NS_WARN_IF(NS_FAILED(rv))) {
478
return rv;
479
}
480
481
nsCOMPtr<nsIObjectOutputStream> objectStream =
482
NS_NewObjectOutputStream(outputStream);
483
484
rv = objectStream->Write64(aPaddingSize);
485
if (NS_WARN_IF(NS_FAILED(rv))) {
486
return rv;
487
}
488
489
return rv;
490
}
491
492
} // namespace
493
494
nsresult BodyDeleteOrphanedFiles(const QuotaInfo& aQuotaInfo, nsIFile* aBaseDir,
495
nsTArray<nsID>& aKnownBodyIdList) {
496
MOZ_DIAGNOSTIC_ASSERT(aBaseDir);
497
498
// body files are stored in a directory structure like:
499
//
500
// /morgue/01/{01fdddb2-884d-4c3d-95ba-0c8062f6c325}.final
501
// /morgue/02/{02fdddb2-884d-4c3d-95ba-0c8062f6c325}.tmp
502
503
nsCOMPtr<nsIFile> dir;
504
nsresult rv = aBaseDir->Clone(getter_AddRefs(dir));
505
if (NS_WARN_IF(NS_FAILED(rv))) {
506
return rv;
507
}
508
509
// Add the root morgue directory
510
rv = dir->Append(NS_LITERAL_STRING("morgue"));
511
if (NS_WARN_IF(NS_FAILED(rv))) {
512
return rv;
513
}
514
515
nsCOMPtr<nsIDirectoryEnumerator> entries;
516
rv = dir->GetDirectoryEntries(getter_AddRefs(entries));
517
if (NS_WARN_IF(NS_FAILED(rv))) {
518
return rv;
519
}
520
521
// Iterate over all the intermediate morgue subdirs
522
nsCOMPtr<nsIFile> subdir;
523
while (NS_SUCCEEDED(rv = entries->GetNextFile(getter_AddRefs(subdir))) &&
524
subdir) {
525
bool isDir = false;
526
rv = subdir->IsDirectory(&isDir);
527
if (NS_WARN_IF(NS_FAILED(rv))) {
528
return rv;
529
}
530
531
// If a file got in here somehow, try to remove it and move on
532
if (NS_WARN_IF(!isDir)) {
533
DebugOnly<nsresult> result =
534
RemoveNsIFile(aQuotaInfo, subdir, /* aTrackQuota */ false);
535
MOZ_ASSERT(NS_SUCCEEDED(result));
536
continue;
537
}
538
539
const auto removeOrphanedFiles =
540
[&aQuotaInfo, &aKnownBodyIdList](
541
nsIFile* bodyFile, const nsACString& leafName, bool& fileDeleted) {
542
MOZ_DIAGNOSTIC_ASSERT(bodyFile);
543
// Finally, parse the uuid out of the name. If its fails to parse,
544
// the ignore the file.
545
nsID id;
546
if (NS_WARN_IF(!id.Parse(leafName.BeginReading()))) {
547
DebugOnly<nsresult> result = RemoveNsIFile(aQuotaInfo, bodyFile);
548
MOZ_ASSERT(NS_SUCCEEDED(result));
549
fileDeleted = true;
550
return NS_OK;
551
}
552
553
if (!aKnownBodyIdList.Contains(id)) {
554
DebugOnly<nsresult> result = RemoveNsIFile(aQuotaInfo, bodyFile);
555
MOZ_ASSERT(NS_SUCCEEDED(result));
556
fileDeleted = true;
557
return NS_OK;
558
}
559
560
fileDeleted = false;
561
return NS_OK;
562
};
563
rv = BodyTraverseFiles(aQuotaInfo, subdir, removeOrphanedFiles,
564
/* aCanRemoveFiles */ true,
565
/* aTrackQuota */ true);
566
if (NS_WARN_IF(NS_FAILED(rv))) {
567
return rv;
568
}
569
}
570
if (NS_WARN_IF(NS_FAILED(rv))) {
571
return rv;
572
}
573
574
return rv;
575
}
576
577
template <typename Func>
578
nsresult BodyTraverseFiles(const QuotaInfo& aQuotaInfo, nsIFile* aBodyDir,
579
const Func& aHandleFileFunc,
580
const bool aCanRemoveFiles, const bool aTrackQuota) {
581
MOZ_DIAGNOSTIC_ASSERT(aBodyDir);
582
583
nsresult rv;
584
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
585
nsCOMPtr<nsIFile> parentFile;
586
rv = aBodyDir->GetParent(getter_AddRefs(parentFile));
587
MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
588
MOZ_DIAGNOSTIC_ASSERT(parentFile);
589
590
nsAutoCString nativeLeafName;
591
rv = parentFile->GetNativeLeafName(nativeLeafName);
592
MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
593
594
MOZ_DIAGNOSTIC_ASSERT(
595
StringEndsWith(nativeLeafName, NS_LITERAL_CSTRING("morgue")));
596
#endif
597
598
nsCOMPtr<nsIDirectoryEnumerator> entries;
599
rv = aBodyDir->GetDirectoryEntries(getter_AddRefs(entries));
600
if (NS_WARN_IF(NS_FAILED(rv))) {
601
return rv;
602
}
603
604
bool isEmpty = true;
605
nsCOMPtr<nsIFile> file;
606
while (NS_SUCCEEDED(rv = entries->GetNextFile(getter_AddRefs(file))) &&
607
file) {
608
bool isDir = false;
609
rv = file->IsDirectory(&isDir);
610
if (NS_WARN_IF(NS_FAILED(rv))) {
611
return rv;
612
}
613
614
// If it's a directory somehow, try to remove it and move on
615
if (NS_WARN_IF(isDir)) {
616
DebugOnly<nsresult> result =
617
RemoveNsIFileRecursively(aQuotaInfo, file, /* aTrackQuota */ false);
618
MOZ_ASSERT(NS_SUCCEEDED(result));
619
continue;
620
}
621
622
nsAutoCString leafName;
623
rv = file->GetNativeLeafName(leafName);
624
if (NS_WARN_IF(NS_FAILED(rv))) {
625
return rv;
626
}
627
628
// Delete all tmp files regardless of known bodies. These are all
629
// considered orphans.
630
if (StringEndsWith(leafName, NS_LITERAL_CSTRING(".tmp"))) {
631
if (aCanRemoveFiles) {
632
DebugOnly<nsresult> result =
633
RemoveNsIFile(aQuotaInfo, file, aTrackQuota);
634
MOZ_ASSERT(NS_SUCCEEDED(result));
635
continue;
636
}
637
} else if (NS_WARN_IF(
638
!StringEndsWith(leafName, NS_LITERAL_CSTRING(".final")))) {
639
// Otherwise, it must be a .final file. If its not, then try to remove it
640
// and move on
641
DebugOnly<nsresult> result =
642
RemoveNsIFile(aQuotaInfo, file, /* aTrackQuota */ false);
643
MOZ_ASSERT(NS_SUCCEEDED(result));
644
continue;
645
}
646
647
bool fileDeleted;
648
rv = aHandleFileFunc(file, leafName, fileDeleted);
649
if (NS_WARN_IF(NS_FAILED(rv))) {
650
return rv;
651
}
652
if (fileDeleted) {
653
continue;
654
}
655
656
isEmpty = false;
657
}
658
if (NS_WARN_IF(NS_FAILED(rv))) {
659
return rv;
660
}
661
662
if (isEmpty && aCanRemoveFiles) {
663
DebugOnly<nsresult> result =
664
RemoveNsIFileRecursively(aQuotaInfo, aBodyDir, /* aTrackQuota */ false);
665
MOZ_ASSERT(NS_SUCCEEDED(result));
666
}
667
668
return rv;
669
}
670
671
namespace {
672
673
nsresult GetMarkerFileHandle(const QuotaInfo& aQuotaInfo, nsIFile** aFileOut) {
674
MOZ_DIAGNOSTIC_ASSERT(aFileOut);
675
676
nsCOMPtr<nsIFile> marker;
677
nsresult rv = aQuotaInfo.mDir->Clone(getter_AddRefs(marker));
678
if (NS_WARN_IF(NS_FAILED(rv))) {
679
return rv;
680
}
681
682
rv = marker->Append(NS_LITERAL_STRING("cache"));
683
if (NS_WARN_IF(NS_FAILED(rv))) {
684
return rv;
685
}
686
687
rv = marker->Append(NS_LITERAL_STRING("context_open.marker"));
688
if (NS_WARN_IF(NS_FAILED(rv))) {
689
return rv;
690
}
691
692
marker.forget(aFileOut);
693
694
return rv;
695
}
696
697
} // namespace
698
699
nsresult CreateMarkerFile(const QuotaInfo& aQuotaInfo) {
700
nsCOMPtr<nsIFile> marker;
701
nsresult rv = GetMarkerFileHandle(aQuotaInfo, getter_AddRefs(marker));
702
if (NS_WARN_IF(NS_FAILED(rv))) {
703
return rv;
704
}
705
706
rv = marker->Create(nsIFile::NORMAL_FILE_TYPE, 0644);
707
if (rv == NS_ERROR_FILE_ALREADY_EXISTS) {
708
rv = NS_OK;
709
}
710
711
// Note, we don't need to fsync here. We only care about actually
712
// writing the marker if later modifications to the Cache are
713
// actually flushed to the disk. If the OS crashes before the marker
714
// is written then we are ensured no other changes to the Cache were
715
// flushed either.
716
717
return rv;
718
}
719
720
nsresult DeleteMarkerFile(const QuotaInfo& aQuotaInfo) {
721
nsCOMPtr<nsIFile> marker;
722
nsresult rv = GetMarkerFileHandle(aQuotaInfo, getter_AddRefs(marker));
723
if (NS_WARN_IF(NS_FAILED(rv))) {
724
return rv;
725
}
726
727
DebugOnly<nsresult> result =
728
RemoveNsIFile(aQuotaInfo, marker, /* aTrackQuota */ false);
729
MOZ_ASSERT(NS_SUCCEEDED(result));
730
731
// Again, no fsync is necessary. If the OS crashes before the file
732
// removal is flushed, then the Cache will search for stale data on
733
// startup. This will cause the next Cache access to be a bit slow, but
734
// it seems appropriate after an OS crash.
735
736
return NS_OK;
737
}
738
739
bool MarkerFileExists(const QuotaInfo& aQuotaInfo) {
740
nsCOMPtr<nsIFile> marker;
741
nsresult rv = GetMarkerFileHandle(aQuotaInfo, getter_AddRefs(marker));
742
if (NS_WARN_IF(NS_FAILED(rv))) {
743
return false;
744
}
745
746
bool exists = false;
747
rv = marker->Exists(&exists);
748
if (NS_WARN_IF(NS_FAILED(rv))) {
749
return false;
750
}
751
752
return exists;
753
}
754
755
// static
756
nsresult RemoveNsIFileRecursively(const QuotaInfo& aQuotaInfo, nsIFile* aFile,
757
const bool aTrackQuota) {
758
MOZ_DIAGNOSTIC_ASSERT(aFile);
759
760
bool isDirectory = false;
761
nsresult rv = aFile->IsDirectory(&isDirectory);
762
if (rv == NS_ERROR_FILE_NOT_FOUND ||
763
rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) {
764
return NS_OK;
765
}
766
if (NS_WARN_IF(NS_FAILED(rv))) {
767
return rv;
768
}
769
770
if (!isDirectory) {
771
return RemoveNsIFile(aQuotaInfo, aFile, aTrackQuota);
772
}
773
774
// Unfortunately, we need to traverse all the entries and delete files one by
775
// one to update their usages to the QuotaManager.
776
nsCOMPtr<nsIDirectoryEnumerator> entries;
777
rv = aFile->GetDirectoryEntries(getter_AddRefs(entries));
778
if (NS_WARN_IF(NS_FAILED(rv))) {
779
return rv;
780
}
781
782
nsCOMPtr<nsIFile> file;
783
while (NS_SUCCEEDED((rv = entries->GetNextFile(getter_AddRefs(file)))) &&
784
file) {
785
rv = RemoveNsIFileRecursively(aQuotaInfo, file, aTrackQuota);
786
if (NS_WARN_IF(NS_FAILED(rv))) {
787
return rv;
788
}
789
}
790
if (NS_WARN_IF(NS_FAILED(rv))) {
791
return rv;
792
}
793
794
// In the end, remove the folder
795
rv = aFile->Remove(/* recursive */ false);
796
if (NS_WARN_IF(NS_FAILED(rv))) {
797
return rv;
798
}
799
800
return rv;
801
}
802
803
// static
804
nsresult RemoveNsIFile(const QuotaInfo& aQuotaInfo, nsIFile* aFile,
805
const bool aTrackQuota) {
806
MOZ_DIAGNOSTIC_ASSERT(aFile);
807
808
nsresult rv;
809
int64_t fileSize = 0;
810
if (aTrackQuota) {
811
rv = aFile->GetFileSize(&fileSize);
812
if (rv == NS_ERROR_FILE_NOT_FOUND ||
813
rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) {
814
return NS_OK;
815
}
816
if (NS_WARN_IF(NS_FAILED(rv))) {
817
return rv;
818
}
819
}
820
821
rv = aFile->Remove(/* recursive */ false);
822
if (rv == NS_ERROR_FILE_NOT_FOUND ||
823
rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) {
824
MOZ_ASSERT(!aTrackQuota);
825
return NS_OK;
826
}
827
if (NS_WARN_IF(NS_FAILED(rv))) {
828
return rv;
829
}
830
831
if (aTrackQuota && fileSize > 0) {
832
DecreaseUsageForQuotaInfo(aQuotaInfo, fileSize);
833
}
834
835
return rv;
836
}
837
838
// static
839
void DecreaseUsageForQuotaInfo(const QuotaInfo& aQuotaInfo,
840
const int64_t& aUpdatingSize) {
841
MOZ_DIAGNOSTIC_ASSERT(aUpdatingSize > 0);
842
843
QuotaManager* quotaManager = QuotaManager::Get();
844
MOZ_DIAGNOSTIC_ASSERT(quotaManager);
845
846
quotaManager->DecreaseUsageForOrigin(PERSISTENCE_TYPE_DEFAULT,
847
aQuotaInfo.mGroup, aQuotaInfo.mOrigin,
848
Client::DOMCACHE, aUpdatingSize);
849
}
850
851
// static
852
bool DirectoryPaddingFileExists(nsIFile* aBaseDir,
853
DirPaddingFile aPaddingFileType) {
854
MOZ_DIAGNOSTIC_ASSERT(aBaseDir);
855
856
nsCOMPtr<nsIFile> file;
857
nsresult rv = aBaseDir->Clone(getter_AddRefs(file));
858
if (NS_WARN_IF(NS_FAILED(rv))) {
859
return false;
860
}
861
862
nsString fileName;
863
if (aPaddingFileType == DirPaddingFile::TMP_FILE) {
864
fileName = NS_LITERAL_STRING(PADDING_TMP_FILE_NAME);
865
} else {
866
fileName = NS_LITERAL_STRING(PADDING_FILE_NAME);
867
}
868
869
rv = file->Append(fileName);
870
if (NS_WARN_IF(NS_FAILED(rv))) {
871
return false;
872
}
873
874
bool exists = false;
875
rv = file->Exists(&exists);
876
if (NS_WARN_IF(NS_FAILED(rv))) {
877
return false;
878
}
879
880
return exists;
881
}
882
883
// static
884
nsresult LockedDirectoryPaddingGet(nsIFile* aBaseDir,
885
int64_t* aPaddingSizeOut) {
886
MOZ_DIAGNOSTIC_ASSERT(aBaseDir);
887
MOZ_DIAGNOSTIC_ASSERT(aPaddingSizeOut);
888
MOZ_DIAGNOSTIC_ASSERT(
889
!DirectoryPaddingFileExists(aBaseDir, DirPaddingFile::TMP_FILE));
890
891
nsCOMPtr<nsIFile> file;
892
nsresult rv = aBaseDir->Clone(getter_AddRefs(file));
893
if (NS_WARN_IF(NS_FAILED(rv))) {
894
return rv;
895
}
896
897
rv = file->Append(NS_LITERAL_STRING(PADDING_FILE_NAME));
898
if (NS_WARN_IF(NS_FAILED(rv))) {
899
return rv;
900
}
901
902
nsCOMPtr<nsIInputStream> stream;
903
rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), file);
904
if (NS_WARN_IF(NS_FAILED(rv))) {
905
return rv;
906
}
907
908
nsCOMPtr<nsIInputStream> bufferedStream;
909
rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedStream),
910
stream.forget(), 512);
911
if (NS_WARN_IF(NS_FAILED(rv))) {
912
return rv;
913
}
914
915
nsCOMPtr<nsIObjectInputStream> objectStream =
916
NS_NewObjectInputStream(bufferedStream);
917
918
uint64_t paddingSize = 0;
919
rv = objectStream->Read64(&paddingSize);
920
if (NS_WARN_IF(NS_FAILED(rv))) {
921
return rv;
922
}
923
924
*aPaddingSizeOut = paddingSize;
925
926
return rv;
927
}
928
929
// static
930
nsresult LockedDirectoryPaddingInit(nsIFile* aBaseDir) {
931
MOZ_DIAGNOSTIC_ASSERT(aBaseDir);
932
933
nsresult rv = LockedDirectoryPaddingWrite(aBaseDir, DirPaddingFile::FILE, 0);
934
Unused << NS_WARN_IF(NS_FAILED(rv));
935
936
return rv;
937
}
938
939
// static
940
nsresult LockedUpdateDirectoryPaddingFile(nsIFile* aBaseDir,
941
mozIStorageConnection* aConn,
942
const int64_t aIncreaseSize,
943
const int64_t aDecreaseSize,
944
const bool aTemporaryFileExist) {
945
MOZ_DIAGNOSTIC_ASSERT(aBaseDir);
946
MOZ_DIAGNOSTIC_ASSERT(aConn);
947
MOZ_DIAGNOSTIC_ASSERT(aIncreaseSize >= 0);
948
MOZ_DIAGNOSTIC_ASSERT(aDecreaseSize >= 0);
949
950
int64_t currentPaddingSize = 0;
951
nsresult rv = NS_OK;
952
if (aTemporaryFileExist ||
953
NS_WARN_IF(NS_FAILED(
954
rv = LockedDirectoryPaddingGet(aBaseDir, &currentPaddingSize)))) {
955
// Fail to read padding size from the dir padding file, so try to restore.
956
if (rv != NS_ERROR_FILE_NOT_FOUND &&
957
rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) {
958
// Not delete the temporary padding file here, because we're going to
959
// overwrite it below anyway.
960
rv = LockedDirectoryPaddingDeleteFile(aBaseDir, DirPaddingFile::FILE);
961
if (NS_WARN_IF(NS_FAILED(rv))) {
962
return rv;
963
}
964
}
965
966
// We don't need to add the aIncreaseSize or aDecreaseSize here, because
967
// it's already encompassed within the database.
968
rv = db::FindOverallPaddingSize(aConn, &currentPaddingSize);
969
if (NS_WARN_IF(NS_FAILED(rv))) {
970
return rv;
971
}
972
} else {
973
bool shouldRevise = false;
974
if (aIncreaseSize > 0) {
975
if (INT64_MAX - currentPaddingSize < aDecreaseSize) {
976
shouldRevise = true;
977
} else {
978
currentPaddingSize += aIncreaseSize;
979
}
980
}
981
982
if (aDecreaseSize > 0) {
983
if (currentPaddingSize < aDecreaseSize) {
984
shouldRevise = true;
985
} else if (!shouldRevise) {
986
currentPaddingSize -= aDecreaseSize;
987
}
988
}
989
990
if (shouldRevise) {
991
// If somehow runing into this condition, the tracking padding size is
992
// incorrect.
993
// Delete padding file to indicate the padding size is incorrect for
994
// avoiding error happening in the following lines.
995
rv = LockedDirectoryPaddingDeleteFile(aBaseDir, DirPaddingFile::FILE);
996
if (NS_WARN_IF(NS_FAILED(rv))) {
997
return rv;
998
}
999
1000
int64_t paddingSizeFromDB = 0;
1001
rv = db::FindOverallPaddingSize(aConn, &paddingSizeFromDB);
1002
if (NS_WARN_IF(NS_FAILED(rv))) {
1003
return rv;
1004
}
1005
currentPaddingSize = paddingSizeFromDB;
1006
1007
// XXXtt: we should have an easy way to update (increase or recalulate)
1008
// padding size in the QM. For now, only correct the padding size in
1009
// padding file and make QM be able to get the correct size in the next QM
1010
// initialization.
1011
// We still want to catch this in the debug build.
1012
MOZ_ASSERT(false, "The padding size is unsync with QM");
1013
}
1014
1015
#ifdef DEBUG
1016
int64_t paddingSizeFromDB = 0;
1017
rv = db::FindOverallPaddingSize(aConn, &paddingSizeFromDB);
1018
if (NS_WARN_IF(NS_FAILED(rv))) {
1019
return rv;
1020
}
1021
1022
MOZ_DIAGNOSTIC_ASSERT(paddingSizeFromDB == currentPaddingSize);
1023
#endif // DEBUG
1024
}
1025
1026
MOZ_DIAGNOSTIC_ASSERT(currentPaddingSize >= 0);
1027
1028
rv = LockedDirectoryPaddingTemporaryWrite(aBaseDir, currentPaddingSize);
1029
if (NS_WARN_IF(NS_FAILED(rv))) {
1030
return rv;
1031
}
1032
1033
return rv;
1034
}
1035
1036
// static
1037
nsresult LockedDirectoryPaddingTemporaryWrite(nsIFile* aBaseDir,
1038
int64_t aPaddingSize) {
1039
MOZ_DIAGNOSTIC_ASSERT(aBaseDir);
1040
MOZ_DIAGNOSTIC_ASSERT(aPaddingSize >= 0);
1041
1042
nsresult rv = LockedDirectoryPaddingWrite(aBaseDir, DirPaddingFile::TMP_FILE,
1043
aPaddingSize);
1044
if (NS_WARN_IF(NS_FAILED(rv))) {
1045
return rv;
1046
}
1047
1048
return rv;
1049
}
1050
1051
// static
1052
nsresult LockedDirectoryPaddingFinalizeWrite(nsIFile* aBaseDir) {
1053
MOZ_DIAGNOSTIC_ASSERT(aBaseDir);
1054
MOZ_DIAGNOSTIC_ASSERT(
1055
DirectoryPaddingFileExists(aBaseDir, DirPaddingFile::TMP_FILE));
1056
1057
nsCOMPtr<nsIFile> file;
1058
nsresult rv = aBaseDir->Clone(getter_AddRefs(file));
1059
if (NS_WARN_IF(NS_FAILED(rv))) {
1060
return rv;
1061
}
1062
1063
rv = file->Append(NS_LITERAL_STRING(PADDING_TMP_FILE_NAME));
1064
if (NS_WARN_IF(NS_FAILED(rv))) {
1065
return rv;
1066
}
1067
1068
rv = file->RenameTo(nullptr, NS_LITERAL_STRING(PADDING_FILE_NAME));
1069
if (NS_WARN_IF(NS_FAILED(rv))) {
1070
return rv;
1071
}
1072
1073
return rv;
1074
}
1075
1076
// static
1077
nsresult LockedDirectoryPaddingRestore(nsIFile* aBaseDir,
1078
mozIStorageConnection* aConn,
1079
bool aMustRestore,
1080
int64_t* aPaddingSizeOut) {
1081
MOZ_DIAGNOSTIC_ASSERT(aBaseDir);
1082
MOZ_DIAGNOSTIC_ASSERT(aConn);
1083
MOZ_DIAGNOSTIC_ASSERT(aPaddingSizeOut);
1084
1085
// The content of padding file is untrusted, so remove it here.
1086
nsresult rv =
1087
LockedDirectoryPaddingDeleteFile(aBaseDir, DirPaddingFile::FILE);
1088
if (NS_WARN_IF(NS_FAILED(rv))) {
1089
return rv;
1090
}
1091
1092
int64_t paddingSize = 0;
1093
rv = db::FindOverallPaddingSize(aConn, &paddingSize);
1094
if (NS_WARN_IF(NS_FAILED(rv))) {
1095
return rv;
1096
}
1097
1098
MOZ_DIAGNOSTIC_ASSERT(paddingSize >= 0);
1099
*aPaddingSizeOut = paddingSize;
1100
1101
rv = LockedDirectoryPaddingWrite(aBaseDir, DirPaddingFile::FILE, paddingSize);
1102
if (NS_WARN_IF(NS_FAILED(rv))) {
1103
// If we cannot write the correct padding size to file, just keep the
1104
// temporary file and let the padding size to be recalculate in the next
1105
// action
1106
return aMustRestore ? rv : NS_OK;
1107
}
1108
1109
rv = LockedDirectoryPaddingDeleteFile(aBaseDir, DirPaddingFile::TMP_FILE);
1110
Unused << NS_WARN_IF(NS_FAILED(rv));
1111
1112
return rv;
1113
}
1114
1115
// static
1116
nsresult LockedDirectoryPaddingDeleteFile(nsIFile* aBaseDir,
1117
DirPaddingFile aPaddingFileType) {
1118
MOZ_DIAGNOSTIC_ASSERT(aBaseDir);
1119
1120
nsCOMPtr<nsIFile> file;
1121
nsresult rv = aBaseDir->Clone(getter_AddRefs(file));
1122
if (NS_WARN_IF(NS_FAILED(rv))) {
1123
return rv;
1124
}
1125
1126
if (aPaddingFileType == DirPaddingFile::TMP_FILE) {
1127
rv = file->Append(NS_LITERAL_STRING(PADDING_TMP_FILE_NAME));
1128
} else {
1129
rv = file->Append(NS_LITERAL_STRING(PADDING_FILE_NAME));
1130
}
1131
if (NS_WARN_IF(NS_FAILED(rv))) {
1132
return rv;
1133
}
1134
1135
rv = file->Remove(/* recursive */ false);
1136
if (rv == NS_ERROR_FILE_NOT_FOUND ||
1137
rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) {
1138
return NS_OK;
1139
}
1140
if (NS_WARN_IF(NS_FAILED(rv))) {
1141
return rv;
1142
}
1143
1144
return rv;
1145
}
1146
} // namespace cache
1147
} // namespace dom
1148
} // namespace mozilla