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