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, &currentPaddingSize)))) {
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, &currentPaddingSize);
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