| // |
| // Copyright (C) 2015 The Android Open Source Project |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| // |
| |
| #include "shill/json_store.h" |
| |
| #include <fcntl.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| |
| #include <cinttypes> |
| #include <memory> |
| #include <typeinfo> |
| #include <utility> |
| #include <vector> |
| |
| #include <base/files/important_file_writer.h> |
| #include <base/files/file_util.h> |
| #include <base/json/json_string_value_serializer.h> |
| #include <base/memory/ptr_util.h> |
| #include <base/strings/string_number_conversions.h> |
| #include <base/strings/string_util.h> |
| #include <base/strings/stringprintf.h> |
| #include <base/values.h> |
| |
| #include "shill/crypto_rot47.h" |
| #include "shill/logging.h" |
| #include "shill/scoped_umask.h" |
| |
| using std::set; |
| using std::string; |
| using std::unique_ptr; |
| using std::vector; |
| |
| namespace shill { |
| |
| namespace Logging { |
| |
| static auto kModuleLogScope = ScopeLogger::kStorage; |
| static string ObjectID(const JsonStore* j) { |
| return "(unknown)"; |
| } |
| |
| } // namespace Logging |
| |
| namespace { |
| |
| static const char kCorruptSuffix[] = ".corrupted"; |
| static const char kCoercedValuePropertyEncodedValue[] = "_encoded_value"; |
| static const char kCoercedValuePropertyNativeType[] = "_native_type"; |
| static const char kNativeTypeNonAsciiString[] = "non_ascii_string"; |
| static const char kNativeTypeUint64[] = "uint64"; |
| static const char kRootPropertyDescription[] = "description"; |
| static const char kRootPropertySettings[] = "settings"; |
| |
| bool DoesGroupContainProperties( |
| const brillo::VariantDictionary& group, |
| const brillo::VariantDictionary& required_properties) { |
| for (const auto& required_property_name_and_value : required_properties) { |
| const auto& required_key = required_property_name_and_value.first; |
| const auto& required_value = required_property_name_and_value.second; |
| const auto& group_it = group.find(required_key); |
| if (group_it == group.end() || group_it->second != required_value) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| // Deserialization helpers. |
| |
| // A coerced value is used to represent values that base::Value does |
| // not directly support. A coerced value has the form |
| // {'_native_type': <type-as-string>, '_encoded_value': <value-as-string>} |
| bool IsCoercedValue(const base::DictionaryValue& value) { |
| return value.HasKey(kCoercedValuePropertyNativeType) && |
| value.HasKey(kCoercedValuePropertyEncodedValue); |
| } |
| |
| unique_ptr<brillo::Any> DecodeCoercedValue( |
| const base::DictionaryValue& coerced_value) { |
| string native_type; |
| if (!coerced_value.GetStringWithoutPathExpansion( |
| kCoercedValuePropertyNativeType, &native_type)) { |
| LOG(ERROR) << "Property |" << kCoercedValuePropertyNativeType |
| << "| is not a string."; |
| return nullptr; |
| } |
| |
| string encoded_value; |
| if (!coerced_value.GetStringWithoutPathExpansion( |
| kCoercedValuePropertyEncodedValue, &encoded_value)) { |
| LOG(ERROR) << "Property |" << kCoercedValuePropertyEncodedValue |
| << "| is not a string."; |
| return nullptr; |
| } |
| |
| if (native_type == kNativeTypeNonAsciiString) { |
| vector<uint8_t> native_value; |
| if (base::HexStringToBytes(encoded_value, &native_value)) { |
| return base::MakeUnique<brillo::Any>( |
| string(native_value.begin(), native_value.end())); |
| } else { |
| LOG(ERROR) << "Failed to decode hex data from |" << encoded_value << "|."; |
| return nullptr; |
| } |
| } else if (native_type == kNativeTypeUint64) { |
| uint64_t native_value; |
| if (base::StringToUint64(encoded_value, &native_value)) { |
| return base::MakeUnique<brillo::Any>(native_value); |
| } else { |
| LOG(ERROR) << "Failed to parse uint64 from |" << encoded_value << "|."; |
| return nullptr; |
| } |
| } else { |
| LOG(ERROR) << "Unsupported native type |" << native_type << "|."; |
| return nullptr; |
| } |
| } |
| |
| unique_ptr<string> MakeStringFromValue(const base::Value& value) { |
| const auto value_type = value.GetType(); |
| |
| if (value_type == base::Value::TYPE_STRING) { |
| auto unwrapped_string = base::MakeUnique<string>(); |
| value.GetAsString(unwrapped_string.get()); |
| return unwrapped_string; |
| } else if (value_type == base::Value::TYPE_DICTIONARY) { |
| const base::DictionaryValue* dictionary_value; |
| value.GetAsDictionary(&dictionary_value); |
| unique_ptr<brillo::Any> decoded_value( |
| DecodeCoercedValue(*dictionary_value)); |
| if (!decoded_value) { |
| LOG(ERROR) << "Failed to decode coerced value."; |
| return nullptr; |
| } |
| |
| if (!decoded_value->IsTypeCompatible<string>()) { |
| LOG(ERROR) << "Can not read |" << brillo::GetUndecoratedTypeName<string>() |
| << "| from |" << decoded_value->GetUndecoratedTypeName() |
| << "."; |
| return nullptr; |
| } |
| return base::MakeUnique<string>(decoded_value->Get<string>()); |
| } else { |
| LOG(ERROR) << "Got unexpected type |" << value_type << "|."; |
| return nullptr; |
| } |
| } |
| |
| unique_ptr<vector<string>> ConvertListValueToStringVector( |
| const base::ListValue& list_value) { |
| const size_t list_len = list_value.GetSize(); |
| for (size_t i = 0; i < list_len; ++i) { |
| const base::Value* list_item; |
| list_value.Get(i, &list_item); |
| const auto item_type = list_item->GetType(); |
| if (item_type != base::Value::TYPE_STRING && |
| item_type != base::Value::TYPE_DICTIONARY) { |
| LOG(ERROR) << "Element " << i << " has type " << item_type << ", " |
| << "instead of expected types " |
| << base::Value::TYPE_STRING << " or " |
| << base::Value::TYPE_DICTIONARY << "."; |
| return nullptr; |
| } |
| } |
| |
| auto result = base::MakeUnique<vector<string>>(); |
| for (size_t i = 0; i < list_len; ++i) { |
| const base::Value* list_item; |
| list_value.Get(i, &list_item); |
| unique_ptr<string> native_string = MakeStringFromValue(*list_item); |
| if (!native_string) { |
| LOG(ERROR) << "Failed to parse string from element " << i << "."; |
| return nullptr; |
| } |
| result->push_back(*native_string); |
| } |
| return result; |
| } |
| |
| unique_ptr<brillo::VariantDictionary> |
| ConvertDictionaryValueToVariantDictionary( |
| const base::DictionaryValue& dictionary_value) { |
| base::DictionaryValue::Iterator it(dictionary_value); |
| auto variant_dictionary = base::MakeUnique<brillo::VariantDictionary>(); |
| while (!it.IsAtEnd()) { |
| const string& key = it.key(); |
| const base::Value& value = it.value(); |
| switch (value.GetType()) { |
| case base::Value::TYPE_NULL: |
| LOG(ERROR) << "Key |" << key << "| has unsupported TYPE_NULL."; |
| return nullptr; |
| case base::Value::TYPE_BOOLEAN: { |
| bool native_bool; |
| value.GetAsBoolean(&native_bool); |
| (*variant_dictionary)[key] = native_bool; |
| break; |
| } |
| case base::Value::TYPE_INTEGER: { |
| int native_int; |
| value.GetAsInteger(&native_int); |
| (*variant_dictionary)[key] = native_int; |
| break; |
| } |
| case base::Value::TYPE_DOUBLE: |
| LOG(ERROR) << "Key |" << key << "| has unsupported TYPE_DOUBLE."; |
| return nullptr; |
| case base::Value::TYPE_STRING: { |
| string native_string; |
| value.GetAsString(&native_string); |
| (*variant_dictionary)[key] = native_string; |
| break; |
| } |
| case base::Value::TYPE_BINARY: |
| /* The JSON parser should never create Values of this type. */ |
| LOG(ERROR) << "Key |" << key << "| has unexpected TYPE_BINARY."; |
| return nullptr; |
| case base::Value::TYPE_DICTIONARY: { |
| const base::DictionaryValue* dictionary_value; |
| value.GetAsDictionary(&dictionary_value); |
| if (!IsCoercedValue(*dictionary_value)) { |
| LOG(ERROR) << "Key |" << key << "| has unsupported TYPE_DICTIONARY."; |
| return nullptr; |
| } |
| unique_ptr<brillo::Any> decoded_coerced_value( |
| DecodeCoercedValue(*dictionary_value)); |
| if (!decoded_coerced_value) { |
| LOG(ERROR) << "Key |" << key << "| could not be decoded."; |
| return nullptr; |
| } |
| (*variant_dictionary)[key] = *decoded_coerced_value; |
| break; |
| } |
| case base::Value::TYPE_LIST: { // Only string lists, for now. |
| const base::ListValue* list_value; |
| value.GetAsList(&list_value); |
| |
| unique_ptr<vector<string>> string_list( |
| ConvertListValueToStringVector(*list_value)); |
| if (!string_list) { |
| LOG(ERROR) << "Key |" << key << "| could not be decoded."; |
| return nullptr; |
| } |
| (*variant_dictionary)[key] = *string_list; |
| break; |
| } |
| } |
| it.Advance(); |
| } |
| return variant_dictionary; |
| } |
| |
| // Serialization helpers. |
| |
| unique_ptr<base::DictionaryValue> MakeCoercedValue( |
| const string& native_type, const string& encoded_value) { |
| auto coerced_value = base::MakeUnique<base::DictionaryValue>(); |
| coerced_value->SetStringWithoutPathExpansion( |
| kCoercedValuePropertyNativeType, native_type); |
| coerced_value->SetStringWithoutPathExpansion( |
| kCoercedValuePropertyEncodedValue, encoded_value); |
| return coerced_value; |
| } |
| |
| unique_ptr<base::Value> MakeValueForString(const string& native_string) { |
| // Strictly speaking, we don't need to escape non-ASCII text, if |
| // that text is UTF-8. Practically speaking, however, it'll be |
| // easier to inspect config files if all non-ASCII strings are |
| // presented as byte sequences. (Unicode has many code points with |
| // similar-looking glyphs.) |
| if (base::IsStringASCII(native_string) && |
| native_string.find('\0') == string::npos) { |
| return base::MakeUnique<base::StringValue>(native_string); |
| } else { |
| const string hex_encoded_string( |
| base::HexEncode(native_string.data(), native_string.size())); |
| return MakeCoercedValue(kNativeTypeNonAsciiString, hex_encoded_string); |
| } |
| } |
| |
| unique_ptr<base::DictionaryValue> ConvertVariantDictionaryToDictionaryValue( |
| const brillo::VariantDictionary& variant_dictionary) { |
| auto dictionary_value = base::MakeUnique<base::DictionaryValue>(); |
| for (const auto& key_and_value : variant_dictionary) { |
| const auto& key = key_and_value.first; |
| const auto& value = key_and_value.second; |
| if (value.IsTypeCompatible<bool>()) { |
| dictionary_value->SetBooleanWithoutPathExpansion(key, value.Get<bool>()); |
| } else if (value.IsTypeCompatible<int32_t>()) { |
| dictionary_value->SetIntegerWithoutPathExpansion(key, value.Get<int>()); |
| } else if (value.IsTypeCompatible<string>()) { |
| dictionary_value->SetWithoutPathExpansion( |
| key, MakeValueForString(value.Get<string>())); |
| } else if (value.IsTypeCompatible<uint64_t>()) { |
| const string encoded_value( |
| base::StringPrintf("%" PRIu64, value.Get<uint64_t>())); |
| dictionary_value->SetWithoutPathExpansion( |
| key, MakeCoercedValue(kNativeTypeUint64, encoded_value)); |
| } else if (value.IsTypeCompatible<vector<string>>()) { |
| auto list_value = base::MakeUnique<base::ListValue>(); |
| for (const auto& string_list_item : value.Get<vector<string>>()) { |
| list_value->Append(MakeValueForString(string_list_item)); |
| } |
| dictionary_value->SetWithoutPathExpansion(key, std::move(list_value)); |
| } else { |
| LOG(ERROR) << "Failed to convert element with key |" << key << "|."; |
| return nullptr; |
| } |
| } |
| return dictionary_value; |
| } |
| |
| } // namespace |
| |
| JsonStore::JsonStore(const base::FilePath& path) |
| : path_(path) { |
| CHECK(!path_.empty()); |
| } |
| |
| bool JsonStore::IsNonEmpty() const { |
| int64_t file_size = 0; |
| return base::GetFileSize(path_, &file_size) && file_size != 0; |
| } |
| |
| bool JsonStore::Open() { |
| if (!IsNonEmpty()) { |
| LOG(INFO) << "Creating a new key file at |" << path_.value() << "|."; |
| return true; |
| } |
| |
| string json_string; |
| if (!base::ReadFileToString(path_, &json_string)) { |
| LOG(ERROR) << "Failed to read data from |" << path_.value() << "|."; |
| return false; |
| } |
| |
| JSONStringValueDeserializer json_deserializer(json_string); |
| json_deserializer.set_allow_trailing_comma(true); |
| string json_error; |
| unique_ptr<base::Value> json_value = |
| json_deserializer.Deserialize(nullptr, &json_error); |
| if (!json_value) { |
| LOG(ERROR) << "Failed to parse JSON data from |" << path_.value() <<"|."; |
| SLOG(this, 5) << json_error; |
| return false; |
| } |
| |
| const base::DictionaryValue* root_dictionary; |
| if (!json_value->GetAsDictionary(&root_dictionary)) { |
| LOG(ERROR) << "JSON value is not a dictionary."; |
| return false; |
| } |
| |
| CHECK(root_dictionary); |
| if (root_dictionary->HasKey(kRootPropertyDescription) && |
| !root_dictionary->GetStringWithoutPathExpansion( |
| kRootPropertyDescription, &file_description_)) { |
| LOG(WARNING) << "Property |" << kRootPropertyDescription |
| << "| is not a string."; |
| // Description is non-critical, so continue processing. |
| } |
| |
| if (!root_dictionary->HasKey(kRootPropertySettings)) { |
| LOG(ERROR) << "Property |" << kRootPropertySettings << "| is missing."; |
| return false; |
| } |
| |
| const base::DictionaryValue* settings_dictionary; |
| if (!root_dictionary->GetDictionaryWithoutPathExpansion( |
| kRootPropertySettings, &settings_dictionary)) { |
| LOG(ERROR) << "Property |" << kRootPropertySettings |
| << "| is not a dictionary."; |
| return false; |
| } |
| |
| if (!group_name_to_settings_.empty()) { |
| LOG(INFO) << "Clearing existing settings on open."; |
| group_name_to_settings_.clear(); |
| } |
| |
| base::DictionaryValue::Iterator it(*settings_dictionary); |
| while (!it.IsAtEnd()) { |
| const string& group_name = it.key(); |
| const base::DictionaryValue* group_settings_as_values; |
| if (!it.value().GetAsDictionary(&group_settings_as_values)) { |
| LOG(ERROR) << "Group |" << group_name << "| is not a dictionary."; |
| return false; |
| } |
| |
| unique_ptr<brillo::VariantDictionary> group_settings_as_variants = |
| ConvertDictionaryValueToVariantDictionary(*group_settings_as_values); |
| if (!group_settings_as_variants) { |
| LOG(ERROR) << "Failed to convert group |" << group_name |
| << "| to variants."; |
| return false; |
| } |
| |
| group_name_to_settings_[group_name] = *group_settings_as_variants; |
| it.Advance(); |
| } |
| |
| return true; |
| } |
| |
| bool JsonStore::Close() { |
| return Flush(); |
| } |
| |
| bool JsonStore::Flush() { |
| auto groups = base::MakeUnique<base::DictionaryValue>(); |
| for (const auto& group_name_and_settings : group_name_to_settings_) { |
| const auto& group_name = group_name_and_settings.first; |
| unique_ptr<base::DictionaryValue> group_settings( |
| ConvertVariantDictionaryToDictionaryValue( |
| group_name_and_settings.second)); |
| if (!group_settings) { |
| // This class maintains the invariant that anything placed in |
| // |group_settings| is convertible. So abort if conversion fails. |
| LOG(FATAL) << "Failed to convert group |" << group_name << "|."; |
| return false; |
| } |
| groups->SetWithoutPathExpansion(group_name, std::move(group_settings)); |
| } |
| |
| base::DictionaryValue root; |
| root.SetStringWithoutPathExpansion( |
| kRootPropertyDescription, file_description_); |
| root.SetWithoutPathExpansion(kRootPropertySettings, std::move(groups)); |
| |
| string json_string; |
| JSONStringValueSerializer json_serializer(&json_string); |
| json_serializer.set_pretty_print(true); |
| if (!json_serializer.Serialize(root)) { |
| LOG(ERROR) << "Failed to serialize to JSON."; |
| return false; |
| } |
| |
| ScopedUmask owner_only_umask(~(S_IRUSR | S_IWUSR) & 0777); |
| if (!base::ImportantFileWriter::WriteFileAtomically(path_, json_string)) { |
| LOG(ERROR) << "Failed to write JSON file: |" << path_.value() << "|."; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool JsonStore::MarkAsCorrupted() { |
| LOG(INFO) << "In " << __func__ << " for " << path_.value(); |
| string corrupted_path = path_.value() + kCorruptSuffix; |
| int ret = rename(path_.value().c_str(), corrupted_path.c_str()); |
| if (ret != 0) { |
| PLOG(ERROR) << "File rename failed."; |
| return false; |
| } |
| return true; |
| } |
| |
| set<string> JsonStore::GetGroups() const { |
| set<string> matching_groups; |
| for (const auto& group_name_and_settings : group_name_to_settings_) { |
| matching_groups.insert(group_name_and_settings.first); |
| } |
| return matching_groups; |
| } |
| |
| // Returns a set so that caller can easily test whether a particular group |
| // is contained within this collection. |
| set<string> JsonStore::GetGroupsWithKey(const string& key) const { |
| set<string> matching_groups; |
| // iterate over groups, find ones with matching key |
| for (const auto& group_name_and_settings : group_name_to_settings_) { |
| const auto& group_name = group_name_and_settings.first; |
| const auto& group_settings = group_name_and_settings.second; |
| if (group_settings.find(key) != group_settings.end()) { |
| matching_groups.insert(group_name); |
| } |
| } |
| return matching_groups; |
| } |
| |
| set<string> JsonStore::GetGroupsWithProperties(const KeyValueStore& properties) |
| const { |
| set<string> matching_groups; |
| const brillo::VariantDictionary& properties_dict(properties.properties()); |
| for (const auto& group_name_and_settings : group_name_to_settings_) { |
| const auto& group_name = group_name_and_settings.first; |
| const auto& group_settings = group_name_and_settings.second; |
| if (DoesGroupContainProperties(group_settings, properties_dict)) { |
| matching_groups.insert(group_name); |
| } |
| } |
| return matching_groups; |
| } |
| |
| bool JsonStore::ContainsGroup(const string& group) const { |
| const auto& it = group_name_to_settings_.find(group); |
| return it != group_name_to_settings_.end(); |
| } |
| |
| bool JsonStore::DeleteKey(const string& group, const string& key) { |
| const auto& group_name_and_settings = group_name_to_settings_.find(group); |
| if (group_name_and_settings == group_name_to_settings_.end()) { |
| LOG(ERROR) << "Could not find group |" << group << "|."; |
| return false; |
| } |
| |
| auto& group_settings = group_name_and_settings->second; |
| auto property_it = group_settings.find(key); |
| if (property_it != group_settings.end()) { |
| group_settings.erase(property_it); |
| } |
| |
| return true; |
| } |
| |
| bool JsonStore::DeleteGroup(const string& group) { |
| auto group_name_and_settings = group_name_to_settings_.find(group); |
| if (group_name_and_settings != group_name_to_settings_.end()) { |
| group_name_to_settings_.erase(group_name_and_settings); |
| } |
| return true; |
| } |
| |
| bool JsonStore::SetHeader(const string& header) { |
| file_description_ = header; |
| return true; |
| } |
| |
| bool JsonStore::GetString(const string& group, |
| const string& key, |
| string* value) const { |
| return ReadSetting(group, key, value); |
| } |
| |
| bool JsonStore::SetString( |
| const string& group, const string& key, const string& value) { |
| return WriteSetting(group, key, value); |
| } |
| |
| bool JsonStore::GetBool(const string& group, const string& key, bool* value) |
| const { |
| return ReadSetting(group, key, value); |
| } |
| |
| bool JsonStore::SetBool(const string& group, const string& key, bool value) { |
| return WriteSetting(group, key, value); |
| } |
| |
| bool JsonStore::GetInt( |
| const string& group, const string& key, int* value) const { |
| return ReadSetting(group, key, value); |
| } |
| |
| bool JsonStore::SetInt(const string& group, const string& key, int value) { |
| return WriteSetting(group, key, value); |
| } |
| |
| bool JsonStore::GetUint64( |
| const string& group, const string& key, uint64_t* value) const { |
| return ReadSetting(group, key, value); |
| } |
| |
| bool JsonStore::SetUint64( |
| const string& group, const string& key, uint64_t value) { |
| return WriteSetting(group, key, value); |
| } |
| |
| bool JsonStore::GetStringList( |
| const string& group, const string& key, vector<string>* value) const { |
| return ReadSetting(group, key, value); |
| } |
| |
| bool JsonStore::SetStringList( |
| const string& group, const string& key, const vector<string>& value) { |
| return WriteSetting(group, key, value); |
| } |
| |
| bool JsonStore::GetCryptedString( |
| const string& group, const string& key, string* value) { |
| string encrypted_value; |
| if (!GetString(group, key, &encrypted_value)) { |
| return false; |
| } |
| |
| // TODO(quiche): Once we've removed the glib dependency in |
| // CryptoProvider, move to using CryptoProvider, instead of |
| // CryptoROT47 directly. This change should be done before using |
| // JsonStore in production, as the on-disk format of crypted strings |
| // will change. |
| CryptoROT47 rot47; |
| string decrypted_value; |
| if (!rot47.Decrypt(encrypted_value, &decrypted_value)) { |
| LOG(ERROR) << "Failed to decrypt value for |" << group << "|" |
| << ":|" << key << "|."; |
| return false; |
| } |
| |
| if (value) { |
| *value = decrypted_value; |
| } |
| return true; |
| } |
| |
| bool JsonStore::SetCryptedString( |
| const string& group, const string& key, const string& value) { |
| CryptoROT47 rot47; |
| string encrypted_value; |
| if (!rot47.Encrypt(value, &encrypted_value)) { |
| LOG(ERROR) << "Failed to encrypt value for |" << group << "|" |
| << ":|" << key << "|."; |
| return false; |
| } |
| |
| return SetString(group, key, encrypted_value); |
| } |
| |
| // Private methods. |
| template<typename T> |
| bool JsonStore::ReadSetting( |
| const string& group, const string& key, T* out) const { |
| const auto& group_name_and_settings = group_name_to_settings_.find(group); |
| if (group_name_and_settings == group_name_to_settings_.end()) { |
| SLOG(this, 10) << "Could not find group |" << group << "|."; |
| return false; |
| } |
| |
| const auto& group_settings = group_name_and_settings->second; |
| const auto& property_name_and_value = group_settings.find(key); |
| if (property_name_and_value == group_settings.end()) { |
| SLOG(this, 10) << "Could not find property |" << key << "|."; |
| return false; |
| } |
| |
| if (!property_name_and_value->second.IsTypeCompatible<T>()) { |
| // We assume that the reader and the writer agree on the exact |
| // type. So we do not allow implicit conversion. |
| LOG(ERROR) << "Can not read |" << brillo::GetUndecoratedTypeName<T>() |
| << "| from |" |
| << property_name_and_value->second.GetUndecoratedTypeName() |
| << "|."; |
| return false; |
| } |
| |
| if (out) { |
| return property_name_and_value->second.GetValue(out); |
| } else { |
| return true; |
| } |
| } |
| |
| template<typename T> |
| bool JsonStore::WriteSetting( |
| const string& group, const string& key, const T& new_value) { |
| auto group_name_and_settings = group_name_to_settings_.find(group); |
| if (group_name_and_settings == group_name_to_settings_.end()) { |
| group_name_to_settings_[group][key] = new_value; |
| return true; |
| } |
| |
| auto& group_settings = group_name_and_settings->second; |
| auto property_name_and_value = group_settings.find(key); |
| if (property_name_and_value == group_settings.end()) { |
| group_settings[key] = new_value; |
| return true; |
| } |
| |
| if (!property_name_and_value->second.IsTypeCompatible<T>()) { |
| SLOG(this, 10) << "New type |" << brillo::GetUndecoratedTypeName<T>() |
| << "| differs from current type |" |
| << property_name_and_value->second.GetUndecoratedTypeName() |
| << "|."; |
| return false; |
| } else { |
| property_name_and_value->second = new_value; |
| return true; |
| } |
| } |
| |
| } // namespace shill |