Alex Vakulenko | 07216fe | 2014-09-19 15:31:09 -0700 | [diff] [blame] | 1 | // Copyright 2014 The Chromium OS Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
| 5 | #include "buffet/states/state_manager.h" |
| 6 | |
| 7 | #include <base/files/file_enumerator.h> |
| 8 | #include <base/files/file_path.h> |
| 9 | #include <base/logging.h> |
| 10 | #include <base/values.h> |
| 11 | #include <chromeos/errors/error_codes.h> |
Christopher Wiley | f4d8d7b | 2015-02-06 13:59:20 -0800 | [diff] [blame] | 12 | #include <chromeos/key_value_store.h> |
Alex Vakulenko | 07216fe | 2014-09-19 15:31:09 -0700 | [diff] [blame] | 13 | #include <chromeos/strings/string_utils.h> |
| 14 | |
| 15 | #include "buffet/states/error_codes.h" |
Alex Vakulenko | 57123b2 | 2014-10-28 13:50:16 -0700 | [diff] [blame] | 16 | #include "buffet/states/state_change_queue_interface.h" |
Alex Vakulenko | 07216fe | 2014-09-19 15:31:09 -0700 | [diff] [blame] | 17 | #include "buffet/utils.h" |
| 18 | |
| 19 | namespace buffet { |
| 20 | |
Christopher Wiley | f4d8d7b | 2015-02-06 13:59:20 -0800 | [diff] [blame] | 21 | namespace { |
| 22 | |
| 23 | const char kBaseStateFirmwareVersion[] = "base.firmwareVersion"; |
| 24 | |
| 25 | } // namespace |
| 26 | |
| 27 | |
Alex Vakulenko | 57123b2 | 2014-10-28 13:50:16 -0700 | [diff] [blame] | 28 | StateManager::StateManager(StateChangeQueueInterface* state_change_queue) |
| 29 | : state_change_queue_(state_change_queue) { |
| 30 | CHECK(state_change_queue_) << "State change queue not specified"; |
| 31 | } |
| 32 | |
Alex Vakulenko | 07216fe | 2014-09-19 15:31:09 -0700 | [diff] [blame] | 33 | void StateManager::Startup() { |
| 34 | LOG(INFO) << "Initializing StateManager."; |
| 35 | |
| 36 | // Load standard device state definition. |
| 37 | base::FilePath base_state_file("/etc/buffet/base_state.schema.json"); |
| 38 | LOG(INFO) << "Loading standard state definition from " |
| 39 | << base_state_file.value(); |
| 40 | CHECK(LoadBaseStateDefinition(base_state_file, nullptr)) |
| 41 | << "Failed to load the standard state definition file."; |
| 42 | |
| 43 | // Load component-specific device state definitions. |
| 44 | base::FilePath device_state_dir("/etc/buffet/states"); |
| 45 | base::FileEnumerator enumerator(device_state_dir, false, |
| 46 | base::FileEnumerator::FILES, |
| 47 | FILE_PATH_LITERAL("*.schema.json")); |
| 48 | base::FilePath json_file_path = enumerator.Next(); |
| 49 | while (!json_file_path.empty()) { |
| 50 | LOG(INFO) << "Loading state definition from " << json_file_path.value(); |
| 51 | CHECK(LoadStateDefinition(json_file_path, nullptr)) |
| 52 | << "Failed to load the state definition file."; |
| 53 | json_file_path = enumerator.Next(); |
| 54 | } |
| 55 | |
| 56 | // Load standard device state defaults. |
| 57 | base::FilePath base_state_defaults("/etc/buffet/base_state.defaults.json"); |
| 58 | LOG(INFO) << "Loading base state defaults from " |
| 59 | << base_state_defaults.value(); |
| 60 | CHECK(LoadStateDefaults(base_state_defaults, nullptr)) |
| 61 | << "Failed to load the base state defaults."; |
| 62 | |
| 63 | // Load component-specific device state defaults. |
| 64 | base::FileEnumerator enumerator2(device_state_dir, false, |
| 65 | base::FileEnumerator::FILES, |
| 66 | FILE_PATH_LITERAL("*.defaults.json")); |
| 67 | json_file_path = enumerator2.Next(); |
| 68 | while (!json_file_path.empty()) { |
| 69 | LOG(INFO) << "Loading state defaults from " << json_file_path.value(); |
| 70 | CHECK(LoadStateDefaults(json_file_path, nullptr)) |
| 71 | << "Failed to load the state defaults."; |
| 72 | json_file_path = enumerator2.Next(); |
| 73 | } |
Christopher Wiley | f4d8d7b | 2015-02-06 13:59:20 -0800 | [diff] [blame] | 74 | |
| 75 | // Populate state fields that belong to the system. |
| 76 | base::FilePath lsb_release_path("/etc/lsb-release"); |
| 77 | chromeos::KeyValueStore lsb_release_store; |
| 78 | std::string firmware_version; |
| 79 | if (lsb_release_store.Load(lsb_release_path)) { |
| 80 | if (!lsb_release_store.GetString("CHROMEOS_RELEASE_VERSION", |
| 81 | &firmware_version)) |
| 82 | LOG(ERROR) << "Missing key for firmware version in version file."; |
| 83 | |
| 84 | } else { |
| 85 | LOG(ERROR) << "Failed to read file for firmwareVersion."; |
| 86 | } |
| 87 | CHECK(SetPropertyValue(kBaseStateFirmwareVersion, |
| 88 | firmware_version, |
| 89 | base::Time::Now(), |
| 90 | nullptr)); |
Alex Vakulenko | 07216fe | 2014-09-19 15:31:09 -0700 | [diff] [blame] | 91 | } |
| 92 | |
| 93 | std::unique_ptr<base::DictionaryValue> StateManager::GetStateValuesAsJson( |
| 94 | chromeos::ErrorPtr* error) const { |
| 95 | std::unique_ptr<base::DictionaryValue> dict{new base::DictionaryValue}; |
| 96 | for (const auto& pair : packages_) { |
| 97 | auto pkg_value = pair.second->GetValuesAsJson(error); |
| 98 | if (!pkg_value) { |
| 99 | dict.reset(); |
| 100 | break; |
| 101 | } |
| 102 | dict->SetWithoutPathExpansion(pair.first, pkg_value.release()); |
| 103 | } |
| 104 | return dict; |
| 105 | } |
| 106 | |
Alex Vakulenko | ff73cf2 | 2014-10-29 09:53:52 -0700 | [diff] [blame] | 107 | bool StateManager::SetPropertyValue(const std::string& full_property_name, |
| 108 | const chromeos::Any& value, |
| 109 | const base::Time& timestamp, |
| 110 | chromeos::ErrorPtr* error) { |
Alex Vakulenko | 07216fe | 2014-09-19 15:31:09 -0700 | [diff] [blame] | 111 | std::string package_name; |
| 112 | std::string property_name; |
| 113 | bool split = chromeos::string_utils::SplitAtFirst( |
| 114 | full_property_name, '.', &package_name, &property_name); |
| 115 | if (full_property_name.empty() || (split && property_name.empty())) { |
Alex Vakulenko | ac8037d | 2014-11-11 11:42:05 -0800 | [diff] [blame] | 116 | chromeos::Error::AddTo(error, FROM_HERE, errors::state::kDomain, |
Alex Vakulenko | 07216fe | 2014-09-19 15:31:09 -0700 | [diff] [blame] | 117 | errors::state::kPropertyNameMissing, |
| 118 | "Property name is missing"); |
| 119 | return false; |
| 120 | } |
| 121 | if (!split || package_name.empty()) { |
Alex Vakulenko | ac8037d | 2014-11-11 11:42:05 -0800 | [diff] [blame] | 122 | chromeos::Error::AddTo(error, FROM_HERE, errors::state::kDomain, |
Alex Vakulenko | 07216fe | 2014-09-19 15:31:09 -0700 | [diff] [blame] | 123 | errors::state::kPackageNameMissing, |
| 124 | "Package name is missing in the property name"); |
| 125 | return false; |
| 126 | } |
| 127 | StatePackage* package = FindPackage(package_name); |
| 128 | if (package == nullptr) { |
Alex Vakulenko | ac8037d | 2014-11-11 11:42:05 -0800 | [diff] [blame] | 129 | chromeos::Error::AddToPrintf(error, FROM_HERE, errors::state::kDomain, |
Alex Vakulenko | 07216fe | 2014-09-19 15:31:09 -0700 | [diff] [blame] | 130 | errors::state::kPropertyNotDefined, |
| 131 | "Unknown state property package '%s'", |
| 132 | package_name.c_str()); |
| 133 | return false; |
| 134 | } |
Alex Vakulenko | ff73cf2 | 2014-10-29 09:53:52 -0700 | [diff] [blame] | 135 | if (!package->SetPropertyValue(property_name, value, error)) |
Alex Vakulenko | 57123b2 | 2014-10-28 13:50:16 -0700 | [diff] [blame] | 136 | return false; |
| 137 | |
Anton Muhin | 0182945 | 2014-11-21 02:16:04 +0400 | [diff] [blame] | 138 | native_types::Object prop_set{{full_property_name, |
| 139 | package->GetProperty(property_name)}}; |
Alex Vakulenko | ff73cf2 | 2014-10-29 09:53:52 -0700 | [diff] [blame] | 140 | state_change_queue_->NotifyPropertiesUpdated(timestamp, prop_set); |
Alex Vakulenko | 07216fe | 2014-09-19 15:31:09 -0700 | [diff] [blame] | 141 | return true; |
| 142 | } |
| 143 | |
Alex Vakulenko | 57123b2 | 2014-10-28 13:50:16 -0700 | [diff] [blame] | 144 | std::vector<StateChange> StateManager::GetAndClearRecordedStateChanges() { |
| 145 | return state_change_queue_->GetAndClearRecordedStateChanges(); |
| 146 | } |
| 147 | |
Alex Vakulenko | 07216fe | 2014-09-19 15:31:09 -0700 | [diff] [blame] | 148 | bool StateManager::LoadStateDefinition(const base::DictionaryValue& json, |
| 149 | const std::string& category, |
| 150 | chromeos::ErrorPtr* error) { |
| 151 | base::DictionaryValue::Iterator iter(json); |
| 152 | while (!iter.IsAtEnd()) { |
| 153 | std::string package_name = iter.key(); |
| 154 | if (package_name.empty()) { |
Alex Vakulenko | ac8037d | 2014-11-11 11:42:05 -0800 | [diff] [blame] | 155 | chromeos::Error::AddTo(error, FROM_HERE, kErrorDomainBuffet, |
| 156 | kInvalidPackageError, |
Alex Vakulenko | 07216fe | 2014-09-19 15:31:09 -0700 | [diff] [blame] | 157 | "State package name is empty"); |
| 158 | return false; |
| 159 | } |
| 160 | const base::DictionaryValue* package_dict = nullptr; |
| 161 | if (!iter.value().GetAsDictionary(&package_dict)) { |
Alex Vakulenko | ac8037d | 2014-11-11 11:42:05 -0800 | [diff] [blame] | 162 | chromeos::Error::AddToPrintf(error, FROM_HERE, |
| 163 | chromeos::errors::json::kDomain, |
Alex Vakulenko | 07216fe | 2014-09-19 15:31:09 -0700 | [diff] [blame] | 164 | chromeos::errors::json::kObjectExpected, |
| 165 | "State package '%s' must be an object", |
| 166 | package_name.c_str()); |
| 167 | return false; |
| 168 | } |
| 169 | StatePackage* package = FindOrCreatePackage(package_name); |
| 170 | CHECK(package) << "Unable to create state package " << package_name; |
| 171 | if (!package->AddSchemaFromJson(package_dict, error)) |
| 172 | return false; |
| 173 | iter.Advance(); |
| 174 | } |
| 175 | if (category != kDefaultCategory) |
| 176 | categories_.insert(category); |
| 177 | |
| 178 | return true; |
| 179 | } |
| 180 | |
| 181 | bool StateManager::LoadStateDefinition(const base::FilePath& json_file_path, |
| 182 | chromeos::ErrorPtr* error) { |
| 183 | std::unique_ptr<const base::DictionaryValue> json = |
| 184 | LoadJsonDict(json_file_path, error); |
| 185 | if (!json) |
| 186 | return false; |
| 187 | std::string category = json_file_path.BaseName().RemoveExtension().value(); |
| 188 | if (category == kDefaultCategory) { |
Alex Vakulenko | ac8037d | 2014-11-11 11:42:05 -0800 | [diff] [blame] | 189 | chromeos::Error::AddToPrintf(error, FROM_HERE, kErrorDomainBuffet, |
Alex Vakulenko | 07216fe | 2014-09-19 15:31:09 -0700 | [diff] [blame] | 190 | kInvalidCategoryError, |
| 191 | "Invalid state category specified in '%s'", |
| 192 | json_file_path.value().c_str()); |
| 193 | return false; |
| 194 | } |
| 195 | |
| 196 | if (!LoadStateDefinition(*json, category, error)) { |
Alex Vakulenko | ac8037d | 2014-11-11 11:42:05 -0800 | [diff] [blame] | 197 | chromeos::Error::AddToPrintf(error, FROM_HERE, kErrorDomainBuffet, |
Alex Vakulenko | 07216fe | 2014-09-19 15:31:09 -0700 | [diff] [blame] | 198 | kFileReadError, |
| 199 | "Failed to load file '%s'", |
| 200 | json_file_path.value().c_str()); |
| 201 | return false; |
| 202 | } |
| 203 | return true; |
| 204 | } |
| 205 | |
| 206 | bool StateManager::LoadBaseStateDefinition(const base::FilePath& json_file_path, |
| 207 | chromeos::ErrorPtr* error) { |
| 208 | std::unique_ptr<const base::DictionaryValue> json = |
| 209 | LoadJsonDict(json_file_path, error); |
| 210 | if (!json) |
| 211 | return false; |
| 212 | if (!LoadStateDefinition(*json, kDefaultCategory, error)) { |
Alex Vakulenko | ac8037d | 2014-11-11 11:42:05 -0800 | [diff] [blame] | 213 | chromeos::Error::AddToPrintf(error, FROM_HERE, kErrorDomainBuffet, |
Alex Vakulenko | 07216fe | 2014-09-19 15:31:09 -0700 | [diff] [blame] | 214 | kFileReadError, |
| 215 | "Failed to load file '%s'", |
| 216 | json_file_path.value().c_str()); |
| 217 | return false; |
| 218 | } |
| 219 | return true; |
| 220 | } |
| 221 | |
| 222 | bool StateManager::LoadStateDefaults(const base::DictionaryValue& json, |
| 223 | chromeos::ErrorPtr* error) { |
| 224 | base::DictionaryValue::Iterator iter(json); |
| 225 | while (!iter.IsAtEnd()) { |
| 226 | std::string package_name = iter.key(); |
| 227 | if (package_name.empty()) { |
Alex Vakulenko | ac8037d | 2014-11-11 11:42:05 -0800 | [diff] [blame] | 228 | chromeos::Error::AddTo(error, FROM_HERE, kErrorDomainBuffet, |
| 229 | kInvalidPackageError, |
Alex Vakulenko | 07216fe | 2014-09-19 15:31:09 -0700 | [diff] [blame] | 230 | "State package name is empty"); |
| 231 | return false; |
| 232 | } |
| 233 | const base::DictionaryValue* package_dict = nullptr; |
| 234 | if (!iter.value().GetAsDictionary(&package_dict)) { |
Alex Vakulenko | ac8037d | 2014-11-11 11:42:05 -0800 | [diff] [blame] | 235 | chromeos::Error::AddToPrintf(error, FROM_HERE, |
| 236 | chromeos::errors::json::kDomain, |
Alex Vakulenko | 07216fe | 2014-09-19 15:31:09 -0700 | [diff] [blame] | 237 | chromeos::errors::json::kObjectExpected, |
| 238 | "State package '%s' must be an object", |
| 239 | package_name.c_str()); |
| 240 | return false; |
| 241 | } |
| 242 | StatePackage* package = FindPackage(package_name); |
| 243 | if (package == nullptr) { |
| 244 | chromeos::Error::AddToPrintf( |
Alex Vakulenko | ac8037d | 2014-11-11 11:42:05 -0800 | [diff] [blame] | 245 | error, FROM_HERE, chromeos::errors::json::kDomain, |
Alex Vakulenko | 07216fe | 2014-09-19 15:31:09 -0700 | [diff] [blame] | 246 | chromeos::errors::json::kObjectExpected, |
| 247 | "Providing values for undefined state package '%s'", |
| 248 | package_name.c_str()); |
| 249 | return false; |
| 250 | } |
| 251 | if (!package->AddValuesFromJson(package_dict, error)) |
| 252 | return false; |
| 253 | iter.Advance(); |
| 254 | } |
| 255 | return true; |
| 256 | } |
| 257 | |
| 258 | bool StateManager::LoadStateDefaults(const base::FilePath& json_file_path, |
| 259 | chromeos::ErrorPtr* error) { |
| 260 | std::unique_ptr<const base::DictionaryValue> json = |
| 261 | LoadJsonDict(json_file_path, error); |
| 262 | if (!json) |
| 263 | return false; |
| 264 | if (!LoadStateDefaults(*json, error)) { |
Alex Vakulenko | ac8037d | 2014-11-11 11:42:05 -0800 | [diff] [blame] | 265 | chromeos::Error::AddToPrintf(error, FROM_HERE, kErrorDomainBuffet, |
Alex Vakulenko | 07216fe | 2014-09-19 15:31:09 -0700 | [diff] [blame] | 266 | kFileReadError, |
| 267 | "Failed to load file '%s'", |
| 268 | json_file_path.value().c_str()); |
| 269 | return false; |
| 270 | } |
| 271 | return true; |
| 272 | } |
| 273 | |
| 274 | StatePackage* StateManager::FindPackage(const std::string& package_name) { |
| 275 | auto it = packages_.find(package_name); |
| 276 | return (it != packages_.end()) ? it->second.get() : nullptr; |
| 277 | } |
| 278 | |
| 279 | StatePackage* StateManager::FindOrCreatePackage( |
| 280 | const std::string& package_name) { |
| 281 | StatePackage* package = FindPackage(package_name); |
| 282 | if (package == nullptr) { |
| 283 | std::unique_ptr<StatePackage> new_package{new StatePackage(package_name)}; |
| 284 | package = packages_.emplace(package_name, |
| 285 | std::move(new_package)).first->second.get(); |
| 286 | } |
| 287 | return package; |
| 288 | } |
| 289 | |
| 290 | } // namespace buffet |