feat(native-certgen): implement logging subsystem

Multi-output logging via tracing: /dev/kmsg for logcat, rotating file
appender (512KB, 3 files), stderr for debug. Diagnostic ZIP dump with
log files and TEE status snapshots. Verbose toggle via JNI flag or
.verbose marker file.
This commit is contained in:
Enginex0
2026-03-09 15:41:46 +01:00
parent 9d61af6ce8
commit 0725aed094
6 changed files with 547 additions and 0 deletions
+208
View File
@@ -0,0 +1,208 @@
use std::fs::{self, File};
use std::io::{Read, Write};
use std::path::Path;
use std::process::Command;
use std::time::{SystemTime, UNIX_EPOCH};
const DUMP_DIR: &str = "/sdcard/Download";
const LOCK_PATH: &str = "/data/adb/tricky_store/.dump_lock";
const DUMP_PATH_FILE: &str = "/data/adb/tricky_store/.dump_path";
const LOG_DIR: &str = "/data/adb/tricky_store/logs";
const BASE_DIR: &str = "/data/adb/tricky_store";
const LOGCAT_SIZE_LIMIT: usize = 2 * 1024 * 1024;
struct FlockGuard {
_file: File,
}
impl FlockGuard {
fn acquire() -> Result<Self, Box<dyn std::error::Error>> {
if let Some(parent) = Path::new(LOCK_PATH).parent() {
fs::create_dir_all(parent)?;
}
let file = File::create(LOCK_PATH)?;
let fd = {
use std::os::unix::io::AsRawFd;
file.as_raw_fd()
};
let ret = unsafe { libc::flock(fd, libc::LOCK_EX | libc::LOCK_NB) };
if ret != 0 {
return Err("dump already in progress".into());
}
Ok(Self { _file: file })
}
}
impl Drop for FlockGuard {
fn drop(&mut self) {
// flock released automatically when file descriptor closes
}
}
fn random_name(len: usize) -> String {
use rand::Rng;
let mut rng = rand::thread_rng();
(0..len)
.map(|_| {
let idx = rng.gen_range(0..36u8);
if idx < 10 {
(b'0' + idx) as char
} else {
(b'a' + idx - 10) as char
}
})
.collect()
}
fn collect_logcat(tag: &str) -> Vec<u8> {
let output = Command::new("logcat")
.args(["-d", "-s", tag])
.output();
match output {
Ok(o) => {
let mut data = o.stdout;
data.truncate(LOGCAT_SIZE_LIMIT);
data
}
Err(_) => Vec::new(),
}
}
fn collect_device_info() -> String {
let mut info = String::new();
if let Ok(output) = Command::new("uname").arg("-a").output() {
info.push_str(&format!(
"uname={}\n",
String::from_utf8_lossy(&output.stdout).trim()
));
}
for (key, prop) in [
("device", "ro.product.device"),
("build", "ro.build.display.id"),
("android", "ro.build.version.release"),
] {
if let Ok(output) = Command::new("getprop").arg(prop).output() {
info.push_str(&format!(
"{}={}\n",
key,
String::from_utf8_lossy(&output.stdout).trim()
));
}
}
// KSU version
if let Ok(ver) = fs::read_to_string("/data/adb/ksu/version") {
info.push_str(&format!("ksu={}\n", ver.trim()));
}
// Module version from module.prop
if let Ok(prop) = fs::read_to_string("/data/adb/modules/tricky_store/module.prop") {
for line in prop.lines() {
if let Some(ver) = line.strip_prefix("version=") {
info.push_str(&format!("module={}\n", ver.trim()));
break;
}
}
}
info
}
fn read_file_bytes(path: &str) -> Option<Vec<u8>> {
let mut buf = Vec::new();
File::open(path).ok()?.read_to_end(&mut buf).ok()?;
Some(buf)
}
fn epoch_millis() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_millis() as u64)
.unwrap_or(0)
}
pub fn execute_dump() -> Result<(), Box<dyn std::error::Error>> {
let _lock = FlockGuard::acquire()?;
let _ = fs::create_dir_all(DUMP_DIR);
let zip_name = format!("{}.zip", random_name(8));
let zip_path = format!("{}/{}", DUMP_DIR, zip_name);
let zip_file = File::create(&zip_path)?;
let mut zip = zip::ZipWriter::new(zip_file);
let options =
zip::write::SimpleFileOptions::default().compression_method(zip::CompressionMethod::Deflated);
let mut file_count = 0u32;
// Log files
let log_files = [
"certgen.log",
"certgen.log.1",
"certgen.log.2",
"certgen.log.3",
"certgen.log.4",
];
for name in &log_files {
let path = format!("{}/{}", LOG_DIR, name);
if let Some(data) = read_file_bytes(&path) {
zip.start_file(*name, options)?;
zip.write_all(&data)?;
file_count += 1;
}
}
// Logcat
let logcat = collect_logcat("TEESimulator");
if !logcat.is_empty() {
zip.start_file("logcat-teesimulator.log", options)?;
zip.write_all(&logcat)?;
file_count += 1;
}
// Config files
for name in ["tee_status.txt", "security_patch.txt"] {
let path = format!("{}/{}", BASE_DIR, name);
if let Some(data) = read_file_bytes(&path) {
zip.start_file(name, options)?;
zip.write_all(&data)?;
file_count += 1;
}
}
// Device info
let device_info = collect_device_info();
if !device_info.is_empty() {
zip.start_file("device-info.txt", options)?;
zip.write_all(device_info.as_bytes())?;
file_count += 1;
}
// Manifest
let manifest = serde_json::json!({
"timestamp": epoch_millis(),
"version": env!("CARGO_PKG_VERSION"),
"files": file_count,
});
zip.start_file("manifest.json", options)?;
zip.write_all(manifest.to_string().as_bytes())?;
zip.finish()?;
let zip_size = fs::metadata(&zip_path).map(|m| m.len()).unwrap_or(0);
fs::write(DUMP_PATH_FILE, &zip_path)?;
let result = serde_json::json!({
"zip": zip_path,
"size": zip_size,
"files": file_count + 1, // +1 for manifest
});
println!("{}", result);
tracing::info!(path = %zip_path, size = zip_size, "diagnostic dump created");
Ok(())
}