Bug 1867190 - Add prefs for PHC probablities r=glandium
[gecko.git] / toolkit / mozapps / defaultagent / Cache.cpp
blob1a323e54d93560093defd476ae8058a37c54bc8a
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
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/. */
7 #include "Cache.h"
9 #include <algorithm>
11 #include "common.h"
12 #include "EventLog.h"
13 #include "mozilla/Unused.h"
15 namespace mozilla::default_agent {
17 // Cache entry version documentation:
18 // Version 1:
19 // The version number is written explicitly when version 1 cache entries are
20 // migrated, but in their original location there is no version key.
21 // Required Keys:
22 // CacheEntryVersion: <DWORD>
23 // NotificationType: <string>
24 // NotificationShown: <string>
25 // NotificationAction: <string>
26 // Version 2:
27 // Required Keys:
28 // CacheEntryVersion: <DWORD>
29 // NotificationType: <string>
30 // NotificationShown: <string>
31 // NotificationAction: <string>
32 // PrevNotificationAction: <string>
34 static std::wstring MakeVersionedRegSubKey(const wchar_t* baseKey) {
35 std::wstring key;
36 if (baseKey) {
37 key = baseKey;
38 } else {
39 key = Cache::kDefaultPingCacheRegKey;
41 key += L"\\version";
42 key += std::to_wstring(Cache::kVersion);
43 return key;
46 Cache::Cache(const wchar_t* cacheRegKey /* = nullptr */)
47 : mCacheRegKey(MakeVersionedRegSubKey(cacheRegKey)),
48 mInitializeResult(mozilla::Nothing()),
49 mCapacity(Cache::kDefaultCapacity),
50 mFront(0),
51 mSize(0) {}
53 Cache::~Cache() {}
55 VoidResult Cache::Init() {
56 if (mInitializeResult.isSome()) {
57 HRESULT hr = mInitializeResult.value();
58 if (FAILED(hr)) {
59 return mozilla::Err(mozilla::WindowsError::FromHResult(hr));
60 } else {
61 return mozilla::Ok();
65 VoidResult result = SetupCache();
66 if (result.isErr()) {
67 HRESULT hr = result.inspectErr().AsHResult();
68 mInitializeResult = mozilla::Some(hr);
69 return result;
72 // At this point, the cache is ready to use, so mark the initialization as
73 // complete. This is important so that when we attempt migration, below,
74 // the migration's attempts to write to the cache don't try to initialize
75 // the cache again.
76 mInitializeResult = mozilla::Some(S_OK);
78 // Ignore the result of the migration. If we failed to migrate, there may be
79 // some data loss. But that's better than failing to ever use the new cache
80 // just because there's something wrong with the old one.
81 mozilla::Unused << MaybeMigrateVersion1();
83 return mozilla::Ok();
86 // If the setting does not exist, the default value is written and returned.
87 DwordResult Cache::EnsureDwordSetting(const wchar_t* regName,
88 uint32_t defaultValue) {
89 MaybeDwordResult readResult = RegistryGetValueDword(
90 IsPrefixed::Unprefixed, regName, mCacheRegKey.c_str());
91 if (readResult.isErr()) {
92 HRESULT hr = readResult.unwrapErr().AsHResult();
93 LOG_ERROR_MESSAGE(L"Failed to read setting \"%s\": %#X", regName, hr);
94 return mozilla::Err(mozilla::WindowsError::FromHResult(hr));
96 mozilla::Maybe<uint32_t> maybeValue = readResult.unwrap();
97 if (maybeValue.isSome()) {
98 return maybeValue.value();
101 VoidResult writeResult = RegistrySetValueDword(
102 IsPrefixed::Unprefixed, regName, defaultValue, mCacheRegKey.c_str());
103 if (writeResult.isErr()) {
104 HRESULT hr = writeResult.unwrapErr().AsHResult();
105 LOG_ERROR_MESSAGE(L"Failed to write setting \"%s\": %#X", regName, hr);
106 return mozilla::Err(mozilla::WindowsError::FromHResult(hr));
108 return defaultValue;
111 // This function does two things:
112 // 1. It creates and sets the registry values used by the cache, if they don't
113 // already exist.
114 // 2. If the the values already existed, it reads the settings of the cache
115 // into their member variables.
116 VoidResult Cache::SetupCache() {
117 DwordResult result =
118 EnsureDwordSetting(Cache::kCapacityRegName, Cache::kDefaultCapacity);
119 if (result.isErr()) {
120 return mozilla::Err(result.unwrapErr());
122 mCapacity = std::min(result.unwrap(), Cache::kMaxCapacity);
124 result = EnsureDwordSetting(Cache::kFrontRegName, 0);
125 if (result.isErr()) {
126 return mozilla::Err(result.unwrapErr());
128 mFront = std::min(result.unwrap(), Cache::kMaxCapacity - 1);
130 result = EnsureDwordSetting(Cache::kSizeRegName, 0);
131 if (result.isErr()) {
132 return mozilla::Err(result.unwrapErr());
134 mSize = std::min(result.unwrap(), mCapacity);
136 return mozilla::Ok();
139 static MaybeStringResult ReadVersion1CacheKey(const wchar_t* baseRegKeyName,
140 uint32_t index) {
141 std::wstring regName = Cache::kVersion1KeyPrefix;
142 regName += baseRegKeyName;
143 regName += std::to_wstring(index);
145 MaybeStringResult result =
146 RegistryGetValueString(IsPrefixed::Unprefixed, regName.c_str());
147 if (result.isErr()) {
148 HRESULT hr = result.inspectErr().AsHResult();
149 LOG_ERROR_MESSAGE(L"Failed to read \"%s\": %#X", regName.c_str(), hr);
151 return result;
154 static VoidResult DeleteVersion1CacheKey(const wchar_t* baseRegKeyName,
155 uint32_t index) {
156 std::wstring regName = Cache::kVersion1KeyPrefix;
157 regName += baseRegKeyName;
158 regName += std::to_wstring(index);
160 VoidResult result =
161 RegistryDeleteValue(IsPrefixed::Unprefixed, regName.c_str());
162 if (result.isErr()) {
163 HRESULT hr = result.inspectErr().AsHResult();
164 LOG_ERROR_MESSAGE(L"Failed to delete \"%s\": %#X", regName.c_str(), hr);
166 return result;
169 static VoidResult DeleteVersion1CacheEntry(uint32_t index) {
170 VoidResult typeResult =
171 DeleteVersion1CacheKey(Cache::kNotificationTypeKey, index);
172 VoidResult shownResult =
173 DeleteVersion1CacheKey(Cache::kNotificationShownKey, index);
174 VoidResult actionResult =
175 DeleteVersion1CacheKey(Cache::kNotificationActionKey, index);
177 if (typeResult.isErr()) {
178 return typeResult;
180 if (shownResult.isErr()) {
181 return shownResult;
183 return actionResult;
186 VoidResult Cache::MaybeMigrateVersion1() {
187 for (uint32_t index = 0; index < Cache::kVersion1MaxSize; ++index) {
188 MaybeStringResult typeResult =
189 ReadVersion1CacheKey(Cache::kNotificationTypeKey, index);
190 if (typeResult.isErr()) {
191 return mozilla::Err(typeResult.unwrapErr());
193 MaybeString maybeType = typeResult.unwrap();
195 MaybeStringResult shownResult =
196 ReadVersion1CacheKey(Cache::kNotificationShownKey, index);
197 if (shownResult.isErr()) {
198 return mozilla::Err(shownResult.unwrapErr());
200 MaybeString maybeShown = shownResult.unwrap();
202 MaybeStringResult actionResult =
203 ReadVersion1CacheKey(Cache::kNotificationActionKey, index);
204 if (actionResult.isErr()) {
205 return mozilla::Err(actionResult.unwrapErr());
207 MaybeString maybeAction = actionResult.unwrap();
209 if (maybeType.isSome() && maybeShown.isSome() && maybeAction.isSome()) {
210 // If something goes wrong, we'd rather lose a little data than migrate
211 // over and over again. So delete the old entry before we add the new one.
212 VoidResult result = DeleteVersion1CacheEntry(index);
213 if (result.isErr()) {
214 return result;
217 VersionedEntry entry = VersionedEntry{
218 .entryVersion = 1,
219 .notificationType = maybeType.value(),
220 .notificationShown = maybeShown.value(),
221 .notificationAction = maybeAction.value(),
222 .prevNotificationAction = mozilla::Nothing(),
224 result = VersionedEnqueue(entry);
225 if (result.isErr()) {
226 // We already deleted the version 1 cache entry. No real reason to abort
227 // now. May as well keep attempting to migrate.
228 LOG_ERROR_MESSAGE(L"Warning: Version 1 cache entry %u dropped: %#X",
229 index, result.unwrapErr().AsHResult());
231 } else if (maybeType.isNothing() && maybeShown.isNothing() &&
232 maybeAction.isNothing()) {
233 // Looks like we've reached the end of the version 1 cache.
234 break;
235 } else {
236 // This cache entry seems to be missing a key. Just drop it.
237 LOG_ERROR_MESSAGE(
238 L"Warning: Version 1 cache entry %u dropped due to missing keys",
239 index);
240 mozilla::Unused << DeleteVersion1CacheEntry(index);
243 return mozilla::Ok();
246 std::wstring Cache::MakeEntryRegKeyName(uint32_t index) {
247 std::wstring regName = mCacheRegKey;
248 regName += L'\\';
249 regName += std::to_wstring(index);
250 return regName;
253 VoidResult Cache::WriteEntryKeys(uint32_t index, const VersionedEntry& entry) {
254 std::wstring subKey = MakeEntryRegKeyName(index);
256 VoidResult result =
257 RegistrySetValueDword(IsPrefixed::Unprefixed, Cache::kEntryVersionKey,
258 entry.entryVersion, subKey.c_str());
259 if (result.isErr()) {
260 LOG_ERROR_MESSAGE(L"Unable to write entry version to index %u: %#X", index,
261 result.inspectErr().AsHResult());
262 return result;
265 result = RegistrySetValueString(
266 IsPrefixed::Unprefixed, Cache::kNotificationTypeKey,
267 entry.notificationType.c_str(), subKey.c_str());
268 if (result.isErr()) {
269 LOG_ERROR_MESSAGE(L"Unable to write notification type to index %u: %#X",
270 index, result.inspectErr().AsHResult());
271 return result;
274 result = RegistrySetValueString(
275 IsPrefixed::Unprefixed, Cache::kNotificationShownKey,
276 entry.notificationShown.c_str(), subKey.c_str());
277 if (result.isErr()) {
278 LOG_ERROR_MESSAGE(L"Unable to write notification shown to index %u: %#X",
279 index, result.inspectErr().AsHResult());
280 return result;
283 result = RegistrySetValueString(
284 IsPrefixed::Unprefixed, Cache::kNotificationActionKey,
285 entry.notificationAction.c_str(), subKey.c_str());
286 if (result.isErr()) {
287 LOG_ERROR_MESSAGE(L"Unable to write notification type to index %u: %#X",
288 index, result.inspectErr().AsHResult());
289 return result;
292 if (entry.prevNotificationAction.isSome()) {
293 result = RegistrySetValueString(
294 IsPrefixed::Unprefixed, Cache::kPrevNotificationActionKey,
295 entry.prevNotificationAction.value().c_str(), subKey.c_str());
296 if (result.isErr()) {
297 LOG_ERROR_MESSAGE(
298 L"Unable to write prev notification type to index %u: %#X", index,
299 result.inspectErr().AsHResult());
300 return result;
304 return mozilla::Ok();
307 // Returns success on an attempt to delete a non-existent entry.
308 VoidResult Cache::DeleteEntry(uint32_t index) {
309 std::wstring key = AGENT_REGKEY_NAME;
310 key += L'\\';
311 key += MakeEntryRegKeyName(index);
312 // We could probably just delete they key here, rather than use this function,
313 // which deletes keys recursively. But this mechanism allows future entry
314 // versions to contain sub-keys without causing problems for older versions.
315 LSTATUS ls = RegDeleteTreeW(HKEY_CURRENT_USER, key.c_str());
316 if (ls != ERROR_SUCCESS && ls != ERROR_FILE_NOT_FOUND) {
317 return mozilla::Err(mozilla::WindowsError::FromWin32Error(ls));
319 return mozilla::Ok();
322 VoidResult Cache::SetFront(uint32_t newFront) {
323 VoidResult result =
324 RegistrySetValueDword(IsPrefixed::Unprefixed, Cache::kFrontRegName,
325 newFront, mCacheRegKey.c_str());
326 if (result.isOk()) {
327 mFront = newFront;
329 return result;
332 VoidResult Cache::SetSize(uint32_t newSize) {
333 VoidResult result =
334 RegistrySetValueDword(IsPrefixed::Unprefixed, Cache::kSizeRegName,
335 newSize, mCacheRegKey.c_str());
336 if (result.isOk()) {
337 mSize = newSize;
339 return result;
342 // The entry passed to this function MUST already be valid. This function does
343 // not do any validation internally. We must not, for example, pass an entry
344 // to it with a version of 2 and a prevNotificationAction of mozilla::Nothing()
345 // because a version 2 entry requires that key.
346 VoidResult Cache::VersionedEnqueue(const VersionedEntry& entry) {
347 VoidResult result = Init();
348 if (result.isErr()) {
349 return result;
352 if (mSize >= mCapacity) {
353 LOG_ERROR_MESSAGE(L"Attempted to add an entry to the cache, but it's full");
354 return mozilla::Err(mozilla::WindowsError::FromHResult(E_BOUNDS));
357 uint32_t index = (mFront + mSize) % mCapacity;
359 // We really don't want to write to a location that has stale cache entry data
360 // already lying around.
361 result = DeleteEntry(index);
362 if (result.isErr()) {
363 LOG_ERROR_MESSAGE(L"Unable to remove stale entry: %#X",
364 result.inspectErr().AsHResult());
365 return result;
368 result = WriteEntryKeys(index, entry);
369 if (result.isErr()) {
370 // We might have written a partial key. Attempt to clean up after ourself.
371 mozilla::Unused << DeleteEntry(index);
372 return result;
375 result = SetSize(mSize + 1);
376 if (result.isErr()) {
377 // If we failed to write the size, the new entry was not added successfully.
378 // Attempt to clean up after ourself.
379 mozilla::Unused << DeleteEntry(index);
380 return result;
383 return mozilla::Ok();
386 VoidResult Cache::Enqueue(const Cache::Entry& entry) {
387 Cache::VersionedEntry vEntry = Cache::VersionedEntry{
388 .entryVersion = Cache::kEntryVersion,
389 .notificationType = entry.notificationType,
390 .notificationShown = entry.notificationShown,
391 .notificationAction = entry.notificationAction,
392 .prevNotificationAction = mozilla::Some(entry.prevNotificationAction),
394 return VersionedEnqueue(vEntry);
397 VoidResult Cache::DiscardFront() {
398 if (mSize < 1) {
399 LOG_ERROR_MESSAGE(L"Attempted to discard entry from an empty cache");
400 return mozilla::Err(mozilla::WindowsError::FromHResult(E_BOUNDS));
402 // It's not a huge deal if we can't delete this. Moving mFront will result in
403 // it being excluded from the cache anyways. We'll try to delete it again
404 // anyways if we try to write to this index again.
405 mozilla::Unused << DeleteEntry(mFront);
407 VoidResult result = SetSize(mSize - 1);
408 // We don't really need to bother moving mFront to the next index if the cache
409 // is empty.
410 if (result.isErr() || mSize == 0) {
411 return result;
413 result = SetFront((mFront + 1) % mCapacity);
414 if (result.isErr()) {
415 // If we failed to set the front after we set the size, the cache is
416 // in an inconsistent state.
417 // But, even if the cache is inconsistent, we'll likely lose some data, but
418 // we should eventually be able to recover. Any expected entries with no
419 // data will be discarded and any unexpected entries with data will be
420 // cleared out before we write data there.
421 LOG_ERROR_MESSAGE(L"Cache inconsistent: Updated Size but not Front: %#X",
422 result.inspectErr().AsHResult());
424 return result;
428 * This function reads a DWORD cache key's value and returns it. If the expected
429 * argument is true and the key is missing, this will delete the entire entry
430 * and return mozilla::Nothing().
432 MaybeDwordResult Cache::ReadEntryKeyDword(const std::wstring& regKey,
433 const wchar_t* regName,
434 bool expected) {
435 MaybeDwordResult result =
436 RegistryGetValueDword(IsPrefixed::Unprefixed, regName, regKey.c_str());
437 if (result.isErr()) {
438 LOG_ERROR_MESSAGE(L"Failed to read \"%s\" from \"%s\": %#X", regName,
439 regKey.c_str(), result.inspectErr().AsHResult());
440 return mozilla::Err(result.unwrapErr());
442 MaybeDword maybeValue = result.unwrap();
443 if (expected && maybeValue.isNothing()) {
444 LOG_ERROR_MESSAGE(L"Missing expected value \"%s\" from \"%s\"", regName,
445 regKey.c_str());
446 VoidResult result = DiscardFront();
447 if (result.isErr()) {
448 return mozilla::Err(result.unwrapErr());
451 return maybeValue;
455 * This function reads a string cache key's value and returns it. If the
456 * expected argument is true and the key is missing, this will delete the entire
457 * entry and return mozilla::Nothing().
459 MaybeStringResult Cache::ReadEntryKeyString(const std::wstring& regKey,
460 const wchar_t* regName,
461 bool expected) {
462 MaybeStringResult result =
463 RegistryGetValueString(IsPrefixed::Unprefixed, regName, regKey.c_str());
464 if (result.isErr()) {
465 LOG_ERROR_MESSAGE(L"Failed to read \"%s\" from \"%s\": %#X", regName,
466 regKey.c_str(), result.inspectErr().AsHResult());
467 return mozilla::Err(result.unwrapErr());
469 MaybeString maybeValue = result.unwrap();
470 if (expected && maybeValue.isNothing()) {
471 LOG_ERROR_MESSAGE(L"Missing expected value \"%s\" from \"%s\"", regName,
472 regKey.c_str());
473 VoidResult result = DiscardFront();
474 if (result.isErr()) {
475 return mozilla::Err(result.unwrapErr());
478 return maybeValue;
481 Cache::MaybeEntryResult Cache::Dequeue() {
482 VoidResult result = Init();
483 if (result.isErr()) {
484 return mozilla::Err(result.unwrapErr());
487 std::wstring subKey = MakeEntryRegKeyName(mFront);
489 // We are going to read within a loop so that if we find incomplete entries,
490 // we can just discard them and try to read the next entry. We'll put a limit
491 // on the maximum number of times this loop can possibly run so that if
492 // something goes horribly wrong, we don't loop forever. If we exit this loop
493 // without returning, it means that not only were we not able to read
494 // anything, but something very unexpected happened.
495 // We are going to potentially loop over this mCapacity + 1 times so that if
496 // we end up discarding every item in the cache, we return mozilla::Nothing()
497 // rather than an error.
498 for (uint32_t i = 0; i <= mCapacity; ++i) {
499 if (mSize == 0) {
500 return MaybeEntry(mozilla::Nothing());
503 Cache::VersionedEntry entry;
505 // CacheEntryVersion
506 MaybeDwordResult dResult =
507 ReadEntryKeyDword(subKey, Cache::kEntryVersionKey, true);
508 if (dResult.isErr()) {
509 return mozilla::Err(dResult.unwrapErr());
511 MaybeDword maybeDValue = dResult.unwrap();
512 if (maybeDValue.isNothing()) {
513 // Note that we only call continue in this function after DiscardFront()
514 // has been called (either directly, or by one of the ReadEntryKey.*
515 // functions). So the continue call results in attempting to read the
516 // next entry in the cache.
517 continue;
519 entry.entryVersion = maybeDValue.value();
520 if (entry.entryVersion < 1) {
521 LOG_ERROR_MESSAGE(L"Invalid entry version of %u in \"%s\"",
522 entry.entryVersion, subKey.c_str());
523 VoidResult result = DiscardFront();
524 if (result.isErr()) {
525 return mozilla::Err(result.unwrapErr());
527 continue;
530 // NotificationType
531 MaybeStringResult sResult =
532 ReadEntryKeyString(subKey, Cache::kNotificationTypeKey, true);
533 if (sResult.isErr()) {
534 return mozilla::Err(sResult.unwrapErr());
536 MaybeString maybeSValue = sResult.unwrap();
537 if (maybeSValue.isNothing()) {
538 continue;
540 entry.notificationType = maybeSValue.value();
542 // NotificationShown
543 sResult = ReadEntryKeyString(subKey, Cache::kNotificationShownKey, true);
544 if (sResult.isErr()) {
545 return mozilla::Err(sResult.unwrapErr());
547 maybeSValue = sResult.unwrap();
548 if (maybeSValue.isNothing()) {
549 continue;
551 entry.notificationShown = maybeSValue.value();
553 // NotificationAction
554 sResult = ReadEntryKeyString(subKey, Cache::kNotificationActionKey, true);
555 if (sResult.isErr()) {
556 return mozilla::Err(sResult.unwrapErr());
558 maybeSValue = sResult.unwrap();
559 if (maybeSValue.isNothing()) {
560 continue;
562 entry.notificationAction = maybeSValue.value();
564 // PrevNotificationAction
565 bool expected =
566 entry.entryVersion >= Cache::kInitialVersionPrevNotificationActionKey;
567 sResult =
568 ReadEntryKeyString(subKey, Cache::kPrevNotificationActionKey, expected);
569 if (sResult.isErr()) {
570 return mozilla::Err(sResult.unwrapErr());
572 maybeSValue = sResult.unwrap();
573 if (expected && maybeSValue.isNothing()) {
574 continue;
576 entry.prevNotificationAction = maybeSValue;
578 // We successfully read the entry. Now we need to remove it from the cache.
579 VoidResult result = DiscardFront();
580 if (result.isErr()) {
581 // If we aren't able to remove the entry from the cache, don't return it.
582 // We don't want to return the same item over and over again if we get
583 // into a bad state.
584 return mozilla::Err(result.unwrapErr());
587 return mozilla::Some(entry);
590 LOG_ERROR_MESSAGE(L"Unexpected: This line shouldn't be reached");
591 return mozilla::Err(mozilla::WindowsError::FromHResult(E_FAIL));
594 } // namespace mozilla::default_agent