You've already forked KernelSU
mirror of
https://github.com/tiann/KernelSU.git
synced 2025-08-27 23:46:34 +00:00
Compare commits
242 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c4c597da9e | ||
|
|
e5617e236c | ||
|
|
b1af4ada60 | ||
|
|
284b962d64 | ||
|
|
86ff022dc6 | ||
|
|
dcbbbab11e | ||
|
|
14b2afe78d | ||
|
|
ba4ffa7598 | ||
|
|
92ae0e5460 | ||
|
|
1ace028cef | ||
|
|
43ca2b9831 | ||
|
|
ecd5af76ab | ||
|
|
be452a22f0 | ||
|
|
6d6f793c69 | ||
|
|
30abd9e310 | ||
|
|
d0e3b2672d | ||
|
|
3b8a3ca26f | ||
|
|
33a096da22 | ||
|
|
72ba3ba086 | ||
|
|
5dd430e6a6 | ||
|
|
77056a7100 | ||
|
|
76b9790ffb | ||
|
|
54d2962a0d | ||
|
|
7846b2a440 | ||
|
|
8bbfe0c26d | ||
|
|
5c67334889 | ||
|
|
0b1b73a05d | ||
|
|
203dc42e75 | ||
|
|
9f6e9f5db6 | ||
|
|
ee09b9f9f4 | ||
|
|
c534ef672e | ||
|
|
c34a5ae2a6 | ||
|
|
1c65048813 | ||
|
|
3b8d0b83d4 | ||
|
|
b2c39af069 | ||
|
|
5c3df7e7a5 | ||
|
|
ffa5a93c75 | ||
|
|
794b725928 | ||
|
|
ffc06525fb | ||
|
|
3fe99712ba | ||
|
|
765c2b7d1f | ||
|
|
f789bb8c53 | ||
|
|
745f109686 | ||
|
|
8ed3bd53ac | ||
|
|
40d7d62af2 | ||
|
|
09fb118d22 | ||
|
|
0c3731b0bd | ||
|
|
349fd09440 | ||
|
|
bd3773f32a | ||
|
|
572708c283 | ||
|
|
fafdacfc41 | ||
|
|
c3ba483b81 | ||
|
|
e309a03515 | ||
|
|
82a304e054 | ||
|
|
b76d973f3a | ||
|
|
237e477876 | ||
|
|
47bcccdce3 | ||
|
|
ba8ca1f9f2 | ||
|
|
4837f2101c | ||
|
|
37d2914611 | ||
|
|
849164e4de | ||
|
|
683ba112aa | ||
|
|
e743722449 | ||
|
|
a5ee2ef93b | ||
|
|
3e66f5e8cf | ||
|
|
83b0aed52a | ||
|
|
498763505a | ||
|
|
8cc4ad4d80 | ||
|
|
6ec0c25173 | ||
|
|
93bcd78f89 | ||
|
|
ebf6a52237 | ||
|
|
a2906093ec | ||
|
|
a161c318a1 | ||
|
|
3f1ee2f784 | ||
|
|
60de2e4a6e | ||
|
|
d5bb79edd5 | ||
|
|
7264a00813 | ||
|
|
cf21767975 | ||
|
|
8fbdd996de | ||
|
|
2c3dcae117 | ||
|
|
b024b5d006 | ||
|
|
d5bab2317e | ||
|
|
0c8b4a48de | ||
|
|
f9b3218ded | ||
|
|
acc37fb387 | ||
|
|
f50b4dfe34 | ||
|
|
9ce7351aaa | ||
|
|
c691a1adb2 | ||
|
|
9a2a21ec5d | ||
|
|
a9fd0aa132 | ||
|
|
198674d889 | ||
|
|
203a5683ac | ||
|
|
eeb8cda175 | ||
|
|
b268971323 | ||
|
|
051fc53a4f | ||
|
|
55602f1f16 | ||
|
|
42428345ff | ||
|
|
ca950d909b | ||
|
|
20ff530962 | ||
|
|
a5dbbf4881 | ||
|
|
e91b1fc89a | ||
|
|
4e35e4ae86 | ||
|
|
6d15cb7e33 | ||
|
|
095acad8a6 | ||
|
|
c187d1ad8a | ||
|
|
f6967d2cfb | ||
|
|
388d2b0b59 | ||
|
|
d9aecbcbca | ||
|
|
eabdf3e78c | ||
|
|
a1fb7c5fdf | ||
|
|
89394245b1 | ||
|
|
91f1eb2d6a | ||
|
|
ab5f6db54b | ||
|
|
626642af76 | ||
|
|
473f02396f | ||
|
|
aa4b1bf9d8 | ||
|
|
8e5a72fc35 | ||
|
|
7302653879 | ||
|
|
b2b563547c | ||
|
|
cc29ad151a | ||
|
|
a4a93d8945 | ||
|
|
3389cd0aea | ||
|
|
d26956ff72 | ||
|
|
66284bfbe3 | ||
|
|
14be75629b | ||
|
|
d77ab8dbff | ||
|
|
c2ac548ac7 | ||
|
|
ad4d8e939e | ||
|
|
aa7a00b299 | ||
|
|
1ff421365e | ||
|
|
0a12d0139d | ||
|
|
181ab4f545 | ||
|
|
3e29e98f2c | ||
|
|
a66c1de660 | ||
|
|
0c322a33bc | ||
|
|
cd33a6dd07 | ||
|
|
c1dceaf11f | ||
|
|
3181dd17bc | ||
|
|
c93fa1af59 | ||
|
|
9f4a8d3dfc | ||
|
|
4da829792f | ||
|
|
1a073224c3 | ||
|
|
175de861bf | ||
|
|
aa73c34db2 | ||
|
|
ed2176af8c | ||
|
|
49f7d56fdd | ||
|
|
71cc166f72 | ||
|
|
8ee00839dc | ||
|
|
6239662a7f | ||
|
|
216c2aa2cd | ||
|
|
70b8b43b48 | ||
|
|
430c2e709f | ||
|
|
b975950b07 | ||
|
|
ed42cf42d2 | ||
|
|
d80c282644 | ||
|
|
a05edb3872 | ||
|
|
43b8987b4b | ||
|
|
219ea1c458 | ||
|
|
bea93f6ad7 | ||
|
|
e4267848f0 | ||
|
|
bc5953b510 | ||
|
|
e41b7cd117 | ||
|
|
cab78e7893 | ||
|
|
199f5cc223 | ||
|
|
962649f7ca | ||
|
|
7b32c0e37b | ||
|
|
12f353a1ae | ||
|
|
c26e170c87 | ||
|
|
6a706de09e | ||
|
|
2a4675e25b | ||
|
|
0bc36b3299 | ||
|
|
dc902b16d4 | ||
|
|
86998a032e | ||
|
|
1727ec41c4 | ||
|
|
3eb812be5b | ||
|
|
64c2f6ba5c | ||
|
|
2db7da0766 | ||
|
|
d1e7bad18f | ||
|
|
d7cef25665 | ||
|
|
23f41145b0 | ||
|
|
a969af8159 | ||
|
|
001fa00355 | ||
|
|
a6dddd32b3 | ||
|
|
cd825e34da | ||
|
|
2bba088319 | ||
|
|
4905bd9bb8 | ||
|
|
3f625a000b | ||
|
|
3bfee10a0d | ||
|
|
d5a05da5b8 | ||
|
|
d8042a36c3 | ||
|
|
85bf01eb65 | ||
|
|
4f2b8b7077 | ||
|
|
417ff8a6c5 | ||
|
|
681c4a3f0d | ||
|
|
619dd1ace1 | ||
|
|
1cd18a643d | ||
|
|
3519d61636 | ||
|
|
46913671a8 | ||
|
|
b7ff6b1a51 | ||
|
|
62be9eb589 | ||
|
|
ab6d483c32 | ||
|
|
2d77f84736 | ||
|
|
c2f7963a43 | ||
|
|
b9e27621ee | ||
|
|
2766e24007 | ||
|
|
45911ac3c4 | ||
|
|
ad89c5a80f | ||
|
|
3413f4a4fe | ||
|
|
2a88cca50c | ||
|
|
634978c14f | ||
|
|
093f7d9758 | ||
|
|
40960f60f8 | ||
|
|
25a4c2930a | ||
|
|
342344db0c | ||
|
|
7785d2a3f8 | ||
|
|
bd6b0d3d12 | ||
|
|
a871b92dc9 | ||
|
|
47d15c47f3 | ||
|
|
684283c585 | ||
|
|
d2d9b0eaad | ||
|
|
22e6b1eec5 | ||
|
|
f6301c5a7c | ||
|
|
b191415173 | ||
|
|
59542bc99a | ||
|
|
0e4b5b3765 | ||
|
|
a986251773 | ||
|
|
0e651fdc99 | ||
|
|
b1e279bd2a | ||
|
|
e76cf08934 | ||
|
|
4cf930a9dc | ||
|
|
599723515e | ||
|
|
e1e8d53da5 | ||
|
|
20a2c0092e | ||
|
|
a40ad07e2b | ||
|
|
65d80aad03 | ||
|
|
905c041a76 | ||
|
|
0dae6ebaee | ||
|
|
d41954b09c | ||
|
|
6e8771b8d9 | ||
|
|
cf5bcc09e8 | ||
|
|
413a8d0a2f | ||
|
|
1852652029 |
33
.github/ISSUE_TEMPLATE/add_device.yml
vendored
Normal file
33
.github/ISSUE_TEMPLATE/add_device.yml
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
name: Contribute to Unofficially Supported Device
|
||||
description: Add your device kernel source to KernelSU's Unofficially Supported Device List
|
||||
title: "[Add Device]: "
|
||||
labels: ["add-device"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for supporting KernelSU !
|
||||
- type: input
|
||||
id: repo-url
|
||||
attributes:
|
||||
label: Repository URL
|
||||
description: Your repository URL
|
||||
placeholder: https://github.com/tiann/KernelSU
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: device
|
||||
attributes:
|
||||
label: Device
|
||||
description: Please describe the device maintained by you.
|
||||
placeholder: GKI 2.0 Device
|
||||
validations:
|
||||
required: true
|
||||
- type: checkboxes
|
||||
id: terms
|
||||
attributes:
|
||||
label: Code of Conduct
|
||||
description: By submitting this issue, you should be the maintainer of the repository.
|
||||
options:
|
||||
- label: I'm the maintainer of this repository
|
||||
required: true
|
||||
32
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
32
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Smartphone (please complete the following information):**
|
||||
- Device: [e.g. iPhone6]
|
||||
- OS: [e.g. iOS8.1]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
10
.github/ISSUE_TEMPLATE/custom.md
vendored
Normal file
10
.github/ISSUE_TEMPLATE/custom.md
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
---
|
||||
name: Custom issue template
|
||||
about: Describe this issue template's purpose here.
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
|
||||
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
56
.github/scripts/build_a12.sh
vendored
Normal file
56
.github/scripts/build_a12.sh
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
build_from_image() {
|
||||
export TITLE
|
||||
TITLE=kernel-aarch64-${1//Image-/}
|
||||
echo "[+] title: $TITLE"
|
||||
|
||||
export PATCH_LEVEL
|
||||
PATCH_LEVEL=$(echo "$1" | awk -F_ '{ print $2}')
|
||||
echo "[+] patch level: $PATCH_LEVEL"
|
||||
|
||||
echo '[+] Download prebuilt ramdisk'
|
||||
curl -Lo gki-kernel.zip https://dl.google.com/android/gki/gki-certified-boot-android12-5.10-"${PATCH_LEVEL}"_r1.zip
|
||||
unzip gki-kernel.zip && rm gki-kernel.zip
|
||||
|
||||
echo '[+] Unpack prebuilt boot.img'
|
||||
BOOT_IMG=$(find . -maxdepth 1 -name "boot*.img")
|
||||
$UNPACK_BOOTIMG --boot_img="$BOOT_IMG"
|
||||
rm "$BOOT_IMG"
|
||||
|
||||
echo '[+] Building Image.gz'
|
||||
$GZIP -n -k -f -9 Image >Image.gz
|
||||
|
||||
echo '[+] Building boot.img'
|
||||
$MKBOOTIMG --header_version 4 --kernel Image --output boot.img --ramdisk out/ramdisk --os_version 12.0.0 --os_patch_level "${PATCH_LEVEL}"
|
||||
$AVBTOOL add_hash_footer --partition_name boot --partition_size $((64 * 1024 * 1024)) --image boot.img --algorithm SHA256_RSA2048 --key ../kernel-build-tools/linux-x86/share/avb/testkey_rsa2048.pem
|
||||
|
||||
echo '[+] Building boot-gz.img'
|
||||
$MKBOOTIMG --header_version 4 --kernel Image.gz --output boot-gz.img --ramdisk out/ramdisk --os_version 12.0.0 --os_patch_level "${PATCH_LEVEL}"
|
||||
$AVBTOOL add_hash_footer --partition_name boot --partition_size $((64 * 1024 * 1024)) --image boot-gz.img --algorithm SHA256_RSA2048 --key ../kernel-build-tools/linux-x86/share/avb/testkey_rsa2048.pem
|
||||
|
||||
echo '[+] Building boot-lz4.img'
|
||||
$MKBOOTIMG --header_version 4 --kernel Image.lz4 --output boot-lz4.img --ramdisk out/ramdisk --os_version 12.0.0 --os_patch_level "${PATCH_LEVEL}"
|
||||
$AVBTOOL add_hash_footer --partition_name boot --partition_size $((64 * 1024 * 1024)) --image boot-lz4.img --algorithm SHA256_RSA2048 --key ../kernel-build-tools/linux-x86/share/avb/testkey_rsa2048.pem
|
||||
|
||||
echo '[+] Compress images'
|
||||
for image in boot*.img; do
|
||||
$GZIP -n -f -9 "$image"
|
||||
mv "$image".gz ksu-"$VERSION"-"$1"-"$image".gz
|
||||
done
|
||||
|
||||
echo "[+] Images to upload"
|
||||
find . -type f -name "*.gz"
|
||||
|
||||
find . -type f -name "*.gz" -exec python3 "$GITHUB_WORKSPACE"/KernelSU/scripts/ksubot.py {} +
|
||||
}
|
||||
|
||||
for dir in Image*; do
|
||||
if [ -d "$dir" ]; then
|
||||
echo "----- Building $dir -----"
|
||||
cd "$dir"
|
||||
build_from_image "$dir"
|
||||
cd ..
|
||||
fi
|
||||
done
|
||||
43
.github/scripts/build_a13.sh
vendored
Normal file
43
.github/scripts/build_a13.sh
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
build_from_image() {
|
||||
export TITLE
|
||||
TITLE=kernel-aarch64-${1//Image-/}
|
||||
|
||||
echo "[+] title: $TITLE"
|
||||
echo '[+] Building Image.gz'
|
||||
$GZIP -n -k -f -9 Image >Image.gz
|
||||
|
||||
echo '[+] Building boot.img'
|
||||
$MKBOOTIMG --header_version 4 --kernel Image --output boot.img
|
||||
$AVBTOOL add_hash_footer --partition_name boot --partition_size $((64 * 1024 * 1024)) --image boot.img --algorithm SHA256_RSA2048 --key ../kernel-build-tools/linux-x86/share/avb/testkey_rsa2048.pem
|
||||
|
||||
echo '[+] Building boot-gz.img'
|
||||
$MKBOOTIMG --header_version 4 --kernel Image.gz --output boot-gz.img
|
||||
$AVBTOOL add_hash_footer --partition_name boot --partition_size $((64 * 1024 * 1024)) --image boot-gz.img --algorithm SHA256_RSA2048 --key ../kernel-build-tools/linux-x86/share/avb/testkey_rsa2048.pem
|
||||
|
||||
echo '[+] Building boot-lz4.img'
|
||||
$MKBOOTIMG --header_version 4 --kernel Image.lz4 --output boot-lz4.img
|
||||
$AVBTOOL add_hash_footer --partition_name boot --partition_size $((64 * 1024 * 1024)) --image boot-lz4.img --algorithm SHA256_RSA2048 --key ../kernel-build-tools/linux-x86/share/avb/testkey_rsa2048.pem
|
||||
|
||||
echo '[+] Compress images'
|
||||
for image in boot*.img; do
|
||||
$GZIP -n -f -9 "$image"
|
||||
mv "$image".gz ksu-"$VERSION"-"$1"-"$image".gz
|
||||
done
|
||||
|
||||
echo '[+] Images to upload'
|
||||
find . -type f -name "*.gz"
|
||||
|
||||
find . -type f -name "*.gz" -exec python3 "$GITHUB_WORKSPACE"/KernelSU/scripts/ksubot.py {} +
|
||||
}
|
||||
|
||||
for dir in Image*; do
|
||||
if [ -d "$dir" ]; then
|
||||
echo "----- Building $dir -----"
|
||||
cd "$dir"
|
||||
build_from_image "$dir"
|
||||
cd ..
|
||||
fi
|
||||
done
|
||||
59
.github/workflows/add-device.yml
vendored
Normal file
59
.github/workflows/add-device.yml
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
name: handle-add-device-issue
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [labeled]
|
||||
|
||||
jobs:
|
||||
handle-add-device:
|
||||
if: github.event.label.name == 'add-device'
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
ISSUE_CONTENT: ${{ github.event.issue.body }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Parse issue body
|
||||
id: handle-add-device
|
||||
run: |
|
||||
python3 scripts/add_device_handler.py website/docs/repos.json || true
|
||||
- name: Commit
|
||||
if: steps.handle-add-device.outputs.success == 'true'
|
||||
run: |
|
||||
git config --local user.name "GitHub Actions"
|
||||
git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
git add website/docs/repos.json
|
||||
git commit -m "add device: ${{ steps.handle-add-device.outputs.device }}"
|
||||
- name: Make pull request
|
||||
if: steps.handle-add-device.outputs.success == 'true'
|
||||
id: cpr
|
||||
uses: peter-evans/create-pull-request@v4
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
commit-message: "[add device]: ${{ steps.handle-add-device.outputs.device }}"
|
||||
title: "[add device]: ${{ steps.handle-add-device.outputs.device }}"
|
||||
body: |
|
||||
${{ steps.handle-add-device.outputs.device }} has been added to the website.
|
||||
Related issue: ${{ github.event.issue.html_url }}
|
||||
branch: "add-device-${{ github.event.issue.number }}"
|
||||
labels: add-device
|
||||
delete-branch: true
|
||||
- name: Check outputs
|
||||
if: ${{ steps.cpr.outputs.pull-request-number }}
|
||||
run: |
|
||||
echo "Pull Request Number - ${{ steps.cpr.outputs.pull-request-number }}"
|
||||
echo "Pull Request URL - ${{ steps.cpr.outputs.pull-request-url }}"
|
||||
- uses: ben-z/actions-comment-on-issue@1.0.2
|
||||
if: ${{ steps.cpr.outputs.pull-request-number }}
|
||||
with:
|
||||
message: "Automatically created pull request: ${{ steps.cpr.outputs.pull-request-url }}"
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- uses: ben-z/actions-comment-on-issue@1.0.2
|
||||
if: steps.handle-add-device.outputs.success != 'true'
|
||||
with:
|
||||
message: "Cannot create pull request. Please check the issue content. Or you can create a pull request manually."
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: close issue
|
||||
uses: peter-evans/close-issue@v1
|
||||
with:
|
||||
issue-number: ${{ github.event.issue.number }}
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
157
.github/workflows/build-WSA-5.10.117-kernel.yml
vendored
157
.github/workflows/build-WSA-5.10.117-kernel.yml
vendored
@@ -1,98 +1,103 @@
|
||||
name: Build WSA-5.10.117-Kernel
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
paths:
|
||||
- '.github/workflows/build-WSA-5.10.117-kernel.yml'
|
||||
- 'kernel/**'
|
||||
branches: ["main"]
|
||||
paths:
|
||||
- ".github/workflows/build-WSA-5.10.117-kernel.yml"
|
||||
- "kernel/**"
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
paths:
|
||||
- 'kernel/**'
|
||||
branches: ["main"]
|
||||
paths:
|
||||
- "kernel/**"
|
||||
workflow_call:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
arch: [x86_64, arm64]
|
||||
version: [5.10.117.2]
|
||||
include:
|
||||
- version: 5.10.117.2
|
||||
arch: "x86_64"
|
||||
out_file: "arch/x86/boot/bzImage"
|
||||
kernel_make_cmd: "bzImage"
|
||||
- file_name: "bzImage"
|
||||
make_config: "config-wsa"
|
||||
date: "20220906"
|
||||
- version: 5.10.117.2
|
||||
arch: "arm64"
|
||||
out_file: "arch/arm64/boot/Image"
|
||||
kernel_make_cmd: "ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu Image"
|
||||
arch: x86_64
|
||||
- file_name: "Image"
|
||||
cross_compile: "aarch64-linux-gnu"
|
||||
make_config: "config-wsa-arm64"
|
||||
date: "20220906"
|
||||
arch: arm64
|
||||
|
||||
name: Build WSA-Kernel-${{ matrix.version }}-${{ matrix.arch }}
|
||||
runs-on: ubuntu-18.04
|
||||
runs-on: ubuntu-20.04
|
||||
env:
|
||||
CCACHE_COMPILERCHECK: "%compiler% -dumpmachine; %compiler% -dumpversion"
|
||||
CCACHE_NOHASHDIR: "true"
|
||||
CCACHE_MAXSIZE: "2G"
|
||||
CCACHE_HARDLINK: "true"
|
||||
|
||||
steps:
|
||||
- uses: hendrikmuhs/ccache-action@v1.2
|
||||
with:
|
||||
key: ccache-WSA-Kernel-${{ matrix.version }}-${{ matrix.arch }}
|
||||
append-timestamp: false
|
||||
save: ${{ github.event_name != 'pull_request' }}
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
path: KernelSU
|
||||
- name: Install Build Tools
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt install -y --no-install-recommends bc bison build-essential ca-certificates flex git gnupg libelf-dev libssl-dev lsb-release software-properties-common wget libncurses-dev binutils-aarch64-linux-gnu gcc-aarch64-linux-gnu nuget
|
||||
export LLVM_VERSION=12
|
||||
wget https://apt.llvm.org/llvm.sh
|
||||
chmod +x llvm.sh
|
||||
sudo ./llvm.sh $LLVM_VERSION
|
||||
rm ./llvm.sh
|
||||
sudo ln -s --force /usr/bin/clang-$LLVM_VERSION /usr/bin/clang
|
||||
sudo ln -s --force /usr/bin/ld.lld-$LLVM_VERSION /usr/bin/ld.lld
|
||||
sudo ln -s --force /usr/bin/llvm-objdump-$LLVM_VERSION /usr/bin/llvm-objdump
|
||||
sudo ln -s --force /usr/bin/llvm-ar-$LLVM_VERSION /usr/bin/llvm-ar
|
||||
sudo ln -s --force /usr/bin/llvm-nm-$LLVM_VERSION /usr/bin/llvm-nm
|
||||
sudo ln -s --force /usr/bin/llvm-strip-$LLVM_VERSION /usr/bin/llvm-strip
|
||||
sudo ln -s --force /usr/bin/llvm-objcopy-$LLVM_VERSION /usr/bin/llvm-objcopy
|
||||
sudo ln -s --force /usr/bin/llvm-readelf-$LLVM_VERSION /usr/bin/llvm-readelf
|
||||
sudo ln -s --force /usr/bin/clang++-$LLVM_VERSION /usr/bin/clang++
|
||||
|
||||
- name: Install LLVM
|
||||
run: |
|
||||
sudo apt install -y --no-install-recommends bc bison build-essential ca-certificates flex git gnupg libelf-dev libssl-dev lsb-release software-properties-common wget libncurses-dev binutils-aarch64-linux-gnu gcc-aarch64-linux-gnu nuget
|
||||
export LLVM_VERSION=10
|
||||
wget https://apt.llvm.org/llvm.sh
|
||||
chmod +x llvm.sh
|
||||
sudo ./llvm.sh $LLVM_VERSION
|
||||
rm ./llvm.sh
|
||||
sudo ln -s --force /usr/bin/clang-$LLVM_VERSION /usr/bin/clang
|
||||
sudo ln -s --force /usr/bin/ld.lld-$LLVM_VERSION /usr/bin/ld.lld
|
||||
sudo ln -s --force /usr/bin/llvm-objdump-$LLVM_VERSION /usr/bin/llvm-objdump
|
||||
sudo ln -s --force /usr/bin/llvm-ar-$LLVM_VERSION /usr/bin/llvm-ar
|
||||
sudo ln -s --force /usr/bin/llvm-nm-$LLVM_VERSION /usr/bin/llvm-nm
|
||||
sudo ln -s --force /usr/bin/llvm-strip-$LLVM_VERSION /usr/bin/llvm-strip
|
||||
sudo ln -s --force /usr/bin/llvm-objcopy-$LLVM_VERSION /usr/bin/llvm-objcopy
|
||||
sudo ln -s --force /usr/bin/llvm-readelf-$LLVM_VERSION /usr/bin/llvm-readelf
|
||||
sudo ln -s --force /usr/bin/clang++-$LLVM_VERSION /usr/bin/clang++
|
||||
- name: Checkout KernelSU
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
path: KernelSU
|
||||
ref: main
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Download kernel source
|
||||
run: |
|
||||
cd $GITHUB_WORKSPACE
|
||||
KERNEL_ROOT=$GITHUB_WORKSPACE/WSA-Linux-Kernel
|
||||
echo "[+] 克隆远程仓库 WSA-Linux-Kernel..."
|
||||
git clone https://github.com/microsoft/WSA-Linux-Kernel
|
||||
cd WSA-Linux-Kernel && git checkout android-lts/latte/${{ matrix.version }}
|
||||
echo "[+] 切换到分支 android-lts/latte/${{ matrix.version }}"
|
||||
echo "[+] 导入 KernelSU"
|
||||
echo "[+] KERNEL_ROOT: $KERNEL_ROOT"
|
||||
echo "[+] 复制 kernel su driver 到路径:$KERNEL_ROOT/drivers"
|
||||
ln -sf $GITHUB_WORKSPACE/KernelSU/kernel $KERNEL_ROOT/drivers/kernelsu
|
||||
DRIVER_MAKEFILE=$KERNEL_ROOT/drivers/Makefile
|
||||
echo "[+] 添加 kernel su driver 到文件:$DRIVER_MAKEFILE"
|
||||
grep -q "kernelsu" $DRIVER_MAKEFILE || echo "obj-y += kernelsu/" >> $DRIVER_MAKEFILE
|
||||
echo "[+] KernelSU 导入完成"
|
||||
cd $KERNEL_ROOT && git apply $GITHUB_WORKSPACE/KernelSU/.github/patches/5.10/*.patch
|
||||
cd -
|
||||
|
||||
- name: Build Kernel
|
||||
working-directory: WSA-Linux-Kernel
|
||||
run: |
|
||||
KERNEL_ROOT=$GITHUB_WORKSPACE/WSA-Linux-Kernel
|
||||
echo "[+] 构建 kernel"
|
||||
cp configs/wsa/${{ matrix.make_config }}-5.10 $KERNEL_ROOT/.config
|
||||
echo "[+] 复制配置文件 configs/wsa/${{ matrix.make_config }}-5.10 到 $KERNEL_ROOT/.config"
|
||||
echo "执行: make -j`nproc` LLVM=1 ${{ matrix.kernel_make_cmd }}"
|
||||
make -j`nproc` LLVM=1 ${{ matrix.kernel_make_cmd }} CCACHE="/usr/bin/ccache"
|
||||
- name: Setup kernel source
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: microsoft/WSA-Linux-Kernel
|
||||
ref: android-lts/latte/${{ matrix.version }}
|
||||
path: WSA-Linux-Kernel
|
||||
|
||||
- name: Upload kernel-${{ matrix.arch }}-${{ matrix.version }}
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: kernel-WSA-${{ matrix.arch }}-${{ matrix.version }}-${{ matrix.date }}
|
||||
path: WSA-Linux-Kernel/${{ matrix.out_file }}
|
||||
- name: Setup Ccache
|
||||
uses: hendrikmuhs/ccache-action@v1.2
|
||||
with:
|
||||
key: WSA-Kernel-${{ matrix.version }}-${{ matrix.arch }}
|
||||
save: ${{ github.event_name != 'pull_request' }}
|
||||
|
||||
- name: Setup KernelSU
|
||||
working-directory: WSA-Linux-Kernel
|
||||
run: |
|
||||
echo "[+] KernelSU setup"
|
||||
KERNEL_ROOT=$GITHUB_WORKSPACE/WSA-Linux-Kernel
|
||||
echo "[+] KERNEL_ROOT: $KERNEL_ROOT"
|
||||
echo "[+] Copy KernelSU driver to $KERNEL_ROOT/drivers"
|
||||
ln -sf $GITHUB_WORKSPACE/KernelSU/kernel $KERNEL_ROOT/drivers/kernelsu
|
||||
echo "[+] Add KernelSU driver to Makefile"
|
||||
DRIVER_MAKEFILE=$KERNEL_ROOT/drivers/Makefile
|
||||
grep -q "kernelsu" $DRIVER_MAKEFILE || echo "obj-y += kernelsu/" >> $DRIVER_MAKEFILE
|
||||
echo "[+] Apply KernelSU patches"
|
||||
cd $KERNEL_ROOT && git apply $GITHUB_WORKSPACE/KernelSU/.github/patches/5.10/*.patch
|
||||
echo "[+] KernelSU setup done."
|
||||
|
||||
- name: Build Kernel
|
||||
working-directory: WSA-Linux-Kernel
|
||||
run: |
|
||||
cp configs/wsa/${{ matrix.make_config }}-5.10 .config
|
||||
make -j`nproc` LLVM=1 ARCH=${{ matrix.arch }} CROSS_COMPILE=${{ matrix.cross_compile }} ${{ matrix.file_name }} CCACHE="/usr/bin/ccache"
|
||||
|
||||
- name: Upload kernel-${{ matrix.arch }}-${{ matrix.version }}
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: kernel-WSA-${{ matrix.arch }}-${{ matrix.version }}
|
||||
path: WSA-Linux-Kernel/arch/${{ matrix.arch }}/boot/${{ matrix.file_name }}
|
||||
|
||||
186
.github/workflows/build-kernel-5.10.yml
vendored
186
.github/workflows/build-kernel-5.10.yml
vendored
@@ -1,186 +0,0 @@
|
||||
name: Build Kernel 5.10
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
paths:
|
||||
- '.github/workflows/build-kernel-5.10.yml'
|
||||
- 'kernel/**'
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
paths:
|
||||
- 'kernel/**'
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- version: android12-5.10-66
|
||||
tag: android12-5.10-2021-11
|
||||
os_version: 12.0.0
|
||||
os_patch_level: 2021-11
|
||||
- version: android12-5.10-81
|
||||
tag: android12-5.10-2022-03
|
||||
os_version: 12.0.0
|
||||
os_patch_level: 2022-03
|
||||
- version: android12-5.10-101
|
||||
tag: android12-5.10-2022-05
|
||||
os_version: 12.0.0
|
||||
os_patch_level: 2022-05
|
||||
- version: android12-5.10-110
|
||||
tag: android12-5.10-2022-07
|
||||
os_version: 12.0.0
|
||||
os_patch_level: 2022-07
|
||||
- version: android12-5.10-136
|
||||
tag: android12-5.10-2022-11
|
||||
os_version: 12.0.0
|
||||
os_patch_level: 2022-11
|
||||
|
||||
name: Build aarch64-${{ matrix.version }}
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CCACHE_COMPILERCHECK: "%compiler% -dumpmachine; %compiler% -dumpversion"
|
||||
CCACHE_NOHASHDIR: "true"
|
||||
CCACHE_MAXSIZE: "2G"
|
||||
CCACHE_HARDLINK: "true"
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
path: KernelSU
|
||||
fetch-depth: 0
|
||||
- uses: hendrikmuhs/ccache-action@v1.2
|
||||
with:
|
||||
key: ccache-aarch64-${{ matrix.version }}
|
||||
append-timestamp: false
|
||||
save: ${{ github.event_name != 'pull_request' }}
|
||||
|
||||
- name: Setup need_upload
|
||||
id: need_upload
|
||||
run: |
|
||||
if [ ! -z "${{ secrets.BOT_TOKEN }}" ]; then
|
||||
echo "UPLOAD=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "UPLOAD=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Setup kernel source
|
||||
run: |
|
||||
cd $GITHUB_WORKSPACE
|
||||
git clone https://gerrit.googlesource.com/git-repo
|
||||
mkdir android-kernel && cd android-kernel
|
||||
../git-repo/repo init --depth=1 --u https://android.googlesource.com/kernel/manifest -b common-${{ matrix.tag }}
|
||||
../git-repo/repo sync -j$(nproc --all)
|
||||
curl -Lo gki-kernel.zip https://dl.google.com/android/gki/gki-certified-boot-${{ matrix.tag }}_r1.zip
|
||||
unzip gki-kernel.zip
|
||||
tools/mkbootimg/unpack_bootimg.py --boot_img=$(find . -maxdepth 1 -name "*.img")
|
||||
|
||||
- name: Setup KernelSU
|
||||
env:
|
||||
PATCH_PATH: "5.10"
|
||||
run: |
|
||||
cd $GITHUB_WORKSPACE/android-kernel
|
||||
echo "[+] KernelSU setup"
|
||||
GKI_ROOT=$(pwd)
|
||||
echo "[+] GKI_ROOT: $GKI_ROOT"
|
||||
echo "[+] Copy kernel su driver to $GKI_ROOT/common/drivers"
|
||||
ln -sf $GITHUB_WORKSPACE/KernelSU/kernel $GKI_ROOT/common/drivers/kernelsu
|
||||
echo "[+] Add kernel su driver to Makefile"
|
||||
DRIVER_MAKEFILE=$GKI_ROOT/common/drivers/Makefile
|
||||
grep -q "kernelsu" $DRIVER_MAKEFILE || echo "obj-y += kernelsu/" >> $DRIVER_MAKEFILE
|
||||
echo "[+] Apply KernelSU patches"
|
||||
cd $GKI_ROOT/common/ && git apply $GITHUB_WORKSPACE/KernelSU/.github/patches/$PATCH_PATH/*.patch
|
||||
cd -
|
||||
echo "[+] KernelSU setup Done."
|
||||
|
||||
- name: Symbol magic
|
||||
run: |
|
||||
echo "[+] Export all symbol from abi_gki_aarch64.xml"
|
||||
COMMON_ROOT=$GITHUB_WORKSPACE/android-kernel/common
|
||||
KSU_ROOT=$GITHUB_WORKSPACE/KernelSU
|
||||
ABI_XML=$COMMON_ROOT/android/abi_gki_aarch64.xml
|
||||
SYMBOL_LIST=$COMMON_ROOT/android/abi_gki_aarch64
|
||||
# python3 $KSU_ROOT/scripts/abi_gki_all.py $ABI_XML > $SYMBOL_LIST
|
||||
echo "[+] Add KernelSU symbols"
|
||||
cat $KSU_ROOT/kernel/export_symbol.txt | awk '{sub("[ \t]+","");print " "$0}' >> $SYMBOL_LIST
|
||||
|
||||
- name: Set boot sign key
|
||||
if: ${{ ( github.event_name != 'pull_request' && github.ref == 'refs/heads/main' ) || github.ref_type == 'tag' }}
|
||||
working-directory: android-kernel
|
||||
env:
|
||||
BOOT_SIGN_KEY: ${{ secrets.BOOT_SIGN_KEY }}
|
||||
run: |
|
||||
if [ ! -z "$BOOT_SIGN_KEY" ]; then
|
||||
echo "$BOOT_SIGN_KEY" > prebuilts/kernel-build-tools/linux-x86/share/avb/testkey_rsa2048.pem
|
||||
fi
|
||||
|
||||
- name: Build boot.img
|
||||
working-directory: android-kernel
|
||||
run: CCACHE="/usr/bin/ccache" BUILD_BOOT_IMG=1 SKIP_VENDOR_BOOT=1 KERNEL_BINARY=Image GKI_RAMDISK_PREBUILT_BINARY=out/ramdisk AVB_SIGN_BOOT_IMG=1 AVB_BOOT_PARTITION_SIZE=$((64*1024*1024)) AVB_BOOT_ALGORITHM=SHA256_RSA2048 AVB_BOOT_KEY=prebuilts/kernel-build-tools/linux-x86/share/avb/testkey_rsa2048.pem BOOT_IMAGE_HEADER_VERSION=4 LTO=thin BUILD_CONFIG=common/build.config.gki.aarch64 build/build.sh
|
||||
|
||||
- name: Build boot-lz4.img
|
||||
working-directory: android-kernel
|
||||
run: |
|
||||
tools/mkbootimg/mkbootimg.py --header_version 4 --kernel ./out/android12-5.10/dist/Image.lz4 --ramdisk out/ramdisk --output ./out/android12-5.10/dist/boot-lz4.img --os_version ${{ matrix.os_version }} --os_patch_level ${{ matrix.os_patch_level }}
|
||||
./build/build-tools/path/linux-x86/avbtool add_hash_footer --partition_name boot --partition_size $((64*1024*1024)) --image out/android12-5.10/dist/boot-lz4.img --algorithm SHA256_RSA2048 --key ./prebuilts/kernel-build-tools/linux-x86/share/avb/testkey_rsa2048.pem
|
||||
|
||||
- name: Build boot-gz.img
|
||||
working-directory: android-kernel
|
||||
run: |
|
||||
cat out/android12-5.10/dist/Image | ./prebuilts/build-tools/path/linux-x86/gzip -n -f -9 > out/android12-5.10/dist/Image.gz
|
||||
tools/mkbootimg/mkbootimg.py --header_version 4 --kernel ./out/android12-5.10/dist/Image.gz --ramdisk out/ramdisk --output ./out/android12-5.10/dist/boot-gz.img --os_version ${{ matrix.os_version }} --os_patch_level ${{ matrix.os_patch_level }}
|
||||
./build/build-tools/path/linux-x86/avbtool add_hash_footer --partition_name boot --partition_size $((64*1024*1024)) --image out/android12-5.10/dist/boot-gz.img --algorithm SHA256_RSA2048 --key ./prebuilts/kernel-build-tools/linux-x86/share/avb/testkey_rsa2048.pem
|
||||
|
||||
- name: Upload Image.gz
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: kernel-aarch64-${{ matrix.version }}-Image.gz
|
||||
path: android-kernel/out/android12-5.10/dist/Image.gz
|
||||
|
||||
- name: Upload boot.img
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: kernel-aarch64-${{ matrix.version }}-boot.img
|
||||
path: android-kernel/out/android12-5.10/dist/boot.img
|
||||
|
||||
- name: Upload boot-lz4.img
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: kernel-aarch64-${{ matrix.version }}-boot-lz4.img
|
||||
path: android-kernel/out/android12-5.10/dist/boot-lz4.img
|
||||
|
||||
- name: Upload boot-gz.img
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: kernel-aarch64-${{ matrix.version }}-boot-gz.img
|
||||
path: android-kernel/out/android12-5.10/dist/boot-gz.img
|
||||
|
||||
- name: Setup mutex for uploading
|
||||
if: github.event_name != 'pull_request' && steps.need_upload.outputs.UPLOAD == 'true'
|
||||
uses: ben-z/gh-action-mutex@v1.0-alpha-7
|
||||
- name: Upload to telegram
|
||||
if: github.event_name != 'pull_request' && steps.need_upload.outputs.UPLOAD == 'true'
|
||||
env:
|
||||
CHAT_ID: ${{ secrets.CHAT_ID }}
|
||||
CACHE_CHAT_ID: ${{ secrets.CACHE_CHAT_ID }}
|
||||
BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
|
||||
MESSAGE_THREAD_ID: ${{ secrets.MESSAGE_THREAD_ID }}
|
||||
COMMIT_MESSAGE: ${{ github.event.head_commit.message }}
|
||||
COMMIT_URL: ${{ github.event.head_commit.url }}
|
||||
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
TITLE: kernel-aarch64-${{ matrix.version }}
|
||||
run: |
|
||||
if [ ! -z "${{ secrets.BOT_TOKEN }}" ]; then
|
||||
cd $GITHUB_WORKSPACE/KernelSU
|
||||
export VERSION=$(git rev-list --count HEAD)
|
||||
cd -
|
||||
OUTDIR=android-kernel/out/android12-5.10/dist
|
||||
IMAGE_GZ=${{ matrix.version }}-Image.gz
|
||||
BOOT=${{ matrix.version }}-boot.img.zip
|
||||
BOOT_LZ4=${{ matrix.version }}-boot-lz4.img.zip
|
||||
BOOT_GZ=${{ matrix.version }}-boot-gz.img.zip
|
||||
mv $OUTDIR/Image.gz $IMAGE_GZ
|
||||
zip $BOOT -j -r $OUTDIR/boot.img
|
||||
zip $BOOT_LZ4 -j -r $OUTDIR/boot-lz4.img
|
||||
zip $BOOT_GZ -j -r $OUTDIR/boot-gz.img
|
||||
pip3 install python-telegram-bot
|
||||
python3 $GITHUB_WORKSPACE/KernelSU/scripts/ksubot.py $IMAGE_GZ $BOOT $BOOT_LZ4 $BOOT_GZ
|
||||
fi
|
||||
137
.github/workflows/build-kernel-5.15.yml
vendored
137
.github/workflows/build-kernel-5.15.yml
vendored
@@ -1,137 +0,0 @@
|
||||
name: Build Kernel 5.15
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
paths:
|
||||
- '.github/workflows/build-kernel-5.15.yml'
|
||||
- 'kernel/**'
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
paths:
|
||||
- 'kernel/**'
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- version: "android13-5.15-41"
|
||||
tag: "android13-5.15-2022-11"
|
||||
os_version: 13.0.0
|
||||
os_version_level: "2022-11"
|
||||
- version: "android13-5.15-74"
|
||||
tag: "android13-5.15-2022-12"
|
||||
os_version: 13.0.0
|
||||
os_version_level: "2022-12"
|
||||
|
||||
name: Build aarch64-${{ matrix.version }}
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CCACHE_COMPILERCHECK: "%compiler% -dumpmachine; %compiler% -dumpversion"
|
||||
CCACHE_NOHASHDIR: "true"
|
||||
CCACHE_MAXSIZE: "2G"
|
||||
CCACHE_HARDLINK: "true"
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
path: KernelSU
|
||||
fetch-depth: 0
|
||||
- uses: hendrikmuhs/ccache-action@v1.2
|
||||
with:
|
||||
key: ccache-aarch64-${{ matrix.version }}
|
||||
append-timestamp: false
|
||||
save: ${{ github.event_name != 'pull_request' }}
|
||||
|
||||
- name: Setup need_upload
|
||||
id: need_upload
|
||||
run: |
|
||||
if [ ! -z "${{ secrets.BOT_TOKEN }}" ]; then
|
||||
echo "UPLOAD=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "UPLOAD=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Setup kernel source
|
||||
run: |
|
||||
cd $GITHUB_WORKSPACE
|
||||
git clone https://gerrit.googlesource.com/git-repo
|
||||
mkdir android-kernel && cd android-kernel
|
||||
../git-repo/repo init --depth=1 --u https://android.googlesource.com/kernel/manifest -b common-${{ matrix.tag }}
|
||||
../git-repo/repo sync -j$(nproc --all)
|
||||
curl -Lo gki-kernel.zip https://dl.google.com/android/gki/gki-certified-boot-${{ matrix.tag }}_r1.zip
|
||||
unzip gki-kernel.zip
|
||||
tools/mkbootimg/unpack_bootimg.py --boot_img=$(find . -maxdepth 1 -name "*.img")
|
||||
|
||||
- name: Setup KernelSU
|
||||
env:
|
||||
PATCH_PATH: "5.15"
|
||||
run: |
|
||||
cd $GITHUB_WORKSPACE/android-kernel
|
||||
echo "[+] KernelSU setup"
|
||||
GKI_ROOT=$(pwd)
|
||||
echo "[+] GKI_ROOT: $GKI_ROOT"
|
||||
echo "[+] Copy kernel su driver to $GKI_ROOT/common/drivers"
|
||||
ln -sf $GITHUB_WORKSPACE/KernelSU/kernel $GKI_ROOT/common/drivers/kernelsu
|
||||
echo "[+] Add kernel su driver to Makefile"
|
||||
DRIVER_MAKEFILE=$GKI_ROOT/common/drivers/Makefile
|
||||
grep -q "kernelsu" $DRIVER_MAKEFILE || echo "obj-y += kernelsu/" >> $DRIVER_MAKEFILE
|
||||
echo "[+] Apply KernelSU patches"
|
||||
cd $GKI_ROOT/common/ && git apply $GITHUB_WORKSPACE/KernelSU/.github/patches/$PATCH_PATH/*.patch
|
||||
cd -
|
||||
echo "[+] KernelSU setup Done."
|
||||
|
||||
- name: Symbol magic
|
||||
run: |
|
||||
echo "[+] Export all symbol from abi_gki_aarch64.xml"
|
||||
COMMON_ROOT=$GITHUB_WORKSPACE/android-kernel/common
|
||||
KSU_ROOT=$GITHUB_WORKSPACE/KernelSU
|
||||
ABI_XML=$COMMON_ROOT/android/abi_gki_aarch64.xml
|
||||
SYMBOL_LIST=$COMMON_ROOT/android/abi_gki_aarch64
|
||||
# python3 $KSU_ROOT/scripts/abi_gki_all.py $ABI_XML > $SYMBOL_LIST
|
||||
echo "[+] Add KernelSU symbols"
|
||||
cat $KSU_ROOT/kernel/export_symbol.txt | awk '{sub("[ \t]+","");print " "$0}' >> $SYMBOL_LIST
|
||||
|
||||
- name: Set boot sign key
|
||||
if: ${{ ( github.event_name != 'pull_request' && github.ref == 'refs/heads/main' ) || github.ref_type == 'tag' }}
|
||||
working-directory: android-kernel
|
||||
env:
|
||||
BOOT_SIGN_KEY: ${{ secrets.BOOT_SIGN_KEY }}
|
||||
run: |
|
||||
if [ ! -z "$BOOT_SIGN_KEY" ]; then
|
||||
echo "$BOOT_SIGN_KEY" > prebuilts/kernel-build-tools/linux-x86/share/avb/testkey_rsa2048.pem
|
||||
fi
|
||||
|
||||
- name: Build boot.img
|
||||
working-directory: android-kernel
|
||||
run: CCACHE="/usr/bin/ccache" BUILD_BOOT_IMG=1 SKIP_VENDOR_BOOT=1 KERNEL_BINARY=Image AVB_SIGN_BOOT_IMG=1 AVB_BOOT_PARTITION_SIZE=$((64*1024*1024)) AVB_BOOT_ALGORITHM=SHA256_RSA2048 AVB_BOOT_KEY=prebuilts/kernel-build-tools/linux-x86/share/avb/testkey_rsa2048.pem BOOT_IMAGE_HEADER_VERSION=4 LTO=thin BUILD_CONFIG=common/build.config.gki.aarch64 build/build.sh
|
||||
|
||||
- name: Upload boot.img
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: kernel-aarch64-${{ matrix.version }}-boot.img
|
||||
path: android-kernel/out/*/dist/boot.img
|
||||
|
||||
- name: Setup mutex for uploading
|
||||
uses: ben-z/gh-action-mutex@v1.0-alpha-7
|
||||
if: github.event_name != 'pull_request' && steps.need_upload.outputs.UPLOAD == 'true'
|
||||
- name: Upload to telegram
|
||||
if: github.event_name != 'pull_request' && steps.need_upload.outputs.UPLOAD == 'true'
|
||||
env:
|
||||
CHAT_ID: ${{ secrets.CHAT_ID }}
|
||||
CACHE_CHAT_ID: ${{ secrets.CACHE_CHAT_ID }}
|
||||
BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
|
||||
MESSAGE_THREAD_ID: ${{ secrets.MESSAGE_THREAD_ID }}
|
||||
COMMIT_MESSAGE: ${{ github.event.head_commit.message }}
|
||||
COMMIT_URL: ${{ github.event.head_commit.url }}
|
||||
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
TITLE: kernel-aarch64-${{ matrix.version }}
|
||||
run: |
|
||||
if [ ! -z "${{ secrets.BOT_TOKEN }}" ]; then
|
||||
cd $GITHUB_WORKSPACE/KernelSU
|
||||
export VERSION=$(git rev-list --count HEAD)
|
||||
cd -
|
||||
OUTDIR=android-kernel/out/android13-5.15/dist
|
||||
BOOT=${{ matrix.version }}-boot.img.zip
|
||||
zip $BOOT -j -r $OUTDIR/boot.img
|
||||
pip3 install python-telegram-bot
|
||||
python3 $GITHUB_WORKSPACE/KernelSU/scripts/ksubot.py $BOOT
|
||||
fi
|
||||
117
.github/workflows/build-kernel-a12.yml
vendored
Normal file
117
.github/workflows/build-kernel-a12.yml
vendored
Normal file
@@ -0,0 +1,117 @@
|
||||
name: Build Kernel - Android 12
|
||||
on:
|
||||
push:
|
||||
branches: ["main", "ci"]
|
||||
paths:
|
||||
- ".github/workflows/build-kernel-a12.yml"
|
||||
- ".github/workflows/gki-kernel.yml"
|
||||
- ".github/scripts/build_a12.sh"
|
||||
- "kernel/**"
|
||||
pull_request:
|
||||
branches: ["main"]
|
||||
paths:
|
||||
- ".github/workflows/build-kernel-a12.yml"
|
||||
- ".github/workflows/gki-kernel.yml"
|
||||
- ".github/scripts/build-a12.sh"
|
||||
- "kernel/**"
|
||||
workflow_call:
|
||||
jobs:
|
||||
build-kernel:
|
||||
if: github.event_name != 'pull_request'
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- sub_level: 66
|
||||
os_patch_level: 2021-11
|
||||
- sub_level: 81
|
||||
os_patch_level: 2022-03
|
||||
- sub_level: 101
|
||||
os_patch_level: 2022-05
|
||||
- sub_level: 110
|
||||
os_patch_level: 2022-07
|
||||
- sub_level: 136
|
||||
os_patch_level: 2022-11
|
||||
uses: ./.github/workflows/gki-kernel.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
version: android12-5.10
|
||||
version_name: android12-5.10.${{ matrix.sub_level }}
|
||||
tag: android12-5.10-${{ matrix.os_patch_level }}
|
||||
os_patch_level: ${{ matrix.os_patch_level }}
|
||||
patch_path: "5.10"
|
||||
upload-artifacts:
|
||||
needs: build-kernel
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ ( github.event_name != 'pull_request' && github.ref == 'refs/heads/main' ) || github.ref_type == 'tag' || github.ref == 'refs/heads/ci' }}
|
||||
env:
|
||||
CHAT_ID: ${{ secrets.CHAT_ID }}
|
||||
CACHE_CHAT_ID: ${{ secrets.CACHE_CHAT_ID }}
|
||||
BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
|
||||
MESSAGE_THREAD_ID: ${{ secrets.MESSAGE_THREAD_ID }}
|
||||
COMMIT_MESSAGE: ${{ github.event.head_commit.message }}
|
||||
COMMIT_URL: ${{ github.event.head_commit.url }}
|
||||
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
steps:
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v3
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
path: KernelSU
|
||||
fetch-depth: 0
|
||||
|
||||
- name: List artifacts
|
||||
run: |
|
||||
tree
|
||||
|
||||
- name: Download prebuilt toolchain
|
||||
run: |
|
||||
AOSP_MIRROR=https://android.googlesource.com
|
||||
BRANCH=master-kernel-build-2022
|
||||
git clone $AOSP_MIRROR/platform/prebuilts/build-tools -b $BRANCH --depth 1 build-tools
|
||||
git clone $AOSP_MIRROR/kernel/prebuilts/build-tools -b $BRANCH --depth 1 kernel-build-tools
|
||||
git clone $AOSP_MIRROR/platform/system/tools/mkbootimg -b $BRANCH --depth 1
|
||||
pip3 install python-telegram-bot
|
||||
|
||||
- name: Set boot sign key
|
||||
env:
|
||||
BOOT_SIGN_KEY: ${{ secrets.BOOT_SIGN_KEY }}
|
||||
run: |
|
||||
if [ ! -z "$BOOT_SIGN_KEY" ]; then
|
||||
echo "$BOOT_SIGN_KEY" > ./kernel-build-tools/linux-x86/share/avb/testkey_rsa2048.pem
|
||||
fi
|
||||
|
||||
- name: Setup mutex for uploading
|
||||
uses: ben-z/gh-action-mutex@v1.0-alpha-7
|
||||
|
||||
- name: Build boot images
|
||||
run: |
|
||||
export AVBTOOL=$GITHUB_WORKSPACE/kernel-build-tools/linux-x86/bin/avbtool
|
||||
export GZIP=$GITHUB_WORKSPACE/build-tools/path/linux-x86/gzip
|
||||
export LZ4=$GITHUB_WORKSPACE/build-tools/path/linux-x86/lz4
|
||||
export MKBOOTIMG=$GITHUB_WORKSPACE/mkbootimg/mkbootimg.py
|
||||
export UNPACK_BOOTIMG=$GITHUB_WORKSPACE/mkbootimg/unpack_bootimg.py
|
||||
cd $GITHUB_WORKSPACE/KernelSU
|
||||
export VERSION=$(($(git rev-list --count HEAD) + 10200))
|
||||
echo "VERSION: $VERSION"
|
||||
cd -
|
||||
bash $GITHUB_WORKSPACE/KernelSU/.github/scripts/build_a12.sh
|
||||
|
||||
- name: Display structure of boot files
|
||||
run: ls -R
|
||||
|
||||
- name: Upload images artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: boot-images-android12
|
||||
path: Image-android12*/*.img.gz
|
||||
|
||||
check-build-kernel:
|
||||
if: github.event_name == 'pull_request'
|
||||
uses: ./.github/workflows/gki-kernel.yml
|
||||
with:
|
||||
version: android12-5.10
|
||||
version_name: android12-5.10.101
|
||||
tag: android12-5.10-2022-05
|
||||
os_patch_level: 2022-05
|
||||
patch_path: "5.10"
|
||||
126
.github/workflows/build-kernel-a13.yml
vendored
Normal file
126
.github/workflows/build-kernel-a13.yml
vendored
Normal file
@@ -0,0 +1,126 @@
|
||||
name: Build Kernel - Android 13
|
||||
on:
|
||||
push:
|
||||
branches: ["main", "ci"]
|
||||
paths:
|
||||
- ".github/workflows/build-kernel-a13.yml"
|
||||
- ".github/workflows/gki-kernel.yml"
|
||||
- ".github/scripts/build_a13.sh"
|
||||
- "kernel/**"
|
||||
pull_request:
|
||||
branches: ["main"]
|
||||
paths:
|
||||
- ".github/workflows/build-kernel-a13.yml"
|
||||
- ".github/workflows/gki-kernel.yml"
|
||||
- ".github/scripts/build-a13.sh"
|
||||
- "kernel/**"
|
||||
workflow_call:
|
||||
jobs:
|
||||
build-kernel:
|
||||
if: github.event_name != 'pull_request'
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- version: "5.10"
|
||||
sub_level: 107
|
||||
os_patch_level: 2022-11
|
||||
- version: "5.10"
|
||||
sub_level: 149
|
||||
os_patch_level: 2023-01
|
||||
- version: "5.15"
|
||||
sub_level: 41
|
||||
os_patch_level: 2022-11
|
||||
- version: "5.15"
|
||||
sub_level: 74
|
||||
os_patch_level: 2022-12
|
||||
uses: ./.github/workflows/gki-kernel.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
version: android13-${{ matrix.version }}
|
||||
version_name: android13-${{ matrix.version }}.${{ matrix.sub_level }}
|
||||
tag: android13-${{ matrix.version }}-${{ matrix.os_patch_level }}
|
||||
patch_path: ${{ matrix.version }}
|
||||
upload-artifacts:
|
||||
needs: build-kernel
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ ( github.event_name != 'pull_request' && github.ref == 'refs/heads/main' ) || github.ref_type == 'tag' || github.ref == 'refs/heads/ci' }}
|
||||
env:
|
||||
CHAT_ID: ${{ secrets.CHAT_ID }}
|
||||
CACHE_CHAT_ID: ${{ secrets.CACHE_CHAT_ID }}
|
||||
BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
|
||||
MESSAGE_THREAD_ID: ${{ secrets.MESSAGE_THREAD_ID }}
|
||||
COMMIT_MESSAGE: ${{ github.event.head_commit.message }}
|
||||
COMMIT_URL: ${{ github.event.head_commit.url }}
|
||||
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
steps:
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v3
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
path: KernelSU
|
||||
fetch-depth: 0
|
||||
|
||||
- name: List artifacts
|
||||
run: |
|
||||
tree
|
||||
|
||||
- name: Download prebuilt toolchain
|
||||
run: |
|
||||
AOSP_MIRROR=https://android.googlesource.com
|
||||
BRANCH=master-kernel-build-2022
|
||||
git clone $AOSP_MIRROR/platform/prebuilts/build-tools -b $BRANCH --depth 1 build-tools
|
||||
git clone $AOSP_MIRROR/kernel/prebuilts/build-tools -b $BRANCH --depth 1 kernel-build-tools
|
||||
git clone $AOSP_MIRROR/platform/system/tools/mkbootimg -b $BRANCH --depth 1
|
||||
pip3 install python-telegram-bot
|
||||
|
||||
- name: Set boot sign key
|
||||
env:
|
||||
BOOT_SIGN_KEY: ${{ secrets.BOOT_SIGN_KEY }}
|
||||
run: |
|
||||
if [ ! -z "$BOOT_SIGN_KEY" ]; then
|
||||
echo "$BOOT_SIGN_KEY" > ./kernel-build-tools/linux-x86/share/avb/testkey_rsa2048.pem
|
||||
fi
|
||||
|
||||
- name: Setup mutex for uploading
|
||||
uses: ben-z/gh-action-mutex@v1.0-alpha-7
|
||||
|
||||
- name: Build boot images
|
||||
run: |
|
||||
export AVBTOOL=$GITHUB_WORKSPACE/kernel-build-tools/linux-x86/bin/avbtool
|
||||
export GZIP=$GITHUB_WORKSPACE/build-tools/path/linux-x86/gzip
|
||||
export LZ4=$GITHUB_WORKSPACE/build-tools/path/linux-x86/lz4
|
||||
export MKBOOTIMG=$GITHUB_WORKSPACE/mkbootimg/mkbootimg.py
|
||||
export UNPACK_BOOTIMG=$GITHUB_WORKSPACE/mkbootimg/unpack_bootimg.py
|
||||
cd $GITHUB_WORKSPACE/KernelSU
|
||||
export VERSION=$(($(git rev-list --count HEAD) + 10200))
|
||||
echo "VERSION: $VERSION"
|
||||
cd -
|
||||
bash $GITHUB_WORKSPACE/KernelSU/.github/scripts/build_a13.sh
|
||||
|
||||
- name: Display structure of boot files
|
||||
run: ls -R
|
||||
|
||||
- name: Upload images artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: boot-images-android13
|
||||
path: Image-android13*/*.img.gz
|
||||
|
||||
check-build-kernel:
|
||||
if: github.event_name == 'pull_request'
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- version: "5.10"
|
||||
sub_level: 107
|
||||
os_patch_level: 2022-11
|
||||
- version: "5.15"
|
||||
sub_level: 41
|
||||
os_patch_level: 2022-11
|
||||
uses: ./.github/workflows/gki-kernel.yml
|
||||
with:
|
||||
version: android13-${{ matrix.version }}
|
||||
version_name: android13-${{ matrix.version }}.${{ matrix.sub_level }}
|
||||
tag: android13-${{ matrix.version }}-${{ matrix.os_patch_level }}
|
||||
patch_path: ${{ matrix.version }}
|
||||
44
.github/workflows/build-ksud.yml
vendored
44
.github/workflows/build-ksud.yml
vendored
@@ -1,14 +1,16 @@
|
||||
name: Build KSUD
|
||||
on:
|
||||
workflow_call:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
branches: [ "main", "ci" ]
|
||||
paths:
|
||||
- '.github/workflows/build-ksud.yml'
|
||||
- '.github/workflows/ksud.yml'
|
||||
- 'userspace/ksud/**'
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
paths:
|
||||
- '.github/workflows/build-ksud.yml'
|
||||
- '.github/workflows/ksud.yml'
|
||||
- 'userspace/ksud/**'
|
||||
jobs:
|
||||
build:
|
||||
@@ -17,37 +19,7 @@ jobs:
|
||||
include:
|
||||
- target: aarch64-linux-android
|
||||
- target: x86_64-linux-android
|
||||
name: Build KSUD
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up cargo cache
|
||||
uses: actions/cache@v3
|
||||
continue-on-error: false
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||
restore-keys: ${{ runner.os }}-cargo-
|
||||
- name: Set up ksud build cache
|
||||
uses: actions/cache@v3
|
||||
continue-on-error: false
|
||||
with:
|
||||
path: ./userspace/ksud/target/
|
||||
key: ${{ runner.os }}-ksud-${{ hashFiles('**/Cargo.lock') }}-${{ matrix.target }}
|
||||
restore-keys: ${{ runner.os }}-ksud-
|
||||
- name: Build ksud
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
use-cross: true
|
||||
command: build
|
||||
args: --target ${{ matrix.target }} --release --manifest-path ./userspace/ksud/Cargo.toml
|
||||
- name: Upload ksud artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ksud-${{ matrix.target }}
|
||||
path: ./userspace/ksud/target/**/release/ksud
|
||||
|
||||
- target: x86_64-pc-windows-gnu # only for build
|
||||
uses: ./.github/workflows/ksud.yml
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
|
||||
10
.github/workflows/build-manager.yml
vendored
10
.github/workflows/build-manager.yml
vendored
@@ -10,9 +10,17 @@ on:
|
||||
branches: [ "main" ]
|
||||
paths:
|
||||
- 'manager/**'
|
||||
workflow_call:
|
||||
jobs:
|
||||
build-ksud:
|
||||
uses: ./.github/workflows/build-ksud.yml
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- target: aarch64-linux-android
|
||||
- target: x86_64-linux-android
|
||||
uses: ./.github/workflows/ksud.yml
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
build-manager:
|
||||
needs: build-ksud
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
22
.github/workflows/clippy-pr.yml
vendored
Normal file
22
.github/workflows/clippy-pr.yml
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
name: Clippy check for pull request
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- 'main'
|
||||
paths:
|
||||
- '.github/workflows/clippy-pr.yml'
|
||||
- 'userspace/ksud/**'
|
||||
|
||||
permissions:
|
||||
checks: write
|
||||
|
||||
jobs:
|
||||
clippy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: giraffate/clippy-action@v1
|
||||
with:
|
||||
workdir: userspace/ksud
|
||||
30
.github/workflows/clippy.yml
vendored
Normal file
30
.github/workflows/clippy.yml
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
name: Clippy check
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
paths:
|
||||
- '.github/workflows/clippy.yml'
|
||||
- 'userspace/ksud/**'
|
||||
|
||||
env:
|
||||
RUSTFLAGS: '-Dwarnings'
|
||||
|
||||
jobs:
|
||||
clippy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: userspace/ksud
|
||||
|
||||
- name: Install cross
|
||||
run: cargo install cross
|
||||
|
||||
- name: Run clippy
|
||||
run: |
|
||||
cross clippy --manifest-path userspace/ksud/Cargo.toml --target aarch64-linux-android
|
||||
cross clippy --manifest-path userspace/ksud/Cargo.toml --target x86_64-linux-android
|
||||
152
.github/workflows/gki-kernel.yml
vendored
Normal file
152
.github/workflows/gki-kernel.yml
vendored
Normal file
@@ -0,0 +1,152 @@
|
||||
name: GKI Kernel Build
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
version:
|
||||
required: true
|
||||
type: string
|
||||
description: >
|
||||
Output directory of gki,
|
||||
for example: android12-5.10
|
||||
version_name:
|
||||
required: true
|
||||
type: string
|
||||
description: >
|
||||
With SUBLEVEL of kernel,
|
||||
for example: android12-5.10.66
|
||||
tag:
|
||||
required: true
|
||||
type: string
|
||||
description: >
|
||||
Part of branch name of common kernel manifest,
|
||||
for example: android12-5.10-2021-11
|
||||
os_patch_level:
|
||||
required: false
|
||||
type: string
|
||||
description: >
|
||||
Patch level of common kernel manifest,
|
||||
for example: 2021-11
|
||||
default: 2022-05
|
||||
patch_path:
|
||||
required: true
|
||||
type: string
|
||||
description: >
|
||||
Directory name of .github/patches/<patch_path>
|
||||
for example: 5.10
|
||||
use_cache:
|
||||
required: false
|
||||
type: boolean
|
||||
default: true
|
||||
embed_ksud:
|
||||
required: false
|
||||
type: string
|
||||
default: ksud-aarch64-linux-android
|
||||
description: >
|
||||
Artifact name of prebuilt ksud to be embedded
|
||||
for example: ksud-aarch64-linux-android
|
||||
secrets:
|
||||
BOOT_SIGN_KEY:
|
||||
required: false
|
||||
CHAT_ID:
|
||||
required: false
|
||||
CACHE_CHAT_ID:
|
||||
required: false
|
||||
BOT_TOKEN:
|
||||
required: false
|
||||
MESSAGE_THREAD_ID:
|
||||
required: false
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build ${{ inputs.version_name }}
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CCACHE_COMPILERCHECK: "%compiler% -dumpmachine; %compiler% -dumpversion"
|
||||
CCACHE_NOHASHDIR: "true"
|
||||
CCACHE_MAXSIZE: "2G"
|
||||
CCACHE_HARDLINK: "true"
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
path: KernelSU
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: hendrikmuhs/ccache-action@v1.2
|
||||
if: inputs.use_cache == true
|
||||
with:
|
||||
key: ccache-aarch64-${{ inputs.version_name }}
|
||||
append-timestamp: false
|
||||
save: ${{ github.event_name != 'pull_request' }}
|
||||
|
||||
- name: Setup need_upload
|
||||
id: need_upload
|
||||
run: |
|
||||
if [ ! -z "${{ secrets.BOT_TOKEN }}" ]; then
|
||||
echo "UPLOAD=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "UPLOAD=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Setup kernel source
|
||||
run: |
|
||||
cd $GITHUB_WORKSPACE
|
||||
git clone https://gerrit.googlesource.com/git-repo
|
||||
mkdir android-kernel && cd android-kernel
|
||||
../git-repo/repo init --depth=1 --u https://android.googlesource.com/kernel/manifest -b common-${{ inputs.tag }}
|
||||
../git-repo/repo sync -j$(nproc --all)
|
||||
|
||||
- name: Setup KernelSU
|
||||
env:
|
||||
PATCH_PATH: ${{ inputs.patch_path }}
|
||||
run: |
|
||||
cd $GITHUB_WORKSPACE/android-kernel
|
||||
echo "[+] KernelSU setup"
|
||||
GKI_ROOT=$(pwd)
|
||||
echo "[+] GKI_ROOT: $GKI_ROOT"
|
||||
echo "[+] Copy KernelSU driver to $GKI_ROOT/common/drivers"
|
||||
ln -sf $GITHUB_WORKSPACE/KernelSU/kernel $GKI_ROOT/common/drivers/kernelsu
|
||||
echo "[+] Add KernelSU driver to Makefile"
|
||||
DRIVER_MAKEFILE=$GKI_ROOT/common/drivers/Makefile
|
||||
grep -q "kernelsu" $DRIVER_MAKEFILE || echo "obj-y += kernelsu/" >> $DRIVER_MAKEFILE
|
||||
echo "[+] Apply KernelSU patches"
|
||||
cd $GKI_ROOT/common/ && git apply $GITHUB_WORKSPACE/KernelSU/.github/patches/$PATCH_PATH/*.patch
|
||||
echo "[+] KernelSU setup done."
|
||||
|
||||
- name: Symbol magic
|
||||
run: |
|
||||
echo "[+] Export all symbol from abi_gki_aarch64.xml"
|
||||
COMMON_ROOT=$GITHUB_WORKSPACE/android-kernel/common
|
||||
KSU_ROOT=$GITHUB_WORKSPACE/KernelSU
|
||||
ABI_XML=$COMMON_ROOT/android/abi_gki_aarch64.xml
|
||||
SYMBOL_LIST=$COMMON_ROOT/android/abi_gki_aarch64
|
||||
# python3 $KSU_ROOT/scripts/abi_gki_all.py $ABI_XML > $SYMBOL_LIST
|
||||
echo "[+] Add KernelSU symbols"
|
||||
cat $KSU_ROOT/kernel/export_symbol.txt | awk '{sub("[ \t]+","");print " "$0}' >> $SYMBOL_LIST
|
||||
|
||||
- name: Build boot.img
|
||||
working-directory: android-kernel
|
||||
run: CCACHE="/usr/bin/ccache" LTO=thin BUILD_CONFIG=common/build.config.gki.aarch64 build/build.sh
|
||||
|
||||
- name: Prepare artifacts
|
||||
id: prepareArtifacts
|
||||
run: |
|
||||
OUTDIR=android-kernel/out/${{ inputs.version }}/dist
|
||||
mkdir output
|
||||
cp $OUTDIR/Image ./output/
|
||||
cp $OUTDIR/Image.lz4 ./output/
|
||||
git clone https://github.com/Kernel-SU/AnyKernel3
|
||||
rm -rf ./AnyKernel3/.git
|
||||
cp $OUTDIR/Image ./AnyKernel3/
|
||||
|
||||
- name: Upload Image and Image.gz
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: Image-${{ inputs.version_name }}_${{ inputs.os_patch_level }}
|
||||
path: ./output/*
|
||||
|
||||
- name: Upload AnyKernel3
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: AnyKernel3-${{ inputs.version_name }}_${{ inputs.os_patch_level }}
|
||||
path: ./AnyKernel3/*
|
||||
36
.github/workflows/ksud.yml
vendored
Normal file
36
.github/workflows/ksud.yml
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
name: Build ksud
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
target:
|
||||
required: true
|
||||
type: string
|
||||
use_cache:
|
||||
required: false
|
||||
type: boolean
|
||||
default: true
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
# cross build failed after Rust 1.68, see https://github.com/cross-rs/cross/issues/1222
|
||||
- run: rustup default 1.67.0
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: userspace/ksud
|
||||
cache-targets: false
|
||||
|
||||
- name: Install cross
|
||||
run: cargo install cross
|
||||
|
||||
- name: Build ksud
|
||||
run: cross build --target ${{ inputs.target }} --release --manifest-path ./userspace/ksud/Cargo.toml
|
||||
|
||||
- name: Upload ksud artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ksud-${{ inputs.target }}
|
||||
path: userspace/ksud/target/**/release/ksud
|
||||
56
.github/workflows/release.yml
vendored
Normal file
56
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
name: Release
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build-manager:
|
||||
uses: ./.github/workflows/build-manager.yml
|
||||
secrets: inherit
|
||||
build-a12-kernel:
|
||||
uses: ./.github/workflows/build-kernel-a12.yml
|
||||
build-a13-kernel:
|
||||
uses: ./.github/workflows/build-kernel-a13.yml
|
||||
build-wsa-kernel:
|
||||
uses: ./.github/workflows/build-WSA-5.10.117-kernel.yml
|
||||
release:
|
||||
needs:
|
||||
- build-manager
|
||||
- build-a12-kernel
|
||||
- build-a13-kernel
|
||||
- build-wsa-kernel
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v3
|
||||
- name: Zip AnyKernel3
|
||||
run: |
|
||||
for dir in AnyKernel3-*; do
|
||||
if [ -d "$dir" ]; then
|
||||
echo "----- Zip $dir -----"
|
||||
(cd $dir && zip -r9 "$dir".zip ./* -x .git .gitignore ./*.zip && mv *.zip ..)
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Zip WSA kernel
|
||||
run: |
|
||||
for dir in kernel-WSA-*; do
|
||||
if [ -d "$dir" ]; then
|
||||
echo "------ Zip $dir ----------"
|
||||
(cd $dir && zip -r9 "$dir".zip ./* -x .git .gitignore ./*.zip && mv *.zip ..)
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Display structure of downloaded files
|
||||
run: ls -R
|
||||
|
||||
- name: release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
files: |
|
||||
manager/*.apk
|
||||
AnyKernel3-*.zip
|
||||
boot-images-*/Image-*/*.img.gz
|
||||
kernel-WSA*.zip
|
||||
33
.github/workflows/rustfmt.yml
vendored
Normal file
33
.github/workflows/rustfmt.yml
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
name: Rustfmt check
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
paths:
|
||||
- '.github/workflows/rustfmt.yml'
|
||||
- 'userspace/ksud/**'
|
||||
pull_request:
|
||||
branches:
|
||||
- 'main'
|
||||
paths:
|
||||
- '.github/workflows/rustfmt.yml'
|
||||
- 'userspace/ksud/**'
|
||||
|
||||
permissions:
|
||||
checks: write
|
||||
|
||||
jobs:
|
||||
format:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: dtolnay/rust-toolchain@nightly
|
||||
with:
|
||||
components: rustfmt
|
||||
|
||||
- uses: LoliGothick/rustfmt-check@v0.2
|
||||
with:
|
||||
token: ${{ github.token }}
|
||||
options: --manifest-path userspace/ksud/Cargo.toml
|
||||
27
.github/workflows/shellcheck.yml
vendored
Normal file
27
.github/workflows/shellcheck.yml
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
name: ShellCheck
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
paths:
|
||||
- '.github/workflows/shellcheck.yml'
|
||||
- '**/*.sh'
|
||||
pull_request:
|
||||
branches:
|
||||
- 'main'
|
||||
paths:
|
||||
- '.github/workflows/shellcheck.yml'
|
||||
- '**/*.sh'
|
||||
|
||||
jobs:
|
||||
shellcheck:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Run ShellCheck
|
||||
uses: ludeeus/action-shellcheck@2.0.0
|
||||
with:
|
||||
ignore_names: gradlew
|
||||
ignore_paths: ./userspace/ksud/src/installer.sh
|
||||
45
README.md
45
README.md
@@ -2,58 +2,37 @@
|
||||
|
||||
# KernelSU
|
||||
|
||||
A Kernel based root solution for Android GKI.
|
||||
A Kernel based root solution for Android devices.
|
||||
|
||||
## Before Reading
|
||||
## Features
|
||||
|
||||
Now KernelSU supports old kernel under 5.10, but **THERE WILL NEVER** be a CI for old kernels, because they are not generic.
|
||||
ANY ISSUES ABOUT HOW TO COMPILE A OLD KERNEL WILL NOT BE ANSWERED AND WILL BE CLOSED.
|
||||
|
||||
KernelSU is in a early development stage and you should not put it into production enviroment. KernelSU developers will not be responsible for any of your losses.
|
||||
|
||||
If you face any issue, feel free to open a [issue](https://github.com/tiann/KernelSU/issues) and tell us about it!
|
||||
1. Kernel-based `su` and root access management.
|
||||
2. Module system based on overlayfs.
|
||||
|
||||
## Compatibility State
|
||||
|
||||
Now KernelSU will work on these version of kernels without any modification:
|
||||
KernelSU officially supports Android GKI 2.0 devices(with kernel 5.10+), old kernels(4.14+) is also compatiable, but you need to build kernel yourself.
|
||||
|
||||
- `5.15`
|
||||
- `5.10`
|
||||
- `5.4`
|
||||
- `4.19`
|
||||
- `4.14`
|
||||
WSA and containter-based Android should also work with KernelSU integrated.
|
||||
|
||||
And the current supported ABIs are : `arm64-v8a` & `x86_64`
|
||||
|
||||
If you confirm KernelSU works on other version, open a [issue](https://github.com/tiann/KernelSU/issues) tell us about it!
|
||||
And the current supported ABIs are : `arm64-v8a` and `x86_64`
|
||||
|
||||
## Usage
|
||||
|
||||
1. Flash a custom kernel with KernelSU, you can build it yourself or [download it from CI](https://github.com/tiann/KernelSU/actions).
|
||||
2. Install Manager App and enjoy :)
|
||||
|
||||
For old kernels under 5.10, you must build custom kernels by yourself.
|
||||
[Installation](https://kernelsu.org/guide/installation.html)
|
||||
|
||||
## Build
|
||||
|
||||
### Build GKI Kernel
|
||||
|
||||
1. Download the GKI source first, you can refer the [GKI build instruction](https://source.android.com/docs/setup/build/building-kernels)
|
||||
2. cd `<GKI kernel source dir>`
|
||||
3. `curl -LSs "https://raw.githubusercontent.com/tiann/KernelSU/main/kernel/setup.sh" | bash -`
|
||||
4. Build the kernel.
|
||||
|
||||
### Build the Manager App
|
||||
|
||||
Android Studio / Gradle
|
||||
[How to build?](https://kernelsu.org/guide/how-to-build.html)
|
||||
|
||||
### Discussion
|
||||
|
||||
[@KernelSU](https://t.me/KernelSU)
|
||||
- Telegram: [@KernelSU](https://t.me/KernelSU)
|
||||
|
||||
## License
|
||||
|
||||
[GPL-3](http://www.gnu.org/copyleft/gpl.html)
|
||||
- Files under `kernel` directory are [GPL-2](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
|
||||
- All other parts except `kernel` directory are [GPL-3](https://www.gnu.org/licenses/gpl-3.0.html)
|
||||
|
||||
## Credits
|
||||
|
||||
|
||||
45
README_CN.md
45
README_CN.md
@@ -2,58 +2,37 @@
|
||||
|
||||
# KernelSU
|
||||
|
||||
一个基于内核,为安卓 GKI 准备的 root 方案。
|
||||
一个 Android 上基于内核的 root 方案。
|
||||
|
||||
## 阅读之前
|
||||
## 特性
|
||||
|
||||
现在 KernelSU 已经支持 5.10 以下的旧内核,但**永远不会**有旧内核的 CI,因为它们不是通用的。
|
||||
**任何关于如何编译旧内核的问题都不会得到任何答复并将被关闭。**
|
||||
|
||||
KernelSU 还处于早期开发阶段,你不应该生产环境中使用它。KernelSU 的开发者将不对你的任何损失负责。
|
||||
|
||||
如果你遇到任何问题,请打开 [issue](https://github.com/tiann/KernelSU/issues) 告诉我们! (最好使用英语)
|
||||
- 基于内核的 su 和权限管理。
|
||||
- 基于 overlayfs 的模块系统。
|
||||
|
||||
## 兼容状态
|
||||
|
||||
现在,KernelSU 可以在这些版本的内核上正常工作,不需要任何修改。
|
||||
KernelSU 官方支持 GKI 2.0 的设备(内核版本5.10以上);旧内核也是兼容的(最低4.14+),不过需要自己编译内核。
|
||||
|
||||
- `5.15`
|
||||
- `5.10`
|
||||
- `5.4`
|
||||
- `4.19`
|
||||
- `4.14`
|
||||
WSA 和运行在容器上的 Android 也可以与 KernelSU 一起工作。
|
||||
|
||||
目前支持架构 : `arm64-v8a` & `x86_64`
|
||||
|
||||
如果你确认 KernelSU 能在其他版本上工作,请打开一个 [issue](https://github.com/tiann/KernelSU/issues) 告诉我们!
|
||||
目前支持架构 : `arm64-v8a` 和 `x86_64`
|
||||
|
||||
## 使用方法
|
||||
|
||||
1. 用 KernelSU 刷入一个自定义的内核,你可以自己构建它或者[从 CI 下载](https://github.com/tiann/KernelSU/actions)。
|
||||
2. 安装管理器应用, 然后享受吧 :)
|
||||
|
||||
对于 5.10 以下的旧内核, 你必须自己构建。
|
||||
[安装教程](https://kernelsu.org/zh_CN/guide/installation.html)
|
||||
|
||||
## 构建
|
||||
|
||||
### 构建GKI内核
|
||||
|
||||
1. 首先下载 GKI 源代码,你可以参考[ GKI 构建说明](https://source.android.com/docs/setup/build/building-kernels)。
|
||||
2. cd `< GKI 内核源代码目录 >`。
|
||||
3. `curl -LSs "https://raw.githubusercontent.com/tiann/KernelSU/main/kernel/setup.sh" | bash -`。
|
||||
4. 构建内核。
|
||||
|
||||
### 构建管理器应用
|
||||
|
||||
Android Studio / Gradle
|
||||
[如何构建?](https://kernelsu.org/zh_CN/guide/how-to-build.html)
|
||||
|
||||
### 讨论
|
||||
|
||||
[@KernelSU](https://t.me/KernelSU)
|
||||
- Telegram: [@KernelSU](https://t.me/KernelSU)
|
||||
|
||||
## 许可证
|
||||
|
||||
[GPL-3](http://www.gnu.org/copyleft/gpl.html)
|
||||
- 目录 `kernel` 下所有文件为 [GPL-2](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
|
||||
- 除 `kernel` 目录的其他部分均为 [GPL-3](https://www.gnu.org/licenses/gpl-3.0.html)
|
||||
|
||||
## 鸣谢
|
||||
|
||||
|
||||
339
kernel/LICENSE
Normal file
339
kernel/LICENSE
Normal file
@@ -0,0 +1,339 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Lesser General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License.
|
||||
@@ -8,8 +8,15 @@ obj-y += uid_observer.o
|
||||
obj-y += manager.o
|
||||
obj-y += core_hook.o
|
||||
obj-y += ksud.o
|
||||
obj-y += embed_ksud.o
|
||||
obj-y += kernel_compat.o
|
||||
|
||||
obj-y += selinux/
|
||||
# .git is a text file while the module is imported by 'git submodule add'.
|
||||
ifeq ($(shell test -e $(srctree)/$(src)/../.git; echo $$?),0)
|
||||
KSU_GIT_VERSION := $(shell cd $(srctree)/$(src); /usr/bin/env PATH=$$PATH:/usr/bin:/usr/local/bin git rev-list --count HEAD)
|
||||
ccflags-y += -DKSU_GIT_VERSION=$(KSU_GIT_VERSION)
|
||||
endif
|
||||
|
||||
ifndef EXPECTED_SIZE
|
||||
EXPECTED_SIZE := 0x033b
|
||||
|
||||
@@ -4,9 +4,10 @@
|
||||
#include "linux/list.h"
|
||||
#include "linux/printk.h"
|
||||
#include "linux/slab.h"
|
||||
|
||||
#include "linux/version.h"
|
||||
#include "klog.h" // IWYU pragma: keep
|
||||
#include "selinux/selinux.h"
|
||||
#include "kernel_compat.h"
|
||||
|
||||
#define FILE_MAGIC 0x7f4b5355 // ' KSU', u32
|
||||
#define FILE_FORMAT_VERSION 1 // u32
|
||||
@@ -21,7 +22,7 @@ struct perm_data {
|
||||
|
||||
static struct list_head allow_list;
|
||||
|
||||
#define KERNEL_SU_ALLOWLIST "/data/adb/.ksu_allowlist"
|
||||
#define KERNEL_SU_ALLOWLIST "/data/adb/ksu/.allowlist"
|
||||
|
||||
static struct work_struct ksu_save_work;
|
||||
static struct work_struct ksu_load_work;
|
||||
@@ -118,7 +119,7 @@ void do_persistent_allow_list(struct work_struct *work)
|
||||
struct perm_data *p = NULL;
|
||||
struct list_head *pos = NULL;
|
||||
loff_t off = 0;
|
||||
|
||||
KWORKER_INSTALL_KEYRING();
|
||||
struct file *fp =
|
||||
filp_open(KERNEL_SU_ALLOWLIST, O_WRONLY | O_CREAT, 0644);
|
||||
|
||||
@@ -128,12 +129,12 @@ void do_persistent_allow_list(struct work_struct *work)
|
||||
}
|
||||
|
||||
// store magic and version
|
||||
if (kernel_write(fp, &magic, sizeof(magic), &off) != sizeof(magic)) {
|
||||
if (ksu_kernel_write_compat(fp, &magic, sizeof(magic), &off) != sizeof(magic)) {
|
||||
pr_err("save_allow_list write magic failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (kernel_write(fp, &version, sizeof(version), &off) !=
|
||||
if (ksu_kernel_write_compat(fp, &version, sizeof(version), &off) !=
|
||||
sizeof(version)) {
|
||||
pr_err("save_allow_list write version failed.\n");
|
||||
goto exit;
|
||||
@@ -143,8 +144,8 @@ void do_persistent_allow_list(struct work_struct *work)
|
||||
p = list_entry(pos, struct perm_data, list);
|
||||
pr_info("save allow list uid :%d, allow: %d\n", p->uid,
|
||||
p->allow);
|
||||
kernel_write(fp, &p->uid, sizeof(p->uid), &off);
|
||||
kernel_write(fp, &p->allow, sizeof(p->allow), &off);
|
||||
ksu_kernel_write_compat(fp, &p->uid, sizeof(p->uid), &off);
|
||||
ksu_kernel_write_compat(fp, &p->allow, sizeof(p->allow), &off);
|
||||
}
|
||||
|
||||
exit:
|
||||
@@ -158,21 +159,7 @@ void do_load_allow_list(struct work_struct *work)
|
||||
struct file *fp = NULL;
|
||||
u32 magic;
|
||||
u32 version;
|
||||
|
||||
fp = filp_open("/data/adb/", O_RDONLY, 0);
|
||||
if (IS_ERR(fp)) {
|
||||
int errno = PTR_ERR(fp);
|
||||
pr_err("load_allow_list open '/data/adb': %d\n", PTR_ERR(fp));
|
||||
if (errno == -ENOENT) {
|
||||
msleep(2000);
|
||||
ksu_queue_work(&ksu_load_work);
|
||||
return;
|
||||
} else {
|
||||
pr_info("load_allow list dir exist now!");
|
||||
}
|
||||
} else {
|
||||
filp_close(fp, 0);
|
||||
}
|
||||
KWORKER_INSTALL_KEYRING();
|
||||
|
||||
// load allowlist now!
|
||||
fp = filp_open(KERNEL_SU_ALLOWLIST, O_RDONLY, 0);
|
||||
@@ -194,13 +181,13 @@ void do_load_allow_list(struct work_struct *work)
|
||||
}
|
||||
|
||||
// verify magic
|
||||
if (kernel_read(fp, &magic, sizeof(magic), &off) != sizeof(magic) ||
|
||||
if (ksu_kernel_read_compat(fp, &magic, sizeof(magic), &off) != sizeof(magic) ||
|
||||
magic != FILE_MAGIC) {
|
||||
pr_err("allowlist file invalid: %d!\n", magic);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (kernel_read(fp, &version, sizeof(version), &off) !=
|
||||
if (ksu_kernel_read_compat(fp, &version, sizeof(version), &off) !=
|
||||
sizeof(version)) {
|
||||
pr_err("allowlist read version: %d failed\n", version);
|
||||
goto exit;
|
||||
@@ -211,12 +198,12 @@ void do_load_allow_list(struct work_struct *work)
|
||||
while (true) {
|
||||
u32 uid;
|
||||
bool allow = false;
|
||||
ret = kernel_read(fp, &uid, sizeof(uid), &off);
|
||||
ret = ksu_kernel_read_compat(fp, &uid, sizeof(uid), &off);
|
||||
if (ret <= 0) {
|
||||
pr_info("load_allow_list read err: %d\n", ret);
|
||||
break;
|
||||
}
|
||||
ret = kernel_read(fp, &allow, sizeof(allow), &off);
|
||||
ret = ksu_kernel_read_compat(fp, &allow, sizeof(allow), &off);
|
||||
|
||||
pr_info("load_allow_uid: %d, allow: %d\n", uid, allow);
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include "apk_sign.h"
|
||||
#include "klog.h" // IWYU pragma: keep
|
||||
#include "kernel_compat.h"
|
||||
|
||||
static __always_inline int
|
||||
check_v2_signature(char *path, unsigned expected_size, unsigned expected_hash)
|
||||
@@ -14,21 +15,25 @@ check_v2_signature(char *path, unsigned expected_size, unsigned expected_hash)
|
||||
loff_t pos;
|
||||
|
||||
int sign = -1;
|
||||
int i;
|
||||
struct file *fp = filp_open(path, O_RDONLY, 0);
|
||||
if (IS_ERR(fp)) {
|
||||
pr_err("open %s error.", path);
|
||||
return PTR_ERR(fp);
|
||||
}
|
||||
|
||||
// disable inotify for this file
|
||||
fp->f_mode |= FMODE_NONOTIFY;
|
||||
|
||||
sign = 1;
|
||||
// https://en.wikipedia.org/wiki/Zip_(file_format)#End_of_central_directory_record_(EOCD)
|
||||
for (int i = 0;; ++i) {
|
||||
for (i = 0;; ++i) {
|
||||
unsigned short n;
|
||||
pos = generic_file_llseek(fp, -i - 2, SEEK_END);
|
||||
kernel_read(fp, &n, 2, &pos);
|
||||
ksu_kernel_read_compat(fp, &n, 2, &pos);
|
||||
if (n == i) {
|
||||
pos -= 22;
|
||||
kernel_read(fp, &size4, 4, &pos);
|
||||
ksu_kernel_read_compat(fp, &size4, 4, &pos);
|
||||
if ((size4 ^ 0xcafebabeu) == 0xccfbf1eeu) {
|
||||
break;
|
||||
}
|
||||
@@ -41,17 +46,17 @@ check_v2_signature(char *path, unsigned expected_size, unsigned expected_hash)
|
||||
|
||||
pos += 12;
|
||||
// offset
|
||||
kernel_read(fp, &size4, 0x4, &pos);
|
||||
ksu_kernel_read_compat(fp, &size4, 0x4, &pos);
|
||||
pos = size4 - 0x18;
|
||||
|
||||
kernel_read(fp, &size8, 0x8, &pos);
|
||||
kernel_read(fp, buffer, 0x10, &pos);
|
||||
ksu_kernel_read_compat(fp, &size8, 0x8, &pos);
|
||||
ksu_kernel_read_compat(fp, buffer, 0x10, &pos);
|
||||
if (strcmp((char *)buffer, "APK Sig Block 42")) {
|
||||
goto clean;
|
||||
}
|
||||
|
||||
pos = size4 - (size8 + 0x8);
|
||||
kernel_read(fp, &size_of_block, 0x8, &pos);
|
||||
ksu_kernel_read_compat(fp, &size_of_block, 0x8, &pos);
|
||||
if (size_of_block != size8) {
|
||||
goto clean;
|
||||
}
|
||||
@@ -59,37 +64,37 @@ check_v2_signature(char *path, unsigned expected_size, unsigned expected_hash)
|
||||
for (;;) {
|
||||
uint32_t id;
|
||||
uint32_t offset;
|
||||
kernel_read(fp, &size8, 0x8, &pos); // sequence length
|
||||
ksu_kernel_read_compat(fp, &size8, 0x8, &pos); // sequence length
|
||||
if (size8 == size_of_block) {
|
||||
break;
|
||||
}
|
||||
kernel_read(fp, &id, 0x4, &pos); // id
|
||||
ksu_kernel_read_compat(fp, &id, 0x4, &pos); // id
|
||||
offset = 4;
|
||||
pr_info("id: 0x%08x\n", id);
|
||||
if ((id ^ 0xdeadbeefu) == 0xafa439f5u ||
|
||||
(id ^ 0xdeadbeefu) == 0x2efed62f) {
|
||||
kernel_read(fp, &size4, 0x4,
|
||||
ksu_kernel_read_compat(fp, &size4, 0x4,
|
||||
&pos); // signer-sequence length
|
||||
kernel_read(fp, &size4, 0x4, &pos); // signer length
|
||||
kernel_read(fp, &size4, 0x4,
|
||||
ksu_kernel_read_compat(fp, &size4, 0x4, &pos); // signer length
|
||||
ksu_kernel_read_compat(fp, &size4, 0x4,
|
||||
&pos); // signed data length
|
||||
offset += 0x4 * 3;
|
||||
|
||||
kernel_read(fp, &size4, 0x4,
|
||||
ksu_kernel_read_compat(fp, &size4, 0x4,
|
||||
&pos); // digests-sequence length
|
||||
pos += size4;
|
||||
offset += 0x4 + size4;
|
||||
|
||||
kernel_read(fp, &size4, 0x4,
|
||||
ksu_kernel_read_compat(fp, &size4, 0x4,
|
||||
&pos); // certificates length
|
||||
kernel_read(fp, &size4, 0x4,
|
||||
ksu_kernel_read_compat(fp, &size4, 0x4,
|
||||
&pos); // certificate length
|
||||
offset += 0x4 * 2;
|
||||
#if 0
|
||||
int hash = 1;
|
||||
signed char c;
|
||||
for (unsigned i = 0; i < size4; ++i) {
|
||||
kernel_read(fp, &c, 0x1, &pos);
|
||||
for (i = 0; i < size4; ++i) {
|
||||
ksu_kernel_read_compat(fp, &c, 0x1, &pos);
|
||||
hash = 31 * hash + c;
|
||||
}
|
||||
offset += size4;
|
||||
@@ -98,8 +103,8 @@ check_v2_signature(char *path, unsigned expected_size, unsigned expected_hash)
|
||||
if (size4 == expected_size) {
|
||||
int hash = 1;
|
||||
signed char c;
|
||||
for (unsigned i = 0; i < size4; ++i) {
|
||||
kernel_read(fp, &c, 0x1, &pos);
|
||||
for (i = 0; i < size4; ++i) {
|
||||
ksu_kernel_read_compat(fp, &c, 0x1, &pos);
|
||||
hash = 31 * hash + c;
|
||||
}
|
||||
offset += size4;
|
||||
@@ -172,4 +177,4 @@ int is_manager_apk(char *path)
|
||||
return check_v2_signature(path, EXPECTED_SIZE, EXPECTED_HASH);
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -22,6 +22,9 @@
|
||||
#include "manager.h"
|
||||
#include "selinux/selinux.h"
|
||||
#include "uid_observer.h"
|
||||
#include "kernel_compat.h"
|
||||
|
||||
extern int handle_sepolicy(unsigned long arg3, void __user *arg4);
|
||||
|
||||
static inline bool is_allow_su()
|
||||
{
|
||||
@@ -197,9 +200,9 @@ int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3,
|
||||
u32 version = KERNEL_SU_VERSION;
|
||||
if (copy_to_user(arg3, &version, sizeof(version))) {
|
||||
pr_err("prctl reply error, cmd: %d\n", arg2);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (arg2 == CMD_REPORT_EVENT) {
|
||||
@@ -230,6 +233,51 @@ int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3,
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (arg2 == CMD_SET_SEPOLICY) {
|
||||
if (!handle_sepolicy(arg3, arg4)) {
|
||||
if (copy_to_user(result, &reply_ok, sizeof(reply_ok))) {
|
||||
pr_err("sepolicy: prctl reply error\n");
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (arg2 == CMD_CHECK_SAFEMODE) {
|
||||
if (ksu_is_safe_mode()) {
|
||||
pr_warn("safemode enabled!\n");
|
||||
if (copy_to_user(result, &reply_ok, sizeof(reply_ok))) {
|
||||
pr_err("safemode: prctl reply error\n");
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (arg2 == CMD_GET_ALLOW_LIST || arg2 == CMD_GET_DENY_LIST) {
|
||||
if (is_manager() || 0 == current_uid().val) {
|
||||
u32 array[128];
|
||||
u32 array_length;
|
||||
bool success =
|
||||
ksu_get_allow_list(array, &array_length,
|
||||
arg2 == CMD_GET_ALLOW_LIST);
|
||||
if (success) {
|
||||
if (!copy_to_user(arg4, &array_length,
|
||||
sizeof(array_length)) &&
|
||||
!copy_to_user(arg3, array,
|
||||
sizeof(u32) * array_length)) {
|
||||
if (copy_to_user(result, &reply_ok,
|
||||
sizeof(reply_ok))) {
|
||||
pr_err("prctl reply error, cmd: %d\n",
|
||||
arg2);
|
||||
}
|
||||
} else {
|
||||
pr_err("prctl copy allowlist error\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// all other cmds are for 'root manager'
|
||||
if (!is_manager()) {
|
||||
pr_info("Only manager can do cmd: %d\n", arg2);
|
||||
@@ -248,25 +296,7 @@ int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3,
|
||||
}
|
||||
}
|
||||
ksu_show_allow_list();
|
||||
} else if (arg2 == CMD_GET_ALLOW_LIST || arg2 == CMD_GET_DENY_LIST) {
|
||||
u32 array[128];
|
||||
u32 array_length;
|
||||
bool success = ksu_get_allow_list(array, &array_length,
|
||||
arg2 == CMD_GET_ALLOW_LIST);
|
||||
if (success) {
|
||||
if (!copy_to_user(arg4, &array_length,
|
||||
sizeof(array_length)) &&
|
||||
!copy_to_user(arg3, array,
|
||||
sizeof(u32) * array_length)) {
|
||||
if (copy_to_user(result, &reply_ok,
|
||||
sizeof(reply_ok))) {
|
||||
pr_err("prctl reply error, cmd: %d\n",
|
||||
arg2);
|
||||
}
|
||||
} else {
|
||||
pr_err("prctl copy allowlist error\n");
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -344,7 +374,23 @@ static int ksu_task_prctl(int option, unsigned long arg2, unsigned long arg3,
|
||||
ksu_handle_prctl(option, arg2, arg3, arg4, arg5);
|
||||
return -ENOSYS;
|
||||
}
|
||||
|
||||
// kernel 4.4 and 4.9
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0)
|
||||
static int ksu_key_permission(key_ref_t key_ref, const struct cred *cred,
|
||||
unsigned perm)
|
||||
{
|
||||
if (init_session_keyring != NULL) {
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(current->comm, "init")) {
|
||||
// we are only interested in `init` process
|
||||
return 0;
|
||||
}
|
||||
init_session_keyring = cred->session_keyring;
|
||||
pr_info("kernel_compat: got init_session_keyring");
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
static int ksu_inode_rename(struct inode *old_inode, struct dentry *old_dentry,
|
||||
struct inode *new_inode, struct dentry *new_dentry)
|
||||
{
|
||||
@@ -354,6 +400,9 @@ static int ksu_inode_rename(struct inode *old_inode, struct dentry *old_dentry,
|
||||
static struct security_hook_list ksu_hooks[] = {
|
||||
LSM_HOOK_INIT(task_prctl, ksu_task_prctl),
|
||||
LSM_HOOK_INIT(inode_rename, ksu_inode_rename),
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0)
|
||||
LSM_HOOK_INIT(key_permission, ksu_key_permission)
|
||||
#endif
|
||||
};
|
||||
|
||||
void __init ksu_lsm_hook_init(void)
|
||||
|
||||
5
kernel/embed_ksud.c
Normal file
5
kernel/embed_ksud.c
Normal file
@@ -0,0 +1,5 @@
|
||||
// WARNING: THIS IS A STUB FILE
|
||||
// This file will be regenerated by CI
|
||||
|
||||
unsigned int ksud_size = 0;
|
||||
const char ksud[0] = {};
|
||||
@@ -21,4 +21,8 @@ int ksu_handle_vfs_read(struct file **file_ptr, char __user **buf_ptr,
|
||||
int ksu_handle_execveat(int *fd, struct filename **filename_ptr, void *argv,
|
||||
void *envp, int *flags);
|
||||
|
||||
#endif
|
||||
// For volume button
|
||||
int ksu_handle_input_handle_event(unsigned int *type, unsigned int *code,
|
||||
int *value);
|
||||
|
||||
#endif
|
||||
|
||||
34
kernel/kernel_compat.c
Normal file
34
kernel/kernel_compat.c
Normal file
@@ -0,0 +1,34 @@
|
||||
#include "linux/version.h"
|
||||
#include "linux/fs.h"
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0)
|
||||
#include "linux/key.h"
|
||||
#include "linux/errno.h"
|
||||
struct key *init_session_keyring = NULL;
|
||||
#endif
|
||||
ssize_t ksu_kernel_read_compat(struct file *p, void *buf, size_t count, loff_t *pos){
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0)
|
||||
return kernel_read(p, buf, count, pos);
|
||||
#else
|
||||
loff_t offset = pos ? *pos : 0;
|
||||
ssize_t result = kernel_read(p, offset, (char *)buf, count);
|
||||
if (pos && result > 0)
|
||||
{
|
||||
*pos = offset + result;
|
||||
}
|
||||
return result;
|
||||
#endif
|
||||
}
|
||||
|
||||
ssize_t ksu_kernel_write_compat(struct file *p, const void *buf, size_t count, loff_t *pos){
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0)
|
||||
return kernel_write(p, buf, count, pos);
|
||||
#else
|
||||
loff_t offset = pos ? *pos : 0;
|
||||
ssize_t result = kernel_write(p, buf, count, offset);
|
||||
if (pos && result > 0)
|
||||
{
|
||||
*pos = offset + result;
|
||||
}
|
||||
return result;
|
||||
#endif
|
||||
}
|
||||
42
kernel/kernel_compat.h
Normal file
42
kernel/kernel_compat.h
Normal file
@@ -0,0 +1,42 @@
|
||||
#ifndef __KSU_H_KERNEL_COMPAT
|
||||
#define __KSU_H_KERNEL_COMPAT
|
||||
|
||||
#include "linux/fs.h"
|
||||
#include "linux/key.h"
|
||||
#include "linux/version.h"
|
||||
|
||||
extern struct key *init_session_keyring;
|
||||
|
||||
extern ssize_t ksu_kernel_read_compat(struct file *p, void *buf, size_t count, loff_t *pos);
|
||||
extern ssize_t ksu_kernel_write_compat(struct file *p, const void *buf, size_t count, loff_t *pos);
|
||||
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0)
|
||||
static inline int install_session_keyring(struct key *keyring)
|
||||
{
|
||||
struct cred *new;
|
||||
int ret;
|
||||
|
||||
new = prepare_creds();
|
||||
if (!new)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = install_session_keyring_to_cred(new, keyring);
|
||||
if (ret < 0) {
|
||||
abort_creds(new);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return commit_creds(new);
|
||||
}
|
||||
#define KWORKER_INSTALL_KEYRING() \
|
||||
static bool keyring_installed = false; \
|
||||
if (init_session_keyring != NULL && !keyring_installed) \
|
||||
{ \
|
||||
install_session_keyring(init_session_keyring); \
|
||||
keyring_installed = true; \
|
||||
}
|
||||
#else
|
||||
#define KWORKER_INSTALL_KEYRING()
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -3,7 +3,12 @@
|
||||
|
||||
#include "linux/workqueue.h"
|
||||
|
||||
#define KERNEL_SU_VERSION 11
|
||||
#ifndef KSU_GIT_VERSION
|
||||
#warning "KSU_GIT_VERSION not defined! It is better to make KernelSU a git submodule!"
|
||||
#define KERNEL_SU_VERSION (16)
|
||||
#else
|
||||
#define KERNEL_SU_VERSION (10000 + KSU_GIT_VERSION + 200) // major * 10000 + git version + 200 for historical reasons
|
||||
#endif
|
||||
|
||||
#define KERNEL_SU_OPTION 0xDEADBEEF
|
||||
|
||||
@@ -15,6 +20,8 @@
|
||||
#define CMD_GET_ALLOW_LIST 5
|
||||
#define CMD_GET_DENY_LIST 6
|
||||
#define CMD_REPORT_EVENT 7
|
||||
#define CMD_SET_SEPOLICY 8
|
||||
#define CMD_CHECK_SAFEMODE 9
|
||||
|
||||
#define EVENT_POST_FS_DATA 1
|
||||
#define EVENT_BOOT_COMPLETED 2
|
||||
|
||||
160
kernel/ksud.c
160
kernel/ksud.c
@@ -1,18 +1,22 @@
|
||||
#include "asm/current.h"
|
||||
#include "linux/string.h"
|
||||
#include "linux/cred.h"
|
||||
#include "linux/dcache.h"
|
||||
#include "linux/err.h"
|
||||
#include "linux/fs.h"
|
||||
#include "linux/input-event-codes.h"
|
||||
#include "linux/kprobes.h"
|
||||
#include "linux/printk.h"
|
||||
#include "linux/types.h"
|
||||
#include "linux/uaccess.h"
|
||||
#include "linux/version.h"
|
||||
#include "linux/workqueue.h"
|
||||
#include "linux/input.h"
|
||||
|
||||
#include "allowlist.h"
|
||||
#include "arch.h"
|
||||
#include "klog.h" // IWYU pragma: keep
|
||||
#include "ksud.h"
|
||||
#include "selinux/selinux.h"
|
||||
|
||||
static const char KERNEL_SU_RC[] =
|
||||
@@ -20,32 +24,35 @@ static const char KERNEL_SU_RC[] =
|
||||
|
||||
"on post-fs-data\n"
|
||||
// We should wait for the post-fs-data finish
|
||||
" exec u:r:su:s0 root -- /data/adb/ksud post-fs-data\n"
|
||||
" exec u:r:su:s0 root -- " KSUD_PATH " post-fs-data\n"
|
||||
"\n"
|
||||
|
||||
"on nonencrypted\n"
|
||||
" exec u:r:su:s0 root -- /data/adb/ksud services\n"
|
||||
" exec u:r:su:s0 root -- " KSUD_PATH " services\n"
|
||||
"\n"
|
||||
|
||||
"on property:vold.decrypt=trigger_restart_framework\n"
|
||||
" exec u:r:su:s0 root -- /data/adb/ksud services\n"
|
||||
" exec u:r:su:s0 root -- " KSUD_PATH " services\n"
|
||||
"\n"
|
||||
|
||||
"on property:sys.boot_completed=1\n"
|
||||
" exec u:r:su:s0 root -- /data/adb/ksud boot-completed\n"
|
||||
" exec u:r:su:s0 root -- " KSUD_PATH " boot-completed\n"
|
||||
"\n"
|
||||
|
||||
"\n";
|
||||
|
||||
static void stop_vfs_read_hook();
|
||||
static void stop_execve_hook();
|
||||
static void stop_input_hook();
|
||||
|
||||
#ifdef CONFIG_KPROBES
|
||||
static struct work_struct stop_vfs_read_work;
|
||||
static struct work_struct stop_execve_hook_work;
|
||||
static struct work_struct stop_input_hook_work;
|
||||
#else
|
||||
static bool vfs_read_hook = true;
|
||||
static bool execveat_hook = true;
|
||||
static bool input_hook = true;
|
||||
#endif
|
||||
|
||||
void on_post_fs_data(void)
|
||||
@@ -56,8 +63,10 @@ void on_post_fs_data(void)
|
||||
return;
|
||||
}
|
||||
done = true;
|
||||
pr_info("ksu_load_allow_list");
|
||||
pr_info("on_post_fs_data!");
|
||||
ksu_load_allow_list();
|
||||
// sanity check, this may influence the performance
|
||||
stop_input_hook();
|
||||
}
|
||||
|
||||
int ksu_handle_execveat_ksud(int *fd, struct filename **filename_ptr,
|
||||
@@ -105,6 +114,35 @@ int ksu_handle_execveat_ksud(int *fd, struct filename **filename_ptr,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t (*orig_read)(struct file *, char __user *, size_t, loff_t *);
|
||||
static ssize_t (*orig_read_iter)(struct kiocb *, struct iov_iter *);
|
||||
static struct file_operations fops_proxy;
|
||||
static ssize_t read_count_append = 0;
|
||||
|
||||
static ssize_t read_proxy(struct file *file, char __user *buf, size_t count,
|
||||
loff_t *pos)
|
||||
{
|
||||
bool first_read = file->f_pos == 0;
|
||||
ssize_t ret = orig_read(file, buf, count, pos);
|
||||
if (first_read) {
|
||||
pr_info("read_proxy append %ld + %ld", ret, read_count_append);
|
||||
ret += read_count_append;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t read_iter_proxy(struct kiocb *iocb, struct iov_iter *to)
|
||||
{
|
||||
bool first_read = iocb->ki_pos == 0;
|
||||
ssize_t ret = orig_read_iter(iocb, to);
|
||||
if (first_read) {
|
||||
pr_info("read_iter_proxy append %ld + %ld", ret,
|
||||
read_count_append);
|
||||
ret += read_count_append;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int ksu_handle_vfs_read(struct file **file_ptr, char __user **buf_ptr,
|
||||
size_t *count_ptr, loff_t **pos)
|
||||
{
|
||||
@@ -177,12 +215,80 @@ int ksu_handle_vfs_read(struct file **file_ptr, char __user **buf_ptr,
|
||||
return 0;
|
||||
}
|
||||
|
||||
// we've succeed to insert ksud.rc, now we need to proxy the read and modify the result!
|
||||
// But, we can not modify the file_operations directly, because it's in read-only memory.
|
||||
// We just replace the whole file_operations with a proxy one.
|
||||
memcpy(&fops_proxy, file->f_op, sizeof(struct file_operations));
|
||||
orig_read = file->f_op->read;
|
||||
if (orig_read) {
|
||||
fops_proxy.read = read_proxy;
|
||||
}
|
||||
orig_read_iter = file->f_op->read_iter;
|
||||
if (orig_read_iter) {
|
||||
fops_proxy.read_iter = read_iter_proxy;
|
||||
}
|
||||
// replace the file_operations
|
||||
file->f_op = &fops_proxy;
|
||||
read_count_append = rc_count;
|
||||
|
||||
*buf_ptr = buf + rc_count;
|
||||
*count_ptr = count - rc_count;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static unsigned int volumedown_pressed_count = 0;
|
||||
|
||||
static bool is_volumedown_enough(unsigned int count)
|
||||
{
|
||||
return count >= 3;
|
||||
}
|
||||
|
||||
int ksu_handle_input_handle_event(unsigned int *type, unsigned int *code,
|
||||
int *value)
|
||||
{
|
||||
#ifndef CONFIG_KPROBES
|
||||
if (!input_hook) {
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
if (*type == EV_KEY && *code == KEY_VOLUMEDOWN) {
|
||||
int val = *value;
|
||||
pr_info("KEY_VOLUMEDOWN val: %d\n", val);
|
||||
if (val) {
|
||||
// key pressed, count it
|
||||
volumedown_pressed_count += 1;
|
||||
if (is_volumedown_enough(volumedown_pressed_count)) {
|
||||
stop_input_hook();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool ksu_is_safe_mode()
|
||||
{
|
||||
static bool safe_mode = false;
|
||||
if (safe_mode) {
|
||||
// don't need to check again, userspace may call multiple times
|
||||
return true;
|
||||
}
|
||||
|
||||
// stop hook first!
|
||||
stop_input_hook();
|
||||
|
||||
pr_info("volumedown_pressed_count: %d\n", volumedown_pressed_count);
|
||||
if (is_volumedown_enough(volumedown_pressed_count)) {
|
||||
// pressed over 3 times
|
||||
pr_info("KEY_VOLUMEDOWN pressed max times, safe mode detected!\n");
|
||||
safe_mode = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_KPROBES
|
||||
|
||||
// https://elixir.bootlin.com/linux/v5.10.158/source/fs/exec.c#L1864
|
||||
@@ -208,14 +314,21 @@ static int read_handler_pre(struct kprobe *p, struct pt_regs *regs)
|
||||
return ksu_handle_vfs_read(file_ptr, buf_ptr, count_ptr, pos_ptr);
|
||||
}
|
||||
|
||||
static int input_handle_event_handler_pre(struct kprobe *p,
|
||||
struct pt_regs *regs)
|
||||
{
|
||||
unsigned int *type = (unsigned int *)&PT_REGS_PARM2(regs);
|
||||
unsigned int *code = (unsigned int *)&PT_REGS_PARM3(regs);
|
||||
int *value = (int *)&PT_REGS_PARM4(regs);
|
||||
return ksu_handle_input_handle_event(type, code, value);
|
||||
}
|
||||
|
||||
static struct kprobe execve_kp = {
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 9, 0)
|
||||
.symbol_name = "do_execveat_common",
|
||||
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(4, 19, 0) && \
|
||||
LINUX_VERSION_CODE < KERNEL_VERSION(5, 9, 0)
|
||||
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(4, 19, 0)
|
||||
.symbol_name = "__do_execve_file",
|
||||
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0) && \
|
||||
LINUX_VERSION_CODE < KERNEL_VERSION(4, 19, 0)
|
||||
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3, 19, 0)
|
||||
.symbol_name = "do_execveat_common",
|
||||
#endif
|
||||
.pre_handler = execve_handler_pre,
|
||||
@@ -226,6 +339,11 @@ static struct kprobe vfs_read_kp = {
|
||||
.pre_handler = read_handler_pre,
|
||||
};
|
||||
|
||||
static struct kprobe input_handle_event_kp = {
|
||||
.symbol_name = "input_handle_event",
|
||||
.pre_handler = input_handle_event_handler_pre,
|
||||
};
|
||||
|
||||
static void do_stop_vfs_read_hook(struct work_struct *work)
|
||||
{
|
||||
unregister_kprobe(&vfs_read_kp);
|
||||
@@ -235,6 +353,11 @@ static void do_stop_execve_hook(struct work_struct *work)
|
||||
{
|
||||
unregister_kprobe(&execve_kp);
|
||||
}
|
||||
|
||||
static void do_stop_input_hook(struct work_struct *work)
|
||||
{
|
||||
unregister_kprobe(&input_handle_event_kp);
|
||||
}
|
||||
#endif
|
||||
|
||||
static void stop_vfs_read_hook()
|
||||
@@ -257,6 +380,21 @@ static void stop_execve_hook()
|
||||
#endif
|
||||
}
|
||||
|
||||
static void stop_input_hook()
|
||||
{
|
||||
static bool input_hook_stopped = false;
|
||||
if (input_hook_stopped) {
|
||||
return;
|
||||
}
|
||||
input_hook_stopped = true;
|
||||
#ifdef CONFIG_KPROBES
|
||||
bool ret = schedule_work(&stop_input_hook_work);
|
||||
pr_info("unregister input kprobe: %d!\n", ret);
|
||||
#else
|
||||
input_hook = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
// ksud: module support
|
||||
void ksu_enable_ksud()
|
||||
{
|
||||
@@ -269,7 +407,11 @@ void ksu_enable_ksud()
|
||||
ret = register_kprobe(&vfs_read_kp);
|
||||
pr_info("ksud: vfs_read_kp: %d\n", ret);
|
||||
|
||||
ret = register_kprobe(&input_handle_event_kp);
|
||||
pr_info("ksud: input_handle_event_kp: %d\n", ret);
|
||||
|
||||
INIT_WORK(&stop_vfs_read_work, do_stop_vfs_read_hook);
|
||||
INIT_WORK(&stop_execve_hook_work, do_stop_execve_hook);
|
||||
INIT_WORK(&stop_input_hook_work, do_stop_input_hook);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
#ifndef __KSU_H_KSUD
|
||||
#define __KSU_H_KSUD
|
||||
|
||||
#define KSUD_PATH "/data/adb/ksud"
|
||||
|
||||
void on_post_fs_data(void);
|
||||
|
||||
bool ksu_is_safe_mode(void);
|
||||
|
||||
#endif
|
||||
@@ -1,9 +1,13 @@
|
||||
#include "linux/uaccess.h"
|
||||
#include "linux/types.h"
|
||||
#include "linux/version.h"
|
||||
|
||||
#include "../klog.h" // IWYU pragma: keep
|
||||
#include "selinux.h"
|
||||
#include "sepolicy.h"
|
||||
#include "ss/services.h"
|
||||
#include "linux/lsm_audit.h"
|
||||
#include "xfrm.h"
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0)
|
||||
#define SELINUX_POLICY_INSTEAD_SELINUX_SS
|
||||
@@ -11,18 +15,14 @@
|
||||
|
||||
#define KERNEL_SU_DOMAIN "su"
|
||||
#define KERNEL_SU_FILE "ksu_file"
|
||||
#define KERNEL_EXEC_TYPE "ksu_exec"
|
||||
#define ALL NULL
|
||||
|
||||
void apply_kernelsu_rules()
|
||||
static struct policydb *get_policydb(void)
|
||||
{
|
||||
struct policydb *db;
|
||||
|
||||
if (!getenforce()) {
|
||||
pr_info("SELinux permissive or disabled, don't apply rules.");
|
||||
return;
|
||||
}
|
||||
|
||||
rcu_read_lock();
|
||||
// selinux_state does not exists before 4.19
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 212)
|
||||
#ifdef SELINUX_POLICY_INSTEAD_SELINUX_SS
|
||||
struct selinux_policy *policy = rcu_dereference(selinux_state.policy);
|
||||
db = &policy->policydb;
|
||||
@@ -30,6 +30,21 @@ void apply_kernelsu_rules()
|
||||
struct selinux_ss *ss = rcu_dereference(selinux_state.ss);
|
||||
db = &ss->policydb;
|
||||
#endif
|
||||
#else
|
||||
db = &policydb;
|
||||
#endif
|
||||
return db;
|
||||
}
|
||||
|
||||
void apply_kernelsu_rules()
|
||||
{
|
||||
if (!getenforce()) {
|
||||
pr_info("SELinux permissive or disabled, don't apply rules.");
|
||||
return;
|
||||
}
|
||||
|
||||
rcu_read_lock();
|
||||
struct policydb *db = get_policydb();
|
||||
|
||||
ksu_permissive(db, KERNEL_SU_DOMAIN);
|
||||
ksu_typeattribute(db, KERNEL_SU_DOMAIN, "mlstrustedsubject");
|
||||
@@ -51,7 +66,7 @@ void apply_kernelsu_rules()
|
||||
ksu_allowxperm(db, KERNEL_SU_DOMAIN, ALL, "chr_file", ALL);
|
||||
}
|
||||
|
||||
// we need to save allowlist in /data/adb
|
||||
// we need to save allowlist in /data/adb/ksu
|
||||
ksu_allow(db, "kernel", "adb_data_file", "dir", ALL);
|
||||
ksu_allow(db, "kernel", "adb_data_file", "file", ALL);
|
||||
// we may need to do mount on shell
|
||||
@@ -61,12 +76,14 @@ void apply_kernelsu_rules()
|
||||
// Android 10+:
|
||||
// http://aospxref.com/android-12.0.0_r3/xref/system/sepolicy/private/file_contexts#512
|
||||
ksu_allow(db, "kernel", "packages_list_file", "file", ALL);
|
||||
// Kernel 4.4
|
||||
ksu_allow(db, "kernel", "packages_list_file", "dir", ALL);
|
||||
// Android 9-:
|
||||
// http://aospxref.com/android-9.0.0_r61/xref/system/sepolicy/private/file_contexts#360
|
||||
ksu_allow(db, "kernel", "system_data_file", "file", ALL);
|
||||
|
||||
ksu_allow(db, "kernel", "system_data_file", "dir", ALL);
|
||||
// our ksud triggered by init
|
||||
ksu_allow(db, "init", "adb_data_file", "file", "execute");
|
||||
ksu_allow(db, "init", "adb_data_file", "file", ALL);
|
||||
ksu_allow(db, "init", KERNEL_SU_DOMAIN, ALL, ALL);
|
||||
|
||||
// copied from Magisk rules
|
||||
@@ -108,4 +125,341 @@ void apply_kernelsu_rules()
|
||||
"write");
|
||||
|
||||
rcu_read_unlock();
|
||||
}
|
||||
}
|
||||
|
||||
#define MAX_SEPOL_LEN 128
|
||||
|
||||
#define CMD_NORMAL_PERM 1
|
||||
#define CMD_XPERM 2
|
||||
#define CMD_TYPE_STATE 3
|
||||
#define CMD_TYPE 4
|
||||
#define CMD_TYPE_ATTR 5
|
||||
#define CMD_ATTR 6
|
||||
#define CMD_TYPE_TRANSITION 7
|
||||
#define CMD_TYPE_CHANGE 8
|
||||
#define CMD_GENFSCON 9
|
||||
|
||||
struct sepol_data {
|
||||
u32 cmd;
|
||||
u32 subcmd;
|
||||
char __user *sepol1;
|
||||
char __user *sepol2;
|
||||
char __user *sepol3;
|
||||
char __user *sepol4;
|
||||
char __user *sepol5;
|
||||
char __user *sepol6;
|
||||
char __user *sepol7;
|
||||
};
|
||||
|
||||
static int get_object(char *buf, char __user *user_object, size_t buf_sz,
|
||||
char **object)
|
||||
{
|
||||
if (!user_object) {
|
||||
*object = ALL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (strncpy_from_user(buf, user_object, buf_sz) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
*object = buf;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// reset avc cache table, otherwise the new rules will not take effect if already denied
|
||||
static void reset_avc_cache() {
|
||||
#if ((KERNEL_VERSION(4, 14, 0) <= LINUX_VERSION_CODE) && (LINUX_VERSION_CODE < KERNEL_VERSION(4, 14, 163))) || (LINUX_VERSION_CODE < KERNEL_VERSION(4, 9, 212))
|
||||
avc_ss_reset(0);
|
||||
selnl_notify_policyload(0);
|
||||
selinux_status_update_policyload(0);
|
||||
#else
|
||||
struct selinux_avc *avc = selinux_state.avc;
|
||||
avc_ss_reset(avc, 0);
|
||||
selnl_notify_policyload(0);
|
||||
selinux_status_update_policyload(&selinux_state, 0);
|
||||
#endif
|
||||
selinux_xfrm_notify_policyload();
|
||||
}
|
||||
|
||||
int handle_sepolicy(unsigned long arg3, void __user *arg4)
|
||||
{
|
||||
if (!arg4) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!getenforce()) {
|
||||
pr_info("SELinux permissive or disabled, don't apply policies.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct sepol_data data;
|
||||
if (copy_from_user(&data, arg4, sizeof(struct sepol_data))) {
|
||||
pr_err("sepol: copy sepol_data failed.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
u32 cmd = data.cmd;
|
||||
u32 subcmd = data.subcmd;
|
||||
|
||||
rcu_read_lock();
|
||||
|
||||
struct policydb *db = get_policydb();
|
||||
|
||||
int ret = -1;
|
||||
if (cmd == CMD_NORMAL_PERM) {
|
||||
char src_buf[MAX_SEPOL_LEN];
|
||||
char tgt_buf[MAX_SEPOL_LEN];
|
||||
char cls_buf[MAX_SEPOL_LEN];
|
||||
char perm_buf[MAX_SEPOL_LEN];
|
||||
|
||||
char *s, *t, *c, *p;
|
||||
if (get_object(src_buf, data.sepol1, sizeof(src_buf), &s) < 0) {
|
||||
pr_err("sepol: copy src failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (get_object(tgt_buf, data.sepol2, sizeof(tgt_buf), &t) < 0) {
|
||||
pr_err("sepol: copy tgt failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (get_object(cls_buf, data.sepol3, sizeof(cls_buf), &c) < 0) {
|
||||
pr_err("sepol: copy cls failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (get_object(perm_buf, data.sepol4, sizeof(perm_buf), &p) <
|
||||
0) {
|
||||
pr_err("sepol: copy perm failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
bool success = false;
|
||||
if (subcmd == 1) {
|
||||
success = ksu_allow(db, s, t, c, p);
|
||||
} else if (subcmd == 2) {
|
||||
success = ksu_deny(db, s, t, c, p);
|
||||
} else if (subcmd == 3) {
|
||||
success = ksu_auditallow(db, s, t, c, p);
|
||||
} else if (subcmd == 4) {
|
||||
success = ksu_dontaudit(db, s, t, c, p);
|
||||
} else {
|
||||
pr_err("sepol: unknown subcmd: %d", subcmd);
|
||||
}
|
||||
ret = success ? 0 : -1;
|
||||
|
||||
} else if (cmd == CMD_XPERM) {
|
||||
char src_buf[MAX_SEPOL_LEN];
|
||||
char tgt_buf[MAX_SEPOL_LEN];
|
||||
char cls_buf[MAX_SEPOL_LEN];
|
||||
|
||||
char __maybe_unused
|
||||
operation[MAX_SEPOL_LEN]; // it is always ioctl now!
|
||||
char perm_set[MAX_SEPOL_LEN];
|
||||
|
||||
char *s, *t, *c;
|
||||
if (get_object(src_buf, data.sepol1, sizeof(src_buf), &s) < 0) {
|
||||
pr_err("sepol: copy src failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (get_object(tgt_buf, data.sepol2, sizeof(tgt_buf), &t) < 0) {
|
||||
pr_err("sepol: copy tgt failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (get_object(cls_buf, data.sepol3, sizeof(cls_buf), &c) < 0) {
|
||||
pr_err("sepol: copy cls failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (strncpy_from_user(operation, data.sepol4,
|
||||
sizeof(operation)) < 0) {
|
||||
pr_err("sepol: copy operation failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (strncpy_from_user(perm_set, data.sepol5, sizeof(perm_set)) <
|
||||
0) {
|
||||
pr_err("sepol: copy perm_set failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
bool success = false;
|
||||
if (subcmd == 1) {
|
||||
success = ksu_allowxperm(db, s, t, c, perm_set);
|
||||
} else if (subcmd == 2) {
|
||||
success = ksu_auditallowxperm(db, s, t, c, perm_set);
|
||||
} else if (subcmd == 3) {
|
||||
success = ksu_dontauditxperm(db, s, t, c, perm_set);
|
||||
} else {
|
||||
pr_err("sepol: unknown subcmd: %d", subcmd);
|
||||
}
|
||||
ret = success ? 0 : -1;
|
||||
} else if (cmd == CMD_TYPE_STATE) {
|
||||
char src[MAX_SEPOL_LEN];
|
||||
|
||||
if (strncpy_from_user(src, data.sepol1, sizeof(src)) < 0) {
|
||||
pr_err("sepol: copy src failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
bool success = false;
|
||||
if (subcmd == 1) {
|
||||
success = ksu_permissive(db, src);
|
||||
} else if (subcmd == 2) {
|
||||
success = ksu_enforce(db, src);
|
||||
} else {
|
||||
pr_err("sepol: unknown subcmd: %d", subcmd);
|
||||
}
|
||||
if (success)
|
||||
ret = 0;
|
||||
|
||||
} else if (cmd == CMD_TYPE || cmd == CMD_TYPE_ATTR) {
|
||||
char type[MAX_SEPOL_LEN];
|
||||
char attr[MAX_SEPOL_LEN];
|
||||
|
||||
if (strncpy_from_user(type, data.sepol1, sizeof(type)) < 0) {
|
||||
pr_err("sepol: copy type failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (strncpy_from_user(attr, data.sepol2, sizeof(attr)) < 0) {
|
||||
pr_err("sepol: copy attr failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
bool success = false;
|
||||
if (cmd == CMD_TYPE) {
|
||||
success = ksu_type(db, type, attr);
|
||||
} else {
|
||||
success = ksu_typeattribute(db, type, attr);
|
||||
}
|
||||
if (!success) {
|
||||
pr_err("sepol: %d failed.\n", cmd);
|
||||
goto exit;
|
||||
}
|
||||
ret = 0;
|
||||
|
||||
} else if (cmd == CMD_ATTR) {
|
||||
char attr[MAX_SEPOL_LEN];
|
||||
|
||||
if (strncpy_from_user(attr, data.sepol1, sizeof(attr)) < 0) {
|
||||
pr_err("sepol: copy attr failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (!ksu_attribute(db, attr)) {
|
||||
pr_err("sepol: %d failed.\n", cmd);
|
||||
goto exit;
|
||||
}
|
||||
ret = 0;
|
||||
|
||||
} else if (cmd == CMD_TYPE_TRANSITION) {
|
||||
char src[MAX_SEPOL_LEN];
|
||||
char tgt[MAX_SEPOL_LEN];
|
||||
char cls[MAX_SEPOL_LEN];
|
||||
char default_type[MAX_SEPOL_LEN];
|
||||
char object[MAX_SEPOL_LEN];
|
||||
|
||||
if (strncpy_from_user(src, data.sepol1, sizeof(src)) < 0) {
|
||||
pr_err("sepol: copy src failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (strncpy_from_user(tgt, data.sepol2, sizeof(tgt)) < 0) {
|
||||
pr_err("sepol: copy tgt failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (strncpy_from_user(cls, data.sepol3, sizeof(cls)) < 0) {
|
||||
pr_err("sepol: copy cls failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (strncpy_from_user(default_type, data.sepol4,
|
||||
sizeof(default_type)) < 0) {
|
||||
pr_err("sepol: copy default_type failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
char *real_object;
|
||||
if (data.sepol5 == NULL) {
|
||||
real_object = NULL;
|
||||
} else {
|
||||
if (strncpy_from_user(object, data.sepol5,
|
||||
sizeof(object)) < 0) {
|
||||
pr_err("sepol: copy object failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
real_object = object;
|
||||
}
|
||||
|
||||
bool success = ksu_type_transition(db, src, tgt, cls,
|
||||
default_type, real_object);
|
||||
if (success)
|
||||
ret = 0;
|
||||
|
||||
} else if (cmd == CMD_TYPE_CHANGE) {
|
||||
char src[MAX_SEPOL_LEN];
|
||||
char tgt[MAX_SEPOL_LEN];
|
||||
char cls[MAX_SEPOL_LEN];
|
||||
char default_type[MAX_SEPOL_LEN];
|
||||
|
||||
if (strncpy_from_user(src, data.sepol1, sizeof(src)) < 0) {
|
||||
pr_err("sepol: copy src failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (strncpy_from_user(tgt, data.sepol2, sizeof(tgt)) < 0) {
|
||||
pr_err("sepol: copy tgt failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (strncpy_from_user(cls, data.sepol3, sizeof(cls)) < 0) {
|
||||
pr_err("sepol: copy cls failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (strncpy_from_user(default_type, data.sepol4,
|
||||
sizeof(default_type)) < 0) {
|
||||
pr_err("sepol: copy default_type failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
bool success = false;
|
||||
if (subcmd == 1) {
|
||||
success = ksu_type_change(db, src, tgt, cls,
|
||||
default_type);
|
||||
} else if (subcmd == 2) {
|
||||
success = ksu_type_member(db, src, tgt, cls,
|
||||
default_type);
|
||||
} else {
|
||||
pr_err("sepol: unknown subcmd: %d", subcmd);
|
||||
}
|
||||
if (success)
|
||||
ret = 0;
|
||||
} else if (cmd == CMD_GENFSCON) {
|
||||
char name[MAX_SEPOL_LEN];
|
||||
char path[MAX_SEPOL_LEN];
|
||||
char context[MAX_SEPOL_LEN];
|
||||
if (strncpy_from_user(name, data.sepol1, sizeof(name)) < 0) {
|
||||
pr_err("sepol: copy name failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (strncpy_from_user(path, data.sepol2, sizeof(path)) < 0) {
|
||||
pr_err("sepol: copy path failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (strncpy_from_user(context, data.sepol3, sizeof(context)) <
|
||||
0) {
|
||||
pr_err("sepol: copy context failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (!ksu_genfscon(db, name, path, context)) {
|
||||
pr_err("sepol: %d failed.\n", cmd);
|
||||
goto exit;
|
||||
}
|
||||
ret = 0;
|
||||
} else {
|
||||
pr_err("sepol: unknown cmd: %d\n", cmd);
|
||||
}
|
||||
|
||||
exit:
|
||||
rcu_read_unlock();
|
||||
|
||||
// only allow and xallow needs to reset avc cache, but we cannot do that because
|
||||
// we are in atomic context. so we just reset it every time.
|
||||
reset_avc_cache();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
#include "selinux.h"
|
||||
#include "objsec.h"
|
||||
|
||||
#include "linux/version.h"
|
||||
#include "../klog.h" // IWYU pragma: keep
|
||||
#if ((KERNEL_VERSION(4, 14, 0) <= LINUX_VERSION_CODE) && (LINUX_VERSION_CODE < KERNEL_VERSION(4, 14, 163))) || (LINUX_VERSION_CODE < KERNEL_VERSION(4, 9, 212))
|
||||
#include "avc.h"
|
||||
#endif
|
||||
|
||||
#define KERNEL_SU_DOMAIN "u:r:su:s0"
|
||||
|
||||
@@ -54,26 +57,50 @@ if (!is_domain_permissive) {
|
||||
void setenforce(bool enforce)
|
||||
{
|
||||
#ifdef CONFIG_SECURITY_SELINUX_DEVELOP
|
||||
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 163)) || ((KERNEL_VERSION(4, 10, 0) > LINUX_VERSION_CODE) && (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 212)))
|
||||
selinux_state.enforcing = enforce;
|
||||
#else
|
||||
selinux_enforcing = enforce;
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
bool getenforce()
|
||||
{
|
||||
#ifdef CONFIG_SECURITY_SELINUX_DISABLE
|
||||
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 163)) || ((KERNEL_VERSION(4, 10, 0) > LINUX_VERSION_CODE) && (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 212)))
|
||||
if (selinux_state.disabled) {
|
||||
#else
|
||||
if (selinux_disabled) {
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_SECURITY_SELINUX_DEVELOP
|
||||
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 163)) || ((KERNEL_VERSION(4, 10, 0) > LINUX_VERSION_CODE) && (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 212)))
|
||||
return selinux_state.enforcing;
|
||||
#else
|
||||
return false;
|
||||
return selinux_enforcing;
|
||||
#endif
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 9, 212)
|
||||
/*
|
||||
* get the subjective security ID of the current task
|
||||
*/
|
||||
static inline u32 current_sid(void)
|
||||
{
|
||||
const struct task_security_struct *tsec = current_security();
|
||||
|
||||
return tsec->sid;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool is_ksu_domain()
|
||||
{
|
||||
return ksu_sid && current_sid() == ksu_sid;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
#include "sepolicy.h"
|
||||
#include "linux/gfp.h"
|
||||
#include "linux/printk.h"
|
||||
#include "linux/slab.h"
|
||||
#include "linux/version.h"
|
||||
|
||||
#include "../klog.h" // IWYU pragma: keep
|
||||
#include "ss/symtab.h"
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0)
|
||||
// TODO: backport to lower kernel
|
||||
#define KSU_SUPPORT_ADD_TYPE
|
||||
#endif
|
||||
|
||||
//////////////////////////////////////////////////////
|
||||
// Declaration
|
||||
@@ -36,11 +35,12 @@ static bool add_xperm_rule(struct policydb *db, const char *s, const char *t,
|
||||
static bool add_type_rule(struct policydb *db, const char *s, const char *t,
|
||||
const char *c, const char *d, int effect);
|
||||
|
||||
static bool add_filename_trans(const char *s, const char *t, const char *c,
|
||||
const char *d, const char *o);
|
||||
static bool add_filename_trans(struct policydb *db, const char *s,
|
||||
const char *t, const char *c, const char *d,
|
||||
const char *o);
|
||||
|
||||
static bool add_genfscon(const char *fs_name, const char *path,
|
||||
const char *context);
|
||||
static bool add_genfscon(struct policydb *db, const char *fs_name,
|
||||
const char *path, const char *context);
|
||||
|
||||
static bool add_type(struct policydb *db, const char *type_name, bool attr);
|
||||
|
||||
@@ -79,6 +79,7 @@ static bool add_typeattribute(struct policydb *db, const char *type,
|
||||
// https://elixir.bootlin.com/linux/v5.9-rc1/source/security/selinux/ss/symtab.h
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 9, 0)
|
||||
#define symtab_search(s, name) hashtab_search((s)->table, name)
|
||||
#define symtab_insert(s, name, datum) hashtab_insert((s)->table, name, datum)
|
||||
#endif
|
||||
|
||||
#define avtab_for_each(avtab, cur) \
|
||||
@@ -125,14 +126,13 @@ static struct avtab_node *get_avtab_node(struct policydb *db,
|
||||
/* this is used to get the node - insertion is actually unique */
|
||||
node = avtab_insert_nonunique(&db->te_avtab, key, &avdatum);
|
||||
|
||||
int grow_size = sizeof(u16) * 4;
|
||||
int grow_size = sizeof(struct avtab_key);
|
||||
grow_size += sizeof(struct avtab_datum);
|
||||
if (key->specified & AVTAB_XPERMS) {
|
||||
grow_size += sizeof(u8);
|
||||
grow_size += sizeof(u8);
|
||||
grow_size += sizeof(u32) *
|
||||
ARRAY_SIZE(avdatum.u.xperms->perms.p);
|
||||
} else {
|
||||
grow_size += sizeof(u32) * 1;
|
||||
}
|
||||
db->len += grow_size;
|
||||
}
|
||||
@@ -453,14 +453,151 @@ static bool add_type_rule(struct policydb *db, const char *s, const char *t,
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool add_filename_trans(const char *s, const char *t, const char *c,
|
||||
const char *d, const char *o)
|
||||
// 5.9.0 : static inline int hashtab_insert(struct hashtab *h, void *key, void *datum, struct hashtab_key_params key_params)
|
||||
// 5.8.0: int hashtab_insert(struct hashtab *h, void *k, void *d);
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 9, 0)
|
||||
static u32 filenametr_hash(const void *k)
|
||||
{
|
||||
return false;
|
||||
const struct filename_trans_key *ft = k;
|
||||
unsigned long hash;
|
||||
unsigned int byte_num;
|
||||
unsigned char focus;
|
||||
|
||||
hash = ft->ttype ^ ft->tclass;
|
||||
|
||||
byte_num = 0;
|
||||
while ((focus = ft->name[byte_num++]))
|
||||
hash = partial_name_hash(focus, hash);
|
||||
return hash;
|
||||
}
|
||||
|
||||
static bool add_genfscon(const char *fs_name, const char *path,
|
||||
const char *context)
|
||||
static int filenametr_cmp(const void *k1, const void *k2)
|
||||
{
|
||||
const struct filename_trans_key *ft1 = k1;
|
||||
const struct filename_trans_key *ft2 = k2;
|
||||
int v;
|
||||
|
||||
v = ft1->ttype - ft2->ttype;
|
||||
if (v)
|
||||
return v;
|
||||
|
||||
v = ft1->tclass - ft2->tclass;
|
||||
if (v)
|
||||
return v;
|
||||
|
||||
return strcmp(ft1->name, ft2->name);
|
||||
|
||||
}
|
||||
|
||||
static const struct hashtab_key_params filenametr_key_params = {
|
||||
.hash = filenametr_hash,
|
||||
.cmp = filenametr_cmp,
|
||||
};
|
||||
#endif
|
||||
|
||||
static bool add_filename_trans(struct policydb *db, const char *s,
|
||||
const char *t, const char *c, const char *d,
|
||||
const char *o)
|
||||
{
|
||||
struct type_datum *src, *tgt, *def;
|
||||
struct class_datum *cls;
|
||||
|
||||
src = symtab_search(&db->p_types, s);
|
||||
if (src == NULL) {
|
||||
pr_warn("source type %s does not exist\n", s);
|
||||
return false;
|
||||
}
|
||||
tgt = symtab_search(&db->p_types, t);
|
||||
if (tgt == NULL) {
|
||||
pr_warn("target type %s does not exist\n", t);
|
||||
return false;
|
||||
}
|
||||
cls = symtab_search(&db->p_classes, c);
|
||||
if (cls == NULL) {
|
||||
pr_warn("class %s does not exist\n", c);
|
||||
return false;
|
||||
}
|
||||
def = symtab_search(&db->p_types, d);
|
||||
if (def == NULL) {
|
||||
pr_warn("default type %s does not exist\n", d);
|
||||
return false;
|
||||
}
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 7, 0)
|
||||
struct filename_trans_key key;
|
||||
key.ttype = tgt->value;
|
||||
key.tclass = cls->value;
|
||||
key.name = (char *)o;
|
||||
|
||||
struct filename_trans_datum *last = NULL;
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 9, 0)
|
||||
struct filename_trans_datum *trans =
|
||||
policydb_filenametr_search(db, &key);
|
||||
#else
|
||||
struct filename_trans_datum *trans =
|
||||
hashtab_search(&db->filename_trans, &key);
|
||||
#endif
|
||||
while (trans) {
|
||||
if (ebitmap_get_bit(&trans->stypes, src->value - 1)) {
|
||||
// Duplicate, overwrite existing data and return
|
||||
trans->otype = def->value;
|
||||
return true;
|
||||
}
|
||||
if (trans->otype == def->value)
|
||||
break;
|
||||
last = trans;
|
||||
trans = trans->next;
|
||||
}
|
||||
|
||||
if (trans == NULL) {
|
||||
trans = (struct filename_trans_datum*) kcalloc(sizeof(*trans), 1, GFP_ATOMIC);
|
||||
struct filename_trans_key *new_key =
|
||||
(struct filename_trans_key*) kmalloc(sizeof(*new_key), GFP_ATOMIC);
|
||||
*new_key = key;
|
||||
new_key->name = kstrdup(key.name, GFP_ATOMIC);
|
||||
trans->next = last;
|
||||
trans->otype = def->value;
|
||||
hashtab_insert(&db->filename_trans, new_key,
|
||||
trans, filenametr_key_params);
|
||||
}
|
||||
|
||||
db->compat_filename_trans_count++;
|
||||
return ebitmap_set_bit(&trans->stypes, src->value - 1, 1) == 0;
|
||||
#else // < 5.7.0, has no filename_trans_key, but struct filename_trans
|
||||
|
||||
struct filename_trans key;
|
||||
key.ttype = tgt->value;
|
||||
key.tclass = cls->value;
|
||||
key.name = (char *)o;
|
||||
|
||||
struct filename_trans_datum *trans =
|
||||
hashtab_search(db->filename_trans, &key);
|
||||
|
||||
if (trans == NULL) {
|
||||
trans = (struct filename_trans_datum*) kcalloc(sizeof(*trans), 1, GFP_ATOMIC);
|
||||
if (!trans) {
|
||||
pr_err("add_filename_trans: Failed to alloc datum");
|
||||
return false;
|
||||
}
|
||||
struct filename_trans *new_key =
|
||||
(struct filename_trans*) kmalloc(sizeof(*new_key), GFP_ATOMIC);
|
||||
if (!new_key) {
|
||||
pr_err("add_filename_trans: Failed to alloc new_key");
|
||||
return false;
|
||||
}
|
||||
*new_key = key;
|
||||
new_key->name = kstrdup(key.name, GFP_ATOMIC);
|
||||
trans->otype = def->value;
|
||||
hashtab_insert(db->filename_trans, new_key, trans);
|
||||
}
|
||||
|
||||
return ebitmap_set_bit(&db->filename_trans_ttypes, src->value - 1, 1) == 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool add_genfscon(struct policydb *db, const char *fs_name,
|
||||
const char *path, const char *context)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -475,7 +612,7 @@ static bool add_type(struct policydb *db, const char *type_name, bool attr)
|
||||
}
|
||||
|
||||
u32 value = ++db->p_types.nprim;
|
||||
type = (struct type_datum *)kmalloc(sizeof(struct type_datum),
|
||||
type = (struct type_datum *)kzalloc(sizeof(struct type_datum),
|
||||
GFP_ATOMIC);
|
||||
if (!type) {
|
||||
pr_err("add_type: alloc type_datum failed.\n");
|
||||
@@ -484,6 +621,7 @@ static bool add_type(struct policydb *db, const char *type_name, bool attr)
|
||||
|
||||
type->primary = 1;
|
||||
type->value = value;
|
||||
type->attribute = attr;
|
||||
|
||||
char *key = kstrdup(type_name, GFP_ATOMIC);
|
||||
if (!key) {
|
||||
@@ -496,6 +634,7 @@ static bool add_type(struct policydb *db, const char *type_name, bool attr)
|
||||
return false;
|
||||
}
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 1, 0)
|
||||
size_t new_size = sizeof(struct ebitmap) * db->p_types.nprim;
|
||||
struct ebitmap *new_type_attr_map_array =
|
||||
(krealloc(db->type_attr_map_array, new_size, GFP_ATOMIC));
|
||||
@@ -541,6 +680,112 @@ static bool add_type(struct policydb *db, const char *type_name, bool attr)
|
||||
}
|
||||
|
||||
return true;
|
||||
#else
|
||||
// flex_array is not extensible, we need to create a new bigger one instead
|
||||
struct flex_array *new_type_attr_map_array = flex_array_alloc(sizeof(struct ebitmap),
|
||||
db->p_types.nprim, GFP_ATOMIC | __GFP_ZERO);
|
||||
|
||||
struct flex_array *new_type_val_to_struct = flex_array_alloc(sizeof(struct type_datum *),
|
||||
db->p_types.nprim, GFP_ATOMIC | __GFP_ZERO);
|
||||
|
||||
struct flex_array *new_val_to_name_types = flex_array_alloc(sizeof(char *),
|
||||
db->symtab[SYM_TYPES].nprim, GFP_ATOMIC | __GFP_ZERO);
|
||||
|
||||
if (!new_type_attr_map_array) {
|
||||
pr_err("add_type: alloc type_attr_map_array failed\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!new_type_val_to_struct) {
|
||||
pr_err("add_type: alloc type_val_to_struct failed\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!new_val_to_name_types) {
|
||||
pr_err("add_type: alloc val_to_name failed\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// preallocate so we don't have to worry about the put ever failing
|
||||
if (flex_array_prealloc(new_type_attr_map_array, 0,
|
||||
db->p_types.nprim, GFP_ATOMIC | __GFP_ZERO)) {
|
||||
pr_err("add_type: prealloc type_attr_map_array failed\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (flex_array_prealloc(new_type_val_to_struct, 0,
|
||||
db->p_types.nprim, GFP_ATOMIC | __GFP_ZERO)) {
|
||||
pr_err("add_type: prealloc type_val_to_struct_array failed\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (flex_array_prealloc(new_val_to_name_types, 0,
|
||||
db->symtab[SYM_TYPES].nprim, GFP_ATOMIC | __GFP_ZERO)) {
|
||||
pr_err("add_type: prealloc val_to_name_types failed\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
int j;
|
||||
void *old_elem;
|
||||
// copy the old data or pointers to new flex arrays
|
||||
for (j = 0; j < db->type_attr_map_array->total_nr_elements; j++) {
|
||||
old_elem = flex_array_get(db->type_attr_map_array, j);
|
||||
if (old_elem)
|
||||
flex_array_put(new_type_attr_map_array, j,
|
||||
old_elem, GFP_ATOMIC | __GFP_ZERO);
|
||||
}
|
||||
|
||||
for (j = 0; j < db->type_val_to_struct_array->total_nr_elements; j++) {
|
||||
old_elem = flex_array_get_ptr(db->type_val_to_struct_array, j);
|
||||
if (old_elem)
|
||||
flex_array_put_ptr(new_type_val_to_struct, j,
|
||||
old_elem, GFP_ATOMIC | __GFP_ZERO);
|
||||
}
|
||||
|
||||
for (j = 0; j < db->symtab[SYM_TYPES].nprim; j++) {
|
||||
old_elem = flex_array_get_ptr(db->sym_val_to_name[SYM_TYPES], j);
|
||||
if (old_elem)
|
||||
flex_array_put_ptr(new_val_to_name_types, j,
|
||||
old_elem, GFP_ATOMIC | __GFP_ZERO);
|
||||
}
|
||||
|
||||
// store the pointer of old flex arrays first, when assigning new ones we should free it
|
||||
struct flex_array *old_fa;
|
||||
|
||||
old_fa = db->type_attr_map_array;
|
||||
db->type_attr_map_array = new_type_attr_map_array;
|
||||
if (old_fa) {
|
||||
flex_array_free(old_fa);
|
||||
}
|
||||
|
||||
ebitmap_init(flex_array_get(db->type_attr_map_array, value - 1));
|
||||
ebitmap_set_bit(flex_array_get(db->type_attr_map_array, value - 1),
|
||||
value - 1, 1);
|
||||
|
||||
old_fa = db->type_val_to_struct_array;
|
||||
db->type_val_to_struct_array = new_type_val_to_struct;
|
||||
if (old_fa) {
|
||||
flex_array_free(old_fa);
|
||||
}
|
||||
flex_array_put_ptr(db->type_val_to_struct_array, value - 1,
|
||||
type, GFP_ATOMIC | __GFP_ZERO);
|
||||
|
||||
old_fa = db->sym_val_to_name[SYM_TYPES];
|
||||
db->sym_val_to_name[SYM_TYPES] = new_val_to_name_types;
|
||||
if (old_fa) {
|
||||
flex_array_free(old_fa);
|
||||
}
|
||||
flex_array_put_ptr(db->sym_val_to_name[SYM_TYPES], value - 1,
|
||||
key, GFP_ATOMIC | __GFP_ZERO);
|
||||
|
||||
int i;
|
||||
for (i = 0; i < db->p_roles.nprim; ++i) {
|
||||
ebitmap_set_bit(&db->role_val_to_struct[i]->types, value - 1,
|
||||
0);
|
||||
}
|
||||
return true;
|
||||
#endif
|
||||
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
@@ -713,23 +958,28 @@ bool ksu_dontauditxperm(struct policydb *db, const char *src, const char *tgt,
|
||||
bool ksu_type_transition(struct policydb *db, const char *src, const char *tgt,
|
||||
const char *cls, const char *def, const char *obj)
|
||||
{
|
||||
return false;
|
||||
if (obj) {
|
||||
return add_filename_trans(db, src, tgt, cls, def, obj);
|
||||
} else {
|
||||
return add_type_rule(db, src, tgt, cls, def, AVTAB_TRANSITION);
|
||||
}
|
||||
}
|
||||
|
||||
bool ksu_type_change(struct policydb *db, const char *src, const char *tgt,
|
||||
const char *cls, const char *def)
|
||||
{
|
||||
return false;
|
||||
return add_type_rule(db, src, tgt, cls, def, AVTAB_CHANGE);
|
||||
}
|
||||
|
||||
bool ksu_type_member(struct policydb *db, const char *src, const char *tgt,
|
||||
const char *cls, const char *def)
|
||||
{
|
||||
return false;
|
||||
return add_type_rule(db, src, tgt, cls, def, AVTAB_MEMBER);
|
||||
}
|
||||
|
||||
// File system labeling
|
||||
bool ksu_genfscon(struct policydb *db, const char *fs_name, const char *path,
|
||||
const char *ctx)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return add_genfscon(db, fs_name, path, ctx);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#! /bin/bash
|
||||
|
||||
set -x
|
||||
#!/bin/sh
|
||||
set -eux
|
||||
|
||||
GKI_ROOT=$(pwd)
|
||||
|
||||
@@ -11,8 +10,8 @@ if test -d "$GKI_ROOT/common/drivers"; then
|
||||
elif test -d "$GKI_ROOT/drivers"; then
|
||||
DRIVER_DIR="$GKI_ROOT/drivers"
|
||||
else
|
||||
echo "[ERROR] "drivers/" directory is not found."
|
||||
echo "[+] You should modify this scrpit by yourself."
|
||||
echo '[ERROR] "drivers/" directory is not found.'
|
||||
echo '[+] You should modify this scrpit by yourself.'
|
||||
exit 127
|
||||
fi
|
||||
|
||||
@@ -26,9 +25,9 @@ echo "[+] Copy kernel su driver to $DRIVER_DIR"
|
||||
|
||||
test -e "$DRIVER_DIR/kernelsu" || ln -sf "$GKI_ROOT/KernelSU/kernel" "$DRIVER_DIR/kernelsu"
|
||||
|
||||
echo "[+] Add kernel su driver to Makefile"
|
||||
echo '[+] Add kernel su driver to Makefile'
|
||||
|
||||
DRIVER_MAKEFILE=$DRIVER_DIR/Makefile
|
||||
grep -q "kernelsu" $DRIVER_MAKEFILE || echo "obj-y += kernelsu/" >> $DRIVER_MAKEFILE
|
||||
grep -q "kernelsu" "$DRIVER_MAKEFILE" || printf "\nobj-y += kernelsu/\n" >> "$DRIVER_MAKEFILE"
|
||||
|
||||
echo "[+] Done."
|
||||
echo '[+] Done.'
|
||||
|
||||
@@ -134,9 +134,15 @@ static int faccessat_handler_pre(struct kprobe *p, struct pt_regs *regs)
|
||||
|
||||
static int newfstatat_handler_pre(struct kprobe *p, struct pt_regs *regs)
|
||||
{
|
||||
int *dfd = (int *)PT_REGS_PARM1(regs);
|
||||
int *dfd = (int *)&PT_REGS_PARM1(regs);
|
||||
const char __user **filename_user = (const char **)&PT_REGS_PARM2(regs);
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0)
|
||||
// static int vfs_statx(int dfd, const char __user *filename, int flags, struct kstat *stat, u32 request_mask)
|
||||
int *flags = (int *)&PT_REGS_PARM3(regs);
|
||||
#else
|
||||
// int vfs_fstatat(int dfd, const char __user *filename, struct kstat *stat,int flag)
|
||||
int *flags = (int *)&PT_REGS_PARM4(regs);
|
||||
#endif
|
||||
|
||||
return ksu_handle_stat(dfd, filename_user, flags);
|
||||
}
|
||||
@@ -165,18 +171,20 @@ static struct kprobe faccessat_kp = {
|
||||
};
|
||||
|
||||
static struct kprobe newfstatat_kp = {
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0)
|
||||
.symbol_name = "vfs_statx",
|
||||
#else
|
||||
.symbol_name = "vfs_fstatat",
|
||||
#endif
|
||||
.pre_handler = newfstatat_handler_pre,
|
||||
};
|
||||
|
||||
static struct kprobe execve_kp = {
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 9, 0)
|
||||
.symbol_name = "do_execveat_common",
|
||||
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(4, 19, 0) && \
|
||||
LINUX_VERSION_CODE < KERNEL_VERSION(5, 9, 0)
|
||||
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(4, 19, 0)
|
||||
.symbol_name = "__do_execve_file",
|
||||
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0) && \
|
||||
LINUX_VERSION_CODE < KERNEL_VERSION(4, 19, 0)
|
||||
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3, 19, 0)
|
||||
.symbol_name = "do_execveat_common",
|
||||
#endif
|
||||
.pre_handler = execve_handler_pre,
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "ksu.h"
|
||||
#include "manager.h"
|
||||
#include "uid_observer.h"
|
||||
#include "kernel_compat.h"
|
||||
|
||||
#define SYSTEM_PACKAGES_LIST_PATH "/data/system/packages.list"
|
||||
static struct work_struct ksu_update_uid_work;
|
||||
@@ -38,6 +39,7 @@ static bool is_uid_exist(uid_t uid, void *data)
|
||||
|
||||
static void do_update_uid(struct work_struct *work)
|
||||
{
|
||||
KWORKER_INSTALL_KEYRING();
|
||||
struct file *fp = filp_open(SYSTEM_PACKAGES_LIST_PATH, O_RDONLY, 0);
|
||||
if (IS_ERR(fp)) {
|
||||
pr_err("do_update_uid, open " SYSTEM_PACKAGES_LIST_PATH
|
||||
@@ -54,13 +56,13 @@ static void do_update_uid(struct work_struct *work)
|
||||
loff_t line_start = 0;
|
||||
char buf[128];
|
||||
for (;;) {
|
||||
ssize_t count = kernel_read(fp, &chr, sizeof(chr), &pos);
|
||||
ssize_t count = ksu_kernel_read_compat(fp, &chr, sizeof(chr), &pos);
|
||||
if (count != sizeof(chr))
|
||||
break;
|
||||
if (chr != '\n')
|
||||
continue;
|
||||
|
||||
count = kernel_read(fp, buf, sizeof(buf), &line_start);
|
||||
count = ksu_kernel_read_compat(fp, buf, sizeof(buf), &line_start);
|
||||
|
||||
struct uid_data *data =
|
||||
kmalloc(sizeof(struct uid_data), GFP_ATOMIC);
|
||||
|
||||
@@ -62,10 +62,10 @@ dependencies {
|
||||
val accompanistVersion = "0.28.0"
|
||||
val composeDestinationsVersion = "1.7.27-beta"
|
||||
implementation(platform("androidx.compose:compose-bom:2022.12.00"))
|
||||
|
||||
debugImplementation("androidx.compose.ui:ui-test-manifest")
|
||||
debugImplementation("androidx.compose.ui:ui-tooling")
|
||||
implementation("androidx.activity:activity-compose:1.6.1")
|
||||
implementation("androidx.compose.material:material:1.4.0-beta02")
|
||||
implementation("androidx.compose.material:material-icons-extended")
|
||||
implementation("androidx.compose.material3:material3")
|
||||
implementation("androidx.compose.ui:ui")
|
||||
@@ -75,7 +75,6 @@ dependencies {
|
||||
implementation("androidx.navigation:navigation-compose:2.5.3")
|
||||
implementation("com.google.accompanist:accompanist-drawablepainter:$accompanistVersion")
|
||||
implementation("com.google.accompanist:accompanist-navigation-animation:$accompanistVersion")
|
||||
implementation("com.google.accompanist:accompanist-swiperefresh:$accompanistVersion")
|
||||
implementation("com.google.accompanist:accompanist-systemuicontroller:$accompanistVersion")
|
||||
implementation("io.github.raamcosta.compose-destinations:animations-core:$composeDestinationsVersion")
|
||||
|
||||
@@ -83,7 +82,7 @@ dependencies {
|
||||
implementation("me.zhanghai.android.appiconloader:appiconloader-coil:1.5.0")
|
||||
|
||||
implementation("com.github.topjohnwu.libsu:core:5.0.3")
|
||||
implementation("com.github.alorma:compose-settings-ui-m3:0.15.0")
|
||||
implementation("com.github.alorma:compose-settings-ui-m3:0.22.0")
|
||||
|
||||
ksp("io.github.raamcosta.compose-destinations:ksp:$composeDestinationsVersion")
|
||||
|
||||
|
||||
@@ -32,6 +32,16 @@
|
||||
android:name="android.app.lib_name"
|
||||
android:value="" />
|
||||
</activity>
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.fileprovider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/filepaths" />
|
||||
</provider>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
||||
@@ -61,3 +61,9 @@ Java_me_weishu_kernelsu_Natives_allowRoot(JNIEnv *env, jclass clazz, jint uid, j
|
||||
}
|
||||
|
||||
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_me_weishu_kernelsu_Natives_isSafeMode(JNIEnv *env, jclass clazz) {
|
||||
return is_safe_mode();
|
||||
}
|
||||
@@ -19,6 +19,7 @@
|
||||
#define CMD_DENY_SU 4
|
||||
#define CMD_GET_ALLOW_LIST 5
|
||||
#define CMD_GET_DENY_LIST 6
|
||||
#define CMD_CHECK_SAFEMODE 9
|
||||
|
||||
static bool ksuctl(int cmd, void* arg1, void* arg2) {
|
||||
int32_t result = 0;
|
||||
@@ -51,4 +52,8 @@ bool get_allow_list(int *uids, int *size) {
|
||||
|
||||
bool get_deny_list(int *uids, int *size) {
|
||||
return ksuctl(CMD_GET_DENY_LIST, uids, size);
|
||||
}
|
||||
|
||||
bool is_safe_mode() {
|
||||
return ksuctl(CMD_CHECK_SAFEMODE, nullptr, nullptr);
|
||||
}
|
||||
@@ -15,4 +15,6 @@ bool get_allow_list(int *uids, int *size);
|
||||
|
||||
bool get_deny_list(int *uids, int *size);
|
||||
|
||||
bool is_safe_mode();
|
||||
|
||||
#endif //KERNELSU_KSU_H
|
||||
|
||||
@@ -21,4 +21,6 @@ public final class Natives {
|
||||
public static native int[] getDenyList();
|
||||
|
||||
public static native boolean allowRoot(int uid, boolean allow);
|
||||
|
||||
public static native boolean isSafeMode();
|
||||
}
|
||||
|
||||
@@ -6,8 +6,9 @@ import androidx.activity.compose.setContent
|
||||
import androidx.compose.animation.ExperimentalAnimationApi
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -15,12 +16,14 @@ import androidx.navigation.NavGraph.Companion.findStartDestination
|
||||
import androidx.navigation.NavHostController
|
||||
import com.google.accompanist.navigation.animation.rememberAnimatedNavController
|
||||
import com.ramcosta.composedestinations.DestinationsNavHost
|
||||
import me.weishu.kernelsu.ui.component.rememberDialogHostState
|
||||
import me.weishu.kernelsu.ui.screen.BottomBarDestination
|
||||
import me.weishu.kernelsu.ui.screen.NavGraphs
|
||||
import me.weishu.kernelsu.ui.screen.appCurrentDestinationAsState
|
||||
import me.weishu.kernelsu.ui.screen.destinations.Destination
|
||||
import me.weishu.kernelsu.ui.screen.startAppDestination
|
||||
import me.weishu.kernelsu.ui.theme.KernelSUTheme
|
||||
import me.weishu.kernelsu.ui.util.LocalDialogHost
|
||||
import me.weishu.kernelsu.ui.util.LocalSnackbarHost
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
@@ -37,7 +40,10 @@ class MainActivity : ComponentActivity() {
|
||||
bottomBar = { BottomBar(navController) },
|
||||
snackbarHost = { SnackbarHost(snackbarHostState) }
|
||||
) { innerPadding ->
|
||||
CompositionLocalProvider(LocalSnackbarHost provides snackbarHostState) {
|
||||
CompositionLocalProvider(
|
||||
LocalSnackbarHost provides snackbarHostState,
|
||||
LocalDialogHost provides rememberDialogHostState(),
|
||||
) {
|
||||
DestinationsNavHost(
|
||||
modifier = Modifier.padding(innerPadding),
|
||||
navGraph = NavGraphs.root,
|
||||
@@ -52,31 +58,35 @@ class MainActivity : ComponentActivity() {
|
||||
|
||||
@Composable
|
||||
private fun BottomBar(navController: NavHostController) {
|
||||
val currentDestination: Destination = navController.appCurrentDestinationAsState().value
|
||||
val topDestination: Destination = navController.appCurrentDestinationAsState().value
|
||||
?: NavGraphs.root.startAppDestination
|
||||
var topDestination by rememberSaveable { mutableStateOf(currentDestination.route) }
|
||||
LaunchedEffect(currentDestination) {
|
||||
val queue = navController.backQueue
|
||||
if (queue.size == 2) topDestination = queue[1].destination.route!!
|
||||
else if (queue.size > 2) topDestination = queue[2].destination.route!!
|
||||
val bottomBarRoutes = remember {
|
||||
BottomBarDestination.values().map { it.direction.route }
|
||||
}
|
||||
|
||||
NavigationBar(tonalElevation = 8.dp) {
|
||||
BottomBarDestination.values().forEach { destination ->
|
||||
NavigationBarItem(
|
||||
selected = topDestination == destination.direction.route,
|
||||
selected = topDestination.route == destination.direction.route,
|
||||
onClick = {
|
||||
val firstRoute = navController.backQueue.reversed().first {
|
||||
it.destination.route in bottomBarRoutes
|
||||
}.destination.route
|
||||
|
||||
navController.navigate(destination.direction.route) {
|
||||
popUpTo(navController.graph.findStartDestination().id) {
|
||||
saveState = true
|
||||
saveState = firstRoute != destination.direction.route
|
||||
}
|
||||
launchSingleTop = true
|
||||
restoreState = true
|
||||
}
|
||||
},
|
||||
icon = {
|
||||
if (topDestination == destination.direction.route) Icon(destination.iconSelected, stringResource(destination.label))
|
||||
else Icon(destination.iconNotSelected, stringResource(destination.label))
|
||||
if (topDestination.route == destination.direction.route) {
|
||||
Icon(destination.iconSelected, stringResource(destination.label))
|
||||
} else {
|
||||
Icon(destination.iconNotSelected, stringResource(destination.label))
|
||||
}
|
||||
},
|
||||
label = { Text(stringResource(destination.label)) },
|
||||
alwaysShowLabel = false
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
package me.weishu.kernelsu.ui.component
|
||||
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.*
|
||||
import kotlinx.coroutines.CancellableContinuation
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import me.weishu.kernelsu.ui.util.LocalDialogHost
|
||||
import kotlin.coroutines.resume
|
||||
|
||||
sealed interface DialogResult {
|
||||
object Confirmed : DialogResult
|
||||
object Dismissed : DialogResult
|
||||
}
|
||||
|
||||
interface DialogVisuals {
|
||||
val title: String
|
||||
val content: String
|
||||
val confirm: String?
|
||||
val dismiss: String?
|
||||
}
|
||||
|
||||
interface DialogData {
|
||||
val visuals: DialogVisuals
|
||||
|
||||
fun confirm()
|
||||
|
||||
fun dismiss()
|
||||
}
|
||||
|
||||
class DialogHostState {
|
||||
|
||||
private data class DialogVisualsImpl(
|
||||
override val title: String,
|
||||
override val content: String,
|
||||
override val confirm: String?,
|
||||
override val dismiss: String?
|
||||
) : DialogVisuals
|
||||
|
||||
private data class DialogDataImpl(
|
||||
override val visuals: DialogVisuals,
|
||||
val continuation: CancellableContinuation<DialogResult>
|
||||
) : DialogData {
|
||||
|
||||
override fun confirm() {
|
||||
if (continuation.isActive) continuation.resume(DialogResult.Confirmed)
|
||||
}
|
||||
|
||||
override fun dismiss() {
|
||||
if (continuation.isActive) continuation.resume(DialogResult.Dismissed)
|
||||
}
|
||||
}
|
||||
|
||||
private val mutex = Mutex()
|
||||
|
||||
var currentDialogData by mutableStateOf<DialogData?>(null)
|
||||
private set
|
||||
|
||||
suspend fun showDialog(
|
||||
title: String,
|
||||
content: String,
|
||||
confirm: String? = null,
|
||||
dismiss: String? = null
|
||||
): DialogResult = mutex.withLock {
|
||||
try {
|
||||
return@withLock suspendCancellableCoroutine { continuation ->
|
||||
currentDialogData = DialogDataImpl(
|
||||
visuals = DialogVisualsImpl(title, content, confirm, dismiss),
|
||||
continuation = continuation
|
||||
)
|
||||
}
|
||||
} finally {
|
||||
currentDialogData = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun rememberDialogHostState(): DialogHostState {
|
||||
return remember {
|
||||
DialogHostState()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun BaseDialog(
|
||||
state: DialogHostState = LocalDialogHost.current,
|
||||
title: @Composable (String) -> Unit,
|
||||
confirmButton: @Composable (String?, () -> Unit) -> Unit,
|
||||
dismissButton: @Composable (String?, () -> Unit) -> Unit,
|
||||
content: @Composable (String) -> Unit = { Text(text = it) },
|
||||
) {
|
||||
val currentDialogData = state.currentDialogData ?: return
|
||||
val visuals = currentDialogData.visuals
|
||||
AlertDialog(
|
||||
onDismissRequest = {
|
||||
currentDialogData.dismiss()
|
||||
},
|
||||
title = {
|
||||
title(visuals.title)
|
||||
},
|
||||
text = {
|
||||
content(visuals.content)
|
||||
},
|
||||
confirmButton = {
|
||||
confirmButton(visuals.confirm, currentDialogData::confirm)
|
||||
},
|
||||
dismissButton = {
|
||||
dismissButton(visuals.dismiss, currentDialogData::dismiss)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SimpleDialog(
|
||||
state: DialogHostState = LocalDialogHost.current,
|
||||
content: @Composable (String) -> Unit
|
||||
) {
|
||||
BaseDialog(
|
||||
state = state,
|
||||
title = {
|
||||
Text(text = it)
|
||||
},
|
||||
confirmButton = { text, confirm ->
|
||||
text?.let {
|
||||
TextButton(onClick = confirm) {
|
||||
Text(text = it)
|
||||
}
|
||||
}
|
||||
},
|
||||
dismissButton = { text, dismiss ->
|
||||
text?.let {
|
||||
TextButton(onClick = dismiss) {
|
||||
Text(text = it)
|
||||
}
|
||||
}
|
||||
},
|
||||
content = content
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ConfirmDialog(state: DialogHostState = LocalDialogHost.current) {
|
||||
BaseDialog(
|
||||
state = state,
|
||||
title = {
|
||||
Text(text = it)
|
||||
},
|
||||
confirmButton = { text, confirm ->
|
||||
text?.let {
|
||||
TextButton(onClick = confirm) {
|
||||
Text(text = it)
|
||||
}
|
||||
}
|
||||
},
|
||||
dismissButton = { text, dismiss ->
|
||||
text?.let {
|
||||
TextButton(onClick = dismiss) {
|
||||
Text(text = it)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -10,8 +10,7 @@ import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material.icons.filled.Search
|
||||
import androidx.compose.material.icons.filled.*
|
||||
import androidx.compose.material.icons.outlined.ArrowBack
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
@@ -22,9 +21,11 @@ import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.focus.onFocusChanged
|
||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import me.weishu.kernelsu.R
|
||||
|
||||
private const val TAG = "SearchBar"
|
||||
|
||||
@@ -36,7 +37,8 @@ fun SearchAppBar(
|
||||
onSearchTextChange: (String) -> Unit,
|
||||
onClearClick: () -> Unit,
|
||||
onBackClick: (() -> Unit)? = null,
|
||||
onConfirm: (() -> Unit)? = null
|
||||
onConfirm: (() -> Unit)? = null,
|
||||
dropdownContent: @Composable (() -> Unit)? = null,
|
||||
) {
|
||||
val keyboardController = LocalSoftwareKeyboardController.current
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
@@ -116,6 +118,11 @@ fun SearchAppBar(
|
||||
content = { Icon(Icons.Filled.Search, null) }
|
||||
)
|
||||
}
|
||||
|
||||
if (dropdownContent != null) {
|
||||
dropdownContent()
|
||||
}
|
||||
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
@@ -130,21 +131,29 @@ private fun StatusCard(kernelVersion: KernelVersion, ksuVersion: Int?) {
|
||||
else MaterialTheme.colorScheme.errorContainer
|
||||
})
|
||||
) {
|
||||
val uriHandler = LocalUriHandler.current
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable {
|
||||
// TODO: Install kernel
|
||||
if (kernelVersion.isGKI() && ksuVersion == null) {
|
||||
uriHandler.openUri("https://kernelsu.org/guide/installation.html")
|
||||
}
|
||||
}
|
||||
.padding(24.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
when {
|
||||
ksuVersion != null -> {
|
||||
val appendText = if (Natives.isSafeMode()) {
|
||||
" [${stringResource(id = R.string.safe_mode)}]"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
Icon(Icons.Outlined.CheckCircle, stringResource(R.string.home_working))
|
||||
Column(Modifier.padding(start = 20.dp)) {
|
||||
Text(
|
||||
text = stringResource(R.string.home_working),
|
||||
text = stringResource(R.string.home_working) + appendText,
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
)
|
||||
Spacer(Modifier.height(4.dp))
|
||||
@@ -213,10 +222,7 @@ private fun InfoCard() {
|
||||
InfoCardItem(stringResource(R.string.home_kernel), uname.release)
|
||||
|
||||
Spacer(Modifier.height(24.dp))
|
||||
InfoCardItem(stringResource(R.string.home_arch), uname.machine)
|
||||
|
||||
Spacer(Modifier.height(24.dp))
|
||||
InfoCardItem(stringResource(R.string.home_version), uname.version)
|
||||
InfoCardItem(stringResource(R.string.home_manager_version), getManagerVersion(context))
|
||||
|
||||
Spacer(Modifier.height(24.dp))
|
||||
InfoCardItem(stringResource(R.string.home_api), Build.VERSION.SDK_INT.toString())
|
||||
@@ -247,6 +253,11 @@ private fun InfoCard() {
|
||||
}
|
||||
}
|
||||
|
||||
fun getManagerVersion(context: Context) : String {
|
||||
val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0)
|
||||
return "${packageInfo.versionName} (${packageInfo.versionCode})"
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun StatusCardPreview() {
|
||||
|
||||
@@ -8,8 +8,12 @@ import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.pullrefresh.PullRefreshIndicator
|
||||
import androidx.compose.material.pullrefresh.pullRefresh
|
||||
import androidx.compose.material.pullrefresh.rememberPullRefreshState
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
@@ -17,23 +21,22 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextDecoration
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.google.accompanist.swiperefresh.SwipeRefresh
|
||||
import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
import kotlinx.coroutines.launch
|
||||
import me.weishu.kernelsu.Natives
|
||||
import me.weishu.kernelsu.R
|
||||
import me.weishu.kernelsu.ui.component.ConfirmDialog
|
||||
import me.weishu.kernelsu.ui.component.DialogResult
|
||||
import me.weishu.kernelsu.ui.screen.destinations.InstallScreenDestination
|
||||
import me.weishu.kernelsu.ui.util.LocalSnackbarHost
|
||||
import me.weishu.kernelsu.ui.util.toggleModule
|
||||
import me.weishu.kernelsu.ui.util.uninstallModule
|
||||
import me.weishu.kernelsu.ui.util.*
|
||||
import me.weishu.kernelsu.ui.viewmodel.ModuleViewModel
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@@ -41,9 +44,6 @@ import me.weishu.kernelsu.ui.viewmodel.ModuleViewModel
|
||||
@Composable
|
||||
fun ModuleScreen(navigator: DestinationsNavigator) {
|
||||
val viewModel = viewModel<ModuleViewModel>()
|
||||
val snackBarHost = LocalSnackbarHost.current
|
||||
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
if (viewModel.moduleList.isEmpty()) {
|
||||
@@ -51,11 +51,18 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
|
||||
}
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopBar()
|
||||
},
|
||||
floatingActionButton = {
|
||||
val isSafeMode = Natives.isSafeMode()
|
||||
val isKSUVersionInvalid = Natives.getVersion() < 0
|
||||
val hasMagisk = hasMagisk()
|
||||
|
||||
val hideInstallButton = isSafeMode || isKSUVersionInvalid || hasMagisk
|
||||
|
||||
Scaffold(topBar = {
|
||||
TopBar()
|
||||
}, floatingActionButton = if (hideInstallButton) {
|
||||
{ /* Empty */ }
|
||||
} else {
|
||||
{
|
||||
val moduleInstall = stringResource(id = R.string.module_install)
|
||||
val selectZipLauncher = rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.StartActivityForResult()
|
||||
@@ -81,87 +88,170 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
|
||||
icon = { Icon(Icons.Filled.Add, moduleInstall) },
|
||||
text = { Text(text = moduleInstall) },
|
||||
)
|
||||
},
|
||||
) { innerPadding ->
|
||||
val failedEnable = stringResource(R.string.module_failed_to_enable)
|
||||
val failedDisable = stringResource(R.string.module_failed_to_disable)
|
||||
val failedUninstall = stringResource(R.string.module_uninstall_failed)
|
||||
val successUninstall = stringResource(R.string.module_uninstall_success)
|
||||
val swipeState = rememberSwipeRefreshState(viewModel.isRefreshing)
|
||||
// TODO: Replace SwipeRefresh with RefreshIndicator when it's ready
|
||||
if (Natives.getVersion() < 8) {
|
||||
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||
Text(stringResource(R.string.require_kernel_version_8))
|
||||
}
|
||||
return@Scaffold
|
||||
}
|
||||
SwipeRefresh(
|
||||
state = swipeState,
|
||||
onRefresh = {
|
||||
scope.launch { viewModel.fetchModuleList() }
|
||||
},
|
||||
modifier = Modifier
|
||||
.padding(innerPadding)
|
||||
.padding(16.dp)
|
||||
.fillMaxSize()
|
||||
) {
|
||||
val isEmpty = viewModel.moduleList.isEmpty()
|
||||
if (isEmpty) {
|
||||
swipeState.isRefreshing = false
|
||||
}) { innerPadding ->
|
||||
|
||||
ConfirmDialog()
|
||||
|
||||
when {
|
||||
isKSUVersionInvalid -> {
|
||||
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||
Text(stringResource(R.string.module_empty))
|
||||
Text(stringResource(R.string.require_kernel_version_8))
|
||||
}
|
||||
} else {
|
||||
LazyColumn(
|
||||
verticalArrangement = Arrangement.spacedBy(15.dp)
|
||||
}
|
||||
hasMagisk -> {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(24.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
stringResource(R.string.module_magisk_conflict),
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
ModuleList(
|
||||
viewModel = viewModel,
|
||||
modifier = Modifier
|
||||
.padding(innerPadding)
|
||||
.fillMaxSize()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
private fun ModuleList(viewModel: ModuleViewModel, modifier: Modifier = Modifier) {
|
||||
val failedEnable = stringResource(R.string.module_failed_to_enable)
|
||||
val failedDisable = stringResource(R.string.module_failed_to_disable)
|
||||
val failedUninstall = stringResource(R.string.module_uninstall_failed)
|
||||
val successUninstall = stringResource(R.string.module_uninstall_success)
|
||||
val reboot = stringResource(id = R.string.reboot)
|
||||
val rebootToApply = stringResource(id = R.string.reboot_to_apply)
|
||||
val moduleStr = stringResource(id = R.string.module)
|
||||
val uninstall = stringResource(id = R.string.uninstall)
|
||||
val cancel = stringResource(id = android.R.string.cancel)
|
||||
val moduleUninstallConfirm =
|
||||
stringResource(id = R.string.module_uninstall_confirm)
|
||||
|
||||
val dialogHost = LocalDialogHost.current
|
||||
val snackBarHost = LocalSnackbarHost.current
|
||||
|
||||
suspend fun onModuleUninstall(module: ModuleViewModel.ModuleInfo) {
|
||||
val dialogResult = dialogHost.showDialog(
|
||||
moduleStr,
|
||||
content = moduleUninstallConfirm.format(module.name),
|
||||
confirm = uninstall,
|
||||
dismiss = cancel
|
||||
)
|
||||
if (dialogResult != DialogResult.Confirmed) {
|
||||
return
|
||||
}
|
||||
|
||||
val success = uninstallModule(module.id)
|
||||
if (success) {
|
||||
viewModel.fetchModuleList()
|
||||
}
|
||||
val message = if (success) {
|
||||
successUninstall.format(module.name)
|
||||
} else {
|
||||
failedUninstall.format(module.name)
|
||||
}
|
||||
val actionLabel = if (success) {
|
||||
reboot
|
||||
} else {
|
||||
null
|
||||
}
|
||||
val result = snackBarHost.showSnackbar(message, actionLabel = actionLabel)
|
||||
if (result == SnackbarResult.ActionPerformed) {
|
||||
reboot()
|
||||
}
|
||||
}
|
||||
|
||||
val refreshState = rememberPullRefreshState(
|
||||
refreshing = viewModel.isRefreshing,
|
||||
onRefresh = { viewModel.fetchModuleList() }
|
||||
)
|
||||
Box(modifier.pullRefresh(refreshState)) {
|
||||
if (viewModel.isOverlayAvailable) {
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
contentPadding = remember {
|
||||
PaddingValues(
|
||||
start = 16.dp,
|
||||
top = 16.dp,
|
||||
end = 16.dp,
|
||||
bottom = 16.dp
|
||||
+ 16.dp + 56.dp /* Scaffold Fab Spacing + Fab container height */
|
||||
)
|
||||
},
|
||||
) {
|
||||
val isEmpty = viewModel.moduleList.isEmpty()
|
||||
if (isEmpty) {
|
||||
item {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(stringResource(R.string.module_empty))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
items(viewModel.moduleList) { module ->
|
||||
var isChecked by rememberSaveable(module) { mutableStateOf(module.enabled) }
|
||||
ModuleItem(module,
|
||||
isChecked,
|
||||
onUninstall = {
|
||||
val scope = rememberCoroutineScope()
|
||||
ModuleItem(module, isChecked, onUninstall = {
|
||||
scope.launch { onModuleUninstall(module) }
|
||||
}, onCheckChanged = {
|
||||
val success = toggleModule(module.id, !isChecked)
|
||||
if (success) {
|
||||
isChecked = it
|
||||
scope.launch {
|
||||
val result = uninstallModule(module.id)
|
||||
if (result) {
|
||||
viewModel.fetchModuleList()
|
||||
}
|
||||
snackBarHost.showSnackbar(
|
||||
if (result) {
|
||||
successUninstall.format(module.name)
|
||||
} else {
|
||||
failedUninstall.format(module.name)
|
||||
}
|
||||
viewModel.fetchModuleList()
|
||||
|
||||
val result = snackBarHost.showSnackbar(
|
||||
rebootToApply, actionLabel = reboot
|
||||
)
|
||||
}
|
||||
},
|
||||
onCheckChanged = {
|
||||
val success = toggleModule(module.id, !isChecked)
|
||||
if (success) {
|
||||
isChecked = it
|
||||
scope.launch {
|
||||
viewModel.fetchModuleList()
|
||||
if (result == SnackbarResult.ActionPerformed) {
|
||||
reboot()
|
||||
}
|
||||
} else scope.launch {
|
||||
val message = if (isChecked) failedDisable else failedEnable
|
||||
snackBarHost.showSnackbar(message.format(module.name))
|
||||
}
|
||||
} else scope.launch {
|
||||
val message = if (isChecked) failedDisable else failedEnable
|
||||
snackBarHost.showSnackbar(message.format(module.name))
|
||||
}
|
||||
)
|
||||
})
|
||||
// fix last item shadow incomplete in LazyColumn
|
||||
Spacer(Modifier.height(1.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||
Text(stringResource(R.string.module_overlay_fs_not_available))
|
||||
}
|
||||
}
|
||||
|
||||
PullRefreshIndicator(
|
||||
refreshing = viewModel.isRefreshing,
|
||||
state = refreshState,
|
||||
modifier = Modifier.align(
|
||||
Alignment.TopCenter
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun TopBar() {
|
||||
TopAppBar(
|
||||
title = { Text(stringResource(R.string.module)) }
|
||||
)
|
||||
TopAppBar(title = { Text(stringResource(R.string.module)) })
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
||||
@@ -1,21 +1,28 @@
|
||||
package me.weishu.kernelsu.ui.screen
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Info
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.content.FileProvider
|
||||
import com.alorma.compose.settings.ui.*
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
import kotlinx.coroutines.launch
|
||||
import me.weishu.kernelsu.BuildConfig
|
||||
import me.weishu.kernelsu.R
|
||||
import me.weishu.kernelsu.ui.component.SimpleDialog
|
||||
import me.weishu.kernelsu.ui.util.LinkifyText
|
||||
import me.weishu.kernelsu.ui.util.LocalDialogHost
|
||||
import me.weishu.kernelsu.ui.util.getBugreportFile
|
||||
|
||||
|
||||
/**
|
||||
* @author weishu
|
||||
@@ -34,46 +41,45 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
||||
}
|
||||
) { paddingValues ->
|
||||
|
||||
var openDialog by remember { mutableStateOf(false) }
|
||||
|
||||
if (openDialog) {
|
||||
AlertDialog(
|
||||
onDismissRequest = {
|
||||
openDialog = false
|
||||
},
|
||||
title = {
|
||||
Text(text = stringResource(id = R.string.about))
|
||||
},
|
||||
text = {
|
||||
SupportCard()
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
openDialog = false
|
||||
}
|
||||
) {
|
||||
Text(stringResource(id = android.R.string.ok))
|
||||
}
|
||||
},
|
||||
)
|
||||
SimpleDialog {
|
||||
SupportCard()
|
||||
}
|
||||
|
||||
Column(modifier = Modifier.padding(paddingValues)) {
|
||||
|
||||
SettingsSwitch(
|
||||
title = {
|
||||
Text(stringResource(id = R.string.settings_system_rw))
|
||||
},
|
||||
subtitle = {
|
||||
Text(stringResource(id = R.string.settings_system_rw_summary))
|
||||
}
|
||||
)
|
||||
val context = LocalContext.current
|
||||
SettingsMenuLink(title = {
|
||||
Text(stringResource(id = R.string.about))
|
||||
Text(stringResource(id = R.string.send_log))
|
||||
},
|
||||
onClick = {
|
||||
openDialog = true
|
||||
val bugreport = getBugreportFile(context)
|
||||
val uri: Uri = FileProvider.getUriForFile(context, "${BuildConfig.APPLICATION_ID}.fileprovider", bugreport)
|
||||
|
||||
val shareIntent = Intent(Intent.ACTION_SEND)
|
||||
shareIntent.putExtra(Intent.EXTRA_STREAM, uri)
|
||||
shareIntent.setDataAndType(uri, "application/zip")
|
||||
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
|
||||
context.startActivity(
|
||||
Intent.createChooser(
|
||||
shareIntent,
|
||||
context.getString(R.string.send_log)
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
val about = stringResource(id = R.string.about)
|
||||
val ok = stringResource(id = android.R.string.ok)
|
||||
val scope = rememberCoroutineScope()
|
||||
val dialogHost = LocalDialogHost.current
|
||||
SettingsMenuLink(title = {
|
||||
Text(about)
|
||||
},
|
||||
onClick = {
|
||||
scope.launch {
|
||||
dialogHost.showDialog(about, content = "unused", confirm = ok)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,35 +1,25 @@
|
||||
package me.weishu.kernelsu.ui.screen
|
||||
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.util.Log
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.MoreVert
|
||||
import androidx.compose.material.pullrefresh.PullRefreshIndicator
|
||||
import androidx.compose.material.pullrefresh.pullRefresh
|
||||
import androidx.compose.material.pullrefresh.rememberPullRefreshState
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import coil.ImageLoader
|
||||
import coil.compose.AsyncImage
|
||||
import coil.compose.rememberAsyncImagePainter
|
||||
import coil.compose.rememberImagePainter
|
||||
import coil.decode.DataSource
|
||||
import coil.fetch.DrawableResult
|
||||
import coil.fetch.FetchResult
|
||||
import coil.fetch.Fetcher
|
||||
import coil.request.CachePolicy
|
||||
import coil.request.ImageRequest
|
||||
import coil.request.Options
|
||||
import com.google.accompanist.swiperefresh.SwipeRefresh
|
||||
import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import kotlinx.coroutines.launch
|
||||
import me.weishu.kernelsu.Natives
|
||||
@@ -37,10 +27,9 @@ import me.weishu.kernelsu.R
|
||||
import me.weishu.kernelsu.ui.component.SearchAppBar
|
||||
import me.weishu.kernelsu.ui.util.LocalSnackbarHost
|
||||
import me.weishu.kernelsu.ui.viewmodel.SuperUserViewModel
|
||||
import me.zhanghai.android.appiconloader.coil.AppIconKeyer
|
||||
import java.util.*
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class)
|
||||
@Destination
|
||||
@Composable
|
||||
fun SuperUserScreen() {
|
||||
@@ -60,24 +49,61 @@ fun SuperUserScreen() {
|
||||
title = { Text(stringResource(R.string.superuser)) },
|
||||
searchText = viewModel.search,
|
||||
onSearchTextChange = { viewModel.search = it },
|
||||
onClearClick = { viewModel.search = "" }
|
||||
onClearClick = { viewModel.search = "" },
|
||||
dropdownContent = {
|
||||
var showDropdown by remember { mutableStateOf(false) }
|
||||
|
||||
IconButton(
|
||||
onClick = { showDropdown = true },
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.MoreVert,
|
||||
contentDescription = stringResource(id = R.string.settings)
|
||||
)
|
||||
|
||||
DropdownMenu(expanded = showDropdown, onDismissRequest = {
|
||||
showDropdown = false
|
||||
}) {
|
||||
DropdownMenuItem(text = {
|
||||
Text(stringResource(R.string.refresh))
|
||||
}, onClick = {
|
||||
scope.launch {
|
||||
viewModel.fetchAppList()
|
||||
}
|
||||
showDropdown = false
|
||||
})
|
||||
DropdownMenuItem(text = {
|
||||
Text(
|
||||
if (viewModel.showSystemApps) {
|
||||
stringResource(R.string.hide_system_apps)
|
||||
} else {
|
||||
stringResource(R.string.show_system_apps)
|
||||
}
|
||||
)
|
||||
}, onClick = {
|
||||
viewModel.showSystemApps = !viewModel.showSystemApps
|
||||
showDropdown = false
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
) { innerPadding ->
|
||||
val failMessage = stringResource(R.string.superuser_failed_to_grant_root)
|
||||
|
||||
// TODO: Replace SwipeRefresh with RefreshIndicator when it's ready
|
||||
SwipeRefresh(
|
||||
state = rememberSwipeRefreshState(viewModel.isRefreshing),
|
||||
onRefresh = {
|
||||
scope.launch { viewModel.fetchAppList() }
|
||||
},
|
||||
val refreshState = rememberPullRefreshState(
|
||||
refreshing = viewModel.isRefreshing,
|
||||
onRefresh = { scope.launch { viewModel.fetchAppList() } },
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(innerPadding)
|
||||
.fillMaxSize()
|
||||
.pullRefresh(refreshState)
|
||||
) {
|
||||
LazyColumn {
|
||||
items(viewModel.appList) { app ->
|
||||
val failMessage = stringResource(R.string.superuser_failed_to_grant_root)
|
||||
|
||||
LazyColumn(Modifier.fillMaxSize()) {
|
||||
items(viewModel.appList, key = { it.packageName }) { app ->
|
||||
var isChecked by rememberSaveable(app) { mutableStateOf(app.onAllowList) }
|
||||
AppItem(app, isChecked) { checked ->
|
||||
val success = Natives.allowRoot(app.uid, checked)
|
||||
@@ -89,6 +115,12 @@ fun SuperUserScreen() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PullRefreshIndicator(
|
||||
refreshing = viewModel.isRefreshing,
|
||||
state = refreshState,
|
||||
modifier = Modifier.align(Alignment.TopCenter)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.view.ViewCompat
|
||||
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
||||
|
||||
@@ -47,6 +48,12 @@ fun KernelSUTheme(
|
||||
color = colorScheme.surface,
|
||||
darkIcons = !darkTheme
|
||||
)
|
||||
|
||||
// To match the App Navbar color
|
||||
systemUiController.setNavigationBarColor(
|
||||
color = colorScheme.surfaceColorAtElevation(8.dp),
|
||||
darkIcons = !darkTheme,
|
||||
)
|
||||
}
|
||||
|
||||
MaterialTheme(
|
||||
@@ -54,4 +61,4 @@ fun KernelSUTheme(
|
||||
typography = Typography,
|
||||
content = content
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,12 @@ package me.weishu.kernelsu.ui.util
|
||||
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.runtime.compositionLocalOf
|
||||
import me.weishu.kernelsu.ui.component.DialogHostState
|
||||
|
||||
val LocalSnackbarHost = compositionLocalOf<SnackbarHostState> {
|
||||
error("CompositionLocal LocalSnackbarController not present")
|
||||
}
|
||||
|
||||
val LocalDialogHost = compositionLocalOf<DialogHostState> {
|
||||
error("CompositionLocal LocalDialogController not present")
|
||||
}
|
||||
@@ -102,4 +102,17 @@ fun reboot(reason: String = "") {
|
||||
ShellUtils.fastCmd(shell, "/system/bin/input keyevent 26")
|
||||
}
|
||||
ShellUtils.fastCmd(shell, "/system/bin/svc power reboot $reason || /system/bin/reboot $reason")
|
||||
}
|
||||
|
||||
fun overlayFsAvailable(): Boolean {
|
||||
val shell = createRootShell()
|
||||
// check /proc/filesystems
|
||||
return ShellUtils.fastCmdResult(shell, "cat /proc/filesystems | grep overlay")
|
||||
}
|
||||
|
||||
fun hasMagisk(): Boolean {
|
||||
val shell = createRootShell()
|
||||
val result = shell.newJob().add("nsenter --mount=/proc/1/ns/mnt which magisk").exec()
|
||||
Log.i(TAG, "has magisk: ${result.isSuccess}")
|
||||
return result.isSuccess
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package me.weishu.kernelsu.ui.util
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.system.Os
|
||||
import me.weishu.kernelsu.Natives
|
||||
import me.weishu.kernelsu.ui.screen.getManagerVersion
|
||||
import java.io.File
|
||||
import java.io.FileWriter
|
||||
import java.io.PrintWriter
|
||||
import java.time.LocalDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
fun getBugreportFile(context: Context): File {
|
||||
|
||||
val bugreportDir = File(context.cacheDir, "bugreport")
|
||||
bugreportDir.mkdirs()
|
||||
|
||||
val dmesgFile = File(bugreportDir, "dmesg.txt")
|
||||
val logcatFile = File(bugreportDir, "logcat.txt")
|
||||
val tombstonesFile = File(bugreportDir, "tombstones.tar.gz")
|
||||
val dropboxFile = File(bugreportDir, "dropbox.tar.gz")
|
||||
val pstoreFile = File(bugreportDir, "pstore.tar.gz")
|
||||
val diagFile = File(bugreportDir, "diag.tar.gz")
|
||||
val mountsFile = File(bugreportDir, "mounts.txt")
|
||||
val fileSystemsFile = File(bugreportDir, "filesystems.txt")
|
||||
val ksuFileTree = File(bugreportDir, "ksu_tree.txt")
|
||||
val appListFile = File(bugreportDir, "app_list.txt")
|
||||
val propFile = File(bugreportDir, "props.txt")
|
||||
|
||||
val shell = createRootShell()
|
||||
|
||||
shell.newJob().add("dmesg > ${dmesgFile.absolutePath}").exec()
|
||||
shell.newJob().add("logcat -d > ${logcatFile.absolutePath}").exec()
|
||||
shell.newJob().add("tar -czf ${tombstonesFile.absolutePath} /data/tombstones").exec()
|
||||
shell.newJob().add("tar -czf ${dropboxFile.absolutePath} /data/system/dropbox").exec()
|
||||
shell.newJob().add("tar -czf ${pstoreFile.absolutePath} /sys/fs/pstore").exec()
|
||||
shell.newJob().add("tar -czf ${diagFile.absolutePath} /data/vendor/diag").exec()
|
||||
|
||||
shell.newJob().add("cat /proc/mounts > ${mountsFile.absolutePath}").exec()
|
||||
shell.newJob().add("cat /proc/filesystems > ${fileSystemsFile.absolutePath}").exec()
|
||||
shell.newJob().add("ls -alRZ /data/adb > ${ksuFileTree.absolutePath}").exec()
|
||||
shell.newJob().add("cat /data/system/packages.list > ${appListFile.absolutePath}").exec()
|
||||
shell.newJob().add("getprop > ${propFile.absolutePath}").exec()
|
||||
|
||||
// basic information
|
||||
val buildInfo = File(bugreportDir, "basic.txt")
|
||||
PrintWriter(FileWriter(buildInfo)).use { pw ->
|
||||
pw.println("Kernel: ${System.getProperty("os.version")}")
|
||||
pw.println("BRAND: " + Build.BRAND)
|
||||
pw.println("MODEL: " + Build.MODEL)
|
||||
pw.println("PRODUCT: " + Build.PRODUCT)
|
||||
pw.println("MANUFACTURER: " + Build.MANUFACTURER)
|
||||
pw.println("SDK: " + Build.VERSION.SDK_INT)
|
||||
pw.println("PREVIEW_SDK: " + Build.VERSION.PREVIEW_SDK_INT)
|
||||
pw.println("FINGERPRINT: " + Build.FINGERPRINT)
|
||||
pw.println("DEVICE: " + Build.DEVICE)
|
||||
pw.println("Manager: " + getManagerVersion(context))
|
||||
|
||||
val uname = Os.uname()
|
||||
pw.println("KernelRelease: ${uname.release}")
|
||||
pw.println("KernelVersion: ${uname.version}")
|
||||
pw.println("Mahcine: ${uname.machine}")
|
||||
pw.println("Nodename: ${uname.nodename}")
|
||||
pw.println("Sysname: ${uname.sysname}")
|
||||
|
||||
val ksuKernel = Natives.getVersion()
|
||||
pw.println("KernelSU: $ksuKernel")
|
||||
val safeMode = Natives.isSafeMode()
|
||||
pw.println("SafeMode: $safeMode")
|
||||
}
|
||||
|
||||
// modules
|
||||
val modulesFile = File(bugreportDir, "modules.json")
|
||||
modulesFile.writeText(listModules())
|
||||
|
||||
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH_mm")
|
||||
val current = LocalDateTime.now().format(formatter)
|
||||
|
||||
val targetFile = File(context.cacheDir, "KernelSU_bugreport_${current}.tar.gz")
|
||||
|
||||
shell.newJob().add("tar czf ${targetFile.absolutePath} -C ${bugreportDir.absolutePath} .").exec()
|
||||
shell.newJob().add("rm -rf ${bugreportDir.absolutePath}").exec()
|
||||
shell.newJob().add("chmod 0644 ${targetFile.absolutePath}").exec()
|
||||
|
||||
return targetFile
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
package me.weishu.kernelsu.ui.viewmodel
|
||||
|
||||
import android.content.Context
|
||||
import android.os.SystemClock
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
@@ -8,9 +7,11 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.coroutines.launch
|
||||
import me.weishu.kernelsu.ui.util.listModules
|
||||
import me.weishu.kernelsu.ui.util.overlayFsAvailable
|
||||
import org.json.JSONArray
|
||||
import java.text.Collator
|
||||
import java.util.*
|
||||
@@ -37,6 +38,9 @@ class ModuleViewModel : ViewModel() {
|
||||
var isRefreshing by mutableStateOf(false)
|
||||
private set
|
||||
|
||||
var isOverlayAvailable by mutableStateOf(overlayFsAvailable())
|
||||
private set
|
||||
|
||||
val moduleList by derivedStateOf {
|
||||
val comparator = compareBy(Collator.getInstance(Locale.getDefault()), ModuleInfo::id)
|
||||
modules.sortedWith(comparator).also {
|
||||
@@ -44,12 +48,16 @@ class ModuleViewModel : ViewModel() {
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun fetchModuleList() {
|
||||
withContext(Dispatchers.IO) {
|
||||
fun fetchModuleList() {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
isRefreshing = true
|
||||
|
||||
val oldModuleList = modules
|
||||
|
||||
val start = SystemClock.elapsedRealtime()
|
||||
|
||||
kotlin.runCatching {
|
||||
isOverlayAvailable = overlayFsAvailable()
|
||||
|
||||
val result = listModules()
|
||||
|
||||
@@ -74,8 +82,14 @@ class ModuleViewModel : ViewModel() {
|
||||
}.toList()
|
||||
}.onFailure { e ->
|
||||
Log.e(TAG, "fetchModuleList: ", e)
|
||||
isRefreshing = false
|
||||
}
|
||||
|
||||
// when both old and new is kotlin.collections.EmptyList
|
||||
// moduleList update will don't trigger
|
||||
if (oldModuleList === modules) {
|
||||
isRefreshing = false
|
||||
}
|
||||
|
||||
Log.i(TAG, "load cost: ${SystemClock.elapsedRealtime() - start}, modules: $modules")
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ class SuperUserViewModel : ViewModel() {
|
||||
)
|
||||
|
||||
var search by mutableStateOf("")
|
||||
var showSystemApps by mutableStateOf(false)
|
||||
var isRefreshing by mutableStateOf(false)
|
||||
private set
|
||||
|
||||
@@ -55,6 +56,9 @@ class SuperUserViewModel : ViewModel() {
|
||||
sortedList.filter {
|
||||
it.label.contains(search) || it.packageName.contains(search) || HanziToPinyin.getInstance()
|
||||
.toPinyinString(it.label).contains(search)
|
||||
}.filter {
|
||||
it.uid == 2000 // Always show shell
|
||||
|| showSystemApps || it.icon.applicationInfo.flags.and(ApplicationInfo.FLAG_SYSTEM) == 0
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
55
manager/app/src/main/res/values-in/strings.xml
Normal file
55
manager/app/src/main/res/values-in/strings.xml
Normal file
@@ -0,0 +1,55 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="home">Beranda</string>
|
||||
<string name="home_not_installed">Tidak terinstall</string>
|
||||
<string name="home_click_to_install">Klik untuk menginstall</string>
|
||||
<string name="home_working">Bekerja</string>
|
||||
<string name="home_working_version">Versi: %d</string>
|
||||
<string name="home_unsupported">Tidak didukung</string>
|
||||
<string name="home_unsupported_reason">Saat ini kernelSu hanya mendukung GKI kernel</string>
|
||||
<string name="home_copied_to_clipboard">Salin ke clipboard</string>
|
||||
<string name="home_support">Dukungan</string>
|
||||
<string name="home_kernel">Kernel</string>
|
||||
<string name="home_arch">Arch</string>
|
||||
<string name="home_manager_version">Versi manager</string>
|
||||
<string name="home_api">API Level</string>
|
||||
<string name="home_abi">ABI</string>
|
||||
<string name="home_fingerprint">Fingerprint</string>
|
||||
<string name="home_securitypatch">Patch keamanan</string>
|
||||
<string name="home_selinux_status">Status SElinux</string>
|
||||
<string name="selinux_status_disabled">Cacat</string>
|
||||
<string name="selinux_status_enforcing">Enforcing</string>
|
||||
<string name="selinux_status_permissive">Permissive</string>
|
||||
<string name="selinux_status_unknown">Tidak tersedia</string>
|
||||
<string name="superuser">Superuser</string>
|
||||
<string name="superuser_failed_to_grant_root">Gagal mengizinkan root untuk %d</string>
|
||||
<string name="module_failed_to_enable">Gagal mengaktifkan module: %s</string>
|
||||
<string name="module_failed_to_disable">Gagal menonaktifkan module: %s</string>
|
||||
<string name="module_empty">Tidak ada module terpasang</string>
|
||||
<string name="module">Module</string>
|
||||
<string name="uninstall">Hapus</string>
|
||||
<string name="module_install">Pasang</string>
|
||||
<string name="install">Pasang</string>
|
||||
<string name="reboot">Reboot perangkat</string>
|
||||
<string name="settings">Pengaturan</string>
|
||||
<string name="reboot_userspace">Soft Reboot</string>
|
||||
<string name="reboot_recovery">Reboot ke Recovery</string>
|
||||
<string name="reboot_bootloader">Reboot ke Bootloader</string>
|
||||
<string name="reboot_download">Reboot ke Download</string>
|
||||
<string name="reboot_edl">Reboot ke EDL</string>
|
||||
<string name="about">Tentang</string>
|
||||
<string name="require_kernel_version_8">Membutuhkan KernelSU Versi 8+</string>
|
||||
<string name="module_uninstall_confirm">Apakah anda yakin ingin menghapus module ini %s?</string>
|
||||
<string name="module_uninstall_success">%s terhapus</string>
|
||||
<string name="module_uninstall_failed">Gagal untuk menghapus: %s</string>
|
||||
<string name="module_version">Versi</string>
|
||||
<string name="module_author">Pembuat</string>
|
||||
<string name="module_overlay_fs_not_available">overlayfs tidak tersedia, module tidak bekerja!</string>
|
||||
<string name="refresh">Segarkan</string>
|
||||
<string name="show_system_apps">Tampilkan system apps</string>
|
||||
<string name="hide_system_apps">Sembunyikan system apps</string>
|
||||
<string name="send_log">Kirim logs</string>
|
||||
<string name="safe_mode">Mode aman</string>
|
||||
<string name="reboot_to_apply">Restart untuk menerapkan</string>
|
||||
<string name="module_magisk_conflict">Module akan di nonaktifkan karna konflik dengan magisk\'s!</string>
|
||||
</resources>
|
||||
54
manager/app/src/main/res/values-ja/strings.xml
Normal file
54
manager/app/src/main/res/values-ja/strings.xml
Normal file
@@ -0,0 +1,54 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name" translatable="false">KernelSU</string>
|
||||
<string name="home">ホーム</string>
|
||||
<string name="home_not_installed">未インストール</string>
|
||||
<string name="home_click_to_install">タップでインストール</string>
|
||||
<string name="home_working">動作中</string>
|
||||
<string name="home_working_version">バージョン: %d</string>
|
||||
<string name="home_unsupported">非対応</string>
|
||||
<string name="home_unsupported_reason">KernelSUは現在、GKIカーネルのみサポートをしています</string>
|
||||
<string name="home_copied_to_clipboard">クリップボードにコピーしました</string>
|
||||
<string name="home_support">対応</string>
|
||||
<string name="home_kernel">カーネル</string>
|
||||
<string name="home_arch">アーキテクチャ</string>
|
||||
<string name="home_manager_version">バージョン</string>
|
||||
<string name="home_api">APIレベル</string>
|
||||
<string name="home_abi">ABI</string>
|
||||
<string name="home_fingerprint">Fingerprint</string>
|
||||
<string name="home_securitypatch">セキュリティパッチ</string>
|
||||
<string name="home_selinux_status">SELinuxの状態</string>
|
||||
<string name="selinux_status_disabled">無効</string>
|
||||
<string name="selinux_status_enforcing">Enforcing</string>
|
||||
<string name="selinux_status_permissive">Permissive</string>
|
||||
<string name="selinux_status_unknown">不明</string>
|
||||
<string name="superuser">スーパーユーザー</string>
|
||||
<string name="superuser_failed_to_grant_root">%dの権限の付与に失敗しました</string>
|
||||
<string name="module_failed_to_enable">モジュールの有効化に失敗: %s</string>
|
||||
<string name="module_failed_to_disable">モジュールの無効化に失敗: %s</string>
|
||||
<string name="module_empty">モジュールはインストールされていません</string>
|
||||
<string name="module">モジュール</string>
|
||||
<string name="uninstall">アンインストール</string>
|
||||
<string name="module_install">インストール</string>
|
||||
<string name="install">インストール</string>
|
||||
<string name="reboot">再起動</string>
|
||||
<string name="settings">設定</string>
|
||||
<string name="reboot_userspace">ソフトリブート</string>
|
||||
<string name="reboot_recovery">リカバリーで再起動</string>
|
||||
<string name="reboot_bootloader">Bootloaderで再起動</string>
|
||||
<string name="reboot_download">ダウンロードモードで再起動</string>
|
||||
<string name="reboot_edl">EDLで再起動</string>
|
||||
<string name="about">アプリについて</string>
|
||||
<string name="require_kernel_version_8">KernelSU バージョン8以降が必要です</string>
|
||||
<string name="module_uninstall_success">%sをアンインストールしました</string>
|
||||
<string name="module_uninstall_failed">アンインストールに失敗: %s</string>
|
||||
<string name="module_version">バージョン</string>
|
||||
<string name="module_author">作者</string>
|
||||
<string name="module_overlay_fs_not_available">OverlayFSが有効でないためモジュールは動作しません</string>
|
||||
<string name="refresh">更新</string>
|
||||
<string name="show_system_apps">システムアプリを表示</string>
|
||||
<string name="hide_system_apps">システムアプリを非表示</string>
|
||||
<string name="send_log">ログを送信</string>
|
||||
<string name="safe_mode">セーフモード</string>
|
||||
<string name="reboot_to_apply">再起動をして有効化する</string>
|
||||
</resources>
|
||||
56
manager/app/src/main/res/values-ru/strings.xml
Normal file
56
manager/app/src/main/res/values-ru/strings.xml
Normal file
@@ -0,0 +1,56 @@
|
||||
<resources>
|
||||
<string name="home">Главная</string>
|
||||
<string name="home_not_installed">Не установлен</string>
|
||||
<string name="home_click_to_install">Нажмите чтобы установить</string>
|
||||
<string name="home_working">Работает</string>
|
||||
<string name="home_working_version">Версия: %d</string>
|
||||
<string name="home_unsupported">Не поддерживается</string>
|
||||
<string name="home_unsupported_reason">KernelSU поддерживает только GKI ядра</string>
|
||||
<string name="home_copied_to_clipboard">Скопировано в буфер обмена</string>
|
||||
<string name="home_support">Поддержка</string>
|
||||
|
||||
<string name="home_kernel">Ядро</string>
|
||||
<string name="home_arch">Архитектура</string>
|
||||
<string name="home_manager_version">Версия</string>
|
||||
<string name="home_api">Уровень API</string>
|
||||
<string name="home_abi">ABI</string>
|
||||
<string name="home_fingerprint">Подпись</string>
|
||||
<string name="home_securitypatch">Патч Безопасности</string>
|
||||
|
||||
<string name="home_selinux_status">Состояние SELinux</string>
|
||||
<string name="selinux_status_disabled">Выключен</string>
|
||||
<string name="selinux_status_enforcing">Принудительный</string>
|
||||
<string name="selinux_status_permissive">Разрешающий</string>
|
||||
<string name="selinux_status_unknown">Неизвестно</string>
|
||||
<string name="superuser">Superuser</string>
|
||||
<string name="superuser_failed_to_grant_root">Failed to grant root for %d</string>
|
||||
<string name="module_failed_to_enable">Не удалось включить модуль: %s</string>
|
||||
<string name="module_failed_to_disable">Не удалось отключить модуль: %s</string>
|
||||
<string name="module_empty">Нет установленных модулей</string>
|
||||
|
||||
<string name="module">Модули</string>
|
||||
<string name="uninstall">Удалить</string>
|
||||
<string name="module_install">Установить</string>
|
||||
<string name="install">Установить</string>
|
||||
<string name="reboot">Перезагрузка</string>
|
||||
<string name="settings">Настройки</string>
|
||||
<string name="reboot_userspace">Soft Reboot</string>
|
||||
<string name="reboot_recovery">Reboot to Recovery</string>
|
||||
<string name="reboot_bootloader">Reboot to Bootloader</string>
|
||||
<string name="reboot_download">Reboot to Download</string>
|
||||
<string name="reboot_edl">Reboot to EDL</string>
|
||||
<string name="about">О KernelSU</string>
|
||||
<string name="require_kernel_version_8">Требуется KernelSU версии 8 и выше</string>
|
||||
<string name="module_uninstall_success">%s удален</string>
|
||||
<string name="module_uninstall_failed">Не удалось удалить: %s</string>
|
||||
<string name="module_version">Версия</string>
|
||||
<string name="module_author">Автор</string>
|
||||
<string name="module_overlay_fs_not_available">overlayfs выключен, модуль не будет работать!</string>
|
||||
<string name="refresh">Обновить</string>
|
||||
<string name="show_system_apps">Показать системные приложения</string>
|
||||
<string name="hide_system_apps">Скрыть системные приложения</string>
|
||||
<string name="send_log">Отправить лог</string>
|
||||
<string name="safe_mode">Безопасный режим</string>
|
||||
<string name="reboot_to_apply">Перезагрузите, чтобы вступить в силу</string>
|
||||
|
||||
</resources>
|
||||
51
manager/app/src/main/res/values-vi/strings.xml
Normal file
51
manager/app/src/main/res/values-vi/strings.xml
Normal file
@@ -0,0 +1,51 @@
|
||||
<resources>
|
||||
<string name="home">Trang chủ</string>
|
||||
<string name="home_not_installed">Chưa được cài đặt</string>
|
||||
<string name="home_click_to_install">Nhấn đề cài dặt</string>
|
||||
<string name="home_working">Đang chạy</string>
|
||||
<string name="home_working_version">Phiên bản: %d</string>
|
||||
<string name="home_unsupported">Không hỗ trợ</string>
|
||||
<string name="home_unsupported_reason">KernelSU hiện tại chỉ hỗ trợ kernel GKI</string>
|
||||
<string name="home_copied_to_clipboard">Sao chép vào clipboard</string>
|
||||
<string name="home_support">Ủng hộ</string>
|
||||
<string name="home_kernel">Kernel</string>
|
||||
<string name="home_arch">Kiến trúc</string>
|
||||
<string name="home_manager_version">Phiên bản</string>
|
||||
<string name="home_api">Cấp độ API</string>
|
||||
<string name="home_abi">ABI</string>
|
||||
<string name="home_fingerprint">Fingerprint</string>
|
||||
<string name="home_securitypatch">Bản vá bảo mật</string>
|
||||
<string name="home_selinux_status">Trạng thái SELinux</string>
|
||||
<string name="selinux_status_disabled">Vô hiệu hóa</string>
|
||||
<string name="selinux_status_enforcing">Thực thi</string>
|
||||
<string name="selinux_status_permissive">Cho phép</string>
|
||||
<string name="selinux_status_unknown">Không rõ</string>
|
||||
<string name="superuser">Superuser</string>
|
||||
<string name="superuser_failed_to_grant_root">Không thể cấp quyền root cho %d</string>
|
||||
<string name="module_failed_to_enable">Không thể kích hoạt mô-đun: %s</string>
|
||||
<string name="module_failed_to_disable">Không thể vô hiệu hóa mô-đun: %s</string>
|
||||
<string name="module_empty">Chưa có mô-đun nào được cài đặt</string>
|
||||
<string name="module">Mô-đun</string>
|
||||
<string name="uninstall">Gỡ cài đặt</string>
|
||||
<string name="module_install">Cài đặt</string>
|
||||
<string name="install">Cài đặt</string>
|
||||
<string name="reboot">Khởi động lại</string>
|
||||
<string name="settings">Thiết lập</string>
|
||||
<string name="reboot_userspace">Khởi động mềm</string>
|
||||
<string name="reboot_recovery">Khởi động lại vào Recovery</string>
|
||||
<string name="reboot_bootloader">Khởi động lại vào Bootloader</string>
|
||||
<string name="reboot_download">Khởi động lại vào Download Mode</string>
|
||||
<string name="reboot_edl">Khởi động vào EDL</string>
|
||||
<string name="about">Thông tin</string>
|
||||
<string name="require_kernel_version_8">Yêu cầu KernelSU phiên bản 8+</string>
|
||||
<string name="module_uninstall_success">%s đã được gỡ cài đặt</string>
|
||||
<string name="module_uninstall_failed">Lỗi khi gỡ cài đặt: %s</string>
|
||||
<string name="module_version">Phiên bản</string>
|
||||
<string name="module_author">Tác giả</string>
|
||||
<string name="module_overlay_fs_not_available">Không tìm thấy overlayfs, mô-đun sẽ không thể hoạt động!</string>
|
||||
<string name="refresh">Làm mới</string>
|
||||
<string name="show_system_apps">Hiển thị ứng dụng hệ thống</string>
|
||||
<string name="hide_system_apps">Ẩn ứng dụng hệ thống</string>
|
||||
<string name="safe_mode">Chế độ an toàn</string>
|
||||
<string name="reboot_to_apply">Khởi động lại để có hiệu lực</string>
|
||||
</resources>
|
||||
@@ -11,7 +11,7 @@
|
||||
<string name="home_support">支持</string>
|
||||
<string name="home_kernel">内核版本</string>
|
||||
<string name="home_arch">设备架构</string>
|
||||
<string name="home_version">系统版本</string>
|
||||
<string name="home_manager_version">管理器版本</string>
|
||||
<string name="home_api">API 版本</string>
|
||||
<string name="home_abi">ABI 支持</string>
|
||||
<string name="home_fingerprint">系统指纹</string>
|
||||
@@ -37,12 +37,19 @@
|
||||
<string name="reboot_bootloader">重启到 BootLoader</string>
|
||||
<string name="reboot_download">重启到 Download</string>
|
||||
<string name="reboot_edl">重启到 EDL</string>
|
||||
<string name="settings_system_rw">使系统可写</string>
|
||||
<string name="settings_system_rw_summary">使用 overlayfs 使系统分区可写, 重启生效</string>
|
||||
<string name="about">关于</string>
|
||||
<string name="require_kernel_version_8">需要 KernelSU 版本 8+</string>
|
||||
<string name="module_uninstall_confirm">确定要卸载模块 %s 吗?</string>
|
||||
<string name="module_uninstall_success">%s 已卸载</string>
|
||||
<string name="module_uninstall_failed">卸载失败: %s</string>
|
||||
<string name="module_version">版本</string>
|
||||
<string name="module_author">作者</string>
|
||||
<string name="module_overlay_fs_not_available">内核不支持 overlayfs,模块功能无法运作!</string>
|
||||
<string name="refresh">刷新</string>
|
||||
<string name="show_system_apps">显示系统应用</string>
|
||||
<string name="hide_system_apps">隐藏系统应用</string>
|
||||
<string name="send_log">发送日志</string>
|
||||
<string name="safe_mode">安全模式</string>
|
||||
<string name="reboot_to_apply">重启生效</string>
|
||||
<string name="module_magisk_conflict">所有模块已被禁用,因为它与 Magisk 的模块系统有冲突!</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,48 +1,58 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="home">主頁</string>
|
||||
<string name="home_not_installed">未安装</string>
|
||||
<string name="home_click_to_install">点击安装</string>
|
||||
<string name="home_working">工作中</string>
|
||||
<string name="home_working_version">版本: %d</string>
|
||||
<string name="home_unsupported">不支持</string>
|
||||
<string name="home_unsupported_reason">KernelSU 现在只支持 GKI 内核</string>
|
||||
<string name="home">首頁</string>
|
||||
<string name="home_not_installed">未安裝</string>
|
||||
<string name="home_click_to_install">按一下以安裝</string>
|
||||
<string name="home_working">正在處理</string>
|
||||
<string name="home_working_version">版本:%d</string>
|
||||
<string name="home_unsupported">不支援</string>
|
||||
<string name="home_unsupported_reason">KernelSU 現在僅支援 GKI 核心</string>
|
||||
<string name="home_copied_to_clipboard">已複製到剪貼簿</string>
|
||||
<string name="home_support">支持</string>
|
||||
<string name="home_kernel">内核版本</string>
|
||||
<string name="home_arch">设备架构</string>
|
||||
<string name="home_version">系统版本</string>
|
||||
<string name="home_api">API 版本</string>
|
||||
<string name="home_abi">ABI 支持</string>
|
||||
<string name="home_fingerprint">系统指纹</string>
|
||||
<string name="home_securitypatch">安全补丁</string>
|
||||
<string name="home_support">支援</string>
|
||||
|
||||
<string name="home_kernel">核心</string>
|
||||
<string name="home_arch">架構</string>
|
||||
<string name="home_manager_version">管理員版本</string>
|
||||
<string name="home_api">API 層級</string>
|
||||
<string name="home_abi">ABI 支援</string>
|
||||
<string name="home_fingerprint">指紋</string>
|
||||
<string name="home_securitypatch">安全性修補程式</string>
|
||||
|
||||
<string name="home_selinux_status">SELinux 狀態</string>
|
||||
<string name="selinux_status_disabled">被禁用</string>
|
||||
<string name="selinux_status_enforcing">強制執行</string>
|
||||
<string name="selinux_status_permissive">寬容模式</string>
|
||||
<string name="selinux_status_disabled">已停用</string>
|
||||
<string name="selinux_status_enforcing">強制</string>
|
||||
<string name="selinux_status_permissive">寬鬆</string>
|
||||
<string name="selinux_status_unknown">未知</string>
|
||||
<string name="superuser">超級用戶</string>
|
||||
<string name="superuser_failed_to_grant_root">无法为 %d 授予 Root</string>
|
||||
<string name="module_failed_to_enable">无法启用模块: %s</string>
|
||||
<string name="module_failed_to_disable">无法禁用模块: %s</string>
|
||||
<string name="module_empty">没有安装模块</string>
|
||||
<string name="module">模块</string>
|
||||
<string name="uninstall">卸载</string>
|
||||
<string name="module_install">安装</string>
|
||||
<string name="install">安装</string>
|
||||
<string name="reboot">重启</string>
|
||||
<string name="superuser">超級使用者</string>
|
||||
<string name="superuser_failed_to_grant_root">無法為 %d 授予 Root 存取權</string>
|
||||
<string name="module_failed_to_enable">無法啟用模組:%s</string>
|
||||
<string name="module_failed_to_disable">無法停用模組:%s</string>
|
||||
<string name="module_empty">尚未安裝模組</string>
|
||||
|
||||
<string name="module">模組</string>
|
||||
<string name="uninstall">解除安裝</string>
|
||||
<string name="module_install">安裝</string>
|
||||
<string name="install">安裝</string>
|
||||
<string name="reboot">重新啟動</string>
|
||||
<string name="settings">設定</string>
|
||||
<string name="reboot_userspace">软重启</string>
|
||||
<string name="reboot_recovery">重启到 Recovery</string>
|
||||
<string name="reboot_bootloader">重启到 BootLoader</string>
|
||||
<string name="reboot_download">重启到 Download</string>
|
||||
<string name="reboot_edl">重启到 EDL</string>
|
||||
<string name="settings_system_rw">使系统可写</string>
|
||||
<string name="settings_system_rw_summary">使用 overlayfs 使系统分区可写, 重启生效</string>
|
||||
<string name="about">关于</string>
|
||||
<string name="require_kernel_version_8">需要 KernelSU 版本 8+</string>
|
||||
<string name="module_uninstall_success">%s 已卸載</string>
|
||||
<string name="module_uninstall_failed">卸載失敗: %s</string>
|
||||
<string name="reboot_userspace">軟啟動</string>
|
||||
<string name="reboot_recovery">重新啟動至 Recovery</string>
|
||||
<string name="reboot_bootloader">重新啟動至 Bootloader</string>
|
||||
<string name="reboot_download">重新啟動至 Download</string>
|
||||
<string name="reboot_edl">重新啟動至 EDL</string>
|
||||
<string name="about">關於</string>
|
||||
<string name="require_kernel_version_8">需要 KernelSU 8+ 版本</string>
|
||||
<string name="module_uninstall_confirm">您確定要解除安裝模組 %s 嗎?</string>
|
||||
<string name="module_uninstall_success">%s 已解除安裝</string>
|
||||
<string name="module_uninstall_failed">無法解除安裝:%s</string>
|
||||
<string name="module_version">版本</string>
|
||||
<string name="module_author">作者</string>
|
||||
<string name="module_overlay_fs_not_available">OverlayFS 無法使用,模組無法正常運作!</string>
|
||||
<string name="refresh">重新整理</string>
|
||||
<string name="show_system_apps">顯示系統應用程式</string>
|
||||
<string name="hide_system_apps">隱藏系統應用程式</string>
|
||||
<string name="send_log">傳送記錄</string>
|
||||
<string name="safe_mode">安全模式</string>
|
||||
<string name="reboot_to_apply">重新啟動以生效</string>
|
||||
<string name="module_magisk_conflict">模組已停用,因其與 Magisk 的模組存在衝突!</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,48 +1,58 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="home">主頁</string>
|
||||
<string name="home">首頁</string>
|
||||
<string name="home_not_installed">未安裝</string>
|
||||
<string name="home_click_to_install">点击安装</string>
|
||||
<string name="home_working">工作中</string>
|
||||
<string name="home_working_version">版本: %d</string>
|
||||
<string name="home_unsupported">不支持</string>
|
||||
<string name="home_unsupported_reason">KernelSU 现在只支持 GKI 内核</string>
|
||||
<string name="home_click_to_install">按一下以安裝</string>
|
||||
<string name="home_working">正在處理</string>
|
||||
<string name="home_working_version">版本:%d</string>
|
||||
<string name="home_unsupported">不支援</string>
|
||||
<string name="home_unsupported_reason">KernelSU 現在僅支援 GKI 核心</string>
|
||||
<string name="home_copied_to_clipboard">已複製到剪貼簿</string>
|
||||
<string name="home_support">支持</string>
|
||||
<string name="home_kernel">内核版本</string>
|
||||
<string name="home_arch">设备架构</string>
|
||||
<string name="home_version">系统版本</string>
|
||||
<string name="home_api">API 版本</string>
|
||||
<string name="home_abi">ABI 支持</string>
|
||||
<string name="home_fingerprint">系统指纹</string>
|
||||
<string name="home_securitypatch">安全补丁</string>
|
||||
<string name="home_support">支援</string>
|
||||
|
||||
<string name="home_kernel">核心</string>
|
||||
<string name="home_arch">架構</string>
|
||||
<string name="home_manager_version">管理員版本</string>
|
||||
<string name="home_api">API 層級</string>
|
||||
<string name="home_abi">ABI 支援</string>
|
||||
<string name="home_fingerprint">指紋</string>
|
||||
<string name="home_securitypatch">安全性修補程式</string>
|
||||
|
||||
<string name="home_selinux_status">SELinux 狀態</string>
|
||||
<string name="selinux_status_disabled">被禁用</string>
|
||||
<string name="selinux_status_enforcing">強制執行</string>
|
||||
<string name="selinux_status_permissive">寬容模式</string>
|
||||
<string name="selinux_status_disabled">已停用</string>
|
||||
<string name="selinux_status_enforcing">強制</string>
|
||||
<string name="selinux_status_permissive">寬鬆</string>
|
||||
<string name="selinux_status_unknown">未知</string>
|
||||
<string name="superuser">超級用戶</string>
|
||||
<string name="superuser_failed_to_grant_root">无法为 %d 授予 Root</string>
|
||||
<string name="module_failed_to_enable">无法启用模块: %s</string>
|
||||
<string name="module_failed_to_disable">无法禁用模块: %s</string>
|
||||
<string name="module_empty">没有安装模块</string>
|
||||
<string name="module">模块</string>
|
||||
<string name="uninstall">卸载</string>
|
||||
<string name="module_install">安装</string>
|
||||
<string name="install">安装</string>
|
||||
<string name="reboot">重启</string>
|
||||
<string name="superuser">超級使用者</string>
|
||||
<string name="superuser_failed_to_grant_root">無法為 %d 授予 Root 存取權</string>
|
||||
<string name="module_failed_to_enable">無法啟用模組:%s</string>
|
||||
<string name="module_failed_to_disable">無法停用模組:%s</string>
|
||||
<string name="module_empty">尚未安裝模組</string>
|
||||
|
||||
<string name="module">模組</string>
|
||||
<string name="uninstall">解除安裝</string>
|
||||
<string name="module_install">安裝</string>
|
||||
<string name="install">安裝</string>
|
||||
<string name="reboot">重新啟動</string>
|
||||
<string name="settings">設定</string>
|
||||
<string name="reboot_userspace">软重启</string>
|
||||
<string name="reboot_recovery">重启到 Recovery</string>
|
||||
<string name="reboot_bootloader">重启到 BootLoader</string>
|
||||
<string name="reboot_download">重启到 Download</string>
|
||||
<string name="reboot_edl">重启到 EDL</string>
|
||||
<string name="settings_system_rw">使系统可写</string>
|
||||
<string name="settings_system_rw_summary">使用 overlayfs 使系统分区可写, 重启生效</string>
|
||||
<string name="about">关于</string>
|
||||
<string name="require_kernel_version_8">需要 KernelSU 版本 8+</string>
|
||||
<string name="module_uninstall_success">%s 已卸載</string>
|
||||
<string name="module_uninstall_failed">卸載失敗: %s</string>
|
||||
<string name="reboot_userspace">軟啟動</string>
|
||||
<string name="reboot_recovery">重新啟動至 Recovery</string>
|
||||
<string name="reboot_bootloader">重新啟動至 Bootloader</string>
|
||||
<string name="reboot_download">重新啟動至 Download</string>
|
||||
<string name="reboot_edl">重新啟動至 EDL</string>
|
||||
<string name="about">關於</string>
|
||||
<string name="require_kernel_version_8">需要 KernelSU 8+ 版本</string>
|
||||
<string name="module_uninstall_confirm">您確定要解除安裝模組 %s 嗎?</string>
|
||||
<string name="module_uninstall_success">%s 已解除安裝</string>
|
||||
<string name="module_uninstall_failed">無法解除安裝:%s</string>
|
||||
<string name="module_version">版本</string>
|
||||
<string name="module_author">作者</string>
|
||||
<string name="module_overlay_fs_not_available">OverlayFS 無法使用,模組無法正常運作!</string>
|
||||
<string name="refresh">重新整理</string>
|
||||
<string name="show_system_apps">顯示系統應用程式</string>
|
||||
<string name="hide_system_apps">隱藏系統應用程式</string>
|
||||
<string name="send_log">傳送記錄</string>
|
||||
<string name="safe_mode">安全模式</string>
|
||||
<string name="reboot_to_apply">重新啟動以生效</string>
|
||||
<string name="module_magisk_conflict">模組已停用,因其與 Magisk 的模組存在衝突!</string>
|
||||
</resources>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
<string name="home_kernel">Kernel</string>
|
||||
<string name="home_arch">Arch</string>
|
||||
<string name="home_version">Version</string>
|
||||
<string name="home_manager_version">Manager Version</string>
|
||||
<string name="home_api">API Level</string>
|
||||
<string name="home_abi">ABI</string>
|
||||
<string name="home_fingerprint">Fingerprint</string>
|
||||
@@ -41,13 +41,20 @@
|
||||
<string name="reboot_bootloader">Reboot to Bootloader</string>
|
||||
<string name="reboot_download">Reboot to Download</string>
|
||||
<string name="reboot_edl">Reboot to EDL</string>
|
||||
<string name="settings_system_rw">Make system writable</string>
|
||||
<string name="settings_system_rw_summary">Use overlayfs to make system partition writable, reboot to take effect.</string>
|
||||
<string name="about">About</string>
|
||||
<string name="require_kernel_version_8">Require KernelSU version 8+</string>
|
||||
<string name="module_uninstall_confirm">Are you sure you want to uninstall module %s?</string>
|
||||
<string name="module_uninstall_success">%s uninstalled</string>
|
||||
<string name="module_uninstall_failed">Failed to uninstall: %s</string>
|
||||
<string name="module_version">Version</string>
|
||||
<string name="module_author">Author</string>
|
||||
<string name="module_overlay_fs_not_available">overlayfs is not available, module cannot work!</string>
|
||||
<string name="refresh">Refresh</string>
|
||||
<string name="show_system_apps">Show system apps</string>
|
||||
<string name="hide_system_apps">Hide system apps</string>
|
||||
<string name="send_log">Send Log</string>
|
||||
<string name="safe_mode">Safe mode</string>
|
||||
<string name="reboot_to_apply">Reboot to take effect</string>
|
||||
<string name="module_magisk_conflict">Modules are disabled because it is conflict with Magisk\'s!</string>
|
||||
|
||||
</resources>
|
||||
|
||||
6
manager/app/src/main/res/xml/filepaths.xml
Normal file
6
manager/app/src/main/res/xml/filepaths.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<paths>
|
||||
<cache-path name="cache" path="."/>
|
||||
<root-path name="root" path="." />
|
||||
</paths>
|
||||
50
scripts/add_device_handler.py
Normal file
50
scripts/add_device_handler.py
Normal file
@@ -0,0 +1,50 @@
|
||||
import json
|
||||
import sys
|
||||
import os
|
||||
|
||||
|
||||
def main():
|
||||
assert len(sys.argv) == 2
|
||||
file_name = sys.argv[1]
|
||||
github = "https://github.com/"
|
||||
issue_content = os.environ["ISSUE_CONTENT"]
|
||||
lines = issue_content.split("\n\n")
|
||||
assert len(lines) == 6
|
||||
url = lines[1]
|
||||
print(url)
|
||||
device = lines[3]
|
||||
print(device)
|
||||
code_of_conduct = lines[5]
|
||||
print(code_of_conduct)
|
||||
assert code_of_conduct.find("[X]") > 0
|
||||
tmp = url.removesuffix("/").replace(github, "").split("/")
|
||||
print(tmp)
|
||||
assert len(tmp) == 2
|
||||
maintainer = tmp[0]
|
||||
print(maintainer)
|
||||
maintainer_link = "%s%s" % (github, maintainer)
|
||||
print(maintainer_link)
|
||||
kernel_name = tmp[1]
|
||||
print(kernel_name)
|
||||
kernel_link = "%s%s/%s" % (github, maintainer, kernel_name)
|
||||
print(kernel_link)
|
||||
with open(file_name, "r") as f:
|
||||
data = json.loads(f.read())
|
||||
data.append(
|
||||
{
|
||||
"maintainer": maintainer,
|
||||
"maintainer_link": maintainer_link,
|
||||
"kernel_name": kernel_name,
|
||||
"kernel_link": kernel_link,
|
||||
"devices": device,
|
||||
}
|
||||
)
|
||||
os.remove(file_name)
|
||||
with open(file_name, "w") as f:
|
||||
f.write(json.dumps(data, indent=4))
|
||||
os.system("echo success=true >> $GITHUB_OUTPUT")
|
||||
os.system("echo device=%s >> $GITHUB_OUTPUT" % device)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
51
scripts/bin2c.py
Normal file
51
scripts/bin2c.py
Normal file
@@ -0,0 +1,51 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
|
||||
line_size = 80
|
||||
|
||||
|
||||
def bin2c(filename, varname='data'):
|
||||
if not os.path.isfile(filename):
|
||||
print('File "%s" is not found!' % filename)
|
||||
return ''
|
||||
if not re.match('[a-zA-Z_][a-zA-Z0-9_]*', varname):
|
||||
print('Invalid variable name "%s"' % varname)
|
||||
return
|
||||
with open(filename, 'rb') as in_file:
|
||||
data = in_file.read()
|
||||
# limit the line length
|
||||
byte_len = 6 # '0x00, '
|
||||
out = 'unsigned int %s_size = %d;\n' \
|
||||
'const char %s[%d] = {\n' % (varname, len(data), varname, len(data))
|
||||
line = ''
|
||||
for byte in data:
|
||||
line += '0x%02x, ' % byte
|
||||
if len(line) + 4 + byte_len >= line_size:
|
||||
out += ' ' * 4 + line + '\n'
|
||||
line = ''
|
||||
# add the last line
|
||||
if len(line) + 4 + byte_len < line_size:
|
||||
out += ' ' * 4 + line + '\n'
|
||||
# strip the last comma
|
||||
out = out.rstrip(', \n') + '\n'
|
||||
out += '};'
|
||||
return out
|
||||
|
||||
|
||||
def main():
|
||||
""" Main func """
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
'filename', help='filename to convert to C array')
|
||||
parser.add_argument(
|
||||
'varname', nargs='?', help='variable name', default='data')
|
||||
args = parser.parse_args()
|
||||
# print out the data
|
||||
print(bin2c(args.filename, args.varname))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
891
userspace/ksud/Cargo.lock
generated
891
userspace/ksud/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -10,7 +10,6 @@ rust-version = "1.65"
|
||||
anyhow = "1.0.68"
|
||||
clap = { version = "4.0.32", features = ["derive"] }
|
||||
const_format = "0.2.30"
|
||||
subprocess = "0.2.9"
|
||||
zip = "0.6.3"
|
||||
zip-extensions = "0.6.1"
|
||||
java-properties = "1.4.1"
|
||||
@@ -23,8 +22,28 @@ encoding = "0.2.33"
|
||||
retry = "2.0.0"
|
||||
humansize = "2.0.0"
|
||||
libc = "0.2"
|
||||
extattr = "1.0.0"
|
||||
jwalk = "0.8.1"
|
||||
is_executable = "1.0.1"
|
||||
nom = "7"
|
||||
derive-new = "0.5"
|
||||
rust-embed = { version = "6.4.2", features = [
|
||||
"debug-embed",
|
||||
"compression", # must clean build after updating binaries
|
||||
] }
|
||||
which = "4.2.2"
|
||||
|
||||
[target.'cfg(any(target_os = "android", target_os = "linux"))'.dependencies]
|
||||
sys-mount = { git = "https://github.com/tiann/sys-mount" }
|
||||
# some android specific dependencies which compiles under unix are also listed here for convenience of coding
|
||||
android-properties = { version = "0.2.2", features = ["bionic-deprecated"] }
|
||||
procfs = "0.15"
|
||||
proc-mounts = "0.3"
|
||||
|
||||
[target.'cfg(target_os = "android")'.dependencies]
|
||||
android_logger = "0.13"
|
||||
|
||||
[profile.release]
|
||||
strip = true
|
||||
opt-level = "z"
|
||||
lto = true
|
||||
lto = true
|
||||
|
||||
BIN
userspace/ksud/bin/aarch64/busybox
Executable file
BIN
userspace/ksud/bin/aarch64/busybox
Executable file
Binary file not shown.
BIN
userspace/ksud/bin/aarch64/resetprop
Normal file
BIN
userspace/ksud/bin/aarch64/resetprop
Normal file
Binary file not shown.
BIN
userspace/ksud/bin/x86_64/busybox
Normal file
BIN
userspace/ksud/bin/x86_64/busybox
Normal file
Binary file not shown.
BIN
userspace/ksud/bin/x86_64/resetprop
Normal file
BIN
userspace/ksud/bin/x86_64/resetprop
Normal file
Binary file not shown.
55
userspace/ksud/build.rs
Normal file
55
userspace/ksud/build.rs
Normal file
@@ -0,0 +1,55 @@
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
|
||||
fn get_git_version() -> Result<(u32, String), std::io::Error> {
|
||||
let output = Command::new("git")
|
||||
.args(["rev-list", "--count", "HEAD"])
|
||||
.output()?;
|
||||
|
||||
let output = output.stdout;
|
||||
let version_code = String::from_utf8(output).expect("Failed to read git count stdout");
|
||||
let version_code: u32 = version_code
|
||||
.trim()
|
||||
.parse()
|
||||
.map_err(|_| std::io::Error::new(std::io::ErrorKind::Other, "Failed to parse git count"))?;
|
||||
let version_code = 10000 + 200 + version_code; // For historical reasons
|
||||
|
||||
let version_name = String::from_utf8(
|
||||
Command::new("git")
|
||||
.args(["describe", "--tags", "--always"])
|
||||
.output()?
|
||||
.stdout,
|
||||
)
|
||||
.map_err(|_| {
|
||||
std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
"Failed to read git describe stdout",
|
||||
)
|
||||
})?;
|
||||
Ok((version_code, version_name))
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let (code, name) = match get_git_version() {
|
||||
Ok((code, name)) => (code, name),
|
||||
Err(_) => {
|
||||
// show warning if git is not installed
|
||||
println!("cargo:warning=Failed to get git version, using 0.0.0");
|
||||
(0, "0.0.0".to_string())
|
||||
}
|
||||
};
|
||||
let out_dir = env::var("OUT_DIR").expect("Failed to get $OUT_DIR");
|
||||
let out_dir = Path::new(&out_dir);
|
||||
File::create(Path::new(out_dir).join("VERSION_CODE"))
|
||||
.expect("Failed to create VERSION_CODE")
|
||||
.write_all(code.to_string().as_bytes())
|
||||
.expect("Failed to write VERSION_CODE");
|
||||
|
||||
File::create(Path::new(out_dir).join("VERSION_NAME"))
|
||||
.expect("Failed to create VERSION_NAME")
|
||||
.write_all(name.trim().as_bytes())
|
||||
.expect("Failed to write VERSION_NAME");
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
|
||||
use anyhow::{ensure, Result};
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
|
||||
pub fn get_apk_signature(apk: &str) -> Result<(u32, u32)> {
|
||||
let mut buffer = [0u8; 0x10];
|
||||
@@ -17,13 +16,13 @@ pub fn get_apk_signature(apk: &str) -> Result<(u32, u32)> {
|
||||
f.read_exact(&mut n)?;
|
||||
|
||||
let n = u16::from_le_bytes(n);
|
||||
if n as i64 == i {
|
||||
if i64::from(n) == i {
|
||||
f.seek(SeekFrom::Current(-22))?;
|
||||
f.read_exact(&mut size4)?;
|
||||
|
||||
if u32::from_le_bytes(size4) ^ 0xcafebabeu32 == 0xccfbf1eeu32 {
|
||||
if u32::from_le_bytes(size4) ^ 0xcafe_babe_u32 == 0xccfb_f1ee_u32 {
|
||||
if i > 0 {
|
||||
println!("warning: comment length is {}", i);
|
||||
println!("warning: comment length is {i}");
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -37,14 +36,14 @@ pub fn get_apk_signature(apk: &str) -> Result<(u32, u32)> {
|
||||
f.seek(SeekFrom::Current(12))?;
|
||||
// offset
|
||||
f.read_exact(&mut size4)?;
|
||||
f.seek(SeekFrom::Start(u32::from_le_bytes(size4) as u64 - 0x18))?;
|
||||
f.seek(SeekFrom::Start(u64::from(u32::from_le_bytes(size4)) - 0x18))?;
|
||||
|
||||
f.read_exact(&mut size8)?;
|
||||
f.read_exact(&mut buffer)?;
|
||||
|
||||
ensure!(&buffer == b"APK Sig Block 42", "Can not found sig block");
|
||||
|
||||
let pos = u32::from_le_bytes(size4) as u64 - (u64::from_le_bytes(size8) + 0x8);
|
||||
let pos = u64::from(u32::from_le_bytes(size4)) - (u64::from_le_bytes(size8) + 0x8);
|
||||
f.seek(SeekFrom::Start(pos))?;
|
||||
f.read_exact(&mut size_of_block)?;
|
||||
|
||||
@@ -62,7 +61,7 @@ pub fn get_apk_signature(apk: &str) -> Result<(u32, u32)> {
|
||||
f.read_exact(&mut id)?; // id
|
||||
|
||||
let id = u32::from_le_bytes(id);
|
||||
if (id ^ 0xdeadbeefu32) == 0xafa439f5u32 || (id ^ 0xdeadbeefu32) == 0x2efed62fu32 {
|
||||
if (id ^ 0xdead_beef_u32) == 0xafa4_39f5_u32 || (id ^ 0xdead_beef_u32) == 0x2efe_d62f_u32 {
|
||||
f.read_exact(&mut size4)?; // signer-sequence length
|
||||
f.read_exact(&mut size4)?; // signer length
|
||||
f.read_exact(&mut size4)?; // signed data length
|
||||
@@ -70,7 +69,7 @@ pub fn get_apk_signature(apk: &str) -> Result<(u32, u32)> {
|
||||
|
||||
f.read_exact(&mut size4)?; // digests-sequcence length
|
||||
let pos = u32::from_le_bytes(size4);
|
||||
f.seek(SeekFrom::Current(pos as i64))?;
|
||||
f.seek(SeekFrom::Current(i64::from(pos)))?;
|
||||
// offset += 0x4 + pos;
|
||||
|
||||
f.read_exact(&mut size4)?; // certificates length
|
||||
@@ -83,18 +82,20 @@ pub fn get_apk_signature(apk: &str) -> Result<(u32, u32)> {
|
||||
let j = u32::from_le_bytes(size4);
|
||||
for _ in 0..j {
|
||||
f.read_exact(&mut c)?;
|
||||
hash = hash.wrapping_mul(31).wrapping_add(c[0] as i8 as i32);
|
||||
hash = hash.wrapping_mul(31).wrapping_add(i32::from(c[0] as i8));
|
||||
}
|
||||
|
||||
// offset += j;
|
||||
|
||||
let out_size = j;
|
||||
let out_hash = (hash as u32) ^ 0x14131211u32;
|
||||
let out_hash = (hash as u32) ^ 0x1413_1211_u32;
|
||||
|
||||
return Ok((out_size, out_hash));
|
||||
}
|
||||
|
||||
f.seek(SeekFrom::Current(i64::from_le_bytes(size8) - offset as i64))?;
|
||||
f.seek(SeekFrom::Current(
|
||||
i64::from_le_bytes(size8) - i64::from(offset),
|
||||
))?;
|
||||
}
|
||||
|
||||
Err(anyhow::anyhow!("Unknown error"))
|
||||
|
||||
28
userspace/ksud/src/assets.rs
Normal file
28
userspace/ksud/src/assets.rs
Normal file
@@ -0,0 +1,28 @@
|
||||
use anyhow::Result;
|
||||
use const_format::concatcp;
|
||||
use rust_embed::RustEmbed;
|
||||
|
||||
use crate::{defs::BINARY_DIR, utils};
|
||||
|
||||
pub const RESETPROP_PATH: &str = concatcp!(BINARY_DIR, "resetprop");
|
||||
pub const BUSYBOX_PATH: &str = concatcp!(BINARY_DIR, "busybox");
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
#[derive(RustEmbed)]
|
||||
#[folder = "bin/aarch64"]
|
||||
struct Asset;
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
#[derive(RustEmbed)]
|
||||
#[folder = "bin/x86_64"]
|
||||
struct Asset;
|
||||
|
||||
pub fn ensure_binaries() -> Result<()> {
|
||||
for file in Asset::iter() {
|
||||
utils::ensure_binary(
|
||||
format!("{BINARY_DIR}{file}"),
|
||||
&Asset::get(&file).unwrap().data,
|
||||
)?
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,11 +1,16 @@
|
||||
use anyhow::{Ok, Result};
|
||||
use clap::Parser;
|
||||
|
||||
use crate::{apk_sign, debug, event, module};
|
||||
#[cfg(target_os = "android")]
|
||||
use android_logger::Config;
|
||||
#[cfg(target_os = "android")]
|
||||
use log::LevelFilter;
|
||||
|
||||
use crate::{apk_sign, debug, defs, event, module, utils};
|
||||
|
||||
/// KernelSU userspace cli
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
#[command(author, version = defs::VERSION_NAME, about, long_about = None)]
|
||||
struct Args {
|
||||
#[command(subcommand)]
|
||||
command: Commands,
|
||||
@@ -35,7 +40,10 @@ enum Commands {
|
||||
Install,
|
||||
|
||||
/// SELinux policy Patch tool
|
||||
Sepolicy,
|
||||
Sepolicy {
|
||||
#[command(subcommand)]
|
||||
command: Sepolicy,
|
||||
},
|
||||
|
||||
/// For developers
|
||||
Debug {
|
||||
@@ -68,6 +76,27 @@ enum Debug {
|
||||
Test,
|
||||
}
|
||||
|
||||
#[derive(clap::Subcommand, Debug)]
|
||||
enum Sepolicy {
|
||||
/// Patch sepolicy
|
||||
Patch {
|
||||
/// sepolicy statements
|
||||
sepolicy: String,
|
||||
},
|
||||
|
||||
/// Apply sepolicy from file
|
||||
Apply {
|
||||
/// sepolicy file path
|
||||
file: String,
|
||||
},
|
||||
|
||||
/// Check if sepolicy statement is supported/valid
|
||||
Check {
|
||||
/// sepolicy statements
|
||||
sepolicy: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(clap::Subcommand, Debug)]
|
||||
enum Module {
|
||||
/// Install module <ZIP>
|
||||
@@ -99,26 +128,45 @@ enum Module {
|
||||
}
|
||||
|
||||
pub fn run() -> Result<()> {
|
||||
#[cfg(target_os = "android")]
|
||||
android_logger::init_once(
|
||||
Config::default()
|
||||
.with_max_level(LevelFilter::Trace) // limit log level
|
||||
.with_tag("KernelSU"), // logs will show under mytag tag
|
||||
);
|
||||
|
||||
#[cfg(not(target_os = "android"))]
|
||||
env_logger::init();
|
||||
|
||||
let cli = Args::parse();
|
||||
|
||||
log::info!("command: {:?}", cli.command);
|
||||
|
||||
let result = match cli.command {
|
||||
Commands::Daemon => event::daemon(),
|
||||
Commands::PostFsData => event::on_post_data_fs(),
|
||||
Commands::BootCompleted => event::on_boot_completed(),
|
||||
|
||||
Commands::Module { command } => {
|
||||
env_logger::init();
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
{
|
||||
utils::switch_mnt_ns(1)?;
|
||||
utils::unshare_mnt_ns()?;
|
||||
}
|
||||
match command {
|
||||
Module::Install { zip } => module::install_module(zip),
|
||||
Module::Uninstall { id } => module::uninstall_module(id),
|
||||
Module::Enable { id } => module::enable_module(id),
|
||||
Module::Disable { id } => module::disable_module(id),
|
||||
Module::Install { zip } => module::install_module(&zip),
|
||||
Module::Uninstall { id } => module::uninstall_module(&id),
|
||||
Module::Enable { id } => module::enable_module(&id),
|
||||
Module::Disable { id } => module::disable_module(&id),
|
||||
Module::List => module::list_modules(),
|
||||
}
|
||||
}
|
||||
Commands::Install => event::install(),
|
||||
Commands::Sepolicy => todo!(),
|
||||
Commands::Sepolicy { command } => match command {
|
||||
Sepolicy::Patch { sepolicy } => crate::sepolicy::live_patch(&sepolicy),
|
||||
Sepolicy::Apply { file } => crate::sepolicy::apply_file(file),
|
||||
Sepolicy::Check { sepolicy } => crate::sepolicy::check_rule(&sepolicy),
|
||||
},
|
||||
Commands::Services => event::on_services(),
|
||||
|
||||
Commands::Debug { command } => match command {
|
||||
|
||||
@@ -39,8 +39,8 @@ fn set_kernel_param(size: u32, hash: u32) -> Result<()> {
|
||||
}
|
||||
|
||||
fn get_apk_path(package_name: &str) -> Result<String> {
|
||||
let cmd = format!("pm path {}", package_name);
|
||||
let output = Command::new("sh").arg("-c").arg(cmd).output()?;
|
||||
// `cmd package path` is not available below Android 9
|
||||
let output = Command::new("pm").args(["path", package_name]).output()?;
|
||||
|
||||
// package:/data/app/<xxxx>/base.apk
|
||||
let output = String::from_utf8_lossy(&output.stdout);
|
||||
@@ -54,8 +54,8 @@ pub fn set_manager(pkg: &str) -> Result<()> {
|
||||
"CONFIG_KSU_DEBUG is not enabled"
|
||||
);
|
||||
|
||||
let path = get_apk_path(pkg).with_context(|| format!("{} not exist!", pkg))?;
|
||||
let sign = get_apk_signature(path.as_str())?;
|
||||
let path = get_apk_path(pkg).with_context(|| format!("{pkg} does not exist!"))?;
|
||||
let sign = get_apk_signature(&path)?;
|
||||
set_kernel_param(sign.0, sign.1)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
use const_format::concatcp;
|
||||
|
||||
pub const DAEMON_PATH: &str = "/data/adb/ksud";
|
||||
pub const WORKING_DIR: &str = "/data/adb/ksu/";
|
||||
pub const ADB_DIR: &str = "/data/adb/";
|
||||
|
||||
pub const MODULE_DIR: &str = concatcp!(WORKING_DIR, "modules/");
|
||||
pub const DAEMON_PATH: &str = concatcp!(ADB_DIR, "ksud");
|
||||
|
||||
pub const WORKING_DIR: &str = concatcp!(ADB_DIR, "ksu/");
|
||||
pub const BINARY_DIR: &str = concatcp!(WORKING_DIR, "bin/");
|
||||
|
||||
pub const MODULE_DIR: &str = concatcp!(ADB_DIR, "modules/");
|
||||
pub const MODULE_IMG: &str = concatcp!(WORKING_DIR, "modules.img");
|
||||
pub const MODULE_UPDATE_IMG: &str = concatcp!(WORKING_DIR, "modules_update.img");
|
||||
|
||||
@@ -15,3 +19,6 @@ pub const MODULE_UPDATE_TMP_DIR: &str = concatcp!(WORKING_DIR, "modules_update/"
|
||||
pub const DISABLE_FILE_NAME: &str = "disable";
|
||||
pub const UPDATE_FILE_NAME: &str = "update";
|
||||
pub const REMOVE_FILE_NAME: &str = "remove";
|
||||
|
||||
pub const VERSION_CODE: &str = include_str!(concat!(env!("OUT_DIR"), "/VERSION_CODE"));
|
||||
pub const VERSION_NAME: &str = include_str!(concat!(env!("OUT_DIR"), "/VERSION_NAME"));
|
||||
|
||||
@@ -1,39 +1,59 @@
|
||||
use anyhow::{bail, Context, Result};
|
||||
use log::{info, warn};
|
||||
use std::{collections::HashMap, path::Path};
|
||||
|
||||
use crate::{
|
||||
defs,
|
||||
utils::{ensure_clean_dir, mount_image},
|
||||
assets, defs, mount,
|
||||
utils::{self, ensure_clean_dir, ensure_dir_exists},
|
||||
};
|
||||
use anyhow::{bail, Result};
|
||||
use subprocess::Exec;
|
||||
|
||||
fn mount_partition(partition: &str, lowerdir: &mut Vec<String>) {
|
||||
fn mount_partition(partition: &str, lowerdir: &mut Vec<String>) -> Result<()> {
|
||||
if lowerdir.is_empty() {
|
||||
println!("partition: {} lowerdir is empty", partition);
|
||||
return;
|
||||
warn!("partition: {partition} lowerdir is empty");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// if /partition is a symlink and linked to /system/partition, then we don't need to overlay it separately
|
||||
if Path::new(&format!("/{partition}")).read_link().is_ok() {
|
||||
warn!("partition: {partition} is a symlink");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// handle stock mounts under /partition, we should restore the mount point after overlay
|
||||
let stock_mount = mount::StockMount::new(&format!("/{partition}/"))
|
||||
.with_context(|| format!("get stock mount of partition: {partition} failed"))?;
|
||||
let result = stock_mount.umount();
|
||||
if result.is_err() {
|
||||
let remount_result = stock_mount.remount();
|
||||
if let Err(e) = remount_result {
|
||||
log::error!("remount stock failed: {:?}", e);
|
||||
}
|
||||
bail!("umount stock mount of failed: {:?}", result);
|
||||
}
|
||||
|
||||
// add /partition as the lowerest dir
|
||||
let lowest_dir = format!("/{}", partition);
|
||||
let lowest_dir = format!("/{partition}");
|
||||
lowerdir.push(lowest_dir.clone());
|
||||
|
||||
let lowerdir = lowerdir.join(":");
|
||||
println!("partition: {} lowerdir: {}", partition, lowerdir);
|
||||
info!("partition: {partition} lowerdir: {lowerdir}");
|
||||
|
||||
let mount_args = format!(
|
||||
"mount -t overlay overlay -o ro,lowerdir={} {}",
|
||||
lowerdir, lowest_dir
|
||||
);
|
||||
if let Ok(result) = Exec::shell(mount_args).join() {
|
||||
if !result.success() {
|
||||
println!("mount partition: {} overlay failed", partition);
|
||||
let result = mount::mount_overlay(&lowerdir, &lowest_dir);
|
||||
|
||||
if let Err(e) = stock_mount.remount() {
|
||||
if result.is_ok() {
|
||||
// if mount overlay ok but stock remount failed, we should umount overlay
|
||||
warn!("remount stock failed: {:?}, umount overlay {lowest_dir}", e);
|
||||
if mount::umount_dir(&lowest_dir).is_err() {
|
||||
warn!("umount overlay {lowest_dir} failed");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
println!("mount partition: {} overlay failed", partition);
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
pub fn do_systemless_mount(module_dir: &str) -> Result<()> {
|
||||
pub fn mount_systemlessly(module_dir: &str) -> Result<()> {
|
||||
// construct overlay mount params
|
||||
let dir = std::fs::read_dir(module_dir);
|
||||
let Ok(dir) = dir else {
|
||||
@@ -45,7 +65,7 @@ pub fn do_systemless_mount(module_dir: &str) -> Result<()> {
|
||||
let partition = vec!["vendor", "product", "system_ext", "odm", "oem"];
|
||||
let mut partition_lowerdir: HashMap<String, Vec<String>> = HashMap::new();
|
||||
for ele in &partition {
|
||||
partition_lowerdir.insert(ele.to_string(), Vec::new());
|
||||
partition_lowerdir.insert((*ele).to_string(), Vec::new());
|
||||
}
|
||||
|
||||
for entry in dir.flatten() {
|
||||
@@ -55,19 +75,19 @@ pub fn do_systemless_mount(module_dir: &str) -> Result<()> {
|
||||
}
|
||||
let disabled = module.join(defs::DISABLE_FILE_NAME).exists();
|
||||
if disabled {
|
||||
println!("module: {} is disabled, ignore!", module.display());
|
||||
info!("module: {} is disabled, ignore!", module.display());
|
||||
continue;
|
||||
}
|
||||
|
||||
let module_system = Path::new(&module).join("system");
|
||||
if !module_system.as_path().exists() {
|
||||
println!("module: {} has no system overlay.", module.display());
|
||||
continue;
|
||||
if module_system.exists() {
|
||||
system_lowerdir.push(format!("{}", module_system.display()));
|
||||
}
|
||||
system_lowerdir.push(format!("{}", module_system.display()));
|
||||
|
||||
for part in &partition {
|
||||
let part_path = Path::new(&module_system).join(part);
|
||||
// if /partition is a mountpoint, we would move it to $MODPATH/$partition when install
|
||||
// otherwise it must be a symlink and we don't need to overlay!
|
||||
let part_path = Path::new(&module).join(part);
|
||||
if !part_path.exists() {
|
||||
continue;
|
||||
}
|
||||
@@ -78,11 +98,15 @@ pub fn do_systemless_mount(module_dir: &str) -> Result<()> {
|
||||
}
|
||||
|
||||
// mount /system first
|
||||
mount_partition("system", &mut system_lowerdir);
|
||||
if let Err(e) = mount_partition("system", &mut system_lowerdir) {
|
||||
warn!("mount system failed: {e}");
|
||||
}
|
||||
|
||||
// mount other partitions
|
||||
for (k, mut v) in partition_lowerdir {
|
||||
mount_partition(&k, &mut v);
|
||||
if let Err(e) = mount_partition(&k, &mut v) {
|
||||
warn!("mount {k} failed: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -90,6 +114,14 @@ pub fn do_systemless_mount(module_dir: &str) -> Result<()> {
|
||||
|
||||
pub fn on_post_data_fs() -> Result<()> {
|
||||
crate::ksu::report_post_fs_data();
|
||||
|
||||
if utils::has_magisk() {
|
||||
warn!("Magisk detected, skip post-fs-data!");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
utils::umask(0);
|
||||
|
||||
let module_update_img = defs::MODULE_UPDATE_IMG;
|
||||
let module_img = defs::MODULE_IMG;
|
||||
let module_dir = defs::MODULE_DIR;
|
||||
@@ -101,6 +133,8 @@ pub fn on_post_data_fs() -> Result<()> {
|
||||
// we should clean the module mount point if it exists
|
||||
ensure_clean_dir(module_dir)?;
|
||||
|
||||
assets::ensure_binaries().with_context(|| "Failed to extract bin assets")?;
|
||||
|
||||
if Path::new(module_update_img).exists() {
|
||||
if module_update_flag.exists() {
|
||||
// if modules_update.img exists, and the the flag indicate this is an update
|
||||
@@ -115,37 +149,80 @@ pub fn on_post_data_fs() -> Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
// If there isn't any image exist, do nothing for module!
|
||||
if !Path::new(target_update_img).exists() {
|
||||
// no image exist, do nothing for module!
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
println!("mount {} to {}", target_update_img, module_dir);
|
||||
mount_image(target_update_img, module_dir)?;
|
||||
// we should always mount the module.img to module dir
|
||||
// becuase we may need to operate the module dir in safe mode
|
||||
info!("mount module image: {target_update_img} to {module_dir}");
|
||||
mount::AutoMountExt4::try_new(target_update_img, module_dir, false)
|
||||
.with_context(|| "mount module image failed".to_string())?;
|
||||
|
||||
// mount systemless overlay
|
||||
if let Err(e) = do_systemless_mount(module_dir) {
|
||||
println!("do systemless mount failed: {}", e);
|
||||
// check safe mode first.
|
||||
if crate::utils::is_safe_mode() {
|
||||
warn!("safe mode, skip post-fs-data scripts and disable all modules!");
|
||||
if let Err(e) = crate::module::disable_all_modules() {
|
||||
warn!("disable all modules failed: {}", e);
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// module mounted, exec modules post-fs-data scripts
|
||||
if !crate::utils::is_safe_mode().unwrap_or(false) {
|
||||
// todo: Add timeout
|
||||
let _ = crate::module::exec_post_fs_data();
|
||||
let _ = crate::module::load_system_prop();
|
||||
} else {
|
||||
println!("safe mode, skip module post-fs-data scripts");
|
||||
// Then exec common post-fs-data scripts
|
||||
if let Err(e) = crate::module::exec_common_scripts("post-fs-data.d", true) {
|
||||
warn!("exec common post-fs-data scripts failed: {}", e);
|
||||
}
|
||||
|
||||
// load sepolicy.rule
|
||||
if crate::module::load_sepolicy_rule().is_err() {
|
||||
warn!("load sepolicy.rule failed");
|
||||
}
|
||||
|
||||
// exec modules post-fs-data scripts
|
||||
// TODO: Add timeout
|
||||
if let Err(e) = crate::module::exec_post_fs_data() {
|
||||
warn!("exec post-fs-data scripts failed: {}", e);
|
||||
}
|
||||
|
||||
// load system.prop
|
||||
if let Err(e) = crate::module::load_system_prop() {
|
||||
warn!("load system.prop failed: {}", e);
|
||||
}
|
||||
|
||||
// Finally, we should do systemless mount
|
||||
// But we should umount all stock overlayfs and remount them after module mounted
|
||||
let stock_overlay = mount::StockOverlay::new();
|
||||
stock_overlay.umount_all();
|
||||
|
||||
// mount moduke systemlessly by overlay
|
||||
if let Err(e) = mount_systemlessly(module_dir) {
|
||||
warn!("do systemless mount failed: {}", e);
|
||||
}
|
||||
|
||||
stock_overlay.mount_all();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn on_services() -> Result<()> {
|
||||
// exec modules service.sh scripts
|
||||
if !crate::utils::is_safe_mode().unwrap_or(false) {
|
||||
let _ = crate::module::exec_services();
|
||||
} else {
|
||||
println!("safe mode, skip module service scripts");
|
||||
utils::umask(0);
|
||||
|
||||
if utils::has_magisk() {
|
||||
warn!("Magisk detected, skip services!");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if crate::utils::is_safe_mode() {
|
||||
warn!("safe mode, skip module service scripts");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if let Err(e) = crate::module::exec_common_scripts("service.d", false) {
|
||||
warn!("Failed to exec common service scripts: {}", e);
|
||||
}
|
||||
if let Err(e) = crate::module::exec_services() {
|
||||
warn!("Failed to exec service scripts: {}", e);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -153,11 +230,17 @@ pub fn on_services() -> Result<()> {
|
||||
|
||||
pub fn on_boot_completed() -> Result<()> {
|
||||
crate::ksu::report_boot_complete();
|
||||
info!("on_boot_completed triggered!");
|
||||
let module_update_img = Path::new(defs::MODULE_UPDATE_IMG);
|
||||
let module_img = Path::new(defs::MODULE_IMG);
|
||||
if module_update_img.exists() {
|
||||
// this is a update and we successfully booted
|
||||
std::fs::rename(module_update_img, module_img)?;
|
||||
if std::fs::rename(module_update_img, module_img).is_err() {
|
||||
warn!("Failed to rename images, copy it now.",);
|
||||
std::fs::copy(module_update_img, module_img)
|
||||
.with_context(|| "Failed to copy images")?;
|
||||
std::fs::remove_file(module_update_img).with_context(|| "Failed to remove image!")?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -167,9 +250,9 @@ pub fn daemon() -> Result<()> {
|
||||
}
|
||||
|
||||
pub fn install() -> Result<()> {
|
||||
let src = "/proc/self/exe";
|
||||
let dst = defs::DAEMON_PATH;
|
||||
ensure_dir_exists(defs::ADB_DIR)?;
|
||||
std::fs::copy("/proc/self/exe", defs::DAEMON_PATH)?;
|
||||
|
||||
std::fs::copy(src, dst)?;
|
||||
Ok(())
|
||||
// install binary assets
|
||||
assets::ensure_binaries().with_context(|| "Failed to extract assets")
|
||||
}
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
#!/system/bin/sh
|
||||
############################################
|
||||
# KernelSU installer script
|
||||
# Credit to Magisk!!!
|
||||
# mostly from module_installer.sh
|
||||
# and util_functions.sh in Magisk
|
||||
############################################
|
||||
|
||||
umask 022
|
||||
|
||||
ui_print() {
|
||||
if $BOOTMODE; then
|
||||
echo "$1"
|
||||
@@ -67,6 +71,11 @@ print_title() {
|
||||
ui_print "$bar"
|
||||
}
|
||||
|
||||
check_sepolicy() {
|
||||
/data/adb/ksud sepolicy check "$1"
|
||||
return $?
|
||||
}
|
||||
|
||||
######################
|
||||
# Environment Related
|
||||
######################
|
||||
@@ -89,14 +98,15 @@ setup_flashable() {
|
||||
}
|
||||
|
||||
ensure_bb() {
|
||||
:
|
||||
}
|
||||
|
||||
recovery_actions() {
|
||||
|
||||
:
|
||||
}
|
||||
|
||||
recovery_cleanup() {
|
||||
|
||||
:
|
||||
}
|
||||
|
||||
#######################
|
||||
@@ -261,12 +271,20 @@ mktouch() {
|
||||
chmod 644 $1
|
||||
}
|
||||
|
||||
request_size_check() {
|
||||
reqSizeM=`du -ms "$1" | cut -f1`
|
||||
mark_remove() {
|
||||
mkdir -p ${1%/*} 2>/dev/null
|
||||
mknod $1 c 0 0
|
||||
chmod 644 $1
|
||||
}
|
||||
|
||||
unzip() {
|
||||
/system/bin/unzip -q "$@"
|
||||
mark_replace() {
|
||||
mkdir -p ${1%/*} 2>/dev/null
|
||||
setfattr -n trusted.overlay.opaque -v y $1
|
||||
chmod 644 $1
|
||||
}
|
||||
|
||||
request_size_check() {
|
||||
reqSizeM=`du -ms "$1" | cut -f1`
|
||||
}
|
||||
|
||||
request_zip_size_check() {
|
||||
@@ -281,6 +299,22 @@ is_legacy_script() {
|
||||
return $?
|
||||
}
|
||||
|
||||
handle_partition() {
|
||||
# if /system/vendor is a symlink, we need to move it out of $MODPATH/system, otherwise it will be overlayed
|
||||
# if /system/vendor is a normal directory, it is ok to overlay it and we don't need to overlay it separately.
|
||||
if [ ! -e $MODPATH/system/$1 ]; then
|
||||
# no partition found
|
||||
return;
|
||||
fi
|
||||
|
||||
if [ -L "/system/$1" ] && [ "$(readlink -f /system/$1)" = "/$1" ]; then
|
||||
ui_print "- Handle partition /$1"
|
||||
# we create a symlink if module want to access $MODPATH/system/$1
|
||||
# but it doesn't always work(ie. write it in post-fs-data.sh would fail because it is readonly)
|
||||
mv -f $MODPATH/system/$1 $MODPATH/$1 && ln -sf /$1 $MODPATH/system/$1
|
||||
fi
|
||||
}
|
||||
|
||||
# Require OUTFD, ZIPFILE to be set
|
||||
install_module() {
|
||||
rm -rf $TMPDIR
|
||||
@@ -348,17 +382,27 @@ install_module() {
|
||||
set_perm_recursive $MODPATH/system/bin 0 2000 0755 0755
|
||||
set_perm_recursive $MODPATH/system/xbin 0 2000 0755 0755
|
||||
set_perm_recursive $MODPATH/system/system_ext/bin 0 2000 0755 0755
|
||||
set_perm_recursive $MODPATH/system/vendor/bin 0 2000 0755 0755 u:object_r:vendor_file:s0
|
||||
set_perm_recursive $MODPATH/system/vendor 0 2000 0755 0755 u:object_r:vendor_file:s0
|
||||
fi
|
||||
|
||||
# Load customization script
|
||||
[ -f $MODPATH/customize.sh ] && . $MODPATH/customize.sh
|
||||
fi
|
||||
|
||||
handle_partition vendor
|
||||
handle_partition system_ext
|
||||
handle_partition product
|
||||
|
||||
# Handle replace folders
|
||||
for TARGET in $REPLACE; do
|
||||
ui_print "- Replace target: $TARGET"
|
||||
mktouch $MODPATH$TARGET/.replace
|
||||
mark_replace $MODPATH$TARGET
|
||||
done
|
||||
|
||||
# Handle remove files
|
||||
for TARGET in $REMOVE; do
|
||||
ui_print "- Remove target: $TARGET"
|
||||
mark_remove $MODPATH$TARGET
|
||||
done
|
||||
|
||||
if $BOOTMODE; then
|
||||
@@ -394,8 +438,5 @@ NVBASE=/data/adb/ksu
|
||||
TMPDIR=/dev/tmp
|
||||
|
||||
# Some modules dependents on this
|
||||
MAGISK_VER=25.2
|
||||
MAGISK_VER_CODE=25200
|
||||
|
||||
# KSU to recognize
|
||||
KSU=true
|
||||
export MAGISK_VER=25.2
|
||||
export MAGISK_VER_CODE=25200
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
#![allow(dead_code, unused_mut, unused_variables, unused_imports)]
|
||||
use anyhow::Result;
|
||||
|
||||
#[cfg(unix)]
|
||||
use anyhow::ensure;
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::process::CommandExt;
|
||||
use anyhow::{Result, ensure};
|
||||
|
||||
const KERNEL_SU_OPTION: u32 = 0xDEADBEEF;
|
||||
pub const KERNEL_SU_OPTION: u32 = 0xDEAD_BEEF;
|
||||
|
||||
const CMD_GRANT_ROOT: u64 = 0;
|
||||
// const CMD_BECOME_MANAGER: u64 = 1;
|
||||
@@ -13,61 +15,81 @@ const CMD_GET_VERSION: u64 = 2;
|
||||
// const CMD_GET_ALLOW_LIST: u64 = 5;
|
||||
// const CMD_GET_DENY_LIST: u64 = 6;
|
||||
const CMD_REPORT_EVENT: u64 = 7;
|
||||
pub const CMD_SET_SEPOLICY: u64 = 8;
|
||||
pub const CMD_CHECK_SAFEMODE: u64 = 9;
|
||||
|
||||
const EVENT_POST_FS_DATA: u64 = 1;
|
||||
const EVENT_BOOT_COMPLETED: u64 = 2;
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
pub fn grant_root() -> Result<()> {
|
||||
let mut result: u32 = 0;
|
||||
unsafe {
|
||||
#[allow(clippy::cast_possible_wrap)]
|
||||
libc::prctl(
|
||||
KERNEL_SU_OPTION as i32,
|
||||
KERNEL_SU_OPTION as i32, // supposed to overflow
|
||||
CMD_GRANT_ROOT,
|
||||
0,
|
||||
0,
|
||||
&mut result as *mut _ as *mut libc::c_void,
|
||||
std::ptr::addr_of_mut!(result).cast::<libc::c_void>(),
|
||||
);
|
||||
}
|
||||
|
||||
ensure!(result == KERNEL_SU_OPTION, "grant root failed");
|
||||
std::process::Command::new("/system/bin/sh").exec();
|
||||
Ok(())
|
||||
Err(std::process::Command::new("sh").exec().into())
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "android"))]
|
||||
#[cfg(not(any(target_os = "linux", target_os = "android")))]
|
||||
pub fn grant_root() -> Result<()> {
|
||||
unimplemented!("grant_root is only available on android");
|
||||
}
|
||||
|
||||
pub fn get_version() -> i32 {
|
||||
let mut result: i32 = 0;
|
||||
#[cfg(target_os = "android")]
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
unsafe {
|
||||
#[allow(clippy::cast_possible_wrap)]
|
||||
libc::prctl(
|
||||
KERNEL_SU_OPTION as i32,
|
||||
KERNEL_SU_OPTION as i32, // supposed to overflow
|
||||
CMD_GET_VERSION,
|
||||
&mut result as *mut _ as *mut libc::c_void,
|
||||
std::ptr::addr_of_mut!(result).cast::<libc::c_void>(),
|
||||
);
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
fn report_event(event: u64) {
|
||||
#[cfg(target_os = "android")]
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
unsafe {
|
||||
#[allow(clippy::cast_possible_wrap)]
|
||||
libc::prctl(
|
||||
KERNEL_SU_OPTION as i32,
|
||||
KERNEL_SU_OPTION as i32, // supposed to overflow
|
||||
CMD_REPORT_EVENT,
|
||||
event,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_kernel_safemode() -> bool {
|
||||
let mut result: i32 = 0;
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
unsafe {
|
||||
#[allow(clippy::cast_possible_wrap)]
|
||||
libc::prctl(
|
||||
KERNEL_SU_OPTION as i32, // supposed to overflow
|
||||
CMD_CHECK_SAFEMODE,
|
||||
0,
|
||||
0,
|
||||
std::ptr::addr_of_mut!(result).cast::<libc::c_void>(),
|
||||
);
|
||||
}
|
||||
result == KERNEL_SU_OPTION as i32
|
||||
}
|
||||
|
||||
pub fn report_post_fs_data() {
|
||||
report_event(EVENT_POST_FS_DATA);
|
||||
}
|
||||
|
||||
pub fn report_boot_complete() {
|
||||
report_event(EVENT_BOOT_COMPLETED);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
mod apk_sign;
|
||||
mod assets;
|
||||
mod cli;
|
||||
mod debug;
|
||||
mod defs;
|
||||
mod event;
|
||||
mod ksu;
|
||||
mod module;
|
||||
mod mount;
|
||||
mod restorecon;
|
||||
mod sepolicy;
|
||||
mod utils;
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
|
||||
@@ -1,38 +1,64 @@
|
||||
#[allow(clippy::wildcard_imports)]
|
||||
use crate::utils::*;
|
||||
use crate::{
|
||||
assets, defs, mount,
|
||||
restorecon::{restore_syscon, setsyscon},
|
||||
sepolicy,
|
||||
};
|
||||
|
||||
use anyhow::{anyhow, bail, ensure, Context, Result};
|
||||
use const_format::concatcp;
|
||||
use is_executable::is_executable;
|
||||
use java_properties::PropertiesIter;
|
||||
use log::{info, warn};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fs::{create_dir_all, remove_dir_all, File, OpenOptions},
|
||||
env::var as env_var,
|
||||
fs::{remove_dir_all, set_permissions, File, OpenOptions, Permissions},
|
||||
io::{Cursor, Write},
|
||||
os::unix::{prelude::PermissionsExt, process::CommandExt},
|
||||
path::{Path, PathBuf},
|
||||
process::{Command, Stdio},
|
||||
str::FromStr,
|
||||
};
|
||||
use subprocess::Exec;
|
||||
use zip_extensions::*;
|
||||
use zip_extensions::zip_extract_file_to_memory;
|
||||
|
||||
use crate::{defs, restorecon};
|
||||
use crate::{restorecon::setsyscon, utils::*};
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::{prelude::PermissionsExt, process::CommandExt};
|
||||
|
||||
use anyhow::{bail, ensure, Context, Result};
|
||||
|
||||
const UTIL_FUNCTIONS: &str = include_str!("./installer.sh");
|
||||
const INSTALL_MODULE_SCRIPT: &str =
|
||||
concatcp!(UTIL_FUNCTIONS, "\n", "install_module", "\n", "exit 0", "\n");
|
||||
const INSTALLER_CONTENT: &str = include_str!("./installer.sh");
|
||||
const INSTALL_MODULE_SCRIPT: &str = concatcp!(
|
||||
INSTALLER_CONTENT,
|
||||
"\n",
|
||||
"install_module",
|
||||
"\n",
|
||||
"exit 0",
|
||||
"\n"
|
||||
);
|
||||
|
||||
fn exec_install_script(module_file: &str) -> Result<()> {
|
||||
let realpath = std::fs::canonicalize(module_file)
|
||||
.with_context(|| format!("realpath: {} failed", module_file))?;
|
||||
.with_context(|| format!("realpath: {module_file} failed"))?;
|
||||
|
||||
let result = Command::new("/system/bin/sh")
|
||||
.args(["-c", INSTALL_MODULE_SCRIPT])
|
||||
let result = Command::new(assets::BUSYBOX_PATH)
|
||||
.args(["sh", "-c", INSTALL_MODULE_SCRIPT])
|
||||
.env("ASH_STANDALONE", "1")
|
||||
.env(
|
||||
"PATH",
|
||||
format!(
|
||||
"{}:{}",
|
||||
env_var("PATH").unwrap(),
|
||||
defs::BINARY_DIR.trim_end_matches('/')
|
||||
),
|
||||
)
|
||||
.env("KSU", "true")
|
||||
.env("KSU_KERNEL_VER_CODE", crate::ksu::get_version().to_string())
|
||||
.env("KSU_VER", defs::VERSION_NAME)
|
||||
.env("KSU_VER_CODE", defs::VERSION_CODE)
|
||||
.env("OUTFD", "1")
|
||||
.env("ZIPFILE", realpath)
|
||||
.stderr(Stdio::null())
|
||||
.status()?;
|
||||
ensure!(result.success(), "install module script failed!");
|
||||
ensure!(result.success(), "Failed to install module script");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -41,46 +67,32 @@ fn exec_install_script(module_file: &str) -> Result<()> {
|
||||
// if someone(such as the module) install a module before the boot_completed
|
||||
// then it may cause some problems, just forbid it
|
||||
fn ensure_boot_completed() -> Result<()> {
|
||||
// ensure getprop sys.boot_completed = 1
|
||||
let output = Command::new("getprop")
|
||||
.arg("sys.boot_completed")
|
||||
.stdout(Stdio::piped())
|
||||
.output()?;
|
||||
let output = String::from_utf8_lossy(&output.stdout);
|
||||
if output.trim() != "1" {
|
||||
// ensure getprop sys.boot_completed == 1
|
||||
if getprop("sys.boot_completed").as_deref() != Some("1") {
|
||||
bail!("Android is Booting!");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn mark_update() -> Result<()> {
|
||||
let update_file = Path::new(defs::WORKING_DIR).join(defs::UPDATE_FILE_NAME);
|
||||
if update_file.exists() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
std::fs::File::create(update_file)?;
|
||||
Ok(())
|
||||
ensure_file_exists(concatcp!(defs::WORKING_DIR, defs::UPDATE_FILE_NAME))
|
||||
}
|
||||
|
||||
fn mark_module_state(module: &str, flag_file: &str, create_or_delete: bool) -> Result<()> {
|
||||
let module_state_file = Path::new(defs::MODULE_DIR).join(module).join(flag_file);
|
||||
if create_or_delete {
|
||||
if module_state_file.exists() {
|
||||
return Ok(());
|
||||
}
|
||||
std::fs::File::create(module_state_file)?;
|
||||
ensure_file_exists(module_state_file)
|
||||
} else {
|
||||
if !module_state_file.exists() {
|
||||
return Ok(());
|
||||
if module_state_file.exists() {
|
||||
std::fs::remove_file(module_state_file)?;
|
||||
}
|
||||
std::fs::remove_file(module_state_file)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_minimal_image_size(img: &str) -> Result<u64> {
|
||||
check_image(img)?;
|
||||
|
||||
let output = Command::new("resize2fs")
|
||||
.args(["-P", img])
|
||||
.stdout(Stdio::piped())
|
||||
@@ -90,8 +102,8 @@ fn get_minimal_image_size(img: &str) -> Result<u64> {
|
||||
println!("- {}", output.trim());
|
||||
let regex = regex::Regex::new(r"filesystem: (\d+)")?;
|
||||
let result = regex
|
||||
.captures(output.as_ref())
|
||||
.ok_or_else(|| anyhow::anyhow!("regex not match"))?;
|
||||
.captures(&output)
|
||||
.ok_or(anyhow::anyhow!("regex not match"))?;
|
||||
let result = &result[1];
|
||||
let result = u64::from_str(result)?;
|
||||
Ok(result)
|
||||
@@ -103,7 +115,7 @@ fn check_image(img: &str) -> Result<()> {
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null())
|
||||
.status()
|
||||
.with_context(|| format!("Failed exec e2fsck {}", img))?;
|
||||
.with_context(|| format!("Failed to exec e2fsck {img}"))?;
|
||||
let code = result.code();
|
||||
// 0 or 1 is ok
|
||||
// 0: no error
|
||||
@@ -111,15 +123,15 @@ fn check_image(img: &str) -> Result<()> {
|
||||
// https://man7.org/linux/man-pages/man8/e2fsck.8.html
|
||||
ensure!(
|
||||
code == Some(0) || code == Some(1),
|
||||
"check image e2fsck exec failed: {}",
|
||||
"Failed to check image, e2fsck exit code: {}",
|
||||
code.unwrap_or(-1)
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn grow_image_size(img: &str, extra_size: u64) -> Result<()> {
|
||||
let minimal_size = get_minimal_image_size(img)?;
|
||||
let target_size = minimal_size + extra_size;
|
||||
let minimal_size = get_minimal_image_size(img)?; // the minimal size is in KB
|
||||
let target_size = minimal_size * 1024 + extra_size;
|
||||
|
||||
// check image
|
||||
check_image(img)?;
|
||||
@@ -128,14 +140,16 @@ fn grow_image_size(img: &str, extra_size: u64) -> Result<()> {
|
||||
"- Target image size: {}",
|
||||
humansize::format_size(target_size, humansize::DECIMAL)
|
||||
);
|
||||
let target_size = target_size / 1024 + 1;
|
||||
let target_size = target_size / 1024 + 1024;
|
||||
|
||||
let result = Exec::shell(format!("resize2fs {} {}K", img, target_size))
|
||||
.stdout(subprocess::NullFile)
|
||||
.stderr(subprocess::Redirection::Merge)
|
||||
.join()
|
||||
.with_context(|| format!("Failed to resize2fs {}", img))?;
|
||||
ensure!(result.success(), "resize2fs exec failed.");
|
||||
let result = Command::new("resize2fs")
|
||||
.args([img, &format!("{target_size}K")])
|
||||
.stdout(Stdio::null())
|
||||
.status()
|
||||
.with_context(|| format!("Failed to exec resize2fs {img}"))?;
|
||||
ensure!(result.success(), "Failed to resize2fs: {}", result);
|
||||
|
||||
check_image(img)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -148,22 +162,90 @@ fn switch_cgroup(grp: &str, pid: u32) {
|
||||
|
||||
let fp = OpenOptions::new().append(true).open(path);
|
||||
if let Ok(mut fp) = fp {
|
||||
let _ = writeln!(fp, "{}", pid);
|
||||
let _ = writeln!(fp, "{pid}");
|
||||
}
|
||||
}
|
||||
|
||||
fn switch_cgroups() -> Result<()> {
|
||||
fn switch_cgroups() {
|
||||
let pid = std::process::id();
|
||||
switch_cgroup("/acct", pid);
|
||||
switch_cgroup("/dev/cg2_bpf", pid);
|
||||
switch_cgroup("/sys/fs/cgroup", pid);
|
||||
if getprop("ro.config.per_app_memcg")? != "false" {
|
||||
|
||||
if getprop("ro.config.per_app_memcg")
|
||||
.filter(|prop| prop == "false")
|
||||
.is_none()
|
||||
{
|
||||
switch_cgroup("/dev/memcg/apps", pid);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_sepolicy_rule() -> Result<()> {
|
||||
let modules_dir = Path::new(defs::MODULE_DIR);
|
||||
let dir = std::fs::read_dir(modules_dir)?;
|
||||
for entry in dir.flatten() {
|
||||
let path = entry.path();
|
||||
let disabled = path.join(defs::DISABLE_FILE_NAME);
|
||||
if disabled.exists() {
|
||||
info!("{} is disabled, skip", path.display());
|
||||
continue;
|
||||
}
|
||||
|
||||
let rule_file = path.join("sepolicy.rule");
|
||||
if !rule_file.exists() {
|
||||
continue;
|
||||
}
|
||||
info!("load policy: {}", &rule_file.display());
|
||||
|
||||
if sepolicy::apply_file(&rule_file).is_err() {
|
||||
warn!("Failed to load sepolicy.rule for {}", &rule_file.display());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn exec_script<T: AsRef<Path>>(path: T, wait: bool) -> Result<()> {
|
||||
info!("exec {}", path.as_ref().display());
|
||||
|
||||
let mut command = &mut Command::new(assets::BUSYBOX_PATH);
|
||||
#[cfg(unix)]
|
||||
{
|
||||
command = command.process_group(0);
|
||||
command = unsafe {
|
||||
command.pre_exec(|| {
|
||||
// ignore the error?
|
||||
switch_cgroups();
|
||||
Ok(())
|
||||
})
|
||||
};
|
||||
}
|
||||
command = command
|
||||
.current_dir(path.as_ref().parent().unwrap())
|
||||
.arg("sh")
|
||||
.arg(path.as_ref())
|
||||
.env("ASH_STANDALONE", "1")
|
||||
.env("KSU", "true")
|
||||
.env("KSU_KERNEL_VER_CODE", crate::ksu::get_version().to_string())
|
||||
.env("KSU_VER_CODE", defs::VERSION_CODE)
|
||||
.env("KSU_VER", defs::VERSION_NAME)
|
||||
.env(
|
||||
"PATH",
|
||||
format!(
|
||||
"{}:{}",
|
||||
env_var("PATH").unwrap(),
|
||||
defs::BINARY_DIR.trim_end_matches('/')
|
||||
),
|
||||
);
|
||||
|
||||
let result = if wait {
|
||||
command.status().map(|_| ())
|
||||
} else {
|
||||
command.spawn().map(|_| ())
|
||||
};
|
||||
result.map_err(|err| anyhow!("Failed to exec {}: {}", path.as_ref().display(), err))
|
||||
}
|
||||
|
||||
/// execute every modules' post-fs-data.sh
|
||||
pub fn exec_post_fs_data() -> Result<()> {
|
||||
let modules_dir = Path::new(defs::MODULE_DIR);
|
||||
@@ -172,7 +254,7 @@ pub fn exec_post_fs_data() -> Result<()> {
|
||||
let path = entry.path();
|
||||
let disabled = path.join(defs::DISABLE_FILE_NAME);
|
||||
if disabled.exists() {
|
||||
println!("{} is disabled, skip", path.display());
|
||||
warn!("{} is disabled, skip", path.display());
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -180,23 +262,30 @@ pub fn exec_post_fs_data() -> Result<()> {
|
||||
if !post_fs_data.exists() {
|
||||
continue;
|
||||
}
|
||||
println!("exec {} post-fs-data.sh", path.display());
|
||||
|
||||
// pre_exec is unsafe!
|
||||
unsafe {
|
||||
Command::new("/system/bin/sh")
|
||||
.arg(&post_fs_data)
|
||||
.process_group(0)
|
||||
.pre_exec(|| {
|
||||
// ignore the error?
|
||||
let _ = switch_cgroups();
|
||||
Ok(())
|
||||
})
|
||||
.current_dir(path)
|
||||
.env("KSU", "true")
|
||||
.status()
|
||||
.with_context(|| format!("Failed to exec {}", post_fs_data.display()))?;
|
||||
exec_script(&post_fs_data, true)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn exec_common_scripts(dir: &str, wait: bool) -> Result<()> {
|
||||
let script_dir = Path::new(defs::ADB_DIR).join(dir);
|
||||
if !script_dir.exists() {
|
||||
info!("{} not exists, skip", script_dir.display());
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let dir = std::fs::read_dir(&script_dir)?;
|
||||
for entry in dir.flatten() {
|
||||
let path = entry.path();
|
||||
|
||||
if !is_executable(&path) {
|
||||
warn!("{} is not executable, skip", path.display());
|
||||
continue;
|
||||
}
|
||||
|
||||
exec_script(path, wait)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -210,7 +299,7 @@ pub fn exec_services() -> Result<()> {
|
||||
let path = entry.path();
|
||||
let disabled = path.join(defs::DISABLE_FILE_NAME);
|
||||
if disabled.exists() {
|
||||
println!("{} is disabled, skip", path.display());
|
||||
warn!("{} is disabled, skip", path.display());
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -218,50 +307,21 @@ pub fn exec_services() -> Result<()> {
|
||||
if !service.exists() {
|
||||
continue;
|
||||
}
|
||||
println!("exec {} service.sh", path.display());
|
||||
|
||||
// pre_exec is unsafe!
|
||||
unsafe {
|
||||
Command::new("/system/bin/sh")
|
||||
.arg(&service)
|
||||
.process_group(0)
|
||||
.pre_exec(|| {
|
||||
// ignore the error?
|
||||
let _ = switch_cgroups();
|
||||
Ok(())
|
||||
})
|
||||
.current_dir(path)
|
||||
.env("KSU", "true")
|
||||
.spawn() // don't wait
|
||||
.with_context(|| format!("Failed to exec {}", service.display()))?;
|
||||
}
|
||||
exec_script(&service, false)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
const RESETPROP: &[u8] = include_bytes!("./resetprop");
|
||||
const RESETPROP_PATH: &str = concatcp!(defs::WORKING_DIR, "/resetprop");
|
||||
|
||||
fn ensure_resetprop() -> Result<()> {
|
||||
if Path::new(RESETPROP_PATH).exists() {
|
||||
return Ok(());
|
||||
}
|
||||
std::fs::write(RESETPROP_PATH, RESETPROP)?;
|
||||
std::fs::set_permissions(RESETPROP_PATH, std::fs::Permissions::from_mode(0o755))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn load_system_prop() -> Result<()> {
|
||||
ensure_resetprop()?;
|
||||
|
||||
let modules_dir = Path::new(defs::MODULE_DIR);
|
||||
let dir = std::fs::read_dir(modules_dir)?;
|
||||
for entry in dir.flatten() {
|
||||
let path = entry.path();
|
||||
let disabled = path.join(defs::DISABLE_FILE_NAME);
|
||||
if disabled.exists() {
|
||||
println!("{} is disabled, skip", path.display());
|
||||
info!("{} is disabled, skip", path.display());
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -269,10 +329,10 @@ pub fn load_system_prop() -> Result<()> {
|
||||
if !system_prop.exists() {
|
||||
continue;
|
||||
}
|
||||
println!("load {} system.prop", path.display());
|
||||
info!("load {} system.prop", path.display());
|
||||
|
||||
// resetprop --file system.prop
|
||||
Command::new(RESETPROP_PATH)
|
||||
// resetprop -n --file system.prop
|
||||
Command::new(assets::RESETPROP_PATH)
|
||||
.arg("-n")
|
||||
.arg("--file")
|
||||
.arg(&system_prop)
|
||||
@@ -283,27 +343,22 @@ pub fn load_system_prop() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn install_module(zip: String) -> Result<()> {
|
||||
fn _install_module(zip: &str) -> Result<()> {
|
||||
ensure_boot_completed()?;
|
||||
|
||||
// print banner
|
||||
println!(include_str!("banner"));
|
||||
|
||||
// first check if workding dir is usable
|
||||
let working_dir = Path::new(defs::WORKING_DIR);
|
||||
if !working_dir.exists() {
|
||||
create_dir_all(working_dir)?;
|
||||
}
|
||||
assets::ensure_binaries().with_context(|| "Failed to extract assets")?;
|
||||
|
||||
ensure!(
|
||||
working_dir.is_dir(),
|
||||
"working dir exists but it is not a regular directory!"
|
||||
);
|
||||
// first check if workding dir is usable
|
||||
ensure_dir_exists(defs::WORKING_DIR).with_context(|| "Failed to create working dir")?;
|
||||
ensure_dir_exists(defs::BINARY_DIR).with_context(|| "Failed to create bin dir")?;
|
||||
|
||||
// read the module_id from zip, if faild if will return early.
|
||||
let mut buffer: Vec<u8> = Vec::new();
|
||||
let entry_path = PathBuf::from_str("module.prop")?;
|
||||
let zip_path = PathBuf::from_str(&zip)?;
|
||||
let zip_path = PathBuf::from_str(zip)?;
|
||||
zip_extract_file_to_memory(&zip_path, &entry_path, &mut buffer)?;
|
||||
|
||||
let mut module_prop = HashMap::new();
|
||||
@@ -312,11 +367,11 @@ pub fn install_module(zip: String) -> Result<()> {
|
||||
module_prop.insert(k, v);
|
||||
},
|
||||
)?;
|
||||
info!("module prop: {:?}", module_prop);
|
||||
|
||||
let Some(module_id) = module_prop.get("id") else {
|
||||
bail!("module id not found in module.prop!");
|
||||
};
|
||||
info!("module id: {}", module_id);
|
||||
|
||||
let modules_img = Path::new(defs::MODULE_IMG);
|
||||
let modules_update_img = Path::new(defs::MODULE_UPDATE_IMG);
|
||||
@@ -333,9 +388,17 @@ pub fn install_module(zip: String) -> Result<()> {
|
||||
}
|
||||
|
||||
let default_reserve_size = 64 * 1024 * 1024;
|
||||
let zip_uncompressed_size = get_zip_uncompressed_size(&zip)?;
|
||||
let zip_uncompressed_size = get_zip_uncompressed_size(zip)?;
|
||||
let grow_size = default_reserve_size + zip_uncompressed_size;
|
||||
let grow_size_per_m = grow_size / 1024 / 1024 + 1;
|
||||
|
||||
info!(
|
||||
"zip uncompressed size: {}",
|
||||
humansize::format_size(zip_uncompressed_size, humansize::DECIMAL)
|
||||
);
|
||||
info!(
|
||||
"grow size: {}",
|
||||
humansize::format_size(grow_size, humansize::DECIMAL)
|
||||
);
|
||||
|
||||
println!("- Preparing image");
|
||||
println!(
|
||||
@@ -346,25 +409,27 @@ pub fn install_module(zip: String) -> Result<()> {
|
||||
if !modules_img_exist && !modules_update_img_exist {
|
||||
// if no modules and modules_update, it is brand new installation, we should create a new img
|
||||
// create a tmp module img and mount it to modules_update
|
||||
let result = Exec::shell(format!(
|
||||
"dd if=/dev/zero of={} bs=1M count={}",
|
||||
tmp_module_img, grow_size_per_m
|
||||
))
|
||||
.stdout(subprocess::NullFile)
|
||||
.stderr(subprocess::Redirection::Merge)
|
||||
.join()?;
|
||||
ensure!(result.success(), "create ext4 image failed!");
|
||||
info!("Creating brand new module image");
|
||||
File::create(tmp_module_img)
|
||||
.context("Failed to create ext4 image file")?
|
||||
.set_len(grow_size)
|
||||
.context("Failed to extend ext4 image")?;
|
||||
|
||||
// format the img to ext4 filesystem
|
||||
let result = Exec::shell(format!("mkfs.ext4 {}", tmp_module_img))
|
||||
.stdout(subprocess::NullFile)
|
||||
.stderr(subprocess::Redirection::Merge)
|
||||
.join()?;
|
||||
ensure!(result.success(), "format ext4 image failed!");
|
||||
let result = Command::new("mkfs.ext4")
|
||||
.arg(tmp_module_img)
|
||||
.stdout(Stdio::null())
|
||||
.output()?;
|
||||
ensure!(
|
||||
result.status.success(),
|
||||
"Failed to format ext4 image: {}",
|
||||
String::from_utf8(result.stderr).unwrap()
|
||||
);
|
||||
|
||||
check_image(tmp_module_img)?;
|
||||
} else if modules_update_img_exist {
|
||||
// modules_update.img exists, we should use it as tmp img
|
||||
info!("Using existing modules_update.img as tmp image");
|
||||
std::fs::copy(modules_update_img, tmp_module_img).with_context(|| {
|
||||
format!(
|
||||
"Failed to copy {} to {}",
|
||||
@@ -376,6 +441,7 @@ pub fn install_module(zip: String) -> Result<()> {
|
||||
grow_image_size(tmp_module_img, grow_size)?;
|
||||
} else {
|
||||
// modules.img exists, we should use it as tmp img
|
||||
info!("Using existing modules.img as tmp image");
|
||||
std::fs::copy(modules_img, tmp_module_img).with_context(|| {
|
||||
format!(
|
||||
"Failed to copy {} to {}",
|
||||
@@ -393,52 +459,59 @@ pub fn install_module(zip: String) -> Result<()> {
|
||||
// mount the modules_update.img to mountpoint
|
||||
println!("- Mounting image");
|
||||
|
||||
mount_image(tmp_module_img, module_update_tmp_dir)?;
|
||||
let _dontdrop = mount::AutoMountExt4::try_new(tmp_module_img, module_update_tmp_dir, true)?;
|
||||
|
||||
info!("mounted {} to {}", tmp_module_img, module_update_tmp_dir);
|
||||
|
||||
setsyscon(module_update_tmp_dir)?;
|
||||
|
||||
let result = {
|
||||
let module_dir = format!("{}/{}", module_update_tmp_dir, module_id);
|
||||
ensure_clean_dir(&module_dir)?;
|
||||
info!("module dir: {}", module_dir);
|
||||
let module_dir = format!("{module_update_tmp_dir}/{module_id}");
|
||||
ensure_clean_dir(&module_dir)?;
|
||||
info!("module dir: {}", module_dir);
|
||||
|
||||
// unzip the image and move it to modules_update/<id> dir
|
||||
let file = File::open(&zip)?;
|
||||
let mut archive = zip::ZipArchive::new(file)?;
|
||||
archive.extract(&module_dir)?;
|
||||
// unzip the image and move it to modules_update/<id> dir
|
||||
let file = File::open(zip)?;
|
||||
let mut archive = zip::ZipArchive::new(file)?;
|
||||
archive.extract(&module_dir)?;
|
||||
|
||||
// set selinux for module/system dir
|
||||
let mut module_system_dir = PathBuf::from(module_dir);
|
||||
module_system_dir.push("system");
|
||||
let module_system_dir = module_system_dir.as_path();
|
||||
if module_system_dir.exists() {
|
||||
let path = format!("{}", module_system_dir.display());
|
||||
restorecon::restore_syscon(&path)?;
|
||||
}
|
||||
// set permission and selinux context for $MOD/system
|
||||
let module_system_dir = PathBuf::from(module_dir).join("system");
|
||||
if module_system_dir.exists() {
|
||||
#[cfg(unix)]
|
||||
set_permissions(&module_system_dir, Permissions::from_mode(0o755))?;
|
||||
restore_syscon(&module_system_dir)?;
|
||||
}
|
||||
|
||||
exec_install_script(&zip)
|
||||
};
|
||||
|
||||
// umount the modules_update.img
|
||||
let _ = umount_dir(module_update_tmp_dir);
|
||||
|
||||
// remove modules_update dir, ignore the error
|
||||
let _ = remove_dir_all(module_update_tmp_dir);
|
||||
|
||||
// return if exec script failed
|
||||
result.with_context(|| format!("Failed to execute install script for {}", module_id))?;
|
||||
exec_install_script(zip)?;
|
||||
|
||||
info!("rename {tmp_module_img} to {}", defs::MODULE_UPDATE_IMG);
|
||||
// all done, rename the tmp image to modules_update.img
|
||||
if std::fs::rename(tmp_module_img, defs::MODULE_UPDATE_IMG).is_err() {
|
||||
warn!("Rename image failed, try copy it.");
|
||||
std::fs::copy(tmp_module_img, defs::MODULE_UPDATE_IMG)
|
||||
.with_context(|| "Failed to copy image.".to_string())?;
|
||||
let _ = std::fs::remove_file(tmp_module_img);
|
||||
}
|
||||
|
||||
mark_update()?;
|
||||
|
||||
info!("Module install successfully!");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn do_module_update<F>(update_dir: &str, id: &str, func: F) -> Result<()>
|
||||
pub fn install_module(zip: &str) -> Result<()> {
|
||||
let result = _install_module(zip);
|
||||
if let Err(ref e) = result {
|
||||
// error happened, do some cleanup!
|
||||
let _ = std::fs::remove_file(defs::MODULE_UPDATE_TMP_IMG);
|
||||
let _ = mount::umount_dir(defs::MODULE_UPDATE_TMP_DIR);
|
||||
println!("- Error: {e}");
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
fn update_module<F>(update_dir: &str, id: &str, func: F) -> Result<()>
|
||||
where
|
||||
F: Fn(&str, &str) -> Result<()>,
|
||||
{
|
||||
@@ -469,28 +542,27 @@ where
|
||||
ensure_clean_dir(update_dir)?;
|
||||
|
||||
// mount the modules_update img
|
||||
mount_image(defs::MODULE_UPDATE_TMP_IMG, update_dir)?;
|
||||
let _dontdrop = mount::AutoMountExt4::try_new(defs::MODULE_UPDATE_TMP_IMG, update_dir, true)?;
|
||||
|
||||
// call the operation func
|
||||
let result = func(id, update_dir);
|
||||
|
||||
// umount modules_update.img
|
||||
let _ = umount_dir(update_dir);
|
||||
let _ = remove_dir_all(update_dir);
|
||||
|
||||
std::fs::rename(modules_update_tmp_img, defs::MODULE_UPDATE_IMG)?;
|
||||
if let Err(e) = std::fs::rename(modules_update_tmp_img, defs::MODULE_UPDATE_IMG) {
|
||||
warn!("Rename image failed: {e}, try copy it.");
|
||||
std::fs::copy(modules_update_tmp_img, defs::MODULE_UPDATE_IMG)
|
||||
.with_context(|| "Failed to copy image.".to_string())?;
|
||||
let _ = std::fs::remove_file(modules_update_tmp_img);
|
||||
}
|
||||
|
||||
mark_update()?;
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
pub fn uninstall_module(id: String) -> Result<()> {
|
||||
do_module_update(defs::MODULE_UPDATE_TMP_DIR, &id, |mid, update_dir| {
|
||||
pub fn uninstall_module(id: &str) -> Result<()> {
|
||||
update_module(defs::MODULE_UPDATE_TMP_DIR, id, |mid, update_dir| {
|
||||
let dir = Path::new(update_dir);
|
||||
if !dir.exists() {
|
||||
bail!("No module installed");
|
||||
}
|
||||
ensure!(dir.exists(), "No module installed");
|
||||
|
||||
// iterate the modules_update dir, find the module to be removed
|
||||
let dir = std::fs::read_dir(dir)?;
|
||||
@@ -515,24 +587,22 @@ pub fn uninstall_module(id: String) -> Result<()> {
|
||||
}
|
||||
|
||||
// santity check
|
||||
let target_module_path = format!("{}/{}", update_dir, mid);
|
||||
let target_module_path = format!("{update_dir}/{mid}");
|
||||
let target_module = Path::new(&target_module_path);
|
||||
if target_module.exists() {
|
||||
remove_dir_all(target_module)?;
|
||||
}
|
||||
|
||||
let _ = mark_module_state(&id, defs::REMOVE_FILE_NAME, true);
|
||||
let _ = mark_module_state(id, defs::REMOVE_FILE_NAME, true);
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn do_enable_module(module_dir: &str, mid: &str, enable: bool) -> Result<()> {
|
||||
let src_module_path = format!("{}/{}", module_dir, mid);
|
||||
fn _enable_module(module_dir: &str, mid: &str, enable: bool) -> Result<()> {
|
||||
let src_module_path = format!("{module_dir}/{mid}");
|
||||
let src_module = Path::new(&src_module_path);
|
||||
if !src_module.exists() {
|
||||
bail!("module: {} not found!", mid);
|
||||
}
|
||||
ensure!(src_module.exists(), "module: {} not found!", mid);
|
||||
|
||||
let disable_path = src_module.join(defs::DISABLE_FILE_NAME);
|
||||
if enable {
|
||||
@@ -541,8 +611,8 @@ fn do_enable_module(module_dir: &str, mid: &str, enable: bool) -> Result<()> {
|
||||
format!("Failed to remove disable file: {}", &disable_path.display())
|
||||
})?;
|
||||
}
|
||||
} else if !disable_path.exists() {
|
||||
std::fs::File::create(disable_path)?;
|
||||
} else {
|
||||
ensure_file_exists(disable_path)?;
|
||||
}
|
||||
|
||||
let _ = mark_module_state(mid, defs::DISABLE_FILE_NAME, !enable);
|
||||
@@ -550,19 +620,33 @@ fn do_enable_module(module_dir: &str, mid: &str, enable: bool) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn enable_module(id: String) -> Result<()> {
|
||||
do_module_update(defs::MODULE_UPDATE_TMP_DIR, &id, |mid, update_dir| {
|
||||
do_enable_module(update_dir, mid, true)
|
||||
pub fn enable_module(id: &str) -> Result<()> {
|
||||
update_module(defs::MODULE_UPDATE_TMP_DIR, id, |mid, update_dir| {
|
||||
_enable_module(update_dir, mid, true)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn disable_module(id: String) -> Result<()> {
|
||||
do_module_update(defs::MODULE_UPDATE_TMP_DIR, &id, |mid, update_dir| {
|
||||
do_enable_module(update_dir, mid, false)
|
||||
pub fn disable_module(id: &str) -> Result<()> {
|
||||
update_module(defs::MODULE_UPDATE_TMP_DIR, id, |mid, update_dir| {
|
||||
_enable_module(update_dir, mid, false)
|
||||
})
|
||||
}
|
||||
|
||||
fn do_list_modules(path: &str) -> Vec<HashMap<String, String>> {
|
||||
pub fn disable_all_modules() -> Result<()> {
|
||||
// we assume the module dir is already mounted
|
||||
let dir = std::fs::read_dir(defs::MODULE_DIR)?;
|
||||
for entry in dir.flatten() {
|
||||
let path = entry.path();
|
||||
let disable_flag = path.join(defs::DISABLE_FILE_NAME);
|
||||
if let Err(e) = ensure_file_exists(disable_flag) {
|
||||
warn!("Failed to disable module: {}: {}", path.display(), e);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn _list_modules(path: &str) -> Vec<HashMap<String, String>> {
|
||||
// first check enabled modules
|
||||
let dir = std::fs::read_dir(path);
|
||||
let Ok(dir) = dir else {
|
||||
@@ -583,7 +667,7 @@ fn do_list_modules(path: &str) -> Vec<HashMap<String, String>> {
|
||||
warn!("Failed to read file: {}", module_prop.display());
|
||||
continue;
|
||||
};
|
||||
let mut module_prop_map = HashMap::new();
|
||||
let mut module_prop_map: HashMap<String, String> = HashMap::new();
|
||||
let encoding = encoding::all::UTF_8;
|
||||
let result =
|
||||
PropertiesIter::new_with_encoding(Cursor::new(content), encoding).read_into(|k, v| {
|
||||
@@ -595,9 +679,9 @@ fn do_list_modules(path: &str) -> Vec<HashMap<String, String>> {
|
||||
let update = path.join(defs::UPDATE_FILE_NAME).exists();
|
||||
let remove = path.join(defs::REMOVE_FILE_NAME).exists();
|
||||
|
||||
module_prop_map.insert("enabled".to_string(), enabled.to_string());
|
||||
module_prop_map.insert("update".to_string(), update.to_string());
|
||||
module_prop_map.insert("remove".to_string(), remove.to_string());
|
||||
module_prop_map.insert("enabled".to_owned(), enabled.to_string());
|
||||
module_prop_map.insert("update".to_owned(), update.to_string());
|
||||
module_prop_map.insert("remove".to_owned(), remove.to_string());
|
||||
|
||||
if result.is_err() {
|
||||
warn!("Failed to parse module.prop: {}", module_prop.display());
|
||||
@@ -610,7 +694,7 @@ fn do_list_modules(path: &str) -> Vec<HashMap<String, String>> {
|
||||
}
|
||||
|
||||
pub fn list_modules() -> Result<()> {
|
||||
let modules = do_list_modules(defs::MODULE_DIR);
|
||||
let modules = _list_modules(defs::MODULE_DIR);
|
||||
println!("{}", serde_json::to_string_pretty(&modules)?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
352
userspace/ksud/src/mount.rs
Normal file
352
userspace/ksud/src/mount.rs
Normal file
@@ -0,0 +1,352 @@
|
||||
use anyhow::{Ok, Result};
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
use anyhow::Context;
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
use retry::delay::NoDelay;
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
use sys_mount::{unmount, FilesystemType, Mount, MountFlags, Unmount, UnmountFlags};
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
use procfs::process::{MountInfo, Process};
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
use std::collections::HashSet;
|
||||
|
||||
pub struct AutoMountExt4 {
|
||||
mnt: String,
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
mount: Option<Mount>,
|
||||
auto_umount: bool,
|
||||
}
|
||||
|
||||
impl AutoMountExt4 {
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
pub fn try_new(src: &str, mnt: &str, auto_umount: bool) -> Result<Self> {
|
||||
let result = Mount::builder()
|
||||
.fstype(FilesystemType::from("ext4"))
|
||||
.flags(MountFlags::empty())
|
||||
.mount(src, mnt)
|
||||
.map(|mount| {
|
||||
Ok(Self {
|
||||
mnt: mnt.to_string(),
|
||||
mount: Some(mount),
|
||||
auto_umount,
|
||||
})
|
||||
});
|
||||
if let Err(e) = result {
|
||||
println!("- Mount failed: {e}, retry with system mount");
|
||||
let result = std::process::Command::new("mount")
|
||||
.arg("-t")
|
||||
.arg("ext4")
|
||||
.arg(src)
|
||||
.arg(mnt)
|
||||
.status();
|
||||
if let Err(e) = result {
|
||||
Err(anyhow::anyhow!(
|
||||
"mount partition: {src} -> {mnt} failed: {e}"
|
||||
))
|
||||
} else {
|
||||
Ok(Self {
|
||||
mnt: mnt.to_string(),
|
||||
mount: None,
|
||||
auto_umount,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
result.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "linux", target_os = "android")))]
|
||||
pub fn try_new(_src: &str, _mnt: &str, _auto_umount: bool) -> Result<Self> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
pub fn umount(&self) -> Result<()> {
|
||||
if let Some(ref mount) = self.mount {
|
||||
mount
|
||||
.unmount(UnmountFlags::empty())
|
||||
.map_err(|e| anyhow::anyhow!(e))
|
||||
} else {
|
||||
let result = std::process::Command::new("umount").arg(&self.mnt).status();
|
||||
if let Err(e) = result {
|
||||
Err(anyhow::anyhow!("umount: {} failed: {e}", self.mnt))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
impl Drop for AutoMountExt4 {
|
||||
fn drop(&mut self) {
|
||||
log::info!(
|
||||
"AutoMountExt4 drop: {}, auto_umount: {}",
|
||||
self.mnt,
|
||||
self.auto_umount
|
||||
);
|
||||
if self.auto_umount {
|
||||
let _ = self.umount();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
fn mount_image(src: &str, target: &str, autodrop: bool) -> Result<()> {
|
||||
if autodrop {
|
||||
Mount::builder()
|
||||
.fstype(FilesystemType::from("ext4"))
|
||||
.mount_autodrop(src, target, UnmountFlags::empty())
|
||||
.with_context(|| format!("Failed to do mount: {src} -> {target}"))?;
|
||||
} else {
|
||||
Mount::builder()
|
||||
.fstype(FilesystemType::from("ext4"))
|
||||
.mount(src, target)
|
||||
.with_context(|| format!("Failed to do mount: {src} -> {target}"))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
pub fn mount_ext4(src: &str, target: &str, autodrop: bool) -> Result<()> {
|
||||
// umount target first.
|
||||
let _ = umount_dir(target);
|
||||
let result = retry::retry(NoDelay.take(3), || mount_image(src, target, autodrop));
|
||||
result
|
||||
.map_err(|e| anyhow::anyhow!("mount partition: {src} -> {target} failed: {e}"))
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
pub fn umount_dir(src: &str) -> Result<()> {
|
||||
unmount(src, UnmountFlags::empty()).with_context(|| format!("Failed to umount {src}"))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
pub fn mount_overlay(lowerdir: &str, mnt: &str) -> Result<()> {
|
||||
Mount::builder()
|
||||
.fstype(FilesystemType::from("overlay"))
|
||||
.flags(MountFlags::RDONLY)
|
||||
.data(&format!("lowerdir={lowerdir}"))
|
||||
.mount("overlay", mnt)
|
||||
.map(|_| ())
|
||||
.map_err(|e| anyhow::anyhow!("mount partition: {mnt} overlay failed: {e}"))
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "linux", target_os = "android")))]
|
||||
pub fn mount_ext4(_src: &str, _target: &str, _autodrop: bool) -> Result<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "linux", target_os = "android")))]
|
||||
pub fn umount_dir(_src: &str) -> Result<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "linux", target_os = "android")))]
|
||||
pub fn mount_overlay(_lowerdir: &str, _mnt: &str) -> Result<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
pub struct StockOverlay {
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
mountinfos: Vec<MountInfo>,
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "linux", target_os = "android")))]
|
||||
impl StockOverlay {
|
||||
pub fn new() -> Self {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
pub fn mount_all(&self) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
pub fn umount_all(&self) {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
impl StockOverlay {
|
||||
pub fn new() -> Self {
|
||||
if let std::result::Result::Ok(process) = Process::myself() {
|
||||
if let std::result::Result::Ok(mountinfos) = process.mountinfo() {
|
||||
let overlay_mounts = mountinfos
|
||||
.into_iter()
|
||||
.filter(|m| m.fs_type == "overlay")
|
||||
.collect::<Vec<_>>();
|
||||
return Self {
|
||||
mountinfos: overlay_mounts,
|
||||
};
|
||||
}
|
||||
}
|
||||
Self { mountinfos: vec![] }
|
||||
}
|
||||
|
||||
pub fn mount_all(&self) {
|
||||
log::info!("stock overlay: mount all: {:?}", self.mountinfos);
|
||||
for mount in self.mountinfos.clone() {
|
||||
let Some(mnt) = mount.mount_point.to_str() else {
|
||||
log::warn!("Failed to get mount point");
|
||||
continue;
|
||||
};
|
||||
|
||||
if mnt == "/system" {
|
||||
log::warn!("stock overlay found /system, skip!");
|
||||
continue;
|
||||
}
|
||||
|
||||
let (_flags, b): (HashSet<_>, HashSet<_>) = mount
|
||||
.mount_options
|
||||
.into_iter()
|
||||
.chain(mount.super_options)
|
||||
.partition(|(_, m)| m.is_none());
|
||||
|
||||
let mut overlay_opts = vec![];
|
||||
for (opt, val) in b {
|
||||
if let Some(val) = val {
|
||||
overlay_opts.push(format!("{opt}={val}"));
|
||||
} else {
|
||||
log::warn!("opt empty: {}", opt);
|
||||
}
|
||||
}
|
||||
let overlay_data = overlay_opts.join(",");
|
||||
let result = Mount::builder()
|
||||
.fstype(FilesystemType::from("overlay"))
|
||||
.flags(MountFlags::RDONLY)
|
||||
.data(&overlay_data)
|
||||
.mount("overlay", mnt);
|
||||
if let Err(e) = result {
|
||||
log::error!(
|
||||
"stock mount overlay: {} failed: {}",
|
||||
mount.mount_point.display(),
|
||||
e
|
||||
);
|
||||
} else {
|
||||
log::info!(
|
||||
"stock mount :{} overlay_opts: {}",
|
||||
mount.mount_point.display(),
|
||||
overlay_opts.join(",")
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn umount_all(&self) {
|
||||
log::info!("stock overlay: umount all: {:?}", self.mountinfos);
|
||||
for mnt in &self.mountinfos {
|
||||
let Some(p) = mnt.mount_point.to_str() else {
|
||||
log::warn!("Failed to umount: {}", mnt.mount_point.display());
|
||||
continue;
|
||||
};
|
||||
|
||||
let result = umount_dir(p);
|
||||
log::info!("stock umount {}: {:?}", p, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// some ROMs mount device(ext4,exfat) to /vendor, when we do overlay mount, it will overlay
|
||||
// the stock mounts, these mounts include bt_firmware, wifi_firmware, etc.
|
||||
// so we to remount these mounts when we do overlay mount.
|
||||
// this is a workaround, we should find a better way to do this.
|
||||
#[derive(Debug)]
|
||||
pub struct StockMount {
|
||||
mnt: String,
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
mountlist: proc_mounts::MountList,
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
impl StockMount {
|
||||
pub fn new(mnt: &str) -> Result<Self> {
|
||||
let mountlist = proc_mounts::MountList::new()?;
|
||||
Ok(Self {
|
||||
mnt: mnt.to_string(),
|
||||
mountlist,
|
||||
})
|
||||
}
|
||||
|
||||
fn get_target_mounts(&self) -> Vec<&proc_mounts::MountInfo> {
|
||||
let mut mounts = self
|
||||
.mountlist
|
||||
.destination_starts_with(std::path::Path::new(&self.mnt))
|
||||
.filter(|m| m.fstype != "overlay" && m.fstype != "rootfs")
|
||||
.collect::<Vec<_>>();
|
||||
mounts.sort_by(|a, b| b.dest.cmp(&a.dest)); // inverse order
|
||||
mounts
|
||||
}
|
||||
|
||||
pub fn umount(&self) -> Result<()> {
|
||||
let mounts = self.get_target_mounts();
|
||||
log::info!("umount stock for {} : {:?}", self.mnt, mounts);
|
||||
for m in mounts {
|
||||
let dst = m
|
||||
.dest
|
||||
.to_str()
|
||||
.ok_or(anyhow::anyhow!("Failed to get dst"))?;
|
||||
umount_dir(dst)?;
|
||||
log::info!("umount: {:?}", m);
|
||||
}
|
||||
log::info!("umount stock succeed!");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn remount(&self) -> Result<()> {
|
||||
let mut mounts = self.get_target_mounts();
|
||||
mounts.reverse(); // remount it in order
|
||||
log::info!("remount stock for {} : {:?}", self.mnt, mounts);
|
||||
for m in mounts {
|
||||
let src = std::fs::canonicalize(&m.source)?;
|
||||
|
||||
let src = src.to_str().ok_or(anyhow::anyhow!("Failed to get src"))?;
|
||||
let dst = m
|
||||
.dest
|
||||
.to_str()
|
||||
.ok_or(anyhow::anyhow!("Failed to get dst"))?;
|
||||
|
||||
let fstype = m.fstype.as_str();
|
||||
let options = m.options.join(",");
|
||||
|
||||
log::info!("begin remount: {src} -> {dst}");
|
||||
let result = std::process::Command::new("mount")
|
||||
.arg("-t")
|
||||
.arg(fstype)
|
||||
.arg("-o")
|
||||
.arg(options)
|
||||
.arg(src)
|
||||
.arg(dst)
|
||||
.status();
|
||||
if let Err(e) = result {
|
||||
log::error!("remount failed: {}", e);
|
||||
} else {
|
||||
log::info!("remount {src} -> {dst} succeed!");
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "linux", target_os = "android")))]
|
||||
impl StockMount {
|
||||
pub fn new(mnt: &str) -> Result<Self> {
|
||||
Ok(Self {
|
||||
mnt: mnt.to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn umount(&self) -> Result<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
pub fn remount(&self) -> Result<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -1,27 +1,45 @@
|
||||
use anyhow::ensure;
|
||||
use anyhow::Ok;
|
||||
use anyhow::Result;
|
||||
use subprocess::Exec;
|
||||
use jwalk::{Parallelism::Serial, WalkDir};
|
||||
use std::path::Path;
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
use anyhow::{Context, Ok};
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
use extattr::{setxattr, Flags as XattrFlags};
|
||||
|
||||
const SYSTEM_CON: &str = "u:object_r:system_file:s0";
|
||||
const _ADB_CON: &str = "u:object_r:adb_data_file:s0";
|
||||
const SELINUX_XATTR: &str = "security.selinux";
|
||||
|
||||
pub fn setcon(path: &str, con: &str) -> Result<()> {
|
||||
// todo use libselinux directly
|
||||
let cmd = format!("chcon {} {}", con, path);
|
||||
let result = Exec::shell(cmd).join()?;
|
||||
ensure!(result.success(), "chcon for: {} failed.", path);
|
||||
pub fn setcon<P: AsRef<Path>>(path: P, con: &str) -> Result<()> {
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
setxattr(&path, SELINUX_XATTR, con, XattrFlags::empty()).with_context(|| {
|
||||
format!(
|
||||
"Failed to change SELinux context for {}",
|
||||
path.as_ref().display()
|
||||
)
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn setsyscon(path: &str) -> Result<()> {
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
pub fn setsyscon<P: AsRef<Path>>(path: P) -> Result<()> {
|
||||
setcon(path, SYSTEM_CON)
|
||||
}
|
||||
|
||||
pub fn restore_syscon(dir: &str) -> Result<()> {
|
||||
// todo use libselinux directly
|
||||
let cmd = format!("chcon -R {} {}", SYSTEM_CON, dir);
|
||||
let result = Exec::shell(cmd).join()?;
|
||||
ensure!(result.success(), "chcon for: {} failed.", dir);
|
||||
#[cfg(not(any(target_os = "linux", target_os = "android")))]
|
||||
pub fn setsyscon<P: AsRef<Path>>(path: P) -> Result<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
pub fn restore_syscon<P: AsRef<Path>>(dir: P) -> Result<()> {
|
||||
for dir_entry in WalkDir::new(dir).parallelism(Serial) {
|
||||
if let Some(path) = dir_entry.ok().map(|dir_entry| dir_entry.path()) {
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
setxattr(&path, SELINUX_XATTR, SYSTEM_CON, XattrFlags::empty()).with_context(|| {
|
||||
format!("Failed to change SELinux context for {}", path.display())
|
||||
})?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
754
userspace/ksud/src/sepolicy.rs
Normal file
754
userspace/ksud/src/sepolicy.rs
Normal file
@@ -0,0 +1,754 @@
|
||||
use anyhow::{bail, Result};
|
||||
use derive_new::new;
|
||||
use nom::{
|
||||
branch::alt,
|
||||
bytes::complete::{tag, take_while, take_while1, take_while_m_n},
|
||||
character::{
|
||||
complete::{space0, space1},
|
||||
is_alphanumeric,
|
||||
},
|
||||
combinator::map,
|
||||
sequence::Tuple,
|
||||
IResult, Parser,
|
||||
};
|
||||
use std::{path::Path, vec};
|
||||
|
||||
type SeObject<'a> = Vec<&'a str>;
|
||||
|
||||
fn is_sepolicy_char(c: char) -> bool {
|
||||
is_alphanumeric(c as u8) || c == '_' || c == '-'
|
||||
}
|
||||
|
||||
fn parse_single_word(input: &str) -> IResult<&str, &str> {
|
||||
take_while1(is_sepolicy_char).parse(input)
|
||||
}
|
||||
|
||||
fn parse_bracket_objs(input: &str) -> IResult<&str, SeObject> {
|
||||
let (input, (_, words, _)) = (
|
||||
tag("{"),
|
||||
take_while_m_n(1, 100, |c: char| is_sepolicy_char(c) || c.is_whitespace()),
|
||||
tag("}"),
|
||||
)
|
||||
.parse(input)?;
|
||||
Ok((input, words.split_whitespace().collect()))
|
||||
}
|
||||
|
||||
fn parse_single_obj(input: &str) -> IResult<&str, SeObject> {
|
||||
let (input, word) = take_while1(is_sepolicy_char).parse(input)?;
|
||||
Ok((input, vec![word]))
|
||||
}
|
||||
|
||||
fn parse_star(input: &str) -> IResult<&str, SeObject> {
|
||||
let (input, _) = tag("*").parse(input)?;
|
||||
Ok((input, vec!["*"]))
|
||||
}
|
||||
|
||||
// 1. a single sepolicy word
|
||||
// 2. { obj1 obj2 obj3 ...}
|
||||
// 3. *
|
||||
fn parse_seobj(input: &str) -> IResult<&str, SeObject> {
|
||||
let (input, strs) = alt((parse_single_obj, parse_bracket_objs, parse_star)).parse(input)?;
|
||||
Ok((input, strs))
|
||||
}
|
||||
|
||||
fn parse_seobj_no_star(input: &str) -> IResult<&str, SeObject> {
|
||||
let (input, strs) = alt((parse_single_obj, parse_bracket_objs)).parse(input)?;
|
||||
Ok((input, strs))
|
||||
}
|
||||
|
||||
trait SeObjectParser<'a> {
|
||||
fn parse(input: &'a str) -> IResult<&'a str, Self>
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, new)]
|
||||
struct NormalPerm<'a> {
|
||||
op: &'a str,
|
||||
source: SeObject<'a>,
|
||||
target: SeObject<'a>,
|
||||
class: SeObject<'a>,
|
||||
perm: SeObject<'a>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, new)]
|
||||
struct XPerm<'a> {
|
||||
op: &'a str,
|
||||
source: SeObject<'a>,
|
||||
target: SeObject<'a>,
|
||||
class: SeObject<'a>,
|
||||
operation: &'a str,
|
||||
perm_set: &'a str,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, new)]
|
||||
struct TypeState<'a> {
|
||||
op: &'a str,
|
||||
stype: SeObject<'a>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, new)]
|
||||
struct TypeAttr<'a> {
|
||||
stype: SeObject<'a>,
|
||||
sattr: SeObject<'a>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, new)]
|
||||
struct Type<'a> {
|
||||
name: &'a str,
|
||||
attrs: SeObject<'a>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, new)]
|
||||
struct Attr<'a> {
|
||||
name: &'a str,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, new)]
|
||||
struct TypeTransition<'a> {
|
||||
source: &'a str,
|
||||
target: &'a str,
|
||||
class: &'a str,
|
||||
default_type: &'a str,
|
||||
object_name: Option<&'a str>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, new)]
|
||||
struct TypeChange<'a> {
|
||||
op: &'a str,
|
||||
source: &'a str,
|
||||
target: &'a str,
|
||||
class: &'a str,
|
||||
default_type: &'a str,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, new)]
|
||||
struct GenFsCon<'a> {
|
||||
fs_name: &'a str,
|
||||
partial_path: &'a str,
|
||||
fs_context: &'a str,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum PolicyStatement<'a> {
|
||||
// "allow *source_type *target_type *class *perm_set"
|
||||
// "deny *source_type *target_type *class *perm_set"
|
||||
// "auditallow *source_type *target_type *class *perm_set"
|
||||
// "dontaudit *source_type *target_type *class *perm_set"
|
||||
NormalPerm(NormalPerm<'a>),
|
||||
|
||||
// "allowxperm *source_type *target_type *class operation xperm_set"
|
||||
// "auditallowxperm *source_type *target_type *class operation xperm_set"
|
||||
// "dontauditxperm *source_type *target_type *class operation xperm_set"
|
||||
XPerm(XPerm<'a>),
|
||||
|
||||
// "permissive ^type"
|
||||
// "enforce ^type"
|
||||
TypeState(TypeState<'a>),
|
||||
|
||||
// "type type_name ^(attribute)"
|
||||
Type(Type<'a>),
|
||||
|
||||
// "typeattribute ^type ^attribute"
|
||||
TypeAttr(TypeAttr<'a>),
|
||||
|
||||
// "attribute ^attribute"
|
||||
Attr(Attr<'a>),
|
||||
|
||||
// "type_transition source_type target_type class default_type (object_name)"
|
||||
TypeTransition(TypeTransition<'a>),
|
||||
|
||||
// "type_change source_type target_type class default_type"
|
||||
// "type_member source_type target_type class default_type"
|
||||
TypeChange(TypeChange<'a>),
|
||||
|
||||
// "genfscon fs_name partial_path fs_context"
|
||||
GenFsCon(GenFsCon<'a>),
|
||||
}
|
||||
|
||||
impl<'a> SeObjectParser<'a> for NormalPerm<'a> {
|
||||
fn parse(input: &'a str) -> IResult<&str, Self> {
|
||||
let (input, op) = alt((
|
||||
tag("allow"),
|
||||
tag("deny"),
|
||||
tag("auditallow"),
|
||||
tag("dontaudit"),
|
||||
))(input)?;
|
||||
|
||||
let (input, _) = space0(input)?;
|
||||
let (input, source) = parse_seobj(input)?;
|
||||
let (input, _) = space0(input)?;
|
||||
let (input, target) = parse_seobj(input)?;
|
||||
let (input, _) = space0(input)?;
|
||||
let (input, class) = parse_seobj(input)?;
|
||||
let (input, _) = space0(input)?;
|
||||
let (input, perm) = parse_seobj(input)?;
|
||||
Ok((input, NormalPerm::new(op, source, target, class, perm)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> SeObjectParser<'a> for XPerm<'a> {
|
||||
fn parse(input: &'a str) -> IResult<&'a str, Self> {
|
||||
let (input, op) = alt((
|
||||
tag("allowxperm"),
|
||||
tag("auditallowxperm"),
|
||||
tag("dontauditxperm"),
|
||||
))(input)?;
|
||||
|
||||
let (input, _) = space0(input)?;
|
||||
let (input, source) = parse_seobj(input)?;
|
||||
let (input, _) = space0(input)?;
|
||||
let (input, target) = parse_seobj(input)?;
|
||||
let (input, _) = space0(input)?;
|
||||
let (input, class) = parse_seobj(input)?;
|
||||
let (input, _) = space0(input)?;
|
||||
let (input, operation) = parse_single_word(input)?;
|
||||
let (input, _) = space0(input)?;
|
||||
let (input, perm_set) = parse_single_word(input)?;
|
||||
|
||||
Ok((
|
||||
input,
|
||||
XPerm::new(op, source, target, class, operation, perm_set),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> SeObjectParser<'a> for TypeState<'a> {
|
||||
fn parse(input: &'a str) -> IResult<&'a str, Self> {
|
||||
let (input, op) = alt((tag("permissive"), tag("enforce")))(input)?;
|
||||
|
||||
let (input, _) = space1(input)?;
|
||||
let (input, stype) = parse_seobj_no_star(input)?;
|
||||
|
||||
Ok((input, TypeState::new(op, stype)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> SeObjectParser<'a> for Type<'a> {
|
||||
fn parse(input: &'a str) -> IResult<&'a str, Self> {
|
||||
let (input, _) = tag("type")(input)?;
|
||||
let (input, _) = space1(input)?;
|
||||
let (input, name) = parse_single_word(input)?;
|
||||
|
||||
if input.is_empty() {
|
||||
return Ok((input, Type::new(name, vec!["domain"]))); // default to domain
|
||||
}
|
||||
|
||||
let (input, _) = space1(input)?;
|
||||
let (input, attrs) = parse_seobj_no_star(input)?;
|
||||
|
||||
Ok((input, Type::new(name, attrs)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> SeObjectParser<'a> for TypeAttr<'a> {
|
||||
fn parse(input: &'a str) -> IResult<&'a str, Self> {
|
||||
let (input, _) = alt((tag("typeattribute"), tag("attradd")))(input)?;
|
||||
let (input, _) = space1(input)?;
|
||||
let (input, stype) = parse_seobj_no_star(input)?;
|
||||
let (input, _) = space1(input)?;
|
||||
let (input, attr) = parse_seobj_no_star(input)?;
|
||||
|
||||
Ok((input, TypeAttr::new(stype, attr)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> SeObjectParser<'a> for Attr<'a> {
|
||||
fn parse(input: &'a str) -> IResult<&'a str, Self> {
|
||||
let (input, _) = tag("attribute")(input)?;
|
||||
let (input, _) = space1(input)?;
|
||||
let (input, attr) = parse_single_word(input)?;
|
||||
|
||||
Ok((input, Attr::new(attr)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> SeObjectParser<'a> for TypeTransition<'a> {
|
||||
fn parse(input: &'a str) -> IResult<&'a str, Self> {
|
||||
let (input, _) = alt((tag("type_transition"), tag("name_transition")))(input)?;
|
||||
let (input, _) = space1(input)?;
|
||||
let (input, source) = parse_single_word(input)?;
|
||||
let (input, _) = space1(input)?;
|
||||
let (input, target) = parse_single_word(input)?;
|
||||
let (input, _) = space1(input)?;
|
||||
let (input, class) = parse_single_word(input)?;
|
||||
let (input, _) = space1(input)?;
|
||||
let (input, default) = parse_single_word(input)?;
|
||||
|
||||
if input.is_empty() {
|
||||
return Ok((
|
||||
input,
|
||||
TypeTransition::new(source, target, class, default, None),
|
||||
));
|
||||
}
|
||||
|
||||
let (input, _) = space1(input)?;
|
||||
let (input, object) = parse_single_word(input)?;
|
||||
|
||||
Ok((
|
||||
input,
|
||||
TypeTransition::new(source, target, class, default, Some(object)),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> SeObjectParser<'a> for TypeChange<'a> {
|
||||
fn parse(input: &'a str) -> IResult<&'a str, Self> {
|
||||
let (input, op) = alt((tag("type_change"), tag("type_member")))(input)?;
|
||||
let (input, _) = space1(input)?;
|
||||
let (input, source) = parse_single_word(input)?;
|
||||
let (input, _) = space1(input)?;
|
||||
let (input, target) = parse_single_word(input)?;
|
||||
let (input, _) = space1(input)?;
|
||||
let (input, class) = parse_single_word(input)?;
|
||||
let (input, _) = space1(input)?;
|
||||
let (input, default) = parse_single_word(input)?;
|
||||
|
||||
Ok((input, TypeChange::new(op, source, target, class, default)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> SeObjectParser<'a> for GenFsCon<'a> {
|
||||
fn parse(input: &'a str) -> IResult<&'a str, Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let (input, _) = tag("genfscon")(input)?;
|
||||
let (input, _) = space1(input)?;
|
||||
let (input, fs) = parse_single_word(input)?;
|
||||
let (input, _) = space1(input)?;
|
||||
let (input, path) = parse_single_word(input)?;
|
||||
let (input, _) = space1(input)?;
|
||||
let (input, context) = parse_single_word(input)?;
|
||||
Ok((input, GenFsCon::new(fs, path, context)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> PolicyStatement<'a> {
|
||||
fn parse(input: &'a str) -> IResult<&'a str, Self> {
|
||||
let (input, _) = space0(input)?;
|
||||
let (input, statement) = alt((
|
||||
map(NormalPerm::parse, PolicyStatement::NormalPerm),
|
||||
map(XPerm::parse, PolicyStatement::XPerm),
|
||||
map(TypeState::parse, PolicyStatement::TypeState),
|
||||
map(Type::parse, PolicyStatement::Type),
|
||||
map(TypeAttr::parse, PolicyStatement::TypeAttr),
|
||||
map(Attr::parse, PolicyStatement::Attr),
|
||||
map(TypeTransition::parse, PolicyStatement::TypeTransition),
|
||||
map(TypeChange::parse, PolicyStatement::TypeChange),
|
||||
map(GenFsCon::parse, PolicyStatement::GenFsCon),
|
||||
))(input)?;
|
||||
let (input, _) = space0(input)?;
|
||||
let (input, _) = take_while(|c| c == ';')(input)?;
|
||||
let (input, _) = space0(input)?;
|
||||
Ok((input, statement))
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_sepolicy<'a, 'b>(input: &'b str, strict: bool) -> Result<Vec<PolicyStatement<'a>>>
|
||||
where
|
||||
'b: 'a,
|
||||
{
|
||||
let mut statements = vec![];
|
||||
|
||||
for line in input.split(['\n', ';']) {
|
||||
if line.trim().is_empty() {
|
||||
continue;
|
||||
}
|
||||
if let Ok((_, statement)) = PolicyStatement::parse(line.trim()) {
|
||||
statements.push(statement);
|
||||
} else if strict {
|
||||
bail!("Failed to parse policy statement: {}", line)
|
||||
}
|
||||
}
|
||||
Ok(statements)
|
||||
}
|
||||
|
||||
const SEPOLICY_MAX_LEN: usize = 128;
|
||||
|
||||
const CMD_NORMAL_PERM: u32 = 1;
|
||||
const CMD_XPERM: u32 = 2;
|
||||
const CMD_TYPE_STATE: u32 = 3;
|
||||
const CMD_TYPE: u32 = 4;
|
||||
const CMD_TYPE_ATTR: u32 = 5;
|
||||
const CMD_ATTR: u32 = 6;
|
||||
const CMD_TYPE_TRANSITION: u32 = 7;
|
||||
const CMD_TYPE_CHANGE: u32 = 8;
|
||||
const CMD_GENFSCON: u32 = 9;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
enum PolicyObject {
|
||||
All, // for "*", stand for all objects, and is NULL in ffi
|
||||
One([u8; SEPOLICY_MAX_LEN]),
|
||||
#[default]
|
||||
None,
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for PolicyObject {
|
||||
type Error = anyhow::Error;
|
||||
fn try_from(s: &str) -> Result<Self> {
|
||||
anyhow::ensure!(s.len() <= SEPOLICY_MAX_LEN, "policy object too long");
|
||||
if s == "*" {
|
||||
return Ok(PolicyObject::All);
|
||||
}
|
||||
let mut buf = [0u8; SEPOLICY_MAX_LEN];
|
||||
buf[..s.len()].copy_from_slice(s.as_bytes());
|
||||
Ok(PolicyObject::One(buf))
|
||||
}
|
||||
}
|
||||
|
||||
/// atomic statement, such as: allow domain1 domain2:file1 read;
|
||||
/// normal statement would be expand to atomic statement, for example:
|
||||
/// allow domain1 domain2:file1 { read write }; would be expand to two atomic statement
|
||||
/// allow domain1 domain2:file1 read;allow domain1 domain2:file1 write;
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
#[derive(Debug, new)]
|
||||
struct AtomicStatement {
|
||||
cmd: u32,
|
||||
subcmd: u32,
|
||||
sepol1: PolicyObject,
|
||||
sepol2: PolicyObject,
|
||||
sepol3: PolicyObject,
|
||||
sepol4: PolicyObject,
|
||||
sepol5: PolicyObject,
|
||||
sepol6: PolicyObject,
|
||||
sepol7: PolicyObject,
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&'a NormalPerm<'a>> for Vec<AtomicStatement> {
|
||||
type Error = anyhow::Error;
|
||||
fn try_from(perm: &'a NormalPerm<'a>) -> Result<Self> {
|
||||
let mut result = vec![];
|
||||
let subcmd = match perm.op {
|
||||
"allow" => 1,
|
||||
"deny" => 2,
|
||||
"auditallow" => 3,
|
||||
"dontaudit" => 4,
|
||||
_ => 0,
|
||||
};
|
||||
for &s in &perm.source {
|
||||
for &t in &perm.target {
|
||||
for &c in &perm.class {
|
||||
for &p in &perm.perm {
|
||||
result.push(AtomicStatement {
|
||||
cmd: CMD_NORMAL_PERM,
|
||||
subcmd,
|
||||
sepol1: s.try_into()?,
|
||||
sepol2: t.try_into()?,
|
||||
sepol3: c.try_into()?,
|
||||
sepol4: p.try_into()?,
|
||||
sepol5: PolicyObject::None,
|
||||
sepol6: PolicyObject::None,
|
||||
sepol7: PolicyObject::None,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&'a XPerm<'a>> for Vec<AtomicStatement> {
|
||||
type Error = anyhow::Error;
|
||||
fn try_from(perm: &'a XPerm<'a>) -> Result<Self> {
|
||||
let mut result = vec![];
|
||||
let subcmd = match perm.op {
|
||||
"allowxperm" => 1,
|
||||
"auditallowxperm" => 2,
|
||||
"dontauditxperm" => 3,
|
||||
_ => 0,
|
||||
};
|
||||
for &s in &perm.source {
|
||||
for &t in &perm.target {
|
||||
for &c in &perm.class {
|
||||
result.push(AtomicStatement {
|
||||
cmd: CMD_XPERM,
|
||||
subcmd,
|
||||
sepol1: s.try_into()?,
|
||||
sepol2: t.try_into()?,
|
||||
sepol3: c.try_into()?,
|
||||
sepol4: perm.operation.try_into()?,
|
||||
sepol5: perm.perm_set.try_into()?,
|
||||
sepol6: PolicyObject::None,
|
||||
sepol7: PolicyObject::None,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&'a TypeState<'a>> for Vec<AtomicStatement> {
|
||||
type Error = anyhow::Error;
|
||||
fn try_from(perm: &'a TypeState<'a>) -> Result<Self> {
|
||||
let mut result = vec![];
|
||||
let subcmd = match perm.op {
|
||||
"permissive" => 1,
|
||||
"enforcing" => 2,
|
||||
_ => 0,
|
||||
};
|
||||
for &t in &perm.stype {
|
||||
result.push(AtomicStatement {
|
||||
cmd: CMD_TYPE_STATE,
|
||||
subcmd,
|
||||
sepol1: t.try_into()?,
|
||||
sepol2: PolicyObject::None,
|
||||
sepol3: PolicyObject::None,
|
||||
sepol4: PolicyObject::None,
|
||||
sepol5: PolicyObject::None,
|
||||
sepol6: PolicyObject::None,
|
||||
sepol7: PolicyObject::None,
|
||||
});
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&'a Type<'a>> for Vec<AtomicStatement> {
|
||||
type Error = anyhow::Error;
|
||||
fn try_from(perm: &'a Type<'a>) -> Result<Self> {
|
||||
let mut result = vec![];
|
||||
for &attr in &perm.attrs {
|
||||
result.push(AtomicStatement {
|
||||
cmd: CMD_TYPE,
|
||||
subcmd: 0,
|
||||
sepol1: perm.name.try_into()?,
|
||||
sepol2: attr.try_into()?,
|
||||
sepol3: PolicyObject::None,
|
||||
sepol4: PolicyObject::None,
|
||||
sepol5: PolicyObject::None,
|
||||
sepol6: PolicyObject::None,
|
||||
sepol7: PolicyObject::None,
|
||||
});
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&'a TypeAttr<'a>> for Vec<AtomicStatement> {
|
||||
type Error = anyhow::Error;
|
||||
fn try_from(perm: &'a TypeAttr<'a>) -> Result<Self> {
|
||||
let mut result = vec![];
|
||||
for &t in &perm.stype {
|
||||
for &attr in &perm.sattr {
|
||||
result.push(AtomicStatement {
|
||||
cmd: CMD_TYPE_ATTR,
|
||||
subcmd: 0,
|
||||
sepol1: t.try_into()?,
|
||||
sepol2: attr.try_into()?,
|
||||
sepol3: PolicyObject::None,
|
||||
sepol4: PolicyObject::None,
|
||||
sepol5: PolicyObject::None,
|
||||
sepol6: PolicyObject::None,
|
||||
sepol7: PolicyObject::None,
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&'a Attr<'a>> for Vec<AtomicStatement> {
|
||||
type Error = anyhow::Error;
|
||||
fn try_from(perm: &'a Attr<'a>) -> Result<Self> {
|
||||
let result = vec![AtomicStatement {
|
||||
cmd: CMD_ATTR,
|
||||
subcmd: 0,
|
||||
sepol1: perm.name.try_into()?,
|
||||
sepol2: PolicyObject::None,
|
||||
sepol3: PolicyObject::None,
|
||||
sepol4: PolicyObject::None,
|
||||
sepol5: PolicyObject::None,
|
||||
sepol6: PolicyObject::None,
|
||||
sepol7: PolicyObject::None,
|
||||
}];
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&'a TypeTransition<'a>> for Vec<AtomicStatement> {
|
||||
type Error = anyhow::Error;
|
||||
fn try_from(perm: &'a TypeTransition<'a>) -> Result<Self> {
|
||||
let mut result = vec![];
|
||||
let obj = match perm.object_name {
|
||||
Some(obj) => obj.try_into()?,
|
||||
None => PolicyObject::None,
|
||||
};
|
||||
result.push(AtomicStatement {
|
||||
cmd: CMD_TYPE_TRANSITION,
|
||||
subcmd: 0,
|
||||
sepol1: perm.source.try_into()?,
|
||||
sepol2: perm.target.try_into()?,
|
||||
sepol3: perm.class.try_into()?,
|
||||
sepol4: perm.default_type.try_into()?,
|
||||
sepol5: obj,
|
||||
sepol6: PolicyObject::None,
|
||||
sepol7: PolicyObject::None,
|
||||
});
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&'a TypeChange<'a>> for Vec<AtomicStatement> {
|
||||
type Error = anyhow::Error;
|
||||
fn try_from(perm: &'a TypeChange<'a>) -> Result<Self> {
|
||||
let mut result = vec![];
|
||||
let subcmd = match perm.op {
|
||||
"type_change" => 1,
|
||||
"type_member" => 2,
|
||||
_ => 0,
|
||||
};
|
||||
result.push(AtomicStatement {
|
||||
cmd: CMD_TYPE_CHANGE,
|
||||
subcmd,
|
||||
sepol1: perm.source.try_into()?,
|
||||
sepol2: perm.target.try_into()?,
|
||||
sepol3: perm.class.try_into()?,
|
||||
sepol4: perm.default_type.try_into()?,
|
||||
sepol5: PolicyObject::None,
|
||||
sepol6: PolicyObject::None,
|
||||
sepol7: PolicyObject::None,
|
||||
});
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&'a GenFsCon<'a>> for Vec<AtomicStatement> {
|
||||
type Error = anyhow::Error;
|
||||
fn try_from(perm: &'a GenFsCon<'a>) -> Result<Self> {
|
||||
let result = vec![AtomicStatement {
|
||||
cmd: CMD_GENFSCON,
|
||||
subcmd: 0,
|
||||
sepol1: perm.fs_name.try_into()?,
|
||||
sepol2: perm.partial_path.try_into()?,
|
||||
sepol3: perm.fs_context.try_into()?,
|
||||
sepol4: PolicyObject::None,
|
||||
sepol5: PolicyObject::None,
|
||||
sepol6: PolicyObject::None,
|
||||
sepol7: PolicyObject::None,
|
||||
}];
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&'a PolicyStatement<'a>> for Vec<AtomicStatement> {
|
||||
type Error = anyhow::Error;
|
||||
fn try_from(value: &'a PolicyStatement) -> Result<Self> {
|
||||
match value {
|
||||
PolicyStatement::NormalPerm(perm) => perm.try_into(),
|
||||
PolicyStatement::XPerm(perm) => perm.try_into(),
|
||||
PolicyStatement::TypeState(perm) => perm.try_into(),
|
||||
PolicyStatement::Type(perm) => perm.try_into(),
|
||||
PolicyStatement::TypeAttr(perm) => perm.try_into(),
|
||||
PolicyStatement::Attr(perm) => perm.try_into(),
|
||||
PolicyStatement::TypeTransition(perm) => perm.try_into(),
|
||||
PolicyStatement::TypeChange(perm) => perm.try_into(),
|
||||
PolicyStatement::GenFsCon(perm) => perm.try_into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
/// for C FFI to call kernel interface
|
||||
///////////////////////////////////////////////////////////////
|
||||
|
||||
#[derive(Debug)]
|
||||
#[repr(C)]
|
||||
struct FfiPolicy {
|
||||
cmd: u32,
|
||||
subcmd: u32,
|
||||
sepol1: *const libc::c_char,
|
||||
sepol2: *const libc::c_char,
|
||||
sepol3: *const libc::c_char,
|
||||
sepol4: *const libc::c_char,
|
||||
sepol5: *const libc::c_char,
|
||||
sepol6: *const libc::c_char,
|
||||
sepol7: *const libc::c_char,
|
||||
}
|
||||
|
||||
fn to_c_ptr(pol: &PolicyObject) -> *const libc::c_char {
|
||||
match pol {
|
||||
PolicyObject::None | PolicyObject::All => std::ptr::null(),
|
||||
PolicyObject::One(s) => s.as_ptr().cast::<libc::c_char>(),
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AtomicStatement> for FfiPolicy {
|
||||
fn from(policy: AtomicStatement) -> FfiPolicy {
|
||||
FfiPolicy {
|
||||
cmd: policy.cmd,
|
||||
subcmd: policy.subcmd,
|
||||
sepol1: to_c_ptr(&policy.sepol1),
|
||||
sepol2: to_c_ptr(&policy.sepol2),
|
||||
sepol3: to_c_ptr(&policy.sepol3),
|
||||
sepol4: to_c_ptr(&policy.sepol4),
|
||||
sepol5: to_c_ptr(&policy.sepol5),
|
||||
sepol6: to_c_ptr(&policy.sepol6),
|
||||
sepol7: to_c_ptr(&policy.sepol7),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
fn apply_one_rule<'a>(statement: &'a PolicyStatement<'a>, strict: bool) -> Result<()> {
|
||||
let policies: Vec<AtomicStatement> = statement.try_into()?;
|
||||
|
||||
for policy in policies {
|
||||
let mut result: u32 = 0;
|
||||
let cpolicy = FfiPolicy::from(policy);
|
||||
unsafe {
|
||||
#[allow(clippy::cast_possible_wrap)]
|
||||
libc::prctl(
|
||||
crate::ksu::KERNEL_SU_OPTION as i32, // supposed to overflow
|
||||
crate::ksu::CMD_SET_SEPOLICY,
|
||||
0,
|
||||
std::ptr::addr_of!(cpolicy).cast::<libc::c_void>(),
|
||||
std::ptr::addr_of_mut!(result).cast::<libc::c_void>(),
|
||||
);
|
||||
}
|
||||
|
||||
if result != crate::ksu::KERNEL_SU_OPTION {
|
||||
log::warn!("apply rule: {:?} failed.", statement);
|
||||
if strict {
|
||||
return Err(anyhow::anyhow!("apply rule {:?} failed.", statement));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "linux", target_os = "android")))]
|
||||
fn apply_one_rule<'a>(_statement: &'a PolicyStatement<'a>, _strict: bool) -> Result<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
pub fn live_patch(policy: &str) -> Result<()> {
|
||||
let result = parse_sepolicy(policy.trim(), false)?;
|
||||
for statement in result {
|
||||
println!("{statement:?}");
|
||||
apply_one_rule(&statement, false)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn apply_file<P: AsRef<Path>>(path: P) -> Result<()> {
|
||||
let input = std::fs::read_to_string(path)?;
|
||||
live_patch(&input)
|
||||
}
|
||||
|
||||
pub fn check_rule(policy: &str) -> Result<()> {
|
||||
let path = Path::new(policy);
|
||||
let policy = if path.exists() {
|
||||
std::fs::read_to_string(path)?
|
||||
} else {
|
||||
policy.to_string()
|
||||
};
|
||||
let result = parse_sepolicy(policy.trim(), true)?;
|
||||
for statement in result {
|
||||
apply_one_rule(&statement, true)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,57 +1,92 @@
|
||||
use anyhow::{bail, Context, Error, Ok, Result};
|
||||
use std::{
|
||||
fs::{create_dir_all, write, File},
|
||||
io::ErrorKind::AlreadyExists,
|
||||
path::Path,
|
||||
process::{Command, Stdio},
|
||||
};
|
||||
|
||||
use anyhow::{ensure, Result};
|
||||
use retry::delay::NoDelay;
|
||||
use subprocess::Exec;
|
||||
|
||||
fn do_mount_image(src: &str, target: &str) -> Result<()> {
|
||||
let result = Exec::shell(format!("mount -t ext4 {} {}", src, target))
|
||||
.stdout(subprocess::NullFile)
|
||||
.stderr(subprocess::Redirection::Merge)
|
||||
.join()?;
|
||||
ensure!(result.success(), "do mount: {} -> {} failed.", src, target);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn mount_image(src: &str, target: &str) -> Result<()> {
|
||||
// umount target first.
|
||||
let _ = umount_dir(target);
|
||||
let result = retry::retry(NoDelay.take(3), || do_mount_image(src, target));
|
||||
ensure!(result.is_ok(), "mount: {} -> {} failed.", src, target);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn umount_dir(src: &str) -> Result<()> {
|
||||
let result = Exec::shell(format!("umount {}", src))
|
||||
.stdout(subprocess::NullFile)
|
||||
.stderr(subprocess::Redirection::Merge)
|
||||
.join()?;
|
||||
ensure!(result.success(), "umount: {} failed", src);
|
||||
Ok(())
|
||||
}
|
||||
#[allow(unused_imports)]
|
||||
use std::fs::{set_permissions, Permissions};
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::prelude::PermissionsExt;
|
||||
|
||||
pub fn ensure_clean_dir(dir: &str) -> Result<()> {
|
||||
let path = Path::new(dir);
|
||||
log::debug!("ensure_clean_dir: {}", path.display());
|
||||
if path.exists() {
|
||||
log::debug!("ensure_clean_dir: {} exists, remove it", path.display());
|
||||
std::fs::remove_dir_all(path)?;
|
||||
}
|
||||
Ok(std::fs::create_dir_all(path)?)
|
||||
}
|
||||
|
||||
pub fn getprop(prop: &str) -> Result<String> {
|
||||
let output = Command::new("getprop")
|
||||
.arg(prop)
|
||||
.stdout(Stdio::piped())
|
||||
.output()?;
|
||||
let output = String::from_utf8_lossy(&output.stdout);
|
||||
Ok(output.trim().to_string())
|
||||
pub fn ensure_file_exists<T: AsRef<Path>>(file: T) -> Result<()> {
|
||||
match File::options().write(true).create_new(true).open(&file) {
|
||||
std::result::Result::Ok(_) => Ok(()),
|
||||
Err(err) => {
|
||||
if err.kind() == AlreadyExists && file.as_ref().is_file() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::from(err))
|
||||
.with_context(|| format!("{} is not a regular file", file.as_ref().display()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_safe_mode() -> Result<bool> {
|
||||
Ok(getprop("persist.sys.safemode")?.eq("1") || getprop("ro.sys.safemode")?.eq("1"))
|
||||
pub fn ensure_dir_exists<T: AsRef<Path>>(dir: T) -> Result<()> {
|
||||
let result = create_dir_all(&dir).map_err(Error::from);
|
||||
if dir.as_ref().is_dir() {
|
||||
result
|
||||
} else if result.is_ok() {
|
||||
bail!("{} is not a regular directory", dir.as_ref().display())
|
||||
} else {
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ensure_binary<T: AsRef<Path>>(path: T, contents: &[u8]) -> Result<()> {
|
||||
if path.as_ref().exists() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
ensure_dir_exists(path.as_ref().parent().ok_or_else(|| {
|
||||
anyhow::anyhow!(
|
||||
"{} does not have parent directory",
|
||||
path.as_ref().to_string_lossy()
|
||||
)
|
||||
})?)?;
|
||||
|
||||
write(&path, contents)?;
|
||||
#[cfg(unix)]
|
||||
set_permissions(&path, Permissions::from_mode(0o755))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
pub fn getprop(prop: &str) -> Option<String> {
|
||||
android_properties::getprop(prop).value()
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "linux", target_os = "android")))]
|
||||
pub fn getprop(_prop: &str) -> Option<String> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
pub fn is_safe_mode() -> bool {
|
||||
let safemode = getprop("persist.sys.safemode")
|
||||
.filter(|prop| prop == "1")
|
||||
.is_some()
|
||||
|| getprop("ro.sys.safemode")
|
||||
.filter(|prop| prop == "1")
|
||||
.is_some();
|
||||
log::info!("safemode: {}", safemode);
|
||||
if safemode {
|
||||
return true;
|
||||
}
|
||||
let safemode = crate::ksu::check_kernel_safemode();
|
||||
log::info!("kernel_safemode: {}", safemode);
|
||||
safemode
|
||||
}
|
||||
|
||||
pub fn get_zip_uncompressed_size(zip_path: &str) -> Result<u64> {
|
||||
@@ -61,3 +96,36 @@ pub fn get_zip_uncompressed_size(zip_path: &str) -> Result<u64> {
|
||||
.sum();
|
||||
Ok(total)
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
pub fn switch_mnt_ns(pid: i32) -> Result<()> {
|
||||
use anyhow::ensure;
|
||||
use std::os::fd::AsRawFd;
|
||||
let path = format!("/proc/{pid}/ns/mnt");
|
||||
let fd = std::fs::File::open(path)?;
|
||||
let ret = unsafe { libc::setns(fd.as_raw_fd(), libc::CLONE_NEWNS) };
|
||||
ensure!(ret == 0, "switch mnt ns failed");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
pub fn unshare_mnt_ns() -> Result<()> {
|
||||
use anyhow::ensure;
|
||||
let ret = unsafe { libc::unshare(libc::CLONE_NEWNS) };
|
||||
ensure!(ret == 0, "unshare mnt ns failed");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
pub fn umask(mask: u32) {
|
||||
unsafe { libc::umask(mask) };
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "linux", target_os = "android")))]
|
||||
pub fn umask(_mask: u32) {
|
||||
unimplemented!("umask is not supported on this platform")
|
||||
}
|
||||
|
||||
pub fn has_magisk() -> bool {
|
||||
which::which("magisk").is_ok()
|
||||
}
|
||||
|
||||
@@ -49,6 +49,9 @@ function sidebarGuide() {
|
||||
{ text: 'Installation', link: '/guide/installation' },
|
||||
{ text: 'How to build?', link: '/guide/how-to-build' },
|
||||
{ text: 'Intergrate for non-GKI devices', link: '/guide/how-to-integrate-for-non-gki'},
|
||||
{ text: 'Unofficially supported devices', link: '/guide/unofficially-support-devices.md' },
|
||||
{ text: 'Module Guide', link: '/guide/module.md' },
|
||||
{ text: 'Rescue from bootloop', link: '/guide/rescue-from-bootloop.md' },
|
||||
{ text: 'FAQ', link: '/guide/faq' },
|
||||
]
|
||||
}
|
||||
|
||||
@@ -49,6 +49,7 @@ function sidebarGuide() {
|
||||
{ text: 'Instalasi', link: '/id_ID/guide/installation' },
|
||||
{ text: 'Bagaimana cara buildnya?', link: '/id_ID/guide/how-to-build' },
|
||||
{ text: 'Integrasi untuk perangkat non-GKI', link: '/id_ID/guide/how-to-integrate-for-non-gki'},
|
||||
{ text: 'Perangkat yang didukung secara tidak resmi', link: '/id_ID/guide/unofficially-support-devices.md' },
|
||||
{ text: 'FAQ', link: '/id_ID/guide/faq' },
|
||||
]
|
||||
}
|
||||
|
||||
@@ -49,6 +49,7 @@ function sidebarGuide() {
|
||||
{ text: 'Cách cài đặt', link: '/vi_VN/guide/installation' },
|
||||
{ text: 'Cách để build?', link: '/vi_VN/guide/how-to-build' },
|
||||
{ text: 'Tích hợp vào thiết bị không sử dụng GKI', link: '/vi_VN/guide/how-to-integrate-for-non-gki'},
|
||||
{ text: 'Thiết bị hỗ trợ không chính thức', link: '/vi_VN/guide/unofficially-support-devices.md' },
|
||||
{ text: 'FAQ - Câu hỏi thường gặp', link: '/vi_VN/guide/faq' },
|
||||
]
|
||||
}
|
||||
|
||||
@@ -49,6 +49,9 @@ function sidebarGuide() {
|
||||
{ text: '安装', link: '/zh_CN/guide/installation' },
|
||||
{ text: '如何构建?', link: '/zh_CN/guide/how-to-build' },
|
||||
{ text: '如何为非GKI设备集成 KernelSU', link: '/zh_CN/guide/how-to-integrate-for-non-gki'},
|
||||
{ text: '非官方支持设备', link: '/zh_CN/guide/unofficially-support-devices.md' },
|
||||
{ text: '模块开发指南', link: '/zh_CN/guide/module.md' },
|
||||
{ text: '救砖', link: '/zh_CN/guide/rescue-from-bootloop.md' },
|
||||
{ text: '常见问题', link: '/zh_CN/guide/faq' },
|
||||
]
|
||||
}
|
||||
|
||||
26
website/docs/guide/difference-with-magisk.md
Normal file
26
website/docs/guide/difference-with-magisk.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# Difference with Magisk
|
||||
|
||||
Although there are many similarities between KernelSU modules and Magisk modules, there are inevitably some differences due to their completely different implementation mechanisms. If you want your module to run on both Magisk and KernelSU, you must understand these differences.
|
||||
|
||||
## Similarities
|
||||
|
||||
- Module file format: both use zip format to organize modules, and the format of modules is almost the same
|
||||
- Module installation directory: both located in `/data/adb/modules`
|
||||
- Systemless: both support modifying /system in a systemless way through modules
|
||||
- post-fs-data.sh: the execution time and semantics are exactly the same
|
||||
- service.sh: the execution time and semantics are exactly the same
|
||||
- system.prop: completely the same
|
||||
- sepolicy.rule: completely the same
|
||||
- BusyBox: scripts are run in BusyBox with "standalone mode" enabled in both cases
|
||||
|
||||
## Differences
|
||||
|
||||
Before understanding the differences, you need to know how to differentiate whether your module is running in KernelSU or Magisk. You can use the environment variable `KSU` to differentiate it in all places where you can run module scripts (`customize.sh`, `post-fs-data.sh`, `service.sh`). In KernelSU, this environment variable will be set to `true`.
|
||||
|
||||
Here are some differences:
|
||||
|
||||
- KernelSU modules cannot be installed in Recovery mode.
|
||||
- KernelSU modules do not have built-in support for Zygisk (but you can use Zygisk modules through [ZygiskOnKernelSU](https://github.com/Dr-TSNG/ZygiskOnKernelSU).
|
||||
- The method for replacing or deleting files in KernelSU modules is completely different from Magisk. KernelSU does not support the `.replace` method. Instead, you need to create a same-named file with `mknod filename c 0 0` to delete the corresponding file.
|
||||
- The directories for BusyBox are different. The built-in BusyBox in KernelSU is located in `/data/adb/ksu/bin/busybox`, while in Magisk it is in `/data/adb/magisk/busybox`. **Note that this is an internal behavior of KernelSU and may change in the future!**
|
||||
- KernelSU does not support `.replace` files; however, KernelSU supports the `REMOVE` and `REPLACE` variable to remove or replace files and folders.
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
First, your devices should be able to unlock the bootloader. If it can't, then it is unsupported.
|
||||
|
||||
Then install KernelSU manager App to your device and open it, if it shows `Unsupported` then your device is unsupported and won't be supported in the future.
|
||||
Then install KernelSU manager App to your device and open it, if it shows `Unsupported` then your device cannot be supported out of box, but you can build kernel source and integrate KernelSU to make it work or using [unofficially-support-devices](unofficially-support-devices).
|
||||
|
||||
## Does KernelSU need to unlock Bootloader?
|
||||
|
||||
@@ -12,11 +12,15 @@ Certainly, yes.
|
||||
|
||||
## Does KernelSU support modules?
|
||||
|
||||
Yes, But it is in early version, may be buggy. Please waiting it to be stable :)
|
||||
Yes, But it is in early version, it may be buggy. Please wait for it to be stable :)
|
||||
|
||||
## Does KernelSU support Xposed?
|
||||
|
||||
Yes, [Dreamland](https://github.com/canyie/Dreamland) and [TaiChi](https::/taichi.cool) partially works now, And we are trying to make other Xposed Framework work.
|
||||
Yes, [Dreamland](https://github.com/canyie/Dreamland) and [TaiChi](https::/taichi.cool) work now. For LSPosed, you can make it work by [Zygisk on KernelSU](https://github.com/Dr-TSNG/ZygiskOnKernelSU)
|
||||
|
||||
## Does KernelSU support Zygisk?
|
||||
|
||||
KernelSU has no builtin Zygisk support, but you can use [Zygisk on KernelSU](https://github.com/Dr-TSNG/ZygiskOnKernelSU) instead.
|
||||
|
||||
## Is KernelSU compatible with Magisk?
|
||||
|
||||
@@ -46,3 +50,18 @@ It is possible, KernelSU is backported to kernel 4.14 now, for older kernel, you
|
||||
## How to integrate KernelSU for old kernel?
|
||||
|
||||
Please refer [guide](how-to-integrate-for-non-gki)
|
||||
|
||||
## Why my Android version is 13, and the kernel shows "android12-5.10"?
|
||||
|
||||
The Kernel version has nothing to do with Android version, if you need to flash kernel, always use the kernel version, Android version is not so important.
|
||||
|
||||
## Is there any --mount-master/global mount namespace in KernelSU?
|
||||
|
||||
There isn't now(maybe in the future), But there are many ways to switch to global mount namespace manully, such as:
|
||||
|
||||
1. `nsenter -t 1 -m sh` to get a shell in global mount namespace.
|
||||
2. Add `nsenter --mount=/proc/1/ns/mnt` to the command you want to execute, then the command is executed in global mount namespace. KernelSU is also [using this way](https://github.com/tiann/KernelSU/blob/77056a710073d7a5f7ee38f9e77c9fd0b3256576/manager/app/src/main/java/me/weishu/kernelsu/ui/util/KsuCli.kt#L115)
|
||||
|
||||
## I am GKI1.0, can i use this?
|
||||
|
||||
GKI1 is completely different from GKI2, you must compile kernel by yourself.
|
||||
|
||||
@@ -37,7 +37,7 @@ But if you encounter a boot loop when integrated KernelSU, it is maybe *kprobe i
|
||||
|
||||
:::tip How to check if kprobe is broken?
|
||||
|
||||
comment out `ksu_enable_sucompat()` and `ksu_enable_ksud()` in `KernelSU/kernel/ksu.c`, the the device boot normally, then kprobe may be broken.
|
||||
comment out `ksu_enable_sucompat()` and `ksu_enable_ksud()` in `KernelSU/kernel/ksu.c`, if the device boots normally, then kprobe may be broken.
|
||||
:::
|
||||
|
||||
## Manully modify the kernel source
|
||||
@@ -142,4 +142,85 @@ You should found the four functions in kernel source:
|
||||
3. vfs_read, usually in `fs/read_write.c`
|
||||
4. vfs_statx, usually in `fs/stat.c`
|
||||
|
||||
If your kernel does not have the `vfs_statx`, use `vfs_fstatat` instead:
|
||||
|
||||
```diff
|
||||
diff --git a/fs/stat.c b/fs/stat.c
|
||||
index 068fdbcc9e26..5348b7bb9db2 100644
|
||||
--- a/fs/stat.c
|
||||
+++ b/fs/stat.c
|
||||
@@ -87,6 +87,8 @@ int vfs_fstat(unsigned int fd, struct kstat *stat)
|
||||
}
|
||||
EXPORT_SYMBOL(vfs_fstat);
|
||||
|
||||
+extern int ksu_handle_stat(int *dfd, const char __user **filename_user, int *flags);
|
||||
+
|
||||
int vfs_fstatat(int dfd, const char __user *filename, struct kstat *stat,
|
||||
int flag)
|
||||
{
|
||||
@@ -94,6 +96,8 @@ int vfs_fstatat(int dfd, const char __user *filename, struct kstat *stat,
|
||||
int error = -EINVAL;
|
||||
unsigned int lookup_flags = 0;
|
||||
|
||||
+ ksu_handle_stat(&dfd, &filename, &flag);
|
||||
+
|
||||
if ((flag & ~(AT_SYMLINK_NOFOLLOW | AT_NO_AUTOMOUNT |
|
||||
AT_EMPTY_PATH)) != 0)
|
||||
goto out;
|
||||
```
|
||||
|
||||
For kernels eariler than 4.17, if you cannot find `do_faccessat`, just go to the definition of the `faccessat` syscall and place the call there:
|
||||
|
||||
```diff
|
||||
diff --git a/fs/open.c b/fs/open.c
|
||||
index 2ff887661237..e758d7db7663 100644
|
||||
--- a/fs/open.c
|
||||
+++ b/fs/open.c
|
||||
@@ -355,6 +355,9 @@ SYSCALL_DEFINE4(fallocate, int, fd, int, mode, loff_t, offset, loff_t, len)
|
||||
return error;
|
||||
}
|
||||
|
||||
+extern int ksu_handle_faccessat(int *dfd, const char __user **filename_user, int *mode,
|
||||
+ int *flags);
|
||||
+
|
||||
/*
|
||||
* access() needs to use the real uid/gid, not the effective uid/gid.
|
||||
* We do this by temporarily clearing all FS-related capabilities and
|
||||
@@ -370,6 +373,8 @@ SYSCALL_DEFINE3(faccessat, int, dfd, const char __user *, filename, int, mode)
|
||||
int res;
|
||||
unsigned int lookup_flags = LOOKUP_FOLLOW;
|
||||
|
||||
+ ksu_handle_faccessat(&dfd, &filename, &mode, NULL);
|
||||
+
|
||||
if (mode & ~S_IRWXO) /* where's F_OK, X_OK, W_OK, R_OK? */
|
||||
return -EINVAL;
|
||||
```
|
||||
|
||||
To enable KernelSU's builtin SafeMode, You should also modify `input_handle_event` in `drivers/input/input.c`:
|
||||
|
||||
:::tip
|
||||
It is strongly recommended to enable this feature, it is very helpful for recusing from bootloop!
|
||||
:::
|
||||
|
||||
```diff
|
||||
diff --git a/drivers/input/input.c b/drivers/input/input.c
|
||||
index 45306f9ef247..815091ebfca4 100755
|
||||
--- a/drivers/input/input.c
|
||||
+++ b/drivers/input/input.c
|
||||
@@ -367,10 +367,13 @@ static int input_get_disposition(struct input_dev *dev,
|
||||
return disposition;
|
||||
}
|
||||
|
||||
+extern int ksu_handle_input_handle_event(unsigned int *type, unsigned int *code, int *value);
|
||||
+
|
||||
static void input_handle_event(struct input_dev *dev,
|
||||
unsigned int type, unsigned int code, int value)
|
||||
{
|
||||
int disposition = input_get_disposition(dev, type, code, &value);
|
||||
+ ksu_handle_input_handle_event(&type, &code, &value);
|
||||
|
||||
if (disposition != INPUT_IGNORE_EVENT && type != EV_SYN)
|
||||
add_input_randomness(type, code, value);
|
||||
```
|
||||
|
||||
Finally, build your kernel again, KernelSU should works well.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user