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