Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/security/manager/ssl/DataStorage.cpp
Line
Count
Source (jump to first uncovered line)
1
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
3
/* This Source Code Form is subject to the terms of the Mozilla Public
4
 * License, v. 2.0. If a copy of the MPL was not distributed with this
5
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7
#include "DataStorage.h"
8
9
#include "mozilla/Assertions.h"
10
#include "mozilla/ClearOnShutdown.h"
11
#include "mozilla/dom/PContent.h"
12
#include "mozilla/dom/ContentChild.h"
13
#include "mozilla/dom/ContentParent.h"
14
#include "mozilla/Preferences.h"
15
#include "mozilla/Services.h"
16
#include "mozilla/StaticMutex.h"
17
#include "mozilla/Telemetry.h"
18
#include "mozilla/Unused.h"
19
#include "nsAppDirectoryServiceDefs.h"
20
#include "nsDirectoryServiceUtils.h"
21
#include "nsIMemoryReporter.h"
22
#include "nsIObserverService.h"
23
#include "nsITimer.h"
24
#include "nsIThread.h"
25
#include "nsNetUtil.h"
26
#include "nsPrintfCString.h"
27
#include "nsStreamUtils.h"
28
#include "nsThreadUtils.h"
29
30
// NB: Read DataStorage.h first.
31
32
// The default time between data changing and a write, in milliseconds.
33
static const uint32_t sDataStorageDefaultTimerDelay = 5u * 60u * 1000u;
34
// The maximum score an entry can have (prevents overflow)
35
static const uint32_t sMaxScore = UINT32_MAX;
36
// The maximum number of entries per type of data (limits resource use)
37
static const uint32_t sMaxDataEntries = 1024;
38
static const int64_t sOneDayInMicroseconds = int64_t(24 * 60 * 60) *
39
                                             PR_USEC_PER_SEC;
40
41
namespace {
42
43
// DataStorageSharedThread provides one shared thread that every DataStorage
44
// instance can use to do background work (reading/writing files and scheduling
45
// timers). This means we don't have to have one thread per DataStorage
46
// instance. The shared thread is initialized when the first DataStorage
47
// instance is initialized (Initialize is idempotent, so it's safe to call
48
// multiple times in any case).
49
// When Gecko shuts down, it will send a "profile-change-teardown" notification
50
// followed by "profile-before-change". As a result of the first event, all
51
// DataStorage instances will dispatch an event to write out their backing data.
52
// As a result of the second event, the shared thread will be shut down, which
53
// ensures that these events actually run (this has to happen in two phases to
54
// ensure that all DataStorage instances get a chance to dispatch their event
55
// before the background thread gets shut down) (again Shutdown is idempotent,
56
// so it's safe to call multiple times).
57
// In some cases (e.g. xpcshell), no profile notifications will be sent, so
58
// instead we rely on the notifications "xpcom-shutdown" and
59
// "xpcom-shutdown-threads", respectively.
60
class DataStorageSharedThread final
61
{
62
public:
63
  static nsresult Initialize();
64
  static nsresult Shutdown();
65
  static nsresult Dispatch(nsIRunnable* event);
66
67
private:
68
  DataStorageSharedThread()
69
    : mThread(nullptr)
70
0
  { }
71
72
  virtual ~DataStorageSharedThread()
73
0
  { }
74
75
  nsCOMPtr<nsIThread> mThread;
76
};
77
78
StaticMutex sDataStorageSharedThreadMutex;
79
static DataStorageSharedThread* gDataStorageSharedThread;
80
static bool gDataStorageSharedThreadShutDown = false;
81
82
nsresult
83
DataStorageSharedThread::Initialize()
84
0
{
85
0
  MOZ_ASSERT(XRE_IsParentProcess());
86
0
  StaticMutexAutoLock lock(sDataStorageSharedThreadMutex);
87
0
88
0
  // If this happens, we initialized a DataStorage after shutdown notifications
89
0
  // were sent, so don't re-initialize the shared thread.
90
0
  if (gDataStorageSharedThreadShutDown) {
91
0
    return NS_ERROR_FAILURE;
92
0
  }
93
0
94
0
  if (!gDataStorageSharedThread) {
95
0
    gDataStorageSharedThread = new DataStorageSharedThread();
96
0
    nsresult rv = NS_NewNamedThread("DataStorage",
97
0
      getter_AddRefs(gDataStorageSharedThread->mThread));
98
0
    if (NS_FAILED(rv)) {
99
0
      gDataStorageSharedThread = nullptr;
100
0
      return rv;
101
0
    }
102
0
  }
103
0
104
0
  return NS_OK;
105
0
}
106
107
nsresult
108
DataStorageSharedThread::Shutdown()
109
0
{
110
0
  MOZ_ASSERT(XRE_IsParentProcess());
111
0
  StaticMutexAutoLock lock(sDataStorageSharedThreadMutex);
112
0
113
0
  if (!gDataStorageSharedThread) {
114
0
    return NS_OK;
115
0
  }
116
0
117
0
  MOZ_ASSERT(gDataStorageSharedThread->mThread);
118
0
  if (!gDataStorageSharedThread->mThread) {
119
0
    return NS_ERROR_FAILURE;
120
0
  }
121
0
122
0
  nsresult rv = gDataStorageSharedThread->mThread->Shutdown();
123
0
  gDataStorageSharedThread->mThread = nullptr;
124
0
  gDataStorageSharedThreadShutDown = true;
125
0
  delete gDataStorageSharedThread;
126
0
  gDataStorageSharedThread = nullptr;
127
0
128
0
  return rv;
129
0
}
130
131
nsresult
132
DataStorageSharedThread::Dispatch(nsIRunnable* event)
133
0
{
134
0
  MOZ_ASSERT(XRE_IsParentProcess());
135
0
  StaticMutexAutoLock lock(sDataStorageSharedThreadMutex);
136
0
  if (!gDataStorageSharedThread || !gDataStorageSharedThread->mThread) {
137
0
    return NS_ERROR_FAILURE;
138
0
  }
139
0
  return gDataStorageSharedThread->mThread->Dispatch(event, NS_DISPATCH_NORMAL);
140
0
}
141
142
} // unnamed namespace
143
144
namespace mozilla {
145
146
class DataStorageMemoryReporter final : public nsIMemoryReporter
147
{
148
  MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf)
149
  ~DataStorageMemoryReporter() = default;
150
151
public:
152
  NS_DECL_ISUPPORTS
153
154
  NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
155
                            nsISupports* aData, bool aAnonymize) final
156
0
  {
157
0
    nsTArray<nsString> fileNames;
158
0
    DataStorage::GetAllFileNames(fileNames);
159
0
    for (const auto& file: fileNames) {
160
0
      RefPtr<DataStorage> ds = DataStorage::GetFromRawFileName(file);
161
0
      size_t amount = ds->SizeOfIncludingThis(MallocSizeOf);
162
0
      nsPrintfCString path("explicit/data-storage/%s",
163
0
                           NS_ConvertUTF16toUTF8(file).get());
164
0
      Unused << aHandleReport->Callback(EmptyCString(), path, KIND_HEAP,
165
0
        UNITS_BYTES, amount,
166
0
        NS_LITERAL_CSTRING("Memory used by PSM data storage cache."),
167
0
        aData);
168
0
    }
169
0
    return NS_OK;
170
0
  }
171
};
172
173
NS_IMPL_ISUPPORTS(DataStorageMemoryReporter, nsIMemoryReporter)
174
175
NS_IMPL_ISUPPORTS(DataStorage, nsIObserver)
176
177
StaticAutoPtr<DataStorage::DataStorages> DataStorage::sDataStorages;
178
179
DataStorage::DataStorage(const nsString& aFilename)
180
  : mMutex("DataStorage::mMutex")
181
  , mTimerDelay(sDataStorageDefaultTimerDelay)
182
  , mPendingWrite(false)
183
  , mShuttingDown(false)
184
  , mInitCalled(false)
185
  , mReadyMonitor("DataStorage::mReadyMonitor")
186
  , mReady(false)
187
  , mFilename(aFilename)
188
0
{
189
0
}
190
191
DataStorage::~DataStorage()
192
0
{
193
0
  Preferences::UnregisterCallback(PREF_CHANGE_METHOD(DataStorage::PrefChanged),
194
0
                                  "test.datastorage.write_timer_ms",
195
0
                                  this);
196
0
}
197
198
// static
199
already_AddRefed<DataStorage>
200
DataStorage::Get(DataStorageClass aFilename)
201
0
{
202
0
  switch (aFilename) {
203
0
#define DATA_STORAGE(_)         \
204
0
    case DataStorageClass::_:   \
205
0
      return GetFromRawFileName(NS_LITERAL_STRING(#_ ".txt"));
206
0
#include "mozilla/DataStorageList.h"
207
0
#undef DATA_STORAGE
208
0
    default:
209
0
      MOZ_ASSERT_UNREACHABLE("Invalid DataStorage type passed?");
210
0
      return nullptr;
211
0
  }
212
0
}
213
214
// static
215
already_AddRefed<DataStorage>
216
DataStorage::GetFromRawFileName(const nsString& aFilename)
217
0
{
218
0
  MOZ_ASSERT(NS_IsMainThread());
219
0
  if (!sDataStorages) {
220
0
    sDataStorages = new DataStorages();
221
0
    ClearOnShutdown(&sDataStorages);
222
0
  }
223
0
  RefPtr<DataStorage> storage;
224
0
  if (!sDataStorages->Get(aFilename, getter_AddRefs(storage))) {
225
0
    storage = new DataStorage(aFilename);
226
0
    sDataStorages->Put(aFilename, storage);
227
0
  }
228
0
  return storage.forget();
229
0
}
230
231
// static
232
already_AddRefed<DataStorage>
233
DataStorage::GetIfExists(DataStorageClass aFilename)
234
0
{
235
0
  MOZ_ASSERT(NS_IsMainThread());
236
0
  if (!sDataStorages) {
237
0
    sDataStorages = new DataStorages();
238
0
  }
239
0
  nsString name;
240
0
  switch (aFilename) {
241
0
#define DATA_STORAGE(_)              \
242
0
    case DataStorageClass::_:        \
243
0
      name.AssignLiteral(#_ ".txt"); \
244
0
      break;
245
0
#include "mozilla/DataStorageList.h"
246
0
#undef DATA_STORAGE
247
0
    default:
248
0
      MOZ_ASSERT_UNREACHABLE("Invalid DataStorages type passed?");
249
0
  }
250
0
  RefPtr<DataStorage> storage;
251
0
  if (!name.IsEmpty()) {
252
0
    sDataStorages->Get(name, getter_AddRefs(storage));
253
0
  }
254
0
  return storage.forget();
255
0
}
256
257
// static
258
void
259
DataStorage::GetAllFileNames(nsTArray<nsString>& aItems)
260
0
{
261
0
  MOZ_ASSERT(NS_IsMainThread());
262
0
  if (!sDataStorages) {
263
0
    return;
264
0
  }
265
0
#define DATA_STORAGE(_)     \
266
0
  aItems.AppendElement(NS_LITERAL_STRING(#_ ".txt"));
267
0
#include "mozilla/DataStorageList.h"
268
0
#undef DATA_STORAGE
269
0
}
270
271
// static
272
void
273
DataStorage::GetAllChildProcessData(
274
  nsTArray<mozilla::dom::DataStorageEntry>& aEntries)
275
0
{
276
0
  nsTArray<nsString> storageFiles;
277
0
  GetAllFileNames(storageFiles);
278
0
  for (auto& file : storageFiles) {
279
0
    dom::DataStorageEntry entry;
280
0
    entry.filename() = file;
281
0
    RefPtr<DataStorage> storage = DataStorage::GetFromRawFileName(file);
282
0
    if (!storage->mInitCalled) {
283
0
      // Perhaps no consumer has initialized the DataStorage object yet,
284
0
      // so do that now!
285
0
      bool dataWillPersist = false;
286
0
      nsresult rv = storage->Init(dataWillPersist);
287
0
      if (NS_WARN_IF(NS_FAILED(rv))) {
288
0
        return;
289
0
      }
290
0
    }
291
0
    storage->GetAll(&entry.items());
292
0
    aEntries.AppendElement(std::move(entry));
293
0
  }
294
0
}
295
296
// static
297
void
298
DataStorage::SetCachedStorageEntries(
299
  const InfallibleTArray<mozilla::dom::DataStorageEntry>& aEntries)
300
0
{
301
0
  MOZ_ASSERT(XRE_IsContentProcess());
302
0
303
0
  // Make sure to initialize all DataStorage classes.
304
0
  // For each one, we look through the list of our entries and if we find
305
0
  // a matching DataStorage object, we initialize it.
306
0
  //
307
0
  // Note that this is an O(n^2) operation, but the n here is very small
308
0
  // (currently 3).  There is a comment in the DataStorageList.h header
309
0
  // about updating the algorithm here to something more fancy if the list
310
0
  // of DataStorage items grows some day.
311
0
  nsTArray<dom::DataStorageEntry> entries;
312
0
#define DATA_STORAGE(_)                              \
313
0
  {                                                  \
314
0
    dom::DataStorageEntry entry;                     \
315
0
    entry.filename() = NS_LITERAL_STRING(#_ ".txt"); \
316
0
    for (auto& e : aEntries) {                       \
317
0
      if (entry.filename().Equals(e.filename())) {   \
318
0
        entry.items() = std::move(e.items());        \
319
0
        break;                                       \
320
0
      }                                              \
321
0
    }                                                \
322
0
    entries.AppendElement(std::move(entry));         \
323
0
  }
324
0
#include "mozilla/DataStorageList.h"
325
0
#undef DATA_STORAGE
326
0
327
0
  for (auto& entry : entries) {
328
0
    RefPtr<DataStorage> storage =
329
0
      DataStorage::GetFromRawFileName(entry.filename());
330
0
    bool dataWillPersist = false;
331
0
    storage->Init(dataWillPersist, &entry.items());
332
0
  }
333
0
}
334
335
size_t
336
DataStorage::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
337
0
{
338
0
  size_t sizeOfExcludingThis =
339
0
    mPersistentDataTable.ShallowSizeOfExcludingThis(aMallocSizeOf) +
340
0
    mTemporaryDataTable.ShallowSizeOfExcludingThis(aMallocSizeOf) +
341
0
    mPrivateDataTable.ShallowSizeOfExcludingThis(aMallocSizeOf) +
342
0
    mFilename.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
343
0
  return aMallocSizeOf(this) + sizeOfExcludingThis;
344
0
}
345
346
nsresult
347
DataStorage::Init(bool& aDataWillPersist,
348
                  const InfallibleTArray<mozilla::dom::DataStorageItem>* aItems)
349
0
{
350
0
  // Don't access the observer service or preferences off the main thread.
351
0
  if (!NS_IsMainThread()) {
352
0
    MOZ_ASSERT_UNREACHABLE("DataStorage::Init called off main thread");
353
0
    return NS_ERROR_NOT_SAME_THREAD;
354
0
  }
355
0
356
0
  MutexAutoLock lock(mMutex);
357
0
358
0
  // Ignore attempts to initialize several times.
359
0
  if (mInitCalled) {
360
0
    return NS_OK;
361
0
  }
362
0
363
0
  mInitCalled = true;
364
0
365
0
  static bool memoryReporterRegistered = false;
366
0
  if (!memoryReporterRegistered) {
367
0
    nsresult rv =
368
0
      RegisterStrongMemoryReporter(new DataStorageMemoryReporter());
369
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
370
0
      return rv;
371
0
    }
372
0
    memoryReporterRegistered = true;
373
0
  }
374
0
375
0
  nsresult rv;
376
0
  if (XRE_IsParentProcess()) {
377
0
    MOZ_ASSERT(!aItems);
378
0
379
0
    rv = DataStorageSharedThread::Initialize();
380
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
381
0
      return rv;
382
0
    }
383
0
384
0
    rv = AsyncReadData(aDataWillPersist, lock);
385
0
    if (NS_FAILED(rv)) {
386
0
      return rv;
387
0
    }
388
0
  } else {
389
0
    // In the child process, we use the data passed to us by the parent process
390
0
    // to initialize.
391
0
    MOZ_ASSERT(XRE_IsContentProcess());
392
0
    MOZ_ASSERT(aItems);
393
0
394
0
    aDataWillPersist = false;
395
0
    for (auto& item : *aItems) {
396
0
      Entry entry;
397
0
      entry.mValue = item.value();
398
0
      rv = PutInternal(item.key(), entry, item.type(), lock);
399
0
      if (NS_FAILED(rv)) {
400
0
        return rv;
401
0
      }
402
0
    }
403
0
    mReady = true;
404
0
    NotifyObservers("data-storage-ready");
405
0
  }
406
0
407
0
  nsCOMPtr<nsIObserverService> os = services::GetObserverService();
408
0
  if (NS_WARN_IF(!os)) {
409
0
    return NS_ERROR_FAILURE;
410
0
  }
411
0
  // Clear private data as appropriate.
412
0
  os->AddObserver(this, "last-pb-context-exited", false);
413
0
  // Observe shutdown; save data and prevent any further writes.
414
0
  // In the parent process, we need to write to the profile directory, so
415
0
  // we should listen for profile-change-teardown and profile-before-change so
416
0
  // that we can safely write to the profile. In the content process however we
417
0
  // don't have access to the profile directory and profile notifications are
418
0
  // not dispatched, so we need to clean up on xpcom-shutdown.
419
0
  // Note that because all DataStorage instances share one background thread, we
420
0
  // have to perform this shutdown in two stages. In the first stage
421
0
  // ("profile-change-teardown"), all instances dispatch their write events. In
422
0
  // the second stage ("profile-before-change"), the shared thread completes
423
0
  // these events and shuts down.
424
0
  if (XRE_IsParentProcess()) {
425
0
    os->AddObserver(this, "profile-change-teardown", false);
426
0
    os->AddObserver(this, "profile-before-change", false);
427
0
  }
428
0
  // In the Parent process, this is a backstop for xpcshell and other cases
429
0
  // where profile-before-change might not get sent.
430
0
  os->AddObserver(this, "xpcom-shutdown", false);
431
0
  os->AddObserver(this, "xpcom-shutdown-threads", false);
432
0
433
0
  // For test purposes, we can set the write timer to be very fast.
434
0
  mTimerDelay = Preferences::GetInt("test.datastorage.write_timer_ms",
435
0
                                    sDataStorageDefaultTimerDelay);
436
0
  Preferences::RegisterCallback(PREF_CHANGE_METHOD(DataStorage::PrefChanged),
437
0
                                "test.datastorage.write_timer_ms",
438
0
                                this);
439
0
440
0
  return NS_OK;
441
0
}
442
443
class DataStorage::Reader : public Runnable
444
{
445
public:
446
  explicit Reader(DataStorage* aDataStorage)
447
    : Runnable("DataStorage::Reader")
448
    , mDataStorage(aDataStorage)
449
0
  {
450
0
  }
451
  ~Reader();
452
453
private:
454
  NS_DECL_NSIRUNNABLE
455
456
  static nsresult ParseLine(nsDependentCSubstring& aLine, nsCString& aKeyOut,
457
                            Entry& aEntryOut);
458
459
  RefPtr<DataStorage> mDataStorage;
460
};
461
462
DataStorage::Reader::~Reader()
463
0
{
464
0
  // Notify that calls to Get can proceed.
465
0
  {
466
0
    MonitorAutoLock readyLock(mDataStorage->mReadyMonitor);
467
0
    mDataStorage->mReady = true;
468
0
    nsresult rv = mDataStorage->mReadyMonitor.NotifyAll();
469
0
    Unused << NS_WARN_IF(NS_FAILED(rv));
470
0
  }
471
0
472
0
  // This is for tests.
473
0
  nsCOMPtr<nsIRunnable> job =
474
0
    NewRunnableMethod<const char*>("DataStorage::NotifyObservers",
475
0
                                   mDataStorage,
476
0
                                   &DataStorage::NotifyObservers,
477
0
                                   "data-storage-ready");
478
0
  nsresult rv = NS_DispatchToMainThread(job, NS_DISPATCH_NORMAL);
479
0
  Unused << NS_WARN_IF(NS_FAILED(rv));
480
0
}
481
482
NS_IMETHODIMP
483
DataStorage::Reader::Run()
484
0
{
485
0
  nsresult rv;
486
0
  // Concurrent operations on nsIFile objects are not guaranteed to be safe,
487
0
  // so we clone the file while holding the lock and then release the lock.
488
0
  // At that point, we can safely operate on the clone.
489
0
  nsCOMPtr<nsIFile> file;
490
0
  {
491
0
    MutexAutoLock lock(mDataStorage->mMutex);
492
0
    // If we don't have a profile, bail.
493
0
    if (!mDataStorage->mBackingFile) {
494
0
      return NS_OK;
495
0
    }
496
0
    rv = mDataStorage->mBackingFile->Clone(getter_AddRefs(file));
497
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
498
0
      return rv;
499
0
    }
500
0
  }
501
0
  nsCOMPtr<nsIInputStream> fileInputStream;
502
0
  rv = NS_NewLocalFileInputStream(getter_AddRefs(fileInputStream), file);
503
0
  // If we failed for some reason other than the file doesn't exist, bail.
504
0
  if (NS_WARN_IF(NS_FAILED(rv) &&
505
0
                 rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST &&  // on Unix
506
0
                 rv != NS_ERROR_FILE_NOT_FOUND)) {             // on Windows
507
0
    return rv;
508
0
  }
509
0
510
0
  // If there is a file with data in it, read it. If there isn't,
511
0
  // we'll essentially fall through to notifying that we're good to go.
512
0
  nsCString data;
513
0
  if (fileInputStream) {
514
0
    // Limit to 2MB of data, but only store sMaxDataEntries entries.
515
0
    rv = NS_ConsumeStream(fileInputStream, 1u << 21, data);
516
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
517
0
      return rv;
518
0
    }
519
0
  }
520
0
521
0
  // Atomically parse the data and insert the entries read.
522
0
  // Don't clear existing entries - they may have been inserted between when
523
0
  // this read was kicked-off and when it was run.
524
0
  {
525
0
    MutexAutoLock lock(mDataStorage->mMutex);
526
0
    // The backing file consists of a list of
527
0
    //   <key>\t<score>\t<last accessed time>\t<value>\n
528
0
    // The final \n is not optional; if it is not present the line is assumed
529
0
    // to be corrupt.
530
0
    int32_t currentIndex = 0;
531
0
    int32_t newlineIndex = 0;
532
0
    do {
533
0
      newlineIndex = data.FindChar('\n', currentIndex);
534
0
      // If there are no more newlines or the data table has too many
535
0
      // entries, we are done.
536
0
      if (newlineIndex < 0 ||
537
0
          mDataStorage->mPersistentDataTable.Count() >= sMaxDataEntries) {
538
0
        break;
539
0
      }
540
0
541
0
      nsDependentCSubstring line(data, currentIndex,
542
0
                                 newlineIndex - currentIndex);
543
0
      currentIndex = newlineIndex + 1;
544
0
      nsCString key;
545
0
      Entry entry;
546
0
      nsresult parseRV = ParseLine(line, key, entry);
547
0
      if (NS_SUCCEEDED(parseRV)) {
548
0
        // It could be the case that a newer entry was added before
549
0
        // we got around to reading the file. Don't overwrite new entries.
550
0
        Entry newerEntry;
551
0
        bool present = mDataStorage->mPersistentDataTable.Get(key, &newerEntry);
552
0
        if (!present) {
553
0
          mDataStorage->mPersistentDataTable.Put(key, entry);
554
0
        }
555
0
      }
556
0
    } while (true);
557
0
558
0
    Telemetry::Accumulate(Telemetry::DATA_STORAGE_ENTRIES,
559
0
                          mDataStorage->mPersistentDataTable.Count());
560
0
  }
561
0
562
0
  return NS_OK;
563
0
}
564
565
// The key must be a non-empty string containing no instances of '\t' or '\n',
566
// and must have a length no more than 256.
567
// The value must not contain '\n' and must have a length no more than 1024.
568
// The length limits are to prevent unbounded memory and disk usage.
569
/* static */
570
nsresult
571
DataStorage::ValidateKeyAndValue(const nsCString& aKey, const nsCString& aValue)
572
0
{
573
0
  if (aKey.IsEmpty()) {
574
0
    return NS_ERROR_INVALID_ARG;
575
0
  }
576
0
  if (aKey.Length() > 256) {
577
0
    return NS_ERROR_INVALID_ARG;
578
0
  }
579
0
  int32_t delimiterIndex = aKey.FindChar('\t', 0);
580
0
  if (delimiterIndex >= 0) {
581
0
    return NS_ERROR_INVALID_ARG;
582
0
  }
583
0
  delimiterIndex = aKey.FindChar('\n', 0);
584
0
  if (delimiterIndex >= 0) {
585
0
    return NS_ERROR_INVALID_ARG;
586
0
  }
587
0
  delimiterIndex = aValue.FindChar('\n', 0);
588
0
  if (delimiterIndex >= 0) {
589
0
    return NS_ERROR_INVALID_ARG;
590
0
  }
591
0
  if (aValue.Length() > 1024) {
592
0
    return NS_ERROR_INVALID_ARG;
593
0
  }
594
0
595
0
  return NS_OK;
596
0
}
597
598
// Each line is: <key>\t<score>\t<last accessed time>\t<value>
599
// Where <score> is a uint32_t as a string, <last accessed time> is a
600
// int32_t as a string, and the rest are strings.
601
// <value> can contain anything but a newline.
602
// Returns a successful status if the line can be decoded into a key and entry.
603
// Otherwise, an error status is returned and the values assigned to the
604
// output parameters are in an undefined state.
605
/* static */
606
nsresult
607
DataStorage::Reader::ParseLine(nsDependentCSubstring& aLine, nsCString& aKeyOut,
608
                               Entry& aEntryOut)
609
0
{
610
0
  // First find the indices to each part of the line.
611
0
  int32_t scoreIndex;
612
0
  scoreIndex = aLine.FindChar('\t', 0) + 1;
613
0
  if (scoreIndex <= 0) {
614
0
    return NS_ERROR_UNEXPECTED;
615
0
  }
616
0
  int32_t accessedIndex = aLine.FindChar('\t', scoreIndex) + 1;
617
0
  if (accessedIndex <= 0) {
618
0
    return NS_ERROR_UNEXPECTED;
619
0
  }
620
0
  int32_t valueIndex = aLine.FindChar('\t', accessedIndex) + 1;
621
0
  if (valueIndex <= 0) {
622
0
    return NS_ERROR_UNEXPECTED;
623
0
  }
624
0
625
0
  // Now make substrings based on where each part is.
626
0
  nsDependentCSubstring keyPart(aLine, 0, scoreIndex - 1);
627
0
  nsDependentCSubstring scorePart(aLine, scoreIndex,
628
0
                                  accessedIndex - scoreIndex - 1);
629
0
  nsDependentCSubstring accessedPart(aLine, accessedIndex,
630
0
                                     valueIndex - accessedIndex - 1);
631
0
  nsDependentCSubstring valuePart(aLine, valueIndex);
632
0
633
0
  nsresult rv;
634
0
  rv = DataStorage::ValidateKeyAndValue(nsCString(keyPart),
635
0
                                        nsCString(valuePart));
636
0
  if (NS_FAILED(rv)) {
637
0
    return NS_ERROR_UNEXPECTED;
638
0
  }
639
0
640
0
  // Now attempt to decode the score part as a uint32_t.
641
0
  // XXX nsDependentCSubstring doesn't support ToInteger
642
0
  int32_t integer = nsCString(scorePart).ToInteger(&rv);
643
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
644
0
    return rv;
645
0
  }
646
0
  if (integer < 0) {
647
0
    return NS_ERROR_UNEXPECTED;
648
0
  }
649
0
  aEntryOut.mScore = (uint32_t)integer;
650
0
651
0
  integer = nsCString(accessedPart).ToInteger(&rv);
652
0
  if (NS_FAILED(rv)) {
653
0
    return rv;
654
0
  }
655
0
  if (integer < 0) {
656
0
    return NS_ERROR_UNEXPECTED;
657
0
  }
658
0
  aEntryOut.mLastAccessed = integer;
659
0
660
0
  // Now set the key and value.
661
0
  aKeyOut.Assign(keyPart);
662
0
  aEntryOut.mValue.Assign(valuePart);
663
0
664
0
  return NS_OK;
665
0
}
666
667
nsresult
668
DataStorage::AsyncReadData(bool& aHaveProfileDir,
669
                           const MutexAutoLock& /*aProofOfLock*/)
670
0
{
671
0
  MOZ_ASSERT(XRE_IsParentProcess());
672
0
  aHaveProfileDir = false;
673
0
  // Allocate a Reader so that even if it isn't dispatched,
674
0
  // the data-storage-ready notification will be fired and Get
675
0
  // will be able to proceed (this happens in its destructor).
676
0
  RefPtr<Reader> job(new Reader(this));
677
0
  nsresult rv;
678
0
  // If we don't have a profile directory, this will fail.
679
0
  // That's okay - it just means there is no persistent state.
680
0
  rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
681
0
                              getter_AddRefs(mBackingFile));
682
0
  if (NS_FAILED(rv)) {
683
0
    mBackingFile = nullptr;
684
0
    return NS_OK;
685
0
  }
686
0
687
0
  rv = mBackingFile->Append(mFilename);
688
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
689
0
    return rv;
690
0
  }
691
0
692
0
  rv = DataStorageSharedThread::Dispatch(job);
693
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
694
0
    return rv;
695
0
  }
696
0
697
0
  aHaveProfileDir = true;
698
0
  return NS_OK;
699
0
}
700
701
void
702
DataStorage::WaitForReady()
703
0
{
704
0
  MOZ_DIAGNOSTIC_ASSERT(mInitCalled, "Waiting before Init() has been called?");
705
0
706
0
  MonitorAutoLock readyLock(mReadyMonitor);
707
0
  while (!mReady) {
708
0
    readyLock.Wait();
709
0
  }
710
0
  MOZ_ASSERT(mReady);
711
0
}
712
713
nsCString
714
DataStorage::Get(const nsCString& aKey, DataStorageType aType)
715
0
{
716
0
  WaitForReady();
717
0
  MutexAutoLock lock(mMutex);
718
0
719
0
  Entry entry;
720
0
  bool foundValue = GetInternal(aKey, &entry, aType, lock);
721
0
  if (!foundValue) {
722
0
    return EmptyCString();
723
0
  }
724
0
725
0
  // If we're here, we found a value. Maybe update its score.
726
0
  if (entry.UpdateScore()) {
727
0
    PutInternal(aKey, entry, aType, lock);
728
0
  }
729
0
730
0
  return entry.mValue;
731
0
}
732
733
bool
734
DataStorage::GetInternal(const nsCString& aKey, Entry* aEntry,
735
                         DataStorageType aType,
736
                         const MutexAutoLock& aProofOfLock)
737
0
{
738
0
  DataStorageTable& table = GetTableForType(aType, aProofOfLock);
739
0
  bool foundValue = table.Get(aKey, aEntry);
740
0
  return foundValue;
741
0
}
742
743
DataStorage::DataStorageTable&
744
DataStorage::GetTableForType(DataStorageType aType,
745
                             const MutexAutoLock& /*aProofOfLock*/)
746
0
{
747
0
  switch (aType) {
748
0
    case DataStorage_Persistent:
749
0
      return mPersistentDataTable;
750
0
    case DataStorage_Temporary:
751
0
      return mTemporaryDataTable;
752
0
    case DataStorage_Private:
753
0
      return mPrivateDataTable;
754
0
  }
755
0
756
0
  MOZ_CRASH("given bad DataStorage storage type");
757
0
}
758
759
void
760
DataStorage::ReadAllFromTable(DataStorageType aType,
761
                              InfallibleTArray<dom::DataStorageItem>* aItems,
762
                              const MutexAutoLock& aProofOfLock)
763
0
{
764
0
  for (auto iter = GetTableForType(aType, aProofOfLock).Iter();
765
0
       !iter.Done(); iter.Next()) {
766
0
    DataStorageItem* item = aItems->AppendElement();
767
0
    item->key() = iter.Key();
768
0
    item->value() = iter.Data().mValue;
769
0
    item->type() = aType;
770
0
  }
771
0
}
772
773
void
774
DataStorage::GetAll(InfallibleTArray<dom::DataStorageItem>* aItems)
775
0
{
776
0
  WaitForReady();
777
0
  MutexAutoLock lock(mMutex);
778
0
779
0
  aItems->SetCapacity(mPersistentDataTable.Count() +
780
0
                      mTemporaryDataTable.Count() +
781
0
                      mPrivateDataTable.Count());
782
0
  ReadAllFromTable(DataStorage_Persistent, aItems, lock);
783
0
  ReadAllFromTable(DataStorage_Temporary, aItems, lock);
784
0
  ReadAllFromTable(DataStorage_Private, aItems, lock);
785
0
}
786
787
// Limit the number of entries per table. This is to prevent unbounded
788
// resource use. The eviction strategy is as follows:
789
// - An entry's score is incremented once for every day it is accessed.
790
// - Evict an entry with score no more than any other entry in the table
791
//   (this is the same as saying evict the entry with the lowest score,
792
//    except for when there are multiple entries with the lowest score,
793
//    in which case one of them is evicted - which one is not specified).
794
void
795
DataStorage::MaybeEvictOneEntry(DataStorageType aType,
796
                                const MutexAutoLock& aProofOfLock)
797
0
{
798
0
  DataStorageTable& table = GetTableForType(aType, aProofOfLock);
799
0
  if (table.Count() >= sMaxDataEntries) {
800
0
    KeyAndEntry toEvict;
801
0
    // If all entries have score sMaxScore, this won't actually remove
802
0
    // anything. This will never happen, however, because having that high
803
0
    // a score either means someone tampered with the backing file or every
804
0
    // entry has been accessed once a day for ~4 billion days.
805
0
    // The worst that will happen is there will be 1025 entries in the
806
0
    // persistent data table, with the 1025th entry being replaced every time
807
0
    // data with a new key is inserted into the table. This is bad but
808
0
    // ultimately not that concerning, considering that if an attacker can
809
0
    // modify data in the profile, they can cause much worse harm.
810
0
    toEvict.mEntry.mScore = sMaxScore;
811
0
812
0
    for (auto iter = table.Iter(); !iter.Done(); iter.Next()) {
813
0
      Entry entry = iter.UserData();
814
0
      if (entry.mScore < toEvict.mEntry.mScore) {
815
0
        toEvict.mKey = iter.Key();
816
0
        toEvict.mEntry = entry;
817
0
      }
818
0
    }
819
0
820
0
    table.Remove(toEvict.mKey);
821
0
  }
822
0
}
823
824
// NB: Because this may cross a thread boundary, any variables captured by the
825
// Functor must be captured by copy and not by reference.
826
template <class Functor>
827
static
828
void
829
RunOnAllContentParents(Functor func)
830
0
{
831
0
  if (!XRE_IsParentProcess()) {
832
0
    return;
833
0
  }
834
0
  using dom::ContentParent;
835
0
836
0
  nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction("RunOnAllContentParents",
837
0
  [func] () {
838
0
    nsTArray<ContentParent*> parents;
839
0
    ContentParent::GetAll(parents);
840
0
    for (auto& parent: parents) {
841
0
      func(parent);
842
0
    }
843
0
  });
Unexecuted instantiation: Unified_cpp_security_manager_ssl0.cpp:void mozilla::RunOnAllContentParents<mozilla::DataStorage::Put(nsTString<char> const&, nsTString<char> const&, mozilla::DataStorageType)::$_0>(mozilla::DataStorage::Put(nsTString<char> const&, nsTString<char> const&, mozilla::DataStorageType)::$_0)::{lambda()#1}::operator()() const
Unexecuted instantiation: Unified_cpp_security_manager_ssl0.cpp:void mozilla::RunOnAllContentParents<mozilla::DataStorage::Remove(nsTString<char> const&, mozilla::DataStorageType)::$_1>(mozilla::DataStorage::Remove(nsTString<char> const&, mozilla::DataStorageType)::$_1)::{lambda()#1}::operator()() const
Unexecuted instantiation: Unified_cpp_security_manager_ssl0.cpp:void mozilla::RunOnAllContentParents<mozilla::DataStorage::Clear()::$_2>(mozilla::DataStorage::Clear()::$_2)::{lambda()#1}::operator()() const
844
0
  MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(r));
845
0
}
Unexecuted instantiation: Unified_cpp_security_manager_ssl0.cpp:void mozilla::RunOnAllContentParents<mozilla::DataStorage::Put(nsTString<char> const&, nsTString<char> const&, mozilla::DataStorageType)::$_0>(mozilla::DataStorage::Put(nsTString<char> const&, nsTString<char> const&, mozilla::DataStorageType)::$_0)
Unexecuted instantiation: Unified_cpp_security_manager_ssl0.cpp:void mozilla::RunOnAllContentParents<mozilla::DataStorage::Remove(nsTString<char> const&, mozilla::DataStorageType)::$_1>(mozilla::DataStorage::Remove(nsTString<char> const&, mozilla::DataStorageType)::$_1)
Unexecuted instantiation: Unified_cpp_security_manager_ssl0.cpp:void mozilla::RunOnAllContentParents<mozilla::DataStorage::Clear()::$_2>(mozilla::DataStorage::Clear()::$_2)
846
847
nsresult
848
DataStorage::Put(const nsCString& aKey, const nsCString& aValue,
849
                 DataStorageType aType)
850
0
{
851
0
  WaitForReady();
852
0
  MutexAutoLock lock(mMutex);
853
0
854
0
  nsresult rv;
855
0
  rv = ValidateKeyAndValue(aKey, aValue);
856
0
  if (NS_FAILED(rv)) {
857
0
    return rv;
858
0
  }
859
0
860
0
  Entry entry;
861
0
  bool exists = GetInternal(aKey, &entry, aType, lock);
862
0
  if (exists) {
863
0
    entry.UpdateScore();
864
0
  } else {
865
0
    MaybeEvictOneEntry(aType, lock);
866
0
  }
867
0
  entry.mValue = aValue;
868
0
  rv = PutInternal(aKey, entry, aType, lock);
869
0
  if (NS_FAILED(rv)) {
870
0
    return rv;
871
0
  }
872
0
873
0
  nsString filename(mFilename);
874
0
  RunOnAllContentParents(
875
0
  [aKey, aValue, aType, filename] (dom::ContentParent* aParent) {
876
0
    DataStorageItem item;
877
0
    item.key() = aKey;
878
0
    item.value() = aValue;
879
0
    item.type() = aType;
880
0
    Unused << aParent->SendDataStoragePut(filename, item);
881
0
  });
882
0
883
0
  return NS_OK;
884
0
}
885
886
nsresult
887
DataStorage::PutInternal(const nsCString& aKey, Entry& aEntry,
888
                         DataStorageType aType,
889
                         const MutexAutoLock& aProofOfLock)
890
0
{
891
0
  DataStorageTable& table = GetTableForType(aType, aProofOfLock);
892
0
  table.Put(aKey, aEntry);
893
0
894
0
  if (aType == DataStorage_Persistent && !mPendingWrite) {
895
0
    return AsyncSetTimer(aProofOfLock);
896
0
  }
897
0
898
0
  return NS_OK;
899
0
}
900
901
void
902
DataStorage::Remove(const nsCString& aKey, DataStorageType aType)
903
0
{
904
0
  WaitForReady();
905
0
  MutexAutoLock lock(mMutex);
906
0
907
0
  DataStorageTable& table = GetTableForType(aType, lock);
908
0
  table.Remove(aKey);
909
0
910
0
  if (aType == DataStorage_Persistent && !mPendingWrite) {
911
0
    Unused << AsyncSetTimer(lock);
912
0
  }
913
0
914
0
  nsString filename(mFilename);
915
0
  RunOnAllContentParents(
916
0
  [filename, aKey, aType] (dom::ContentParent* aParent) {
917
0
    Unused << aParent->SendDataStorageRemove(filename, aKey, aType);
918
0
  });
919
0
}
920
921
class DataStorage::Writer : public Runnable
922
{
923
public:
924
  Writer(nsCString& aData, DataStorage* aDataStorage)
925
    : Runnable("DataStorage::Writer")
926
    , mData(aData)
927
    , mDataStorage(aDataStorage)
928
0
  {
929
0
  }
930
931
private:
932
  NS_DECL_NSIRUNNABLE
933
934
  nsCString mData;
935
  RefPtr<DataStorage> mDataStorage;
936
};
937
938
NS_IMETHODIMP
939
DataStorage::Writer::Run()
940
0
{
941
0
  nsresult rv;
942
0
  // Concurrent operations on nsIFile objects are not guaranteed to be safe,
943
0
  // so we clone the file while holding the lock and then release the lock.
944
0
  // At that point, we can safely operate on the clone.
945
0
  nsCOMPtr<nsIFile> file;
946
0
  {
947
0
    MutexAutoLock lock(mDataStorage->mMutex);
948
0
    // If we don't have a profile, bail.
949
0
    if (!mDataStorage->mBackingFile) {
950
0
      return NS_OK;
951
0
    }
952
0
    rv = mDataStorage->mBackingFile->Clone(getter_AddRefs(file));
953
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
954
0
      return rv;
955
0
    }
956
0
  }
957
0
958
0
  nsCOMPtr<nsIOutputStream> outputStream;
959
0
  rv = NS_NewLocalFileOutputStream(getter_AddRefs(outputStream), file,
960
0
                                   PR_CREATE_FILE | PR_TRUNCATE | PR_WRONLY);
961
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
962
0
    return rv;
963
0
  }
964
0
965
0
  const char* ptr = mData.get();
966
0
  int32_t remaining = mData.Length();
967
0
  uint32_t written = 0;
968
0
  while (remaining > 0) {
969
0
    rv = outputStream->Write(ptr, remaining, &written);
970
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
971
0
      return rv;
972
0
    }
973
0
    remaining -= written;
974
0
    ptr += written;
975
0
  }
976
0
977
0
  // Observed by tests.
978
0
  nsCOMPtr<nsIRunnable> job =
979
0
    NewRunnableMethod<const char*>("DataStorage::NotifyObservers",
980
0
                                   mDataStorage,
981
0
                                   &DataStorage::NotifyObservers,
982
0
                                   "data-storage-written");
983
0
  rv = NS_DispatchToMainThread(job, NS_DISPATCH_NORMAL);
984
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
985
0
    return rv;
986
0
  }
987
0
988
0
  return NS_OK;
989
0
}
990
991
nsresult
992
DataStorage::AsyncWriteData(const MutexAutoLock& /*aProofOfLock*/)
993
0
{
994
0
  MOZ_ASSERT(XRE_IsParentProcess());
995
0
996
0
  if (mShuttingDown || !mBackingFile) {
997
0
    return NS_OK;
998
0
  }
999
0
1000
0
  nsCString output;
1001
0
  for (auto iter = mPersistentDataTable.Iter(); !iter.Done(); iter.Next()) {
1002
0
    Entry entry = iter.UserData();
1003
0
    output.Append(iter.Key());
1004
0
    output.Append('\t');
1005
0
    output.AppendInt(entry.mScore);
1006
0
    output.Append('\t');
1007
0
    output.AppendInt(entry.mLastAccessed);
1008
0
    output.Append('\t');
1009
0
    output.Append(entry.mValue);
1010
0
    output.Append('\n');
1011
0
  }
1012
0
1013
0
  RefPtr<Writer> job(new Writer(output, this));
1014
0
  nsresult rv = DataStorageSharedThread::Dispatch(job);
1015
0
  mPendingWrite = false;
1016
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
1017
0
    return rv;
1018
0
  }
1019
0
1020
0
  return NS_OK;
1021
0
}
1022
1023
nsresult
1024
DataStorage::Clear()
1025
0
{
1026
0
  WaitForReady();
1027
0
  MutexAutoLock lock(mMutex);
1028
0
  mPersistentDataTable.Clear();
1029
0
  mTemporaryDataTable.Clear();
1030
0
  mPrivateDataTable.Clear();
1031
0
1032
0
  if (XRE_IsParentProcess()) {
1033
0
    // Asynchronously clear the file. This is similar to the permission manager
1034
0
    // in that it doesn't wait to synchronously remove the data from its backing
1035
0
    // storage either.
1036
0
    nsresult rv = AsyncWriteData(lock);
1037
0
    if (NS_FAILED(rv)) {
1038
0
      return rv;
1039
0
    }
1040
0
  }
1041
0
1042
0
  nsString filename(mFilename);
1043
0
  RunOnAllContentParents([filename] (dom::ContentParent* aParent) {
1044
0
    Unused << aParent->SendDataStorageClear(filename);
1045
0
  });
1046
0
1047
0
  return NS_OK;
1048
0
}
1049
1050
/* static */
1051
void
1052
DataStorage::TimerCallback(nsITimer* aTimer, void* aClosure)
1053
0
{
1054
0
  MOZ_ASSERT(XRE_IsParentProcess());
1055
0
1056
0
  RefPtr<DataStorage> aDataStorage = (DataStorage*)aClosure;
1057
0
  MutexAutoLock lock(aDataStorage->mMutex);
1058
0
  Unused << aDataStorage->AsyncWriteData(lock);
1059
0
}
1060
1061
// We only initialize the timer on the worker thread because it's not safe
1062
// to mix what threads are operating on the timer.
1063
nsresult
1064
DataStorage::AsyncSetTimer(const MutexAutoLock& /*aProofOfLock*/)
1065
0
{
1066
0
  if (mShuttingDown || !XRE_IsParentProcess()) {
1067
0
    return NS_OK;
1068
0
  }
1069
0
1070
0
  mPendingWrite = true;
1071
0
  nsCOMPtr<nsIRunnable> job =
1072
0
    NewRunnableMethod("DataStorage::SetTimer", this, &DataStorage::SetTimer);
1073
0
  nsresult rv = DataStorageSharedThread::Dispatch(job);
1074
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
1075
0
    return rv;
1076
0
  }
1077
0
  return NS_OK;
1078
0
}
1079
1080
void
1081
DataStorage::SetTimer()
1082
0
{
1083
0
  MOZ_ASSERT(!NS_IsMainThread());
1084
0
  MOZ_ASSERT(XRE_IsParentProcess());
1085
0
1086
0
  MutexAutoLock lock(mMutex);
1087
0
1088
0
  nsresult rv;
1089
0
  if (!mTimer) {
1090
0
    mTimer = NS_NewTimer();
1091
0
    if (NS_WARN_IF(!mTimer)) {
1092
0
      return;
1093
0
    }
1094
0
  }
1095
0
1096
0
  rv = mTimer->InitWithNamedFuncCallback(TimerCallback,
1097
0
                                         this,
1098
0
                                         mTimerDelay,
1099
0
                                         nsITimer::TYPE_ONE_SHOT,
1100
0
                                         "DataStorage::SetTimer");
1101
0
  Unused << NS_WARN_IF(NS_FAILED(rv));
1102
0
}
1103
1104
void
1105
DataStorage::NotifyObservers(const char* aTopic)
1106
0
{
1107
0
  // Don't access the observer service off the main thread.
1108
0
  if (!NS_IsMainThread()) {
1109
0
    MOZ_ASSERT_UNREACHABLE("DataStorage::NotifyObservers called off main thread");
1110
0
    return;
1111
0
  }
1112
0
1113
0
  nsCOMPtr<nsIObserverService> os = services::GetObserverService();
1114
0
  if (os) {
1115
0
    os->NotifyObservers(nullptr, aTopic, mFilename.get());
1116
0
  }
1117
0
}
1118
1119
nsresult
1120
DataStorage::DispatchShutdownTimer(const MutexAutoLock& /*aProofOfLock*/)
1121
0
{
1122
0
  MOZ_ASSERT(XRE_IsParentProcess());
1123
0
1124
0
  nsCOMPtr<nsIRunnable> job = NewRunnableMethod(
1125
0
    "DataStorage::ShutdownTimer", this, &DataStorage::ShutdownTimer);
1126
0
  nsresult rv = DataStorageSharedThread::Dispatch(job);
1127
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
1128
0
    return rv;
1129
0
  }
1130
0
  return NS_OK;
1131
0
}
1132
1133
void
1134
DataStorage::ShutdownTimer()
1135
0
{
1136
0
  MOZ_ASSERT(XRE_IsParentProcess());
1137
0
  MOZ_ASSERT(!NS_IsMainThread());
1138
0
  MutexAutoLock lock(mMutex);
1139
0
  nsresult rv = mTimer->Cancel();
1140
0
  Unused << NS_WARN_IF(NS_FAILED(rv));
1141
0
  mTimer = nullptr;
1142
0
}
1143
1144
//------------------------------------------------------------
1145
// DataStorage::nsIObserver
1146
//------------------------------------------------------------
1147
1148
NS_IMETHODIMP
1149
DataStorage::Observe(nsISupports* /*aSubject*/, const char* aTopic,
1150
                     const char16_t* /*aData*/)
1151
0
{
1152
0
  // Don't access preferences off the main thread.
1153
0
  if (!NS_IsMainThread()) {
1154
0
    MOZ_ASSERT_UNREACHABLE("DataStorage::Observe called off main thread");
1155
0
    return NS_ERROR_NOT_SAME_THREAD;
1156
0
  }
1157
0
1158
0
  if (strcmp(aTopic, "last-pb-context-exited") == 0) {
1159
0
    MutexAutoLock lock(mMutex);
1160
0
    mPrivateDataTable.Clear();
1161
0
  }
1162
0
1163
0
  if (!XRE_IsParentProcess()) {
1164
0
    if (strcmp(aTopic, "xpcom-shutdown") == 0) {
1165
0
      sDataStorages->Clear();
1166
0
    }
1167
0
    return NS_OK;
1168
0
  }
1169
0
1170
0
  // Saving data at shutdown involves two phases. The first phase dispatches the
1171
0
  // events to write the data out. The second phase runs those events and shuts
1172
0
  // down the background thread. This ensures all DataStorage instances have an
1173
0
  // opportunity to dispatch their events before the thread goes away.
1174
0
  if (strcmp(aTopic, "profile-change-teardown") == 0 ||
1175
0
      strcmp(aTopic, "xpcom-shutdown") == 0) {
1176
0
    MutexAutoLock lock(mMutex);
1177
0
    if (!mShuttingDown) {
1178
0
      nsresult rv = AsyncWriteData(lock);
1179
0
      mShuttingDown = true;
1180
0
      Unused << NS_WARN_IF(NS_FAILED(rv));
1181
0
      if (mTimer) {
1182
0
        Unused << DispatchShutdownTimer(lock);
1183
0
      }
1184
0
    }
1185
0
    sDataStorages->Clear();
1186
0
  } else if (strcmp(aTopic, "profile-before-change") == 0 ||
1187
0
             strcmp(aTopic, "xpcom-shutdown-threads") == 0) {
1188
0
    DataStorageSharedThread::Shutdown();
1189
0
  }
1190
0
1191
0
  return NS_OK;
1192
0
}
1193
1194
void
1195
DataStorage::PrefChanged(const char* aPref)
1196
0
{
1197
0
  MutexAutoLock lock(mMutex);
1198
0
  mTimerDelay = Preferences::GetInt("test.datastorage.write_timer_ms",
1199
0
                                    sDataStorageDefaultTimerDelay);
1200
0
}
1201
1202
DataStorage::Entry::Entry()
1203
  : mScore(0)
1204
  , mLastAccessed((int32_t)(PR_Now() / sOneDayInMicroseconds))
1205
0
{
1206
0
}
1207
1208
// Updates this entry's score. Returns true if the score has actually changed.
1209
// If it's been less than a day since this entry has been accessed, the score
1210
// does not change. Otherwise, the score increases by 1.
1211
// The default score is 0. The maximum score is the maximum value that can
1212
// be represented by an unsigned 32 bit integer.
1213
// This is to handle evictions from our tables, which in turn is to prevent
1214
// unbounded resource use.
1215
bool
1216
DataStorage::Entry::UpdateScore()
1217
0
{
1218
0
1219
0
  int32_t nowInDays = (int32_t)(PR_Now() / sOneDayInMicroseconds);
1220
0
  int32_t daysSinceAccessed = (nowInDays - mLastAccessed);
1221
0
1222
0
  // Update the last accessed time.
1223
0
  mLastAccessed = nowInDays;
1224
0
1225
0
  // If it's been less than a day since we've been accessed,
1226
0
  // the score isn't updated.
1227
0
  if (daysSinceAccessed < 1) {
1228
0
    return false;
1229
0
  }
1230
0
1231
0
  // Otherwise, increment the score (but don't overflow).
1232
0
  if (mScore < sMaxScore) {
1233
0
    mScore++;
1234
0
  }
1235
0
  return true;
1236
0
}
1237
1238
} // namespace mozilla