1 // Copyright 2021, The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 //! Android VM control tool.
16
17 mod create_idsig;
18 mod create_partition;
19 mod run;
20 mod sync;
21
22 use android_system_virtualizationservice::aidl::android::system::virtualizationservice::{
23 IVirtualizationService::IVirtualizationService, PartitionType::PartitionType,
24 VirtualMachineAppConfig::DebugLevel::DebugLevel,
25 };
26 use android_system_virtualizationservice::binder::{wait_for_interface, ProcessState, Strong};
27 use anyhow::{Context, Error};
28 use create_idsig::command_create_idsig;
29 use create_partition::command_create_partition;
30 use run::{command_run, command_run_app};
31 use rustutils::system_properties;
32 use std::path::{Path, PathBuf};
33 use structopt::clap::AppSettings;
34 use structopt::StructOpt;
35
36 const VIRTUALIZATION_SERVICE_BINDER_SERVICE_IDENTIFIER: &str =
37 "android.system.virtualizationservice";
38
39 #[derive(Debug)]
40 struct Idsigs(Vec<PathBuf>);
41
42 #[derive(StructOpt)]
43 #[structopt(no_version, global_settings = &[AppSettings::DisableVersion])]
44 enum Opt {
45 /// Run a virtual machine with a config in APK
46 RunApp {
47 /// Path to VM Payload APK
48 #[structopt(parse(from_os_str))]
49 apk: PathBuf,
50
51 /// Path to idsig of the APK
52 #[structopt(parse(from_os_str))]
53 idsig: PathBuf,
54
55 /// Path to the instance image. Created if not exists.
56 #[structopt(parse(from_os_str))]
57 instance: PathBuf,
58
59 /// Path to VM config JSON within APK (e.g. assets/vm_config.json)
60 config_path: String,
61
62 /// Detach VM from the terminal and run in the background
63 #[structopt(short, long)]
64 daemonize: bool,
65
66 /// Path to file for VM console output.
67 #[structopt(long)]
68 console: Option<PathBuf>,
69
70 /// Path to file for VM log output.
71 #[structopt(long)]
72 log: Option<PathBuf>,
73
74 /// Debug level of the VM. Supported values: "none" (default), "app_only", and "full".
75 #[structopt(long, default_value = "none", parse(try_from_str=parse_debug_level))]
76 debug: DebugLevel,
77
78 /// Run VM in protected mode.
79 #[structopt(short, long)]
80 protected: bool,
81
82 /// Memory size (in MiB) of the VM. If unspecified, defaults to the value of `memory_mib`
83 /// in the VM config file.
84 #[structopt(short, long)]
85 mem: Option<u32>,
86
87 /// Number of vCPUs in the VM. If unspecified, defaults to 1.
88 #[structopt(long)]
89 cpus: Option<u32>,
90
91 /// Host CPUs where vCPUs are run on. If unspecified, vCPU runs on any host CPU.
92 #[structopt(long)]
93 cpu_affinity: Option<String>,
94
95 /// Comma separated list of task profile names to apply to the VM
96 #[structopt(long)]
97 task_profiles: Vec<String>,
98
99 /// Paths to extra idsig files.
100 #[structopt(long = "extra-idsig")]
101 extra_idsigs: Vec<PathBuf>,
102 },
103 /// Run a virtual machine
104 Run {
105 /// Path to VM config JSON
106 #[structopt(parse(from_os_str))]
107 config: PathBuf,
108
109 /// Detach VM from the terminal and run in the background
110 #[structopt(short, long)]
111 daemonize: bool,
112
113 /// Number of vCPUs in the VM. If unspecified, defaults to 1.
114 #[structopt(long)]
115 cpus: Option<u32>,
116
117 /// Host CPUs where vCPUs are run on. If unspecified, vCPU runs on any host CPU. The format
118 /// can be either a comma-separated list of CPUs or CPU ranges to run vCPUs on (e.g.
119 /// "0,1-3,5" to choose host CPUs 0, 1, 2, 3, and 5, or a colon-separated list of
120 /// assignments of vCPU-to-host-CPU assignments e.g. "0=0:1=1:2=2" to map vCPU 0 to host
121 /// CPU 0 and so on.
122 #[structopt(long)]
123 cpu_affinity: Option<String>,
124
125 /// Comma separated list of task profile names to apply to the VM
126 #[structopt(long)]
127 task_profiles: Vec<String>,
128
129 /// Path to file for VM console output.
130 #[structopt(long)]
131 console: Option<PathBuf>,
132
133 /// Path to file for VM log output.
134 #[structopt(long)]
135 log: Option<PathBuf>,
136 },
137 /// Stop a virtual machine running in the background
138 Stop {
139 /// CID of the virtual machine
140 cid: u32,
141 },
142 /// List running virtual machines
143 List,
144 /// Print information about virtual machine support
145 Info,
146 /// Create a new empty partition to be used as a writable partition for a VM
147 CreatePartition {
148 /// Path at which to create the image file
149 #[structopt(parse(from_os_str))]
150 path: PathBuf,
151
152 /// The desired size of the partition, in bytes.
153 size: u64,
154
155 /// Type of the partition
156 #[structopt(short="t", long="type", default_value="raw", parse(try_from_str=parse_partition_type))]
157 partition_type: PartitionType,
158 },
159 /// Creates or update the idsig file by digesting the input APK file.
160 CreateIdsig {
161 /// Path to VM Payload APK
162 #[structopt(parse(from_os_str))]
163 apk: PathBuf,
164 /// Path to idsig of the APK
165 #[structopt(parse(from_os_str))]
166 path: PathBuf,
167 },
168 }
169
parse_debug_level(s: &str) -> Result<DebugLevel, String>170 fn parse_debug_level(s: &str) -> Result<DebugLevel, String> {
171 match s {
172 "none" => Ok(DebugLevel::NONE),
173 "app_only" => Ok(DebugLevel::APP_ONLY),
174 "full" => Ok(DebugLevel::FULL),
175 _ => Err(format!("Invalid debug level {}", s)),
176 }
177 }
178
parse_partition_type(s: &str) -> Result<PartitionType, String>179 fn parse_partition_type(s: &str) -> Result<PartitionType, String> {
180 match s {
181 "raw" => Ok(PartitionType::RAW),
182 "instance" => Ok(PartitionType::ANDROID_VM_INSTANCE),
183 _ => Err(format!("Invalid partition type {}", s)),
184 }
185 }
186
main() -> Result<(), Error>187 fn main() -> Result<(), Error> {
188 env_logger::init();
189 let opt = Opt::from_args();
190
191 // We need to start the thread pool for Binder to work properly, especially link_to_death.
192 ProcessState::start_thread_pool();
193
194 let service = wait_for_interface(VIRTUALIZATION_SERVICE_BINDER_SERVICE_IDENTIFIER)
195 .context("Failed to find VirtualizationService")?;
196
197 match opt {
198 Opt::RunApp {
199 apk,
200 idsig,
201 instance,
202 config_path,
203 daemonize,
204 console,
205 log,
206 debug,
207 protected,
208 mem,
209 cpus,
210 cpu_affinity,
211 task_profiles,
212 extra_idsigs,
213 } => command_run_app(
214 service,
215 &apk,
216 &idsig,
217 &instance,
218 &config_path,
219 daemonize,
220 console.as_deref(),
221 log.as_deref(),
222 debug,
223 protected,
224 mem,
225 cpus,
226 cpu_affinity,
227 task_profiles,
228 &extra_idsigs,
229 ),
230 Opt::Run { config, daemonize, cpus, cpu_affinity, task_profiles, console, log } => {
231 command_run(
232 service,
233 &config,
234 daemonize,
235 console.as_deref(),
236 log.as_deref(),
237 /* mem */ None,
238 cpus,
239 cpu_affinity,
240 task_profiles,
241 )
242 }
243 Opt::Stop { cid } => command_stop(service, cid),
244 Opt::List => command_list(service),
245 Opt::Info => command_info(),
246 Opt::CreatePartition { path, size, partition_type } => {
247 command_create_partition(service, &path, size, partition_type)
248 }
249 Opt::CreateIdsig { apk, path } => command_create_idsig(service, &apk, &path),
250 }
251 }
252
253 /// Retrieve reference to a previously daemonized VM and stop it.
command_stop(service: Strong<dyn IVirtualizationService>, cid: u32) -> Result<(), Error>254 fn command_stop(service: Strong<dyn IVirtualizationService>, cid: u32) -> Result<(), Error> {
255 service
256 .debugDropVmRef(cid as i32)
257 .context("Failed to get VM from VirtualizationService")?
258 .context("CID does not correspond to a running background VM")?;
259 Ok(())
260 }
261
262 /// List the VMs currently running.
command_list(service: Strong<dyn IVirtualizationService>) -> Result<(), Error>263 fn command_list(service: Strong<dyn IVirtualizationService>) -> Result<(), Error> {
264 let vms = service.debugListVms().context("Failed to get list of VMs")?;
265 println!("Running VMs: {:#?}", vms);
266 Ok(())
267 }
268
269 /// Print information about supported VM types.
command_info() -> Result<(), Error>270 fn command_info() -> Result<(), Error> {
271 let unprotected_vm_supported =
272 system_properties::read_bool("ro.boot.hypervisor.vm.supported", false)?;
273 let protected_vm_supported =
274 system_properties::read_bool("ro.boot.hypervisor.protected_vm.supported", false)?;
275 match (unprotected_vm_supported, protected_vm_supported) {
276 (false, false) => println!("VMs are not supported."),
277 (false, true) => println!("Only protected VMs are supported."),
278 (true, false) => println!("Only unprotected VMs are supported."),
279 (true, true) => println!("Both protected and unprotected VMs are supported."),
280 }
281
282 if let Some(version) = system_properties::read("ro.boot.hypervisor.version")? {
283 println!("Hypervisor version: {}", version);
284 } else {
285 println!("Hypervisor version not set.");
286 }
287
288 if Path::new("/dev/kvm").exists() {
289 println!("/dev/kvm exists.");
290 } else {
291 println!("/dev/kvm does not exist.");
292 }
293
294 Ok(())
295 }
296