1 /*
2 * Copyright (C) 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "host/libs/vm_manager/vm_manager.h"
18
19 #include <memory>
20
21 #include <glog/logging.h>
22 #include <sys/utsname.h>
23
24 #include "common/libs/utils/users.h"
25 #include "host/libs/config/cuttlefish_config.h"
26 #include "host/libs/vm_manager/qemu_manager.h"
27 #include "host/libs/vm_manager/crosvm_manager.h"
28
29 namespace vm_manager {
30
VmManager(const vsoc::CuttlefishConfig * config)31 VmManager::VmManager(const vsoc::CuttlefishConfig* config)
32 : config_(config) {}
33
34 namespace{
35 template <typename T>
GetManagerSingleton(const vsoc::CuttlefishConfig * config)36 VmManager* GetManagerSingleton(const vsoc::CuttlefishConfig* config) {
37 static std::shared_ptr<VmManager> vm_manager(new T(config));
38 return vm_manager.get();
39 }
40 }
41
42 std::map<std::string, VmManager::VmManagerHelper>
43 VmManager::vm_manager_helpers_ = {
44 {
45 QemuManager::name(),
46 {
47 GetManagerSingleton<QemuManager>,
48 vsoc::HostSupportsQemuCli,
49 QemuManager::ConfigureGpu,
50 QemuManager::ConfigureBootDevices,
51 },
52 },
53 {
54 CrosvmManager::name(),
55 {
56 GetManagerSingleton<CrosvmManager>,
57 // Same as Qemu for the time being
58 vsoc::HostSupportsQemuCli,
59 CrosvmManager::ConfigureGpu,
60 CrosvmManager::ConfigureBootDevices,
61 }
62 }
63 };
64
Get(const std::string & vm_manager_name,const vsoc::CuttlefishConfig * config)65 VmManager* VmManager::Get(const std::string& vm_manager_name,
66 const vsoc::CuttlefishConfig* config) {
67 if (VmManager::IsValidName(vm_manager_name)) {
68 return vm_manager_helpers_[vm_manager_name].builder(config);
69 }
70 LOG(ERROR) << "Requested invalid VmManager: " << vm_manager_name;
71 return nullptr;
72 }
73
IsValidName(const std::string & name)74 bool VmManager::IsValidName(const std::string& name) {
75 return vm_manager_helpers_.count(name) > 0;
76 }
77
IsVmManagerSupported(const std::string & name)78 bool VmManager::IsVmManagerSupported(const std::string& name) {
79 return VmManager::IsValidName(name) &&
80 vm_manager_helpers_[name].support_checker();
81 }
82
ConfigureGpuMode(const std::string & vmm_name,const std::string & gpu_mode)83 std::vector<std::string> VmManager::ConfigureGpuMode(
84 const std::string& vmm_name, const std::string& gpu_mode) {
85 auto it = vm_manager_helpers_.find(vmm_name);
86 if (it == vm_manager_helpers_.end()) {
87 return {};
88 }
89 return it->second.configure_gpu_mode(gpu_mode);
90 }
91
ConfigureBootDevices(const std::string & vmm_name)92 std::vector<std::string> VmManager::ConfigureBootDevices(
93 const std::string& vmm_name) {
94 auto it = vm_manager_helpers_.find(vmm_name);
95 if (it == vm_manager_helpers_.end()) {
96 return {};
97 }
98 return it->second.configure_boot_devices();
99 }
100
GetValidNames()101 std::vector<std::string> VmManager::GetValidNames() {
102 std::vector<std::string> ret = {};
103 for (const auto& key_val: vm_manager_helpers_) {
104 ret.push_back(key_val.first);
105 }
106 return ret;
107 }
108
UserInGroup(const std::string & group,std::vector<std::string> * config_commands)109 bool VmManager::UserInGroup(const std::string& group,
110 std::vector<std::string>* config_commands) {
111 if (!cvd::InGroup(group)) {
112 LOG(ERROR) << "User must be a member of " << group;
113 config_commands->push_back("# Add your user to the " + group + " group:");
114 config_commands->push_back("sudo usermod -aG " + group + " $USER");
115 return false;
116 }
117 return true;
118 }
119
GetLinuxVersion()120 std::pair<int,int> VmManager::GetLinuxVersion() {
121 struct utsname info;
122 if (!uname(&info)) {
123 char* digit = strtok(info.release, "+.-");
124 int major = atoi(digit);
125 if (digit) {
126 digit = strtok(NULL, "+.-");
127 int minor = atoi(digit);
128 return std::pair<int,int>{major, minor};
129 }
130 }
131 LOG(ERROR) << "Failed to detect Linux kernel version";
132 return invalid_linux_version;
133 }
134
LinuxVersionAtLeast(std::vector<std::string> * config_commands,const std::pair<int,int> & version,int major,int minor)135 bool VmManager::LinuxVersionAtLeast(std::vector<std::string>* config_commands,
136 const std::pair<int,int>& version,
137 int major, int minor) {
138 if (version.first > major ||
139 (version.first == major && version.second >= minor)) {
140 return true;
141 }
142
143 LOG(ERROR) << "Kernel version must be >=" << major << "." << minor
144 << ", have " << version.first << "." << version.second;
145 config_commands->push_back("# Please upgrade your kernel to >=" +
146 std::to_string(major) + "." +
147 std::to_string(minor));
148 return false;
149 }
150
ValidateHostConfiguration(std::vector<std::string> * config_commands) const151 bool VmManager::ValidateHostConfiguration(
152 std::vector<std::string>* config_commands) const {
153 // if we can't detect the kernel version, just fail
154 auto version = VmManager::GetLinuxVersion();
155 if (version == invalid_linux_version) {
156 return false;
157 }
158
159 // the check for cvdnetwork needs to happen even if the user is not in kvm, so
160 // we can't just say UserInGroup("kvm") && UserInGroup("cvdnetwork")
161 auto in_cvdnetwork = VmManager::UserInGroup("cvdnetwork", config_commands);
162
163 // if we're in the virtaccess group this is likely to be a CrOS environment.
164 auto is_cros = cvd::InGroup("virtaccess");
165 if (is_cros) {
166 // relax the minimum kernel requirement slightly, as chromeos-4.4 has the
167 // needed backports to enable vhost_vsock
168 auto linux_ver_4_4 =
169 VmManager::LinuxVersionAtLeast(config_commands, version, 4, 4);
170 return in_cvdnetwork && linux_ver_4_4;
171 } else {
172 // this is regular Linux, so use the Debian group name and be more
173 // conservative with the kernel version check.
174 auto in_kvm = VmManager::UserInGroup("kvm", config_commands);
175 auto linux_ver_4_8 =
176 VmManager::LinuxVersionAtLeast(config_commands, version, 4, 8);
177 return in_cvdnetwork && in_kvm && linux_ver_4_8;
178 }
179 }
180
WithFrontend(bool enabled)181 void VmManager::WithFrontend(bool enabled) {
182 frontend_enabled_ = enabled;
183 }
184
WithKernelCommandLine(const std::string & kernel_cmdline)185 void VmManager::WithKernelCommandLine(const std::string& kernel_cmdline) {
186 kernel_cmdline_ = kernel_cmdline;
187 }
188
189 } // namespace vm_manager
190