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