1 /* 2 * Copyright (C) 2021 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 //! pvm_exec is a proxy/wrapper command to run a command remotely. It does not transport the 18 //! program and just pass the command line arguments to compsvc to execute. The most important task 19 //! for this program is to run a `fd_server` that serves remote file read/write requests. 20 //! 21 //! Example: 22 //! $ adb shell exec 3</dev/zero 4<>/dev/null pvm_exec --in-fd 3 --out-fd 4 -- sleep 10 23 //! 24 //! Note the immediate argument right after "--" (e.g. "sleep" in the example above) is not really 25 //! used. It is only for ergonomics. 26 27 use anyhow::{bail, Context, Result}; 28 use log::{error, warn}; 29 use minijail::Minijail; 30 use nix::fcntl::{fcntl, FcntlArg::F_GETFD}; 31 use nix::sys::stat::fstat; 32 use std::os::unix::io::RawFd; 33 use std::path::Path; 34 use std::process::exit; 35 36 use compos_aidl_interface::aidl::com::android::compos::{ 37 ICompService::ICompService, InputFdAnnotation::InputFdAnnotation, Metadata::Metadata, 38 OutputFdAnnotation::OutputFdAnnotation, 39 }; 40 use compos_aidl_interface::binder::Strong; 41 42 static SERVICE_NAME: &str = "compsvc"; 43 static FD_SERVER_BIN: &str = "/apex/com.android.virt/bin/fd_server"; 44 45 fn get_local_service() -> Strong<dyn ICompService> { 46 compos_aidl_interface::binder::get_interface(SERVICE_NAME).expect("Cannot reach compsvc") 47 } 48 49 fn spawn_fd_server(metadata: &Metadata, debuggable: bool) -> Result<Minijail> { 50 let mut inheritable_fds = if debuggable { 51 vec![1, 2] // inherit/redirect stdout/stderr for debugging 52 } else { 53 vec![] 54 }; 55 56 let mut args = vec![FD_SERVER_BIN.to_string()]; 57 for metadata in &metadata.input_fd_annotations { 58 args.push("--ro-fds".to_string()); 59 args.push(metadata.fd.to_string()); 60 inheritable_fds.push(metadata.fd); 61 } 62 for metadata in &metadata.output_fd_annotations { 63 args.push("--rw-fds".to_string()); 64 args.push(metadata.fd.to_string()); 65 inheritable_fds.push(metadata.fd); 66 } 67 68 let jail = Minijail::new()?; 69 let _pid = jail.run(Path::new(FD_SERVER_BIN), &inheritable_fds, &args)?; 70 Ok(jail) 71 } 72 73 fn is_fd_valid(fd: RawFd) -> Result<bool> { 74 let retval = fcntl(fd, F_GETFD)?; 75 Ok(retval >= 0) 76 } 77 78 fn parse_arg_fd(arg: &str) -> Result<RawFd> { 79 let fd = arg.parse::<RawFd>()?; 80 if !is_fd_valid(fd)? { 81 bail!("Bad FD: {}", fd); 82 } 83 Ok(fd) 84 } 85 86 struct Config { 87 args: Vec<String>, 88 metadata: Metadata, 89 debuggable: bool, 90 } 91 92 fn parse_args() -> Result<Config> { 93 #[rustfmt::skip] 94 let matches = clap::App::new("pvm_exec") 95 .arg(clap::Arg::with_name("in-fd") 96 .long("in-fd") 97 .takes_value(true) 98 .multiple(true) 99 .use_delimiter(true)) 100 .arg(clap::Arg::with_name("out-fd") 101 .long("out-fd") 102 .takes_value(true) 103 .multiple(true) 104 .use_delimiter(true)) 105 .arg(clap::Arg::with_name("debug") 106 .long("debug")) 107 .arg(clap::Arg::with_name("args") 108 .last(true) 109 .required(true) 110 .multiple(true)) 111 .get_matches(); 112 113 let results: Result<Vec<_>> = matches 114 .values_of("in-fd") 115 .unwrap_or_default() 116 .map(|arg| { 117 let fd = parse_arg_fd(arg)?; 118 let file_size = fstat(fd)?.st_size; 119 Ok(InputFdAnnotation { fd, file_size }) 120 }) 121 .collect(); 122 let input_fd_annotations = results?; 123 124 let results: Result<Vec<_>> = matches 125 .values_of("out-fd") 126 .unwrap_or_default() 127 .map(|arg| { 128 let fd = parse_arg_fd(arg)?; 129 Ok(OutputFdAnnotation { fd }) 130 }) 131 .collect(); 132 let output_fd_annotations = results?; 133 134 let args: Vec<_> = matches.values_of("args").unwrap().map(|s| s.to_string()).collect(); 135 let debuggable = matches.is_present("debug"); 136 137 Ok(Config { 138 args, 139 metadata: Metadata { input_fd_annotations, output_fd_annotations }, 140 debuggable, 141 }) 142 } 143 144 fn main() -> Result<()> { 145 // 1. Parse the command line arguments for collect execution data. 146 let Config { args, metadata, debuggable } = parse_args()?; 147 148 // 2. Spawn and configure a fd_server to serve remote read/write requests. 149 let fd_server_jail = spawn_fd_server(&metadata, debuggable)?; 150 let fd_server_lifetime = scopeguard::guard(fd_server_jail, |fd_server_jail| { 151 if let Err(e) = fd_server_jail.kill() { 152 if !matches!(e, minijail::Error::Killed(_)) { 153 warn!("Failed to kill fd_server: {}", e); 154 } 155 } 156 }); 157 158 // 3. Send the command line args to the remote to execute. 159 let exit_code = get_local_service().execute(&args, &metadata).context("Binder call failed")?; 160 161 // Be explicit about the lifetime, which should last at least until the task is finished. 162 drop(fd_server_lifetime); 163 164 if exit_code > 0 { 165 error!("remote execution failed with exit code {}", exit_code); 166 exit(exit_code as i32); 167 } 168 Ok(()) 169 } 170