Build KernelSU as LKM (#1254)
Co-authored-by: weishu <twsxtd@gmail.com>
This commit is contained in:
3
.github/workflows/build-kernel-a14.yml
vendored
3
.github/workflows/build-kernel-a14.yml
vendored
@@ -132,6 +132,9 @@ jobs:
|
|||||||
- version: "5.15"
|
- version: "5.15"
|
||||||
sub_level: 110
|
sub_level: 110
|
||||||
os_patch_level: 2023-09
|
os_patch_level: 2023-09
|
||||||
|
- version: "6.1"
|
||||||
|
sub_level: 68
|
||||||
|
os_patch_level: 2024-02
|
||||||
uses: ./.github/workflows/gki-kernel.yml
|
uses: ./.github/workflows/gki-kernel.yml
|
||||||
with:
|
with:
|
||||||
version: android14-${{ matrix.version }}
|
version: android14-${{ matrix.version }}
|
||||||
|
|||||||
14
.github/workflows/build-ksud.yml
vendored
14
.github/workflows/build-ksud.yml
vendored
@@ -18,8 +18,20 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- target: aarch64-linux-android
|
- target: aarch64-linux-android
|
||||||
|
os: ubuntu-latest
|
||||||
- target: x86_64-linux-android
|
- target: x86_64-linux-android
|
||||||
- target: x86_64-pc-windows-gnu # only for build
|
os: ubuntu-latest
|
||||||
|
- target: x86_64-pc-windows-gnu # windows pc
|
||||||
|
os: ubuntu-latest
|
||||||
|
- target: x86_64-apple-darwin # Intel mac
|
||||||
|
os: macos-latest
|
||||||
|
- target: aarch64-apple-darwin # M chip mac
|
||||||
|
os: macos-latest
|
||||||
|
- target: aarch64-unknown-linux-musl # arm64 Linux
|
||||||
|
os: ubuntu-latest
|
||||||
|
- target: x86_64-unknown-linux-musl # x86 Linux
|
||||||
|
os: ubuntu-latest
|
||||||
uses: ./.github/workflows/ksud.yml
|
uses: ./.github/workflows/ksud.yml
|
||||||
with:
|
with:
|
||||||
target: ${{ matrix.target }}
|
target: ${{ matrix.target }}
|
||||||
|
os: ${{ matrix.os }}
|
||||||
|
|||||||
42
.github/workflows/build-lkm.yml
vendored
Normal file
42
.github/workflows/build-lkm.yml
vendored
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
name: Build LKM for KernelSU
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: ["main", "ci", "checkci"]
|
||||||
|
paths:
|
||||||
|
- ".github/workflows/gki-kernel.yml"
|
||||||
|
- ".github/workflows/build-lkm.yml"
|
||||||
|
- "kernel/**"
|
||||||
|
pull_request:
|
||||||
|
branches: ["main"]
|
||||||
|
paths:
|
||||||
|
- ".github/workflows/gki-kernel.yml"
|
||||||
|
- ".github/workflows/build-lkm.yml"
|
||||||
|
- "kernel/**"
|
||||||
|
workflow_call:
|
||||||
|
jobs:
|
||||||
|
build-lkm:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- version: "android12-5.10"
|
||||||
|
sub_level: 198
|
||||||
|
os_patch_level: "2024-01"
|
||||||
|
- version: "android13-5.10"
|
||||||
|
sub_level: 198
|
||||||
|
os_patch_level: 2023-12
|
||||||
|
- version: "android13-5.15"
|
||||||
|
sub_level: 137
|
||||||
|
os_patch_level: 2023-12
|
||||||
|
- version: "android14-5.15"
|
||||||
|
sub_level: 110
|
||||||
|
os_patch_level: 2023-09
|
||||||
|
- version: "android14-6.1"
|
||||||
|
sub_level: 43
|
||||||
|
os_patch_level: 2023-11
|
||||||
|
uses: ./.github/workflows/gki-kernel.yml
|
||||||
|
with:
|
||||||
|
version: ${{ matrix.version }}
|
||||||
|
version_name: ${{ matrix.version }}.${{ matrix.sub_level }}
|
||||||
|
tag: ${{ matrix.version }}-${{ matrix.os_patch_level }}
|
||||||
|
os_patch_level: ${{ matrix.os_patch_level }}
|
||||||
|
build_lkm: true
|
||||||
50
.github/workflows/gki-kernel.yml
vendored
50
.github/workflows/gki-kernel.yml
vendored
@@ -29,7 +29,7 @@ on:
|
|||||||
for example: 2021-11
|
for example: 2021-11
|
||||||
default: 2022-05
|
default: 2022-05
|
||||||
patch_path:
|
patch_path:
|
||||||
required: true
|
required: false
|
||||||
type: string
|
type: string
|
||||||
description: >
|
description: >
|
||||||
Directory name of .github/patches/<patch_path>
|
Directory name of .github/patches/<patch_path>
|
||||||
@@ -49,6 +49,10 @@ on:
|
|||||||
required: false
|
required: false
|
||||||
type: boolean
|
type: boolean
|
||||||
default: false
|
default: false
|
||||||
|
build_lkm:
|
||||||
|
required: false
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
secrets:
|
secrets:
|
||||||
BOOT_SIGN_KEY:
|
BOOT_SIGN_KEY:
|
||||||
required: false
|
required: false
|
||||||
@@ -154,6 +158,34 @@ jobs:
|
|||||||
max-size: 2G
|
max-size: 2G
|
||||||
save: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
|
save: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
|
||||||
|
|
||||||
|
- name: Setup for LKM
|
||||||
|
if: ${{ inputs.build_lkm == true }}
|
||||||
|
working-directory: android-kernel
|
||||||
|
run: |
|
||||||
|
pip install ast-grep-cli
|
||||||
|
sudo apt-get install llvm-15 -y
|
||||||
|
ast-grep -U -p '$$$ check_exports($$$) {$$$}' -r '' common/scripts/mod/modpost.c
|
||||||
|
ast-grep -U -p 'check_exports($$$);' -r '' common/scripts/mod/modpost.c
|
||||||
|
sed -i '1i KSU_MODULE := 1' $GITHUB_WORKSPACE/KernelSU/kernel/Makefile
|
||||||
|
echo "drivers/kernelsu/kernelsu.ko" >> common/android/gki_aarch64_modules
|
||||||
|
|
||||||
|
# bazel build, android14-5.15, android14-6.1 use bazel
|
||||||
|
if [ ! -e build/build.sh ]; then
|
||||||
|
sed -i 's/needs unknown symbol/Dont abort when unknown symbol/g' build/kernel/*.sh || echo "No unknown symbol scripts found"
|
||||||
|
if [ -e common/modules.bzl ]; then
|
||||||
|
sed -i 's/_COMMON_GKI_MODULES_LIST = \[/_COMMON_GKI_MODULES_LIST = \[ "drivers\/kernelsu\/kernelsu.ko",/g' common/modules.bzl
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
TARGET_FILE="build/kernel/build.sh"
|
||||||
|
if [ ! -e "$TARGET_FILE" ]; then
|
||||||
|
TARGET_FILE="build/build.sh"
|
||||||
|
fi
|
||||||
|
sed -i 's/needs unknown symbol/Dont abort when unknown symbol/g' $TARGET_FILE || echo "No unknown symbol in $TARGET_FILE"
|
||||||
|
sed -i 's/if ! diff -u "\${KERNEL_DIR}\/\${MODULES_ORDER}" "\${OUT_DIR}\/modules\.order"; then/if false; then/g' $TARGET_FILE
|
||||||
|
sed -i 's@${ROOT_DIR}/build/abi/compare_to_symbol_list@echo@g' $TARGET_FILE
|
||||||
|
sed -i 's/needs unknown symbol/Dont abort when unknown symbol/g' build/kernel/*.sh || echo "No unknown symbol scripts found"
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Make working directory clean to avoid dirty
|
- name: Make working directory clean to avoid dirty
|
||||||
working-directory: android-kernel
|
working-directory: android-kernel
|
||||||
run: |
|
run: |
|
||||||
@@ -163,7 +195,7 @@ jobs:
|
|||||||
cd common/ && git add -A && git commit -a -m "Add KernelSU"
|
cd common/ && git add -A && git commit -a -m "Add KernelSU"
|
||||||
repo status
|
repo status
|
||||||
|
|
||||||
- name: Build boot.img
|
- name: Build Kernel/LKM
|
||||||
working-directory: android-kernel
|
working-directory: android-kernel
|
||||||
run: |
|
run: |
|
||||||
if [ ! -z ${{ vars.EXPECTED_SIZE }} ] && [ ! -z ${{ vars.EXPECTED_HASH }} ]; then
|
if [ ! -z ${{ vars.EXPECTED_SIZE }} ] && [ ! -z ${{ vars.EXPECTED_HASH }} ]; then
|
||||||
@@ -184,20 +216,34 @@ jobs:
|
|||||||
OUTDIR=android-kernel/dist
|
OUTDIR=android-kernel/dist
|
||||||
fi
|
fi
|
||||||
mkdir output
|
mkdir output
|
||||||
|
if [ "${{ inputs.build_lkm}}" = "true" ]; then
|
||||||
|
llvm-strip-15 $OUTDIR/kernelsu.ko
|
||||||
|
cp $OUTDIR/kernelsu.ko ./output/
|
||||||
|
else
|
||||||
cp $OUTDIR/Image ./output/
|
cp $OUTDIR/Image ./output/
|
||||||
cp $OUTDIR/Image.lz4 ./output/
|
cp $OUTDIR/Image.lz4 ./output/
|
||||||
git clone https://github.com/Kernel-SU/AnyKernel3
|
git clone https://github.com/Kernel-SU/AnyKernel3
|
||||||
rm -rf ./AnyKernel3/.git
|
rm -rf ./AnyKernel3/.git
|
||||||
cp $OUTDIR/Image ./AnyKernel3/
|
cp $OUTDIR/Image ./AnyKernel3/
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Upload Image and Image.gz
|
- name: Upload Image and Image.gz
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
|
if: ${{ inputs.build_lkm == false }}
|
||||||
with:
|
with:
|
||||||
name: Image-${{ inputs.version_name }}_${{ inputs.os_patch_level }}
|
name: Image-${{ inputs.version_name }}_${{ inputs.os_patch_level }}
|
||||||
path: ./output/*
|
path: ./output/*
|
||||||
|
|
||||||
- name: Upload AnyKernel3
|
- name: Upload AnyKernel3
|
||||||
|
if: ${{ inputs.build_lkm == false }}
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: AnyKernel3-${{ inputs.version_name }}_${{ inputs.os_patch_level }}
|
name: AnyKernel3-${{ inputs.version_name }}_${{ inputs.os_patch_level }}
|
||||||
path: ./AnyKernel3/*
|
path: ./AnyKernel3/*
|
||||||
|
|
||||||
|
- name: Upload kernelsu.ko
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
if: ${{ inputs.build_lkm == true }}
|
||||||
|
with:
|
||||||
|
name: ${{ inputs.version }}_kernelsu.ko
|
||||||
|
path: ./output/kernelsu.ko
|
||||||
14
.github/workflows/ksud.yml
vendored
14
.github/workflows/ksud.yml
vendored
@@ -5,19 +5,27 @@ on:
|
|||||||
target:
|
target:
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
|
os:
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
default: ubuntu-latest
|
||||||
use_cache:
|
use_cache:
|
||||||
required: false
|
required: false
|
||||||
type: boolean
|
type: boolean
|
||||||
default: true
|
default: true
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ${{ inputs.os }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
# cross build failed after Rust 1.68, see https://github.com/cross-rs/cross/issues/1222
|
# cross build failed after Rust 1.68, see https://github.com/cross-rs/cross/issues/1222
|
||||||
- run: rustup default 1.67.0
|
- name: Setup rustup
|
||||||
|
run: |
|
||||||
|
rustup default 1.67.0
|
||||||
|
rustup target add x86_64-apple-darwin
|
||||||
|
rustup target add aarch64-apple-darwin
|
||||||
- uses: Swatinem/rust-cache@v2
|
- uses: Swatinem/rust-cache@v2
|
||||||
with:
|
with:
|
||||||
workspaces: userspace/ksud
|
workspaces: userspace/ksud
|
||||||
@@ -33,4 +41,4 @@ jobs:
|
|||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: ksud-${{ inputs.target }}
|
name: ksud-${{ inputs.target }}
|
||||||
path: userspace/ksud/target/**/release/ksud
|
path: userspace/ksud/target/**/release/ksud*
|
||||||
|
|||||||
@@ -14,4 +14,11 @@ config KSU_DEBUG
|
|||||||
help
|
help
|
||||||
Enable KernelSU debug mode
|
Enable KernelSU debug mode
|
||||||
|
|
||||||
|
config KSU_MODULE
|
||||||
|
bool "Build KernelSU as a module"
|
||||||
|
depends on KSU
|
||||||
|
default n
|
||||||
|
help
|
||||||
|
Build KernelSU as a loadable kernel module
|
||||||
|
|
||||||
endmenu
|
endmenu
|
||||||
|
|||||||
@@ -1,17 +1,27 @@
|
|||||||
obj-y += ksu.o
|
kernelsu-objs := ksu.o
|
||||||
obj-y += allowlist.o
|
kernelsu-objs += allowlist.o
|
||||||
kernelsu-objs := apk_sign.o
|
kernelsu-objs += apk_sign.o
|
||||||
obj-y += kernelsu.o
|
kernelsu-objs += module_api.o
|
||||||
obj-y += module_api.o
|
kernelsu-objs += sucompat.o
|
||||||
obj-y += sucompat.o
|
kernelsu-objs += uid_observer.o
|
||||||
obj-y += uid_observer.o
|
kernelsu-objs += manager.o
|
||||||
obj-y += manager.o
|
kernelsu-objs += core_hook.o
|
||||||
obj-y += core_hook.o
|
kernelsu-objs += ksud.o
|
||||||
obj-y += ksud.o
|
kernelsu-objs += embed_ksud.o
|
||||||
obj-y += embed_ksud.o
|
kernelsu-objs += kernel_compat.o
|
||||||
obj-y += kernel_compat.o
|
|
||||||
|
kernelsu-objs += selinux/selinux.o
|
||||||
|
kernelsu-objs += selinux/sepolicy.o
|
||||||
|
kernelsu-objs += selinux/rules.o
|
||||||
|
ccflags-y += -I$(srctree)/security/selinux -I$(srctree)/security/selinux/include
|
||||||
|
ccflags-y += -I$(objtree)/security/selinux -include $(srctree)/include/uapi/asm-generic/errno.h
|
||||||
|
|
||||||
|
ifndef KSU_MODULE
|
||||||
|
obj-y += kernelsu.o
|
||||||
|
else
|
||||||
|
obj-m += kernelsu.o
|
||||||
|
endif
|
||||||
|
|
||||||
obj-y += selinux/
|
|
||||||
# .git is a text file while the module is imported by 'git submodule add'.
|
# .git is a text file while the module is imported by 'git submodule add'.
|
||||||
ifeq ($(shell test -e $(srctree)/$(src)/../.git; echo $$?),0)
|
ifeq ($(shell test -e $(srctree)/$(src)/../.git; echo $$?),0)
|
||||||
$(shell cd $(srctree)/$(src); /usr/bin/env PATH="$$PATH":/usr/bin:/usr/local/bin [ -f ../.git/shallow ] && git fetch --unshallow)
|
$(shell cd $(srctree)/$(src); /usr/bin/env PATH="$$PATH":/usr/bin:/usr/local/bin [ -f ../.git/shallow ] && git fetch --unshallow)
|
||||||
@@ -25,6 +35,14 @@ $(warning "KSU_GIT_VERSION not defined! It is better to make KernelSU a git subm
|
|||||||
ccflags-y += -DKSU_VERSION=16
|
ccflags-y += -DKSU_VERSION=16
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
ifeq ($(shell grep -q " current_sid(void)" $(srctree)/security/selinux/include/objsec.h; echo $$?),0)
|
||||||
|
ccflags-y += -DKSU_COMPAT_HAS_CURRENT_SID
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifeq ($(shell grep -q "struct selinux_state " $(srctree)/security/selinux/include/security.h; echo $$?),0)
|
||||||
|
ccflags-y += -DKSU_COMPAT_HAS_SELINUX_STATE
|
||||||
|
endif
|
||||||
|
|
||||||
ifndef KSU_EXPECTED_SIZE
|
ifndef KSU_EXPECTED_SIZE
|
||||||
KSU_EXPECTED_SIZE := 0x033b
|
KSU_EXPECTED_SIZE := 0x033b
|
||||||
endif
|
endif
|
||||||
@@ -43,5 +61,6 @@ $(info -- KernelSU Manager signature hash: $(KSU_EXPECTED_HASH))
|
|||||||
|
|
||||||
ccflags-y += -DEXPECTED_SIZE=$(KSU_EXPECTED_SIZE)
|
ccflags-y += -DEXPECTED_SIZE=$(KSU_EXPECTED_SIZE)
|
||||||
ccflags-y += -DEXPECTED_HASH=\"$(KSU_EXPECTED_HASH)\"
|
ccflags-y += -DEXPECTED_HASH=\"$(KSU_EXPECTED_HASH)\"
|
||||||
|
|
||||||
ccflags-y += -Wno-implicit-function-declaration -Wno-strict-prototypes -Wno-int-conversion -Wno-gcc-compat
|
ccflags-y += -Wno-implicit-function-declaration -Wno-strict-prototypes -Wno-int-conversion -Wno-gcc-compat
|
||||||
ccflags-y += -Wno-declaration-after-statement
|
ccflags-y += -Wno-declaration-after-statement -Wno-unused-function
|
||||||
@@ -4,12 +4,20 @@
|
|||||||
#include "linux/err.h"
|
#include "linux/err.h"
|
||||||
#include "linux/init.h"
|
#include "linux/init.h"
|
||||||
#include "linux/init_task.h"
|
#include "linux/init_task.h"
|
||||||
|
#include "linux/kallsyms.h"
|
||||||
#include "linux/kernel.h"
|
#include "linux/kernel.h"
|
||||||
#include "linux/kprobes.h"
|
#include "linux/kprobes.h"
|
||||||
|
#include "linux/list.h"
|
||||||
#include "linux/lsm_hooks.h"
|
#include "linux/lsm_hooks.h"
|
||||||
|
#include "linux/mm.h"
|
||||||
|
#include "linux/mm_types.h"
|
||||||
#include "linux/nsproxy.h"
|
#include "linux/nsproxy.h"
|
||||||
#include "linux/path.h"
|
#include "linux/path.h"
|
||||||
#include "linux/printk.h"
|
#include "linux/printk.h"
|
||||||
|
#include "linux/sched.h"
|
||||||
|
#include "linux/security.h"
|
||||||
|
#include "linux/stddef.h"
|
||||||
|
#include "linux/types.h"
|
||||||
#include "linux/uaccess.h"
|
#include "linux/uaccess.h"
|
||||||
#include "linux/uidgid.h"
|
#include "linux/uidgid.h"
|
||||||
#include "linux/version.h"
|
#include "linux/version.h"
|
||||||
@@ -25,6 +33,7 @@
|
|||||||
#include "klog.h" // IWYU pragma: keep
|
#include "klog.h" // IWYU pragma: keep
|
||||||
#include "ksu.h"
|
#include "ksu.h"
|
||||||
#include "ksud.h"
|
#include "ksud.h"
|
||||||
|
#include "linux/vmalloc.h"
|
||||||
#include "manager.h"
|
#include "manager.h"
|
||||||
#include "selinux/selinux.h"
|
#include "selinux/selinux.h"
|
||||||
#include "uid_observer.h"
|
#include "uid_observer.h"
|
||||||
@@ -726,14 +735,181 @@ void __init ksu_lsm_hook_init(void)
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef MODULE
|
||||||
|
static int override_security_head(void *head, const void *new_head, size_t len)
|
||||||
|
{
|
||||||
|
unsigned long base = (unsigned long)head & PAGE_MASK;
|
||||||
|
unsigned long offset = offset_in_page(head);
|
||||||
|
|
||||||
|
// this is impossible for our case because the page alignment
|
||||||
|
// but be careful for other cases!
|
||||||
|
BUG_ON(offset + len > PAGE_SIZE);
|
||||||
|
struct page *page = phys_to_page(__pa(base));
|
||||||
|
if (!page) {
|
||||||
|
return -EFAULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
void *addr = vmap(&page, 1, VM_MAP, PAGE_KERNEL);
|
||||||
|
if (!addr) {
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
memcpy(addr + offset, new_head, len);
|
||||||
|
vunmap(addr);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void free_security_hook_list(struct hlist_head *head)
|
||||||
|
{
|
||||||
|
struct hlist_node *temp;
|
||||||
|
struct security_hook_list *entry;
|
||||||
|
|
||||||
|
if (!head)
|
||||||
|
return;
|
||||||
|
|
||||||
|
hlist_for_each_entry_safe (entry, temp, head, list) {
|
||||||
|
hlist_del(&entry->list);
|
||||||
|
kfree(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
kfree(head);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct hlist_head *copy_security_hlist(struct hlist_head *orig)
|
||||||
|
{
|
||||||
|
struct hlist_head *new_head = kmalloc(sizeof(*new_head), GFP_KERNEL);
|
||||||
|
if (!new_head)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
INIT_HLIST_HEAD(new_head);
|
||||||
|
|
||||||
|
struct security_hook_list *entry;
|
||||||
|
struct security_hook_list *new_entry;
|
||||||
|
|
||||||
|
hlist_for_each_entry (entry, orig, list) {
|
||||||
|
new_entry = kmalloc(sizeof(*new_entry), GFP_KERNEL);
|
||||||
|
if (!new_entry) {
|
||||||
|
free_security_hook_list(new_head);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
*new_entry = *entry;
|
||||||
|
|
||||||
|
hlist_add_tail_rcu(&new_entry->list, new_head);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new_head;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define LSM_SEARCH_MAX 180 // This should be enough to iterate
|
||||||
|
static void *find_head_addr(void *security_ptr, int *index)
|
||||||
|
{
|
||||||
|
if (!security_ptr) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
struct hlist_head *head_start =
|
||||||
|
(struct hlist_head *)&security_hook_heads;
|
||||||
|
|
||||||
|
for (int i = 0; i < LSM_SEARCH_MAX; i++) {
|
||||||
|
struct hlist_head *head = head_start + i;
|
||||||
|
struct security_hook_list *pos;
|
||||||
|
hlist_for_each_entry (pos, head, list) {
|
||||||
|
if (pos->hook.capget == security_ptr) {
|
||||||
|
if (index) {
|
||||||
|
*index = i;
|
||||||
|
}
|
||||||
|
return head;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define GET_SYMBOL_ADDR(sym) \
|
||||||
|
({ \
|
||||||
|
void *addr = kallsyms_lookup_name(#sym ".cfi_jt"); \
|
||||||
|
if (!addr) { \
|
||||||
|
addr = kallsyms_lookup_name(#sym); \
|
||||||
|
} \
|
||||||
|
addr; \
|
||||||
|
})
|
||||||
|
|
||||||
|
#define KSU_LSM_HOOK_HACK_INIT(head_ptr, name, func) \
|
||||||
|
do { \
|
||||||
|
static struct security_hook_list hook = { \
|
||||||
|
.hook = { .name = func } \
|
||||||
|
}; \
|
||||||
|
hook.head = head_ptr; \
|
||||||
|
hook.lsm = "ksu"; \
|
||||||
|
struct hlist_head *new_head = copy_security_hlist(hook.head); \
|
||||||
|
if (!new_head) { \
|
||||||
|
pr_err("Failed to copy security list: %s\n", #name); \
|
||||||
|
break; \
|
||||||
|
} \
|
||||||
|
hlist_add_tail_rcu(&hook.list, new_head); \
|
||||||
|
if (override_security_head(hook.head, new_head, \
|
||||||
|
sizeof(*new_head))) { \
|
||||||
|
free_security_hook_list(new_head); \
|
||||||
|
pr_err("Failed to hack lsm for: %s\n", #name); \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
void __init ksu_lsm_hook_init_hack(void)
|
||||||
|
{
|
||||||
|
void *cap_prctl = GET_SYMBOL_ADDR(cap_task_prctl);
|
||||||
|
void *prctl_head = find_head_addr(cap_prctl, NULL);
|
||||||
|
if (prctl_head) {
|
||||||
|
if (prctl_head != &security_hook_heads.task_prctl) {
|
||||||
|
pr_warn("prctl's address has shifted!\n");
|
||||||
|
}
|
||||||
|
KSU_LSM_HOOK_HACK_INIT(prctl_head, task_prctl, ksu_task_prctl);
|
||||||
|
} else {
|
||||||
|
pr_warn("Failed to find task_prctl!\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
int inode_killpriv_index = -1;
|
||||||
|
void *cap_killpriv = GET_SYMBOL_ADDR(cap_inode_killpriv);
|
||||||
|
find_head_addr(cap_killpriv, &inode_killpriv_index);
|
||||||
|
if (inode_killpriv_index < 0) {
|
||||||
|
pr_warn("Failed to find inode_rename, use kprobe instead!\n");
|
||||||
|
register_kprobe(&renameat_kp);
|
||||||
|
} else {
|
||||||
|
int inode_rename_index = inode_killpriv_index +
|
||||||
|
&security_hook_heads.inode_rename -
|
||||||
|
&security_hook_heads.inode_killpriv;
|
||||||
|
struct hlist_head *head_start =
|
||||||
|
(struct hlist_head *)&security_hook_heads;
|
||||||
|
void *inode_rename_head = head_start + inode_rename_index;
|
||||||
|
if (inode_rename_head != &security_hook_heads.inode_rename) {
|
||||||
|
pr_warn("inode_rename's address has shifted!\n");
|
||||||
|
}
|
||||||
|
KSU_LSM_HOOK_HACK_INIT(inode_rename_head, inode_rename,
|
||||||
|
ksu_inode_rename);
|
||||||
|
}
|
||||||
|
void *cap_setuid = GET_SYMBOL_ADDR(cap_task_fix_setuid);
|
||||||
|
void *setuid_head = find_head_addr(cap_setuid, NULL);
|
||||||
|
if (setuid_head) {
|
||||||
|
if (setuid_head != &security_hook_heads.task_fix_setuid) {
|
||||||
|
pr_warn("setuid's address has shifted!\n");
|
||||||
|
}
|
||||||
|
KSU_LSM_HOOK_HACK_INIT(setuid_head, task_fix_setuid,
|
||||||
|
ksu_task_fix_setuid);
|
||||||
|
} else {
|
||||||
|
pr_warn("Failed to find task_fix_setuid!\n");
|
||||||
|
}
|
||||||
|
smp_mb();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
void __init ksu_core_init(void)
|
void __init ksu_core_init(void)
|
||||||
{
|
{
|
||||||
#ifndef MODULE
|
#ifndef MODULE
|
||||||
pr_info("ksu_lsm_hook_init\n");
|
pr_info("ksu_lsm_hook_init\n");
|
||||||
ksu_lsm_hook_init();
|
ksu_lsm_hook_init();
|
||||||
|
|
||||||
#else
|
#else
|
||||||
pr_info("ksu_kprobe_init\n");
|
pr_info("ksu_lsm_hook_init hack!!!!\n");
|
||||||
ksu_kprobe_init();
|
ksu_lsm_hook_init_hack();
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,23 @@ fun parseKernelVersion(version: String): KernelVersion {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun parseKMI(input: String): String? {
|
||||||
|
val regex = Regex("(.* )?(\\d+\\.\\d+)(\\S+)?(android\\d+)(.*)")
|
||||||
|
val result = regex.find(input)
|
||||||
|
|
||||||
|
return result?.let {
|
||||||
|
val androidVersion = it.groups[4]?.value ?: ""
|
||||||
|
val kernelVersion = it.groups[2]?.value ?: ""
|
||||||
|
"$androidVersion-$kernelVersion"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getKMI(): String? {
|
||||||
|
Os.uname().release.let {
|
||||||
|
return parseKMI(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun getKernelVersion(): KernelVersion {
|
fun getKernelVersion(): KernelVersion {
|
||||||
Os.uname().release.let {
|
Os.uname().release.let {
|
||||||
return parseKernelVersion(it)
|
return parseKernelVersion(it)
|
||||||
|
|||||||
189
manager/app/src/main/java/me/weishu/kernelsu/ui/screen/Flash.kt
Normal file
189
manager/app/src/main/java/me/weishu/kernelsu/ui/screen/Flash.kt
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
package me.weishu.kernelsu.ui.screen
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Environment
|
||||||
|
import android.os.Parcelable
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.ArrowBack
|
||||||
|
import androidx.compose.material.icons.filled.Refresh
|
||||||
|
import androidx.compose.material.icons.filled.Save
|
||||||
|
import androidx.compose.material3.*
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
|
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.input.key.Key
|
||||||
|
import androidx.compose.ui.input.key.key
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.ramcosta.composedestinations.annotation.Destination
|
||||||
|
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
import me.weishu.kernelsu.R
|
||||||
|
import me.weishu.kernelsu.ui.component.KeyEventBlocker
|
||||||
|
import me.weishu.kernelsu.ui.util.LocalSnackbarHost
|
||||||
|
import me.weishu.kernelsu.ui.util.installBoot
|
||||||
|
import me.weishu.kernelsu.ui.util.installModule
|
||||||
|
import me.weishu.kernelsu.ui.util.reboot
|
||||||
|
import java.io.File
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author weishu
|
||||||
|
* @date 2023/1/1.
|
||||||
|
*/
|
||||||
|
@OptIn(ExperimentalComposeUiApi::class)
|
||||||
|
@Composable
|
||||||
|
@Destination
|
||||||
|
fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
|
||||||
|
|
||||||
|
var text by rememberSaveable { mutableStateOf("") }
|
||||||
|
val logContent = rememberSaveable { StringBuilder() }
|
||||||
|
var showFloatAction by rememberSaveable { mutableStateOf(false) }
|
||||||
|
|
||||||
|
val snackBarHost = LocalSnackbarHost.current
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
val scrollState = rememberScrollState()
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
if (text.isNotEmpty()) {
|
||||||
|
return@LaunchedEffect
|
||||||
|
}
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
flashIt(flashIt, onFinish = { showReboot ->
|
||||||
|
if (showReboot) {
|
||||||
|
showFloatAction = true
|
||||||
|
}
|
||||||
|
}, onStdout = {
|
||||||
|
text += "$it\n"
|
||||||
|
logContent.append(it).append("\n")
|
||||||
|
}, onStderr = {
|
||||||
|
logContent.append(it).append("\n")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
|
TopBar(
|
||||||
|
onBack = {
|
||||||
|
navigator.popBackStack()
|
||||||
|
},
|
||||||
|
onSave = {
|
||||||
|
scope.launch {
|
||||||
|
val format = SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.getDefault())
|
||||||
|
val date = format.format(Date())
|
||||||
|
val file = File(
|
||||||
|
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
|
||||||
|
"KernelSU_install_log_${date}.log"
|
||||||
|
)
|
||||||
|
file.writeText(logContent.toString())
|
||||||
|
snackBarHost.showSnackbar("Log saved to ${file.absolutePath}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
floatingActionButton = {
|
||||||
|
if (showFloatAction) {
|
||||||
|
val reboot = stringResource(id = R.string.reboot)
|
||||||
|
ExtendedFloatingActionButton(
|
||||||
|
onClick = {
|
||||||
|
scope.launch {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
reboot()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
icon = { Icon(Icons.Filled.Refresh, reboot) },
|
||||||
|
text = { Text(text = reboot) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
) { innerPadding ->
|
||||||
|
KeyEventBlocker {
|
||||||
|
it.key == Key.VolumeDown || it.key == Key.VolumeUp
|
||||||
|
}
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize(1f)
|
||||||
|
.padding(innerPadding)
|
||||||
|
.verticalScroll(scrollState),
|
||||||
|
) {
|
||||||
|
LaunchedEffect(text) {
|
||||||
|
scrollState.animateScrollTo(scrollState.maxValue)
|
||||||
|
}
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.padding(8.dp),
|
||||||
|
text = text,
|
||||||
|
fontSize = MaterialTheme.typography.bodySmall.fontSize,
|
||||||
|
fontFamily = FontFamily.Monospace,
|
||||||
|
lineHeight = MaterialTheme.typography.bodySmall.lineHeight,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
sealed class FlashIt : Parcelable {
|
||||||
|
data class FlashBoot(val bootUri: Uri? = null, val koUri: Uri, val ota: Boolean) : FlashIt()
|
||||||
|
|
||||||
|
data class FlashModule(val uri: Uri) : FlashIt()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun flashIt(
|
||||||
|
flashIt: FlashIt, onFinish: (Boolean) -> Unit,
|
||||||
|
onStdout: (String) -> Unit,
|
||||||
|
onStderr: (String) -> Unit
|
||||||
|
) {
|
||||||
|
when (flashIt) {
|
||||||
|
is FlashIt.FlashBoot -> installBoot(
|
||||||
|
flashIt.bootUri,
|
||||||
|
flashIt.koUri,
|
||||||
|
flashIt.ota,
|
||||||
|
onFinish,
|
||||||
|
onStdout,
|
||||||
|
onStderr
|
||||||
|
)
|
||||||
|
|
||||||
|
is FlashIt.FlashModule -> installModule(flashIt.uri, onFinish, onStdout, onStderr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
private fun TopBar(onBack: () -> Unit = {}, onSave: () -> Unit = {}) {
|
||||||
|
TopAppBar(
|
||||||
|
title = { Text(stringResource(R.string.install)) },
|
||||||
|
navigationIcon = {
|
||||||
|
IconButton(
|
||||||
|
onClick = onBack
|
||||||
|
) { Icon(Icons.Filled.ArrowBack, contentDescription = null) }
|
||||||
|
},
|
||||||
|
actions = {
|
||||||
|
IconButton(onClick = onSave) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.Save,
|
||||||
|
contentDescription = "Localized description"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun InstallPreview() {
|
||||||
|
// InstallScreen(DestinationsNavigator(), uri = Uri.EMPTY)
|
||||||
|
}
|
||||||
@@ -34,6 +34,7 @@ import kotlinx.coroutines.withContext
|
|||||||
import me.weishu.kernelsu.*
|
import me.weishu.kernelsu.*
|
||||||
import me.weishu.kernelsu.R
|
import me.weishu.kernelsu.R
|
||||||
import me.weishu.kernelsu.ui.component.rememberConfirmDialog
|
import me.weishu.kernelsu.ui.component.rememberConfirmDialog
|
||||||
|
import me.weishu.kernelsu.ui.screen.destinations.InstallScreenDestination
|
||||||
import me.weishu.kernelsu.ui.screen.destinations.SettingScreenDestination
|
import me.weishu.kernelsu.ui.screen.destinations.SettingScreenDestination
|
||||||
import me.weishu.kernelsu.ui.util.*
|
import me.weishu.kernelsu.ui.util.*
|
||||||
|
|
||||||
@@ -60,7 +61,9 @@ fun HomeScreen(navigator: DestinationsNavigator) {
|
|||||||
}
|
}
|
||||||
val ksuVersion = if (isManager) Natives.version else null
|
val ksuVersion = if (isManager) Natives.version else null
|
||||||
|
|
||||||
StatusCard(kernelVersion, ksuVersion)
|
StatusCard(kernelVersion, ksuVersion) {
|
||||||
|
navigator.navigate(InstallScreenDestination)
|
||||||
|
}
|
||||||
if (isManager && Natives.requireNewKernel()) {
|
if (isManager && Natives.requireNewKernel()) {
|
||||||
WarningCard(
|
WarningCard(
|
||||||
stringResource(id = R.string.require_kernel_version).format(
|
stringResource(id = R.string.require_kernel_version).format(
|
||||||
@@ -68,7 +71,7 @@ fun HomeScreen(navigator: DestinationsNavigator) {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (!rootAvailable()) {
|
if (ksuVersion != null && !rootAvailable()) {
|
||||||
WarningCard(
|
WarningCard(
|
||||||
stringResource(id = R.string.grant_root_failed)
|
stringResource(id = R.string.grant_root_failed)
|
||||||
)
|
)
|
||||||
@@ -174,7 +177,7 @@ private fun TopBar(onSettingsClick: () -> Unit) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun StatusCard(kernelVersion: KernelVersion, ksuVersion: Int?) {
|
private fun StatusCard(kernelVersion: KernelVersion, ksuVersion: Int?, onClickInstall: () -> Unit = {}) {
|
||||||
ElevatedCard(
|
ElevatedCard(
|
||||||
colors = CardDefaults.elevatedCardColors(containerColor = run {
|
colors = CardDefaults.elevatedCardColors(containerColor = run {
|
||||||
if (ksuVersion != null) MaterialTheme.colorScheme.secondaryContainer
|
if (ksuVersion != null) MaterialTheme.colorScheme.secondaryContainer
|
||||||
@@ -185,8 +188,8 @@ private fun StatusCard(kernelVersion: KernelVersion, ksuVersion: Int?) {
|
|||||||
Row(modifier = Modifier
|
Row(modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clickable {
|
.clickable {
|
||||||
if (kernelVersion.isGKI() && ksuVersion == null) {
|
if (kernelVersion.isGKI()) {
|
||||||
uriHandler.openUri("https://kernelsu.org/guide/installation.html")
|
onClickInstall()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(24.dp), verticalAlignment = Alignment.CenterVertically) {
|
.padding(24.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
|||||||
@@ -1,140 +1,262 @@
|
|||||||
package me.weishu.kernelsu.ui.screen
|
package me.weishu.kernelsu.ui.screen
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Environment
|
import android.widget.Toast
|
||||||
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.rememberScrollState
|
|
||||||
import androidx.compose.foundation.verticalScroll
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.ArrowBack
|
import androidx.compose.material.icons.filled.ArrowBack
|
||||||
import androidx.compose.material.icons.filled.Refresh
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material.icons.filled.Save
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
import androidx.compose.material3.RadioButton
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TopAppBar
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.input.key.Key
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.input.key.key
|
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.font.FontFamily
|
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.ramcosta.composedestinations.annotation.Destination
|
import com.ramcosta.composedestinations.annotation.Destination
|
||||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import me.weishu.kernelsu.R
|
import me.weishu.kernelsu.R
|
||||||
import me.weishu.kernelsu.ui.component.KeyEventBlocker
|
import me.weishu.kernelsu.ui.component.rememberConfirmDialog
|
||||||
import me.weishu.kernelsu.ui.util.LocalSnackbarHost
|
import me.weishu.kernelsu.ui.component.rememberLoadingDialog
|
||||||
import me.weishu.kernelsu.ui.util.installModule
|
import me.weishu.kernelsu.ui.screen.destinations.FlashScreenDestination
|
||||||
import me.weishu.kernelsu.ui.util.reboot
|
import me.weishu.kernelsu.ui.util.DownloadListener
|
||||||
import java.io.File
|
import me.weishu.kernelsu.ui.util.download
|
||||||
import java.text.SimpleDateFormat
|
import me.weishu.kernelsu.ui.util.getLKMUrl
|
||||||
import java.util.*
|
import me.weishu.kernelsu.ui.util.isAbDevice
|
||||||
|
import me.weishu.kernelsu.ui.util.rootAvailable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author weishu
|
* @author weishu
|
||||||
* @date 2023/1/1.
|
* @date 2024/3/12.
|
||||||
*/
|
*/
|
||||||
@OptIn(ExperimentalComposeUiApi::class)
|
|
||||||
@Composable
|
|
||||||
@Destination
|
@Destination
|
||||||
fun InstallScreen(navigator: DestinationsNavigator, uri: Uri) {
|
@Composable
|
||||||
|
fun InstallScreen(navigator: DestinationsNavigator) {
|
||||||
var text by rememberSaveable { mutableStateOf("") }
|
|
||||||
val logContent = rememberSaveable { StringBuilder() }
|
|
||||||
var showFloatAction by rememberSaveable { mutableStateOf(false) }
|
|
||||||
|
|
||||||
val snackBarHost = LocalSnackbarHost.current
|
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val scrollState = rememberScrollState()
|
val loadingDialog = rememberLoadingDialog()
|
||||||
|
val context = LocalContext.current
|
||||||
|
var installMethod by remember {
|
||||||
|
mutableStateOf<InstallMethod?>(null)
|
||||||
|
}
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
val onFileDownloaded = { uri: Uri ->
|
||||||
if (text.isNotEmpty()) {
|
|
||||||
return@LaunchedEffect
|
installMethod?.let {
|
||||||
|
scope.launch(Dispatchers.Main) {
|
||||||
|
when (it) {
|
||||||
|
InstallMethod.DirectInstall -> {
|
||||||
|
navigator.navigate(
|
||||||
|
FlashScreenDestination(
|
||||||
|
FlashIt.FlashBoot(
|
||||||
|
null,
|
||||||
|
uri,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
InstallMethod.DirectInstallToInactiveSlot -> {
|
||||||
|
navigator.navigate(
|
||||||
|
FlashScreenDestination(
|
||||||
|
FlashIt.FlashBoot(
|
||||||
|
null,
|
||||||
|
uri,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
is InstallMethod.SelectFile -> {
|
||||||
|
navigator.navigate(
|
||||||
|
FlashScreenDestination(
|
||||||
|
FlashIt.FlashBoot(
|
||||||
|
it.uri,
|
||||||
|
uri,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
withContext(Dispatchers.IO) {
|
|
||||||
installModule(uri, onFinish = { success ->
|
|
||||||
if (success) {
|
|
||||||
showFloatAction = true
|
|
||||||
}
|
}
|
||||||
}, onStdout = {
|
|
||||||
text += "$it\n"
|
|
||||||
logContent.append(it).append("\n")
|
|
||||||
}, onStderr = {
|
|
||||||
logContent.append(it).append("\n")
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(topBar = {
|
||||||
topBar = {
|
TopBar {
|
||||||
TopBar(
|
|
||||||
onBack = {
|
|
||||||
navigator.popBackStack()
|
navigator.popBackStack()
|
||||||
},
|
|
||||||
onSave = {
|
|
||||||
scope.launch {
|
|
||||||
val format = SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.getDefault())
|
|
||||||
val date = format.format(Date())
|
|
||||||
val file = File(
|
|
||||||
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
|
|
||||||
"KernelSU_install_log_${date}.log"
|
|
||||||
)
|
|
||||||
file.writeText(logContent.toString())
|
|
||||||
snackBarHost.showSnackbar("Log saved to ${file.absolutePath}")
|
|
||||||
}
|
}
|
||||||
|
}) {
|
||||||
|
Column(modifier = Modifier.padding(it)) {
|
||||||
|
SelectInstallMethod { method ->
|
||||||
|
installMethod = method
|
||||||
}
|
}
|
||||||
)
|
|
||||||
},
|
Row(
|
||||||
floatingActionButton = {
|
modifier = Modifier
|
||||||
if (showFloatAction) {
|
.fillMaxWidth()
|
||||||
val reboot = stringResource(id = R.string.reboot)
|
.padding(16.dp)
|
||||||
ExtendedFloatingActionButton(
|
) {
|
||||||
|
|
||||||
|
DownloadListener(context = context) { uri ->
|
||||||
|
onFileDownloaded(uri)
|
||||||
|
loadingDialog.hide()
|
||||||
|
}
|
||||||
|
Button(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
enabled = installMethod != null,
|
||||||
onClick = {
|
onClick = {
|
||||||
scope.launch {
|
loadingDialog.showLoading()
|
||||||
withContext(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
reboot()
|
getLKMUrl().onFailure { throwable ->
|
||||||
}
|
loadingDialog.hide()
|
||||||
|
scope.launch(Dispatchers.Main) {
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
"Failed to fetch LKM url: ${throwable.message}",
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
}
|
}
|
||||||
|
}.onSuccess { result ->
|
||||||
|
loadingDialog.hide()
|
||||||
|
|
||||||
|
download(
|
||||||
|
context = context,
|
||||||
|
url = result.second,
|
||||||
|
fileName = result.first,
|
||||||
|
description = "Downloading ${result.first}",
|
||||||
|
onDownloaded = { uri ->
|
||||||
|
onFileDownloaded(uri)
|
||||||
|
loadingDialog.hide()
|
||||||
},
|
},
|
||||||
icon = { Icon(Icons.Filled.Refresh, reboot) },
|
onDownloading = {}
|
||||||
text = { Text(text = reboot) },
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
Text("Next", fontSize = MaterialTheme.typography.bodyMedium.fontSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class InstallMethod {
|
||||||
|
data class SelectFile(val uri: Uri? = null, override val label: Int = R.string.select_file) :
|
||||||
|
InstallMethod()
|
||||||
|
|
||||||
|
object DirectInstall : InstallMethod() {
|
||||||
|
override val label: Int
|
||||||
|
get() = R.string.direct_install
|
||||||
|
}
|
||||||
|
|
||||||
|
object DirectInstallToInactiveSlot : InstallMethod() {
|
||||||
|
override val label: Int
|
||||||
|
get() = R.string.install_inactive_slot
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract val label: Int
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun SelectInstallMethod(onSelected: (InstallMethod) -> Unit = {}) {
|
||||||
|
val rootAvailable = rootAvailable()
|
||||||
|
val isAbDevice = isAbDevice()
|
||||||
|
val radioOptions = mutableListOf<InstallMethod>(InstallMethod.SelectFile())
|
||||||
|
if (rootAvailable) {
|
||||||
|
radioOptions.add(InstallMethod.DirectInstall)
|
||||||
|
|
||||||
|
if (isAbDevice) {
|
||||||
|
radioOptions.add(InstallMethod.DirectInstallToInactiveSlot)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var selectedOption by remember { mutableStateOf<InstallMethod?>(null) }
|
||||||
|
val selectImageLauncher = rememberLauncherForActivityResult(
|
||||||
|
contract = ActivityResultContracts.StartActivityForResult()
|
||||||
|
) {
|
||||||
|
if (it.resultCode == Activity.RESULT_OK) {
|
||||||
|
it.data?.data?.let { uri ->
|
||||||
|
val option = InstallMethod.SelectFile(uri)
|
||||||
|
selectedOption = option
|
||||||
|
onSelected(option)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val confirmDialog = rememberConfirmDialog(onConfirm = {
|
||||||
|
selectedOption = InstallMethod.DirectInstallToInactiveSlot
|
||||||
|
onSelected(InstallMethod.DirectInstallToInactiveSlot)
|
||||||
|
}, onDismiss = null)
|
||||||
|
val dialogTitle = stringResource(id = android.R.string.dialog_alert_title)
|
||||||
|
val dialogContent = stringResource(id = R.string.install_inactive_slot_warning)
|
||||||
|
|
||||||
|
val onClick = { option: InstallMethod ->
|
||||||
|
|
||||||
|
when (option) {
|
||||||
|
is InstallMethod.SelectFile -> {
|
||||||
|
selectImageLauncher.launch(
|
||||||
|
Intent(Intent.ACTION_GET_CONTENT).apply {
|
||||||
|
type = "application/octet-stream"
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is InstallMethod.DirectInstall -> {
|
||||||
|
selectedOption = option
|
||||||
|
onSelected(option)
|
||||||
}
|
}
|
||||||
) { innerPadding ->
|
is InstallMethod.DirectInstallToInactiveSlot -> {
|
||||||
KeyEventBlocker {
|
confirmDialog.showConfirm(dialogTitle, dialogContent)
|
||||||
it.key == Key.VolumeDown || it.key == Key.VolumeUp
|
|
||||||
}
|
}
|
||||||
Column(
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
radioOptions.forEach { option ->
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize(1f)
|
.fillMaxWidth()
|
||||||
.padding(innerPadding)
|
.clickable {
|
||||||
.verticalScroll(scrollState),
|
onClick(option)
|
||||||
) {
|
}) {
|
||||||
LaunchedEffect(text) {
|
RadioButton(selected = option.javaClass == selectedOption?.javaClass, onClick = {
|
||||||
scrollState.animateScrollTo(scrollState.maxValue)
|
onClick(option)
|
||||||
|
})
|
||||||
|
Text(text = stringResource(id = option.label))
|
||||||
}
|
}
|
||||||
Text(
|
|
||||||
modifier = Modifier.padding(8.dp),
|
|
||||||
text = text,
|
|
||||||
fontSize = MaterialTheme.typography.bodySmall.fontSize,
|
|
||||||
fontFamily = FontFamily.Monospace,
|
|
||||||
lineHeight = MaterialTheme.typography.bodySmall.lineHeight,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
private fun TopBar(onBack: () -> Unit = {}, onSave: () -> Unit = {}) {
|
private fun TopBar(onBack: () -> Unit = {}) {
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = { Text(stringResource(R.string.install)) },
|
title = { Text(stringResource(R.string.install)) },
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
@@ -142,19 +264,11 @@ private fun TopBar(onBack: () -> Unit = {}, onSave: () -> Unit = {}) {
|
|||||||
onClick = onBack
|
onClick = onBack
|
||||||
) { Icon(Icons.Filled.ArrowBack, contentDescription = null) }
|
) { Icon(Icons.Filled.ArrowBack, contentDescription = null) }
|
||||||
},
|
},
|
||||||
actions = {
|
|
||||||
IconButton(onClick = onSave) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Filled.Save,
|
|
||||||
contentDescription = "Localized description"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Preview
|
|
||||||
@Composable
|
@Composable
|
||||||
fun InstallPreview() {
|
@Preview
|
||||||
// InstallScreen(DestinationsNavigator(), uri = Uri.EMPTY)
|
fun SelectInstall_Preview() {
|
||||||
|
// InstallScreen(DestinationsNavigator())
|
||||||
}
|
}
|
||||||
@@ -43,7 +43,7 @@ import me.weishu.kernelsu.R
|
|||||||
import me.weishu.kernelsu.ui.component.ConfirmResult
|
import me.weishu.kernelsu.ui.component.ConfirmResult
|
||||||
import me.weishu.kernelsu.ui.component.rememberConfirmDialog
|
import me.weishu.kernelsu.ui.component.rememberConfirmDialog
|
||||||
import me.weishu.kernelsu.ui.component.rememberLoadingDialog
|
import me.weishu.kernelsu.ui.component.rememberLoadingDialog
|
||||||
import me.weishu.kernelsu.ui.screen.destinations.InstallScreenDestination
|
import me.weishu.kernelsu.ui.screen.destinations.FlashScreenDestination
|
||||||
import me.weishu.kernelsu.ui.screen.destinations.WebScreenDestination
|
import me.weishu.kernelsu.ui.screen.destinations.WebScreenDestination
|
||||||
import me.weishu.kernelsu.ui.util.*
|
import me.weishu.kernelsu.ui.util.*
|
||||||
import me.weishu.kernelsu.ui.viewmodel.ModuleViewModel
|
import me.weishu.kernelsu.ui.viewmodel.ModuleViewModel
|
||||||
@@ -81,7 +81,7 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
|
|||||||
val data = it.data ?: return@rememberLauncherForActivityResult
|
val data = it.data ?: return@rememberLauncherForActivityResult
|
||||||
val uri = data.data ?: return@rememberLauncherForActivityResult
|
val uri = data.data ?: return@rememberLauncherForActivityResult
|
||||||
|
|
||||||
navigator.navigate(InstallScreenDestination(uri))
|
navigator.navigate(FlashScreenDestination(FlashIt.FlashModule(uri)))
|
||||||
|
|
||||||
viewModel.markNeedRefresh()
|
viewModel.markNeedRefresh()
|
||||||
|
|
||||||
@@ -123,7 +123,7 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
|
|||||||
.fillMaxSize(),
|
.fillMaxSize(),
|
||||||
onInstallModule =
|
onInstallModule =
|
||||||
{
|
{
|
||||||
navigator.navigate(InstallScreenDestination(it))
|
navigator.navigate(FlashScreenDestination(FlashIt.FlashModule(it)))
|
||||||
}, onClickModule = { id, name, hasWebUi ->
|
}, onClickModule = { id, name, hasWebUi ->
|
||||||
if (hasWebUi) {
|
if (hasWebUi) {
|
||||||
navigator.navigate(WebScreenDestination(id, name))
|
navigator.navigate(WebScreenDestination(id, name))
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import android.net.Uri
|
|||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.DisposableEffect
|
import androidx.compose.runtime.DisposableEffect
|
||||||
|
import me.weishu.kernelsu.getKMI
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author weishu
|
* @author weishu
|
||||||
@@ -94,6 +95,38 @@ fun checkNewVersion(): Triple<Int, String, String> {
|
|||||||
}
|
}
|
||||||
return defaultValue
|
return defaultValue
|
||||||
}
|
}
|
||||||
|
fun getLKMUrl(): Result<Pair<String, String>> {
|
||||||
|
val url = "https://api.github.com/repos/tiann/KernelSU/releases/latest"
|
||||||
|
|
||||||
|
val kmi = getKMI() ?: return Result.failure(RuntimeException("Get KMI failed"))
|
||||||
|
runCatching {
|
||||||
|
okhttp3.OkHttpClient().newCall(okhttp3.Request.Builder().url(url).build()).execute()
|
||||||
|
.use { response ->
|
||||||
|
val body = response.body?.string() ?: return Result.failure(RuntimeException("request body failed"))
|
||||||
|
if (!response.isSuccessful) {
|
||||||
|
return Result.failure(RuntimeException("Request failed, code: ${response.code}, message: $body"))
|
||||||
|
}
|
||||||
|
val json = org.json.JSONObject(body)
|
||||||
|
|
||||||
|
val assets = json.getJSONArray("assets")
|
||||||
|
for (i in 0 until assets.length()) {
|
||||||
|
val asset = assets.getJSONObject(i)
|
||||||
|
val name = asset.getString("name")
|
||||||
|
if (!name.endsWith(".ko")) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name.contains(kmi)) {
|
||||||
|
return Result.success(Pair(name, asset.getString("browser_download_url")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.onFailure {
|
||||||
|
return Result.failure(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.failure(RuntimeException("Cannot find LKM for $kmi"))
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DownloadListener(context: Context, onDownloaded: (Uri) -> Unit) {
|
fun DownloadListener(context: Context, onDownloaded: (Uri) -> Unit) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package me.weishu.kernelsu.ui.util
|
package me.weishu.kernelsu.ui.util
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.os.Environment
|
||||||
import android.os.SystemClock
|
import android.os.SystemClock
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.topjohnwu.superuser.CallbackList
|
import com.topjohnwu.superuser.CallbackList
|
||||||
@@ -138,6 +139,84 @@ fun installModule(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun installBoot(
|
||||||
|
bootUri: Uri?,
|
||||||
|
lkmUri: Uri,
|
||||||
|
ota: Boolean,
|
||||||
|
onFinish: (Boolean) -> Unit,
|
||||||
|
onStdout: (String) -> Unit,
|
||||||
|
onStderr: (String) -> Unit
|
||||||
|
): Boolean {
|
||||||
|
val resolver = ksuApp.contentResolver
|
||||||
|
|
||||||
|
with(resolver.openInputStream(lkmUri)) {
|
||||||
|
val lkmFile = File(ksuApp.cacheDir, "kernelsu.ko")
|
||||||
|
lkmFile.outputStream().use { output ->
|
||||||
|
this?.copyTo(output)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!lkmFile.exists()) {
|
||||||
|
onStdout("- kernelsu.ko not found")
|
||||||
|
onFinish(false)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
val bootFile = bootUri?.let { uri ->
|
||||||
|
with(resolver.openInputStream(uri)) {
|
||||||
|
val bootFile = File(ksuApp.cacheDir, "boot.img")
|
||||||
|
bootFile.outputStream().use { output ->
|
||||||
|
this?.copyTo(output)
|
||||||
|
}
|
||||||
|
|
||||||
|
bootFile
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val magiskboot = File(ksuApp.applicationInfo.nativeLibraryDir, "libmagiskboot.so")
|
||||||
|
var cmd = "boot-patch -m ${lkmFile.absolutePath} --magiskboot ${magiskboot.absolutePath}"
|
||||||
|
|
||||||
|
cmd += if (bootFile == null) {
|
||||||
|
// no boot.img, use -f to force install
|
||||||
|
" -f"
|
||||||
|
} else {
|
||||||
|
" -b ${bootFile.absolutePath}"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ota) {
|
||||||
|
cmd += " -u"
|
||||||
|
}
|
||||||
|
|
||||||
|
// output dir
|
||||||
|
val downloadsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
|
||||||
|
cmd += " -o $downloadsDir"
|
||||||
|
|
||||||
|
val shell = createRootShell()
|
||||||
|
|
||||||
|
val stdoutCallback: CallbackList<String?> = object : CallbackList<String?>() {
|
||||||
|
override fun onAddElement(s: String?) {
|
||||||
|
onStdout(s ?: "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val stderrCallback: CallbackList<String?> = object : CallbackList<String?>() {
|
||||||
|
override fun onAddElement(s: String?) {
|
||||||
|
onStderr(s ?: "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val result =
|
||||||
|
shell.newJob().add("${getKsuDaemonPath()} $cmd").to(stdoutCallback, stderrCallback)
|
||||||
|
.exec()
|
||||||
|
Log.i("KernelSU", "install boot $lkmUri result: $result")
|
||||||
|
|
||||||
|
lkmFile.delete()
|
||||||
|
bootFile?.delete()
|
||||||
|
|
||||||
|
onFinish(bootUri != null && result.isSuccess)
|
||||||
|
return result.isSuccess
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun reboot(reason: String = "") {
|
fun reboot(reason: String = "") {
|
||||||
val shell = getRootShell()
|
val shell = getRootShell()
|
||||||
if (reason == "recovery") {
|
if (reason == "recovery") {
|
||||||
@@ -152,6 +231,11 @@ fun rootAvailable(): Boolean {
|
|||||||
return shell.isRoot
|
return shell.isRoot
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun isAbDevice(): Boolean {
|
||||||
|
val shell = getRootShell()
|
||||||
|
return ShellUtils.fastCmd(shell, "getprop ro.build.ab_update").trim().toBoolean()
|
||||||
|
}
|
||||||
|
|
||||||
fun overlayFsAvailable(): Boolean {
|
fun overlayFsAvailable(): Boolean {
|
||||||
val shell = getRootShell()
|
val shell = getRootShell()
|
||||||
// check /proc/filesystems
|
// check /proc/filesystems
|
||||||
|
|||||||
BIN
manager/app/src/main/jniLibs/arm64-v8a/libmagiskboot.so
Normal file
BIN
manager/app/src/main/jniLibs/arm64-v8a/libmagiskboot.so
Normal file
Binary file not shown.
@@ -108,4 +108,8 @@
|
|||||||
<string name="open">打开</string>
|
<string name="open">打开</string>
|
||||||
<string name="enable_web_debugging">启用 WebView 调试</string>
|
<string name="enable_web_debugging">启用 WebView 调试</string>
|
||||||
<string name="enable_web_debugging_summary">可用于调试 WebUI ,请仅在需要时启用。</string>
|
<string name="enable_web_debugging_summary">可用于调试 WebUI ,请仅在需要时启用。</string>
|
||||||
|
<string name="direct_install">直接安装(推荐)</string>
|
||||||
|
<string name="select_file">选择一个文件</string>
|
||||||
|
<string name="install_inactive_slot">安装到未使用的槽位(OTA 后)</string>
|
||||||
|
<string name="install_inactive_slot_warning">将在重启后强制切换到另一个槽位!\n注意只能在 OTA 更新完成后的重启之前使用。\n确认?</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -110,4 +110,8 @@
|
|||||||
<string name="open">Open</string>
|
<string name="open">Open</string>
|
||||||
<string name="enable_web_debugging">Enable WebView Debugging</string>
|
<string name="enable_web_debugging">Enable WebView Debugging</string>
|
||||||
<string name="enable_web_debugging_summary">Can be used to debug WebUI, please enable only when needed.</string>
|
<string name="enable_web_debugging_summary">Can be used to debug WebUI, please enable only when needed.</string>
|
||||||
|
<string name="direct_install">Direct Install (Recommended)</string>
|
||||||
|
<string name="select_file">Select a File</string>
|
||||||
|
<string name="install_inactive_slot">Install to Inactive Slot (After OTA)</string>
|
||||||
|
<string name="install_inactive_slot_warning">Your device will be **FORCED** to boot to the current inactive slot after a reboot!\nOnly use this option after OTA is done.\nContinue?</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
BIN
userspace/ksud/bin/aarch64/bootctl
Normal file
BIN
userspace/ksud/bin/aarch64/bootctl
Normal file
Binary file not shown.
BIN
userspace/ksud/bin/aarch64/ksuinit
Executable file
BIN
userspace/ksud/bin/aarch64/ksuinit
Executable file
Binary file not shown.
BIN
userspace/ksud/bin/x86_64/ksuinit
Executable file
BIN
userspace/ksud/bin/x86_64/ksuinit
Executable file
Binary file not shown.
@@ -1,29 +1,38 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use const_format::concatcp;
|
use const_format::concatcp;
|
||||||
use rust_embed::RustEmbed;
|
use rust_embed::RustEmbed;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
use crate::{defs::BINARY_DIR, utils};
|
use crate::{defs::BINARY_DIR, utils};
|
||||||
|
|
||||||
pub const RESETPROP_PATH: &str = concatcp!(BINARY_DIR, "resetprop");
|
pub const RESETPROP_PATH: &str = concatcp!(BINARY_DIR, "resetprop");
|
||||||
pub const BUSYBOX_PATH: &str = concatcp!(BINARY_DIR, "busybox");
|
pub const BUSYBOX_PATH: &str = concatcp!(BINARY_DIR, "busybox");
|
||||||
|
pub const BOOTCTL_PATH: &str = concatcp!(BINARY_DIR, "bootctl");
|
||||||
|
|
||||||
#[cfg(target_arch = "aarch64")]
|
#[cfg(all(target_arch = "x86_64", target_os = "android"))]
|
||||||
#[derive(RustEmbed)]
|
|
||||||
#[folder = "bin/aarch64"]
|
|
||||||
struct Asset;
|
|
||||||
|
|
||||||
#[cfg(target_arch = "x86_64")]
|
|
||||||
#[derive(RustEmbed)]
|
#[derive(RustEmbed)]
|
||||||
#[folder = "bin/x86_64"]
|
#[folder = "bin/x86_64"]
|
||||||
struct Asset;
|
struct Asset;
|
||||||
|
|
||||||
|
// IF NOT x86_64 ANDROID, ie. macos, linux, windows, always use aarch64
|
||||||
|
#[cfg(not(all(target_arch = "x86_64", target_os = "android")))]
|
||||||
|
#[derive(RustEmbed)]
|
||||||
|
#[folder = "bin/aarch64"]
|
||||||
|
struct Asset;
|
||||||
|
|
||||||
pub fn ensure_binaries(ignore_if_exist: bool) -> Result<()> {
|
pub fn ensure_binaries(ignore_if_exist: bool) -> Result<()> {
|
||||||
for file in Asset::iter() {
|
for file in Asset::iter() {
|
||||||
utils::ensure_binary(
|
if file == "ksuinit" {
|
||||||
format!("{BINARY_DIR}{file}"),
|
continue;
|
||||||
&Asset::get(&file).unwrap().data,
|
}
|
||||||
ignore_if_exist,
|
let asset = Asset::get(&file).ok_or(anyhow::anyhow!("asset not found: {}", file))?;
|
||||||
)?
|
utils::ensure_binary(format!("{BINARY_DIR}{file}"), &asset.data, ignore_if_exist)?
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn copy_assets_to_file(name: &str, dst: impl AsRef<Path>) -> Result<()> {
|
||||||
|
let asset = Asset::get(name).ok_or(anyhow::anyhow!("asset not found: {}", name))?;
|
||||||
|
std::fs::write(dst, asset.data)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,27 +1,52 @@
|
|||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
use std::os::unix::fs::PermissionsExt;
|
use std::os::unix::fs::PermissionsExt;
|
||||||
|
|
||||||
|
use anyhow::anyhow;
|
||||||
use anyhow::bail;
|
use anyhow::bail;
|
||||||
use anyhow::ensure;
|
use anyhow::ensure;
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use is_executable::IsExecutable;
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
use std::process::Stdio;
|
use std::process::Stdio;
|
||||||
|
use which::which;
|
||||||
|
|
||||||
use crate::utils;
|
use crate::{assets, utils};
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(target_os = "android")]
|
||||||
fn ensure_gki_kernel() -> Result<()> {
|
fn ensure_gki_kernel() -> Result<()> {
|
||||||
let version =
|
let version = get_kernel_version()?;
|
||||||
procfs::sys::kernel::Version::current().with_context(|| "get kernel version failed")?;
|
let is_gki = version.0 == 5 && version.1 >= 10 || version.2 > 5;
|
||||||
let is_gki = version.major == 5 && version.minor >= 10 || version.major > 5;
|
|
||||||
ensure!(is_gki, "only support GKI kernel");
|
ensure!(is_gki, "only support GKI kernel");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "android")]
|
||||||
|
pub fn get_kernel_version() -> Result<(i32, i32, i32)> {
|
||||||
|
use regex::Regex;
|
||||||
|
let uname = rustix::system::uname();
|
||||||
|
let version = uname.release().to_string_lossy();
|
||||||
|
let re = Regex::new(r"(\d+)\.(\d+)\.(\d+)")?;
|
||||||
|
if let Some(captures) = re.captures(&version) {
|
||||||
|
let major = captures
|
||||||
|
.get(1)
|
||||||
|
.and_then(|m| m.as_str().parse::<i32>().ok())
|
||||||
|
.ok_or_else(|| anyhow!("Major version parse error"))?;
|
||||||
|
let minor = captures
|
||||||
|
.get(2)
|
||||||
|
.and_then(|m| m.as_str().parse::<i32>().ok())
|
||||||
|
.ok_or_else(|| anyhow!("Minor version parse error"))?;
|
||||||
|
let patch = captures
|
||||||
|
.get(3)
|
||||||
|
.and_then(|m| m.as_str().parse::<i32>().ok())
|
||||||
|
.ok_or_else(|| anyhow!("Patch version parse error"))?;
|
||||||
|
Ok((major, minor, patch))
|
||||||
|
} else {
|
||||||
|
Err(anyhow!("Invalid kernel version string"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn do_cpio_cmd(magiskboot: &Path, workding_dir: &Path, cmd: &str) -> Result<()> {
|
fn do_cpio_cmd(magiskboot: &Path, workding_dir: &Path, cmd: &str) -> Result<()> {
|
||||||
let status = Command::new(magiskboot)
|
let status = Command::new(magiskboot)
|
||||||
.current_dir(workding_dir)
|
.current_dir(workding_dir)
|
||||||
@@ -63,8 +88,28 @@ pub fn patch(
|
|||||||
out: Option<PathBuf>,
|
out: Option<PathBuf>,
|
||||||
magiskboot_path: Option<PathBuf>,
|
magiskboot_path: Option<PathBuf>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
|
let result = do_patch(image, kernel, kmod, init, ota, flash, out, magiskboot_path);
|
||||||
|
if let Err(ref e) = result {
|
||||||
|
println!("- Install Error: {e}");
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
fn do_patch(
|
||||||
|
image: Option<PathBuf>,
|
||||||
|
kernel: Option<PathBuf>,
|
||||||
|
kmod: Option<PathBuf>,
|
||||||
|
init: Option<PathBuf>,
|
||||||
|
ota: bool,
|
||||||
|
flash: bool,
|
||||||
|
out: Option<PathBuf>,
|
||||||
|
magiskboot_path: Option<PathBuf>,
|
||||||
|
) -> Result<()> {
|
||||||
|
println!(include_str!("banner"));
|
||||||
|
|
||||||
if image.is_none() {
|
if image.is_none() {
|
||||||
#[cfg(unix)]
|
#[cfg(target_os = "android")]
|
||||||
ensure_gki_kernel()?;
|
ensure_gki_kernel()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,19 +121,17 @@ pub fn patch(
|
|||||||
"init and module must not be specified."
|
"init and module must not be specified."
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
ensure!(
|
ensure!(kmod.is_some(), "module must be specified");
|
||||||
init.is_some() && kmod.is_some(),
|
|
||||||
"init and module must be specified"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let workding_dir = tempdir::TempDir::new("KernelSU")?;
|
let workding_dir =
|
||||||
|
tempdir::TempDir::new("KernelSU").with_context(|| "create temp dir failed")?;
|
||||||
|
|
||||||
let bootimage;
|
let bootimage;
|
||||||
|
|
||||||
let mut bootdevice = None;
|
let mut bootdevice = None;
|
||||||
|
|
||||||
if let Some(image) = image {
|
if let Some(ref image) = image {
|
||||||
ensure!(image.exists(), "boot image not found");
|
ensure!(image.exists(), "boot image not found");
|
||||||
bootimage = std::fs::canonicalize(image)?;
|
bootimage = std::fs::canonicalize(image)?;
|
||||||
} else {
|
} else {
|
||||||
@@ -111,7 +154,7 @@ pub fn patch(
|
|||||||
format!("/dev/block/by-name/boot{slot_suffix}")
|
format!("/dev/block/by-name/boot{slot_suffix}")
|
||||||
};
|
};
|
||||||
|
|
||||||
println!("bootdevice: {boot_partition}");
|
println!("- Bootdevice: {boot_partition}");
|
||||||
let tmp_boot_path = workding_dir.path().join("boot.img");
|
let tmp_boot_path = workding_dir.path().join("boot.img");
|
||||||
|
|
||||||
dd(&boot_partition, &tmp_boot_path)?;
|
dd(&boot_partition, &tmp_boot_path)?;
|
||||||
@@ -122,37 +165,56 @@ pub fn patch(
|
|||||||
bootdevice = Some(boot_partition);
|
bootdevice = Some(boot_partition);
|
||||||
};
|
};
|
||||||
|
|
||||||
println!("boot image: {bootimage:?}");
|
// try extract magiskboot/bootctl
|
||||||
|
let _ = assets::ensure_binaries(false);
|
||||||
|
|
||||||
let magiskboot = magiskboot_path
|
// extract magiskboot
|
||||||
.map(std::fs::canonicalize)
|
let magiskboot = {
|
||||||
.transpose()?
|
if which("magiskboot").is_ok() {
|
||||||
.unwrap_or_else(|| "magiskboot".into());
|
let _ = assets::ensure_binaries(true);
|
||||||
|
"magiskboot".into()
|
||||||
if !magiskboot.is_executable() {
|
} else {
|
||||||
|
// magiskboot is not in $PATH, use builtin or specified one
|
||||||
|
let magiskboot = if let Some(magiskboot_path) = magiskboot_path {
|
||||||
|
std::fs::canonicalize(magiskboot_path)?
|
||||||
|
} else {
|
||||||
|
let magiskboot_path = workding_dir.path().join("magiskboot");
|
||||||
|
assets::copy_assets_to_file("magiskboot", &magiskboot_path)
|
||||||
|
.with_context(|| "copy magiskboot failed")?;
|
||||||
|
magiskboot_path
|
||||||
|
};
|
||||||
|
ensure!(magiskboot.exists(), "{magiskboot:?} is not exist");
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
std::fs::set_permissions(&magiskboot, std::fs::Permissions::from_mode(0o755))
|
let _ = std::fs::set_permissions(&magiskboot, std::fs::Permissions::from_mode(0o755));
|
||||||
.with_context(|| "set magiskboot executable failed".to_string())?;
|
magiskboot
|
||||||
}
|
}
|
||||||
|
};
|
||||||
ensure!(magiskboot.exists(), "magiskboot not found");
|
|
||||||
|
|
||||||
if let Some(kernel) = kernel {
|
if let Some(kernel) = kernel {
|
||||||
std::fs::copy(kernel, workding_dir.path().join("kernel"))
|
std::fs::copy(kernel, workding_dir.path().join("kernel"))
|
||||||
.with_context(|| "copy kernel from failed".to_string())?;
|
.with_context(|| "copy kernel from failed".to_string())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let (Some(kmod), Some(init)) = (kmod, init) {
|
if let Some(kmod) = kmod {
|
||||||
|
println!("- Preparing assets");
|
||||||
|
|
||||||
std::fs::copy(kmod, workding_dir.path().join("kernelsu.ko"))
|
std::fs::copy(kmod, workding_dir.path().join("kernelsu.ko"))
|
||||||
.with_context(|| "copy kernel module failed".to_string())?;
|
.with_context(|| "copy kernel module failed".to_string())?;
|
||||||
|
let init_file = workding_dir.path().join("init");
|
||||||
|
if let Some(init) = init {
|
||||||
std::fs::copy(init, workding_dir.path().join("init"))
|
std::fs::copy(init, workding_dir.path().join("init"))
|
||||||
.with_context(|| "copy init failed".to_string())?;
|
.with_context(|| "copy init failed".to_string())?;
|
||||||
|
} else {
|
||||||
|
crate::assets::copy_assets_to_file("ksuinit", init_file)
|
||||||
|
.with_context(|| "copy ksuinit failed")?;
|
||||||
|
}
|
||||||
|
|
||||||
// magiskboot unpack boot.img
|
// magiskboot unpack boot.img
|
||||||
// magiskboot cpio ramdisk.cpio 'cp init init.real'
|
// magiskboot cpio ramdisk.cpio 'cp init init.real'
|
||||||
// magiskboot cpio ramdisk.cpio 'add 0755 ksuinit init'
|
// magiskboot cpio ramdisk.cpio 'add 0755 ksuinit init'
|
||||||
// magiskboot cpio ramdisk.cpio 'add 0755 <kmod> kernelsu.ko'
|
// magiskboot cpio ramdisk.cpio 'add 0755 <kmod> kernelsu.ko'
|
||||||
|
|
||||||
|
println!("- Unpacking boot image");
|
||||||
let status = Command::new(&magiskboot)
|
let status = Command::new(&magiskboot)
|
||||||
.current_dir(workding_dir.path())
|
.current_dir(workding_dir.path())
|
||||||
.stdout(Stdio::null())
|
.stdout(Stdio::null())
|
||||||
@@ -162,6 +224,10 @@ pub fn patch(
|
|||||||
.status()?;
|
.status()?;
|
||||||
ensure!(status.success(), "magiskboot unpack failed");
|
ensure!(status.success(), "magiskboot unpack failed");
|
||||||
|
|
||||||
|
let not_magisk = do_cpio_cmd(&magiskboot, workding_dir.path(), "test").is_ok();
|
||||||
|
ensure!(not_magisk, "Cannot work with Magisk patched image");
|
||||||
|
|
||||||
|
println!("- Adding KernelSU LKM");
|
||||||
let is_kernelsu_patched =
|
let is_kernelsu_patched =
|
||||||
do_cpio_cmd(&magiskboot, workding_dir.path(), "exists kernelsu.ko").is_ok();
|
do_cpio_cmd(&magiskboot, workding_dir.path(), "exists kernelsu.ko").is_ok();
|
||||||
if !is_kernelsu_patched {
|
if !is_kernelsu_patched {
|
||||||
@@ -180,6 +246,7 @@ pub fn patch(
|
|||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
println!("- Repacking boot image");
|
||||||
// magiskboot repack boot.img
|
// magiskboot repack boot.img
|
||||||
let status = Command::new(&magiskboot)
|
let status = Command::new(&magiskboot)
|
||||||
.current_dir(workding_dir.path())
|
.current_dir(workding_dir.path())
|
||||||
@@ -189,18 +256,25 @@ pub fn patch(
|
|||||||
.arg(bootimage.display().to_string())
|
.arg(bootimage.display().to_string())
|
||||||
.status()?;
|
.status()?;
|
||||||
ensure!(status.success(), "magiskboot repack failed");
|
ensure!(status.success(), "magiskboot repack failed");
|
||||||
|
let new_boot = workding_dir.path().join("new-boot.img");
|
||||||
|
|
||||||
let out = out.unwrap_or(std::env::current_dir()?);
|
if image.is_some() {
|
||||||
|
// if image is specified, write to output file
|
||||||
|
let output_dir = out.unwrap_or(std::env::current_dir()?);
|
||||||
let now = chrono::Utc::now();
|
let now = chrono::Utc::now();
|
||||||
let output_image = out.join(format!(
|
let output_image =
|
||||||
"kernelsu_patched_boot_{}.img",
|
output_dir.join(format!("kernelsu_boot_{}.img", now.format("%Y%m%d_%H%M%S")));
|
||||||
now.format("%Y%m%d_%H%M%S")
|
|
||||||
));
|
if std::fs::rename(&new_boot, &output_image).is_err() {
|
||||||
std::fs::copy(workding_dir.path().join("new-boot.img"), &output_image)
|
std::fs::copy(&new_boot, &output_image)
|
||||||
.with_context(|| "copy out new boot failed".to_string())?;
|
.with_context(|| "copy out new boot failed".to_string())?;
|
||||||
|
}
|
||||||
|
println!("- Output file is written to");
|
||||||
|
println!("- {}", output_image.display().to_string().trim_matches('"'));
|
||||||
|
}
|
||||||
|
|
||||||
if flash {
|
if flash {
|
||||||
|
println!("- Flashing new boot image");
|
||||||
let Some(bootdevice) = bootdevice else {
|
let Some(bootdevice) = bootdevice else {
|
||||||
bail!("boot device not found")
|
bail!("boot device not found")
|
||||||
};
|
};
|
||||||
@@ -210,7 +284,52 @@ pub fn patch(
|
|||||||
.status()?;
|
.status()?;
|
||||||
ensure!(status.success(), "set boot device rw failed");
|
ensure!(status.success(), "set boot device rw failed");
|
||||||
|
|
||||||
dd(&output_image, &bootdevice).with_context(|| "flash boot failed")?;
|
dd(&new_boot, &bootdevice).with_context(|| "flash boot failed")?;
|
||||||
|
|
||||||
|
if ota {
|
||||||
|
post_ota()?;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("- Done!");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn post_ota() -> Result<()> {
|
||||||
|
use crate::defs::ADB_DIR;
|
||||||
|
use assets::BOOTCTL_PATH;
|
||||||
|
let status = Command::new(BOOTCTL_PATH).arg("hal-info").status()?;
|
||||||
|
if !status.success() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let current_slot = Command::new(BOOTCTL_PATH)
|
||||||
|
.arg("get-current-slot")
|
||||||
|
.output()?
|
||||||
|
.stdout;
|
||||||
|
let current_slot = String::from_utf8(current_slot)?;
|
||||||
|
let current_slot = current_slot.trim();
|
||||||
|
let target_slot = if current_slot == "0" { 1 } else { 0 };
|
||||||
|
|
||||||
|
Command::new(BOOTCTL_PATH)
|
||||||
|
.arg(format!("set-active-boot-slot {target_slot}"))
|
||||||
|
.status()?;
|
||||||
|
|
||||||
|
let post_ota_sh = std::path::Path::new(ADB_DIR)
|
||||||
|
.join("post-fs-data.d")
|
||||||
|
.join("post_ota.sh");
|
||||||
|
|
||||||
|
let sh_content = format!(
|
||||||
|
r###"
|
||||||
|
{BOOTCTL_PATH} mark-boot-successful
|
||||||
|
rm -f {BOOTCTL_PATH}
|
||||||
|
rm -f /data/adb/post-fs-data.d/post_ota.sh
|
||||||
|
"###
|
||||||
|
);
|
||||||
|
|
||||||
|
std::fs::write(&post_ota_sh, sh_content)?;
|
||||||
|
#[cfg(unix)]
|
||||||
|
std::fs::set_permissions(post_ota_sh, std::fs::Permissions::from_mode(0o755))?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use android_logger::Config;
|
|||||||
#[cfg(target_os = "android")]
|
#[cfg(target_os = "android")]
|
||||||
use log::LevelFilter;
|
use log::LevelFilter;
|
||||||
|
|
||||||
use crate::{apk_sign, debug, defs, init_event, ksucalls, module, utils};
|
use crate::{apk_sign, assets, debug, defs, init_event, ksucalls, module, utils};
|
||||||
|
|
||||||
/// KernelSU userspace cli
|
/// KernelSU userspace cli
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
@@ -60,10 +60,10 @@ enum Commands {
|
|||||||
kernel: Option<PathBuf>,
|
kernel: Option<PathBuf>,
|
||||||
|
|
||||||
/// LKM module path to replace
|
/// LKM module path to replace
|
||||||
#[arg(short, long, requires("init"))]
|
#[arg(short, long)]
|
||||||
module: Option<PathBuf>,
|
module: Option<PathBuf>,
|
||||||
|
|
||||||
/// init to be replaced, if use LKM, this must be specified
|
/// init to be replaced
|
||||||
#[arg(short, long, requires("module"))]
|
#[arg(short, long, requires("module"))]
|
||||||
init: Option<PathBuf>,
|
init: Option<PathBuf>,
|
||||||
|
|
||||||
@@ -304,7 +304,7 @@ pub fn run() -> Result<()> {
|
|||||||
utils::copy_sparse_file(src, dst, punch_hole)?;
|
utils::copy_sparse_file(src, dst, punch_hole)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Debug::Test => todo!(),
|
Debug::Test => assets::ensure_binaries(false),
|
||||||
},
|
},
|
||||||
|
|
||||||
Commands::BootPatch {
|
Commands::BootPatch {
|
||||||
|
|||||||
@@ -79,12 +79,12 @@ fn set_identity(uid: u32, gid: u32, groups: &[u32]) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(unix))]
|
#[cfg(not(any(target_os = "linux", target_os = "android")))]
|
||||||
pub fn root_shell() -> Result<()> {
|
pub fn root_shell() -> Result<()> {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||||
pub fn root_shell() -> Result<()> {
|
pub fn root_shell() -> Result<()> {
|
||||||
// we are root now, this was set in kernel!
|
// we are root now, this was set in kernel!
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user