You've already forked Magisk
mirror of
https://github.com/topjohnwu/Magisk.git
synced 2025-09-06 06:36:58 +00:00
Restructure the native module
Consolidate all code into the src folder
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
test.cpp
|
||||
target/
|
||||
@@ -0,0 +1,25 @@
|
||||
LOCAL_PATH := $(call my-dir)
|
||||
|
||||
###########################
|
||||
# Rust compilation outputs
|
||||
###########################
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := magisk-rs
|
||||
LOCAL_SRC_FILES := ../out/$(TARGET_ARCH_ABI)/libmagisk-rs.a
|
||||
include $(PREBUILT_STATIC_LIBRARY)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := boot-rs
|
||||
LOCAL_SRC_FILES := ../out/$(TARGET_ARCH_ABI)/libmagiskboot-rs.a
|
||||
include $(PREBUILT_STATIC_LIBRARY)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := init-rs
|
||||
LOCAL_SRC_FILES := ../out/$(TARGET_ARCH_ABI)/libmagiskinit-rs.a
|
||||
include $(PREBUILT_STATIC_LIBRARY)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := policy-rs
|
||||
LOCAL_SRC_FILES := ../out/$(TARGET_ARCH_ABI)/libmagiskpolicy-rs.a
|
||||
include $(PREBUILT_STATIC_LIBRARY)
|
||||
@@ -0,0 +1,197 @@
|
||||
LOCAL_PATH := $(call my-dir)
|
||||
|
||||
########################
|
||||
# Binaries
|
||||
########################
|
||||
|
||||
ifdef B_MAGISK
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := magisk
|
||||
LOCAL_STATIC_LIBRARIES := \
|
||||
libbase \
|
||||
libnanopb \
|
||||
libsystemproperties \
|
||||
libphmap \
|
||||
libxhook \
|
||||
libmincrypt \
|
||||
libmagisk-rs
|
||||
|
||||
LOCAL_SRC_FILES := \
|
||||
core/applets.cpp \
|
||||
core/magisk.cpp \
|
||||
core/daemon.cpp \
|
||||
core/bootstages.cpp \
|
||||
core/socket.cpp \
|
||||
core/db.cpp \
|
||||
core/package.cpp \
|
||||
core/cert.cpp \
|
||||
core/scripting.cpp \
|
||||
core/restorecon.cpp \
|
||||
core/module.cpp \
|
||||
core/logging.cpp \
|
||||
core/thread.cpp \
|
||||
resetprop/persist.cpp \
|
||||
resetprop/resetprop.cpp \
|
||||
su/su.cpp \
|
||||
su/connect.cpp \
|
||||
su/pts.cpp \
|
||||
su/su_daemon.cpp \
|
||||
zygisk/entry.cpp \
|
||||
zygisk/main.cpp \
|
||||
zygisk/utils.cpp \
|
||||
zygisk/hook.cpp \
|
||||
zygisk/memory.cpp \
|
||||
zygisk/deny/cli.cpp \
|
||||
zygisk/deny/utils.cpp \
|
||||
zygisk/deny/revert.cpp
|
||||
|
||||
LOCAL_LDLIBS := -llog
|
||||
|
||||
include $(BUILD_EXECUTABLE)
|
||||
|
||||
endif
|
||||
|
||||
ifdef B_PRELOAD
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := preload
|
||||
LOCAL_SRC_FILES := init/preload.c
|
||||
include $(BUILD_SHARED_LIBRARY)
|
||||
|
||||
endif
|
||||
|
||||
ifdef B_INIT
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := magiskinit
|
||||
LOCAL_STATIC_LIBRARIES := \
|
||||
libbase \
|
||||
libcompat \
|
||||
libpolicy \
|
||||
libxz \
|
||||
libinit-rs
|
||||
|
||||
LOCAL_SRC_FILES := \
|
||||
init/init.cpp \
|
||||
init/mount.cpp \
|
||||
init/rootdir.cpp \
|
||||
init/getinfo.cpp \
|
||||
init/twostage.cpp \
|
||||
init/selinux.cpp
|
||||
|
||||
include $(BUILD_EXECUTABLE)
|
||||
|
||||
endif
|
||||
|
||||
ifdef B_BOOT
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := magiskboot
|
||||
LOCAL_STATIC_LIBRARIES := \
|
||||
libbase \
|
||||
libcompat \
|
||||
libmincrypt \
|
||||
liblzma \
|
||||
liblz4 \
|
||||
libbz2 \
|
||||
libfdt \
|
||||
libz \
|
||||
libzopfli \
|
||||
libboot-rs
|
||||
|
||||
LOCAL_SRC_FILES := \
|
||||
boot/main.cpp \
|
||||
boot/bootimg.cpp \
|
||||
boot/hexpatch.cpp \
|
||||
boot/compress.cpp \
|
||||
boot/format.cpp \
|
||||
boot/dtb.cpp \
|
||||
boot/ramdisk.cpp \
|
||||
boot/pattern.cpp \
|
||||
boot/cpio.cpp
|
||||
|
||||
include $(BUILD_EXECUTABLE)
|
||||
|
||||
endif
|
||||
|
||||
ifdef B_POLICY
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := magiskpolicy
|
||||
LOCAL_STATIC_LIBRARIES := \
|
||||
libbase \
|
||||
libbase \
|
||||
libpolicy \
|
||||
libpolicy-rs
|
||||
|
||||
LOCAL_SRC_FILES := sepolicy/main.cpp
|
||||
|
||||
include $(BUILD_EXECUTABLE)
|
||||
|
||||
endif
|
||||
|
||||
ifdef B_PROP
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := resetprop
|
||||
LOCAL_STATIC_LIBRARIES := \
|
||||
libbase \
|
||||
libcompat \
|
||||
libnanopb \
|
||||
libsystemproperties \
|
||||
libmagisk-rs
|
||||
|
||||
LOCAL_SRC_FILES := \
|
||||
core/applet_stub.cpp \
|
||||
resetprop/persist_properties.cpp \
|
||||
resetprop/resetprop.cpp \
|
||||
|
||||
LOCAL_CFLAGS := -DAPPLET_STUB_MAIN=resetprop_main
|
||||
include $(BUILD_EXECUTABLE)
|
||||
|
||||
endif
|
||||
|
||||
ifdef B_TEST
|
||||
ifneq (,$(wildcard src/test.cpp))
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := test
|
||||
LOCAL_STATIC_LIBRARIES := \
|
||||
libbase \
|
||||
libphmap
|
||||
|
||||
LOCAL_SRC_FILES := test.cpp
|
||||
include $(BUILD_EXECUTABLE)
|
||||
|
||||
endif
|
||||
endif
|
||||
|
||||
########################
|
||||
# Libraries
|
||||
########################
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := libpolicy
|
||||
LOCAL_STATIC_LIBRARIES := \
|
||||
libbase \
|
||||
libsepol
|
||||
LOCAL_C_INCLUDES := src/sepolicy/include
|
||||
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_C_INCLUDES)
|
||||
LOCAL_SRC_FILES := \
|
||||
sepolicy/api.cpp \
|
||||
sepolicy/sepolicy.cpp \
|
||||
sepolicy/rules.cpp \
|
||||
sepolicy/policydb.cpp \
|
||||
sepolicy/statement.cpp
|
||||
include $(BUILD_STATIC_LIBRARY)
|
||||
|
||||
include src/Android-rs.mk
|
||||
include src/base/Android.mk
|
||||
include src/external/Android.mk
|
||||
|
||||
ifdef B_BB
|
||||
|
||||
include src/external/busybox/Android.mk
|
||||
|
||||
endif
|
||||
@@ -0,0 +1,2 @@
|
||||
<!-- This file exists only to make Android Studio happy -->
|
||||
<manifest/>
|
||||
@@ -0,0 +1,18 @@
|
||||
APP_BUILD_SCRIPT := src/Android.mk
|
||||
APP_ABI := armeabi-v7a arm64-v8a x86 x86_64
|
||||
APP_CFLAGS := -Wall -Oz -fomit-frame-pointer -flto
|
||||
APP_LDFLAGS := -flto
|
||||
APP_CPPFLAGS := -std=c++20
|
||||
APP_STL := none
|
||||
APP_PLATFORM := android-21
|
||||
APP_THIN_ARCHIVE := true
|
||||
APP_STRIP_MODE := --strip-all
|
||||
|
||||
# Busybox should use stock libc.a
|
||||
ifdef B_BB
|
||||
APP_PLATFORM := android-24
|
||||
APP_LDFLAGS += -T src/lto_fix.lds
|
||||
ifeq ($(OS),Windows_NT)
|
||||
APP_SHORT_COMMANDS := true
|
||||
endif
|
||||
endif
|
||||
Generated
+103
@@ -0,0 +1,103 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "base"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cxx",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.73"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
|
||||
|
||||
[[package]]
|
||||
name = "cxx"
|
||||
version = "1.0.69"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cxxbridge-flags",
|
||||
"cxxbridge-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cxxbridge-flags"
|
||||
version = "1.0.69"
|
||||
|
||||
[[package]]
|
||||
name = "cxxbridge-macro"
|
||||
version = "1.0.69"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "magisk"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"base",
|
||||
"cxx",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "magiskboot"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"base",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "magiskinit"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"base",
|
||||
"cxx",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "magiskpolicy"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"base",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.98"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c"
|
||||
@@ -0,0 +1,26 @@
|
||||
[workspace]
|
||||
|
||||
members = [
|
||||
"base",
|
||||
"boot",
|
||||
"core",
|
||||
"init",
|
||||
"sepolicy",
|
||||
]
|
||||
|
||||
[profile.dev]
|
||||
opt-level = "z"
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
panic = "abort"
|
||||
strip = true
|
||||
|
||||
[profile.release]
|
||||
opt-level = "z"
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
panic = "abort"
|
||||
strip = true
|
||||
|
||||
[patch.crates-io]
|
||||
cxx = { path = "../src/external/cxx-rs" }
|
||||
@@ -0,0 +1,32 @@
|
||||
LOCAL_PATH := $(call my-dir)
|
||||
|
||||
# Magisk project-wide common code
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := libbase
|
||||
LOCAL_C_INCLUDES := src/include $(LOCAL_PATH)/include out/generated
|
||||
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_C_INCLUDES)
|
||||
LOCAL_EXPORT_STATIC_LIBRARIES := libcxx
|
||||
LOCAL_STATIC_LIBRARIES := libcxx
|
||||
LOCAL_SRC_FILES := \
|
||||
new.cpp \
|
||||
files.cpp \
|
||||
misc.cpp \
|
||||
selinux.cpp \
|
||||
logging.cpp \
|
||||
xwrap.cpp \
|
||||
stream.cpp \
|
||||
../external/cxx-rs/src/cxx.cc
|
||||
include $(BUILD_STATIC_LIBRARY)
|
||||
|
||||
# All static executables should link with libcompat
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := libcompat
|
||||
# Workaround "hacky" libc.a missing symbols
|
||||
# To build Magisk with vanilla NDK, comment out the next line
|
||||
LOCAL_SRC_FILES := compat/compat.cpp
|
||||
# Fix static variables' ctor/dtor when using LTO
|
||||
# See: https://github.com/android/ndk/issues/1461
|
||||
LOCAL_EXPORT_LDLIBS := -static -T src/lto_fix.lds
|
||||
include $(BUILD_STATIC_LIBRARY)
|
||||
@@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "base"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
cxx = "1.0.69"
|
||||
@@ -0,0 +1,153 @@
|
||||
// This file implements all missing symbols that should exist in normal API 21
|
||||
// libc.a but missing in our extremely lean libc.a replacements.
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <mntent.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
extern "C" {
|
||||
|
||||
// Original source: https://github.com/freebsd/freebsd/blob/master/contrib/file/src/getline.c
|
||||
// License: BSD, full copyright notice please check original source
|
||||
|
||||
ssize_t getdelim(char **buf, size_t *bufsiz, int delimiter, FILE *fp) {
|
||||
char *ptr, *eptr;
|
||||
|
||||
if (*buf == nullptr || *bufsiz == 0) {
|
||||
*bufsiz = BUFSIZ;
|
||||
if ((*buf = (char *) malloc(*bufsiz)) == nullptr)
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (ptr = *buf, eptr = *buf + *bufsiz;;) {
|
||||
int c = fgetc(fp);
|
||||
if (c == -1) {
|
||||
if (feof(fp))
|
||||
return ptr == *buf ? -1 : ptr - *buf;
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
*ptr++ = c;
|
||||
if (c == delimiter) {
|
||||
*ptr = '\0';
|
||||
return ptr - *buf;
|
||||
}
|
||||
if (ptr + 2 >= eptr) {
|
||||
char *nbuf;
|
||||
size_t nbufsiz = *bufsiz * 2;
|
||||
ssize_t d = ptr - *buf;
|
||||
if ((nbuf = (char *) realloc(*buf, nbufsiz)) == nullptr)
|
||||
return -1;
|
||||
*buf = nbuf;
|
||||
*bufsiz = nbufsiz;
|
||||
eptr = nbuf + nbufsiz;
|
||||
ptr = nbuf + d;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ssize_t getline(char **buf, size_t *bufsiz, FILE *fp) {
|
||||
return getdelim(buf, bufsiz, '\n', fp);
|
||||
}
|
||||
|
||||
FILE *setmntent(const char *path, const char *mode) {
|
||||
return fopen(path, mode);
|
||||
}
|
||||
|
||||
int endmntent(FILE *fp) {
|
||||
if (fp != nullptr) {
|
||||
fclose(fp);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Missing system call wrappers
|
||||
|
||||
int setns(int fd, int nstype) {
|
||||
return syscall(__NR_setns, fd, nstype);
|
||||
}
|
||||
|
||||
int unshare(int flags) {
|
||||
return syscall(__NR_unshare, flags);
|
||||
}
|
||||
|
||||
int accept4(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int flags) {
|
||||
return syscall(__NR_accept4, sockfd, addr, addrlen, flags);
|
||||
}
|
||||
|
||||
int dup3(int oldfd, int newfd, int flags) {
|
||||
return syscall(__NR_dup3, oldfd, newfd, flags);
|
||||
}
|
||||
|
||||
ssize_t readlinkat(int dirfd, const char *pathname, char *buf, size_t bufsiz) {
|
||||
return syscall(__NR_readlinkat, dirfd, pathname, buf, bufsiz);
|
||||
}
|
||||
|
||||
int symlinkat(const char *target, int newdirfd, const char *linkpath) {
|
||||
return syscall(__NR_symlinkat, target, newdirfd, linkpath);
|
||||
}
|
||||
|
||||
int linkat(int olddirfd, const char *oldpath,
|
||||
int newdirfd, const char *newpath, int flags) {
|
||||
return syscall(__NR_linkat, olddirfd, oldpath, newdirfd, newpath, flags);
|
||||
}
|
||||
|
||||
int inotify_init1(int flags) {
|
||||
return syscall(__NR_inotify_init1, flags);
|
||||
}
|
||||
|
||||
int faccessat(int dirfd, const char *pathname, int mode, int flags) {
|
||||
return syscall(__NR_faccessat, dirfd, pathname, mode, flags);
|
||||
}
|
||||
|
||||
int mkfifo(const char *path, mode_t mode) {
|
||||
return mknod(path, (mode & ~S_IFMT) | S_IFIFO, 0);
|
||||
}
|
||||
|
||||
#define SPLIT_64(v) (unsigned)((v) & 0xFFFFFFFF), (unsigned)((v) >> 32)
|
||||
|
||||
#if defined(__arm__)
|
||||
// Why the additional 0 is required: https://man7.org/linux/man-pages/man2/syscall.2.html
|
||||
int ftruncate64(int fd, off64_t length) {
|
||||
return syscall(__NR_ftruncate64, fd, 0, SPLIT_64(length));
|
||||
}
|
||||
#elif defined(__i386__)
|
||||
int ftruncate64(int fd, off64_t length) {
|
||||
return syscall(__NR_ftruncate64, fd, SPLIT_64(length));
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !defined(__LP64__)
|
||||
void android_set_abort_message(const char *) {}
|
||||
|
||||
// Original source: <android/legacy_signal_inlines.h>
|
||||
int sigaddset(sigset_t *set, int signum) {
|
||||
/* Signal numbers start at 1, but bit positions start at 0. */
|
||||
int bit = signum - 1;
|
||||
auto *local_set = (unsigned long *)set;
|
||||
if (set == nullptr || bit < 0 || bit >= (int)(8 * sizeof(sigset_t))) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
local_set[bit / LONG_BIT] |= 1UL << (bit % LONG_BIT);
|
||||
return 0;
|
||||
}
|
||||
int sigemptyset(sigset_t *set) {
|
||||
if (set == nullptr) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
memset(set, 0, sizeof(sigset_t));
|
||||
return 0;
|
||||
}
|
||||
|
||||
#include "fortify.hpp"
|
||||
|
||||
#endif
|
||||
|
||||
} // extern "C"
|
||||
@@ -0,0 +1,110 @@
|
||||
// Original source: https://android.googlesource.com/platform/bionic/+/master/libc/bionic/fortify.cpp
|
||||
// License: AOSP, full copyright notice please check original source
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#undef _FORTIFY_SOURCE
|
||||
|
||||
extern void __vloge(const char* fmt, va_list ap);
|
||||
|
||||
static inline __noreturn __printflike(1, 2) void __fortify_fatal(const char* fmt, ...) {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
__vloge(fmt, args);
|
||||
va_end(args);
|
||||
abort();
|
||||
}
|
||||
static inline void __check_count(const char* fn, const char* identifier, size_t value) {
|
||||
if (__predict_false(value > SSIZE_MAX)) {
|
||||
__fortify_fatal("%s: %s %zu > SSIZE_MAX", fn, identifier, value);
|
||||
}
|
||||
}
|
||||
static inline void __check_buffer_access(const char* fn, const char* action,
|
||||
size_t claim, size_t actual) {
|
||||
if (__predict_false(claim > actual)) {
|
||||
__fortify_fatal("%s: prevented %zu-byte %s %zu-byte buffer", fn, claim, action, actual);
|
||||
}
|
||||
}
|
||||
char* __strcpy_chk(char* dst, const char* src, size_t dst_len) {
|
||||
// TODO: optimize so we don't scan src twice.
|
||||
size_t src_len = __builtin_strlen(src) + 1;
|
||||
__check_buffer_access("strcpy", "write into", src_len, dst_len);
|
||||
return __builtin_strcpy(dst, src);
|
||||
}
|
||||
size_t __strlcpy_chk(char* dst, const char* src,
|
||||
size_t supplied_size, size_t dst_len_from_compiler) {
|
||||
__check_buffer_access("strlcpy", "write into", supplied_size, dst_len_from_compiler);
|
||||
return __call_bypassing_fortify(strlcpy)(dst, src, supplied_size);
|
||||
}
|
||||
char* __strchr_chk(const char* p, int ch, size_t s_len) {
|
||||
for (;; ++p, s_len--) {
|
||||
if (__predict_false(s_len == 0)) {
|
||||
__fortify_fatal("strchr: prevented read past end of buffer");
|
||||
}
|
||||
if (*p == static_cast<char>(ch)) {
|
||||
return const_cast<char*>(p);
|
||||
}
|
||||
if (*p == '\0') {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
char* __strcat_chk(char* dst, const char* src, size_t dst_buf_size) {
|
||||
char* save = dst;
|
||||
size_t dst_len = __strlen_chk(dst, dst_buf_size);
|
||||
dst += dst_len;
|
||||
dst_buf_size -= dst_len;
|
||||
while ((*dst++ = *src++) != '\0') {
|
||||
dst_buf_size--;
|
||||
if (__predict_false(dst_buf_size == 0)) {
|
||||
__fortify_fatal("strcat: prevented write past end of %zu-byte buffer", dst_buf_size);
|
||||
}
|
||||
}
|
||||
return save;
|
||||
}
|
||||
size_t __strlen_chk(const char* s, size_t s_len) {
|
||||
// TODO: "prevented" here would be a lie because this strlen can run off the end.
|
||||
// strlen is too important to be expensive, so we wanted to be able to call the optimized
|
||||
// implementation, but I think we need to implement optimized assembler __strlen_chk routines.
|
||||
size_t ret = __builtin_strlen(s);
|
||||
if (__predict_false(ret >= s_len)) {
|
||||
__fortify_fatal("strlen: detected read past end of buffer");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
int __vsprintf_chk(char* dst, int /*flags*/,
|
||||
size_t dst_len_from_compiler, const char* format, va_list va) {
|
||||
// The compiler uses SIZE_MAX to mean "no idea", but our vsnprintf rejects sizes that large.
|
||||
int result = __call_bypassing_fortify(vsnprintf)(dst,
|
||||
dst_len_from_compiler == SIZE_MAX ? SSIZE_MAX : dst_len_from_compiler,
|
||||
format, va);
|
||||
// Try to catch failures after the fact...
|
||||
__check_buffer_access("vsprintf", "write into", result + 1, dst_len_from_compiler);
|
||||
return result;
|
||||
}
|
||||
mode_t __umask_chk(mode_t mode) {
|
||||
if (__predict_false((mode & 0777) != mode)) {
|
||||
__fortify_fatal("umask: called with invalid mask %o", mode);
|
||||
}
|
||||
return __umask_real(mode);
|
||||
}
|
||||
ssize_t __read_chk(int fd, void* buf, size_t count, size_t buf_size) {
|
||||
__check_count("read", "count", count);
|
||||
__check_buffer_access("read", "write into", count, buf_size);
|
||||
return __call_bypassing_fortify(read)(fd, buf, count);
|
||||
}
|
||||
static inline bool needs_mode(int flags) {
|
||||
return ((flags & O_CREAT) == O_CREAT) || ((flags & O_TMPFILE) == O_TMPFILE);
|
||||
}
|
||||
static inline int force_O_LARGEFILE(int flags) {
|
||||
return flags | O_LARGEFILE;
|
||||
}
|
||||
int __open_2(const char* pathname, int flags) {
|
||||
if (needs_mode(flags)) __fortify_fatal("open: called with O_CREAT/O_TMPFILE but no mode");
|
||||
return __openat_real(AT_FDCWD, pathname, force_O_LARGEFILE(flags), 0);
|
||||
}
|
||||
int __openat_2(int fd, const char* pathname, int flags) {
|
||||
if (needs_mode(flags)) __fortify_fatal("open: called with O_CREAT/O_TMPFILE but no mode");
|
||||
return __openat_real(fd, pathname, force_O_LARGEFILE(flags), 0);
|
||||
}
|
||||
@@ -0,0 +1,531 @@
|
||||
#include <sys/sendfile.h>
|
||||
#include <linux/fs.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <libgen.h>
|
||||
|
||||
#include <base.hpp>
|
||||
#include <selinux.hpp>
|
||||
|
||||
using namespace std;
|
||||
|
||||
ssize_t fd_path(int fd, char *path, size_t size) {
|
||||
snprintf(path, size, "/proc/self/fd/%d", fd);
|
||||
return xreadlink(path, path, size);
|
||||
}
|
||||
|
||||
int fd_pathat(int dirfd, const char *name, char *path, size_t size) {
|
||||
if (fd_path(dirfd, path, size) < 0)
|
||||
return -1;
|
||||
auto len = strlen(path);
|
||||
path[len] = '/';
|
||||
strlcpy(path + len + 1, name, size - len - 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mkdirs(string path, mode_t mode) {
|
||||
errno = 0;
|
||||
for (char *p = path.data() + 1; *p; ++p) {
|
||||
if (*p == '/') {
|
||||
*p = '\0';
|
||||
if (mkdir(path.data(), mode) == -1) {
|
||||
if (errno != EEXIST)
|
||||
return -1;
|
||||
}
|
||||
*p = '/';
|
||||
}
|
||||
}
|
||||
if (mkdir(path.data(), mode) == -1) {
|
||||
if (errno != EEXIST)
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <typename Func>
|
||||
static void post_order_walk(int dirfd, const Func &fn) {
|
||||
auto dir = xopen_dir(dirfd);
|
||||
if (!dir) return;
|
||||
|
||||
for (dirent *entry; (entry = xreaddir(dir.get()));) {
|
||||
if (entry->d_type == DT_DIR)
|
||||
post_order_walk(xopenat(dirfd, entry->d_name, O_RDONLY | O_CLOEXEC), fn);
|
||||
fn(dirfd, entry);
|
||||
}
|
||||
}
|
||||
|
||||
enum walk_result {
|
||||
CONTINUE, SKIP, ABORT
|
||||
};
|
||||
|
||||
template <typename Func>
|
||||
static walk_result pre_order_walk(int dirfd, const Func &fn) {
|
||||
auto dir = xopen_dir(dirfd);
|
||||
if (!dir) {
|
||||
close(dirfd);
|
||||
return SKIP;
|
||||
}
|
||||
|
||||
for (dirent *entry; (entry = xreaddir(dir.get()));) {
|
||||
switch (fn(dirfd, entry)) {
|
||||
case CONTINUE:
|
||||
break;
|
||||
case SKIP:
|
||||
continue;
|
||||
case ABORT:
|
||||
return ABORT;
|
||||
}
|
||||
if (entry->d_type == DT_DIR) {
|
||||
int fd = xopenat(dirfd, entry->d_name, O_RDONLY | O_CLOEXEC);
|
||||
if (pre_order_walk(fd, fn) == ABORT)
|
||||
return ABORT;
|
||||
}
|
||||
}
|
||||
return CONTINUE;
|
||||
}
|
||||
|
||||
static void remove_at(int dirfd, struct dirent *entry) {
|
||||
unlinkat(dirfd, entry->d_name, entry->d_type == DT_DIR ? AT_REMOVEDIR : 0);
|
||||
}
|
||||
|
||||
void rm_rf(const char *path) {
|
||||
struct stat st;
|
||||
if (lstat(path, &st) < 0)
|
||||
return;
|
||||
if (S_ISDIR(st.st_mode))
|
||||
frm_rf(xopen(path, O_RDONLY | O_CLOEXEC));
|
||||
remove(path);
|
||||
}
|
||||
|
||||
void frm_rf(int dirfd) {
|
||||
post_order_walk(dirfd, remove_at);
|
||||
}
|
||||
|
||||
void mv_path(const char *src, const char *dest) {
|
||||
file_attr attr;
|
||||
getattr(src, &attr);
|
||||
if (S_ISDIR(attr.st.st_mode)) {
|
||||
if (access(dest, F_OK) != 0) {
|
||||
xmkdirs(dest, 0);
|
||||
setattr(dest, &attr);
|
||||
}
|
||||
mv_dir(xopen(src, O_RDONLY | O_CLOEXEC), xopen(dest, O_RDONLY | O_CLOEXEC));
|
||||
} else{
|
||||
xrename(src, dest);
|
||||
}
|
||||
rmdir(src);
|
||||
}
|
||||
|
||||
void mv_dir(int src, int dest) {
|
||||
auto dir = xopen_dir(src);
|
||||
run_finally f([=]{ close(dest); });
|
||||
for (dirent *entry; (entry = xreaddir(dir.get()));) {
|
||||
switch (entry->d_type) {
|
||||
case DT_DIR:
|
||||
if (xfaccessat(dest, entry->d_name) == 0) {
|
||||
// Destination folder exists, needs recursive move
|
||||
int newsrc = xopenat(src, entry->d_name, O_RDONLY | O_CLOEXEC);
|
||||
int newdest = xopenat(dest, entry->d_name, O_RDONLY | O_CLOEXEC);
|
||||
mv_dir(newsrc, newdest);
|
||||
unlinkat(src, entry->d_name, AT_REMOVEDIR);
|
||||
break;
|
||||
}
|
||||
// Else fall through
|
||||
case DT_LNK:
|
||||
case DT_REG:
|
||||
renameat(src, entry->d_name, dest, entry->d_name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void cp_afc(const char *src, const char *dest) {
|
||||
file_attr a;
|
||||
getattr(src, &a);
|
||||
|
||||
if (S_ISDIR(a.st.st_mode)) {
|
||||
xmkdirs(dest, 0);
|
||||
clone_dir(xopen(src, O_RDONLY | O_CLOEXEC), xopen(dest, O_RDONLY | O_CLOEXEC));
|
||||
} else{
|
||||
unlink(dest);
|
||||
if (S_ISREG(a.st.st_mode)) {
|
||||
int sfd = xopen(src, O_RDONLY | O_CLOEXEC);
|
||||
int dfd = xopen(dest, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0);
|
||||
xsendfile(dfd, sfd, nullptr, a.st.st_size);
|
||||
close(sfd);
|
||||
close(dfd);
|
||||
} else if (S_ISLNK(a.st.st_mode)) {
|
||||
char buf[4096];
|
||||
xreadlink(src, buf, sizeof(buf));
|
||||
xsymlink(buf, dest);
|
||||
}
|
||||
}
|
||||
setattr(dest, &a);
|
||||
}
|
||||
|
||||
void clone_dir(int src, int dest) {
|
||||
auto dir = xopen_dir(src);
|
||||
run_finally f([&]{ close(dest); });
|
||||
for (dirent *entry; (entry = xreaddir(dir.get()));) {
|
||||
file_attr a;
|
||||
getattrat(src, entry->d_name, &a);
|
||||
switch (entry->d_type) {
|
||||
case DT_DIR: {
|
||||
xmkdirat(dest, entry->d_name, 0);
|
||||
setattrat(dest, entry->d_name, &a);
|
||||
int sfd = xopenat(src, entry->d_name, O_RDONLY | O_CLOEXEC);
|
||||
int dst = xopenat(dest, entry->d_name, O_RDONLY | O_CLOEXEC);
|
||||
clone_dir(sfd, dst);
|
||||
break;
|
||||
}
|
||||
case DT_REG: {
|
||||
int sfd = xopenat(src, entry->d_name, O_RDONLY | O_CLOEXEC);
|
||||
int dfd = xopenat(dest, entry->d_name, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0);
|
||||
xsendfile(dfd, sfd, nullptr, a.st.st_size);
|
||||
fsetattr(dfd, &a);
|
||||
close(dfd);
|
||||
close(sfd);
|
||||
break;
|
||||
}
|
||||
case DT_LNK: {
|
||||
char buf[4096];
|
||||
xreadlinkat(src, entry->d_name, buf, sizeof(buf));
|
||||
xsymlinkat(buf, dest, entry->d_name);
|
||||
setattrat(dest, entry->d_name, &a);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void link_path(const char *src, const char *dest) {
|
||||
link_dir(xopen(src, O_RDONLY | O_CLOEXEC), xopen(dest, O_RDONLY | O_CLOEXEC));
|
||||
}
|
||||
|
||||
void link_dir(int src, int dest) {
|
||||
auto dir = xopen_dir(src);
|
||||
run_finally f([&]{ close(dest); });
|
||||
for (dirent *entry; (entry = xreaddir(dir.get()));) {
|
||||
if (entry->d_type == DT_DIR) {
|
||||
file_attr a;
|
||||
getattrat(src, entry->d_name, &a);
|
||||
xmkdirat(dest, entry->d_name, 0);
|
||||
setattrat(dest, entry->d_name, &a);
|
||||
int sfd = xopenat(src, entry->d_name, O_RDONLY | O_CLOEXEC);
|
||||
int dfd = xopenat(dest, entry->d_name, O_RDONLY | O_CLOEXEC);
|
||||
link_dir(sfd, dfd);
|
||||
} else {
|
||||
xlinkat(src, entry->d_name, dest, entry->d_name, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int getattr(const char *path, file_attr *a) {
|
||||
if (xlstat(path, &a->st) == -1)
|
||||
return -1;
|
||||
char *con;
|
||||
if (lgetfilecon(path, &con) == -1)
|
||||
return -1;
|
||||
strcpy(a->con, con);
|
||||
freecon(con);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int getattrat(int dirfd, const char *name, file_attr *a) {
|
||||
char path[4096];
|
||||
fd_pathat(dirfd, name, path, sizeof(path));
|
||||
return getattr(path, a);
|
||||
}
|
||||
|
||||
int fgetattr(int fd, file_attr *a) {
|
||||
if (xfstat(fd, &a->st) < 0)
|
||||
return -1;
|
||||
char *con;
|
||||
if (fgetfilecon(fd, &con) < 0)
|
||||
return -1;
|
||||
strcpy(a->con, con);
|
||||
freecon(con);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int setattr(const char *path, file_attr *a) {
|
||||
if (chmod(path, a->st.st_mode & 0777) < 0)
|
||||
return -1;
|
||||
if (chown(path, a->st.st_uid, a->st.st_gid) < 0)
|
||||
return -1;
|
||||
if (a->con[0] && lsetfilecon(path, a->con) < 0)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int setattrat(int dirfd, const char *name, file_attr *a) {
|
||||
char path[4096];
|
||||
fd_pathat(dirfd, name, path, sizeof(path));
|
||||
return setattr(path, a);
|
||||
}
|
||||
|
||||
int fsetattr(int fd, file_attr *a) {
|
||||
if (fchmod(fd, a->st.st_mode & 0777) < 0)
|
||||
return -1;
|
||||
if (fchown(fd, a->st.st_uid, a->st.st_gid) < 0)
|
||||
return -1;
|
||||
if (a->con[0] && fsetfilecon(fd, a->con) < 0)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void clone_attr(const char *src, const char *dest) {
|
||||
file_attr a;
|
||||
getattr(src, &a);
|
||||
setattr(dest, &a);
|
||||
}
|
||||
|
||||
void fclone_attr(int src, int dest) {
|
||||
file_attr a;
|
||||
fgetattr(src, &a);
|
||||
fsetattr(dest, &a);
|
||||
}
|
||||
|
||||
void full_read(int fd, string &str) {
|
||||
char buf[4096];
|
||||
for (ssize_t len; (len = xread(fd, buf, sizeof(buf))) > 0;)
|
||||
str.insert(str.end(), buf, buf + len);
|
||||
}
|
||||
|
||||
void full_read(const char *filename, string &str) {
|
||||
if (int fd = xopen(filename, O_RDONLY | O_CLOEXEC); fd >= 0) {
|
||||
full_read(fd, str);
|
||||
close(fd);
|
||||
}
|
||||
}
|
||||
|
||||
string full_read(int fd) {
|
||||
string str;
|
||||
full_read(fd, str);
|
||||
return str;
|
||||
}
|
||||
|
||||
string full_read(const char *filename) {
|
||||
string str;
|
||||
full_read(filename, str);
|
||||
return str;
|
||||
}
|
||||
|
||||
void write_zero(int fd, size_t size) {
|
||||
char buf[4096] = {0};
|
||||
size_t len;
|
||||
while (size > 0) {
|
||||
len = sizeof(buf) > size ? size : sizeof(buf);
|
||||
write(fd, buf, len);
|
||||
size -= len;
|
||||
}
|
||||
}
|
||||
|
||||
void file_readline(bool trim, FILE *fp, const function<bool(string_view)> &fn) {
|
||||
size_t len = 1024;
|
||||
char *buf = (char *) malloc(len);
|
||||
char *start;
|
||||
ssize_t read;
|
||||
while ((read = getline(&buf, &len, fp)) >= 0) {
|
||||
start = buf;
|
||||
if (trim) {
|
||||
while (read && "\n\r "sv.find(buf[read - 1]) != string::npos)
|
||||
--read;
|
||||
buf[read] = '\0';
|
||||
while (*start == ' ')
|
||||
++start;
|
||||
}
|
||||
if (!fn(start))
|
||||
break;
|
||||
}
|
||||
free(buf);
|
||||
}
|
||||
|
||||
void file_readline(bool trim, const char *file, const function<bool(string_view)> &fn) {
|
||||
if (auto fp = open_file(file, "re"))
|
||||
file_readline(trim, fp.get(), fn);
|
||||
}
|
||||
void file_readline(const char *file, const function<bool(string_view)> &fn) {
|
||||
file_readline(false, file, fn);
|
||||
}
|
||||
|
||||
void parse_prop_file(FILE *fp, const function<bool(string_view, string_view)> &fn) {
|
||||
file_readline(true, fp, [&](string_view line_view) -> bool {
|
||||
char *line = (char *) line_view.data();
|
||||
if (line[0] == '#')
|
||||
return true;
|
||||
char *eql = strchr(line, '=');
|
||||
if (eql == nullptr || eql == line)
|
||||
return true;
|
||||
*eql = '\0';
|
||||
return fn(line, eql + 1);
|
||||
});
|
||||
}
|
||||
|
||||
void parse_prop_file(const char *file, const function<bool(string_view, string_view)> &fn) {
|
||||
if (auto fp = open_file(file, "re"))
|
||||
parse_prop_file(fp.get(), fn);
|
||||
}
|
||||
|
||||
// Original source: https://android.googlesource.com/platform/bionic/+/master/libc/bionic/mntent.cpp
|
||||
// License: AOSP, full copyright notice please check original source
|
||||
static struct mntent *compat_getmntent_r(FILE *fp, struct mntent *e, char *buf, int buf_len) {
|
||||
memset(e, 0, sizeof(*e));
|
||||
while (fgets(buf, buf_len, fp) != nullptr) {
|
||||
// Entries look like "proc /proc proc rw,nosuid,nodev,noexec,relatime 0 0".
|
||||
// That is: mnt_fsname mnt_dir mnt_type mnt_opts 0 0.
|
||||
int fsname0, fsname1, dir0, dir1, type0, type1, opts0, opts1;
|
||||
if (sscanf(buf, " %n%*s%n %n%*s%n %n%*s%n %n%*s%n %d %d",
|
||||
&fsname0, &fsname1, &dir0, &dir1, &type0, &type1, &opts0, &opts1,
|
||||
&e->mnt_freq, &e->mnt_passno) == 2) {
|
||||
e->mnt_fsname = &buf[fsname0];
|
||||
buf[fsname1] = '\0';
|
||||
e->mnt_dir = &buf[dir0];
|
||||
buf[dir1] = '\0';
|
||||
e->mnt_type = &buf[type0];
|
||||
buf[type1] = '\0';
|
||||
e->mnt_opts = &buf[opts0];
|
||||
buf[opts1] = '\0';
|
||||
return e;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void parse_mnt(const char *file, const function<bool(mntent*)> &fn) {
|
||||
auto fp = sFILE(setmntent(file, "re"), endmntent);
|
||||
if (fp) {
|
||||
mntent mentry{};
|
||||
char buf[4096];
|
||||
// getmntent_r from system's libc.so is broken on old platform
|
||||
// use the compat one instead
|
||||
while (compat_getmntent_r(fp.get(), &mentry, buf, sizeof(buf))) {
|
||||
if (!fn(&mentry))
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void backup_folder(const char *dir, vector<raw_file> &files) {
|
||||
char path[PATH_MAX];
|
||||
xrealpath(dir, path);
|
||||
int len = strlen(path);
|
||||
pre_order_walk(xopen(dir, O_RDONLY), [&](int dfd, dirent *entry) -> walk_result {
|
||||
int fd = xopenat(dfd, entry->d_name, O_RDONLY);
|
||||
if (fd < 0)
|
||||
return SKIP;
|
||||
run_finally f([&]{ close(fd); });
|
||||
if (fd_path(fd, path, sizeof(path)) < 0)
|
||||
return SKIP;
|
||||
raw_file file;
|
||||
file.path = path + len + 1;
|
||||
if (fgetattr(fd, &file.attr) < 0)
|
||||
return SKIP;
|
||||
if (entry->d_type == DT_REG) {
|
||||
file.content = full_read(fd);
|
||||
} else if (entry->d_type == DT_LNK) {
|
||||
xreadlinkat(dfd, entry->d_name, path, sizeof(path));
|
||||
file.content = path;
|
||||
}
|
||||
files.emplace_back(std::move(file));
|
||||
return CONTINUE;
|
||||
});
|
||||
}
|
||||
|
||||
void restore_folder(const char *dir, vector<raw_file> &files) {
|
||||
string base(dir);
|
||||
// Pre-order means folders will always be first
|
||||
for (raw_file &file : files) {
|
||||
string path = base + "/" + file.path;
|
||||
if (S_ISDIR(file.attr.st.st_mode)) {
|
||||
mkdirs(path, 0);
|
||||
} else if (S_ISREG(file.attr.st.st_mode)) {
|
||||
if (auto fp = xopen_file(path.data(), "we"))
|
||||
fwrite(file.content.data(), 1, file.content.size(), fp.get());
|
||||
} else if (S_ISLNK(file.attr.st.st_mode)) {
|
||||
symlink(file.content.data(), path.data());
|
||||
}
|
||||
setattr(path.data(), &file.attr);
|
||||
}
|
||||
}
|
||||
|
||||
sDIR make_dir(DIR *dp) {
|
||||
return sDIR(dp, [](DIR *dp){ return dp ? closedir(dp) : 1; });
|
||||
}
|
||||
|
||||
sFILE make_file(FILE *fp) {
|
||||
return sFILE(fp, [](FILE *fp){ return fp ? fclose(fp) : 1; });
|
||||
}
|
||||
|
||||
int byte_data::patch(bool log, str_pairs list) {
|
||||
if (buf == nullptr)
|
||||
return 0;
|
||||
int count = 0;
|
||||
for (uint8_t *p = buf, *eof = buf + sz; p < eof; ++p) {
|
||||
for (auto [from, to] : list) {
|
||||
if (memcmp(p, from.data(), from.length() + 1) == 0) {
|
||||
if (log) LOGD("Replace [%s] -> [%s]\n", from.data(), to.data());
|
||||
memset(p, 0, from.length());
|
||||
memcpy(p, to.data(), to.length());
|
||||
++count;
|
||||
p += from.length();
|
||||
}
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
bool byte_data::contains(string_view pattern, bool log) const {
|
||||
if (buf == nullptr)
|
||||
return false;
|
||||
for (uint8_t *p = buf, *eof = buf + sz; p < eof; ++p) {
|
||||
if (memcmp(p, pattern.data(), pattern.length() + 1) == 0) {
|
||||
if (log) LOGD("Found pattern [%s]\n", pattern.data());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void byte_data::swap(byte_data &o) {
|
||||
std::swap(buf, o.buf);
|
||||
std::swap(sz, o.sz);
|
||||
}
|
||||
|
||||
mmap_data::mmap_data(const char *name, bool rw) {
|
||||
int fd = xopen(name, (rw ? O_RDWR : O_RDONLY) | O_CLOEXEC);
|
||||
if (fd < 0)
|
||||
return;
|
||||
struct stat st;
|
||||
if (fstat(fd, &st))
|
||||
return;
|
||||
if (S_ISBLK(st.st_mode)) {
|
||||
uint64_t size;
|
||||
ioctl(fd, BLKGETSIZE64, &size);
|
||||
sz = size;
|
||||
} else {
|
||||
sz = st.st_size;
|
||||
}
|
||||
void *b = sz > 0
|
||||
? xmmap(nullptr, sz, PROT_READ | PROT_WRITE, rw ? MAP_SHARED : MAP_PRIVATE, fd, 0)
|
||||
: nullptr;
|
||||
close(fd);
|
||||
buf = static_cast<uint8_t *>(b);
|
||||
}
|
||||
|
||||
string find_apk_path(const char *pkg) {
|
||||
char buf[PATH_MAX];
|
||||
pre_order_walk(xopen("/data/app", O_RDONLY), [&](int dfd, dirent *entry) -> walk_result {
|
||||
if (entry->d_type != DT_DIR)
|
||||
return SKIP;
|
||||
size_t len = strlen(pkg);
|
||||
if (strncmp(entry->d_name, pkg, len) == 0 && entry->d_name[len] == '-') {
|
||||
fd_pathat(dfd, entry->d_name, buf, sizeof(buf));
|
||||
return ABORT;
|
||||
} else if (strncmp(entry->d_name, "~~", 2) == 0) {
|
||||
return CONTINUE;
|
||||
} else return SKIP;
|
||||
});
|
||||
string path(buf);
|
||||
return path.append("/base.apk");
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
#pragma once
|
||||
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <mntent.h>
|
||||
#include <functional>
|
||||
#include <string_view>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "xwrap.hpp"
|
||||
|
||||
template <typename T>
|
||||
static inline T align_to(T v, int a) {
|
||||
static_assert(std::is_integral<T>::value);
|
||||
return (v + a - 1) / a * a;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static inline T align_padding(T v, int a) {
|
||||
return align_to(v, a) - v;
|
||||
}
|
||||
|
||||
struct file_attr {
|
||||
struct stat st;
|
||||
char con[128];
|
||||
};
|
||||
|
||||
struct byte_data {
|
||||
using str_pairs = std::initializer_list<std::pair<std::string_view, std::string_view>>;
|
||||
|
||||
uint8_t *buf = nullptr;
|
||||
size_t sz = 0;
|
||||
|
||||
int patch(str_pairs list) { return patch(true, list); }
|
||||
int patch(bool log, str_pairs list);
|
||||
bool contains(std::string_view pattern, bool log = true) const;
|
||||
protected:
|
||||
void swap(byte_data &o);
|
||||
};
|
||||
|
||||
struct raw_file {
|
||||
std::string path;
|
||||
file_attr attr;
|
||||
std::string content;
|
||||
|
||||
raw_file() : attr{} {}
|
||||
raw_file(const raw_file&) = delete;
|
||||
raw_file(raw_file &&o) : path(std::move(o.path)), attr(o.attr), content(std::move(o.content)) {}
|
||||
};
|
||||
|
||||
struct mmap_data : public byte_data {
|
||||
mmap_data() = default;
|
||||
mmap_data(const mmap_data&) = delete;
|
||||
mmap_data(mmap_data &&o) { swap(o); }
|
||||
mmap_data(const char *name, bool rw = false);
|
||||
~mmap_data() { if (buf) munmap(buf, sz); }
|
||||
mmap_data& operator=(mmap_data &&other) { swap(other); return *this; }
|
||||
};
|
||||
|
||||
ssize_t fd_path(int fd, char *path, size_t size);
|
||||
int fd_pathat(int dirfd, const char *name, char *path, size_t size);
|
||||
int mkdirs(std::string path, mode_t mode);
|
||||
void rm_rf(const char *path);
|
||||
void mv_path(const char *src, const char *dest);
|
||||
void mv_dir(int src, int dest);
|
||||
void cp_afc(const char *src, const char *dest);
|
||||
void link_path(const char *src, const char *dest);
|
||||
void link_dir(int src, int dest);
|
||||
int getattr(const char *path, file_attr *a);
|
||||
int getattrat(int dirfd, const char *name, file_attr *a);
|
||||
int fgetattr(int fd, file_attr *a);
|
||||
int setattr(const char *path, file_attr *a);
|
||||
int setattrat(int dirfd, const char *name, file_attr *a);
|
||||
int fsetattr(int fd, file_attr *a);
|
||||
void fclone_attr(int src, int dest);
|
||||
void clone_attr(const char *src, const char *dest);
|
||||
void full_read(int fd, std::string &str);
|
||||
void full_read(const char *filename, std::string &str);
|
||||
std::string full_read(int fd);
|
||||
std::string full_read(const char *filename);
|
||||
void write_zero(int fd, size_t size);
|
||||
void file_readline(bool trim, FILE *fp, const std::function<bool(std::string_view)> &fn);
|
||||
void file_readline(bool trim, const char *file, const std::function<bool(std::string_view)> &fn);
|
||||
void file_readline(const char *file, const std::function<bool(std::string_view)> &fn);
|
||||
void parse_prop_file(FILE *fp, const std::function<bool(std::string_view, std::string_view)> &fn);
|
||||
void parse_prop_file(const char *file,
|
||||
const std::function<bool(std::string_view, std::string_view)> &fn);
|
||||
void frm_rf(int dirfd);
|
||||
void clone_dir(int src, int dest);
|
||||
void parse_mnt(const char *file, const std::function<bool(mntent*)> &fn);
|
||||
void backup_folder(const char *dir, std::vector<raw_file> &files);
|
||||
void restore_folder(const char *dir, std::vector<raw_file> &files);
|
||||
std::string find_apk_path(const char *pkg);
|
||||
|
||||
using sFILE = std::unique_ptr<FILE, decltype(&fclose)>;
|
||||
using sDIR = std::unique_ptr<DIR, decltype(&closedir)>;
|
||||
sDIR make_dir(DIR *dp);
|
||||
sFILE make_file(FILE *fp);
|
||||
|
||||
static inline sDIR open_dir(const char *path) {
|
||||
return make_dir(opendir(path));
|
||||
}
|
||||
|
||||
static inline sDIR xopen_dir(const char *path) {
|
||||
return make_dir(xopendir(path));
|
||||
}
|
||||
|
||||
static inline sDIR xopen_dir(int dirfd) {
|
||||
return make_dir(xfdopendir(dirfd));
|
||||
}
|
||||
|
||||
static inline sFILE open_file(const char *path, const char *mode) {
|
||||
return make_file(fopen(path, mode));
|
||||
}
|
||||
|
||||
static inline sFILE xopen_file(const char *path, const char *mode) {
|
||||
return make_file(xfopen(path, mode));
|
||||
}
|
||||
|
||||
static inline sFILE xopen_file(int fd, const char *mode) {
|
||||
return make_file(xfdopen(fd, mode));
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include "../missing.hpp"
|
||||
#include "../xwrap.hpp"
|
||||
#include "../files.hpp"
|
||||
#include "../misc.hpp"
|
||||
#include "../logging.hpp"
|
||||
#include <base-rs.hpp>
|
||||
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
// selinuxfs paths
|
||||
#define SELINUX_MNT "/sys/fs/selinux"
|
||||
#define SELINUX_ENFORCE SELINUX_MNT "/enforce"
|
||||
#define SELINUX_POLICY SELINUX_MNT "/policy"
|
||||
#define SELINUX_LOAD SELINUX_MNT "/load"
|
||||
#define SELINUX_CONTEXT SELINUX_MNT "/context"
|
||||
#define SELINUX_VERSION SELINUX_MNT "/policyvers"
|
||||
|
||||
// sepolicy paths
|
||||
#define PLAT_POLICY_DIR "/system/etc/selinux/"
|
||||
#define VEND_POLICY_DIR "/vendor/etc/selinux/"
|
||||
#define PROD_POLICY_DIR "/product/etc/selinux/"
|
||||
#define ODM_POLICY_DIR "/odm/etc/selinux/"
|
||||
#define SYSEXT_POLICY_DIR "/system_ext/etc/selinux/"
|
||||
#define SPLIT_PLAT_CIL PLAT_POLICY_DIR "plat_sepolicy.cil"
|
||||
|
||||
// Unconstrained domain the daemon and root processes run in
|
||||
#define SEPOL_PROC_DOMAIN "magisk"
|
||||
// Highly constrained domain, sole purpose is to connect to daemon
|
||||
#define SEPOL_CLIENT_DOMAIN "magisk_client"
|
||||
// Unconstrained file type that anyone can access
|
||||
#define SEPOL_FILE_TYPE "magisk_file"
|
||||
// Special file type to allow clients to transit to client domain automatically
|
||||
#define SEPOL_EXEC_TYPE "magisk_exec"
|
||||
|
||||
extern void (*freecon)(char *con);
|
||||
extern int (*setcon)(const char *con);
|
||||
extern int (*getfilecon)(const char *path, char **con);
|
||||
extern int (*lgetfilecon)(const char *path, char **con);
|
||||
extern int (*fgetfilecon)(int fd, char **con);
|
||||
extern int (*setfilecon)(const char *path, const char *con);
|
||||
extern int (*lsetfilecon)(const char *path, const char *con);
|
||||
extern int (*fsetfilecon)(int fd, const char *con);
|
||||
void getfilecon_at(int dirfd, const char *name, char **con);
|
||||
void setfilecon_at(int dirfd, const char *name, const char *con);
|
||||
|
||||
bool selinux_enabled();
|
||||
void enable_selinux();
|
||||
void restorecon();
|
||||
void restore_tmpcon();
|
||||
void restore_databincon();
|
||||
@@ -0,0 +1,133 @@
|
||||
#pragma once
|
||||
|
||||
#include <sys/uio.h>
|
||||
#include <cstdio>
|
||||
#include <memory>
|
||||
|
||||
#include "../files.hpp"
|
||||
|
||||
class stream {
|
||||
public:
|
||||
virtual ssize_t read(void *buf, size_t len);
|
||||
virtual ssize_t readFully(void *buf, size_t len);
|
||||
virtual ssize_t readv(const iovec *iov, int iovcnt);
|
||||
virtual bool write(const void *buf, size_t len);
|
||||
virtual ssize_t writev(const iovec *iov, int iovcnt);
|
||||
virtual off_t seek(off_t off, int whence);
|
||||
virtual ~stream() = default;
|
||||
};
|
||||
|
||||
using stream_ptr = std::unique_ptr<stream>;
|
||||
|
||||
// Delegates all operations to base stream
|
||||
class filter_stream : public stream {
|
||||
public:
|
||||
filter_stream(stream_ptr &&base) : base(std::move(base)) {}
|
||||
|
||||
ssize_t read(void *buf, size_t len) override;
|
||||
bool write(const void *buf, size_t len) override;
|
||||
virtual bool write(const void *buf, size_t len, bool final);
|
||||
|
||||
// Seeking while filtering does not make sense
|
||||
off_t seek(off_t off, int whence) final { return stream::seek(off, whence); }
|
||||
|
||||
protected:
|
||||
stream_ptr base;
|
||||
};
|
||||
|
||||
using filter_strm_ptr = std::unique_ptr<filter_stream>;
|
||||
|
||||
// Buffered output stream, writing in chunks
|
||||
class chunk_out_stream : public filter_stream {
|
||||
public:
|
||||
chunk_out_stream(stream_ptr &&base, size_t buf_sz, size_t chunk_sz)
|
||||
: filter_stream(std::move(base)), chunk_sz(chunk_sz), buf_sz(buf_sz) {}
|
||||
|
||||
chunk_out_stream(stream_ptr &&base, size_t buf_sz = 4096)
|
||||
: chunk_out_stream(std::move(base), buf_sz, buf_sz) {}
|
||||
|
||||
~chunk_out_stream() override { delete[] _buf; }
|
||||
|
||||
// Reading does not make sense
|
||||
ssize_t read(void *buf, size_t len) final { return stream::read(buf, len); }
|
||||
bool write(const void *buf, size_t len) final;
|
||||
bool write(const void *buf, size_t len, bool final) final;
|
||||
|
||||
protected:
|
||||
// Classes inheriting this class has to call finalize() in its destructor
|
||||
void finalize();
|
||||
virtual bool write_chunk(const void *buf, size_t len, bool final) = 0;
|
||||
|
||||
size_t chunk_sz;
|
||||
|
||||
private:
|
||||
size_t buf_sz;
|
||||
size_t buf_off = 0;
|
||||
uint8_t *_buf = nullptr;
|
||||
};
|
||||
|
||||
// Byte stream that dynamically allocates memory
|
||||
class byte_stream : public stream {
|
||||
public:
|
||||
byte_stream(uint8_t *&buf, size_t &len);
|
||||
template <class Byte>
|
||||
byte_stream(Byte *&buf, size_t &len) : byte_stream(reinterpret_cast<uint8_t *&>(buf), len) {}
|
||||
|
||||
ssize_t read(void *buf, size_t len) override;
|
||||
bool write(const void *buf, size_t len) override;
|
||||
off_t seek(off_t off, int whence) override;
|
||||
|
||||
private:
|
||||
uint8_t *&_buf;
|
||||
size_t &_len;
|
||||
size_t _pos = 0;
|
||||
size_t _cap = 0;
|
||||
|
||||
void resize(size_t new_pos, bool zero = false);
|
||||
};
|
||||
|
||||
class file_stream : public stream {
|
||||
public:
|
||||
bool write(const void *buf, size_t len) final;
|
||||
protected:
|
||||
virtual ssize_t do_write(const void *buf, size_t len) = 0;
|
||||
};
|
||||
|
||||
// File stream but does not close the file descriptor at any time
|
||||
class fd_stream : public file_stream {
|
||||
public:
|
||||
fd_stream(int fd) : fd(fd) {}
|
||||
ssize_t read(void *buf, size_t len) override;
|
||||
ssize_t readv(const iovec *iov, int iovcnt) override;
|
||||
ssize_t writev(const iovec *iov, int iovcnt) override;
|
||||
off_t seek(off_t off, int whence) override;
|
||||
protected:
|
||||
ssize_t do_write(const void *buf, size_t len) override;
|
||||
private:
|
||||
int fd;
|
||||
};
|
||||
|
||||
/* ****************************************
|
||||
* Bridge between stream class and C stdio
|
||||
* ****************************************/
|
||||
|
||||
// sFILE -> stream_ptr
|
||||
class fp_stream final : public file_stream {
|
||||
public:
|
||||
fp_stream(FILE *fp = nullptr) : fp(fp, fclose) {}
|
||||
fp_stream(sFILE &&fp) : fp(std::move(fp)) {}
|
||||
ssize_t read(void *buf, size_t len) override;
|
||||
off_t seek(off_t off, int whence) override;
|
||||
protected:
|
||||
ssize_t do_write(const void *buf, size_t len) override;
|
||||
private:
|
||||
sFILE fp;
|
||||
};
|
||||
|
||||
// stream_ptr -> sFILE
|
||||
sFILE make_stream_fp(stream_ptr &&strm);
|
||||
|
||||
template <class T, class... Args>
|
||||
sFILE make_stream_fp(Args &&... args) {
|
||||
return make_stream_fp(stream_ptr(new T(std::forward<Args>(args)...)));
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#![feature(format_args_nl)]
|
||||
|
||||
pub use logging::*;
|
||||
pub use misc::*;
|
||||
|
||||
mod logging;
|
||||
mod misc;
|
||||
|
||||
#[cxx::bridge]
|
||||
pub mod ffi {
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum LogLevel {
|
||||
Error,
|
||||
Warn,
|
||||
Info,
|
||||
Debug,
|
||||
}
|
||||
|
||||
extern "Rust" {
|
||||
fn log_with_rs(level: LogLevel, msg: &str);
|
||||
fn exit_on_error(b: bool);
|
||||
fn set_log_level_state(level: LogLevel, enabled: bool);
|
||||
fn cmdline_logging();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
|
||||
#include <android/log.h>
|
||||
|
||||
#include <flags.h>
|
||||
#include <base.hpp>
|
||||
|
||||
// Just need to include it somewhere
|
||||
#include <base-rs.cpp>
|
||||
|
||||
using namespace std;
|
||||
|
||||
static int fmt_and_log_with_rs(LogLevel level, const char *fmt, va_list ap) {
|
||||
char buf[4096];
|
||||
int ret = vsnprintf(buf, sizeof(buf), fmt, ap);
|
||||
log_with_rs(level, rust::Str(buf, ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
int (*cpp_logger)(LogLevel level, const char *fmt, va_list ap) = fmt_and_log_with_rs;
|
||||
|
||||
// Used to override external C library logging
|
||||
extern "C" int magisk_log_print(int prio, const char *tag, const char *fmt, ...) {
|
||||
LogLevel level;
|
||||
switch (prio) {
|
||||
case ANDROID_LOG_DEBUG:
|
||||
level = LogLevel::Debug;
|
||||
break;
|
||||
case ANDROID_LOG_INFO:
|
||||
level = LogLevel::Info;
|
||||
break;
|
||||
case ANDROID_LOG_WARN:
|
||||
level = LogLevel::Warn;
|
||||
break;
|
||||
case ANDROID_LOG_ERROR:
|
||||
level = LogLevel::Error;
|
||||
break;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
||||
char fmt_buf[4096];
|
||||
auto len = strlcpy(fmt_buf, tag, sizeof(fmt_buf));
|
||||
// Prevent format specifications in the tag
|
||||
std::replace(fmt_buf, fmt_buf + len, '%', '_');
|
||||
snprintf(fmt_buf + len, sizeof(fmt_buf) - len, ": %s", fmt);
|
||||
va_list argv;
|
||||
va_start(argv, fmt);
|
||||
int ret = cpp_logger(level, fmt_buf, argv);
|
||||
va_end(argv);
|
||||
return ret;
|
||||
}
|
||||
|
||||
#define LOG_BODY(level) { \
|
||||
va_list argv; \
|
||||
va_start(argv, fmt); \
|
||||
cpp_logger(LogLevel::level, fmt, argv); \
|
||||
va_end(argv); \
|
||||
}
|
||||
|
||||
// LTO will optimize out the NOP function
|
||||
#if MAGISK_DEBUG
|
||||
void LOGD(const char *fmt, ...) { LOG_BODY(Debug) }
|
||||
#else
|
||||
void LOGD(const char *fmt, ...) {}
|
||||
#endif
|
||||
void LOGI(const char *fmt, ...) { LOG_BODY(Info) }
|
||||
void LOGW(const char *fmt, ...) { LOG_BODY(Warn) }
|
||||
void LOGE(const char *fmt, ...) { LOG_BODY(Error) }
|
||||
|
||||
// Export raw symbol to fortify compat
|
||||
extern "C" void __vloge(const char* fmt, va_list ap) {
|
||||
cpp_logger(LogLevel::Error, fmt, ap);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include <cerrno>
|
||||
#include <cstdarg>
|
||||
|
||||
#include <base-rs.hpp>
|
||||
|
||||
extern int (*cpp_logger)(LogLevel level, const char *fmt, va_list ap);
|
||||
|
||||
void LOGD(const char *fmt, ...) __printflike(1, 2);
|
||||
void LOGI(const char *fmt, ...) __printflike(1, 2);
|
||||
void LOGW(const char *fmt, ...) __printflike(1, 2);
|
||||
void LOGE(const char *fmt, ...) __printflike(1, 2);
|
||||
#define PLOGE(fmt, args...) LOGE(fmt " failed with %d: %s\n", ##args, errno, std::strerror(errno))
|
||||
@@ -0,0 +1,139 @@
|
||||
use std::fmt::Arguments;
|
||||
use std::io::{stderr, stdout, Write};
|
||||
use std::process::exit;
|
||||
|
||||
use crate::ffi::LogLevel;
|
||||
|
||||
// Ugly hack to avoid using enum
|
||||
#[allow(non_snake_case, non_upper_case_globals)]
|
||||
mod LogFlag {
|
||||
pub const DisableError: u32 = 0x1;
|
||||
pub const DisableWarn: u32 = 0x2;
|
||||
pub const DisableInfo: u32 = 0x4;
|
||||
pub const DisableDebug: u32 = 0x8;
|
||||
pub const ExitOnError: u32 = 0x10;
|
||||
}
|
||||
|
||||
// We don't need to care about thread safety, because all
|
||||
// logger changes will only happen on the main thread.
|
||||
pub static mut LOGGER: Logger = Logger {
|
||||
fmt: |_, _| {},
|
||||
write: |_, _| {},
|
||||
flags: 0,
|
||||
};
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Logger {
|
||||
pub fmt: fn(level: LogLevel, args: Arguments),
|
||||
pub write: fn(level: LogLevel, msg: &[u8]),
|
||||
pub flags: u32,
|
||||
}
|
||||
|
||||
pub fn exit_on_error(b: bool) {
|
||||
unsafe {
|
||||
if b {
|
||||
LOGGER.flags |= LogFlag::ExitOnError;
|
||||
} else {
|
||||
LOGGER.flags &= !LogFlag::ExitOnError;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LogLevel {
|
||||
fn to_disable_flag(&self) -> u32 {
|
||||
match *self {
|
||||
LogLevel::Error => LogFlag::DisableError,
|
||||
LogLevel::Warn => LogFlag::DisableWarn,
|
||||
LogLevel::Info => LogFlag::DisableInfo,
|
||||
LogLevel::Debug => LogFlag::DisableDebug,
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_log_level_state(level: LogLevel, enabled: bool) {
|
||||
let flag = level.to_disable_flag();
|
||||
unsafe {
|
||||
if enabled {
|
||||
LOGGER.flags &= !flag
|
||||
} else {
|
||||
LOGGER.flags |= flag
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn log_with_rs(level: LogLevel, msg: &str) {
|
||||
let logger = unsafe { LOGGER };
|
||||
if (logger.flags & level.to_disable_flag()) != 0 {
|
||||
return;
|
||||
}
|
||||
(logger.write)(level, msg.as_bytes());
|
||||
if level == LogLevel::Error && (logger.flags & LogFlag::ExitOnError) != 0 {
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn log_impl(level: LogLevel, args: Arguments) {
|
||||
let logger = unsafe { LOGGER };
|
||||
if (logger.flags & level.to_disable_flag()) != 0 {
|
||||
return;
|
||||
}
|
||||
(logger.fmt)(level, args);
|
||||
if level == LogLevel::Error && (logger.flags & LogFlag::ExitOnError) != 0 {
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cmdline_logging() {
|
||||
fn print(level: LogLevel, args: Arguments) {
|
||||
if level == LogLevel::Info {
|
||||
print!("{}", args);
|
||||
} else {
|
||||
eprint!("{}", args);
|
||||
}
|
||||
}
|
||||
|
||||
fn write(level: LogLevel, msg: &[u8]) {
|
||||
if level == LogLevel::Info {
|
||||
stdout().write_all(msg).ok();
|
||||
} else {
|
||||
stderr().write_all(msg).ok();
|
||||
}
|
||||
}
|
||||
|
||||
let logger = Logger {
|
||||
fmt: print,
|
||||
write,
|
||||
flags: LogFlag::ExitOnError,
|
||||
};
|
||||
unsafe {
|
||||
LOGGER = logger;
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! error {
|
||||
($($arg:tt)+) => ($crate::log_impl($crate::ffi::LogLevel::Error, format_args_nl!($($arg)+)))
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! warn {
|
||||
($($arg:tt)+) => ($crate::log_impl($crate::ffi::LogLevel::Warn, format_args_nl!($($arg)+)))
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! info {
|
||||
($($arg:tt)+) => ($crate::log_impl($crate::ffi::LogLevel::Info, format_args_nl!($($arg)+)))
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
#[macro_export]
|
||||
macro_rules! debug {
|
||||
($($arg:tt)+) => ($crate::log_impl($crate::ffi::LogLevel::Debug, format_args_nl!($($arg)+)))
|
||||
}
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[macro_export]
|
||||
macro_rules! debug {
|
||||
($($arg:tt)+) => {};
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/prctl.h>
|
||||
#include <sys/sysmacros.h>
|
||||
#include <fcntl.h>
|
||||
#include <pwd.h>
|
||||
#include <unistd.h>
|
||||
#include <syscall.h>
|
||||
#include <random>
|
||||
#include <string>
|
||||
|
||||
#include <base.hpp>
|
||||
|
||||
using namespace std;
|
||||
|
||||
int fork_dont_care() {
|
||||
if (int pid = xfork()) {
|
||||
waitpid(pid, nullptr, 0);
|
||||
return pid;
|
||||
} else if (xfork()) {
|
||||
exit(0);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int fork_no_orphan() {
|
||||
int pid = xfork();
|
||||
if (pid)
|
||||
return pid;
|
||||
prctl(PR_SET_PDEATHSIG, SIGKILL);
|
||||
if (getppid() == 1)
|
||||
exit(1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int gen_rand_str(char *buf, int len, bool varlen) {
|
||||
constexpr char ALPHANUM[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||
static std::mt19937 gen([]{
|
||||
if (access("/dev/urandom", F_OK) != 0)
|
||||
mknod("/dev/urandom", 0600 | S_IFCHR, makedev(1, 9));
|
||||
int fd = xopen("/dev/urandom", O_RDONLY | O_CLOEXEC);
|
||||
unsigned seed;
|
||||
xxread(fd, &seed, sizeof(seed));
|
||||
return seed;
|
||||
}());
|
||||
std::uniform_int_distribution<int> dist(0, sizeof(ALPHANUM) - 2);
|
||||
if (varlen) {
|
||||
std::uniform_int_distribution<int> len_dist(len / 2, len);
|
||||
len = len_dist(gen);
|
||||
}
|
||||
for (int i = 0; i < len - 1; ++i)
|
||||
buf[i] = ALPHANUM[dist(gen)];
|
||||
buf[len - 1] = '\0';
|
||||
return len - 1;
|
||||
}
|
||||
|
||||
int exec_command(exec_t &exec) {
|
||||
int pipefd[] = {-1, -1};
|
||||
int outfd = -1;
|
||||
|
||||
if (exec.fd == -1) {
|
||||
if (xpipe2(pipefd, O_CLOEXEC) == -1)
|
||||
return -1;
|
||||
outfd = pipefd[1];
|
||||
} else if (exec.fd >= 0) {
|
||||
outfd = exec.fd;
|
||||
}
|
||||
|
||||
int pid = exec.fork();
|
||||
if (pid < 0) {
|
||||
close(pipefd[0]);
|
||||
close(pipefd[1]);
|
||||
return -1;
|
||||
} else if (pid) {
|
||||
if (exec.fd == -1) {
|
||||
exec.fd = pipefd[0];
|
||||
close(pipefd[1]);
|
||||
}
|
||||
return pid;
|
||||
}
|
||||
|
||||
// Unblock all signals
|
||||
sigset_t set;
|
||||
sigfillset(&set);
|
||||
pthread_sigmask(SIG_UNBLOCK, &set, nullptr);
|
||||
|
||||
if (outfd >= 0) {
|
||||
xdup2(outfd, STDOUT_FILENO);
|
||||
if (exec.err)
|
||||
xdup2(outfd, STDERR_FILENO);
|
||||
close(outfd);
|
||||
}
|
||||
|
||||
// Call the pre-exec callback
|
||||
if (exec.pre_exec)
|
||||
exec.pre_exec();
|
||||
|
||||
execve(exec.argv[0], (char **) exec.argv, environ);
|
||||
PLOGE("execve %s", exec.argv[0]);
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
int exec_command_sync(exec_t &exec) {
|
||||
int pid = exec_command(exec);
|
||||
if (pid < 0)
|
||||
return -1;
|
||||
int status;
|
||||
waitpid(pid, &status, 0);
|
||||
return WEXITSTATUS(status);
|
||||
}
|
||||
|
||||
int new_daemon_thread(thread_entry entry, void *arg) {
|
||||
pthread_t thread;
|
||||
pthread_attr_t attr;
|
||||
pthread_attr_init(&attr);
|
||||
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
|
||||
return xpthread_create(&thread, &attr, entry, arg);
|
||||
}
|
||||
|
||||
static char *argv0;
|
||||
static size_t name_len;
|
||||
void init_argv0(int argc, char **argv) {
|
||||
argv0 = argv[0];
|
||||
name_len = (argv[argc - 1] - argv[0]) + strlen(argv[argc - 1]) + 1;
|
||||
}
|
||||
|
||||
void set_nice_name(const char *name) {
|
||||
memset(argv0, 0, name_len);
|
||||
strlcpy(argv0, name, name_len);
|
||||
prctl(PR_SET_NAME, name);
|
||||
}
|
||||
|
||||
/*
|
||||
* Bionic's atoi runs through strtol().
|
||||
* Use our own implementation for faster conversion.
|
||||
*/
|
||||
int parse_int(string_view s) {
|
||||
int val = 0;
|
||||
for (char c : s) {
|
||||
if (!c) break;
|
||||
if (c > '9' || c < '0')
|
||||
return -1;
|
||||
val = val * 10 + c - '0';
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
uint32_t binary_gcd(uint32_t u, uint32_t v) {
|
||||
if (u == 0) return v;
|
||||
if (v == 0) return u;
|
||||
auto shift = __builtin_ctz(u | v);
|
||||
u >>= __builtin_ctz(u);
|
||||
do {
|
||||
v >>= __builtin_ctz(v);
|
||||
if (u > v) {
|
||||
auto t = v;
|
||||
v = u;
|
||||
u = t;
|
||||
}
|
||||
v -= u;
|
||||
} while (v != 0);
|
||||
return u << shift;
|
||||
}
|
||||
|
||||
int switch_mnt_ns(int pid) {
|
||||
char mnt[32];
|
||||
snprintf(mnt, sizeof(mnt), "/proc/%d/ns/mnt", pid);
|
||||
if (access(mnt, R_OK) == -1) return 1; // Maybe process died..
|
||||
|
||||
int fd, ret;
|
||||
fd = xopen(mnt, O_RDONLY);
|
||||
if (fd < 0) return 1;
|
||||
// Switch to its namespace
|
||||
ret = xsetns(fd, 0);
|
||||
close(fd);
|
||||
return ret;
|
||||
}
|
||||
|
||||
string &replace_all(string &str, string_view from, string_view to) {
|
||||
size_t pos = 0;
|
||||
while((pos = str.find(from, pos)) != string::npos) {
|
||||
str.replace(pos, from.length(), to);
|
||||
pos += to.length();
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
static auto split_impl(T s, T delims) {
|
||||
vector<std::decay_t<T>> result;
|
||||
size_t base = 0;
|
||||
size_t found;
|
||||
while (true) {
|
||||
found = s.find_first_of(delims, base);
|
||||
result.push_back(s.substr(base, found - base));
|
||||
if (found == string::npos)
|
||||
break;
|
||||
base = found + 1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
vector<string> split(const string &s, const string &delims) {
|
||||
return split_impl<const string&>(s, delims);
|
||||
}
|
||||
|
||||
vector<string_view> split_ro(string_view s, string_view delims) {
|
||||
return split_impl<string_view>(s, delims);
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
#pragma once
|
||||
|
||||
#include <pthread.h>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <string_view>
|
||||
#include <bitset>
|
||||
|
||||
#define DISALLOW_COPY_AND_MOVE(clazz) \
|
||||
clazz(const clazz &) = delete; \
|
||||
clazz(clazz &&) = delete;
|
||||
|
||||
class mutex_guard {
|
||||
DISALLOW_COPY_AND_MOVE(mutex_guard)
|
||||
public:
|
||||
explicit mutex_guard(pthread_mutex_t &m): mutex(&m) {
|
||||
pthread_mutex_lock(mutex);
|
||||
}
|
||||
void unlock() {
|
||||
pthread_mutex_unlock(mutex);
|
||||
mutex = nullptr;
|
||||
}
|
||||
~mutex_guard() {
|
||||
if (mutex) pthread_mutex_unlock(mutex);
|
||||
}
|
||||
private:
|
||||
pthread_mutex_t *mutex;
|
||||
};
|
||||
|
||||
template <class Func>
|
||||
class run_finally {
|
||||
DISALLOW_COPY_AND_MOVE(run_finally)
|
||||
public:
|
||||
explicit run_finally(Func &&fn) : fn(std::move(fn)) {}
|
||||
~run_finally() { fn(); }
|
||||
private:
|
||||
Func fn;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class reversed_container {
|
||||
public:
|
||||
reversed_container(T &base) : base(base) {}
|
||||
decltype(std::declval<T>().rbegin()) begin() { return base.rbegin(); }
|
||||
decltype(std::declval<T>().crbegin()) begin() const { return base.crbegin(); }
|
||||
decltype(std::declval<T>().crbegin()) cbegin() const { return base.crbegin(); }
|
||||
decltype(std::declval<T>().rend()) end() { return base.rend(); }
|
||||
decltype(std::declval<T>().crend()) end() const { return base.crend(); }
|
||||
decltype(std::declval<T>().crend()) cend() const { return base.crend(); }
|
||||
private:
|
||||
T &base;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
reversed_container<T> reversed(T &base) {
|
||||
return reversed_container<T>(base);
|
||||
}
|
||||
|
||||
template<class T>
|
||||
static inline void default_new(T *&p) { p = new T(); }
|
||||
|
||||
template<class T>
|
||||
static inline void default_new(std::unique_ptr<T> &p) { p.reset(new T()); }
|
||||
|
||||
template<typename T, typename Impl>
|
||||
class stateless_allocator {
|
||||
public:
|
||||
using value_type = T;
|
||||
T *allocate(size_t num) { return static_cast<T*>(Impl::allocate(sizeof(T) * num)); }
|
||||
void deallocate(T *ptr, size_t num) { Impl::deallocate(ptr, sizeof(T) * num); }
|
||||
stateless_allocator() = default;
|
||||
stateless_allocator(const stateless_allocator&) = default;
|
||||
stateless_allocator(stateless_allocator&&) = default;
|
||||
template <typename U>
|
||||
stateless_allocator(const stateless_allocator<U, Impl>&) {}
|
||||
bool operator==(const stateless_allocator&) { return true; }
|
||||
bool operator!=(const stateless_allocator&) { return false; }
|
||||
};
|
||||
|
||||
class dynamic_bitset_impl {
|
||||
public:
|
||||
using slot_type = unsigned long;
|
||||
constexpr static int slot_size = sizeof(slot_type) * 8;
|
||||
using slot_bits = std::bitset<slot_size>;
|
||||
|
||||
size_t slots() const { return slot_list.size(); }
|
||||
slot_type get_slot(size_t slot) const {
|
||||
return slot_list.size() > slot ? slot_list[slot].to_ulong() : 0ul;
|
||||
}
|
||||
void emplace_back(slot_type l) {
|
||||
slot_list.emplace_back(l);
|
||||
}
|
||||
protected:
|
||||
slot_bits::reference get(size_t pos) {
|
||||
size_t slot = pos / slot_size;
|
||||
size_t index = pos % slot_size;
|
||||
if (slot_list.size() <= slot) {
|
||||
slot_list.resize(slot + 1);
|
||||
}
|
||||
return slot_list[slot][index];
|
||||
}
|
||||
bool get(size_t pos) const {
|
||||
size_t slot = pos / slot_size;
|
||||
size_t index = pos % slot_size;
|
||||
return slot_list.size() > slot && slot_list[slot][index];
|
||||
}
|
||||
private:
|
||||
std::vector<slot_bits> slot_list;
|
||||
};
|
||||
|
||||
struct dynamic_bitset : public dynamic_bitset_impl {
|
||||
slot_bits::reference operator[] (size_t pos) { return get(pos); }
|
||||
bool operator[] (size_t pos) const { return get(pos); }
|
||||
};
|
||||
|
||||
struct StringCmp {
|
||||
using is_transparent = void;
|
||||
bool operator()(std::string_view a, std::string_view b) const { return a < b; }
|
||||
};
|
||||
|
||||
int parse_int(std::string_view s);
|
||||
|
||||
using thread_entry = void *(*)(void *);
|
||||
int new_daemon_thread(thread_entry entry, void *arg = nullptr);
|
||||
|
||||
static inline bool str_contains(std::string_view s, std::string_view ss) {
|
||||
return s.find(ss) != std::string::npos;
|
||||
}
|
||||
static inline bool str_starts(std::string_view s, std::string_view ss) {
|
||||
return s.size() >= ss.size() && s.compare(0, ss.size(), ss) == 0;
|
||||
}
|
||||
static inline bool str_ends(std::string_view s, std::string_view ss) {
|
||||
return s.size() >= ss.size() && s.compare(s.size() - ss.size(), std::string::npos, ss) == 0;
|
||||
}
|
||||
static inline std::string ltrim(std::string &&s) {
|
||||
s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) {
|
||||
return !std::isspace(ch);
|
||||
}));
|
||||
return std::move(s);
|
||||
}
|
||||
static inline std::string rtrim(std::string &&s) {
|
||||
s.erase(std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) {
|
||||
return !std::isspace(ch) && ch != '\0';
|
||||
}).base(), s.end());
|
||||
return std::move(s);
|
||||
}
|
||||
|
||||
int fork_dont_care();
|
||||
int fork_no_orphan();
|
||||
void init_argv0(int argc, char **argv);
|
||||
void set_nice_name(const char *name);
|
||||
uint32_t binary_gcd(uint32_t u, uint32_t v);
|
||||
int switch_mnt_ns(int pid);
|
||||
int gen_rand_str(char *buf, int len, bool varlen = true);
|
||||
std::string &replace_all(std::string &str, std::string_view from, std::string_view to);
|
||||
std::vector<std::string> split(const std::string &s, const std::string &delims);
|
||||
std::vector<std::string_view> split_ro(std::string_view, std::string_view delims);
|
||||
|
||||
struct exec_t {
|
||||
bool err = false;
|
||||
int fd = -2;
|
||||
void (*pre_exec)() = nullptr;
|
||||
int (*fork)() = xfork;
|
||||
const char **argv = nullptr;
|
||||
};
|
||||
|
||||
int exec_command(exec_t &exec);
|
||||
template <class ...Args>
|
||||
int exec_command(exec_t &exec, Args &&...args) {
|
||||
const char *argv[] = {args..., nullptr};
|
||||
exec.argv = argv;
|
||||
return exec_command(exec);
|
||||
}
|
||||
int exec_command_sync(exec_t &exec);
|
||||
template <class ...Args>
|
||||
int exec_command_sync(exec_t &exec, Args &&...args) {
|
||||
const char *argv[] = {args..., nullptr};
|
||||
exec.argv = argv;
|
||||
return exec_command_sync(exec);
|
||||
}
|
||||
template <class ...Args>
|
||||
int exec_command_sync(Args &&...args) {
|
||||
exec_t exec;
|
||||
return exec_command_sync(exec, args...);
|
||||
}
|
||||
template <class ...Args>
|
||||
void exec_command_async(Args &&...args) {
|
||||
const char *argv[] = {args..., nullptr};
|
||||
exec_t exec {
|
||||
.fork = fork_dont_care,
|
||||
.argv = argv,
|
||||
};
|
||||
exec_command(exec);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
use std::cmp::min;
|
||||
use std::fmt;
|
||||
use std::fmt::Arguments;
|
||||
|
||||
struct BufFmtWriter<'a> {
|
||||
buf: &'a mut [u8],
|
||||
used: usize,
|
||||
}
|
||||
|
||||
impl<'a> BufFmtWriter<'a> {
|
||||
fn new(buf: &'a mut [u8]) -> Self {
|
||||
BufFmtWriter { buf, used: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> fmt::Write for BufFmtWriter<'a> {
|
||||
// The buffer should always be null terminated
|
||||
fn write_str(&mut self, s: &str) -> fmt::Result {
|
||||
if self.used >= self.buf.len() - 1 {
|
||||
// Silent truncate
|
||||
return Ok(());
|
||||
}
|
||||
let remain = &mut self.buf[self.used..];
|
||||
let s_bytes = s.as_bytes();
|
||||
let copied = min(s_bytes.len(), remain.len() - 1);
|
||||
remain[..copied].copy_from_slice(&s_bytes[..copied]);
|
||||
self.used += copied;
|
||||
self.buf[self.used] = b'\0';
|
||||
// Silent truncate
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fmt_to_buf(buf: &mut [u8], args: Arguments) -> usize {
|
||||
let mut w = BufFmtWriter::new(buf);
|
||||
if let Ok(()) = fmt::write(&mut w, args) {
|
||||
w.used
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include <sys/syscall.h>
|
||||
#include <linux/fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <cerrno>
|
||||
#include <cstdio>
|
||||
|
||||
static inline int sigtimedwait(const sigset_t* set, siginfo_t* info, const timespec* timeout) {
|
||||
union {
|
||||
sigset_t set;
|
||||
sigset_t set64;
|
||||
} s{};
|
||||
s.set = *set;
|
||||
return syscall(__NR_rt_sigtimedwait, &s.set64, info, timeout, sizeof(sigset64_t));
|
||||
}
|
||||
|
||||
static inline int fexecve(int fd, char* const* argv, char* const* envp) {
|
||||
syscall(__NR_execveat, fd, "", argv, envp, AT_EMPTY_PATH);
|
||||
if (errno == ENOSYS) {
|
||||
char buf[256];
|
||||
std::snprintf(buf, sizeof(buf), "/proc/self/fd/%d", fd);
|
||||
execve(buf, argv, envp);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
#include <new>
|
||||
#include <stdlib.h>
|
||||
|
||||
/* Override libc++ new implementation
|
||||
* to optimize final build size */
|
||||
|
||||
void* operator new(std::size_t s) { return malloc(s); }
|
||||
void* operator new[](std::size_t s) { return malloc(s); }
|
||||
void operator delete(void *p) { free(p); }
|
||||
void operator delete[](void *p) { free(p); }
|
||||
void* operator new(std::size_t s, const std::nothrow_t&) noexcept { return malloc(s); }
|
||||
void* operator new[](std::size_t s, const std::nothrow_t&) noexcept { return malloc(s); }
|
||||
void operator delete(void *p, const std::nothrow_t&) noexcept { free(p); }
|
||||
void operator delete[](void *p, const std::nothrow_t&) noexcept { free(p); }
|
||||
@@ -0,0 +1,129 @@
|
||||
#include <unistd.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <sys/xattr.h>
|
||||
|
||||
#include <base.hpp>
|
||||
#include <selinux.hpp>
|
||||
#include <flags.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
// Stub implementation
|
||||
|
||||
static int stub(const char *) { return 0; }
|
||||
|
||||
static int stub(const char *, const char *) { return 0; }
|
||||
|
||||
static int stub(const char *, char **ctx) {
|
||||
*ctx = strdup("");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stub(int, const char *) { return 0; }
|
||||
|
||||
static int stub(int, char **ctx) {
|
||||
*ctx = strdup("");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Builtin implementation
|
||||
|
||||
static void __freecon(char *s) {
|
||||
free(s);
|
||||
}
|
||||
|
||||
static int __setcon(const char *ctx) {
|
||||
int fd = open("/proc/self/attr/current", O_WRONLY | O_CLOEXEC);
|
||||
if (fd < 0)
|
||||
return fd;
|
||||
size_t len = strlen(ctx) + 1;
|
||||
int rc = write(fd, ctx, len);
|
||||
close(fd);
|
||||
return rc != len;
|
||||
}
|
||||
|
||||
static int __getfilecon(const char *path, char **ctx) {
|
||||
char buf[1024];
|
||||
int rc = syscall(__NR_getxattr, path, XATTR_NAME_SELINUX, buf, sizeof(buf) - 1);
|
||||
if (rc >= 0)
|
||||
*ctx = strdup(buf);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int __lgetfilecon(const char *path, char **ctx) {
|
||||
char buf[1024];
|
||||
int rc = syscall(__NR_lgetxattr, path, XATTR_NAME_SELINUX, buf, sizeof(buf) - 1);
|
||||
if (rc >= 0)
|
||||
*ctx = strdup(buf);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int __fgetfilecon(int fd, char **ctx) {
|
||||
char buf[1024];
|
||||
int rc = syscall(__NR_fgetxattr, fd, XATTR_NAME_SELINUX, buf, sizeof(buf) - 1);
|
||||
if (rc >= 0)
|
||||
*ctx = strdup(buf);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int __setfilecon(const char *path, const char *ctx) {
|
||||
return syscall(__NR_setxattr, path, XATTR_NAME_SELINUX, ctx, strlen(ctx) + 1, 0);
|
||||
}
|
||||
|
||||
static int __lsetfilecon(const char *path, const char *ctx) {
|
||||
return syscall(__NR_lsetxattr, path, XATTR_NAME_SELINUX, ctx, strlen(ctx) + 1, 0);
|
||||
}
|
||||
|
||||
static int __fsetfilecon(int fd, const char *ctx) {
|
||||
return syscall(__NR_fsetxattr, fd, XATTR_NAME_SELINUX, ctx, strlen(ctx) + 1, 0);
|
||||
}
|
||||
|
||||
// Function pointers
|
||||
|
||||
void (*freecon)(char *) = __freecon;
|
||||
int (*setcon)(const char *) = stub;
|
||||
int (*getfilecon)(const char *, char **) = stub;
|
||||
int (*lgetfilecon)(const char *, char **) = stub;
|
||||
int (*fgetfilecon)(int, char **) = stub;
|
||||
int (*setfilecon)(const char *, const char *) = stub;
|
||||
int (*lsetfilecon)(const char *, const char *) = stub;
|
||||
int (*fsetfilecon)(int, const char *) = stub;
|
||||
|
||||
void getfilecon_at(int dirfd, const char *name, char **con) {
|
||||
char path[4096];
|
||||
fd_pathat(dirfd, name, path, sizeof(path));
|
||||
if (lgetfilecon(path, con))
|
||||
*con = strdup("");
|
||||
}
|
||||
|
||||
void setfilecon_at(int dirfd, const char *name, const char *con) {
|
||||
char path[4096];
|
||||
fd_pathat(dirfd, name, path, sizeof(path));
|
||||
lsetfilecon(path, con);
|
||||
}
|
||||
|
||||
#if MAGISK_DEBUG
|
||||
static bool se_state = false;
|
||||
bool selinux_enabled() {
|
||||
return se_state;
|
||||
}
|
||||
#else
|
||||
bool selinux_enabled() {
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
void enable_selinux() {
|
||||
#if MAGISK_DEBUG
|
||||
if (access(SELINUX_MNT, F_OK) != 0)
|
||||
return;
|
||||
se_state = true;
|
||||
#endif
|
||||
setcon = __setcon;
|
||||
getfilecon = __getfilecon;
|
||||
lgetfilecon = __lgetfilecon;
|
||||
fgetfilecon = __fgetfilecon;
|
||||
setfilecon = __setfilecon;
|
||||
lsetfilecon = __lsetfilecon;
|
||||
fsetfilecon = __fsetfilecon;
|
||||
}
|
||||
@@ -0,0 +1,261 @@
|
||||
#include <unistd.h>
|
||||
#include <cstddef>
|
||||
|
||||
#include <base.hpp>
|
||||
#include <stream.hpp>
|
||||
|
||||
using namespace std;
|
||||
|
||||
static int strm_read(void *v, char *buf, int len) {
|
||||
auto strm = static_cast<stream *>(v);
|
||||
return strm->read(buf, len);
|
||||
}
|
||||
|
||||
static int strm_write(void *v, const char *buf, int len) {
|
||||
auto strm = static_cast<stream *>(v);
|
||||
if (!strm->write(buf, len))
|
||||
return -1;
|
||||
return len;
|
||||
}
|
||||
|
||||
static fpos_t strm_seek(void *v, fpos_t off, int whence) {
|
||||
auto strm = static_cast<stream *>(v);
|
||||
return strm->seek(off, whence);
|
||||
}
|
||||
|
||||
static int strm_close(void *v) {
|
||||
auto strm = static_cast<stream *>(v);
|
||||
delete strm;
|
||||
return 0;
|
||||
}
|
||||
|
||||
sFILE make_stream_fp(stream_ptr &&strm) {
|
||||
auto fp = make_file(funopen(strm.release(), strm_read, strm_write, strm_seek, strm_close));
|
||||
setbuf(fp.get(), nullptr);
|
||||
return fp;
|
||||
}
|
||||
|
||||
ssize_t stream::read(void *buf, size_t len) {
|
||||
LOGE("This stream does not implement read\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
ssize_t stream::readFully(void *buf, size_t len) {
|
||||
size_t read_sz = 0;
|
||||
ssize_t ret;
|
||||
do {
|
||||
ret = read((byte *) buf + read_sz, len - read_sz);
|
||||
if (ret < 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
return ret;
|
||||
}
|
||||
read_sz += ret;
|
||||
} while (read_sz != len && ret != 0);
|
||||
return read_sz;
|
||||
}
|
||||
|
||||
ssize_t stream::readv(const iovec *iov, int iovcnt) {
|
||||
size_t read_sz = 0;
|
||||
for (int i = 0; i < iovcnt; ++i) {
|
||||
auto ret = readFully(iov[i].iov_base, iov[i].iov_len);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
read_sz += ret;
|
||||
}
|
||||
return read_sz;
|
||||
}
|
||||
|
||||
bool stream::write(const void *buf, size_t len) {
|
||||
LOGE("This stream does not implement write\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
ssize_t stream::writev(const iovec *iov, int iovcnt) {
|
||||
size_t write_sz = 0;
|
||||
for (int i = 0; i < iovcnt; ++i) {
|
||||
if (!write(iov[i].iov_base, iov[i].iov_len))
|
||||
return write_sz;
|
||||
write_sz += iov[i].iov_len;
|
||||
}
|
||||
return write_sz;
|
||||
}
|
||||
|
||||
off_t stream::seek(off_t off, int whence) {
|
||||
LOGE("This stream does not implement seek\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
ssize_t fp_stream::read(void *buf, size_t len) {
|
||||
auto ret = fread(buf, 1, len, fp.get());
|
||||
return ret ? ret : (ferror(fp.get()) ? -1 : 0);
|
||||
}
|
||||
|
||||
ssize_t fp_stream::do_write(const void *buf, size_t len) {
|
||||
return fwrite(buf, 1, len, fp.get());
|
||||
}
|
||||
|
||||
off_t fp_stream::seek(off_t off, int whence) {
|
||||
return fseek(fp.get(), off, whence);
|
||||
}
|
||||
|
||||
ssize_t filter_stream::read(void *buf, size_t len) {
|
||||
return base->read(buf, len);
|
||||
}
|
||||
|
||||
bool filter_stream::write(const void *buf, size_t len) {
|
||||
return base->write(buf, len);
|
||||
}
|
||||
|
||||
bool filter_stream::write(const void *buf, size_t len, bool final) {
|
||||
return write(buf, len);
|
||||
}
|
||||
|
||||
bool chunk_out_stream::write(const void *buf, size_t len) {
|
||||
return write(buf, len, false);
|
||||
}
|
||||
|
||||
bool chunk_out_stream::write(const void *_in, size_t len, bool final) {
|
||||
auto in = static_cast<const uint8_t *>(_in);
|
||||
while (len) {
|
||||
if (buf_off + len >= chunk_sz) {
|
||||
// Enough input for a chunk
|
||||
const uint8_t *src;
|
||||
if (buf_off) {
|
||||
src = _buf;
|
||||
auto copy = chunk_sz - buf_off;
|
||||
memcpy(_buf + buf_off, in, copy);
|
||||
in += copy;
|
||||
len -= copy;
|
||||
buf_off = 0;
|
||||
} else {
|
||||
src = in;
|
||||
in += chunk_sz;
|
||||
len -= chunk_sz;
|
||||
}
|
||||
if (!write_chunk(src, chunk_sz, final && len == 0))
|
||||
return false;
|
||||
} else if (final) {
|
||||
// Final input data, write regardless whether it is chunk sized
|
||||
if (buf_off) {
|
||||
memcpy(_buf + buf_off, in, len);
|
||||
auto avail = buf_off + len;
|
||||
buf_off = 0;
|
||||
if (!write_chunk(_buf, avail, true))
|
||||
return false;
|
||||
} else {
|
||||
if (!write_chunk(in, len, true))
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
// Buffer internally
|
||||
if (!_buf) {
|
||||
_buf = new uint8_t[buf_sz];
|
||||
}
|
||||
memcpy(_buf + buf_off, in, len);
|
||||
buf_off += len;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void chunk_out_stream::finalize() {
|
||||
if (buf_off) {
|
||||
if (!write_chunk(_buf, buf_off, true)) {
|
||||
LOGE("Error in finalize, file truncated\n");
|
||||
}
|
||||
delete[] _buf;
|
||||
_buf = nullptr;
|
||||
buf_off = 0;
|
||||
}
|
||||
}
|
||||
|
||||
byte_stream::byte_stream(uint8_t *&buf, size_t &len) : _buf(buf), _len(len) {
|
||||
buf = nullptr;
|
||||
len = 0;
|
||||
}
|
||||
|
||||
ssize_t byte_stream::read(void *buf, size_t len) {
|
||||
len = std::min((size_t) len, _len - _pos);
|
||||
memcpy(buf, _buf + _pos, len);
|
||||
return len;
|
||||
}
|
||||
|
||||
bool byte_stream::write(const void *buf, size_t len) {
|
||||
resize(_pos + len);
|
||||
memcpy(_buf + _pos, buf, len);
|
||||
_pos += len;
|
||||
_len = std::max(_len, _pos);
|
||||
return true;
|
||||
}
|
||||
|
||||
off_t byte_stream::seek(off_t off, int whence) {
|
||||
off_t np;
|
||||
switch (whence) {
|
||||
case SEEK_CUR:
|
||||
np = _pos + off;
|
||||
break;
|
||||
case SEEK_END:
|
||||
np = _len + off;
|
||||
break;
|
||||
case SEEK_SET:
|
||||
np = off;
|
||||
break;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
resize(np, true);
|
||||
_pos = np;
|
||||
return np;
|
||||
}
|
||||
|
||||
void byte_stream::resize(size_t new_pos, bool zero) {
|
||||
bool resize = false;
|
||||
size_t old_cap = _cap;
|
||||
while (new_pos > _cap) {
|
||||
_cap = _cap ? (_cap << 1) - (_cap >> 1) : 1 << 12;
|
||||
resize = true;
|
||||
}
|
||||
if (resize) {
|
||||
_buf = (uint8_t *) xrealloc(_buf, _cap);
|
||||
if (zero)
|
||||
memset(_buf + old_cap, 0, _cap - old_cap);
|
||||
}
|
||||
}
|
||||
|
||||
ssize_t fd_stream::read(void *buf, size_t len) {
|
||||
return ::read(fd, buf, len);
|
||||
}
|
||||
|
||||
ssize_t fd_stream::readv(const iovec *iov, int iovcnt) {
|
||||
return ::readv(fd, iov, iovcnt);
|
||||
}
|
||||
|
||||
ssize_t fd_stream::do_write(const void *buf, size_t len) {
|
||||
return ::write(fd, buf, len);
|
||||
}
|
||||
|
||||
ssize_t fd_stream::writev(const iovec *iov, int iovcnt) {
|
||||
return ::writev(fd, iov, iovcnt);
|
||||
}
|
||||
|
||||
off_t fd_stream::seek(off_t off, int whence) {
|
||||
return lseek(fd, off, whence);
|
||||
}
|
||||
|
||||
bool file_stream::write(const void *buf, size_t len) {
|
||||
size_t write_sz = 0;
|
||||
ssize_t ret;
|
||||
do {
|
||||
ret = do_write((byte *) buf + write_sz, len - write_sz);
|
||||
if (ret < 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
return false;
|
||||
}
|
||||
write_sz += ret;
|
||||
} while (write_sz != len && ret != 0);
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,524 @@
|
||||
#include <sched.h>
|
||||
#include <unistd.h>
|
||||
#include <pthread.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/mount.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/sendfile.h>
|
||||
#include <sys/ptrace.h>
|
||||
#include <sys/inotify.h>
|
||||
|
||||
#include <base.hpp>
|
||||
|
||||
using namespace std;
|
||||
|
||||
FILE *xfopen(const char *pathname, const char *mode) {
|
||||
FILE *fp = fopen(pathname, mode);
|
||||
if (fp == nullptr) {
|
||||
PLOGE("fopen: %s", pathname);
|
||||
}
|
||||
return fp;
|
||||
}
|
||||
|
||||
FILE *xfdopen(int fd, const char *mode) {
|
||||
FILE *fp = fdopen(fd, mode);
|
||||
if (fp == nullptr) {
|
||||
PLOGE("fopen");
|
||||
}
|
||||
return fp;
|
||||
}
|
||||
|
||||
int xopen(const char *pathname, int flags) {
|
||||
int fd = open(pathname, flags);
|
||||
if (fd < 0) {
|
||||
PLOGE("open: %s", pathname);
|
||||
}
|
||||
return fd;
|
||||
}
|
||||
|
||||
int xopen(const char *pathname, int flags, mode_t mode) {
|
||||
int fd = open(pathname, flags, mode);
|
||||
if (fd < 0) {
|
||||
PLOGE("open: %s", pathname);
|
||||
}
|
||||
return fd;
|
||||
}
|
||||
|
||||
int xopenat(int dirfd, const char *pathname, int flags) {
|
||||
int fd = openat(dirfd, pathname, flags);
|
||||
if (fd < 0) {
|
||||
PLOGE("openat: %s", pathname);
|
||||
}
|
||||
return fd;
|
||||
}
|
||||
|
||||
int xopenat(int dirfd, const char *pathname, int flags, mode_t mode) {
|
||||
int fd = openat(dirfd, pathname, flags, mode);
|
||||
if (fd < 0) {
|
||||
PLOGE("openat: %s", pathname);
|
||||
}
|
||||
return fd;
|
||||
}
|
||||
|
||||
// Write exact same size as count
|
||||
ssize_t xwrite(int fd, const void *buf, size_t count) {
|
||||
size_t write_sz = 0;
|
||||
ssize_t ret;
|
||||
do {
|
||||
ret = write(fd, (byte *) buf + write_sz, count - write_sz);
|
||||
if (ret < 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
PLOGE("write");
|
||||
return ret;
|
||||
}
|
||||
write_sz += ret;
|
||||
} while (write_sz != count && ret != 0);
|
||||
if (write_sz != count) {
|
||||
PLOGE("write (%zu != %zu)", count, write_sz);
|
||||
}
|
||||
return write_sz;
|
||||
}
|
||||
|
||||
// Read error other than EOF
|
||||
ssize_t xread(int fd, void *buf, size_t count) {
|
||||
int ret = read(fd, buf, count);
|
||||
if (ret < 0) {
|
||||
PLOGE("read");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Read exact same size as count
|
||||
ssize_t xxread(int fd, void *buf, size_t count) {
|
||||
size_t read_sz = 0;
|
||||
ssize_t ret;
|
||||
do {
|
||||
ret = read(fd, (byte *) buf + read_sz, count - read_sz);
|
||||
if (ret < 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
PLOGE("read");
|
||||
return ret;
|
||||
}
|
||||
read_sz += ret;
|
||||
} while (read_sz != count && ret != 0);
|
||||
if (read_sz != count) {
|
||||
PLOGE("read (%zu != %zu)", count, read_sz);
|
||||
}
|
||||
return read_sz;
|
||||
}
|
||||
|
||||
off_t xlseek(int fd, off_t offset, int whence) {
|
||||
off_t ret = lseek(fd, offset, whence);
|
||||
if (ret < 0) {
|
||||
PLOGE("lseek");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int xpipe2(int pipefd[2], int flags) {
|
||||
int ret = pipe2(pipefd, flags);
|
||||
if (ret < 0) {
|
||||
PLOGE("pipe2");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int xsetns(int fd, int nstype) {
|
||||
int ret = setns(fd, nstype);
|
||||
if (ret < 0) {
|
||||
PLOGE("setns");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int xunshare(int flags) {
|
||||
int ret = unshare(flags);
|
||||
if (ret < 0) {
|
||||
PLOGE("unshare");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
DIR *xopendir(const char *name) {
|
||||
DIR *d = opendir(name);
|
||||
if (d == nullptr) {
|
||||
PLOGE("opendir: %s", name);
|
||||
}
|
||||
return d;
|
||||
}
|
||||
|
||||
DIR *xfdopendir(int fd) {
|
||||
DIR *d = fdopendir(fd);
|
||||
if (d == nullptr) {
|
||||
PLOGE("fdopendir");
|
||||
}
|
||||
return d;
|
||||
}
|
||||
|
||||
struct dirent *xreaddir(DIR *dirp) {
|
||||
errno = 0;
|
||||
for (dirent *e;;) {
|
||||
e = readdir(dirp);
|
||||
if (e == nullptr) {
|
||||
if (errno)
|
||||
PLOGE("readdir");
|
||||
return nullptr;
|
||||
} else if (e->d_name == "."sv || e->d_name == ".."sv) {
|
||||
// Filter . and .. for users
|
||||
continue;
|
||||
}
|
||||
return e;
|
||||
}
|
||||
}
|
||||
|
||||
pid_t xsetsid() {
|
||||
pid_t pid = setsid();
|
||||
if (pid < 0) {
|
||||
PLOGE("setsid");
|
||||
}
|
||||
return pid;
|
||||
}
|
||||
|
||||
int xsocket(int domain, int type, int protocol) {
|
||||
int fd = socket(domain, type, protocol);
|
||||
if (fd < 0) {
|
||||
PLOGE("socket");
|
||||
}
|
||||
return fd;
|
||||
}
|
||||
|
||||
int xbind(int sockfd, const struct sockaddr *addr, socklen_t addrlen) {
|
||||
int ret = bind(sockfd, addr, addrlen);
|
||||
if (ret < 0) {
|
||||
PLOGE("bind");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int xlisten(int sockfd, int backlog) {
|
||||
int ret = listen(sockfd, backlog);
|
||||
if (ret < 0) {
|
||||
PLOGE("listen");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int xaccept4(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int flags) {
|
||||
int fd = accept4(sockfd, addr, addrlen, flags);
|
||||
if (fd < 0) {
|
||||
PLOGE("accept4");
|
||||
}
|
||||
return fd;
|
||||
}
|
||||
|
||||
void *xmalloc(size_t size) {
|
||||
void *p = malloc(size);
|
||||
if (p == nullptr) {
|
||||
PLOGE("malloc");
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
void *xcalloc(size_t nmemb, size_t size) {
|
||||
void *p = calloc(nmemb, size);
|
||||
if (p == nullptr) {
|
||||
PLOGE("calloc");
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
void *xrealloc(void *ptr, size_t size) {
|
||||
void *p = realloc(ptr, size);
|
||||
if (p == nullptr) {
|
||||
PLOGE("realloc");
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
ssize_t xsendmsg(int sockfd, const struct msghdr *msg, int flags) {
|
||||
int sent = sendmsg(sockfd, msg, flags);
|
||||
if (sent < 0) {
|
||||
PLOGE("sendmsg");
|
||||
}
|
||||
return sent;
|
||||
}
|
||||
|
||||
ssize_t xrecvmsg(int sockfd, struct msghdr *msg, int flags) {
|
||||
int rec = recvmsg(sockfd, msg, flags);
|
||||
if (rec < 0) {
|
||||
PLOGE("recvmsg");
|
||||
}
|
||||
return rec;
|
||||
}
|
||||
|
||||
int xpthread_create(pthread_t *thread, const pthread_attr_t *attr,
|
||||
void *(*start_routine) (void *), void *arg) {
|
||||
errno = pthread_create(thread, attr, start_routine, arg);
|
||||
if (errno) {
|
||||
PLOGE("pthread_create");
|
||||
}
|
||||
return errno;
|
||||
}
|
||||
|
||||
int xaccess(const char *path, int mode) {
|
||||
int ret = access(path, mode);
|
||||
if (ret < 0) {
|
||||
PLOGE("access %s", path);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int xstat(const char *pathname, struct stat *buf) {
|
||||
int ret = stat(pathname, buf);
|
||||
if (ret < 0) {
|
||||
PLOGE("stat %s", pathname);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int xlstat(const char *pathname, struct stat *buf) {
|
||||
int ret = lstat(pathname, buf);
|
||||
if (ret < 0) {
|
||||
PLOGE("lstat %s", pathname);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int xfstat(int fd, struct stat *buf) {
|
||||
int ret = fstat(fd, buf);
|
||||
if (ret < 0) {
|
||||
PLOGE("fstat %d", fd);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int xfstatat(int dirfd, const char *pathname, struct stat *buf, int flags) {
|
||||
int ret = fstatat(dirfd, pathname, buf, flags);
|
||||
if (ret < 0) {
|
||||
PLOGE("fstatat %s", pathname);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int xdup(int fd) {
|
||||
int ret = dup(fd);
|
||||
if (ret < 0) {
|
||||
PLOGE("dup");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int xdup2(int oldfd, int newfd) {
|
||||
int ret = dup2(oldfd, newfd);
|
||||
if (ret < 0) {
|
||||
PLOGE("dup2");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int xdup3(int oldfd, int newfd, int flags) {
|
||||
int ret = dup3(oldfd, newfd, flags);
|
||||
if (ret < 0) {
|
||||
PLOGE("dup3");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
ssize_t xreadlink(const char *pathname, char *buf, size_t bufsiz) {
|
||||
ssize_t ret = readlink(pathname, buf, bufsiz);
|
||||
if (ret < 0) {
|
||||
PLOGE("readlink %s", pathname);
|
||||
} else {
|
||||
buf[ret] = '\0';
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
ssize_t xreadlinkat(int dirfd, const char *pathname, char *buf, size_t bufsiz) {
|
||||
// readlinkat() may fail on x86 platform, returning random value
|
||||
// instead of number of bytes placed in buf (length of link)
|
||||
#if defined(__i386__) || defined(__x86_64__)
|
||||
memset(buf, 0, bufsiz);
|
||||
ssize_t ret = readlinkat(dirfd, pathname, buf, bufsiz);
|
||||
if (ret < 0) {
|
||||
PLOGE("readlinkat %s", pathname);
|
||||
}
|
||||
return ret;
|
||||
#else
|
||||
ssize_t ret = readlinkat(dirfd, pathname, buf, bufsiz);
|
||||
if (ret < 0) {
|
||||
PLOGE("readlinkat %s", pathname);
|
||||
} else {
|
||||
buf[ret] = '\0';
|
||||
}
|
||||
return ret;
|
||||
#endif
|
||||
}
|
||||
|
||||
int xfaccessat(int dirfd, const char *pathname) {
|
||||
int ret = faccessat(dirfd, pathname, F_OK, 0);
|
||||
if (ret < 0) {
|
||||
PLOGE("faccessat %s", pathname);
|
||||
}
|
||||
#if defined(__i386__) || defined(__x86_64__)
|
||||
if (ret > 0 && errno == 0) {
|
||||
LOGD("faccessat success but ret is %d\n", ret);
|
||||
ret = 0;
|
||||
}
|
||||
#endif
|
||||
return ret;
|
||||
}
|
||||
|
||||
int xsymlink(const char *target, const char *linkpath) {
|
||||
int ret = symlink(target, linkpath);
|
||||
if (ret < 0) {
|
||||
PLOGE("symlink %s->%s", target, linkpath);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int xsymlinkat(const char *target, int newdirfd, const char *linkpath) {
|
||||
int ret = symlinkat(target, newdirfd, linkpath);
|
||||
if (ret < 0) {
|
||||
PLOGE("symlinkat %s->%s", target, linkpath);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int xlinkat(int olddirfd, const char *oldpath, int newdirfd, const char *newpath, int flags) {
|
||||
int ret = linkat(olddirfd, oldpath, newdirfd, newpath, flags);
|
||||
if (ret < 0) {
|
||||
PLOGE("linkat %s->%s", oldpath, newpath);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int xmount(const char *source, const char *target,
|
||||
const char *filesystemtype, unsigned long mountflags,
|
||||
const void *data) {
|
||||
int ret = mount(source, target, filesystemtype, mountflags, data);
|
||||
if (ret < 0) {
|
||||
PLOGE("mount %s->%s", source, target);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int xumount(const char *target) {
|
||||
int ret = umount(target);
|
||||
if (ret < 0) {
|
||||
PLOGE("umount %s", target);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int xumount2(const char *target, int flags) {
|
||||
int ret = umount2(target, flags);
|
||||
if (ret < 0) {
|
||||
PLOGE("umount2 %s", target);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int xrename(const char *oldpath, const char *newpath) {
|
||||
int ret = rename(oldpath, newpath);
|
||||
if (ret < 0) {
|
||||
PLOGE("rename %s->%s", oldpath, newpath);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int xmkdir(const char *pathname, mode_t mode) {
|
||||
int ret = mkdir(pathname, mode);
|
||||
if (ret < 0 && errno != EEXIST) {
|
||||
PLOGE("mkdir %s %u", pathname, mode);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int xmkdirs(const char *pathname, mode_t mode) {
|
||||
int ret = mkdirs(pathname, mode);
|
||||
if (ret < 0) {
|
||||
PLOGE("mkdirs %s", pathname);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int xmkdirat(int dirfd, const char *pathname, mode_t mode) {
|
||||
int ret = mkdirat(dirfd, pathname, mode);
|
||||
if (ret < 0 && errno != EEXIST) {
|
||||
PLOGE("mkdirat %s %u", pathname, mode);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void *xmmap(void *addr, size_t length, int prot, int flags,
|
||||
int fd, off_t offset) {
|
||||
void *ret = mmap(addr, length, prot, flags, fd, offset);
|
||||
if (ret == MAP_FAILED) {
|
||||
PLOGE("mmap");
|
||||
return nullptr;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
ssize_t xsendfile(int out_fd, int in_fd, off_t *offset, size_t count) {
|
||||
ssize_t ret = sendfile(out_fd, in_fd, offset, count);
|
||||
if (ret < 0) {
|
||||
PLOGE("sendfile");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
pid_t xfork() {
|
||||
int ret = fork();
|
||||
if (ret < 0) {
|
||||
PLOGE("fork");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int xpoll(struct pollfd *fds, nfds_t nfds, int timeout) {
|
||||
int ret = poll(fds, nfds, timeout);
|
||||
if (ret < 0) {
|
||||
PLOGE("poll");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int xinotify_init1(int flags) {
|
||||
int ret = inotify_init1(flags);
|
||||
if (ret < 0) {
|
||||
PLOGE("inotify_init1");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
char *xrealpath(const char *path, char *resolved_path) {
|
||||
char buf[PATH_MAX];
|
||||
char *ret = realpath(path, buf);
|
||||
if (ret == nullptr) {
|
||||
PLOGE("xrealpath");
|
||||
} else {
|
||||
strcpy(resolved_path, buf);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int xmknod(const char *pathname, mode_t mode, dev_t dev) {
|
||||
int ret = mknod(pathname, mode, dev);
|
||||
if (ret < 0) {
|
||||
PLOGE("mknod");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
long xptrace(int request, pid_t pid, void *addr, void *data) {
|
||||
long ret = ptrace(request, pid, addr, data);
|
||||
if (ret < 0)
|
||||
PLOGE("ptrace %d", pid);
|
||||
return ret;
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
#pragma once
|
||||
|
||||
#include <dirent.h>
|
||||
#include <stdio.h>
|
||||
#include <poll.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
FILE *xfopen(const char *pathname, const char *mode);
|
||||
FILE *xfdopen(int fd, const char *mode);
|
||||
int xopen(const char *pathname, int flags);
|
||||
int xopen(const char *pathname, int flags, mode_t mode);
|
||||
int xopenat(int dirfd, const char *pathname, int flags);
|
||||
int xopenat(int dirfd, const char *pathname, int flags, mode_t mode);
|
||||
ssize_t xwrite(int fd, const void *buf, size_t count);
|
||||
ssize_t xread(int fd, void *buf, size_t count);
|
||||
ssize_t xxread(int fd, void *buf, size_t count);
|
||||
off_t xlseek(int fd, off_t offset, int whence);
|
||||
int xpipe2(int pipefd[2], int flags);
|
||||
int xsetns(int fd, int nstype);
|
||||
int xunshare(int flags);
|
||||
DIR *xopendir(const char *name);
|
||||
DIR *xfdopendir(int fd);
|
||||
struct dirent *xreaddir(DIR *dirp);
|
||||
pid_t xsetsid();
|
||||
int xsocket(int domain, int type, int protocol);
|
||||
int xbind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
|
||||
int xconnect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
|
||||
int xlisten(int sockfd, int backlog);
|
||||
int xaccept4(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int flags);
|
||||
void *xmalloc(size_t size);
|
||||
void *xcalloc(size_t nmemb, size_t size);
|
||||
void *xrealloc(void *ptr, size_t size);
|
||||
ssize_t xsendmsg(int sockfd, const struct msghdr *msg, int flags);
|
||||
ssize_t xrecvmsg(int sockfd, struct msghdr *msg, int flags);
|
||||
int xpthread_create(pthread_t *thread, const pthread_attr_t *attr,
|
||||
void *(*start_routine) (void *), void *arg);
|
||||
int xaccess(const char *path, int mode);
|
||||
int xstat(const char *pathname, struct stat *buf);
|
||||
int xlstat(const char *pathname, struct stat *buf);
|
||||
int xfstat(int fd, struct stat *buf);
|
||||
int xfstatat(int dirfd, const char *pathname, struct stat *buf, int flags);
|
||||
int xdup(int fd);
|
||||
int xdup2(int oldfd, int newfd);
|
||||
int xdup3(int oldfd, int newfd, int flags);
|
||||
ssize_t xreadlink(const char *pathname, char *buf, size_t bufsiz);
|
||||
ssize_t xreadlinkat(int dirfd, const char *pathname, char *buf, size_t bufsiz);
|
||||
int xfaccessat(int dirfd, const char *pathname);
|
||||
int xsymlink(const char *target, const char *linkpath);
|
||||
int xsymlinkat(const char *target, int newdirfd, const char *linkpath);
|
||||
int xlinkat(int olddirfd, const char *oldpath, int newdirfd, const char *newpath, int flags);
|
||||
int xmount(const char *source, const char *target,
|
||||
const char *filesystemtype, unsigned long mountflags,
|
||||
const void *data);
|
||||
int xumount(const char *target);
|
||||
int xumount2(const char *target, int flags);
|
||||
int xrename(const char *oldpath, const char *newpath);
|
||||
int xmkdir(const char *pathname, mode_t mode);
|
||||
int xmkdirs(const char *pathname, mode_t mode);
|
||||
int xmkdirat(int dirfd, const char *pathname, mode_t mode);
|
||||
void *xmmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
|
||||
ssize_t xsendfile(int out_fd, int in_fd, off_t *offset, size_t count);
|
||||
pid_t xfork();
|
||||
int xpoll(struct pollfd *fds, nfds_t nfds, int timeout);
|
||||
int xinotify_init1(int flags);
|
||||
char *xrealpath(const char *path, char *resolved_path);
|
||||
int xmknod(const char *pathname, mode_t mode, dev_t dev);
|
||||
long xptrace(int request, pid_t pid, void *addr = nullptr, void *data = nullptr);
|
||||
static inline long xptrace(int request, pid_t pid, void *addr, uintptr_t data) {
|
||||
return xptrace(request, pid, addr, reinterpret_cast<void *>(data));
|
||||
}
|
||||
#define WEVENT(s) (((s) & 0xffff0000) >> 16)
|
||||
@@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "magiskboot"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["staticlib"]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
base = { path = "../base" }
|
||||
@@ -0,0 +1,788 @@
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
#include <libfdt.h>
|
||||
#include <mincrypt/sha.h>
|
||||
#include <mincrypt/sha256.h>
|
||||
#include <base.hpp>
|
||||
|
||||
#include "bootimg.hpp"
|
||||
#include "magiskboot.hpp"
|
||||
#include "compress.hpp"
|
||||
|
||||
using namespace std;
|
||||
|
||||
uint32_t dyn_img_hdr::j32 = 0;
|
||||
uint64_t dyn_img_hdr::j64 = 0;
|
||||
|
||||
#define PADDING 15
|
||||
|
||||
static void decompress(format_t type, int fd, const void *in, size_t size) {
|
||||
auto ptr = get_decoder(type, make_unique<fd_stream>(fd));
|
||||
ptr->write(in, size, true);
|
||||
}
|
||||
|
||||
static off_t compress(format_t type, int fd, const void *in, size_t size) {
|
||||
auto prev = lseek(fd, 0, SEEK_CUR);
|
||||
{
|
||||
auto strm = get_encoder(type, make_unique<fd_stream>(fd));
|
||||
strm->write(in, size, true);
|
||||
}
|
||||
auto now = lseek(fd, 0, SEEK_CUR);
|
||||
return now - prev;
|
||||
}
|
||||
|
||||
static void dump(void *buf, size_t size, const char *filename) {
|
||||
if (size == 0)
|
||||
return;
|
||||
int fd = creat(filename, 0644);
|
||||
xwrite(fd, buf, size);
|
||||
close(fd);
|
||||
}
|
||||
|
||||
static size_t restore(int fd, const char *filename) {
|
||||
int ifd = xopen(filename, O_RDONLY);
|
||||
size_t size = lseek(ifd, 0, SEEK_END);
|
||||
lseek(ifd, 0, SEEK_SET);
|
||||
xsendfile(fd, ifd, nullptr, size);
|
||||
close(ifd);
|
||||
return size;
|
||||
}
|
||||
|
||||
void dyn_img_hdr::print() {
|
||||
uint32_t ver = header_version();
|
||||
fprintf(stderr, "%-*s [%u]\n", PADDING, "HEADER_VER", ver);
|
||||
if (!is_vendor)
|
||||
fprintf(stderr, "%-*s [%u]\n", PADDING, "KERNEL_SZ", kernel_size());
|
||||
fprintf(stderr, "%-*s [%u]\n", PADDING, "RAMDISK_SZ", ramdisk_size());
|
||||
if (ver < 3)
|
||||
fprintf(stderr, "%-*s [%u]\n", PADDING, "SECOND_SZ", second_size());
|
||||
if (ver == 0)
|
||||
fprintf(stderr, "%-*s [%u]\n", PADDING, "EXTRA_SZ", extra_size());
|
||||
if (ver == 1 || ver == 2)
|
||||
fprintf(stderr, "%-*s [%u]\n", PADDING, "RECOV_DTBO_SZ", recovery_dtbo_size());
|
||||
if (ver == 2 || is_vendor)
|
||||
fprintf(stderr, "%-*s [%u]\n", PADDING, "DTB_SZ", dtb_size());
|
||||
|
||||
if (uint32_t os_ver = os_version()) {
|
||||
int a,b,c,y,m = 0;
|
||||
int version = os_ver >> 11;
|
||||
int patch_level = os_ver & 0x7ff;
|
||||
|
||||
a = (version >> 14) & 0x7f;
|
||||
b = (version >> 7) & 0x7f;
|
||||
c = version & 0x7f;
|
||||
fprintf(stderr, "%-*s [%d.%d.%d]\n", PADDING, "OS_VERSION", a, b, c);
|
||||
|
||||
y = (patch_level >> 4) + 2000;
|
||||
m = patch_level & 0xf;
|
||||
fprintf(stderr, "%-*s [%d-%02d]\n", PADDING, "OS_PATCH_LEVEL", y, m);
|
||||
}
|
||||
|
||||
fprintf(stderr, "%-*s [%u]\n", PADDING, "PAGESIZE", page_size());
|
||||
if (char *n = name()) {
|
||||
fprintf(stderr, "%-*s [%s]\n", PADDING, "NAME", n);
|
||||
}
|
||||
fprintf(stderr, "%-*s [%.*s%.*s]\n", PADDING, "CMDLINE",
|
||||
BOOT_ARGS_SIZE, cmdline(), BOOT_EXTRA_ARGS_SIZE, extra_cmdline());
|
||||
if (char *checksum = id()) {
|
||||
fprintf(stderr, "%-*s [", PADDING, "CHECKSUM");
|
||||
for (int i = 0; i < SHA256_DIGEST_SIZE; ++i)
|
||||
fprintf(stderr, "%02hhx", checksum[i]);
|
||||
fprintf(stderr, "]\n");
|
||||
}
|
||||
}
|
||||
|
||||
void dyn_img_hdr::dump_hdr_file() {
|
||||
FILE *fp = xfopen(HEADER_FILE, "w");
|
||||
if (name())
|
||||
fprintf(fp, "name=%s\n", name());
|
||||
fprintf(fp, "cmdline=%.*s%.*s\n", BOOT_ARGS_SIZE, cmdline(), BOOT_EXTRA_ARGS_SIZE, extra_cmdline());
|
||||
uint32_t ver = os_version();
|
||||
if (ver) {
|
||||
int a, b, c, y, m;
|
||||
int version, patch_level;
|
||||
version = ver >> 11;
|
||||
patch_level = ver & 0x7ff;
|
||||
|
||||
a = (version >> 14) & 0x7f;
|
||||
b = (version >> 7) & 0x7f;
|
||||
c = version & 0x7f;
|
||||
fprintf(fp, "os_version=%d.%d.%d\n", a, b, c);
|
||||
|
||||
y = (patch_level >> 4) + 2000;
|
||||
m = patch_level & 0xf;
|
||||
fprintf(fp, "os_patch_level=%d-%02d\n", y, m);
|
||||
}
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
void dyn_img_hdr::load_hdr_file() {
|
||||
parse_prop_file(HEADER_FILE, [=](string_view key, string_view value) -> bool {
|
||||
if (key == "name" && name()) {
|
||||
memset(name(), 0, 16);
|
||||
memcpy(name(), value.data(), value.length() > 15 ? 15 : value.length());
|
||||
} else if (key == "cmdline") {
|
||||
memset(cmdline(), 0, BOOT_ARGS_SIZE);
|
||||
memset(extra_cmdline(), 0, BOOT_EXTRA_ARGS_SIZE);
|
||||
if (value.length() > BOOT_ARGS_SIZE) {
|
||||
memcpy(cmdline(), value.data(), BOOT_ARGS_SIZE);
|
||||
auto len = std::min(value.length() - BOOT_ARGS_SIZE, (size_t) BOOT_EXTRA_ARGS_SIZE);
|
||||
memcpy(extra_cmdline(), &value[BOOT_ARGS_SIZE], len);
|
||||
} else {
|
||||
memcpy(cmdline(), value.data(), value.length());
|
||||
}
|
||||
} else if (key == "os_version") {
|
||||
int patch_level = os_version() & 0x7ff;
|
||||
int a, b, c;
|
||||
sscanf(value.data(), "%d.%d.%d", &a, &b, &c);
|
||||
os_version() = (((a << 14) | (b << 7) | c) << 11) | patch_level;
|
||||
} else if (key == "os_patch_level") {
|
||||
int os_ver = os_version() >> 11;
|
||||
int y, m;
|
||||
sscanf(value.data(), "%d-%d", &y, &m);
|
||||
y -= 2000;
|
||||
os_version() = (os_ver << 11) | (y << 4) | m;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
boot_img::boot_img(const char *image) : map(image) {
|
||||
fprintf(stderr, "Parsing boot image: [%s]\n", image);
|
||||
for (uint8_t *addr = map.buf; addr < map.buf + map.sz; ++addr) {
|
||||
format_t fmt = check_fmt(addr, map.sz);
|
||||
switch (fmt) {
|
||||
case CHROMEOS:
|
||||
// chromeos require external signing
|
||||
flags[CHROMEOS_FLAG] = true;
|
||||
addr += 65535;
|
||||
break;
|
||||
case DHTB:
|
||||
flags[DHTB_FLAG] = true;
|
||||
flags[SEANDROID_FLAG] = true;
|
||||
fprintf(stderr, "DHTB_HDR\n");
|
||||
addr += sizeof(dhtb_hdr) - 1;
|
||||
break;
|
||||
case BLOB:
|
||||
flags[BLOB_FLAG] = true;
|
||||
fprintf(stderr, "TEGRA_BLOB\n");
|
||||
addr += sizeof(blob_hdr) - 1;
|
||||
break;
|
||||
case AOSP:
|
||||
case AOSP_VENDOR:
|
||||
parse_image(addr, fmt);
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
exit(1);
|
||||
}
|
||||
|
||||
boot_img::~boot_img() {
|
||||
delete hdr;
|
||||
}
|
||||
|
||||
static int find_dtb_offset(uint8_t *buf, unsigned sz) {
|
||||
uint8_t * const end = buf + sz;
|
||||
|
||||
for (uint8_t *curr = buf; curr < end; curr += sizeof(fdt_header)) {
|
||||
curr = static_cast<uint8_t*>(memmem(curr, end - curr, DTB_MAGIC, sizeof(fdt32_t)));
|
||||
if (curr == nullptr)
|
||||
return -1;
|
||||
|
||||
auto fdt_hdr = reinterpret_cast<fdt_header *>(curr);
|
||||
|
||||
// Check that fdt_header.totalsize does not overflow kernel image size
|
||||
uint32_t totalsize = fdt32_to_cpu(fdt_hdr->totalsize);
|
||||
if (totalsize > end - curr)
|
||||
continue;
|
||||
|
||||
// Check that fdt_header.off_dt_struct does not overflow kernel image size
|
||||
uint32_t off_dt_struct = fdt32_to_cpu(fdt_hdr->off_dt_struct);
|
||||
if (off_dt_struct > end - curr)
|
||||
continue;
|
||||
|
||||
// Check that fdt_node_header.tag of first node is FDT_BEGIN_NODE
|
||||
auto fdt_node_hdr = reinterpret_cast<fdt_node_header *>(curr + off_dt_struct);
|
||||
if (fdt32_to_cpu(fdt_node_hdr->tag) != FDT_BEGIN_NODE)
|
||||
continue;
|
||||
|
||||
return curr - buf;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static format_t check_fmt_lg(uint8_t *buf, unsigned sz) {
|
||||
format_t fmt = check_fmt(buf, sz);
|
||||
if (fmt == LZ4_LEGACY) {
|
||||
// We need to check if it is LZ4_LG
|
||||
uint32_t off = 4;
|
||||
uint32_t block_sz;
|
||||
while (off + sizeof(block_sz) <= sz) {
|
||||
memcpy(&block_sz, buf + off, sizeof(block_sz));
|
||||
off += sizeof(block_sz);
|
||||
if (off + block_sz > sz)
|
||||
return LZ4_LG;
|
||||
off += block_sz;
|
||||
}
|
||||
}
|
||||
return fmt;
|
||||
}
|
||||
|
||||
#define CMD_MATCH(s) BUFFER_MATCH(h->cmdline, s)
|
||||
|
||||
dyn_img_hdr *boot_img::create_hdr(uint8_t *addr, format_t type) {
|
||||
if (type == AOSP_VENDOR) {
|
||||
fprintf(stderr, "VENDOR_BOOT_HDR\n");
|
||||
auto h = reinterpret_cast<boot_img_hdr_vnd_v3*>(addr);
|
||||
hdr_addr = addr;
|
||||
switch (h->header_version) {
|
||||
case 4:
|
||||
return new dyn_img_vnd_v4(addr);
|
||||
default:
|
||||
return new dyn_img_vnd_v3(addr);
|
||||
}
|
||||
}
|
||||
|
||||
auto h = reinterpret_cast<boot_img_hdr_v0*>(addr);
|
||||
|
||||
if (h->page_size >= 0x02000000) {
|
||||
fprintf(stderr, "PXA_BOOT_HDR\n");
|
||||
hdr_addr = addr;
|
||||
return new dyn_img_pxa(addr);
|
||||
}
|
||||
|
||||
if (CMD_MATCH(NOOKHD_RL_MAGIC) ||
|
||||
CMD_MATCH(NOOKHD_GL_MAGIC) ||
|
||||
CMD_MATCH(NOOKHD_GR_MAGIC) ||
|
||||
CMD_MATCH(NOOKHD_EB_MAGIC) ||
|
||||
CMD_MATCH(NOOKHD_ER_MAGIC)) {
|
||||
flags[NOOKHD_FLAG] = true;
|
||||
fprintf(stderr, "NOOKHD_LOADER\n");
|
||||
addr += NOOKHD_PRE_HEADER_SZ;
|
||||
} else if (memcmp(h->name, ACCLAIM_MAGIC, 10) == 0) {
|
||||
flags[ACCLAIM_FLAG] = true;
|
||||
fprintf(stderr, "ACCLAIM_LOADER\n");
|
||||
addr += ACCLAIM_PRE_HEADER_SZ;
|
||||
}
|
||||
|
||||
// addr could be adjusted
|
||||
h = reinterpret_cast<boot_img_hdr_v0*>(addr);
|
||||
hdr_addr = addr;
|
||||
|
||||
switch (h->header_version) {
|
||||
case 1:
|
||||
return new dyn_img_v1(addr);
|
||||
case 2:
|
||||
return new dyn_img_v2(addr);
|
||||
case 3:
|
||||
return new dyn_img_v3(addr);
|
||||
case 4:
|
||||
return new dyn_img_v4(addr);
|
||||
default:
|
||||
return new dyn_img_v0(addr);
|
||||
}
|
||||
}
|
||||
|
||||
#define get_block(name) \
|
||||
name = hdr_addr + off; \
|
||||
off += hdr->name##_size(); \
|
||||
off = align_to(off, hdr->page_size());
|
||||
|
||||
#define get_ignore(name) \
|
||||
if (hdr->name##_size()) { \
|
||||
auto blk_sz = align_to(hdr->name##_size(), hdr->page_size()); \
|
||||
ignore_size += blk_sz; \
|
||||
off += blk_sz; \
|
||||
}
|
||||
|
||||
void boot_img::parse_image(uint8_t *addr, format_t type) {
|
||||
hdr = create_hdr(addr, type);
|
||||
|
||||
if (char *id = hdr->id()) {
|
||||
for (int i = SHA_DIGEST_SIZE + 4; i < SHA256_DIGEST_SIZE; ++i) {
|
||||
if (id[i]) {
|
||||
flags[SHA256_FLAG] = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hdr->print();
|
||||
|
||||
size_t off = hdr->hdr_space();
|
||||
get_block(kernel);
|
||||
get_block(ramdisk);
|
||||
get_block(second);
|
||||
get_block(extra);
|
||||
get_block(recovery_dtbo);
|
||||
get_block(dtb);
|
||||
|
||||
ignore = hdr_addr + off;
|
||||
get_ignore(signature)
|
||||
get_ignore(vendor_ramdisk_table)
|
||||
get_ignore(bootconfig)
|
||||
|
||||
if (auto size = hdr->kernel_size()) {
|
||||
if (int dtb_off = find_dtb_offset(kernel, size); dtb_off > 0) {
|
||||
kernel_dtb = kernel + dtb_off;
|
||||
hdr->kernel_dt_size = size - dtb_off;
|
||||
hdr->kernel_size() = dtb_off;
|
||||
fprintf(stderr, "%-*s [%u]\n", PADDING, "KERNEL_DTB_SZ", hdr->kernel_dt_size);
|
||||
}
|
||||
|
||||
k_fmt = check_fmt_lg(kernel, hdr->kernel_size());
|
||||
if (k_fmt == MTK) {
|
||||
fprintf(stderr, "MTK_KERNEL_HDR\n");
|
||||
flags[MTK_KERNEL] = true;
|
||||
k_hdr = reinterpret_cast<mtk_hdr *>(kernel);
|
||||
fprintf(stderr, "%-*s [%u]\n", PADDING, "SIZE", k_hdr->size);
|
||||
fprintf(stderr, "%-*s [%s]\n", PADDING, "NAME", k_hdr->name);
|
||||
kernel += sizeof(mtk_hdr);
|
||||
hdr->kernel_size() -= sizeof(mtk_hdr);
|
||||
k_fmt = check_fmt_lg(kernel, hdr->kernel_size());
|
||||
}
|
||||
if (k_fmt == ZIMAGE) {
|
||||
z_hdr = reinterpret_cast<zimage_hdr *>(kernel);
|
||||
if (void *gzip_offset = memmem(kernel, hdr->kernel_size(), GZIP1_MAGIC "\x08\x00", 4)) {
|
||||
fprintf(stderr, "ZIMAGE_KERNEL\n");
|
||||
z_info.hdr_sz = (uint8_t *) gzip_offset - kernel;
|
||||
|
||||
// Find end of piggy
|
||||
uint32_t zImage_size = z_hdr->end - z_hdr->start;
|
||||
uint32_t piggy_end = zImage_size;
|
||||
uint32_t offsets[16];
|
||||
memcpy(offsets, kernel + zImage_size - sizeof(offsets), sizeof(offsets));
|
||||
for (int i = 15; i >= 0; --i) {
|
||||
if (offsets[i] > (zImage_size - 0xFF) && offsets[i] < zImage_size) {
|
||||
piggy_end = offsets[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (piggy_end == zImage_size) {
|
||||
fprintf(stderr, "! Could not find end of zImage piggy, keeping raw kernel\n");
|
||||
} else {
|
||||
flags[ZIMAGE_KERNEL] = true;
|
||||
z_info.tail = kernel + piggy_end;
|
||||
z_info.tail_sz = hdr->kernel_size() - piggy_end;
|
||||
kernel += z_info.hdr_sz;
|
||||
hdr->kernel_size() = piggy_end - z_info.hdr_sz;
|
||||
k_fmt = check_fmt_lg(kernel, hdr->kernel_size());
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr, "! Could not find zImage gzip piggy, keeping raw kernel\n");
|
||||
}
|
||||
}
|
||||
fprintf(stderr, "%-*s [%s]\n", PADDING, "KERNEL_FMT", fmt2name[k_fmt]);
|
||||
}
|
||||
if (auto size = hdr->ramdisk_size()) {
|
||||
if (hdr->is_vendor && hdr->header_version() >= 4) {
|
||||
// v4 vendor boot contains multiple ramdisks
|
||||
// Do not try to mess with it for now
|
||||
r_fmt = UNKNOWN;
|
||||
} else {
|
||||
r_fmt = check_fmt_lg(ramdisk, size);
|
||||
}
|
||||
if (r_fmt == MTK) {
|
||||
fprintf(stderr, "MTK_RAMDISK_HDR\n");
|
||||
flags[MTK_RAMDISK] = true;
|
||||
r_hdr = reinterpret_cast<mtk_hdr *>(ramdisk);
|
||||
fprintf(stderr, "%-*s [%u]\n", PADDING, "SIZE", r_hdr->size);
|
||||
fprintf(stderr, "%-*s [%s]\n", PADDING, "NAME", r_hdr->name);
|
||||
ramdisk += sizeof(mtk_hdr);
|
||||
hdr->ramdisk_size() -= sizeof(mtk_hdr);
|
||||
r_fmt = check_fmt_lg(ramdisk, hdr->ramdisk_size());
|
||||
}
|
||||
fprintf(stderr, "%-*s [%s]\n", PADDING, "RAMDISK_FMT", fmt2name[r_fmt]);
|
||||
}
|
||||
if (!hdr->is_vendor && hdr->ramdisk_size() == 0 && hdr->header_version() == 4) {
|
||||
// When a v4 boot image does not contain ramdisk, we will have to create
|
||||
// the CPIO from scratch. However, since this ramdisk will have to be merged
|
||||
// with other vendor ramdisks, it has to use the exact same compression method.
|
||||
// v4 GKIs are required to use lz4 (legacy), so hardcode it here.
|
||||
r_fmt = LZ4_LEGACY;
|
||||
}
|
||||
if (auto size = hdr->extra_size()) {
|
||||
e_fmt = check_fmt_lg(extra, size);
|
||||
fprintf(stderr, "%-*s [%s]\n", PADDING, "EXTRA_FMT", fmt2name[e_fmt]);
|
||||
}
|
||||
|
||||
if (addr + off < map.buf + map.sz) {
|
||||
tail = addr + off;
|
||||
tail_size = map.buf + map.sz - tail;
|
||||
|
||||
// Check special flags
|
||||
if (tail_size >= 16 && BUFFER_MATCH(tail, SEANDROID_MAGIC)) {
|
||||
fprintf(stderr, "SAMSUNG_SEANDROID\n");
|
||||
flags[SEANDROID_FLAG] = true;
|
||||
} else if (tail_size >= 16 && BUFFER_MATCH(tail, LG_BUMP_MAGIC)) {
|
||||
fprintf(stderr, "LG_BUMP_IMAGE\n");
|
||||
flags[LG_BUMP_FLAG] = true;
|
||||
}
|
||||
|
||||
// Find AVB footer
|
||||
void *footer = tail + tail_size - sizeof(AvbFooter);
|
||||
if (BUFFER_MATCH(footer, AVB_FOOTER_MAGIC)) {
|
||||
avb_footer = reinterpret_cast<AvbFooter*>(footer);
|
||||
// Double check if meta header exists
|
||||
void *meta = hdr_addr + __builtin_bswap64(avb_footer->vbmeta_offset);
|
||||
if (BUFFER_MATCH(meta, AVB_MAGIC)) {
|
||||
fprintf(stderr, "VBMETA\n");
|
||||
flags[AVB_FLAG] = true;
|
||||
vbmeta = reinterpret_cast<AvbVBMetaImageHeader*>(meta);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int split_image_dtb(const char *filename) {
|
||||
auto img = mmap_data(filename);
|
||||
|
||||
if (int off = find_dtb_offset(img.buf, img.sz); off > 0) {
|
||||
format_t fmt = check_fmt_lg(img.buf, img.sz);
|
||||
if (COMPRESSED(fmt)) {
|
||||
int fd = creat(KERNEL_FILE, 0644);
|
||||
decompress(fmt, fd, img.buf, off);
|
||||
close(fd);
|
||||
} else {
|
||||
dump(img.buf, off, KERNEL_FILE);
|
||||
}
|
||||
dump(img.buf + off, img.sz - off, KER_DTB_FILE);
|
||||
return 0;
|
||||
} else {
|
||||
fprintf(stderr, "Cannot find DTB in %s\n", filename);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
int unpack(const char *image, bool skip_decomp, bool hdr) {
|
||||
boot_img boot(image);
|
||||
|
||||
if (hdr)
|
||||
boot.hdr->dump_hdr_file();
|
||||
|
||||
// Dump kernel
|
||||
if (!skip_decomp && COMPRESSED(boot.k_fmt)) {
|
||||
if (boot.hdr->kernel_size() != 0) {
|
||||
int fd = creat(KERNEL_FILE, 0644);
|
||||
decompress(boot.k_fmt, fd, boot.kernel, boot.hdr->kernel_size());
|
||||
close(fd);
|
||||
}
|
||||
} else {
|
||||
dump(boot.kernel, boot.hdr->kernel_size(), KERNEL_FILE);
|
||||
}
|
||||
|
||||
// Dump kernel_dtb
|
||||
dump(boot.kernel_dtb, boot.hdr->kernel_dt_size, KER_DTB_FILE);
|
||||
|
||||
// Dump ramdisk
|
||||
if (!skip_decomp && COMPRESSED(boot.r_fmt)) {
|
||||
if (boot.hdr->ramdisk_size() != 0) {
|
||||
int fd = creat(RAMDISK_FILE, 0644);
|
||||
decompress(boot.r_fmt, fd, boot.ramdisk, boot.hdr->ramdisk_size());
|
||||
close(fd);
|
||||
}
|
||||
} else {
|
||||
dump(boot.ramdisk, boot.hdr->ramdisk_size(), RAMDISK_FILE);
|
||||
}
|
||||
|
||||
// Dump second
|
||||
dump(boot.second, boot.hdr->second_size(), SECOND_FILE);
|
||||
|
||||
// Dump extra
|
||||
if (!skip_decomp && COMPRESSED(boot.e_fmt)) {
|
||||
if (boot.hdr->extra_size() != 0) {
|
||||
int fd = creat(EXTRA_FILE, 0644);
|
||||
decompress(boot.e_fmt, fd, boot.extra, boot.hdr->extra_size());
|
||||
close(fd);
|
||||
}
|
||||
} else {
|
||||
dump(boot.extra, boot.hdr->extra_size(), EXTRA_FILE);
|
||||
}
|
||||
|
||||
// Dump recovery_dtbo
|
||||
dump(boot.recovery_dtbo, boot.hdr->recovery_dtbo_size(), RECV_DTBO_FILE);
|
||||
|
||||
// Dump dtb
|
||||
dump(boot.dtb, boot.hdr->dtb_size(), DTB_FILE);
|
||||
|
||||
return boot.flags[CHROMEOS_FLAG] ? 2 : 0;
|
||||
}
|
||||
|
||||
#define file_align_with(page_size) \
|
||||
write_zero(fd, align_padding(lseek(fd, 0, SEEK_CUR) - off.header, page_size))
|
||||
|
||||
#define file_align() file_align_with(boot.hdr->page_size())
|
||||
|
||||
void repack(const char *src_img, const char *out_img, bool skip_comp) {
|
||||
const boot_img boot(src_img);
|
||||
fprintf(stderr, "Repack to boot image: [%s]\n", out_img);
|
||||
|
||||
struct {
|
||||
uint32_t header;
|
||||
uint32_t kernel;
|
||||
uint32_t ramdisk;
|
||||
uint32_t second;
|
||||
uint32_t extra;
|
||||
uint32_t dtb;
|
||||
uint32_t total;
|
||||
uint32_t vbmeta;
|
||||
} off{};
|
||||
|
||||
// Create a new boot header and reset sizes
|
||||
auto hdr = boot.hdr->clone();
|
||||
hdr->kernel_size() = 0;
|
||||
hdr->ramdisk_size() = 0;
|
||||
hdr->second_size() = 0;
|
||||
hdr->dtb_size() = 0;
|
||||
hdr->kernel_dt_size = 0;
|
||||
|
||||
if (access(HEADER_FILE, R_OK) == 0)
|
||||
hdr->load_hdr_file();
|
||||
|
||||
/***************
|
||||
* Write blocks
|
||||
***************/
|
||||
|
||||
// Create new image
|
||||
int fd = creat(out_img, 0644);
|
||||
|
||||
if (boot.flags[DHTB_FLAG]) {
|
||||
// Skip DHTB header
|
||||
write_zero(fd, sizeof(dhtb_hdr));
|
||||
} else if (boot.flags[BLOB_FLAG]) {
|
||||
xwrite(fd, boot.map.buf, sizeof(blob_hdr));
|
||||
} else if (boot.flags[NOOKHD_FLAG]) {
|
||||
xwrite(fd, boot.map.buf, NOOKHD_PRE_HEADER_SZ);
|
||||
} else if (boot.flags[ACCLAIM_FLAG]) {
|
||||
xwrite(fd, boot.map.buf, ACCLAIM_PRE_HEADER_SZ);
|
||||
}
|
||||
|
||||
// Copy raw header
|
||||
off.header = lseek(fd, 0, SEEK_CUR);
|
||||
xwrite(fd, boot.hdr_addr, hdr->hdr_space());
|
||||
|
||||
// kernel
|
||||
off.kernel = lseek(fd, 0, SEEK_CUR);
|
||||
if (boot.flags[MTK_KERNEL]) {
|
||||
// Copy MTK headers
|
||||
xwrite(fd, boot.k_hdr, sizeof(mtk_hdr));
|
||||
}
|
||||
if (boot.flags[ZIMAGE_KERNEL]) {
|
||||
// Copy zImage headers
|
||||
xwrite(fd, boot.z_hdr, boot.z_info.hdr_sz);
|
||||
}
|
||||
if (access(KERNEL_FILE, R_OK) == 0) {
|
||||
auto m = mmap_data(KERNEL_FILE);
|
||||
if (!skip_comp && !COMPRESSED_ANY(check_fmt(m.buf, m.sz)) && COMPRESSED(boot.k_fmt)) {
|
||||
// Always use zopfli for zImage compression
|
||||
auto fmt = (boot.flags[ZIMAGE_KERNEL] && boot.k_fmt == GZIP) ? ZOPFLI : boot.k_fmt;
|
||||
hdr->kernel_size() = compress(fmt, fd, m.buf, m.sz);
|
||||
} else {
|
||||
hdr->kernel_size() = xwrite(fd, m.buf, m.sz);
|
||||
}
|
||||
|
||||
if (boot.flags[ZIMAGE_KERNEL]) {
|
||||
if (hdr->kernel_size() > boot.hdr->kernel_size()) {
|
||||
fprintf(stderr, "! Recompressed kernel is too large, using original kernel\n");
|
||||
ftruncate64(fd, lseek64(fd, - (off64_t) hdr->kernel_size(), SEEK_CUR));
|
||||
xwrite(fd, boot.kernel, boot.hdr->kernel_size());
|
||||
} else {
|
||||
// Pad zeros to make sure the zImage file size does not change
|
||||
// Also ensure the last 4 bytes are the uncompressed vmlinux size
|
||||
uint32_t sz = m.sz;
|
||||
write_zero(fd, boot.hdr->kernel_size() - hdr->kernel_size() - sizeof(sz));
|
||||
xwrite(fd, &sz, sizeof(sz));
|
||||
}
|
||||
|
||||
// zImage size shall remain the same
|
||||
hdr->kernel_size() = boot.hdr->kernel_size();
|
||||
}
|
||||
}
|
||||
if (boot.flags[ZIMAGE_KERNEL]) {
|
||||
// Copy zImage tail and adjust size accordingly
|
||||
hdr->kernel_size() += boot.z_info.hdr_sz;
|
||||
hdr->kernel_size() += xwrite(fd, boot.z_info.tail, boot.z_info.tail_sz);
|
||||
}
|
||||
|
||||
// kernel dtb
|
||||
if (access(KER_DTB_FILE, R_OK) == 0)
|
||||
hdr->kernel_size() += restore(fd, KER_DTB_FILE);
|
||||
file_align();
|
||||
|
||||
// ramdisk
|
||||
off.ramdisk = lseek(fd, 0, SEEK_CUR);
|
||||
if (boot.flags[MTK_RAMDISK]) {
|
||||
// Copy MTK headers
|
||||
xwrite(fd, boot.r_hdr, sizeof(mtk_hdr));
|
||||
}
|
||||
if (access(RAMDISK_FILE, R_OK) == 0) {
|
||||
auto m = mmap_data(RAMDISK_FILE);
|
||||
if (!skip_comp && !COMPRESSED_ANY(check_fmt(m.buf, m.sz)) && COMPRESSED(boot.r_fmt)) {
|
||||
hdr->ramdisk_size() = compress(boot.r_fmt, fd, m.buf, m.sz);
|
||||
} else {
|
||||
hdr->ramdisk_size() = xwrite(fd, m.buf, m.sz);
|
||||
}
|
||||
file_align();
|
||||
}
|
||||
|
||||
// second
|
||||
off.second = lseek(fd, 0, SEEK_CUR);
|
||||
if (access(SECOND_FILE, R_OK) == 0) {
|
||||
hdr->second_size() = restore(fd, SECOND_FILE);
|
||||
file_align();
|
||||
}
|
||||
|
||||
// extra
|
||||
off.extra = lseek(fd, 0, SEEK_CUR);
|
||||
if (access(EXTRA_FILE, R_OK) == 0) {
|
||||
auto m = mmap_data(EXTRA_FILE);
|
||||
if (!skip_comp && !COMPRESSED_ANY(check_fmt(m.buf, m.sz)) && COMPRESSED(boot.e_fmt)) {
|
||||
hdr->extra_size() = compress(boot.e_fmt, fd, m.buf, m.sz);
|
||||
} else {
|
||||
hdr->extra_size() = xwrite(fd, m.buf, m.sz);
|
||||
}
|
||||
file_align();
|
||||
}
|
||||
|
||||
// recovery_dtbo
|
||||
if (access(RECV_DTBO_FILE, R_OK) == 0) {
|
||||
hdr->recovery_dtbo_offset() = lseek(fd, 0, SEEK_CUR);
|
||||
hdr->recovery_dtbo_size() = restore(fd, RECV_DTBO_FILE);
|
||||
file_align();
|
||||
}
|
||||
|
||||
// dtb
|
||||
off.dtb = lseek(fd, 0, SEEK_CUR);
|
||||
if (access(DTB_FILE, R_OK) == 0) {
|
||||
hdr->dtb_size() = restore(fd, DTB_FILE);
|
||||
file_align();
|
||||
}
|
||||
|
||||
// Directly copy ignored blobs
|
||||
if (boot.ignore_size) {
|
||||
// ignore_size should already be aligned
|
||||
xwrite(fd, boot.ignore, boot.ignore_size);
|
||||
}
|
||||
|
||||
// Proprietary stuffs
|
||||
if (boot.flags[SEANDROID_FLAG]) {
|
||||
xwrite(fd, SEANDROID_MAGIC, 16);
|
||||
if (boot.flags[DHTB_FLAG]) {
|
||||
xwrite(fd, "\xFF\xFF\xFF\xFF", 4);
|
||||
}
|
||||
} else if (boot.flags[LG_BUMP_FLAG]) {
|
||||
xwrite(fd, LG_BUMP_MAGIC, 16);
|
||||
}
|
||||
|
||||
off.total = lseek(fd, 0, SEEK_CUR);
|
||||
file_align();
|
||||
|
||||
// vbmeta
|
||||
if (boot.flags[AVB_FLAG]) {
|
||||
// According to avbtool.py, if the input is not an Android sparse image
|
||||
// (which boot images are not), the default block size is 4096
|
||||
file_align_with(4096);
|
||||
off.vbmeta = lseek(fd, 0, SEEK_CUR);
|
||||
uint64_t vbmeta_size = __builtin_bswap64(boot.avb_footer->vbmeta_size);
|
||||
xwrite(fd, boot.vbmeta, vbmeta_size);
|
||||
}
|
||||
|
||||
// Pad image to original size if not chromeos (as it requires post processing)
|
||||
if (!boot.flags[CHROMEOS_FLAG]) {
|
||||
off_t current = lseek(fd, 0, SEEK_CUR);
|
||||
if (current < boot.map.sz) {
|
||||
write_zero(fd, boot.map.sz - current);
|
||||
}
|
||||
}
|
||||
|
||||
close(fd);
|
||||
|
||||
/******************
|
||||
* Patch the image
|
||||
******************/
|
||||
|
||||
// Map output image as rw
|
||||
auto out = mmap_data(out_img, true);
|
||||
|
||||
// MTK headers
|
||||
if (boot.flags[MTK_KERNEL]) {
|
||||
auto m_hdr = reinterpret_cast<mtk_hdr *>(out.buf + off.kernel);
|
||||
m_hdr->size = hdr->kernel_size();
|
||||
hdr->kernel_size() += sizeof(mtk_hdr);
|
||||
}
|
||||
if (boot.flags[MTK_RAMDISK]) {
|
||||
auto m_hdr = reinterpret_cast<mtk_hdr *>(out.buf + off.ramdisk);
|
||||
m_hdr->size = hdr->ramdisk_size();
|
||||
hdr->ramdisk_size() += sizeof(mtk_hdr);
|
||||
}
|
||||
|
||||
// Make sure header size matches
|
||||
hdr->header_size() = hdr->hdr_size();
|
||||
|
||||
// Update checksum
|
||||
if (char *id = hdr->id()) {
|
||||
HASH_CTX ctx;
|
||||
boot.flags[SHA256_FLAG] ? SHA256_init(&ctx) : SHA_init(&ctx);
|
||||
uint32_t size = hdr->kernel_size();
|
||||
HASH_update(&ctx, out.buf + off.kernel, size);
|
||||
HASH_update(&ctx, &size, sizeof(size));
|
||||
size = hdr->ramdisk_size();
|
||||
HASH_update(&ctx, out.buf + off.ramdisk, size);
|
||||
HASH_update(&ctx, &size, sizeof(size));
|
||||
size = hdr->second_size();
|
||||
HASH_update(&ctx, out.buf + off.second, size);
|
||||
HASH_update(&ctx, &size, sizeof(size));
|
||||
size = hdr->extra_size();
|
||||
if (size) {
|
||||
HASH_update(&ctx, out.buf + off.extra, size);
|
||||
HASH_update(&ctx, &size, sizeof(size));
|
||||
}
|
||||
uint32_t ver = hdr->header_version();
|
||||
if (ver == 1 || ver == 2) {
|
||||
size = hdr->recovery_dtbo_size();
|
||||
HASH_update(&ctx, out.buf + hdr->recovery_dtbo_offset(), size);
|
||||
HASH_update(&ctx, &size, sizeof(size));
|
||||
}
|
||||
if (ver == 2) {
|
||||
size = hdr->dtb_size();
|
||||
HASH_update(&ctx, out.buf + off.dtb, size);
|
||||
HASH_update(&ctx, &size, sizeof(size));
|
||||
}
|
||||
memset(id, 0, BOOT_ID_SIZE);
|
||||
memcpy(id, HASH_final(&ctx), boot.flags[SHA256_FLAG] ? SHA256_DIGEST_SIZE : SHA_DIGEST_SIZE);
|
||||
}
|
||||
|
||||
// Print new header info
|
||||
hdr->print();
|
||||
|
||||
// Copy main header
|
||||
memcpy(out.buf + off.header, hdr->raw_hdr(), hdr->hdr_size());
|
||||
|
||||
if (boot.flags[AVB_FLAG]) {
|
||||
// Copy and patch AVB structures
|
||||
auto footer = reinterpret_cast<AvbFooter*>(out.buf + out.sz - sizeof(AvbFooter));
|
||||
auto vbmeta = reinterpret_cast<AvbVBMetaImageHeader*>(out.buf + off.vbmeta);
|
||||
memcpy(footer, boot.avb_footer, sizeof(AvbFooter));
|
||||
footer->original_image_size = __builtin_bswap64(off.total);
|
||||
footer->vbmeta_offset = __builtin_bswap64(off.vbmeta);
|
||||
if (check_env("PATCHVBMETAFLAG")) {
|
||||
vbmeta->flags = __builtin_bswap32(3);
|
||||
}
|
||||
}
|
||||
|
||||
if (boot.flags[DHTB_FLAG]) {
|
||||
// DHTB header
|
||||
auto d_hdr = reinterpret_cast<dhtb_hdr *>(out.buf);
|
||||
memcpy(d_hdr, DHTB_MAGIC, 8);
|
||||
d_hdr->size = off.total - sizeof(dhtb_hdr);
|
||||
SHA256_hash(out.buf + sizeof(dhtb_hdr), d_hdr->size, d_hdr->checksum);
|
||||
} else if (boot.flags[BLOB_FLAG]) {
|
||||
// Blob header
|
||||
auto b_hdr = reinterpret_cast<blob_hdr *>(out.buf);
|
||||
b_hdr->size = off.total - sizeof(blob_hdr);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,631 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <utility>
|
||||
#include <bitset>
|
||||
#include "format.hpp"
|
||||
|
||||
/******************
|
||||
* Special Headers
|
||||
*****************/
|
||||
|
||||
struct mtk_hdr {
|
||||
uint32_t magic; /* MTK magic */
|
||||
uint32_t size; /* Size of the content */
|
||||
char name[32]; /* The type of the header */
|
||||
|
||||
char padding[472]; /* Padding to 512 bytes */
|
||||
} __attribute__((packed));
|
||||
|
||||
struct dhtb_hdr {
|
||||
char magic[8]; /* DHTB magic */
|
||||
uint8_t checksum[40]; /* Payload SHA256, whole image + SEANDROIDENFORCE + 0xFFFFFFFF */
|
||||
uint32_t size; /* Payload size, whole image + SEANDROIDENFORCE + 0xFFFFFFFF */
|
||||
|
||||
char padding[460]; /* Padding to 512 bytes */
|
||||
} __attribute__((packed));
|
||||
|
||||
struct blob_hdr {
|
||||
char secure_magic[20]; /* "-SIGNED-BY-SIGNBLOB-" */
|
||||
uint32_t datalen; /* 0x00000000 */
|
||||
uint32_t signature; /* 0x00000000 */
|
||||
char magic[16]; /* "MSM-RADIO-UPDATE" */
|
||||
uint32_t hdr_version; /* 0x00010000 */
|
||||
uint32_t hdr_size; /* Size of header */
|
||||
uint32_t part_offset; /* Same as size */
|
||||
uint32_t num_parts; /* Number of partitions */
|
||||
uint32_t unknown[7]; /* All 0x00000000 */
|
||||
char name[4]; /* Name of partition */
|
||||
uint32_t offset; /* offset in blob where this partition starts */
|
||||
uint32_t size; /* Size of data */
|
||||
uint32_t version; /* 0x00000001 */
|
||||
} __attribute__((packed));
|
||||
|
||||
struct zimage_hdr {
|
||||
uint32_t code[9];
|
||||
uint32_t magic; /* zImage magic */
|
||||
uint32_t start; /* absolute load/run zImage address */
|
||||
uint32_t end; /* zImage end address */
|
||||
uint32_t endian; /* endianess flag */
|
||||
// There could be more fields, but we don't care
|
||||
} __attribute__((packed));
|
||||
|
||||
/**************
|
||||
* AVB Headers
|
||||
**************/
|
||||
|
||||
#define AVB_FOOTER_MAGIC_LEN 4
|
||||
#define AVB_MAGIC_LEN 4
|
||||
#define AVB_RELEASE_STRING_SIZE 48
|
||||
|
||||
// https://android.googlesource.com/platform/external/avb/+/refs/heads/android11-release/libavb/avb_footer.h
|
||||
struct AvbFooter {
|
||||
uint8_t magic[AVB_FOOTER_MAGIC_LEN];
|
||||
uint32_t version_major;
|
||||
uint32_t version_minor;
|
||||
uint64_t original_image_size;
|
||||
uint64_t vbmeta_offset;
|
||||
uint64_t vbmeta_size;
|
||||
uint8_t reserved[28];
|
||||
} __attribute__((packed));
|
||||
|
||||
// https://android.googlesource.com/platform/external/avb/+/refs/heads/android11-release/libavb/avb_vbmeta_image.h
|
||||
struct AvbVBMetaImageHeader {
|
||||
uint8_t magic[AVB_MAGIC_LEN];
|
||||
uint32_t required_libavb_version_major;
|
||||
uint32_t required_libavb_version_minor;
|
||||
uint64_t authentication_data_block_size;
|
||||
uint64_t auxiliary_data_block_size;
|
||||
uint32_t algorithm_type;
|
||||
uint64_t hash_offset;
|
||||
uint64_t hash_size;
|
||||
uint64_t signature_offset;
|
||||
uint64_t signature_size;
|
||||
uint64_t public_key_offset;
|
||||
uint64_t public_key_size;
|
||||
uint64_t public_key_metadata_offset;
|
||||
uint64_t public_key_metadata_size;
|
||||
uint64_t descriptors_offset;
|
||||
uint64_t descriptors_size;
|
||||
uint64_t rollback_index;
|
||||
uint32_t flags;
|
||||
uint32_t rollback_index_location;
|
||||
uint8_t release_string[AVB_RELEASE_STRING_SIZE];
|
||||
uint8_t reserved[80];
|
||||
} __attribute__((packed));
|
||||
|
||||
/*********************
|
||||
* Boot Image Headers
|
||||
*********************/
|
||||
|
||||
// https://android.googlesource.com/platform/system/tools/mkbootimg/+/refs/heads/android12-release/include/bootimg/bootimg.h
|
||||
|
||||
#define BOOT_MAGIC_SIZE 8
|
||||
#define BOOT_NAME_SIZE 16
|
||||
#define BOOT_ID_SIZE 32
|
||||
#define BOOT_ARGS_SIZE 512
|
||||
#define BOOT_EXTRA_ARGS_SIZE 1024
|
||||
#define VENDOR_BOOT_ARGS_SIZE 2048
|
||||
#define VENDOR_RAMDISK_NAME_SIZE 32
|
||||
#define VENDOR_RAMDISK_TABLE_ENTRY_BOARD_ID_SIZE 16
|
||||
|
||||
/* When the boot image header has a version of 0 - 2, the structure of the boot
|
||||
* image is as follows:
|
||||
*
|
||||
* +-----------------+
|
||||
* | boot header | 1 page
|
||||
* +-----------------+
|
||||
* | kernel | n pages
|
||||
* +-----------------+
|
||||
* | ramdisk | m pages
|
||||
* +-----------------+
|
||||
* | second stage | o pages
|
||||
* +-----------------+
|
||||
* | extra blob | x pages (non standard)
|
||||
* +-----------------+
|
||||
* | recovery dtbo | p pages
|
||||
* +-----------------+
|
||||
* | dtb | q pages
|
||||
* +-----------------+
|
||||
*
|
||||
* n = (kernel_size + page_size - 1) / page_size
|
||||
* m = (ramdisk_size + page_size - 1) / page_size
|
||||
* o = (second_size + page_size - 1) / page_size
|
||||
* p = (recovery_dtbo_size + page_size - 1) / page_size
|
||||
* q = (dtb_size + page_size - 1) / page_size
|
||||
* x = (extra_size + page_size - 1) / page_size
|
||||
*/
|
||||
|
||||
struct boot_img_hdr_v0_common {
|
||||
char magic[BOOT_MAGIC_SIZE];
|
||||
|
||||
uint32_t kernel_size; /* size in bytes */
|
||||
uint32_t kernel_addr; /* physical load addr */
|
||||
|
||||
uint32_t ramdisk_size; /* size in bytes */
|
||||
uint32_t ramdisk_addr; /* physical load addr */
|
||||
|
||||
uint32_t second_size; /* size in bytes */
|
||||
uint32_t second_addr; /* physical load addr */
|
||||
} __attribute__((packed));
|
||||
|
||||
struct boot_img_hdr_v0 : public boot_img_hdr_v0_common {
|
||||
uint32_t tags_addr; /* physical addr for kernel tags */
|
||||
|
||||
// In AOSP headers, this field is used for page size.
|
||||
// For Samsung PXA headers, the use of this field is unknown;
|
||||
// however, its value is something unrealistic to be treated as page size.
|
||||
// We use this fact to determine whether this is an AOSP or PXA header.
|
||||
union {
|
||||
uint32_t unknown;
|
||||
uint32_t page_size; /* flash page size we assume */
|
||||
};
|
||||
|
||||
// In header v1, this field is used for header version
|
||||
// However, on some devices like Samsung, this field is used to store DTB
|
||||
// We treat this field differently based on its value
|
||||
union {
|
||||
uint32_t header_version; /* the version of the header */
|
||||
uint32_t extra_size; /* extra blob size in bytes */
|
||||
};
|
||||
|
||||
// Operating system version and security patch level.
|
||||
// For version "A.B.C" and patch level "Y-M-D":
|
||||
// (7 bits for each of A, B, C; 7 bits for (Y-2000), 4 bits for M)
|
||||
// os_version = A[31:25] B[24:18] C[17:11] (Y-2000)[10:4] M[3:0]
|
||||
uint32_t os_version;
|
||||
|
||||
char name[BOOT_NAME_SIZE]; /* asciiz product name */
|
||||
char cmdline[BOOT_ARGS_SIZE];
|
||||
char id[BOOT_ID_SIZE]; /* timestamp / checksum / sha1 / etc */
|
||||
|
||||
// Supplemental command line data; kept here to maintain
|
||||
// binary compatibility with older versions of mkbootimg.
|
||||
char extra_cmdline[BOOT_EXTRA_ARGS_SIZE];
|
||||
} __attribute__((packed));
|
||||
|
||||
struct boot_img_hdr_v1 : public boot_img_hdr_v0 {
|
||||
uint32_t recovery_dtbo_size; /* size in bytes for recovery DTBO/ACPIO image */
|
||||
uint64_t recovery_dtbo_offset; /* offset to recovery dtbo/acpio in boot image */
|
||||
uint32_t header_size;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct boot_img_hdr_v2 : public boot_img_hdr_v1 {
|
||||
uint32_t dtb_size; /* size in bytes for DTB image */
|
||||
uint64_t dtb_addr; /* physical load address for DTB image */
|
||||
} __attribute__((packed));
|
||||
|
||||
// Special Samsung header
|
||||
struct boot_img_hdr_pxa : public boot_img_hdr_v0_common {
|
||||
uint32_t extra_size; /* extra blob size in bytes */
|
||||
uint32_t unknown;
|
||||
uint32_t tags_addr; /* physical addr for kernel tags */
|
||||
uint32_t page_size; /* flash page size we assume */
|
||||
|
||||
char name[24]; /* asciiz product name */
|
||||
char cmdline[BOOT_ARGS_SIZE];
|
||||
char id[BOOT_ID_SIZE]; /* timestamp / checksum / sha1 / etc */
|
||||
|
||||
char extra_cmdline[BOOT_EXTRA_ARGS_SIZE];
|
||||
} __attribute__((packed));
|
||||
|
||||
/* When the boot image header has a version of 3 - 4, the structure of the boot
|
||||
* image is as follows:
|
||||
*
|
||||
* +---------------------+
|
||||
* | boot header | 4096 bytes
|
||||
* +---------------------+
|
||||
* | kernel | m pages
|
||||
* +---------------------+
|
||||
* | ramdisk | n pages
|
||||
* +---------------------+
|
||||
* | boot signature | g pages
|
||||
* +---------------------+
|
||||
*
|
||||
* m = (kernel_size + 4096 - 1) / 4096
|
||||
* n = (ramdisk_size + 4096 - 1) / 4096
|
||||
* g = (signature_size + 4096 - 1) / 4096
|
||||
*
|
||||
* Page size is fixed at 4096 bytes.
|
||||
*
|
||||
* The structure of the vendor boot image is as follows:
|
||||
*
|
||||
* +------------------------+
|
||||
* | vendor boot header | o pages
|
||||
* +------------------------+
|
||||
* | vendor ramdisk section | p pages
|
||||
* +------------------------+
|
||||
* | dtb | q pages
|
||||
* +------------------------+
|
||||
* | vendor ramdisk table | r pages
|
||||
* +------------------------+
|
||||
* | bootconfig | s pages
|
||||
* +------------------------+
|
||||
*
|
||||
* o = (2128 + page_size - 1) / page_size
|
||||
* p = (vendor_ramdisk_size + page_size - 1) / page_size
|
||||
* q = (dtb_size + page_size - 1) / page_size
|
||||
* r = (vendor_ramdisk_table_size + page_size - 1) / page_size
|
||||
* s = (vendor_bootconfig_size + page_size - 1) / page_size
|
||||
*
|
||||
* Note that in version 4 of the vendor boot image, multiple vendor ramdisks can
|
||||
* be included in the vendor boot image. The bootloader can select a subset of
|
||||
* ramdisks to load at runtime. To help the bootloader select the ramdisks, each
|
||||
* ramdisk is tagged with a type tag and a set of hardware identifiers
|
||||
* describing the board, soc or platform that this ramdisk is intended for.
|
||||
*
|
||||
* The vendor ramdisk section is consist of multiple ramdisk images concatenated
|
||||
* one after another, and vendor_ramdisk_size is the size of the section, which
|
||||
* is the total size of all the ramdisks included in the vendor boot image.
|
||||
*
|
||||
* The vendor ramdisk table holds the size, offset, type, name and hardware
|
||||
* identifiers of each ramdisk. The type field denotes the type of its content.
|
||||
* The vendor ramdisk names are unique. The hardware identifiers are specified
|
||||
* in the board_id field in each table entry. The board_id field is consist of a
|
||||
* vector of unsigned integer words, and the encoding scheme is defined by the
|
||||
* hardware vendor.
|
||||
*
|
||||
* For the different type of ramdisks, there are:
|
||||
* - VENDOR_RAMDISK_TYPE_NONE indicates the value is unspecified.
|
||||
* - VENDOR_RAMDISK_TYPE_PLATFORM ramdisks contain platform specific bits, so
|
||||
* the bootloader should always load these into memory.
|
||||
* - VENDOR_RAMDISK_TYPE_RECOVERY ramdisks contain recovery resources, so
|
||||
* the bootloader should load these when booting into recovery.
|
||||
* - VENDOR_RAMDISK_TYPE_DLKM ramdisks contain dynamic loadable kernel
|
||||
* modules.
|
||||
*
|
||||
* Version 4 of the vendor boot image also adds a bootconfig section to the end
|
||||
* of the image. This section contains Boot Configuration parameters known at
|
||||
* build time. The bootloader is responsible for placing this section directly
|
||||
* after the generic ramdisk, followed by the bootconfig trailer, before
|
||||
* entering the kernel.
|
||||
*/
|
||||
|
||||
struct boot_img_hdr_v3 {
|
||||
uint8_t magic[BOOT_MAGIC_SIZE];
|
||||
|
||||
uint32_t kernel_size; /* size in bytes */
|
||||
uint32_t ramdisk_size; /* size in bytes */
|
||||
uint32_t os_version;
|
||||
uint32_t header_size;
|
||||
uint32_t reserved[4];
|
||||
|
||||
uint32_t header_version;
|
||||
|
||||
char cmdline[BOOT_ARGS_SIZE + BOOT_EXTRA_ARGS_SIZE];
|
||||
} __attribute__((packed));
|
||||
|
||||
struct boot_img_hdr_vnd_v3 {
|
||||
// Must be VENDOR_BOOT_MAGIC.
|
||||
uint8_t magic[BOOT_MAGIC_SIZE];
|
||||
// Version of the vendor boot image header.
|
||||
uint32_t header_version;
|
||||
uint32_t page_size; /* flash page size we assume */
|
||||
uint32_t kernel_addr; /* physical load addr */
|
||||
uint32_t ramdisk_addr; /* physical load addr */
|
||||
uint32_t ramdisk_size; /* size in bytes */
|
||||
char cmdline[VENDOR_BOOT_ARGS_SIZE];
|
||||
uint32_t tags_addr; /* physical addr for kernel tags (if required) */
|
||||
char name[BOOT_NAME_SIZE]; /* asciiz product name */
|
||||
uint32_t header_size;
|
||||
uint32_t dtb_size; /* size in bytes for DTB image */
|
||||
uint64_t dtb_addr; /* physical load address for DTB image */
|
||||
} __attribute__((packed));
|
||||
|
||||
struct boot_img_hdr_v4 : public boot_img_hdr_v3 {
|
||||
uint32_t signature_size; /* size in bytes */
|
||||
} __attribute__((packed));
|
||||
|
||||
struct boot_img_hdr_vnd_v4 : public boot_img_hdr_vnd_v3 {
|
||||
uint32_t vendor_ramdisk_table_size; /* size in bytes for the vendor ramdisk table */
|
||||
uint32_t vendor_ramdisk_table_entry_num; /* number of entries in the vendor ramdisk table */
|
||||
uint32_t vendor_ramdisk_table_entry_size; /* size in bytes for a vendor ramdisk table entry */
|
||||
uint32_t bootconfig_size; /* size in bytes for the bootconfig section */
|
||||
} __attribute__((packed));
|
||||
|
||||
struct vendor_ramdisk_table_entry_v4 {
|
||||
uint32_t ramdisk_size; /* size in bytes for the ramdisk image */
|
||||
uint32_t ramdisk_offset; /* offset to the ramdisk image in vendor ramdisk section */
|
||||
uint32_t ramdisk_type; /* type of the ramdisk */
|
||||
uint8_t ramdisk_name[VENDOR_RAMDISK_NAME_SIZE]; /* asciiz ramdisk name */
|
||||
|
||||
// Hardware identifiers describing the board, soc or platform which this
|
||||
// ramdisk is intended to be loaded on.
|
||||
uint32_t board_id[VENDOR_RAMDISK_TABLE_ENTRY_BOARD_ID_SIZE];
|
||||
} __attribute__((packed));
|
||||
|
||||
/*******************************
|
||||
* Polymorphic Universal Header
|
||||
*******************************/
|
||||
|
||||
#define decl_var(name, len) \
|
||||
virtual uint##len##_t &name() { j##len = 0; return j##len; }
|
||||
#define decl_val(name, type) \
|
||||
virtual type name() { return 0; }
|
||||
|
||||
struct dyn_img_hdr {
|
||||
|
||||
const bool is_vendor;
|
||||
|
||||
// Standard entries
|
||||
decl_var(kernel_size, 32)
|
||||
decl_var(ramdisk_size, 32)
|
||||
decl_var(second_size, 32)
|
||||
decl_val(page_size, uint32_t)
|
||||
decl_val(header_version, uint32_t)
|
||||
decl_var(extra_size, 32)
|
||||
decl_var(os_version, 32)
|
||||
decl_val(name, char *)
|
||||
decl_val(cmdline, char *)
|
||||
decl_val(id, char *)
|
||||
decl_val(extra_cmdline, char *)
|
||||
uint32_t kernel_dt_size = 0;
|
||||
|
||||
// v1/v2 specific
|
||||
decl_var(recovery_dtbo_size, 32)
|
||||
decl_var(recovery_dtbo_offset, 64)
|
||||
decl_var(header_size, 32)
|
||||
decl_var(dtb_size, 32)
|
||||
|
||||
// v4 specific
|
||||
decl_val(signature_size, uint32_t)
|
||||
decl_val(vendor_ramdisk_table_size, uint32_t)
|
||||
decl_val(bootconfig_size, uint32_t)
|
||||
|
||||
virtual ~dyn_img_hdr() {
|
||||
free(raw);
|
||||
}
|
||||
|
||||
virtual size_t hdr_size() = 0;
|
||||
virtual size_t hdr_space() { return page_size(); }
|
||||
virtual dyn_img_hdr *clone() = 0;
|
||||
|
||||
const void *raw_hdr() const { return raw; }
|
||||
void print();
|
||||
void dump_hdr_file();
|
||||
void load_hdr_file();
|
||||
|
||||
protected:
|
||||
union {
|
||||
boot_img_hdr_v2 *v2_hdr; /* AOSP v2 header */
|
||||
boot_img_hdr_v4 *v4_hdr; /* AOSP v4 header */
|
||||
boot_img_hdr_vnd_v4 *v4_vnd; /* AOSP vendor v4 header */
|
||||
boot_img_hdr_pxa *hdr_pxa; /* Samsung PXA header */
|
||||
void *raw; /* Raw pointer */
|
||||
};
|
||||
dyn_img_hdr(bool b) : is_vendor(b) {}
|
||||
|
||||
private:
|
||||
// Junk for references
|
||||
static uint32_t j32;
|
||||
static uint64_t j64;
|
||||
};
|
||||
|
||||
#undef decl_var
|
||||
#undef decl_val
|
||||
|
||||
#define __impl_cls(name, hdr) \
|
||||
protected: name() = default; \
|
||||
public: \
|
||||
name(void *ptr) { \
|
||||
raw = xmalloc(sizeof(hdr)); \
|
||||
memcpy(raw, ptr, sizeof(hdr)); \
|
||||
} \
|
||||
size_t hdr_size() override { \
|
||||
return sizeof(hdr); \
|
||||
} \
|
||||
dyn_img_hdr *clone() override { \
|
||||
auto p = new name(raw); \
|
||||
p->kernel_dt_size = kernel_dt_size; \
|
||||
return p; \
|
||||
};
|
||||
|
||||
#define __impl_val(name, hdr_name) \
|
||||
decltype(std::declval<dyn_img_hdr>().name()) name() override { return hdr_name->name; }
|
||||
|
||||
struct dyn_img_hdr_boot : public dyn_img_hdr {
|
||||
protected:
|
||||
dyn_img_hdr_boot() : dyn_img_hdr(false) {}
|
||||
};
|
||||
|
||||
#define impl_cls(ver) __impl_cls(dyn_img_##ver, boot_img_hdr_##ver)
|
||||
#define impl_val(name) __impl_val(name, v2_hdr)
|
||||
|
||||
struct dyn_img_common : public dyn_img_hdr_boot {
|
||||
impl_val(kernel_size)
|
||||
impl_val(ramdisk_size)
|
||||
impl_val(second_size)
|
||||
};
|
||||
|
||||
struct dyn_img_v0 : public dyn_img_common {
|
||||
impl_cls(v0)
|
||||
|
||||
impl_val(page_size)
|
||||
impl_val(extra_size)
|
||||
impl_val(os_version)
|
||||
impl_val(name)
|
||||
impl_val(cmdline)
|
||||
impl_val(id)
|
||||
impl_val(extra_cmdline)
|
||||
};
|
||||
|
||||
struct dyn_img_v1 : public dyn_img_v0 {
|
||||
impl_cls(v1)
|
||||
|
||||
impl_val(header_version)
|
||||
impl_val(recovery_dtbo_size)
|
||||
impl_val(recovery_dtbo_offset)
|
||||
impl_val(header_size)
|
||||
|
||||
uint32_t &extra_size() override { return dyn_img_hdr::extra_size(); }
|
||||
};
|
||||
|
||||
struct dyn_img_v2 : public dyn_img_v1 {
|
||||
impl_cls(v2)
|
||||
|
||||
impl_val(dtb_size)
|
||||
};
|
||||
|
||||
#undef impl_val
|
||||
#define impl_val(name) __impl_val(name, hdr_pxa)
|
||||
|
||||
struct dyn_img_pxa : public dyn_img_common {
|
||||
impl_cls(pxa)
|
||||
|
||||
impl_val(extra_size)
|
||||
impl_val(page_size)
|
||||
impl_val(name)
|
||||
impl_val(cmdline)
|
||||
impl_val(id)
|
||||
impl_val(extra_cmdline)
|
||||
};
|
||||
|
||||
#undef impl_val
|
||||
#define impl_val(name) __impl_val(name, v4_hdr)
|
||||
|
||||
struct dyn_img_v3 : public dyn_img_hdr_boot {
|
||||
impl_cls(v3)
|
||||
|
||||
impl_val(kernel_size)
|
||||
impl_val(ramdisk_size)
|
||||
impl_val(os_version)
|
||||
impl_val(header_size)
|
||||
impl_val(header_version)
|
||||
impl_val(cmdline)
|
||||
|
||||
// Make API compatible
|
||||
uint32_t page_size() override { return 4096; }
|
||||
char *extra_cmdline() override { return &v4_hdr->cmdline[BOOT_ARGS_SIZE]; }
|
||||
};
|
||||
|
||||
struct dyn_img_v4 : public dyn_img_v3 {
|
||||
impl_cls(v4)
|
||||
|
||||
impl_val(signature_size)
|
||||
};
|
||||
|
||||
struct dyn_img_hdr_vendor : public dyn_img_hdr {
|
||||
protected:
|
||||
dyn_img_hdr_vendor() : dyn_img_hdr(true) {}
|
||||
};
|
||||
|
||||
#undef impl_val
|
||||
#define impl_val(name) __impl_val(name, v4_vnd)
|
||||
|
||||
struct dyn_img_vnd_v3 : public dyn_img_hdr_vendor {
|
||||
impl_cls(vnd_v3)
|
||||
|
||||
impl_val(header_version)
|
||||
impl_val(page_size)
|
||||
impl_val(ramdisk_size)
|
||||
impl_val(cmdline)
|
||||
impl_val(name)
|
||||
impl_val(header_size)
|
||||
impl_val(dtb_size)
|
||||
|
||||
size_t hdr_space() override { return align_to(hdr_size(), page_size()); }
|
||||
|
||||
// Make API compatible
|
||||
char *extra_cmdline() override { return &v4_vnd->cmdline[BOOT_ARGS_SIZE]; }
|
||||
};
|
||||
|
||||
struct dyn_img_vnd_v4 : public dyn_img_vnd_v3 {
|
||||
impl_cls(vnd_v4)
|
||||
|
||||
impl_val(vendor_ramdisk_table_size)
|
||||
impl_val(bootconfig_size)
|
||||
};
|
||||
|
||||
#undef __impl_cls
|
||||
#undef __impl_val
|
||||
#undef impl_cls
|
||||
#undef impl_val
|
||||
|
||||
/******************
|
||||
* Full Boot Image
|
||||
******************/
|
||||
|
||||
enum {
|
||||
MTK_KERNEL,
|
||||
MTK_RAMDISK,
|
||||
CHROMEOS_FLAG,
|
||||
DHTB_FLAG,
|
||||
SEANDROID_FLAG,
|
||||
LG_BUMP_FLAG,
|
||||
SHA256_FLAG,
|
||||
BLOB_FLAG,
|
||||
NOOKHD_FLAG,
|
||||
ACCLAIM_FLAG,
|
||||
AVB_FLAG,
|
||||
ZIMAGE_KERNEL,
|
||||
BOOT_FLAGS_MAX
|
||||
};
|
||||
|
||||
struct boot_img {
|
||||
// Memory map of the whole image
|
||||
mmap_data map;
|
||||
|
||||
// Android image header
|
||||
dyn_img_hdr *hdr;
|
||||
|
||||
// Flags to indicate the state of current boot image
|
||||
std::bitset<BOOT_FLAGS_MAX> flags;
|
||||
|
||||
// The format of kernel, ramdisk and extra
|
||||
format_t k_fmt = UNKNOWN;
|
||||
format_t r_fmt = UNKNOWN;
|
||||
format_t e_fmt = UNKNOWN;
|
||||
|
||||
/***************************************************
|
||||
* Following pointers points within the mmap region
|
||||
***************************************************/
|
||||
|
||||
// MTK headers
|
||||
mtk_hdr *k_hdr;
|
||||
mtk_hdr *r_hdr;
|
||||
|
||||
// The pointers/values after parse_image
|
||||
// +---------------+
|
||||
// | z_hdr | z_info.hdr_sz
|
||||
// +---------------+
|
||||
// | kernel | hdr->kernel_size()
|
||||
// +---------------+
|
||||
// | z_info.tail | z_info.tail_sz
|
||||
// +---------------+
|
||||
zimage_hdr *z_hdr;
|
||||
struct {
|
||||
uint32_t hdr_sz;
|
||||
uint32_t tail_sz = 0;
|
||||
uint8_t *tail = nullptr;
|
||||
} z_info;
|
||||
|
||||
// Pointer to dtb that is embedded in kernel
|
||||
uint8_t *kernel_dtb;
|
||||
|
||||
// Pointer to end of image
|
||||
uint8_t *tail;
|
||||
size_t tail_size = 0;
|
||||
|
||||
// AVB structs
|
||||
AvbFooter *avb_footer;
|
||||
AvbVBMetaImageHeader *vbmeta;
|
||||
|
||||
// Pointers to blocks defined in header
|
||||
uint8_t *hdr_addr;
|
||||
uint8_t *kernel;
|
||||
uint8_t *ramdisk;
|
||||
uint8_t *second;
|
||||
uint8_t *extra;
|
||||
uint8_t *recovery_dtbo;
|
||||
uint8_t *dtb;
|
||||
|
||||
// Pointer to blocks defined in header, but we do not care
|
||||
uint8_t *ignore;
|
||||
size_t ignore_size = 0;
|
||||
|
||||
boot_img(const char *);
|
||||
~boot_img();
|
||||
|
||||
void parse_image(uint8_t *addr, format_t type);
|
||||
dyn_img_hdr *create_hdr(uint8_t *addr, format_t type);
|
||||
};
|
||||
@@ -0,0 +1,675 @@
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
|
||||
#include <zlib.h>
|
||||
#include <bzlib.h>
|
||||
#include <lzma.h>
|
||||
#include <lz4.h>
|
||||
#include <lz4frame.h>
|
||||
#include <lz4hc.h>
|
||||
#include <zopfli/util.h>
|
||||
#include <zopfli/deflate.h>
|
||||
|
||||
#include <base.hpp>
|
||||
|
||||
#include "magiskboot.hpp"
|
||||
#include "compress.hpp"
|
||||
|
||||
using namespace std;
|
||||
|
||||
#define bwrite this->base->write
|
||||
|
||||
constexpr size_t CHUNK = 0x40000;
|
||||
constexpr size_t LZ4_UNCOMPRESSED = 0x800000;
|
||||
constexpr size_t LZ4_COMPRESSED = LZ4_COMPRESSBOUND(LZ4_UNCOMPRESSED);
|
||||
|
||||
class out_stream : public filter_stream {
|
||||
using filter_stream::filter_stream;
|
||||
using stream::read;
|
||||
};
|
||||
|
||||
class gz_strm : public out_stream {
|
||||
public:
|
||||
bool write(const void *buf, size_t len) override {
|
||||
return len == 0 || do_write(buf, len, Z_NO_FLUSH);
|
||||
}
|
||||
|
||||
~gz_strm() override {
|
||||
do_write(nullptr, 0, Z_FINISH);
|
||||
switch(mode) {
|
||||
case DECODE:
|
||||
inflateEnd(&strm);
|
||||
break;
|
||||
case ENCODE:
|
||||
deflateEnd(&strm);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
enum mode_t {
|
||||
DECODE,
|
||||
ENCODE
|
||||
} mode;
|
||||
|
||||
gz_strm(mode_t mode, stream_ptr &&base) :
|
||||
out_stream(std::move(base)), mode(mode), strm{}, outbuf{0} {
|
||||
switch(mode) {
|
||||
case DECODE:
|
||||
inflateInit2(&strm, 15 | 16);
|
||||
break;
|
||||
case ENCODE:
|
||||
deflateInit2(&strm, 9, Z_DEFLATED, 15 | 16, 8, Z_DEFAULT_STRATEGY);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
z_stream strm;
|
||||
uint8_t outbuf[CHUNK];
|
||||
|
||||
bool do_write(const void *buf, size_t len, int flush) {
|
||||
strm.next_in = (Bytef *) buf;
|
||||
strm.avail_in = len;
|
||||
do {
|
||||
int code;
|
||||
strm.next_out = outbuf;
|
||||
strm.avail_out = sizeof(outbuf);
|
||||
switch(mode) {
|
||||
case DECODE:
|
||||
code = inflate(&strm, flush);
|
||||
break;
|
||||
case ENCODE:
|
||||
code = deflate(&strm, flush);
|
||||
break;
|
||||
}
|
||||
if (code == Z_STREAM_ERROR) {
|
||||
LOGW("gzip %s failed (%d)\n", mode ? "encode" : "decode", code);
|
||||
return false;
|
||||
}
|
||||
if (!bwrite(outbuf, sizeof(outbuf) - strm.avail_out))
|
||||
return false;
|
||||
} while (strm.avail_out == 0);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
class gz_decoder : public gz_strm {
|
||||
public:
|
||||
explicit gz_decoder(stream_ptr &&base) : gz_strm(DECODE, std::move(base)) {};
|
||||
};
|
||||
|
||||
class gz_encoder : public gz_strm {
|
||||
public:
|
||||
explicit gz_encoder(stream_ptr &&base) : gz_strm(ENCODE, std::move(base)) {};
|
||||
};
|
||||
|
||||
class zopfli_encoder : public chunk_out_stream {
|
||||
public:
|
||||
explicit zopfli_encoder(stream_ptr &&base) :
|
||||
chunk_out_stream(std::move(base), ZOPFLI_MASTER_BLOCK_SIZE),
|
||||
zo{}, out(nullptr), outsize(0), crc(crc32_z(0L, Z_NULL, 0)), in_total(0), bp(0) {
|
||||
ZopfliInitOptions(&zo);
|
||||
|
||||
// This config is already better than gzip -9
|
||||
zo.numiterations = 1;
|
||||
zo.blocksplitting = 0;
|
||||
|
||||
ZOPFLI_APPEND_DATA(31, &out, &outsize); /* ID1 */
|
||||
ZOPFLI_APPEND_DATA(139, &out, &outsize); /* ID2 */
|
||||
ZOPFLI_APPEND_DATA(8, &out, &outsize); /* CM */
|
||||
ZOPFLI_APPEND_DATA(0, &out, &outsize); /* FLG */
|
||||
/* MTIME */
|
||||
ZOPFLI_APPEND_DATA(0, &out, &outsize);
|
||||
ZOPFLI_APPEND_DATA(0, &out, &outsize);
|
||||
ZOPFLI_APPEND_DATA(0, &out, &outsize);
|
||||
ZOPFLI_APPEND_DATA(0, &out, &outsize);
|
||||
|
||||
ZOPFLI_APPEND_DATA(2, &out, &outsize); /* XFL, 2 indicates best compression. */
|
||||
ZOPFLI_APPEND_DATA(3, &out, &outsize); /* OS follows Unix conventions. */
|
||||
}
|
||||
|
||||
~zopfli_encoder() override {
|
||||
finalize();
|
||||
|
||||
/* CRC */
|
||||
ZOPFLI_APPEND_DATA(crc % 256, &out, &outsize);
|
||||
ZOPFLI_APPEND_DATA((crc >> 8) % 256, &out, &outsize);
|
||||
ZOPFLI_APPEND_DATA((crc >> 16) % 256, &out, &outsize);
|
||||
ZOPFLI_APPEND_DATA((crc >> 24) % 256, &out, &outsize);
|
||||
|
||||
/* ISIZE */
|
||||
ZOPFLI_APPEND_DATA(in_total % 256, &out, &outsize);
|
||||
ZOPFLI_APPEND_DATA((in_total >> 8) % 256, &out, &outsize);
|
||||
ZOPFLI_APPEND_DATA((in_total >> 16) % 256, &out, &outsize);
|
||||
ZOPFLI_APPEND_DATA((in_total >> 24) % 256, &out, &outsize);
|
||||
|
||||
bwrite(out, outsize);
|
||||
free(out);
|
||||
}
|
||||
|
||||
protected:
|
||||
bool write_chunk(const void *buf, size_t len, bool final) override {
|
||||
if (len == 0)
|
||||
return true;
|
||||
|
||||
auto in = static_cast<const unsigned char *>(buf);
|
||||
|
||||
in_total += len;
|
||||
crc = crc32_z(crc, in, len);
|
||||
|
||||
ZopfliDeflatePart(&zo, 2, final, in, 0, len, &bp, &out, &outsize);
|
||||
|
||||
// ZOPFLI_APPEND_DATA is extremely dumb, so we always preserve the
|
||||
// last byte to make sure that realloc is used instead of malloc
|
||||
if (!bwrite(out, outsize - 1))
|
||||
return false;
|
||||
out[0] = out[outsize - 1];
|
||||
outsize = 1;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
ZopfliOptions zo;
|
||||
unsigned char *out;
|
||||
size_t outsize;
|
||||
unsigned long crc;
|
||||
uint32_t in_total;
|
||||
unsigned char bp;
|
||||
};
|
||||
|
||||
class bz_strm : public out_stream {
|
||||
public:
|
||||
bool write(const void *buf, size_t len) override {
|
||||
return len == 0 || do_write(buf, len, BZ_RUN);
|
||||
}
|
||||
|
||||
~bz_strm() override {
|
||||
switch(mode) {
|
||||
case DECODE:
|
||||
BZ2_bzDecompressEnd(&strm);
|
||||
break;
|
||||
case ENCODE:
|
||||
do_write(nullptr, 0, BZ_FINISH);
|
||||
BZ2_bzCompressEnd(&strm);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
enum mode_t {
|
||||
DECODE,
|
||||
ENCODE
|
||||
} mode;
|
||||
|
||||
bz_strm(mode_t mode, stream_ptr &&base) :
|
||||
out_stream(std::move(base)), mode(mode), strm{}, outbuf{0} {
|
||||
switch(mode) {
|
||||
case DECODE:
|
||||
BZ2_bzDecompressInit(&strm, 0, 0);
|
||||
break;
|
||||
case ENCODE:
|
||||
BZ2_bzCompressInit(&strm, 9, 0, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
bz_stream strm;
|
||||
char outbuf[CHUNK];
|
||||
|
||||
bool do_write(const void *buf, size_t len, int flush) {
|
||||
strm.next_in = (char *) buf;
|
||||
strm.avail_in = len;
|
||||
do {
|
||||
int code;
|
||||
strm.avail_out = sizeof(outbuf);
|
||||
strm.next_out = outbuf;
|
||||
switch(mode) {
|
||||
case DECODE:
|
||||
code = BZ2_bzDecompress(&strm);
|
||||
break;
|
||||
case ENCODE:
|
||||
code = BZ2_bzCompress(&strm, flush);
|
||||
break;
|
||||
}
|
||||
if (code < 0) {
|
||||
LOGW("bzip2 %s failed (%d)\n", mode ? "encode" : "decode", code);
|
||||
return false;
|
||||
}
|
||||
if (!bwrite(outbuf, sizeof(outbuf) - strm.avail_out))
|
||||
return false;
|
||||
} while (strm.avail_out == 0);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
class bz_decoder : public bz_strm {
|
||||
public:
|
||||
explicit bz_decoder(stream_ptr &&base) : bz_strm(DECODE, std::move(base)) {};
|
||||
};
|
||||
|
||||
class bz_encoder : public bz_strm {
|
||||
public:
|
||||
explicit bz_encoder(stream_ptr &&base) : bz_strm(ENCODE, std::move(base)) {};
|
||||
};
|
||||
|
||||
class lzma_strm : public out_stream {
|
||||
public:
|
||||
bool write(const void *buf, size_t len) override {
|
||||
return len == 0 || do_write(buf, len, LZMA_RUN);
|
||||
}
|
||||
|
||||
~lzma_strm() override {
|
||||
do_write(nullptr, 0, LZMA_FINISH);
|
||||
lzma_end(&strm);
|
||||
}
|
||||
|
||||
protected:
|
||||
enum mode_t {
|
||||
DECODE,
|
||||
ENCODE_XZ,
|
||||
ENCODE_LZMA
|
||||
} mode;
|
||||
|
||||
lzma_strm(mode_t mode, stream_ptr &&base) :
|
||||
out_stream(std::move(base)), mode(mode), strm(LZMA_STREAM_INIT), outbuf{0} {
|
||||
lzma_options_lzma opt;
|
||||
|
||||
// Initialize preset
|
||||
lzma_lzma_preset(&opt, 9);
|
||||
lzma_filter filters[] = {
|
||||
{ .id = LZMA_FILTER_LZMA2, .options = &opt },
|
||||
{ .id = LZMA_VLI_UNKNOWN, .options = nullptr },
|
||||
};
|
||||
|
||||
lzma_ret code;
|
||||
switch(mode) {
|
||||
case DECODE:
|
||||
code = lzma_auto_decoder(&strm, UINT64_MAX, 0);
|
||||
break;
|
||||
case ENCODE_XZ:
|
||||
code = lzma_stream_encoder(&strm, filters, LZMA_CHECK_CRC32);
|
||||
break;
|
||||
case ENCODE_LZMA:
|
||||
code = lzma_alone_encoder(&strm, &opt);
|
||||
break;
|
||||
}
|
||||
if (code != LZMA_OK) {
|
||||
LOGE("LZMA initialization failed (%d)\n", code);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
lzma_stream strm;
|
||||
uint8_t outbuf[CHUNK];
|
||||
|
||||
bool do_write(const void *buf, size_t len, lzma_action flush) {
|
||||
strm.next_in = (uint8_t *) buf;
|
||||
strm.avail_in = len;
|
||||
do {
|
||||
strm.avail_out = sizeof(outbuf);
|
||||
strm.next_out = outbuf;
|
||||
int code = lzma_code(&strm, flush);
|
||||
if (code != LZMA_OK && code != LZMA_STREAM_END) {
|
||||
LOGW("LZMA %s failed (%d)\n", mode ? "encode" : "decode", code);
|
||||
return false;
|
||||
}
|
||||
if (!bwrite(outbuf, sizeof(outbuf) - strm.avail_out))
|
||||
return false;
|
||||
} while (strm.avail_out == 0);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
class lzma_decoder : public lzma_strm {
|
||||
public:
|
||||
explicit lzma_decoder(stream_ptr &&base) : lzma_strm(DECODE, std::move(base)) {}
|
||||
};
|
||||
|
||||
class xz_encoder : public lzma_strm {
|
||||
public:
|
||||
explicit xz_encoder(stream_ptr &&base) : lzma_strm(ENCODE_XZ, std::move(base)) {}
|
||||
};
|
||||
|
||||
class lzma_encoder : public lzma_strm {
|
||||
public:
|
||||
explicit lzma_encoder(stream_ptr &&base) : lzma_strm(ENCODE_LZMA, std::move(base)) {}
|
||||
};
|
||||
|
||||
class LZ4F_decoder : public out_stream {
|
||||
public:
|
||||
explicit LZ4F_decoder(stream_ptr &&base) :
|
||||
out_stream(std::move(base)), ctx(nullptr), outbuf(nullptr), outCapacity(0) {
|
||||
LZ4F_createDecompressionContext(&ctx, LZ4F_VERSION);
|
||||
}
|
||||
|
||||
~LZ4F_decoder() override {
|
||||
LZ4F_freeDecompressionContext(ctx);
|
||||
delete[] outbuf;
|
||||
}
|
||||
|
||||
bool write(const void *buf, size_t len) override {
|
||||
auto in = reinterpret_cast<const uint8_t *>(buf);
|
||||
if (!outbuf) {
|
||||
size_t read = len;
|
||||
LZ4F_frameInfo_t info;
|
||||
LZ4F_getFrameInfo(ctx, &info, in, &read);
|
||||
switch (info.blockSizeID) {
|
||||
case LZ4F_default:
|
||||
case LZ4F_max64KB: outCapacity = 1 << 16; break;
|
||||
case LZ4F_max256KB: outCapacity = 1 << 18; break;
|
||||
case LZ4F_max1MB: outCapacity = 1 << 20; break;
|
||||
case LZ4F_max4MB: outCapacity = 1 << 22; break;
|
||||
}
|
||||
outbuf = new uint8_t[outCapacity];
|
||||
in += read;
|
||||
len -= read;
|
||||
}
|
||||
size_t read, write;
|
||||
LZ4F_errorCode_t code;
|
||||
do {
|
||||
read = len;
|
||||
write = outCapacity;
|
||||
code = LZ4F_decompress(ctx, outbuf, &write, in, &read, nullptr);
|
||||
if (LZ4F_isError(code)) {
|
||||
LOGW("LZ4F decode error: %s\n", LZ4F_getErrorName(code));
|
||||
return false;
|
||||
}
|
||||
len -= read;
|
||||
in += read;
|
||||
if (!bwrite(outbuf, write))
|
||||
return false;
|
||||
} while (len != 0 || write != 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
LZ4F_decompressionContext_t ctx;
|
||||
uint8_t *outbuf;
|
||||
size_t outCapacity;
|
||||
};
|
||||
|
||||
class LZ4F_encoder : public out_stream {
|
||||
public:
|
||||
explicit LZ4F_encoder(stream_ptr &&base) :
|
||||
out_stream(std::move(base)), ctx(nullptr), out_buf(nullptr), outCapacity(0) {
|
||||
LZ4F_createCompressionContext(&ctx, LZ4F_VERSION);
|
||||
}
|
||||
|
||||
bool write(const void *buf, size_t len) override {
|
||||
if (!out_buf) {
|
||||
LZ4F_preferences_t prefs {
|
||||
.frameInfo = {
|
||||
.blockSizeID = LZ4F_max4MB,
|
||||
.blockMode = LZ4F_blockIndependent,
|
||||
.contentChecksumFlag = LZ4F_contentChecksumEnabled,
|
||||
.blockChecksumFlag = LZ4F_noBlockChecksum,
|
||||
},
|
||||
.compressionLevel = 9,
|
||||
.autoFlush = 1,
|
||||
};
|
||||
outCapacity = LZ4F_compressBound(BLOCK_SZ, &prefs);
|
||||
out_buf = new uint8_t[outCapacity];
|
||||
size_t write = LZ4F_compressBegin(ctx, out_buf, outCapacity, &prefs);
|
||||
if (!bwrite(out_buf, write))
|
||||
return false;
|
||||
}
|
||||
if (len == 0)
|
||||
return true;
|
||||
|
||||
auto in = reinterpret_cast<const uint8_t *>(buf);
|
||||
size_t read, write;
|
||||
do {
|
||||
read = len > BLOCK_SZ ? BLOCK_SZ : len;
|
||||
write = LZ4F_compressUpdate(ctx, out_buf, outCapacity, in, read, nullptr);
|
||||
if (LZ4F_isError(write)) {
|
||||
LOGW("LZ4F encode error: %s\n", LZ4F_getErrorName(write));
|
||||
return false;
|
||||
}
|
||||
len -= read;
|
||||
in += read;
|
||||
if (!bwrite(out_buf, write))
|
||||
return false;
|
||||
} while (len != 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
~LZ4F_encoder() override {
|
||||
size_t len = LZ4F_compressEnd(ctx, out_buf, outCapacity, nullptr);
|
||||
if (LZ4F_isError(len)) {
|
||||
LOGE("LZ4F end of frame error: %s\n", LZ4F_getErrorName(len));
|
||||
} else if (!bwrite(out_buf, len)) {
|
||||
LOGE("LZ4F end of frame error: I/O error\n");
|
||||
}
|
||||
LZ4F_freeCompressionContext(ctx);
|
||||
delete[] out_buf;
|
||||
}
|
||||
|
||||
private:
|
||||
LZ4F_compressionContext_t ctx;
|
||||
uint8_t *out_buf;
|
||||
size_t outCapacity;
|
||||
|
||||
static constexpr size_t BLOCK_SZ = 1 << 22;
|
||||
};
|
||||
|
||||
class LZ4_decoder : public chunk_out_stream {
|
||||
public:
|
||||
explicit LZ4_decoder(stream_ptr &&base) :
|
||||
chunk_out_stream(std::move(base), LZ4_COMPRESSED, sizeof(block_sz)),
|
||||
out_buf(new char[LZ4_UNCOMPRESSED]), block_sz(0) {}
|
||||
|
||||
~LZ4_decoder() override {
|
||||
finalize();
|
||||
delete[] out_buf;
|
||||
}
|
||||
|
||||
protected:
|
||||
bool write_chunk(const void *buf, size_t len, bool final) override {
|
||||
// This is an error
|
||||
if (len != chunk_sz)
|
||||
return false;
|
||||
|
||||
auto in = reinterpret_cast<const char *>(buf);
|
||||
|
||||
if (block_sz == 0) {
|
||||
memcpy(&block_sz, in, sizeof(block_sz));
|
||||
if (block_sz == 0x184C2102) {
|
||||
// This is actually the lz4 magic, read the next 4 bytes
|
||||
block_sz = 0;
|
||||
chunk_sz = sizeof(block_sz);
|
||||
return true;
|
||||
}
|
||||
// Read the next block chunk
|
||||
chunk_sz = block_sz;
|
||||
return true;
|
||||
} else {
|
||||
int r = LZ4_decompress_safe(in, out_buf, block_sz, LZ4_UNCOMPRESSED);
|
||||
chunk_sz = sizeof(block_sz);
|
||||
block_sz = 0;
|
||||
if (r < 0) {
|
||||
LOGW("LZ4HC decompression failure (%d)\n", r);
|
||||
return false;
|
||||
}
|
||||
return bwrite(out_buf, r);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
char *out_buf;
|
||||
uint32_t block_sz;
|
||||
};
|
||||
|
||||
class LZ4_encoder : public chunk_out_stream {
|
||||
public:
|
||||
explicit LZ4_encoder(stream_ptr &&base, bool lg) :
|
||||
chunk_out_stream(std::move(base), LZ4_UNCOMPRESSED),
|
||||
out_buf(new char[LZ4_COMPRESSED]), lg(lg), in_total(0) {
|
||||
bwrite("\x02\x21\x4c\x18", 4);
|
||||
}
|
||||
|
||||
~LZ4_encoder() override {
|
||||
finalize();
|
||||
if (lg)
|
||||
bwrite(&in_total, sizeof(in_total));
|
||||
delete[] out_buf;
|
||||
}
|
||||
|
||||
protected:
|
||||
bool write_chunk(const void *buf, size_t len, bool final) override {
|
||||
auto in = static_cast<const char *>(buf);
|
||||
uint32_t block_sz = LZ4_compress_HC(in, out_buf, len, LZ4_COMPRESSED, LZ4HC_CLEVEL_MAX);
|
||||
if (block_sz == 0) {
|
||||
LOGW("LZ4HC compression failure\n");
|
||||
return false;
|
||||
}
|
||||
if (bwrite(&block_sz, sizeof(block_sz)) && bwrite(out_buf, block_sz)) {
|
||||
in_total += len;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
char *out_buf;
|
||||
bool lg;
|
||||
uint32_t in_total;
|
||||
};
|
||||
|
||||
filter_strm_ptr get_encoder(format_t type, stream_ptr &&base) {
|
||||
switch (type) {
|
||||
case XZ:
|
||||
return make_unique<xz_encoder>(std::move(base));
|
||||
case LZMA:
|
||||
return make_unique<lzma_encoder>(std::move(base));
|
||||
case BZIP2:
|
||||
return make_unique<bz_encoder>(std::move(base));
|
||||
case LZ4:
|
||||
return make_unique<LZ4F_encoder>(std::move(base));
|
||||
case LZ4_LEGACY:
|
||||
return make_unique<LZ4_encoder>(std::move(base), false);
|
||||
case LZ4_LG:
|
||||
return make_unique<LZ4_encoder>(std::move(base), true);
|
||||
case ZOPFLI:
|
||||
return make_unique<zopfli_encoder>(std::move(base));
|
||||
case GZIP:
|
||||
default:
|
||||
return make_unique<gz_encoder>(std::move(base));
|
||||
}
|
||||
}
|
||||
|
||||
filter_strm_ptr get_decoder(format_t type, stream_ptr &&base) {
|
||||
switch (type) {
|
||||
case XZ:
|
||||
case LZMA:
|
||||
return make_unique<lzma_decoder>(std::move(base));
|
||||
case BZIP2:
|
||||
return make_unique<bz_decoder>(std::move(base));
|
||||
case LZ4:
|
||||
return make_unique<LZ4F_decoder>(std::move(base));
|
||||
case LZ4_LEGACY:
|
||||
case LZ4_LG:
|
||||
return make_unique<LZ4_decoder>(std::move(base));
|
||||
case ZOPFLI:
|
||||
case GZIP:
|
||||
default:
|
||||
return make_unique<gz_decoder>(std::move(base));
|
||||
}
|
||||
}
|
||||
|
||||
void decompress(char *infile, const char *outfile) {
|
||||
bool in_std = infile == "-"sv;
|
||||
bool rm_in = false;
|
||||
|
||||
FILE *in_fp = in_std ? stdin : xfopen(infile, "re");
|
||||
stream_ptr strm;
|
||||
|
||||
char buf[4096];
|
||||
size_t len;
|
||||
while ((len = fread(buf, 1, sizeof(buf), in_fp))) {
|
||||
if (!strm) {
|
||||
format_t type = check_fmt(buf, len);
|
||||
|
||||
fprintf(stderr, "Detected format: [%s]\n", fmt2name[type]);
|
||||
|
||||
if (!COMPRESSED(type))
|
||||
LOGE("Input file is not a supported compressed type!\n");
|
||||
|
||||
/* If user does not provide outfile, infile has to be either
|
||||
* <path>.[ext], or '-'. Outfile will be either <path> or '-'.
|
||||
* If the input does not have proper format, abort */
|
||||
|
||||
char *ext = nullptr;
|
||||
if (outfile == nullptr) {
|
||||
outfile = infile;
|
||||
if (!in_std) {
|
||||
ext = strrchr(infile, '.');
|
||||
if (ext == nullptr || strcmp(ext, fmt2ext[type]) != 0)
|
||||
LOGE("Input file is not a supported type!\n");
|
||||
|
||||
// Strip out extension and remove input
|
||||
*ext = '\0';
|
||||
rm_in = true;
|
||||
fprintf(stderr, "Decompressing to [%s]\n", outfile);
|
||||
}
|
||||
}
|
||||
|
||||
FILE *out_fp = outfile == "-"sv ? stdout : xfopen(outfile, "we");
|
||||
strm = get_decoder(type, make_unique<fp_stream>(out_fp));
|
||||
if (ext) *ext = '.';
|
||||
}
|
||||
if (!strm->write(buf, len))
|
||||
LOGE("Decompression error!\n");
|
||||
}
|
||||
|
||||
strm.reset(nullptr);
|
||||
fclose(in_fp);
|
||||
|
||||
if (rm_in)
|
||||
unlink(infile);
|
||||
}
|
||||
|
||||
void compress(const char *method, const char *infile, const char *outfile) {
|
||||
format_t fmt = name2fmt[method];
|
||||
if (fmt == UNKNOWN)
|
||||
LOGE("Unknown compression method: [%s]\n", method);
|
||||
|
||||
bool in_std = infile == "-"sv;
|
||||
bool rm_in = false;
|
||||
|
||||
FILE *in_fp = in_std ? stdin : xfopen(infile, "re");
|
||||
FILE *out_fp;
|
||||
|
||||
if (outfile == nullptr) {
|
||||
if (in_std) {
|
||||
out_fp = stdout;
|
||||
} else {
|
||||
/* If user does not provide outfile and infile is not
|
||||
* STDIN, output to <infile>.[ext] */
|
||||
string tmp(infile);
|
||||
tmp += fmt2ext[fmt];
|
||||
out_fp = xfopen(tmp.data(), "we");
|
||||
fprintf(stderr, "Compressing to [%s]\n", tmp.data());
|
||||
rm_in = true;
|
||||
}
|
||||
} else {
|
||||
out_fp = outfile == "-"sv ? stdout : xfopen(outfile, "we");
|
||||
}
|
||||
|
||||
auto strm = get_encoder(fmt, make_unique<fp_stream>(out_fp));
|
||||
|
||||
char buf[4096];
|
||||
size_t len;
|
||||
while ((len = fread(buf, 1, sizeof(buf), in_fp))) {
|
||||
if (!strm->write(buf, len))
|
||||
LOGE("Compression error!\n");
|
||||
}
|
||||
|
||||
strm.reset(nullptr);
|
||||
fclose(in_fp);
|
||||
|
||||
if (rm_in)
|
||||
unlink(infile);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <stream.hpp>
|
||||
|
||||
#include "format.hpp"
|
||||
|
||||
filter_strm_ptr get_encoder(format_t type, stream_ptr &&base);
|
||||
|
||||
filter_strm_ptr get_decoder(format_t type, stream_ptr &&base);
|
||||
|
||||
void compress(const char *method, const char *infile, const char *outfile);
|
||||
|
||||
void decompress(char *infile, const char *outfile);
|
||||
@@ -0,0 +1,240 @@
|
||||
#include <unistd.h>
|
||||
#include <fcntl.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), filesize(0), data(nullptr) {}
|
||||
|
||||
cpio_entry::cpio_entry(const cpio_newc_header *h) :
|
||||
mode(x8u(h->mode)), uid(x8u(h->uid)), gid(x8u(h->gid)), filesize(x8u(h->filesize)), data(nullptr)
|
||||
{}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void cpio::extract_entry(const entry_map::value_type &e, const char *file) {
|
||||
fprintf(stderr, "Extract [%s] to [%s]\n", e.first.data(), file);
|
||||
unlink(file);
|
||||
rmdir(file);
|
||||
if (S_ISDIR(e.second->mode)) {
|
||||
::mkdir(file, e.second->mode & 0777);
|
||||
} else if (S_ISREG(e.second->mode)) {
|
||||
int fd = creat(file, e.second->mode & 0777);
|
||||
xwrite(fd, e.second->data, e.second->filesize);
|
||||
fchown(fd, e.second->uid, e.second->gid);
|
||||
close(fd);
|
||||
} else if (S_ISLNK(e.second->mode)) {
|
||||
auto target = strndup((char *) e.second->data, e.second->filesize);
|
||||
symlink(target, file);
|
||||
free(target);
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
e.second->filesize,
|
||||
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->filesize) {
|
||||
do_out(e.second->data, e.second->filesize);
|
||||
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);
|
||||
auto m = mmap_data(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) {
|
||||
auto m = mmap_data(file);
|
||||
auto e = new cpio_entry(S_IFREG | mode);
|
||||
e->filesize = m.sz;
|
||||
e->data = xmalloc(m.sz);
|
||||
memcpy(e->data, m.buf, m.sz);
|
||||
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);
|
||||
e->filesize = strlen(target);
|
||||
e->data = strdup(target);
|
||||
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);
|
||||
entry->data = xmalloc(entry->filesize);
|
||||
memcpy(entry->data, buf + pos, entry->filesize);
|
||||
pos += entry->filesize;
|
||||
insert(name, entry);
|
||||
pos_align(pos);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <map>
|
||||
#include <string_view>
|
||||
|
||||
struct cpio_newc_header;
|
||||
|
||||
struct cpio_entry {
|
||||
uint32_t mode;
|
||||
uint32_t uid;
|
||||
uint32_t gid;
|
||||
uint32_t filesize;
|
||||
void *data;
|
||||
|
||||
explicit cpio_entry(uint32_t mode = 0);
|
||||
explicit cpio_entry(const cpio_newc_header *h);
|
||||
~cpio_entry() { free(data); }
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
static void extract_entry(const entry_map::value_type &e, const char *file);
|
||||
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);
|
||||
};
|
||||
@@ -0,0 +1,452 @@
|
||||
#include <bitset>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <libfdt.h>
|
||||
|
||||
#include <base.hpp>
|
||||
|
||||
#include "magiskboot.hpp"
|
||||
#include "dtb.hpp"
|
||||
#include "format.hpp"
|
||||
|
||||
using namespace std;
|
||||
|
||||
constexpr int MAX_DEPTH = 32;
|
||||
static bitset<MAX_DEPTH> depth_set;
|
||||
|
||||
static void pretty_node(int depth) {
|
||||
if (depth == 0)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < depth - 1; ++i)
|
||||
printf(depth_set[i] ? "│ " : " ");
|
||||
|
||||
printf(depth_set[depth - 1] ? "├── " : "└── ");
|
||||
}
|
||||
|
||||
static void pretty_prop(int depth) {
|
||||
for (int i = 0; i < depth; ++i)
|
||||
printf(depth_set[i] ? "│ " : " ");
|
||||
|
||||
printf(depth_set[depth] ? "│ " : " ");
|
||||
}
|
||||
|
||||
static void print_node(const void *fdt, int node = 0, int depth = 0) {
|
||||
// Print node itself
|
||||
pretty_node(depth);
|
||||
printf("#%d: %s\n", node, fdt_get_name(fdt, node, nullptr));
|
||||
|
||||
// Print properties
|
||||
depth_set[depth] = fdt_first_subnode(fdt, node) >= 0;
|
||||
int prop;
|
||||
fdt_for_each_property_offset(prop, fdt, node) {
|
||||
pretty_prop(depth);
|
||||
int size;
|
||||
const char *name;
|
||||
auto value = static_cast<const char *>(fdt_getprop_by_offset(fdt, prop, &name, &size));
|
||||
|
||||
bool is_str = !(size > 1 && value[0] == 0);
|
||||
if (is_str) {
|
||||
// Scan through value to see if printable
|
||||
for (int i = 0; i < size; ++i) {
|
||||
char c = value[i];
|
||||
if (i == size - 1) {
|
||||
// Make sure null terminate
|
||||
is_str = c == '\0';
|
||||
} else if ((c > 0 && c < 32) || c >= 127) {
|
||||
is_str = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (is_str) {
|
||||
printf("[%s]: [%s]\n", name, value);
|
||||
} else {
|
||||
printf("[%s]: <bytes>(%d)\n", name, size);
|
||||
}
|
||||
}
|
||||
|
||||
// Recursive
|
||||
if (depth_set[depth]) {
|
||||
int child;
|
||||
int prev = -1;
|
||||
fdt_for_each_subnode(child, fdt, node) {
|
||||
if (prev >= 0)
|
||||
print_node(fdt, prev, depth + 1);
|
||||
prev = child;
|
||||
}
|
||||
depth_set[depth] = false;
|
||||
print_node(fdt, prev, depth + 1);
|
||||
}
|
||||
}
|
||||
|
||||
static int find_fstab(const void *fdt, int node = 0) {
|
||||
if (auto name = fdt_get_name(fdt, node, nullptr); name && name == "fstab"sv)
|
||||
return node;
|
||||
int child;
|
||||
fdt_for_each_subnode(child, fdt, node) {
|
||||
int fstab = find_fstab(fdt, child);
|
||||
if (fstab >= 0)
|
||||
return fstab;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
template<typename Func>
|
||||
static void for_each_fdt(const char *file, bool rw, Func fn) {
|
||||
auto m = mmap_data(file, rw);
|
||||
uint8_t *end = m.buf + m.sz;
|
||||
for (uint8_t *fdt = m.buf; fdt < end;) {
|
||||
fdt = static_cast<uint8_t*>(memmem(fdt, end - fdt, DTB_MAGIC, sizeof(fdt32_t)));
|
||||
if (fdt == nullptr)
|
||||
break;
|
||||
fn(fdt);
|
||||
fdt += fdt_totalsize(fdt);
|
||||
}
|
||||
}
|
||||
|
||||
static void dtb_print(const char *file, bool fstab) {
|
||||
fprintf(stderr, "Loading dtbs from [%s]\n", file);
|
||||
int dtb_num = 0;
|
||||
for_each_fdt(file, false, [&](uint8_t *fdt) {
|
||||
if (fstab) {
|
||||
if (int node = find_fstab(fdt); node >= 0) {
|
||||
fprintf(stderr, "Found fstab in dtb.%04d\n", dtb_num);
|
||||
print_node(fdt, node);
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr, "Printing dtb.%04d\n", dtb_num);
|
||||
print_node(fdt);
|
||||
}
|
||||
++dtb_num;
|
||||
});
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
|
||||
static bool dtb_patch(const char *file) {
|
||||
fprintf(stderr, "Loading dtbs from [%s]\n", file);
|
||||
|
||||
bool keep_verity = check_env("KEEPVERITY");
|
||||
bool patched = false;
|
||||
for_each_fdt(file, true, [&](uint8_t *fdt) {
|
||||
int node;
|
||||
// Patch the chosen node for bootargs
|
||||
fdt_for_each_subnode(node, fdt, 0) {
|
||||
if (auto name = fdt_get_name(fdt, node, nullptr); !name || name != "chosen"sv)
|
||||
continue;
|
||||
int len;
|
||||
if (auto value = fdt_getprop(fdt, node, "bootargs", &len)) {
|
||||
if (void *skip = memmem(value, len, "skip_initramfs", 14)) {
|
||||
fprintf(stderr, "Patch [skip_initramfs] -> [want_initramfs]\n");
|
||||
memcpy(skip, "want", 4);
|
||||
patched = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (!keep_verity) {
|
||||
if (int fstab = find_fstab(fdt); fstab >= 0) {
|
||||
fdt_for_each_subnode(node, fdt, fstab) {
|
||||
int len;
|
||||
char *value = (char *) fdt_getprop(fdt, node, "fsmgr_flags", &len);
|
||||
patched |= patch_verity(value, len) != len;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return patched;
|
||||
}
|
||||
|
||||
[[noreturn]]
|
||||
static void dtb_test(const char *file) {
|
||||
for_each_fdt(file, false, [&](uint8_t *fdt) {
|
||||
// Find the system node in fstab
|
||||
if (int fstab = find_fstab(fdt); fstab >= 0) {
|
||||
int node;
|
||||
fdt_for_each_subnode(node, fdt, fstab) {
|
||||
if (auto name = fdt_get_name(fdt, node, nullptr); !name || name != "system"sv)
|
||||
continue;
|
||||
int len;
|
||||
if (auto value = fdt_getprop(fdt, node, "mnt_point", &len)) {
|
||||
// If mnt_point is set to /system_root, abort!
|
||||
if (strncmp(static_cast<const char *>(value), "/system_root", len) == 0) {
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
exit(0);
|
||||
}
|
||||
|
||||
int dtb_commands(int argc, char *argv[]) {
|
||||
char *dtb = argv[0];
|
||||
++argv;
|
||||
--argc;
|
||||
|
||||
if (argv[0] == "print"sv) {
|
||||
dtb_print(dtb, argc > 1 && argv[1] == "-f"sv);
|
||||
return 0;
|
||||
} else if (argv[0] == "patch"sv) {
|
||||
if (!dtb_patch(dtb))
|
||||
exit(1);
|
||||
return 0;
|
||||
} else if (argv[0] == "test"sv) {
|
||||
dtb_test(dtb);
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// The following code is unused, left here for historical purpose. Since the code is
|
||||
// extremely complicated, I won't want to rewrite this whole thing if somehow we need
|
||||
// to use it in the future...
|
||||
|
||||
namespace {
|
||||
|
||||
struct fdt_blob {
|
||||
void *fdt;
|
||||
uint32_t offset;
|
||||
uint32_t len;
|
||||
};
|
||||
|
||||
static bool fdt_patch(void *fdt) {
|
||||
int fstab = find_fstab(fdt);
|
||||
if (fstab < 0)
|
||||
return false;
|
||||
bool modified = false;
|
||||
int node;
|
||||
fdt_for_each_subnode(node, fdt, fstab) {
|
||||
const char *name = fdt_get_name(fdt, node, nullptr);
|
||||
// Force remove AVB for 2SI since it may bootloop some devices
|
||||
int len;
|
||||
auto value = (const char *) fdt_getprop(fdt, node, "fsmgr_flags", &len);
|
||||
string copy(value, len);
|
||||
uint32_t new_len = patch_verity(copy.data(), len);
|
||||
if (new_len != len) {
|
||||
modified = true;
|
||||
fdt_setprop(fdt, node, "fsmgr_flags", copy.data(), new_len);
|
||||
}
|
||||
if (name == "system"sv) {
|
||||
fprintf(stderr, "Setting [mnt_point] to [/system_root]\n");
|
||||
fdt_setprop_string(fdt, node, "mnt_point", "/system_root");
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
return modified;
|
||||
}
|
||||
|
||||
#define MAX_FDT_GROWTH 256
|
||||
|
||||
template <class Table, class Header>
|
||||
static bool dt_table_patch(const Header *hdr, const char *out) {
|
||||
map<uint32_t, fdt_blob> dtb_map;
|
||||
auto buf = reinterpret_cast<const uint8_t *>(hdr);
|
||||
auto tables = reinterpret_cast<const Table *>(buf + sizeof(Header));
|
||||
|
||||
constexpr bool is_aosp = std::is_same_v<Header, dt_table_header>;
|
||||
|
||||
// AOSP DTB store ints in big endian
|
||||
using endian_conv = uint32_t (*)(uint32_t);
|
||||
endian_conv be_to_le;
|
||||
endian_conv le_to_be;
|
||||
if constexpr (is_aosp) {
|
||||
be_to_le = fdt32_to_cpu;
|
||||
le_to_be = cpu_to_fdt32;
|
||||
} else {
|
||||
be_to_le = le_to_be = [](uint32_t x) { return x; };
|
||||
}
|
||||
|
||||
// Collect all dtbs
|
||||
auto num_dtb = be_to_le(hdr->num_dtbs);
|
||||
for (int i = 0; i < num_dtb; ++i) {
|
||||
auto offset = be_to_le(tables[i].offset);
|
||||
if (dtb_map.count(offset) == 0) {
|
||||
auto blob = buf + offset;
|
||||
uint32_t size = fdt_totalsize(blob);
|
||||
auto fdt = xmalloc(size + MAX_FDT_GROWTH);
|
||||
memcpy(fdt, blob, size);
|
||||
fdt_open_into(fdt, fdt, size + MAX_FDT_GROWTH);
|
||||
dtb_map[offset] = { fdt, offset };
|
||||
}
|
||||
}
|
||||
if (dtb_map.empty())
|
||||
return false;
|
||||
|
||||
// Patch fdt
|
||||
bool modified = false;
|
||||
for (auto &[_, blob] : dtb_map)
|
||||
modified |= fdt_patch(blob.fdt);
|
||||
if (!modified)
|
||||
return false;
|
||||
|
||||
unlink(out);
|
||||
int fd = xopen(out, O_RDWR | O_CREAT | O_CLOEXEC, 0644);
|
||||
|
||||
// This value is only used if AOSP DTB
|
||||
uint32_t total_size = 0;
|
||||
|
||||
// Copy headers and tables
|
||||
total_size += xwrite(fd, buf, dtb_map.begin()->first);
|
||||
|
||||
// mmap rw to patch table values retroactively
|
||||
auto mmap_sz = lseek(fd, 0, SEEK_CUR);
|
||||
auto addr = (uint8_t *) xmmap(nullptr, mmap_sz, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
||||
|
||||
// Guess alignment using gcd
|
||||
uint32_t align = 1;
|
||||
if constexpr (!is_aosp) {
|
||||
auto it = dtb_map.begin();
|
||||
align = (it++)->first;
|
||||
for (; it != dtb_map.end(); ++it)
|
||||
align = binary_gcd(align, it->first);
|
||||
}
|
||||
|
||||
// Write dtbs
|
||||
for (auto &val : dtb_map) {
|
||||
val.second.offset = lseek(fd, 0, SEEK_CUR);
|
||||
auto fdt = val.second.fdt;
|
||||
fdt_pack(fdt);
|
||||
auto size = fdt_totalsize(fdt);
|
||||
total_size += xwrite(fd, fdt, size);
|
||||
if constexpr (!is_aosp) {
|
||||
val.second.len = align_to(size, align);
|
||||
write_zero(fd, align_padding(lseek(fd, 0, SEEK_CUR), align));
|
||||
}
|
||||
free(fdt);
|
||||
}
|
||||
|
||||
// Patch headers
|
||||
if constexpr (is_aosp) {
|
||||
auto hdr_rw = reinterpret_cast<Header *>(addr);
|
||||
hdr_rw->total_size = le_to_be(total_size);
|
||||
}
|
||||
auto tables_rw = reinterpret_cast<Table *>(addr + sizeof(Header));
|
||||
for (int i = 0; i < num_dtb; ++i) {
|
||||
auto &blob = dtb_map[be_to_le(tables_rw[i].offset)];
|
||||
tables_rw[i].offset = le_to_be(blob.offset);
|
||||
tables_rw[i].len = le_to_be(blob.len);
|
||||
}
|
||||
|
||||
munmap(addr, mmap_sz);
|
||||
close(fd);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool blob_patch(uint8_t *dtb, size_t dtb_sz, const char *out) {
|
||||
vector<uint8_t *> fdt_list;
|
||||
vector<uint32_t> padding_list;
|
||||
|
||||
uint8_t * const end = dtb + dtb_sz;
|
||||
for (uint8_t *curr = dtb; curr < end;) {
|
||||
curr = static_cast<uint8_t*>(memmem(curr, end - curr, DTB_MAGIC, sizeof(fdt32_t)));
|
||||
if (curr == nullptr)
|
||||
break;
|
||||
auto len = fdt_totalsize(curr);
|
||||
auto fdt = static_cast<uint8_t *>(xmalloc(len + MAX_FDT_GROWTH));
|
||||
memcpy(fdt, curr, len);
|
||||
fdt_pack(fdt);
|
||||
uint32_t padding = len - fdt_totalsize(fdt);
|
||||
padding_list.push_back(padding);
|
||||
fdt_open_into(fdt, fdt, len + MAX_FDT_GROWTH);
|
||||
fdt_list.push_back(fdt);
|
||||
curr += len;
|
||||
}
|
||||
|
||||
bool modified = false;
|
||||
for (auto fdt : fdt_list)
|
||||
modified |= fdt_patch(fdt);
|
||||
if (!modified)
|
||||
return false;
|
||||
|
||||
unlink(out);
|
||||
int fd = xopen(out, O_WRONLY | O_CREAT | O_CLOEXEC, 0644);
|
||||
|
||||
for (int i = 0; i < fdt_list.size(); ++i) {
|
||||
auto fdt = fdt_list[i];
|
||||
fdt_pack(fdt);
|
||||
// Only add padding back if it is anything meaningful
|
||||
if (padding_list[i] > 4) {
|
||||
auto len = fdt_totalsize(fdt);
|
||||
fdt_set_totalsize(fdt, len + padding_list[i]);
|
||||
}
|
||||
xwrite(fd, fdt, fdt_totalsize(fdt));
|
||||
free(fdt);
|
||||
}
|
||||
close(fd);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#define DTB_MATCH(s) BUFFER_MATCH(dtb, s)
|
||||
|
||||
[[maybe_unused]]
|
||||
static bool dtb_patch_rebuild(uint8_t *dtb, size_t dtb_sz, const char *file) {
|
||||
if (DTB_MATCH(QCDT_MAGIC)) {
|
||||
auto hdr = reinterpret_cast<qcdt_hdr*>(dtb);
|
||||
switch (hdr->version) {
|
||||
case 1:
|
||||
fprintf(stderr, "QCDT v1\n");
|
||||
return dt_table_patch<qctable_v1>(hdr, file);
|
||||
case 2:
|
||||
fprintf(stderr, "QCDT v2\n");
|
||||
return dt_table_patch<qctable_v2>(hdr, file);
|
||||
case 3:
|
||||
fprintf(stderr, "QCDT v3\n");
|
||||
return dt_table_patch<qctable_v3>(hdr, file);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
} else if (DTB_MATCH(DTBH_MAGIC)) {
|
||||
auto hdr = reinterpret_cast<dtbh_hdr *>(dtb);
|
||||
switch (hdr->version) {
|
||||
case 2:
|
||||
fprintf(stderr, "DTBH v2\n");
|
||||
return dt_table_patch<bhtable_v2>(hdr, file);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
} else if (DTB_MATCH(PXADT_MAGIC)) {
|
||||
auto hdr = reinterpret_cast<pxadt_hdr *>(dtb);
|
||||
switch (hdr->version) {
|
||||
case 1:
|
||||
fprintf(stderr, "PXA-DT v1\n");
|
||||
return dt_table_patch<pxatable_v1>(hdr, file);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
} else if (DTB_MATCH(PXA19xx_MAGIC)) {
|
||||
auto hdr = reinterpret_cast<pxa19xx_hdr *>(dtb);
|
||||
switch (hdr->version) {
|
||||
case 1:
|
||||
fprintf(stderr, "PXA-19xx v1\n");
|
||||
return dt_table_patch<pxatable_v1>(hdr, file);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
} else if (DTB_MATCH(SPRD_MAGIC)) {
|
||||
auto hdr = reinterpret_cast<sprd_hdr *>(dtb);
|
||||
switch (hdr->version) {
|
||||
case 1:
|
||||
fprintf(stderr, "SPRD v1\n");
|
||||
return dt_table_patch<sprdtable_v1>(hdr, file);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
} else if (DTB_MATCH(DT_TABLE_MAGIC)) {
|
||||
auto hdr = reinterpret_cast<dt_table_header *>(dtb);
|
||||
switch (hdr->version) {
|
||||
case 0:
|
||||
fprintf(stderr, "DT_TABLE v0\n");
|
||||
return dt_table_patch<dt_table_entry>(hdr, file);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return blob_patch(dtb, dtb_sz, file);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -0,0 +1,103 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define DT_TABLE_MAGIC "\xd7\xb7\xab\x1e"
|
||||
#define QCDT_MAGIC "QCDT"
|
||||
#define DTBH_MAGIC "DTBH"
|
||||
#define PXADT_MAGIC "PXA-DT"
|
||||
#define PXA19xx_MAGIC "PXA-19xx"
|
||||
#define SPRD_MAGIC "SPRD"
|
||||
|
||||
struct qcdt_hdr {
|
||||
char magic[4]; /* "QCDT" */
|
||||
uint32_t version; /* QCDT version */
|
||||
uint32_t num_dtbs; /* Number of DTBs */
|
||||
} __attribute__((packed));
|
||||
|
||||
struct qctable_v1 {
|
||||
uint32_t cpu_info[3]; /* Some CPU info */
|
||||
uint32_t offset; /* DTB offset in QCDT */
|
||||
uint32_t len; /* DTB size */
|
||||
} __attribute__((packed));
|
||||
|
||||
struct qctable_v2 {
|
||||
uint32_t cpu_info[4]; /* Some CPU info */
|
||||
uint32_t offset; /* DTB offset in QCDT */
|
||||
uint32_t len; /* DTB size */
|
||||
} __attribute__((packed));
|
||||
|
||||
struct qctable_v3 {
|
||||
uint32_t cpu_info[8]; /* Some CPU info */
|
||||
uint32_t offset; /* DTB offset in QCDT */
|
||||
uint32_t len; /* DTB size */
|
||||
} __attribute__((packed));
|
||||
|
||||
struct dtbh_hdr {
|
||||
char magic[4]; /* "DTBH" */
|
||||
uint32_t version; /* DTBH version */
|
||||
uint32_t num_dtbs; /* Number of DTBs */
|
||||
} __attribute__((packed));
|
||||
|
||||
struct bhtable_v2 {
|
||||
uint32_t cpu_info[5]; /* Some CPU info */
|
||||
uint32_t offset; /* DTB offset in DTBH */
|
||||
uint32_t len; /* DTB size */
|
||||
uint32_t space; /* 0x00000020 */
|
||||
} __attribute__((packed));
|
||||
|
||||
struct pxadt_hdr {
|
||||
char magic[6]; /* "PXA-DT" */
|
||||
uint32_t version; /* PXA-* version */
|
||||
uint32_t num_dtbs; /* Number of DTBs */
|
||||
} __attribute__((packed));
|
||||
|
||||
struct pxa19xx_hdr {
|
||||
char magic[8]; /* "PXA-19xx" */
|
||||
uint32_t version; /* PXA-* version */
|
||||
uint32_t num_dtbs; /* Number of DTBs */
|
||||
} __attribute__((packed));
|
||||
|
||||
struct pxatable_v1 {
|
||||
uint32_t cpu_info[2]; /* Some CPU info */
|
||||
uint32_t offset; /* DTB offset in PXA-* */
|
||||
uint32_t len; /* DTB size */
|
||||
} __attribute__((packed));
|
||||
|
||||
struct sprd_hdr {
|
||||
char magic[4]; /* "SPRD" */
|
||||
uint32_t version; /* SPRD version */
|
||||
uint32_t num_dtbs; /* Number of DTBs */
|
||||
} __attribute__((packed));
|
||||
|
||||
struct sprdtable_v1 {
|
||||
uint32_t cpu_info[3]; /* Some CPU info */
|
||||
uint32_t offset; /* DTB offset in SPRD */
|
||||
uint32_t len; /* DTB size */
|
||||
} __attribute__((packed));
|
||||
|
||||
/* AOSP DTB/DTBO partition layout */
|
||||
|
||||
struct dt_table_header {
|
||||
uint32_t magic; /* DT_TABLE_MAGIC */
|
||||
uint32_t total_size; /* includes dt_table_header + all dt_table_entry */
|
||||
uint32_t header_size; /* sizeof(dt_table_header) */
|
||||
|
||||
uint32_t dt_entry_size; /* sizeof(dt_table_entry) */
|
||||
uint32_t num_dtbs; /* number of dt_table_entry */
|
||||
uint32_t dt_entries_offset; /* offset to the first dt_table_entry */
|
||||
|
||||
uint32_t page_size; /* flash page size we assume */
|
||||
uint32_t version; /* DTBO image version */
|
||||
} __attribute__((packed));
|
||||
|
||||
struct dt_table_entry {
|
||||
uint32_t len; /* DTB size */
|
||||
uint32_t offset;
|
||||
|
||||
uint32_t id;
|
||||
uint32_t rev;
|
||||
uint32_t flags;
|
||||
|
||||
uint32_t custom[3];
|
||||
} __attribute__((packed));
|
||||
@@ -0,0 +1,110 @@
|
||||
#include "format.hpp"
|
||||
|
||||
Name2Fmt name2fmt;
|
||||
Fmt2Name fmt2name;
|
||||
Fmt2Ext fmt2ext;
|
||||
|
||||
#define CHECKED_MATCH(s) (len >= (sizeof(s) - 1) && BUFFER_MATCH(buf, s))
|
||||
|
||||
format_t check_fmt(const void *buf, size_t len) {
|
||||
if (CHECKED_MATCH(CHROMEOS_MAGIC)) {
|
||||
return CHROMEOS;
|
||||
} else if (CHECKED_MATCH(BOOT_MAGIC)) {
|
||||
return AOSP;
|
||||
} else if (CHECKED_MATCH(VENDOR_BOOT_MAGIC)) {
|
||||
return AOSP_VENDOR;
|
||||
} else if (CHECKED_MATCH(GZIP1_MAGIC) || CHECKED_MATCH(GZIP2_MAGIC)) {
|
||||
return GZIP;
|
||||
} else if (CHECKED_MATCH(LZOP_MAGIC)) {
|
||||
return LZOP;
|
||||
} else if (CHECKED_MATCH(XZ_MAGIC)) {
|
||||
return XZ;
|
||||
} else if (len >= 13 && memcmp(buf, "\x5d\x00\x00", 3) == 0
|
||||
&& (((char *)buf)[12] == '\xff' || ((char *)buf)[12] == '\x00')) {
|
||||
return LZMA;
|
||||
} else if (CHECKED_MATCH(BZIP_MAGIC)) {
|
||||
return BZIP2;
|
||||
} else if (CHECKED_MATCH(LZ41_MAGIC) || CHECKED_MATCH(LZ42_MAGIC)) {
|
||||
return LZ4;
|
||||
} else if (CHECKED_MATCH(LZ4_LEG_MAGIC)) {
|
||||
return LZ4_LEGACY;
|
||||
} else if (CHECKED_MATCH(MTK_MAGIC)) {
|
||||
return MTK;
|
||||
} else if (CHECKED_MATCH(DTB_MAGIC)) {
|
||||
return DTB;
|
||||
} else if (CHECKED_MATCH(DHTB_MAGIC)) {
|
||||
return DHTB;
|
||||
} else if (CHECKED_MATCH(TEGRABLOB_MAGIC)) {
|
||||
return BLOB;
|
||||
} else if (len >= 0x28 && memcmp(&((char *)buf)[0x24], ZIMAGE_MAGIC, 4) == 0) {
|
||||
return ZIMAGE;
|
||||
} else {
|
||||
return UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
const char *Fmt2Name::operator[](format_t fmt) {
|
||||
switch (fmt) {
|
||||
case GZIP:
|
||||
return "gzip";
|
||||
case ZOPFLI:
|
||||
return "zopfli";
|
||||
case LZOP:
|
||||
return "lzop";
|
||||
case XZ:
|
||||
return "xz";
|
||||
case LZMA:
|
||||
return "lzma";
|
||||
case BZIP2:
|
||||
return "bzip2";
|
||||
case LZ4:
|
||||
return "lz4";
|
||||
case LZ4_LEGACY:
|
||||
return "lz4_legacy";
|
||||
case LZ4_LG:
|
||||
return "lz4_lg";
|
||||
case DTB:
|
||||
return "dtb";
|
||||
case ZIMAGE:
|
||||
return "zimage";
|
||||
default:
|
||||
return "raw";
|
||||
}
|
||||
}
|
||||
|
||||
const char *Fmt2Ext::operator[](format_t fmt) {
|
||||
switch (fmt) {
|
||||
case GZIP:
|
||||
case ZOPFLI:
|
||||
return ".gz";
|
||||
case LZOP:
|
||||
return ".lzo";
|
||||
case XZ:
|
||||
return ".xz";
|
||||
case LZMA:
|
||||
return ".lzma";
|
||||
case BZIP2:
|
||||
return ".bz2";
|
||||
case LZ4:
|
||||
case LZ4_LEGACY:
|
||||
case LZ4_LG:
|
||||
return ".lz4";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
#define CHECK(s, f) else if (name == s) return f;
|
||||
|
||||
format_t Name2Fmt::operator[](std::string_view name) {
|
||||
if (0) {}
|
||||
CHECK("gzip", GZIP)
|
||||
CHECK("zopfli", ZOPFLI)
|
||||
CHECK("xz", XZ)
|
||||
CHECK("lzma", LZMA)
|
||||
CHECK("bzip2", BZIP2)
|
||||
CHECK("lz4", LZ4)
|
||||
CHECK("lz4_legacy", LZ4_LEGACY)
|
||||
CHECK("lz4_lg", LZ4_LG)
|
||||
else return UNKNOWN;
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
|
||||
typedef enum {
|
||||
UNKNOWN,
|
||||
/* Boot formats */
|
||||
CHROMEOS,
|
||||
AOSP,
|
||||
AOSP_VENDOR,
|
||||
DHTB,
|
||||
BLOB,
|
||||
/* Compression formats */
|
||||
GZIP,
|
||||
ZOPFLI,
|
||||
XZ,
|
||||
LZMA,
|
||||
BZIP2,
|
||||
LZ4,
|
||||
LZ4_LEGACY,
|
||||
LZ4_LG,
|
||||
/* Unsupported compression */
|
||||
LZOP,
|
||||
/* Misc */
|
||||
MTK,
|
||||
DTB,
|
||||
ZIMAGE,
|
||||
} format_t;
|
||||
|
||||
#define COMPRESSED(fmt) ((fmt) >= GZIP && (fmt) < LZOP)
|
||||
#define COMPRESSED_ANY(fmt) ((fmt) >= GZIP && (fmt) <= LZOP)
|
||||
|
||||
#define BUFFER_MATCH(buf, s) (memcmp(buf, s, sizeof(s) - 1) == 0)
|
||||
|
||||
#define BOOT_MAGIC "ANDROID!"
|
||||
#define VENDOR_BOOT_MAGIC "VNDRBOOT"
|
||||
#define CHROMEOS_MAGIC "CHROMEOS"
|
||||
#define GZIP1_MAGIC "\x1f\x8b"
|
||||
#define GZIP2_MAGIC "\x1f\x9e"
|
||||
#define LZOP_MAGIC "\x89""LZO"
|
||||
#define XZ_MAGIC "\xfd""7zXZ"
|
||||
#define BZIP_MAGIC "BZh"
|
||||
#define LZ4_LEG_MAGIC "\x02\x21\x4c\x18"
|
||||
#define LZ41_MAGIC "\x03\x21\x4c\x18"
|
||||
#define LZ42_MAGIC "\x04\x22\x4d\x18"
|
||||
#define MTK_MAGIC "\x88\x16\x88\x58"
|
||||
#define DTB_MAGIC "\xd0\x0d\xfe\xed"
|
||||
#define LG_BUMP_MAGIC "\x41\xa9\xe4\x67\x74\x4d\x1d\x1b\xa4\x29\xf2\xec\xea\x65\x52\x79"
|
||||
#define DHTB_MAGIC "\x44\x48\x54\x42\x01\x00\x00\x00"
|
||||
#define SEANDROID_MAGIC "SEANDROIDENFORCE"
|
||||
#define TEGRABLOB_MAGIC "-SIGNED-BY-SIGNBLOB-"
|
||||
#define NOOKHD_RL_MAGIC "Red Loader"
|
||||
#define NOOKHD_GL_MAGIC "Green Loader"
|
||||
#define NOOKHD_GR_MAGIC "Green Recovery"
|
||||
#define NOOKHD_EB_MAGIC "eMMC boot.img+secondloader"
|
||||
#define NOOKHD_ER_MAGIC "eMMC recovery.img+secondloader"
|
||||
#define NOOKHD_PRE_HEADER_SZ 1048576
|
||||
#define ACCLAIM_MAGIC "BauwksBoot"
|
||||
#define ACCLAIM_PRE_HEADER_SZ 262144
|
||||
#define AVB_FOOTER_MAGIC "AVBf"
|
||||
#define AVB_MAGIC "AVB0"
|
||||
#define ZIMAGE_MAGIC "\x18\x28\x6f\x01"
|
||||
|
||||
class Fmt2Name {
|
||||
public:
|
||||
const char *operator[](format_t fmt);
|
||||
};
|
||||
|
||||
class Fmt2Ext {
|
||||
public:
|
||||
const char *operator[](format_t fmt);
|
||||
};
|
||||
|
||||
class Name2Fmt {
|
||||
public:
|
||||
format_t operator[](std::string_view name);
|
||||
};
|
||||
|
||||
format_t check_fmt(const void *buf, size_t len);
|
||||
|
||||
extern Name2Fmt name2fmt;
|
||||
extern Fmt2Name fmt2name;
|
||||
extern Fmt2Ext fmt2ext;
|
||||
@@ -0,0 +1,41 @@
|
||||
#include <sys/mman.h>
|
||||
|
||||
#include <base.hpp>
|
||||
|
||||
#include "magiskboot.hpp"
|
||||
|
||||
using namespace std;
|
||||
|
||||
static void hex2byte(const char *hex, uint8_t *buf) {
|
||||
char high, low;
|
||||
for (int i = 0, length = strlen(hex); i < length; i += 2) {
|
||||
high = toupper(hex[i]) - '0';
|
||||
low = toupper(hex[i + 1]) - '0';
|
||||
buf[i / 2] = ((high > 9 ? high - 7 : high) << 4) + (low > 9 ? low - 7 : low);
|
||||
}
|
||||
}
|
||||
|
||||
int hexpatch(const char *file, const char *from, const char *to) {
|
||||
int patched = 1;
|
||||
|
||||
auto m = mmap_data(file, true);
|
||||
|
||||
vector<uint8_t> pattern(strlen(from) / 2);
|
||||
vector<uint8_t> patch(strlen(to) / 2);
|
||||
|
||||
hex2byte(from, pattern.data());
|
||||
hex2byte(to, patch.data());
|
||||
|
||||
uint8_t * const end = m.buf + m.sz;
|
||||
for (uint8_t *curr = m.buf; curr < end; curr += pattern.size()) {
|
||||
curr = static_cast<uint8_t*>(memmem(curr, end - curr, pattern.data(), pattern.size()));
|
||||
if (curr == nullptr)
|
||||
return patched;
|
||||
fprintf(stderr, "Patch @ %08X [%s] -> [%s]\n", (unsigned)(curr - m.buf), from, to);
|
||||
memset(curr, 0, pattern.size());
|
||||
memcpy(curr, patch.data(), patch.size());
|
||||
patched = 0;
|
||||
}
|
||||
|
||||
return patched;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub use base;
|
||||
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#define HEADER_FILE "header"
|
||||
#define KERNEL_FILE "kernel"
|
||||
#define RAMDISK_FILE "ramdisk.cpio"
|
||||
#define SECOND_FILE "second"
|
||||
#define EXTRA_FILE "extra"
|
||||
#define KER_DTB_FILE "kernel_dtb"
|
||||
#define RECV_DTBO_FILE "recovery_dtbo"
|
||||
#define DTB_FILE "dtb"
|
||||
#define NEW_BOOT "new-boot.img"
|
||||
|
||||
int unpack(const char *image, bool skip_decomp = false, bool hdr = false);
|
||||
void repack(const char *src_img, const char *out_img, bool skip_comp = false);
|
||||
int split_image_dtb(const char *filename);
|
||||
int hexpatch(const char *file, const char *from, const char *to);
|
||||
int cpio_commands(int argc, char *argv[]);
|
||||
int dtb_commands(int argc, char *argv[]);
|
||||
|
||||
uint32_t patch_verity(void *buf, uint32_t size);
|
||||
uint32_t patch_encryption(void *buf, uint32_t size);
|
||||
bool check_env(const char *name);
|
||||
@@ -0,0 +1,209 @@
|
||||
#include <mincrypt/sha.h>
|
||||
#include <base.hpp>
|
||||
|
||||
#include "magiskboot.hpp"
|
||||
#include "compress.hpp"
|
||||
|
||||
using namespace std;
|
||||
|
||||
static void print_formats() {
|
||||
for (int fmt = GZIP; fmt < LZOP; ++fmt) {
|
||||
fprintf(stderr, "%s ", fmt2name[(format_t) fmt]);
|
||||
}
|
||||
}
|
||||
|
||||
static void usage(char *arg0) {
|
||||
fprintf(stderr,
|
||||
R"EOF(MagiskBoot - Boot Image Modification Tool
|
||||
|
||||
Usage: %s <action> [args...]
|
||||
|
||||
Supported actions:
|
||||
unpack [-n] [-h] <bootimg>
|
||||
Unpack <bootimg> to its individual components, each component to
|
||||
a file with its corresponding file name in the current directory.
|
||||
Supported components: kernel, kernel_dtb, ramdisk.cpio, second,
|
||||
dtb, extra, and recovery_dtbo.
|
||||
By default, each component will be automatically decompressed
|
||||
on-the-fly before writing to the output file.
|
||||
If '-n' is provided, all decompression operations will be skipped;
|
||||
each component will remain untouched, dumped in its original format.
|
||||
If '-h' is provided, the boot image header information will be
|
||||
dumped to the file 'header', which can be used to modify header
|
||||
configurations during repacking.
|
||||
Return values:
|
||||
0:valid 1:error 2:chromeos
|
||||
|
||||
repack [-n] <origbootimg> [outbootimg]
|
||||
Repack boot image components using files from the current directory
|
||||
to [outbootimg], or 'new-boot.img' if not specified.
|
||||
<origbootimg> is the original boot image used to unpack the components.
|
||||
By default, each component will be automatically compressed using its
|
||||
corresponding format detected in <origbootimg>. If a component file
|
||||
in the current directory is already compressed, then no addition
|
||||
compression will be performed for that specific component.
|
||||
If '-n' is provided, all compression operations will be skipped.
|
||||
If env variable PATCHVBMETAFLAG is set to true, all disable flags in
|
||||
the boot image's vbmeta header will be set.
|
||||
|
||||
hexpatch <file> <hexpattern1> <hexpattern2>
|
||||
Search <hexpattern1> in <file>, and replace it with <hexpattern2>
|
||||
|
||||
cpio <incpio> [commands...]
|
||||
Do cpio commands to <incpio> (modifications are done in-place)
|
||||
Each command is a single argument, add quotes for each command.
|
||||
Supported commands:
|
||||
exists ENTRY
|
||||
Return 0 if ENTRY exists, else return 1
|
||||
rm [-r] ENTRY
|
||||
Remove ENTRY, specify [-r] to remove recursively
|
||||
mkdir MODE ENTRY
|
||||
Create directory ENTRY in permissions MODE
|
||||
ln TARGET ENTRY
|
||||
Create a symlink to TARGET with the name ENTRY
|
||||
mv SOURCE DEST
|
||||
Move SOURCE to DEST
|
||||
add MODE ENTRY INFILE
|
||||
Add INFILE as ENTRY in permissions MODE; replaces ENTRY if exists
|
||||
extract [ENTRY OUT]
|
||||
Extract ENTRY to OUT, or extract all entries to current directory
|
||||
test
|
||||
Test the cpio's status
|
||||
Return value is 0 or bitwise or-ed of following values:
|
||||
0x1:Magisk 0x2:unsupported 0x4:Sony
|
||||
patch
|
||||
Apply ramdisk patches
|
||||
Configure with env variables: KEEPVERITY KEEPFORCEENCRYPT
|
||||
backup ORIG
|
||||
Create ramdisk backups from ORIG
|
||||
restore
|
||||
Restore ramdisk from ramdisk backup stored within incpio
|
||||
sha1
|
||||
Print stock boot SHA1 if previously backed up in ramdisk
|
||||
|
||||
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
|
||||
|
||||
split <file>
|
||||
Split image.*-dtb into kernel + kernel_dtb
|
||||
|
||||
sha1 <file>
|
||||
Print the SHA1 checksum for <file>
|
||||
|
||||
cleanup
|
||||
Cleanup the current working directory
|
||||
|
||||
compress[=format] <infile> [outfile]
|
||||
Compress <infile> with [format] to [outfile].
|
||||
<infile>/[outfile] can be '-' to be STDIN/STDOUT.
|
||||
If [format] is not specified, then gzip will be used.
|
||||
If [outfile] is not specified, then <infile> will be replaced
|
||||
with another file suffixed with a matching file extension.
|
||||
Supported formats: )EOF", arg0);
|
||||
|
||||
print_formats();
|
||||
|
||||
fprintf(stderr, R"EOF(
|
||||
|
||||
decompress <infile> [outfile]
|
||||
Detect format and decompress <infile> to [outfile].
|
||||
<infile>/[outfile] can be '-' to be STDIN/STDOUT.
|
||||
If [outfile] is not specified, then <infile> will be replaced
|
||||
with another file removing its archive format file extension.
|
||||
Supported formats: )EOF");
|
||||
|
||||
print_formats();
|
||||
|
||||
fprintf(stderr, "\n\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
cmdline_logging();
|
||||
umask(0);
|
||||
|
||||
if (argc < 2)
|
||||
usage(argv[0]);
|
||||
|
||||
// Skip '--' for backwards compatibility
|
||||
string_view action(argv[1]);
|
||||
if (str_starts(action, "--"))
|
||||
action = argv[1] + 2;
|
||||
|
||||
if (action == "cleanup") {
|
||||
fprintf(stderr, "Cleaning up...\n");
|
||||
unlink(HEADER_FILE);
|
||||
unlink(KERNEL_FILE);
|
||||
unlink(RAMDISK_FILE);
|
||||
unlink(SECOND_FILE);
|
||||
unlink(KER_DTB_FILE);
|
||||
unlink(EXTRA_FILE);
|
||||
unlink(RECV_DTBO_FILE);
|
||||
unlink(DTB_FILE);
|
||||
} else if (argc > 2 && action == "sha1") {
|
||||
uint8_t sha1[SHA_DIGEST_SIZE];
|
||||
auto m = mmap_data(argv[2]);
|
||||
SHA_hash(m.buf, m.sz, sha1);
|
||||
for (uint8_t i : sha1)
|
||||
printf("%02x", i);
|
||||
printf("\n");
|
||||
} else if (argc > 2 && action == "split") {
|
||||
return split_image_dtb(argv[2]);
|
||||
} else if (argc > 2 && action == "unpack") {
|
||||
int idx = 2;
|
||||
bool nodecomp = false;
|
||||
bool hdr = false;
|
||||
for (;;) {
|
||||
if (idx >= argc)
|
||||
usage(argv[0]);
|
||||
if (argv[idx][0] != '-')
|
||||
break;
|
||||
for (char *flag = &argv[idx][1]; *flag; ++flag) {
|
||||
if (*flag == 'n')
|
||||
nodecomp = true;
|
||||
else if (*flag == 'h')
|
||||
hdr = true;
|
||||
else
|
||||
usage(argv[0]);
|
||||
}
|
||||
++idx;
|
||||
}
|
||||
return unpack(argv[idx], nodecomp, hdr);
|
||||
} else if (argc > 2 && action == "repack") {
|
||||
if (argv[2] == "-n"sv) {
|
||||
if (argc == 3)
|
||||
usage(argv[0]);
|
||||
repack(argv[3], argv[4] ? argv[4] : NEW_BOOT, true);
|
||||
} else {
|
||||
repack(argv[2], argv[3] ? argv[3] : NEW_BOOT);
|
||||
}
|
||||
} else if (argc > 2 && action == "decompress") {
|
||||
decompress(argv[2], argv[3]);
|
||||
} else if (argc > 2 && str_starts(action, "compress")) {
|
||||
compress(action[8] == '=' ? &action[9] : "gzip", argv[2], argv[3]);
|
||||
} 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))
|
||||
usage(argv[0]);
|
||||
} else if (argc > 3 && action == "dtb") {
|
||||
if (dtb_commands(argc - 2, argv + 2))
|
||||
usage(argv[0]);
|
||||
} else {
|
||||
usage(argv[0]);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
#include <base.hpp>
|
||||
|
||||
#include "magiskboot.hpp"
|
||||
|
||||
#define MATCH(p) else if (strncmp(s + skip, p, sizeof(p) - 1) == 0) skip += (sizeof(p) - 1)
|
||||
|
||||
static int skip_verity_pattern(const char *s) {
|
||||
int skip = s[0] == ',';
|
||||
|
||||
if (0) {}
|
||||
MATCH("verifyatboot");
|
||||
MATCH("verify");
|
||||
MATCH("avb_keys");
|
||||
MATCH("avb");
|
||||
MATCH("support_scfs");
|
||||
MATCH("fsverity");
|
||||
else return -1;
|
||||
|
||||
if (s[skip] == '=') {
|
||||
while (!strchr(" \n,", s[skip]))
|
||||
++skip;
|
||||
}
|
||||
return skip;
|
||||
}
|
||||
|
||||
static int skip_encryption_pattern(const char *s) {
|
||||
int skip = s[0] == ',';
|
||||
|
||||
if (0) {}
|
||||
MATCH("forceencrypt");
|
||||
MATCH("forcefdeorfbe");
|
||||
MATCH("fileencryption");
|
||||
else return -1;
|
||||
|
||||
if (s[skip] == '=') {
|
||||
while (!strchr(" \n,", s[skip]))
|
||||
++skip;
|
||||
}
|
||||
return skip;
|
||||
}
|
||||
|
||||
static uint32_t remove_pattern(char *src, uint32_t size, int(*pattern_skip)(const char *)) {
|
||||
int orig_sz = size;
|
||||
int write = 0;
|
||||
for (int read = 0; read < orig_sz;) {
|
||||
if (int skip = pattern_skip(src + read); skip > 0) {
|
||||
fprintf(stderr, "Remove pattern [%.*s]\n", skip, src + read);
|
||||
size -= skip;
|
||||
read += skip;
|
||||
} else {
|
||||
src[write++] = src[read++];
|
||||
}
|
||||
}
|
||||
memset(src + write, 0, orig_sz - write);
|
||||
return size;
|
||||
}
|
||||
|
||||
uint32_t patch_verity(void *buf, uint32_t size) {
|
||||
return remove_pattern(static_cast<char *>(buf), size, skip_verity_pattern);
|
||||
}
|
||||
|
||||
uint32_t patch_encryption(void *buf, uint32_t size) {
|
||||
return remove_pattern(static_cast<char *>(buf), size, skip_encryption_pattern);
|
||||
}
|
||||
@@ -0,0 +1,301 @@
|
||||
#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();
|
||||
char *sha1();
|
||||
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());
|
||||
cur->second->filesize = patch_verity(cur->second->data, cur->second->filesize);
|
||||
} else if (cur->first == "verity_key") {
|
||||
rm(cur);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (!keepforceencrypt) {
|
||||
if (fstab) {
|
||||
cur->second->filesize = patch_encryption(cur->second->data, cur->second->filesize);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#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_line(line, buf, size) \
|
||||
for (char *line = (char *) buf; line < (char *) buf + size && line[0]; line = strchr(line + 1, '\n') + 1)
|
||||
|
||||
char *magisk_cpio::sha1() {
|
||||
char sha1[41];
|
||||
for (auto &e : entries) {
|
||||
if (e.first == "init.magisk.rc" || e.first == "overlay/init.magisk.rc") {
|
||||
for_each_line(line, e.second->data, e.second->filesize) {
|
||||
if (strncmp(line, "#STOCKSHA1=", 11) == 0) {
|
||||
strncpy(sha1, line + 12, 40);
|
||||
sha1[40] = '\0';
|
||||
return strdup(sha1);
|
||||
}
|
||||
}
|
||||
} else if (e.first == ".backup/.magisk") {
|
||||
for_each_line(line, e.second->data, e.second->filesize) {
|
||||
if (str_starts(line, "SHA1=")) {
|
||||
strncpy(sha1, line + 5, 40);
|
||||
sha1[40] = '\0';
|
||||
return strdup(sha1);
|
||||
}
|
||||
}
|
||||
} else if (e.first == ".backup/.sha1") {
|
||||
return (char *) e.second->data;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#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, rl->second->filesize) {
|
||||
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->filesize != rhs->second->filesize ||
|
||||
memcmp(lhs->second->data, rhs->second->data, lhs->second->filesize) != 0) {
|
||||
// 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_file->filesize = rm_list.length();
|
||||
rm_list_file->data = xmalloc(rm_list.length());
|
||||
memcpy(rm_list_file->data, rm_list.data(), rm_list.length());
|
||||
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] == "sha1"sv) {
|
||||
char *sha1 = cpio.sha1();
|
||||
if (sha1) printf("%s\n", sha1);
|
||||
return 0;
|
||||
} 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;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "magisk"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["staticlib"]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
base = { path = "../base" }
|
||||
cxx = "1.0.69"
|
||||
@@ -0,0 +1,10 @@
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <magisk.hpp>
|
||||
#include <base.hpp>
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
umask(0);
|
||||
cmdline_logging();
|
||||
return APPLET_STUB_MAIN(argc, argv);
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
#include <libgen.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <magisk.hpp>
|
||||
#include <selinux.hpp>
|
||||
#include <base.hpp>
|
||||
|
||||
using namespace std;
|
||||
|
||||
using main_fun = int (*)(int, char *[]);
|
||||
|
||||
constexpr const char *applets[] = { "su", "resetprop", "zygisk", nullptr };
|
||||
static main_fun applet_mains[] = { su_client_main, resetprop_main, zygisk_main, nullptr };
|
||||
|
||||
static int call_applet(int argc, char *argv[]) {
|
||||
// Applets
|
||||
string_view base = basename(argv[0]);
|
||||
for (int i = 0; applets[i]; ++i) {
|
||||
if (base == applets[i]) {
|
||||
return (*applet_mains[i])(argc, argv);
|
||||
}
|
||||
}
|
||||
fprintf(stderr, "%s: applet not found\n", base.data());
|
||||
return 1;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
enable_selinux();
|
||||
cmdline_logging();
|
||||
init_argv0(argc, argv);
|
||||
|
||||
string_view base = basename(argv[0]);
|
||||
|
||||
// app_process is actually not an applet
|
||||
if (str_starts(base, "app_process")) {
|
||||
return app_process_main(argc, argv);
|
||||
}
|
||||
|
||||
umask(0);
|
||||
if (base == "magisk" || base == "magisk32" || base == "magisk64") {
|
||||
if (argc > 1 && argv[1][0] != '-') {
|
||||
// Calling applet via magisk [applet] args
|
||||
--argc;
|
||||
++argv;
|
||||
} else {
|
||||
return magisk_main(argc, argv);
|
||||
}
|
||||
}
|
||||
|
||||
return call_applet(argc, argv);
|
||||
}
|
||||
@@ -0,0 +1,382 @@
|
||||
#include <sys/mount.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/sysmacros.h>
|
||||
#include <linux/input.h>
|
||||
#include <libgen.h>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
#include <magisk.hpp>
|
||||
#include <db.hpp>
|
||||
#include <base.hpp>
|
||||
#include <daemon.hpp>
|
||||
#include <resetprop.hpp>
|
||||
#include <selinux.hpp>
|
||||
|
||||
#include "core.hpp"
|
||||
|
||||
using namespace std;
|
||||
|
||||
static bool safe_mode = false;
|
||||
bool zygisk_enabled = false;
|
||||
|
||||
/*********
|
||||
* Setup *
|
||||
*********/
|
||||
|
||||
#define MNT_DIR_IS(dir) (me->mnt_dir == string_view(dir))
|
||||
#define MNT_TYPE_IS(type) (me->mnt_type == string_view(type))
|
||||
#define SETMIR(b, part) snprintf(b, sizeof(b), "%s/" MIRRDIR "/" #part, MAGISKTMP.data())
|
||||
#define SETBLK(b, part) snprintf(b, sizeof(b), "%s/" BLOCKDIR "/" #part, MAGISKTMP.data())
|
||||
|
||||
#define do_mount_mirror(part) { \
|
||||
SETMIR(buf1, part); \
|
||||
SETBLK(buf2, part); \
|
||||
unlink(buf2); \
|
||||
mknod(buf2, S_IFBLK | 0600, st.st_dev); \
|
||||
xmkdir(buf1, 0755); \
|
||||
int flags = 0; \
|
||||
auto opts = split_ro(me->mnt_opts, ",");\
|
||||
for (string_view s : opts) { \
|
||||
if (s == "ro") { \
|
||||
flags |= MS_RDONLY; \
|
||||
break; \
|
||||
} \
|
||||
} \
|
||||
xmount(buf2, buf1, me->mnt_type, flags, nullptr); \
|
||||
LOGI("mount: %s\n", buf1); \
|
||||
}
|
||||
|
||||
#define mount_mirror(part) \
|
||||
if (MNT_DIR_IS("/" #part) \
|
||||
&& !MNT_TYPE_IS("tmpfs") \
|
||||
&& !MNT_TYPE_IS("overlay") \
|
||||
&& lstat(me->mnt_dir, &st) == 0) { \
|
||||
do_mount_mirror(part); \
|
||||
break; \
|
||||
}
|
||||
|
||||
#define link_mirror(part) \
|
||||
SETMIR(buf1, part); \
|
||||
if (access("/system/" #part, F_OK) == 0 && access(buf1, F_OK) != 0) { \
|
||||
xsymlink("./system/" #part, buf1); \
|
||||
LOGI("link: %s\n", buf1); \
|
||||
}
|
||||
|
||||
#define link_orig_dir(dir, part) \
|
||||
if (MNT_DIR_IS(dir) && !MNT_TYPE_IS("tmpfs") && !MNT_TYPE_IS("overlay")) { \
|
||||
SETMIR(buf1, part); \
|
||||
rmdir(buf1); \
|
||||
xsymlink(dir, buf1); \
|
||||
LOGI("link: %s\n", buf1); \
|
||||
break; \
|
||||
}
|
||||
|
||||
#define link_orig(part) link_orig_dir("/" #part, part)
|
||||
|
||||
static void mount_mirrors() {
|
||||
char buf1[4096];
|
||||
char buf2[4096];
|
||||
|
||||
LOGI("* Mounting mirrors\n");
|
||||
|
||||
parse_mnt("/proc/mounts", [&](mntent *me) {
|
||||
struct stat st{};
|
||||
do {
|
||||
mount_mirror(system)
|
||||
mount_mirror(vendor)
|
||||
mount_mirror(product)
|
||||
mount_mirror(system_ext)
|
||||
mount_mirror(data)
|
||||
link_orig(cache)
|
||||
link_orig(metadata)
|
||||
link_orig(persist)
|
||||
link_orig_dir("/mnt/vendor/persist", persist)
|
||||
if (SDK_INT >= 24 && MNT_DIR_IS("/proc") && !strstr(me->mnt_opts, "hidepid=2")) {
|
||||
xmount(nullptr, "/proc", nullptr, MS_REMOUNT, "hidepid=2,gid=3009");
|
||||
break;
|
||||
}
|
||||
} while (false);
|
||||
return true;
|
||||
});
|
||||
SETMIR(buf1, system);
|
||||
if (access(buf1, F_OK) != 0) {
|
||||
xsymlink("./system_root/system", buf1);
|
||||
LOGI("link: %s\n", buf1);
|
||||
parse_mnt("/proc/mounts", [&](mntent *me) {
|
||||
struct stat st;
|
||||
if (MNT_DIR_IS("/") && me->mnt_type != "rootfs"sv && stat("/", &st) == 0) {
|
||||
do_mount_mirror(system_root)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
link_mirror(vendor)
|
||||
link_mirror(product)
|
||||
link_mirror(system_ext)
|
||||
}
|
||||
|
||||
static bool magisk_env() {
|
||||
char buf[4096];
|
||||
|
||||
LOGI("* Initializing Magisk environment\n");
|
||||
|
||||
preserve_stub_apk();
|
||||
string pkg;
|
||||
get_manager(0, &pkg);
|
||||
|
||||
sprintf(buf, "%s/0/%s/install", APP_DATA_DIR,
|
||||
pkg.empty() ? "xxx" /* Ensure non-exist path */ : pkg.data());
|
||||
|
||||
// Alternative binaries paths
|
||||
const char *alt_bin[] = { "/cache/data_adb/magisk", "/data/magisk", buf };
|
||||
for (auto alt : alt_bin) {
|
||||
struct stat st{};
|
||||
if (lstat(alt, &st) == 0) {
|
||||
if (S_ISLNK(st.st_mode)) {
|
||||
unlink(alt);
|
||||
continue;
|
||||
}
|
||||
rm_rf(DATABIN);
|
||||
cp_afc(alt, DATABIN);
|
||||
rm_rf(alt);
|
||||
break;
|
||||
}
|
||||
}
|
||||
rm_rf("/cache/data_adb");
|
||||
|
||||
// Directories in /data/adb
|
||||
xmkdir(DATABIN, 0755);
|
||||
xmkdir(MODULEROOT, 0755);
|
||||
xmkdir(SECURE_DIR "/post-fs-data.d", 0755);
|
||||
xmkdir(SECURE_DIR "/service.d", 0755);
|
||||
|
||||
restore_databincon();
|
||||
|
||||
if (access(DATABIN "/busybox", X_OK))
|
||||
return false;
|
||||
|
||||
sprintf(buf, "%s/" BBPATH "/busybox", MAGISKTMP.data());
|
||||
mkdir(dirname(buf), 0755);
|
||||
cp_afc(DATABIN "/busybox", buf);
|
||||
exec_command_async(buf, "--install", "-s", dirname(buf));
|
||||
|
||||
if (access(DATABIN "/magiskpolicy", X_OK) == 0) {
|
||||
sprintf(buf, "%s/magiskpolicy", MAGISKTMP.data());
|
||||
cp_afc(DATABIN "/magiskpolicy", buf);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void reboot() {
|
||||
if (RECOVERY_MODE)
|
||||
exec_command_sync("/system/bin/reboot", "recovery");
|
||||
else
|
||||
exec_command_sync("/system/bin/reboot");
|
||||
}
|
||||
|
||||
static bool check_data() {
|
||||
bool mnt = false;
|
||||
file_readline("/proc/mounts", [&](string_view s) {
|
||||
if (str_contains(s, " /data ") && !str_contains(s, "tmpfs")) {
|
||||
mnt = true;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
if (!mnt)
|
||||
return false;
|
||||
auto crypto = getprop("ro.crypto.state");
|
||||
if (!crypto.empty()) {
|
||||
if (crypto != "encrypted") {
|
||||
// Unencrypted, we can directly access data
|
||||
return true;
|
||||
} else {
|
||||
// Encrypted, check whether vold is started
|
||||
return !getprop("init.svc.vold").empty();
|
||||
}
|
||||
}
|
||||
// ro.crypto.state is not set, assume it's unencrypted
|
||||
return true;
|
||||
}
|
||||
|
||||
void unlock_blocks() {
|
||||
int fd, dev, OFF = 0;
|
||||
|
||||
auto dir = xopen_dir("/dev/block");
|
||||
if (!dir)
|
||||
return;
|
||||
dev = dirfd(dir.get());
|
||||
|
||||
for (dirent *entry; (entry = readdir(dir.get()));) {
|
||||
if (entry->d_type == DT_BLK) {
|
||||
if ((fd = openat(dev, entry->d_name, O_RDONLY | O_CLOEXEC)) < 0)
|
||||
continue;
|
||||
if (ioctl(fd, BLKROSET, &OFF) < 0)
|
||||
PLOGE("unlock %s", entry->d_name);
|
||||
close(fd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#define test_bit(bit, array) (array[bit / 8] & (1 << (bit % 8)))
|
||||
|
||||
static bool check_key_combo() {
|
||||
uint8_t bitmask[(KEY_MAX + 1) / 8];
|
||||
vector<int> events;
|
||||
constexpr char name[] = "/dev/.ev";
|
||||
|
||||
// First collect candidate events that accepts volume down
|
||||
for (int minor = 64; minor < 96; ++minor) {
|
||||
if (xmknod(name, S_IFCHR | 0444, makedev(13, minor)))
|
||||
continue;
|
||||
int fd = open(name, O_RDONLY | O_CLOEXEC);
|
||||
unlink(name);
|
||||
if (fd < 0)
|
||||
continue;
|
||||
memset(bitmask, 0, sizeof(bitmask));
|
||||
ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(bitmask)), bitmask);
|
||||
if (test_bit(KEY_VOLUMEDOWN, bitmask))
|
||||
events.push_back(fd);
|
||||
else
|
||||
close(fd);
|
||||
}
|
||||
if (events.empty())
|
||||
return false;
|
||||
|
||||
run_finally fin([&]{ std::for_each(events.begin(), events.end(), close); });
|
||||
|
||||
// Check if volume down key is held continuously for more than 3 seconds
|
||||
for (int i = 0; i < 300; ++i) {
|
||||
bool pressed = false;
|
||||
for (const int &fd : events) {
|
||||
memset(bitmask, 0, sizeof(bitmask));
|
||||
ioctl(fd, EVIOCGKEY(sizeof(bitmask)), bitmask);
|
||||
if (test_bit(KEY_VOLUMEDOWN, bitmask)) {
|
||||
pressed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!pressed)
|
||||
return false;
|
||||
// Check every 10ms
|
||||
usleep(10000);
|
||||
}
|
||||
LOGD("KEY_VOLUMEDOWN detected: enter safe mode\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
/***********************
|
||||
* Boot Stage Handlers *
|
||||
***********************/
|
||||
|
||||
static pthread_mutex_t stage_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
extern int disable_deny();
|
||||
|
||||
void post_fs_data(int client) {
|
||||
close(client);
|
||||
|
||||
mutex_guard lock(stage_lock);
|
||||
|
||||
if (getenv("REMOUNT_ROOT"))
|
||||
xmount(nullptr, "/", nullptr, MS_REMOUNT | MS_RDONLY, nullptr);
|
||||
|
||||
if (!check_data())
|
||||
goto unblock_init;
|
||||
|
||||
DAEMON_STATE = STATE_POST_FS_DATA;
|
||||
setup_logfile(true);
|
||||
|
||||
LOGI("** post-fs-data mode running\n");
|
||||
|
||||
unlock_blocks();
|
||||
mount_mirrors();
|
||||
prune_su_access();
|
||||
|
||||
if (access(SECURE_DIR, F_OK) != 0) {
|
||||
if (SDK_INT < 24) {
|
||||
// There is no FBE pre 7.0, we can directly create the folder without issues
|
||||
xmkdir(SECURE_DIR, 0700);
|
||||
} else {
|
||||
// If the folder is not automatically created by Android,
|
||||
// do NOT proceed further. Manual creation of the folder
|
||||
// will have no encryption flag, which will cause bootloops on FBE devices.
|
||||
LOGE(SECURE_DIR " is not present, abort\n");
|
||||
goto early_abort;
|
||||
}
|
||||
}
|
||||
|
||||
if (!magisk_env()) {
|
||||
LOGE("* Magisk environment incomplete, abort\n");
|
||||
goto early_abort;
|
||||
}
|
||||
|
||||
if (getprop("persist.sys.safemode", true) == "1" || check_key_combo()) {
|
||||
safe_mode = true;
|
||||
// Disable all modules and denylist so next boot will be clean
|
||||
disable_modules();
|
||||
disable_deny();
|
||||
} else {
|
||||
exec_common_scripts("post-fs-data");
|
||||
db_settings dbs;
|
||||
get_db_settings(dbs, ZYGISK_CONFIG);
|
||||
zygisk_enabled = dbs[ZYGISK_CONFIG];
|
||||
initialize_denylist();
|
||||
handle_modules();
|
||||
}
|
||||
|
||||
early_abort:
|
||||
// We still do magic mount because root itself might need it
|
||||
magic_mount();
|
||||
DAEMON_STATE = STATE_POST_FS_DATA_DONE;
|
||||
|
||||
unblock_init:
|
||||
close(xopen(UNBLOCKFILE, O_RDONLY | O_CREAT, 0));
|
||||
}
|
||||
|
||||
void late_start(int client) {
|
||||
close(client);
|
||||
|
||||
mutex_guard lock(stage_lock);
|
||||
run_finally fin([]{ DAEMON_STATE = STATE_LATE_START_DONE; });
|
||||
setup_logfile(false);
|
||||
|
||||
LOGI("** late_start service mode running\n");
|
||||
|
||||
if (DAEMON_STATE < STATE_POST_FS_DATA_DONE || safe_mode)
|
||||
return;
|
||||
|
||||
exec_common_scripts("service");
|
||||
exec_module_scripts("service");
|
||||
}
|
||||
|
||||
void boot_complete(int client) {
|
||||
close(client);
|
||||
|
||||
mutex_guard lock(stage_lock);
|
||||
DAEMON_STATE = STATE_BOOT_COMPLETE;
|
||||
setup_logfile(false);
|
||||
|
||||
LOGI("** boot-complete triggered\n");
|
||||
|
||||
if (safe_mode)
|
||||
return;
|
||||
|
||||
// At this point it's safe to create the folder
|
||||
if (access(SECURE_DIR, F_OK) != 0)
|
||||
xmkdir(SECURE_DIR, 0700);
|
||||
|
||||
// Ensure manager exists
|
||||
check_pkg_refresh();
|
||||
get_manager(0, nullptr, true);
|
||||
}
|
||||
|
||||
void zygote_restart(int client) {
|
||||
close(client);
|
||||
|
||||
LOGI("** zygote restarted\n");
|
||||
pkg_xml_ino = 0;
|
||||
prune_su_access();
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
#include <base.hpp>
|
||||
|
||||
using namespace std;
|
||||
|
||||
#define APK_SIGNING_BLOCK_MAGIC "APK Sig Block 42"
|
||||
#define SIGNATURE_SCHEME_V2_MAGIC 0x7109871a
|
||||
#define EOCD_MAGIC 0x6054b50
|
||||
|
||||
// Top-level block container
|
||||
struct signing_block {
|
||||
uint64_t block_sz;
|
||||
|
||||
struct id_value_pair {
|
||||
uint64_t len;
|
||||
struct /* v2_signature */ {
|
||||
uint32_t id;
|
||||
uint8_t value[0]; // size = (len - 4)
|
||||
};
|
||||
} id_value_pair_sequence[0];
|
||||
|
||||
uint64_t block_sz_; // *MUST* be same as block_sz
|
||||
char magic[16]; // "APK Sig Block 42"
|
||||
};
|
||||
|
||||
struct len_prefixed {
|
||||
uint32_t len;
|
||||
};
|
||||
|
||||
// Generic length prefixed raw data
|
||||
struct len_prefixed_value : public len_prefixed {
|
||||
uint8_t value[0];
|
||||
};
|
||||
|
||||
// V2 Signature Block
|
||||
struct v2_signature {
|
||||
uint32_t id; // 0x7109871a
|
||||
uint32_t signer_sequence_len;
|
||||
struct signer : public len_prefixed {
|
||||
struct signed_data : public len_prefixed {
|
||||
uint32_t digest_sequence_len;
|
||||
struct : public len_prefixed {
|
||||
uint32_t algorithm;
|
||||
len_prefixed_value digest;
|
||||
} digest_sequence[0];
|
||||
|
||||
uint32_t certificate_sequence_len;
|
||||
len_prefixed_value certificate_sequence[0];
|
||||
|
||||
uint32_t attribute_sequence_len;
|
||||
struct attribute : public len_prefixed {
|
||||
uint32_t id;
|
||||
uint8_t value[0]; // size = (len - 4)
|
||||
} attribute_sequence[0];
|
||||
} signed_data;
|
||||
|
||||
uint32_t signature_sequence_len;
|
||||
struct : public len_prefixed {
|
||||
uint32_t id;
|
||||
len_prefixed_value signature;
|
||||
} signature_sequence[0];
|
||||
|
||||
len_prefixed_value public_key;
|
||||
} signer_sequence[0];
|
||||
};
|
||||
|
||||
// End of central directory record
|
||||
struct EOCD {
|
||||
uint32_t magic; // 0x6054b50
|
||||
uint8_t pad[8]; // 8 bytes of irrelevant data
|
||||
uint32_t central_dir_sz; // size of central directory
|
||||
uint32_t central_dir_off; // offset of central directory
|
||||
uint16_t comment_sz; // size of comment
|
||||
char comment[0];
|
||||
} __attribute__((packed));
|
||||
|
||||
/*
|
||||
* A v2/v3 signed APK has the format as following
|
||||
*
|
||||
* +---------------+
|
||||
* | zip content |
|
||||
* +---------------+
|
||||
* | signing block |
|
||||
* +---------------+
|
||||
* | central dir |
|
||||
* +---------------+
|
||||
* | EOCD |
|
||||
* +---------------+
|
||||
*
|
||||
* Scan from end of file to find EOCD, and figure our way back to the
|
||||
* offset of the signing block. Next, directly extract the certificate
|
||||
* from the v2 signature block.
|
||||
*
|
||||
* All structures above are mostly just for documentation purpose.
|
||||
*
|
||||
* This method extracts the first certificate of the first signer
|
||||
* within the APK v2 signature block.
|
||||
*/
|
||||
string read_certificate(int fd, int version) {
|
||||
uint32_t u32;
|
||||
uint64_t u64;
|
||||
|
||||
// Find EOCD
|
||||
for (int i = 0;; i++) {
|
||||
// i is the absolute offset to end of file
|
||||
uint16_t comment_sz = 0;
|
||||
xlseek(fd, -static_cast<off_t>(sizeof(comment_sz)) - i, SEEK_END);
|
||||
xxread(fd, &comment_sz, sizeof(comment_sz));
|
||||
if (comment_sz == i) {
|
||||
// Double check if we actually found the structure
|
||||
xlseek(fd, -static_cast<off_t>(sizeof(EOCD)), SEEK_CUR);
|
||||
uint32_t magic = 0;
|
||||
xxread(fd, &magic, sizeof(magic));
|
||||
if (magic == EOCD_MAGIC) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i == 0xffff) {
|
||||
// Comments cannot be longer than 0xffff (overflow), abort
|
||||
LOGE("cert: invalid APK format\n");
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
// We are now at EOCD + sizeof(magic)
|
||||
// Seek and read central_dir_off to find start of central directory
|
||||
uint32_t central_dir_off = 0;
|
||||
{
|
||||
constexpr off_t off = offsetof(EOCD, central_dir_off) - sizeof(EOCD::magic);
|
||||
xlseek(fd, off, SEEK_CUR);
|
||||
}
|
||||
xxread(fd, ¢ral_dir_off, sizeof(central_dir_off));
|
||||
|
||||
// Parse APK comment to get version code
|
||||
if (version >= 0) {
|
||||
xlseek(fd, sizeof(EOCD::comment_sz), SEEK_CUR);
|
||||
FILE *fp = fdopen(fd, "r"); // DO NOT close this file pointer
|
||||
int apk_ver = -1;
|
||||
parse_prop_file(fp, [&](string_view key, string_view value) -> bool {
|
||||
if (key == "versionCode") {
|
||||
apk_ver = parse_int(value);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
if (version > apk_ver) {
|
||||
// Enforce the magisk app to always be newer than magiskd
|
||||
LOGE("cert: APK version too low\n");
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
// Next, find the start of the APK signing block
|
||||
{
|
||||
constexpr int off = sizeof(signing_block::block_sz_) + sizeof(signing_block::magic);
|
||||
xlseek(fd, (off_t) (central_dir_off - off), SEEK_SET);
|
||||
}
|
||||
xxread(fd, &u64, sizeof(u64)); // u64 = block_sz_
|
||||
char magic[sizeof(signing_block::magic)] = {0};
|
||||
xxread(fd, magic, sizeof(magic));
|
||||
if (memcmp(magic, APK_SIGNING_BLOCK_MAGIC, sizeof(magic)) != 0) {
|
||||
// Invalid signing block magic, abort
|
||||
LOGE("cert: invalid signing block magic\n");
|
||||
return {};
|
||||
}
|
||||
uint64_t signing_blk_sz = 0;
|
||||
xlseek(fd, -static_cast<off_t>(u64 + sizeof(signing_blk_sz)), SEEK_CUR);
|
||||
xxread(fd, &signing_blk_sz, sizeof(signing_blk_sz));
|
||||
if (signing_blk_sz != u64) {
|
||||
// block_sz != block_sz_, invalid signing block format, abort
|
||||
LOGE("cert: invalid signing block format\n");
|
||||
return {};
|
||||
}
|
||||
|
||||
// Finally, we are now at the beginning of the id-value pair sequence
|
||||
|
||||
for (;;) {
|
||||
xxread(fd, &u64, sizeof(u64)); // id-value pair length
|
||||
if (u64 == signing_blk_sz) {
|
||||
// Outside of the id-value pair sequence; actually reading block_sz_
|
||||
break;
|
||||
}
|
||||
|
||||
uint32_t id;
|
||||
xxread(fd, &id, sizeof(id));
|
||||
if (id == SIGNATURE_SCHEME_V2_MAGIC) {
|
||||
// Skip [signer sequence length] + [1st signer length] + [signed data length]
|
||||
xlseek(fd, sizeof(uint32_t) * 3, SEEK_CUR);
|
||||
|
||||
xxread(fd, &u32, sizeof(u32)); // digest sequence length
|
||||
xlseek(fd, u32, SEEK_CUR); // skip all digests
|
||||
|
||||
xlseek(fd, sizeof(uint32_t), SEEK_CUR); // cert sequence length
|
||||
xxread(fd, &u32, sizeof(u32)); // 1st cert length
|
||||
|
||||
string cert;
|
||||
cert.resize(u32);
|
||||
xxread(fd, cert.data(), u32);
|
||||
|
||||
return cert;
|
||||
} else {
|
||||
// Skip this id-value pair
|
||||
xlseek(fd, u64 - sizeof(id), SEEK_CUR);
|
||||
}
|
||||
}
|
||||
|
||||
LOGE("cert: cannot find certificate\n");
|
||||
return {};
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
extern bool RECOVERY_MODE;
|
||||
extern int DAEMON_STATE;
|
||||
extern std::atomic<ino_t> pkg_xml_ino;
|
||||
|
||||
// Daemon state
|
||||
enum : int {
|
||||
STATE_NONE,
|
||||
STATE_POST_FS_DATA,
|
||||
STATE_POST_FS_DATA_DONE,
|
||||
STATE_LATE_START_DONE,
|
||||
STATE_BOOT_COMPLETE
|
||||
};
|
||||
|
||||
void unlock_blocks();
|
||||
void reboot();
|
||||
void start_log_daemon();
|
||||
void setup_logfile(bool reset);
|
||||
std::string read_certificate(int fd, int version = -1);
|
||||
|
||||
// Module stuffs
|
||||
void handle_modules();
|
||||
void magic_mount();
|
||||
void disable_modules();
|
||||
void remove_modules();
|
||||
void exec_module_scripts(const char *stage);
|
||||
|
||||
// Scripting
|
||||
void exec_script(const char *script);
|
||||
void exec_common_scripts(const char *stage);
|
||||
void exec_module_scripts(const char *stage, const std::vector<std::string_view> &modules);
|
||||
void install_apk(const char *apk);
|
||||
void uninstall_pkg(const char *pkg);
|
||||
void clear_pkg(const char *pkg, int user_id);
|
||||
[[noreturn]] void install_module(const char *file);
|
||||
@@ -0,0 +1,461 @@
|
||||
#include <csignal>
|
||||
#include <libgen.h>
|
||||
#include <sys/un.h>
|
||||
#include <sys/mount.h>
|
||||
|
||||
#include <magisk.hpp>
|
||||
#include <base.hpp>
|
||||
#include <daemon.hpp>
|
||||
#include <selinux.hpp>
|
||||
#include <db.hpp>
|
||||
#include <resetprop.hpp>
|
||||
#include <flags.h>
|
||||
|
||||
#include <core-rs.cpp>
|
||||
|
||||
#include "core.hpp"
|
||||
|
||||
using namespace std;
|
||||
|
||||
int SDK_INT = -1;
|
||||
string MAGISKTMP;
|
||||
|
||||
bool RECOVERY_MODE = false;
|
||||
int DAEMON_STATE = STATE_NONE;
|
||||
|
||||
static struct stat self_st;
|
||||
|
||||
static map<int, poll_callback> *poll_map;
|
||||
static vector<pollfd> *poll_fds;
|
||||
static int poll_ctrl;
|
||||
|
||||
enum {
|
||||
POLL_CTRL_NEW,
|
||||
POLL_CTRL_RM,
|
||||
};
|
||||
|
||||
void register_poll(const pollfd *pfd, poll_callback callback) {
|
||||
if (gettid() == getpid()) {
|
||||
// On main thread, directly modify
|
||||
poll_map->try_emplace(pfd->fd, callback);
|
||||
poll_fds->emplace_back(*pfd);
|
||||
} else {
|
||||
// Send it to poll_ctrl
|
||||
write_int(poll_ctrl, POLL_CTRL_NEW);
|
||||
xwrite(poll_ctrl, pfd, sizeof(*pfd));
|
||||
xwrite(poll_ctrl, &callback, sizeof(callback));
|
||||
}
|
||||
}
|
||||
|
||||
void unregister_poll(int fd, bool auto_close) {
|
||||
if (fd < 0)
|
||||
return;
|
||||
|
||||
if (gettid() == getpid()) {
|
||||
// On main thread, directly modify
|
||||
poll_map->erase(fd);
|
||||
for (auto &poll_fd : *poll_fds) {
|
||||
if (poll_fd.fd == fd) {
|
||||
if (auto_close) {
|
||||
close(poll_fd.fd);
|
||||
}
|
||||
// Cannot modify while iterating, invalidate it instead
|
||||
// It will be removed in the next poll loop
|
||||
poll_fd.fd = -1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Send it to poll_ctrl
|
||||
write_int(poll_ctrl, POLL_CTRL_RM);
|
||||
write_int(poll_ctrl, fd);
|
||||
write_int(poll_ctrl, auto_close);
|
||||
}
|
||||
}
|
||||
|
||||
void clear_poll() {
|
||||
if (poll_fds) {
|
||||
for (auto &poll_fd : *poll_fds) {
|
||||
close(poll_fd.fd);
|
||||
}
|
||||
}
|
||||
delete poll_fds;
|
||||
delete poll_map;
|
||||
poll_fds = nullptr;
|
||||
poll_map = nullptr;
|
||||
}
|
||||
|
||||
static void poll_ctrl_handler(pollfd *pfd) {
|
||||
int code = read_int(pfd->fd);
|
||||
switch (code) {
|
||||
case POLL_CTRL_NEW: {
|
||||
pollfd new_fd;
|
||||
poll_callback cb;
|
||||
xxread(pfd->fd, &new_fd, sizeof(new_fd));
|
||||
xxread(pfd->fd, &cb, sizeof(cb));
|
||||
register_poll(&new_fd, cb);
|
||||
break;
|
||||
}
|
||||
case POLL_CTRL_RM: {
|
||||
int fd = read_int(pfd->fd);
|
||||
bool auto_close = read_int(pfd->fd);
|
||||
unregister_poll(fd, auto_close);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[[noreturn]] static void poll_loop() {
|
||||
// Register poll_ctrl
|
||||
int pipefd[2];
|
||||
xpipe2(pipefd, O_CLOEXEC);
|
||||
poll_ctrl = pipefd[1];
|
||||
pollfd poll_ctrl_pfd = { pipefd[0], POLLIN, 0 };
|
||||
register_poll(&poll_ctrl_pfd, poll_ctrl_handler);
|
||||
|
||||
for (;;) {
|
||||
if (poll(poll_fds->data(), poll_fds->size(), -1) <= 0)
|
||||
continue;
|
||||
|
||||
// MUST iterate with index because any poll_callback could add new elements to poll_fds
|
||||
for (int i = 0; i < poll_fds->size();) {
|
||||
auto &pfd = (*poll_fds)[i];
|
||||
if (pfd.revents) {
|
||||
if (pfd.revents & POLLERR || pfd.revents & POLLNVAL) {
|
||||
poll_map->erase(pfd.fd);
|
||||
poll_fds->erase(poll_fds->begin() + i);
|
||||
continue;
|
||||
}
|
||||
if (auto it = poll_map->find(pfd.fd); it != poll_map->end()) {
|
||||
it->second(&pfd);
|
||||
}
|
||||
}
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void handle_request_async(int client, int code, const sock_cred &cred) {
|
||||
switch (code) {
|
||||
case MainRequest::DENYLIST:
|
||||
denylist_handler(client, &cred);
|
||||
break;
|
||||
case MainRequest::SUPERUSER:
|
||||
su_daemon_handler(client, &cred);
|
||||
break;
|
||||
case MainRequest::POST_FS_DATA:
|
||||
post_fs_data(client);
|
||||
break;
|
||||
case MainRequest::LATE_START:
|
||||
late_start(client);
|
||||
break;
|
||||
case MainRequest::BOOT_COMPLETE:
|
||||
boot_complete(client);
|
||||
break;
|
||||
case MainRequest::ZYGOTE_RESTART:
|
||||
zygote_restart(client);
|
||||
break;
|
||||
case MainRequest::SQLITE_CMD:
|
||||
exec_sql(client);
|
||||
break;
|
||||
case MainRequest::REMOVE_MODULES:
|
||||
remove_modules();
|
||||
write_int(client, 0);
|
||||
close(client);
|
||||
reboot();
|
||||
break;
|
||||
case MainRequest::ZYGISK:
|
||||
case MainRequest::ZYGISK_PASSTHROUGH:
|
||||
zygisk_handler(client, &cred);
|
||||
break;
|
||||
default:
|
||||
__builtin_unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
static void handle_request_sync(int client, int code) {
|
||||
switch (code) {
|
||||
case MainRequest::CHECK_VERSION:
|
||||
#if MAGISK_DEBUG
|
||||
write_string(client, MAGISK_VERSION ":MAGISK:D");
|
||||
#else
|
||||
write_string(client, MAGISK_VERSION ":MAGISK:R");
|
||||
#endif
|
||||
break;
|
||||
case MainRequest::CHECK_VERSION_CODE:
|
||||
write_int(client, MAGISK_VER_CODE);
|
||||
break;
|
||||
case MainRequest::GET_PATH:
|
||||
write_string(client, MAGISKTMP.data());
|
||||
break;
|
||||
case MainRequest::START_DAEMON:
|
||||
setup_logfile(true);
|
||||
break;
|
||||
case MainRequest::STOP_DAEMON:
|
||||
denylist_handler(-1, nullptr);
|
||||
write_int(client, 0);
|
||||
// Terminate the daemon!
|
||||
exit(0);
|
||||
default:
|
||||
__builtin_unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
static bool is_client(pid_t pid) {
|
||||
// Verify caller is the same as server
|
||||
char path[32];
|
||||
sprintf(path, "/proc/%d/exe", pid);
|
||||
struct stat st{};
|
||||
return !(stat(path, &st) || st.st_dev != self_st.st_dev || st.st_ino != self_st.st_ino);
|
||||
}
|
||||
|
||||
static void handle_request(pollfd *pfd) {
|
||||
int client = xaccept4(pfd->fd, nullptr, nullptr, SOCK_CLOEXEC);
|
||||
|
||||
// Verify client credentials
|
||||
sock_cred cred;
|
||||
bool is_root;
|
||||
bool is_zygote;
|
||||
int code;
|
||||
|
||||
if (!get_client_cred(client, &cred)) {
|
||||
// Client died
|
||||
goto done;
|
||||
}
|
||||
is_root = cred.uid == AID_ROOT;
|
||||
is_zygote = cred.context == "u:r:zygote:s0";
|
||||
|
||||
if (!is_root && !is_zygote && !is_client(cred.pid)) {
|
||||
// Unsupported client state
|
||||
write_int(client, MainResponse::ACCESS_DENIED);
|
||||
goto done;
|
||||
}
|
||||
|
||||
code = read_int(client);
|
||||
if (code < 0 || code >= MainRequest::END || code == MainRequest::_SYNC_BARRIER_) {
|
||||
// Unknown request code
|
||||
goto done;
|
||||
}
|
||||
|
||||
// Check client permissions
|
||||
switch (code) {
|
||||
case MainRequest::POST_FS_DATA:
|
||||
case MainRequest::LATE_START:
|
||||
case MainRequest::BOOT_COMPLETE:
|
||||
case MainRequest::ZYGOTE_RESTART:
|
||||
case MainRequest::SQLITE_CMD:
|
||||
case MainRequest::GET_PATH:
|
||||
case MainRequest::DENYLIST:
|
||||
case MainRequest::STOP_DAEMON:
|
||||
if (!is_root) {
|
||||
write_int(client, MainResponse::ROOT_REQUIRED);
|
||||
goto done;
|
||||
}
|
||||
break;
|
||||
case MainRequest::REMOVE_MODULES:
|
||||
if (!is_root && cred.uid != AID_SHELL) {
|
||||
write_int(client, MainResponse::ACCESS_DENIED);
|
||||
goto done;
|
||||
}
|
||||
break;
|
||||
case MainRequest::ZYGISK:
|
||||
if (!is_zygote && selinux_enabled()) {
|
||||
// Invalid client context
|
||||
write_int(client, MainResponse::ACCESS_DENIED);
|
||||
goto done;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
write_int(client, MainResponse::OK);
|
||||
|
||||
if (code < MainRequest::_SYNC_BARRIER_) {
|
||||
handle_request_sync(client, code);
|
||||
goto done;
|
||||
}
|
||||
|
||||
// Handle async requests in another thread
|
||||
exec_task([=] { handle_request_async(client, code, cred); });
|
||||
return;
|
||||
|
||||
done:
|
||||
close(client);
|
||||
}
|
||||
|
||||
static void switch_cgroup(const char *cgroup, int pid) {
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof(buf), "%s/cgroup.procs", cgroup);
|
||||
if (access(buf, F_OK) != 0)
|
||||
return;
|
||||
int fd = xopen(buf, O_WRONLY | O_APPEND | O_CLOEXEC);
|
||||
if (fd == -1)
|
||||
return;
|
||||
snprintf(buf, sizeof(buf), "%d\n", pid);
|
||||
xwrite(fd, buf, strlen(buf));
|
||||
close(fd);
|
||||
}
|
||||
|
||||
static void daemon_entry() {
|
||||
magisk_logging();
|
||||
|
||||
// Block all signals
|
||||
sigset_t block_set;
|
||||
sigfillset(&block_set);
|
||||
pthread_sigmask(SIG_SETMASK, &block_set, nullptr);
|
||||
|
||||
// Change process name
|
||||
set_nice_name("magiskd");
|
||||
|
||||
int fd = xopen("/dev/null", O_WRONLY);
|
||||
xdup2(fd, STDOUT_FILENO);
|
||||
xdup2(fd, STDERR_FILENO);
|
||||
if (fd > STDERR_FILENO)
|
||||
close(fd);
|
||||
fd = xopen("/dev/zero", O_RDONLY);
|
||||
xdup2(fd, STDIN_FILENO);
|
||||
if (fd > STDERR_FILENO)
|
||||
close(fd);
|
||||
|
||||
setsid();
|
||||
setcon("u:r:" SEPOL_PROC_DOMAIN ":s0");
|
||||
|
||||
start_log_daemon();
|
||||
|
||||
LOGI(NAME_WITH_VER(Magisk) " daemon started\n");
|
||||
|
||||
// Escape from cgroup
|
||||
int pid = getpid();
|
||||
switch_cgroup("/acct", pid);
|
||||
switch_cgroup("/dev/cg2_bpf", pid);
|
||||
switch_cgroup("/sys/fs/cgroup", pid);
|
||||
if (getprop("ro.config.per_app_memcg") != "false") {
|
||||
switch_cgroup("/dev/memcg/apps", pid);
|
||||
}
|
||||
|
||||
// Get self stat
|
||||
char buf[64];
|
||||
xreadlink("/proc/self/exe", buf, sizeof(buf));
|
||||
MAGISKTMP = dirname(buf);
|
||||
xstat("/proc/self/exe", &self_st);
|
||||
|
||||
// Get API level
|
||||
parse_prop_file("/system/build.prop", [](auto key, auto val) -> bool {
|
||||
if (key == "ro.build.version.sdk") {
|
||||
SDK_INT = parse_int(val);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
if (SDK_INT < 0) {
|
||||
// In case some devices do not store this info in build.prop, fallback to getprop
|
||||
auto sdk = getprop("ro.build.version.sdk");
|
||||
if (!sdk.empty()) {
|
||||
SDK_INT = parse_int(sdk);
|
||||
}
|
||||
}
|
||||
LOGI("* Device API level: %d\n", SDK_INT);
|
||||
|
||||
restore_tmpcon();
|
||||
|
||||
// SAR cleanups
|
||||
auto mount_list = MAGISKTMP + "/" ROOTMNT;
|
||||
if (access(mount_list.data(), F_OK) == 0) {
|
||||
file_readline(true, mount_list.data(), [](string_view line) -> bool {
|
||||
umount2(line.data(), MNT_DETACH);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
rm_rf((MAGISKTMP + "/" ROOTOVL).data());
|
||||
|
||||
// Load config status
|
||||
auto config = MAGISKTMP + "/" INTLROOT "/config";
|
||||
parse_prop_file(config.data(), [](auto key, auto val) -> bool {
|
||||
if (key == "RECOVERYMODE" && val == "true")
|
||||
RECOVERY_MODE = true;
|
||||
return true;
|
||||
});
|
||||
|
||||
// Use isolated devpts if kernel support
|
||||
if (access("/dev/pts/ptmx", F_OK) == 0) {
|
||||
auto pts = MAGISKTMP + "/" SHELLPTS;
|
||||
if (access(pts.data(), F_OK)) {
|
||||
xmkdirs(pts.data(), 0755);
|
||||
xmount("devpts", pts.data(), "devpts",
|
||||
MS_NOSUID | MS_NOEXEC, "newinstance");
|
||||
auto ptmx = pts + "/ptmx";
|
||||
if (access(ptmx.data(), F_OK)) {
|
||||
xumount(pts.data());
|
||||
rmdir(pts.data());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sockaddr_un sun{};
|
||||
socklen_t len = setup_sockaddr(&sun, MAIN_SOCKET);
|
||||
fd = xsocket(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0);
|
||||
if (xbind(fd, (sockaddr *) &sun, len))
|
||||
exit(1);
|
||||
xlisten(fd, 10);
|
||||
|
||||
default_new(poll_map);
|
||||
default_new(poll_fds);
|
||||
default_new(module_list);
|
||||
|
||||
// Register handler for main socket
|
||||
pollfd main_socket_pfd = { fd, POLLIN, 0 };
|
||||
register_poll(&main_socket_pfd, handle_request);
|
||||
|
||||
// Loop forever to listen for requests
|
||||
poll_loop();
|
||||
}
|
||||
|
||||
int connect_daemon(int req, bool create) {
|
||||
sockaddr_un sun{};
|
||||
socklen_t len = setup_sockaddr(&sun, MAIN_SOCKET);
|
||||
int fd = xsocket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
|
||||
if (connect(fd, (sockaddr *) &sun, len)) {
|
||||
if (!create || getuid() != AID_ROOT) {
|
||||
LOGE("No daemon is currently running!\n");
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
char buf[64];
|
||||
xreadlink("/proc/self/exe", buf, sizeof(buf));
|
||||
if (str_starts(buf, "/system/bin/")) {
|
||||
LOGE("Start daemon on /dev or /sbin\n");
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (fork_dont_care() == 0) {
|
||||
close(fd);
|
||||
daemon_entry();
|
||||
}
|
||||
|
||||
while (connect(fd, (struct sockaddr *) &sun, len))
|
||||
usleep(10000);
|
||||
}
|
||||
write_int(fd, req);
|
||||
int res = read_int(fd);
|
||||
if (res < MainResponse::ERROR || res >= MainResponse::END)
|
||||
res = MainResponse::ERROR;
|
||||
switch (res) {
|
||||
case MainResponse::OK:
|
||||
break;
|
||||
case MainResponse::ERROR:
|
||||
LOGE("Daemon error\n");
|
||||
exit(-1);
|
||||
case MainResponse::ROOT_REQUIRED:
|
||||
LOGE("Root is required for this operation\n");
|
||||
exit(-1);
|
||||
case MainResponse::ACCESS_DENIED:
|
||||
LOGE("Access denied\n");
|
||||
exit(-1);
|
||||
default:
|
||||
__builtin_unreachable();
|
||||
}
|
||||
return fd;
|
||||
}
|
||||
@@ -0,0 +1,397 @@
|
||||
#include <unistd.h>
|
||||
#include <dlfcn.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <magisk.hpp>
|
||||
#include <db.hpp>
|
||||
#include <socket.hpp>
|
||||
#include <base.hpp>
|
||||
|
||||
#define DB_VERSION 12
|
||||
|
||||
using namespace std;
|
||||
|
||||
struct sqlite3;
|
||||
|
||||
static sqlite3 *mDB = nullptr;
|
||||
|
||||
#define DBLOGV(...)
|
||||
//#define DBLOGV(...) LOGD("magiskdb: " __VA_ARGS__)
|
||||
|
||||
// SQLite APIs
|
||||
|
||||
#define SQLITE_OPEN_READWRITE 0x00000002 /* Ok for sqlite3_open_v2() */
|
||||
#define SQLITE_OPEN_CREATE 0x00000004 /* Ok for sqlite3_open_v2() */
|
||||
#define SQLITE_OPEN_FULLMUTEX 0x00010000 /* Ok for sqlite3_open_v2() */
|
||||
|
||||
static int (*sqlite3_open_v2)(
|
||||
const char *filename,
|
||||
sqlite3 **ppDb,
|
||||
int flags,
|
||||
const char *zVfs);
|
||||
static const char *(*sqlite3_errmsg)(sqlite3 *db);
|
||||
static int (*sqlite3_close)(sqlite3 *db);
|
||||
static void (*sqlite3_free)(void *v);
|
||||
static int (*sqlite3_exec)(
|
||||
sqlite3 *db,
|
||||
const char *sql,
|
||||
int (*callback)(void*, int, char**, char**),
|
||||
void *v,
|
||||
char **errmsg);
|
||||
|
||||
// Internal Android linker APIs
|
||||
|
||||
static void (*android_get_LD_LIBRARY_PATH)(char *buffer, size_t buffer_size);
|
||||
static void (*android_update_LD_LIBRARY_PATH)(const char *ld_library_path);
|
||||
|
||||
#define DLERR(ptr) if (!(ptr)) { \
|
||||
LOGE("db: %s\n", dlerror()); \
|
||||
return false; \
|
||||
}
|
||||
|
||||
#define DLOAD(handle, arg) {\
|
||||
auto f = dlsym(handle, #arg); \
|
||||
DLERR(f) \
|
||||
*(void **) &(arg) = f; \
|
||||
}
|
||||
|
||||
#ifdef __LP64__
|
||||
constexpr char apex_path[] = "/apex/com.android.runtime/lib64:/apex/com.android.art/lib64:/apex/com.android.i18n/lib64:";
|
||||
#else
|
||||
constexpr char apex_path[] = "/apex/com.android.runtime/lib:/apex/com.android.art/lib:/apex/com.android.i18n/lib:";
|
||||
#endif
|
||||
|
||||
static int dl_init = 0;
|
||||
|
||||
static bool dload_sqlite() {
|
||||
if (dl_init)
|
||||
return dl_init > 0;
|
||||
dl_init = -1;
|
||||
|
||||
auto sqlite = dlopen("libsqlite.so", RTLD_LAZY);
|
||||
if (!sqlite) {
|
||||
// Should only happen on Android 10+
|
||||
auto dl = dlopen("libdl_android.so", RTLD_LAZY);
|
||||
DLERR(dl);
|
||||
|
||||
DLOAD(dl, android_get_LD_LIBRARY_PATH);
|
||||
DLOAD(dl, android_update_LD_LIBRARY_PATH);
|
||||
|
||||
// Inject APEX into LD_LIBRARY_PATH
|
||||
char ld_path[4096];
|
||||
memcpy(ld_path, apex_path, sizeof(apex_path));
|
||||
constexpr int len = sizeof(apex_path) - 1;
|
||||
android_get_LD_LIBRARY_PATH(ld_path + len, sizeof(ld_path) - len);
|
||||
android_update_LD_LIBRARY_PATH(ld_path);
|
||||
sqlite = dlopen("libsqlite.so", RTLD_LAZY);
|
||||
|
||||
// Revert LD_LIBRARY_PATH just in case
|
||||
android_update_LD_LIBRARY_PATH(ld_path + len);
|
||||
}
|
||||
DLERR(sqlite);
|
||||
|
||||
DLOAD(sqlite, sqlite3_open_v2);
|
||||
DLOAD(sqlite, sqlite3_errmsg);
|
||||
DLOAD(sqlite, sqlite3_close);
|
||||
DLOAD(sqlite, sqlite3_exec);
|
||||
DLOAD(sqlite, sqlite3_free);
|
||||
|
||||
dl_init = 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
int db_strings::get_idx(string_view key) const {
|
||||
int idx = 0;
|
||||
for (const char *k : DB_STRING_KEYS) {
|
||||
if (key == k)
|
||||
break;
|
||||
++idx;
|
||||
}
|
||||
return idx;
|
||||
}
|
||||
|
||||
db_settings::db_settings() {
|
||||
// Default settings
|
||||
data[ROOT_ACCESS] = ROOT_ACCESS_APPS_AND_ADB;
|
||||
data[SU_MULTIUSER_MODE] = MULTIUSER_MODE_OWNER_ONLY;
|
||||
data[SU_MNT_NS] = NAMESPACE_MODE_REQUESTER;
|
||||
data[DENYLIST_CONFIG] = false;
|
||||
data[ZYGISK_CONFIG] = false;
|
||||
}
|
||||
|
||||
int db_settings::get_idx(string_view key) const {
|
||||
int idx = 0;
|
||||
for (const char *k : DB_SETTING_KEYS) {
|
||||
if (key == k)
|
||||
break;
|
||||
++idx;
|
||||
}
|
||||
return idx;
|
||||
}
|
||||
|
||||
static int ver_cb(void *ver, int, char **data, char **) {
|
||||
*((int *) ver) = parse_int(data[0]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define err_ret(e) if (e) return e;
|
||||
|
||||
static char *open_and_init_db(sqlite3 *&db) {
|
||||
if (!dload_sqlite())
|
||||
return strdup("Cannot load libsqlite.so");
|
||||
|
||||
int ret = sqlite3_open_v2(MAGISKDB, &db,
|
||||
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX, nullptr);
|
||||
if (ret)
|
||||
return strdup(sqlite3_errmsg(db));
|
||||
int ver = 0;
|
||||
bool upgrade = false;
|
||||
char *err = nullptr;
|
||||
sqlite3_exec(db, "PRAGMA user_version", ver_cb, &ver, &err);
|
||||
err_ret(err);
|
||||
if (ver > DB_VERSION) {
|
||||
// Don't support downgrading database
|
||||
sqlite3_close(db);
|
||||
return strdup("Downgrading database is not supported");
|
||||
}
|
||||
|
||||
auto create_policy = [&] {
|
||||
sqlite3_exec(db,
|
||||
"CREATE TABLE IF NOT EXISTS policies "
|
||||
"(uid INT, policy INT, until INT, logging INT, "
|
||||
"notification INT, PRIMARY KEY(uid))",
|
||||
nullptr, nullptr, &err);
|
||||
};
|
||||
auto create_settings = [&] {
|
||||
sqlite3_exec(db,
|
||||
"CREATE TABLE IF NOT EXISTS settings "
|
||||
"(key TEXT, value INT, PRIMARY KEY(key))",
|
||||
nullptr, nullptr, &err);
|
||||
};
|
||||
auto create_strings = [&] {
|
||||
sqlite3_exec(db,
|
||||
"CREATE TABLE IF NOT EXISTS strings "
|
||||
"(key TEXT, value TEXT, PRIMARY KEY(key))",
|
||||
nullptr, nullptr, &err);
|
||||
};
|
||||
auto create_denylist = [&] {
|
||||
sqlite3_exec(db,
|
||||
"CREATE TABLE IF NOT EXISTS denylist "
|
||||
"(package_name TEXT, process TEXT, PRIMARY KEY(package_name, process))",
|
||||
nullptr, nullptr, &err);
|
||||
};
|
||||
|
||||
// Database changelog:
|
||||
//
|
||||
// 0 - 6: DB stored in app private data. There are no longer any code in the project to
|
||||
// migrate these data, so no need to take any of these versions into consideration.
|
||||
// 7 : create table `hidelist` (process TEXT, PRIMARY KEY(process))
|
||||
// 8 : add new column (package_name TEXT) to table `hidelist`
|
||||
// 9 : rebuild table `hidelist` to change primary key (PRIMARY KEY(package_name, process))
|
||||
// 10: remove table `logs`
|
||||
// 11: remove table `hidelist` and create table `denylist` (same data structure)
|
||||
// 12: rebuild table `policies` to drop column `package_name`
|
||||
|
||||
if (/* 0, 1, 2, 3, 4, 5, 6 */ ver <= 6) {
|
||||
create_policy();
|
||||
err_ret(err);
|
||||
create_settings();
|
||||
err_ret(err);
|
||||
create_strings();
|
||||
err_ret(err);
|
||||
create_denylist();
|
||||
err_ret(err);
|
||||
|
||||
// Directly jump to latest
|
||||
ver = DB_VERSION;
|
||||
upgrade = true;
|
||||
}
|
||||
if (ver == 7) {
|
||||
sqlite3_exec(db,
|
||||
"BEGIN TRANSACTION;"
|
||||
"ALTER TABLE hidelist RENAME TO hidelist_tmp;"
|
||||
"CREATE TABLE IF NOT EXISTS hidelist "
|
||||
"(package_name TEXT, process TEXT, PRIMARY KEY(package_name, process));"
|
||||
"INSERT INTO hidelist SELECT process as package_name, process FROM hidelist_tmp;"
|
||||
"DROP TABLE hidelist_tmp;"
|
||||
"COMMIT;",
|
||||
nullptr, nullptr, &err);
|
||||
err_ret(err);
|
||||
// Directly jump to version 9
|
||||
ver = 9;
|
||||
upgrade = true;
|
||||
}
|
||||
if (ver == 8) {
|
||||
sqlite3_exec(db,
|
||||
"BEGIN TRANSACTION;"
|
||||
"ALTER TABLE hidelist RENAME TO hidelist_tmp;"
|
||||
"CREATE TABLE IF NOT EXISTS hidelist "
|
||||
"(package_name TEXT, process TEXT, PRIMARY KEY(package_name, process));"
|
||||
"INSERT INTO hidelist SELECT * FROM hidelist_tmp;"
|
||||
"DROP TABLE hidelist_tmp;"
|
||||
"COMMIT;",
|
||||
nullptr, nullptr, &err);
|
||||
err_ret(err);
|
||||
ver = 9;
|
||||
upgrade = true;
|
||||
}
|
||||
if (ver == 9) {
|
||||
sqlite3_exec(db, "DROP TABLE IF EXISTS logs", nullptr, nullptr, &err);
|
||||
err_ret(err);
|
||||
ver = 10;
|
||||
upgrade = true;
|
||||
}
|
||||
if (ver == 10) {
|
||||
sqlite3_exec(db,
|
||||
"DROP TABLE IF EXISTS hidelist;"
|
||||
"DELETE FROM settings WHERE key='magiskhide';",
|
||||
nullptr, nullptr, &err);
|
||||
err_ret(err);
|
||||
create_denylist();
|
||||
err_ret(err);
|
||||
ver = 11;
|
||||
upgrade = true;
|
||||
}
|
||||
if (ver == 11) {
|
||||
sqlite3_exec(db,
|
||||
"BEGIN TRANSACTION;"
|
||||
"ALTER TABLE policies RENAME TO policies_tmp;"
|
||||
"CREATE TABLE IF NOT EXISTS policies "
|
||||
"(uid INT, policy INT, until INT, logging INT, "
|
||||
"notification INT, PRIMARY KEY(uid));"
|
||||
"INSERT INTO policies "
|
||||
"SELECT uid, policy, until, logging, notification FROM policies_tmp;"
|
||||
"DROP TABLE policies_tmp;"
|
||||
"COMMIT;",
|
||||
nullptr, nullptr, &err);
|
||||
err_ret(err);
|
||||
ver = 12;
|
||||
upgrade = true;
|
||||
}
|
||||
|
||||
if (upgrade) {
|
||||
// Set version
|
||||
char query[32];
|
||||
sprintf(query, "PRAGMA user_version=%d", ver);
|
||||
sqlite3_exec(db, query, nullptr, nullptr, &err);
|
||||
err_ret(err);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
char *db_exec(const char *sql) {
|
||||
char *err = nullptr;
|
||||
if (mDB == nullptr) {
|
||||
err = open_and_init_db(mDB);
|
||||
db_err_cmd(err,
|
||||
// Open fails, remove and reconstruct
|
||||
unlink(MAGISKDB);
|
||||
err = open_and_init_db(mDB);
|
||||
err_ret(err);
|
||||
);
|
||||
}
|
||||
if (mDB) {
|
||||
sqlite3_exec(mDB, sql, nullptr, nullptr, &err);
|
||||
return err;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static int sqlite_db_row_callback(void *cb, int col_num, char **data, char **col_name) {
|
||||
auto &func = *static_cast<const db_row_cb*>(cb);
|
||||
db_row row;
|
||||
for (int i = 0; i < col_num; ++i)
|
||||
row[col_name[i]] = data[i];
|
||||
return func(row) ? 0 : 1;
|
||||
}
|
||||
|
||||
char *db_exec(const char *sql, const db_row_cb &fn) {
|
||||
char *err = nullptr;
|
||||
if (mDB == nullptr) {
|
||||
err = open_and_init_db(mDB);
|
||||
db_err_cmd(err,
|
||||
// Open fails, remove and reconstruct
|
||||
unlink(MAGISKDB);
|
||||
err = open_and_init_db(mDB);
|
||||
err_ret(err);
|
||||
);
|
||||
}
|
||||
if (mDB) {
|
||||
sqlite3_exec(mDB, sql, sqlite_db_row_callback, (void *) &fn, &err);
|
||||
return err;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int get_db_settings(db_settings &cfg, int key) {
|
||||
char *err = nullptr;
|
||||
auto settings_cb = [&](db_row &row) -> bool {
|
||||
cfg[row["key"]] = parse_int(row["value"]);
|
||||
DBLOGV("query %s=[%s]\n", row["key"].data(), row["value"].data());
|
||||
return true;
|
||||
};
|
||||
if (key >= 0) {
|
||||
char query[128];
|
||||
snprintf(query, sizeof(query), "SELECT * FROM settings WHERE key='%s'", DB_SETTING_KEYS[key]);
|
||||
err = db_exec(query, settings_cb);
|
||||
} else {
|
||||
err = db_exec("SELECT * FROM settings", settings_cb);
|
||||
}
|
||||
db_err_cmd(err, return 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int get_db_strings(db_strings &str, int key) {
|
||||
char *err = nullptr;
|
||||
auto string_cb = [&](db_row &row) -> bool {
|
||||
str[row["key"]] = row["value"];
|
||||
DBLOGV("query %s=[%s]\n", row["key"].data(), row["value"].data());
|
||||
return true;
|
||||
};
|
||||
if (key >= 0) {
|
||||
char query[128];
|
||||
snprintf(query, sizeof(query), "SELECT * FROM strings WHERE key='%s'", DB_STRING_KEYS[key]);
|
||||
err = db_exec(query, string_cb);
|
||||
} else {
|
||||
err = db_exec("SELECT * FROM strings", string_cb);
|
||||
}
|
||||
db_err_cmd(err, return 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void rm_db_strings(int key) {
|
||||
char *err;
|
||||
char query[128];
|
||||
snprintf(query, sizeof(query), "DELETE FROM strings WHERE key == '%s'", DB_STRING_KEYS[key]);
|
||||
err = db_exec(query);
|
||||
db_err_cmd(err, return);
|
||||
}
|
||||
|
||||
void exec_sql(int client) {
|
||||
run_finally f([=]{ close(client); });
|
||||
string sql = read_string(client);
|
||||
char *err = db_exec(sql.data(), [client](db_row &row) -> bool {
|
||||
string out;
|
||||
bool first = true;
|
||||
for (auto it : row) {
|
||||
if (first) first = false;
|
||||
else out += '|';
|
||||
out += it.first;
|
||||
out += '=';
|
||||
out += it.second;
|
||||
}
|
||||
write_string(client, out);
|
||||
return true;
|
||||
});
|
||||
write_int(client, 0);
|
||||
db_err_cmd(err, return; );
|
||||
}
|
||||
|
||||
bool db_err(char *e) {
|
||||
if (e) {
|
||||
LOGE("sqlite3_exec: %s\n", e);
|
||||
sqlite3_free(e);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
pub use base;
|
||||
pub use logging::*;
|
||||
|
||||
mod logging;
|
||||
|
||||
#[cxx::bridge]
|
||||
pub mod ffi {
|
||||
extern "Rust" {
|
||||
fn rust_test_entry();
|
||||
fn android_logging();
|
||||
fn magisk_logging();
|
||||
fn zygisk_logging();
|
||||
}
|
||||
}
|
||||
|
||||
fn rust_test_entry() {}
|
||||
@@ -0,0 +1,159 @@
|
||||
#include <sys/uio.h>
|
||||
#include <android/log.h>
|
||||
|
||||
#include <magisk.hpp>
|
||||
#include <base.hpp>
|
||||
#include <daemon.hpp>
|
||||
#include <stream.hpp>
|
||||
|
||||
#include "core.hpp"
|
||||
|
||||
using namespace std;
|
||||
|
||||
struct log_meta {
|
||||
int prio;
|
||||
int len;
|
||||
int pid;
|
||||
int tid;
|
||||
};
|
||||
|
||||
atomic<int> logd_fd = -1;
|
||||
|
||||
void setup_logfile(bool reset) {
|
||||
if (logd_fd < 0)
|
||||
return;
|
||||
|
||||
iovec iov{};
|
||||
log_meta meta = {
|
||||
.prio = -1,
|
||||
.len = reset
|
||||
};
|
||||
|
||||
iov.iov_base = &meta;
|
||||
iov.iov_len = sizeof(meta);
|
||||
writev(logd_fd, &iov, 1);
|
||||
}
|
||||
|
||||
// Maximum message length for pipes to transfer atomically
|
||||
#define MAX_MSG_LEN (int) (PIPE_BUF - sizeof(log_meta))
|
||||
|
||||
static void *logfile_writer(void *arg) {
|
||||
int pipefd = (long) arg;
|
||||
|
||||
run_finally close_pipes([=] {
|
||||
// Close up all logging pipes when thread dies
|
||||
close(pipefd);
|
||||
close(logd_fd.exchange(-1));
|
||||
});
|
||||
|
||||
struct {
|
||||
void *data;
|
||||
size_t len;
|
||||
} tmp{};
|
||||
stream_ptr strm = make_unique<byte_stream>(tmp.data, tmp.len);
|
||||
bool switched = false;
|
||||
|
||||
log_meta meta{};
|
||||
char buf[MAX_MSG_LEN];
|
||||
char aux[64];
|
||||
|
||||
iovec iov[2];
|
||||
iov[0].iov_base = aux;
|
||||
iov[1].iov_base = buf;
|
||||
|
||||
for (;;) {
|
||||
// Read meta data
|
||||
if (read(pipefd, &meta, sizeof(meta)) != sizeof(meta))
|
||||
return nullptr;
|
||||
|
||||
if (meta.prio < 0) {
|
||||
if (!switched) {
|
||||
run_finally free_tmp([&] {
|
||||
free(tmp.data);
|
||||
tmp.data = nullptr;
|
||||
tmp.len = 0;
|
||||
});
|
||||
|
||||
rename(LOGFILE, LOGFILE ".bak");
|
||||
int fd = open(LOGFILE, O_WRONLY | O_APPEND | O_CREAT | O_CLOEXEC, 0644);
|
||||
if (fd < 0)
|
||||
return nullptr;
|
||||
if (tmp.data)
|
||||
write(fd, tmp.data, tmp.len);
|
||||
|
||||
strm = make_unique<fd_stream>(fd);
|
||||
switched = true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Read message
|
||||
if (read(pipefd, buf, meta.len) != meta.len)
|
||||
return nullptr;
|
||||
|
||||
timeval tv;
|
||||
tm tm;
|
||||
gettimeofday(&tv, nullptr);
|
||||
localtime_r(&tv.tv_sec, &tm);
|
||||
|
||||
// Format detailed info
|
||||
char type;
|
||||
switch (meta.prio) {
|
||||
case ANDROID_LOG_DEBUG:
|
||||
type = 'D';
|
||||
break;
|
||||
case ANDROID_LOG_INFO:
|
||||
type = 'I';
|
||||
break;
|
||||
case ANDROID_LOG_WARN:
|
||||
type = 'W';
|
||||
break;
|
||||
default:
|
||||
type = 'E';
|
||||
break;
|
||||
}
|
||||
long ms = tv.tv_usec / 1000;
|
||||
size_t off = strftime(aux, sizeof(aux), "%m-%d %T", &tm);
|
||||
off += snprintf(aux + off, sizeof(aux) - off,
|
||||
".%03ld %5d %5d %c : ", ms, meta.pid, meta.tid, type);
|
||||
|
||||
iov[0].iov_len = off;
|
||||
iov[1].iov_len = meta.len;
|
||||
|
||||
strm->writev(iov, 2);
|
||||
}
|
||||
}
|
||||
|
||||
void magisk_log_write(int prio, const char *msg, int len) {
|
||||
if (logd_fd >= 0) {
|
||||
// Truncate
|
||||
len = std::min(MAX_MSG_LEN, len);
|
||||
|
||||
log_meta meta = {
|
||||
.prio = prio,
|
||||
.len = len,
|
||||
.pid = getpid(),
|
||||
.tid = gettid()
|
||||
};
|
||||
|
||||
iovec iov[2];
|
||||
iov[0].iov_base = &meta;
|
||||
iov[0].iov_len = sizeof(meta);
|
||||
iov[1].iov_base = (void *) msg;
|
||||
iov[1].iov_len = len;
|
||||
|
||||
if (writev(logd_fd, iov, 2) < 0) {
|
||||
// Stop trying to write to file
|
||||
close(logd_fd.exchange(-1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void start_log_daemon() {
|
||||
int fds[2];
|
||||
if (pipe2(fds, O_CLOEXEC) == 0) {
|
||||
logd_fd = fds[1];
|
||||
long fd = fds[0];
|
||||
new_daemon_thread(logfile_writer, (void *) fd);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
use base::ffi::LogLevel;
|
||||
use base::*;
|
||||
use std::fmt::Arguments;
|
||||
|
||||
#[allow(dead_code, non_camel_case_types)]
|
||||
#[repr(i32)]
|
||||
enum ALogPriority {
|
||||
ANDROID_LOG_UNKNOWN = 0,
|
||||
ANDROID_LOG_DEFAULT,
|
||||
ANDROID_LOG_VERBOSE,
|
||||
ANDROID_LOG_DEBUG,
|
||||
ANDROID_LOG_INFO,
|
||||
ANDROID_LOG_WARN,
|
||||
ANDROID_LOG_ERROR,
|
||||
ANDROID_LOG_FATAL,
|
||||
ANDROID_LOG_SILENT,
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
fn __android_log_write(prio: i32, tag: *const u8, msg: *const u8);
|
||||
fn magisk_log_write(prio: i32, msg: *const u8, len: i32);
|
||||
fn zygisk_log_write(prio: i32, msg: *const u8, len: i32);
|
||||
}
|
||||
|
||||
fn level_to_prio(level: LogLevel) -> i32 {
|
||||
match level {
|
||||
LogLevel::Error => ALogPriority::ANDROID_LOG_ERROR as i32,
|
||||
LogLevel::Warn => ALogPriority::ANDROID_LOG_WARN as i32,
|
||||
LogLevel::Info => ALogPriority::ANDROID_LOG_INFO as i32,
|
||||
LogLevel::Debug => ALogPriority::ANDROID_LOG_DEBUG as i32,
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn android_logging() {
|
||||
fn android_log_fmt(level: LogLevel, args: Arguments) {
|
||||
let mut buf: [u8; 4096] = [0; 4096];
|
||||
fmt_to_buf(&mut buf, args);
|
||||
unsafe {
|
||||
__android_log_write(level_to_prio(level), b"Magisk\0".as_ptr(), buf.as_ptr());
|
||||
}
|
||||
}
|
||||
fn android_log_write(level: LogLevel, msg: &[u8]) {
|
||||
unsafe {
|
||||
__android_log_write(level_to_prio(level), b"Magisk\0".as_ptr(), msg.as_ptr());
|
||||
}
|
||||
}
|
||||
|
||||
let logger = Logger {
|
||||
fmt: android_log_fmt,
|
||||
write: android_log_write,
|
||||
flags: 0,
|
||||
};
|
||||
exit_on_error(false);
|
||||
unsafe {
|
||||
LOGGER = logger;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn magisk_logging() {
|
||||
fn magisk_fmt(level: LogLevel, args: Arguments) {
|
||||
let mut buf: [u8; 4096] = [0; 4096];
|
||||
let len = fmt_to_buf(&mut buf, args);
|
||||
unsafe {
|
||||
__android_log_write(level_to_prio(level), b"Magisk\0".as_ptr(), buf.as_ptr());
|
||||
magisk_log_write(level_to_prio(level), buf.as_ptr(), len as i32);
|
||||
}
|
||||
}
|
||||
fn magisk_write(level: LogLevel, msg: &[u8]) {
|
||||
unsafe {
|
||||
__android_log_write(level_to_prio(level), b"Magisk\0".as_ptr(), msg.as_ptr());
|
||||
magisk_log_write(level_to_prio(level), msg.as_ptr(), msg.len() as i32);
|
||||
}
|
||||
}
|
||||
|
||||
let logger = Logger {
|
||||
fmt: magisk_fmt,
|
||||
write: magisk_write,
|
||||
flags: 0,
|
||||
};
|
||||
exit_on_error(false);
|
||||
unsafe {
|
||||
LOGGER = logger;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn zygisk_logging() {
|
||||
fn zygisk_fmt(level: LogLevel, args: Arguments) {
|
||||
let mut buf: [u8; 4096] = [0; 4096];
|
||||
let len = fmt_to_buf(&mut buf, args);
|
||||
unsafe {
|
||||
__android_log_write(level_to_prio(level), b"Magisk\0".as_ptr(), buf.as_ptr());
|
||||
zygisk_log_write(level_to_prio(level), buf.as_ptr(), len as i32);
|
||||
}
|
||||
}
|
||||
fn zygisk_write(level: LogLevel, msg: &[u8]) {
|
||||
unsafe {
|
||||
__android_log_write(level_to_prio(level), b"Magisk\0".as_ptr(), msg.as_ptr());
|
||||
zygisk_log_write(level_to_prio(level), msg.as_ptr(), msg.len() as i32);
|
||||
}
|
||||
}
|
||||
|
||||
let logger = Logger {
|
||||
fmt: zygisk_fmt,
|
||||
write: zygisk_write,
|
||||
flags: 0,
|
||||
};
|
||||
exit_on_error(false);
|
||||
unsafe {
|
||||
LOGGER = logger;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
#include <sys/mount.h>
|
||||
#include <libgen.h>
|
||||
|
||||
#include <base.hpp>
|
||||
#include <magisk.hpp>
|
||||
#include <daemon.hpp>
|
||||
#include <selinux.hpp>
|
||||
#include <flags.h>
|
||||
|
||||
#include "core.hpp"
|
||||
|
||||
using namespace std;
|
||||
|
||||
[[noreturn]] static void usage() {
|
||||
fprintf(stderr,
|
||||
R"EOF(Magisk - Multi-purpose Utility
|
||||
|
||||
Usage: magisk [applet [arguments]...]
|
||||
or: magisk [options]...
|
||||
|
||||
Options:
|
||||
-c print current binary version
|
||||
-v print running daemon version
|
||||
-V print running daemon version code
|
||||
--list list all available applets
|
||||
--remove-modules remove all modules and reboot
|
||||
--install-module ZIP install a module zip file
|
||||
|
||||
Advanced Options (Internal APIs):
|
||||
--daemon manually start magisk daemon
|
||||
--stop remove all magisk changes and stop daemon
|
||||
--[init trigger] callback on init triggers. Valid triggers:
|
||||
post-fs-data, service, boot-complete, zygote-restart
|
||||
--unlock-blocks set BLKROSET flag to OFF for all block devices
|
||||
--restorecon restore selinux context on Magisk files
|
||||
--clone-attr SRC DEST clone permission, owner, and selinux context
|
||||
--clone SRC DEST clone SRC to DEST
|
||||
--sqlite SQL exec SQL commands to Magisk database
|
||||
--path print Magisk tmpfs mount path
|
||||
--denylist ARGS denylist config CLI
|
||||
|
||||
Available applets:
|
||||
)EOF");
|
||||
|
||||
for (int i = 0; applet_names[i]; ++i)
|
||||
fprintf(stderr, i ? ", %s" : " %s", applet_names[i]);
|
||||
fprintf(stderr, "\n\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
int magisk_main(int argc, char *argv[]) {
|
||||
if (argc < 2)
|
||||
usage();
|
||||
if (argv[1] == "-c"sv) {
|
||||
#if MAGISK_DEBUG
|
||||
printf(MAGISK_VERSION ":MAGISK:D (" str(MAGISK_VER_CODE) ")\n");
|
||||
#else
|
||||
printf(MAGISK_VERSION ":MAGISK:R (" str(MAGISK_VER_CODE) ")\n");
|
||||
#endif
|
||||
return 0;
|
||||
} else if (argv[1] == "-v"sv) {
|
||||
int fd = connect_daemon(MainRequest::CHECK_VERSION);
|
||||
string v = read_string(fd);
|
||||
printf("%s\n", v.data());
|
||||
return 0;
|
||||
} else if (argv[1] == "-V"sv) {
|
||||
int fd = connect_daemon(MainRequest::CHECK_VERSION_CODE);
|
||||
printf("%d\n", read_int(fd));
|
||||
return 0;
|
||||
} else if (argv[1] == "--list"sv) {
|
||||
for (int i = 0; applet_names[i]; ++i)
|
||||
printf("%s\n", applet_names[i]);
|
||||
return 0;
|
||||
} else if (argv[1] == "--unlock-blocks"sv) {
|
||||
unlock_blocks();
|
||||
return 0;
|
||||
} else if (argv[1] == "--restorecon"sv) {
|
||||
restorecon();
|
||||
return 0;
|
||||
} else if (argc >= 4 && argv[1] == "--clone-attr"sv) {
|
||||
clone_attr(argv[2], argv[3]);
|
||||
return 0;
|
||||
} else if (argc >= 4 && argv[1] == "--clone"sv) {
|
||||
cp_afc(argv[2], argv[3]);
|
||||
return 0;
|
||||
} else if (argv[1] == "--daemon"sv) {
|
||||
close(connect_daemon(MainRequest::START_DAEMON, true));
|
||||
return 0;
|
||||
} else if (argv[1] == "--stop"sv) {
|
||||
int fd = connect_daemon(MainRequest::STOP_DAEMON);
|
||||
return read_int(fd);
|
||||
} else if (argv[1] == "--post-fs-data"sv) {
|
||||
close(connect_daemon(MainRequest::POST_FS_DATA, true));
|
||||
return 0;
|
||||
} else if (argv[1] == "--service"sv) {
|
||||
close(connect_daemon(MainRequest::LATE_START, true));
|
||||
return 0;
|
||||
} else if (argv[1] == "--boot-complete"sv) {
|
||||
close(connect_daemon(MainRequest::BOOT_COMPLETE));
|
||||
return 0;
|
||||
} else if (argv[1] == "--zygote-restart"sv) {
|
||||
close(connect_daemon(MainRequest::ZYGOTE_RESTART));
|
||||
return 0;
|
||||
} else if (argv[1] == "--denylist"sv) {
|
||||
return denylist_cli(argc - 1, argv + 1);
|
||||
} else if (argc >= 3 && argv[1] == "--sqlite"sv) {
|
||||
int fd = connect_daemon(MainRequest::SQLITE_CMD);
|
||||
write_string(fd, argv[2]);
|
||||
string res;
|
||||
for (;;) {
|
||||
read_string(fd, res);
|
||||
if (res.empty())
|
||||
return 0;
|
||||
printf("%s\n", res.data());
|
||||
}
|
||||
} else if (argv[1] == "--remove-modules"sv) {
|
||||
int fd = connect_daemon(MainRequest::REMOVE_MODULES);
|
||||
return read_int(fd);
|
||||
} else if (argv[1] == "--path"sv) {
|
||||
int fd = connect_daemon(MainRequest::GET_PATH);
|
||||
string path = read_string(fd);
|
||||
printf("%s\n", path.data());
|
||||
return 0;
|
||||
} else if (argc >= 3 && argv[1] == "--install-module"sv) {
|
||||
install_module(argv[2]);
|
||||
}
|
||||
#if 0
|
||||
/* Entry point for testing stuffs */
|
||||
else if (argv[1] == "--test"sv) {
|
||||
rust_test_entry();
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
usage();
|
||||
}
|
||||
@@ -0,0 +1,808 @@
|
||||
#include <sys/mount.h>
|
||||
#include <map>
|
||||
#include <utility>
|
||||
|
||||
#include <base.hpp>
|
||||
#include <magisk.hpp>
|
||||
#include <daemon.hpp>
|
||||
#include <selinux.hpp>
|
||||
#include <resetprop.hpp>
|
||||
|
||||
#include "core.hpp"
|
||||
|
||||
using namespace std;
|
||||
|
||||
#define VLOGD(tag, from, to) LOGD("%-8s: %s <- %s\n", tag, to, from)
|
||||
|
||||
#define TYPE_MIRROR (1 << 0) /* mount from mirror */
|
||||
#define TYPE_INTER (1 << 1) /* intermediate node */
|
||||
#define TYPE_TMPFS (1 << 2) /* replace with tmpfs */
|
||||
#define TYPE_MODULE (1 << 3) /* mount from module */
|
||||
#define TYPE_ROOT (1 << 4) /* partition root */
|
||||
#define TYPE_CUSTOM (1 << 5) /* custom node type overrides all */
|
||||
#define TYPE_DIR (TYPE_INTER|TYPE_TMPFS|TYPE_ROOT)
|
||||
|
||||
class node_entry;
|
||||
class dir_node;
|
||||
class inter_node;
|
||||
class mirror_node;
|
||||
class tmpfs_node;
|
||||
class module_node;
|
||||
class root_node;
|
||||
|
||||
template<class T> static bool isa(node_entry *node);
|
||||
static int bind_mount(const char *from, const char *to) {
|
||||
int ret = xmount(from, to, nullptr, MS_BIND, nullptr);
|
||||
if (ret == 0)
|
||||
VLOGD("bind_mnt", from, to);
|
||||
return ret;
|
||||
}
|
||||
|
||||
template<class T> uint8_t type_id() { return TYPE_CUSTOM; }
|
||||
template<> uint8_t type_id<dir_node>() { return TYPE_DIR; }
|
||||
template<> uint8_t type_id<inter_node>() { return TYPE_INTER; }
|
||||
template<> uint8_t type_id<mirror_node>() { return TYPE_MIRROR; }
|
||||
template<> uint8_t type_id<tmpfs_node>() { return TYPE_TMPFS; }
|
||||
template<> uint8_t type_id<module_node>() { return TYPE_MODULE; }
|
||||
template<> uint8_t type_id<root_node>() { return TYPE_ROOT; }
|
||||
|
||||
class node_entry {
|
||||
public:
|
||||
virtual ~node_entry() = default;
|
||||
|
||||
// Node info
|
||||
bool is_dir() { return file_type() == DT_DIR; }
|
||||
bool is_lnk() { return file_type() == DT_LNK; }
|
||||
bool is_reg() { return file_type() == DT_REG; }
|
||||
uint8_t type() { return node_type; }
|
||||
const string &name() { return _name; }
|
||||
|
||||
// Don't call the following two functions before prepare
|
||||
const string &node_path();
|
||||
string mirror_path() { return mirror_dir + node_path(); }
|
||||
|
||||
// Tree methods
|
||||
dir_node *parent() { return _parent; }
|
||||
void merge(node_entry *other);
|
||||
|
||||
virtual void mount() = 0;
|
||||
|
||||
static string module_mnt;
|
||||
static string mirror_dir;
|
||||
|
||||
protected:
|
||||
template<class T>
|
||||
node_entry(const char *name, uint8_t file_type, T*)
|
||||
: _name(name), _file_type(file_type), node_type(type_id<T>()) {}
|
||||
|
||||
template<class T>
|
||||
explicit node_entry(T*) : _file_type(0), node_type(type_id<T>()) {}
|
||||
|
||||
void create_and_mount(const string &src);
|
||||
|
||||
// Use top bit of _file_type for node exist status
|
||||
bool exist() { return static_cast<bool>(_file_type & (1 << 7)); }
|
||||
void set_exist(bool b) { if (b) _file_type |= (1 << 7); else _file_type &= ~(1 << 7); }
|
||||
uint8_t file_type() { return static_cast<uint8_t>(_file_type & ~(1 << 7)); }
|
||||
|
||||
private:
|
||||
friend class dir_node;
|
||||
|
||||
static bool should_be_tmpfs(node_entry *child);
|
||||
|
||||
// Node properties
|
||||
string _name;
|
||||
uint8_t _file_type;
|
||||
uint8_t node_type;
|
||||
|
||||
dir_node *_parent = nullptr;
|
||||
|
||||
// Cache, it should only be used within prepare
|
||||
string _node_path;
|
||||
};
|
||||
|
||||
class dir_node : public node_entry {
|
||||
public:
|
||||
friend void node_entry::merge(node_entry *other);
|
||||
|
||||
using map_type = map<string_view, node_entry *>;
|
||||
using iterator = map_type::iterator;
|
||||
|
||||
~dir_node() override {
|
||||
for (auto &it : children)
|
||||
delete it.second;
|
||||
children.clear();
|
||||
}
|
||||
|
||||
// Return false to indicate need to upgrade to module
|
||||
bool collect_files(const char *module, int dfd);
|
||||
|
||||
// Return false to indicate need to upgrade to skeleton
|
||||
bool prepare();
|
||||
|
||||
// Default directory mount logic
|
||||
void mount() override {
|
||||
for (auto &pair : children)
|
||||
pair.second->mount();
|
||||
}
|
||||
|
||||
/***************
|
||||
* Tree Methods
|
||||
***************/
|
||||
|
||||
bool is_empty() { return children.empty(); }
|
||||
|
||||
template<class T>
|
||||
T *child(string_view name) { return iterator_to_node<T>(children.find(name)); }
|
||||
|
||||
// Lazy val
|
||||
root_node *root() {
|
||||
if (!_root)
|
||||
_root = _parent->root();
|
||||
return _root;
|
||||
}
|
||||
|
||||
// Return child with name or nullptr
|
||||
node_entry *extract(string_view name);
|
||||
|
||||
// Return false if rejected
|
||||
bool insert(node_entry *node) {
|
||||
auto fn = [=](auto) { return node; };
|
||||
return node && iterator_to_node(insert(node->_name, node->node_type, fn));
|
||||
}
|
||||
|
||||
// Return inserted node or null if rejected
|
||||
template<class T, class ...Args>
|
||||
T *emplace(string_view name, Args &&...args) {
|
||||
auto fn = [&](auto) { return new T(std::forward<Args>(args)...); };
|
||||
return iterator_to_node<T>(insert(name, type_id<T>(), fn));
|
||||
}
|
||||
|
||||
// Return inserted node, existing node with same rank, or null if rejected
|
||||
template<class T, class ...Args>
|
||||
T *emplace_or_get(string_view name, Args &&...args) {
|
||||
auto fn = [&](auto) { return new T(std::forward<Args>(args)...); };
|
||||
return iterator_to_node<T>(insert(name, type_id<T>(), fn, true));
|
||||
}
|
||||
|
||||
// Return upgraded node or null if rejected
|
||||
template<class T, class ...Args>
|
||||
T *upgrade(string_view name, Args &...args) {
|
||||
return iterator_to_node<T>(upgrade<T>(children.find(name), args...));
|
||||
}
|
||||
|
||||
protected:
|
||||
template<class T>
|
||||
dir_node(const char *name, uint8_t file_type, T *self) : node_entry(name, file_type, self) {
|
||||
if constexpr (std::is_same_v<T, root_node>)
|
||||
_root = self;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
dir_node(node_entry *node, T *self) : node_entry(self) {
|
||||
merge(node);
|
||||
if constexpr (std::is_same_v<T, root_node>)
|
||||
_root = self;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
dir_node(const char *name, T *self) : dir_node(name, DT_DIR, self) {}
|
||||
|
||||
template<class T = node_entry>
|
||||
T *iterator_to_node(iterator it) {
|
||||
return static_cast<T*>(it == children.end() ? nullptr : it->second);
|
||||
}
|
||||
|
||||
// Emplace insert a new node, or upgrade if the requested type has a higher rank.
|
||||
// Return iterator to new node or end() if insertion is rejected.
|
||||
// If get_same is true and a node with the same rank exists, it will return that node instead.
|
||||
// fn is the node construction callback. Signature: (node_ent *&) -> node_ent *
|
||||
// fn gets a reference to the existing node pointer and returns a new node object.
|
||||
// Input is null when there is no existing node. If returns null, the insertion is rejected.
|
||||
// If fn consumes the input, it should set the reference to null.
|
||||
template<typename Func>
|
||||
iterator insert(iterator it, uint8_t type, const Func &fn, bool get_same);
|
||||
|
||||
template<typename Func>
|
||||
iterator insert(string_view name, uint8_t type, const Func &fn, bool get_same = false) {
|
||||
return insert(children.find(name), type, fn, get_same);
|
||||
}
|
||||
|
||||
template<class To, class From = node_entry, class ...Args>
|
||||
iterator upgrade(iterator it, Args &&...args);
|
||||
|
||||
// dir nodes host children
|
||||
map_type children;
|
||||
|
||||
// Root node lookup cache
|
||||
root_node *_root = nullptr;
|
||||
};
|
||||
|
||||
class root_node : public dir_node {
|
||||
public:
|
||||
explicit root_node(const char *name) : dir_node(name, this), prefix("") {}
|
||||
explicit root_node(node_entry *node) : dir_node(node, this), prefix("/system") {}
|
||||
const char * const prefix;
|
||||
};
|
||||
|
||||
class inter_node : public dir_node {
|
||||
public:
|
||||
inter_node(const char *name, const char *module) : dir_node(name, this), module(module) {}
|
||||
private:
|
||||
const char *module;
|
||||
friend class module_node;
|
||||
};
|
||||
|
||||
class module_node : public node_entry {
|
||||
public:
|
||||
module_node(const char *module, dirent *entry)
|
||||
: node_entry(entry->d_name, entry->d_type, this), module(module) {}
|
||||
|
||||
module_node(node_entry *node, const char *module) : node_entry(this), module(module) {
|
||||
merge(node);
|
||||
}
|
||||
|
||||
explicit module_node(inter_node *node) : module_node(node, node->module) {}
|
||||
|
||||
void mount() override;
|
||||
private:
|
||||
const char *module;
|
||||
};
|
||||
|
||||
// Don't create the following two nodes before prepare
|
||||
class mirror_node : public node_entry {
|
||||
public:
|
||||
explicit mirror_node(dirent *entry) : node_entry(entry->d_name, entry->d_type, this) {}
|
||||
void mount() override {
|
||||
create_and_mount(mirror_path());
|
||||
}
|
||||
};
|
||||
|
||||
class tmpfs_node : public dir_node {
|
||||
public:
|
||||
explicit tmpfs_node(node_entry *node);
|
||||
void mount() override;
|
||||
};
|
||||
|
||||
// Poor man's dynamic cast without RTTI
|
||||
template<class T>
|
||||
static bool isa(node_entry *node) {
|
||||
return node && (node->type() & type_id<T>());
|
||||
}
|
||||
template<class T>
|
||||
static T *dyn_cast(node_entry *node) {
|
||||
return isa<T>(node) ? static_cast<T*>(node) : nullptr;
|
||||
}
|
||||
|
||||
string node_entry::module_mnt;
|
||||
string node_entry::mirror_dir;
|
||||
|
||||
// other will be deleted
|
||||
void node_entry::merge(node_entry *other) {
|
||||
_name.swap(other->_name);
|
||||
_file_type = other->_file_type;
|
||||
_parent = other->_parent;
|
||||
|
||||
// Merge children if both is dir
|
||||
if (auto a = dyn_cast<dir_node>(this)) {
|
||||
if (auto b = dyn_cast<dir_node>(other)) {
|
||||
a->children.merge(b->children);
|
||||
for (auto &pair : a->children)
|
||||
pair.second->_parent = a;
|
||||
}
|
||||
}
|
||||
delete other;
|
||||
}
|
||||
|
||||
const string &node_entry::node_path() {
|
||||
if (_parent && _node_path.empty())
|
||||
_node_path = _parent->node_path() + '/' + _name;
|
||||
return _node_path;
|
||||
}
|
||||
|
||||
/*************************
|
||||
* Node Tree Construction
|
||||
*************************/
|
||||
|
||||
template<typename Func>
|
||||
dir_node::iterator dir_node::insert(iterator it, uint8_t type, const Func &fn, bool get_same) {
|
||||
node_entry *node = nullptr;
|
||||
if (it != children.end()) {
|
||||
// Upgrade existing node only if higher rank
|
||||
if (it->second->node_type < type) {
|
||||
node = fn(it->second);
|
||||
if (!node)
|
||||
return children.end();
|
||||
if (it->second)
|
||||
node->merge(it->second);
|
||||
it = children.erase(it);
|
||||
// Minor optimization to make insert O(1) by using hint
|
||||
if (it == children.begin())
|
||||
it = children.emplace(node->_name, node).first;
|
||||
else
|
||||
it = children.emplace_hint(--it, node->_name, node);
|
||||
} else {
|
||||
if (get_same && it->second->node_type != type)
|
||||
return children.end();
|
||||
return it;
|
||||
}
|
||||
} else {
|
||||
node = fn(node);
|
||||
if (!node)
|
||||
return children.end();
|
||||
node->_parent = this;
|
||||
it = children.emplace(node->_name, node).first;
|
||||
}
|
||||
return it;
|
||||
}
|
||||
|
||||
template<class To, class From, class... Args>
|
||||
dir_node::iterator dir_node::upgrade(iterator it, Args &&... args) {
|
||||
return insert(it, type_id<To>(), [&](node_entry *&ex) -> node_entry * {
|
||||
if (!ex)
|
||||
return nullptr;
|
||||
if constexpr (!std::is_same_v<From, node_entry>) {
|
||||
// Type check if type is specified
|
||||
if (!isa<From>(ex))
|
||||
return nullptr;
|
||||
}
|
||||
auto node = new To(static_cast<From *>(ex), std::forward<Args>(args)...);
|
||||
ex = nullptr;
|
||||
return node;
|
||||
}, false);
|
||||
}
|
||||
|
||||
node_entry* dir_node::extract(string_view name) {
|
||||
auto it = children.find(name);
|
||||
if (it != children.end()) {
|
||||
auto ret = it->second;
|
||||
children.erase(it);
|
||||
return ret;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
tmpfs_node::tmpfs_node(node_entry *node) : dir_node(node, this) {
|
||||
string mirror = mirror_path();
|
||||
if (auto dir = open_dir(mirror.data())) {
|
||||
set_exist(true);
|
||||
for (dirent *entry; (entry = xreaddir(dir.get()));) {
|
||||
// Insert mirror nodes
|
||||
emplace<mirror_node>(entry->d_name, entry);
|
||||
}
|
||||
} else {
|
||||
// It is actually possible that mirror does not exist (nested mount points)
|
||||
// Set self to non exist so this node will be ignored at mount
|
||||
// Keep it the same as `node`
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto it = children.begin(); it != children.end(); ++it) {
|
||||
// Need to upgrade all inter_node children to tmpfs_node
|
||||
if (isa<inter_node>(it->second))
|
||||
it = upgrade<tmpfs_node>(it);
|
||||
}
|
||||
}
|
||||
|
||||
// We need to upgrade to tmpfs node if any child:
|
||||
// - Target does not exist
|
||||
// - Source or target is a symlink
|
||||
bool node_entry::should_be_tmpfs(node_entry *child) {
|
||||
struct stat st;
|
||||
if (lstat(child->node_path().data(), &st) != 0) {
|
||||
return true;
|
||||
} else {
|
||||
child->set_exist(true);
|
||||
if (child->is_lnk() || S_ISLNK(st.st_mode))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool dir_node::prepare() {
|
||||
bool to_tmpfs = false;
|
||||
for (auto it = children.begin(); it != children.end();) {
|
||||
if (should_be_tmpfs(it->second)) {
|
||||
if (node_type > type_id<tmpfs_node>()) {
|
||||
// Upgrade will fail, remove the unsupported child node
|
||||
LOGW("Unable to add: %s, skipped\n", it->second->node_path().data());
|
||||
delete it->second;
|
||||
it = children.erase(it);
|
||||
continue;
|
||||
}
|
||||
// Tell parent to upgrade self to tmpfs
|
||||
to_tmpfs = true;
|
||||
// If child is inter_node and it does not (need to) exist, upgrade to module
|
||||
if (auto dn = dyn_cast<inter_node>(it->second); dn) {
|
||||
if (!dn->exist()) {
|
||||
if (auto nit = upgrade<module_node, inter_node>(it); nit != children.end()) {
|
||||
it = nit;
|
||||
goto next_node;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (auto dn = dyn_cast<dir_node>(it->second); dn && dn->is_dir() && !dn->prepare()) {
|
||||
// Upgrade child to tmpfs
|
||||
it = upgrade<tmpfs_node>(it);
|
||||
}
|
||||
next_node:
|
||||
++it;
|
||||
}
|
||||
return !to_tmpfs;
|
||||
}
|
||||
|
||||
bool dir_node::collect_files(const char *module, int dfd) {
|
||||
auto dir = xopen_dir(xopenat(dfd, _name.data(), O_RDONLY | O_CLOEXEC));
|
||||
if (!dir)
|
||||
return true;
|
||||
|
||||
for (dirent *entry; (entry = xreaddir(dir.get()));) {
|
||||
if (entry->d_name == ".replace"sv) {
|
||||
// Stop traversing and tell parent to upgrade self to module
|
||||
return false;
|
||||
}
|
||||
|
||||
if (entry->d_type == DT_DIR) {
|
||||
dir_node *dn;
|
||||
if (auto it = children.find(entry->d_name); it == children.end()) {
|
||||
dn = emplace<inter_node>(entry->d_name, entry->d_name, module);
|
||||
} else {
|
||||
dn = dyn_cast<inter_node>(it->second);
|
||||
// it has been accessed by at least two modules, it must be guarantee to exist
|
||||
// set it so that it won't be upgrade to module_node but tmpfs_node
|
||||
if (dn) dn->set_exist(true);
|
||||
}
|
||||
if (dn && !dn->collect_files(module, dirfd(dir.get()))) {
|
||||
upgrade<module_node>(dn->name(), module);
|
||||
}
|
||||
} else {
|
||||
emplace<module_node>(entry->d_name, module, entry);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/************************
|
||||
* Mount Implementations
|
||||
************************/
|
||||
|
||||
void node_entry::create_and_mount(const string &src) {
|
||||
const string &dest = node_path();
|
||||
if (is_lnk()) {
|
||||
VLOGD("cp_link", src.data(), dest.data());
|
||||
cp_afc(src.data(), dest.data());
|
||||
} else {
|
||||
if (is_dir())
|
||||
xmkdir(dest.data(), 0);
|
||||
else if (is_reg())
|
||||
close(xopen(dest.data(), O_RDONLY | O_CREAT | O_CLOEXEC, 0));
|
||||
else
|
||||
return;
|
||||
bind_mount(src.data(), dest.data());
|
||||
}
|
||||
}
|
||||
|
||||
void module_node::mount() {
|
||||
string src = module_mnt + module + parent()->root()->prefix + node_path();
|
||||
if (exist())
|
||||
clone_attr(mirror_path().data(), src.data());
|
||||
if (isa<tmpfs_node>(parent()))
|
||||
create_and_mount(src);
|
||||
else if (is_dir() || is_reg())
|
||||
bind_mount(src.data(), node_path().data());
|
||||
}
|
||||
|
||||
void tmpfs_node::mount() {
|
||||
if (!exist())
|
||||
return;
|
||||
string src = mirror_path();
|
||||
const string &dest = node_path();
|
||||
file_attr a{};
|
||||
if (access(src.data(), F_OK) == 0)
|
||||
getattr(src.data(), &a);
|
||||
else
|
||||
getattr(parent()->node_path().data(), &a);
|
||||
mkdir(dest.data(), 0);
|
||||
if (!isa<tmpfs_node>(parent())) {
|
||||
// We don't need another layer of tmpfs if parent is skel
|
||||
xmount("tmpfs", dest.data(), "tmpfs", 0, nullptr);
|
||||
VLOGD("mnt_tmp", "tmpfs", dest.data());
|
||||
}
|
||||
setattr(dest.data(), &a);
|
||||
dir_node::mount();
|
||||
}
|
||||
|
||||
/****************
|
||||
* Magisk Stuffs
|
||||
****************/
|
||||
|
||||
class magisk_node : public node_entry {
|
||||
public:
|
||||
explicit magisk_node(const char *name) : node_entry(name, DT_REG, this) {}
|
||||
|
||||
void mount() override {
|
||||
const string src = MAGISKTMP + "/" + name();
|
||||
if (access(src.data(), F_OK))
|
||||
return;
|
||||
|
||||
const string &dir_name = parent()->node_path();
|
||||
if (name() == "magisk") {
|
||||
for (int i = 0; applet_names[i]; ++i) {
|
||||
string dest = dir_name + "/" + applet_names[i];
|
||||
VLOGD("create", "./magisk", dest.data());
|
||||
xsymlink("./magisk", dest.data());
|
||||
}
|
||||
} else {
|
||||
string dest = dir_name + "/supolicy";
|
||||
VLOGD("create", "./magiskpolicy", dest.data());
|
||||
xsymlink("./magiskpolicy", dest.data());
|
||||
}
|
||||
create_and_mount(src);
|
||||
}
|
||||
};
|
||||
|
||||
static void inject_magisk_bins(root_node *system) {
|
||||
auto bin = system->child<inter_node>("bin");
|
||||
if (!bin) {
|
||||
bin = new inter_node("bin", "");
|
||||
system->insert(bin);
|
||||
}
|
||||
|
||||
// Insert binaries
|
||||
bin->insert(new magisk_node("magisk"));
|
||||
bin->insert(new magisk_node("magiskpolicy"));
|
||||
|
||||
// Also delete all applets to make sure no modules can override it
|
||||
for (int i = 0; applet_names[i]; ++i)
|
||||
delete bin->extract(applet_names[i]);
|
||||
delete bin->extract("supolicy");
|
||||
}
|
||||
|
||||
vector<module_info> *module_list;
|
||||
int app_process_32 = -1;
|
||||
int app_process_64 = -1;
|
||||
|
||||
#define mount_zygisk(bit) \
|
||||
if (access("/system/bin/app_process" #bit, F_OK) == 0) { \
|
||||
app_process_##bit = xopen("/system/bin/app_process" #bit, O_RDONLY | O_CLOEXEC); \
|
||||
string zbin = zygisk_bin + "/app_process" #bit; \
|
||||
string dbin = zygisk_bin + "/magisk" #bit; \
|
||||
string mbin = MAGISKTMP + "/magisk" #bit; \
|
||||
int src = xopen(mbin.data(), O_RDONLY | O_CLOEXEC); \
|
||||
int out = xopen(zbin.data(), O_CREAT | O_WRONLY | O_CLOEXEC, 0); \
|
||||
xsendfile(out, src, nullptr, INT_MAX); \
|
||||
close(out); \
|
||||
out = xopen(dbin.data(), O_CREAT | O_WRONLY | O_CLOEXEC, 0); \
|
||||
lseek(src, 0, SEEK_SET); \
|
||||
xsendfile(out, src, nullptr, INT_MAX); \
|
||||
close(out); \
|
||||
close(src); \
|
||||
clone_attr("/system/bin/app_process" #bit, zbin.data()); \
|
||||
clone_attr("/system/bin/app_process" #bit, dbin.data()); \
|
||||
bind_mount(zbin.data(), "/system/bin/app_process" #bit); \
|
||||
}
|
||||
|
||||
void magic_mount() {
|
||||
node_entry::mirror_dir = MAGISKTMP + "/" MIRRDIR;
|
||||
node_entry::module_mnt = MAGISKTMP + "/" MODULEMNT "/";
|
||||
|
||||
auto root = make_unique<root_node>("");
|
||||
auto system = new root_node("system");
|
||||
root->insert(system);
|
||||
|
||||
char buf[4096];
|
||||
LOGI("* Loading modules\n");
|
||||
for (const auto &m : *module_list) {
|
||||
const char *module = m.name.data();
|
||||
char *b = buf + sprintf(buf, "%s/" MODULEMNT "/%s/", MAGISKTMP.data(), module);
|
||||
|
||||
// Read props
|
||||
strcpy(b, "system.prop");
|
||||
if (access(buf, F_OK) == 0) {
|
||||
LOGI("%s: loading [system.prop]\n", module);
|
||||
load_prop_file(buf, false);
|
||||
}
|
||||
|
||||
// Check whether skip mounting
|
||||
strcpy(b, "skip_mount");
|
||||
if (access(buf, F_OK) == 0)
|
||||
continue;
|
||||
|
||||
// Double check whether the system folder exists
|
||||
strcpy(b, "system");
|
||||
if (access(buf, F_OK) != 0)
|
||||
continue;
|
||||
|
||||
LOGI("%s: loading mount files\n", module);
|
||||
b[-1] = '\0';
|
||||
int fd = xopen(buf, O_RDONLY | O_CLOEXEC);
|
||||
system->collect_files(module, fd);
|
||||
close(fd);
|
||||
}
|
||||
if (MAGISKTMP != "/sbin") {
|
||||
// Need to inject our binaries into /system/bin
|
||||
inject_magisk_bins(system);
|
||||
}
|
||||
|
||||
if (!system->is_empty()) {
|
||||
// Handle special read-only partitions
|
||||
for (const char *part : { "/vendor", "/product", "/system_ext" }) {
|
||||
struct stat st{};
|
||||
if (lstat(part, &st) == 0 && S_ISDIR(st.st_mode)) {
|
||||
if (auto old = system->extract(part + 1)) {
|
||||
auto new_node = new root_node(old);
|
||||
root->insert(new_node);
|
||||
}
|
||||
}
|
||||
}
|
||||
root->prepare();
|
||||
root->mount();
|
||||
}
|
||||
|
||||
// Mount on top of modules to enable zygisk
|
||||
if (zygisk_enabled) {
|
||||
string zygisk_bin = MAGISKTMP + "/" ZYGISKBIN;
|
||||
mkdir(zygisk_bin.data(), 0);
|
||||
mount_zygisk(32)
|
||||
mount_zygisk(64)
|
||||
}
|
||||
}
|
||||
|
||||
static void prepare_modules() {
|
||||
// Upgrade modules
|
||||
if (auto dir = open_dir(MODULEUPGRADE); dir) {
|
||||
int ufd = dirfd(dir.get());
|
||||
int mfd = xopen(MODULEROOT, O_RDONLY | O_CLOEXEC);
|
||||
for (dirent *entry; (entry = xreaddir(dir.get()));) {
|
||||
if (entry->d_type == DT_DIR) {
|
||||
// Cleanup old module if exists
|
||||
if (faccessat(mfd, entry->d_name, F_OK, 0) == 0) {
|
||||
int modfd = xopenat(mfd, entry->d_name, O_RDONLY | O_CLOEXEC);
|
||||
if (faccessat(modfd, "disable", F_OK, 0) == 0) {
|
||||
auto disable = entry->d_name + "/disable"s;
|
||||
close(xopenat(ufd, disable.data(), O_RDONLY | O_CREAT | O_CLOEXEC, 0));
|
||||
}
|
||||
frm_rf(modfd);
|
||||
unlinkat(mfd, entry->d_name, AT_REMOVEDIR);
|
||||
}
|
||||
LOGI("Upgrade / New module: %s\n", entry->d_name);
|
||||
renameat(ufd, entry->d_name, mfd, entry->d_name);
|
||||
}
|
||||
}
|
||||
close(mfd);
|
||||
rm_rf(MODULEUPGRADE);
|
||||
}
|
||||
|
||||
// Setup module mount (workaround nosuid selabel issue)
|
||||
auto src = MAGISKTMP + "/" MIRRDIR MODULEROOT;
|
||||
auto dest = MAGISKTMP + "/" MODULEMNT;
|
||||
xmkdir(dest.data(), 0755);
|
||||
bind_mount(src.data(), dest.data());
|
||||
|
||||
restorecon();
|
||||
chmod(SECURE_DIR, 0700);
|
||||
}
|
||||
|
||||
template<typename Func>
|
||||
static void foreach_module(Func fn) {
|
||||
auto dir = open_dir(MODULEROOT);
|
||||
if (!dir)
|
||||
return;
|
||||
|
||||
int dfd = dirfd(dir.get());
|
||||
for (dirent *entry; (entry = xreaddir(dir.get()));) {
|
||||
if (entry->d_type == DT_DIR && entry->d_name != ".core"sv) {
|
||||
int modfd = xopenat(dfd, entry->d_name, O_RDONLY | O_CLOEXEC);
|
||||
fn(dfd, entry, modfd);
|
||||
close(modfd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void collect_modules(bool open_zygisk) {
|
||||
foreach_module([=](int dfd, dirent *entry, int modfd) {
|
||||
if (faccessat(modfd, "remove", F_OK, 0) == 0) {
|
||||
LOGI("%s: remove\n", entry->d_name);
|
||||
auto uninstaller = MODULEROOT + "/"s + entry->d_name + "/uninstall.sh";
|
||||
if (access(uninstaller.data(), F_OK) == 0)
|
||||
exec_script(uninstaller.data());
|
||||
frm_rf(xdup(modfd));
|
||||
unlinkat(dfd, entry->d_name, AT_REMOVEDIR);
|
||||
return;
|
||||
}
|
||||
unlinkat(modfd, "update", 0);
|
||||
if (faccessat(modfd, "disable", F_OK, 0) == 0)
|
||||
return;
|
||||
|
||||
module_info info;
|
||||
if (zygisk_enabled) {
|
||||
// Riru and its modules are not compatible with zygisk
|
||||
if (entry->d_name == "riru-core"sv || faccessat(modfd, "riru", F_OK, 0) == 0) {
|
||||
LOGI("%s: ignore\n", entry->d_name);
|
||||
return;
|
||||
}
|
||||
if (open_zygisk) {
|
||||
#if defined(__arm__)
|
||||
info.z32 = openat(modfd, "zygisk/armeabi-v7a.so", O_RDONLY | O_CLOEXEC);
|
||||
#elif defined(__aarch64__)
|
||||
info.z32 = openat(modfd, "zygisk/armeabi-v7a.so", O_RDONLY | O_CLOEXEC);
|
||||
info.z64 = openat(modfd, "zygisk/arm64-v8a.so", O_RDONLY | O_CLOEXEC);
|
||||
#elif defined(__i386__)
|
||||
info.z32 = openat(modfd, "zygisk/x86.so", O_RDONLY | O_CLOEXEC);
|
||||
#elif defined(__x86_64__)
|
||||
info.z32 = openat(modfd, "zygisk/x86.so", O_RDONLY | O_CLOEXEC);
|
||||
info.z64 = openat(modfd, "zygisk/x86_64.so", O_RDONLY | O_CLOEXEC);
|
||||
#else
|
||||
#error Unsupported ABI
|
||||
#endif
|
||||
unlinkat(modfd, "zygisk/unloaded", 0);
|
||||
}
|
||||
} else {
|
||||
// Ignore zygisk modules when zygisk is not enabled
|
||||
if (faccessat(modfd, "zygisk", F_OK, 0) == 0) {
|
||||
LOGI("%s: ignore\n", entry->d_name);
|
||||
return;
|
||||
}
|
||||
}
|
||||
info.name = entry->d_name;
|
||||
module_list->push_back(info);
|
||||
});
|
||||
if (zygisk_enabled) {
|
||||
bool use_memfd = true;
|
||||
auto convert_to_memfd = [&](int fd) -> int {
|
||||
if (fd < 0)
|
||||
return -1;
|
||||
if (use_memfd) {
|
||||
int memfd = syscall(__NR_memfd_create, "jit-cache", MFD_CLOEXEC);
|
||||
if (memfd >= 0) {
|
||||
xsendfile(memfd, fd, nullptr, INT_MAX);
|
||||
close(fd);
|
||||
return memfd;
|
||||
} else {
|
||||
// memfd_create failed, just use what we had
|
||||
use_memfd = false;
|
||||
}
|
||||
}
|
||||
return fd;
|
||||
};
|
||||
std::for_each(module_list->begin(), module_list->end(), [&](module_info &info) {
|
||||
info.z32 = convert_to_memfd(info.z32);
|
||||
#if defined(__LP64__)
|
||||
info.z64 = convert_to_memfd(info.z64);
|
||||
#endif
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void handle_modules() {
|
||||
prepare_modules();
|
||||
collect_modules(false);
|
||||
exec_module_scripts("post-fs-data");
|
||||
|
||||
// Recollect modules (module scripts could remove itself)
|
||||
module_list->clear();
|
||||
collect_modules(true);
|
||||
}
|
||||
|
||||
void disable_modules() {
|
||||
foreach_module([](int, auto, int modfd) {
|
||||
close(xopenat(modfd, "disable", O_RDONLY | O_CREAT | O_CLOEXEC, 0));
|
||||
});
|
||||
}
|
||||
|
||||
void remove_modules() {
|
||||
foreach_module([](int, dirent *entry, int) {
|
||||
auto uninstaller = MODULEROOT + "/"s + entry->d_name + "/uninstall.sh";
|
||||
if (access(uninstaller.data(), F_OK) == 0)
|
||||
exec_script(uninstaller.data());
|
||||
});
|
||||
rm_rf(MODULEROOT);
|
||||
}
|
||||
|
||||
void exec_module_scripts(const char *stage) {
|
||||
vector<string_view> module_names;
|
||||
std::transform(module_list->begin(), module_list->end(), std::back_inserter(module_names),
|
||||
[](const module_info &info) -> string_view { return info.name; });
|
||||
exec_module_scripts(stage, module_names);
|
||||
}
|
||||
@@ -0,0 +1,279 @@
|
||||
#include <base.hpp>
|
||||
#include <magisk.hpp>
|
||||
#include <daemon.hpp>
|
||||
#include <db.hpp>
|
||||
#include <flags.h>
|
||||
|
||||
#include "core.hpp"
|
||||
|
||||
using namespace std;
|
||||
|
||||
#define ENFORCE_SIGNATURE (!MAGISK_DEBUG)
|
||||
|
||||
// These functions will be called on every single zygote process specialization and su request,
|
||||
// so performance is absolutely critical. Most operations should either have its result cached
|
||||
// or simply skipped unless necessary.
|
||||
|
||||
atomic<ino_t> pkg_xml_ino = 0;
|
||||
static atomic_flag skip_mgr_check;
|
||||
|
||||
static pthread_mutex_t pkg_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
// pkg_lock protects all following variables
|
||||
static int mgr_app_id = -1;
|
||||
static string *mgr_pkg;
|
||||
static string *mgr_cert;
|
||||
static int stub_apk_fd = -1;
|
||||
static const string *default_cert;
|
||||
|
||||
void check_pkg_refresh() {
|
||||
struct stat st{};
|
||||
if (stat("/data/system/packages.xml", &st) == 0 &&
|
||||
pkg_xml_ino.exchange(st.st_ino) != st.st_ino) {
|
||||
skip_mgr_check.clear();
|
||||
skip_pkg_rescan.clear();
|
||||
}
|
||||
}
|
||||
|
||||
// app_id = app_no + AID_APP_START
|
||||
// app_no range: [0, 9999]
|
||||
vector<bool> get_app_no_list() {
|
||||
vector<bool> list;
|
||||
auto data_dir = xopen_dir(APP_DATA_DIR);
|
||||
if (!data_dir)
|
||||
return list;
|
||||
dirent *entry;
|
||||
while ((entry = xreaddir(data_dir.get()))) {
|
||||
// For each user
|
||||
int dfd = xopenat(dirfd(data_dir.get()), entry->d_name, O_RDONLY);
|
||||
if (auto dir = xopen_dir(dfd)) {
|
||||
while ((entry = xreaddir(dir.get()))) {
|
||||
// For each package
|
||||
struct stat st{};
|
||||
xfstatat(dfd, entry->d_name, &st, 0);
|
||||
int app_id = to_app_id(st.st_uid);
|
||||
if (app_id >= AID_APP_START && app_id <= AID_APP_END) {
|
||||
int app_no = app_id - AID_APP_START;
|
||||
if (list.size() <= app_no) {
|
||||
list.resize(app_no + 1);
|
||||
}
|
||||
list[app_no] = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
close(dfd);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
void preserve_stub_apk() {
|
||||
mutex_guard g(pkg_lock);
|
||||
string stub_path = MAGISKTMP + "/stub.apk";
|
||||
stub_apk_fd = xopen(stub_path.data(), O_RDONLY | O_CLOEXEC);
|
||||
unlink(stub_path.data());
|
||||
default_cert = new string(read_certificate(stub_apk_fd));
|
||||
lseek(stub_apk_fd, 0, SEEK_SET);
|
||||
}
|
||||
|
||||
static void install_stub() {
|
||||
if (stub_apk_fd < 0)
|
||||
return;
|
||||
struct stat st{};
|
||||
fstat(stub_apk_fd, &st);
|
||||
char apk[] = "/data/stub.apk";
|
||||
int dfd = xopen(apk, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0600);
|
||||
xsendfile(dfd, stub_apk_fd, nullptr, st.st_size);
|
||||
lseek(stub_apk_fd, 0, SEEK_SET);
|
||||
close(dfd);
|
||||
install_apk(apk);
|
||||
}
|
||||
|
||||
int get_manager(int user_id, string *pkg, bool install) {
|
||||
mutex_guard g(pkg_lock);
|
||||
|
||||
char app_path[128];
|
||||
struct stat st{};
|
||||
if (mgr_pkg == nullptr)
|
||||
default_new(mgr_pkg);
|
||||
if (mgr_cert == nullptr)
|
||||
default_new(mgr_cert);
|
||||
|
||||
auto check_dyn = [&](int u) -> bool {
|
||||
#if ENFORCE_SIGNATURE
|
||||
snprintf(app_path, sizeof(app_path),
|
||||
"%s/%d/%s/dyn/current.apk", APP_DATA_DIR, u, mgr_pkg->data());
|
||||
int dyn = open(app_path, O_RDONLY | O_CLOEXEC);
|
||||
if (dyn < 0) {
|
||||
LOGW("pkg: no dyn APK, ignore\n");
|
||||
return false;
|
||||
}
|
||||
bool mismatch = default_cert && read_certificate(dyn, MAGISK_VER_CODE) != *default_cert;
|
||||
close(dyn);
|
||||
if (mismatch) {
|
||||
LOGE("pkg: dyn APK signature mismatch: %s\n", app_path);
|
||||
clear_pkg(mgr_pkg->data(), u);
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
};
|
||||
|
||||
if (skip_mgr_check.test_and_set()) {
|
||||
if (mgr_app_id < 0) {
|
||||
goto not_found;
|
||||
}
|
||||
// Just need to check whether the app is installed in the user
|
||||
const char *name = mgr_pkg->empty() ? JAVA_PACKAGE_NAME : mgr_pkg->data();
|
||||
snprintf(app_path, sizeof(app_path), "%s/%d/%s", APP_DATA_DIR, user_id, name);
|
||||
if (access(app_path, F_OK) == 0) {
|
||||
// Always check dyn signature for repackaged app
|
||||
if (!mgr_pkg->empty() && !check_dyn(user_id))
|
||||
goto not_found;
|
||||
if (pkg) *pkg = name;
|
||||
return user_id * AID_USER_OFFSET + mgr_app_id;
|
||||
} else {
|
||||
goto not_found;
|
||||
}
|
||||
} else {
|
||||
// Here, we want to actually find the manager app and cache the results.
|
||||
// This means that we check all users, not just the requested user.
|
||||
// Certificates are also verified to prevent manipulation.
|
||||
|
||||
db_strings str;
|
||||
get_db_strings(str, SU_MANAGER);
|
||||
|
||||
vector<int> users;
|
||||
bool collected = false;
|
||||
|
||||
auto collect_users = [&] {
|
||||
if (collected)
|
||||
return;
|
||||
collected = true;
|
||||
auto data_dir = xopen_dir(APP_DATA_DIR);
|
||||
if (!data_dir)
|
||||
return;
|
||||
dirent *entry;
|
||||
while ((entry = xreaddir(data_dir.get()))) {
|
||||
// Only collect users not requested as we've already checked it
|
||||
if (int u = parse_int(entry->d_name); u >= 0 && u != user_id)
|
||||
users.push_back(parse_int(entry->d_name));
|
||||
}
|
||||
};
|
||||
|
||||
if (!str[SU_MANAGER].empty()) {
|
||||
// Check the repackaged package name
|
||||
|
||||
bool invalid = false;
|
||||
auto check_pkg = [&](int u) -> bool {
|
||||
snprintf(app_path, sizeof(app_path),
|
||||
"%s/%d/%s", APP_DATA_DIR, u, str[SU_MANAGER].data());
|
||||
if (stat(app_path, &st) == 0) {
|
||||
int app_id = to_app_id(st.st_uid);
|
||||
|
||||
string apk = find_apk_path(str[SU_MANAGER].data());
|
||||
int fd = xopen(apk.data(), O_RDONLY | O_CLOEXEC);
|
||||
string cert = read_certificate(fd);
|
||||
close(fd);
|
||||
|
||||
// Verify validity
|
||||
if (str[SU_MANAGER] == *mgr_pkg) {
|
||||
if (app_id != mgr_app_id || cert != *mgr_cert) {
|
||||
// app ID or cert should never change
|
||||
LOGE("pkg: repackaged APK signature invalid: %s\n", apk.data());
|
||||
uninstall_pkg(mgr_pkg->data());
|
||||
invalid = true;
|
||||
install = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
mgr_pkg->swap(str[SU_MANAGER]);
|
||||
mgr_app_id = app_id;
|
||||
mgr_cert->swap(cert);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
if (check_pkg(user_id)) {
|
||||
if (!check_dyn(user_id))
|
||||
goto not_found;
|
||||
if (pkg) *pkg = *mgr_pkg;
|
||||
return st.st_uid;
|
||||
}
|
||||
if (!invalid) {
|
||||
collect_users();
|
||||
for (int u : users) {
|
||||
if (check_pkg(u)) {
|
||||
// Found repackaged app, but not installed in the requested user
|
||||
goto not_found;
|
||||
}
|
||||
if (invalid)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Repackaged app not found, remove package from db
|
||||
rm_db_strings(SU_MANAGER);
|
||||
|
||||
// Fallthrough
|
||||
}
|
||||
|
||||
// Check the original package name
|
||||
|
||||
bool invalid = false;
|
||||
auto check_pkg = [&](int u) -> bool {
|
||||
snprintf(app_path, sizeof(app_path), "%s/%d/" JAVA_PACKAGE_NAME, APP_DATA_DIR, u);
|
||||
if (stat(app_path, &st) == 0) {
|
||||
#if ENFORCE_SIGNATURE
|
||||
string apk = find_apk_path(JAVA_PACKAGE_NAME);
|
||||
int fd = xopen(apk.data(), O_RDONLY | O_CLOEXEC);
|
||||
string cert = read_certificate(fd, MAGISK_VER_CODE);
|
||||
close(fd);
|
||||
if (default_cert && cert != *default_cert) {
|
||||
// Found APK with invalid signature, force replace with stub
|
||||
LOGE("pkg: APK signature mismatch: %s\n", apk.data());
|
||||
uninstall_pkg(JAVA_PACKAGE_NAME);
|
||||
invalid = true;
|
||||
install = true;
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
mgr_pkg->clear();
|
||||
mgr_cert->clear();
|
||||
mgr_app_id = to_app_id(st.st_uid);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
if (check_pkg(user_id)) {
|
||||
if (pkg) *pkg = JAVA_PACKAGE_NAME;
|
||||
return st.st_uid;
|
||||
}
|
||||
if (!invalid) {
|
||||
collect_users();
|
||||
for (int u : users) {
|
||||
if (check_pkg(u)) {
|
||||
// Found app, but not installed in the requested user
|
||||
goto not_found;
|
||||
}
|
||||
if (invalid)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No manager app is found, clear all cached value
|
||||
mgr_app_id = -1;
|
||||
mgr_pkg->clear();
|
||||
mgr_cert->clear();
|
||||
if (install)
|
||||
install_stub();
|
||||
|
||||
not_found:
|
||||
const char *name = mgr_pkg->empty() ? JAVA_PACKAGE_NAME : mgr_pkg->data();
|
||||
LOGW("pkg: cannot find %s for user=[%d]\n", name, user_id);
|
||||
if (pkg) pkg->clear();
|
||||
return -1;
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
#include <string_view>
|
||||
|
||||
#include <magisk.hpp>
|
||||
#include <selinux.hpp>
|
||||
#include <base.hpp>
|
||||
|
||||
using namespace std;
|
||||
|
||||
#define UNLABEL_CON "u:object_r:unlabeled:s0"
|
||||
#define SYSTEM_CON "u:object_r:system_file:s0"
|
||||
#define ADB_CON "u:object_r:adb_data_file:s0"
|
||||
#define ROOT_CON "u:object_r:rootfs:s0"
|
||||
#define MAGISK_CON "u:object_r:" SEPOL_FILE_TYPE ":s0"
|
||||
#define EXEC_CON "u:object_r:" SEPOL_EXEC_TYPE ":s0"
|
||||
|
||||
static void restore_syscon(int dirfd) {
|
||||
struct dirent *entry;
|
||||
char *con;
|
||||
|
||||
if (fgetfilecon(dirfd, &con) >= 0) {
|
||||
if (strlen(con) == 0 || strcmp(con, UNLABEL_CON) == 0 || strcmp(con, ADB_CON) == 0)
|
||||
fsetfilecon(dirfd, SYSTEM_CON);
|
||||
freecon(con);
|
||||
}
|
||||
|
||||
auto dir = xopen_dir(dirfd);
|
||||
while ((entry = xreaddir(dir.get()))) {
|
||||
int fd = openat(dirfd, entry->d_name, O_RDONLY | O_CLOEXEC);
|
||||
if (entry->d_type == DT_DIR) {
|
||||
restore_syscon(fd);
|
||||
continue;
|
||||
} else if (entry->d_type == DT_REG) {
|
||||
if (fgetfilecon(fd, &con) >= 0) {
|
||||
if (con[0] == '\0' || strcmp(con, UNLABEL_CON) == 0 || strcmp(con, ADB_CON) == 0)
|
||||
fsetfilecon(fd, SYSTEM_CON);
|
||||
freecon(con);
|
||||
}
|
||||
} else if (entry->d_type == DT_LNK) {
|
||||
getfilecon_at(dirfd, entry->d_name, &con);
|
||||
if (con[0] == '\0' || strcmp(con, UNLABEL_CON) == 0 || strcmp(con, ADB_CON) == 0)
|
||||
setfilecon_at(dirfd, entry->d_name, con);
|
||||
freecon(con);
|
||||
}
|
||||
close(fd);
|
||||
}
|
||||
}
|
||||
|
||||
static void restore_magiskcon(int dirfd) {
|
||||
struct dirent *entry;
|
||||
|
||||
fsetfilecon(dirfd, MAGISK_CON);
|
||||
fchown(dirfd, 0, 0);
|
||||
|
||||
auto dir = xopen_dir(dirfd);
|
||||
while ((entry = xreaddir(dir.get()))) {
|
||||
int fd = xopenat(dirfd, entry->d_name, O_RDONLY | O_CLOEXEC);
|
||||
if (entry->d_type == DT_DIR) {
|
||||
restore_magiskcon(fd);
|
||||
continue;
|
||||
} else if (entry->d_type) {
|
||||
fsetfilecon(fd, MAGISK_CON);
|
||||
fchown(fd, 0, 0);
|
||||
}
|
||||
close(fd);
|
||||
}
|
||||
}
|
||||
|
||||
void restorecon() {
|
||||
if (!selinux_enabled())
|
||||
return;
|
||||
int fd = xopen(SELINUX_CONTEXT, O_WRONLY | O_CLOEXEC);
|
||||
if (write(fd, ADB_CON, sizeof(ADB_CON)) >= 0)
|
||||
lsetfilecon(SECURE_DIR, ADB_CON);
|
||||
close(fd);
|
||||
lsetfilecon(MODULEROOT, SYSTEM_CON);
|
||||
restore_syscon(xopen(MODULEROOT, O_RDONLY | O_CLOEXEC));
|
||||
}
|
||||
|
||||
void restore_databincon() {
|
||||
restore_magiskcon(xopen(DATABIN, O_RDONLY | O_CLOEXEC));
|
||||
}
|
||||
|
||||
void restore_tmpcon() {
|
||||
if (!selinux_enabled())
|
||||
return;
|
||||
if (MAGISKTMP == "/sbin")
|
||||
setfilecon(MAGISKTMP.data(), ROOT_CON);
|
||||
else
|
||||
chmod(MAGISKTMP.data(), 0700);
|
||||
|
||||
auto dir = xopen_dir(MAGISKTMP.data());
|
||||
int dfd = dirfd(dir.get());
|
||||
|
||||
for (dirent *entry; (entry = xreaddir(dir.get()));)
|
||||
setfilecon_at(dfd, entry->d_name, SYSTEM_CON);
|
||||
|
||||
if (SDK_INT >= 26) {
|
||||
string magisk = MAGISKTMP + "/magisk";
|
||||
setfilecon(magisk.data(), EXEC_CON);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <sys/wait.h>
|
||||
|
||||
#include <magisk.hpp>
|
||||
#include <base.hpp>
|
||||
#include <selinux.hpp>
|
||||
#include <daemon.hpp>
|
||||
|
||||
#include "core.hpp"
|
||||
|
||||
using namespace std;
|
||||
|
||||
#define BBEXEC_CMD bbpath(), "sh"
|
||||
|
||||
static const char *bbpath() {
|
||||
static string path;
|
||||
if (path.empty())
|
||||
path = MAGISKTMP + "/" BBPATH "/busybox";
|
||||
return path.data();
|
||||
}
|
||||
|
||||
static void set_script_env() {
|
||||
setenv("ASH_STANDALONE", "1", 1);
|
||||
char new_path[4096];
|
||||
sprintf(new_path, "%s:%s", getenv("PATH"), MAGISKTMP.data());
|
||||
setenv("PATH", new_path, 1);
|
||||
if (zygisk_enabled)
|
||||
setenv("ZYGISK_ENABLED", "1", 1);
|
||||
};
|
||||
|
||||
void exec_script(const char *script) {
|
||||
exec_t exec {
|
||||
.pre_exec = set_script_env,
|
||||
.fork = fork_no_orphan
|
||||
};
|
||||
exec_command_sync(exec, BBEXEC_CMD, script);
|
||||
}
|
||||
|
||||
static timespec pfs_timeout;
|
||||
|
||||
#define PFS_SETUP() \
|
||||
if (pfs) { \
|
||||
if (int pid = xfork()) { \
|
||||
if (pid < 0) \
|
||||
return; \
|
||||
/* In parent process, simply wait for child to finish */ \
|
||||
waitpid(pid, nullptr, 0); \
|
||||
return; \
|
||||
} \
|
||||
timer_pid = xfork(); \
|
||||
if (timer_pid == 0) { \
|
||||
/* In timer process, count down */ \
|
||||
clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &pfs_timeout, nullptr); \
|
||||
exit(0); \
|
||||
} \
|
||||
}
|
||||
|
||||
#define PFS_WAIT() \
|
||||
if (pfs) { \
|
||||
/* If we ran out of time, don't block */ \
|
||||
if (timer_pid < 0) \
|
||||
continue; \
|
||||
if (int pid = waitpid(-1, nullptr, 0); pid == timer_pid) { \
|
||||
LOGW("* post-fs-data scripts blocking phase timeout\n"); \
|
||||
timer_pid = -1; \
|
||||
} \
|
||||
}
|
||||
|
||||
#define PFS_DONE() \
|
||||
if (pfs) { \
|
||||
if (timer_pid > 0) \
|
||||
kill(timer_pid, SIGKILL); \
|
||||
exit(0); \
|
||||
}
|
||||
|
||||
void exec_common_scripts(const char *stage) {
|
||||
LOGI("* Running %s.d scripts\n", stage);
|
||||
char path[4096];
|
||||
char *name = path + sprintf(path, SECURE_DIR "/%s.d", stage);
|
||||
auto dir = xopen_dir(path);
|
||||
if (!dir) return;
|
||||
|
||||
bool pfs = stage == "post-fs-data"sv;
|
||||
int timer_pid = -1;
|
||||
if (pfs) {
|
||||
// Setup timer
|
||||
clock_gettime(CLOCK_MONOTONIC, &pfs_timeout);
|
||||
pfs_timeout.tv_sec += POST_FS_DATA_SCRIPT_MAX_TIME;
|
||||
}
|
||||
PFS_SETUP()
|
||||
|
||||
*(name++) = '/';
|
||||
int dfd = dirfd(dir.get());
|
||||
for (dirent *entry; (entry = xreaddir(dir.get()));) {
|
||||
if (entry->d_type == DT_REG) {
|
||||
if (faccessat(dfd, entry->d_name, X_OK, 0) != 0)
|
||||
continue;
|
||||
LOGI("%s.d: exec [%s]\n", stage, entry->d_name);
|
||||
strcpy(name, entry->d_name);
|
||||
exec_t exec {
|
||||
.pre_exec = set_script_env,
|
||||
.fork = pfs ? xfork : fork_dont_care
|
||||
};
|
||||
exec_command(exec, BBEXEC_CMD, path);
|
||||
PFS_WAIT()
|
||||
}
|
||||
}
|
||||
|
||||
PFS_DONE()
|
||||
}
|
||||
|
||||
static bool operator>(const timespec &a, const timespec &b) {
|
||||
if (a.tv_sec != b.tv_sec)
|
||||
return a.tv_sec > b.tv_sec;
|
||||
return a.tv_nsec > b.tv_nsec;
|
||||
}
|
||||
|
||||
void exec_module_scripts(const char *stage, const vector<string_view> &modules) {
|
||||
LOGI("* Running module %s scripts\n", stage);
|
||||
if (modules.empty())
|
||||
return;
|
||||
|
||||
bool pfs = stage == "post-fs-data"sv;
|
||||
if (pfs) {
|
||||
timespec now{};
|
||||
clock_gettime(CLOCK_MONOTONIC, &now);
|
||||
// If we had already timed out, treat it as service mode
|
||||
if (now > pfs_timeout)
|
||||
pfs = false;
|
||||
}
|
||||
int timer_pid = -1;
|
||||
PFS_SETUP()
|
||||
|
||||
char path[4096];
|
||||
for (auto &m : modules) {
|
||||
const char *module = m.data();
|
||||
sprintf(path, MODULEROOT "/%s/%s.sh", module, stage);
|
||||
if (access(path, F_OK) == -1)
|
||||
continue;
|
||||
LOGI("%s: exec [%s.sh]\n", module, stage);
|
||||
exec_t exec {
|
||||
.pre_exec = set_script_env,
|
||||
.fork = pfs ? xfork : fork_dont_care
|
||||
};
|
||||
exec_command(exec, BBEXEC_CMD, path);
|
||||
PFS_WAIT()
|
||||
}
|
||||
|
||||
PFS_DONE()
|
||||
}
|
||||
|
||||
constexpr char install_script[] = R"EOF(
|
||||
APK=%s
|
||||
log -t Magisk "pm_install: $APK"
|
||||
log -t Magisk "pm_install: $(pm install -r $APK 2>&1)"
|
||||
rm -f $APK
|
||||
)EOF";
|
||||
|
||||
void install_apk(const char *apk) {
|
||||
setfilecon(apk, "u:object_r:" SEPOL_FILE_TYPE ":s0");
|
||||
exec_t exec {
|
||||
.fork = fork_no_orphan
|
||||
};
|
||||
char cmds[sizeof(install_script) + 4096];
|
||||
snprintf(cmds, sizeof(cmds), install_script, apk);
|
||||
exec_command_sync(exec, "/system/bin/sh", "-c", cmds);
|
||||
}
|
||||
|
||||
constexpr char uninstall_script[] = R"EOF(
|
||||
PKG=%s
|
||||
log -t Magisk "pm_uninstall: $PKG"
|
||||
log -t Magisk "pm_uninstall: $(pm uninstall $PKG 2>&1)"
|
||||
)EOF";
|
||||
|
||||
void uninstall_pkg(const char *pkg) {
|
||||
exec_t exec {
|
||||
.fork = fork_no_orphan
|
||||
};
|
||||
char cmds[sizeof(uninstall_script) + 256];
|
||||
snprintf(cmds, sizeof(cmds), uninstall_script, pkg);
|
||||
exec_command_sync(exec, "/system/bin/sh", "-c", cmds);
|
||||
}
|
||||
|
||||
constexpr char clear_script[] = R"EOF(
|
||||
PKG=%s
|
||||
USER=%d
|
||||
log -t Magisk "pm_clear: $PKG (user=$USER)"
|
||||
log -t Magisk "pm_clear: $(pm clear --user $USER $PKG 2>&1)"
|
||||
)EOF";
|
||||
|
||||
void clear_pkg(const char *pkg, int user_id) {
|
||||
exec_t exec {
|
||||
.fork = fork_no_orphan
|
||||
};
|
||||
char cmds[sizeof(clear_script) + 288];
|
||||
snprintf(cmds, sizeof(cmds), clear_script, pkg, user_id);
|
||||
exec_command_sync(exec, "/system/bin/sh", "-c", cmds);
|
||||
}
|
||||
|
||||
[[noreturn]] __printflike(2, 3)
|
||||
static void abort(FILE *fp, const char *fmt, ...) {
|
||||
va_list valist;
|
||||
va_start(valist, fmt);
|
||||
vfprintf(fp, fmt, valist);
|
||||
fprintf(fp, "\n\n");
|
||||
va_end(valist);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
constexpr char install_module_script[] = R"EOF(
|
||||
exec $(magisk --path)/.magisk/busybox/busybox sh -c '
|
||||
. /data/adb/magisk/util_functions.sh
|
||||
install_module
|
||||
exit 0'
|
||||
)EOF";
|
||||
|
||||
void install_module(const char *file) {
|
||||
if (getuid() != 0)
|
||||
abort(stderr, "Run this command with root");
|
||||
if (access(DATABIN, F_OK) ||
|
||||
access(DATABIN "/busybox", X_OK) ||
|
||||
access(DATABIN "/util_functions.sh", F_OK))
|
||||
abort(stderr, "Incomplete Magisk install");
|
||||
if (access(file, F_OK))
|
||||
abort(stderr, "'%s' does not exist", file);
|
||||
|
||||
char *zip = realpath(file, nullptr);
|
||||
setenv("OUTFD", "1", 1);
|
||||
setenv("ZIPFILE", zip, 1);
|
||||
setenv("ASH_STANDALONE", "1", 1);
|
||||
free(zip);
|
||||
|
||||
int fd = xopen("/dev/null", O_RDONLY);
|
||||
xdup2(fd, STDERR_FILENO);
|
||||
close(fd);
|
||||
|
||||
const char *argv[] = { "/system/bin/sh", "-c", install_module_script, nullptr };
|
||||
execve(argv[0], (char **) argv, environ);
|
||||
abort(stdout, "Failed to execute BusyBox shell");
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
#include <fcntl.h>
|
||||
#include <endian.h>
|
||||
|
||||
#include <socket.hpp>
|
||||
#include <base.hpp>
|
||||
|
||||
using namespace std;
|
||||
|
||||
static size_t socket_len(sockaddr_un *sun) {
|
||||
if (sun->sun_path[0])
|
||||
return sizeof(sa_family_t) + strlen(sun->sun_path) + 1;
|
||||
else
|
||||
return sizeof(sa_family_t) + strlen(sun->sun_path + 1) + 1;
|
||||
}
|
||||
|
||||
socklen_t setup_sockaddr(sockaddr_un *sun, const char *name) {
|
||||
memset(sun, 0, sizeof(*sun));
|
||||
sun->sun_family = AF_UNIX;
|
||||
strcpy(sun->sun_path + 1, name);
|
||||
return socket_len(sun);
|
||||
}
|
||||
|
||||
bool get_client_cred(int fd, sock_cred *cred) {
|
||||
socklen_t len = sizeof(ucred);
|
||||
if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, cred, &len) != 0)
|
||||
return false;
|
||||
char buf[4096];
|
||||
len = sizeof(buf);
|
||||
if (getsockopt(fd, SOL_SOCKET, SO_PEERSEC, buf, &len) != 0)
|
||||
len = 0;
|
||||
buf[len] = '\0';
|
||||
cred->context = buf;
|
||||
return true;
|
||||
}
|
||||
|
||||
static int send_fds(int sockfd, void *cmsgbuf, size_t bufsz, const int *fds, int cnt) {
|
||||
iovec iov = {
|
||||
.iov_base = &cnt,
|
||||
.iov_len = sizeof(cnt),
|
||||
};
|
||||
msghdr msg = {
|
||||
.msg_iov = &iov,
|
||||
.msg_iovlen = 1,
|
||||
};
|
||||
|
||||
if (cnt) {
|
||||
msg.msg_control = cmsgbuf;
|
||||
msg.msg_controllen = bufsz;
|
||||
cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
|
||||
cmsg->cmsg_len = CMSG_LEN(sizeof(int) * cnt);
|
||||
cmsg->cmsg_level = SOL_SOCKET;
|
||||
cmsg->cmsg_type = SCM_RIGHTS;
|
||||
|
||||
memcpy(CMSG_DATA(cmsg), fds, sizeof(int) * cnt);
|
||||
}
|
||||
|
||||
return xsendmsg(sockfd, &msg, 0);
|
||||
}
|
||||
|
||||
int send_fds(int sockfd, const int *fds, int cnt) {
|
||||
if (cnt == 0) {
|
||||
return send_fds(sockfd, nullptr, 0, nullptr, 0);
|
||||
}
|
||||
vector<char> cmsgbuf;
|
||||
cmsgbuf.resize(CMSG_SPACE(sizeof(int) * cnt));
|
||||
return send_fds(sockfd, cmsgbuf.data(), cmsgbuf.size(), fds, cnt);
|
||||
}
|
||||
|
||||
int send_fd(int sockfd, int fd) {
|
||||
if (fd < 0) {
|
||||
return send_fds(sockfd, nullptr, 0, nullptr, 0);
|
||||
}
|
||||
char cmsgbuf[CMSG_SPACE(sizeof(int))];
|
||||
return send_fds(sockfd, cmsgbuf, sizeof(cmsgbuf), &fd, 1);
|
||||
}
|
||||
|
||||
static void *recv_fds(int sockfd, char *cmsgbuf, size_t bufsz, int cnt) {
|
||||
iovec iov = {
|
||||
.iov_base = &cnt,
|
||||
.iov_len = sizeof(cnt),
|
||||
};
|
||||
msghdr msg = {
|
||||
.msg_iov = &iov,
|
||||
.msg_iovlen = 1,
|
||||
.msg_control = cmsgbuf,
|
||||
.msg_controllen = bufsz
|
||||
};
|
||||
|
||||
xrecvmsg(sockfd, &msg, MSG_WAITALL);
|
||||
cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
|
||||
|
||||
if (msg.msg_controllen != bufsz ||
|
||||
cmsg == nullptr ||
|
||||
cmsg->cmsg_len != CMSG_LEN(sizeof(int) * cnt) ||
|
||||
cmsg->cmsg_level != SOL_SOCKET ||
|
||||
cmsg->cmsg_type != SCM_RIGHTS) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return CMSG_DATA(cmsg);
|
||||
}
|
||||
|
||||
vector<int> recv_fds(int sockfd) {
|
||||
vector<int> results;
|
||||
|
||||
// Peek fd count to allocate proper buffer
|
||||
int cnt;
|
||||
recv(sockfd, &cnt, sizeof(cnt), MSG_PEEK);
|
||||
if (cnt == 0)
|
||||
return results;
|
||||
|
||||
vector<char> cmsgbuf;
|
||||
cmsgbuf.resize(CMSG_SPACE(sizeof(int) * cnt));
|
||||
|
||||
void *data = recv_fds(sockfd, cmsgbuf.data(), cmsgbuf.size(), cnt);
|
||||
if (data == nullptr)
|
||||
return results;
|
||||
|
||||
results.resize(cnt);
|
||||
memcpy(results.data(), data, sizeof(int) * cnt);
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
int recv_fd(int sockfd) {
|
||||
char cmsgbuf[CMSG_SPACE(sizeof(int))];
|
||||
|
||||
void *data = recv_fds(sockfd, cmsgbuf, sizeof(cmsgbuf), 1);
|
||||
if (data == nullptr)
|
||||
return -1;
|
||||
|
||||
int result;
|
||||
memcpy(&result, data, sizeof(int));
|
||||
return result;
|
||||
}
|
||||
|
||||
int read_int(int fd) {
|
||||
int val;
|
||||
if (xxread(fd, &val, sizeof(val)) != sizeof(val))
|
||||
return -1;
|
||||
return val;
|
||||
}
|
||||
|
||||
int read_int_be(int fd) {
|
||||
uint32_t val;
|
||||
if (xxread(fd, &val, sizeof(val)) != sizeof(val))
|
||||
return -1;
|
||||
return ntohl(val);
|
||||
}
|
||||
|
||||
void write_int(int fd, int val) {
|
||||
if (fd < 0) return;
|
||||
xwrite(fd, &val, sizeof(val));
|
||||
}
|
||||
|
||||
void write_int_be(int fd, int val) {
|
||||
uint32_t nl = htonl(val);
|
||||
xwrite(fd, &nl, sizeof(nl));
|
||||
}
|
||||
|
||||
bool read_string(int fd, std::string &str) {
|
||||
int len = read_int(fd);
|
||||
str.clear();
|
||||
if (len < 0)
|
||||
return false;
|
||||
str.resize(len);
|
||||
return xxread(fd, str.data(), len) == len;
|
||||
}
|
||||
|
||||
string read_string(int fd) {
|
||||
string str;
|
||||
read_string(fd, str);
|
||||
return str;
|
||||
}
|
||||
|
||||
void write_string(int fd, string_view str) {
|
||||
if (fd < 0) return;
|
||||
write_int(fd, str.size());
|
||||
xwrite(fd, str.data(), str.size());
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
// Cached thread pool implementation
|
||||
|
||||
#include <base.hpp>
|
||||
|
||||
#include <daemon.hpp>
|
||||
|
||||
using namespace std;
|
||||
|
||||
#define THREAD_IDLE_MAX_SEC 60
|
||||
#define CORE_POOL_SIZE 3
|
||||
|
||||
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
static pthread_cond_t send_task = PTHREAD_COND_INITIALIZER_MONOTONIC_NP;
|
||||
static pthread_cond_t recv_task = PTHREAD_COND_INITIALIZER_MONOTONIC_NP;
|
||||
|
||||
// The following variables should be guarded by lock
|
||||
static int idle_threads = 0;
|
||||
static int total_threads = 0;
|
||||
static function<void()> pending_task;
|
||||
|
||||
static void operator+=(timespec &a, const timespec &b) {
|
||||
a.tv_sec += b.tv_sec;
|
||||
a.tv_nsec += b.tv_nsec;
|
||||
if (a.tv_nsec >= 1000000000L) {
|
||||
a.tv_sec++;
|
||||
a.tv_nsec -= 1000000000L;
|
||||
}
|
||||
}
|
||||
|
||||
static void reset_pool() {
|
||||
clear_poll();
|
||||
pthread_mutex_unlock(&lock);
|
||||
pthread_mutex_destroy(&lock);
|
||||
pthread_mutex_init(&lock, nullptr);
|
||||
pthread_cond_destroy(&send_task);
|
||||
send_task = PTHREAD_COND_INITIALIZER_MONOTONIC_NP;
|
||||
pthread_cond_destroy(&recv_task);
|
||||
recv_task = PTHREAD_COND_INITIALIZER_MONOTONIC_NP;
|
||||
idle_threads = 0;
|
||||
total_threads = 0;
|
||||
pending_task = nullptr;
|
||||
}
|
||||
|
||||
static void *thread_pool_loop(void * const is_core_pool) {
|
||||
pthread_atfork(nullptr, nullptr, &reset_pool);
|
||||
|
||||
// Block all signals
|
||||
sigset_t mask;
|
||||
sigfillset(&mask);
|
||||
|
||||
for (;;) {
|
||||
// Restore sigmask
|
||||
pthread_sigmask(SIG_SETMASK, &mask, nullptr);
|
||||
function<void()> local_task;
|
||||
{
|
||||
mutex_guard g(lock);
|
||||
++idle_threads;
|
||||
if (!pending_task) {
|
||||
if (is_core_pool) {
|
||||
pthread_cond_wait(&send_task, &lock);
|
||||
} else {
|
||||
timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
ts += { THREAD_IDLE_MAX_SEC, 0 };
|
||||
if (pthread_cond_timedwait(&send_task, &lock, &ts) == ETIMEDOUT) {
|
||||
// Terminate thread after max idle time
|
||||
--idle_threads;
|
||||
--total_threads;
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (pending_task) {
|
||||
local_task.swap(pending_task);
|
||||
pthread_cond_signal(&recv_task);
|
||||
}
|
||||
--idle_threads;
|
||||
}
|
||||
if (local_task)
|
||||
local_task();
|
||||
if (getpid() == gettid())
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
void exec_task(function<void()> &&task) {
|
||||
mutex_guard g(lock);
|
||||
pending_task.swap(task);
|
||||
if (idle_threads == 0) {
|
||||
++total_threads;
|
||||
long is_core_pool = total_threads <= CORE_POOL_SIZE;
|
||||
new_daemon_thread(thread_pool_loop, (void *) is_core_pool);
|
||||
} else {
|
||||
pthread_cond_signal(&send_task);
|
||||
}
|
||||
pthread_cond_wait(&recv_task, &lock);
|
||||
}
|
||||
Vendored
+430
@@ -0,0 +1,430 @@
|
||||
LOCAL_PATH := $(call my-dir)
|
||||
|
||||
# Header only library
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE:= libphmap
|
||||
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/parallel-hashmap
|
||||
include $(BUILD_STATIC_LIBRARY)
|
||||
|
||||
# libxz.a
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE:= libxz
|
||||
LOCAL_C_INCLUDES := $(LOCAL_PATH)/xz-embedded
|
||||
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_C_INCLUDES)
|
||||
LOCAL_SRC_FILES := \
|
||||
xz-embedded/xz_crc32.c \
|
||||
xz-embedded/xz_dec_lzma2.c \
|
||||
xz-embedded/xz_dec_stream.c
|
||||
include $(BUILD_STATIC_LIBRARY)
|
||||
|
||||
# libnanopb.a
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE:= libnanopb
|
||||
LOCAL_C_INCLUDES := $(LOCAL_PATH)/nanopb
|
||||
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_C_INCLUDES)
|
||||
LOCAL_SRC_FILES := \
|
||||
nanopb/pb_common.c \
|
||||
nanopb/pb_decode.c \
|
||||
nanopb/pb_encode.c
|
||||
include $(BUILD_STATIC_LIBRARY)
|
||||
|
||||
# libfdt.a
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE:= libfdt
|
||||
LOCAL_C_INCLUDES := $(LOCAL_PATH)/dtc/libfdt
|
||||
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_C_INCLUDES)
|
||||
LOCAL_SRC_FILES := \
|
||||
dtc/libfdt/fdt.c \
|
||||
dtc/libfdt/fdt_addresses.c \
|
||||
dtc/libfdt/fdt_empty_tree.c \
|
||||
dtc/libfdt/fdt_overlay.c \
|
||||
dtc/libfdt/fdt_ro.c \
|
||||
dtc/libfdt/fdt_rw.c \
|
||||
dtc/libfdt/fdt_strerror.c \
|
||||
dtc/libfdt/fdt_sw.c \
|
||||
dtc/libfdt/fdt_wip.c
|
||||
include $(BUILD_STATIC_LIBRARY)
|
||||
|
||||
# liblz4.a
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := liblz4
|
||||
LOCAL_C_INCLUDES := $(LOCAL_PATH)/lz4/lib
|
||||
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_C_INCLUDES)
|
||||
LOCAL_SRC_FILES := \
|
||||
lz4/lib/lz4.c \
|
||||
lz4/lib/lz4frame.c \
|
||||
lz4/lib/lz4hc.c \
|
||||
lz4/lib/xxhash.c
|
||||
include $(BUILD_STATIC_LIBRARY)
|
||||
|
||||
# libbz2.a
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := libbz2
|
||||
LOCAL_C_INCLUDES := $(LOCAL_PATH)/bzip2
|
||||
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_C_INCLUDES)
|
||||
LOCAL_SRC_FILES := \
|
||||
bzip2/blocksort.c \
|
||||
bzip2/huffman.c \
|
||||
bzip2/crctable.c \
|
||||
bzip2/randtable.c \
|
||||
bzip2/compress.c \
|
||||
bzip2/decompress.c \
|
||||
bzip2/bzlib.c
|
||||
include $(BUILD_STATIC_LIBRARY)
|
||||
|
||||
# liblzma.a
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := liblzma
|
||||
LOCAL_C_INCLUDES := \
|
||||
$(LOCAL_PATH)/xz_config \
|
||||
$(LOCAL_PATH)/xz/src/common \
|
||||
$(LOCAL_PATH)/xz/src/liblzma/api \
|
||||
$(LOCAL_PATH)/xz/src/liblzma/check \
|
||||
$(LOCAL_PATH)/xz/src/liblzma/common \
|
||||
$(LOCAL_PATH)/xz/src/liblzma/delta \
|
||||
$(LOCAL_PATH)/xz/src/liblzma/lz \
|
||||
$(LOCAL_PATH)/xz/src/liblzma/lzma \
|
||||
$(LOCAL_PATH)/xz/src/liblzma/rangecoder \
|
||||
$(LOCAL_PATH)/xz/src/liblzma/simple \
|
||||
$(LOCAL_PATH)/xz/src/liblzma
|
||||
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/xz/src/liblzma/api
|
||||
LOCAL_SRC_FILES := \
|
||||
xz/src/common/tuklib_cpucores.c \
|
||||
xz/src/common/tuklib_exit.c \
|
||||
xz/src/common/tuklib_mbstr_fw.c \
|
||||
xz/src/common/tuklib_mbstr_width.c \
|
||||
xz/src/common/tuklib_open_stdxxx.c \
|
||||
xz/src/common/tuklib_physmem.c \
|
||||
xz/src/common/tuklib_progname.c \
|
||||
xz/src/liblzma/check/check.c \
|
||||
xz/src/liblzma/check/crc32_fast.c \
|
||||
xz/src/liblzma/check/crc32_table.c \
|
||||
xz/src/liblzma/check/crc64_fast.c \
|
||||
xz/src/liblzma/check/crc64_table.c \
|
||||
xz/src/liblzma/check/sha256.c \
|
||||
xz/src/liblzma/common/alone_decoder.c \
|
||||
xz/src/liblzma/common/alone_encoder.c \
|
||||
xz/src/liblzma/common/auto_decoder.c \
|
||||
xz/src/liblzma/common/block_buffer_decoder.c \
|
||||
xz/src/liblzma/common/block_buffer_encoder.c \
|
||||
xz/src/liblzma/common/block_decoder.c \
|
||||
xz/src/liblzma/common/block_encoder.c \
|
||||
xz/src/liblzma/common/block_header_decoder.c \
|
||||
xz/src/liblzma/common/block_header_encoder.c \
|
||||
xz/src/liblzma/common/block_util.c \
|
||||
xz/src/liblzma/common/common.c \
|
||||
xz/src/liblzma/common/easy_buffer_encoder.c \
|
||||
xz/src/liblzma/common/easy_decoder_memusage.c \
|
||||
xz/src/liblzma/common/easy_encoder.c \
|
||||
xz/src/liblzma/common/easy_encoder_memusage.c \
|
||||
xz/src/liblzma/common/easy_preset.c \
|
||||
xz/src/liblzma/common/filter_buffer_decoder.c \
|
||||
xz/src/liblzma/common/filter_buffer_encoder.c \
|
||||
xz/src/liblzma/common/filter_common.c \
|
||||
xz/src/liblzma/common/filter_decoder.c \
|
||||
xz/src/liblzma/common/filter_encoder.c \
|
||||
xz/src/liblzma/common/filter_flags_decoder.c \
|
||||
xz/src/liblzma/common/filter_flags_encoder.c \
|
||||
xz/src/liblzma/common/hardware_cputhreads.c \
|
||||
xz/src/liblzma/common/hardware_physmem.c \
|
||||
xz/src/liblzma/common/index.c \
|
||||
xz/src/liblzma/common/index_decoder.c \
|
||||
xz/src/liblzma/common/index_encoder.c \
|
||||
xz/src/liblzma/common/index_hash.c \
|
||||
xz/src/liblzma/common/outqueue.c \
|
||||
xz/src/liblzma/common/stream_buffer_decoder.c \
|
||||
xz/src/liblzma/common/stream_buffer_encoder.c \
|
||||
xz/src/liblzma/common/stream_decoder.c \
|
||||
xz/src/liblzma/common/stream_encoder.c \
|
||||
xz/src/liblzma/common/stream_encoder_mt.c \
|
||||
xz/src/liblzma/common/stream_flags_common.c \
|
||||
xz/src/liblzma/common/stream_flags_decoder.c \
|
||||
xz/src/liblzma/common/stream_flags_encoder.c \
|
||||
xz/src/liblzma/common/vli_decoder.c \
|
||||
xz/src/liblzma/common/vli_encoder.c \
|
||||
xz/src/liblzma/common/vli_size.c \
|
||||
xz/src/liblzma/delta/delta_common.c \
|
||||
xz/src/liblzma/delta/delta_decoder.c \
|
||||
xz/src/liblzma/delta/delta_encoder.c \
|
||||
xz/src/liblzma/lz/lz_decoder.c \
|
||||
xz/src/liblzma/lz/lz_encoder.c \
|
||||
xz/src/liblzma/lz/lz_encoder_mf.c \
|
||||
xz/src/liblzma/lzma/fastpos_table.c \
|
||||
xz/src/liblzma/lzma/fastpos_tablegen.c \
|
||||
xz/src/liblzma/lzma/lzma2_decoder.c \
|
||||
xz/src/liblzma/lzma/lzma2_encoder.c \
|
||||
xz/src/liblzma/lzma/lzma_decoder.c \
|
||||
xz/src/liblzma/lzma/lzma_encoder.c \
|
||||
xz/src/liblzma/lzma/lzma_encoder_optimum_fast.c \
|
||||
xz/src/liblzma/lzma/lzma_encoder_optimum_normal.c \
|
||||
xz/src/liblzma/lzma/lzma_encoder_presets.c \
|
||||
xz/src/liblzma/rangecoder/price_table.c \
|
||||
xz/src/liblzma/rangecoder/price_tablegen.c \
|
||||
xz/src/liblzma/simple/arm.c \
|
||||
xz/src/liblzma/simple/armthumb.c \
|
||||
xz/src/liblzma/simple/ia64.c \
|
||||
xz/src/liblzma/simple/powerpc.c \
|
||||
xz/src/liblzma/simple/simple_coder.c \
|
||||
xz/src/liblzma/simple/simple_decoder.c \
|
||||
xz/src/liblzma/simple/simple_encoder.c \
|
||||
xz/src/liblzma/simple/sparc.c \
|
||||
xz/src/liblzma/simple/x86.c
|
||||
LOCAL_CFLAGS := -DHAVE_CONFIG_H -Wno-implicit-function-declaration
|
||||
include $(BUILD_STATIC_LIBRARY)
|
||||
|
||||
SE_PATH := $(LOCAL_PATH)/selinux
|
||||
|
||||
# libsepol.a
|
||||
include $(CLEAR_VARS)
|
||||
LIBSEPOL := $(SE_PATH)/libsepol/include $(SE_PATH)/libsepol/cil/include
|
||||
LOCAL_MODULE := libsepol
|
||||
LOCAL_C_INCLUDES := $(LIBSEPOL) $(LOCAL_PATH)/selinux/libsepol/src
|
||||
LOCAL_EXPORT_C_INCLUDES := $(LIBSEPOL)
|
||||
LOCAL_SRC_FILES := \
|
||||
selinux/libsepol/src/assertion.c \
|
||||
selinux/libsepol/src/avrule_block.c \
|
||||
selinux/libsepol/src/avtab.c \
|
||||
selinux/libsepol/src/boolean_record.c \
|
||||
selinux/libsepol/src/booleans.c \
|
||||
selinux/libsepol/src/conditional.c \
|
||||
selinux/libsepol/src/constraint.c \
|
||||
selinux/libsepol/src/context.c \
|
||||
selinux/libsepol/src/context_record.c \
|
||||
selinux/libsepol/src/debug.c \
|
||||
selinux/libsepol/src/ebitmap.c \
|
||||
selinux/libsepol/src/expand.c \
|
||||
selinux/libsepol/src/handle.c \
|
||||
selinux/libsepol/src/hashtab.c \
|
||||
selinux/libsepol/src/hierarchy.c \
|
||||
selinux/libsepol/src/ibendport_record.c \
|
||||
selinux/libsepol/src/ibendports.c \
|
||||
selinux/libsepol/src/ibpkey_record.c \
|
||||
selinux/libsepol/src/ibpkeys.c \
|
||||
selinux/libsepol/src/iface_record.c \
|
||||
selinux/libsepol/src/interfaces.c \
|
||||
selinux/libsepol/src/kernel_to_cil.c \
|
||||
selinux/libsepol/src/kernel_to_common.c \
|
||||
selinux/libsepol/src/kernel_to_conf.c \
|
||||
selinux/libsepol/src/link.c \
|
||||
selinux/libsepol/src/mls.c \
|
||||
selinux/libsepol/src/module.c \
|
||||
selinux/libsepol/src/module_to_cil.c \
|
||||
selinux/libsepol/src/node_record.c \
|
||||
selinux/libsepol/src/nodes.c \
|
||||
selinux/libsepol/src/optimize.c \
|
||||
selinux/libsepol/src/polcaps.c \
|
||||
selinux/libsepol/src/policydb.c \
|
||||
selinux/libsepol/src/policydb_convert.c \
|
||||
selinux/libsepol/src/policydb_public.c \
|
||||
selinux/libsepol/src/policydb_validate.c \
|
||||
selinux/libsepol/src/port_record.c \
|
||||
selinux/libsepol/src/ports.c \
|
||||
selinux/libsepol/src/services.c \
|
||||
selinux/libsepol/src/sidtab.c \
|
||||
selinux/libsepol/src/symtab.c \
|
||||
selinux/libsepol/src/user_record.c \
|
||||
selinux/libsepol/src/users.c \
|
||||
selinux/libsepol/src/util.c \
|
||||
selinux/libsepol/src/write.c \
|
||||
selinux/libsepol/cil/src/cil.c \
|
||||
selinux/libsepol/cil/src/cil_binary.c \
|
||||
selinux/libsepol/cil/src/cil_build_ast.c \
|
||||
selinux/libsepol/cil/src/cil_copy_ast.c \
|
||||
selinux/libsepol/cil/src/cil_find.c \
|
||||
selinux/libsepol/cil/src/cil_fqn.c \
|
||||
selinux/libsepol/cil/src/cil_lexer.c \
|
||||
selinux/libsepol/cil/src/cil_list.c \
|
||||
selinux/libsepol/cil/src/cil_log.c \
|
||||
selinux/libsepol/cil/src/cil_mem.c \
|
||||
selinux/libsepol/cil/src/cil_parser.c \
|
||||
selinux/libsepol/cil/src/cil_policy.c \
|
||||
selinux/libsepol/cil/src/cil_post.c \
|
||||
selinux/libsepol/cil/src/cil_reset_ast.c \
|
||||
selinux/libsepol/cil/src/cil_resolve_ast.c \
|
||||
selinux/libsepol/cil/src/cil_stack.c \
|
||||
selinux/libsepol/cil/src/cil_strpool.c \
|
||||
selinux/libsepol/cil/src/cil_symtab.c \
|
||||
selinux/libsepol/cil/src/cil_tree.c \
|
||||
selinux/libsepol/cil/src/cil_verify.c \
|
||||
selinux/libsepol/cil/src/cil_write_ast.c
|
||||
|
||||
LOCAL_CFLAGS := -Wno-unused-but-set-variable
|
||||
include $(BUILD_STATIC_LIBRARY)
|
||||
|
||||
# libselinux.a
|
||||
include $(CLEAR_VARS)
|
||||
LIBSELINUX := $(SE_PATH)/libselinux/include
|
||||
LOCAL_MODULE:= libselinux
|
||||
LOCAL_C_INCLUDES := $(LIBSELINUX)
|
||||
LOCAL_EXPORT_C_INCLUDES := $(LIBSELINUX)
|
||||
LOCAL_STATIC_LIBRARIES := libpcre2
|
||||
LOCAL_CFLAGS := \
|
||||
-Wno-implicit-function-declaration -Wno-int-conversion -Wno-unused-function \
|
||||
-Wno-macro-redefined -Wno-unused-but-set-variable -D_GNU_SOURCE -DUSE_PCRE2 \
|
||||
-DNO_PERSISTENTLY_STORED_PATTERNS -DDISABLE_SETRANS -DDISABLE_BOOL \
|
||||
-DNO_MEDIA_BACKEND -DNO_X_BACKEND -DNO_DB_BACKEND -DNO_ANDROID_BACKEND \
|
||||
-Dfgets_unlocked=fgets -D'__fsetlocking(...)='
|
||||
LOCAL_SRC_FILES := \
|
||||
selinux/libselinux/src/avc.c \
|
||||
selinux/libselinux/src/avc_internal.c \
|
||||
selinux/libselinux/src/avc_sidtab.c \
|
||||
selinux/libselinux/src/booleans.c \
|
||||
selinux/libselinux/src/callbacks.c \
|
||||
selinux/libselinux/src/canonicalize_context.c \
|
||||
selinux/libselinux/src/checkAccess.c \
|
||||
selinux/libselinux/src/check_context.c \
|
||||
selinux/libselinux/src/checkreqprot.c \
|
||||
selinux/libselinux/src/compute_av.c \
|
||||
selinux/libselinux/src/compute_create.c \
|
||||
selinux/libselinux/src/compute_member.c \
|
||||
selinux/libselinux/src/compute_relabel.c \
|
||||
selinux/libselinux/src/compute_user.c \
|
||||
selinux/libselinux/src/context.c \
|
||||
selinux/libselinux/src/deny_unknown.c \
|
||||
selinux/libselinux/src/disable.c \
|
||||
selinux/libselinux/src/enabled.c \
|
||||
selinux/libselinux/src/fgetfilecon.c \
|
||||
selinux/libselinux/src/freecon.c \
|
||||
selinux/libselinux/src/freeconary.c \
|
||||
selinux/libselinux/src/fsetfilecon.c \
|
||||
selinux/libselinux/src/get_context_list.c \
|
||||
selinux/libselinux/src/get_default_type.c \
|
||||
selinux/libselinux/src/get_initial_context.c \
|
||||
selinux/libselinux/src/getenforce.c \
|
||||
selinux/libselinux/src/getfilecon.c \
|
||||
selinux/libselinux/src/getpeercon.c \
|
||||
selinux/libselinux/src/init.c \
|
||||
selinux/libselinux/src/is_customizable_type.c \
|
||||
selinux/libselinux/src/label.c \
|
||||
selinux/libselinux/src/label_file.c \
|
||||
selinux/libselinux/src/label_support.c \
|
||||
selinux/libselinux/src/lgetfilecon.c \
|
||||
selinux/libselinux/src/load_policy.c \
|
||||
selinux/libselinux/src/lsetfilecon.c \
|
||||
selinux/libselinux/src/mapping.c \
|
||||
selinux/libselinux/src/matchmediacon.c \
|
||||
selinux/libselinux/src/matchpathcon.c \
|
||||
selinux/libselinux/src/policyvers.c \
|
||||
selinux/libselinux/src/procattr.c \
|
||||
selinux/libselinux/src/query_user_context.c \
|
||||
selinux/libselinux/src/regex.c \
|
||||
selinux/libselinux/src/reject_unknown.c \
|
||||
selinux/libselinux/src/selinux_check_securetty_context.c \
|
||||
selinux/libselinux/src/selinux_config.c \
|
||||
selinux/libselinux/src/selinux_restorecon.c \
|
||||
selinux/libselinux/src/sestatus.c \
|
||||
selinux/libselinux/src/setenforce.c \
|
||||
selinux/libselinux/src/setexecfilecon.c \
|
||||
selinux/libselinux/src/setfilecon.c \
|
||||
selinux/libselinux/src/setrans_client.c \
|
||||
selinux/libselinux/src/seusers.c \
|
||||
selinux/libselinux/src/sha1.c \
|
||||
selinux/libselinux/src/stringrep.c \
|
||||
selinux/libselinux/src/validatetrans.c
|
||||
include $(BUILD_STATIC_LIBRARY)
|
||||
|
||||
# libpcre2.a
|
||||
include $(CLEAR_VARS)
|
||||
LIBPCRE2 := $(LOCAL_PATH)/pcre/include
|
||||
LOCAL_MODULE:= libpcre2
|
||||
LOCAL_CFLAGS := -DHAVE_CONFIG_H -DPCRE2_CODE_UNIT_WIDTH=8
|
||||
LOCAL_C_INCLUDES := $(LIBPCRE2) $(LIBPCRE2)_internal
|
||||
LOCAL_EXPORT_C_INCLUDES := $(LIBPCRE2)
|
||||
LOCAL_SRC_FILES := \
|
||||
pcre/src/pcre2_auto_possess.c \
|
||||
pcre/src/pcre2_compile.c \
|
||||
pcre/src/pcre2_config.c \
|
||||
pcre/src/pcre2_context.c \
|
||||
pcre/src/pcre2_convert.c \
|
||||
pcre/src/pcre2_dfa_match.c \
|
||||
pcre/src/pcre2_error.c \
|
||||
pcre/src/pcre2_extuni.c \
|
||||
pcre/src/pcre2_find_bracket.c \
|
||||
pcre/src/pcre2_fuzzsupport.c \
|
||||
pcre/src/pcre2_maketables.c \
|
||||
pcre/src/pcre2_match.c \
|
||||
pcre/src/pcre2_match_data.c \
|
||||
pcre/src/pcre2_jit_compile.c \
|
||||
pcre/src/pcre2_newline.c \
|
||||
pcre/src/pcre2_ord2utf.c \
|
||||
pcre/src/pcre2_pattern_info.c \
|
||||
pcre/src/pcre2_script_run.c \
|
||||
pcre/src/pcre2_serialize.c \
|
||||
pcre/src/pcre2_string_utils.c \
|
||||
pcre/src/pcre2_study.c \
|
||||
pcre/src/pcre2_substitute.c \
|
||||
pcre/src/pcre2_substring.c \
|
||||
pcre/src/pcre2_tables.c \
|
||||
pcre/src/pcre2_ucd.c \
|
||||
pcre/src/pcre2_valid_utf.c \
|
||||
pcre/src/pcre2_xclass.c \
|
||||
pcre2_workaround.c
|
||||
include $(BUILD_STATIC_LIBRARY)
|
||||
|
||||
# libxhook.a
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE:= libxhook
|
||||
LOCAL_C_INCLUDES := $(LOCAL_PATH)/libxhook
|
||||
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_C_INCLUDES)
|
||||
LOCAL_CFLAGS := -Wall -Wextra -Werror -fvisibility=hidden -D__android_log_print=magisk_log_print
|
||||
LOCAL_CONLYFLAGS := -std=c11
|
||||
LOCAL_SRC_FILES := \
|
||||
libxhook/xh_log.c \
|
||||
libxhook/xh_version.c \
|
||||
libxhook/xh_jni.c \
|
||||
libxhook/xhook.c \
|
||||
libxhook/xh_core.c \
|
||||
libxhook/xh_util.c \
|
||||
libxhook/xh_elf.c
|
||||
include $(BUILD_STATIC_LIBRARY)
|
||||
|
||||
# libz.a
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE:= libz
|
||||
LOCAL_C_INCLUDES := $(LOCAL_PATH)/zlib
|
||||
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_C_INCLUDES)
|
||||
LOCAL_CFLAGS := -DHAVE_HIDDEN -DZLIB_CONST -Wall -Werror -Wno-unused -Wno-unused-parameter
|
||||
LOCAL_SRC_FILES := \
|
||||
zlib/adler32.c \
|
||||
zlib/compress.c \
|
||||
zlib/cpu_features.c \
|
||||
zlib/crc32.c \
|
||||
zlib/deflate.c \
|
||||
zlib/gzclose.c \
|
||||
zlib/gzlib.c \
|
||||
zlib/gzread.c \
|
||||
zlib/gzwrite.c \
|
||||
zlib/infback.c \
|
||||
zlib/inffast.c \
|
||||
zlib/inflate.c \
|
||||
zlib/inftrees.c \
|
||||
zlib/trees.c \
|
||||
zlib/uncompr.c \
|
||||
zlib/zutil.c
|
||||
include $(BUILD_STATIC_LIBRARY)
|
||||
|
||||
# libzopfli.a
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE:= libzopfli
|
||||
LOCAL_C_INCLUDES := $(LOCAL_PATH)/zopfli/src
|
||||
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_C_INCLUDES)
|
||||
LOCAL_CFLAGS := -Wall -Werror -Wno-unused -Wno-unused-parameter
|
||||
LOCAL_SRC_FILES := \
|
||||
zopfli/src/zopfli/blocksplitter.c \
|
||||
zopfli/src/zopfli/cache.c \
|
||||
zopfli/src/zopfli/deflate.c \
|
||||
zopfli/src/zopfli/gzip_container.c \
|
||||
zopfli/src/zopfli/hash.c \
|
||||
zopfli/src/zopfli/katajainen.c \
|
||||
zopfli/src/zopfli/lz77.c \
|
||||
zopfli/src/zopfli/squeeze.c \
|
||||
zopfli/src/zopfli/tree.c \
|
||||
zopfli/src/zopfli/util.c \
|
||||
zopfli/src/zopfli/zlib_container.c \
|
||||
zopfli/src/zopfli/zopfli_lib.c
|
||||
include $(BUILD_STATIC_LIBRARY)
|
||||
|
||||
CWD := $(LOCAL_PATH)
|
||||
include $(CWD)/systemproperties/Android.mk
|
||||
include $(CWD)/mincrypt/Android.mk
|
||||
include $(CWD)/libcxx/Android.mk
|
||||
+1
Submodule native/src/external/busybox added at c2479d10d9
+1
Submodule native/src/external/bzip2 added at 67d818584d
+1
Submodule native/src/external/cxx-rs added at 650e64bc39
+1
Submodule native/src/external/dtc added at c0c2e115f8
+1
Submodule native/src/external/libcxx added at 9b40e8b936
@@ -0,0 +1,18 @@
|
||||
.DS_Store
|
||||
|
||||
libxhook/libs/
|
||||
libxhook/obj/
|
||||
libbiz/libs/
|
||||
libbiz/obj/
|
||||
libtest/libs/
|
||||
libtest/obj/
|
||||
|
||||
#for xhookwrapper
|
||||
build/
|
||||
.gradle/
|
||||
.idea/
|
||||
local.properties
|
||||
*.iml
|
||||
*.log
|
||||
*.so
|
||||
xhookwrapper/xhook/libs/
|
||||
Vendored
+90
@@ -0,0 +1,90 @@
|
||||
Copyright (c) 2018-present, iQIYI, Inc. All rights reserved.
|
||||
|
||||
Most source code in xhook are MIT licensed. Some other source code
|
||||
have BSD-style licenses.
|
||||
|
||||
A copy of the MIT License is included in this file.
|
||||
|
||||
|
||||
Source code Licensed under the BSD 2-Clause License
|
||||
===================================================
|
||||
|
||||
tree.h
|
||||
|
||||
Copyright 2002 Niels Provos <provos@citi.umich.edu>
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
Source code Licensed under the BSD 3-Clause License
|
||||
===================================================
|
||||
|
||||
queue.h
|
||||
|
||||
Copyright (c) 1991, 1993 The Regents of the University of California.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
3. Neither the name of the University nor the names of its contributors
|
||||
may be used to endorse or promote products derived from this software
|
||||
without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGE.
|
||||
|
||||
|
||||
Terms of the MIT License
|
||||
========================
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+393
@@ -0,0 +1,393 @@
|
||||
Attribution 4.0 International
|
||||
|
||||
=======================================================================
|
||||
|
||||
Creative Commons Corporation ("Creative Commons") is not a law firm and
|
||||
does not provide legal services or legal advice. Distribution of
|
||||
Creative Commons public licenses does not create a lawyer-client or
|
||||
other relationship. Creative Commons makes its licenses and related
|
||||
information available on an "as-is" basis. Creative Commons gives no
|
||||
warranties regarding its licenses, any material licensed under their
|
||||
terms and conditions, or any related information. Creative Commons
|
||||
disclaims all liability for damages resulting from their use to the
|
||||
fullest extent possible.
|
||||
|
||||
Using Creative Commons Public Licenses
|
||||
|
||||
Creative Commons public licenses provide a standard set of terms and
|
||||
conditions that creators and other rights holders may use to share
|
||||
original works of authorship and other material subject to copyright
|
||||
and certain other rights specified in the public license below. The
|
||||
following considerations are for informational purposes only, are not
|
||||
exhaustive, and do not form part of our licenses.
|
||||
|
||||
Considerations for licensors: Our public licenses are
|
||||
intended for use by those authorized to give the public
|
||||
permission to use material in ways otherwise restricted by
|
||||
copyright and certain other rights. Our licenses are
|
||||
irrevocable. Licensors should read and understand the terms
|
||||
and conditions of the license they choose before applying it.
|
||||
Licensors should also secure all rights necessary before
|
||||
applying our licenses so that the public can reuse the
|
||||
material as expected. Licensors should clearly mark any
|
||||
material not subject to the license. This includes other CC-
|
||||
licensed material, or material used under an exception or
|
||||
limitation to copyright. More considerations for licensors:
|
||||
wiki.creativecommons.org/Considerations_for_licensors
|
||||
|
||||
Considerations for the public: By using one of our public
|
||||
licenses, a licensor grants the public permission to use the
|
||||
licensed material under specified terms and conditions. If
|
||||
the licensor's permission is not necessary for any reason--for
|
||||
example, because of any applicable exception or limitation to
|
||||
copyright--then that use is not regulated by the license. Our
|
||||
licenses grant only permissions under copyright and certain
|
||||
other rights that a licensor has authority to grant. Use of
|
||||
the licensed material may still be restricted for other
|
||||
reasons, including because others have copyright or other
|
||||
rights in the material. A licensor may make special requests,
|
||||
such as asking that all changes be marked or described.
|
||||
Although not required by our licenses, you are encouraged to
|
||||
respect those requests where reasonable. More_considerations
|
||||
for the public:
|
||||
wiki.creativecommons.org/Considerations_for_licensees
|
||||
|
||||
=======================================================================
|
||||
|
||||
Creative Commons Attribution 4.0 International Public License
|
||||
|
||||
By exercising the Licensed Rights (defined below), You accept and agree
|
||||
to be bound by the terms and conditions of this Creative Commons
|
||||
Attribution 4.0 International Public License ("Public License"). To the
|
||||
extent this Public License may be interpreted as a contract, You are
|
||||
granted the Licensed Rights in consideration of Your acceptance of
|
||||
these terms and conditions, and the Licensor grants You such rights in
|
||||
consideration of benefits the Licensor receives from making the
|
||||
Licensed Material available under these terms and conditions.
|
||||
|
||||
|
||||
Section 1 -- Definitions.
|
||||
|
||||
a. Adapted Material means material subject to Copyright and Similar
|
||||
Rights that is derived from or based upon the Licensed Material
|
||||
and in which the Licensed Material is translated, altered,
|
||||
arranged, transformed, or otherwise modified in a manner requiring
|
||||
permission under the Copyright and Similar Rights held by the
|
||||
Licensor. For purposes of this Public License, where the Licensed
|
||||
Material is a musical work, performance, or sound recording,
|
||||
Adapted Material is always produced where the Licensed Material is
|
||||
synched in timed relation with a moving image.
|
||||
|
||||
b. Adapter's License means the license You apply to Your Copyright
|
||||
and Similar Rights in Your contributions to Adapted Material in
|
||||
accordance with the terms and conditions of this Public License.
|
||||
|
||||
c. Copyright and Similar Rights means copyright and/or similar rights
|
||||
closely related to copyright including, without limitation,
|
||||
performance, broadcast, sound recording, and Sui Generis Database
|
||||
Rights, without regard to how the rights are labeled or
|
||||
categorized. For purposes of this Public License, the rights
|
||||
specified in Section 2(b)(1)-(2) are not Copyright and Similar
|
||||
Rights.
|
||||
|
||||
d. Effective Technological Measures means those measures that, in the
|
||||
absence of proper authority, may not be circumvented under laws
|
||||
fulfilling obligations under Article 11 of the WIPO Copyright
|
||||
Treaty adopted on December 20, 1996, and/or similar international
|
||||
agreements.
|
||||
|
||||
e. Exceptions and Limitations means fair use, fair dealing, and/or
|
||||
any other exception or limitation to Copyright and Similar Rights
|
||||
that applies to Your use of the Licensed Material.
|
||||
|
||||
f. Licensed Material means the artistic or literary work, database,
|
||||
or other material to which the Licensor applied this Public
|
||||
License.
|
||||
|
||||
g. Licensed Rights means the rights granted to You subject to the
|
||||
terms and conditions of this Public License, which are limited to
|
||||
all Copyright and Similar Rights that apply to Your use of the
|
||||
Licensed Material and that the Licensor has authority to license.
|
||||
|
||||
h. Licensor means the individual(s) or entity(ies) granting rights
|
||||
under this Public License.
|
||||
|
||||
i. Share means to provide material to the public by any means or
|
||||
process that requires permission under the Licensed Rights, such
|
||||
as reproduction, public display, public performance, distribution,
|
||||
dissemination, communication, or importation, and to make material
|
||||
available to the public including in ways that members of the
|
||||
public may access the material from a place and at a time
|
||||
individually chosen by them.
|
||||
|
||||
j. Sui Generis Database Rights means rights other than copyright
|
||||
resulting from Directive 96/9/EC of the European Parliament and of
|
||||
the Council of 11 March 1996 on the legal protection of databases,
|
||||
as amended and/or succeeded, as well as other essentially
|
||||
equivalent rights anywhere in the world.
|
||||
|
||||
k. You means the individual or entity exercising the Licensed Rights
|
||||
under this Public License. Your has a corresponding meaning.
|
||||
|
||||
|
||||
Section 2 -- Scope.
|
||||
|
||||
a. License grant.
|
||||
|
||||
1. Subject to the terms and conditions of this Public License,
|
||||
the Licensor hereby grants You a worldwide, royalty-free,
|
||||
non-sublicensable, non-exclusive, irrevocable license to
|
||||
exercise the Licensed Rights in the Licensed Material to:
|
||||
|
||||
a. reproduce and Share the Licensed Material, in whole or
|
||||
in part; and
|
||||
|
||||
b. produce, reproduce, and Share Adapted Material.
|
||||
|
||||
2. Exceptions and Limitations. For the avoidance of doubt, where
|
||||
Exceptions and Limitations apply to Your use, this Public
|
||||
License does not apply, and You do not need to comply with
|
||||
its terms and conditions.
|
||||
|
||||
3. Term. The term of this Public License is specified in Section
|
||||
6(a).
|
||||
|
||||
4. Media and formats; technical modifications allowed. The
|
||||
Licensor authorizes You to exercise the Licensed Rights in
|
||||
all media and formats whether now known or hereafter created,
|
||||
and to make technical modifications necessary to do so. The
|
||||
Licensor waives and/or agrees not to assert any right or
|
||||
authority to forbid You from making technical modifications
|
||||
necessary to exercise the Licensed Rights, including
|
||||
technical modifications necessary to circumvent Effective
|
||||
Technological Measures. For purposes of this Public License,
|
||||
simply making modifications authorized by this Section 2(a)
|
||||
(4) never produces Adapted Material.
|
||||
|
||||
5. Downstream recipients.
|
||||
|
||||
a. Offer from the Licensor -- Licensed Material. Every
|
||||
recipient of the Licensed Material automatically
|
||||
receives an offer from the Licensor to exercise the
|
||||
Licensed Rights under the terms and conditions of this
|
||||
Public License.
|
||||
|
||||
b. No downstream restrictions. You may not offer or impose
|
||||
any additional or different terms or conditions on, or
|
||||
apply any Effective Technological Measures to, the
|
||||
Licensed Material if doing so restricts exercise of the
|
||||
Licensed Rights by any recipient of the Licensed
|
||||
Material.
|
||||
|
||||
6. No endorsement. Nothing in this Public License constitutes or
|
||||
may be construed as permission to assert or imply that You
|
||||
are, or that Your use of the Licensed Material is, connected
|
||||
with, or sponsored, endorsed, or granted official status by,
|
||||
the Licensor or others designated to receive attribution as
|
||||
provided in Section 3(a)(1)(A)(i).
|
||||
|
||||
b. Other rights.
|
||||
|
||||
1. Moral rights, such as the right of integrity, are not
|
||||
licensed under this Public License, nor are publicity,
|
||||
privacy, and/or other similar personality rights; however, to
|
||||
the extent possible, the Licensor waives and/or agrees not to
|
||||
assert any such rights held by the Licensor to the limited
|
||||
extent necessary to allow You to exercise the Licensed
|
||||
Rights, but not otherwise.
|
||||
|
||||
2. Patent and trademark rights are not licensed under this
|
||||
Public License.
|
||||
|
||||
3. To the extent possible, the Licensor waives any right to
|
||||
collect royalties from You for the exercise of the Licensed
|
||||
Rights, whether directly or through a collecting society
|
||||
under any voluntary or waivable statutory or compulsory
|
||||
licensing scheme. In all other cases the Licensor expressly
|
||||
reserves any right to collect such royalties.
|
||||
|
||||
|
||||
Section 3 -- License Conditions.
|
||||
|
||||
Your exercise of the Licensed Rights is expressly made subject to the
|
||||
following conditions.
|
||||
|
||||
a. Attribution.
|
||||
|
||||
1. If You Share the Licensed Material (including in modified
|
||||
form), You must:
|
||||
|
||||
a. retain the following if it is supplied by the Licensor
|
||||
with the Licensed Material:
|
||||
|
||||
i. identification of the creator(s) of the Licensed
|
||||
Material and any others designated to receive
|
||||
attribution, in any reasonable manner requested by
|
||||
the Licensor (including by pseudonym if
|
||||
designated);
|
||||
|
||||
ii. a copyright notice;
|
||||
|
||||
iii. a notice that refers to this Public License;
|
||||
|
||||
iv. a notice that refers to the disclaimer of
|
||||
warranties;
|
||||
|
||||
v. a URI or hyperlink to the Licensed Material to the
|
||||
extent reasonably practicable;
|
||||
|
||||
b. indicate if You modified the Licensed Material and
|
||||
retain an indication of any previous modifications; and
|
||||
|
||||
c. indicate the Licensed Material is licensed under this
|
||||
Public License, and include the text of, or the URI or
|
||||
hyperlink to, this Public License.
|
||||
|
||||
2. You may satisfy the conditions in Section 3(a)(1) in any
|
||||
reasonable manner based on the medium, means, and context in
|
||||
which You Share the Licensed Material. For example, it may be
|
||||
reasonable to satisfy the conditions by providing a URI or
|
||||
hyperlink to a resource that includes the required
|
||||
information.
|
||||
|
||||
3. If requested by the Licensor, You must remove any of the
|
||||
information required by Section 3(a)(1)(A) to the extent
|
||||
reasonably practicable.
|
||||
|
||||
4. If You Share Adapted Material You produce, the Adapter's
|
||||
License You apply must not prevent recipients of the Adapted
|
||||
Material from complying with this Public License.
|
||||
|
||||
|
||||
Section 4 -- Sui Generis Database Rights.
|
||||
|
||||
Where the Licensed Rights include Sui Generis Database Rights that
|
||||
apply to Your use of the Licensed Material:
|
||||
|
||||
a. for the avoidance of doubt, Section 2(a)(1) grants You the right
|
||||
to extract, reuse, reproduce, and Share all or a substantial
|
||||
portion of the contents of the database;
|
||||
|
||||
b. if You include all or a substantial portion of the database
|
||||
contents in a database in which You have Sui Generis Database
|
||||
Rights, then the database in which You have Sui Generis Database
|
||||
Rights (but not its individual contents) is Adapted Material; and
|
||||
|
||||
c. You must comply with the conditions in Section 3(a) if You Share
|
||||
all or a substantial portion of the contents of the database.
|
||||
|
||||
For the avoidance of doubt, this Section 4 supplements and does not
|
||||
replace Your obligations under this Public License where the Licensed
|
||||
Rights include other Copyright and Similar Rights.
|
||||
|
||||
|
||||
Section 5 -- Disclaimer of Warranties and Limitation of Liability.
|
||||
|
||||
a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
|
||||
EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
|
||||
AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
|
||||
ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
|
||||
IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
|
||||
WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
|
||||
ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
|
||||
KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
|
||||
ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
|
||||
|
||||
b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
|
||||
TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
|
||||
NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
|
||||
INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
|
||||
COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
|
||||
USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
|
||||
ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
|
||||
DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
|
||||
IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
|
||||
|
||||
c. The disclaimer of warranties and limitation of liability provided
|
||||
above shall be interpreted in a manner that, to the extent
|
||||
possible, most closely approximates an absolute disclaimer and
|
||||
waiver of all liability.
|
||||
|
||||
|
||||
Section 6 -- Term and Termination.
|
||||
|
||||
a. This Public License applies for the term of the Copyright and
|
||||
Similar Rights licensed here. However, if You fail to comply with
|
||||
this Public License, then Your rights under this Public License
|
||||
terminate automatically.
|
||||
|
||||
b. Where Your right to use the Licensed Material has terminated under
|
||||
Section 6(a), it reinstates:
|
||||
|
||||
1. automatically as of the date the violation is cured, provided
|
||||
it is cured within 30 days of Your discovery of the
|
||||
violation; or
|
||||
|
||||
2. upon express reinstatement by the Licensor.
|
||||
|
||||
For the avoidance of doubt, this Section 6(b) does not affect any
|
||||
right the Licensor may have to seek remedies for Your violations
|
||||
of this Public License.
|
||||
|
||||
c. For the avoidance of doubt, the Licensor may also offer the
|
||||
Licensed Material under separate terms or conditions or stop
|
||||
distributing the Licensed Material at any time; however, doing so
|
||||
will not terminate this Public License.
|
||||
|
||||
d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
|
||||
License.
|
||||
|
||||
|
||||
Section 7 -- Other Terms and Conditions.
|
||||
|
||||
a. The Licensor shall not be bound by any additional or different
|
||||
terms or conditions communicated by You unless expressly agreed.
|
||||
|
||||
b. Any arrangements, understandings, or agreements regarding the
|
||||
Licensed Material not stated herein are separate from and
|
||||
independent of the terms and conditions of this Public License.
|
||||
|
||||
|
||||
Section 8 -- Interpretation.
|
||||
|
||||
a. For the avoidance of doubt, this Public License does not, and
|
||||
shall not be interpreted to, reduce, limit, restrict, or impose
|
||||
conditions on any use of the Licensed Material that could lawfully
|
||||
be made without permission under this Public License.
|
||||
|
||||
b. To the extent possible, if any provision of this Public License is
|
||||
deemed unenforceable, it shall be automatically reformed to the
|
||||
minimum extent necessary to make it enforceable. If the provision
|
||||
cannot be reformed, it shall be severed from this Public License
|
||||
without affecting the enforceability of the remaining terms and
|
||||
conditions.
|
||||
|
||||
c. No term or condition of this Public License will be waived and no
|
||||
failure to comply consented to unless expressly agreed to by the
|
||||
Licensor.
|
||||
|
||||
d. Nothing in this Public License constitutes or may be interpreted
|
||||
as a limitation upon, or waiver of, any privileges and immunities
|
||||
that apply to the Licensor or You, including from the legal
|
||||
processes of any jurisdiction or authority.
|
||||
|
||||
|
||||
=======================================================================
|
||||
|
||||
Creative Commons is not a party to its public licenses.
|
||||
Notwithstanding, Creative Commons may elect to apply one of its public
|
||||
licenses to material it publishes and in those instances will be
|
||||
considered the "Licensor." Except for the limited purpose of indicating
|
||||
that material is shared under a Creative Commons public license or as
|
||||
otherwise permitted by the Creative Commons policies published at
|
||||
creativecommons.org/policies, Creative Commons does not authorize the
|
||||
use of the trademark "Creative Commons" or any other trademark or logo
|
||||
of Creative Commons without its prior written consent including,
|
||||
without limitation, in connection with any unauthorized modifications
|
||||
to any of its public licenses or any other arrangements,
|
||||
understandings, or agreements concerning use of licensed material. For
|
||||
the avoidance of doubt, this paragraph does not form part of the public
|
||||
licenses.
|
||||
|
||||
Creative Commons may be contacted at creativecommons.org.
|
||||
+200
@@ -0,0 +1,200 @@
|
||||
<p align="center"><img src="https://github.com/iqiyi/xHook/blob/master/docs/xhooklogo.png?raw=true" alt="xhook" width="50%"></p>
|
||||
|
||||
# xHook
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
[README 中文版](README.zh-CN.md)
|
||||
|
||||
[Android PLT hook 概述 中文版](docs/overview/android_plt_hook_overview.zh-CN.md)
|
||||
|
||||
xHook is a PLT (Procedure Linkage Table) hook library for Android native ELF (executable and shared libraries).
|
||||
|
||||
xHook has been keeping optimized for stability and compatibility.
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
* Support Android 4.0 - 10 (API level 14 - 29).
|
||||
* Support armeabi, armeabi-v7a, arm64-v8a, x86 and x86_64.
|
||||
* Support **ELF HASH** and **GNU HASH** indexed symbols.
|
||||
* Support **SLEB128** encoded relocation info.
|
||||
* Support setting hook info via regular expressions.
|
||||
* Do not require root permission or any system permissions.
|
||||
* Do not depends on any third-party shared libraries.
|
||||
|
||||
|
||||
## Build
|
||||
|
||||
* Download [Android NDK r16b](https://developer.android.com/ndk/downloads/revision_history.html), set environment PATH. (support for armeabi has been removed since r17)
|
||||
|
||||
* Build and install the native libraries.
|
||||
|
||||
```
|
||||
./build_libs.sh
|
||||
./install_libs.sh
|
||||
```
|
||||
|
||||
|
||||
## Demo
|
||||
|
||||
```
|
||||
cd ./xhookwrapper/
|
||||
./gradlew assembleDebug
|
||||
adb install ./app/build/outputs/apk/debug/app-debug.apk
|
||||
```
|
||||
|
||||
|
||||
## API
|
||||
|
||||
External API header file: `libxhook/jni/xhook.h`
|
||||
|
||||
### 1. Register hook info
|
||||
|
||||
```c
|
||||
int xhook_register(const char *pathname_regex_str,
|
||||
const char *symbol,
|
||||
void *new_func,
|
||||
void **old_func);
|
||||
```
|
||||
|
||||
In current process's memory space, in every loaded ELF which pathname matches regular expression `pathname_regex_str`, every PLT entries to `symbol` will be **replaced with** `new_func`. The original one will be saved in `old_func`.
|
||||
|
||||
The `new_func` **must** have the same function declaration as the original one.
|
||||
|
||||
Return zero if successful, non-zero otherwise.
|
||||
|
||||
The regular expression for `pathname_regex_str` only support **POSIX BRE (Basic Regular Expression)**.
|
||||
|
||||
### 2. Ignore some hook info
|
||||
|
||||
```c
|
||||
int xhook_ignore(const char *pathname_regex_str,
|
||||
const char *symbol);
|
||||
```
|
||||
|
||||
Ignore some hook info according to `pathname_regex_str` and `symbol`, from registered hooks by `xhook_register`. If `symbol` is `NULL`, xhook will ignore all symbols from ELF which pathname matches `pathname_regex_str`.
|
||||
|
||||
Return zero if successful, non-zero otherwise.
|
||||
|
||||
The regular expression for `pathname_regex_str` only support **POSIX BRE**.
|
||||
|
||||
### 3. Do hook
|
||||
|
||||
```c
|
||||
int xhook_refresh(int async);
|
||||
```
|
||||
|
||||
Do the real hook operations according to the registered hook info.
|
||||
|
||||
Pass `1` to `async` for asynchronous hook. Pass `0` to `async` for synchronous hook.
|
||||
|
||||
Return zero if successful, non-zero otherwise.
|
||||
|
||||
xhook will keep a global cache for saving the last ELF loading info from `/proc/self/maps`. This cache will also be updated in `xhook_refresh`. With this cache, `xhook_refresh` can determine which ELF is newly loaded. We only need to do hook in these newly loaded ELF.
|
||||
|
||||
### 4. Clear cache
|
||||
|
||||
```c
|
||||
void xhook_clear();
|
||||
```
|
||||
|
||||
Clear all cache owned by xhook, reset all global flags to default value.
|
||||
|
||||
If you confirm that all PLT entries you want have been hooked, you could call this function to save some memory.
|
||||
|
||||
### 5. Enable/Disable debug info
|
||||
|
||||
```c
|
||||
void xhook_enable_debug(int flag);
|
||||
```
|
||||
|
||||
Pass `1` to `flag` for enable debug info. Pass `0` to `flag` for disable. (**disabled** by default)
|
||||
|
||||
Debug info will be sent to logcat with tag `xhook`.
|
||||
|
||||
### 6. Enable/Disable SFP (segmentation fault protection)
|
||||
|
||||
```c
|
||||
void xhook_enable_sigsegv_protection(int flag);
|
||||
```
|
||||
|
||||
Pass `1` to `flag` for enable SFP. Pass `0` to `flag` for disable. (**enabled** by default)
|
||||
|
||||
xhook is NOT a compliant business layer library. We have to calculate the value of some pointers directly. Reading or writing the memory pointed to by these pointers will cause a segmentation fault in some unusual situations and environment. The APP crash rate increased which caused by xhook is about one ten-millionth (0.0000001) according to our test. (The increased crash rate is also related to the ELFs and symbols you need to hook). Finally, we have to use some trick to prevent this harmless crashing. We called it SFP (segmentation fault protection) which consists of: `sigaction()`, `SIGSEGV`, `siglongjmp()` and `sigsetjmp()`.
|
||||
|
||||
**You should always enable SFP for release-APP, this will prevent your app from crashing. On the other hand, you should always disable SFP for debug-APP, so you can't miss any common coding mistakes that should be fixed.**
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
```c
|
||||
//detect memory leaks
|
||||
xhook_register(".*\\.so$", "malloc", my_malloc, NULL);
|
||||
xhook_register(".*\\.so$", "calloc", my_calloc, NULL);
|
||||
xhook_register(".*\\.so$", "realloc", my_realloc, NULL);
|
||||
xhook_register(".*\\.so$", "free", my_free, NULL);
|
||||
|
||||
//inspect sockets lifecycle
|
||||
xhook_register(".*\\.so$", "getaddrinfo", my_getaddrinfo, NULL);
|
||||
xhook_register(".*\\.so$", "socket", my_socket, NULL);
|
||||
xhook_register(".*\\.so$", "setsockopt" my_setsockopt, NULL);
|
||||
xhook_register(".*\\.so$", "bind", my_bind, NULL);
|
||||
xhook_register(".*\\.so$", "listen", my_listen, NULL);
|
||||
xhook_register(".*\\.so$", "connect", my_connect, NULL);
|
||||
xhook_register(".*\\.so$", "shutdown", my_shutdown, NULL);
|
||||
xhook_register(".*\\.so$", "close", my_close, NULL);
|
||||
|
||||
//filter off and save some android log to local file
|
||||
xhook_register(".*\\.so$", "__android_log_write", my_log_write, NULL);
|
||||
xhook_register(".*\\.so$", "__android_log_print", my_log_print, NULL);
|
||||
xhook_register(".*\\.so$", "__android_log_vprint", my_log_vprint, NULL);
|
||||
xhook_register(".*\\.so$", "__android_log_assert", my_log_assert, NULL);
|
||||
|
||||
//tracking (ignore linker and linker64)
|
||||
xhook_register("^/system/.*$", "mmap", my_mmap, NULL);
|
||||
xhook_register("^/vendor/.*$", "munmap", my_munmap, NULL);
|
||||
xhook_ignore (".*/linker$", "mmap");
|
||||
xhook_ignore (".*/linker$", "munmap");
|
||||
xhook_ignore (".*/linker64$", "mmap");
|
||||
xhook_ignore (".*/linker64$", "munmap");
|
||||
|
||||
//defense to some injection attacks
|
||||
xhook_register(".*com\\.hacker.*\\.so$", "malloc", my_malloc_always_return_NULL, NULL);
|
||||
xhook_register(".*/libhacker\\.so$", "connect", my_connect_with_recorder, NULL);
|
||||
|
||||
//fix some system bug
|
||||
xhook_register(".*some_vendor.*/libvictim\\.so$", "bad_func", my_nice_func, NULL);
|
||||
|
||||
//ignore all hooks in libwebviewchromium.so
|
||||
xhook_ignore(".*/libwebviewchromium.so$", NULL);
|
||||
|
||||
//hook now!
|
||||
xhook_refresh(1);
|
||||
```
|
||||
|
||||
|
||||
## Support
|
||||
|
||||
1. Check the [xhook-sample](libbiz/jni).
|
||||
2. Communicate on [GitHub issues](https://github.com/iqiyi/xHook/issues).
|
||||
3. Mail: <a href="mailto:caikelun@gmail.com">caikelun@gmail.com</a>
|
||||
4. QQ group: 603635869. QR code:
|
||||
|
||||
<p align="left"><img src="docs/qq_group.jpg" alt="qq group" width="300px"></p>
|
||||
|
||||
|
||||
## Contributing
|
||||
|
||||
See [xHook Contributing Guide](CONTRIBUTING.md).
|
||||
|
||||
|
||||
## License
|
||||
|
||||
xHook is MIT licensed, as found in the [LICENSE](LICENSE) file.
|
||||
|
||||
xHook documentation is Creative Commons licensed, as found in the [LICENSE-docs](LICENSE-docs) file.
|
||||
+200
@@ -0,0 +1,200 @@
|
||||
<p align="center"><img src="https://github.com/iqiyi/xHook/blob/master/docs/xhooklogo.png?raw=true" alt="xhook" width="50%"></p>
|
||||
|
||||
# xHook
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
[README English Version](README.md)
|
||||
|
||||
[Android PLT hook 概述 中文版](docs/overview/android_plt_hook_overview.zh-CN.md)
|
||||
|
||||
xHook 是一个针对 Android 平台 ELF (可执行文件和动态库) 的 PLT (Procedure Linkage Table) hook 库。
|
||||
|
||||
xHook 一直在稳定性和兼容性方面做着持续的优化。
|
||||
|
||||
|
||||
## 特征
|
||||
|
||||
* 支持 Android 4.0 - 10(API level 14 - 29)。
|
||||
* 支持 armeabi,armeabi-v7a,arm64-v8a,x86 和 x86_64。
|
||||
* 支持 **ELF HASH** 和 **GNU HASH** 索引的符号。
|
||||
* 支持 **SLEB128** 编码的重定位信息。
|
||||
* 支持通过正则表达式批量设置 hook 信息。
|
||||
* 不需要 root 权限或任何系统权限。
|
||||
* 不依赖于任何的第三方动态库。
|
||||
|
||||
|
||||
## 编译
|
||||
|
||||
* 下载 [Android NDK r16b](https://developer.android.com/ndk/downloads/revision_history.html),设置 PATH 环境变量。(对 armeabi 的支持,从 r17 版本开始被移除了)
|
||||
|
||||
* 编译和安装 native 库。
|
||||
|
||||
```
|
||||
./build_libs.sh
|
||||
./install_libs.sh
|
||||
```
|
||||
|
||||
|
||||
## Demo
|
||||
|
||||
```
|
||||
cd ./xhookwrapper/
|
||||
./gradlew assembleDebug
|
||||
adb install ./app/build/outputs/apk/debug/app-debug.apk
|
||||
```
|
||||
|
||||
|
||||
## API
|
||||
|
||||
外部 API 头文件: `libxhook/jni/xhook.h`
|
||||
|
||||
### 1. 注册 hook 信息
|
||||
|
||||
```c
|
||||
int xhook_register(const char *pathname_regex_str,
|
||||
const char *symbol,
|
||||
void *new_func,
|
||||
void **old_func);
|
||||
```
|
||||
|
||||
在当前进程的内存空间中,在每一个符合正则表达式 `pathname_regex_str` 的已加载ELF中,每一个调用 `symbol` 的 PLT 入口点的地址值都将给替换成 `new_func`。之前的 PLT 入口点的地址值将被保存在 `old_func` 中。
|
||||
|
||||
`new_func` 必须具有和原函数同样的函数声明。
|
||||
|
||||
成功返回 0,失败返回 非0。
|
||||
|
||||
`pathname_regex_str` 只支持 **POSIX BRE (Basic Regular Expression)** 定义的正则表达式语法。
|
||||
|
||||
### 2. 忽略部分 hook 信息
|
||||
|
||||
```c
|
||||
int xhook_ignore(const char *pathname_regex_str,
|
||||
const char *symbol);
|
||||
```
|
||||
|
||||
根据 `pathname_regex_str` 和 `symbol`,从已经通过 `xhook_register` 注册的 hook 信息中,忽略一部分 hook 信息。如果 `symbol` 为 `NULL`,xhook 将忽略所有路径名符合正则表达式 `pathname_regex_str` 的 ELF。
|
||||
|
||||
成功返回 0,失败返回 非0。
|
||||
|
||||
`pathname_regex_str` 只支持 **POSIX BRE** 定义的正则表达式语法。
|
||||
|
||||
### 3. 执行 hook
|
||||
|
||||
```c
|
||||
int xhook_refresh(int async);
|
||||
```
|
||||
|
||||
根据前面注册的 hook 信息,执行真正的 hook 操作。
|
||||
|
||||
给 `async` 参数传 `1` 表示执行异步的 hook 操作,传 `0` 表示执行同步的 hook 操作。
|
||||
|
||||
成功返回 0,失败返回 非0。
|
||||
|
||||
xhook 在内部维护了一个全局的缓存,用于保存最后一次从 `/proc/self/maps` 读取到的 ELF 加载信息。每次一调用 `xhook_refresh` 函数,这个缓存都将被更新。xhook 使用这个缓存来判断哪些 ELF 是这次新被加载到内存中的。我们每次只需要针对这些新加载的 ELF 做 hook 就可以了。
|
||||
|
||||
### 4. 清除缓存
|
||||
|
||||
```c
|
||||
void xhook_clear();
|
||||
```
|
||||
|
||||
清除 xhook 的缓存,重置所有的全局标示。
|
||||
|
||||
如果你确定你需要的所有 PLT 入口点都已经被替换了,你可以调用这个函数来释放和节省一些内存空间。
|
||||
|
||||
### 5. 启用/禁用 调试信息
|
||||
|
||||
```c
|
||||
void xhook_enable_debug(int flag);
|
||||
```
|
||||
|
||||
给 `flag` 参数传 `1` 表示启用调试信息,传 `0` 表示禁用调试信息。 (默认为:**禁用**)
|
||||
|
||||
调试信息将被输出到 logcat,对应的 TAG 为:`xhook`。
|
||||
|
||||
### 6. 启用/禁用 SFP (段错误保护)
|
||||
|
||||
```c
|
||||
void xhook_enable_sigsegv_protection(int flag);
|
||||
```
|
||||
|
||||
给 `flag` 参数传 `1` 表示启用 SFP,传 `0` 表示禁用 SFP。 (默认为:**启用**)
|
||||
|
||||
xhook 并不是一个常规的业务层的动态库。在 xhook 中,我们不得不直接计算一些内存指针的值。在一些极端的情况和环境下,读或者写这些指针指向的内存会发生段错误。根据我们的测试,xhook 的行为将导致 APP 崩溃率增加 “一千万分之一” (0.0000001)。(具体崩溃率可能会增加多少,也和你想要 hook 的库和符号有关)。最终,我们不得不使用某些方法来防止这些无害的崩溃。我们叫它SFP (段错误保护),它是由这些调用和值组成的:`sigaction()`, `SIGSEGV`, `siglongjmp()` 和 `sigsetjmp()`。
|
||||
|
||||
**在 release 版本的 APP 中,你应该始终启用 SFP,这能防止你的 APP 因为 xhook 而崩溃。在 debug 版本的 APP 中,你应该始终禁用 SFP,这样你就不会丢失那些一般性的编码失误导致的段错误,这些段错误是应该被修复的。**
|
||||
|
||||
|
||||
## 例子
|
||||
|
||||
```c
|
||||
//监测内存泄露
|
||||
xhook_register(".*\\.so$", "malloc", my_malloc, NULL);
|
||||
xhook_register(".*\\.so$", "calloc", my_calloc, NULL);
|
||||
xhook_register(".*\\.so$", "realloc", my_realloc, NULL);
|
||||
xhook_register(".*\\.so$", "free", my_free, NULL);
|
||||
|
||||
//监控 sockets 生命周期
|
||||
xhook_register(".*\\.so$", "getaddrinfo", my_getaddrinfo, NULL);
|
||||
xhook_register(".*\\.so$", "socket", my_socket, NULL);
|
||||
xhook_register(".*\\.so$", "setsockopt" my_setsockopt, NULL);
|
||||
xhook_register(".*\\.so$", "bind", my_bind, NULL);
|
||||
xhook_register(".*\\.so$", "listen", my_listen, NULL);
|
||||
xhook_register(".*\\.so$", "connect", my_connect, NULL);
|
||||
xhook_register(".*\\.so$", "shutdown", my_shutdown, NULL);
|
||||
xhook_register(".*\\.so$", "close", my_close, NULL);
|
||||
|
||||
//过滤出和保存部分安卓 log 到本地文件
|
||||
xhook_register(".*\\.so$", "__android_log_write", my_log_write, NULL);
|
||||
xhook_register(".*\\.so$", "__android_log_print", my_log_print, NULL);
|
||||
xhook_register(".*\\.so$", "__android_log_vprint", my_log_vprint, NULL);
|
||||
xhook_register(".*\\.so$", "__android_log_assert", my_log_assert, NULL);
|
||||
|
||||
//追踪某些调用 (忽略 linker 和 linker64)
|
||||
xhook_register("^/system/.*$", "mmap", my_mmap, NULL);
|
||||
xhook_register("^/vendor/.*$", "munmap", my_munmap, NULL);
|
||||
xhook_ignore (".*/linker$", "mmap");
|
||||
xhook_ignore (".*/linker$", "munmap");
|
||||
xhook_ignore (".*/linker64$", "mmap");
|
||||
xhook_ignore (".*/linker64$", "munmap");
|
||||
|
||||
//防御某些注入攻击
|
||||
xhook_register(".*com\\.hacker.*\\.so$", "malloc", my_malloc_always_return_NULL, NULL);
|
||||
xhook_register(".*/libhacker\\.so$", "connect", my_connect_with_recorder, NULL);
|
||||
|
||||
//修复某些系统 bug
|
||||
xhook_register(".*some_vendor.*/libvictim\\.so$", "bad_func", my_nice_func, NULL);
|
||||
|
||||
//忽略 libwebviewchromium.so 的所有 hook 信息
|
||||
xhook_ignore(".*/libwebviewchromium.so$", NULL);
|
||||
|
||||
//现在执行 hook!
|
||||
xhook_refresh(1);
|
||||
```
|
||||
|
||||
|
||||
## 技术支持
|
||||
|
||||
1. 查看 [xhook-sample](libbiz/jni)。
|
||||
2. 在 [GitHub issues](https://github.com/iqiyi/xHook/issues) 交流。
|
||||
3. 邮件: <a href="mailto:caikelun@gmail.com">caikelun@gmail.com</a>
|
||||
5. QQ 群: 603635869。二维码:
|
||||
|
||||
<p align="left"><img src="docs/qq_group.jpg" alt="qq group" width="320px"></p>
|
||||
|
||||
|
||||
## 贡献
|
||||
|
||||
请阅读 [xHook Contributing Guide](CONTRIBUTING.md)。
|
||||
|
||||
|
||||
## 许可证
|
||||
|
||||
xHook 使用 [MIT 许可证](LICENSE)。
|
||||
|
||||
xHook 的文档使用 [Creative Commons 许可证](LICENSE-docs)。
|
||||
Vendored
+554
@@ -0,0 +1,554 @@
|
||||
/*-
|
||||
* Copyright (c) 1991, 1993
|
||||
* The Regents of the University of California. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of the University nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*
|
||||
* @(#)queue.h 8.5 (Berkeley) 8/20/94
|
||||
* $FreeBSD: stable/9/sys/sys/queue.h 252365 2013-06-29 04:25:40Z lstewart $
|
||||
*/
|
||||
|
||||
#ifndef QUEUE_H
|
||||
#define QUEUE_H
|
||||
|
||||
/* #include <sys/cdefs.h> */
|
||||
#define __containerof(ptr, type, field) ((type *)((char *)(ptr) - ((char *)&((type *)0)->field)))
|
||||
|
||||
/*
|
||||
* This file defines four types of data structures: singly-linked lists,
|
||||
* singly-linked tail queues, lists and tail queues.
|
||||
*
|
||||
* A singly-linked list is headed by a single forward pointer. The elements
|
||||
* are singly linked for minimum space and pointer manipulation overhead at
|
||||
* the expense of O(n) removal for arbitrary elements. New elements can be
|
||||
* added to the list after an existing element or at the head of the list.
|
||||
* Elements being removed from the head of the list should use the explicit
|
||||
* macro for this purpose for optimum efficiency. A singly-linked list may
|
||||
* only be traversed in the forward direction. Singly-linked lists are ideal
|
||||
* for applications with large datasets and few or no removals or for
|
||||
* implementing a LIFO queue.
|
||||
*
|
||||
* A singly-linked tail queue is headed by a pair of pointers, one to the
|
||||
* head of the list and the other to the tail of the list. The elements are
|
||||
* singly linked for minimum space and pointer manipulation overhead at the
|
||||
* expense of O(n) removal for arbitrary elements. New elements can be added
|
||||
* to the list after an existing element, at the head of the list, or at the
|
||||
* end of the list. Elements being removed from the head of the tail queue
|
||||
* should use the explicit macro for this purpose for optimum efficiency.
|
||||
* A singly-linked tail queue may only be traversed in the forward direction.
|
||||
* Singly-linked tail queues are ideal for applications with large datasets
|
||||
* and few or no removals or for implementing a FIFO queue.
|
||||
*
|
||||
* A list is headed by a single forward pointer (or an array of forward
|
||||
* pointers for a hash table header). The elements are doubly linked
|
||||
* so that an arbitrary element can be removed without a need to
|
||||
* traverse the list. New elements can be added to the list before
|
||||
* or after an existing element or at the head of the list. A list
|
||||
* may be traversed in either direction.
|
||||
*
|
||||
* A tail queue is headed by a pair of pointers, one to the head of the
|
||||
* list and the other to the tail of the list. The elements are doubly
|
||||
* linked so that an arbitrary element can be removed without a need to
|
||||
* traverse the list. New elements can be added to the list before or
|
||||
* after an existing element, at the head of the list, or at the end of
|
||||
* the list. A tail queue may be traversed in either direction.
|
||||
*
|
||||
* For details on the use of these macros, see the queue(3) manual page.
|
||||
*
|
||||
* SLIST LIST STAILQ TAILQ
|
||||
* _HEAD + + + +
|
||||
* _HEAD_INITIALIZER + + + +
|
||||
* _ENTRY + + + +
|
||||
* _INIT + + + +
|
||||
* _EMPTY + + + +
|
||||
* _FIRST + + + +
|
||||
* _NEXT + + + +
|
||||
* _PREV - + - +
|
||||
* _LAST - - + +
|
||||
* _FOREACH + + + +
|
||||
* _FOREACH_FROM + + + +
|
||||
* _FOREACH_SAFE + + + +
|
||||
* _FOREACH_FROM_SAFE + + + +
|
||||
* _FOREACH_REVERSE - - - +
|
||||
* _FOREACH_REVERSE_FROM - - - +
|
||||
* _FOREACH_REVERSE_SAFE - - - +
|
||||
* _FOREACH_REVERSE_FROM_SAFE - - - +
|
||||
* _INSERT_HEAD + + + +
|
||||
* _INSERT_BEFORE - + - +
|
||||
* _INSERT_AFTER + + + +
|
||||
* _INSERT_TAIL - - + +
|
||||
* _CONCAT - - + +
|
||||
* _REMOVE_AFTER + - + -
|
||||
* _REMOVE_HEAD + - + -
|
||||
* _REMOVE + + + +
|
||||
* _SWAP + + + +
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* Singly-linked List declarations.
|
||||
*/
|
||||
#define SLIST_HEAD(name, type, qual) \
|
||||
struct name { \
|
||||
struct type *qual slh_first; /* first element */ \
|
||||
}
|
||||
|
||||
#define SLIST_HEAD_INITIALIZER(head) \
|
||||
{ NULL }
|
||||
|
||||
#define SLIST_ENTRY(type, qual) \
|
||||
struct { \
|
||||
struct type *qual sle_next; /* next element */ \
|
||||
}
|
||||
|
||||
/*
|
||||
* Singly-linked List functions.
|
||||
*/
|
||||
#define SLIST_INIT(head) do { \
|
||||
SLIST_FIRST((head)) = NULL; \
|
||||
} while (0)
|
||||
|
||||
#define SLIST_EMPTY(head) ((head)->slh_first == NULL)
|
||||
|
||||
#define SLIST_FIRST(head) ((head)->slh_first)
|
||||
|
||||
#define SLIST_NEXT(elm, field) ((elm)->field.sle_next)
|
||||
|
||||
#define SLIST_FOREACH(var, head, field) \
|
||||
for ((var) = SLIST_FIRST((head)); \
|
||||
(var); \
|
||||
(var) = SLIST_NEXT((var), field))
|
||||
|
||||
#define SLIST_FOREACH_FROM(var, head, field) \
|
||||
for ((var) = ((var) ? (var) : SLIST_FIRST((head))); \
|
||||
(var); \
|
||||
(var) = SLIST_NEXT((var), field))
|
||||
|
||||
#define SLIST_FOREACH_SAFE(var, head, field, tvar) \
|
||||
for ((var) = SLIST_FIRST((head)); \
|
||||
(var) && ((tvar) = SLIST_NEXT((var), field), 1); \
|
||||
(var) = (tvar))
|
||||
|
||||
#define SLIST_FOREACH_FROM_SAFE(var, head, field, tvar) \
|
||||
for ((var) = ((var) ? (var) : SLIST_FIRST((head))); \
|
||||
(var) && ((tvar) = SLIST_NEXT((var), field), 1); \
|
||||
(var) = (tvar))
|
||||
|
||||
#define SLIST_INSERT_HEAD(head, elm, field) do { \
|
||||
SLIST_NEXT((elm), field) = SLIST_FIRST((head)); \
|
||||
SLIST_FIRST((head)) = (elm); \
|
||||
} while (0)
|
||||
|
||||
#define SLIST_INSERT_AFTER(slistelm, elm, field) do { \
|
||||
SLIST_NEXT((elm), field) = SLIST_NEXT((slistelm), field); \
|
||||
SLIST_NEXT((slistelm), field) = (elm); \
|
||||
} while (0)
|
||||
|
||||
#define SLIST_REMOVE_AFTER(elm, field) do { \
|
||||
SLIST_NEXT(elm, field) = \
|
||||
SLIST_NEXT(SLIST_NEXT(elm, field), field); \
|
||||
} while (0)
|
||||
|
||||
#define SLIST_REMOVE_HEAD(head, field) do { \
|
||||
SLIST_FIRST((head)) = SLIST_NEXT(SLIST_FIRST((head)), field); \
|
||||
} while (0)
|
||||
|
||||
#define SLIST_REMOVE(head, elm, type, field) do { \
|
||||
if (SLIST_FIRST((head)) == (elm)) { \
|
||||
SLIST_REMOVE_HEAD((head), field); \
|
||||
} \
|
||||
else { \
|
||||
struct type *curelm = SLIST_FIRST((head)); \
|
||||
while (SLIST_NEXT(curelm, field) != (elm)) \
|
||||
curelm = SLIST_NEXT(curelm, field); \
|
||||
SLIST_REMOVE_AFTER(curelm, field); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define SLIST_SWAP(head1, head2, type) do { \
|
||||
struct type *swap_first = SLIST_FIRST(head1); \
|
||||
SLIST_FIRST(head1) = SLIST_FIRST(head2); \
|
||||
SLIST_FIRST(head2) = swap_first; \
|
||||
} while (0)
|
||||
|
||||
/*
|
||||
* List declarations.
|
||||
*/
|
||||
#define LIST_HEAD(name, type, qual) \
|
||||
struct name { \
|
||||
struct type *qual lh_first; /* first element */ \
|
||||
}
|
||||
|
||||
#define LIST_HEAD_INITIALIZER(head) \
|
||||
{ NULL }
|
||||
|
||||
#define LIST_ENTRY(type, qual) \
|
||||
struct { \
|
||||
struct type *qual le_next; /* next element */ \
|
||||
struct type *qual *le_prev; /* address of previous next element */ \
|
||||
}
|
||||
|
||||
/*
|
||||
* List functions.
|
||||
*/
|
||||
#define LIST_INIT(head) do { \
|
||||
LIST_FIRST((head)) = NULL; \
|
||||
} while (0)
|
||||
|
||||
#define LIST_EMPTY(head) ((head)->lh_first == NULL)
|
||||
|
||||
#define LIST_FIRST(head) ((head)->lh_first)
|
||||
|
||||
#define LIST_NEXT(elm, field) ((elm)->field.le_next)
|
||||
|
||||
#define LIST_PREV(elm, head, type, field) \
|
||||
((elm)->field.le_prev == &LIST_FIRST((head)) ? NULL : \
|
||||
__containerof((elm)->field.le_prev, struct type, field.le_next))
|
||||
|
||||
#define LIST_FOREACH(var, head, field) \
|
||||
for ((var) = LIST_FIRST((head)); \
|
||||
(var); \
|
||||
(var) = LIST_NEXT((var), field))
|
||||
|
||||
#define LIST_FOREACH_FROM(var, head, field) \
|
||||
for ((var) = ((var) ? (var) : LIST_FIRST((head))); \
|
||||
(var); \
|
||||
(var) = LIST_NEXT((var), field))
|
||||
|
||||
#define LIST_FOREACH_SAFE(var, head, field, tvar) \
|
||||
for ((var) = LIST_FIRST((head)); \
|
||||
(var) && ((tvar) = LIST_NEXT((var), field), 1); \
|
||||
(var) = (tvar))
|
||||
|
||||
#define LIST_FOREACH_FROM_SAFE(var, head, field, tvar) \
|
||||
for ((var) = ((var) ? (var) : LIST_FIRST((head))); \
|
||||
(var) && ((tvar) = LIST_NEXT((var), field), 1); \
|
||||
(var) = (tvar))
|
||||
|
||||
#define LIST_INSERT_HEAD(head, elm, field) do { \
|
||||
if ((LIST_NEXT((elm), field) = LIST_FIRST((head))) != NULL) \
|
||||
LIST_FIRST((head))->field.le_prev = &LIST_NEXT((elm), field); \
|
||||
LIST_FIRST((head)) = (elm); \
|
||||
(elm)->field.le_prev = &LIST_FIRST((head)); \
|
||||
} while (0)
|
||||
|
||||
#define LIST_INSERT_BEFORE(listelm, elm, field) do { \
|
||||
(elm)->field.le_prev = (listelm)->field.le_prev; \
|
||||
LIST_NEXT((elm), field) = (listelm); \
|
||||
*(listelm)->field.le_prev = (elm); \
|
||||
(listelm)->field.le_prev = &LIST_NEXT((elm), field); \
|
||||
} while (0)
|
||||
|
||||
#define LIST_INSERT_AFTER(listelm, elm, field) do { \
|
||||
if ((LIST_NEXT((elm), field) = LIST_NEXT((listelm), field)) != NULL) \
|
||||
LIST_NEXT((listelm), field)->field.le_prev = \
|
||||
&LIST_NEXT((elm), field); \
|
||||
LIST_NEXT((listelm), field) = (elm); \
|
||||
(elm)->field.le_prev = &LIST_NEXT((listelm), field); \
|
||||
} while (0)
|
||||
|
||||
#define LIST_REMOVE(elm, field) do { \
|
||||
if (LIST_NEXT((elm), field) != NULL) \
|
||||
LIST_NEXT((elm), field)->field.le_prev = \
|
||||
(elm)->field.le_prev; \
|
||||
*(elm)->field.le_prev = LIST_NEXT((elm), field); \
|
||||
} while (0)
|
||||
|
||||
#define LIST_SWAP(head1, head2, type, field) do { \
|
||||
struct type *swap_tmp = LIST_FIRST((head1)); \
|
||||
LIST_FIRST((head1)) = LIST_FIRST((head2)); \
|
||||
LIST_FIRST((head2)) = swap_tmp; \
|
||||
if ((swap_tmp = LIST_FIRST((head1))) != NULL) \
|
||||
swap_tmp->field.le_prev = &LIST_FIRST((head1)); \
|
||||
if ((swap_tmp = LIST_FIRST((head2))) != NULL) \
|
||||
swap_tmp->field.le_prev = &LIST_FIRST((head2)); \
|
||||
} while (0)
|
||||
|
||||
/*
|
||||
* Singly-linked Tail queue declarations.
|
||||
*/
|
||||
#define STAILQ_HEAD(name, type, qual) \
|
||||
struct name { \
|
||||
struct type *qual stqh_first;/* first element */ \
|
||||
struct type *qual *stqh_last;/* addr of last next element */ \
|
||||
}
|
||||
|
||||
#define STAILQ_HEAD_INITIALIZER(head) \
|
||||
{ NULL, &(head).stqh_first }
|
||||
|
||||
#define STAILQ_ENTRY(type, qual) \
|
||||
struct { \
|
||||
struct type *qual stqe_next; /* next element */ \
|
||||
}
|
||||
|
||||
/*
|
||||
* Singly-linked Tail queue functions.
|
||||
*/
|
||||
#define STAILQ_INIT(head) do { \
|
||||
STAILQ_FIRST((head)) = NULL; \
|
||||
(head)->stqh_last = &STAILQ_FIRST((head)); \
|
||||
} while (0)
|
||||
|
||||
#define STAILQ_EMPTY(head) ((head)->stqh_first == NULL)
|
||||
|
||||
#define STAILQ_FIRST(head) ((head)->stqh_first)
|
||||
|
||||
#define STAILQ_NEXT(elm, field) ((elm)->field.stqe_next)
|
||||
|
||||
#define STAILQ_LAST(head, type, field) \
|
||||
(STAILQ_EMPTY((head)) ? NULL : \
|
||||
__containerof((head)->stqh_last, struct type, field.stqe_next))
|
||||
|
||||
#define STAILQ_FOREACH(var, head, field) \
|
||||
for((var) = STAILQ_FIRST((head)); \
|
||||
(var); \
|
||||
(var) = STAILQ_NEXT((var), field))
|
||||
|
||||
#define STAILQ_FOREACH_FROM(var, head, field) \
|
||||
for ((var) = ((var) ? (var) : STAILQ_FIRST((head))); \
|
||||
(var); \
|
||||
(var) = STAILQ_NEXT((var), field))
|
||||
|
||||
#define STAILQ_FOREACH_SAFE(var, head, field, tvar) \
|
||||
for ((var) = STAILQ_FIRST((head)); \
|
||||
(var) && ((tvar) = STAILQ_NEXT((var), field), 1); \
|
||||
(var) = (tvar))
|
||||
|
||||
#define STAILQ_FOREACH_FROM_SAFE(var, head, field, tvar) \
|
||||
for ((var) = ((var) ? (var) : STAILQ_FIRST((head))); \
|
||||
(var) && ((tvar) = STAILQ_NEXT((var), field), 1); \
|
||||
(var) = (tvar))
|
||||
|
||||
#define STAILQ_INSERT_HEAD(head, elm, field) do { \
|
||||
if ((STAILQ_NEXT((elm), field) = STAILQ_FIRST((head))) == NULL) \
|
||||
(head)->stqh_last = &STAILQ_NEXT((elm), field); \
|
||||
STAILQ_FIRST((head)) = (elm); \
|
||||
} while (0)
|
||||
|
||||
#define STAILQ_INSERT_AFTER(head, tqelm, elm, field) do { \
|
||||
if ((STAILQ_NEXT((elm), field) = STAILQ_NEXT((tqelm), field)) == NULL) \
|
||||
(head)->stqh_last = &STAILQ_NEXT((elm), field); \
|
||||
STAILQ_NEXT((tqelm), field) = (elm); \
|
||||
} while (0)
|
||||
|
||||
#define STAILQ_INSERT_TAIL(head, elm, field) do { \
|
||||
STAILQ_NEXT((elm), field) = NULL; \
|
||||
*(head)->stqh_last = (elm); \
|
||||
(head)->stqh_last = &STAILQ_NEXT((elm), field); \
|
||||
} while (0)
|
||||
|
||||
#define STAILQ_CONCAT(head1, head2) do { \
|
||||
if (!STAILQ_EMPTY((head2))) { \
|
||||
*(head1)->stqh_last = (head2)->stqh_first; \
|
||||
(head1)->stqh_last = (head2)->stqh_last; \
|
||||
STAILQ_INIT((head2)); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define STAILQ_REMOVE_AFTER(head, elm, field) do { \
|
||||
if ((STAILQ_NEXT(elm, field) = \
|
||||
STAILQ_NEXT(STAILQ_NEXT(elm, field), field)) == NULL) \
|
||||
(head)->stqh_last = &STAILQ_NEXT((elm), field); \
|
||||
} while (0)
|
||||
|
||||
#define STAILQ_REMOVE_HEAD(head, field) do { \
|
||||
if ((STAILQ_FIRST((head)) = \
|
||||
STAILQ_NEXT(STAILQ_FIRST((head)), field)) == NULL) \
|
||||
(head)->stqh_last = &STAILQ_FIRST((head)); \
|
||||
} while (0)
|
||||
|
||||
#define STAILQ_REMOVE(head, elm, type, field) do { \
|
||||
if (STAILQ_FIRST((head)) == (elm)) { \
|
||||
STAILQ_REMOVE_HEAD((head), field); \
|
||||
} \
|
||||
else { \
|
||||
struct type *curelm = STAILQ_FIRST((head)); \
|
||||
while (STAILQ_NEXT(curelm, field) != (elm)) \
|
||||
curelm = STAILQ_NEXT(curelm, field); \
|
||||
STAILQ_REMOVE_AFTER(head, curelm, field); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define STAILQ_SWAP(head1, head2, type) do { \
|
||||
struct type *swap_first = STAILQ_FIRST(head1); \
|
||||
struct type **swap_last = (head1)->stqh_last; \
|
||||
STAILQ_FIRST(head1) = STAILQ_FIRST(head2); \
|
||||
(head1)->stqh_last = (head2)->stqh_last; \
|
||||
STAILQ_FIRST(head2) = swap_first; \
|
||||
(head2)->stqh_last = swap_last; \
|
||||
if (STAILQ_EMPTY(head1)) \
|
||||
(head1)->stqh_last = &STAILQ_FIRST(head1); \
|
||||
if (STAILQ_EMPTY(head2)) \
|
||||
(head2)->stqh_last = &STAILQ_FIRST(head2); \
|
||||
} while (0)
|
||||
|
||||
/*
|
||||
* Tail queue declarations.
|
||||
*/
|
||||
#define TAILQ_HEAD(name, type, qual) \
|
||||
struct name { \
|
||||
struct type *qual tqh_first; /* first element */ \
|
||||
struct type *qual *tqh_last; /* addr of last next element */ \
|
||||
}
|
||||
|
||||
#define TAILQ_HEAD_INITIALIZER(head) \
|
||||
{ NULL, &(head).tqh_first }
|
||||
|
||||
#define TAILQ_ENTRY(type, qual) \
|
||||
struct { \
|
||||
struct type *qual tqe_next; /* next element */ \
|
||||
struct type *qual *tqe_prev; /* address of previous next element */ \
|
||||
}
|
||||
|
||||
/*
|
||||
* Tail queue functions.
|
||||
*/
|
||||
#define TAILQ_INIT(head) do { \
|
||||
TAILQ_FIRST((head)) = NULL; \
|
||||
(head)->tqh_last = &TAILQ_FIRST((head)); \
|
||||
} while (0)
|
||||
|
||||
#define TAILQ_EMPTY(head) ((head)->tqh_first == NULL)
|
||||
|
||||
#define TAILQ_FIRST(head) ((head)->tqh_first)
|
||||
|
||||
#define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next)
|
||||
|
||||
#define TAILQ_PREV(elm, headname, field) \
|
||||
(*(((struct headname *)((elm)->field.tqe_prev))->tqh_last))
|
||||
|
||||
#define TAILQ_LAST(head, headname) \
|
||||
(*(((struct headname *)((head)->tqh_last))->tqh_last))
|
||||
|
||||
#define TAILQ_FOREACH(var, head, field) \
|
||||
for ((var) = TAILQ_FIRST((head)); \
|
||||
(var); \
|
||||
(var) = TAILQ_NEXT((var), field))
|
||||
|
||||
#define TAILQ_FOREACH_FROM(var, head, field) \
|
||||
for ((var) = ((var) ? (var) : TAILQ_FIRST((head))); \
|
||||
(var); \
|
||||
(var) = TAILQ_NEXT((var), field))
|
||||
|
||||
#define TAILQ_FOREACH_SAFE(var, head, field, tvar) \
|
||||
for ((var) = TAILQ_FIRST((head)); \
|
||||
(var) && ((tvar) = TAILQ_NEXT((var), field), 1); \
|
||||
(var) = (tvar))
|
||||
|
||||
#define TAILQ_FOREACH_FROM_SAFE(var, head, field, tvar) \
|
||||
for ((var) = ((var) ? (var) : TAILQ_FIRST((head))); \
|
||||
(var) && ((tvar) = TAILQ_NEXT((var), field), 1); \
|
||||
(var) = (tvar))
|
||||
|
||||
#define TAILQ_FOREACH_REVERSE(var, head, headname, field) \
|
||||
for ((var) = TAILQ_LAST((head), headname); \
|
||||
(var); \
|
||||
(var) = TAILQ_PREV((var), headname, field))
|
||||
|
||||
#define TAILQ_FOREACH_REVERSE_FROM(var, head, headname, field) \
|
||||
for ((var) = ((var) ? (var) : TAILQ_LAST((head), headname)); \
|
||||
(var); \
|
||||
(var) = TAILQ_PREV((var), headname, field))
|
||||
|
||||
#define TAILQ_FOREACH_REVERSE_SAFE(var, head, headname, field, tvar) \
|
||||
for ((var) = TAILQ_LAST((head), headname); \
|
||||
(var) && ((tvar) = TAILQ_PREV((var), headname, field), 1); \
|
||||
(var) = (tvar))
|
||||
|
||||
#define TAILQ_FOREACH_REVERSE_FROM_SAFE(var, head, headname, field, tvar) \
|
||||
for ((var) = ((var) ? (var) : TAILQ_LAST((head), headname)); \
|
||||
(var) && ((tvar) = TAILQ_PREV((var), headname, field), 1); \
|
||||
(var) = (tvar))
|
||||
|
||||
#define TAILQ_INSERT_HEAD(head, elm, field) do { \
|
||||
if ((TAILQ_NEXT((elm), field) = TAILQ_FIRST((head))) != NULL) \
|
||||
TAILQ_FIRST((head))->field.tqe_prev = \
|
||||
&TAILQ_NEXT((elm), field); \
|
||||
else \
|
||||
(head)->tqh_last = &TAILQ_NEXT((elm), field); \
|
||||
TAILQ_FIRST((head)) = (elm); \
|
||||
(elm)->field.tqe_prev = &TAILQ_FIRST((head)); \
|
||||
} while (0)
|
||||
|
||||
#define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \
|
||||
(elm)->field.tqe_prev = (listelm)->field.tqe_prev; \
|
||||
TAILQ_NEXT((elm), field) = (listelm); \
|
||||
*(listelm)->field.tqe_prev = (elm); \
|
||||
(listelm)->field.tqe_prev = &TAILQ_NEXT((elm), field); \
|
||||
} while (0)
|
||||
|
||||
#define TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \
|
||||
if ((TAILQ_NEXT((elm), field) = TAILQ_NEXT((listelm), field)) != NULL) \
|
||||
TAILQ_NEXT((elm), field)->field.tqe_prev = \
|
||||
&TAILQ_NEXT((elm), field); \
|
||||
else \
|
||||
(head)->tqh_last = &TAILQ_NEXT((elm), field); \
|
||||
TAILQ_NEXT((listelm), field) = (elm); \
|
||||
(elm)->field.tqe_prev = &TAILQ_NEXT((listelm), field); \
|
||||
} while (0)
|
||||
|
||||
#define TAILQ_INSERT_TAIL(head, elm, field) do { \
|
||||
TAILQ_NEXT((elm), field) = NULL; \
|
||||
(elm)->field.tqe_prev = (head)->tqh_last; \
|
||||
*(head)->tqh_last = (elm); \
|
||||
(head)->tqh_last = &TAILQ_NEXT((elm), field); \
|
||||
} while (0)
|
||||
|
||||
#define TAILQ_CONCAT(head1, head2, field) do { \
|
||||
if (!TAILQ_EMPTY(head2)) { \
|
||||
*(head1)->tqh_last = (head2)->tqh_first; \
|
||||
(head2)->tqh_first->field.tqe_prev = (head1)->tqh_last; \
|
||||
(head1)->tqh_last = (head2)->tqh_last; \
|
||||
TAILQ_INIT((head2)); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define TAILQ_REMOVE(head, elm, field) do { \
|
||||
if ((TAILQ_NEXT((elm), field)) != NULL) \
|
||||
TAILQ_NEXT((elm), field)->field.tqe_prev = \
|
||||
(elm)->field.tqe_prev; \
|
||||
else \
|
||||
(head)->tqh_last = (elm)->field.tqe_prev; \
|
||||
*(elm)->field.tqe_prev = TAILQ_NEXT((elm), field); \
|
||||
} while (0)
|
||||
|
||||
#define TAILQ_SWAP(head1, head2, type, field) do { \
|
||||
struct type *swap_first = (head1)->tqh_first; \
|
||||
struct type **swap_last = (head1)->tqh_last; \
|
||||
(head1)->tqh_first = (head2)->tqh_first; \
|
||||
(head1)->tqh_last = (head2)->tqh_last; \
|
||||
(head2)->tqh_first = swap_first; \
|
||||
(head2)->tqh_last = swap_last; \
|
||||
if ((swap_first = (head1)->tqh_first) != NULL) \
|
||||
swap_first->field.tqe_prev = &(head1)->tqh_first; \
|
||||
else \
|
||||
(head1)->tqh_last = &(head1)->tqh_first; \
|
||||
if ((swap_first = (head2)->tqh_first) != NULL) \
|
||||
swap_first->field.tqe_prev = &(head2)->tqh_first; \
|
||||
else \
|
||||
(head2)->tqh_last = &(head2)->tqh_first; \
|
||||
} while (0)
|
||||
|
||||
#endif
|
||||
Vendored
+768
@@ -0,0 +1,768 @@
|
||||
/* $NetBSD: tree.h,v 1.8 2004/03/28 19:38:30 provos Exp $ */
|
||||
/* $OpenBSD: tree.h,v 1.7 2002/10/17 21:51:54 art Exp $ */
|
||||
/* $FreeBSD: stable/9/sys/sys/tree.h 189204 2009-03-01 04:57:23Z bms $ */
|
||||
|
||||
/*-
|
||||
* Copyright 2002 Niels Provos <provos@citi.umich.edu>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef TREE_H
|
||||
#define TREE_H
|
||||
|
||||
/* #include <sys/cdefs.h> */
|
||||
#ifndef __unused
|
||||
#define __unused __attribute__((__unused__))
|
||||
#endif
|
||||
|
||||
/*
|
||||
* This file defines data structures for different types of trees:
|
||||
* splay trees and red-black trees.
|
||||
*
|
||||
* A splay tree is a self-organizing data structure. Every operation
|
||||
* on the tree causes a splay to happen. The splay moves the requested
|
||||
* node to the root of the tree and partly rebalances it.
|
||||
*
|
||||
* This has the benefit that request locality causes faster lookups as
|
||||
* the requested nodes move to the top of the tree. On the other hand,
|
||||
* every lookup causes memory writes.
|
||||
*
|
||||
* The Balance Theorem bounds the total access time for m operations
|
||||
* and n inserts on an initially empty tree as O((m + n)lg n). The
|
||||
* amortized cost for a sequence of m accesses to a splay tree is O(lg n);
|
||||
*
|
||||
* A red-black tree is a binary search tree with the node color as an
|
||||
* extra attribute. It fulfills a set of conditions:
|
||||
* - every search path from the root to a leaf consists of the
|
||||
* same number of black nodes,
|
||||
* - each red node (except for the root) has a black parent,
|
||||
* - each leaf node is black.
|
||||
*
|
||||
* Every operation on a red-black tree is bounded as O(lg n).
|
||||
* The maximum height of a red-black tree is 2lg (n+1).
|
||||
*/
|
||||
|
||||
#define SPLAY_HEAD(name, type) \
|
||||
struct name { \
|
||||
struct type *sph_root; /* root of the tree */ \
|
||||
}
|
||||
|
||||
#define SPLAY_INITIALIZER(root) \
|
||||
{ NULL }
|
||||
|
||||
#define SPLAY_INIT(root) do { \
|
||||
(root)->sph_root = NULL; \
|
||||
} while (/*CONSTCOND*/ 0)
|
||||
|
||||
#define SPLAY_ENTRY(type) \
|
||||
struct { \
|
||||
struct type *spe_left; /* left element */ \
|
||||
struct type *spe_right; /* right element */ \
|
||||
}
|
||||
|
||||
#define SPLAY_LEFT(elm, field) (elm)->field.spe_left
|
||||
#define SPLAY_RIGHT(elm, field) (elm)->field.spe_right
|
||||
#define SPLAY_ROOT(head) (head)->sph_root
|
||||
#define SPLAY_EMPTY(head) (SPLAY_ROOT(head) == NULL)
|
||||
|
||||
/* SPLAY_ROTATE_{LEFT,RIGHT} expect that tmp hold SPLAY_{RIGHT,LEFT} */
|
||||
#define SPLAY_ROTATE_RIGHT(head, tmp, field) do { \
|
||||
SPLAY_LEFT((head)->sph_root, field) = SPLAY_RIGHT(tmp, field); \
|
||||
SPLAY_RIGHT(tmp, field) = (head)->sph_root; \
|
||||
(head)->sph_root = tmp; \
|
||||
} while (/*CONSTCOND*/ 0)
|
||||
|
||||
#define SPLAY_ROTATE_LEFT(head, tmp, field) do { \
|
||||
SPLAY_RIGHT((head)->sph_root, field) = SPLAY_LEFT(tmp, field); \
|
||||
SPLAY_LEFT(tmp, field) = (head)->sph_root; \
|
||||
(head)->sph_root = tmp; \
|
||||
} while (/*CONSTCOND*/ 0)
|
||||
|
||||
#define SPLAY_LINKLEFT(head, tmp, field) do { \
|
||||
SPLAY_LEFT(tmp, field) = (head)->sph_root; \
|
||||
tmp = (head)->sph_root; \
|
||||
(head)->sph_root = SPLAY_LEFT((head)->sph_root, field); \
|
||||
} while (/*CONSTCOND*/ 0)
|
||||
|
||||
#define SPLAY_LINKRIGHT(head, tmp, field) do { \
|
||||
SPLAY_RIGHT(tmp, field) = (head)->sph_root; \
|
||||
tmp = (head)->sph_root; \
|
||||
(head)->sph_root = SPLAY_RIGHT((head)->sph_root, field); \
|
||||
} while (/*CONSTCOND*/ 0)
|
||||
|
||||
#define SPLAY_ASSEMBLE(head, node, left, right, field) do { \
|
||||
SPLAY_RIGHT(left, field) = SPLAY_LEFT((head)->sph_root, field); \
|
||||
SPLAY_LEFT(right, field) = SPLAY_RIGHT((head)->sph_root, field);\
|
||||
SPLAY_LEFT((head)->sph_root, field) = SPLAY_RIGHT(node, field); \
|
||||
SPLAY_RIGHT((head)->sph_root, field) = SPLAY_LEFT(node, field); \
|
||||
} while (/*CONSTCOND*/ 0)
|
||||
|
||||
/* Generates prototypes and inline functions */
|
||||
|
||||
#define SPLAY_PROTOTYPE(name, type, field, cmp) \
|
||||
void name##_SPLAY(struct name *, struct type *); \
|
||||
void name##_SPLAY_MINMAX(struct name *, int); \
|
||||
struct type *name##_SPLAY_INSERT(struct name *, struct type *); \
|
||||
struct type *name##_SPLAY_REMOVE(struct name *, struct type *); \
|
||||
\
|
||||
/* Finds the node with the same key as elm */ \
|
||||
static __inline struct type * \
|
||||
name##_SPLAY_FIND(struct name *head, struct type *elm) \
|
||||
{ \
|
||||
if (SPLAY_EMPTY(head)) \
|
||||
return(NULL); \
|
||||
name##_SPLAY(head, elm); \
|
||||
if ((cmp)(elm, (head)->sph_root) == 0) \
|
||||
return (head->sph_root); \
|
||||
return (NULL); \
|
||||
} \
|
||||
\
|
||||
static __inline struct type * \
|
||||
name##_SPLAY_NEXT(struct name *head, struct type *elm) \
|
||||
{ \
|
||||
name##_SPLAY(head, elm); \
|
||||
if (SPLAY_RIGHT(elm, field) != NULL) { \
|
||||
elm = SPLAY_RIGHT(elm, field); \
|
||||
while (SPLAY_LEFT(elm, field) != NULL) { \
|
||||
elm = SPLAY_LEFT(elm, field); \
|
||||
} \
|
||||
} else \
|
||||
elm = NULL; \
|
||||
return (elm); \
|
||||
} \
|
||||
\
|
||||
static __inline struct type * \
|
||||
name##_SPLAY_MIN_MAX(struct name *head, int val) \
|
||||
{ \
|
||||
name##_SPLAY_MINMAX(head, val); \
|
||||
return (SPLAY_ROOT(head)); \
|
||||
}
|
||||
|
||||
/* Main splay operation.
|
||||
* Moves node close to the key of elm to top
|
||||
*/
|
||||
#define SPLAY_GENERATE(name, type, field, cmp) \
|
||||
struct type * \
|
||||
name##_SPLAY_INSERT(struct name *head, struct type *elm) \
|
||||
{ \
|
||||
if (SPLAY_EMPTY(head)) { \
|
||||
SPLAY_LEFT(elm, field) = SPLAY_RIGHT(elm, field) = NULL; \
|
||||
} else { \
|
||||
int __comp; \
|
||||
name##_SPLAY(head, elm); \
|
||||
__comp = (cmp)(elm, (head)->sph_root); \
|
||||
if(__comp < 0) { \
|
||||
SPLAY_LEFT(elm, field) = SPLAY_LEFT((head)->sph_root, field);\
|
||||
SPLAY_RIGHT(elm, field) = (head)->sph_root; \
|
||||
SPLAY_LEFT((head)->sph_root, field) = NULL; \
|
||||
} else if (__comp > 0) { \
|
||||
SPLAY_RIGHT(elm, field) = SPLAY_RIGHT((head)->sph_root, field);\
|
||||
SPLAY_LEFT(elm, field) = (head)->sph_root; \
|
||||
SPLAY_RIGHT((head)->sph_root, field) = NULL; \
|
||||
} else \
|
||||
return ((head)->sph_root); \
|
||||
} \
|
||||
(head)->sph_root = (elm); \
|
||||
return (NULL); \
|
||||
} \
|
||||
\
|
||||
struct type * \
|
||||
name##_SPLAY_REMOVE(struct name *head, struct type *elm) \
|
||||
{ \
|
||||
struct type *__tmp; \
|
||||
if (SPLAY_EMPTY(head)) \
|
||||
return (NULL); \
|
||||
name##_SPLAY(head, elm); \
|
||||
if ((cmp)(elm, (head)->sph_root) == 0) { \
|
||||
if (SPLAY_LEFT((head)->sph_root, field) == NULL) { \
|
||||
(head)->sph_root = SPLAY_RIGHT((head)->sph_root, field);\
|
||||
} else { \
|
||||
__tmp = SPLAY_RIGHT((head)->sph_root, field); \
|
||||
(head)->sph_root = SPLAY_LEFT((head)->sph_root, field);\
|
||||
name##_SPLAY(head, elm); \
|
||||
SPLAY_RIGHT((head)->sph_root, field) = __tmp; \
|
||||
} \
|
||||
return (elm); \
|
||||
} \
|
||||
return (NULL); \
|
||||
} \
|
||||
\
|
||||
void \
|
||||
name##_SPLAY(struct name *head, struct type *elm) \
|
||||
{ \
|
||||
struct type __node, *__left, *__right, *__tmp; \
|
||||
int __comp; \
|
||||
\
|
||||
SPLAY_LEFT(&__node, field) = SPLAY_RIGHT(&__node, field) = NULL;\
|
||||
__left = __right = &__node; \
|
||||
\
|
||||
while ((__comp = (cmp)(elm, (head)->sph_root)) != 0) { \
|
||||
if (__comp < 0) { \
|
||||
__tmp = SPLAY_LEFT((head)->sph_root, field); \
|
||||
if (__tmp == NULL) \
|
||||
break; \
|
||||
if ((cmp)(elm, __tmp) < 0){ \
|
||||
SPLAY_ROTATE_RIGHT(head, __tmp, field); \
|
||||
if (SPLAY_LEFT((head)->sph_root, field) == NULL)\
|
||||
break; \
|
||||
} \
|
||||
SPLAY_LINKLEFT(head, __right, field); \
|
||||
} else if (__comp > 0) { \
|
||||
__tmp = SPLAY_RIGHT((head)->sph_root, field); \
|
||||
if (__tmp == NULL) \
|
||||
break; \
|
||||
if ((cmp)(elm, __tmp) > 0){ \
|
||||
SPLAY_ROTATE_LEFT(head, __tmp, field); \
|
||||
if (SPLAY_RIGHT((head)->sph_root, field) == NULL)\
|
||||
break; \
|
||||
} \
|
||||
SPLAY_LINKRIGHT(head, __left, field); \
|
||||
} \
|
||||
} \
|
||||
SPLAY_ASSEMBLE(head, &__node, __left, __right, field); \
|
||||
} \
|
||||
\
|
||||
/* Splay with either the minimum or the maximum element \
|
||||
* Used to find minimum or maximum element in tree. \
|
||||
*/ \
|
||||
void name##_SPLAY_MINMAX(struct name *head, int __comp) \
|
||||
{ \
|
||||
struct type __node, *__left, *__right, *__tmp; \
|
||||
\
|
||||
SPLAY_LEFT(&__node, field) = SPLAY_RIGHT(&__node, field) = NULL;\
|
||||
__left = __right = &__node; \
|
||||
\
|
||||
while (1) { \
|
||||
if (__comp < 0) { \
|
||||
__tmp = SPLAY_LEFT((head)->sph_root, field); \
|
||||
if (__tmp == NULL) \
|
||||
break; \
|
||||
if (__comp < 0){ \
|
||||
SPLAY_ROTATE_RIGHT(head, __tmp, field); \
|
||||
if (SPLAY_LEFT((head)->sph_root, field) == NULL)\
|
||||
break; \
|
||||
} \
|
||||
SPLAY_LINKLEFT(head, __right, field); \
|
||||
} else if (__comp > 0) { \
|
||||
__tmp = SPLAY_RIGHT((head)->sph_root, field); \
|
||||
if (__tmp == NULL) \
|
||||
break; \
|
||||
if (__comp > 0) { \
|
||||
SPLAY_ROTATE_LEFT(head, __tmp, field); \
|
||||
if (SPLAY_RIGHT((head)->sph_root, field) == NULL)\
|
||||
break; \
|
||||
} \
|
||||
SPLAY_LINKRIGHT(head, __left, field); \
|
||||
} \
|
||||
} \
|
||||
SPLAY_ASSEMBLE(head, &__node, __left, __right, field); \
|
||||
}
|
||||
|
||||
#define SPLAY_NEGINF -1
|
||||
#define SPLAY_INF 1
|
||||
|
||||
#define SPLAY_INSERT(name, x, y) name##_SPLAY_INSERT(x, y)
|
||||
#define SPLAY_REMOVE(name, x, y) name##_SPLAY_REMOVE(x, y)
|
||||
#define SPLAY_FIND(name, x, y) name##_SPLAY_FIND(x, y)
|
||||
#define SPLAY_NEXT(name, x, y) name##_SPLAY_NEXT(x, y)
|
||||
#define SPLAY_MIN(name, x) (SPLAY_EMPTY(x) ? NULL \
|
||||
: name##_SPLAY_MIN_MAX(x, SPLAY_NEGINF))
|
||||
#define SPLAY_MAX(name, x) (SPLAY_EMPTY(x) ? NULL \
|
||||
: name##_SPLAY_MIN_MAX(x, SPLAY_INF))
|
||||
|
||||
#define SPLAY_FOREACH(x, name, head) \
|
||||
for ((x) = SPLAY_MIN(name, head); \
|
||||
(x) != NULL; \
|
||||
(x) = SPLAY_NEXT(name, head, x))
|
||||
|
||||
/* Macros that define a red-black tree */
|
||||
#define RB_HEAD(name, type) \
|
||||
struct name { \
|
||||
struct type *rbh_root; /* root of the tree */ \
|
||||
}
|
||||
|
||||
#define RB_INITIALIZER(root) \
|
||||
{ NULL }
|
||||
|
||||
#define RB_INIT(root) do { \
|
||||
(root)->rbh_root = NULL; \
|
||||
} while (/*CONSTCOND*/ 0)
|
||||
|
||||
#define RB_BLACK 0
|
||||
#define RB_RED 1
|
||||
#define RB_ENTRY(type) \
|
||||
struct { \
|
||||
struct type *rbe_left; /* left element */ \
|
||||
struct type *rbe_right; /* right element */ \
|
||||
struct type *rbe_parent; /* parent element */ \
|
||||
int rbe_color; /* node color */ \
|
||||
}
|
||||
|
||||
#define RB_LEFT(elm, field) (elm)->field.rbe_left
|
||||
#define RB_RIGHT(elm, field) (elm)->field.rbe_right
|
||||
#define RB_PARENT(elm, field) (elm)->field.rbe_parent
|
||||
#define RB_COLOR(elm, field) (elm)->field.rbe_color
|
||||
#define RB_ROOT(head) (head)->rbh_root
|
||||
#define RB_EMPTY(head) (RB_ROOT(head) == NULL)
|
||||
|
||||
#define RB_SET(elm, parent, field) do { \
|
||||
RB_PARENT(elm, field) = parent; \
|
||||
RB_LEFT(elm, field) = RB_RIGHT(elm, field) = NULL; \
|
||||
RB_COLOR(elm, field) = RB_RED; \
|
||||
} while (/*CONSTCOND*/ 0)
|
||||
|
||||
#define RB_SET_BLACKRED(black, red, field) do { \
|
||||
RB_COLOR(black, field) = RB_BLACK; \
|
||||
RB_COLOR(red, field) = RB_RED; \
|
||||
} while (/*CONSTCOND*/ 0)
|
||||
|
||||
#ifndef RB_AUGMENT
|
||||
#define RB_AUGMENT(x) do {} while (0)
|
||||
#endif
|
||||
|
||||
#define RB_ROTATE_LEFT(head, elm, tmp, field) do { \
|
||||
(tmp) = RB_RIGHT(elm, field); \
|
||||
if ((RB_RIGHT(elm, field) = RB_LEFT(tmp, field)) != NULL) { \
|
||||
RB_PARENT(RB_LEFT(tmp, field), field) = (elm); \
|
||||
} \
|
||||
RB_AUGMENT(elm); \
|
||||
if ((RB_PARENT(tmp, field) = RB_PARENT(elm, field)) != NULL) { \
|
||||
if ((elm) == RB_LEFT(RB_PARENT(elm, field), field)) \
|
||||
RB_LEFT(RB_PARENT(elm, field), field) = (tmp); \
|
||||
else \
|
||||
RB_RIGHT(RB_PARENT(elm, field), field) = (tmp); \
|
||||
} else \
|
||||
(head)->rbh_root = (tmp); \
|
||||
RB_LEFT(tmp, field) = (elm); \
|
||||
RB_PARENT(elm, field) = (tmp); \
|
||||
RB_AUGMENT(tmp); \
|
||||
if ((RB_PARENT(tmp, field))) \
|
||||
RB_AUGMENT(RB_PARENT(tmp, field)); \
|
||||
} while (/*CONSTCOND*/ 0)
|
||||
|
||||
#define RB_ROTATE_RIGHT(head, elm, tmp, field) do { \
|
||||
(tmp) = RB_LEFT(elm, field); \
|
||||
if ((RB_LEFT(elm, field) = RB_RIGHT(tmp, field)) != NULL) { \
|
||||
RB_PARENT(RB_RIGHT(tmp, field), field) = (elm); \
|
||||
} \
|
||||
RB_AUGMENT(elm); \
|
||||
if ((RB_PARENT(tmp, field) = RB_PARENT(elm, field)) != NULL) { \
|
||||
if ((elm) == RB_LEFT(RB_PARENT(elm, field), field)) \
|
||||
RB_LEFT(RB_PARENT(elm, field), field) = (tmp); \
|
||||
else \
|
||||
RB_RIGHT(RB_PARENT(elm, field), field) = (tmp); \
|
||||
} else \
|
||||
(head)->rbh_root = (tmp); \
|
||||
RB_RIGHT(tmp, field) = (elm); \
|
||||
RB_PARENT(elm, field) = (tmp); \
|
||||
RB_AUGMENT(tmp); \
|
||||
if ((RB_PARENT(tmp, field))) \
|
||||
RB_AUGMENT(RB_PARENT(tmp, field)); \
|
||||
} while (/*CONSTCOND*/ 0)
|
||||
|
||||
/* Generates prototypes and inline functions */
|
||||
#define RB_PROTOTYPE(name, type, field, cmp) \
|
||||
RB_PROTOTYPE_INTERNAL(name, type, field, cmp,)
|
||||
#define RB_PROTOTYPE_STATIC(name, type, field, cmp) \
|
||||
RB_PROTOTYPE_INTERNAL(name, type, field, cmp, __unused static)
|
||||
#define RB_PROTOTYPE_INTERNAL(name, type, field, cmp, attr) \
|
||||
attr void name##_RB_INSERT_COLOR(struct name *, struct type *); \
|
||||
attr void name##_RB_REMOVE_COLOR(struct name *, struct type *, struct type *);\
|
||||
attr struct type *name##_RB_REMOVE(struct name *, struct type *); \
|
||||
attr struct type *name##_RB_INSERT(struct name *, struct type *); \
|
||||
attr struct type *name##_RB_FIND(struct name *, struct type *); \
|
||||
attr struct type *name##_RB_NFIND(struct name *, struct type *); \
|
||||
attr struct type *name##_RB_NEXT(struct type *); \
|
||||
attr struct type *name##_RB_PREV(struct type *); \
|
||||
attr struct type *name##_RB_MINMAX(struct name *, int); \
|
||||
\
|
||||
|
||||
/* Main rb operation.
|
||||
* Moves node close to the key of elm to top
|
||||
*/
|
||||
#define RB_GENERATE(name, type, field, cmp) \
|
||||
RB_GENERATE_INTERNAL(name, type, field, cmp,)
|
||||
#define RB_GENERATE_STATIC(name, type, field, cmp) \
|
||||
RB_GENERATE_INTERNAL(name, type, field, cmp, __unused static)
|
||||
#define RB_GENERATE_INTERNAL(name, type, field, cmp, attr) \
|
||||
attr void \
|
||||
name##_RB_INSERT_COLOR(struct name *head, struct type *elm) \
|
||||
{ \
|
||||
struct type *parent, *gparent, *tmp; \
|
||||
while ((parent = RB_PARENT(elm, field)) != NULL && \
|
||||
RB_COLOR(parent, field) == RB_RED) { \
|
||||
gparent = RB_PARENT(parent, field); \
|
||||
if (parent == RB_LEFT(gparent, field)) { \
|
||||
tmp = RB_RIGHT(gparent, field); \
|
||||
if (tmp && RB_COLOR(tmp, field) == RB_RED) { \
|
||||
RB_COLOR(tmp, field) = RB_BLACK; \
|
||||
RB_SET_BLACKRED(parent, gparent, field);\
|
||||
elm = gparent; \
|
||||
continue; \
|
||||
} \
|
||||
if (RB_RIGHT(parent, field) == elm) { \
|
||||
RB_ROTATE_LEFT(head, parent, tmp, field);\
|
||||
tmp = parent; \
|
||||
parent = elm; \
|
||||
elm = tmp; \
|
||||
} \
|
||||
RB_SET_BLACKRED(parent, gparent, field); \
|
||||
RB_ROTATE_RIGHT(head, gparent, tmp, field); \
|
||||
} else { \
|
||||
tmp = RB_LEFT(gparent, field); \
|
||||
if (tmp && RB_COLOR(tmp, field) == RB_RED) { \
|
||||
RB_COLOR(tmp, field) = RB_BLACK; \
|
||||
RB_SET_BLACKRED(parent, gparent, field);\
|
||||
elm = gparent; \
|
||||
continue; \
|
||||
} \
|
||||
if (RB_LEFT(parent, field) == elm) { \
|
||||
RB_ROTATE_RIGHT(head, parent, tmp, field);\
|
||||
tmp = parent; \
|
||||
parent = elm; \
|
||||
elm = tmp; \
|
||||
} \
|
||||
RB_SET_BLACKRED(parent, gparent, field); \
|
||||
RB_ROTATE_LEFT(head, gparent, tmp, field); \
|
||||
} \
|
||||
} \
|
||||
RB_COLOR(head->rbh_root, field) = RB_BLACK; \
|
||||
} \
|
||||
\
|
||||
attr void \
|
||||
name##_RB_REMOVE_COLOR(struct name *head, struct type *parent, struct type *elm) \
|
||||
{ \
|
||||
struct type *tmp; \
|
||||
while ((elm == NULL || RB_COLOR(elm, field) == RB_BLACK) && \
|
||||
elm != RB_ROOT(head)) { \
|
||||
if (RB_LEFT(parent, field) == elm) { \
|
||||
tmp = RB_RIGHT(parent, field); \
|
||||
if (RB_COLOR(tmp, field) == RB_RED) { \
|
||||
RB_SET_BLACKRED(tmp, parent, field); \
|
||||
RB_ROTATE_LEFT(head, parent, tmp, field);\
|
||||
tmp = RB_RIGHT(parent, field); \
|
||||
} \
|
||||
if ((RB_LEFT(tmp, field) == NULL || \
|
||||
RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) &&\
|
||||
(RB_RIGHT(tmp, field) == NULL || \
|
||||
RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK)) {\
|
||||
RB_COLOR(tmp, field) = RB_RED; \
|
||||
elm = parent; \
|
||||
parent = RB_PARENT(elm, field); \
|
||||
} else { \
|
||||
if (RB_RIGHT(tmp, field) == NULL || \
|
||||
RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK) {\
|
||||
struct type *oleft; \
|
||||
if ((oleft = RB_LEFT(tmp, field)) \
|
||||
!= NULL) \
|
||||
RB_COLOR(oleft, field) = RB_BLACK;\
|
||||
RB_COLOR(tmp, field) = RB_RED; \
|
||||
RB_ROTATE_RIGHT(head, tmp, oleft, field);\
|
||||
tmp = RB_RIGHT(parent, field); \
|
||||
} \
|
||||
RB_COLOR(tmp, field) = RB_COLOR(parent, field);\
|
||||
RB_COLOR(parent, field) = RB_BLACK; \
|
||||
if (RB_RIGHT(tmp, field)) \
|
||||
RB_COLOR(RB_RIGHT(tmp, field), field) = RB_BLACK;\
|
||||
RB_ROTATE_LEFT(head, parent, tmp, field);\
|
||||
elm = RB_ROOT(head); \
|
||||
break; \
|
||||
} \
|
||||
} else { \
|
||||
tmp = RB_LEFT(parent, field); \
|
||||
if (RB_COLOR(tmp, field) == RB_RED) { \
|
||||
RB_SET_BLACKRED(tmp, parent, field); \
|
||||
RB_ROTATE_RIGHT(head, parent, tmp, field);\
|
||||
tmp = RB_LEFT(parent, field); \
|
||||
} \
|
||||
if ((RB_LEFT(tmp, field) == NULL || \
|
||||
RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) &&\
|
||||
(RB_RIGHT(tmp, field) == NULL || \
|
||||
RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK)) {\
|
||||
RB_COLOR(tmp, field) = RB_RED; \
|
||||
elm = parent; \
|
||||
parent = RB_PARENT(elm, field); \
|
||||
} else { \
|
||||
if (RB_LEFT(tmp, field) == NULL || \
|
||||
RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) {\
|
||||
struct type *oright; \
|
||||
if ((oright = RB_RIGHT(tmp, field)) \
|
||||
!= NULL) \
|
||||
RB_COLOR(oright, field) = RB_BLACK;\
|
||||
RB_COLOR(tmp, field) = RB_RED; \
|
||||
RB_ROTATE_LEFT(head, tmp, oright, field);\
|
||||
tmp = RB_LEFT(parent, field); \
|
||||
} \
|
||||
RB_COLOR(tmp, field) = RB_COLOR(parent, field);\
|
||||
RB_COLOR(parent, field) = RB_BLACK; \
|
||||
if (RB_LEFT(tmp, field)) \
|
||||
RB_COLOR(RB_LEFT(tmp, field), field) = RB_BLACK;\
|
||||
RB_ROTATE_RIGHT(head, parent, tmp, field);\
|
||||
elm = RB_ROOT(head); \
|
||||
break; \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
if (elm) \
|
||||
RB_COLOR(elm, field) = RB_BLACK; \
|
||||
} \
|
||||
\
|
||||
attr struct type * \
|
||||
name##_RB_REMOVE(struct name *head, struct type *elm) \
|
||||
{ \
|
||||
struct type *child, *parent, *old = elm; \
|
||||
int color; \
|
||||
if (RB_LEFT(elm, field) == NULL) \
|
||||
child = RB_RIGHT(elm, field); \
|
||||
else if (RB_RIGHT(elm, field) == NULL) \
|
||||
child = RB_LEFT(elm, field); \
|
||||
else { \
|
||||
struct type *left; \
|
||||
elm = RB_RIGHT(elm, field); \
|
||||
while ((left = RB_LEFT(elm, field)) != NULL) \
|
||||
elm = left; \
|
||||
child = RB_RIGHT(elm, field); \
|
||||
parent = RB_PARENT(elm, field); \
|
||||
color = RB_COLOR(elm, field); \
|
||||
if (child) \
|
||||
RB_PARENT(child, field) = parent; \
|
||||
if (parent) { \
|
||||
if (RB_LEFT(parent, field) == elm) \
|
||||
RB_LEFT(parent, field) = child; \
|
||||
else \
|
||||
RB_RIGHT(parent, field) = child; \
|
||||
RB_AUGMENT(parent); \
|
||||
} else \
|
||||
RB_ROOT(head) = child; \
|
||||
if (RB_PARENT(elm, field) == old) \
|
||||
parent = elm; \
|
||||
(elm)->field = (old)->field; \
|
||||
if (RB_PARENT(old, field)) { \
|
||||
if (RB_LEFT(RB_PARENT(old, field), field) == old)\
|
||||
RB_LEFT(RB_PARENT(old, field), field) = elm;\
|
||||
else \
|
||||
RB_RIGHT(RB_PARENT(old, field), field) = elm;\
|
||||
RB_AUGMENT(RB_PARENT(old, field)); \
|
||||
} else \
|
||||
RB_ROOT(head) = elm; \
|
||||
RB_PARENT(RB_LEFT(old, field), field) = elm; \
|
||||
if (RB_RIGHT(old, field)) \
|
||||
RB_PARENT(RB_RIGHT(old, field), field) = elm; \
|
||||
if (parent) { \
|
||||
left = parent; \
|
||||
do { \
|
||||
RB_AUGMENT(left); \
|
||||
} while ((left = RB_PARENT(left, field)) != NULL); \
|
||||
} \
|
||||
goto color; \
|
||||
} \
|
||||
parent = RB_PARENT(elm, field); \
|
||||
color = RB_COLOR(elm, field); \
|
||||
if (child) \
|
||||
RB_PARENT(child, field) = parent; \
|
||||
if (parent) { \
|
||||
if (RB_LEFT(parent, field) == elm) \
|
||||
RB_LEFT(parent, field) = child; \
|
||||
else \
|
||||
RB_RIGHT(parent, field) = child; \
|
||||
RB_AUGMENT(parent); \
|
||||
} else \
|
||||
RB_ROOT(head) = child; \
|
||||
color: \
|
||||
if (color == RB_BLACK) \
|
||||
name##_RB_REMOVE_COLOR(head, parent, child); \
|
||||
return (old); \
|
||||
} \
|
||||
\
|
||||
/* Inserts a node into the RB tree */ \
|
||||
attr struct type * \
|
||||
name##_RB_INSERT(struct name *head, struct type *elm) \
|
||||
{ \
|
||||
struct type *tmp; \
|
||||
struct type *parent = NULL; \
|
||||
int comp = 0; \
|
||||
tmp = RB_ROOT(head); \
|
||||
while (tmp) { \
|
||||
parent = tmp; \
|
||||
comp = (cmp)(elm, parent); \
|
||||
if (comp < 0) \
|
||||
tmp = RB_LEFT(tmp, field); \
|
||||
else if (comp > 0) \
|
||||
tmp = RB_RIGHT(tmp, field); \
|
||||
else \
|
||||
return (tmp); \
|
||||
} \
|
||||
RB_SET(elm, parent, field); \
|
||||
if (parent != NULL) { \
|
||||
if (comp < 0) \
|
||||
RB_LEFT(parent, field) = elm; \
|
||||
else \
|
||||
RB_RIGHT(parent, field) = elm; \
|
||||
RB_AUGMENT(parent); \
|
||||
} else \
|
||||
RB_ROOT(head) = elm; \
|
||||
name##_RB_INSERT_COLOR(head, elm); \
|
||||
return (NULL); \
|
||||
} \
|
||||
\
|
||||
/* Finds the node with the same key as elm */ \
|
||||
attr struct type * \
|
||||
name##_RB_FIND(struct name *head, struct type *elm) \
|
||||
{ \
|
||||
struct type *tmp = RB_ROOT(head); \
|
||||
int comp; \
|
||||
while (tmp) { \
|
||||
comp = cmp(elm, tmp); \
|
||||
if (comp < 0) \
|
||||
tmp = RB_LEFT(tmp, field); \
|
||||
else if (comp > 0) \
|
||||
tmp = RB_RIGHT(tmp, field); \
|
||||
else \
|
||||
return (tmp); \
|
||||
} \
|
||||
return (NULL); \
|
||||
} \
|
||||
\
|
||||
/* Finds the first node greater than or equal to the search key */ \
|
||||
attr struct type * \
|
||||
name##_RB_NFIND(struct name *head, struct type *elm) \
|
||||
{ \
|
||||
struct type *tmp = RB_ROOT(head); \
|
||||
struct type *res = NULL; \
|
||||
int comp; \
|
||||
while (tmp) { \
|
||||
comp = cmp(elm, tmp); \
|
||||
if (comp < 0) { \
|
||||
res = tmp; \
|
||||
tmp = RB_LEFT(tmp, field); \
|
||||
} \
|
||||
else if (comp > 0) \
|
||||
tmp = RB_RIGHT(tmp, field); \
|
||||
else \
|
||||
return (tmp); \
|
||||
} \
|
||||
return (res); \
|
||||
} \
|
||||
\
|
||||
/* ARGSUSED */ \
|
||||
attr struct type * \
|
||||
name##_RB_NEXT(struct type *elm) \
|
||||
{ \
|
||||
if (RB_RIGHT(elm, field)) { \
|
||||
elm = RB_RIGHT(elm, field); \
|
||||
while (RB_LEFT(elm, field)) \
|
||||
elm = RB_LEFT(elm, field); \
|
||||
} else { \
|
||||
if (RB_PARENT(elm, field) && \
|
||||
(elm == RB_LEFT(RB_PARENT(elm, field), field))) \
|
||||
elm = RB_PARENT(elm, field); \
|
||||
else { \
|
||||
while (RB_PARENT(elm, field) && \
|
||||
(elm == RB_RIGHT(RB_PARENT(elm, field), field)))\
|
||||
elm = RB_PARENT(elm, field); \
|
||||
elm = RB_PARENT(elm, field); \
|
||||
} \
|
||||
} \
|
||||
return (elm); \
|
||||
} \
|
||||
\
|
||||
/* ARGSUSED */ \
|
||||
attr struct type * \
|
||||
name##_RB_PREV(struct type *elm) \
|
||||
{ \
|
||||
if (RB_LEFT(elm, field)) { \
|
||||
elm = RB_LEFT(elm, field); \
|
||||
while (RB_RIGHT(elm, field)) \
|
||||
elm = RB_RIGHT(elm, field); \
|
||||
} else { \
|
||||
if (RB_PARENT(elm, field) && \
|
||||
(elm == RB_RIGHT(RB_PARENT(elm, field), field))) \
|
||||
elm = RB_PARENT(elm, field); \
|
||||
else { \
|
||||
while (RB_PARENT(elm, field) && \
|
||||
(elm == RB_LEFT(RB_PARENT(elm, field), field)))\
|
||||
elm = RB_PARENT(elm, field); \
|
||||
elm = RB_PARENT(elm, field); \
|
||||
} \
|
||||
} \
|
||||
return (elm); \
|
||||
} \
|
||||
\
|
||||
attr struct type * \
|
||||
name##_RB_MINMAX(struct name *head, int val) \
|
||||
{ \
|
||||
struct type *tmp = RB_ROOT(head); \
|
||||
struct type *parent = NULL; \
|
||||
while (tmp) { \
|
||||
parent = tmp; \
|
||||
if (val < 0) \
|
||||
tmp = RB_LEFT(tmp, field); \
|
||||
else \
|
||||
tmp = RB_RIGHT(tmp, field); \
|
||||
} \
|
||||
return (parent); \
|
||||
}
|
||||
|
||||
#define RB_NEGINF -1
|
||||
#define RB_INF 1
|
||||
|
||||
#define RB_INSERT(name, x, y) name##_RB_INSERT(x, y)
|
||||
#define RB_REMOVE(name, x, y) name##_RB_REMOVE(x, y)
|
||||
#define RB_FIND(name, x, y) name##_RB_FIND(x, y)
|
||||
#define RB_NFIND(name, x, y) name##_RB_NFIND(x, y)
|
||||
#define RB_NEXT(name, x, y) name##_RB_NEXT(y)
|
||||
#define RB_PREV(name, x, y) name##_RB_PREV(y)
|
||||
#define RB_MIN(name, x) name##_RB_MINMAX(x, RB_NEGINF)
|
||||
#define RB_MAX(name, x) name##_RB_MINMAX(x, RB_INF)
|
||||
|
||||
#define RB_FOREACH(x, name, head) \
|
||||
for ((x) = RB_MIN(name, head); \
|
||||
(x) != NULL; \
|
||||
(x) = name##_RB_NEXT(x))
|
||||
|
||||
#define RB_FOREACH_FROM(x, name, y) \
|
||||
for ((x) = (y); \
|
||||
((x) != NULL) && ((y) = name##_RB_NEXT(x), (x) != NULL); \
|
||||
(x) = (y))
|
||||
|
||||
#define RB_FOREACH_SAFE(x, name, head, y) \
|
||||
for ((x) = RB_MIN(name, head); \
|
||||
((x) != NULL) && ((y) = name##_RB_NEXT(x), (x) != NULL); \
|
||||
(x) = (y))
|
||||
|
||||
#define RB_FOREACH_REVERSE(x, name, head) \
|
||||
for ((x) = RB_MAX(name, head); \
|
||||
(x) != NULL; \
|
||||
(x) = name##_RB_PREV(x))
|
||||
|
||||
#define RB_FOREACH_REVERSE_FROM(x, name, y) \
|
||||
for ((x) = (y); \
|
||||
((x) != NULL) && ((y) = name##_RB_PREV(x), (x) != NULL); \
|
||||
(x) = (y))
|
||||
|
||||
#define RB_FOREACH_REVERSE_SAFE(x, name, head, y) \
|
||||
for ((x) = RB_MAX(name, head); \
|
||||
((x) != NULL) && ((y) = name##_RB_PREV(x), (x) != NULL); \
|
||||
(x) = (y))
|
||||
|
||||
#endif
|
||||
+663
@@ -0,0 +1,663 @@
|
||||
// Copyright (c) 2018-present, iQIYI, Inc. All rights reserved.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
// Created by caikelun on 2018-04-11.
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <ctype.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <pthread.h>
|
||||
#include <regex.h>
|
||||
#include <setjmp.h>
|
||||
#include <errno.h>
|
||||
#include "queue.h"
|
||||
#include "tree.h"
|
||||
#include "xh_errno.h"
|
||||
#include "xh_log.h"
|
||||
#include "xh_elf.h"
|
||||
#include "xh_version.h"
|
||||
#include "xh_core.h"
|
||||
|
||||
#define XH_CORE_DEBUG 0
|
||||
|
||||
//registered hook info collection
|
||||
typedef struct xh_core_hook_info
|
||||
{
|
||||
#if XH_CORE_DEBUG
|
||||
char *pathname_regex_str;
|
||||
#endif
|
||||
regex_t pathname_regex;
|
||||
char *symbol;
|
||||
void *new_func;
|
||||
void **old_func;
|
||||
TAILQ_ENTRY(xh_core_hook_info,) link;
|
||||
} xh_core_hook_info_t;
|
||||
typedef TAILQ_HEAD(xh_core_hook_info_queue, xh_core_hook_info,) xh_core_hook_info_queue_t;
|
||||
|
||||
//ignored hook info collection
|
||||
typedef struct xh_core_ignore_info
|
||||
{
|
||||
#if XH_CORE_DEBUG
|
||||
char *pathname_regex_str;
|
||||
#endif
|
||||
regex_t pathname_regex;
|
||||
char *symbol; //NULL meaning for all symbols
|
||||
TAILQ_ENTRY(xh_core_ignore_info,) link;
|
||||
} xh_core_ignore_info_t;
|
||||
typedef TAILQ_HEAD(xh_core_ignore_info_queue, xh_core_ignore_info,) xh_core_ignore_info_queue_t;
|
||||
|
||||
//required info from /proc/self/maps
|
||||
typedef struct xh_core_map_info
|
||||
{
|
||||
char *pathname;
|
||||
unsigned long inode;
|
||||
uintptr_t base_addr;
|
||||
xh_elf_t elf;
|
||||
RB_ENTRY(xh_core_map_info) link;
|
||||
} xh_core_map_info_t;
|
||||
static __inline__ int xh_core_map_info_cmp(xh_core_map_info_t *a, xh_core_map_info_t *b)
|
||||
{
|
||||
int path_cmp = strcmp(a->pathname, b->pathname);
|
||||
if (path_cmp == 0) {
|
||||
return a->inode - b->inode;
|
||||
} else {
|
||||
return path_cmp;
|
||||
}
|
||||
}
|
||||
typedef RB_HEAD(xh_core_map_info_tree, xh_core_map_info) xh_core_map_info_tree_t;
|
||||
RB_GENERATE_STATIC(xh_core_map_info_tree, xh_core_map_info, link, xh_core_map_info_cmp)
|
||||
|
||||
//signal handler for SIGSEGV
|
||||
//for xh_elf_init(), xh_elf_hook(), xh_elf_check_elfheader()
|
||||
static int xh_core_sigsegv_enable = 1; //enable by default
|
||||
static struct sigaction xh_core_sigsegv_act_old;
|
||||
static volatile int xh_core_sigsegv_flag = 0;
|
||||
static sigjmp_buf xh_core_sigsegv_env;
|
||||
static void xh_core_sigsegv_handler(int sig)
|
||||
{
|
||||
(void)sig;
|
||||
|
||||
if(xh_core_sigsegv_flag)
|
||||
siglongjmp(xh_core_sigsegv_env, 1);
|
||||
else
|
||||
sigaction(SIGSEGV, &xh_core_sigsegv_act_old, NULL);
|
||||
}
|
||||
static int xh_core_add_sigsegv_handler()
|
||||
{
|
||||
struct sigaction act;
|
||||
|
||||
if(!xh_core_sigsegv_enable) return 0;
|
||||
|
||||
if(0 != sigemptyset(&act.sa_mask)) return (0 == errno ? XH_ERRNO_UNKNOWN : errno);
|
||||
act.sa_handler = xh_core_sigsegv_handler;
|
||||
|
||||
if(0 != sigaction(SIGSEGV, &act, &xh_core_sigsegv_act_old))
|
||||
return (0 == errno ? XH_ERRNO_UNKNOWN : errno);
|
||||
|
||||
return 0;
|
||||
}
|
||||
static void xh_core_del_sigsegv_handler()
|
||||
{
|
||||
if(!xh_core_sigsegv_enable) return;
|
||||
|
||||
sigaction(SIGSEGV, &xh_core_sigsegv_act_old, NULL);
|
||||
}
|
||||
|
||||
|
||||
static xh_core_hook_info_queue_t xh_core_hook_info = TAILQ_HEAD_INITIALIZER(xh_core_hook_info);
|
||||
static xh_core_ignore_info_queue_t xh_core_ignore_info = TAILQ_HEAD_INITIALIZER(xh_core_ignore_info);
|
||||
static xh_core_map_info_tree_t xh_core_map_info = RB_INITIALIZER(&xh_core_map_info);
|
||||
static pthread_mutex_t xh_core_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
static pthread_cond_t xh_core_cond = PTHREAD_COND_INITIALIZER;
|
||||
static volatile int xh_core_inited = 0;
|
||||
static volatile int xh_core_init_ok = 0;
|
||||
static volatile int xh_core_async_inited = 0;
|
||||
static volatile int xh_core_async_init_ok = 0;
|
||||
static pthread_mutex_t xh_core_refresh_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
static pthread_t xh_core_refresh_thread_tid;
|
||||
static volatile int xh_core_refresh_thread_running = 0;
|
||||
static volatile int xh_core_refresh_thread_do = 0;
|
||||
|
||||
|
||||
int xh_core_register(const char *pathname_regex_str, const char *symbol,
|
||||
void *new_func, void **old_func)
|
||||
{
|
||||
xh_core_hook_info_t *hi;
|
||||
regex_t regex;
|
||||
|
||||
if(NULL == pathname_regex_str || NULL == symbol || NULL == new_func) return XH_ERRNO_INVAL;
|
||||
|
||||
if(xh_core_inited)
|
||||
{
|
||||
XH_LOG_ERROR("do not register hook after refresh(): %s, %s", pathname_regex_str, symbol);
|
||||
return XH_ERRNO_INVAL;
|
||||
}
|
||||
|
||||
if(0 != regcomp(®ex, pathname_regex_str, REG_NOSUB)) return XH_ERRNO_INVAL;
|
||||
|
||||
if(NULL == (hi = malloc(sizeof(xh_core_hook_info_t)))) return XH_ERRNO_NOMEM;
|
||||
if(NULL == (hi->symbol = strdup(symbol)))
|
||||
{
|
||||
free(hi);
|
||||
return XH_ERRNO_NOMEM;
|
||||
}
|
||||
#if XH_CORE_DEBUG
|
||||
if(NULL == (hi->pathname_regex_str = strdup(pathname_regex_str)))
|
||||
{
|
||||
free(hi->symbol);
|
||||
free(hi);
|
||||
return XH_ERRNO_NOMEM;
|
||||
}
|
||||
#endif
|
||||
hi->pathname_regex = regex;
|
||||
hi->new_func = new_func;
|
||||
hi->old_func = old_func;
|
||||
|
||||
pthread_mutex_lock(&xh_core_mutex);
|
||||
TAILQ_INSERT_TAIL(&xh_core_hook_info, hi, link);
|
||||
pthread_mutex_unlock(&xh_core_mutex);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int xh_core_ignore(const char *pathname_regex_str, const char *symbol)
|
||||
{
|
||||
xh_core_ignore_info_t *ii;
|
||||
regex_t regex;
|
||||
|
||||
if(NULL == pathname_regex_str) return XH_ERRNO_INVAL;
|
||||
|
||||
if(xh_core_inited)
|
||||
{
|
||||
XH_LOG_ERROR("do not ignore hook after refresh(): %s, %s", pathname_regex_str, symbol ? symbol : "ALL");
|
||||
return XH_ERRNO_INVAL;
|
||||
}
|
||||
|
||||
if(0 != regcomp(®ex, pathname_regex_str, REG_NOSUB)) return XH_ERRNO_INVAL;
|
||||
|
||||
if(NULL == (ii = malloc(sizeof(xh_core_ignore_info_t)))) return XH_ERRNO_NOMEM;
|
||||
if(NULL != symbol)
|
||||
{
|
||||
if(NULL == (ii->symbol = strdup(symbol)))
|
||||
{
|
||||
free(ii);
|
||||
return XH_ERRNO_NOMEM;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ii->symbol = NULL; //ignore all symbols
|
||||
}
|
||||
#if XH_CORE_DEBUG
|
||||
if(NULL == (ii->pathname_regex_str = strdup(pathname_regex_str)))
|
||||
{
|
||||
free(ii->symbol);
|
||||
free(ii);
|
||||
return XH_ERRNO_NOMEM;
|
||||
}
|
||||
#endif
|
||||
ii->pathname_regex = regex;
|
||||
|
||||
pthread_mutex_lock(&xh_core_mutex);
|
||||
TAILQ_INSERT_TAIL(&xh_core_ignore_info, ii, link);
|
||||
pthread_mutex_unlock(&xh_core_mutex);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int xh_core_check_elf_header(uintptr_t base_addr, const char *pathname)
|
||||
{
|
||||
if(!xh_core_sigsegv_enable)
|
||||
{
|
||||
return xh_elf_check_elfheader(base_addr);
|
||||
}
|
||||
else
|
||||
{
|
||||
int ret = XH_ERRNO_UNKNOWN;
|
||||
|
||||
xh_core_sigsegv_flag = 1;
|
||||
if(0 == sigsetjmp(xh_core_sigsegv_env, 1))
|
||||
{
|
||||
ret = xh_elf_check_elfheader(base_addr);
|
||||
}
|
||||
else
|
||||
{
|
||||
ret = XH_ERRNO_SEGVERR;
|
||||
XH_LOG_WARN("catch SIGSEGV when check_elfheader: %s", pathname);
|
||||
}
|
||||
xh_core_sigsegv_flag = 0;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
static void xh_core_hook_impl(xh_core_map_info_t *mi)
|
||||
{
|
||||
//init
|
||||
if(0 != xh_elf_init(&(mi->elf), mi->base_addr, mi->pathname)) return;
|
||||
|
||||
//hook
|
||||
xh_core_hook_info_t *hi;
|
||||
xh_core_ignore_info_t *ii;
|
||||
int ignore;
|
||||
TAILQ_FOREACH(hi, &xh_core_hook_info, link) //find hook info
|
||||
{
|
||||
if(0 == regexec(&(hi->pathname_regex), mi->pathname, 0, NULL, 0))
|
||||
{
|
||||
ignore = 0;
|
||||
TAILQ_FOREACH(ii, &xh_core_ignore_info, link) //find ignore info
|
||||
{
|
||||
if(0 == regexec(&(ii->pathname_regex), mi->pathname, 0, NULL, 0))
|
||||
{
|
||||
if(NULL == ii->symbol) //ignore all symbols
|
||||
return;
|
||||
|
||||
if(0 == strcmp(ii->symbol, hi->symbol)) //ignore the current symbol
|
||||
{
|
||||
ignore = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(0 == ignore)
|
||||
xh_elf_hook(&(mi->elf), hi->symbol, hi->new_func, hi->old_func);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void xh_core_hook(xh_core_map_info_t *mi)
|
||||
{
|
||||
if(!xh_core_sigsegv_enable)
|
||||
{
|
||||
xh_core_hook_impl(mi);
|
||||
}
|
||||
else
|
||||
{
|
||||
xh_core_sigsegv_flag = 1;
|
||||
if(0 == sigsetjmp(xh_core_sigsegv_env, 1))
|
||||
{
|
||||
xh_core_hook_impl(mi);
|
||||
}
|
||||
else
|
||||
{
|
||||
XH_LOG_WARN("catch SIGSEGV when init or hook: %s %lu", mi->pathname, mi->inode);
|
||||
}
|
||||
xh_core_sigsegv_flag = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void xh_core_refresh_impl()
|
||||
{
|
||||
char line[512];
|
||||
FILE *fp;
|
||||
uintptr_t base_addr;
|
||||
char perm[5];
|
||||
unsigned long offset;
|
||||
unsigned long inode;
|
||||
int pathname_pos;
|
||||
char *pathname;
|
||||
size_t pathname_len;
|
||||
xh_core_map_info_t *mi, *mi_tmp;
|
||||
xh_core_map_info_t mi_key;
|
||||
xh_core_hook_info_t *hi;
|
||||
xh_core_ignore_info_t *ii;
|
||||
int match;
|
||||
xh_core_map_info_tree_t map_info_refreshed = RB_INITIALIZER(&map_info_refreshed);
|
||||
|
||||
if(NULL == (fp = fopen("/proc/self/maps", "r")))
|
||||
{
|
||||
XH_LOG_ERROR("fopen /proc/self/maps failed");
|
||||
return;
|
||||
}
|
||||
|
||||
while(fgets(line, sizeof(line), fp))
|
||||
{
|
||||
if(sscanf(line, "%"PRIxPTR"-%*lx %4s %lx %*x:%*x %lu %n", &base_addr, perm, &offset, &inode, &pathname_pos) != 4) continue;
|
||||
|
||||
//check permission
|
||||
if(perm[0] != 'r') continue;
|
||||
if(perm[3] != 'p') continue; //do not touch the shared memory
|
||||
|
||||
//check offset
|
||||
//
|
||||
//We are trying to find ELF header in memory.
|
||||
//It can only be found at the beginning of a mapped memory regions
|
||||
//whose offset is 0.
|
||||
if(0 != offset) continue;
|
||||
|
||||
//get pathname
|
||||
while(isspace(line[pathname_pos]) && pathname_pos < (int)(sizeof(line) - 1))
|
||||
pathname_pos += 1;
|
||||
if(pathname_pos >= (int)(sizeof(line) - 1)) continue;
|
||||
pathname = line + pathname_pos;
|
||||
pathname_len = strlen(pathname);
|
||||
if(0 == pathname_len) continue;
|
||||
if(pathname[pathname_len - 1] == '\n')
|
||||
{
|
||||
pathname[pathname_len - 1] = '\0';
|
||||
pathname_len -= 1;
|
||||
}
|
||||
if(0 == pathname_len) continue;
|
||||
if('[' == pathname[0]) continue;
|
||||
|
||||
//check pathname
|
||||
//if we need to hook this elf?
|
||||
match = 0;
|
||||
TAILQ_FOREACH(hi, &xh_core_hook_info, link) //find hook info
|
||||
{
|
||||
if(0 == regexec(&(hi->pathname_regex), pathname, 0, NULL, 0))
|
||||
{
|
||||
TAILQ_FOREACH(ii, &xh_core_ignore_info, link) //find ignore info
|
||||
{
|
||||
if(0 == regexec(&(ii->pathname_regex), pathname, 0, NULL, 0))
|
||||
{
|
||||
if(NULL == ii->symbol)
|
||||
goto check_finished;
|
||||
|
||||
if(0 == strcmp(ii->symbol, hi->symbol))
|
||||
goto check_continue;
|
||||
}
|
||||
}
|
||||
|
||||
match = 1;
|
||||
check_continue:
|
||||
break;
|
||||
}
|
||||
}
|
||||
check_finished:
|
||||
if(0 == match) continue;
|
||||
|
||||
//check elf header format
|
||||
//We are trying to do ELF header checking as late as possible.
|
||||
if(0 != xh_core_check_elf_header(base_addr, pathname)) continue;
|
||||
|
||||
//check existed map item
|
||||
mi_key.pathname = pathname;
|
||||
mi_key.inode = inode;
|
||||
if(NULL != (mi = RB_FIND(xh_core_map_info_tree, &xh_core_map_info, &mi_key)))
|
||||
{
|
||||
//exist
|
||||
RB_REMOVE(xh_core_map_info_tree, &xh_core_map_info, mi);
|
||||
|
||||
//repeated?
|
||||
//We only keep the first one, that is the real base address
|
||||
if(NULL != RB_INSERT(xh_core_map_info_tree, &map_info_refreshed, mi))
|
||||
{
|
||||
#if XH_CORE_DEBUG
|
||||
XH_LOG_DEBUG("repeated map info when update: %s", line);
|
||||
#endif
|
||||
free(mi->pathname);
|
||||
free(mi);
|
||||
continue;
|
||||
}
|
||||
|
||||
//re-hook if base_addr changed
|
||||
if(mi->base_addr != base_addr)
|
||||
{
|
||||
mi->base_addr = base_addr;
|
||||
xh_core_hook(mi);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//not exist, create a new map info
|
||||
if(NULL == (mi = (xh_core_map_info_t *)malloc(sizeof(xh_core_map_info_t)))) continue;
|
||||
if(NULL == (mi->pathname = strdup(pathname)))
|
||||
{
|
||||
free(mi);
|
||||
continue;
|
||||
}
|
||||
mi->inode = inode;
|
||||
mi->base_addr = base_addr;
|
||||
|
||||
//repeated?
|
||||
//We only keep the first one, that is the real base address
|
||||
if(NULL != RB_INSERT(xh_core_map_info_tree, &map_info_refreshed, mi))
|
||||
{
|
||||
XH_LOG_WARN("repeated map info when create: %s", line);
|
||||
free(mi->pathname);
|
||||
free(mi);
|
||||
continue;
|
||||
}
|
||||
|
||||
//hook
|
||||
xh_core_hook(mi); //hook
|
||||
}
|
||||
}
|
||||
fclose(fp);
|
||||
|
||||
//free all missing map item, maybe dlclosed?
|
||||
RB_FOREACH_SAFE(mi, xh_core_map_info_tree, &xh_core_map_info, mi_tmp)
|
||||
{
|
||||
#if XH_CORE_DEBUG
|
||||
XH_LOG_DEBUG("remove missing map info: %s", mi->pathname);
|
||||
#endif
|
||||
RB_REMOVE(xh_core_map_info_tree, &xh_core_map_info, mi);
|
||||
if(mi->pathname) free(mi->pathname);
|
||||
free(mi);
|
||||
}
|
||||
|
||||
//save the new refreshed map info tree
|
||||
xh_core_map_info = map_info_refreshed;
|
||||
|
||||
XH_LOG_INFO("map refreshed");
|
||||
|
||||
#if XH_CORE_DEBUG
|
||||
RB_FOREACH(mi, xh_core_map_info_tree, &xh_core_map_info)
|
||||
XH_LOG_DEBUG(" %"PRIxPTR" %s\n", mi->base_addr, mi->pathname);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void *xh_core_refresh_thread_func(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
|
||||
pthread_setname_np(pthread_self(), "xh_refresh_loop");
|
||||
|
||||
while(xh_core_refresh_thread_running)
|
||||
{
|
||||
//waiting for a refresh task or exit
|
||||
pthread_mutex_lock(&xh_core_mutex);
|
||||
while(!xh_core_refresh_thread_do && xh_core_refresh_thread_running)
|
||||
{
|
||||
pthread_cond_wait(&xh_core_cond, &xh_core_mutex);
|
||||
}
|
||||
if(!xh_core_refresh_thread_running)
|
||||
{
|
||||
pthread_mutex_unlock(&xh_core_mutex);
|
||||
break;
|
||||
}
|
||||
xh_core_refresh_thread_do = 0;
|
||||
pthread_mutex_unlock(&xh_core_mutex);
|
||||
|
||||
//refresh
|
||||
pthread_mutex_lock(&xh_core_refresh_mutex);
|
||||
xh_core_refresh_impl();
|
||||
pthread_mutex_unlock(&xh_core_refresh_mutex);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void xh_core_init_once()
|
||||
{
|
||||
if(xh_core_inited) return;
|
||||
|
||||
pthread_mutex_lock(&xh_core_mutex);
|
||||
|
||||
if(xh_core_inited) goto end;
|
||||
|
||||
xh_core_inited = 1;
|
||||
|
||||
//dump debug info
|
||||
XH_LOG_INFO("%s\n", xh_version_str_full());
|
||||
#if XH_CORE_DEBUG
|
||||
xh_core_hook_info_t *hi;
|
||||
TAILQ_FOREACH(hi, &xh_core_hook_info, link)
|
||||
XH_LOG_INFO(" hook: %s @ %s, (%p, %p)\n", hi->symbol, hi->pathname_regex_str,
|
||||
hi->new_func, hi->old_func);
|
||||
xh_core_ignore_info_t *ii;
|
||||
TAILQ_FOREACH(ii, &xh_core_ignore_info, link)
|
||||
XH_LOG_INFO(" ignore: %s @ %s\n", ii->symbol ? ii->symbol : "ALL ",
|
||||
ii->pathname_regex_str);
|
||||
#endif
|
||||
|
||||
//register signal handler
|
||||
if(0 != xh_core_add_sigsegv_handler()) goto end;
|
||||
|
||||
//OK
|
||||
xh_core_init_ok = 1;
|
||||
|
||||
end:
|
||||
pthread_mutex_unlock(&xh_core_mutex);
|
||||
}
|
||||
|
||||
static void xh_core_init_async_once()
|
||||
{
|
||||
if(xh_core_async_inited) return;
|
||||
|
||||
pthread_mutex_lock(&xh_core_mutex);
|
||||
|
||||
if(xh_core_async_inited) goto end;
|
||||
|
||||
xh_core_async_inited = 1;
|
||||
|
||||
//create async refresh thread
|
||||
xh_core_refresh_thread_running = 1;
|
||||
if(0 != pthread_create(&xh_core_refresh_thread_tid, NULL, &xh_core_refresh_thread_func, NULL))
|
||||
{
|
||||
xh_core_refresh_thread_running = 0;
|
||||
goto end;
|
||||
}
|
||||
|
||||
//OK
|
||||
xh_core_async_init_ok = 1;
|
||||
|
||||
end:
|
||||
pthread_mutex_unlock(&xh_core_mutex);
|
||||
}
|
||||
|
||||
int xh_core_refresh(int async)
|
||||
{
|
||||
//init
|
||||
xh_core_init_once();
|
||||
if(!xh_core_init_ok) return XH_ERRNO_UNKNOWN;
|
||||
|
||||
if(async)
|
||||
{
|
||||
//init for async
|
||||
xh_core_init_async_once();
|
||||
if(!xh_core_async_init_ok) return XH_ERRNO_UNKNOWN;
|
||||
|
||||
//refresh async
|
||||
pthread_mutex_lock(&xh_core_mutex);
|
||||
xh_core_refresh_thread_do = 1;
|
||||
pthread_cond_signal(&xh_core_cond);
|
||||
pthread_mutex_unlock(&xh_core_mutex);
|
||||
}
|
||||
else
|
||||
{
|
||||
//refresh sync
|
||||
pthread_mutex_lock(&xh_core_refresh_mutex);
|
||||
xh_core_refresh_impl();
|
||||
pthread_mutex_unlock(&xh_core_refresh_mutex);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void xh_core_clear()
|
||||
{
|
||||
//stop the async refresh thread
|
||||
if(xh_core_async_init_ok)
|
||||
{
|
||||
pthread_mutex_lock(&xh_core_mutex);
|
||||
xh_core_refresh_thread_running = 0;
|
||||
pthread_cond_signal(&xh_core_cond);
|
||||
pthread_mutex_unlock(&xh_core_mutex);
|
||||
|
||||
pthread_join(xh_core_refresh_thread_tid, NULL);
|
||||
xh_core_async_init_ok = 0;
|
||||
}
|
||||
xh_core_async_inited = 0;
|
||||
|
||||
//unregister the sig handler
|
||||
if(xh_core_init_ok)
|
||||
{
|
||||
xh_core_del_sigsegv_handler();
|
||||
xh_core_init_ok = 0;
|
||||
}
|
||||
xh_core_inited = 0;
|
||||
|
||||
pthread_mutex_lock(&xh_core_mutex);
|
||||
pthread_mutex_lock(&xh_core_refresh_mutex);
|
||||
|
||||
//free all map info
|
||||
xh_core_map_info_t *mi, *mi_tmp;
|
||||
RB_FOREACH_SAFE(mi, xh_core_map_info_tree, &xh_core_map_info, mi_tmp)
|
||||
{
|
||||
RB_REMOVE(xh_core_map_info_tree, &xh_core_map_info, mi);
|
||||
if(mi->pathname) free(mi->pathname);
|
||||
free(mi);
|
||||
}
|
||||
|
||||
//free all hook info
|
||||
xh_core_hook_info_t *hi, *hi_tmp;
|
||||
TAILQ_FOREACH_SAFE(hi, &xh_core_hook_info, link, hi_tmp)
|
||||
{
|
||||
TAILQ_REMOVE(&xh_core_hook_info, hi, link);
|
||||
#if XH_CORE_DEBUG
|
||||
free(hi->pathname_regex_str);
|
||||
#endif
|
||||
regfree(&(hi->pathname_regex));
|
||||
free(hi->symbol);
|
||||
free(hi);
|
||||
}
|
||||
|
||||
//free all ignore info
|
||||
xh_core_ignore_info_t *ii, *ii_tmp;
|
||||
TAILQ_FOREACH_SAFE(ii, &xh_core_ignore_info, link, ii_tmp)
|
||||
{
|
||||
TAILQ_REMOVE(&xh_core_ignore_info, ii, link);
|
||||
#if XH_CORE_DEBUG
|
||||
free(ii->pathname_regex_str);
|
||||
#endif
|
||||
regfree(&(ii->pathname_regex));
|
||||
free(ii->symbol);
|
||||
free(ii);
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&xh_core_refresh_mutex);
|
||||
pthread_mutex_unlock(&xh_core_mutex);
|
||||
}
|
||||
|
||||
void xh_core_enable_debug(int flag)
|
||||
{
|
||||
xh_log_priority = (flag ? ANDROID_LOG_DEBUG : ANDROID_LOG_WARN);
|
||||
}
|
||||
|
||||
void xh_core_enable_sigsegv_protection(int flag)
|
||||
{
|
||||
xh_core_sigsegv_enable = (flag ? 1 : 0);
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
// Copyright (c) 2018-present, iQIYI, Inc. All rights reserved.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
// Created by caikelun on 2018-04-11.
|
||||
|
||||
#ifndef XH_CORE_H
|
||||
#define XH_CORE_H 1
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
int xh_core_register(const char *pathname_regex_str, const char *symbol,
|
||||
void *new_func, void **old_func);
|
||||
|
||||
int xh_core_ignore(const char *pathname_regex_str, const char *symbol);
|
||||
|
||||
int xh_core_refresh(int async);
|
||||
|
||||
void xh_core_clear();
|
||||
|
||||
void xh_core_enable_debug(int flag);
|
||||
|
||||
void xh_core_enable_sigsegv_protection(int flag);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
+1046
File diff suppressed because it is too large
Load Diff
+85
@@ -0,0 +1,85 @@
|
||||
// Copyright (c) 2018-present, iQIYI, Inc. All rights reserved.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
// Created by caikelun on 2018-04-11.
|
||||
|
||||
#ifndef XH_ELF_H
|
||||
#define XH_ELF_H 1
|
||||
|
||||
#include <stdint.h>
|
||||
#include <elf.h>
|
||||
#include <link.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct
|
||||
{
|
||||
const char *pathname;
|
||||
|
||||
ElfW(Addr) base_addr;
|
||||
ElfW(Addr) bias_addr;
|
||||
|
||||
ElfW(Ehdr) *ehdr;
|
||||
ElfW(Phdr) *phdr;
|
||||
|
||||
ElfW(Dyn) *dyn; //.dynamic
|
||||
ElfW(Word) dyn_sz;
|
||||
|
||||
const char *strtab; //.dynstr (string-table)
|
||||
ElfW(Sym) *symtab; //.dynsym (symbol-index to string-table's offset)
|
||||
|
||||
ElfW(Addr) relplt; //.rel.plt or .rela.plt
|
||||
ElfW(Word) relplt_sz;
|
||||
|
||||
ElfW(Addr) reldyn; //.rel.dyn or .rela.dyn
|
||||
ElfW(Word) reldyn_sz;
|
||||
|
||||
ElfW(Addr) relandroid; //android compressed rel or rela
|
||||
ElfW(Word) relandroid_sz;
|
||||
|
||||
//for ELF hash
|
||||
uint32_t *bucket;
|
||||
uint32_t bucket_cnt;
|
||||
uint32_t *chain;
|
||||
uint32_t chain_cnt; //invalid for GNU hash
|
||||
|
||||
//append for GNU hash
|
||||
uint32_t symoffset;
|
||||
ElfW(Addr) *bloom;
|
||||
uint32_t bloom_sz;
|
||||
uint32_t bloom_shift;
|
||||
|
||||
int is_use_rela;
|
||||
int is_use_gnu_hash;
|
||||
} xh_elf_t;
|
||||
|
||||
int xh_elf_init(xh_elf_t *self, uintptr_t base_addr, const char *pathname);
|
||||
int xh_elf_hook(xh_elf_t *self, const char *symbol, void *new_func, void **old_func);
|
||||
|
||||
int xh_elf_check_elfheader(uintptr_t base_addr);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
// Copyright (c) 2018-present, iQIYI, Inc. All rights reserved.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
// Created by caikelun on 2018-04-11.
|
||||
|
||||
#ifndef XH_ERRNO_H
|
||||
#define XH_ERRNO_H 1
|
||||
|
||||
#define XH_ERRNO_UNKNOWN 1001
|
||||
#define XH_ERRNO_INVAL 1002
|
||||
#define XH_ERRNO_NOMEM 1003
|
||||
#define XH_ERRNO_REPEAT 1004
|
||||
#define XH_ERRNO_NOTFND 1005
|
||||
#define XH_ERRNO_BADMAPS 1006
|
||||
#define XH_ERRNO_FORMAT 1007
|
||||
#define XH_ERRNO_ELFINIT 1008
|
||||
#define XH_ERRNO_SEGVERR 1009
|
||||
|
||||
#endif
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
// Copyright (c) 2018-present, iQIYI, Inc. All rights reserved.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
// Created by caikelun on 2018-04-11.
|
||||
|
||||
#include <jni.h>
|
||||
#include "xhook.h"
|
||||
|
||||
#define JNI_API_DEF(f) Java_com_qiyi_xhook_NativeHandler_##f
|
||||
|
||||
JNIEXPORT jint JNI_API_DEF(refresh)(JNIEnv *env, jobject obj, jboolean async)
|
||||
{
|
||||
(void)env;
|
||||
(void)obj;
|
||||
|
||||
return xhook_refresh(async ? 1 : 0);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNI_API_DEF(clear)(JNIEnv *env, jobject obj)
|
||||
{
|
||||
(void)env;
|
||||
(void)obj;
|
||||
|
||||
xhook_clear();
|
||||
}
|
||||
|
||||
JNIEXPORT void JNI_API_DEF(enableDebug)(JNIEnv *env, jobject obj, jboolean flag)
|
||||
{
|
||||
(void)env;
|
||||
(void)obj;
|
||||
|
||||
xhook_enable_debug(flag ? 1 : 0);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNI_API_DEF(enableSigSegvProtection)(JNIEnv *env, jobject obj, jboolean flag)
|
||||
{
|
||||
(void)env;
|
||||
(void)obj;
|
||||
|
||||
xhook_enable_sigsegv_protection(flag ? 1 : 0);
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
// Copyright (c) 2018-present, iQIYI, Inc. All rights reserved.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
// Created by caikelun on 2018-04-11.
|
||||
|
||||
#include <android/log.h>
|
||||
#include "xh_log.h"
|
||||
|
||||
android_LogPriority xh_log_priority = ANDROID_LOG_WARN;
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
// Copyright (c) 2018-present, iQIYI, Inc. All rights reserved.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
// Created by caikelun on 2018-04-11.
|
||||
|
||||
#ifndef XH_LOG_H
|
||||
#define XH_LOG_H 1
|
||||
|
||||
#include <android/log.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
extern android_LogPriority xh_log_priority;
|
||||
|
||||
#define XH_LOG_TAG "xhook"
|
||||
#define XH_LOG_DEBUG(fmt, ...) do{if(xh_log_priority <= ANDROID_LOG_DEBUG) __android_log_print(ANDROID_LOG_DEBUG, XH_LOG_TAG, fmt, ##__VA_ARGS__);}while(0)
|
||||
#define XH_LOG_INFO(fmt, ...) do{if(xh_log_priority <= ANDROID_LOG_INFO) __android_log_print(ANDROID_LOG_INFO, XH_LOG_TAG, fmt, ##__VA_ARGS__);}while(0)
|
||||
#define XH_LOG_WARN(fmt, ...) do{if(xh_log_priority <= ANDROID_LOG_WARN) __android_log_print(ANDROID_LOG_WARN, XH_LOG_TAG, fmt, ##__VA_ARGS__);}while(0)
|
||||
#define XH_LOG_ERROR(fmt, ...) do{if(xh_log_priority <= ANDROID_LOG_ERROR) __android_log_print(ANDROID_LOG_ERROR, XH_LOG_TAG, fmt, ##__VA_ARGS__);}while(0)
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
// Copyright (c) 2018-present, iQIYI, Inc. All rights reserved.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
// Created by caikelun on 2018-04-11.
|
||||
|
||||
#include <unistd.h>
|
||||
#include <stdint.h>
|
||||
#include <inttypes.h>
|
||||
#include <elf.h>
|
||||
#include <link.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/syscall.h>
|
||||
#include "xh_util.h"
|
||||
#include "xh_errno.h"
|
||||
#include "xh_log.h"
|
||||
|
||||
#define PAGE_START(addr) ((addr) & PAGE_MASK)
|
||||
#define PAGE_END(addr) (PAGE_START(addr + sizeof(uintptr_t) - 1) + PAGE_SIZE)
|
||||
#define PAGE_COVER(addr) (PAGE_END(addr) - PAGE_START(addr))
|
||||
|
||||
int xh_util_get_mem_protect(uintptr_t addr, size_t len, const char *pathname, unsigned int *prot)
|
||||
{
|
||||
uintptr_t start_addr = addr;
|
||||
uintptr_t end_addr = addr + len;
|
||||
FILE *fp;
|
||||
char line[512];
|
||||
uintptr_t start, end;
|
||||
char perm[5];
|
||||
int load0 = 1;
|
||||
int found_all = 0;
|
||||
|
||||
*prot = 0;
|
||||
|
||||
if(NULL == (fp = fopen("/proc/self/maps", "r"))) return XH_ERRNO_BADMAPS;
|
||||
|
||||
while(fgets(line, sizeof(line), fp))
|
||||
{
|
||||
if(NULL != pathname)
|
||||
if(NULL == strstr(line, pathname)) continue;
|
||||
|
||||
if(sscanf(line, "%"PRIxPTR"-%"PRIxPTR" %4s ", &start, &end, perm) != 3) continue;
|
||||
|
||||
if(perm[3] != 'p') continue;
|
||||
|
||||
if(start_addr >= start && start_addr < end)
|
||||
{
|
||||
if(load0)
|
||||
{
|
||||
//first load segment
|
||||
if(perm[0] == 'r') *prot |= PROT_READ;
|
||||
if(perm[1] == 'w') *prot |= PROT_WRITE;
|
||||
if(perm[2] == 'x') *prot |= PROT_EXEC;
|
||||
load0 = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
//others
|
||||
if(perm[0] != 'r') *prot &= ~PROT_READ;
|
||||
if(perm[1] != 'w') *prot &= ~PROT_WRITE;
|
||||
if(perm[2] != 'x') *prot &= ~PROT_EXEC;
|
||||
}
|
||||
|
||||
if(end_addr <= end)
|
||||
{
|
||||
found_all = 1;
|
||||
break; //finished
|
||||
}
|
||||
else
|
||||
{
|
||||
start_addr = end; //try to find the next load segment
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
|
||||
if(!found_all) return XH_ERRNO_SEGVERR;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int xh_util_get_addr_protect(uintptr_t addr, const char *pathname, unsigned int *prot)
|
||||
{
|
||||
return xh_util_get_mem_protect(addr, sizeof(addr), pathname, prot);
|
||||
}
|
||||
|
||||
int xh_util_set_addr_protect(uintptr_t addr, unsigned int prot)
|
||||
{
|
||||
if(0 != mprotect((void *)PAGE_START(addr), PAGE_COVER(addr), (int)prot))
|
||||
return 0 == errno ? XH_ERRNO_UNKNOWN : errno;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void xh_util_flush_instruction_cache(uintptr_t addr)
|
||||
{
|
||||
__builtin___clear_cache((void *)PAGE_START(addr), (void *)PAGE_END(addr));
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
// Copyright (c) 2018-present, iQIYI, Inc. All rights reserved.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
// Created by caikelun on 2018-04-11.
|
||||
|
||||
#ifndef XH_UTILS_H
|
||||
#define XH_UTILS_H 1
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#if defined(__LP64__)
|
||||
#define XH_UTIL_FMT_LEN "16"
|
||||
#define XH_UTIL_FMT_X "llx"
|
||||
#else
|
||||
#define XH_UTIL_FMT_LEN "8"
|
||||
#define XH_UTIL_FMT_X "x"
|
||||
#endif
|
||||
|
||||
#define XH_UTIL_FMT_FIXED_X XH_UTIL_FMT_LEN XH_UTIL_FMT_X
|
||||
#define XH_UTIL_FMT_FIXED_S XH_UTIL_FMT_LEN "s"
|
||||
|
||||
int xh_util_get_mem_protect(uintptr_t addr, size_t len, const char *pathname, unsigned int *prot);
|
||||
int xh_util_get_addr_protect(uintptr_t addr, const char *pathname, unsigned int *prot);
|
||||
int xh_util_set_addr_protect(uintptr_t addr, unsigned int prot);
|
||||
void xh_util_flush_instruction_cache(uintptr_t addr);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
// Copyright (c) 2018-present, iQIYI, Inc. All rights reserved.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
// Created by caikelun on 2018-04-11.
|
||||
|
||||
#include "xh_version.h"
|
||||
|
||||
#define XH_VERSION_MAJOR 1
|
||||
#define XH_VERSION_MINOR 2
|
||||
#define XH_VERSION_EXTRA 0
|
||||
|
||||
#define XH_VERSION ((XH_VERSION_MAJOR << 16) | (XH_VERSION_MINOR << 8) | (XH_VERSION_EXTRA))
|
||||
|
||||
#define XH_VERSION_TO_STR_HELPER(x) #x
|
||||
#define XH_VERSION_TO_STR(x) XH_VERSION_TO_STR_HELPER(x)
|
||||
|
||||
#define XH_VERSION_STR XH_VERSION_TO_STR(XH_VERSION_MAJOR) "." \
|
||||
XH_VERSION_TO_STR(XH_VERSION_MINOR) "." \
|
||||
XH_VERSION_TO_STR(XH_VERSION_EXTRA)
|
||||
|
||||
#if defined(__arm__)
|
||||
#define XH_VERSION_ARCH "arm"
|
||||
#elif defined(__aarch64__)
|
||||
#define XH_VERSION_ARCH "aarch64"
|
||||
#elif defined(__i386__)
|
||||
#define XH_VERSION_ARCH "x86"
|
||||
#elif defined(__x86_64__)
|
||||
#define XH_VERSION_ARCH "x86_64"
|
||||
#else
|
||||
#define XH_VERSION_ARCH "unknown"
|
||||
#endif
|
||||
|
||||
#define XH_VERSION_STR_FULL "libxhook "XH_VERSION_STR" ("XH_VERSION_ARCH")"
|
||||
|
||||
unsigned int xh_version()
|
||||
{
|
||||
return XH_VERSION;
|
||||
}
|
||||
|
||||
const char *xh_version_str()
|
||||
{
|
||||
return XH_VERSION_STR;
|
||||
}
|
||||
|
||||
const char *xh_version_str_full()
|
||||
{
|
||||
return XH_VERSION_STR_FULL;
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
// Copyright (c) 2018-present, iQIYI, Inc. All rights reserved.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
// Created by caikelun on 2018-04-11.
|
||||
|
||||
#ifndef XH_VERSION_H
|
||||
#define XH_VERSION_H 1
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
unsigned int xh_version();
|
||||
|
||||
const char *xh_version_str();
|
||||
|
||||
const char *xh_version_str_full();
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
Vendored
+56
@@ -0,0 +1,56 @@
|
||||
// Copyright (c) 2018-present, iQIYI, Inc. All rights reserved.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
// Created by caikelun on 2018-04-11.
|
||||
|
||||
#include "xh_core.h"
|
||||
#include "xhook.h"
|
||||
|
||||
int xhook_register(const char *pathname_regex_str, const char *symbol,
|
||||
void *new_func, void **old_func)
|
||||
{
|
||||
return xh_core_register(pathname_regex_str, symbol, new_func, old_func);
|
||||
}
|
||||
|
||||
int xhook_ignore(const char *pathname_regex_str, const char *symbol)
|
||||
{
|
||||
return xh_core_ignore(pathname_regex_str, symbol);
|
||||
}
|
||||
|
||||
int xhook_refresh(int async)
|
||||
{
|
||||
return xh_core_refresh(async);
|
||||
}
|
||||
|
||||
void xhook_clear()
|
||||
{
|
||||
return xh_core_clear();
|
||||
}
|
||||
|
||||
void xhook_enable_debug(int flag)
|
||||
{
|
||||
return xh_core_enable_debug(flag);
|
||||
}
|
||||
|
||||
void xhook_enable_sigsegv_protection(int flag)
|
||||
{
|
||||
return xh_core_enable_sigsegv_protection(flag);
|
||||
}
|
||||
Vendored
+50
@@ -0,0 +1,50 @@
|
||||
// Copyright (c) 2018-present, iQIYI, Inc. All rights reserved.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
// Created by caikelun on 2018-04-11.
|
||||
|
||||
#ifndef XHOOK_H
|
||||
#define XHOOK_H 1
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define XHOOK_EXPORT __attribute__((visibility("default")))
|
||||
|
||||
int xhook_register(const char *pathname_regex_str, const char *symbol,
|
||||
void *new_func, void **old_func) XHOOK_EXPORT;
|
||||
|
||||
int xhook_ignore(const char *pathname_regex_str, const char *symbol) XHOOK_EXPORT;
|
||||
|
||||
int xhook_refresh(int async) XHOOK_EXPORT;
|
||||
|
||||
void xhook_clear() XHOOK_EXPORT;
|
||||
|
||||
void xhook_enable_debug(int flag) XHOOK_EXPORT;
|
||||
|
||||
void xhook_enable_sigsegv_protection(int flag) XHOOK_EXPORT;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
+1
Submodule native/src/external/lz4 added at d44371841a
+1
Submodule native/src/external/mincrypt added at 1f355c50a4
+1
Submodule native/src/external/nanopb added at c9124132a6
+1
Submodule native/src/external/parallel-hashmap added at 7684faf186
+1
Submodule native/src/external/pcre added at 8e12681a1a
+4
@@ -0,0 +1,4 @@
|
||||
// Workaround pcre2_chartables.c symlink to pcre2_chartables.c.dist failing on Windows NDK if Cygwin git used,
|
||||
// and NDK not directly accepting a .c.dist file in LOCAL_SRC_FILES list.
|
||||
|
||||
#include "pcre/src/pcre2_chartables.c.dist"
|
||||
+1
Submodule native/src/external/selinux added at d0f582372b
+19
@@ -0,0 +1,19 @@
|
||||
LOCAL_PATH := $(call my-dir)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE:= libsystemproperties
|
||||
LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
|
||||
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_C_INCLUDES)
|
||||
LOCAL_STATIC_LIBRARIES := libcxx
|
||||
LOCAL_SRC_FILES := \
|
||||
context_node.cpp \
|
||||
contexts_serialized.cpp \
|
||||
contexts_split.cpp \
|
||||
prop_area.cpp \
|
||||
prop_info.cpp \
|
||||
system_properties.cpp \
|
||||
system_property_api.cpp \
|
||||
system_property_set.cpp \
|
||||
property_info_parser.cpp
|
||||
|
||||
include $(BUILD_STATIC_LIBRARY)
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright (C) 2008 The Android Open Source Project
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in
|
||||
* the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
|
||||
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
||||
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
|
||||
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "system_properties/context_node.h"
|
||||
|
||||
#include <limits.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <async_safe/log.h>
|
||||
|
||||
#include "system_properties/system_properties.h"
|
||||
|
||||
// pthread_mutex_lock() calls into system_properties in the case of contention.
|
||||
// This creates a risk of dead lock if any system_properties functions
|
||||
// use pthread locks after system_property initialization.
|
||||
//
|
||||
// For this reason, the below three functions use a bionic Lock and static
|
||||
// allocation of memory for each filename.
|
||||
|
||||
bool ContextNode::Open(bool access_rw, bool* fsetxattr_failed) {
|
||||
lock_.lock();
|
||||
if (pa_) {
|
||||
lock_.unlock();
|
||||
return true;
|
||||
}
|
||||
|
||||
char filename[PROP_FILENAME_MAX];
|
||||
int len = async_safe_format_buffer(filename, sizeof(filename), "%s/%s", filename_, context_);
|
||||
if (len < 0 || len >= PROP_FILENAME_MAX) {
|
||||
lock_.unlock();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (access_rw) {
|
||||
pa_ = prop_area::map_prop_area_rw(filename, context_, fsetxattr_failed);
|
||||
} else {
|
||||
pa_ = prop_area::map_prop_area(filename);
|
||||
}
|
||||
lock_.unlock();
|
||||
return pa_;
|
||||
}
|
||||
|
||||
bool ContextNode::CheckAccessAndOpen() {
|
||||
if (!pa_ && !no_access_) {
|
||||
if (!CheckAccess() || !Open(false, nullptr)) {
|
||||
no_access_ = true;
|
||||
}
|
||||
}
|
||||
return pa_;
|
||||
}
|
||||
|
||||
void ContextNode::ResetAccess() {
|
||||
if (!CheckAccess()) {
|
||||
Unmap();
|
||||
no_access_ = true;
|
||||
} else {
|
||||
no_access_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool ContextNode::CheckAccess() {
|
||||
char filename[PROP_FILENAME_MAX];
|
||||
int len = async_safe_format_buffer(filename, sizeof(filename), "%s/%s", filename_, context_);
|
||||
if (len < 0 || len >= PROP_FILENAME_MAX) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return access(filename, R_OK) == 0;
|
||||
}
|
||||
|
||||
void ContextNode::Unmap() {
|
||||
prop_area::unmap_prop_area(&pa_);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user