blob: d98015db3680a5e090c9ae7ed6122b10fcd1b967 [file] [log] [blame]
Alex Vakulenko07216fe2014-09-19 15:31:09 -07001// 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 Wileyf4d8d7b2015-02-06 13:59:20 -080012#include <chromeos/key_value_store.h>
Alex Vakulenko07216fe2014-09-19 15:31:09 -070013#include <chromeos/strings/string_utils.h>
14
15#include "buffet/states/error_codes.h"
Alex Vakulenko57123b22014-10-28 13:50:16 -070016#include "buffet/states/state_change_queue_interface.h"
Alex Vakulenko07216fe2014-09-19 15:31:09 -070017#include "buffet/utils.h"
18
19namespace buffet {
20
Christopher Wileyf4d8d7b2015-02-06 13:59:20 -080021namespace {
22
23const char kBaseStateFirmwareVersion[] = "base.firmwareVersion";
24
25} // namespace
26
27
Alex Vakulenko57123b22014-10-28 13:50:16 -070028StateManager::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 Vakulenko07216fe2014-09-19 15:31:09 -070033void 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 Wileyf4d8d7b2015-02-06 13:59:20 -080074
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 Vakulenko07216fe2014-09-19 15:31:09 -070091}
92
93std::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 Vakulenkoff73cf22014-10-29 09:53:52 -0700107bool StateManager::SetPropertyValue(const std::string& full_property_name,
108 const chromeos::Any& value,
109 const base::Time& timestamp,
110 chromeos::ErrorPtr* error) {
Alex Vakulenko07216fe2014-09-19 15:31:09 -0700111 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 Vakulenkoac8037d2014-11-11 11:42:05 -0800116 chromeos::Error::AddTo(error, FROM_HERE, errors::state::kDomain,
Alex Vakulenko07216fe2014-09-19 15:31:09 -0700117 errors::state::kPropertyNameMissing,
118 "Property name is missing");
119 return false;
120 }
121 if (!split || package_name.empty()) {
Alex Vakulenkoac8037d2014-11-11 11:42:05 -0800122 chromeos::Error::AddTo(error, FROM_HERE, errors::state::kDomain,
Alex Vakulenko07216fe2014-09-19 15:31:09 -0700123 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 Vakulenkoac8037d2014-11-11 11:42:05 -0800129 chromeos::Error::AddToPrintf(error, FROM_HERE, errors::state::kDomain,
Alex Vakulenko07216fe2014-09-19 15:31:09 -0700130 errors::state::kPropertyNotDefined,
131 "Unknown state property package '%s'",
132 package_name.c_str());
133 return false;
134 }
Alex Vakulenkoff73cf22014-10-29 09:53:52 -0700135 if (!package->SetPropertyValue(property_name, value, error))
Alex Vakulenko57123b22014-10-28 13:50:16 -0700136 return false;
137
Anton Muhin01829452014-11-21 02:16:04 +0400138 native_types::Object prop_set{{full_property_name,
139 package->GetProperty(property_name)}};
Alex Vakulenkoff73cf22014-10-29 09:53:52 -0700140 state_change_queue_->NotifyPropertiesUpdated(timestamp, prop_set);
Alex Vakulenko07216fe2014-09-19 15:31:09 -0700141 return true;
142}
143
Alex Vakulenko57123b22014-10-28 13:50:16 -0700144std::vector<StateChange> StateManager::GetAndClearRecordedStateChanges() {
145 return state_change_queue_->GetAndClearRecordedStateChanges();
146}
147
Alex Vakulenko07216fe2014-09-19 15:31:09 -0700148bool 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 Vakulenkoac8037d2014-11-11 11:42:05 -0800155 chromeos::Error::AddTo(error, FROM_HERE, kErrorDomainBuffet,
156 kInvalidPackageError,
Alex Vakulenko07216fe2014-09-19 15:31:09 -0700157 "State package name is empty");
158 return false;
159 }
160 const base::DictionaryValue* package_dict = nullptr;
161 if (!iter.value().GetAsDictionary(&package_dict)) {
Alex Vakulenkoac8037d2014-11-11 11:42:05 -0800162 chromeos::Error::AddToPrintf(error, FROM_HERE,
163 chromeos::errors::json::kDomain,
Alex Vakulenko07216fe2014-09-19 15:31:09 -0700164 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
181bool 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 Vakulenkoac8037d2014-11-11 11:42:05 -0800189 chromeos::Error::AddToPrintf(error, FROM_HERE, kErrorDomainBuffet,
Alex Vakulenko07216fe2014-09-19 15:31:09 -0700190 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 Vakulenkoac8037d2014-11-11 11:42:05 -0800197 chromeos::Error::AddToPrintf(error, FROM_HERE, kErrorDomainBuffet,
Alex Vakulenko07216fe2014-09-19 15:31:09 -0700198 kFileReadError,
199 "Failed to load file '%s'",
200 json_file_path.value().c_str());
201 return false;
202 }
203 return true;
204}
205
206bool 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 Vakulenkoac8037d2014-11-11 11:42:05 -0800213 chromeos::Error::AddToPrintf(error, FROM_HERE, kErrorDomainBuffet,
Alex Vakulenko07216fe2014-09-19 15:31:09 -0700214 kFileReadError,
215 "Failed to load file '%s'",
216 json_file_path.value().c_str());
217 return false;
218 }
219 return true;
220}
221
222bool 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 Vakulenkoac8037d2014-11-11 11:42:05 -0800228 chromeos::Error::AddTo(error, FROM_HERE, kErrorDomainBuffet,
229 kInvalidPackageError,
Alex Vakulenko07216fe2014-09-19 15:31:09 -0700230 "State package name is empty");
231 return false;
232 }
233 const base::DictionaryValue* package_dict = nullptr;
234 if (!iter.value().GetAsDictionary(&package_dict)) {
Alex Vakulenkoac8037d2014-11-11 11:42:05 -0800235 chromeos::Error::AddToPrintf(error, FROM_HERE,
236 chromeos::errors::json::kDomain,
Alex Vakulenko07216fe2014-09-19 15:31:09 -0700237 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 Vakulenkoac8037d2014-11-11 11:42:05 -0800245 error, FROM_HERE, chromeos::errors::json::kDomain,
Alex Vakulenko07216fe2014-09-19 15:31:09 -0700246 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
258bool 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 Vakulenkoac8037d2014-11-11 11:42:05 -0800265 chromeos::Error::AddToPrintf(error, FROM_HERE, kErrorDomainBuffet,
Alex Vakulenko07216fe2014-09-19 15:31:09 -0700266 kFileReadError,
267 "Failed to load file '%s'",
268 json_file_path.value().c_str());
269 return false;
270 }
271 return true;
272}
273
274StatePackage* 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
279StatePackage* 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