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 //! ProfCollect Binder service implementation.
18 
19 use anyhow::{anyhow, bail, Context, Error, Result};
20 use binder::public_api::Result as BinderResult;
21 use binder::Status;
22 use profcollectd_aidl_interface::aidl::com::android::server::profcollect::IProfCollectd::IProfCollectd;
23 use std::ffi::CString;
24 use std::fs::{copy, read_dir, read_to_string, remove_file, write};
25 use std::path::PathBuf;
26 use std::str::FromStr;
27 use std::sync::{Mutex, MutexGuard};
28 use std::time::Duration;
29 
30 use crate::config::{
31     clear_data, Config, BETTERBUG_CACHE_DIR_PREFIX, BETTERBUG_CACHE_DIR_SUFFIX, CONFIG_FILE,
32     PROFILE_OUTPUT_DIR, REPORT_OUTPUT_DIR, REPORT_RETENTION_SECS,
33 };
34 use crate::report::{get_report_ts, pack_report};
35 use crate::scheduler::Scheduler;
36 
err_to_binder_status(msg: Error) -> Status37 fn err_to_binder_status(msg: Error) -> Status {
38     let msg = format!("{:#?}", msg);
39     let msg = CString::new(msg).expect("Failed to convert to CString");
40     Status::new_service_specific_error(1, Some(&msg))
41 }
42 
43 pub struct ProfcollectdBinderService {
44     lock: Mutex<Lock>,
45 }
46 
47 struct Lock {
48     config: Config,
49     scheduler: Scheduler,
50 }
51 
52 impl binder::Interface for ProfcollectdBinderService {}
53 
54 impl IProfCollectd for ProfcollectdBinderService {
schedule(&self) -> BinderResult<()>55     fn schedule(&self) -> BinderResult<()> {
56         let lock = &mut *self.lock();
57         lock.scheduler
58             .schedule_periodic(&lock.config)
59             .context("Failed to schedule collection.")
60             .map_err(err_to_binder_status)
61     }
terminate(&self) -> BinderResult<()>62     fn terminate(&self) -> BinderResult<()> {
63         self.lock()
64             .scheduler
65             .terminate_periodic()
66             .context("Failed to terminate collection.")
67             .map_err(err_to_binder_status)
68     }
trace_once(&self, tag: &str) -> BinderResult<()>69     fn trace_once(&self, tag: &str) -> BinderResult<()> {
70         let lock = &mut *self.lock();
71         lock.scheduler
72             .one_shot(&lock.config, tag)
73             .context("Failed to initiate an one-off trace.")
74             .map_err(err_to_binder_status)
75     }
process(&self, blocking: bool) -> BinderResult<()>76     fn process(&self, blocking: bool) -> BinderResult<()> {
77         let lock = &mut *self.lock();
78         lock.scheduler
79             .process(blocking)
80             .context("Failed to process profiles.")
81             .map_err(err_to_binder_status)
82     }
report(&self) -> BinderResult<String>83     fn report(&self) -> BinderResult<String> {
84         self.process(true)?;
85 
86         let lock = &mut *self.lock();
87         pack_report(&PROFILE_OUTPUT_DIR, &REPORT_OUTPUT_DIR, &lock.config)
88             .context("Failed to create profile report.")
89             .map_err(err_to_binder_status)
90     }
delete_report(&self, report_name: &str) -> BinderResult<()>91     fn delete_report(&self, report_name: &str) -> BinderResult<()> {
92         verify_report_name(&report_name).map_err(err_to_binder_status)?;
93 
94         let mut report = PathBuf::from(&*REPORT_OUTPUT_DIR);
95         report.push(report_name);
96         report.set_extension("zip");
97         remove_file(&report).ok();
98         Ok(())
99     }
copy_report_to_bb(&self, bb_profile_id: i32, report_name: &str) -> BinderResult<()>100     fn copy_report_to_bb(&self, bb_profile_id: i32, report_name: &str) -> BinderResult<()> {
101         if bb_profile_id < 0 {
102             return Err(err_to_binder_status(anyhow!("Invalid profile ID")));
103         }
104         verify_report_name(&report_name).map_err(err_to_binder_status)?;
105 
106         let mut report = PathBuf::from(&*REPORT_OUTPUT_DIR);
107         report.push(report_name);
108         report.set_extension("zip");
109 
110         let mut dest = PathBuf::from(&*BETTERBUG_CACHE_DIR_PREFIX);
111         dest.push(bb_profile_id.to_string());
112         dest.push(&*BETTERBUG_CACHE_DIR_SUFFIX);
113         if !dest.is_dir() {
114             return Err(err_to_binder_status(anyhow!("Cannot open BetterBug cache dir")));
115         }
116         dest.push(report_name);
117         dest.set_extension("zip");
118 
119         copy(report, dest)
120             .map(|_| ())
121             .context("Failed to copy report to bb storage.")
122             .map_err(err_to_binder_status)
123     }
get_supported_provider(&self) -> BinderResult<String>124     fn get_supported_provider(&self) -> BinderResult<String> {
125         Ok(self.lock().scheduler.get_trace_provider_name().to_string())
126     }
127 }
128 
129 /// Verify that the report name is valid, i.e. not a relative path component, to prevent potential
130 /// attack.
verify_report_name(report_name: &str) -> Result<()>131 fn verify_report_name(report_name: &str) -> Result<()> {
132     match report_name.chars().all(|c| c.is_ascii_hexdigit() || c == '-') {
133         true => Ok(()),
134         false => bail!("Invalid report name: {}", report_name),
135     }
136 }
137 
138 impl ProfcollectdBinderService {
new() -> Result<Self>139     pub fn new() -> Result<Self> {
140         let new_scheduler = Scheduler::new()?;
141         let new_config = Config::from_env()?;
142 
143         let config_changed = read_to_string(*CONFIG_FILE)
144             .ok()
145             .and_then(|s| Config::from_str(&s).ok())
146             .filter(|c| new_config == *c)
147             .is_none();
148 
149         if config_changed {
150             log::info!("Config change detected, resetting profcollect.");
151             clear_data()?;
152 
153             write(*CONFIG_FILE, &new_config.to_string())?;
154         }
155 
156         // Clear profile reports out of rentention period.
157         for report in read_dir(*REPORT_OUTPUT_DIR)? {
158             let report = report?.path();
159             let report_name = report
160                 .file_stem()
161                 .and_then(|f| f.to_str())
162                 .ok_or_else(|| anyhow!("Malformed path {}", report.display()))?;
163             let report_ts = get_report_ts(report_name);
164             if let Err(e) = report_ts {
165                 log::error!(
166                     "Cannot decode creation timestamp for report {}, caused by {}, deleting",
167                     report_name,
168                     e
169                 );
170                 remove_file(report)?;
171                 continue;
172             }
173             let report_age = report_ts.unwrap().elapsed()?;
174             if report_age > Duration::from_secs(REPORT_RETENTION_SECS) {
175                 log::info!("Report {} past rentention period, deleting", report_name);
176                 remove_file(report)?;
177             }
178         }
179 
180         Ok(ProfcollectdBinderService {
181             lock: Mutex::new(Lock { scheduler: new_scheduler, config: new_config }),
182         })
183     }
184 
lock(&self) -> MutexGuard<Lock>185     fn lock(&self) -> MutexGuard<Lock> {
186         self.lock.lock().unwrap()
187     }
188 }
189