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
get_local_service() -> Strong<dyn ICompService>45 fn get_local_service() -> Strong<dyn ICompService> {
46 compos_aidl_interface::binder::get_interface(SERVICE_NAME).expect("Cannot reach compsvc")
47 }
48
spawn_fd_server(metadata: &Metadata, debuggable: bool) -> Result<Minijail>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
is_fd_valid(fd: RawFd) -> Result<bool>73 fn is_fd_valid(fd: RawFd) -> Result<bool> {
74 let retval = fcntl(fd, F_GETFD)?;
75 Ok(retval >= 0)
76 }
77
parse_arg_fd(arg: &str) -> Result<RawFd>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
parse_args() -> Result<Config>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
main() -> Result<()>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