/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 |