1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 
5 #include "CacheLog.h"
6 #include "CacheFileIOManager.h"
7 
8 #include "../cache/nsCacheUtils.h"
9 #include "CacheHashUtils.h"
10 #include "CacheStorageService.h"
11 #include "CacheIndex.h"
12 #include "CacheFileUtils.h"
13 #include "nsThreadUtils.h"
14 #include "CacheFile.h"
15 #include "CacheObserver.h"
16 #include "nsIFile.h"
17 #include "CacheFileContextEvictor.h"
18 #include "nsITimer.h"
19 #include "nsIDirectoryEnumerator.h"
20 #include "nsEffectiveTLDService.h"
21 #include "nsIObserverService.h"
22 #include "nsISizeOf.h"
23 #include "mozilla/net/MozURL.h"
24 #include "mozilla/Telemetry.h"
25 #include "mozilla/DebugOnly.h"
26 #include "mozilla/Services.h"
27 #include "mozilla/StoragePrincipalHelper.h"
28 #include "nsDirectoryServiceUtils.h"
29 #include "nsAppDirectoryServiceDefs.h"
30 #include "private/pprio.h"
31 #include "mozilla/IntegerPrintfMacros.h"
32 #include "mozilla/Preferences.h"
33 #include "nsNetUtil.h"
34 
35 // include files for ftruncate (or equivalent)
36 #if defined(XP_UNIX)
37 #  include <unistd.h>
38 #elif defined(XP_WIN)
39 #  include <windows.h>
40 #  undef CreateFile
41 #  undef CREATE_NEW
42 #else
43 // XXX add necessary include file for ftruncate (or equivalent)
44 #endif
45 
46 namespace mozilla::net {
47 
48 #define kOpenHandlesLimit 128
49 #define kMetadataWriteDelay 5000
50 #define kRemoveTrashStartDelay 60000    // in milliseconds
51 #define kSmartSizeUpdateInterval 60000  // in milliseconds
52 
53 #ifdef ANDROID
54 const uint32_t kMaxCacheSizeKB = 512 * 1024;  // 512 MB
55 #else
56 const uint32_t kMaxCacheSizeKB = 1024 * 1024;  // 1 GB
57 #endif
58 const uint32_t kMaxClearOnShutdownCacheSizeKB = 150 * 1024;  // 150 MB
59 
DispatchRelease()60 bool CacheFileHandle::DispatchRelease() {
61   if (CacheFileIOManager::IsOnIOThreadOrCeased()) {
62     return false;
63   }
64 
65   nsCOMPtr<nsIEventTarget> ioTarget = CacheFileIOManager::IOTarget();
66   if (!ioTarget) {
67     return false;
68   }
69 
70   nsresult rv = ioTarget->Dispatch(
71       NewNonOwningRunnableMethod("net::CacheFileHandle::Release", this,
72                                  &CacheFileHandle::Release),
73       nsIEventTarget::DISPATCH_NORMAL);
74   return NS_SUCCEEDED(rv);
75 }
76 
77 NS_IMPL_ADDREF(CacheFileHandle)
NS_IMETHODIMP_(MozExternalRefCountType)78 NS_IMETHODIMP_(MozExternalRefCountType)
79 CacheFileHandle::Release() {
80   nsrefcnt count = mRefCnt - 1;
81   if (DispatchRelease()) {
82     // Redispatched to the IO thread.
83     return count;
84   }
85 
86   MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
87 
88   LOG(("CacheFileHandle::Release() [this=%p, refcnt=%" PRIuPTR "]", this,
89        mRefCnt.get()));
90   MOZ_ASSERT(0 != mRefCnt, "dup release");
91   count = --mRefCnt;
92   NS_LOG_RELEASE(this, count, "CacheFileHandle");
93 
94   if (0 == count) {
95     mRefCnt = 1;
96     delete (this);
97     return 0;
98   }
99 
100   return count;
101 }
102 
103 NS_INTERFACE_MAP_BEGIN(CacheFileHandle)
NS_INTERFACE_MAP_ENTRY(nsISupports)104   NS_INTERFACE_MAP_ENTRY(nsISupports)
105 NS_INTERFACE_MAP_END
106 
107 CacheFileHandle::CacheFileHandle(const SHA1Sum::Hash* aHash, bool aPriority,
108                                  PinningStatus aPinning)
109     : mHash(aHash),
110       mIsDoomed(false),
111       mClosed(false),
112       mPriority(aPriority),
113       mSpecialFile(false),
114       mInvalid(false),
115       mFileExists(false),
116       mDoomWhenFoundPinned(false),
117       mDoomWhenFoundNonPinned(false),
118       mKilled(false),
119       mPinning(aPinning),
120       mFileSize(-1),
121       mFD(nullptr) {
122   // If we initialize mDoomed in the initialization list, that initialization is
123   // not guaranteeded to be atomic.  Whereas this assignment here is guaranteed
124   // to be atomic.  TSan will see this (atomic) assignment and be satisfied
125   // that cross-thread accesses to mIsDoomed are properly synchronized.
126   mIsDoomed = false;
127   LOG((
128       "CacheFileHandle::CacheFileHandle() [this=%p, hash=%08x%08x%08x%08x%08x]",
129       this, LOGSHA1(aHash)));
130 }
131 
CacheFileHandle(const nsACString & aKey,bool aPriority,PinningStatus aPinning)132 CacheFileHandle::CacheFileHandle(const nsACString& aKey, bool aPriority,
133                                  PinningStatus aPinning)
134     : mHash(nullptr),
135       mIsDoomed(false),
136       mClosed(false),
137       mPriority(aPriority),
138       mSpecialFile(true),
139       mInvalid(false),
140       mFileExists(false),
141       mDoomWhenFoundPinned(false),
142       mDoomWhenFoundNonPinned(false),
143       mKilled(false),
144       mPinning(aPinning),
145       mFileSize(-1),
146       mFD(nullptr),
147       mKey(aKey) {
148   // See comment above about the initialization of mIsDoomed.
149   mIsDoomed = false;
150   LOG(("CacheFileHandle::CacheFileHandle() [this=%p, key=%s]", this,
151        PromiseFlatCString(aKey).get()));
152 }
153 
~CacheFileHandle()154 CacheFileHandle::~CacheFileHandle() {
155   LOG(("CacheFileHandle::~CacheFileHandle() [this=%p]", this));
156 
157   MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
158 
159   RefPtr<CacheFileIOManager> ioMan = CacheFileIOManager::gInstance;
160   if (!IsClosed() && ioMan) {
161     ioMan->CloseHandleInternal(this);
162   }
163 }
164 
Log()165 void CacheFileHandle::Log() {
166   nsAutoCString leafName;
167   if (mFile) {
168     mFile->GetNativeLeafName(leafName);
169   }
170 
171   if (mSpecialFile) {
172     LOG(
173         ("CacheFileHandle::Log() - special file [this=%p, "
174          "isDoomed=%d, priority=%d, closed=%d, invalid=%d, "
175          "pinning=%" PRIu32 ", fileExists=%d, fileSize=%" PRId64
176          ", leafName=%s, key=%s]",
177          this, bool(mIsDoomed), bool(mPriority), bool(mClosed), bool(mInvalid),
178          static_cast<uint32_t>(mPinning), bool(mFileExists), int64_t(mFileSize),
179          leafName.get(), mKey.get()));
180   } else {
181     LOG(
182         ("CacheFileHandle::Log() - entry file [this=%p, "
183          "hash=%08x%08x%08x%08x%08x, "
184          "isDoomed=%d, priority=%d, closed=%d, invalid=%d, "
185          "pinning=%" PRIu32 ", fileExists=%d, fileSize=%" PRId64
186          ", leafName=%s, key=%s]",
187          this, LOGSHA1(mHash), bool(mIsDoomed), bool(mPriority), bool(mClosed),
188          bool(mInvalid), static_cast<uint32_t>(mPinning), bool(mFileExists),
189          int64_t(mFileSize), leafName.get(), mKey.get()));
190   }
191 }
192 
FileSizeInK() const193 uint32_t CacheFileHandle::FileSizeInK() const {
194   MOZ_ASSERT(mFileSize != -1);
195   uint64_t size64 = mFileSize;
196 
197   size64 += 0x3FF;
198   size64 >>= 10;
199 
200   uint32_t size;
201   if (size64 >> 32) {
202     NS_WARNING(
203         "CacheFileHandle::FileSizeInK() - FileSize is too large, "
204         "truncating to PR_UINT32_MAX");
205     size = PR_UINT32_MAX;
206   } else {
207     size = static_cast<uint32_t>(size64);
208   }
209 
210   return size;
211 }
212 
SetPinned(bool aPinned)213 bool CacheFileHandle::SetPinned(bool aPinned) {
214   LOG(("CacheFileHandle::SetPinned [this=%p, pinned=%d]", this, aPinned));
215 
216   MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
217 
218   mPinning = aPinned ? PinningStatus::PINNED : PinningStatus::NON_PINNED;
219 
220   if ((MOZ_UNLIKELY(mDoomWhenFoundPinned) && aPinned) ||
221       (MOZ_UNLIKELY(mDoomWhenFoundNonPinned) && !aPinned)) {
222     LOG(("  dooming, when: pinned=%d, non-pinned=%d, found: pinned=%d",
223          bool(mDoomWhenFoundPinned), bool(mDoomWhenFoundNonPinned), aPinned));
224 
225     mDoomWhenFoundPinned = false;
226     mDoomWhenFoundNonPinned = false;
227 
228     return false;
229   }
230 
231   return true;
232 }
233 
234 // Memory reporting
235 
SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const236 size_t CacheFileHandle::SizeOfExcludingThis(
237     mozilla::MallocSizeOf mallocSizeOf) const {
238   size_t n = 0;
239   nsCOMPtr<nsISizeOf> sizeOf;
240 
241   sizeOf = do_QueryInterface(mFile);
242   if (sizeOf) {
243     n += sizeOf->SizeOfIncludingThis(mallocSizeOf);
244   }
245 
246   n += mallocSizeOf(mFD);
247   n += mKey.SizeOfExcludingThisIfUnshared(mallocSizeOf);
248   return n;
249 }
250 
SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const251 size_t CacheFileHandle::SizeOfIncludingThis(
252     mozilla::MallocSizeOf mallocSizeOf) const {
253   return mallocSizeOf(this) + SizeOfExcludingThis(mallocSizeOf);
254 }
255 
256 /******************************************************************************
257  *  CacheFileHandles::HandleHashKey
258  *****************************************************************************/
259 
AddHandle(CacheFileHandle * aHandle)260 void CacheFileHandles::HandleHashKey::AddHandle(CacheFileHandle* aHandle) {
261   MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
262 
263   mHandles.InsertElementAt(0, aHandle);
264 }
265 
RemoveHandle(CacheFileHandle * aHandle)266 void CacheFileHandles::HandleHashKey::RemoveHandle(CacheFileHandle* aHandle) {
267   MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
268 
269   DebugOnly<bool> found{};
270   found = mHandles.RemoveElement(aHandle);
271   MOZ_ASSERT(found);
272 }
273 
274 already_AddRefed<CacheFileHandle>
GetNewestHandle()275 CacheFileHandles::HandleHashKey::GetNewestHandle() {
276   MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
277 
278   RefPtr<CacheFileHandle> handle;
279   if (mHandles.Length()) {
280     handle = mHandles[0];
281   }
282 
283   return handle.forget();
284 }
285 
GetHandles(nsTArray<RefPtr<CacheFileHandle>> & aResult)286 void CacheFileHandles::HandleHashKey::GetHandles(
287     nsTArray<RefPtr<CacheFileHandle>>& aResult) {
288   MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
289 
290   for (uint32_t i = 0; i < mHandles.Length(); ++i) {
291     CacheFileHandle* handle = mHandles[i];
292     aResult.AppendElement(handle);
293   }
294 }
295 
296 #ifdef DEBUG
297 
AssertHandlesState()298 void CacheFileHandles::HandleHashKey::AssertHandlesState() {
299   for (uint32_t i = 0; i < mHandles.Length(); ++i) {
300     CacheFileHandle* handle = mHandles[i];
301     MOZ_ASSERT(handle->IsDoomed());
302   }
303 }
304 
305 #endif
306 
SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const307 size_t CacheFileHandles::HandleHashKey::SizeOfExcludingThis(
308     mozilla::MallocSizeOf mallocSizeOf) const {
309   MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
310 
311   size_t n = 0;
312   n += mallocSizeOf(mHash.get());
313   for (uint32_t i = 0; i < mHandles.Length(); ++i) {
314     n += mHandles[i]->SizeOfIncludingThis(mallocSizeOf);
315   }
316 
317   return n;
318 }
319 
320 /******************************************************************************
321  *  CacheFileHandles
322  *****************************************************************************/
323 
CacheFileHandles()324 CacheFileHandles::CacheFileHandles() {
325   LOG(("CacheFileHandles::CacheFileHandles() [this=%p]", this));
326   MOZ_COUNT_CTOR(CacheFileHandles);
327 }
328 
~CacheFileHandles()329 CacheFileHandles::~CacheFileHandles() {
330   LOG(("CacheFileHandles::~CacheFileHandles() [this=%p]", this));
331   MOZ_COUNT_DTOR(CacheFileHandles);
332 }
333 
GetHandle(const SHA1Sum::Hash * aHash,CacheFileHandle ** _retval)334 nsresult CacheFileHandles::GetHandle(const SHA1Sum::Hash* aHash,
335                                      CacheFileHandle** _retval) {
336   MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
337   MOZ_ASSERT(aHash);
338 
339 #ifdef DEBUG_HANDLES
340   LOG(("CacheFileHandles::GetHandle() [hash=%08x%08x%08x%08x%08x]",
341        LOGSHA1(aHash)));
342 #endif
343 
344   // find hash entry for key
345   HandleHashKey* entry = mTable.GetEntry(*aHash);
346   if (!entry) {
347     LOG(
348         ("CacheFileHandles::GetHandle() hash=%08x%08x%08x%08x%08x "
349          "no handle entries found",
350          LOGSHA1(aHash)));
351     return NS_ERROR_NOT_AVAILABLE;
352   }
353 
354 #ifdef DEBUG_HANDLES
355   Log(entry);
356 #endif
357 
358   // Check if the entry is doomed
359   RefPtr<CacheFileHandle> handle = entry->GetNewestHandle();
360   if (!handle) {
361     LOG(
362         ("CacheFileHandles::GetHandle() hash=%08x%08x%08x%08x%08x "
363          "no handle found %p, entry %p",
364          LOGSHA1(aHash), handle.get(), entry));
365     return NS_ERROR_NOT_AVAILABLE;
366   }
367 
368   if (handle->IsDoomed()) {
369     LOG(
370         ("CacheFileHandles::GetHandle() hash=%08x%08x%08x%08x%08x "
371          "found doomed handle %p, entry %p",
372          LOGSHA1(aHash), handle.get(), entry));
373     return NS_ERROR_NOT_AVAILABLE;
374   }
375 
376   LOG(
377       ("CacheFileHandles::GetHandle() hash=%08x%08x%08x%08x%08x "
378        "found handle %p, entry %p",
379        LOGSHA1(aHash), handle.get(), entry));
380 
381   handle.forget(_retval);
382   return NS_OK;
383 }
384 
NewHandle(const SHA1Sum::Hash * aHash,bool aPriority,CacheFileHandle::PinningStatus aPinning)385 already_AddRefed<CacheFileHandle> CacheFileHandles::NewHandle(
386     const SHA1Sum::Hash* aHash, bool aPriority,
387     CacheFileHandle::PinningStatus aPinning) {
388   MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
389   MOZ_ASSERT(aHash);
390 
391 #ifdef DEBUG_HANDLES
392   LOG(("CacheFileHandles::NewHandle() [hash=%08x%08x%08x%08x%08x]",
393        LOGSHA1(aHash)));
394 #endif
395 
396   // find hash entry for key
397   HandleHashKey* entry = mTable.PutEntry(*aHash);
398 
399 #ifdef DEBUG_HANDLES
400   Log(entry);
401 #endif
402 
403 #ifdef DEBUG
404   entry->AssertHandlesState();
405 #endif
406 
407   RefPtr<CacheFileHandle> handle =
408       new CacheFileHandle(entry->Hash(), aPriority, aPinning);
409   entry->AddHandle(handle);
410 
411   LOG(
412       ("CacheFileHandles::NewHandle() hash=%08x%08x%08x%08x%08x "
413        "created new handle %p, entry=%p",
414        LOGSHA1(aHash), handle.get(), entry));
415   return handle.forget();
416 }
417 
RemoveHandle(CacheFileHandle * aHandle)418 void CacheFileHandles::RemoveHandle(CacheFileHandle* aHandle) {
419   MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
420   MOZ_ASSERT(aHandle);
421 
422   if (!aHandle) {
423     return;
424   }
425 
426 #ifdef DEBUG_HANDLES
427   LOG((
428       "CacheFileHandles::RemoveHandle() [handle=%p, hash=%08x%08x%08x%08x%08x]",
429       aHandle, LOGSHA1(aHandle->Hash())));
430 #endif
431 
432   // find hash entry for key
433   HandleHashKey* entry = mTable.GetEntry(*aHandle->Hash());
434   if (!entry) {
435     MOZ_ASSERT(CacheFileIOManager::IsShutdown(),
436                "Should find entry when removing a handle before shutdown");
437 
438     LOG(
439         ("CacheFileHandles::RemoveHandle() hash=%08x%08x%08x%08x%08x "
440          "no entries found",
441          LOGSHA1(aHandle->Hash())));
442     return;
443   }
444 
445 #ifdef DEBUG_HANDLES
446   Log(entry);
447 #endif
448 
449   LOG(
450       ("CacheFileHandles::RemoveHandle() hash=%08x%08x%08x%08x%08x "
451        "removing handle %p",
452        LOGSHA1(entry->Hash()), aHandle));
453   entry->RemoveHandle(aHandle);
454 
455   if (entry->IsEmpty()) {
456     LOG(
457         ("CacheFileHandles::RemoveHandle() hash=%08x%08x%08x%08x%08x "
458          "list is empty, removing entry %p",
459          LOGSHA1(entry->Hash()), entry));
460     mTable.RemoveEntry(entry);
461   }
462 }
463 
GetAllHandles(nsTArray<RefPtr<CacheFileHandle>> * _retval)464 void CacheFileHandles::GetAllHandles(
465     nsTArray<RefPtr<CacheFileHandle>>* _retval) {
466   MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
467   for (auto iter = mTable.Iter(); !iter.Done(); iter.Next()) {
468     iter.Get()->GetHandles(*_retval);
469   }
470 }
471 
GetActiveHandles(nsTArray<RefPtr<CacheFileHandle>> * _retval)472 void CacheFileHandles::GetActiveHandles(
473     nsTArray<RefPtr<CacheFileHandle>>* _retval) {
474   MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
475   for (auto iter = mTable.Iter(); !iter.Done(); iter.Next()) {
476     RefPtr<CacheFileHandle> handle = iter.Get()->GetNewestHandle();
477     MOZ_ASSERT(handle);
478 
479     if (!handle->IsDoomed()) {
480       _retval->AppendElement(handle);
481     }
482   }
483 }
484 
ClearAll()485 void CacheFileHandles::ClearAll() {
486   MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
487   mTable.Clear();
488 }
489 
HandleCount()490 uint32_t CacheFileHandles::HandleCount() { return mTable.Count(); }
491 
492 #ifdef DEBUG_HANDLES
Log(CacheFileHandlesEntry * entry)493 void CacheFileHandles::Log(CacheFileHandlesEntry* entry) {
494   LOG(("CacheFileHandles::Log() BEGIN [entry=%p]", entry));
495 
496   nsTArray<RefPtr<CacheFileHandle>> array;
497   aEntry->GetHandles(array);
498 
499   for (uint32_t i = 0; i < array.Length(); ++i) {
500     CacheFileHandle* handle = array[i];
501     handle->Log();
502   }
503 
504   LOG(("CacheFileHandles::Log() END [entry=%p]", entry));
505 }
506 #endif
507 
508 // Memory reporting
509 
SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const510 size_t CacheFileHandles::SizeOfExcludingThis(
511     mozilla::MallocSizeOf mallocSizeOf) const {
512   MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
513 
514   return mTable.SizeOfExcludingThis(mallocSizeOf);
515 }
516 
517 // Events
518 
519 class ShutdownEvent : public Runnable {
520  public:
ShutdownEvent()521   ShutdownEvent()
522       : Runnable("net::ShutdownEvent"), mMonitor("ShutdownEvent.mMonitor") {}
523 
524  protected:
525   ~ShutdownEvent() = default;
526 
527  public:
Run()528   NS_IMETHOD Run() override {
529     MonitorAutoLock mon(mMonitor);
530 
531     CacheFileIOManager::gInstance->ShutdownInternal();
532 
533     mNotified = true;
534     mon.Notify();
535 
536     return NS_OK;
537   }
538 
PostAndWait()539   void PostAndWait() {
540     MonitorAutoLock mon(mMonitor);
541 
542     nsresult rv = CacheFileIOManager::gInstance->mIOThread->Dispatch(
543         this,
544         CacheIOThread::WRITE);  // When writes and closing of handles is done
545     MOZ_ASSERT(NS_SUCCEEDED(rv));
546 
547     // If we failed to post the even there's no reason to go into the loop
548     // because mNotified will never be set to true.
549     if (NS_FAILED(rv)) {
550       NS_WARNING("Posting ShutdownEvent task failed");
551       return;
552     }
553 
554     TimeDuration waitTime = TimeDuration::FromSeconds(1);
555     while (!mNotified) {
556       mon.Wait(waitTime);
557       if (!mNotified) {
558         // If there is any IO blocking on the IO thread, this will
559         // try to cancel it.  Returns no later than after two seconds.
560         MonitorAutoUnlock unmon(mMonitor);  // Prevent delays
561         CacheFileIOManager::gInstance->mIOThread->CancelBlockingIO();
562       }
563     }
564   }
565 
566  protected:
567   mozilla::Monitor mMonitor;
568   bool mNotified{false};
569 };
570 
571 // Class responsible for reporting IO performance stats
572 class IOPerfReportEvent {
573  public:
IOPerfReportEvent(CacheFileUtils::CachePerfStats::EDataType aType)574   explicit IOPerfReportEvent(CacheFileUtils::CachePerfStats::EDataType aType)
575       : mType(aType), mEventCounter(0) {}
576 
Start(CacheIOThread * aIOThread)577   void Start(CacheIOThread* aIOThread) {
578     mStartTime = TimeStamp::Now();
579     mEventCounter = aIOThread->EventCounter();
580   }
581 
Report(CacheIOThread * aIOThread)582   void Report(CacheIOThread* aIOThread) {
583     if (mStartTime.IsNull()) {
584       return;
585     }
586 
587     // Single IO operations can take less than 1ms. So we use microseconds to
588     // keep a good resolution of data.
589     uint32_t duration = (TimeStamp::Now() - mStartTime).ToMicroseconds();
590 
591     // This is a simple prefiltering of values that might differ a lot from the
592     // average value. Do not add the value to the filtered stats when the event
593     // had to wait in a long queue.
594     uint32_t eventCounter = aIOThread->EventCounter();
595     bool shortOnly = eventCounter - mEventCounter >= 5;
596 
597     CacheFileUtils::CachePerfStats::AddValue(mType, duration, shortOnly);
598   }
599 
600  protected:
601   CacheFileUtils::CachePerfStats::EDataType mType;
602   TimeStamp mStartTime;
603   uint32_t mEventCounter;
604 };
605 
606 class OpenFileEvent : public Runnable, public IOPerfReportEvent {
607  public:
OpenFileEvent(const nsACString & aKey,uint32_t aFlags,CacheFileIOListener * aCallback)608   OpenFileEvent(const nsACString& aKey, uint32_t aFlags,
609                 CacheFileIOListener* aCallback)
610       : Runnable("net::OpenFileEvent"),
611         IOPerfReportEvent(CacheFileUtils::CachePerfStats::IO_OPEN),
612         mFlags(aFlags),
613         mCallback(aCallback),
614         mKey(aKey) {
615     mIOMan = CacheFileIOManager::gInstance;
616     if (!(mFlags & CacheFileIOManager::SPECIAL_FILE)) {
617       Start(mIOMan->mIOThread);
618     }
619   }
620 
621  protected:
622   ~OpenFileEvent() = default;
623 
624  public:
Run()625   NS_IMETHOD Run() override {
626     nsresult rv = NS_OK;
627 
628     if (!(mFlags & CacheFileIOManager::SPECIAL_FILE)) {
629       SHA1Sum sum;
630       sum.update(mKey.BeginReading(), mKey.Length());
631       sum.finish(mHash);
632     }
633 
634     if (!mIOMan) {
635       rv = NS_ERROR_NOT_INITIALIZED;
636     } else {
637       if (mFlags & CacheFileIOManager::SPECIAL_FILE) {
638         rv = mIOMan->OpenSpecialFileInternal(mKey, mFlags,
639                                              getter_AddRefs(mHandle));
640       } else {
641         rv = mIOMan->OpenFileInternal(&mHash, mKey, mFlags,
642                                       getter_AddRefs(mHandle));
643         if (NS_SUCCEEDED(rv)) {
644           Report(mIOMan->mIOThread);
645         }
646       }
647       mIOMan = nullptr;
648       if (mHandle) {
649         if (mHandle->Key().IsEmpty()) {
650           mHandle->Key() = mKey;
651         }
652       }
653     }
654 
655     mCallback->OnFileOpened(mHandle, rv);
656     return NS_OK;
657   }
658 
659  protected:
660   SHA1Sum::Hash mHash{};
661   uint32_t mFlags;
662   nsCOMPtr<CacheFileIOListener> mCallback;
663   RefPtr<CacheFileIOManager> mIOMan;
664   RefPtr<CacheFileHandle> mHandle;
665   nsCString mKey;
666 };
667 
668 class ReadEvent : public Runnable, public IOPerfReportEvent {
669  public:
ReadEvent(CacheFileHandle * aHandle,int64_t aOffset,char * aBuf,int32_t aCount,CacheFileIOListener * aCallback)670   ReadEvent(CacheFileHandle* aHandle, int64_t aOffset, char* aBuf,
671             int32_t aCount, CacheFileIOListener* aCallback)
672       : Runnable("net::ReadEvent"),
673         IOPerfReportEvent(CacheFileUtils::CachePerfStats::IO_READ),
674         mHandle(aHandle),
675         mOffset(aOffset),
676         mBuf(aBuf),
677         mCount(aCount),
678         mCallback(aCallback) {
679     if (!mHandle->IsSpecialFile()) {
680       Start(CacheFileIOManager::gInstance->mIOThread);
681     }
682   }
683 
684  protected:
685   ~ReadEvent() = default;
686 
687  public:
Run()688   NS_IMETHOD Run() override {
689     nsresult rv;
690 
691     if (mHandle->IsClosed() || (mCallback && mCallback->IsKilled())) {
692       rv = NS_ERROR_NOT_INITIALIZED;
693     } else {
694       rv = CacheFileIOManager::gInstance->ReadInternal(mHandle, mOffset, mBuf,
695                                                        mCount);
696       if (NS_SUCCEEDED(rv)) {
697         Report(CacheFileIOManager::gInstance->mIOThread);
698       }
699     }
700 
701     mCallback->OnDataRead(mHandle, mBuf, rv);
702     return NS_OK;
703   }
704 
705  protected:
706   RefPtr<CacheFileHandle> mHandle;
707   int64_t mOffset;
708   char* mBuf;
709   int32_t mCount;
710   nsCOMPtr<CacheFileIOListener> mCallback;
711 };
712 
713 class WriteEvent : public Runnable, public IOPerfReportEvent {
714  public:
WriteEvent(CacheFileHandle * aHandle,int64_t aOffset,const char * aBuf,int32_t aCount,bool aValidate,bool aTruncate,CacheFileIOListener * aCallback)715   WriteEvent(CacheFileHandle* aHandle, int64_t aOffset, const char* aBuf,
716              int32_t aCount, bool aValidate, bool aTruncate,
717              CacheFileIOListener* aCallback)
718       : Runnable("net::WriteEvent"),
719         IOPerfReportEvent(CacheFileUtils::CachePerfStats::IO_WRITE),
720         mHandle(aHandle),
721         mOffset(aOffset),
722         mBuf(aBuf),
723         mCount(aCount),
724         mValidate(aValidate),
725         mTruncate(aTruncate),
726         mCallback(aCallback) {
727     if (!mHandle->IsSpecialFile()) {
728       Start(CacheFileIOManager::gInstance->mIOThread);
729     }
730   }
731 
732  protected:
~WriteEvent()733   ~WriteEvent() {
734     if (!mCallback && mBuf) {
735       free(const_cast<char*>(mBuf));
736     }
737   }
738 
739  public:
Run()740   NS_IMETHOD Run() override {
741     nsresult rv;
742 
743     if (mHandle->IsClosed() || (mCallback && mCallback->IsKilled())) {
744       // We usually get here only after the internal shutdown
745       // (i.e. mShuttingDown == true).  Pretend write has succeeded
746       // to avoid any past-shutdown file dooming.
747       rv = (CacheObserver::IsPastShutdownIOLag() ||
748             CacheFileIOManager::gInstance->mShuttingDown)
749                ? NS_OK
750                : NS_ERROR_NOT_INITIALIZED;
751     } else {
752       rv = CacheFileIOManager::gInstance->WriteInternal(
753           mHandle, mOffset, mBuf, mCount, mValidate, mTruncate);
754       if (NS_SUCCEEDED(rv)) {
755         Report(CacheFileIOManager::gInstance->mIOThread);
756       }
757       if (NS_FAILED(rv) && !mCallback) {
758         // No listener is going to handle the error, doom the file
759         CacheFileIOManager::gInstance->DoomFileInternal(mHandle);
760       }
761     }
762     if (mCallback) {
763       mCallback->OnDataWritten(mHandle, mBuf, rv);
764     } else {
765       free(const_cast<char*>(mBuf));
766       mBuf = nullptr;
767     }
768 
769     return NS_OK;
770   }
771 
772  protected:
773   RefPtr<CacheFileHandle> mHandle;
774   int64_t mOffset;
775   const char* mBuf;
776   int32_t mCount;
777   bool mValidate : 1;
778   bool mTruncate : 1;
779   nsCOMPtr<CacheFileIOListener> mCallback;
780 };
781 
782 class DoomFileEvent : public Runnable {
783  public:
DoomFileEvent(CacheFileHandle * aHandle,CacheFileIOListener * aCallback)784   DoomFileEvent(CacheFileHandle* aHandle, CacheFileIOListener* aCallback)
785       : Runnable("net::DoomFileEvent"),
786         mCallback(aCallback),
787         mHandle(aHandle) {}
788 
789  protected:
790   ~DoomFileEvent() = default;
791 
792  public:
Run()793   NS_IMETHOD Run() override {
794     nsresult rv;
795 
796     if (mHandle->IsClosed()) {
797       rv = NS_ERROR_NOT_INITIALIZED;
798     } else {
799       rv = CacheFileIOManager::gInstance->DoomFileInternal(mHandle);
800     }
801 
802     if (mCallback) {
803       mCallback->OnFileDoomed(mHandle, rv);
804     }
805 
806     return NS_OK;
807   }
808 
809  protected:
810   nsCOMPtr<CacheFileIOListener> mCallback;
811   nsCOMPtr<nsIEventTarget> mTarget;
812   RefPtr<CacheFileHandle> mHandle;
813 };
814 
815 class DoomFileByKeyEvent : public Runnable {
816  public:
DoomFileByKeyEvent(const nsACString & aKey,CacheFileIOListener * aCallback)817   DoomFileByKeyEvent(const nsACString& aKey, CacheFileIOListener* aCallback)
818       : Runnable("net::DoomFileByKeyEvent"), mCallback(aCallback) {
819     SHA1Sum sum;
820     sum.update(aKey.BeginReading(), aKey.Length());
821     sum.finish(mHash);
822 
823     mIOMan = CacheFileIOManager::gInstance;
824   }
825 
826  protected:
827   ~DoomFileByKeyEvent() = default;
828 
829  public:
Run()830   NS_IMETHOD Run() override {
831     nsresult rv;
832 
833     if (!mIOMan) {
834       rv = NS_ERROR_NOT_INITIALIZED;
835     } else {
836       rv = mIOMan->DoomFileByKeyInternal(&mHash);
837       mIOMan = nullptr;
838     }
839 
840     if (mCallback) {
841       mCallback->OnFileDoomed(nullptr, rv);
842     }
843 
844     return NS_OK;
845   }
846 
847  protected:
848   SHA1Sum::Hash mHash{};
849   nsCOMPtr<CacheFileIOListener> mCallback;
850   RefPtr<CacheFileIOManager> mIOMan;
851 };
852 
853 class ReleaseNSPRHandleEvent : public Runnable {
854  public:
ReleaseNSPRHandleEvent(CacheFileHandle * aHandle)855   explicit ReleaseNSPRHandleEvent(CacheFileHandle* aHandle)
856       : Runnable("net::ReleaseNSPRHandleEvent"), mHandle(aHandle) {}
857 
858  protected:
859   ~ReleaseNSPRHandleEvent() = default;
860 
861  public:
Run()862   NS_IMETHOD Run() override {
863     if (!mHandle->IsClosed()) {
864       CacheFileIOManager::gInstance->MaybeReleaseNSPRHandleInternal(mHandle);
865     }
866 
867     return NS_OK;
868   }
869 
870  protected:
871   RefPtr<CacheFileHandle> mHandle;
872 };
873 
874 class TruncateSeekSetEOFEvent : public Runnable {
875  public:
TruncateSeekSetEOFEvent(CacheFileHandle * aHandle,int64_t aTruncatePos,int64_t aEOFPos,CacheFileIOListener * aCallback)876   TruncateSeekSetEOFEvent(CacheFileHandle* aHandle, int64_t aTruncatePos,
877                           int64_t aEOFPos, CacheFileIOListener* aCallback)
878       : Runnable("net::TruncateSeekSetEOFEvent"),
879         mHandle(aHandle),
880         mTruncatePos(aTruncatePos),
881         mEOFPos(aEOFPos),
882         mCallback(aCallback) {}
883 
884  protected:
885   ~TruncateSeekSetEOFEvent() = default;
886 
887  public:
Run()888   NS_IMETHOD Run() override {
889     nsresult rv;
890 
891     if (mHandle->IsClosed() || (mCallback && mCallback->IsKilled())) {
892       rv = NS_ERROR_NOT_INITIALIZED;
893     } else {
894       rv = CacheFileIOManager::gInstance->TruncateSeekSetEOFInternal(
895           mHandle, mTruncatePos, mEOFPos);
896     }
897 
898     if (mCallback) {
899       mCallback->OnEOFSet(mHandle, rv);
900     }
901 
902     return NS_OK;
903   }
904 
905  protected:
906   RefPtr<CacheFileHandle> mHandle;
907   int64_t mTruncatePos;
908   int64_t mEOFPos;
909   nsCOMPtr<CacheFileIOListener> mCallback;
910 };
911 
912 class RenameFileEvent : public Runnable {
913  public:
RenameFileEvent(CacheFileHandle * aHandle,const nsACString & aNewName,CacheFileIOListener * aCallback)914   RenameFileEvent(CacheFileHandle* aHandle, const nsACString& aNewName,
915                   CacheFileIOListener* aCallback)
916       : Runnable("net::RenameFileEvent"),
917         mHandle(aHandle),
918         mNewName(aNewName),
919         mCallback(aCallback) {}
920 
921  protected:
922   ~RenameFileEvent() = default;
923 
924  public:
Run()925   NS_IMETHOD Run() override {
926     nsresult rv;
927 
928     if (mHandle->IsClosed()) {
929       rv = NS_ERROR_NOT_INITIALIZED;
930     } else {
931       rv = CacheFileIOManager::gInstance->RenameFileInternal(mHandle, mNewName);
932     }
933 
934     if (mCallback) {
935       mCallback->OnFileRenamed(mHandle, rv);
936     }
937 
938     return NS_OK;
939   }
940 
941  protected:
942   RefPtr<CacheFileHandle> mHandle;
943   nsCString mNewName;
944   nsCOMPtr<CacheFileIOListener> mCallback;
945 };
946 
947 class InitIndexEntryEvent : public Runnable {
948  public:
InitIndexEntryEvent(CacheFileHandle * aHandle,OriginAttrsHash aOriginAttrsHash,bool aAnonymous,bool aPinning)949   InitIndexEntryEvent(CacheFileHandle* aHandle,
950                       OriginAttrsHash aOriginAttrsHash, bool aAnonymous,
951                       bool aPinning)
952       : Runnable("net::InitIndexEntryEvent"),
953         mHandle(aHandle),
954         mOriginAttrsHash(aOriginAttrsHash),
955         mAnonymous(aAnonymous),
956         mPinning(aPinning) {}
957 
958  protected:
959   ~InitIndexEntryEvent() = default;
960 
961  public:
Run()962   NS_IMETHOD Run() override {
963     if (mHandle->IsClosed() || mHandle->IsDoomed()) {
964       return NS_OK;
965     }
966 
967     CacheIndex::InitEntry(mHandle->Hash(), mOriginAttrsHash, mAnonymous,
968                           mPinning);
969 
970     // We cannot set the filesize before we init the entry. If we're opening
971     // an existing entry file, frecency will be set after parsing the entry
972     // file, but we must set the filesize here since nobody is going to set it
973     // if there is no write to the file.
974     uint32_t sizeInK = mHandle->FileSizeInK();
975     CacheIndex::UpdateEntry(mHandle->Hash(), nullptr, nullptr, nullptr, nullptr,
976                             nullptr, &sizeInK);
977 
978     return NS_OK;
979   }
980 
981  protected:
982   RefPtr<CacheFileHandle> mHandle;
983   OriginAttrsHash mOriginAttrsHash;
984   bool mAnonymous;
985   bool mPinning;
986 };
987 
988 class UpdateIndexEntryEvent : public Runnable {
989  public:
UpdateIndexEntryEvent(CacheFileHandle * aHandle,const uint32_t * aFrecency,const bool * aHasAltData,const uint16_t * aOnStartTime,const uint16_t * aOnStopTime,const uint8_t * aContentType)990   UpdateIndexEntryEvent(CacheFileHandle* aHandle, const uint32_t* aFrecency,
991                         const bool* aHasAltData, const uint16_t* aOnStartTime,
992                         const uint16_t* aOnStopTime,
993                         const uint8_t* aContentType)
994       : Runnable("net::UpdateIndexEntryEvent"),
995         mHandle(aHandle),
996         mHasFrecency(false),
997         mHasHasAltData(false),
998         mHasOnStartTime(false),
999         mHasOnStopTime(false),
1000         mHasContentType(false),
1001         mFrecency(0),
1002         mHasAltData(false),
1003         mOnStartTime(0),
1004         mOnStopTime(0),
1005         mContentType(nsICacheEntry::CONTENT_TYPE_UNKNOWN) {
1006     if (aFrecency) {
1007       mHasFrecency = true;
1008       mFrecency = *aFrecency;
1009     }
1010     if (aHasAltData) {
1011       mHasHasAltData = true;
1012       mHasAltData = *aHasAltData;
1013     }
1014     if (aOnStartTime) {
1015       mHasOnStartTime = true;
1016       mOnStartTime = *aOnStartTime;
1017     }
1018     if (aOnStopTime) {
1019       mHasOnStopTime = true;
1020       mOnStopTime = *aOnStopTime;
1021     }
1022     if (aContentType) {
1023       mHasContentType = true;
1024       mContentType = *aContentType;
1025     }
1026   }
1027 
1028  protected:
1029   ~UpdateIndexEntryEvent() = default;
1030 
1031  public:
Run()1032   NS_IMETHOD Run() override {
1033     if (mHandle->IsClosed() || mHandle->IsDoomed()) {
1034       return NS_OK;
1035     }
1036 
1037     CacheIndex::UpdateEntry(mHandle->Hash(),
1038                             mHasFrecency ? &mFrecency : nullptr,
1039                             mHasHasAltData ? &mHasAltData : nullptr,
1040                             mHasOnStartTime ? &mOnStartTime : nullptr,
1041                             mHasOnStopTime ? &mOnStopTime : nullptr,
1042                             mHasContentType ? &mContentType : nullptr, nullptr);
1043     return NS_OK;
1044   }
1045 
1046  protected:
1047   RefPtr<CacheFileHandle> mHandle;
1048 
1049   bool mHasFrecency;
1050   bool mHasHasAltData;
1051   bool mHasOnStartTime;
1052   bool mHasOnStopTime;
1053   bool mHasContentType;
1054 
1055   uint32_t mFrecency;
1056   bool mHasAltData;
1057   uint16_t mOnStartTime;
1058   uint16_t mOnStopTime;
1059   uint8_t mContentType;
1060 };
1061 
1062 class MetadataWriteScheduleEvent : public Runnable {
1063  public:
1064   enum EMode { SCHEDULE, UNSCHEDULE, SHUTDOWN } mMode;
1065 
1066   RefPtr<CacheFile> mFile;
1067   RefPtr<CacheFileIOManager> mIOMan;
1068 
MetadataWriteScheduleEvent(CacheFileIOManager * aManager,CacheFile * aFile,EMode aMode)1069   MetadataWriteScheduleEvent(CacheFileIOManager* aManager, CacheFile* aFile,
1070                              EMode aMode)
1071       : Runnable("net::MetadataWriteScheduleEvent"),
1072         mMode(aMode),
1073         mFile(aFile),
1074         mIOMan(aManager) {}
1075 
1076   virtual ~MetadataWriteScheduleEvent() = default;
1077 
Run()1078   NS_IMETHOD Run() override {
1079     RefPtr<CacheFileIOManager> ioMan = CacheFileIOManager::gInstance;
1080     if (!ioMan) {
1081       NS_WARNING(
1082           "CacheFileIOManager already gone in "
1083           "MetadataWriteScheduleEvent::Run()");
1084       return NS_OK;
1085     }
1086 
1087     switch (mMode) {
1088       case SCHEDULE:
1089         ioMan->ScheduleMetadataWriteInternal(mFile);
1090         break;
1091       case UNSCHEDULE:
1092         ioMan->UnscheduleMetadataWriteInternal(mFile);
1093         break;
1094       case SHUTDOWN:
1095         ioMan->ShutdownMetadataWriteSchedulingInternal();
1096         break;
1097     }
1098     return NS_OK;
1099   }
1100 };
1101 
1102 StaticRefPtr<CacheFileIOManager> CacheFileIOManager::gInstance;
1103 
NS_IMPL_ISUPPORTS(CacheFileIOManager,nsITimerCallback,nsINamed)1104 NS_IMPL_ISUPPORTS(CacheFileIOManager, nsITimerCallback, nsINamed)
1105 
1106 CacheFileIOManager::CacheFileIOManager()
1107 
1108 {
1109   LOG(("CacheFileIOManager::CacheFileIOManager [this=%p]", this));
1110   MOZ_ASSERT(!gInstance, "multiple CacheFileIOManager instances!");
1111 }
1112 
~CacheFileIOManager()1113 CacheFileIOManager::~CacheFileIOManager() {
1114   LOG(("CacheFileIOManager::~CacheFileIOManager [this=%p]", this));
1115 }
1116 
1117 // static
Init()1118 nsresult CacheFileIOManager::Init() {
1119   LOG(("CacheFileIOManager::Init()"));
1120 
1121   MOZ_ASSERT(NS_IsMainThread());
1122 
1123   if (gInstance) {
1124     return NS_ERROR_ALREADY_INITIALIZED;
1125   }
1126 
1127   RefPtr<CacheFileIOManager> ioMan = new CacheFileIOManager();
1128 
1129   nsresult rv = ioMan->InitInternal();
1130   NS_ENSURE_SUCCESS(rv, rv);
1131 
1132   gInstance = std::move(ioMan);
1133   return NS_OK;
1134 }
1135 
InitInternal()1136 nsresult CacheFileIOManager::InitInternal() {
1137   nsresult rv;
1138 
1139   mIOThread = new CacheIOThread();
1140 
1141   rv = mIOThread->Init();
1142   MOZ_ASSERT(NS_SUCCEEDED(rv), "Can't create background thread");
1143   NS_ENSURE_SUCCESS(rv, rv);
1144 
1145   mStartTime = TimeStamp::NowLoRes();
1146 
1147   return NS_OK;
1148 }
1149 
1150 // static
Shutdown()1151 nsresult CacheFileIOManager::Shutdown() {
1152   LOG(("CacheFileIOManager::Shutdown() [gInstance=%p]", gInstance.get()));
1153 
1154   MOZ_ASSERT(NS_IsMainThread());
1155 
1156   if (!gInstance) {
1157     return NS_ERROR_NOT_INITIALIZED;
1158   }
1159 
1160   Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_SHUTDOWN_V2> shutdownTimer;
1161 
1162   CacheIndex::PreShutdown();
1163 
1164   ShutdownMetadataWriteScheduling();
1165 
1166   RefPtr<ShutdownEvent> ev = new ShutdownEvent();
1167   ev->PostAndWait();
1168 
1169   MOZ_ASSERT(gInstance->mHandles.HandleCount() == 0);
1170   MOZ_ASSERT(gInstance->mHandlesByLastUsed.Length() == 0);
1171 
1172   if (gInstance->mIOThread) {
1173     gInstance->mIOThread->Shutdown();
1174   }
1175 
1176   CacheIndex::Shutdown();
1177 
1178   if (CacheObserver::ClearCacheOnShutdown()) {
1179     Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE2_SHUTDOWN_CLEAR_PRIVATE>
1180         totalTimer;
1181     gInstance->SyncRemoveAllCacheFiles();
1182   }
1183 
1184   gInstance = nullptr;
1185 
1186   return NS_OK;
1187 }
1188 
ShutdownInternal()1189 void CacheFileIOManager::ShutdownInternal() {
1190   LOG(("CacheFileIOManager::ShutdownInternal() [this=%p]", this));
1191 
1192   MOZ_ASSERT(mIOThread->IsCurrentThread());
1193 
1194   // No new handles can be created after this flag is set
1195   mShuttingDown = true;
1196 
1197   if (mTrashTimer) {
1198     mTrashTimer->Cancel();
1199     mTrashTimer = nullptr;
1200   }
1201 
1202   // close all handles and delete all associated files
1203   nsTArray<RefPtr<CacheFileHandle>> handles;
1204   mHandles.GetAllHandles(&handles);
1205   handles.AppendElements(mSpecialHandles);
1206 
1207   for (uint32_t i = 0; i < handles.Length(); i++) {
1208     CacheFileHandle* h = handles[i];
1209     h->mClosed = true;
1210 
1211     h->Log();
1212 
1213     // Close completely written files.
1214     MaybeReleaseNSPRHandleInternal(h);
1215     // Don't bother removing invalid and/or doomed files to improve
1216     // shutdown perfomrance.
1217     // Doomed files are already in the doomed directory from which
1218     // we never reuse files and delete the dir on next session startup.
1219     // Invalid files don't have metadata and thus won't load anyway
1220     // (hashes won't match).
1221 
1222     if (!h->IsSpecialFile() && !h->mIsDoomed && !h->mFileExists) {
1223       CacheIndex::RemoveEntry(h->Hash());
1224     }
1225 
1226     // Remove the handle from mHandles/mSpecialHandles
1227     if (h->IsSpecialFile()) {
1228       mSpecialHandles.RemoveElement(h);
1229     } else {
1230       mHandles.RemoveHandle(h);
1231     }
1232 
1233     // Pointer to the hash is no longer valid once the last handle with the
1234     // given hash is released. Null out the pointer so that we crash if there
1235     // is a bug in this code and we dereference the pointer after this point.
1236     if (!h->IsSpecialFile()) {
1237       h->mHash = nullptr;
1238     }
1239   }
1240 
1241   // Assert the table is empty. When we are here, no new handles can be added
1242   // and handles will no longer remove them self from this table and we don't
1243   // want to keep invalid handles here. Also, there is no lookup after this
1244   // point to happen.
1245   MOZ_ASSERT(mHandles.HandleCount() == 0);
1246 
1247   // Release trash directory enumerator
1248   if (mTrashDirEnumerator) {
1249     mTrashDirEnumerator->Close();
1250     mTrashDirEnumerator = nullptr;
1251   }
1252 
1253   if (mContextEvictor) {
1254     mContextEvictor->Shutdown();
1255     mContextEvictor = nullptr;
1256   }
1257 }
1258 
1259 // static
OnProfile()1260 nsresult CacheFileIOManager::OnProfile() {
1261   LOG(("CacheFileIOManager::OnProfile() [gInstance=%p]", gInstance.get()));
1262 
1263   RefPtr<CacheFileIOManager> ioMan = gInstance;
1264   if (!ioMan) {
1265     // CacheFileIOManager::Init() failed, probably could not create the IO
1266     // thread, just go with it...
1267     return NS_ERROR_NOT_INITIALIZED;
1268   }
1269 
1270   nsresult rv;
1271 
1272   nsCOMPtr<nsIFile> directory;
1273 
1274   CacheObserver::ParentDirOverride(getter_AddRefs(directory));
1275 
1276 #if defined(MOZ_WIDGET_ANDROID)
1277   nsCOMPtr<nsIFile> profilelessDirectory;
1278   char* cachePath = getenv("CACHE_DIRECTORY");
1279   if (!directory && cachePath && *cachePath) {
1280     rv = NS_NewNativeLocalFile(nsDependentCString(cachePath), true,
1281                                getter_AddRefs(directory));
1282     if (NS_SUCCEEDED(rv)) {
1283       // Save this directory as the profileless path.
1284       rv = directory->Clone(getter_AddRefs(profilelessDirectory));
1285       NS_ENSURE_SUCCESS(rv, rv);
1286 
1287       // Add profile leaf name to the directory name to distinguish
1288       // multiple profiles Fennec supports.
1289       nsCOMPtr<nsIFile> profD;
1290       rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
1291                                   getter_AddRefs(profD));
1292 
1293       nsAutoCString leafName;
1294       if (NS_SUCCEEDED(rv)) {
1295         rv = profD->GetNativeLeafName(leafName);
1296       }
1297       if (NS_SUCCEEDED(rv)) {
1298         rv = directory->AppendNative(leafName);
1299       }
1300       if (NS_FAILED(rv)) {
1301         directory = nullptr;
1302       }
1303     }
1304   }
1305 #endif
1306 
1307   if (!directory) {
1308     rv = NS_GetSpecialDirectory(NS_APP_CACHE_PARENT_DIR,
1309                                 getter_AddRefs(directory));
1310   }
1311 
1312   if (!directory) {
1313     rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR,
1314                                 getter_AddRefs(directory));
1315   }
1316 
1317   if (directory) {
1318     rv = directory->Append(u"cache2"_ns);
1319     NS_ENSURE_SUCCESS(rv, rv);
1320   }
1321 
1322   // All functions return a clone.
1323   ioMan->mCacheDirectory.swap(directory);
1324 
1325 #if defined(MOZ_WIDGET_ANDROID)
1326   if (profilelessDirectory) {
1327     rv = profilelessDirectory->Append(u"cache2"_ns);
1328     NS_ENSURE_SUCCESS(rv, rv);
1329   }
1330 
1331   ioMan->mCacheProfilelessDirectory.swap(profilelessDirectory);
1332 #endif
1333 
1334   if (ioMan->mCacheDirectory) {
1335     CacheIndex::Init(ioMan->mCacheDirectory);
1336   }
1337 
1338   return NS_OK;
1339 }
1340 
1341 // static
IOTarget()1342 already_AddRefed<nsIEventTarget> CacheFileIOManager::IOTarget() {
1343   nsCOMPtr<nsIEventTarget> target;
1344   if (gInstance && gInstance->mIOThread) {
1345     target = gInstance->mIOThread->Target();
1346   }
1347 
1348   return target.forget();
1349 }
1350 
1351 // static
IOThread()1352 already_AddRefed<CacheIOThread> CacheFileIOManager::IOThread() {
1353   RefPtr<CacheIOThread> thread;
1354   if (gInstance) {
1355     thread = gInstance->mIOThread;
1356   }
1357 
1358   return thread.forget();
1359 }
1360 
1361 // static
IsOnIOThread()1362 bool CacheFileIOManager::IsOnIOThread() {
1363   RefPtr<CacheFileIOManager> ioMan = gInstance;
1364   if (ioMan && ioMan->mIOThread) {
1365     return ioMan->mIOThread->IsCurrentThread();
1366   }
1367 
1368   return false;
1369 }
1370 
1371 // static
IsOnIOThreadOrCeased()1372 bool CacheFileIOManager::IsOnIOThreadOrCeased() {
1373   RefPtr<CacheFileIOManager> ioMan = gInstance;
1374   if (ioMan && ioMan->mIOThread) {
1375     return ioMan->mIOThread->IsCurrentThread();
1376   }
1377 
1378   // Ceased...
1379   return true;
1380 }
1381 
1382 // static
IsShutdown()1383 bool CacheFileIOManager::IsShutdown() {
1384   if (!gInstance) {
1385     return true;
1386   }
1387   return gInstance->mShuttingDown;
1388 }
1389 
1390 // static
ScheduleMetadataWrite(CacheFile * aFile)1391 nsresult CacheFileIOManager::ScheduleMetadataWrite(CacheFile* aFile) {
1392   RefPtr<CacheFileIOManager> ioMan = gInstance;
1393   NS_ENSURE_TRUE(ioMan, NS_ERROR_NOT_INITIALIZED);
1394 
1395   NS_ENSURE_TRUE(!ioMan->mShuttingDown, NS_ERROR_NOT_INITIALIZED);
1396 
1397   RefPtr<MetadataWriteScheduleEvent> event = new MetadataWriteScheduleEvent(
1398       ioMan, aFile, MetadataWriteScheduleEvent::SCHEDULE);
1399   nsCOMPtr<nsIEventTarget> target = ioMan->IOTarget();
1400   NS_ENSURE_TRUE(target, NS_ERROR_UNEXPECTED);
1401   return target->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
1402 }
1403 
ScheduleMetadataWriteInternal(CacheFile * aFile)1404 nsresult CacheFileIOManager::ScheduleMetadataWriteInternal(CacheFile* aFile) {
1405   MOZ_ASSERT(IsOnIOThreadOrCeased());
1406 
1407   nsresult rv;
1408 
1409   if (!mMetadataWritesTimer) {
1410     rv = NS_NewTimerWithCallback(getter_AddRefs(mMetadataWritesTimer), this,
1411                                  kMetadataWriteDelay, nsITimer::TYPE_ONE_SHOT);
1412     NS_ENSURE_SUCCESS(rv, rv);
1413   }
1414 
1415   if (mScheduledMetadataWrites.IndexOf(aFile) !=
1416       nsTArray<RefPtr<mozilla::net::CacheFile>>::NoIndex) {
1417     return NS_OK;
1418   }
1419 
1420   mScheduledMetadataWrites.AppendElement(aFile);
1421 
1422   return NS_OK;
1423 }
1424 
1425 // static
UnscheduleMetadataWrite(CacheFile * aFile)1426 nsresult CacheFileIOManager::UnscheduleMetadataWrite(CacheFile* aFile) {
1427   RefPtr<CacheFileIOManager> ioMan = gInstance;
1428   NS_ENSURE_TRUE(ioMan, NS_ERROR_NOT_INITIALIZED);
1429 
1430   NS_ENSURE_TRUE(!ioMan->mShuttingDown, NS_ERROR_NOT_INITIALIZED);
1431 
1432   RefPtr<MetadataWriteScheduleEvent> event = new MetadataWriteScheduleEvent(
1433       ioMan, aFile, MetadataWriteScheduleEvent::UNSCHEDULE);
1434   nsCOMPtr<nsIEventTarget> target = ioMan->IOTarget();
1435   NS_ENSURE_TRUE(target, NS_ERROR_UNEXPECTED);
1436   return target->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
1437 }
1438 
UnscheduleMetadataWriteInternal(CacheFile * aFile)1439 void CacheFileIOManager::UnscheduleMetadataWriteInternal(CacheFile* aFile) {
1440   MOZ_ASSERT(IsOnIOThreadOrCeased());
1441 
1442   mScheduledMetadataWrites.RemoveElement(aFile);
1443 
1444   if (mScheduledMetadataWrites.Length() == 0 && mMetadataWritesTimer) {
1445     mMetadataWritesTimer->Cancel();
1446     mMetadataWritesTimer = nullptr;
1447   }
1448 }
1449 
1450 // static
ShutdownMetadataWriteScheduling()1451 nsresult CacheFileIOManager::ShutdownMetadataWriteScheduling() {
1452   RefPtr<CacheFileIOManager> ioMan = gInstance;
1453   NS_ENSURE_TRUE(ioMan, NS_ERROR_NOT_INITIALIZED);
1454 
1455   RefPtr<MetadataWriteScheduleEvent> event = new MetadataWriteScheduleEvent(
1456       ioMan, nullptr, MetadataWriteScheduleEvent::SHUTDOWN);
1457   nsCOMPtr<nsIEventTarget> target = ioMan->IOTarget();
1458   NS_ENSURE_TRUE(target, NS_ERROR_UNEXPECTED);
1459   return target->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
1460 }
1461 
ShutdownMetadataWriteSchedulingInternal()1462 void CacheFileIOManager::ShutdownMetadataWriteSchedulingInternal() {
1463   MOZ_ASSERT(IsOnIOThreadOrCeased());
1464 
1465   nsTArray<RefPtr<CacheFile>> files = std::move(mScheduledMetadataWrites);
1466   for (uint32_t i = 0; i < files.Length(); ++i) {
1467     CacheFile* file = files[i];
1468     file->WriteMetadataIfNeeded();
1469   }
1470 
1471   if (mMetadataWritesTimer) {
1472     mMetadataWritesTimer->Cancel();
1473     mMetadataWritesTimer = nullptr;
1474   }
1475 }
1476 
1477 NS_IMETHODIMP
Notify(nsITimer * aTimer)1478 CacheFileIOManager::Notify(nsITimer* aTimer) {
1479   MOZ_ASSERT(IsOnIOThreadOrCeased());
1480   MOZ_ASSERT(mMetadataWritesTimer == aTimer);
1481 
1482   mMetadataWritesTimer = nullptr;
1483 
1484   nsTArray<RefPtr<CacheFile>> files = std::move(mScheduledMetadataWrites);
1485   for (uint32_t i = 0; i < files.Length(); ++i) {
1486     CacheFile* file = files[i];
1487     file->WriteMetadataIfNeeded();
1488   }
1489 
1490   return NS_OK;
1491 }
1492 
1493 NS_IMETHODIMP
GetName(nsACString & aName)1494 CacheFileIOManager::GetName(nsACString& aName) {
1495   aName.AssignLiteral("CacheFileIOManager");
1496   return NS_OK;
1497 }
1498 
1499 // static
OpenFile(const nsACString & aKey,uint32_t aFlags,CacheFileIOListener * aCallback)1500 nsresult CacheFileIOManager::OpenFile(const nsACString& aKey, uint32_t aFlags,
1501                                       CacheFileIOListener* aCallback) {
1502   LOG(("CacheFileIOManager::OpenFile() [key=%s, flags=%d, listener=%p]",
1503        PromiseFlatCString(aKey).get(), aFlags, aCallback));
1504 
1505   nsresult rv;
1506   RefPtr<CacheFileIOManager> ioMan = gInstance;
1507 
1508   if (!ioMan) {
1509     return NS_ERROR_NOT_INITIALIZED;
1510   }
1511 
1512   bool priority = aFlags & CacheFileIOManager::PRIORITY;
1513   RefPtr<OpenFileEvent> ev = new OpenFileEvent(aKey, aFlags, aCallback);
1514   rv = ioMan->mIOThread->Dispatch(
1515       ev, priority ? CacheIOThread::OPEN_PRIORITY : CacheIOThread::OPEN);
1516   NS_ENSURE_SUCCESS(rv, rv);
1517 
1518   return NS_OK;
1519 }
1520 
OpenFileInternal(const SHA1Sum::Hash * aHash,const nsACString & aKey,uint32_t aFlags,CacheFileHandle ** _retval)1521 nsresult CacheFileIOManager::OpenFileInternal(const SHA1Sum::Hash* aHash,
1522                                               const nsACString& aKey,
1523                                               uint32_t aFlags,
1524                                               CacheFileHandle** _retval) {
1525   LOG(
1526       ("CacheFileIOManager::OpenFileInternal() [hash=%08x%08x%08x%08x%08x, "
1527        "key=%s, flags=%d]",
1528        LOGSHA1(aHash), PromiseFlatCString(aKey).get(), aFlags));
1529 
1530   MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
1531 
1532   nsresult rv;
1533 
1534   if (mShuttingDown) {
1535     return NS_ERROR_NOT_INITIALIZED;
1536   }
1537 
1538   CacheIOThread::Cancelable cancelable(
1539       true /* never called for special handles */);
1540 
1541   if (!mTreeCreated) {
1542     rv = CreateCacheTree();
1543     if (NS_FAILED(rv)) return rv;
1544   }
1545 
1546   CacheFileHandle::PinningStatus pinning =
1547       aFlags & PINNED ? CacheFileHandle::PinningStatus::PINNED
1548                       : CacheFileHandle::PinningStatus::NON_PINNED;
1549 
1550   nsCOMPtr<nsIFile> file;
1551   rv = GetFile(aHash, getter_AddRefs(file));
1552   NS_ENSURE_SUCCESS(rv, rv);
1553 
1554   RefPtr<CacheFileHandle> handle;
1555   mHandles.GetHandle(aHash, getter_AddRefs(handle));
1556 
1557   if ((aFlags & (OPEN | CREATE | CREATE_NEW)) == CREATE_NEW) {
1558     if (handle) {
1559       rv = DoomFileInternal(handle);
1560       NS_ENSURE_SUCCESS(rv, rv);
1561       handle = nullptr;
1562     }
1563 
1564     handle = mHandles.NewHandle(aHash, aFlags & PRIORITY, pinning);
1565 
1566     bool exists;
1567     rv = file->Exists(&exists);
1568     NS_ENSURE_SUCCESS(rv, rv);
1569 
1570     if (exists) {
1571       CacheIndex::RemoveEntry(aHash);
1572 
1573       LOG(
1574           ("CacheFileIOManager::OpenFileInternal() - Removing old file from "
1575            "disk"));
1576       rv = file->Remove(false);
1577       if (NS_FAILED(rv)) {
1578         NS_WARNING("Cannot remove old entry from the disk");
1579         LOG(
1580             ("CacheFileIOManager::OpenFileInternal() - Removing old file failed"
1581              ". [rv=0x%08" PRIx32 "]",
1582              static_cast<uint32_t>(rv)));
1583       }
1584     }
1585 
1586     CacheIndex::AddEntry(aHash);
1587     handle->mFile.swap(file);
1588     handle->mFileSize = 0;
1589   }
1590 
1591   if (handle) {
1592     handle.swap(*_retval);
1593     return NS_OK;
1594   }
1595 
1596   bool exists, evictedAsPinned = false, evictedAsNonPinned = false;
1597   rv = file->Exists(&exists);
1598   NS_ENSURE_SUCCESS(rv, rv);
1599 
1600   if (exists && mContextEvictor) {
1601     if (mContextEvictor->ContextsCount() == 0) {
1602       mContextEvictor = nullptr;
1603     } else {
1604       mContextEvictor->WasEvicted(aKey, file, &evictedAsPinned,
1605                                   &evictedAsNonPinned);
1606     }
1607   }
1608 
1609   if (!exists && (aFlags & (OPEN | CREATE | CREATE_NEW)) == OPEN) {
1610     return NS_ERROR_NOT_AVAILABLE;
1611   }
1612 
1613   if (exists) {
1614     // For existing files we determine the pinning status later, after the
1615     // metadata gets parsed.
1616     pinning = CacheFileHandle::PinningStatus::UNKNOWN;
1617   }
1618 
1619   handle = mHandles.NewHandle(aHash, aFlags & PRIORITY, pinning);
1620   if (exists) {
1621     // If this file has been found evicted through the context file evictor
1622     // above for any of pinned or non-pinned state, these calls ensure we doom
1623     // the handle ASAP we know the real pinning state after metadta has been
1624     // parsed.  DoomFileInternal on the |handle| doesn't doom right now, since
1625     // the pinning state is unknown and we pass down a pinning restriction.
1626     if (evictedAsPinned) {
1627       rv = DoomFileInternal(handle, DOOM_WHEN_PINNED);
1628       MOZ_ASSERT(!handle->IsDoomed() && NS_SUCCEEDED(rv));
1629     }
1630     if (evictedAsNonPinned) {
1631       rv = DoomFileInternal(handle, DOOM_WHEN_NON_PINNED);
1632       MOZ_ASSERT(!handle->IsDoomed() && NS_SUCCEEDED(rv));
1633     }
1634 
1635     int64_t fileSize = -1;
1636     rv = file->GetFileSize(&fileSize);
1637     NS_ENSURE_SUCCESS(rv, rv);
1638 
1639     handle->mFileSize = fileSize;
1640     handle->mFileExists = true;
1641 
1642     CacheIndex::EnsureEntryExists(aHash);
1643   } else {
1644     handle->mFileSize = 0;
1645 
1646     CacheIndex::AddEntry(aHash);
1647   }
1648 
1649   handle->mFile.swap(file);
1650   handle.swap(*_retval);
1651   return NS_OK;
1652 }
1653 
OpenSpecialFileInternal(const nsACString & aKey,uint32_t aFlags,CacheFileHandle ** _retval)1654 nsresult CacheFileIOManager::OpenSpecialFileInternal(
1655     const nsACString& aKey, uint32_t aFlags, CacheFileHandle** _retval) {
1656   LOG(("CacheFileIOManager::OpenSpecialFileInternal() [key=%s, flags=%d]",
1657        PromiseFlatCString(aKey).get(), aFlags));
1658 
1659   MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
1660 
1661   nsresult rv;
1662 
1663   if (mShuttingDown) {
1664     return NS_ERROR_NOT_INITIALIZED;
1665   }
1666 
1667   if (!mTreeCreated) {
1668     rv = CreateCacheTree();
1669     if (NS_FAILED(rv)) return rv;
1670   }
1671 
1672   nsCOMPtr<nsIFile> file;
1673   rv = GetSpecialFile(aKey, getter_AddRefs(file));
1674   NS_ENSURE_SUCCESS(rv, rv);
1675 
1676   RefPtr<CacheFileHandle> handle;
1677   for (uint32_t i = 0; i < mSpecialHandles.Length(); i++) {
1678     if (!mSpecialHandles[i]->IsDoomed() && mSpecialHandles[i]->Key() == aKey) {
1679       handle = mSpecialHandles[i];
1680       break;
1681     }
1682   }
1683 
1684   if ((aFlags & (OPEN | CREATE | CREATE_NEW)) == CREATE_NEW) {
1685     if (handle) {
1686       rv = DoomFileInternal(handle);
1687       NS_ENSURE_SUCCESS(rv, rv);
1688       handle = nullptr;
1689     }
1690 
1691     handle = new CacheFileHandle(aKey, aFlags & PRIORITY,
1692                                  CacheFileHandle::PinningStatus::NON_PINNED);
1693     mSpecialHandles.AppendElement(handle);
1694 
1695     bool exists;
1696     rv = file->Exists(&exists);
1697     NS_ENSURE_SUCCESS(rv, rv);
1698 
1699     if (exists) {
1700       LOG(
1701           ("CacheFileIOManager::OpenSpecialFileInternal() - Removing file from "
1702            "disk"));
1703       rv = file->Remove(false);
1704       if (NS_FAILED(rv)) {
1705         NS_WARNING("Cannot remove old entry from the disk");
1706         LOG(
1707             ("CacheFileIOManager::OpenSpecialFileInternal() - Removing file "
1708              "failed. [rv=0x%08" PRIx32 "]",
1709              static_cast<uint32_t>(rv)));
1710       }
1711     }
1712 
1713     handle->mFile.swap(file);
1714     handle->mFileSize = 0;
1715   }
1716 
1717   if (handle) {
1718     handle.swap(*_retval);
1719     return NS_OK;
1720   }
1721 
1722   bool exists;
1723   rv = file->Exists(&exists);
1724   NS_ENSURE_SUCCESS(rv, rv);
1725 
1726   if (!exists && (aFlags & (OPEN | CREATE | CREATE_NEW)) == OPEN) {
1727     return NS_ERROR_NOT_AVAILABLE;
1728   }
1729 
1730   handle = new CacheFileHandle(aKey, aFlags & PRIORITY,
1731                                CacheFileHandle::PinningStatus::NON_PINNED);
1732   mSpecialHandles.AppendElement(handle);
1733 
1734   if (exists) {
1735     int64_t fileSize = -1;
1736     rv = file->GetFileSize(&fileSize);
1737     NS_ENSURE_SUCCESS(rv, rv);
1738 
1739     handle->mFileSize = fileSize;
1740     handle->mFileExists = true;
1741   } else {
1742     handle->mFileSize = 0;
1743   }
1744 
1745   handle->mFile.swap(file);
1746   handle.swap(*_retval);
1747   return NS_OK;
1748 }
1749 
CloseHandleInternal(CacheFileHandle * aHandle)1750 void CacheFileIOManager::CloseHandleInternal(CacheFileHandle* aHandle) {
1751   nsresult rv;
1752   LOG(("CacheFileIOManager::CloseHandleInternal() [handle=%p]", aHandle));
1753 
1754   MOZ_ASSERT(!aHandle->IsClosed());
1755 
1756   aHandle->Log();
1757 
1758   MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
1759 
1760   CacheIOThread::Cancelable cancelable(!aHandle->IsSpecialFile());
1761 
1762   // Maybe close file handle (can be legally bypassed after shutdown)
1763   rv = MaybeReleaseNSPRHandleInternal(aHandle);
1764 
1765   // Delete the file if the entry was doomed or invalid and
1766   // filedesc properly closed
1767   if ((aHandle->mIsDoomed || aHandle->mInvalid) && aHandle->mFileExists &&
1768       NS_SUCCEEDED(rv)) {
1769     LOG(
1770         ("CacheFileIOManager::CloseHandleInternal() - Removing file from "
1771          "disk"));
1772 
1773     rv = aHandle->mFile->Remove(false);
1774     if (NS_SUCCEEDED(rv)) {
1775       aHandle->mFileExists = false;
1776     } else {
1777       LOG(("  failed to remove the file [rv=0x%08" PRIx32 "]",
1778            static_cast<uint32_t>(rv)));
1779     }
1780   }
1781 
1782   if (!aHandle->IsSpecialFile() && !aHandle->mIsDoomed &&
1783       (aHandle->mInvalid || !aHandle->mFileExists)) {
1784     CacheIndex::RemoveEntry(aHandle->Hash());
1785   }
1786 
1787   // Don't remove handles after shutdown
1788   if (!mShuttingDown) {
1789     if (aHandle->IsSpecialFile()) {
1790       mSpecialHandles.RemoveElement(aHandle);
1791     } else {
1792       mHandles.RemoveHandle(aHandle);
1793     }
1794   }
1795 }
1796 
1797 // static
Read(CacheFileHandle * aHandle,int64_t aOffset,char * aBuf,int32_t aCount,CacheFileIOListener * aCallback)1798 nsresult CacheFileIOManager::Read(CacheFileHandle* aHandle, int64_t aOffset,
1799                                   char* aBuf, int32_t aCount,
1800                                   CacheFileIOListener* aCallback) {
1801   LOG(("CacheFileIOManager::Read() [handle=%p, offset=%" PRId64 ", count=%d, "
1802        "listener=%p]",
1803        aHandle, aOffset, aCount, aCallback));
1804 
1805   if (CacheObserver::ShuttingDown()) {
1806     LOG(("  no reads after shutdown"));
1807     return NS_ERROR_NOT_INITIALIZED;
1808   }
1809 
1810   nsresult rv;
1811   RefPtr<CacheFileIOManager> ioMan = gInstance;
1812 
1813   if (aHandle->IsClosed() || !ioMan) {
1814     return NS_ERROR_NOT_INITIALIZED;
1815   }
1816 
1817   RefPtr<ReadEvent> ev =
1818       new ReadEvent(aHandle, aOffset, aBuf, aCount, aCallback);
1819   rv = ioMan->mIOThread->Dispatch(ev, aHandle->IsPriority()
1820                                           ? CacheIOThread::READ_PRIORITY
1821                                           : CacheIOThread::READ);
1822   NS_ENSURE_SUCCESS(rv, rv);
1823 
1824   return NS_OK;
1825 }
1826 
ReadInternal(CacheFileHandle * aHandle,int64_t aOffset,char * aBuf,int32_t aCount)1827 nsresult CacheFileIOManager::ReadInternal(CacheFileHandle* aHandle,
1828                                           int64_t aOffset, char* aBuf,
1829                                           int32_t aCount) {
1830   LOG(("CacheFileIOManager::ReadInternal() [handle=%p, offset=%" PRId64
1831        ", count=%d]",
1832        aHandle, aOffset, aCount));
1833 
1834   nsresult rv;
1835 
1836   if (CacheObserver::ShuttingDown()) {
1837     LOG(("  no reads after shutdown"));
1838     return NS_ERROR_NOT_INITIALIZED;
1839   }
1840 
1841   if (!aHandle->mFileExists) {
1842     NS_WARNING("Trying to read from non-existent file");
1843     return NS_ERROR_NOT_AVAILABLE;
1844   }
1845 
1846   CacheIOThread::Cancelable cancelable(!aHandle->IsSpecialFile());
1847 
1848   if (!aHandle->mFD) {
1849     rv = OpenNSPRHandle(aHandle);
1850     NS_ENSURE_SUCCESS(rv, rv);
1851   } else {
1852     NSPRHandleUsed(aHandle);
1853   }
1854 
1855   // Check again, OpenNSPRHandle could figure out the file was gone.
1856   if (!aHandle->mFileExists) {
1857     NS_WARNING("Trying to read from non-existent file");
1858     return NS_ERROR_NOT_AVAILABLE;
1859   }
1860 
1861   int64_t offset = PR_Seek64(aHandle->mFD, aOffset, PR_SEEK_SET);
1862   if (offset == -1) {
1863     return NS_ERROR_FAILURE;
1864   }
1865 
1866   int32_t bytesRead = PR_Read(aHandle->mFD, aBuf, aCount);
1867   if (bytesRead != aCount) {
1868     return NS_ERROR_FAILURE;
1869   }
1870 
1871   return NS_OK;
1872 }
1873 
1874 // static
Write(CacheFileHandle * aHandle,int64_t aOffset,const char * aBuf,int32_t aCount,bool aValidate,bool aTruncate,CacheFileIOListener * aCallback)1875 nsresult CacheFileIOManager::Write(CacheFileHandle* aHandle, int64_t aOffset,
1876                                    const char* aBuf, int32_t aCount,
1877                                    bool aValidate, bool aTruncate,
1878                                    CacheFileIOListener* aCallback) {
1879   LOG(("CacheFileIOManager::Write() [handle=%p, offset=%" PRId64 ", count=%d, "
1880        "validate=%d, truncate=%d, listener=%p]",
1881        aHandle, aOffset, aCount, aValidate, aTruncate, aCallback));
1882 
1883   nsresult rv;
1884   RefPtr<CacheFileIOManager> ioMan = gInstance;
1885 
1886   if (aHandle->IsClosed() || (aCallback && aCallback->IsKilled()) || !ioMan) {
1887     if (!aCallback) {
1888       // When no callback is provided, CacheFileIOManager is responsible for
1889       // releasing the buffer. We must release it even in case of failure.
1890       free(const_cast<char*>(aBuf));
1891     }
1892     return NS_ERROR_NOT_INITIALIZED;
1893   }
1894 
1895   RefPtr<WriteEvent> ev = new WriteEvent(aHandle, aOffset, aBuf, aCount,
1896                                          aValidate, aTruncate, aCallback);
1897   rv = ioMan->mIOThread->Dispatch(ev, aHandle->mPriority
1898                                           ? CacheIOThread::WRITE_PRIORITY
1899                                           : CacheIOThread::WRITE);
1900   NS_ENSURE_SUCCESS(rv, rv);
1901 
1902   return NS_OK;
1903 }
1904 
TruncFile(PRFileDesc * aFD,int64_t aEOF)1905 static nsresult TruncFile(PRFileDesc* aFD, int64_t aEOF) {
1906 #if defined(XP_UNIX)
1907   if (ftruncate(PR_FileDesc2NativeHandle(aFD), aEOF) != 0) {
1908     NS_ERROR("ftruncate failed");
1909     return NS_ERROR_FAILURE;
1910   }
1911 #elif defined(XP_WIN)
1912   int64_t cnt = PR_Seek64(aFD, aEOF, PR_SEEK_SET);
1913   if (cnt == -1) {
1914     return NS_ERROR_FAILURE;
1915   }
1916   if (!SetEndOfFile((HANDLE)PR_FileDesc2NativeHandle(aFD))) {
1917     NS_ERROR("SetEndOfFile failed");
1918     return NS_ERROR_FAILURE;
1919   }
1920 #else
1921   MOZ_ASSERT(false, "Not implemented!");
1922   return NS_ERROR_NOT_IMPLEMENTED;
1923 #endif
1924 
1925   return NS_OK;
1926 }
1927 
WriteInternal(CacheFileHandle * aHandle,int64_t aOffset,const char * aBuf,int32_t aCount,bool aValidate,bool aTruncate)1928 nsresult CacheFileIOManager::WriteInternal(CacheFileHandle* aHandle,
1929                                            int64_t aOffset, const char* aBuf,
1930                                            int32_t aCount, bool aValidate,
1931                                            bool aTruncate) {
1932   LOG(("CacheFileIOManager::WriteInternal() [handle=%p, offset=%" PRId64
1933        ", count=%d, "
1934        "validate=%d, truncate=%d]",
1935        aHandle, aOffset, aCount, aValidate, aTruncate));
1936 
1937   nsresult rv;
1938 
1939   if (aHandle->mKilled) {
1940     LOG(("  handle already killed, nothing written"));
1941     return NS_OK;
1942   }
1943 
1944   if (CacheObserver::ShuttingDown() && (!aValidate || !aHandle->mFD)) {
1945     aHandle->mKilled = true;
1946     LOG(("  killing the handle, nothing written"));
1947     return NS_OK;
1948   }
1949 
1950   if (CacheObserver::IsPastShutdownIOLag()) {
1951     LOG(("  past the shutdown I/O lag, nothing written"));
1952     // Pretend the write has succeeded, otherwise upper layers will doom
1953     // the file and we end up with I/O anyway.
1954     return NS_OK;
1955   }
1956 
1957   CacheIOThread::Cancelable cancelable(!aHandle->IsSpecialFile());
1958 
1959   if (!aHandle->mFileExists) {
1960     rv = CreateFile(aHandle);
1961     NS_ENSURE_SUCCESS(rv, rv);
1962   }
1963 
1964   if (!aHandle->mFD) {
1965     rv = OpenNSPRHandle(aHandle);
1966     NS_ENSURE_SUCCESS(rv, rv);
1967   } else {
1968     NSPRHandleUsed(aHandle);
1969   }
1970 
1971   // Check again, OpenNSPRHandle could figure out the file was gone.
1972   if (!aHandle->mFileExists) {
1973     return NS_ERROR_NOT_AVAILABLE;
1974   }
1975 
1976   // When this operation would increase cache size, check whether the cache size
1977   // reached the hard limit and whether it would cause critical low disk space.
1978   if (aHandle->mFileSize < aOffset + aCount) {
1979     if (mOverLimitEvicting && mCacheSizeOnHardLimit) {
1980       LOG(
1981           ("CacheFileIOManager::WriteInternal() - failing because cache size "
1982            "reached hard limit!"));
1983       return NS_ERROR_FILE_NO_DEVICE_SPACE;
1984     }
1985 
1986     int64_t freeSpace;
1987     rv = mCacheDirectory->GetDiskSpaceAvailable(&freeSpace);
1988     if (NS_WARN_IF(NS_FAILED(rv))) {
1989       freeSpace = -1;
1990       LOG(
1991           ("CacheFileIOManager::WriteInternal() - GetDiskSpaceAvailable() "
1992            "failed! [rv=0x%08" PRIx32 "]",
1993            static_cast<uint32_t>(rv)));
1994     } else {
1995       freeSpace >>= 10;  // bytes to kilobytes
1996       uint32_t limit = CacheObserver::DiskFreeSpaceHardLimit();
1997       if (freeSpace - aOffset - aCount + aHandle->mFileSize < limit) {
1998         LOG(
1999             ("CacheFileIOManager::WriteInternal() - Low free space, refusing "
2000              "to write! [freeSpace=%" PRId64 "kB, limit=%ukB]",
2001              freeSpace, limit));
2002         return NS_ERROR_FILE_NO_DEVICE_SPACE;
2003       }
2004     }
2005   }
2006 
2007   // Write invalidates the entry by default
2008   aHandle->mInvalid = true;
2009 
2010   int64_t offset = PR_Seek64(aHandle->mFD, aOffset, PR_SEEK_SET);
2011   if (offset == -1) {
2012     return NS_ERROR_FAILURE;
2013   }
2014 
2015   int32_t bytesWritten = PR_Write(aHandle->mFD, aBuf, aCount);
2016 
2017   if (bytesWritten != -1) {
2018     uint32_t oldSizeInK = aHandle->FileSizeInK();
2019     int64_t writeEnd = aOffset + bytesWritten;
2020 
2021     if (aTruncate) {
2022       rv = TruncFile(aHandle->mFD, writeEnd);
2023       NS_ENSURE_SUCCESS(rv, rv);
2024 
2025       aHandle->mFileSize = writeEnd;
2026     } else {
2027       if (aHandle->mFileSize < writeEnd) {
2028         aHandle->mFileSize = writeEnd;
2029       }
2030     }
2031 
2032     uint32_t newSizeInK = aHandle->FileSizeInK();
2033 
2034     if (oldSizeInK != newSizeInK && !aHandle->IsDoomed() &&
2035         !aHandle->IsSpecialFile()) {
2036       CacheIndex::UpdateEntry(aHandle->Hash(), nullptr, nullptr, nullptr,
2037                               nullptr, nullptr, &newSizeInK);
2038 
2039       if (oldSizeInK < newSizeInK) {
2040         EvictIfOverLimitInternal();
2041       }
2042     }
2043 
2044     CacheIndex::UpdateTotalBytesWritten(bytesWritten);
2045   }
2046 
2047   if (bytesWritten != aCount) {
2048     return NS_ERROR_FAILURE;
2049   }
2050 
2051   // Write was successful and this write validates the entry (i.e. metadata)
2052   if (aValidate) {
2053     aHandle->mInvalid = false;
2054   }
2055 
2056   return NS_OK;
2057 }
2058 
2059 // static
DoomFile(CacheFileHandle * aHandle,CacheFileIOListener * aCallback)2060 nsresult CacheFileIOManager::DoomFile(CacheFileHandle* aHandle,
2061                                       CacheFileIOListener* aCallback) {
2062   LOG(("CacheFileIOManager::DoomFile() [handle=%p, listener=%p]", aHandle,
2063        aCallback));
2064 
2065   nsresult rv;
2066   RefPtr<CacheFileIOManager> ioMan = gInstance;
2067 
2068   if (aHandle->IsClosed() || !ioMan) {
2069     return NS_ERROR_NOT_INITIALIZED;
2070   }
2071 
2072   RefPtr<DoomFileEvent> ev = new DoomFileEvent(aHandle, aCallback);
2073   rv = ioMan->mIOThread->Dispatch(ev, aHandle->IsPriority()
2074                                           ? CacheIOThread::OPEN_PRIORITY
2075                                           : CacheIOThread::OPEN);
2076   NS_ENSURE_SUCCESS(rv, rv);
2077 
2078   return NS_OK;
2079 }
2080 
DoomFileInternal(CacheFileHandle * aHandle,PinningDoomRestriction aPinningDoomRestriction)2081 nsresult CacheFileIOManager::DoomFileInternal(
2082     CacheFileHandle* aHandle, PinningDoomRestriction aPinningDoomRestriction) {
2083   LOG(("CacheFileIOManager::DoomFileInternal() [handle=%p]", aHandle));
2084   aHandle->Log();
2085 
2086   MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
2087 
2088   nsresult rv;
2089 
2090   if (aHandle->IsDoomed()) {
2091     return NS_OK;
2092   }
2093 
2094   CacheIOThread::Cancelable cancelable(!aHandle->IsSpecialFile());
2095 
2096   if (aPinningDoomRestriction > NO_RESTRICTION) {
2097     switch (aHandle->mPinning) {
2098       case CacheFileHandle::PinningStatus::NON_PINNED:
2099         if (MOZ_LIKELY(aPinningDoomRestriction != DOOM_WHEN_NON_PINNED)) {
2100           LOG(("  not dooming, it's a non-pinned handle"));
2101           return NS_OK;
2102         }
2103         // Doom now
2104         break;
2105 
2106       case CacheFileHandle::PinningStatus::PINNED:
2107         if (MOZ_UNLIKELY(aPinningDoomRestriction != DOOM_WHEN_PINNED)) {
2108           LOG(("  not dooming, it's a pinned handle"));
2109           return NS_OK;
2110         }
2111         // Doom now
2112         break;
2113 
2114       case CacheFileHandle::PinningStatus::UNKNOWN:
2115         if (MOZ_LIKELY(aPinningDoomRestriction == DOOM_WHEN_NON_PINNED)) {
2116           LOG(("  doom when non-pinned set"));
2117           aHandle->mDoomWhenFoundNonPinned = true;
2118         } else if (MOZ_UNLIKELY(aPinningDoomRestriction == DOOM_WHEN_PINNED)) {
2119           LOG(("  doom when pinned set"));
2120           aHandle->mDoomWhenFoundPinned = true;
2121         }
2122 
2123         LOG(("  pinning status not known, deferring doom decision"));
2124         return NS_OK;
2125     }
2126   }
2127 
2128   if (aHandle->mFileExists) {
2129     // we need to move the current file to the doomed directory
2130     rv = MaybeReleaseNSPRHandleInternal(aHandle, true);
2131     NS_ENSURE_SUCCESS(rv, rv);
2132 
2133     // find unused filename
2134     nsCOMPtr<nsIFile> file;
2135     rv = GetDoomedFile(getter_AddRefs(file));
2136     NS_ENSURE_SUCCESS(rv, rv);
2137 
2138     nsCOMPtr<nsIFile> parentDir;
2139     rv = file->GetParent(getter_AddRefs(parentDir));
2140     NS_ENSURE_SUCCESS(rv, rv);
2141 
2142     nsAutoCString leafName;
2143     rv = file->GetNativeLeafName(leafName);
2144     NS_ENSURE_SUCCESS(rv, rv);
2145 
2146     rv = aHandle->mFile->MoveToNative(parentDir, leafName);
2147     if (NS_ERROR_FILE_NOT_FOUND == rv ||
2148         NS_ERROR_FILE_TARGET_DOES_NOT_EXIST == rv) {
2149       LOG(("  file already removed under our hands"));
2150       aHandle->mFileExists = false;
2151       rv = NS_OK;
2152     } else {
2153       NS_ENSURE_SUCCESS(rv, rv);
2154       aHandle->mFile.swap(file);
2155     }
2156   }
2157 
2158   if (!aHandle->IsSpecialFile()) {
2159     CacheIndex::RemoveEntry(aHandle->Hash());
2160   }
2161 
2162   aHandle->mIsDoomed = true;
2163 
2164   if (!aHandle->IsSpecialFile()) {
2165     RefPtr<CacheStorageService> storageService = CacheStorageService::Self();
2166     if (storageService) {
2167       nsAutoCString idExtension, url;
2168       nsCOMPtr<nsILoadContextInfo> info =
2169           CacheFileUtils::ParseKey(aHandle->Key(), &idExtension, &url);
2170       MOZ_ASSERT(info);
2171       if (info) {
2172         storageService->CacheFileDoomed(info, idExtension, url);
2173       }
2174     }
2175   }
2176 
2177   return NS_OK;
2178 }
2179 
2180 // static
DoomFileByKey(const nsACString & aKey,CacheFileIOListener * aCallback)2181 nsresult CacheFileIOManager::DoomFileByKey(const nsACString& aKey,
2182                                            CacheFileIOListener* aCallback) {
2183   LOG(("CacheFileIOManager::DoomFileByKey() [key=%s, listener=%p]",
2184        PromiseFlatCString(aKey).get(), aCallback));
2185 
2186   nsresult rv;
2187   RefPtr<CacheFileIOManager> ioMan = gInstance;
2188 
2189   if (!ioMan) {
2190     return NS_ERROR_NOT_INITIALIZED;
2191   }
2192 
2193   RefPtr<DoomFileByKeyEvent> ev = new DoomFileByKeyEvent(aKey, aCallback);
2194   rv = ioMan->mIOThread->DispatchAfterPendingOpens(ev);
2195   NS_ENSURE_SUCCESS(rv, rv);
2196 
2197   return NS_OK;
2198 }
2199 
DoomFileByKeyInternal(const SHA1Sum::Hash * aHash)2200 nsresult CacheFileIOManager::DoomFileByKeyInternal(const SHA1Sum::Hash* aHash) {
2201   LOG((
2202       "CacheFileIOManager::DoomFileByKeyInternal() [hash=%08x%08x%08x%08x%08x]",
2203       LOGSHA1(aHash)));
2204 
2205   MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
2206 
2207   nsresult rv;
2208 
2209   if (mShuttingDown) {
2210     return NS_ERROR_NOT_INITIALIZED;
2211   }
2212 
2213   if (!mCacheDirectory) {
2214     return NS_ERROR_FILE_INVALID_PATH;
2215   }
2216 
2217   // Find active handle
2218   RefPtr<CacheFileHandle> handle;
2219   mHandles.GetHandle(aHash, getter_AddRefs(handle));
2220 
2221   if (handle) {
2222     handle->Log();
2223 
2224     return DoomFileInternal(handle);
2225   }
2226 
2227   CacheIOThread::Cancelable cancelable(true);
2228 
2229   // There is no handle for this file, delete the file if exists
2230   nsCOMPtr<nsIFile> file;
2231   rv = GetFile(aHash, getter_AddRefs(file));
2232   NS_ENSURE_SUCCESS(rv, rv);
2233 
2234   bool exists;
2235   rv = file->Exists(&exists);
2236   NS_ENSURE_SUCCESS(rv, rv);
2237 
2238   if (!exists) {
2239     return NS_ERROR_NOT_AVAILABLE;
2240   }
2241 
2242   LOG(
2243       ("CacheFileIOManager::DoomFileByKeyInternal() - Removing file from "
2244        "disk"));
2245   rv = file->Remove(false);
2246   if (NS_FAILED(rv)) {
2247     NS_WARNING("Cannot remove old entry from the disk");
2248     LOG(
2249         ("CacheFileIOManager::DoomFileByKeyInternal() - Removing file failed. "
2250          "[rv=0x%08" PRIx32 "]",
2251          static_cast<uint32_t>(rv)));
2252   }
2253 
2254   CacheIndex::RemoveEntry(aHash);
2255 
2256   return NS_OK;
2257 }
2258 
2259 // static
ReleaseNSPRHandle(CacheFileHandle * aHandle)2260 nsresult CacheFileIOManager::ReleaseNSPRHandle(CacheFileHandle* aHandle) {
2261   LOG(("CacheFileIOManager::ReleaseNSPRHandle() [handle=%p]", aHandle));
2262 
2263   nsresult rv;
2264   RefPtr<CacheFileIOManager> ioMan = gInstance;
2265 
2266   if (aHandle->IsClosed() || !ioMan) {
2267     return NS_ERROR_NOT_INITIALIZED;
2268   }
2269 
2270   RefPtr<ReleaseNSPRHandleEvent> ev = new ReleaseNSPRHandleEvent(aHandle);
2271   rv = ioMan->mIOThread->Dispatch(ev, aHandle->mPriority
2272                                           ? CacheIOThread::WRITE_PRIORITY
2273                                           : CacheIOThread::WRITE);
2274   NS_ENSURE_SUCCESS(rv, rv);
2275 
2276   return NS_OK;
2277 }
2278 
MaybeReleaseNSPRHandleInternal(CacheFileHandle * aHandle,bool aIgnoreShutdownLag)2279 nsresult CacheFileIOManager::MaybeReleaseNSPRHandleInternal(
2280     CacheFileHandle* aHandle, bool aIgnoreShutdownLag) {
2281   LOG(
2282       ("CacheFileIOManager::MaybeReleaseNSPRHandleInternal() [handle=%p, "
2283        "ignore shutdown=%d]",
2284        aHandle, aIgnoreShutdownLag));
2285 
2286   MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
2287 
2288   if (aHandle->mFD) {
2289     DebugOnly<bool> found{};
2290     found = mHandlesByLastUsed.RemoveElement(aHandle);
2291     MOZ_ASSERT(found);
2292   }
2293 
2294   PRFileDesc* fd = aHandle->mFD;
2295   aHandle->mFD = nullptr;
2296 
2297   // Leak invalid (w/o metadata) and doomed handles immediately after shutdown.
2298   // Leak other handles when past the shutdown time maximum lag.
2299   if (
2300 #ifndef DEBUG
2301       ((aHandle->mInvalid || aHandle->mIsDoomed) &&
2302        MOZ_UNLIKELY(CacheObserver::ShuttingDown())) ||
2303 #endif
2304       MOZ_UNLIKELY(!aIgnoreShutdownLag &&
2305                    CacheObserver::IsPastShutdownIOLag())) {
2306     // Don't bother closing this file.  Return a failure code from here will
2307     // cause any following IO operation on the file (mainly removal) to be
2308     // bypassed, which is what we want.
2309     // For mInvalid == true the entry will never be used, since it doesn't
2310     // have correct metadata, thus we don't need to worry about removing it.
2311     // For mIsDoomed == true the file is already in the doomed sub-dir and
2312     // will be removed on next session start.
2313     LOG(("  past the shutdown I/O lag, leaking file handle"));
2314     return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
2315   }
2316 
2317   if (!fd) {
2318     // The filedesc has already been closed before, just let go.
2319     return NS_OK;
2320   }
2321 
2322   CacheIOThread::Cancelable cancelable(!aHandle->IsSpecialFile());
2323 
2324   PRStatus status = PR_Close(fd);
2325   if (status != PR_SUCCESS) {
2326     LOG(
2327         ("CacheFileIOManager::MaybeReleaseNSPRHandleInternal() "
2328          "failed to close [handle=%p, status=%u]",
2329          aHandle, status));
2330     return NS_ERROR_FAILURE;
2331   }
2332 
2333   LOG(("CacheFileIOManager::MaybeReleaseNSPRHandleInternal() DONE"));
2334 
2335   return NS_OK;
2336 }
2337 
2338 // static
TruncateSeekSetEOF(CacheFileHandle * aHandle,int64_t aTruncatePos,int64_t aEOFPos,CacheFileIOListener * aCallback)2339 nsresult CacheFileIOManager::TruncateSeekSetEOF(
2340     CacheFileHandle* aHandle, int64_t aTruncatePos, int64_t aEOFPos,
2341     CacheFileIOListener* aCallback) {
2342   LOG(
2343       ("CacheFileIOManager::TruncateSeekSetEOF() [handle=%p, "
2344        "truncatePos=%" PRId64 ", "
2345        "EOFPos=%" PRId64 ", listener=%p]",
2346        aHandle, aTruncatePos, aEOFPos, aCallback));
2347 
2348   nsresult rv;
2349   RefPtr<CacheFileIOManager> ioMan = gInstance;
2350 
2351   if (aHandle->IsClosed() || (aCallback && aCallback->IsKilled()) || !ioMan) {
2352     return NS_ERROR_NOT_INITIALIZED;
2353   }
2354 
2355   RefPtr<TruncateSeekSetEOFEvent> ev =
2356       new TruncateSeekSetEOFEvent(aHandle, aTruncatePos, aEOFPos, aCallback);
2357   rv = ioMan->mIOThread->Dispatch(ev, aHandle->mPriority
2358                                           ? CacheIOThread::WRITE_PRIORITY
2359                                           : CacheIOThread::WRITE);
2360   NS_ENSURE_SUCCESS(rv, rv);
2361 
2362   return NS_OK;
2363 }
2364 
2365 // static
GetCacheDirectory(nsIFile ** result)2366 void CacheFileIOManager::GetCacheDirectory(nsIFile** result) {
2367   *result = nullptr;
2368 
2369   RefPtr<CacheFileIOManager> ioMan = gInstance;
2370   if (!ioMan || !ioMan->mCacheDirectory) {
2371     return;
2372   }
2373 
2374   ioMan->mCacheDirectory->Clone(result);
2375 }
2376 
2377 #if defined(MOZ_WIDGET_ANDROID)
2378 
2379 // static
GetProfilelessCacheDirectory(nsIFile ** result)2380 void CacheFileIOManager::GetProfilelessCacheDirectory(nsIFile** result) {
2381   *result = nullptr;
2382 
2383   RefPtr<CacheFileIOManager> ioMan = gInstance;
2384   if (!ioMan || !ioMan->mCacheProfilelessDirectory) {
2385     return;
2386   }
2387 
2388   ioMan->mCacheProfilelessDirectory->Clone(result);
2389 }
2390 
2391 #endif
2392 
2393 // static
GetEntryInfo(const SHA1Sum::Hash * aHash,CacheStorageService::EntryInfoCallback * aCallback)2394 nsresult CacheFileIOManager::GetEntryInfo(
2395     const SHA1Sum::Hash* aHash,
2396     CacheStorageService::EntryInfoCallback* aCallback) {
2397   MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
2398 
2399   nsresult rv;
2400 
2401   RefPtr<CacheFileIOManager> ioMan = gInstance;
2402   if (!ioMan) {
2403     return NS_ERROR_NOT_INITIALIZED;
2404   }
2405 
2406   nsAutoCString enhanceId;
2407   nsAutoCString uriSpec;
2408 
2409   RefPtr<CacheFileHandle> handle;
2410   ioMan->mHandles.GetHandle(aHash, getter_AddRefs(handle));
2411   if (handle) {
2412     RefPtr<nsILoadContextInfo> info =
2413         CacheFileUtils::ParseKey(handle->Key(), &enhanceId, &uriSpec);
2414 
2415     MOZ_ASSERT(info);
2416     if (!info) {
2417       return NS_OK;  // ignore
2418     }
2419 
2420     RefPtr<CacheStorageService> service = CacheStorageService::Self();
2421     if (!service) {
2422       return NS_ERROR_NOT_INITIALIZED;
2423     }
2424 
2425     // Invokes OnCacheEntryInfo when an existing entry is found
2426     if (service->GetCacheEntryInfo(info, enhanceId, uriSpec, aCallback)) {
2427       return NS_OK;
2428     }
2429 
2430     // When we are here, there is no existing entry and we need
2431     // to synchrnously load metadata from a disk file.
2432   }
2433 
2434   // Locate the actual file
2435   nsCOMPtr<nsIFile> file;
2436   ioMan->GetFile(aHash, getter_AddRefs(file));
2437 
2438   // Read metadata from the file synchronously
2439   RefPtr<CacheFileMetadata> metadata = new CacheFileMetadata();
2440   rv = metadata->SyncReadMetadata(file);
2441   if (NS_FAILED(rv)) {
2442     return NS_OK;
2443   }
2444 
2445   // Now get the context + enhance id + URL from the key.
2446   RefPtr<nsILoadContextInfo> info =
2447       CacheFileUtils::ParseKey(metadata->GetKey(), &enhanceId, &uriSpec);
2448   MOZ_ASSERT(info);
2449   if (!info) {
2450     return NS_OK;
2451   }
2452 
2453   // Pick all data to pass to the callback.
2454   int64_t dataSize = metadata->Offset();
2455   uint32_t fetchCount = metadata->GetFetchCount();
2456   uint32_t expirationTime = metadata->GetExpirationTime();
2457   uint32_t lastModified = metadata->GetLastModified();
2458 
2459   // Call directly on the callback.
2460   aCallback->OnEntryInfo(uriSpec, enhanceId, dataSize, fetchCount, lastModified,
2461                          expirationTime, metadata->Pinned(), info);
2462 
2463   return NS_OK;
2464 }
2465 
TruncateSeekSetEOFInternal(CacheFileHandle * aHandle,int64_t aTruncatePos,int64_t aEOFPos)2466 nsresult CacheFileIOManager::TruncateSeekSetEOFInternal(
2467     CacheFileHandle* aHandle, int64_t aTruncatePos, int64_t aEOFPos) {
2468   LOG(
2469       ("CacheFileIOManager::TruncateSeekSetEOFInternal() [handle=%p, "
2470        "truncatePos=%" PRId64 ", EOFPos=%" PRId64 "]",
2471        aHandle, aTruncatePos, aEOFPos));
2472 
2473   nsresult rv;
2474 
2475   if (aHandle->mKilled) {
2476     LOG(("  handle already killed, file not truncated"));
2477     return NS_OK;
2478   }
2479 
2480   if (CacheObserver::ShuttingDown() && !aHandle->mFD) {
2481     aHandle->mKilled = true;
2482     LOG(("  killing the handle, file not truncated"));
2483     return NS_OK;
2484   }
2485 
2486   CacheIOThread::Cancelable cancelable(!aHandle->IsSpecialFile());
2487 
2488   if (!aHandle->mFileExists) {
2489     rv = CreateFile(aHandle);
2490     NS_ENSURE_SUCCESS(rv, rv);
2491   }
2492 
2493   if (!aHandle->mFD) {
2494     rv = OpenNSPRHandle(aHandle);
2495     NS_ENSURE_SUCCESS(rv, rv);
2496   } else {
2497     NSPRHandleUsed(aHandle);
2498   }
2499 
2500   // Check again, OpenNSPRHandle could figure out the file was gone.
2501   if (!aHandle->mFileExists) {
2502     return NS_ERROR_NOT_AVAILABLE;
2503   }
2504 
2505   // When this operation would increase cache size, check whether the cache size
2506   // reached the hard limit and whether it would cause critical low disk space.
2507   if (aHandle->mFileSize < aEOFPos) {
2508     if (mOverLimitEvicting && mCacheSizeOnHardLimit) {
2509       LOG(
2510           ("CacheFileIOManager::TruncateSeekSetEOFInternal() - failing because "
2511            "cache size reached hard limit!"));
2512       return NS_ERROR_FILE_NO_DEVICE_SPACE;
2513     }
2514 
2515     int64_t freeSpace;
2516     rv = mCacheDirectory->GetDiskSpaceAvailable(&freeSpace);
2517     if (NS_WARN_IF(NS_FAILED(rv))) {
2518       freeSpace = -1;
2519       LOG(
2520           ("CacheFileIOManager::TruncateSeekSetEOFInternal() - "
2521            "GetDiskSpaceAvailable() failed! [rv=0x%08" PRIx32 "]",
2522            static_cast<uint32_t>(rv)));
2523     } else {
2524       freeSpace >>= 10;  // bytes to kilobytes
2525       uint32_t limit = CacheObserver::DiskFreeSpaceHardLimit();
2526       if (freeSpace - aEOFPos + aHandle->mFileSize < limit) {
2527         LOG(
2528             ("CacheFileIOManager::TruncateSeekSetEOFInternal() - Low free space"
2529              ", refusing to write! [freeSpace=%" PRId64 "kB, limit=%ukB]",
2530              freeSpace, limit));
2531         return NS_ERROR_FILE_NO_DEVICE_SPACE;
2532       }
2533     }
2534   }
2535 
2536   // This operation always invalidates the entry
2537   aHandle->mInvalid = true;
2538 
2539   rv = TruncFile(aHandle->mFD, aTruncatePos);
2540   NS_ENSURE_SUCCESS(rv, rv);
2541 
2542   if (aTruncatePos != aEOFPos) {
2543     rv = TruncFile(aHandle->mFD, aEOFPos);
2544     NS_ENSURE_SUCCESS(rv, rv);
2545   }
2546 
2547   uint32_t oldSizeInK = aHandle->FileSizeInK();
2548   aHandle->mFileSize = aEOFPos;
2549   uint32_t newSizeInK = aHandle->FileSizeInK();
2550 
2551   if (oldSizeInK != newSizeInK && !aHandle->IsDoomed() &&
2552       !aHandle->IsSpecialFile()) {
2553     CacheIndex::UpdateEntry(aHandle->Hash(), nullptr, nullptr, nullptr, nullptr,
2554                             nullptr, &newSizeInK);
2555 
2556     if (oldSizeInK < newSizeInK) {
2557       EvictIfOverLimitInternal();
2558     }
2559   }
2560 
2561   return NS_OK;
2562 }
2563 
2564 // static
RenameFile(CacheFileHandle * aHandle,const nsACString & aNewName,CacheFileIOListener * aCallback)2565 nsresult CacheFileIOManager::RenameFile(CacheFileHandle* aHandle,
2566                                         const nsACString& aNewName,
2567                                         CacheFileIOListener* aCallback) {
2568   LOG(("CacheFileIOManager::RenameFile() [handle=%p, newName=%s, listener=%p]",
2569        aHandle, PromiseFlatCString(aNewName).get(), aCallback));
2570 
2571   nsresult rv;
2572   RefPtr<CacheFileIOManager> ioMan = gInstance;
2573 
2574   if (aHandle->IsClosed() || !ioMan) {
2575     return NS_ERROR_NOT_INITIALIZED;
2576   }
2577 
2578   if (!aHandle->IsSpecialFile()) {
2579     return NS_ERROR_UNEXPECTED;
2580   }
2581 
2582   RefPtr<RenameFileEvent> ev =
2583       new RenameFileEvent(aHandle, aNewName, aCallback);
2584   rv = ioMan->mIOThread->Dispatch(ev, aHandle->mPriority
2585                                           ? CacheIOThread::WRITE_PRIORITY
2586                                           : CacheIOThread::WRITE);
2587   NS_ENSURE_SUCCESS(rv, rv);
2588 
2589   return NS_OK;
2590 }
2591 
RenameFileInternal(CacheFileHandle * aHandle,const nsACString & aNewName)2592 nsresult CacheFileIOManager::RenameFileInternal(CacheFileHandle* aHandle,
2593                                                 const nsACString& aNewName) {
2594   LOG(("CacheFileIOManager::RenameFileInternal() [handle=%p, newName=%s]",
2595        aHandle, PromiseFlatCString(aNewName).get()));
2596 
2597   nsresult rv;
2598 
2599   MOZ_ASSERT(aHandle->IsSpecialFile());
2600 
2601   if (aHandle->IsDoomed()) {
2602     return NS_ERROR_NOT_AVAILABLE;
2603   }
2604 
2605   // Doom old handle if it exists and is not doomed
2606   for (uint32_t i = 0; i < mSpecialHandles.Length(); i++) {
2607     if (!mSpecialHandles[i]->IsDoomed() &&
2608         mSpecialHandles[i]->Key() == aNewName) {
2609       MOZ_ASSERT(aHandle != mSpecialHandles[i]);
2610       rv = DoomFileInternal(mSpecialHandles[i]);
2611       NS_ENSURE_SUCCESS(rv, rv);
2612       break;
2613     }
2614   }
2615 
2616   nsCOMPtr<nsIFile> file;
2617   rv = GetSpecialFile(aNewName, getter_AddRefs(file));
2618   NS_ENSURE_SUCCESS(rv, rv);
2619 
2620   bool exists;
2621   rv = file->Exists(&exists);
2622   NS_ENSURE_SUCCESS(rv, rv);
2623 
2624   if (exists) {
2625     LOG(
2626         ("CacheFileIOManager::RenameFileInternal() - Removing old file from "
2627          "disk"));
2628     rv = file->Remove(false);
2629     if (NS_FAILED(rv)) {
2630       NS_WARNING("Cannot remove file from the disk");
2631       LOG(
2632           ("CacheFileIOManager::RenameFileInternal() - Removing old file failed"
2633            ". [rv=0x%08" PRIx32 "]",
2634            static_cast<uint32_t>(rv)));
2635     }
2636   }
2637 
2638   if (!aHandle->FileExists()) {
2639     aHandle->mKey = aNewName;
2640     return NS_OK;
2641   }
2642 
2643   rv = MaybeReleaseNSPRHandleInternal(aHandle, true);
2644   NS_ENSURE_SUCCESS(rv, rv);
2645 
2646   rv = aHandle->mFile->MoveToNative(nullptr, aNewName);
2647   NS_ENSURE_SUCCESS(rv, rv);
2648 
2649   aHandle->mKey = aNewName;
2650   return NS_OK;
2651 }
2652 
2653 // static
EvictIfOverLimit()2654 nsresult CacheFileIOManager::EvictIfOverLimit() {
2655   LOG(("CacheFileIOManager::EvictIfOverLimit()"));
2656 
2657   nsresult rv;
2658   RefPtr<CacheFileIOManager> ioMan = gInstance;
2659 
2660   if (!ioMan) {
2661     return NS_ERROR_NOT_INITIALIZED;
2662   }
2663 
2664   nsCOMPtr<nsIRunnable> ev;
2665   ev = NewRunnableMethod("net::CacheFileIOManager::EvictIfOverLimitInternal",
2666                          ioMan, &CacheFileIOManager::EvictIfOverLimitInternal);
2667 
2668   rv = ioMan->mIOThread->Dispatch(ev, CacheIOThread::EVICT);
2669   NS_ENSURE_SUCCESS(rv, rv);
2670 
2671   return NS_OK;
2672 }
2673 
EvictIfOverLimitInternal()2674 nsresult CacheFileIOManager::EvictIfOverLimitInternal() {
2675   LOG(("CacheFileIOManager::EvictIfOverLimitInternal()"));
2676 
2677   nsresult rv;
2678 
2679   MOZ_ASSERT(mIOThread->IsCurrentThread());
2680 
2681   if (mShuttingDown) {
2682     return NS_ERROR_NOT_INITIALIZED;
2683   }
2684 
2685   if (mOverLimitEvicting) {
2686     LOG(
2687         ("CacheFileIOManager::EvictIfOverLimitInternal() - Eviction already "
2688          "running."));
2689     return NS_OK;
2690   }
2691 
2692   CacheIOThread::Cancelable cancelable(true);
2693 
2694   int64_t freeSpace;
2695   rv = mCacheDirectory->GetDiskSpaceAvailable(&freeSpace);
2696   if (NS_WARN_IF(NS_FAILED(rv))) {
2697     freeSpace = -1;
2698 
2699     // Do not change smart size.
2700     LOG(
2701         ("CacheFileIOManager::EvictIfOverLimitInternal() - "
2702          "GetDiskSpaceAvailable() failed! [rv=0x%08" PRIx32 "]",
2703          static_cast<uint32_t>(rv)));
2704   } else {
2705     freeSpace >>= 10;  // bytes to kilobytes
2706     UpdateSmartCacheSize(freeSpace);
2707   }
2708 
2709   uint32_t cacheUsage;
2710   rv = CacheIndex::GetCacheSize(&cacheUsage);
2711   NS_ENSURE_SUCCESS(rv, rv);
2712 
2713   uint32_t cacheLimit = CacheObserver::DiskCacheCapacity();
2714   uint32_t freeSpaceLimit = CacheObserver::DiskFreeSpaceSoftLimit();
2715 
2716   if (cacheUsage <= cacheLimit &&
2717       (freeSpace == -1 || freeSpace >= freeSpaceLimit)) {
2718     LOG(
2719         ("CacheFileIOManager::EvictIfOverLimitInternal() - Cache size and free "
2720          "space in limits. [cacheSize=%ukB, cacheSizeLimit=%ukB, "
2721          "freeSpace=%" PRId64 "kB, freeSpaceLimit=%ukB]",
2722          cacheUsage, cacheLimit, freeSpace, freeSpaceLimit));
2723     return NS_OK;
2724   }
2725 
2726   LOG(
2727       ("CacheFileIOManager::EvictIfOverLimitInternal() - Cache size exceeded "
2728        "limit. Starting overlimit eviction. [cacheSize=%ukB, limit=%ukB]",
2729        cacheUsage, cacheLimit));
2730 
2731   nsCOMPtr<nsIRunnable> ev;
2732   ev = NewRunnableMethod("net::CacheFileIOManager::OverLimitEvictionInternal",
2733                          this, &CacheFileIOManager::OverLimitEvictionInternal);
2734 
2735   rv = mIOThread->Dispatch(ev, CacheIOThread::EVICT);
2736   NS_ENSURE_SUCCESS(rv, rv);
2737 
2738   mOverLimitEvicting = true;
2739   return NS_OK;
2740 }
2741 
OverLimitEvictionInternal()2742 nsresult CacheFileIOManager::OverLimitEvictionInternal() {
2743   LOG(("CacheFileIOManager::OverLimitEvictionInternal()"));
2744 
2745   nsresult rv;
2746 
2747   MOZ_ASSERT(mIOThread->IsCurrentThread());
2748 
2749   // mOverLimitEvicting is accessed only on IO thread, so we can set it to false
2750   // here and set it to true again once we dispatch another event that will
2751   // continue with the eviction. The reason why we do so is that we can fail
2752   // early anywhere in this method and the variable will contain a correct
2753   // value. Otherwise we would need to set it to false on every failing place.
2754   mOverLimitEvicting = false;
2755 
2756   if (mShuttingDown) {
2757     return NS_ERROR_NOT_INITIALIZED;
2758   }
2759 
2760   while (true) {
2761     int64_t freeSpace;
2762     rv = mCacheDirectory->GetDiskSpaceAvailable(&freeSpace);
2763     if (NS_WARN_IF(NS_FAILED(rv))) {
2764       freeSpace = -1;
2765 
2766       // Do not change smart size.
2767       LOG(
2768           ("CacheFileIOManager::EvictIfOverLimitInternal() - "
2769            "GetDiskSpaceAvailable() failed! [rv=0x%08" PRIx32 "]",
2770            static_cast<uint32_t>(rv)));
2771     } else {
2772       freeSpace >>= 10;  // bytes to kilobytes
2773       UpdateSmartCacheSize(freeSpace);
2774     }
2775 
2776     uint32_t cacheUsage;
2777     rv = CacheIndex::GetCacheSize(&cacheUsage);
2778     NS_ENSURE_SUCCESS(rv, rv);
2779 
2780     uint32_t cacheLimit = CacheObserver::DiskCacheCapacity();
2781     uint32_t freeSpaceLimit = CacheObserver::DiskFreeSpaceSoftLimit();
2782 
2783     if (cacheUsage > cacheLimit) {
2784       LOG(
2785           ("CacheFileIOManager::OverLimitEvictionInternal() - Cache size over "
2786            "limit. [cacheSize=%ukB, limit=%ukB]",
2787            cacheUsage, cacheLimit));
2788 
2789       // We allow cache size to go over the specified limit. Eviction should
2790       // keep the size within the limit in a long run, but in case the eviction
2791       // is too slow, the cache could go way over the limit. To prevent this we
2792       // set flag mCacheSizeOnHardLimit when the size reaches 105% of the limit
2793       // and WriteInternal() and TruncateSeekSetEOFInternal() fail to cache
2794       // additional data.
2795       if ((cacheUsage - cacheLimit) > (cacheLimit / 20)) {
2796         LOG(
2797             ("CacheFileIOManager::OverLimitEvictionInternal() - Cache size "
2798              "reached hard limit."));
2799         mCacheSizeOnHardLimit = true;
2800       } else {
2801         mCacheSizeOnHardLimit = false;
2802       }
2803     } else if (freeSpace != -1 && freeSpace < freeSpaceLimit) {
2804       LOG(
2805           ("CacheFileIOManager::OverLimitEvictionInternal() - Free space under "
2806            "limit. [freeSpace=%" PRId64 "kB, freeSpaceLimit=%ukB]",
2807            freeSpace, freeSpaceLimit));
2808     } else {
2809       LOG(
2810           ("CacheFileIOManager::OverLimitEvictionInternal() - Cache size and "
2811            "free space in limits. [cacheSize=%ukB, cacheSizeLimit=%ukB, "
2812            "freeSpace=%" PRId64 "kB, freeSpaceLimit=%ukB]",
2813            cacheUsage, cacheLimit, freeSpace, freeSpaceLimit));
2814 
2815       mCacheSizeOnHardLimit = false;
2816       return NS_OK;
2817     }
2818 
2819     if (CacheIOThread::YieldAndRerun()) {
2820       LOG(
2821           ("CacheFileIOManager::OverLimitEvictionInternal() - Breaking loop "
2822            "for higher level events."));
2823       mOverLimitEvicting = true;
2824       return NS_OK;
2825     }
2826 
2827     SHA1Sum::Hash hash;
2828     uint32_t cnt;
2829     static uint32_t consecutiveFailures = 0;
2830     rv = CacheIndex::GetEntryForEviction(false, &hash, &cnt);
2831     NS_ENSURE_SUCCESS(rv, rv);
2832 
2833     rv = DoomFileByKeyInternal(&hash);
2834     if (NS_SUCCEEDED(rv)) {
2835       consecutiveFailures = 0;
2836     } else if (rv == NS_ERROR_NOT_AVAILABLE) {
2837       LOG(
2838           ("CacheFileIOManager::OverLimitEvictionInternal() - "
2839            "DoomFileByKeyInternal() failed. [rv=0x%08" PRIx32 "]",
2840            static_cast<uint32_t>(rv)));
2841       // TODO index is outdated, start update
2842 
2843       // Make sure index won't return the same entry again
2844       CacheIndex::RemoveEntry(&hash);
2845       consecutiveFailures = 0;
2846     } else {
2847       // This shouldn't normally happen, but the eviction must not fail
2848       // completely if we ever encounter this problem.
2849       NS_WARNING(
2850           "CacheFileIOManager::OverLimitEvictionInternal() - Unexpected "
2851           "failure of DoomFileByKeyInternal()");
2852 
2853       LOG(
2854           ("CacheFileIOManager::OverLimitEvictionInternal() - "
2855            "DoomFileByKeyInternal() failed. [rv=0x%08" PRIx32 "]",
2856            static_cast<uint32_t>(rv)));
2857 
2858       // Normally, CacheIndex::UpdateEntry() is called only to update newly
2859       // created/opened entries which are always fresh and UpdateEntry() expects
2860       // and checks this flag. The way we use UpdateEntry() here is a kind of
2861       // hack and we must make sure the flag is set by calling
2862       // EnsureEntryExists().
2863       rv = CacheIndex::EnsureEntryExists(&hash);
2864       NS_ENSURE_SUCCESS(rv, rv);
2865 
2866       // Move the entry at the end of both lists to make sure we won't end up
2867       // failing on one entry forever.
2868       uint32_t frecency = 0;
2869       rv = CacheIndex::UpdateEntry(&hash, &frecency, nullptr, nullptr, nullptr,
2870                                    nullptr, nullptr);
2871       NS_ENSURE_SUCCESS(rv, rv);
2872 
2873       consecutiveFailures++;
2874       if (consecutiveFailures >= cnt) {
2875         // This doesn't necessarily mean that we've tried to doom every entry
2876         // but we've reached a sane number of tries. It is likely that another
2877         // eviction will start soon. And as said earlier, this normally doesn't
2878         // happen at all.
2879         return NS_OK;
2880       }
2881     }
2882   }
2883 
2884   MOZ_ASSERT_UNREACHABLE("We should never get here");
2885   return NS_OK;
2886 }
2887 
2888 // static
EvictAll()2889 nsresult CacheFileIOManager::EvictAll() {
2890   LOG(("CacheFileIOManager::EvictAll()"));
2891 
2892   nsresult rv;
2893   RefPtr<CacheFileIOManager> ioMan = gInstance;
2894 
2895   if (!ioMan) {
2896     return NS_ERROR_NOT_INITIALIZED;
2897   }
2898 
2899   nsCOMPtr<nsIRunnable> ev;
2900   ev = NewRunnableMethod("net::CacheFileIOManager::EvictAllInternal", ioMan,
2901                          &CacheFileIOManager::EvictAllInternal);
2902 
2903   rv = ioMan->mIOThread->DispatchAfterPendingOpens(ev);
2904   if (NS_WARN_IF(NS_FAILED(rv))) {
2905     return rv;
2906   }
2907 
2908   return NS_OK;
2909 }
2910 
2911 namespace {
2912 
2913 class EvictionNotifierRunnable : public Runnable {
2914  public:
EvictionNotifierRunnable()2915   EvictionNotifierRunnable() : Runnable("EvictionNotifierRunnable") {}
2916   NS_DECL_NSIRUNNABLE
2917 };
2918 
2919 NS_IMETHODIMP
Run()2920 EvictionNotifierRunnable::Run() {
2921   nsCOMPtr<nsIObserverService> obsSvc = mozilla::services::GetObserverService();
2922   if (obsSvc) {
2923     obsSvc->NotifyObservers(nullptr, "cacheservice:empty-cache", nullptr);
2924   }
2925   return NS_OK;
2926 }
2927 
2928 }  // namespace
2929 
EvictAllInternal()2930 nsresult CacheFileIOManager::EvictAllInternal() {
2931   LOG(("CacheFileIOManager::EvictAllInternal()"));
2932 
2933   nsresult rv;
2934 
2935   MOZ_ASSERT(mIOThread->IsCurrentThread());
2936 
2937   RefPtr<EvictionNotifierRunnable> r = new EvictionNotifierRunnable();
2938 
2939   if (!mCacheDirectory) {
2940     // This is a kind of hack. Somebody called EvictAll() without a profile.
2941     // This happens in xpcshell tests that use cache without profile. We need
2942     // to notify observers in this case since the tests are waiting for it.
2943     NS_DispatchToMainThread(r);
2944     return NS_ERROR_FILE_INVALID_PATH;
2945   }
2946 
2947   if (mShuttingDown) {
2948     return NS_ERROR_NOT_INITIALIZED;
2949   }
2950 
2951   if (!mTreeCreated) {
2952     rv = CreateCacheTree();
2953     if (NS_FAILED(rv)) {
2954       return rv;
2955     }
2956   }
2957 
2958   // Doom all active handles
2959   nsTArray<RefPtr<CacheFileHandle>> handles;
2960   mHandles.GetActiveHandles(&handles);
2961 
2962   for (uint32_t i = 0; i < handles.Length(); ++i) {
2963     rv = DoomFileInternal(handles[i]);
2964     if (NS_WARN_IF(NS_FAILED(rv))) {
2965       LOG(
2966           ("CacheFileIOManager::EvictAllInternal() - Cannot doom handle "
2967            "[handle=%p]",
2968            handles[i].get()));
2969     }
2970   }
2971 
2972   nsCOMPtr<nsIFile> file;
2973   rv = mCacheDirectory->Clone(getter_AddRefs(file));
2974   if (NS_WARN_IF(NS_FAILED(rv))) {
2975     return rv;
2976   }
2977 
2978   rv = file->AppendNative(nsLiteralCString(ENTRIES_DIR));
2979   if (NS_WARN_IF(NS_FAILED(rv))) {
2980     return rv;
2981   }
2982 
2983   // Trash current entries directory
2984   rv = TrashDirectory(file);
2985   if (NS_WARN_IF(NS_FAILED(rv))) {
2986     return rv;
2987   }
2988 
2989   // Files are now inaccessible in entries directory, notify observers.
2990   NS_DispatchToMainThread(r);
2991 
2992   // Create a new empty entries directory
2993   rv = CheckAndCreateDir(mCacheDirectory, ENTRIES_DIR, false);
2994   if (NS_WARN_IF(NS_FAILED(rv))) {
2995     return rv;
2996   }
2997 
2998   CacheIndex::RemoveAll();
2999 
3000   return NS_OK;
3001 }
3002 
3003 // static
EvictByContext(nsILoadContextInfo * aLoadContextInfo,bool aPinned,const nsAString & aOrigin,const nsAString & aBaseDomain)3004 nsresult CacheFileIOManager::EvictByContext(
3005     nsILoadContextInfo* aLoadContextInfo, bool aPinned,
3006     const nsAString& aOrigin, const nsAString& aBaseDomain) {
3007   LOG(("CacheFileIOManager::EvictByContext() [loadContextInfo=%p]",
3008        aLoadContextInfo));
3009 
3010   nsresult rv;
3011   RefPtr<CacheFileIOManager> ioMan = gInstance;
3012 
3013   if (!ioMan) {
3014     return NS_ERROR_NOT_INITIALIZED;
3015   }
3016 
3017   nsCOMPtr<nsIRunnable> ev;
3018   ev =
3019       NewRunnableMethod<nsCOMPtr<nsILoadContextInfo>, bool, nsString, nsString>(
3020           "net::CacheFileIOManager::EvictByContextInternal", ioMan,
3021           &CacheFileIOManager::EvictByContextInternal, aLoadContextInfo,
3022           aPinned, aOrigin, aBaseDomain);
3023 
3024   rv = ioMan->mIOThread->DispatchAfterPendingOpens(ev);
3025   if (NS_WARN_IF(NS_FAILED(rv))) {
3026     return rv;
3027   }
3028 
3029   return NS_OK;
3030 }
3031 
EvictByContextInternal(nsILoadContextInfo * aLoadContextInfo,bool aPinned,const nsAString & aOrigin,const nsAString & aBaseDomain)3032 nsresult CacheFileIOManager::EvictByContextInternal(
3033     nsILoadContextInfo* aLoadContextInfo, bool aPinned,
3034     const nsAString& aOrigin, const nsAString& aBaseDomain) {
3035   LOG(
3036       ("CacheFileIOManager::EvictByContextInternal() [loadContextInfo=%p, "
3037        "pinned=%d]",
3038        aLoadContextInfo, aPinned));
3039 
3040   nsresult rv;
3041 
3042   if (aLoadContextInfo) {
3043     nsAutoCString suffix;
3044     aLoadContextInfo->OriginAttributesPtr()->CreateSuffix(suffix);
3045     LOG(("  anonymous=%u, suffix=%s]", aLoadContextInfo->IsAnonymous(),
3046          suffix.get()));
3047 
3048     MOZ_ASSERT(mIOThread->IsCurrentThread());
3049 
3050     MOZ_ASSERT(!aLoadContextInfo->IsPrivate());
3051     if (aLoadContextInfo->IsPrivate()) {
3052       return NS_ERROR_INVALID_ARG;
3053     }
3054   }
3055 
3056   if (!mCacheDirectory) {
3057     // This is a kind of hack. Somebody called EvictAll() without a profile.
3058     // This happens in xpcshell tests that use cache without profile. We need
3059     // to notify observers in this case since the tests are waiting for it.
3060     // Also notify for aPinned == true, those are interested as well.
3061     if (!aLoadContextInfo) {
3062       RefPtr<EvictionNotifierRunnable> r = new EvictionNotifierRunnable();
3063       NS_DispatchToMainThread(r);
3064     }
3065     return NS_ERROR_FILE_INVALID_PATH;
3066   }
3067 
3068   if (mShuttingDown) {
3069     return NS_ERROR_NOT_INITIALIZED;
3070   }
3071 
3072   if (!mTreeCreated) {
3073     rv = CreateCacheTree();
3074     if (NS_FAILED(rv)) {
3075       return rv;
3076     }
3077   }
3078 
3079   NS_ConvertUTF16toUTF8 origin(aOrigin);
3080   NS_ConvertUTF16toUTF8 baseDomain(aBaseDomain);
3081 
3082   // Doom all active handles that matches the load context
3083   nsTArray<RefPtr<CacheFileHandle>> handles;
3084   mHandles.GetActiveHandles(&handles);
3085 
3086   for (uint32_t i = 0; i < handles.Length(); ++i) {
3087     CacheFileHandle* handle = handles[i];
3088 
3089     const bool shouldRemove = [&] {
3090       nsAutoCString uriSpec;
3091       RefPtr<nsILoadContextInfo> info =
3092           CacheFileUtils::ParseKey(handle->Key(), nullptr, &uriSpec);
3093       if (!info) {
3094         LOG(
3095             ("CacheFileIOManager::EvictByContextInternal() - Cannot parse key "
3096              "in "
3097              "handle! [handle=%p, key=%s]",
3098              handle, handle->Key().get()));
3099         MOZ_CRASH("Unexpected error!");
3100       }
3101 
3102       // Filter by base domain.
3103       if (!aBaseDomain.IsEmpty()) {
3104         if (StoragePrincipalHelper::PartitionKeyHasBaseDomain(
3105                 info->OriginAttributesPtr()->mPartitionKey, aBaseDomain)) {
3106           return true;
3107         }
3108 
3109         // If the partitionKey does not match, check the entry URI next.
3110 
3111         // Get host portion of uriSpec.
3112         nsCOMPtr<nsIURI> uri;
3113         rv = NS_NewURI(getter_AddRefs(uri), uriSpec);
3114         if (NS_WARN_IF(NS_FAILED(rv))) {
3115           return false;
3116         }
3117         nsAutoCString host;
3118         rv = uri->GetHost(host);
3119         // Some entries may not have valid hosts. We can skip them.
3120         if (NS_FAILED(rv) || host.IsEmpty()) {
3121           return false;
3122         }
3123 
3124         // Clear entry if the host belongs to the given base domain.
3125         bool hasRootDomain = false;
3126         rv = NS_HasRootDomain(host, baseDomain, &hasRootDomain);
3127         if (NS_WARN_IF(NS_FAILED(rv))) {
3128           return false;
3129         }
3130 
3131         return hasRootDomain;
3132       }
3133 
3134       // Filter by LoadContextInfo.
3135       if (aLoadContextInfo && !info->Equals(aLoadContextInfo)) {
3136         return false;
3137       }
3138 
3139       // Filter by origin.
3140       if (!origin.IsEmpty()) {
3141         RefPtr<MozURL> url;
3142         rv = MozURL::Init(getter_AddRefs(url), uriSpec);
3143         if (NS_FAILED(rv)) {
3144           return false;
3145         }
3146 
3147         nsAutoCString urlOrigin;
3148         url->Origin(urlOrigin);
3149 
3150         if (!urlOrigin.Equals(origin)) {
3151           return false;
3152         }
3153       }
3154 
3155       return true;
3156     }();
3157 
3158     if (!shouldRemove) {
3159       continue;
3160     }
3161 
3162     // handle will be doomed only when pinning status is known and equal or
3163     // doom decision will be deferred until pinning status is determined.
3164     rv = DoomFileInternal(handle,
3165                           aPinned ? CacheFileIOManager::DOOM_WHEN_PINNED
3166                                   : CacheFileIOManager::DOOM_WHEN_NON_PINNED);
3167     if (NS_WARN_IF(NS_FAILED(rv))) {
3168       LOG(
3169           ("CacheFileIOManager::EvictByContextInternal() - Cannot doom handle"
3170            " [handle=%p]",
3171            handle));
3172     }
3173   }
3174 
3175   if (!aLoadContextInfo) {
3176     RefPtr<EvictionNotifierRunnable> r = new EvictionNotifierRunnable();
3177     NS_DispatchToMainThread(r);
3178   }
3179 
3180   if (!mContextEvictor) {
3181     mContextEvictor = new CacheFileContextEvictor();
3182     mContextEvictor->Init(mCacheDirectory);
3183   }
3184 
3185   mContextEvictor->AddContext(aLoadContextInfo, aPinned, aOrigin);
3186 
3187   return NS_OK;
3188 }
3189 
3190 // static
CacheIndexStateChanged()3191 nsresult CacheFileIOManager::CacheIndexStateChanged() {
3192   LOG(("CacheFileIOManager::CacheIndexStateChanged()"));
3193 
3194   nsresult rv;
3195 
3196   // CacheFileIOManager lives longer than CacheIndex so gInstance must be
3197   // non-null here.
3198   MOZ_ASSERT(gInstance);
3199 
3200   // We have to re-distatch even if we are on IO thread to prevent reentering
3201   // the lock in CacheIndex
3202   nsCOMPtr<nsIRunnable> ev = NewRunnableMethod(
3203       "net::CacheFileIOManager::CacheIndexStateChangedInternal",
3204       gInstance.get(), &CacheFileIOManager::CacheIndexStateChangedInternal);
3205 
3206   nsCOMPtr<nsIEventTarget> ioTarget = IOTarget();
3207   MOZ_ASSERT(ioTarget);
3208 
3209   rv = ioTarget->Dispatch(ev, nsIEventTarget::DISPATCH_NORMAL);
3210   if (NS_WARN_IF(NS_FAILED(rv))) {
3211     return rv;
3212   }
3213 
3214   return NS_OK;
3215 }
3216 
CacheIndexStateChangedInternal()3217 void CacheFileIOManager::CacheIndexStateChangedInternal() {
3218   if (mShuttingDown) {
3219     // ignore notification during shutdown
3220     return;
3221   }
3222 
3223   if (!mContextEvictor) {
3224     return;
3225   }
3226 
3227   mContextEvictor->CacheIndexStateChanged();
3228 }
3229 
TrashDirectory(nsIFile * aFile)3230 nsresult CacheFileIOManager::TrashDirectory(nsIFile* aFile) {
3231   LOG(("CacheFileIOManager::TrashDirectory() [file=%s]",
3232        aFile->HumanReadablePath().get()));
3233 
3234   nsresult rv;
3235 
3236   MOZ_ASSERT(mIOThread->IsCurrentThread());
3237   MOZ_ASSERT(mCacheDirectory);
3238 
3239   // When the directory is empty, it is cheaper to remove it directly instead of
3240   // using the trash mechanism.
3241   bool isEmpty;
3242   rv = IsEmptyDirectory(aFile, &isEmpty);
3243   NS_ENSURE_SUCCESS(rv, rv);
3244 
3245   if (isEmpty) {
3246     rv = aFile->Remove(false);
3247     LOG(
3248         ("CacheFileIOManager::TrashDirectory() - Directory removed "
3249          "[rv=0x%08" PRIx32 "]",
3250          static_cast<uint32_t>(rv)));
3251     return rv;
3252   }
3253 
3254 #ifdef DEBUG
3255   nsCOMPtr<nsIFile> dirCheck;
3256   rv = aFile->GetParent(getter_AddRefs(dirCheck));
3257   NS_ENSURE_SUCCESS(rv, rv);
3258 
3259   bool equals = false;
3260   rv = dirCheck->Equals(mCacheDirectory, &equals);
3261   NS_ENSURE_SUCCESS(rv, rv);
3262 
3263   MOZ_ASSERT(equals);
3264 #endif
3265 
3266   nsCOMPtr<nsIFile> dir, trash;
3267   nsAutoCString leaf;
3268 
3269   rv = aFile->Clone(getter_AddRefs(dir));
3270   NS_ENSURE_SUCCESS(rv, rv);
3271 
3272   rv = aFile->Clone(getter_AddRefs(trash));
3273   NS_ENSURE_SUCCESS(rv, rv);
3274 
3275   const int32_t kMaxTries = 16;
3276   srand(static_cast<unsigned>(PR_Now()));
3277   for (int32_t triesCount = 0;; ++triesCount) {
3278     leaf = TRASH_DIR;
3279     leaf.AppendInt(rand());
3280     rv = trash->SetNativeLeafName(leaf);
3281     NS_ENSURE_SUCCESS(rv, rv);
3282 
3283     bool exists;
3284     if (NS_SUCCEEDED(trash->Exists(&exists)) && !exists) {
3285       break;
3286     }
3287 
3288     LOG(
3289         ("CacheFileIOManager::TrashDirectory() - Trash directory already "
3290          "exists [leaf=%s]",
3291          leaf.get()));
3292 
3293     if (triesCount == kMaxTries) {
3294       LOG(
3295           ("CacheFileIOManager::TrashDirectory() - Could not find unused trash "
3296            "directory in %d tries.",
3297            kMaxTries));
3298       return NS_ERROR_FAILURE;
3299     }
3300   }
3301 
3302   LOG(("CacheFileIOManager::TrashDirectory() - Renaming directory [leaf=%s]",
3303        leaf.get()));
3304 
3305   rv = dir->MoveToNative(nullptr, leaf);
3306   NS_ENSURE_SUCCESS(rv, rv);
3307 
3308   StartRemovingTrash();
3309   return NS_OK;
3310 }
3311 
3312 // static
OnTrashTimer(nsITimer * aTimer,void * aClosure)3313 void CacheFileIOManager::OnTrashTimer(nsITimer* aTimer, void* aClosure) {
3314   LOG(("CacheFileIOManager::OnTrashTimer() [timer=%p, closure=%p]", aTimer,
3315        aClosure));
3316 
3317   RefPtr<CacheFileIOManager> ioMan = gInstance;
3318 
3319   if (!ioMan) {
3320     return;
3321   }
3322 
3323   ioMan->mTrashTimer = nullptr;
3324   ioMan->StartRemovingTrash();
3325 }
3326 
StartRemovingTrash()3327 nsresult CacheFileIOManager::StartRemovingTrash() {
3328   LOG(("CacheFileIOManager::StartRemovingTrash()"));
3329 
3330   nsresult rv;
3331 
3332   MOZ_ASSERT(mIOThread->IsCurrentThread());
3333 
3334   if (mShuttingDown) {
3335     return NS_ERROR_NOT_INITIALIZED;
3336   }
3337 
3338   if (!mCacheDirectory) {
3339     return NS_ERROR_FILE_INVALID_PATH;
3340   }
3341 
3342   if (mTrashTimer) {
3343     LOG(("CacheFileIOManager::StartRemovingTrash() - Trash timer exists."));
3344     return NS_OK;
3345   }
3346 
3347   if (mRemovingTrashDirs) {
3348     LOG(
3349         ("CacheFileIOManager::StartRemovingTrash() - Trash removing in "
3350          "progress."));
3351     return NS_OK;
3352   }
3353 
3354   uint32_t elapsed = (TimeStamp::NowLoRes() - mStartTime).ToMilliseconds();
3355   if (elapsed < kRemoveTrashStartDelay) {
3356     nsCOMPtr<nsIEventTarget> ioTarget = IOTarget();
3357     MOZ_ASSERT(ioTarget);
3358 
3359     return NS_NewTimerWithFuncCallback(
3360         getter_AddRefs(mTrashTimer), CacheFileIOManager::OnTrashTimer, nullptr,
3361         kRemoveTrashStartDelay - elapsed, nsITimer::TYPE_ONE_SHOT,
3362         "net::CacheFileIOManager::StartRemovingTrash", ioTarget);
3363   }
3364 
3365   nsCOMPtr<nsIRunnable> ev;
3366   ev = NewRunnableMethod("net::CacheFileIOManager::RemoveTrashInternal", this,
3367                          &CacheFileIOManager::RemoveTrashInternal);
3368 
3369   rv = mIOThread->Dispatch(ev, CacheIOThread::EVICT);
3370   NS_ENSURE_SUCCESS(rv, rv);
3371 
3372   mRemovingTrashDirs = true;
3373   return NS_OK;
3374 }
3375 
RemoveTrashInternal()3376 nsresult CacheFileIOManager::RemoveTrashInternal() {
3377   LOG(("CacheFileIOManager::RemoveTrashInternal()"));
3378 
3379   nsresult rv;
3380 
3381   MOZ_ASSERT(mIOThread->IsCurrentThread());
3382 
3383   if (mShuttingDown) {
3384     return NS_ERROR_NOT_INITIALIZED;
3385   }
3386 
3387   CacheIOThread::Cancelable cancelable(true);
3388 
3389   MOZ_ASSERT(!mTrashTimer);
3390   MOZ_ASSERT(mRemovingTrashDirs);
3391 
3392   if (!mTreeCreated) {
3393     rv = CreateCacheTree();
3394     if (NS_FAILED(rv)) {
3395       return rv;
3396     }
3397   }
3398 
3399   // mRemovingTrashDirs is accessed only on IO thread, so we can drop the flag
3400   // here and set it again once we dispatch a continuation event. By doing so,
3401   // we don't have to drop the flag on any possible early return.
3402   mRemovingTrashDirs = false;
3403 
3404   while (true) {
3405     if (CacheIOThread::YieldAndRerun()) {
3406       LOG(
3407           ("CacheFileIOManager::RemoveTrashInternal() - Breaking loop for "
3408            "higher level events."));
3409       mRemovingTrashDirs = true;
3410       return NS_OK;
3411     }
3412 
3413     // Find some trash directory
3414     if (!mTrashDir) {
3415       MOZ_ASSERT(!mTrashDirEnumerator);
3416 
3417       rv = FindTrashDirToRemove();
3418       if (rv == NS_ERROR_NOT_AVAILABLE) {
3419         LOG(
3420             ("CacheFileIOManager::RemoveTrashInternal() - No trash directory "
3421              "found."));
3422         return NS_OK;
3423       }
3424       NS_ENSURE_SUCCESS(rv, rv);
3425 
3426       rv = mTrashDir->GetDirectoryEntries(getter_AddRefs(mTrashDirEnumerator));
3427       NS_ENSURE_SUCCESS(rv, rv);
3428 
3429       continue;  // check elapsed time
3430     }
3431 
3432     // We null out mTrashDirEnumerator once we remove all files in the
3433     // directory, so remove the trash directory if we don't have enumerator.
3434     if (!mTrashDirEnumerator) {
3435       rv = mTrashDir->Remove(false);
3436       if (NS_FAILED(rv)) {
3437         // There is no reason why removing an empty directory should fail, but
3438         // if it does, we should continue and try to remove all other trash
3439         // directories.
3440         nsAutoCString leafName;
3441         mTrashDir->GetNativeLeafName(leafName);
3442         mFailedTrashDirs.AppendElement(leafName);
3443         LOG(
3444             ("CacheFileIOManager::RemoveTrashInternal() - Cannot remove "
3445              "trashdir. [name=%s]",
3446              leafName.get()));
3447       }
3448 
3449       mTrashDir = nullptr;
3450       continue;  // check elapsed time
3451     }
3452 
3453     nsCOMPtr<nsIFile> file;
3454     rv = mTrashDirEnumerator->GetNextFile(getter_AddRefs(file));
3455     if (!file) {
3456       mTrashDirEnumerator->Close();
3457       mTrashDirEnumerator = nullptr;
3458       continue;  // check elapsed time
3459     }
3460     bool isDir = false;
3461     file->IsDirectory(&isDir);
3462     if (isDir) {
3463       NS_WARNING(
3464           "Found a directory in a trash directory! It will be removed "
3465           "recursively, but this can block IO thread for a while!");
3466       if (LOG_ENABLED()) {
3467         LOG(
3468             ("CacheFileIOManager::RemoveTrashInternal() - Found a directory in "
3469              "a trash "
3470              "directory! It will be removed recursively, but this can block IO "
3471              "thread for a while! [file=%s]",
3472              file->HumanReadablePath().get()));
3473       }
3474     }
3475     file->Remove(isDir);
3476   }
3477 
3478   MOZ_ASSERT_UNREACHABLE("We should never get here");
3479   return NS_OK;
3480 }
3481 
FindTrashDirToRemove()3482 nsresult CacheFileIOManager::FindTrashDirToRemove() {
3483   LOG(("CacheFileIOManager::FindTrashDirToRemove()"));
3484 
3485   nsresult rv;
3486 
3487   // We call this method on the main thread during shutdown when user wants to
3488   // remove all cache files.
3489   MOZ_ASSERT(mIOThread->IsCurrentThread() || mShuttingDown);
3490 
3491   nsCOMPtr<nsIDirectoryEnumerator> iter;
3492   rv = mCacheDirectory->GetDirectoryEntries(getter_AddRefs(iter));
3493   NS_ENSURE_SUCCESS(rv, rv);
3494 
3495   nsCOMPtr<nsIFile> file;
3496   while (NS_SUCCEEDED(iter->GetNextFile(getter_AddRefs(file))) && file) {
3497     bool isDir = false;
3498     file->IsDirectory(&isDir);
3499     if (!isDir) {
3500       continue;
3501     }
3502 
3503     nsAutoCString leafName;
3504     rv = file->GetNativeLeafName(leafName);
3505     if (NS_FAILED(rv)) {
3506       continue;
3507     }
3508 
3509     if (leafName.Length() < strlen(TRASH_DIR)) {
3510       continue;
3511     }
3512 
3513     if (!StringBeginsWith(leafName, nsLiteralCString(TRASH_DIR))) {
3514       continue;
3515     }
3516 
3517     if (mFailedTrashDirs.Contains(leafName)) {
3518       continue;
3519     }
3520 
3521     LOG(("CacheFileIOManager::FindTrashDirToRemove() - Returning directory %s",
3522          leafName.get()));
3523 
3524     mTrashDir = file;
3525     return NS_OK;
3526   }
3527 
3528   // When we're here we've tried to delete all trash directories. Clear
3529   // mFailedTrashDirs so we will try to delete them again when we start removing
3530   // trash directories next time.
3531   mFailedTrashDirs.Clear();
3532   return NS_ERROR_NOT_AVAILABLE;
3533 }
3534 
3535 // static
InitIndexEntry(CacheFileHandle * aHandle,OriginAttrsHash aOriginAttrsHash,bool aAnonymous,bool aPinning)3536 nsresult CacheFileIOManager::InitIndexEntry(CacheFileHandle* aHandle,
3537                                             OriginAttrsHash aOriginAttrsHash,
3538                                             bool aAnonymous, bool aPinning) {
3539   LOG(
3540       ("CacheFileIOManager::InitIndexEntry() [handle=%p, "
3541        "originAttrsHash=%" PRIx64 ", "
3542        "anonymous=%d, pinning=%d]",
3543        aHandle, aOriginAttrsHash, aAnonymous, aPinning));
3544 
3545   nsresult rv;
3546   RefPtr<CacheFileIOManager> ioMan = gInstance;
3547 
3548   if (aHandle->IsClosed() || !ioMan) {
3549     return NS_ERROR_NOT_INITIALIZED;
3550   }
3551 
3552   if (aHandle->IsSpecialFile()) {
3553     return NS_ERROR_UNEXPECTED;
3554   }
3555 
3556   RefPtr<InitIndexEntryEvent> ev =
3557       new InitIndexEntryEvent(aHandle, aOriginAttrsHash, aAnonymous, aPinning);
3558   rv = ioMan->mIOThread->Dispatch(ev, aHandle->mPriority
3559                                           ? CacheIOThread::WRITE_PRIORITY
3560                                           : CacheIOThread::WRITE);
3561   NS_ENSURE_SUCCESS(rv, rv);
3562 
3563   return NS_OK;
3564 }
3565 
3566 // static
UpdateIndexEntry(CacheFileHandle * aHandle,const uint32_t * aFrecency,const bool * aHasAltData,const uint16_t * aOnStartTime,const uint16_t * aOnStopTime,const uint8_t * aContentType)3567 nsresult CacheFileIOManager::UpdateIndexEntry(CacheFileHandle* aHandle,
3568                                               const uint32_t* aFrecency,
3569                                               const bool* aHasAltData,
3570                                               const uint16_t* aOnStartTime,
3571                                               const uint16_t* aOnStopTime,
3572                                               const uint8_t* aContentType) {
3573   LOG(
3574       ("CacheFileIOManager::UpdateIndexEntry() [handle=%p, frecency=%s, "
3575        "hasAltData=%s, onStartTime=%s, onStopTime=%s, contentType=%s]",
3576        aHandle, aFrecency ? nsPrintfCString("%u", *aFrecency).get() : "",
3577        aHasAltData ? (*aHasAltData ? "true" : "false") : "",
3578        aOnStartTime ? nsPrintfCString("%u", *aOnStartTime).get() : "",
3579        aOnStopTime ? nsPrintfCString("%u", *aOnStopTime).get() : "",
3580        aContentType ? nsPrintfCString("%u", *aContentType).get() : ""));
3581 
3582   nsresult rv;
3583   RefPtr<CacheFileIOManager> ioMan = gInstance;
3584 
3585   if (aHandle->IsClosed() || !ioMan) {
3586     return NS_ERROR_NOT_INITIALIZED;
3587   }
3588 
3589   if (aHandle->IsSpecialFile()) {
3590     return NS_ERROR_UNEXPECTED;
3591   }
3592 
3593   RefPtr<UpdateIndexEntryEvent> ev = new UpdateIndexEntryEvent(
3594       aHandle, aFrecency, aHasAltData, aOnStartTime, aOnStopTime, aContentType);
3595   rv = ioMan->mIOThread->Dispatch(ev, aHandle->mPriority
3596                                           ? CacheIOThread::WRITE_PRIORITY
3597                                           : CacheIOThread::WRITE);
3598   NS_ENSURE_SUCCESS(rv, rv);
3599 
3600   return NS_OK;
3601 }
3602 
CreateFile(CacheFileHandle * aHandle)3603 nsresult CacheFileIOManager::CreateFile(CacheFileHandle* aHandle) {
3604   MOZ_ASSERT(!aHandle->mFD);
3605   MOZ_ASSERT(aHandle->mFile);
3606 
3607   nsresult rv;
3608 
3609   if (aHandle->IsDoomed()) {
3610     nsCOMPtr<nsIFile> file;
3611 
3612     rv = GetDoomedFile(getter_AddRefs(file));
3613     NS_ENSURE_SUCCESS(rv, rv);
3614 
3615     aHandle->mFile.swap(file);
3616   } else {
3617     bool exists;
3618     if (NS_SUCCEEDED(aHandle->mFile->Exists(&exists)) && exists) {
3619       NS_WARNING("Found a file that should not exist!");
3620     }
3621   }
3622 
3623   rv = OpenNSPRHandle(aHandle, true);
3624   NS_ENSURE_SUCCESS(rv, rv);
3625 
3626   aHandle->mFileSize = 0;
3627   return NS_OK;
3628 }
3629 
3630 // static
HashToStr(const SHA1Sum::Hash * aHash,nsACString & _retval)3631 void CacheFileIOManager::HashToStr(const SHA1Sum::Hash* aHash,
3632                                    nsACString& _retval) {
3633   _retval.Truncate();
3634   const char hexChars[] = {'0', '1', '2', '3', '4', '5', '6', '7',
3635                            '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
3636   for (uint32_t i = 0; i < sizeof(SHA1Sum::Hash); i++) {
3637     _retval.Append(hexChars[(*aHash)[i] >> 4]);
3638     _retval.Append(hexChars[(*aHash)[i] & 0xF]);
3639   }
3640 }
3641 
3642 // static
StrToHash(const nsACString & aHash,SHA1Sum::Hash * _retval)3643 nsresult CacheFileIOManager::StrToHash(const nsACString& aHash,
3644                                        SHA1Sum::Hash* _retval) {
3645   if (aHash.Length() != 2 * sizeof(SHA1Sum::Hash)) {
3646     return NS_ERROR_INVALID_ARG;
3647   }
3648 
3649   for (uint32_t i = 0; i < aHash.Length(); i++) {
3650     uint8_t value;
3651 
3652     if (aHash[i] >= '0' && aHash[i] <= '9') {
3653       value = aHash[i] - '0';
3654     } else if (aHash[i] >= 'A' && aHash[i] <= 'F') {
3655       value = aHash[i] - 'A' + 10;
3656     } else if (aHash[i] >= 'a' && aHash[i] <= 'f') {
3657       value = aHash[i] - 'a' + 10;
3658     } else {
3659       return NS_ERROR_INVALID_ARG;
3660     }
3661 
3662     if (i % 2 == 0) {
3663       (reinterpret_cast<uint8_t*>(_retval))[i / 2] = value << 4;
3664     } else {
3665       (reinterpret_cast<uint8_t*>(_retval))[i / 2] += value;
3666     }
3667   }
3668 
3669   return NS_OK;
3670 }
3671 
GetFile(const SHA1Sum::Hash * aHash,nsIFile ** _retval)3672 nsresult CacheFileIOManager::GetFile(const SHA1Sum::Hash* aHash,
3673                                      nsIFile** _retval) {
3674   nsresult rv;
3675   nsCOMPtr<nsIFile> file;
3676   rv = mCacheDirectory->Clone(getter_AddRefs(file));
3677   NS_ENSURE_SUCCESS(rv, rv);
3678 
3679   rv = file->AppendNative(nsLiteralCString(ENTRIES_DIR));
3680   NS_ENSURE_SUCCESS(rv, rv);
3681 
3682   nsAutoCString leafName;
3683   HashToStr(aHash, leafName);
3684 
3685   rv = file->AppendNative(leafName);
3686   NS_ENSURE_SUCCESS(rv, rv);
3687 
3688   file.swap(*_retval);
3689   return NS_OK;
3690 }
3691 
GetSpecialFile(const nsACString & aKey,nsIFile ** _retval)3692 nsresult CacheFileIOManager::GetSpecialFile(const nsACString& aKey,
3693                                             nsIFile** _retval) {
3694   nsresult rv;
3695   nsCOMPtr<nsIFile> file;
3696   rv = mCacheDirectory->Clone(getter_AddRefs(file));
3697   NS_ENSURE_SUCCESS(rv, rv);
3698 
3699   rv = file->AppendNative(aKey);
3700   NS_ENSURE_SUCCESS(rv, rv);
3701 
3702   file.swap(*_retval);
3703   return NS_OK;
3704 }
3705 
GetDoomedFile(nsIFile ** _retval)3706 nsresult CacheFileIOManager::GetDoomedFile(nsIFile** _retval) {
3707   nsresult rv;
3708   nsCOMPtr<nsIFile> file;
3709   rv = mCacheDirectory->Clone(getter_AddRefs(file));
3710   NS_ENSURE_SUCCESS(rv, rv);
3711 
3712   rv = file->AppendNative(nsLiteralCString(DOOMED_DIR));
3713   NS_ENSURE_SUCCESS(rv, rv);
3714 
3715   rv = file->AppendNative("dummyleaf"_ns);
3716   NS_ENSURE_SUCCESS(rv, rv);
3717 
3718   const int32_t kMaxTries = 64;
3719   srand(static_cast<unsigned>(PR_Now()));
3720   nsAutoCString leafName;
3721   for (int32_t triesCount = 0;; ++triesCount) {
3722     leafName.AppendInt(rand());
3723     rv = file->SetNativeLeafName(leafName);
3724     NS_ENSURE_SUCCESS(rv, rv);
3725 
3726     bool exists;
3727     if (NS_SUCCEEDED(file->Exists(&exists)) && !exists) {
3728       break;
3729     }
3730 
3731     if (triesCount == kMaxTries) {
3732       LOG(
3733           ("CacheFileIOManager::GetDoomedFile() - Could not find unused file "
3734            "name in %d tries.",
3735            kMaxTries));
3736       return NS_ERROR_FAILURE;
3737     }
3738 
3739     leafName.Truncate();
3740   }
3741 
3742   file.swap(*_retval);
3743   return NS_OK;
3744 }
3745 
IsEmptyDirectory(nsIFile * aFile,bool * _retval)3746 nsresult CacheFileIOManager::IsEmptyDirectory(nsIFile* aFile, bool* _retval) {
3747   MOZ_ASSERT(mIOThread->IsCurrentThread());
3748 
3749   nsresult rv;
3750 
3751   nsCOMPtr<nsIDirectoryEnumerator> enumerator;
3752   rv = aFile->GetDirectoryEntries(getter_AddRefs(enumerator));
3753   NS_ENSURE_SUCCESS(rv, rv);
3754 
3755   bool hasMoreElements = false;
3756   rv = enumerator->HasMoreElements(&hasMoreElements);
3757   NS_ENSURE_SUCCESS(rv, rv);
3758 
3759   *_retval = !hasMoreElements;
3760   return NS_OK;
3761 }
3762 
CheckAndCreateDir(nsIFile * aFile,const char * aDir,bool aEnsureEmptyDir)3763 nsresult CacheFileIOManager::CheckAndCreateDir(nsIFile* aFile, const char* aDir,
3764                                                bool aEnsureEmptyDir) {
3765   nsresult rv;
3766 
3767   nsCOMPtr<nsIFile> file;
3768   if (!aDir) {
3769     file = aFile;
3770   } else {
3771     nsAutoCString dir(aDir);
3772     rv = aFile->Clone(getter_AddRefs(file));
3773     NS_ENSURE_SUCCESS(rv, rv);
3774     rv = file->AppendNative(dir);
3775     NS_ENSURE_SUCCESS(rv, rv);
3776   }
3777 
3778   bool exists = false;
3779   rv = file->Exists(&exists);
3780   if (NS_SUCCEEDED(rv) && exists) {
3781     bool isDirectory = false;
3782     rv = file->IsDirectory(&isDirectory);
3783     if (NS_FAILED(rv) || !isDirectory) {
3784       // Try to remove the file
3785       rv = file->Remove(false);
3786       if (NS_SUCCEEDED(rv)) {
3787         exists = false;
3788       }
3789     }
3790     NS_ENSURE_SUCCESS(rv, rv);
3791   }
3792 
3793   if (aEnsureEmptyDir && NS_SUCCEEDED(rv) && exists) {
3794     bool isEmpty;
3795     rv = IsEmptyDirectory(file, &isEmpty);
3796     NS_ENSURE_SUCCESS(rv, rv);
3797 
3798     if (!isEmpty) {
3799       // Don't check the result, if this fails, it's OK.  We do this
3800       // only for the doomed directory that doesn't need to be deleted
3801       // for the cost of completely disabling the whole browser.
3802       TrashDirectory(file);
3803     }
3804   }
3805 
3806   if (NS_SUCCEEDED(rv) && !exists) {
3807     rv = file->Create(nsIFile::DIRECTORY_TYPE, 0700);
3808   }
3809   if (NS_FAILED(rv)) {
3810     NS_WARNING("Cannot create directory");
3811     return NS_ERROR_FAILURE;
3812   }
3813 
3814   return NS_OK;
3815 }
3816 
CreateCacheTree()3817 nsresult CacheFileIOManager::CreateCacheTree() {
3818   MOZ_ASSERT(mIOThread->IsCurrentThread());
3819   MOZ_ASSERT(!mTreeCreated);
3820 
3821   if (!mCacheDirectory || mTreeCreationFailed) {
3822     return NS_ERROR_FILE_INVALID_PATH;
3823   }
3824 
3825   nsresult rv;
3826 
3827   // Set the flag here and clear it again below when the tree is created
3828   // successfully.
3829   mTreeCreationFailed = true;
3830 
3831   // ensure parent directory exists
3832   nsCOMPtr<nsIFile> parentDir;
3833   rv = mCacheDirectory->GetParent(getter_AddRefs(parentDir));
3834   NS_ENSURE_SUCCESS(rv, rv);
3835   rv = CheckAndCreateDir(parentDir, nullptr, false);
3836   NS_ENSURE_SUCCESS(rv, rv);
3837 
3838   // ensure cache directory exists
3839   rv = CheckAndCreateDir(mCacheDirectory, nullptr, false);
3840   NS_ENSURE_SUCCESS(rv, rv);
3841 
3842   // ensure entries directory exists
3843   rv = CheckAndCreateDir(mCacheDirectory, ENTRIES_DIR, false);
3844   NS_ENSURE_SUCCESS(rv, rv);
3845 
3846   // ensure doomed directory exists
3847   rv = CheckAndCreateDir(mCacheDirectory, DOOMED_DIR, true);
3848   NS_ENSURE_SUCCESS(rv, rv);
3849 
3850   mTreeCreated = true;
3851   mTreeCreationFailed = false;
3852 
3853   if (!mContextEvictor) {
3854     RefPtr<CacheFileContextEvictor> contextEvictor;
3855     contextEvictor = new CacheFileContextEvictor();
3856 
3857     // Init() method will try to load unfinished contexts from the disk. Store
3858     // the evictor as a member only when there is some unfinished job.
3859     contextEvictor->Init(mCacheDirectory);
3860     if (contextEvictor->ContextsCount()) {
3861       contextEvictor.swap(mContextEvictor);
3862     }
3863   }
3864 
3865   StartRemovingTrash();
3866 
3867   return NS_OK;
3868 }
3869 
OpenNSPRHandle(CacheFileHandle * aHandle,bool aCreate)3870 nsresult CacheFileIOManager::OpenNSPRHandle(CacheFileHandle* aHandle,
3871                                             bool aCreate) {
3872   LOG(("CacheFileIOManager::OpenNSPRHandle BEGIN, handle=%p", aHandle));
3873 
3874   MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
3875   MOZ_ASSERT(!aHandle->mFD);
3876   MOZ_ASSERT(mHandlesByLastUsed.IndexOf(aHandle) == mHandlesByLastUsed.NoIndex);
3877   MOZ_ASSERT(mHandlesByLastUsed.Length() <= kOpenHandlesLimit);
3878   MOZ_ASSERT((aCreate && !aHandle->mFileExists) ||
3879              (!aCreate && aHandle->mFileExists));
3880 
3881   nsresult rv;
3882 
3883   if (mHandlesByLastUsed.Length() == kOpenHandlesLimit) {
3884     // close handle that hasn't been used for the longest time
3885     rv = MaybeReleaseNSPRHandleInternal(mHandlesByLastUsed[0], true);
3886     NS_ENSURE_SUCCESS(rv, rv);
3887   }
3888 
3889   if (aCreate) {
3890     rv = aHandle->mFile->OpenNSPRFileDesc(
3891         PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE, 0600, &aHandle->mFD);
3892     if (rv == NS_ERROR_FILE_ALREADY_EXISTS ||   // error from nsLocalFileWin
3893         rv == NS_ERROR_FILE_NO_DEVICE_SPACE) {  // error from nsLocalFileUnix
3894       LOG(
3895           ("CacheFileIOManager::OpenNSPRHandle() - Cannot create a new file, we"
3896            " might reached a limit on FAT32. Will evict a single entry and try "
3897            "again. [hash=%08x%08x%08x%08x%08x]",
3898            LOGSHA1(aHandle->Hash())));
3899 
3900       SHA1Sum::Hash hash;
3901       uint32_t cnt;
3902 
3903       rv = CacheIndex::GetEntryForEviction(true, &hash, &cnt);
3904       if (NS_SUCCEEDED(rv)) {
3905         rv = DoomFileByKeyInternal(&hash);
3906       }
3907       if (NS_SUCCEEDED(rv)) {
3908         rv = aHandle->mFile->OpenNSPRFileDesc(
3909             PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE, 0600, &aHandle->mFD);
3910         LOG(
3911             ("CacheFileIOManager::OpenNSPRHandle() - Successfully evicted entry"
3912              " with hash %08x%08x%08x%08x%08x. %s to create the new file.",
3913              LOGSHA1(&hash), NS_SUCCEEDED(rv) ? "Succeeded" : "Failed"));
3914 
3915         // Report the full size only once per session
3916         static bool sSizeReported = false;
3917         if (!sSizeReported) {
3918           uint32_t cacheUsage;
3919           if (NS_SUCCEEDED(CacheIndex::GetCacheSize(&cacheUsage))) {
3920             cacheUsage >>= 10;
3921             Telemetry::Accumulate(Telemetry::NETWORK_CACHE_SIZE_FULL_FAT,
3922                                   cacheUsage);
3923             sSizeReported = true;
3924           }
3925         }
3926       } else {
3927         LOG(
3928             ("CacheFileIOManager::OpenNSPRHandle() - Couldn't evict an existing"
3929              " entry."));
3930         rv = NS_ERROR_FILE_NO_DEVICE_SPACE;
3931       }
3932     }
3933     if (NS_FAILED(rv)) {
3934       LOG(
3935           ("CacheFileIOManager::OpenNSPRHandle() Create failed with "
3936            "0x%08" PRIx32,
3937            static_cast<uint32_t>(rv)));
3938     }
3939     NS_ENSURE_SUCCESS(rv, rv);
3940 
3941     aHandle->mFileExists = true;
3942   } else {
3943     rv = aHandle->mFile->OpenNSPRFileDesc(PR_RDWR, 0600, &aHandle->mFD);
3944     if (NS_ERROR_FILE_NOT_FOUND == rv) {
3945       LOG(("  file doesn't exists"));
3946       aHandle->mFileExists = false;
3947       return DoomFileInternal(aHandle);
3948     }
3949     if (NS_FAILED(rv)) {
3950       LOG(("CacheFileIOManager::OpenNSPRHandle() Open failed with 0x%08" PRIx32,
3951            static_cast<uint32_t>(rv)));
3952     }
3953     NS_ENSURE_SUCCESS(rv, rv);
3954   }
3955 
3956   mHandlesByLastUsed.AppendElement(aHandle);
3957 
3958   LOG(("CacheFileIOManager::OpenNSPRHandle END, handle=%p", aHandle));
3959 
3960   return NS_OK;
3961 }
3962 
NSPRHandleUsed(CacheFileHandle * aHandle)3963 void CacheFileIOManager::NSPRHandleUsed(CacheFileHandle* aHandle) {
3964   MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
3965   MOZ_ASSERT(aHandle->mFD);
3966 
3967   DebugOnly<bool> found{};
3968   found = mHandlesByLastUsed.RemoveElement(aHandle);
3969   MOZ_ASSERT(found);
3970 
3971   mHandlesByLastUsed.AppendElement(aHandle);
3972 }
3973 
SyncRemoveDir(nsIFile * aFile,const char * aDir)3974 nsresult CacheFileIOManager::SyncRemoveDir(nsIFile* aFile, const char* aDir) {
3975   nsresult rv;
3976   nsCOMPtr<nsIFile> file;
3977 
3978   if (!aDir) {
3979     file = aFile;
3980   } else {
3981     rv = aFile->Clone(getter_AddRefs(file));
3982     if (NS_WARN_IF(NS_FAILED(rv))) {
3983       return rv;
3984     }
3985 
3986     rv = file->AppendNative(nsDependentCString(aDir));
3987     if (NS_WARN_IF(NS_FAILED(rv))) {
3988       return rv;
3989     }
3990   }
3991 
3992   if (LOG_ENABLED()) {
3993     LOG(("CacheFileIOManager::SyncRemoveDir() - Removing directory %s",
3994          file->HumanReadablePath().get()));
3995   }
3996 
3997   rv = file->Remove(true);
3998   if (NS_WARN_IF(NS_FAILED(rv))) {
3999     LOG(
4000         ("CacheFileIOManager::SyncRemoveDir() - Removing failed! "
4001          "[rv=0x%08" PRIx32 "]",
4002          static_cast<uint32_t>(rv)));
4003   }
4004 
4005   return rv;
4006 }
4007 
SyncRemoveAllCacheFiles()4008 void CacheFileIOManager::SyncRemoveAllCacheFiles() {
4009   LOG(("CacheFileIOManager::SyncRemoveAllCacheFiles()"));
4010 
4011   nsresult rv;
4012 
4013   SyncRemoveDir(mCacheDirectory, ENTRIES_DIR);
4014   SyncRemoveDir(mCacheDirectory, DOOMED_DIR);
4015 
4016   // Clear any intermediate state of trash dir enumeration.
4017   mFailedTrashDirs.Clear();
4018   mTrashDir = nullptr;
4019 
4020   while (true) {
4021     // FindTrashDirToRemove() fills mTrashDir if there is any trash directory.
4022     rv = FindTrashDirToRemove();
4023     if (rv == NS_ERROR_NOT_AVAILABLE) {
4024       LOG(
4025           ("CacheFileIOManager::SyncRemoveAllCacheFiles() - No trash directory "
4026            "found."));
4027       break;
4028     }
4029     if (NS_WARN_IF(NS_FAILED(rv))) {
4030       LOG(
4031           ("CacheFileIOManager::SyncRemoveAllCacheFiles() - "
4032            "FindTrashDirToRemove() returned an unexpected error. "
4033            "[rv=0x%08" PRIx32 "]",
4034            static_cast<uint32_t>(rv)));
4035       break;
4036     }
4037 
4038     rv = SyncRemoveDir(mTrashDir, nullptr);
4039     if (NS_FAILED(rv)) {
4040       nsAutoCString leafName;
4041       mTrashDir->GetNativeLeafName(leafName);
4042       mFailedTrashDirs.AppendElement(leafName);
4043     }
4044   }
4045 }
4046 
4047 // Returns default ("smart") size (in KB) of cache, given available disk space
4048 // (also in KB)
SmartCacheSize(const int64_t availKB)4049 static uint32_t SmartCacheSize(const int64_t availKB) {
4050   uint32_t maxSize;
4051 
4052   if (CacheObserver::ClearCacheOnShutdown()) {
4053     maxSize = kMaxClearOnShutdownCacheSizeKB;
4054   } else {
4055     maxSize = kMaxCacheSizeKB;
4056   }
4057 
4058   if (availKB > 25 * 1024 * 1024) {
4059     return maxSize;  // skip computing if we're over 25 GB
4060   }
4061 
4062   // Grow/shrink in 10 MB units, deliberately, so that in the common case we
4063   // don't shrink cache and evict items every time we startup (it's important
4064   // that we don't slow down startup benchmarks).
4065   uint32_t sz10MBs = 0;
4066   uint32_t avail10MBs = availKB / (1024 * 10);
4067 
4068   // 2.5% of space above 7GB
4069   if (avail10MBs > 700) {
4070     sz10MBs += static_cast<uint32_t>((avail10MBs - 700) * .025);
4071     avail10MBs = 700;
4072   }
4073   // 7.5% of space between 500 MB -> 7 GB
4074   if (avail10MBs > 50) {
4075     sz10MBs += static_cast<uint32_t>((avail10MBs - 50) * .075);
4076     avail10MBs = 50;
4077   }
4078 
4079 #ifdef ANDROID
4080   // On Android, smaller/older devices may have very little storage and
4081   // device owners may be sensitive to storage footprint: Use a smaller
4082   // percentage of available space and a smaller minimum.
4083 
4084   // 16% of space up to 500 MB (10 MB min)
4085   sz10MBs += std::max<uint32_t>(1, static_cast<uint32_t>(avail10MBs * .16));
4086 #else
4087   // 30% of space up to 500 MB (50 MB min)
4088   sz10MBs += std::max<uint32_t>(5, static_cast<uint32_t>(avail10MBs * .3));
4089 #endif
4090 
4091   return std::min<uint32_t>(maxSize, sz10MBs * 10 * 1024);
4092 }
4093 
UpdateSmartCacheSize(int64_t aFreeSpace)4094 nsresult CacheFileIOManager::UpdateSmartCacheSize(int64_t aFreeSpace) {
4095   MOZ_ASSERT(mIOThread->IsCurrentThread());
4096 
4097   nsresult rv;
4098 
4099   if (!CacheObserver::SmartCacheSizeEnabled()) {
4100     return NS_ERROR_NOT_AVAILABLE;
4101   }
4102 
4103   // Wait at least kSmartSizeUpdateInterval before recomputing smart size.
4104   static const TimeDuration kUpdateLimit =
4105       TimeDuration::FromMilliseconds(kSmartSizeUpdateInterval);
4106   if (!mLastSmartSizeTime.IsNull() &&
4107       (TimeStamp::NowLoRes() - mLastSmartSizeTime) < kUpdateLimit) {
4108     return NS_OK;
4109   }
4110 
4111   // Do not compute smart size when cache size is not reliable.
4112   bool isUpToDate = false;
4113   CacheIndex::IsUpToDate(&isUpToDate);
4114   if (!isUpToDate) {
4115     return NS_ERROR_NOT_AVAILABLE;
4116   }
4117 
4118   uint32_t cacheUsage;
4119   rv = CacheIndex::GetCacheSize(&cacheUsage);
4120   if (NS_WARN_IF(NS_FAILED(rv))) {
4121     LOG(
4122         ("CacheFileIOManager::UpdateSmartCacheSize() - Cannot get cacheUsage! "
4123          "[rv=0x%08" PRIx32 "]",
4124          static_cast<uint32_t>(rv)));
4125     return rv;
4126   }
4127 
4128   mLastSmartSizeTime = TimeStamp::NowLoRes();
4129 
4130   uint32_t smartSize = SmartCacheSize(aFreeSpace + cacheUsage);
4131 
4132   if (smartSize == CacheObserver::DiskCacheCapacity()) {
4133     // Smart size has not changed.
4134     return NS_OK;
4135   }
4136 
4137   CacheObserver::SetSmartDiskCacheCapacity(smartSize);
4138 
4139   return NS_OK;
4140 }
4141 
4142 // Memory reporting
4143 
4144 namespace {
4145 
4146 // A helper class that dispatches and waits for an event that gets result of
4147 // CacheFileIOManager->mHandles.SizeOfExcludingThis() on the I/O thread
4148 // to safely get handles memory report.
4149 // We must do this, since the handle list is only accessed and managed w/o
4150 // locking on the I/O thread.  That is by design.
4151 class SizeOfHandlesRunnable : public Runnable {
4152  public:
SizeOfHandlesRunnable(mozilla::MallocSizeOf mallocSizeOf,CacheFileHandles const & handles,nsTArray<CacheFileHandle * > const & specialHandles)4153   SizeOfHandlesRunnable(mozilla::MallocSizeOf mallocSizeOf,
4154                         CacheFileHandles const& handles,
4155                         nsTArray<CacheFileHandle*> const& specialHandles)
4156       : Runnable("net::SizeOfHandlesRunnable"),
4157         mMonitor("SizeOfHandlesRunnable.mMonitor"),
4158         mMonitorNotified(false),
4159         mMallocSizeOf(mallocSizeOf),
4160         mHandles(handles),
4161         mSpecialHandles(specialHandles),
4162         mSize(0) {}
4163 
Get(CacheIOThread * thread)4164   size_t Get(CacheIOThread* thread) {
4165     nsCOMPtr<nsIEventTarget> target = thread->Target();
4166     if (!target) {
4167       NS_ERROR("If we have the I/O thread we also must have the I/O target");
4168       return 0;
4169     }
4170 
4171     mozilla::MonitorAutoLock mon(mMonitor);
4172     mMonitorNotified = false;
4173     nsresult rv = target->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL);
4174     if (NS_FAILED(rv)) {
4175       NS_ERROR("Dispatch failed, cannot do memory report of CacheFileHandles");
4176       return 0;
4177     }
4178 
4179     while (!mMonitorNotified) {
4180       mon.Wait();
4181     }
4182     return mSize;
4183   }
4184 
Run()4185   NS_IMETHOD Run() override {
4186     mozilla::MonitorAutoLock mon(mMonitor);
4187     // Excluding this since the object itself is a member of CacheFileIOManager
4188     // reported in CacheFileIOManager::SizeOfIncludingThis as part of |this|.
4189     mSize = mHandles.SizeOfExcludingThis(mMallocSizeOf);
4190     for (uint32_t i = 0; i < mSpecialHandles.Length(); ++i) {
4191       mSize += mSpecialHandles[i]->SizeOfIncludingThis(mMallocSizeOf);
4192     }
4193 
4194     mMonitorNotified = true;
4195     mon.Notify();
4196     return NS_OK;
4197   }
4198 
4199  private:
4200   mozilla::Monitor mMonitor;
4201   bool mMonitorNotified;
4202   mozilla::MallocSizeOf mMallocSizeOf;
4203   CacheFileHandles const& mHandles;
4204   nsTArray<CacheFileHandle*> const& mSpecialHandles;
4205   size_t mSize;
4206 };
4207 
4208 }  // namespace
4209 
SizeOfExcludingThisInternal(mozilla::MallocSizeOf mallocSizeOf) const4210 size_t CacheFileIOManager::SizeOfExcludingThisInternal(
4211     mozilla::MallocSizeOf mallocSizeOf) const {
4212   size_t n = 0;
4213   nsCOMPtr<nsISizeOf> sizeOf;
4214 
4215   if (mIOThread) {
4216     n += mIOThread->SizeOfIncludingThis(mallocSizeOf);
4217 
4218     // mHandles and mSpecialHandles must be accessed only on the I/O thread,
4219     // must sync dispatch.
4220     RefPtr<SizeOfHandlesRunnable> sizeOfHandlesRunnable =
4221         new SizeOfHandlesRunnable(mallocSizeOf, mHandles, mSpecialHandles);
4222     n += sizeOfHandlesRunnable->Get(mIOThread);
4223   }
4224 
4225   // mHandlesByLastUsed just refers handles reported by mHandles.
4226 
4227   sizeOf = do_QueryInterface(mCacheDirectory);
4228   if (sizeOf) n += sizeOf->SizeOfIncludingThis(mallocSizeOf);
4229 
4230   sizeOf = do_QueryInterface(mMetadataWritesTimer);
4231   if (sizeOf) n += sizeOf->SizeOfIncludingThis(mallocSizeOf);
4232 
4233   sizeOf = do_QueryInterface(mTrashTimer);
4234   if (sizeOf) n += sizeOf->SizeOfIncludingThis(mallocSizeOf);
4235 
4236   sizeOf = do_QueryInterface(mTrashDir);
4237   if (sizeOf) n += sizeOf->SizeOfIncludingThis(mallocSizeOf);
4238 
4239   for (uint32_t i = 0; i < mFailedTrashDirs.Length(); ++i) {
4240     n += mFailedTrashDirs[i].SizeOfExcludingThisIfUnshared(mallocSizeOf);
4241   }
4242 
4243   return n;
4244 }
4245 
4246 // static
SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf)4247 size_t CacheFileIOManager::SizeOfExcludingThis(
4248     mozilla::MallocSizeOf mallocSizeOf) {
4249   if (!gInstance) return 0;
4250 
4251   return gInstance->SizeOfExcludingThisInternal(mallocSizeOf);
4252 }
4253 
4254 // static
SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf)4255 size_t CacheFileIOManager::SizeOfIncludingThis(
4256     mozilla::MallocSizeOf mallocSizeOf) {
4257   return mallocSizeOf(gInstance) + SizeOfExcludingThis(mallocSizeOf);
4258 }
4259 
4260 }  // namespace mozilla::net
4261