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 <android-base/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 cuttlefish {
30 namespace vm_manager {
31 
VmManager(const cuttlefish::CuttlefishConfig * config)32 VmManager::VmManager(const cuttlefish::CuttlefishConfig* config)
33     : config_(config) {}
34 
35 namespace{
36 template <typename T>
GetManagerSingleton(const cuttlefish::CuttlefishConfig * config)37 VmManager* GetManagerSingleton(const cuttlefish::CuttlefishConfig* config) {
38   static std::shared_ptr<VmManager> vm_manager(new T(config));
39   return vm_manager.get();
40 }
41 } // namespace
42 
43 std::map<std::string, VmManager::VmManagerHelper>
44     VmManager::vm_manager_helpers_ = {
45         {
46           QemuManager::name(),
47           {
48             GetManagerSingleton<QemuManager>,
49             cuttlefish::HostSupportsQemuCli,
50             QemuManager::ConfigureGpu,
51             QemuManager::ConfigureBootDevices,
52           },
53         },
54         {
55           CrosvmManager::name(),
56           {
57             GetManagerSingleton<CrosvmManager>,
58             // Same as Qemu for the time being
59             cuttlefish::HostSupportsQemuCli,
60             CrosvmManager::ConfigureGpu,
61             CrosvmManager::ConfigureBootDevices,
62           }
63         }
64     };
65 
Get(const std::string & vm_manager_name,const cuttlefish::CuttlefishConfig * config)66 VmManager* VmManager::Get(const std::string& vm_manager_name,
67                           const cuttlefish::CuttlefishConfig* config) {
68   if (VmManager::IsValidName(vm_manager_name)) {
69     return vm_manager_helpers_[vm_manager_name].builder(config);
70   }
71   LOG(ERROR) << "Requested invalid VmManager: " << vm_manager_name;
72   return nullptr;
73 }
74 
IsValidName(const std::string & name)75 bool VmManager::IsValidName(const std::string& name) {
76   return vm_manager_helpers_.count(name) > 0;
77 }
78 
IsVmManagerSupported(const std::string & name)79 bool VmManager::IsVmManagerSupported(const std::string& name) {
80   return VmManager::IsValidName(name) &&
81          vm_manager_helpers_[name].support_checker();
82 }
83 
ConfigureGpuMode(const std::string & vmm_name,const std::string & gpu_mode)84 std::vector<std::string> VmManager::ConfigureGpuMode(
85     const std::string& vmm_name, const std::string& gpu_mode) {
86   auto it = vm_manager_helpers_.find(vmm_name);
87   if (it == vm_manager_helpers_.end()) {
88     return {};
89   }
90   return it->second.configure_gpu_mode(gpu_mode);
91 }
92 
ConfigureBootDevices(const std::string & vmm_name)93 std::vector<std::string> VmManager::ConfigureBootDevices(
94     const std::string& vmm_name) {
95   auto it = vm_manager_helpers_.find(vmm_name);
96   if (it == vm_manager_helpers_.end()) {
97     return {};
98   }
99   return it->second.configure_boot_devices();
100 }
101 
GetValidNames()102 std::vector<std::string> VmManager::GetValidNames() {
103   std::vector<std::string> ret = {};
104   for (const auto& key_val: vm_manager_helpers_) {
105     ret.push_back(key_val.first);
106   }
107   return ret;
108 }
109 
UserInGroup(const std::string & group,std::vector<std::string> * config_commands)110 bool VmManager::UserInGroup(const std::string& group,
111                             std::vector<std::string>* config_commands) {
112   if (!cuttlefish::InGroup(group)) {
113     LOG(ERROR) << "User must be a member of " << group;
114     config_commands->push_back("# Add your user to the " + group + " group:");
115     config_commands->push_back("sudo usermod -aG " + group + " $USER");
116     return false;
117   }
118   return true;
119 }
120 
GetLinuxVersion()121 std::pair<int,int> VmManager::GetLinuxVersion() {
122   struct utsname info;
123   if (!uname(&info)) {
124     char* digit = strtok(info.release, "+.-");
125     int major = atoi(digit);
126     if (digit) {
127       digit = strtok(NULL, "+.-");
128       int minor = atoi(digit);
129       return std::pair<int,int>{major, minor};
130     }
131   }
132   LOG(ERROR) << "Failed to detect Linux kernel version";
133   return invalid_linux_version;
134 }
135 
LinuxVersionAtLeast(std::vector<std::string> * config_commands,const std::pair<int,int> & version,int major,int minor)136 bool VmManager::LinuxVersionAtLeast(std::vector<std::string>* config_commands,
137                                     const std::pair<int,int>& version,
138                                     int major, int minor) {
139   if (version.first > major ||
140       (version.first == major && version.second >= minor)) {
141     return true;
142   }
143 
144   LOG(ERROR) << "Kernel version must be >=" << major << "." << minor
145              << ", have " << version.first << "." << version.second;
146   config_commands->push_back("# Please upgrade your kernel to >=" +
147                              std::to_string(major) + "." +
148                              std::to_string(minor));
149   return false;
150 }
151 
ValidateHostConfiguration(std::vector<std::string> * config_commands) const152 bool VmManager::ValidateHostConfiguration(
153     std::vector<std::string>* config_commands) const {
154   // if we can't detect the kernel version, just fail
155   auto version = VmManager::GetLinuxVersion();
156   if (version == invalid_linux_version) {
157     return false;
158   }
159 
160   // the check for cvdnetwork needs to happen even if the user is not in kvm, so
161   // we can't just say UserInGroup("kvm") && UserInGroup("cvdnetwork")
162   auto in_cvdnetwork = VmManager::UserInGroup("cvdnetwork", config_commands);
163 
164   // if we're in the virtaccess group this is likely to be a CrOS environment.
165   auto is_cros = cuttlefish::InGroup("virtaccess");
166   if (is_cros) {
167     // relax the minimum kernel requirement slightly, as chromeos-4.4 has the
168     // needed backports to enable vhost_vsock
169     auto linux_ver_4_4 =
170       VmManager::LinuxVersionAtLeast(config_commands, version, 4, 4);
171     return in_cvdnetwork && linux_ver_4_4;
172   } else {
173     // this is regular Linux, so use the Debian group name and be more
174     // conservative with the kernel version check.
175     auto in_kvm = VmManager::UserInGroup("kvm", config_commands);
176     auto linux_ver_4_8 =
177       VmManager::LinuxVersionAtLeast(config_commands, version, 4, 8);
178     return in_cvdnetwork && in_kvm && linux_ver_4_8;
179   }
180 }
181 
WithFrontend(bool enabled)182 void VmManager::WithFrontend(bool enabled) {
183   frontend_enabled_ = enabled;
184 }
185 
WithKernelCommandLine(const std::string & kernel_cmdline)186 void VmManager::WithKernelCommandLine(const std::string& kernel_cmdline) {
187   kernel_cmdline_ = kernel_cmdline;
188 }
189 
190 } // namespace vm_manager
191 } // namespace cuttlefish
192 
193