1 /*
2 * Copyright 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 //! Handle running odrefresh in the VM, with an async interface to allow cancellation
18
19 use crate::fd_server_helper::FdServerConfig;
20 use crate::instance_starter::CompOsInstance;
21 use android_system_composd::aidl::android::system::composd::{
22 ICompilationTask::ICompilationTask,
23 ICompilationTaskCallback::{FailureReason::FailureReason, ICompilationTaskCallback},
24 };
25 use anyhow::{Context, Result};
26 use binder::{Interface, Result as BinderResult, Strong};
27 use compos_aidl_interface::aidl::com::android::compos::ICompOsService::{
28 CompilationMode::CompilationMode, ICompOsService, OdrefreshArgs::OdrefreshArgs,
29 };
30 use compos_common::odrefresh::{
31 is_system_property_interesting, ExitCode, CURRENT_ARTIFACTS_SUBDIR, ODREFRESH_OUTPUT_ROOT_DIR,
32 PENDING_ARTIFACTS_SUBDIR,
33 };
34 use compos_common::BUILD_MANIFEST_SYSTEM_EXT_APK_PATH;
35 use log::{error, info, warn};
36 use odsign_proto::odsign_info::OdsignInfo;
37 use protobuf::Message;
38 use rustutils::system_properties;
39 use std::fs::{remove_dir_all, File, OpenOptions};
40 use std::os::fd::AsFd;
41 use std::os::unix::fs::OpenOptionsExt;
42 use std::os::unix::io::{AsRawFd, OwnedFd};
43 use std::path::Path;
44 use std::sync::{Arc, Mutex};
45 use std::thread;
46
47 #[derive(Clone)]
48 pub struct OdrefreshTask {
49 running_task: Arc<Mutex<Option<RunningTask>>>,
50 }
51
52 impl Interface for OdrefreshTask {}
53
54 impl ICompilationTask for OdrefreshTask {
cancel(&self) -> BinderResult<()>55 fn cancel(&self) -> BinderResult<()> {
56 let task = self.take();
57 // Drop the VM, which should end compilation - and cause our thread to exit.
58 // Note that we don't do a graceful shutdown here; we've been asked to give up our resources
59 // ASAP, and the VM has not failed so we don't need to ensure VM logs are written.
60 drop(task);
61 Ok(())
62 }
63 }
64
65 struct RunningTask {
66 callback: Strong<dyn ICompilationTaskCallback>,
67 #[allow(dead_code)] // Keeps the CompOS VM alive
68 comp_os: CompOsInstance,
69 }
70
71 impl OdrefreshTask {
72 /// Return the current running task, if any, removing it from this CompilationTask.
73 /// Once removed, meaning the task has ended or been canceled, further calls will always return
74 /// None.
take(&self) -> Option<RunningTask>75 fn take(&self) -> Option<RunningTask> {
76 self.running_task.lock().unwrap().take()
77 }
78
start( comp_os: CompOsInstance, compilation_mode: CompilationMode, target_dir_name: String, callback: &Strong<dyn ICompilationTaskCallback>, ) -> Result<OdrefreshTask>79 pub fn start(
80 comp_os: CompOsInstance,
81 compilation_mode: CompilationMode,
82 target_dir_name: String,
83 callback: &Strong<dyn ICompilationTaskCallback>,
84 ) -> Result<OdrefreshTask> {
85 let service = comp_os.get_service();
86 let task = RunningTask { comp_os, callback: callback.clone() };
87 let task = OdrefreshTask { running_task: Arc::new(Mutex::new(Some(task))) };
88
89 task.clone().start_thread(service, compilation_mode, target_dir_name);
90
91 Ok(task)
92 }
93
start_thread( self, service: Strong<dyn ICompOsService>, compilation_mode: CompilationMode, target_dir_name: String, )94 fn start_thread(
95 self,
96 service: Strong<dyn ICompOsService>,
97 compilation_mode: CompilationMode,
98 target_dir_name: String,
99 ) {
100 thread::spawn(move || {
101 let exit_code = run_in_vm(service, compilation_mode, &target_dir_name);
102
103 let task = self.take();
104 // We don't do the callback if cancel has already happened.
105 if let Some(RunningTask { callback, comp_os }) = task {
106 // Make sure we keep our service alive until we have called the callback.
107 let lazy_service_guard = comp_os.shutdown();
108
109 let result = match exit_code {
110 Ok(ExitCode::CompilationSuccess) => {
111 if compilation_mode == CompilationMode::TEST_COMPILE {
112 info!("Compilation success");
113 callback.onSuccess()
114 } else {
115 // compos.info is generated only during NORMAL_COMPILE
116 if let Err(e) = enable_fsverity_to_all() {
117 let message =
118 format!("Unexpected failure when enabling fs-verity: {:?}", e);
119 error!("{}", message);
120 callback.onFailure(FailureReason::FailedToEnableFsverity, &message)
121 } else {
122 info!("Compilation success, fs-verity enabled");
123 callback.onSuccess()
124 }
125 }
126 }
127 Ok(exit_code) => {
128 let message = format!("Unexpected odrefresh result: {:?}", exit_code);
129 error!("{}", message);
130 callback.onFailure(FailureReason::UnexpectedCompilationResult, &message)
131 }
132 Err(e) => {
133 let message = format!("Running odrefresh failed: {:?}", e);
134 error!("{}", message);
135 callback.onFailure(FailureReason::CompilationFailed, &message)
136 }
137 };
138 if let Err(e) = result {
139 warn!("Failed to deliver callback: {:?}", e);
140 }
141 drop(lazy_service_guard);
142 }
143 });
144 }
145 }
146
run_in_vm( service: Strong<dyn ICompOsService>, compilation_mode: CompilationMode, target_dir_name: &str, ) -> Result<ExitCode>147 fn run_in_vm(
148 service: Strong<dyn ICompOsService>,
149 compilation_mode: CompilationMode,
150 target_dir_name: &str,
151 ) -> Result<ExitCode> {
152 let mut names = Vec::new();
153 let mut values = Vec::new();
154 system_properties::foreach(|name, value| {
155 if is_system_property_interesting(name) {
156 names.push(name.to_owned());
157 values.push(value.to_owned());
158 }
159 })?;
160 service.initializeSystemProperties(&names, &values).context("initialize system properties")?;
161
162 let output_root = Path::new(ODREFRESH_OUTPUT_ROOT_DIR);
163
164 // We need to remove the target directory because odrefresh running in compos will create it
165 // (and can't see the existing one, since authfs doesn't show it existing files in an output
166 // directory).
167 let target_path = output_root.join(target_dir_name);
168 if target_path.exists() {
169 remove_dir_all(&target_path)
170 .with_context(|| format!("Failed to delete {}", target_path.display()))?;
171 }
172
173 let staging_dir_fd = open_dir(composd_native::palette_create_odrefresh_staging_directory()?)?;
174 let system_dir_fd = open_dir(Path::new("/system"))?;
175 let output_dir_fd = open_dir(output_root)?;
176
177 // Get the raw FD before passing the ownership, since borrowing will violate the borrow check.
178 let system_dir_raw_fd = system_dir_fd.as_raw_fd();
179 let output_dir_raw_fd = output_dir_fd.as_raw_fd();
180 let staging_dir_raw_fd = staging_dir_fd.as_raw_fd();
181
182 // When the VM starts, it starts with or without mouting the extra build manifest APK from
183 // /system_ext. Later on request (here), we need to pass the directory FD of /system_ext, but
184 // only if the VM is configured to need it.
185 //
186 // It is possible to plumb the information from ComposClient to here, but it's extra complexity
187 // and feel slightly weird to encode the VM's state to the task itself, as it is a request to
188 // the VM.
189 let need_system_ext = Path::new(BUILD_MANIFEST_SYSTEM_EXT_APK_PATH).exists();
190 let (system_ext_dir_raw_fd, ro_dir_fds) = if need_system_ext {
191 let system_ext_dir_fd = open_dir(Path::new("/system_ext"))?;
192 (system_ext_dir_fd.as_raw_fd(), vec![system_dir_fd, system_ext_dir_fd])
193 } else {
194 (-1, vec![system_dir_fd])
195 };
196
197 // Spawn a fd_server to serve the FDs.
198 let fd_server_config = FdServerConfig {
199 ro_dir_fds,
200 rw_dir_fds: vec![staging_dir_fd, output_dir_fd],
201 ..Default::default()
202 };
203 let fd_server_raii = fd_server_config.into_fd_server()?;
204
205 let zygote_arch = system_properties::read("ro.zygote")?.context("ro.zygote not set")?;
206 let system_server_compiler_filter =
207 system_properties::read("dalvik.vm.systemservercompilerfilter")?.unwrap_or_default();
208
209 let args = OdrefreshArgs {
210 compilationMode: compilation_mode,
211 systemDirFd: system_dir_raw_fd,
212 systemExtDirFd: system_ext_dir_raw_fd,
213 outputDirFd: output_dir_raw_fd,
214 stagingDirFd: staging_dir_raw_fd,
215 targetDirName: target_dir_name.to_string(),
216 zygoteArch: zygote_arch,
217 systemServerCompilerFilter: system_server_compiler_filter,
218 };
219 let exit_code = service.odrefresh(&args)?;
220
221 drop(fd_server_raii);
222 ExitCode::from_i32(exit_code.into())
223 }
224
225 /// Enable fs-verity to output artifacts according to compos.info in the pending directory. Any
226 /// error before the completion will just abort, leaving the previous files enabled.
enable_fsverity_to_all() -> Result<()>227 fn enable_fsverity_to_all() -> Result<()> {
228 let odrefresh_current_dir = Path::new(ODREFRESH_OUTPUT_ROOT_DIR).join(CURRENT_ARTIFACTS_SUBDIR);
229 let pending_dir = Path::new(ODREFRESH_OUTPUT_ROOT_DIR).join(PENDING_ARTIFACTS_SUBDIR);
230 let mut reader =
231 File::open(pending_dir.join("compos.info")).context("Failed to open compos.info")?;
232 let compos_info = OdsignInfo::parse_from_reader(&mut reader).context("Failed to parse")?;
233
234 for path_str in compos_info.file_hashes.keys() {
235 // Need to rebase the directory on to compos-pending first
236 if let Ok(relpath) = Path::new(path_str).strip_prefix(&odrefresh_current_dir) {
237 let path = pending_dir.join(relpath);
238 let file = File::open(&path).with_context(|| format!("Failed to open {:?}", path))?;
239 // We don't expect error. But when it happens, don't bother handle it here. For
240 // simplicity, just let odsign do the regular check.
241 fsverity::enable(file.as_fd())
242 .with_context(|| format!("Failed to enable fs-verity to {:?}", path))?;
243 } else {
244 warn!("Skip due to unexpected path: {}", path_str);
245 }
246 }
247 Ok(())
248 }
249
250 /// Returns an `OwnedFD` of the directory.
open_dir(path: &Path) -> Result<OwnedFd>251 fn open_dir(path: &Path) -> Result<OwnedFd> {
252 Ok(OwnedFd::from(
253 OpenOptions::new()
254 .custom_flags(libc::O_DIRECTORY)
255 .read(true) // O_DIRECTORY can only be opened with read
256 .open(path)
257 .with_context(|| format!("Failed to open {:?} directory as path fd", path))?,
258 ))
259 }
260