| // Copyright 2014 The Chromium OS 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 "buffet/states/state_manager.h" |
| |
| #include <base/files/file_enumerator.h> |
| #include <base/files/file_path.h> |
| #include <base/logging.h> |
| #include <base/values.h> |
| #include <chromeos/errors/error_codes.h> |
| #include <chromeos/key_value_store.h> |
| #include <chromeos/strings/string_utils.h> |
| |
| #include "buffet/states/error_codes.h" |
| #include "buffet/states/state_change_queue_interface.h" |
| #include "buffet/utils.h" |
| |
| namespace buffet { |
| |
| namespace { |
| |
| const char kBaseStateFirmwareVersion[] = "base.firmwareVersion"; |
| |
| } // namespace |
| |
| |
| StateManager::StateManager(StateChangeQueueInterface* state_change_queue) |
| : state_change_queue_(state_change_queue) { |
| CHECK(state_change_queue_) << "State change queue not specified"; |
| } |
| |
| void StateManager::Startup() { |
| LOG(INFO) << "Initializing StateManager."; |
| |
| // Load standard device state definition. |
| base::FilePath base_state_file("/etc/buffet/base_state.schema.json"); |
| LOG(INFO) << "Loading standard state definition from " |
| << base_state_file.value(); |
| CHECK(LoadBaseStateDefinition(base_state_file, nullptr)) |
| << "Failed to load the standard state definition file."; |
| |
| // Load component-specific device state definitions. |
| base::FilePath device_state_dir("/etc/buffet/states"); |
| base::FileEnumerator enumerator(device_state_dir, false, |
| base::FileEnumerator::FILES, |
| FILE_PATH_LITERAL("*.schema.json")); |
| base::FilePath json_file_path = enumerator.Next(); |
| while (!json_file_path.empty()) { |
| LOG(INFO) << "Loading state definition from " << json_file_path.value(); |
| CHECK(LoadStateDefinition(json_file_path, nullptr)) |
| << "Failed to load the state definition file."; |
| json_file_path = enumerator.Next(); |
| } |
| |
| // Load standard device state defaults. |
| base::FilePath base_state_defaults("/etc/buffet/base_state.defaults.json"); |
| LOG(INFO) << "Loading base state defaults from " |
| << base_state_defaults.value(); |
| CHECK(LoadStateDefaults(base_state_defaults, nullptr)) |
| << "Failed to load the base state defaults."; |
| |
| // Load component-specific device state defaults. |
| base::FileEnumerator enumerator2(device_state_dir, false, |
| base::FileEnumerator::FILES, |
| FILE_PATH_LITERAL("*.defaults.json")); |
| json_file_path = enumerator2.Next(); |
| while (!json_file_path.empty()) { |
| LOG(INFO) << "Loading state defaults from " << json_file_path.value(); |
| CHECK(LoadStateDefaults(json_file_path, nullptr)) |
| << "Failed to load the state defaults."; |
| json_file_path = enumerator2.Next(); |
| } |
| |
| // Populate state fields that belong to the system. |
| base::FilePath lsb_release_path("/etc/lsb-release"); |
| chromeos::KeyValueStore lsb_release_store; |
| std::string firmware_version; |
| if (lsb_release_store.Load(lsb_release_path)) { |
| if (!lsb_release_store.GetString("CHROMEOS_RELEASE_VERSION", |
| &firmware_version)) |
| LOG(ERROR) << "Missing key for firmware version in version file."; |
| |
| } else { |
| LOG(ERROR) << "Failed to read file for firmwareVersion."; |
| } |
| CHECK(SetPropertyValue(kBaseStateFirmwareVersion, |
| firmware_version, |
| base::Time::Now(), |
| nullptr)); |
| } |
| |
| std::unique_ptr<base::DictionaryValue> StateManager::GetStateValuesAsJson( |
| chromeos::ErrorPtr* error) const { |
| std::unique_ptr<base::DictionaryValue> dict{new base::DictionaryValue}; |
| for (const auto& pair : packages_) { |
| auto pkg_value = pair.second->GetValuesAsJson(error); |
| if (!pkg_value) { |
| dict.reset(); |
| break; |
| } |
| dict->SetWithoutPathExpansion(pair.first, pkg_value.release()); |
| } |
| return dict; |
| } |
| |
| bool StateManager::SetPropertyValue(const std::string& full_property_name, |
| const chromeos::Any& value, |
| const base::Time& timestamp, |
| chromeos::ErrorPtr* error) { |
| std::string package_name; |
| std::string property_name; |
| bool split = chromeos::string_utils::SplitAtFirst( |
| full_property_name, ".", &package_name, &property_name); |
| if (full_property_name.empty() || (split && property_name.empty())) { |
| chromeos::Error::AddTo(error, FROM_HERE, errors::state::kDomain, |
| errors::state::kPropertyNameMissing, |
| "Property name is missing"); |
| return false; |
| } |
| if (!split || package_name.empty()) { |
| chromeos::Error::AddTo(error, FROM_HERE, errors::state::kDomain, |
| errors::state::kPackageNameMissing, |
| "Package name is missing in the property name"); |
| return false; |
| } |
| StatePackage* package = FindPackage(package_name); |
| if (package == nullptr) { |
| chromeos::Error::AddToPrintf(error, FROM_HERE, errors::state::kDomain, |
| errors::state::kPropertyNotDefined, |
| "Unknown state property package '%s'", |
| package_name.c_str()); |
| return false; |
| } |
| if (!package->SetPropertyValue(property_name, value, error)) |
| return false; |
| |
| native_types::Object prop_set{{full_property_name, |
| package->GetProperty(property_name)}}; |
| state_change_queue_->NotifyPropertiesUpdated(timestamp, prop_set); |
| return true; |
| } |
| |
| std::vector<StateChange> StateManager::GetAndClearRecordedStateChanges() { |
| return state_change_queue_->GetAndClearRecordedStateChanges(); |
| } |
| |
| bool StateManager::LoadStateDefinition(const base::DictionaryValue& json, |
| const std::string& category, |
| chromeos::ErrorPtr* error) { |
| base::DictionaryValue::Iterator iter(json); |
| while (!iter.IsAtEnd()) { |
| std::string package_name = iter.key(); |
| if (package_name.empty()) { |
| chromeos::Error::AddTo(error, FROM_HERE, kErrorDomainBuffet, |
| kInvalidPackageError, |
| "State package name is empty"); |
| return false; |
| } |
| const base::DictionaryValue* package_dict = nullptr; |
| if (!iter.value().GetAsDictionary(&package_dict)) { |
| chromeos::Error::AddToPrintf(error, FROM_HERE, |
| chromeos::errors::json::kDomain, |
| chromeos::errors::json::kObjectExpected, |
| "State package '%s' must be an object", |
| package_name.c_str()); |
| return false; |
| } |
| StatePackage* package = FindOrCreatePackage(package_name); |
| CHECK(package) << "Unable to create state package " << package_name; |
| if (!package->AddSchemaFromJson(package_dict, error)) |
| return false; |
| iter.Advance(); |
| } |
| if (category != kDefaultCategory) |
| categories_.insert(category); |
| |
| return true; |
| } |
| |
| bool StateManager::LoadStateDefinition(const base::FilePath& json_file_path, |
| chromeos::ErrorPtr* error) { |
| std::unique_ptr<const base::DictionaryValue> json = |
| LoadJsonDict(json_file_path, error); |
| if (!json) |
| return false; |
| std::string category = json_file_path.BaseName().RemoveExtension().value(); |
| if (category == kDefaultCategory) { |
| chromeos::Error::AddToPrintf(error, FROM_HERE, kErrorDomainBuffet, |
| kInvalidCategoryError, |
| "Invalid state category specified in '%s'", |
| json_file_path.value().c_str()); |
| return false; |
| } |
| |
| if (!LoadStateDefinition(*json, category, error)) { |
| chromeos::Error::AddToPrintf(error, FROM_HERE, kErrorDomainBuffet, |
| kFileReadError, |
| "Failed to load file '%s'", |
| json_file_path.value().c_str()); |
| return false; |
| } |
| return true; |
| } |
| |
| bool StateManager::LoadBaseStateDefinition(const base::FilePath& json_file_path, |
| chromeos::ErrorPtr* error) { |
| std::unique_ptr<const base::DictionaryValue> json = |
| LoadJsonDict(json_file_path, error); |
| if (!json) |
| return false; |
| if (!LoadStateDefinition(*json, kDefaultCategory, error)) { |
| chromeos::Error::AddToPrintf(error, FROM_HERE, kErrorDomainBuffet, |
| kFileReadError, |
| "Failed to load file '%s'", |
| json_file_path.value().c_str()); |
| return false; |
| } |
| return true; |
| } |
| |
| bool StateManager::LoadStateDefaults(const base::DictionaryValue& json, |
| chromeos::ErrorPtr* error) { |
| base::DictionaryValue::Iterator iter(json); |
| while (!iter.IsAtEnd()) { |
| std::string package_name = iter.key(); |
| if (package_name.empty()) { |
| chromeos::Error::AddTo(error, FROM_HERE, kErrorDomainBuffet, |
| kInvalidPackageError, |
| "State package name is empty"); |
| return false; |
| } |
| const base::DictionaryValue* package_dict = nullptr; |
| if (!iter.value().GetAsDictionary(&package_dict)) { |
| chromeos::Error::AddToPrintf(error, FROM_HERE, |
| chromeos::errors::json::kDomain, |
| chromeos::errors::json::kObjectExpected, |
| "State package '%s' must be an object", |
| package_name.c_str()); |
| return false; |
| } |
| StatePackage* package = FindPackage(package_name); |
| if (package == nullptr) { |
| chromeos::Error::AddToPrintf( |
| error, FROM_HERE, chromeos::errors::json::kDomain, |
| chromeos::errors::json::kObjectExpected, |
| "Providing values for undefined state package '%s'", |
| package_name.c_str()); |
| return false; |
| } |
| if (!package->AddValuesFromJson(package_dict, error)) |
| return false; |
| iter.Advance(); |
| } |
| return true; |
| } |
| |
| bool StateManager::LoadStateDefaults(const base::FilePath& json_file_path, |
| chromeos::ErrorPtr* error) { |
| std::unique_ptr<const base::DictionaryValue> json = |
| LoadJsonDict(json_file_path, error); |
| if (!json) |
| return false; |
| if (!LoadStateDefaults(*json, error)) { |
| chromeos::Error::AddToPrintf(error, FROM_HERE, kErrorDomainBuffet, |
| kFileReadError, |
| "Failed to load file '%s'", |
| json_file_path.value().c_str()); |
| return false; |
| } |
| return true; |
| } |
| |
| StatePackage* StateManager::FindPackage(const std::string& package_name) { |
| auto it = packages_.find(package_name); |
| return (it != packages_.end()) ? it->second.get() : nullptr; |
| } |
| |
| StatePackage* StateManager::FindOrCreatePackage( |
| const std::string& package_name) { |
| StatePackage* package = FindPackage(package_name); |
| if (package == nullptr) { |
| std::unique_ptr<StatePackage> new_package{new StatePackage(package_name)}; |
| package = packages_.emplace(package_name, |
| std::move(new_package)).first->second.get(); |
| } |
| return package; |
| } |
| |
| } // namespace buffet |