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