123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585 |
- // Copyright (c) 2012 The Chromium Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style license that can be
- // found in the LICENSE file.
- #include "rlz/chromeos/lib/rlz_value_store_chromeos.h"
- #include <tuple>
- #include "base/base_paths.h"
- #include "base/bind.h"
- #include "base/containers/contains.h"
- #include "base/containers/cxx20_erase.h"
- #include "base/cxx17_backports.h"
- #include "base/files/file_util.h"
- #include "base/files/important_file_writer.h"
- #include "base/json/json_file_value_serializer.h"
- #include "base/json/json_string_value_serializer.h"
- #include "base/lazy_instance.h"
- #include "base/logging.h"
- #include "base/memory/ptr_util.h"
- #include "base/metrics/histogram_macros.h"
- #include "base/path_service.h"
- #include "base/strings/string_number_conversions.h"
- #include "base/strings/string_piece.h"
- #include "base/task/sequenced_task_runner.h"
- #include "base/values.h"
- #include "chromeos/ash/components/dbus/dbus_thread_manager.h"
- #include "chromeos/ash/components/dbus/debug_daemon/debug_daemon_client.h"
- #include "chromeos/system/factory_ping_embargo_check.h"
- #include "chromeos/system/statistics_provider.h"
- #include "dbus/bus.h"
- #include "rlz/lib/lib_values.h"
- #include "rlz/lib/recursive_cross_process_lock_posix.h"
- #include "rlz/lib/supplementary_branding.h"
- #include "rlz/lib/time_util.h"
- namespace rlz_lib {
- namespace {
- // Key names.
- const char kPingTimeKey[] = "ping_time";
- const char kAccessPointKey[] = "access_points";
- const char kProductEventKey[] = "product_events";
- const char kStatefulEventKey[] = "stateful_events";
- // Brand name used when there is no supplementary brand name.
- const char kNoSupplementaryBrand[] = "_";
- // RLZ store filename.
- const base::FilePath::CharType kRLZDataFileName[] =
- FILE_PATH_LITERAL("RLZ Data");
- // RLZ store lock filename
- const base::FilePath::CharType kRLZLockFileName[] =
- FILE_PATH_LITERAL("RLZ Data.lock");
- // RLZ store path for testing.
- base::LazyInstance<base::FilePath>::Leaky g_testing_rlz_store_path =
- LAZY_INSTANCE_INITIALIZER;
- base::FilePath GetRlzStorePathCommon() {
- base::FilePath homedir;
- base::PathService::Get(base::DIR_HOME, &homedir);
- return g_testing_rlz_store_path.Get().empty()
- ? homedir
- : g_testing_rlz_store_path.Get();
- }
- // Returns file path of the RLZ storage.
- base::FilePath GetRlzStorePath() {
- return GetRlzStorePathCommon().Append(kRLZDataFileName);
- }
- // Returns file path of the RLZ storage lock file.
- base::FilePath GetRlzStoreLockPath() {
- return GetRlzStorePathCommon().Append(kRLZLockFileName);
- }
- // Returns the dictionary key for storing access point-related prefs.
- std::string GetKeyName(const std::string& key, AccessPoint access_point) {
- std::string brand = SupplementaryBranding::GetBrand();
- if (brand.empty())
- brand = kNoSupplementaryBrand;
- return key + "." + GetAccessPointName(access_point) + "." + brand;
- }
- // Returns the dictionary key for storing product-related prefs.
- std::string GetKeyName(const std::string& key, Product product) {
- std::string brand = SupplementaryBranding::GetBrand();
- if (brand.empty())
- brand = kNoSupplementaryBrand;
- return key + "." + GetProductName(product) + "." + brand;
- }
- // Uses |brand| to replace the brand code contained in |rlz|. No-op if |rlz| is
- // in incorrect format or already contains |brand|. Returns whether the
- // replacement took place.
- bool ConvertToDynamicRlz(const std::string& brand,
- std::string* rlz,
- AccessPoint access_point) {
- if (brand.size() != 4) {
- LOG(ERROR) << "Invalid brand code format: " + brand;
- return false;
- }
- // Do a sanity check for the rlz string format. It must start with a
- // single-digit rlz encoding version, followed by a two-alphanum access point
- // name, and a four-letter brand code.
- if (rlz->size() < 7 ||
- rlz->substr(1, 2) != GetAccessPointName(access_point)) {
- LOG(ERROR) << "Invalid rlz string format: " + *rlz;
- return false;
- }
- if (rlz->substr(3, 4) == brand)
- return false;
- rlz->replace(3, 4, brand);
- return true;
- }
- // Forward declare so that it could be referred in SetRlzPingSent.
- void OnSetRlzPingSent(int retry_count, bool success);
- // Calls debug daemon client to set |should_send_rlz_ping| to 0 in RW_VPD.
- // Re-post the work on DBus's original thread if it is not called from there
- // because DBus code is not thread safe.
- void SetRlzPingSent(int retry_count) {
- // GetSystemBus() could return null in tests.
- base::SequencedTaskRunner* const origin_task_runner =
- chromeos::DBusThreadManager::Get()->GetSystemBus()
- ? chromeos::DBusThreadManager::Get()
- ->GetSystemBus()
- ->GetOriginTaskRunner()
- : nullptr;
- if (origin_task_runner && !origin_task_runner->RunsTasksInCurrentSequence()) {
- origin_task_runner->PostTask(FROM_HERE,
- base::BindOnce(&SetRlzPingSent, retry_count));
- return;
- }
- ash::DebugDaemonClient::Get()->SetRlzPingSent(
- base::BindOnce(&OnSetRlzPingSent, retry_count + 1));
- }
- // Callback invoked for DebugDaemonClient::SetRlzPingSent.
- void OnSetRlzPingSent(int retry_count, bool success) {
- if (success) {
- UMA_HISTOGRAM_BOOLEAN("Rlz.SetRlzPingSent", true);
- return;
- }
- if (retry_count >= RlzValueStoreChromeOS::kMaxRetryCount) {
- UMA_HISTOGRAM_BOOLEAN("Rlz.SetRlzPingSent", false);
- LOG(ERROR) << "Setting " << chromeos::system::kShouldSendRlzPingKey
- << " failed after " << RlzValueStoreChromeOS::kMaxRetryCount
- << " attempts.";
- return;
- }
- SetRlzPingSent(retry_count);
- }
- // Copy |value| without empty children.
- absl::optional<base::Value> CopyWithoutEmptyChildren(const base::Value& value) {
- switch (value.type()) {
- case base::Value::Type::DICT: {
- base::Value::Dict dict;
- const base::Value::Dict& dict_in = value.GetDict();
- for (auto it = dict_in.begin(); it != dict_in.end(); ++it) {
- absl::optional<base::Value> item_copy =
- CopyWithoutEmptyChildren(it->second);
- if (item_copy)
- dict.Set(it->first, std::move(*item_copy));
- }
- if (dict.empty())
- return absl::nullopt;
- return base::Value(std::move(dict));
- }
- case base::Value::Type::LIST: {
- base::Value::List list;
- list.reserve(value.GetList().size());
- for (const base::Value& item : value.GetList()) {
- absl::optional<base::Value> item_copy = CopyWithoutEmptyChildren(item);
- if (item_copy)
- list.Append(std::move(*item_copy));
- }
- if (list.empty())
- return absl::nullopt;
- return base::Value(std::move(list));
- }
- default:
- return value.Clone();
- }
- }
- } // namespace
- const int RlzValueStoreChromeOS::kMaxRetryCount = 3;
- RlzValueStoreChromeOS::RlzValueStoreChromeOS(const base::FilePath& store_path)
- : rlz_store_(base::Value::Type::DICTIONARY),
- store_path_(store_path),
- read_only_(true) {
- ReadStore();
- }
- RlzValueStoreChromeOS::~RlzValueStoreChromeOS() {
- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- WriteStore();
- }
- bool RlzValueStoreChromeOS::HasAccess(AccessType type) {
- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- return type == kReadAccess || !read_only_;
- }
- bool RlzValueStoreChromeOS::WritePingTime(Product product, int64_t time) {
- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- rlz_store_.SetStringPath(GetKeyName(kPingTimeKey, product),
- base::NumberToString(time));
- return true;
- }
- bool RlzValueStoreChromeOS::ReadPingTime(Product product, int64_t* time) {
- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- // TODO(wzang): Make sure time is correct (check that npupdate has updated
- // successfully). See AutoEnrollmentController::SystemClockSyncWaiter for
- // potential refactor in the ping embargo class.
- if (!HasRlzEmbargoEndDatePassed()) {
- *time = GetSystemTimeAsInt64();
- return true;
- }
- const std::string* ping_time =
- rlz_store_.FindStringPath(GetKeyName(kPingTimeKey, product));
- return ping_time ? base::StringToInt64(*ping_time, time) : false;
- }
- bool RlzValueStoreChromeOS::ClearPingTime(Product product) {
- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- rlz_store_.RemovePath(GetKeyName(kPingTimeKey, product));
- return true;
- }
- bool RlzValueStoreChromeOS::WriteAccessPointRlz(AccessPoint access_point,
- const char* new_rlz) {
- // If an access point already exists, don't overwrite it. This is to prevent
- // writing cohort data for first search which is not needed in Chrome OS.
- //
- // There are two possible cases: either the user performs a search before the
- // first ping is sent on first run, or they do not. If they do, then
- // |new_rlz| contain cohorts for install and first search, but they will be
- // the same. If they don't, the first time WriteAccessPointRlz() is called
- // |new_rlz| will contain only install cohort. The second time it will
- // contain both install and first search cohorts. Ignoring the second
- // means the first search cohort will never be stored.
- if (HasAccessPointRlz(access_point))
- return true;
- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- rlz_store_.SetStringPath(GetKeyName(kAccessPointKey, access_point), new_rlz);
- return true;
- }
- bool RlzValueStoreChromeOS::ReadAccessPointRlz(AccessPoint access_point,
- char* rlz,
- size_t rlz_size) {
- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- const std::string* rlz_value =
- rlz_store_.FindStringPath(GetKeyName(kAccessPointKey, access_point));
- if (rlz_value && rlz_value->size() < rlz_size) {
- strncpy(rlz, rlz_value->c_str(), rlz_size);
- return true;
- }
- if (rlz_size > 0)
- *rlz = '\0';
- return rlz_value == nullptr;
- }
- bool RlzValueStoreChromeOS::ClearAccessPointRlz(AccessPoint access_point) {
- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- rlz_store_.RemovePath(GetKeyName(kAccessPointKey, access_point));
- return true;
- }
- bool RlzValueStoreChromeOS::UpdateExistingAccessPointRlz(
- const std::string& brand) {
- DCHECK(SupplementaryBranding::GetBrand().empty());
- bool updated = false;
- for (int i = NO_ACCESS_POINT + 1; i < LAST_ACCESS_POINT; ++i) {
- AccessPoint access_point = static_cast<AccessPoint>(i);
- const std::string access_point_key =
- GetKeyName(kAccessPointKey, access_point);
- const std::string* rlz = rlz_store_.FindStringPath(access_point_key);
- if (rlz) {
- std::string rlz_copy = *rlz;
- if (ConvertToDynamicRlz(brand, &rlz_copy, access_point)) {
- rlz_store_.SetStringPath(access_point_key, rlz_copy);
- updated = true;
- }
- }
- }
- return updated;
- }
- bool RlzValueStoreChromeOS::AddProductEvent(Product product,
- const char* event_rlz) {
- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- return AddValueToList(GetKeyName(kProductEventKey, product),
- base::Value(event_rlz));
- }
- bool RlzValueStoreChromeOS::ReadProductEvents(
- Product product,
- std::vector<std::string>* events) {
- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- const base::Value* events_list =
- rlz_store_.FindListPath(GetKeyName(kProductEventKey, product));
- if (!events_list)
- return false;
- events->clear();
- bool remove_caf = false;
- for (const base::Value& item : events_list->GetList()) {
- const std::string* event = item.GetIfString();
- if (!event)
- continue;
- if (*event == "CAF" && IsStatefulEvent(product, "CAF"))
- remove_caf = true;
- events->push_back(*event);
- }
- if (remove_caf)
- ClearProductEvent(product, "CAF");
- return true;
- }
- bool RlzValueStoreChromeOS::ClearProductEvent(Product product,
- const char* event_rlz) {
- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- return RemoveValueFromList(GetKeyName(kProductEventKey, product),
- base::Value(event_rlz));
- }
- bool RlzValueStoreChromeOS::ClearAllProductEvents(Product product) {
- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- rlz_store_.RemovePath(GetKeyName(kProductEventKey, product));
- return true;
- }
- bool RlzValueStoreChromeOS::AddStatefulEvent(Product product,
- const char* event_rlz) {
- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- if (strcmp(event_rlz, "CAF") == 0)
- SetRlzPingSent(/*retry_count=*/0);
- return AddValueToList(GetKeyName(kStatefulEventKey, product),
- base::Value(event_rlz));
- }
- bool RlzValueStoreChromeOS::IsStatefulEvent(Product product,
- const char* event_rlz) {
- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- const bool event_exists = ListContainsValue(
- GetKeyName(kStatefulEventKey, product), base::Value(event_rlz));
- if (strcmp(event_rlz, "CAF") == 0) {
- chromeos::system::StatisticsProvider* stats =
- chromeos::system::StatisticsProvider::GetInstance();
- std::string should_send_rlz_ping_value;
- if (stats->GetMachineStatistic(chromeos::system::kShouldSendRlzPingKey,
- &should_send_rlz_ping_value)) {
- if (should_send_rlz_ping_value ==
- chromeos::system::kShouldSendRlzPingValueFalse) {
- return true;
- } else if (should_send_rlz_ping_value !=
- chromeos::system::kShouldSendRlzPingValueTrue) {
- LOG(WARNING) << chromeos::system::kShouldSendRlzPingKey
- << " has an unexpected value: "
- << should_send_rlz_ping_value << ". Treat it as "
- << chromeos::system::kShouldSendRlzPingValueFalse
- << " to avoid sending duplicate rlz ping.";
- return true;
- }
- if (!HasRlzEmbargoEndDatePassed())
- return true;
- } else {
- // If |kShouldSendRlzPingKey| doesn't exist in RW_VPD, treat it in the
- // same way with the case of |kShouldSendRlzPingValueFalse|.
- return true;
- }
- }
- return event_exists;
- }
- bool RlzValueStoreChromeOS::ClearAllStatefulEvents(Product product) {
- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- rlz_store_.RemovePath(GetKeyName(kStatefulEventKey, product));
- return true;
- }
- void RlzValueStoreChromeOS::CollectGarbage() {
- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- NOTIMPLEMENTED();
- }
- // static
- bool RlzValueStoreChromeOS::HasRlzEmbargoEndDatePassed() {
- chromeos::system::StatisticsProvider* statistics_provider =
- chromeos::system::StatisticsProvider::GetInstance();
- return chromeos::system::GetRlzPingEmbargoState(statistics_provider) !=
- chromeos::system::FactoryPingEmbargoState::kNotPassed;
- }
- void RlzValueStoreChromeOS::ReadStore() {
- int error_code = 0;
- std::string error_msg;
- JSONFileValueDeserializer deserializer(store_path_);
- std::unique_ptr<base::Value> value =
- deserializer.Deserialize(&error_code, &error_msg);
- switch (error_code) {
- case JSONFileValueDeserializer::JSON_NO_SUCH_FILE:
- read_only_ = false;
- break;
- case JSONFileValueDeserializer::JSON_NO_ERROR:
- read_only_ = false;
- rlz_store_ = std::move(*value);
- break;
- default:
- LOG(ERROR) << "Error reading RLZ store: " << error_msg;
- }
- }
- void RlzValueStoreChromeOS::WriteStore() {
- std::string json_data;
- JSONStringValueSerializer serializer(&json_data);
- serializer.set_pretty_print(true);
- base::Value copy = CopyWithoutEmptyChildren(rlz_store_)
- .value_or(base::Value(base::Value::Type::DICTIONARY));
- if (!serializer.Serialize(copy)) {
- LOG(ERROR) << "Failed to serialize RLZ data";
- NOTREACHED();
- return;
- }
- if (!base::ImportantFileWriter::WriteFileAtomically(store_path_, json_data))
- LOG(ERROR) << "Error writing RLZ store";
- }
- bool RlzValueStoreChromeOS::AddValueToList(const std::string& list_name,
- base::Value value) {
- base::Value::List* list =
- rlz_store_.GetDict().FindListByDottedPath(list_name);
- if (!list) {
- list = &rlz_store_.SetPath(list_name, base::Value(base::Value::Type::LIST))
- ->GetList();
- }
- if (!base::Contains(*list, value)) {
- list->Append(std::move(value));
- }
- return true;
- }
- bool RlzValueStoreChromeOS::RemoveValueFromList(const std::string& list_name,
- const base::Value& to_remove) {
- base::Value::List* list =
- rlz_store_.GetDict().FindListByDottedPath(list_name);
- if (!list)
- return false;
- list->EraseIf(
- [&to_remove](const base::Value& value) { return value == to_remove; });
- return true;
- }
- bool RlzValueStoreChromeOS::ListContainsValue(const std::string& list_name,
- const base::Value& value) const {
- const base::Value::List* list =
- rlz_store_.GetDict().FindListByDottedPath(list_name);
- if (!list)
- return false;
- return base::Contains(*list, value);
- }
- bool RlzValueStoreChromeOS::HasAccessPointRlz(AccessPoint access_point) const {
- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- const std::string* value =
- rlz_store_.FindStringPath(GetKeyName(kAccessPointKey, access_point));
- return value && !value->empty();
- }
- namespace {
- // RlzValueStoreChromeOS keeps its data in memory and only writes it to disk
- // when ScopedRlzValueStoreLock goes out of scope. Hence, if several
- // ScopedRlzValueStoreLocks are nested, they all need to use the same store
- // object.
- RecursiveCrossProcessLock g_recursive_lock =
- RECURSIVE_CROSS_PROCESS_LOCK_INITIALIZER;
- // This counts the nesting depth of |ScopedRlzValueStoreLock|.
- int g_lock_depth = 0;
- // This is the shared store object. Non-|NULL| only when |g_lock_depth > 0|.
- RlzValueStoreChromeOS* g_store = NULL;
- } // namespace
- ScopedRlzValueStoreLock::ScopedRlzValueStoreLock() {
- bool got_cross_process_lock =
- g_recursive_lock.TryGetCrossProcessLock(GetRlzStoreLockPath());
- // At this point, we hold the in-process lock, no matter the value of
- // |got_cross_process_lock|.
- ++g_lock_depth;
- if (!got_cross_process_lock) {
- // Acquiring cross-process lock failed, so simply return here.
- // In-process lock will be released in dtor.
- DCHECK(!g_store);
- return;
- }
- if (g_lock_depth > 1) {
- // Reuse the already existing store object.
- DCHECK(g_store);
- store_.reset(g_store);
- return;
- }
- // This is the topmost lock, create a new store object.
- DCHECK(!g_store);
- g_store = new RlzValueStoreChromeOS(GetRlzStorePath());
- store_.reset(g_store);
- }
- ScopedRlzValueStoreLock::~ScopedRlzValueStoreLock() {
- --g_lock_depth;
- DCHECK_GE(g_lock_depth, 0);
- if (g_lock_depth > 0) {
- // Other locks are still using store_, so don't free it yet.
- std::ignore = store_.release();
- return;
- }
- g_store = NULL;
- g_recursive_lock.ReleaseLock();
- }
- RlzValueStore* ScopedRlzValueStoreLock::GetStore() {
- return store_.get();
- }
- namespace testing {
- void SetRlzStoreDirectory(const base::FilePath& directory) {
- g_testing_rlz_store_path.Get() = directory;
- }
- std::string RlzStoreFilenameStr() {
- return GetRlzStorePath().value();
- }
- } // namespace testing
- } // namespace rlz_lib
|