mirror of
https://github.com/topjohnwu/Magisk.git
synced 2025-04-29 22:14:27 +02:00
308 lines
8.5 KiB
Rust
308 lines
8.5 KiB
Rust
use std::{cell::UnsafeCell, process::exit};
|
|
|
|
use argh::FromArgs;
|
|
use fdt::{
|
|
Fdt, FdtError,
|
|
node::{FdtNode, NodeProperty},
|
|
};
|
|
|
|
use base::{
|
|
EarlyExitExt, LoggedResult, MappedFile, ResultExt, Utf8CStr, libc::c_char, log_err, map_args,
|
|
};
|
|
|
|
use crate::{check_env, patch::patch_verity};
|
|
|
|
#[derive(FromArgs)]
|
|
struct DtbCli {
|
|
#[argh(positional)]
|
|
file: String,
|
|
#[argh(subcommand)]
|
|
action: DtbAction,
|
|
}
|
|
|
|
#[derive(FromArgs)]
|
|
#[argh(subcommand)]
|
|
enum DtbAction {
|
|
Print(Print),
|
|
Patch(Patch),
|
|
Test(Test),
|
|
}
|
|
|
|
#[derive(FromArgs)]
|
|
#[argh(subcommand, name = "print")]
|
|
struct Print {
|
|
#[argh(switch, short = 'f')]
|
|
fstab: bool,
|
|
}
|
|
|
|
#[derive(FromArgs)]
|
|
#[argh(subcommand, name = "patch")]
|
|
struct Patch {}
|
|
|
|
#[derive(FromArgs)]
|
|
#[argh(subcommand, name = "test")]
|
|
struct Test {}
|
|
|
|
fn print_dtb_usage() {
|
|
eprintln!(
|
|
r#"Usage: magiskboot dtb <file> <action> [args...]
|
|
Do dtb related actions to <file>.
|
|
|
|
Supported actions:
|
|
print [-f]
|
|
Print all contents of dtb for debugging
|
|
Specify [-f] to only print fstab nodes
|
|
patch
|
|
Search for fstab and remove verity/avb
|
|
Modifications are done directly to the file in-place
|
|
Configure with env variables: KEEPVERITY
|
|
test
|
|
Test the fstab's status
|
|
Return values:
|
|
0:valid 1:error"#
|
|
);
|
|
}
|
|
|
|
const MAX_PRINT_LEN: usize = 32;
|
|
|
|
fn print_node(node: &FdtNode) {
|
|
fn pretty_node(depth_set: &[bool]) {
|
|
let mut depth_set = depth_set.iter().peekable();
|
|
while let Some(depth) = depth_set.next() {
|
|
let last = depth_set.peek().is_none();
|
|
if *depth {
|
|
if last {
|
|
print!("├── ");
|
|
} else {
|
|
print!("│ ");
|
|
}
|
|
} else if last {
|
|
print!("└── ");
|
|
} else {
|
|
print!(" ");
|
|
}
|
|
}
|
|
}
|
|
|
|
fn pretty_prop(depth_set: &[bool]) {
|
|
let mut depth_set = depth_set.iter().peekable();
|
|
while let Some(depth) = depth_set.next() {
|
|
let last = depth_set.peek().is_none();
|
|
if *depth {
|
|
if last {
|
|
print!("│ ");
|
|
} else {
|
|
print!("│ ");
|
|
}
|
|
} else if last {
|
|
print!("└─ ");
|
|
} else {
|
|
print!(" ");
|
|
}
|
|
}
|
|
}
|
|
|
|
fn do_print_node(node: &FdtNode, depth_set: &mut Vec<bool>) {
|
|
pretty_node(depth_set);
|
|
let depth = depth_set.len();
|
|
depth_set.push(true);
|
|
println!("{}", node.name);
|
|
let mut properties = node.properties().peekable();
|
|
let mut children = node.children().peekable();
|
|
while let Some(NodeProperty { name, value }) = properties.next() {
|
|
let size = value.len();
|
|
let is_str = !(size > 1 && value[0] == 0)
|
|
&& matches!(value.last(), Some(0u8) | None)
|
|
&& value.iter().all(|c| *c == 0 || (*c >= 32 && *c < 127));
|
|
|
|
if depth_set[depth] && properties.peek().is_none() && children.peek().is_none() {
|
|
depth_set[depth] = false;
|
|
}
|
|
|
|
pretty_prop(depth_set);
|
|
if is_str {
|
|
println!(
|
|
"[{}]: [\"{}\"]",
|
|
name,
|
|
if value.is_empty() {
|
|
""
|
|
} else {
|
|
unsafe { Utf8CStr::from_bytes_unchecked(value) }
|
|
}
|
|
);
|
|
} else if size > MAX_PRINT_LEN {
|
|
println!("[{}]: <bytes>({})", name, size);
|
|
} else {
|
|
println!("[{}]: {:02x?}", name, value);
|
|
}
|
|
}
|
|
|
|
while let Some(child) = children.next() {
|
|
if depth_set[depth] && children.peek().is_none() {
|
|
depth_set[depth] = false;
|
|
}
|
|
do_print_node(&child, depth_set);
|
|
}
|
|
depth_set.pop();
|
|
}
|
|
|
|
do_print_node(node, &mut vec![]);
|
|
}
|
|
|
|
fn for_each_fdt<F: FnMut(usize, Fdt) -> LoggedResult<()>>(
|
|
file: &Utf8CStr,
|
|
rw: bool,
|
|
mut f: F,
|
|
) -> LoggedResult<()> {
|
|
eprintln!("Loading dtbs from [{}]", file);
|
|
let file = if rw {
|
|
MappedFile::open_rw(file)?
|
|
} else {
|
|
MappedFile::open(file)?
|
|
};
|
|
let mut buf = Some(file.as_ref());
|
|
let mut dtb_num = 0usize;
|
|
while let Some(slice) = buf {
|
|
let slice = if let Some(pos) = slice.windows(4).position(|w| w == b"\xd0\x0d\xfe\xed") {
|
|
&slice[pos..]
|
|
} else {
|
|
break;
|
|
};
|
|
if slice.len() < 40 {
|
|
break;
|
|
}
|
|
let fdt = match Fdt::new(slice) {
|
|
Err(FdtError::BufferTooSmall) => {
|
|
eprintln!("dtb.{:04} is truncated", dtb_num);
|
|
break;
|
|
}
|
|
Ok(fdt) => fdt,
|
|
e => e?,
|
|
};
|
|
|
|
let size = fdt.total_size();
|
|
|
|
f(dtb_num, fdt)?;
|
|
|
|
dtb_num += 1;
|
|
buf = Some(&slice[size..]);
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn find_fstab<'b, 'a: 'b>(fdt: &'b Fdt<'a>) -> Option<FdtNode<'b, 'a>> {
|
|
fdt.all_nodes().find(|node| node.name == "fstab")
|
|
}
|
|
|
|
fn dtb_print(file: &Utf8CStr, fstab: bool) -> LoggedResult<()> {
|
|
for_each_fdt(file, false, |n, fdt| {
|
|
if fstab {
|
|
if let Some(fstab) = find_fstab(&fdt) {
|
|
eprintln!("Found fstab in dtb.{:04}", n);
|
|
print_node(&fstab);
|
|
}
|
|
} else if let Some(mut root) = fdt.find_node("/") {
|
|
eprintln!("Printing dtb.{:04}", n);
|
|
if root.name.is_empty() {
|
|
root.name = "/";
|
|
}
|
|
print_node(&root);
|
|
}
|
|
Ok(())
|
|
})
|
|
}
|
|
|
|
fn dtb_test(file: &Utf8CStr) -> LoggedResult<bool> {
|
|
let mut ret = true;
|
|
for_each_fdt(file, false, |_, fdt| {
|
|
if let Some(fstab) = find_fstab(&fdt) {
|
|
for child in fstab.children() {
|
|
if child.name != "system" {
|
|
continue;
|
|
}
|
|
if let Some(mount_point) = child.property("mnt_point") {
|
|
if mount_point.value == b"/system_root\0" {
|
|
ret = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Ok(())
|
|
})?;
|
|
Ok(ret)
|
|
}
|
|
|
|
fn dtb_patch(file: &Utf8CStr) -> LoggedResult<bool> {
|
|
let keep_verity = check_env("KEEPVERITY");
|
|
let mut patched = false;
|
|
for_each_fdt(file, true, |n, fdt| {
|
|
for node in fdt.all_nodes() {
|
|
if node.name != "chosen" {
|
|
continue;
|
|
}
|
|
if let Some(boot_args) = node.property("bootargs") {
|
|
boot_args.value.windows(14).for_each(|w| {
|
|
if w == b"skip_initramfs" {
|
|
let w = unsafe {
|
|
&mut *std::mem::transmute::<&[u8], &UnsafeCell<[u8]>>(w).get()
|
|
};
|
|
w[..=4].copy_from_slice(b"want");
|
|
eprintln!("Patch [skip_initramfs] -> [want_initramfs] in dtb.{:04}", n);
|
|
patched = true;
|
|
}
|
|
});
|
|
}
|
|
}
|
|
if keep_verity {
|
|
return Ok(());
|
|
}
|
|
if let Some(fstab) = find_fstab(&fdt) {
|
|
for child in fstab.children() {
|
|
if let Some(flags) = child.property("fsmgr_flags") {
|
|
let flags = unsafe {
|
|
&mut *std::mem::transmute::<&[u8], &UnsafeCell<[u8]>>(flags.value).get()
|
|
};
|
|
if patch_verity(flags) != flags.len() {
|
|
patched = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Ok(())
|
|
})?;
|
|
Ok(patched)
|
|
}
|
|
|
|
pub fn dtb_commands(argc: i32, argv: *const *const c_char) -> bool {
|
|
let res: LoggedResult<()> = try {
|
|
if argc < 1 {
|
|
Err(log_err!("No arguments"))?;
|
|
}
|
|
let cmds = map_args(argc, argv)?;
|
|
|
|
let mut cli =
|
|
DtbCli::from_args(&["magiskboot", "dtb"], &cmds).on_early_exit(print_dtb_usage);
|
|
|
|
let file = Utf8CStr::from_string(&mut cli.file);
|
|
|
|
match cli.action {
|
|
DtbAction::Print(Print { fstab }) => {
|
|
dtb_print(file, fstab)?;
|
|
}
|
|
DtbAction::Test(_) => {
|
|
if !dtb_test(file)? {
|
|
exit(1);
|
|
}
|
|
}
|
|
DtbAction::Patch(_) => {
|
|
if !dtb_patch(file)? {
|
|
exit(1);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
res.log_with_msg(|w| w.write_str("Failed to process dtb"))
|
|
.is_ok()
|
|
}
|