use std::cmp::Ordering; use std::collections::HashMap; use std::env; use std::str::from_utf8; use base::libc::{S_IFDIR, S_IFMT, S_IFREG}; use base::Utf8CStr; use crate::cpio::{Cpio, CpioEntry}; use crate::ffi::{patch_encryption, patch_verity}; pub trait MagiskCpio { fn patch(&mut self); fn test(&self) -> i32; fn restore(&mut self) -> anyhow::Result<()>; fn backup(&mut self, origin: &Utf8CStr) -> anyhow::Result<()>; } const MAGISK_PATCHED: i32 = 1 << 0; const UNSUPPORTED_CPIO: i32 = 1 << 1; const SONY_INIT: i32 = 1 << 2; #[inline(always)] fn check_env(env: &str) -> bool { env::var(env).map_or(false, |var| var == "true") } impl MagiskCpio for Cpio { fn patch(&mut self) { let keep_verity = check_env("KEEPVERITY"); let keep_force_encrypt = check_env("KEEPFORCEENCRYPT"); eprintln!( "Patch with flag KEEPVERITY=[{}] KEEPFORCEENCRYPT=[{}]", keep_verity, keep_force_encrypt ); self.entries.retain(|name, entry| { let fstab = (!keep_verity || !keep_force_encrypt) && entry.mode & S_IFMT == S_IFREG && !name.starts_with(".backup") && !name.starts_with("twrp") && !name.starts_with("recovery") && name.starts_with("fstab"); if !keep_verity { if fstab { eprintln!("Found fstab file [{}]", name); let len = patch_verity(entry.data.as_mut_slice()); if len != entry.data.len() { entry.data.resize(len, 0); } } else if name == "verity_key" { return false; } } if !keep_force_encrypt && fstab { let len = patch_encryption(entry.data.as_mut_slice()); if len != entry.data.len() { entry.data.resize(len, 0); } } true }); } fn test(&self) -> i32 { let mut ret = 0; for file in [ "sbin/launch_daemonsu.sh", "sbin/su", "init.xposed.rc", "boot/sbin/launch_daemonsu.sh", ] { if self.exists(file) { return UNSUPPORTED_CPIO; } } for file in [ ".backup/.magisk", "init.magisk.rc", "overlay/init.magisk.rc", ] { if self.exists(file) { ret |= MAGISK_PATCHED; break; } } if self.exists("init.sony.rc") { ret |= SONY_INIT; } ret } fn restore(&mut self) -> anyhow::Result<()> { let mut backups = HashMap::>::new(); let mut rm_list = String::new(); self.entries .drain_filter(|name, _| name.starts_with(".backup/")) .for_each(|(name, entry)| { if name == ".backup/.rmlist" { if let Ok(data) = from_utf8(&entry.data) { rm_list.push_str(data); } } else if name != ".backup/.magisk" { let new_name = &name[8..]; eprintln!("Restore [{}] -> [{}]", name, new_name); backups.insert(new_name.to_string(), entry); } }); self.rm(".backup", false); if rm_list.is_empty() && backups.is_empty() { self.entries.clear(); return Ok(()); } for rm in rm_list.split('\0') { if !rm.is_empty() { self.rm(rm, false); } } self.entries.extend(backups); Ok(()) } fn backup(&mut self, origin: &Utf8CStr) -> anyhow::Result<()> { let mut backups = HashMap::>::new(); let mut rm_list = String::new(); backups.insert( ".backup".to_string(), Box::new(CpioEntry { mode: S_IFDIR, uid: 0, gid: 0, rdevmajor: 0, rdevminor: 0, data: vec![], }), ); let mut o = Cpio::load_from_file(origin)?; o.rm(".backup", true); self.rm(".backup", true); let mut lhs = o.entries.into_iter().peekable(); let mut rhs = self.entries.iter().peekable(); loop { enum Action<'a> { Backup(String, Box), Record(&'a String), Noop, } let action = match (lhs.peek(), rhs.peek()) { (Some((l, _)), Some((r, re))) => match l.as_str().cmp(r.as_str()) { Ordering::Less => { let (l, le) = lhs.next().unwrap(); Action::Backup(l, le) } Ordering::Greater => Action::Record(rhs.next().unwrap().0), Ordering::Equal => { let (l, le) = lhs.next().unwrap(); let action = if re.data != le.data { Action::Backup(l, le) } else { Action::Noop }; rhs.next(); action } }, (Some(_), None) => { let (l, le) = lhs.next().unwrap(); Action::Backup(l, le) } (None, Some(_)) => Action::Record(rhs.next().unwrap().0), (None, None) => { break; } }; match action { Action::Backup(name, entry) => { let backup = format!(".backup/{}", name); eprintln!("Backup [{}] -> [{}]", name, backup); backups.insert(backup, entry); } Action::Record(name) => { eprintln!("Record new entry: [{}] -> [.backup/.rmlist]", name); rm_list.push_str(&format!("{}\0", name)); } Action::Noop => {} } } if !rm_list.is_empty() { backups.insert( ".backup/.rmlist".to_string(), Box::new(CpioEntry { mode: S_IFREG, uid: 0, gid: 0, rdevmajor: 0, rdevminor: 0, data: rm_list.as_bytes().to_vec(), }), ); } self.entries.extend(backups); Ok(()) } }