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