Coverage Report

Created: 2018-09-25 14:53

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