mirror of
https://github.com/topjohnwu/Magisk.git
synced 2024-12-21 23:47:39 +00:00
Refactor magiskboot cpio
This commit is contained in:
parent
c1038ac6f9
commit
b8cb9cd84d
@ -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)
|
||||
|
135
native/src/Cargo.lock
generated
135
native/src/Cargo.lock
generated
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -1,241 +0,0 @@
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <libgen.h>
|
||||
#include <algorithm>
|
||||
|
||||
#include <base.hpp>
|
||||
|
||||
#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<char *>(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<const cpio_newc_header *>(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<const char *>(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);
|
||||
}
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <map>
|
||||
#include <string_view>
|
||||
|
||||
#include <base.hpp>
|
||||
|
||||
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<std::string, std::unique_ptr<cpio_entry>, 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);
|
||||
};
|
519
native/src/boot/cpio.rs
Normal file
519
native/src/boot/cpio.rs
Normal file
@ -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<String>,
|
||||
out: Option<String>,
|
||||
},
|
||||
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<String>,
|
||||
#[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<String, CpioEntry>,
|
||||
}
|
||||
|
||||
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<u8>,
|
||||
}
|
||||
|
||||
impl Cpio {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
entries: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
fn load_from_data(data: &[u8]) -> anyhow::Result<Self> {
|
||||
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::<CpioHeader>();
|
||||
let name = CStr::from_bytes_until_nul(&data[pos..])?
|
||||
.to_str()?
|
||||
.to_string();
|
||||
pos += x8u::<usize>(&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::<usize>(&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<Self> {
|
||||
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<U>(x: &[u8; 8]) -> anyhow::Result<U>
|
||||
where
|
||||
U: TryFrom<u32>,
|
||||
{
|
||||
// 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, String> {
|
||||
mode_t::from_str_radix(s, 8).map_err(|e| e.to_string())
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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<uint8_t> data);
|
||||
bool patch_encryption(byte_data data);
|
||||
void patch_encryption(rust::Slice<uint8_t> 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;
|
||||
}
|
||||
|
@ -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))
|
||||
|
@ -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<uint8_t> 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<uint8_t> data) {
|
||||
patch_encryption(byte_data{data.data(), data.size()});
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -1,261 +0,0 @@
|
||||
#include <base.hpp>
|
||||
|
||||
#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<entry_map::iterator> 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;
|
||||
}
|
192
native/src/boot/ramdisk.rs
Normal file
192
native/src/boot/ramdisk.rs
Normal file
@ -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::<String, CpioEntry>::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::<String, CpioEntry>::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(())
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user