// // Copyright (C) 2021 The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //! Pack profiles into reports. use anyhow::{anyhow, Result}; use lazy_static::lazy_static; use macaddr::MacAddr6; use std::fs::{self, File, Permissions}; use std::io::{Read, Write}; use std::os::unix::fs::PermissionsExt; use std::path::{Path, PathBuf}; use std::time::{Duration, SystemTime}; use uuid::v1::{Context, Timestamp}; use uuid::Uuid; use zip::write::FileOptions; use zip::CompressionMethod::Deflated; use zip::ZipWriter; use crate::config::Config; lazy_static! { pub static ref UUID_CONTEXT: Context = Context::new(0); } pub fn pack_report(profile: &Path, report: &Path, config: &Config) -> Result { let mut report = PathBuf::from(report); let report_filename = get_report_filename(&config.node_id)?; report.push(&report_filename); report.set_extension("zip"); // Remove the current report file if exists. fs::remove_file(&report).ok(); let report_file = fs::OpenOptions::new().create_new(true).write(true).open(&report)?; // Set report file ACL bits to 644, so that this can be shared to uploaders. // Who has permission to actually read the file is protected by SELinux policy. fs::set_permissions(&report, Permissions::from_mode(0o644))?; let options = FileOptions::default().compression_method(Deflated); let mut zip = ZipWriter::new(report_file); fs::read_dir(profile)? .filter_map(|e| e.ok()) .map(|e| e.path()) .filter(|e| e.is_file()) .try_for_each(|e| -> Result<()> { let filename = e .file_name() .and_then(|f| f.to_str()) .ok_or_else(|| anyhow!("Malformed profile path: {}", e.display()))?; zip.start_file(filename, options)?; let mut f = File::open(e)?; let mut buffer = Vec::new(); f.read_to_end(&mut buffer)?; zip.write_all(&*buffer)?; Ok(()) })?; zip.finish()?; Ok(report_filename) } fn get_report_filename(node_id: &MacAddr6) -> Result { let since_epoch = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?; let ts = Timestamp::from_unix(&*UUID_CONTEXT, since_epoch.as_secs(), since_epoch.subsec_nanos()); let uuid = Uuid::new_v1(ts, &node_id.as_bytes())?; Ok(uuid.to_string()) } /// Get report creation timestamp through its filename (version 1 UUID). pub fn get_report_ts(filename: &str) -> Result { let uuid_ts = Uuid::parse_str(filename)? .to_timestamp() .ok_or_else(|| anyhow!("filename is not a valid V1 UUID."))? .to_unix(); Ok(SystemTime::UNIX_EPOCH + Duration::new(uuid_ts.0, uuid_ts.1)) }