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