| // Copyright 2022 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/ash/guest_os/guest_id.h" |
| |
| #include <algorithm> |
| #include <memory> |
| #include <string_view> |
| #include <vector> |
| |
| #include "base/containers/contains.h" |
| #include "base/logging.h" |
| #include "base/no_destructor.h" |
| #include "base/strings/string_split.h" |
| #include "base/strings/stringprintf.h" |
| #include "chrome/browser/ash/guest_os/guest_os_pref_names.h" |
| #include "chrome/browser/ash/guest_os/public/types.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chromeos/ash/components/dbus/vm_applications/apps.pb.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/prefs/scoped_user_pref_update.h" |
| |
| namespace guest_os { |
| namespace { |
| |
| static const base::NoDestructor<std::vector<std::string>> kPropertiesAllowList{{ |
| prefs::kContainerCreateOptions, |
| prefs::kContainerOsVersionKey, |
| prefs::kContainerOsPrettyNameKey, |
| prefs::kContainerColorKey, |
| prefs::kTerminalSupportedKey, |
| prefs::kTerminalLabel, |
| prefs::kTerminalPolicyDisabled, |
| prefs::kContainerSharedVmDevicesKey, |
| prefs::kBruschettaConfigId, |
| }}; |
| |
| } // namespace |
| |
| GuestId::GuestId(VmType vm_type, |
| std::string vm_name, |
| std::string container_name) noexcept |
| : vm_type(vm_type), |
| vm_name(std::move(vm_name)), |
| container_name(std::move(container_name)) {} |
| |
| GuestId::GuestId(std::string vm_name, std::string container_name) noexcept |
| : vm_type(VmType::UNKNOWN), |
| vm_name(std::move(vm_name)), |
| container_name(std::move(container_name)) {} |
| |
| GuestId::GuestId(const base::Value& value) noexcept { |
| const base::Value::Dict* dict = value.GetIfDict(); |
| vm_type = VmTypeFromPref(value); |
| const std::string* vm = nullptr; |
| const std::string* container = nullptr; |
| if (dict != nullptr) { |
| vm = dict->FindString(prefs::kVmNameKey); |
| container = dict->FindString(prefs::kContainerNameKey); |
| } |
| vm_name = vm ? *vm : ""; |
| container_name = container ? *container : ""; |
| } |
| |
| base::flat_map<std::string, std::string> GuestId::ToMap() const { |
| base::flat_map<std::string, std::string> extras; |
| extras[prefs::kVmNameKey] = vm_name; |
| extras[prefs::kContainerNameKey] = container_name; |
| return extras; |
| } |
| |
| base::Value::Dict GuestId::ToDictValue() const { |
| base::Value::Dict dict; |
| dict.Set(prefs::kVmTypeKey, static_cast<int>(vm_type)); |
| dict.Set(prefs::kVmNameKey, vm_name); |
| dict.Set(prefs::kContainerNameKey, container_name); |
| return dict; |
| } |
| |
| bool operator<(const GuestId& lhs, const GuestId& rhs) noexcept { |
| int result = lhs.vm_name.compare(rhs.vm_name); |
| if (result != 0) { |
| return result < 0; |
| } |
| return lhs.container_name < rhs.container_name; |
| } |
| |
| bool operator==(const GuestId& lhs, const GuestId& rhs) noexcept { |
| return lhs.vm_name == rhs.vm_name && lhs.container_name == rhs.container_name; |
| } |
| |
| std::ostream& operator<<(std::ostream& ostream, const GuestId& container_id) { |
| return ostream << "(type:" << container_id.vm_type << ":" |
| << vm_tools::apps::VmType_Name(container_id.vm_type) |
| << " vm:\"" << container_id.vm_name << "\" container:\"" |
| << container_id.container_name << "\")"; |
| } |
| |
| std::string GuestId::Serialize() const { |
| return base::StringPrintf("%s:%s:%s", VmType_Name(this->vm_type), |
| this->vm_name, this->container_name); |
| } |
| |
| std::optional<GuestId> Deserialize(std::string_view guest_id_string) { |
| std::vector<std::string> string_tokens = base::SplitString( |
| guest_id_string, ":", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); |
| |
| if (string_tokens.size() != 3) { |
| return {}; |
| } |
| |
| // vm_type and vm_name should be present, but container name may be empty for |
| // containerless guests. |
| if (string_tokens[0].empty() || string_tokens[1].empty()) { |
| return {}; |
| } |
| |
| VmType vm_type; |
| if (!VmType_Parse(string_tokens[0], &vm_type)) { |
| return {}; |
| } |
| |
| return GuestId(vm_type, string_tokens[1], string_tokens[2]); |
| } |
| |
| bool MatchContainerDict(const base::Value& dict, const GuestId& container_id) { |
| const std::string* vm_name = dict.GetDict().FindString(prefs::kVmNameKey); |
| const std::string* container_name = |
| dict.GetDict().FindString(prefs::kContainerNameKey); |
| return (vm_name && *vm_name == container_id.vm_name) && |
| (container_name && *container_name == container_id.container_name); |
| } |
| |
| std::vector<GuestId> GetContainers(Profile* profile, VmType vm_type) { |
| std::vector<GuestId> result; |
| const base::Value::List& container_list = |
| profile->GetPrefs()->GetList(prefs::kGuestOsContainers); |
| for (const auto& container : container_list) { |
| guest_os::GuestId id(container); |
| if (id.vm_type == vm_type) { |
| result.push_back(std::move(id)); |
| } |
| } |
| return result; |
| } |
| |
| void AddContainerToPrefs(Profile* profile, |
| const GuestId& container_id, |
| base::Value::Dict properties) { |
| ScopedListPrefUpdate updater(profile->GetPrefs(), prefs::kGuestOsContainers); |
| if (std::ranges::any_of(*updater, [&container_id](const auto& dict) { |
| return MatchContainerDict(dict, container_id); |
| })) { |
| return; |
| } |
| |
| base::Value::Dict new_container = container_id.ToDictValue(); |
| for (auto [key, value] : properties) { |
| if (base::Contains(*kPropertiesAllowList, key)) { |
| new_container.Set(key, std::move(value)); |
| } |
| } |
| updater->Append(std::move(new_container)); |
| } |
| |
| void RemoveContainerFromPrefs(Profile* profile, const GuestId& container_id) { |
| auto* pref_service = profile->GetPrefs(); |
| ScopedListPrefUpdate updater(pref_service, prefs::kGuestOsContainers); |
| base::Value::List& update_list = updater.Get(); |
| auto it = std::ranges::find_if(update_list, [&](const auto& dict) { |
| return MatchContainerDict(dict, container_id); |
| }); |
| if (it != update_list.end()) { |
| update_list.erase(it); |
| } |
| } |
| |
| void RemoveVmFromPrefs(Profile* profile, VmType vm_type) { |
| auto* pref_service = profile->GetPrefs(); |
| ScopedListPrefUpdate updater(pref_service, prefs::kGuestOsContainers); |
| base::Value::List& update_list = updater.Get(); |
| auto it = std::ranges::find(update_list, vm_type, &VmTypeFromPref); |
| if (it != update_list.end()) { |
| update_list.erase(it); |
| } |
| } |
| |
| const base::Value* GetContainerPrefValue(Profile* profile, |
| const GuestId& container_id, |
| const std::string& key) { |
| const base::Value::List& containers = |
| profile->GetPrefs()->GetList(prefs::kGuestOsContainers); |
| for (const auto& dict : containers) { |
| if (MatchContainerDict(dict, container_id)) { |
| return dict.GetDict().Find(key); |
| } |
| } |
| return nullptr; |
| } |
| |
| void UpdateContainerPref(Profile* profile, |
| const GuestId& container_id, |
| const std::string& key, |
| base::Value value) { |
| ScopedListPrefUpdate updater(profile->GetPrefs(), prefs::kGuestOsContainers); |
| auto it = std::ranges::find_if(*updater, [&](const auto& dict) { |
| return MatchContainerDict(dict, container_id); |
| }); |
| if (it != updater->end()) { |
| if (base::Contains(*kPropertiesAllowList, key)) { |
| it->GetDict().Set(key, std::move(value)); |
| } else { |
| LOG(ERROR) << "Ignoring disallowed property: " << key; |
| } |
| } |
| } |
| |
| void MergeContainerPref(Profile* profile, |
| const GuestId& container_id, |
| const std::string& key, |
| base::Value::Dict dict) { |
| ScopedListPrefUpdate updater(profile->GetPrefs(), prefs::kGuestOsContainers); |
| auto it = std::ranges::find_if(*updater, [&](const auto& dict) { |
| return MatchContainerDict(dict, container_id); |
| }); |
| if (it != updater->end()) { |
| if (base::Contains(*kPropertiesAllowList, key)) { |
| base::Value::Dict* old_container_dict = it->GetIfDict(); |
| if (old_container_dict) { |
| base::Value::Dict wrapped; |
| wrapped.Set(key, std::move(dict)); |
| old_container_dict->Merge(std::move(wrapped)); |
| } else { |
| LOG(ERROR) << "Expected a dict for " << container_id; |
| } |
| } else { |
| LOG(ERROR) << "Ignoring disallowed property: " << key; |
| } |
| } |
| } |
| |
| VmType VmTypeFromPref(const base::Value& pref) { |
| if (!pref.is_dict()) { |
| return VmType::UNKNOWN; |
| } |
| |
| // Default is TERMINA(0) if field not present since this field was introduced |
| // when only TERMINA was using prefs.. |
| auto type = pref.GetDict().FindInt(guest_os::prefs::kVmTypeKey); |
| if (!type.has_value()) { |
| LOG(WARNING) << "No VM type in pref, defaulting to termina"; |
| return VmType::TERMINA; |
| } |
| if (*type < vm_tools::apps::VmType_MIN || |
| *type > vm_tools::apps::VmType_MAX) { |
| return VmType::UNKNOWN; |
| } |
| return static_cast<guest_os::VmType>(*type); |
| } |
| |
| } // namespace guest_os |