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