Coverage Report

Created: 2018-09-25 14:53

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