diff --git a/native/src/Android.mk b/native/src/Android.mk index 3736e1874..26f5ddd77 100644 --- a/native/src/Android.mk +++ b/native/src/Android.mk @@ -116,9 +116,7 @@ LOCAL_SRC_FILES := \ boot/compress.cpp \ boot/format.cpp \ boot/dtb.cpp \ - boot/ramdisk.cpp \ boot/pattern.cpp \ - boot/cpio.cpp \ boot/boot-rs.cpp include $(BUILD_EXECUTABLE) diff --git a/native/src/Cargo.lock b/native/src/Cargo.lock index 274b5cb97..3e4af2af9 100644 --- a/native/src/Cargo.lock +++ b/native/src/Cargo.lock @@ -11,6 +11,55 @@ dependencies = [ "memchr", ] +[[package]] +name = "anstream" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is-terminal", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d" + +[[package]] +name = "anstyle-parse" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e765fd216e48e067936442276d1d57399e37bce53c264d6fefbe298080cb57ee" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys 0.48.0", +] + +[[package]] +name = "anstyle-wincon" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" +dependencies = [ + "anstyle", + "windows-sys 0.48.0", +] + [[package]] name = "anyhow" version = "1.0.71" @@ -59,6 +108,48 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "clap" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "401a4694d2bf92537b6867d94de48c4842089645fdcdf6c71865b175d836e9c2" +dependencies = [ + "clap_builder", + "clap_derive", + "once_cell", +] + +[[package]] +name = "clap_builder" +version = "4.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72394f3339a76daf211e57d4bcb374410f3965dcc606dd0e03738c7888766980" +dependencies = [ + "anstream", + "anstyle", + "bitflags", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8cd2b2a819ad6eec39e8f1d6b53001af1e5469f8c177579cdaeb313115b825f" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.16", +] + +[[package]] +name = "clap_lex" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" + [[package]] name = "codespan-reporting" version = "0.11.1" @@ -69,6 +160,12 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + [[package]] name = "cxx" version = "1.0.94" @@ -143,6 +240,12 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "hermit-abi" version = "0.3.1" @@ -179,6 +282,18 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "is-terminal" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" +dependencies = [ + "hermit-abi", + "io-lifetimes", + "rustix", + "windows-sys 0.48.0", +] + [[package]] name = "libc" version = "0.2.144" @@ -218,10 +333,12 @@ dependencies = [ "anyhow", "base", "byteorder", + "clap", "cxx", "cxx-gen", "protobuf", "protobuf-codegen", + "size", ] [[package]] @@ -385,6 +502,18 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "size" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fed904c7fb2856d868b92464fc8fa597fce366edea1a9cbfaa8cb5fe080bd6d" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "syn" version = "1.0.109" @@ -461,6 +590,12 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + [[package]] name = "which" version = "4.4.0" diff --git a/native/src/boot/Cargo.toml b/native/src/boot/Cargo.toml index a0e160dc5..4fb31e5e9 100644 --- a/native/src/boot/Cargo.toml +++ b/native/src/boot/Cargo.toml @@ -17,3 +17,5 @@ cxx = { path = "../external/cxx-rs" } protobuf = { workspace = true } byteorder = { workspace = true } anyhow = { workspace = true } +clap = { version = "4.3.2", features = ["derive"] } +size = "0.4.1" diff --git a/native/src/boot/cpio.cpp b/native/src/boot/cpio.cpp deleted file mode 100644 index b933f68a1..000000000 --- a/native/src/boot/cpio.cpp +++ /dev/null @@ -1,241 +0,0 @@ -#include -#include -#include -#include - -#include - -#include "cpio.hpp" - -using namespace std; - -struct cpio_newc_header { - char magic[6]; - char ino[8]; - char mode[8]; - char uid[8]; - char gid[8]; - char nlink[8]; - char mtime[8]; - char filesize[8]; - char devmajor[8]; - char devminor[8]; - char rdevmajor[8]; - char rdevminor[8]; - char namesize[8]; - char check[8]; -} __attribute__((packed)); - -static uint32_t x8u(const char *hex) { - uint32_t val, inpos = 8, outpos; - char pattern[6]; - - while (*hex == '0') { - hex++; - if (!--inpos) return 0; - } - // Because scanf gratuitously treats %*X differently than printf does. - sprintf(pattern, "%%%dx%%n", inpos); - sscanf(hex, pattern, &val, &outpos); - if (inpos != outpos) - LOGE("bad cpio header\n"); - - return val; -} - -cpio_entry::cpio_entry(uint32_t mode) : mode(mode), uid(0), gid(0), data(0) {} - -cpio_entry::cpio_entry(uint32_t mode, byte_view data) : -mode(mode), uid(0), gid(0), data(data.clone()) {} - -cpio_entry::cpio_entry(const cpio_newc_header *h) : -mode(x8u(h->mode)), uid(x8u(h->uid)), gid(x8u(h->gid)), data(x8u(h->filesize)) {} - -void cpio::dump(const char *file) { - fprintf(stderr, "Dump cpio: [%s]\n", file); - dump(xfopen(file, "we")); -} - -void cpio::rm(entry_map::iterator it) { - if (it == entries.end()) - return; - fprintf(stderr, "Remove [%s]\n", it->first.data()); - entries.erase(it); -} - -void cpio::rm(const char *name, bool r) { - size_t len = strlen(name); - for (auto it = entries.begin(); it != entries.end();) { - if (it->first.compare(0, len, name) == 0 && - ((r && it->first[len] == '/') || it->first[len] == '\0')) { - auto tmp = it; - ++it; - rm(tmp); - if (!r) return; - } else { - ++it; - } - } -} - -static void extract_entry(const cpio::entry_map::value_type &e, const char *file) { - fprintf(stderr, "Extract [%s] to [%s]\n", e.first.data(), file); - unlink(file); - rmdir(file); - // Make sure parent folders exist - char *parent = dirname(file); - xmkdirs(parent, 0755); - if (S_ISDIR(e.second->mode)) { - xmkdir(file, e.second->mode & 0777); - } else if (S_ISREG(e.second->mode)) { - int fd = xopen(file, O_CREAT | O_WRONLY | O_TRUNC, e.second->mode & 0777); - xwrite(fd, e.second->data.buf(), e.second->data.sz()); - fchown(fd, e.second->uid, e.second->gid); - close(fd); - } else if (S_ISLNK(e.second->mode) && e.second->data.sz() < 4096) { - char target[4096]; - memcpy(target, e.second->data.buf(), e.second->data.sz()); - target[e.second->data.sz()] = '\0'; - symlink(target, file); - } -} - -void cpio::extract() { - for (auto &e : entries) - extract_entry(e, e.first.data()); -} - -bool cpio::extract(const char *name, const char *file) { - auto it = entries.find(name); - if (it != entries.end()) { - extract_entry(*it, file); - return true; - } - fprintf(stderr, "Cannot find the file entry [%s]\n", name); - return false; -} - -bool cpio::exists(const char *name) { - return entries.count(name) != 0; -} - -#define do_out(buf, len) pos += fwrite(buf, 1, len, out); -#define out_align() do_out(zeros, align_padding(pos, 4)) -void cpio::dump(FILE *out) { - size_t pos = 0; - unsigned inode = 300000; - char header[111]; - char zeros[4] = {0}; - for (auto &e : entries) { - sprintf(header, "070701%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x", - inode++, // e->ino - e.second->mode, - e.second->uid, - e.second->gid, - 1, // e->nlink - 0, // e->mtime - (uint32_t) e.second->data.sz(), - 0, // e->devmajor - 0, // e->devminor - 0, // e->rdevmajor - 0, // e->rdevminor - (uint32_t) e.first.size() + 1, - 0 // e->check - ); - do_out(header, 110); - do_out(e.first.data(), e.first.size() + 1); - out_align(); - if (e.second->data.sz()) { - do_out(e.second->data.buf(), e.second->data.sz()); - out_align(); - } - } - // Write trailer - sprintf(header, "070701%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x", - inode++, 0755, 0, 0, 1, 0, 0, 0, 0, 0, 0, 11, 0); - do_out(header, 110); - do_out("TRAILER!!!\0", 11); - out_align(); - fclose(out); -} - -void cpio::load_cpio(const char *file) { - fprintf(stderr, "Loading cpio: [%s]\n", file); - mmap_data m(file); - load_cpio(reinterpret_cast(m.buf()), m.sz()); -} - -void cpio::insert(string_view name, cpio_entry *e) { - auto it = entries.find(name); - if (it != entries.end()) { - it->second.reset(e); - } else { - entries.emplace(name, e); - } -} - -void cpio::add(mode_t mode, const char *name, const char *file) { - mmap_data m(file); - auto e = new cpio_entry(S_IFREG | mode, m); - insert(name, e); - fprintf(stderr, "Add entry [%s] (%04o)\n", name, mode); -} - -void cpio::mkdir(mode_t mode, const char *name) { - insert(name, new cpio_entry(S_IFDIR | mode)); - fprintf(stderr, "Create directory [%s] (%04o)\n", name, mode); -} - -void cpio::ln(const char *target, const char *name) { - auto e = new cpio_entry(S_IFLNK, {target, false}); - insert(name, e); - fprintf(stderr, "Create symlink [%s] -> [%s]\n", name, target); -} - -void cpio::mv(entry_map::iterator it, const char *name) { - fprintf(stderr, "Move [%s] -> [%s]\n", it->first.data(), name); - auto e = it->second.release(); - entries.erase(it); - insert(name, e); -} - -bool cpio::mv(const char *from, const char *to) { - auto it = entries.find(from); - if (it != entries.end()) { - mv(it, to); - return true; - } - fprintf(stderr, "Cannot find entry %s\n", from); - return false; -} - -#define pos_align(p) p = align_to(p, 4) - -void cpio::load_cpio(const char *buf, size_t sz) { - size_t pos = 0; - while (pos < sz) { - auto hdr = reinterpret_cast(buf + pos); - if (memcmp(hdr->magic, "070701", 6) != 0) - LOGE("bad cpio header\n"); - pos += sizeof(cpio_newc_header); - string_view name(buf + pos); - pos += x8u(hdr->namesize); - pos_align(pos); - if (name == "." || name == "..") - continue; - if (name == "TRAILER!!!") { - // Android support multiple CPIO being concatenated - // Search for the next cpio header - auto next = static_cast(memmem(buf + pos, sz - pos, "070701", 6)); - if (next == nullptr) - break; - pos = next - buf; - continue; - } - auto entry = new cpio_entry(hdr); - memcpy(entry->data.buf(), buf + pos, entry->data.sz()); - pos += entry->data.sz(); - insert(name, entry); - pos_align(pos); - } -} diff --git a/native/src/boot/cpio.hpp b/native/src/boot/cpio.hpp deleted file mode 100644 index 4a03dfe8d..000000000 --- a/native/src/boot/cpio.hpp +++ /dev/null @@ -1,55 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -#include - -struct cpio_newc_header; - -struct cpio_entry { - uint32_t mode; - uint32_t uid; - uint32_t gid; - heap_data data; - - explicit cpio_entry(uint32_t mode); - explicit cpio_entry(uint32_t mode, byte_view data); - explicit cpio_entry(const cpio_newc_header *h); -}; - -class cpio { -public: - struct StringCmp { - using is_transparent = void; - bool operator()(std::string_view a, std::string_view b) const { - return a < b; - } - }; - using entry_map = std::map, StringCmp>; - - void load_cpio(const char *file); - void dump(const char *file); - void rm(const char *name, bool r = false); - void extract(); - bool extract(const char *name, const char *file); - bool exists(const char *name); - void add(mode_t mode, const char *name, const char *file); - void mkdir(mode_t mode, const char *name); - void ln(const char *target, const char *name); - bool mv(const char *from, const char *to); - -protected: - entry_map entries; - - void rm(entry_map::iterator it); - void mv(entry_map::iterator it, const char *name); - -private: - void dump(FILE *out); - void insert(std::string_view name, cpio_entry *e); - void load_cpio(const char *buf, size_t sz); -}; diff --git a/native/src/boot/cpio.rs b/native/src/boot/cpio.rs new file mode 100644 index 000000000..412781d35 --- /dev/null +++ b/native/src/boot/cpio.rs @@ -0,0 +1,519 @@ +use crate::ramdisk::MagiskCpio; +use anyhow::{anyhow, Context}; +use base::libc::{ + c_char, dev_t, gid_t, major, makedev, minor, mknod, mmap, mode_t, munmap, uid_t, MAP_FAILED, + MAP_PRIVATE, PROT_READ, S_IFBLK, S_IFCHR, S_IFDIR, S_IFLNK, S_IFMT, S_IFREG, S_IRGRP, S_IROTH, + S_IRUSR, S_IWGRP, S_IWOTH, S_IWUSR, S_IXGRP, S_IXOTH, S_IXUSR, +}; +use base::{ptr_to_str_result, ResultExt, WriteExt}; +use clap::{Parser, Subcommand}; +use size::{Base, Size, Style}; +use std::collections::BTreeMap; +use std::ffi::CStr; +use std::fmt::{Display, Formatter}; +use std::fs::{metadata, read, DirBuilder, File}; +use std::io::Write; +use std::mem::size_of; +use std::os::fd::AsRawFd; +use std::os::unix::fs::{symlink, DirBuilderExt, FileTypeExt, MetadataExt}; +use std::path::Path; +use std::process::exit; + +#[derive(Parser)] +struct CpioCli { + #[command(subcommand)] + command: CpioCommands, +} + +#[derive(Subcommand)] +enum CpioCommands { + Test {}, + Restore {}, + Patch {}, + Exists { + path: String, + }, + Backup { + origin: String, + }, + Rm { + path: String, + #[arg(short)] + recursive: bool, + }, + Mv { + from: String, + to: String, + }, + Extract { + path: Option, + out: Option, + }, + Mkdir { + #[arg(value_parser=parse_mode)] + mode: mode_t, + dir: String, + }, + Ln { + src: String, + dst: String, + }, + Add { + #[arg(value_parser=parse_mode)] + mode: mode_t, + path: String, + file: String, + }, + Ls { + path: Option, + #[arg(short)] + recursive: bool, + }, +} + +#[repr(C, packed)] +struct CpioHeader { + magic: [u8; 6], + ino: [u8; 8], + mode: [u8; 8], + uid: [u8; 8], + gid: [u8; 8], + nlink: [u8; 8], + mtime: [u8; 8], + filesize: [u8; 8], + devmajor: [u8; 8], + devminor: [u8; 8], + rdevmajor: [u8; 8], + rdevminor: [u8; 8], + namesize: [u8; 8], + check: [u8; 8], +} + +pub(crate) struct Cpio { + pub(crate) entries: BTreeMap, +} + +pub(crate) struct CpioEntry { + pub(crate) mode: mode_t, + pub(crate) uid: uid_t, + pub(crate) gid: gid_t, + pub(crate) rdevmajor: dev_t, + pub(crate) rdevminor: dev_t, + pub(crate) data: Vec, +} + +impl Cpio { + fn new() -> Self { + Self { + entries: BTreeMap::new(), + } + } + fn load_from_data(data: &[u8]) -> anyhow::Result { + let mut cpio = Cpio::new(); + let mut pos = 0usize; + while pos < data.len() { + let hdr = unsafe { &*(data.as_ptr().add(pos) as *const CpioHeader) }; + if &hdr.magic != b"070701" { + return Err(anyhow!("invalid cpio magic")); + } + pos += size_of::(); + let name = CStr::from_bytes_until_nul(&data[pos..])? + .to_str()? + .to_string(); + pos += x8u::(&hdr.namesize)?; + pos = align_4(pos); + if name == "." || name == ".." { + continue; + } + if name == "TRAILER!!!" { + match data[pos..].windows(6).position(|x| x == b"070701") { + Some(x) => pos += x, + None => break, + } + continue; + } + let file_size = x8u::(&hdr.filesize)?; + let entry = CpioEntry { + mode: x8u(&hdr.mode)?, + uid: x8u(&hdr.uid)?, + gid: x8u(&hdr.gid)?, + rdevmajor: x8u(&hdr.rdevmajor)?, + rdevminor: x8u(&hdr.rdevminor)?, + data: data[pos..pos + file_size].to_vec(), + }; + pos += file_size; + cpio.entries.insert(name, entry); + pos = align_4(pos); + } + Ok(cpio) + } + pub(crate) fn load_from_file(path: &str) -> anyhow::Result { + eprintln!("Loading cpio: [{}]", path); + let file = File::open(path)?; + let len = file.metadata()?.len() as usize; + let mmap = unsafe { + mmap( + std::ptr::null_mut(), + len, + PROT_READ, + MAP_PRIVATE, + file.as_raw_fd(), + 0, + ) + }; + if mmap == MAP_FAILED { + return Err(anyhow!("mmap failed")); + } + let data = unsafe { std::slice::from_raw_parts(mmap as *const u8, len) }; + let cpio = Self::load_from_data(data)?; + unsafe { + if munmap(mmap, len) != 0 { + return Err(anyhow!("munmap failed")); + } + } + Ok(cpio) + } + fn dump(&self, path: &str) -> anyhow::Result<()> { + eprintln!("Dumping cpio: [{}]", path); + let mut file = File::create(path)?; + let mut pos = 0usize; + let mut inode = 300000i64; + for (name, entry) in &self.entries { + pos += file.write( + format!( + "070701{:08x}{:08x}{:08x}{:08x}{:08x}{:08x}{:08x}{:08x}{:08x}{:08x}{:08x}{:08x}{:08x}", + inode, + entry.mode, + entry.uid, + entry.gid, + 1, + 0, + entry.data.len(), + 0, + 0, + entry.rdevmajor, + entry.rdevminor, + name.len() + 1, + 0 + ).as_bytes(), + )?; + pos += file.write(name.as_bytes())?; + pos += file.write(&[0])?; + file.write_zeros(align_4(pos) - pos)?; + pos = align_4(pos); + pos += file.write(&entry.data)?; + file.write_zeros(align_4(pos) - pos)?; + pos = align_4(pos); + inode += 1; + } + pos += file.write( + format!("070701{:08x}{:08x}{:08x}{:08x}{:08x}{:08x}{:08x}{:08x}{:08x}{:08x}{:08x}{:08x}{:08x}", + inode, 0o755, 0, 0, 1, 0, 0, 0, 0, 0, 0, 11, 0 + ).as_bytes() + )?; + pos += file.write("TRAILER!!!\0".as_bytes())?; + file.write_zeros(align_4(pos) - pos)?; + Ok(()) + } + pub(crate) fn rm(&mut self, path: &str, recursive: bool) -> anyhow::Result<()> { + let path = norm_path(path); + let entry = self + .entries + .get(&path) + .ok_or(anyhow!("no such entry {}", path))?; + if entry.mode & S_IFMT == S_IFDIR && !recursive { + return Err(anyhow!("cannot remove directory without -r")); + } + if entry.mode & S_IFMT != S_IFDIR && recursive { + return Err(anyhow!("cannot remove file with -r")); + } + self.entries.remove(&path); + eprintln!("Removed entry [{}]", path); + if recursive { + let path = path + "/"; + self.entries.retain(|k, _| { + if k.starts_with(&path) { + eprintln!("Removed entry [{}]", k); + true + } else { + false + } + }) + } + Ok(()) + } + fn extract_entry(&self, path: &str, out: &Path) -> anyhow::Result<()> { + let entry = self.entries.get(path).ok_or(anyhow!("No such file"))?; + eprintln!("Extracting entry [{}] to [{}]", path, out.to_string_lossy()); + if let Some(parent) = out.parent() { + DirBuilder::new() + .mode(0o755) + .recursive(true) + .create(parent)?; + } + match entry.mode & S_IFMT { + S_IFDIR => { + DirBuilder::new() + .mode((entry.mode & 0o777).into()) + .create(out)?; + } + S_IFREG => { + let mut file = File::create(out)?; + file.write_all(&entry.data)?; + } + S_IFLNK => { + symlink(Path::new(&std::str::from_utf8(entry.data.as_slice())?), out)?; + } + S_IFBLK | S_IFCHR => { + let dev = makedev(entry.rdevmajor.try_into()?, entry.rdevminor.try_into()?); + unsafe { + mknod( + out.to_str().unwrap().as_ptr() as *const c_char, + entry.mode, + dev, + ) + }; + } + _ => { + return Err(anyhow!("unknown entry type")); + } + } + Ok(()) + } + fn extract(&self, path: Option<&str>, out: Option<&str>) -> anyhow::Result<()> { + let path = path.map(norm_path); + let out = out.map(Path::new); + if let (Some(path), Some(out)) = (&path, &out) { + return self.extract_entry(path, out); + } else { + for path in self.entries.keys() { + if path == "." || path == ".." { + continue; + } + self.extract_entry(path, Path::new(path))?; + } + } + Ok(()) + } + pub(crate) fn exists(&self, path: &str) -> bool { + self.entries.contains_key(&norm_path(path)) + } + fn add(&mut self, mode: &mode_t, path: &str, file: &str) -> anyhow::Result<()> { + if path.ends_with('/') { + return Err(anyhow!("path cannot end with / for add")); + } + let file = Path::new(file); + let content = read(file)?; + let metadata = metadata(file)?; + let mut rdevmajor: dev_t = 0; + let mut rdevminor: dev_t = 0; + let mode = if metadata.file_type().is_file() { + mode | S_IFREG + } else { + rdevmajor = unsafe { major(metadata.rdev().try_into()?).try_into()? }; + rdevminor = unsafe { minor(metadata.rdev().try_into()?).try_into()? }; + if metadata.file_type().is_block_device() { + mode | S_IFBLK + } else if metadata.file_type().is_char_device() { + mode | S_IFCHR + } else { + return Err(anyhow!("unsupported file type")); + } + }; + self.entries.insert( + norm_path(path), + CpioEntry { + mode, + uid: 0, + gid: 0, + rdevmajor, + rdevminor, + data: content, + }, + ); + eprintln!("Add file [{}] ({:04o})", path, mode); + Ok(()) + } + fn mkdir(&mut self, mode: &mode_t, dir: &str) { + self.entries.insert( + norm_path(dir), + CpioEntry { + mode: *mode | S_IFDIR, + uid: 0, + gid: 0, + rdevmajor: 0, + rdevminor: 0, + data: vec![], + }, + ); + eprintln!("Create directory [{}] ({:04o})", dir, mode); + } + fn ln(&mut self, src: &str, dst: &str) { + self.entries.insert( + norm_path(dst), + CpioEntry { + mode: S_IFLNK, + uid: 0, + gid: 0, + rdevmajor: 0, + rdevminor: 0, + data: norm_path(src).as_bytes().to_vec(), + }, + ); + eprintln!("Create symlink [{}] -> [{}]", dst, src); + } + fn mv(&mut self, from: &str, to: &str) -> anyhow::Result<()> { + let entry = self + .entries + .remove(&norm_path(from)) + .ok_or(anyhow!("no such entry {}", from))?; + self.entries.insert(norm_path(to), entry); + eprintln!("Move [{}] -> [{}]", from, to); + Ok(()) + } + fn ls(&self, path: Option<&str>, recursive: bool) { + let path = path + .map(norm_path) + .map(|p| "/".to_owned() + p.as_str()) + .unwrap_or("".to_string()); + for (name, entry) in &self.entries { + let p = "/".to_owned() + name.as_str(); + if !p.starts_with(&path) { + continue; + } + let p = p.strip_prefix(&path).unwrap(); + if !p.is_empty() && !p.starts_with('/') { + continue; + } + if !recursive && !p.is_empty() && p.matches('/').count() > 1 { + continue; + } + println!("{}\t{}", entry, name); + } + } +} + +impl Display for CpioEntry { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}{}{}{}{}{}{}{}{}{}\t{}\t{}\t{}\t{}:{}", + match self.mode & S_IFMT { + S_IFDIR => "d", + S_IFREG => "-", + S_IFLNK => "l", + S_IFBLK => "b", + S_IFCHR => "c", + _ => "?", + }, + if self.mode & S_IRUSR != 0 { "r" } else { "-" }, + if self.mode & S_IWUSR != 0 { "w" } else { "-" }, + if self.mode & S_IXUSR != 0 { "x" } else { "-" }, + if self.mode & S_IRGRP != 0 { "r" } else { "-" }, + if self.mode & S_IWGRP != 0 { "w" } else { "-" }, + if self.mode & S_IXGRP != 0 { "x" } else { "-" }, + if self.mode & S_IROTH != 0 { "r" } else { "-" }, + if self.mode & S_IWOTH != 0 { "w" } else { "-" }, + if self.mode & S_IXOTH != 0 { "x" } else { "-" }, + self.uid, + self.gid, + Size::from_bytes(self.data.len()) + .format() + .with_style(Style::Abbreviated) + .with_base(Base::Base10) + .to_string(), + self.rdevmajor, + self.rdevminor, + ) + } +} + +pub fn cpio_commands(argc: i32, argv: *const *const c_char) -> bool { + fn inner(argc: i32, argv: *const *const c_char) -> anyhow::Result<()> { + let mut cmds = Vec::new(); + if argc < 1 { + return Err(anyhow!("no arguments")); + } + for i in 0..argc { + let arg = unsafe { ptr_to_str_result(*argv.offset(i as isize)) }; + match arg { + Ok(arg) => cmds.push(arg), + Err(e) => Err(e)?, + } + } + let file = cmds[0]; + + let mut cpio = if Path::new(file).exists() { + Cpio::load_from_file(file)? + } else { + Cpio::new() + }; + for cmd in &cmds[1..] { + if cmd.starts_with('#') { + continue; + } + let cmd = "magiskboot ".to_string() + cmd; + let cli = CpioCli::try_parse_from(cmd.split(' ').filter(|x| !x.is_empty()))?; + match &cli.command { + CpioCommands::Test {} => exit(cpio.test()), + CpioCommands::Restore {} => cpio.restore()?, + CpioCommands::Patch {} => cpio.patch(), + CpioCommands::Exists { path } => { + if cpio.exists(path) { + exit(0); + } else { + exit(1); + } + } + CpioCommands::Backup { origin } => cpio.backup(origin)?, + CpioCommands::Rm { path, recursive } => cpio.rm(path, *recursive)?, + CpioCommands::Mv { from, to } => cpio.mv(from, to)?, + CpioCommands::Extract { path, out } => { + cpio.extract(path.as_deref(), out.as_deref())? + } + CpioCommands::Mkdir { mode, dir } => cpio.mkdir(mode, dir), + CpioCommands::Ln { src, dst } => cpio.ln(src, dst), + CpioCommands::Add { mode, path, file } => cpio.add(mode, path, file)?, + CpioCommands::Ls { path, recursive } => { + cpio.ls(path.as_deref(), *recursive); + exit(0); + } + } + } + cpio.dump(file)?; + Ok(()) + } + inner(argc, argv) + .context("Failed to process cpio") + .log() + .is_ok() +} + +fn x8u(x: &[u8; 8]) -> anyhow::Result +where + U: TryFrom, +{ + // parse hex + let mut ret = 0u32; + for i in x { + let c = *i as char; + let v = c.to_digit(16).ok_or(anyhow!("bad cpio header"))?; + ret = ret * 16 + v; + } + ret.try_into().map_err(|_| anyhow!("bad cpio header")) +} + +#[inline(always)] +fn align_4(x: usize) -> usize { + (x + 3) & !3 +} + +#[inline(always)] +fn norm_path(path: &str) -> String { + let path = path.strip_prefix('/').unwrap_or(path); + path.strip_suffix('/').unwrap_or(path).to_string() +} + +fn parse_mode(s: &str) -> Result { + mode_t::from_str_radix(s, 8).map_err(|e| e.to_string()) +} diff --git a/native/src/boot/lib.rs b/native/src/boot/lib.rs index 449bcb23e..e748590ea 100644 --- a/native/src/boot/lib.rs +++ b/native/src/boot/lib.rs @@ -1,18 +1,27 @@ #![feature(format_args_nl)] +#![feature(btree_drain_filter)] +#![feature(iter_collect_into)] extern crate core; pub use base; +pub use cpio::*; pub use payload::*; +pub use ramdisk::*; +mod cpio; mod payload; +mod ramdisk; mod update_metadata; #[cxx::bridge] pub mod ffi { unsafe extern "C++" { include!("compress.hpp"); + include!("magiskboot.hpp"); fn decompress(buf: &[u8], fd: i32) -> bool; + fn patch_encryption(buf: &mut [u8]); + fn patch_verity(buf: &mut [u8]); } #[namespace = "rust"] @@ -22,5 +31,7 @@ pub mod ffi { in_path: *const c_char, out_path: *const c_char, ) -> bool; + + unsafe fn cpio_commands(argc: i32, argv: *const *const c_char) -> bool; } } diff --git a/native/src/boot/magiskboot.hpp b/native/src/boot/magiskboot.hpp index 327ed1825..abe20408b 100644 --- a/native/src/boot/magiskboot.hpp +++ b/native/src/boot/magiskboot.hpp @@ -21,7 +21,13 @@ int hexpatch(const char *file, std::string_view from, std::string_view to); int cpio_commands(int argc, char *argv[]); int dtb_commands(int argc, char *argv[]); -bool patch_verity(byte_data &data); -bool patch_encryption(byte_data &data); +bool patch_verity(byte_data data); +void patch_verity(rust::Slice data); +bool patch_encryption(byte_data data); +void patch_encryption(rust::Slice data); -bool check_env(const char *name); +inline bool check_env(const char *name) { + using namespace std::string_view_literals; + const char *val = getenv(name); + return val != nullptr && val == "true"sv; +} diff --git a/native/src/boot/main.cpp b/native/src/boot/main.cpp index e573190b5..6465b74f3 100644 --- a/native/src/boot/main.cpp +++ b/native/src/boot/main.cpp @@ -202,7 +202,7 @@ int main(int argc, char *argv[]) { } else if (argc > 4 && action == "hexpatch") { return hexpatch(argv[2], argv[3], argv[4]); } else if (argc > 2 && action == "cpio"sv) { - if (cpio_commands(argc - 2, argv + 2)) + if (!rust::cpio_commands(argc - 2, argv + 2)) usage(argv[0]); } else if (argc > 3 && action == "dtb") { if (dtb_commands(argc - 2, argv + 2)) diff --git a/native/src/boot/pattern.cpp b/native/src/boot/pattern.cpp index 6f1be627c..780fb1f09 100644 --- a/native/src/boot/pattern.cpp +++ b/native/src/boot/pattern.cpp @@ -57,10 +57,18 @@ static bool remove_pattern(byte_data &data, int(*pattern_skip)(const char *)) { return data.sz() != orig_sz; } -bool patch_verity(byte_data &data) { +bool patch_verity(byte_data data) { return remove_pattern(data, skip_verity_pattern); } -bool patch_encryption(byte_data &data) { +void patch_verity(rust::Slice data) { + patch_verity(byte_data{data.data(), data.size()}); +} + +bool patch_encryption(byte_data data) { return remove_pattern(data, skip_encryption_pattern); } + +void patch_encryption(rust::Slice data) { + patch_encryption(byte_data{data.data(), data.size()}); +} diff --git a/native/src/boot/payload.rs b/native/src/boot/payload.rs index 2691c83a5..61aa89d90 100644 --- a/native/src/boot/payload.rs +++ b/native/src/boot/payload.rs @@ -64,7 +64,7 @@ fn do_extract_boot_from_payload( let manifest = { let manifest = &mut buf[..manifest_len]; reader.read_exact(manifest)?; - DeltaArchiveManifest::parse_from_bytes(&manifest)? + DeltaArchiveManifest::parse_from_bytes(manifest)? }; if !manifest.has_minor_version() || manifest.minor_version() != 0 { return Err(bad_payload!( @@ -152,7 +152,7 @@ fn do_extract_boot_from_payload( match data_type { Type::REPLACE => { out_file.seek(SeekFrom::Start(out_offset))?; - out_file.write_all(&data)?; + out_file.write_all(data)?; } Type::ZERO => { for ext in operation.dst_extents.iter() { diff --git a/native/src/boot/ramdisk.cpp b/native/src/boot/ramdisk.cpp deleted file mode 100644 index 5d3102721..000000000 --- a/native/src/boot/ramdisk.cpp +++ /dev/null @@ -1,261 +0,0 @@ -#include - -#include "cpio.hpp" -#include "magiskboot.hpp" -#include "compress.hpp" - -using namespace std; - -static const char *UNSUPPORT_LIST[] = - { "sbin/launch_daemonsu.sh", "sbin/su", "init.xposed.rc", - "boot/sbin/launch_daemonsu.sh" }; - -static const char *MAGISK_LIST[] = - { ".backup/.magisk", "init.magisk.rc", - "overlay/init.magisk.rc" }; - -class magisk_cpio : public cpio { -public: - void patch(); - int test(); - void restore(); - void backup(const char *orig); -}; - -bool check_env(const char *name) { - const char *val = getenv(name); - return val != nullptr && val == "true"sv; -} - -void magisk_cpio::patch() { - bool keepverity = check_env("KEEPVERITY"); - bool keepforceencrypt = check_env("KEEPFORCEENCRYPT"); - fprintf(stderr, "Patch with flag KEEPVERITY=[%s] KEEPFORCEENCRYPT=[%s]\n", - keepverity ? "true" : "false", keepforceencrypt ? "true" : "false"); - - for (auto it = entries.begin(); it != entries.end();) { - auto cur = it++; - bool fstab = (!keepverity || !keepforceencrypt) && - S_ISREG(cur->second->mode) && - !str_starts(cur->first, ".backup") && - !str_contains(cur->first, "twrp") && - !str_contains(cur->first, "recovery") && - str_contains(cur->first, "fstab"); - if (!keepverity) { - if (fstab) { - fprintf(stderr, "Found fstab file [%s]\n", cur->first.data()); - patch_verity(cur->second->data); - } else if (cur->first == "verity_key") { - rm(cur); - continue; - } - } - if (!keepforceencrypt && fstab) { - patch_encryption(cur->second->data); - } - } -} - -#define MAGISK_PATCHED (1 << 0) -#define UNSUPPORTED_CPIO (1 << 1) -#define SONY_INIT (1 << 2) - -int magisk_cpio::test() { - int ret = 0; - for (auto file : UNSUPPORT_LIST) { - if (exists(file)) { - return UNSUPPORTED_CPIO; - } - } - for (auto file : MAGISK_LIST) { - if (exists(file)) { - ret |= MAGISK_PATCHED; - break; - } - } - if (exists("init.real")) - ret |= SONY_INIT; - return ret; -} - -#define for_each_str(str, buf, size) \ -for (char *str = (char *) buf; str < (char *) buf + size; str += strlen(str) + 1) - -void magisk_cpio::restore() { - // Collect files - auto bk = entries.end(); - auto rl = entries.end(); - auto mg = entries.end(); - vector backups; - for (auto it = entries.begin(); it != entries.end(); ++it) { - if (it->first == ".backup") { - bk = it; - } else if (it->first == ".backup/.rmlist") { - rl = it; - } else if (it->first == ".backup/.magisk") { - mg = it; - } else if (str_starts(it->first, ".backup/")) { - backups.emplace_back(it); - } - } - - // If the .backup folder is effectively empty, this means that the boot ramdisk was - // created from scratch by an old broken magiskboot. This is just a hacky workaround. - if (bk != entries.end() && mg != entries.end() && rl == entries.end() && backups.empty()) { - fprintf(stderr, "Remove all in ramdisk\n"); - entries.clear(); - return; - } - - // Remove files - rm(bk); - rm(mg); - if (rl != entries.end()) { - for_each_str(file, rl->second->data.buf(), rl->second->data.sz()) { - rm(file); - } - rm(rl); - } - - // Restore files - for (auto it : backups) { - const char *name = &it->first[8]; - mv(it, name); - } -} - -void magisk_cpio::backup(const char *orig) { - entry_map backups; - string rm_list; - backups.emplace(".backup", new cpio_entry(S_IFDIR)); - - magisk_cpio o; - if (access(orig, R_OK) == 0) - o.load_cpio(orig); - - // Remove existing backups in original ramdisk - o.rm(".backup", true); - rm(".backup", true); - - auto lhs = o.entries.begin(); - auto rhs = entries.begin(); - - while (lhs != o.entries.end() || rhs != entries.end()) { - int res; - bool do_backup = false; - if (lhs != o.entries.end() && rhs != entries.end()) { - res = lhs->first.compare(rhs->first); - } else if (lhs == o.entries.end()) { - res = 1; - } else { - res = -1; - } - - if (res < 0) { - // Something is missing in new ramdisk, do_backup! - do_backup = true; - fprintf(stderr, "Backup missing entry: "); - } else if (res == 0) { - if (!lhs->second->data.equals(rhs->second->data)) { - // Not the same! - do_backup = true; - fprintf(stderr, "Backup mismatch entry: "); - } - } else { - // Something new in ramdisk - rm_list += rhs->first; - rm_list += (char) '\0'; - fprintf(stderr, "Record new entry: [%s] -> [.backup/.rmlist]\n", rhs->first.data()); - } - - if (do_backup) { - string name = ".backup/" + lhs->first; - fprintf(stderr, "[%s] -> [%s]\n", lhs->first.data(), name.data()); - auto e = lhs->second.release(); - backups.emplace(name, e); - } - - // Increment positions - if (res < 0) { - ++lhs; - } else if (res == 0) { - ++lhs; ++rhs; - } else { - ++rhs; - } - } - - if (!rm_list.empty()) { - auto rm_list_file = new cpio_entry(S_IFREG, {rm_list, false}); - backups.emplace(".backup/.rmlist", rm_list_file); - } - - if (backups.size() > 1) - entries.merge(backups); -} - -int cpio_commands(int argc, char *argv[]) { - char *incpio = argv[0]; - ++argv; - --argc; - - magisk_cpio cpio; - if (access(incpio, R_OK) == 0) - cpio.load_cpio(incpio); - - int cmdc; - char *cmdv[6]; - - for (int i = 0; i < argc; ++i) { - // Reset - cmdc = 0; - memset(cmdv, 0, sizeof(cmdv)); - - // Split the commands - char *tok = strtok(argv[i], " "); - while (tok && cmdc < std::size(cmdv)) { - if (cmdc == 0 && tok[0] == '#') - break; - cmdv[cmdc++] = tok; - tok = strtok(nullptr, " "); - } - - if (cmdc == 0) - continue; - - if (cmdv[0] == "test"sv) { - exit(cpio.test()); - } else if (cmdv[0] == "restore"sv) { - cpio.restore(); - } else if (cmdv[0] == "patch"sv) { - cpio.patch(); - } else if (cmdc == 2 && cmdv[0] == "exists"sv) { - exit(!cpio.exists(cmdv[1])); - } else if (cmdc == 2 && cmdv[0] == "backup"sv) { - cpio.backup(cmdv[1]); - } else if (cmdc >= 2 && cmdv[0] == "rm"sv) { - bool r = cmdc > 2 && cmdv[1] == "-r"sv; - cpio.rm(cmdv[1 + r], r); - } else if (cmdc == 3 && cmdv[0] == "mv"sv) { - cpio.mv(cmdv[1], cmdv[2]); - } else if (cmdv[0] == "extract"sv) { - if (cmdc == 3) { - return !cpio.extract(cmdv[1], cmdv[2]); - } else { - cpio.extract(); - return 0; - } - } else if (cmdc == 3 && cmdv[0] == "mkdir"sv) { - cpio.mkdir(strtoul(cmdv[1], nullptr, 8), cmdv[2]); - } else if (cmdc == 3 && cmdv[0] == "ln"sv) { - cpio.ln(cmdv[1], cmdv[2]); - } else if (cmdc == 4 && cmdv[0] == "add"sv) { - cpio.add(strtoul(cmdv[1], nullptr, 8), cmdv[2], cmdv[3]); - } else { - return 1; - } - } - - cpio.dump(incpio); - return 0; -} diff --git a/native/src/boot/ramdisk.rs b/native/src/boot/ramdisk.rs new file mode 100644 index 000000000..275ea22e7 --- /dev/null +++ b/native/src/boot/ramdisk.rs @@ -0,0 +1,192 @@ +use crate::cpio::{Cpio, CpioEntry}; +use crate::ffi::{patch_encryption, patch_verity}; +use base::libc::{S_IFDIR, S_IFMT, S_IFREG}; +use std::cmp::Ordering; +use std::collections::HashMap; +use std::env; +use std::str::from_utf8; + +pub trait MagiskCpio { + fn patch(&mut self); + fn test(&self) -> i32; + fn restore(&mut self) -> anyhow::Result<()>; + fn backup(&mut self, origin: &str) -> 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); + patch_verity(entry.data.as_mut_slice()); + } else if name == "verity_key" { + return false; + } + } + if !keep_force_encrypt && fstab { + patch_encryption(entry.data.as_mut_slice()); + } + 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" { + backups.insert(name[8..].to_string(), entry); + } + }); + self.rm(".backup", false).ok(); + 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: &str) -> anyhow::Result<()> { + let mut backups = HashMap::::new(); + let mut rm_list = String::new(); + backups.insert( + ".backup".to_string(), + 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).ok(); + self.rm(".backup", true).ok(); + + let mut lhs = o.entries.drain_filter(|_, _| true).peekable(); + let mut rhs = self.entries.iter().peekable(); + + loop { + enum Action<'a> { + Backup(String, CpioEntry), + 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) => { + rm_list.push_str(&format!("{}\0", name)); + } + Action::Noop => {} + } + } + if !rm_list.is_empty() { + backups.insert( + ".backup/.rmlist".to_string(), + CpioEntry { + mode: S_IFREG, + uid: 0, + gid: 0, + rdevmajor: 0, + rdevminor: 0, + data: rm_list.as_bytes().to_vec(), + }, + ); + } + self.entries.extend(backups); + + Ok(()) + } +}