You've already forked KernelSU
mirror of
https://github.com/tiann/KernelSU.git
synced 2025-08-27 23:46:34 +00:00
Compare commits
308 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
58ffaeb4e2 | ||
|
|
76499ee629 | ||
|
|
fedfa3e3fd | ||
|
|
2902e42f91 | ||
|
|
37f4045499 | ||
|
|
12761ee167 | ||
|
|
0d254237a4 | ||
|
|
f5bb24676e | ||
|
|
303a3a8482 | ||
|
|
07273b6971 | ||
|
|
c7c9e9c3ed | ||
|
|
c3c990c50a | ||
|
|
6942fe12b5 | ||
|
|
f5cfb32882 | ||
|
|
e17f3eab96 | ||
|
|
08884da423 | ||
|
|
5f1d70dabb | ||
|
|
79bb9813ef | ||
|
|
1cda4ba245 | ||
|
|
1cc678d20b | ||
|
|
cd3e292489 | ||
|
|
40ea27a59c | ||
|
|
e95ca9316c | ||
|
|
9b2f907a48 | ||
|
|
90299ad0bb | ||
|
|
22d084f89b | ||
|
|
99770a7362 | ||
|
|
bbc7ebe5b1 | ||
|
|
d131b7561d | ||
|
|
ff8c614737 | ||
|
|
c12ad9d1c7 | ||
|
|
1703c16948 | ||
|
|
a48d7b112f | ||
|
|
f2d5e57f01 | ||
|
|
4111bbf720 | ||
|
|
cd32ad8751 | ||
|
|
fefb826177 | ||
|
|
e27fc04b5b | ||
|
|
168f412324 | ||
|
|
bd8434f4f4 | ||
|
|
c697398893 | ||
|
|
1e0176242a | ||
|
|
0e5e12b331 | ||
|
|
a2b240121a | ||
|
|
246dadea05 | ||
|
|
b5291077a9 | ||
|
|
ee548d513a | ||
|
|
bdd269ea22 | ||
|
|
e1d7bb91e8 | ||
|
|
593bc06302 | ||
|
|
8b6204a534 | ||
|
|
01ea9c9dce | ||
|
|
12e664b0a4 | ||
|
|
0ca92b9de9 | ||
|
|
2fc9e03d86 | ||
|
|
4218aa488f | ||
|
|
d95ede1f76 | ||
|
|
56f72e8702 | ||
|
|
964bf34325 | ||
|
|
3191165ba2 | ||
|
|
3ad27c5999 | ||
|
|
5b27f6c010 | ||
|
|
be44fad288 | ||
|
|
2a5fb76f95 | ||
|
|
5db7075432 | ||
|
|
1408175a35 | ||
|
|
ab1dc894e1 | ||
|
|
c5bc1c8b15 | ||
|
|
6d1ee60d67 | ||
|
|
a58e929205 | ||
|
|
53be8612c8 | ||
|
|
c7f6a7d11b | ||
|
|
e9011041c5 | ||
|
|
9803371fdb | ||
|
|
710edb72fa | ||
|
|
a4ddf59562 | ||
|
|
37dc9a27a7 | ||
|
|
27ccfa6395 | ||
|
|
116bc56cfe | ||
|
|
9ae0126be5 | ||
|
|
e8755f8ae0 | ||
|
|
6ba2bd3af9 | ||
|
|
4c5e485e76 | ||
|
|
05c6892f43 | ||
|
|
670a20c37f | ||
|
|
75509aba72 | ||
|
|
887f02e742 | ||
|
|
ef8da39d34 | ||
|
|
b7c759ece1 | ||
|
|
019da4a634 | ||
|
|
33aa6de50b | ||
|
|
0b3688c3b1 | ||
|
|
076e5d3655 | ||
|
|
c9608af0c8 | ||
|
|
908fbadaf5 | ||
|
|
ee97fdfc56 | ||
|
|
4ac137313f | ||
|
|
11d0029a4b | ||
|
|
8af5a9038b | ||
|
|
5d449988fb | ||
|
|
e969563df0 | ||
|
|
990626cf7d | ||
|
|
fc77ca989f | ||
|
|
2ce3976023 | ||
|
|
63ec531814 | ||
|
|
a4b55b30ca | ||
|
|
807556f361 | ||
|
|
2d854f2f37 | ||
|
|
5980c113fe | ||
|
|
1491465b55 | ||
|
|
728380222a | ||
|
|
005404f552 | ||
|
|
b55c229038 | ||
|
|
32538c9833 | ||
|
|
b31fc47197 | ||
|
|
5003824fa8 | ||
|
|
eea75b72a4 | ||
|
|
82d5ec3cc1 | ||
|
|
75721be8c0 | ||
|
|
de72eedb46 | ||
|
|
66827ab7de | ||
|
|
2a33433272 | ||
|
|
581dff8a5f | ||
|
|
fc425cbba2 | ||
|
|
87f55c1acb | ||
|
|
68d639e325 | ||
|
|
89f6cd044e | ||
|
|
bd3a1291da | ||
|
|
3abb7e4ca2 | ||
|
|
41265b0203 | ||
|
|
f2cb841b8a | ||
|
|
c69da29081 | ||
|
|
e304ef8cfb | ||
|
|
2ce76351da | ||
|
|
66cbd931a7 | ||
|
|
07bc28e386 | ||
|
|
2ef4ffe5eb | ||
|
|
128e7e394e | ||
|
|
998dc9b94b | ||
|
|
c6dafb1333 | ||
|
|
8ae7299d59 | ||
|
|
8f1b9c579b | ||
|
|
4b6573b521 | ||
|
|
325e843569 | ||
|
|
d014947a54 | ||
|
|
8858cc899e | ||
|
|
8f3e59803f | ||
|
|
95044bb551 | ||
|
|
b56448a929 | ||
|
|
c2b981dbdb | ||
|
|
28fb482720 | ||
|
|
d744a705a8 | ||
|
|
c62b89f02b | ||
|
|
f876b0114e | ||
|
|
9965988f26 | ||
|
|
828bb6fbb4 | ||
|
|
10d8d9efcd | ||
|
|
e59f3333cb | ||
|
|
31a9189d80 | ||
|
|
d162221fac | ||
|
|
0cdca0d053 | ||
|
|
4c934d460b | ||
|
|
76612b9cf7 | ||
|
|
9cf8ac9c51 | ||
|
|
b80e06256d | ||
|
|
08d9e5d6bc | ||
|
|
ed0cfd231e | ||
|
|
538d3f06f4 | ||
|
|
f5d3fb6217 | ||
|
|
ffa3579e6f | ||
|
|
c7adb8e3b1 | ||
|
|
c1427f658a | ||
|
|
eccce7b31f | ||
|
|
f81caf75a9 | ||
|
|
d4680c6de7 | ||
|
|
2716ec58a0 | ||
|
|
f25dbd8fed | ||
|
|
e35180c919 | ||
|
|
2f54ceb7c4 | ||
|
|
6506ef468d | ||
|
|
da46dfbde1 | ||
|
|
09ecc2c9b5 | ||
|
|
0b2899a961 | ||
|
|
c6ed3fa27f | ||
|
|
43cd3b9cad | ||
|
|
294d6fa05e | ||
|
|
009a479c17 | ||
|
|
72ee14e6be | ||
|
|
2b01a1c395 | ||
|
|
f69793d38e | ||
|
|
eda0d6d23b | ||
|
|
abba36d786 | ||
|
|
33ea9a6f33 | ||
|
|
9a0da7270b | ||
|
|
9caf440200 | ||
|
|
c9e53cf355 | ||
|
|
432eb318ac | ||
|
|
cea0db4e0d | ||
|
|
842c0b674f | ||
|
|
36ed99d1f6 | ||
|
|
86d0f37b37 | ||
|
|
5303c9c461 | ||
|
|
67667b6df2 | ||
|
|
d9d27b4229 | ||
|
|
a2a1b19758 | ||
|
|
fb9d39d6d5 | ||
|
|
0f3d425f64 | ||
|
|
08e3580646 | ||
|
|
0fa4a4c6db | ||
|
|
d452e01a3d | ||
|
|
f7da373f8b | ||
|
|
b80cf7ba15 | ||
|
|
fe1cd4b27a | ||
|
|
925206f9c8 | ||
|
|
f24a317e2d | ||
|
|
aeaa3ce982 | ||
|
|
8c6913a9af | ||
|
|
7795232a58 | ||
|
|
057330c68f | ||
|
|
91c80279bd | ||
|
|
5715df0b10 | ||
|
|
9f0c540fba | ||
|
|
f9d19a957a | ||
|
|
76b1165572 | ||
|
|
80c85b3bb9 | ||
|
|
900652a82b | ||
|
|
6cbef7d987 | ||
|
|
01d66834f0 | ||
|
|
a40eae9b8c | ||
|
|
23b46bde2b | ||
|
|
b38ada30e6 | ||
|
|
3452841752 | ||
|
|
3b9cab3432 | ||
|
|
2f6df20085 | ||
|
|
609ea40d1c | ||
|
|
515a309831 | ||
|
|
02c2228ab7 | ||
|
|
ca246ba67d | ||
|
|
f9008b67c4 | ||
|
|
e228197906 | ||
|
|
d98e324618 | ||
|
|
1880fdfda9 | ||
|
|
d86b524558 | ||
|
|
70fa38a589 | ||
|
|
ea93140b8a | ||
|
|
45c229dc9f | ||
|
|
2b5ac95165 | ||
|
|
38c65e7e4b | ||
|
|
f40eaf4499 | ||
|
|
3a667ef389 | ||
|
|
f35e03d816 | ||
|
|
2bfd4d71d8 | ||
|
|
2bc84014c2 | ||
|
|
a3c72c22c1 | ||
|
|
ddf2b7e9ff | ||
|
|
04d1bee7b4 | ||
|
|
0c9ecf3abc | ||
|
|
c8b4798b61 | ||
|
|
550c8ae45d | ||
|
|
0b4f4683b3 | ||
|
|
5504b0dd29 | ||
|
|
f0dcddd391 | ||
|
|
75be1e1bd6 | ||
|
|
c569d803c5 | ||
|
|
48cd6e112b | ||
|
|
d41354e1d7 | ||
|
|
9a5e36c0a4 | ||
|
|
f963e40a5f | ||
|
|
f1e2402316 | ||
|
|
31bb8c75f5 | ||
|
|
fb4ab44aa5 | ||
|
|
71216b1458 | ||
|
|
c969ff4011 | ||
|
|
dc45eb6ed4 | ||
|
|
29aaaae4b4 | ||
|
|
1fe53b9549 | ||
|
|
6274dbebc0 | ||
|
|
a1cd3ab6fa | ||
|
|
c058cb8848 | ||
|
|
029061177b | ||
|
|
9c8e813642 | ||
|
|
7be61b9657 | ||
|
|
2f67d24ec9 | ||
|
|
17d6f1cdf1 | ||
|
|
a0c34b40c6 | ||
|
|
00856f8cb9 | ||
|
|
3df6387ee0 | ||
|
|
fe1c826b1b | ||
|
|
0bda101d4d | ||
|
|
5e738129d9 | ||
|
|
39b5014add | ||
|
|
3c6560ade9 | ||
|
|
183d1a91c1 | ||
|
|
d711ab8b1f | ||
|
|
6c1a48952e | ||
|
|
a343aa5eb0 | ||
|
|
273a0b0b99 | ||
|
|
2f1e64dc1b | ||
|
|
a46d4ecd3e | ||
|
|
5f04954a5c | ||
|
|
d065a7ca22 | ||
|
|
2f8373f9c5 | ||
|
|
2d36e98246 | ||
|
|
f2d8f1ee60 | ||
|
|
e7881c350c | ||
|
|
ffe3e68c35 | ||
|
|
4d6fafd778 | ||
|
|
2bbf77ab4c |
@@ -1,43 +0,0 @@
|
||||
From dbdd2906c0b3a967ca28c6b870b46f905c170661 Mon Sep 17 00:00:00 2001
|
||||
From: Park Ju Hyung <qkrwngud825@gmail.com>
|
||||
Date: Wed, 13 Mar 2019 13:36:37 +0900
|
||||
Subject: [PATCH] setlocalversion: don't check for uncommitted changes
|
||||
|
||||
I ofter push after the build is done and I hate seeing "-dirty"
|
||||
|
||||
Signed-off-by: Park Ju Hyung <qkrwngud825@gmail.com>
|
||||
Signed-off-by: Danny Lin <danny@kdrag0n.dev>
|
||||
Signed-off-by: Divyanshu-Modi <divyan.m05@gmail.com>
|
||||
Change-Id: I240c516520879da680794fd144b1f273f9e21e13
|
||||
Signed-off-by: Divyanshu-Modi <divyan.m05@gmail.com>
|
||||
---
|
||||
scripts/setlocalversion | 13 -------------
|
||||
1 file changed, 13 deletions(-)
|
||||
|
||||
diff --git a/scripts/setlocalversion b/scripts/setlocalversion
|
||||
index 842936656b84..ef27a273ebf5 100755
|
||||
--- a/scripts/setlocalversion
|
||||
+++ b/scripts/setlocalversion
|
||||
@@ -107,19 +107,6 @@ scm_version()
|
||||
printf -- '-svn%s' "$(git svn find-rev $head)"
|
||||
fi
|
||||
|
||||
- # Check for uncommitted changes.
|
||||
- # First, with git-status, but --no-optional-locks is only
|
||||
- # supported in git >= 2.14, so fall back to git-diff-index if
|
||||
- # it fails. Note that git-diff-index does not refresh the
|
||||
- # index, so it may give misleading results. See
|
||||
- # git-update-index(1), git-diff-index(1), and git-status(1).
|
||||
- if {
|
||||
- git --no-optional-locks status -uno --porcelain 2>/dev/null ||
|
||||
- git diff-index --name-only HEAD
|
||||
- } | grep -qvE '^(.. )?scripts/package'; then
|
||||
- printf '%s' -dirty
|
||||
- fi
|
||||
-
|
||||
# All done with git
|
||||
return
|
||||
fi
|
||||
--
|
||||
2.37.2
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
From bbb9e7fb1ccadac47b58ba615e6874ddeaa9e628 Mon Sep 17 00:00:00 2001
|
||||
From: Park Ju Hyung <qkrwngud825@gmail.com>
|
||||
Date: Wed, 13 Mar 2019 13:36:37 +0900
|
||||
Subject: [PATCH] setlocalversion: don't check for uncommitted changes
|
||||
|
||||
I ofter push after the build is done and I hate seeing "-dirty"
|
||||
|
||||
Signed-off-by: Park Ju Hyung <qkrwngud825@gmail.com>
|
||||
Signed-off-by: Danny Lin <danny@kdrag0n.dev>
|
||||
Signed-off-by: Divyanshu-Modi <divyan.m05@gmail.com>
|
||||
Change-Id: I240c516520879da680794fd144b1f273f9e21e13
|
||||
Signed-off-by: Divyanshu-Modi <divyan.m05@gmail.com>
|
||||
---
|
||||
scripts/setlocalversion | 16 ----------------
|
||||
1 file changed, 16 deletions(-)
|
||||
|
||||
diff --git a/scripts/setlocalversion b/scripts/setlocalversion
|
||||
index 1b733ae4c..2a3ea7684 100755
|
||||
--- a/scripts/setlocalversion
|
||||
+++ b/scripts/setlocalversion
|
||||
@@ -90,22 +90,6 @@ scm_version()
|
||||
printf '%s%s' -g "$(echo $head | cut -c1-12)"
|
||||
fi
|
||||
|
||||
- # Check for uncommitted changes.
|
||||
- # This script must avoid any write attempt to the source tree,
|
||||
- # which might be read-only.
|
||||
- # You cannot use 'git describe --dirty' because it tries to
|
||||
- # create .git/index.lock .
|
||||
- # First, with git-status, but --no-optional-locks is only
|
||||
- # supported in git >= 2.14, so fall back to git-diff-index if
|
||||
- # it fails. Note that git-diff-index does not refresh the
|
||||
- # index, so it may give misleading results. See
|
||||
- # git-update-index(1), git-diff-index(1), and git-status(1).
|
||||
- if {
|
||||
- git --no-optional-locks status -uno --porcelain 2>/dev/null ||
|
||||
- git diff-index --name-only HEAD
|
||||
- } | read dummy; then
|
||||
- printf '%s' -dirty
|
||||
- fi
|
||||
fi
|
||||
}
|
||||
|
||||
--
|
||||
2.37.2
|
||||
|
||||
12
.github/scripts/build_a12.sh
vendored
12
.github/scripts/build_a12.sh
vendored
@@ -11,7 +11,15 @@ build_from_image() {
|
||||
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
|
||||
GKI_URL=https://dl.google.com/android/gki/gki-certified-boot-android12-5.10-"${PATCH_LEVEL}"_r1.zip
|
||||
FALLBACK_URL=https://dl.google.com/android/gki/gki-certified-boot-android12-5.10-2023-01_r1.zip
|
||||
status=$(curl -sL -w "%{http_code}" "$GKI_URL" -o /dev/null)
|
||||
if [ "$status" = "200" ]; then
|
||||
curl -Lo gki-kernel.zip "$GKI_URL"
|
||||
else
|
||||
echo "[+] $GKI_URL not found, using $FALLBACK_URL"
|
||||
curl -Lo gki-kernel.zip "$FALLBACK_URL"
|
||||
fi
|
||||
unzip gki-kernel.zip && rm gki-kernel.zip
|
||||
|
||||
echo '[+] Unpack prebuilt boot.img'
|
||||
@@ -37,7 +45,7 @@ build_from_image() {
|
||||
echo '[+] Compress images'
|
||||
for image in boot*.img; do
|
||||
$GZIP -n -f -9 "$image"
|
||||
mv "$image".gz ksu-"$VERSION"-"$1"-"$image".gz
|
||||
mv "$image".gz "${1//Image-/}"-"$image".gz
|
||||
done
|
||||
|
||||
echo "[+] Images to upload"
|
||||
|
||||
2
.github/scripts/build_a13.sh
vendored
2
.github/scripts/build_a13.sh
vendored
@@ -24,7 +24,7 @@ build_from_image() {
|
||||
echo '[+] Compress images'
|
||||
for image in boot*.img; do
|
||||
$GZIP -n -f -9 "$image"
|
||||
mv "$image".gz ksu-"$VERSION"-"$1"-"$image".gz
|
||||
mv "$image".gz "${1//Image-/}"-"$image".gz
|
||||
done
|
||||
|
||||
echo '[+] Images to upload'
|
||||
|
||||
6
.github/workflows/add-device.yml
vendored
6
.github/workflows/add-device.yml
vendored
@@ -42,12 +42,12 @@ jobs:
|
||||
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
|
||||
- uses: Kernel-SU/actions-comment-on-issue@master
|
||||
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
|
||||
- uses: Kernel-SU/actions-comment-on-issue@master
|
||||
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."
|
||||
@@ -56,4 +56,4 @@ jobs:
|
||||
uses: peter-evans/close-issue@v1
|
||||
with:
|
||||
issue-number: ${{ github.event.issue.number }}
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
31
.github/workflows/build-debug-kernel.yml
vendored
Normal file
31
.github/workflows/build-debug-kernel.yml
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
name: Build debug kernel
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build-debug-kernel-a12:
|
||||
uses: ./.github/workflows/gki-kernel.yml
|
||||
with:
|
||||
version: android12-5.10
|
||||
version_name: android12-5.10.177
|
||||
tag: android12-5.10-2023-06
|
||||
os_patch_level: 2023-06
|
||||
patch_path: "5.10"
|
||||
debug: true
|
||||
build-debug-kernel-a13:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- version: "5.10"
|
||||
sub_level: 177
|
||||
os_patch_level: 2023-06
|
||||
- version: "5.15"
|
||||
sub_level: 94
|
||||
os_patch_level: 2023-06
|
||||
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 }}
|
||||
debug: true
|
||||
12
.github/workflows/build-kernel-a12.yml
vendored
12
.github/workflows/build-kernel-a12.yml
vendored
@@ -34,7 +34,11 @@ jobs:
|
||||
- sub_level: 149
|
||||
os_patch_level: 2023-01
|
||||
- sub_level: 160
|
||||
os_patch_level: 2023-02
|
||||
os_patch_level: 2023-03
|
||||
- sub_level: 168
|
||||
os_patch_level: 2023-05
|
||||
- sub_level: 177
|
||||
os_patch_level: 2023-06
|
||||
uses: ./.github/workflows/gki-kernel.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
@@ -115,7 +119,7 @@ jobs:
|
||||
uses: ./.github/workflows/gki-kernel.yml
|
||||
with:
|
||||
version: android12-5.10
|
||||
version_name: android12-5.10.160
|
||||
tag: android12-5.10-2023-02
|
||||
os_patch_level: 2023-02
|
||||
version_name: android12-5.10.177
|
||||
tag: android12-5.10-2023-06
|
||||
os_patch_level: 2023-06
|
||||
patch_path: "5.10"
|
||||
|
||||
29
.github/workflows/build-kernel-a13.yml
vendored
29
.github/workflows/build-kernel-a13.yml
vendored
@@ -29,22 +29,35 @@ jobs:
|
||||
os_patch_level: 2023-01
|
||||
- version: "5.10"
|
||||
sub_level: 157
|
||||
os_patch_level: 2023-02
|
||||
os_patch_level: 2023-03
|
||||
- version: "5.10"
|
||||
sub_level: 168
|
||||
os_patch_level: 2023-05
|
||||
- version: "5.10"
|
||||
sub_level: 177
|
||||
os_patch_level: 2023-06
|
||||
- version: "5.15"
|
||||
sub_level: 41
|
||||
os_patch_level: 2022-11
|
||||
- version: "5.15"
|
||||
sub_level: 74
|
||||
os_patch_level: 2023-01
|
||||
os_patch_level: 2023-02
|
||||
- version: "5.15"
|
||||
sub_level: 78
|
||||
os_patch_level: 2023-02
|
||||
os_patch_level: 2023-03
|
||||
- version: "5.15"
|
||||
sub_level: 94
|
||||
os_patch_level: 2023-05
|
||||
- version: "5.15"
|
||||
sub_level: 104
|
||||
os_patch_level: 2023-06
|
||||
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 }}
|
||||
os_patch_level: ${{ matrix.os_patch_level }}
|
||||
patch_path: ${{ matrix.version }}
|
||||
upload-artifacts:
|
||||
needs: build-kernel
|
||||
@@ -119,14 +132,14 @@ jobs:
|
||||
matrix:
|
||||
include:
|
||||
- version: "5.10"
|
||||
sub_level: 149
|
||||
os_patch_level: 2022-11
|
||||
sub_level: 177
|
||||
os_patch_level: 2023-06
|
||||
- version: "5.15"
|
||||
sub_level: 74
|
||||
os_patch_level: 2023-01
|
||||
sub_level: 104
|
||||
os_patch_level: 2023-06
|
||||
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 }}
|
||||
patch_path: ${{ matrix.version }}
|
||||
|
||||
137
.github/workflows/build-kernel-arcvm.yml
vendored
Normal file
137
.github/workflows/build-kernel-arcvm.yml
vendored
Normal file
@@ -0,0 +1,137 @@
|
||||
name: Build Kernel - ChromeOS ARCVM
|
||||
on:
|
||||
push:
|
||||
branches: ["main"]
|
||||
paths:
|
||||
- ".github/workflows/build-kernel-arcvm.yml"
|
||||
- "kernel/**"
|
||||
pull_request:
|
||||
branches: ["main"]
|
||||
paths:
|
||||
- ".github/workflows/build-kernel-arcvm.yml"
|
||||
- "kernel/**"
|
||||
workflow_call:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
arch: [x86_64]
|
||||
version: ["5.10.178"]
|
||||
include:
|
||||
- arch: x86_64
|
||||
git_tag: chromeos-5.10-arcvm
|
||||
file_name: "bzImage"
|
||||
|
||||
name: Build ChromeOS ARCVM kernel
|
||||
runs-on: ubuntu-20.04
|
||||
env:
|
||||
LTO: thin
|
||||
ROOT_DIR: /
|
||||
KERNEL_DIR: ${{ github.workspace }}/kernel
|
||||
|
||||
steps:
|
||||
- name: Install Build Tools
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get 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 gzip \
|
||||
rsync python3 device-tree-compiler
|
||||
|
||||
sudo ln -s --force python3 /usr/bin/python
|
||||
|
||||
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: Checkout KernelSU
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
path: KernelSU
|
||||
ref: main
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup kernel source
|
||||
run: git clone https://chromium.googlesource.com/chromiumos/third_party/kernel.git -b ${{ matrix.git_tag }} --depth=1
|
||||
|
||||
- name: Setup KernelSU
|
||||
working-directory: kernel
|
||||
run: |
|
||||
echo "[+] KernelSU setup"
|
||||
KERNEL_ROOT=$GITHUB_WORKSPACE/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 "[+] Patch script/setlocalversion"
|
||||
sed -i 's/-dirty//g' $KERNEL_ROOT/scripts/setlocalversion
|
||||
|
||||
echo "[+] KernelSU setup done."
|
||||
cd $GITHUB_WORKSPACE/KernelSU
|
||||
VERSION=$(($(git rev-list --count HEAD) + 10200))
|
||||
echo "VERSION: $VERSION"
|
||||
echo "kernelsu_version=$VERSION" >> $GITHUB_ENV
|
||||
|
||||
- name: Build Kernel
|
||||
working-directory: kernel
|
||||
run: |
|
||||
set -a && . build.config.gki.x86_64; set +a
|
||||
export DEFCONFIG=x86_64_arcvm_defconfig
|
||||
|
||||
make LLVM=1 LLVM_IAS=1 DEPMOD=depmod DTC=dtc O=${PWD} mrproper
|
||||
make LLVM=1 LLVM_IAS=1 DEPMOD=depmod DTC=dtc O=${PWD} ${DEFCONFIG} < /dev/null
|
||||
scripts/config --file .config -e LTO_CLANG -d LTO_NONE -e LTO_CLANG_THIN -d LTO_CLANG_FULL -e THINLTO
|
||||
make LLVM=1 LLVM_IAS=1 DEPMOD=depmod DTC=dtc O=${PWD} -j$(nproc) bzImage modules prepare-objtool
|
||||
|
||||
echo "file_path=${PWD}/arch/x86/boot/bzImage" >> $GITHUB_ENV
|
||||
|
||||
- name: Upload kernel-ARCVM-${{ matrix.arch }}-${{ matrix.version }}
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: kernel-ARCVM-${{ matrix.arch }}-${{ matrix.version }}
|
||||
path: "${{ env.file_path }}"
|
||||
|
||||
- name: Post to Telegram
|
||||
if: ${{ ( github.event_name == 'push' && github.ref == 'refs/heads/main' ) || github.ref_type == 'tag' }}
|
||||
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 }}
|
||||
run: |
|
||||
TITLE=kernel-ARCVM-${{ matrix.arch }}-${{ matrix.version }}
|
||||
echo "[+] title: $TITLE"
|
||||
export TITLE
|
||||
export VERSION="${{ env.kernelsu_version }}"
|
||||
echo "[+] Compress images"
|
||||
gzip -n -f -9 "${{ env.file_path }}"
|
||||
echo "[+] Image to upload"
|
||||
ls -l "${{ env.file_path }}.gz"
|
||||
if [ -n "${{ secrets.BOT_TOKEN }}" ]; then
|
||||
pip3 install python-telegram-bot
|
||||
python3 "$GITHUB_WORKSPACE/KernelSU/scripts/ksubot.py" "${{ env.file_path }}.gz"
|
||||
fi
|
||||
@@ -1,13 +1,14 @@
|
||||
name: Build WSA-5.10.117-Kernel
|
||||
name: Build Kernel - WSA
|
||||
on:
|
||||
push:
|
||||
branches: ["main"]
|
||||
paths:
|
||||
- ".github/workflows/build-WSA-5.10.117-kernel.yml"
|
||||
- ".github/workflows/build-kernel-wsa.yml"
|
||||
- "kernel/**"
|
||||
pull_request:
|
||||
branches: ["main"]
|
||||
paths:
|
||||
- ".github/workflows/build-kernel-wsa.yml"
|
||||
- "kernel/**"
|
||||
workflow_call:
|
||||
workflow_dispatch:
|
||||
@@ -17,29 +18,45 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
arch: [x86_64, arm64]
|
||||
version: [5.10.117.2]
|
||||
version: ["5.15.78.1", "5.15.94.4"]
|
||||
include:
|
||||
- file_name: "bzImage"
|
||||
make_config: "config-wsa"
|
||||
arch: x86_64
|
||||
- file_name: "Image"
|
||||
- arch: x86_64
|
||||
file_name: "bzImage"
|
||||
- arch: arm64
|
||||
file_name: "Image"
|
||||
cross_compile: "aarch64-linux-gnu"
|
||||
make_config: "config-wsa-arm64"
|
||||
- version: "5.15.78.1"
|
||||
arch: x86_64
|
||||
make_config: config-wsa-x64
|
||||
- version: "5.15.78.1"
|
||||
arch: arm64
|
||||
make_config: config-wsa-arm64
|
||||
- version: "5.15.94.4"
|
||||
arch: x86_64
|
||||
make_config: config-wsa-x64
|
||||
- version: "5.15.94.4"
|
||||
arch: arm64
|
||||
make_config: config-wsa-arm64
|
||||
- version: "5.15.78.1"
|
||||
device_code: latte-2
|
||||
kernel_version: "5.15"
|
||||
- version: "5.15.94.4"
|
||||
device_code: latte-2
|
||||
kernel_version: "5.15"
|
||||
|
||||
|
||||
name: Build WSA-Kernel-${{ matrix.version }}-${{ matrix.arch }}
|
||||
runs-on: ubuntu-20.04
|
||||
env:
|
||||
CCACHE_COMPILERCHECK: "%compiler% -dumpmachine; %compiler% -dumpversion"
|
||||
CCACHE_NOHASHDIR: "true"
|
||||
CCACHE_MAXSIZE: "2G"
|
||||
CCACHE_HARDLINK: "true"
|
||||
|
||||
steps:
|
||||
- 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
|
||||
sudo apt-get update
|
||||
sudo apt-get 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 gzip
|
||||
export LLVM_VERSION=12
|
||||
wget https://apt.llvm.org/llvm.sh
|
||||
chmod +x llvm.sh
|
||||
@@ -66,14 +83,15 @@ jobs:
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: microsoft/WSA-Linux-Kernel
|
||||
ref: android-lts/latte/${{ matrix.version }}
|
||||
ref: android-lts/${{ matrix.device_code }}/${{ matrix.version }}
|
||||
path: WSA-Linux-Kernel
|
||||
|
||||
- name: Setup Ccache
|
||||
uses: hendrikmuhs/ccache-action@v1.2
|
||||
with:
|
||||
key: WSA-Kernel-${{ matrix.version }}-${{ matrix.arch }}
|
||||
save: ${{ github.event_name != 'pull_request' }}
|
||||
save: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
|
||||
max-size: 2G
|
||||
|
||||
- name: Setup KernelSU
|
||||
working-directory: WSA-Linux-Kernel
|
||||
@@ -87,17 +105,47 @@ jobs:
|
||||
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
|
||||
cd $KERNEL_ROOT && git apply $GITHUB_WORKSPACE/KernelSU/.github/patches/${{ matrix.kernel_version }}/*.patch
|
||||
echo "[+] KernelSU setup done."
|
||||
cd $GITHUB_WORKSPACE/KernelSU
|
||||
VERSION=$(($(git rev-list --count HEAD) + 10200))
|
||||
echo "VERSION: $VERSION"
|
||||
echo "kernelsu_version=$VERSION" >> $GITHUB_ENV
|
||||
|
||||
- name: Build Kernel
|
||||
working-directory: WSA-Linux-Kernel
|
||||
run: |
|
||||
cp configs/wsa/${{ matrix.make_config }}-5.10 .config
|
||||
cp configs/wsa/${{ matrix.make_config }} .config
|
||||
make olddefconfig
|
||||
make -j`nproc` LLVM=1 ARCH=${{ matrix.arch }} CROSS_COMPILE=${{ matrix.cross_compile }} ${{ matrix.file_name }} CCACHE="/usr/bin/ccache"
|
||||
echo "file_path=WSA-Linux-Kernel/arch/${{ matrix.arch }}/boot/${{ matrix.file_name }}" >> $GITHUB_ENV
|
||||
|
||||
- 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 }}
|
||||
path: "${{ env.file_path }}"
|
||||
|
||||
- name: Post to Telegram
|
||||
if: ${{ ( github.event_name == 'push' && github.ref == 'refs/heads/main' ) || github.ref_type == 'tag' }}
|
||||
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 }}
|
||||
run: |
|
||||
TITLE=kernel-${{ matrix.arch }}-WSA-${{ matrix.version }}
|
||||
echo "[+] title: $TITLE"
|
||||
export TITLE
|
||||
export VERSION="${{ env.kernelsu_version }}"
|
||||
echo "[+] Compress images"
|
||||
gzip -n -f -9 "${{ env.file_path }}"
|
||||
echo "[+] Image to upload"
|
||||
ls -l "${{ env.file_path }}.gz"
|
||||
if [ -n "${{ secrets.BOT_TOKEN }}" ]; then
|
||||
pip3 install python-telegram-bot
|
||||
python3 "$GITHUB_WORKSPACE/KernelSU/scripts/ksubot.py" "${{ env.file_path }}.gz"
|
||||
fi
|
||||
177
.github/workflows/build-manager.yml
vendored
177
.github/workflows/build-manager.yml
vendored
@@ -1,16 +1,18 @@
|
||||
name: Build Manager
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
paths:
|
||||
paths:
|
||||
- '.github/workflows/build-manager.yml'
|
||||
- 'manager/**'
|
||||
- 'userspace/ksud/**'
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
paths:
|
||||
paths:
|
||||
- 'manager/**'
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
build-ksud:
|
||||
strategy:
|
||||
@@ -21,87 +23,104 @@ jobs:
|
||||
uses: ./.github/workflows/ksud.yml
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
|
||||
build-manager:
|
||||
needs: build-ksud
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./manager
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- 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: set up JDK 11
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: '11'
|
||||
distribution: 'temurin'
|
||||
cache: gradle
|
||||
- uses: nttld/setup-ndk@v1
|
||||
with:
|
||||
ndk-version: r25b
|
||||
local-cache: true
|
||||
- name: Extract keystore
|
||||
if: ${{ ( github.event_name != 'pull_request' && github.ref == 'refs/heads/main' ) || github.ref_type == 'tag' }}
|
||||
run: |
|
||||
if [ ! -z "${{ secrets.KEYSTORE }}" ]; then
|
||||
echo KEYSTORE_PASSWORD='${{ secrets.KEYSTORE_PASSWORD }}' >> sign.properties
|
||||
echo KEY_ALIAS='${{ secrets.KEY_ALIAS }}' >> sign.properties
|
||||
echo KEY_PASSWORD='${{ secrets.KEY_PASSWORD }}' >> sign.properties
|
||||
echo KEYSTORE_FILE='../key.jks' >> sign.properties
|
||||
echo ${{ secrets.KEYSTORE }} | base64 --decode > key.jks
|
||||
fi
|
||||
- name: Download arm64 ksud
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: ksud-aarch64-linux-android
|
||||
path: .
|
||||
- name: Download x86_64 ksud
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: ksud-x86_64-linux-android
|
||||
path: .
|
||||
- name: Copy ksud to app jniLibs
|
||||
run: |
|
||||
mkdir -p app/src/main/jniLibs/arm64-v8a
|
||||
mkdir -p app/src/main/jniLibs/x86_64
|
||||
cp -f ../aarch64-linux-android/release/ksud ../manager/app/src/main/jniLibs/arm64-v8a/libksud.so
|
||||
cp -f ../x86_64-linux-android/release/ksud ../manager/app/src/main/jniLibs/x86_64/libksud.so
|
||||
- name: Grant execute permission for gradlew
|
||||
run: chmod +x gradlew
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew clean assembleRelease
|
||||
- name: Upload build artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: manager
|
||||
path: manager/app/build/outputs/apk/release/*.apk
|
||||
- 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: Manager
|
||||
run: |
|
||||
if [ ! -z "${{ secrets.BOT_TOKEN }}" ]; then
|
||||
export VERSION=$(git rev-list --count HEAD)
|
||||
APK=$(find ./app/build/outputs/apk/release -name "*.apk")
|
||||
pip3 install python-telegram-bot
|
||||
python3 $GITHUB_WORKSPACE/scripts/ksubot.py $APK
|
||||
fi
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- 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: Write key
|
||||
if: ${{ ( github.event_name != 'pull_request' && github.ref == 'refs/heads/main' ) || github.ref_type == 'tag' }}
|
||||
run: |
|
||||
if [ ! -z "${{ secrets.KEYSTORE }}" ]; then
|
||||
echo KEYSTORE_PASSWORD='${{ secrets.KEYSTORE_PASSWORD }}' >> gradle.properties
|
||||
echo KEY_ALIAS='${{ secrets.KEY_ALIAS }}' >> gradle.properties
|
||||
echo KEY_PASSWORD='${{ secrets.KEY_PASSWORD }}' >> gradle.properties
|
||||
echo KEYSTORE_FILE='../key.jks' >> gradle.properties
|
||||
echo ${{ secrets.KEYSTORE }} | base64 --decode > key.jks
|
||||
fi
|
||||
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: "temurin"
|
||||
java-version: "17"
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/gradle-build-action@v2
|
||||
with:
|
||||
gradle-home-cache-cleanup: true
|
||||
|
||||
- name: Download arm64 ksud
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: ksud-aarch64-linux-android
|
||||
path: .
|
||||
|
||||
- name: Download x86_64 ksud
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: ksud-x86_64-linux-android
|
||||
path: .
|
||||
|
||||
- name: Copy ksud to app jniLibs
|
||||
run: |
|
||||
mkdir -p app/src/main/jniLibs/arm64-v8a
|
||||
mkdir -p app/src/main/jniLibs/x86_64
|
||||
cp -f ../aarch64-linux-android/release/ksud ../manager/app/src/main/jniLibs/arm64-v8a/libksud.so
|
||||
cp -f ../x86_64-linux-android/release/ksud ../manager/app/src/main/jniLibs/x86_64/libksud.so
|
||||
|
||||
- name: Build with Gradle
|
||||
run: |
|
||||
echo 'org.gradle.parallel=true' >> gradle.properties
|
||||
echo 'org.gradle.vfs.watch=true' >> gradle.properties
|
||||
echo 'org.gradle.jvmargs=-Xmx2048m' >> gradle.properties
|
||||
echo 'android.native.buildOutput=verbose' >> gradle.properties
|
||||
sed -i 's/org.gradle.configuration-cache=true//g' gradle.properties
|
||||
./gradlew clean assembleRelease
|
||||
|
||||
- name: Upload build artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: manager
|
||||
path: manager/app/build/outputs/apk/release/*.apk
|
||||
|
||||
- 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: Manager
|
||||
run: |
|
||||
if [ ! -z "${{ secrets.BOT_TOKEN }}" ]; then
|
||||
export VERSION=$(git rev-list --count HEAD)
|
||||
APK=$(find ./app/build/outputs/apk/release -name "*.apk")
|
||||
pip3 install python-telegram-bot
|
||||
python3 $GITHUB_WORKSPACE/scripts/ksubot.py $APK
|
||||
fi
|
||||
|
||||
22
.github/workflows/clippy-pr.yml
vendored
22
.github/workflows/clippy-pr.yml
vendored
@@ -1,22 +0,0 @@
|
||||
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
|
||||
15
.github/workflows/clippy.yml
vendored
15
.github/workflows/clippy.yml
vendored
@@ -3,7 +3,13 @@ name: Clippy check
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
- main
|
||||
paths:
|
||||
- '.github/workflows/clippy.yml'
|
||||
- 'userspace/ksud/**'
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- '.github/workflows/clippy.yml'
|
||||
- 'userspace/ksud/**'
|
||||
@@ -16,7 +22,8 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
# 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
|
||||
@@ -26,5 +33,5 @@ jobs:
|
||||
|
||||
- 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
|
||||
cross clippy --manifest-path userspace/ksud/Cargo.toml --target aarch64-linux-android --release
|
||||
cross clippy --manifest-path userspace/ksud/Cargo.toml --target x86_64-linux-android --release
|
||||
|
||||
36
.github/workflows/gki-kernel.yml
vendored
36
.github/workflows/gki-kernel.yml
vendored
@@ -45,6 +45,10 @@ on:
|
||||
description: >
|
||||
Artifact name of prebuilt ksud to be embedded
|
||||
for example: ksud-aarch64-linux-android
|
||||
debug:
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
secrets:
|
||||
BOOT_SIGN_KEY:
|
||||
required: false
|
||||
@@ -64,7 +68,6 @@ jobs:
|
||||
env:
|
||||
CCACHE_COMPILERCHECK: "%compiler% -dumpmachine; %compiler% -dumpversion"
|
||||
CCACHE_NOHASHDIR: "true"
|
||||
CCACHE_MAXSIZE: "2G"
|
||||
CCACHE_HARDLINK: "true"
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@@ -72,13 +75,6 @@ jobs:
|
||||
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: |
|
||||
@@ -94,11 +90,18 @@ jobs:
|
||||
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 }}
|
||||
REMOTE_BRANCH=$(git ls-remote https://android.googlesource.com/kernel/common ${{ inputs.tag }})
|
||||
if grep -q deprecated <<< $REMOTE_BRANCH; then
|
||||
echo "Found deprecated branch: ${{ inputs.tag }}"
|
||||
sed -i 's/"${{ inputs.tag }}"/"deprecated\/${{ inputs.tag }}"/g' .repo/manifests/default.xml
|
||||
cat .repo/manifests/default.xml
|
||||
fi
|
||||
../git-repo/repo sync -j$(nproc --all)
|
||||
|
||||
- name: Setup KernelSU
|
||||
env:
|
||||
PATCH_PATH: ${{ inputs.patch_path }}
|
||||
IS_DEBUG_KERNEL: ${{ inputs.debug }}
|
||||
run: |
|
||||
cd $GITHUB_WORKSPACE/android-kernel
|
||||
echo "[+] KernelSU setup"
|
||||
@@ -111,6 +114,13 @@ jobs:
|
||||
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 "[+] Patch script/setlocalversion"
|
||||
sed -i 's/-dirty//g' $GKI_ROOT/common/scripts/setlocalversion
|
||||
|
||||
if [ "$IS_DEBUG_KERNEL" = "true" ]; then
|
||||
echo "[+] Enable debug features for kernel"
|
||||
echo "ccflags-y += -DCONFIG_KSU_DEBUG" >> $GITHUB_WORKSPACE/KernelSU/kernel/Makefile
|
||||
fi
|
||||
echo "[+] KernelSU setup done."
|
||||
|
||||
- name: Symbol magic
|
||||
@@ -124,6 +134,14 @@ jobs:
|
||||
echo "[+] Add KernelSU symbols"
|
||||
cat $KSU_ROOT/kernel/export_symbol.txt | awk '{sub("[ \t]+","");print " "$0}' >> $SYMBOL_LIST
|
||||
|
||||
- name: Setup ccache
|
||||
if: inputs.use_cache == true
|
||||
uses: hendrikmuhs/ccache-action@v1.2
|
||||
with:
|
||||
key: gki-kernel-aarch64-${{ inputs.version_name }}
|
||||
max-size: 2G
|
||||
save: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
|
||||
|
||||
- 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
|
||||
@@ -149,4 +167,4 @@ jobs:
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: AnyKernel3-${{ inputs.version_name }}_${{ inputs.os_patch_level }}
|
||||
path: ./AnyKernel3/*
|
||||
path: ./AnyKernel3/*
|
||||
|
||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
build-a13-kernel:
|
||||
uses: ./.github/workflows/build-kernel-a13.yml
|
||||
build-wsa-kernel:
|
||||
uses: ./.github/workflows/build-WSA-5.10.117-kernel.yml
|
||||
uses: ./.github/workflows/build-kernel-wsa.yml
|
||||
release:
|
||||
needs:
|
||||
- build-manager
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1 +1,2 @@
|
||||
.vscode/
|
||||
.idea
|
||||
.vscode
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
**English** | [中文](README_CN.md)
|
||||
**English** | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | [日本語](README_JP.md) | [Portuguese-Brazil](README_PT-BR.md) | [Türkçe](README_TR.md)
|
||||
|
||||
# KernelSU
|
||||
|
||||
@@ -11,7 +11,7 @@ A Kernel based root solution for Android devices.
|
||||
|
||||
## Compatibility State
|
||||
|
||||
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.
|
||||
KernelSU officially supports Android GKI 2.0 devices(with kernel 5.10+), old kernels(4.14+) is also compatible, but you need to build kernel yourself.
|
||||
|
||||
WSA and containter-based Android should also work with KernelSU integrated.
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
[English](README.md) | **中文**
|
||||
[English](README.md) | **简体中文** | [繁體中文](README_TW.md) | [日本語](README_JP.md) | [Portuguese-Brazil](README_PT-BR.md) | [Türkçe](README_TR.md)
|
||||
|
||||
# KernelSU
|
||||
|
||||
@@ -37,6 +37,6 @@ WSA 和运行在容器上的 Android 也可以与 KernelSU 一起工作。
|
||||
## 鸣谢
|
||||
|
||||
- [kernel-assisted-superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/):KernelSU 的灵感。
|
||||
- [true](https://github.com/brevent/genuine/):apk v2 签名验证。
|
||||
- [genuine](https://github.com/brevent/genuine/):apk v2 签名验证。
|
||||
- [Diamorphine](https://github.com/m0nad/Diamorphine):一些 rootkit 技巧。
|
||||
- [Magisk](https://github.com/topjohnwu/Magisk):sepolicy 的实现。
|
||||
|
||||
42
README_JP.md
Normal file
42
README_JP.md
Normal file
@@ -0,0 +1,42 @@
|
||||
[English](README.md) | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | **日本語** | [Portuguese-Brazil](README_PT-BR.md) | [Türkçe](README_TR.md)
|
||||
|
||||
# KernelSU
|
||||
|
||||
Android におけるカーネルベースの root ソリューションです。
|
||||
|
||||
## 特徴
|
||||
|
||||
1. カーネルベースの `su` と権限管理
|
||||
2. OverlayFS に基づくモジュールシステム
|
||||
|
||||
## 対応状況
|
||||
|
||||
KernelSU は GKI 2.0 デバイス(カーネルバージョン 5.10 以上)を公式にサポートしています。古いカーネル(4.14以上)とも互換性がありますが、自分でカーネルをビルドする必要があります。
|
||||
|
||||
WSA とコンテナ上で動作する Android でも KernelSU を統合して動かせます。
|
||||
|
||||
現在サポートしているアーキテクチャは `arm64-v8a` および `x86_64` です。
|
||||
|
||||
## 使用方法
|
||||
|
||||
[インストール方法はこちら](https://kernelsu.org/ja_JP/guide/installation.html)
|
||||
|
||||
## ビルド
|
||||
|
||||
[ビルド方法はこちら](https://kernelsu.org/guide/how-to-build.html)
|
||||
|
||||
### ディスカッション
|
||||
|
||||
- Telegram: [@KernelSU](https://t.me/KernelSU)
|
||||
|
||||
## ライセンス
|
||||
|
||||
- `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)
|
||||
|
||||
## クレジット
|
||||
|
||||
- [kernel-assisted-superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/):KernelSU のアイデア元
|
||||
- [genuine](https://github.com/brevent/genuine/):apk v2 の署名検証
|
||||
- [Diamorphine](https://github.com/m0nad/Diamorphine): rootkit のスキル
|
||||
- [Magisk](https://github.com/topjohnwu/Magisk):sepolicy の実装
|
||||
47
README_PT-BR.md
Normal file
47
README_PT-BR.md
Normal file
@@ -0,0 +1,47 @@
|
||||
[English](README.md) | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | [日本語](README_JP.md) | **Portuguese-Brazil** | [Türkçe](README_TR.md)
|
||||
|
||||
# KernelSU
|
||||
|
||||
Uma solução raiz baseada em Kernel para dispositivos Android.
|
||||
|
||||
## Características
|
||||
|
||||
1. `su` baseado em kernel e gerenciamento de acesso root.
|
||||
|
||||
2. Sistema modular baseado em overlayfs.
|
||||
|
||||
## Estado de compatibilidade
|
||||
|
||||
O KernelSU suporta oficialmente dispositivos Android GKI 2.0 (com kernel 5.10+), kernels antigos (4.14+) também são compatíveis, mas você mesmo precisa construir o kernel.
|
||||
|
||||
O Android baseado em WSA e contêiner também deve funcionar com o KernelSU integrado.
|
||||
|
||||
E os ABIs atualmente suportados são: `arm64-v8a` e `x86_64`
|
||||
|
||||
## Uso
|
||||
|
||||
[Instalação](https://kernelsu.org/guide/installation.html)
|
||||
|
||||
## Construir
|
||||
|
||||
[Como construir?](https://kernelsu.org/guide/how-to-build.html)
|
||||
|
||||
### Discussão
|
||||
|
||||
- Telegram: [@KernelSU](https://t.me/KernelSU)
|
||||
|
||||
## Licença
|
||||
|
||||
- Os arquivos no diretório `kernel` são [GPL-2](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
|
||||
|
||||
- Todas as outras partes, exceto o diretório `kernel`, são [GPL-3](https://www.gnu.org/licenses/gpl-3.0.html)
|
||||
|
||||
## Créditos
|
||||
|
||||
- [kernel-assisted-superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/): a ideia do KernelSU.
|
||||
|
||||
- [genuine](https://github.com/brevent/genuine/): validação de assinatura apk v2.
|
||||
|
||||
- [Diamorphine](https://github.com/m0nad/Diamorphine): algumas habilidades de rootkit.
|
||||
|
||||
- [Magisk](https://github.com/topjohnwu/Magisk): a implementação da sepolicy.
|
||||
42
README_TR.md
Normal file
42
README_TR.md
Normal file
@@ -0,0 +1,42 @@
|
||||
[English](README.md) | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | [日本語](README_JP.md) | [Portuguese-Brazil](README_PT-BR.md) | **Türkçe**
|
||||
|
||||
# KernelSU
|
||||
|
||||
Android cihazlar için kernel tabanlı bir root çözümü.
|
||||
|
||||
## Özellikler
|
||||
|
||||
1. Kernel-tabanlı `su` ve root erişimi yönetimi.
|
||||
2. Overlayfs'ye dayalı modül sistemi.
|
||||
|
||||
## Uyumluluk Durumu
|
||||
|
||||
KernelSU resmi olarak Android GKI 2.0 cihazlarını ( 5.10+ kernelli) destekler, eski kernellerle de (4.14+) uyumludur, ancak kerneli kendinizin inşaa etmesi gerekir.
|
||||
|
||||
WSA ve konteyner tabanlı Android'in de, KernelSU ile entegre olarak da çalışması gerekmektedir.
|
||||
|
||||
Ve desteklenen mevcut ABI'ler : `arm64-v8a` ve `x86_64`
|
||||
|
||||
## Kullanım
|
||||
|
||||
[Yükleme](https://kernelsu.org/guide/installation.html)
|
||||
|
||||
## İnşaa
|
||||
|
||||
[Nasıl inşa edilir?](https://kernelsu.org/guide/how-to-build.html)
|
||||
|
||||
### Tartışma
|
||||
|
||||
- Telegram: [@KernelSU](https://t.me/KernelSU)
|
||||
|
||||
## Lisans
|
||||
|
||||
- `kernel` klasöründeki dosyalar [GPL-2](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) lisansı altındadır.
|
||||
- `kernel` klasörü dışındaki bütün diğer bölümler [GPL-3](https://www.gnu.org/licenses/gpl-3.0.html) lisansı altındadır.
|
||||
|
||||
## Krediler
|
||||
|
||||
- [kernel-assisted-superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/): KernelSU fikri.
|
||||
- [genuine](https://github.com/brevent/genuine/): apk v2 imza doğrulama.
|
||||
- [Diamorphine](https://github.com/m0nad/Diamorphine): bazı rootkit becerileri.
|
||||
- [Magisk](https://github.com/topjohnwu/Magisk): sepolicy uygulaması.
|
||||
42
README_TW.md
Normal file
42
README_TW.md
Normal file
@@ -0,0 +1,42 @@
|
||||
[English](README.md) | [简体中文](README_CN.md) | **繁體中文** | [日本語](README_JP.md) | [Portuguese-Brazil](README_PT-BR.md) | [Türkçe](README_TR.md)
|
||||
|
||||
# KernelSU
|
||||
|
||||
一個基於核心的 Android 裝置 Root 解決方案
|
||||
|
||||
## 功能
|
||||
|
||||
- 基於核心的 Su 和 Root 存取權管理。
|
||||
- 基於 Overlayfs 的模組系統。
|
||||
|
||||
## 相容性狀態
|
||||
|
||||
KernelSU 官方支援 Android GKI 2.0 的裝置 (核心版本 5.10+);舊版核心同樣相容 (最低 4.14+),但需要自行編譯核心。
|
||||
|
||||
WSA 和執行在容器中的 Android 也可以與 KernelSU 一同運作。
|
||||
|
||||
目前支援架構:`arm64-v8a` 和 `x86_64`
|
||||
|
||||
## 使用方法
|
||||
|
||||
[安裝教學](https://kernelsu.org/zh_TW/guide/installation.html)
|
||||
|
||||
## 建置
|
||||
|
||||
[如何建置?](https://kernelsu.org/zh_TW/guide/how-to-build.html)
|
||||
|
||||
### 討論
|
||||
|
||||
- Telegram:[@KernelSU](https://t.me/KernelSU)
|
||||
|
||||
## 授權
|
||||
|
||||
- 目錄 `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)
|
||||
|
||||
## 致謝
|
||||
|
||||
- [kernel-assisted-superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/):KernelSU 的靈感。
|
||||
- [genuine](https://github.com/brevent/genuine/):apk v2 簽章驗證。
|
||||
- [Diamorphine](https://github.com/m0nad/Diamorphine):一些 rootkit 技巧。
|
||||
- [Magisk](https://github.com/topjohnwu/Magisk):sepolicy 實作。
|
||||
548
kernel/.clang-format
Normal file
548
kernel/.clang-format
Normal file
@@ -0,0 +1,548 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
#
|
||||
# clang-format configuration file. Intended for clang-format >= 4.
|
||||
#
|
||||
# For more information, see:
|
||||
#
|
||||
# Documentation/process/clang-format.rst
|
||||
# https://clang.llvm.org/docs/ClangFormat.html
|
||||
# https://clang.llvm.org/docs/ClangFormatStyleOptions.html
|
||||
#
|
||||
---
|
||||
AccessModifierOffset: -4
|
||||
AlignAfterOpenBracket: Align
|
||||
AlignConsecutiveAssignments: false
|
||||
AlignConsecutiveDeclarations: false
|
||||
#AlignEscapedNewlines: Left # Unknown to clang-format-4.0
|
||||
AlignOperands: true
|
||||
AlignTrailingComments: false
|
||||
AllowAllParametersOfDeclarationOnNextLine: false
|
||||
AllowShortBlocksOnASingleLine: false
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: None
|
||||
AllowShortIfStatementsOnASingleLine: false
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
AlwaysBreakAfterDefinitionReturnType: None
|
||||
AlwaysBreakAfterReturnType: None
|
||||
AlwaysBreakBeforeMultilineStrings: false
|
||||
AlwaysBreakTemplateDeclarations: false
|
||||
BinPackArguments: true
|
||||
BinPackParameters: true
|
||||
BraceWrapping:
|
||||
AfterClass: false
|
||||
AfterControlStatement: false
|
||||
AfterEnum: false
|
||||
AfterFunction: true
|
||||
AfterNamespace: true
|
||||
AfterObjCDeclaration: false
|
||||
AfterStruct: false
|
||||
AfterUnion: false
|
||||
#AfterExternBlock: false # Unknown to clang-format-5.0
|
||||
BeforeCatch: false
|
||||
BeforeElse: false
|
||||
IndentBraces: false
|
||||
#SplitEmptyFunction: true # Unknown to clang-format-4.0
|
||||
#SplitEmptyRecord: true # Unknown to clang-format-4.0
|
||||
#SplitEmptyNamespace: true # Unknown to clang-format-4.0
|
||||
BreakBeforeBinaryOperators: None
|
||||
BreakBeforeBraces: Custom
|
||||
#BreakBeforeInheritanceComma: false # Unknown to clang-format-4.0
|
||||
BreakBeforeTernaryOperators: false
|
||||
BreakConstructorInitializersBeforeComma: false
|
||||
#BreakConstructorInitializers: BeforeComma # Unknown to clang-format-4.0
|
||||
BreakAfterJavaFieldAnnotations: false
|
||||
BreakStringLiterals: false
|
||||
ColumnLimit: 80
|
||||
CommentPragmas: '^ IWYU pragma:'
|
||||
#CompactNamespaces: false # Unknown to clang-format-4.0
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: false
|
||||
ConstructorInitializerIndentWidth: 8
|
||||
ContinuationIndentWidth: 8
|
||||
Cpp11BracedListStyle: false
|
||||
DerivePointerAlignment: false
|
||||
DisableFormat: false
|
||||
ExperimentalAutoDetectBinPacking: false
|
||||
#FixNamespaceComments: false # Unknown to clang-format-4.0
|
||||
|
||||
# Taken from:
|
||||
# git grep -h '^#define [^[:space:]]*for_each[^[:space:]]*(' include/ \
|
||||
# | sed "s,^#define \([^[:space:]]*for_each[^[:space:]]*\)(.*$, - '\1'," \
|
||||
# | sort | uniq
|
||||
ForEachMacros:
|
||||
- 'apei_estatus_for_each_section'
|
||||
- 'ata_for_each_dev'
|
||||
- 'ata_for_each_link'
|
||||
- '__ata_qc_for_each'
|
||||
- 'ata_qc_for_each'
|
||||
- 'ata_qc_for_each_raw'
|
||||
- 'ata_qc_for_each_with_internal'
|
||||
- 'ax25_for_each'
|
||||
- 'ax25_uid_for_each'
|
||||
- '__bio_for_each_bvec'
|
||||
- 'bio_for_each_bvec'
|
||||
- 'bio_for_each_bvec_all'
|
||||
- 'bio_for_each_integrity_vec'
|
||||
- '__bio_for_each_segment'
|
||||
- 'bio_for_each_segment'
|
||||
- 'bio_for_each_segment_all'
|
||||
- 'bio_list_for_each'
|
||||
- 'bip_for_each_vec'
|
||||
- 'bitmap_for_each_clear_region'
|
||||
- 'bitmap_for_each_set_region'
|
||||
- 'blkg_for_each_descendant_post'
|
||||
- 'blkg_for_each_descendant_pre'
|
||||
- 'blk_queue_for_each_rl'
|
||||
- 'bond_for_each_slave'
|
||||
- 'bond_for_each_slave_rcu'
|
||||
- 'bpf_for_each_spilled_reg'
|
||||
- 'btree_for_each_safe128'
|
||||
- 'btree_for_each_safe32'
|
||||
- 'btree_for_each_safe64'
|
||||
- 'btree_for_each_safel'
|
||||
- 'card_for_each_dev'
|
||||
- 'cgroup_taskset_for_each'
|
||||
- 'cgroup_taskset_for_each_leader'
|
||||
- 'cpufreq_for_each_entry'
|
||||
- 'cpufreq_for_each_entry_idx'
|
||||
- 'cpufreq_for_each_valid_entry'
|
||||
- 'cpufreq_for_each_valid_entry_idx'
|
||||
- 'css_for_each_child'
|
||||
- 'css_for_each_descendant_post'
|
||||
- 'css_for_each_descendant_pre'
|
||||
- 'device_for_each_child_node'
|
||||
- 'dma_fence_chain_for_each'
|
||||
- 'do_for_each_ftrace_op'
|
||||
- 'drm_atomic_crtc_for_each_plane'
|
||||
- 'drm_atomic_crtc_state_for_each_plane'
|
||||
- 'drm_atomic_crtc_state_for_each_plane_state'
|
||||
- 'drm_atomic_for_each_plane_damage'
|
||||
- 'drm_client_for_each_connector_iter'
|
||||
- 'drm_client_for_each_modeset'
|
||||
- 'drm_connector_for_each_possible_encoder'
|
||||
- 'drm_for_each_bridge_in_chain'
|
||||
- 'drm_for_each_connector_iter'
|
||||
- 'drm_for_each_crtc'
|
||||
- 'drm_for_each_encoder'
|
||||
- 'drm_for_each_encoder_mask'
|
||||
- 'drm_for_each_fb'
|
||||
- 'drm_for_each_legacy_plane'
|
||||
- 'drm_for_each_plane'
|
||||
- 'drm_for_each_plane_mask'
|
||||
- 'drm_for_each_privobj'
|
||||
- 'drm_mm_for_each_hole'
|
||||
- 'drm_mm_for_each_node'
|
||||
- 'drm_mm_for_each_node_in_range'
|
||||
- 'drm_mm_for_each_node_safe'
|
||||
- 'flow_action_for_each'
|
||||
- 'for_each_active_dev_scope'
|
||||
- 'for_each_active_drhd_unit'
|
||||
- 'for_each_active_iommu'
|
||||
- 'for_each_aggr_pgid'
|
||||
- 'for_each_available_child_of_node'
|
||||
- 'for_each_bio'
|
||||
- 'for_each_board_func_rsrc'
|
||||
- 'for_each_bvec'
|
||||
- 'for_each_card_auxs'
|
||||
- 'for_each_card_auxs_safe'
|
||||
- 'for_each_card_components'
|
||||
- 'for_each_card_dapms'
|
||||
- 'for_each_card_pre_auxs'
|
||||
- 'for_each_card_prelinks'
|
||||
- 'for_each_card_rtds'
|
||||
- 'for_each_card_rtds_safe'
|
||||
- 'for_each_card_widgets'
|
||||
- 'for_each_card_widgets_safe'
|
||||
- 'for_each_cgroup_storage_type'
|
||||
- 'for_each_child_of_node'
|
||||
- 'for_each_clear_bit'
|
||||
- 'for_each_clear_bit_from'
|
||||
- 'for_each_cmsghdr'
|
||||
- 'for_each_compatible_node'
|
||||
- 'for_each_component_dais'
|
||||
- 'for_each_component_dais_safe'
|
||||
- 'for_each_comp_order'
|
||||
- 'for_each_console'
|
||||
- 'for_each_cpu'
|
||||
- 'for_each_cpu_and'
|
||||
- 'for_each_cpu_not'
|
||||
- 'for_each_cpu_wrap'
|
||||
- 'for_each_dapm_widgets'
|
||||
- 'for_each_dev_addr'
|
||||
- 'for_each_dev_scope'
|
||||
- 'for_each_displayid_db'
|
||||
- 'for_each_dma_cap_mask'
|
||||
- 'for_each_dpcm_be'
|
||||
- 'for_each_dpcm_be_rollback'
|
||||
- 'for_each_dpcm_be_safe'
|
||||
- 'for_each_dpcm_fe'
|
||||
- 'for_each_drhd_unit'
|
||||
- 'for_each_dss_dev'
|
||||
- 'for_each_efi_memory_desc'
|
||||
- 'for_each_efi_memory_desc_in_map'
|
||||
- 'for_each_element'
|
||||
- 'for_each_element_extid'
|
||||
- 'for_each_element_id'
|
||||
- 'for_each_endpoint_of_node'
|
||||
- 'for_each_evictable_lru'
|
||||
- 'for_each_fib6_node_rt_rcu'
|
||||
- 'for_each_fib6_walker_rt'
|
||||
- 'for_each_free_mem_pfn_range_in_zone'
|
||||
- 'for_each_free_mem_pfn_range_in_zone_from'
|
||||
- 'for_each_free_mem_range'
|
||||
- 'for_each_free_mem_range_reverse'
|
||||
- 'for_each_func_rsrc'
|
||||
- 'for_each_hstate'
|
||||
- 'for_each_if'
|
||||
- 'for_each_iommu'
|
||||
- 'for_each_ip_tunnel_rcu'
|
||||
- 'for_each_irq_nr'
|
||||
- 'for_each_link_codecs'
|
||||
- 'for_each_link_cpus'
|
||||
- 'for_each_link_platforms'
|
||||
- 'for_each_lru'
|
||||
- 'for_each_matching_node'
|
||||
- 'for_each_matching_node_and_match'
|
||||
- 'for_each_member'
|
||||
- 'for_each_mem_region'
|
||||
- 'for_each_memblock_type'
|
||||
- 'for_each_memcg_cache_index'
|
||||
- 'for_each_mem_pfn_range'
|
||||
- '__for_each_mem_range'
|
||||
- 'for_each_mem_range'
|
||||
- '__for_each_mem_range_rev'
|
||||
- 'for_each_mem_range_rev'
|
||||
- 'for_each_migratetype_order'
|
||||
- 'for_each_msi_entry'
|
||||
- 'for_each_msi_entry_safe'
|
||||
- 'for_each_net'
|
||||
- 'for_each_net_continue_reverse'
|
||||
- 'for_each_netdev'
|
||||
- 'for_each_netdev_continue'
|
||||
- 'for_each_netdev_continue_rcu'
|
||||
- 'for_each_netdev_continue_reverse'
|
||||
- 'for_each_netdev_feature'
|
||||
- 'for_each_netdev_in_bond_rcu'
|
||||
- 'for_each_netdev_rcu'
|
||||
- 'for_each_netdev_reverse'
|
||||
- 'for_each_netdev_safe'
|
||||
- 'for_each_net_rcu'
|
||||
- 'for_each_new_connector_in_state'
|
||||
- 'for_each_new_crtc_in_state'
|
||||
- 'for_each_new_mst_mgr_in_state'
|
||||
- 'for_each_new_plane_in_state'
|
||||
- 'for_each_new_private_obj_in_state'
|
||||
- 'for_each_node'
|
||||
- 'for_each_node_by_name'
|
||||
- 'for_each_node_by_type'
|
||||
- 'for_each_node_mask'
|
||||
- 'for_each_node_state'
|
||||
- 'for_each_node_with_cpus'
|
||||
- 'for_each_node_with_property'
|
||||
- 'for_each_nonreserved_multicast_dest_pgid'
|
||||
- 'for_each_of_allnodes'
|
||||
- 'for_each_of_allnodes_from'
|
||||
- 'for_each_of_cpu_node'
|
||||
- 'for_each_of_pci_range'
|
||||
- 'for_each_old_connector_in_state'
|
||||
- 'for_each_old_crtc_in_state'
|
||||
- 'for_each_old_mst_mgr_in_state'
|
||||
- 'for_each_oldnew_connector_in_state'
|
||||
- 'for_each_oldnew_crtc_in_state'
|
||||
- 'for_each_oldnew_mst_mgr_in_state'
|
||||
- 'for_each_oldnew_plane_in_state'
|
||||
- 'for_each_oldnew_plane_in_state_reverse'
|
||||
- 'for_each_oldnew_private_obj_in_state'
|
||||
- 'for_each_old_plane_in_state'
|
||||
- 'for_each_old_private_obj_in_state'
|
||||
- 'for_each_online_cpu'
|
||||
- 'for_each_online_node'
|
||||
- 'for_each_online_pgdat'
|
||||
- 'for_each_pci_bridge'
|
||||
- 'for_each_pci_dev'
|
||||
- 'for_each_pci_msi_entry'
|
||||
- 'for_each_pcm_streams'
|
||||
- 'for_each_physmem_range'
|
||||
- 'for_each_populated_zone'
|
||||
- 'for_each_possible_cpu'
|
||||
- 'for_each_present_cpu'
|
||||
- 'for_each_prime_number'
|
||||
- 'for_each_prime_number_from'
|
||||
- 'for_each_process'
|
||||
- 'for_each_process_thread'
|
||||
- 'for_each_property_of_node'
|
||||
- 'for_each_registered_fb'
|
||||
- 'for_each_requested_gpio'
|
||||
- 'for_each_requested_gpio_in_range'
|
||||
- 'for_each_reserved_mem_range'
|
||||
- 'for_each_reserved_mem_region'
|
||||
- 'for_each_rtd_codec_dais'
|
||||
- 'for_each_rtd_codec_dais_rollback'
|
||||
- 'for_each_rtd_components'
|
||||
- 'for_each_rtd_cpu_dais'
|
||||
- 'for_each_rtd_cpu_dais_rollback'
|
||||
- 'for_each_rtd_dais'
|
||||
- 'for_each_set_bit'
|
||||
- 'for_each_set_bit_from'
|
||||
- 'for_each_set_clump8'
|
||||
- 'for_each_sg'
|
||||
- 'for_each_sg_dma_page'
|
||||
- 'for_each_sg_page'
|
||||
- 'for_each_sgtable_dma_page'
|
||||
- 'for_each_sgtable_dma_sg'
|
||||
- 'for_each_sgtable_page'
|
||||
- 'for_each_sgtable_sg'
|
||||
- 'for_each_sibling_event'
|
||||
- 'for_each_subelement'
|
||||
- 'for_each_subelement_extid'
|
||||
- 'for_each_subelement_id'
|
||||
- '__for_each_thread'
|
||||
- 'for_each_thread'
|
||||
- 'for_each_unicast_dest_pgid'
|
||||
- 'for_each_wakeup_source'
|
||||
- 'for_each_zone'
|
||||
- 'for_each_zone_zonelist'
|
||||
- 'for_each_zone_zonelist_nodemask'
|
||||
- 'fwnode_for_each_available_child_node'
|
||||
- 'fwnode_for_each_child_node'
|
||||
- 'fwnode_graph_for_each_endpoint'
|
||||
- 'gadget_for_each_ep'
|
||||
- 'genradix_for_each'
|
||||
- 'genradix_for_each_from'
|
||||
- 'hash_for_each'
|
||||
- 'hash_for_each_possible'
|
||||
- 'hash_for_each_possible_rcu'
|
||||
- 'hash_for_each_possible_rcu_notrace'
|
||||
- 'hash_for_each_possible_safe'
|
||||
- 'hash_for_each_rcu'
|
||||
- 'hash_for_each_safe'
|
||||
- 'hctx_for_each_ctx'
|
||||
- 'hlist_bl_for_each_entry'
|
||||
- 'hlist_bl_for_each_entry_rcu'
|
||||
- 'hlist_bl_for_each_entry_safe'
|
||||
- 'hlist_for_each'
|
||||
- 'hlist_for_each_entry'
|
||||
- 'hlist_for_each_entry_continue'
|
||||
- 'hlist_for_each_entry_continue_rcu'
|
||||
- 'hlist_for_each_entry_continue_rcu_bh'
|
||||
- 'hlist_for_each_entry_from'
|
||||
- 'hlist_for_each_entry_from_rcu'
|
||||
- 'hlist_for_each_entry_rcu'
|
||||
- 'hlist_for_each_entry_rcu_bh'
|
||||
- 'hlist_for_each_entry_rcu_notrace'
|
||||
- 'hlist_for_each_entry_safe'
|
||||
- '__hlist_for_each_rcu'
|
||||
- 'hlist_for_each_safe'
|
||||
- 'hlist_nulls_for_each_entry'
|
||||
- 'hlist_nulls_for_each_entry_from'
|
||||
- 'hlist_nulls_for_each_entry_rcu'
|
||||
- 'hlist_nulls_for_each_entry_safe'
|
||||
- 'i3c_bus_for_each_i2cdev'
|
||||
- 'i3c_bus_for_each_i3cdev'
|
||||
- 'ide_host_for_each_port'
|
||||
- 'ide_port_for_each_dev'
|
||||
- 'ide_port_for_each_present_dev'
|
||||
- 'idr_for_each_entry'
|
||||
- 'idr_for_each_entry_continue'
|
||||
- 'idr_for_each_entry_continue_ul'
|
||||
- 'idr_for_each_entry_ul'
|
||||
- 'in_dev_for_each_ifa_rcu'
|
||||
- 'in_dev_for_each_ifa_rtnl'
|
||||
- 'inet_bind_bucket_for_each'
|
||||
- 'inet_lhash2_for_each_icsk_rcu'
|
||||
- 'key_for_each'
|
||||
- 'key_for_each_safe'
|
||||
- 'klp_for_each_func'
|
||||
- 'klp_for_each_func_safe'
|
||||
- 'klp_for_each_func_static'
|
||||
- 'klp_for_each_object'
|
||||
- 'klp_for_each_object_safe'
|
||||
- 'klp_for_each_object_static'
|
||||
- 'kunit_suite_for_each_test_case'
|
||||
- 'kvm_for_each_memslot'
|
||||
- 'kvm_for_each_vcpu'
|
||||
- 'list_for_each'
|
||||
- 'list_for_each_codec'
|
||||
- 'list_for_each_codec_safe'
|
||||
- 'list_for_each_continue'
|
||||
- 'list_for_each_entry'
|
||||
- 'list_for_each_entry_continue'
|
||||
- 'list_for_each_entry_continue_rcu'
|
||||
- 'list_for_each_entry_continue_reverse'
|
||||
- 'list_for_each_entry_from'
|
||||
- 'list_for_each_entry_from_rcu'
|
||||
- 'list_for_each_entry_from_reverse'
|
||||
- 'list_for_each_entry_lockless'
|
||||
- 'list_for_each_entry_rcu'
|
||||
- 'list_for_each_entry_reverse'
|
||||
- 'list_for_each_entry_safe'
|
||||
- 'list_for_each_entry_safe_continue'
|
||||
- 'list_for_each_entry_safe_from'
|
||||
- 'list_for_each_entry_safe_reverse'
|
||||
- 'list_for_each_prev'
|
||||
- 'list_for_each_prev_safe'
|
||||
- 'list_for_each_safe'
|
||||
- 'llist_for_each'
|
||||
- 'llist_for_each_entry'
|
||||
- 'llist_for_each_entry_safe'
|
||||
- 'llist_for_each_safe'
|
||||
- 'mci_for_each_dimm'
|
||||
- 'media_device_for_each_entity'
|
||||
- 'media_device_for_each_intf'
|
||||
- 'media_device_for_each_link'
|
||||
- 'media_device_for_each_pad'
|
||||
- 'nanddev_io_for_each_page'
|
||||
- 'netdev_for_each_lower_dev'
|
||||
- 'netdev_for_each_lower_private'
|
||||
- 'netdev_for_each_lower_private_rcu'
|
||||
- 'netdev_for_each_mc_addr'
|
||||
- 'netdev_for_each_uc_addr'
|
||||
- 'netdev_for_each_upper_dev_rcu'
|
||||
- 'netdev_hw_addr_list_for_each'
|
||||
- 'nft_rule_for_each_expr'
|
||||
- 'nla_for_each_attr'
|
||||
- 'nla_for_each_nested'
|
||||
- 'nlmsg_for_each_attr'
|
||||
- 'nlmsg_for_each_msg'
|
||||
- 'nr_neigh_for_each'
|
||||
- 'nr_neigh_for_each_safe'
|
||||
- 'nr_node_for_each'
|
||||
- 'nr_node_for_each_safe'
|
||||
- 'of_for_each_phandle'
|
||||
- 'of_property_for_each_string'
|
||||
- 'of_property_for_each_u32'
|
||||
- 'pci_bus_for_each_resource'
|
||||
- 'pcm_for_each_format'
|
||||
- 'ping_portaddr_for_each_entry'
|
||||
- 'plist_for_each'
|
||||
- 'plist_for_each_continue'
|
||||
- 'plist_for_each_entry'
|
||||
- 'plist_for_each_entry_continue'
|
||||
- 'plist_for_each_entry_safe'
|
||||
- 'plist_for_each_safe'
|
||||
- 'pnp_for_each_card'
|
||||
- 'pnp_for_each_dev'
|
||||
- 'protocol_for_each_card'
|
||||
- 'protocol_for_each_dev'
|
||||
- 'queue_for_each_hw_ctx'
|
||||
- 'radix_tree_for_each_slot'
|
||||
- 'radix_tree_for_each_tagged'
|
||||
- 'rbtree_postorder_for_each_entry_safe'
|
||||
- 'rdma_for_each_block'
|
||||
- 'rdma_for_each_port'
|
||||
- 'rdma_umem_for_each_dma_block'
|
||||
- 'resource_list_for_each_entry'
|
||||
- 'resource_list_for_each_entry_safe'
|
||||
- 'rhl_for_each_entry_rcu'
|
||||
- 'rhl_for_each_rcu'
|
||||
- 'rht_for_each'
|
||||
- 'rht_for_each_entry'
|
||||
- 'rht_for_each_entry_from'
|
||||
- 'rht_for_each_entry_rcu'
|
||||
- 'rht_for_each_entry_rcu_from'
|
||||
- 'rht_for_each_entry_safe'
|
||||
- 'rht_for_each_from'
|
||||
- 'rht_for_each_rcu'
|
||||
- 'rht_for_each_rcu_from'
|
||||
- '__rq_for_each_bio'
|
||||
- 'rq_for_each_bvec'
|
||||
- 'rq_for_each_segment'
|
||||
- 'scsi_for_each_prot_sg'
|
||||
- 'scsi_for_each_sg'
|
||||
- 'sctp_for_each_hentry'
|
||||
- 'sctp_skb_for_each'
|
||||
- 'shdma_for_each_chan'
|
||||
- '__shost_for_each_device'
|
||||
- 'shost_for_each_device'
|
||||
- 'sk_for_each'
|
||||
- 'sk_for_each_bound'
|
||||
- 'sk_for_each_entry_offset_rcu'
|
||||
- 'sk_for_each_from'
|
||||
- 'sk_for_each_rcu'
|
||||
- 'sk_for_each_safe'
|
||||
- 'sk_nulls_for_each'
|
||||
- 'sk_nulls_for_each_from'
|
||||
- 'sk_nulls_for_each_rcu'
|
||||
- 'snd_array_for_each'
|
||||
- 'snd_pcm_group_for_each_entry'
|
||||
- 'snd_soc_dapm_widget_for_each_path'
|
||||
- 'snd_soc_dapm_widget_for_each_path_safe'
|
||||
- 'snd_soc_dapm_widget_for_each_sink_path'
|
||||
- 'snd_soc_dapm_widget_for_each_source_path'
|
||||
- 'tb_property_for_each'
|
||||
- 'tcf_exts_for_each_action'
|
||||
- 'udp_portaddr_for_each_entry'
|
||||
- 'udp_portaddr_for_each_entry_rcu'
|
||||
- 'usb_hub_for_each_child'
|
||||
- 'v4l2_device_for_each_subdev'
|
||||
- 'v4l2_m2m_for_each_dst_buf'
|
||||
- 'v4l2_m2m_for_each_dst_buf_safe'
|
||||
- 'v4l2_m2m_for_each_src_buf'
|
||||
- 'v4l2_m2m_for_each_src_buf_safe'
|
||||
- 'virtio_device_for_each_vq'
|
||||
- 'while_for_each_ftrace_op'
|
||||
- 'xa_for_each'
|
||||
- 'xa_for_each_marked'
|
||||
- 'xa_for_each_range'
|
||||
- 'xa_for_each_start'
|
||||
- 'xas_for_each'
|
||||
- 'xas_for_each_conflict'
|
||||
- 'xas_for_each_marked'
|
||||
- 'xbc_array_for_each_value'
|
||||
- 'xbc_for_each_key_value'
|
||||
- 'xbc_node_for_each_array_value'
|
||||
- 'xbc_node_for_each_child'
|
||||
- 'xbc_node_for_each_key_value'
|
||||
- 'zorro_for_each_dev'
|
||||
|
||||
#IncludeBlocks: Preserve # Unknown to clang-format-5.0
|
||||
IncludeCategories:
|
||||
- Regex: '.*'
|
||||
Priority: 1
|
||||
IncludeIsMainRegex: '(Test)?$'
|
||||
IndentCaseLabels: false
|
||||
#IndentPPDirectives: None # Unknown to clang-format-5.0
|
||||
IndentWidth: 8
|
||||
IndentWrappedFunctionNames: false
|
||||
JavaScriptQuotes: Leave
|
||||
JavaScriptWrapImports: true
|
||||
KeepEmptyLinesAtTheStartOfBlocks: false
|
||||
MacroBlockBegin: ''
|
||||
MacroBlockEnd: ''
|
||||
MaxEmptyLinesToKeep: 1
|
||||
NamespaceIndentation: None
|
||||
#ObjCBinPackProtocolList: Auto # Unknown to clang-format-5.0
|
||||
ObjCBlockIndentWidth: 8
|
||||
ObjCSpaceAfterProperty: true
|
||||
ObjCSpaceBeforeProtocolList: true
|
||||
|
||||
# Taken from git's rules
|
||||
#PenaltyBreakAssignment: 10 # Unknown to clang-format-4.0
|
||||
PenaltyBreakBeforeFirstCallParameter: 30
|
||||
PenaltyBreakComment: 10
|
||||
PenaltyBreakFirstLessLess: 0
|
||||
PenaltyBreakString: 10
|
||||
PenaltyExcessCharacter: 100
|
||||
PenaltyReturnTypeOnItsOwnLine: 60
|
||||
|
||||
PointerAlignment: Right
|
||||
ReflowComments: false
|
||||
SortIncludes: false
|
||||
#SortUsingDeclarations: false # Unknown to clang-format-4.0
|
||||
SpaceAfterCStyleCast: false
|
||||
SpaceAfterTemplateKeyword: true
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
#SpaceBeforeCtorInitializerColon: true # Unknown to clang-format-5.0
|
||||
#SpaceBeforeInheritanceColon: true # Unknown to clang-format-5.0
|
||||
SpaceBeforeParens: ControlStatements
|
||||
#SpaceBeforeRangeBasedForLoopColon: true # Unknown to clang-format-5.0
|
||||
SpaceInEmptyParentheses: false
|
||||
SpacesBeforeTrailingComments: 1
|
||||
SpacesInAngles: false
|
||||
SpacesInContainerLiterals: false
|
||||
SpacesInCStyleCastParentheses: false
|
||||
SpacesInParentheses: false
|
||||
SpacesInSquareBrackets: false
|
||||
Standard: Cpp03
|
||||
TabWidth: 8
|
||||
UseTab: Always
|
||||
...
|
||||
@@ -1,4 +1,4 @@
|
||||
Diagnostics:
|
||||
UnusedIncludes: Strict
|
||||
ClangTidy:
|
||||
Remove: bugprone-sizeof-expression
|
||||
Remove: bugprone-sizeof-expression
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
menu "KernelSU"
|
||||
|
||||
config KSU
|
||||
tristate "KernelSU module"
|
||||
tristate "KernelSU function support"
|
||||
select OVERLAY_FS
|
||||
default y
|
||||
depends on KPROBES
|
||||
depends on OVERLAY_FS
|
||||
help
|
||||
This is the KSU privilege driver for android system.
|
||||
Enable kernel-level root privileges on Android System.
|
||||
|
||||
config KSU_DEBUG
|
||||
tristate "KernelSU module debug mode"
|
||||
default n
|
||||
bool "KernelSU debug mode"
|
||||
depends on KSU
|
||||
default n
|
||||
help
|
||||
This enables debug mode for KSU
|
||||
Enable KernelSU debug mode
|
||||
|
||||
endmenu
|
||||
|
||||
@@ -14,7 +14,7 @@ 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)
|
||||
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
|
||||
|
||||
@@ -29,4 +29,4 @@ endif
|
||||
ccflags-y += -DEXPECTED_SIZE=$(EXPECTED_SIZE)
|
||||
ccflags-y += -DEXPECTED_HASH=$(EXPECTED_HASH)
|
||||
ccflags-y += -Wno-implicit-function-declaration -Wno-strict-prototypes -Wno-int-conversion -Wno-gcc-compat
|
||||
ccflags-y += -Wno-macro-redefined -Wno-declaration-after-statement
|
||||
ccflags-y += -Wno-declaration-after-statement
|
||||
|
||||
@@ -1,27 +1,91 @@
|
||||
#include "linux/delay.h"
|
||||
#include "ksu.h"
|
||||
#include "linux/compiler.h"
|
||||
#include "linux/fs.h"
|
||||
#include "linux/gfp.h"
|
||||
#include "linux/kernel.h"
|
||||
#include "linux/list.h"
|
||||
#include "linux/printk.h"
|
||||
#include "linux/slab.h"
|
||||
#include "linux/types.h"
|
||||
#include "linux/version.h"
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0)
|
||||
#include "linux/compiler_types.h"
|
||||
#endif
|
||||
|
||||
#include "klog.h" // IWYU pragma: keep
|
||||
#include "selinux/selinux.h"
|
||||
#include "kernel_compat.h"
|
||||
#include "allowlist.h"
|
||||
|
||||
#define FILE_MAGIC 0x7f4b5355 // ' KSU', u32
|
||||
#define FILE_FORMAT_VERSION 1 // u32
|
||||
#define FILE_FORMAT_VERSION 3 // u32
|
||||
|
||||
#define KSU_APP_PROFILE_PRESERVE_UID 9999 // NOBODY_UID
|
||||
#define KSU_DEFAULT_SELINUX_DOMAIN "u:r:su:s0"
|
||||
|
||||
static DEFINE_MUTEX(allowlist_mutex);
|
||||
|
||||
// default profiles, these may be used frequently, so we cache it
|
||||
static struct root_profile default_root_profile;
|
||||
static struct non_root_profile default_non_root_profile;
|
||||
|
||||
static int allow_list_arr[PAGE_SIZE / sizeof(int)] __read_mostly __aligned(PAGE_SIZE);
|
||||
static int allow_list_pointer __read_mostly = 0;
|
||||
|
||||
static void remove_uid_from_arr(uid_t uid)
|
||||
{
|
||||
int *temp_arr;
|
||||
int i, j;
|
||||
|
||||
if (allow_list_pointer == 0)
|
||||
return;
|
||||
|
||||
temp_arr = kmalloc(sizeof(allow_list_arr), GFP_KERNEL);
|
||||
if (temp_arr == NULL) {
|
||||
pr_err("%s: unable to allocate memory\n", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = j = 0; i < allow_list_pointer; i++) {
|
||||
if (allow_list_arr[i] == uid)
|
||||
continue;
|
||||
temp_arr[j++] = allow_list_arr[i];
|
||||
}
|
||||
|
||||
allow_list_pointer = j;
|
||||
|
||||
for (; j < ARRAY_SIZE(allow_list_arr); j++)
|
||||
temp_arr[j] = -1;
|
||||
|
||||
memcpy(&allow_list_arr, temp_arr, PAGE_SIZE);
|
||||
kfree(temp_arr);
|
||||
}
|
||||
|
||||
static void init_default_profiles()
|
||||
{
|
||||
default_root_profile.uid = 0;
|
||||
default_root_profile.gid = 0;
|
||||
default_root_profile.groups_count = 1;
|
||||
default_root_profile.groups[0] = 0;
|
||||
memset(&default_root_profile.capabilities, 0xff,
|
||||
sizeof(default_root_profile.capabilities));
|
||||
default_root_profile.namespaces = 0;
|
||||
strcpy(default_root_profile.selinux_domain, KSU_DEFAULT_SELINUX_DOMAIN);
|
||||
|
||||
// This means that we will umount modules by default!
|
||||
default_non_root_profile.umount_modules = true;
|
||||
}
|
||||
|
||||
struct perm_data {
|
||||
struct list_head list;
|
||||
uid_t uid;
|
||||
bool allow;
|
||||
struct app_profile profile;
|
||||
};
|
||||
|
||||
static struct list_head allow_list;
|
||||
|
||||
static uint8_t allow_list_bitmap[PAGE_SIZE] __read_mostly __aligned(PAGE_SIZE);
|
||||
#define BITMAP_UID_MAX ((sizeof(allow_list_bitmap) * BITS_PER_BYTE) - 1)
|
||||
|
||||
#define KERNEL_SU_ALLOWLIST "/data/adb/ksu/.allowlist"
|
||||
|
||||
static struct work_struct ksu_save_work;
|
||||
@@ -36,65 +100,231 @@ void ksu_show_allow_list(void)
|
||||
pr_info("ksu_show_allow_list");
|
||||
list_for_each (pos, &allow_list) {
|
||||
p = list_entry(pos, struct perm_data, list);
|
||||
pr_info("uid :%d, allow: %d\n", p->uid, p->allow);
|
||||
pr_info("uid :%d, allow: %d\n", p->profile.current_uid,
|
||||
p->profile.allow_su);
|
||||
}
|
||||
}
|
||||
|
||||
bool ksu_allow_uid(uid_t uid, bool allow, bool persist)
|
||||
#ifdef CONFIG_KSU_DEBUG
|
||||
static void ksu_grant_root_to_shell()
|
||||
{
|
||||
struct app_profile profile = {
|
||||
.allow_su = true,
|
||||
.current_uid = 2000,
|
||||
};
|
||||
strcpy(profile.key, "com.android.shell");
|
||||
strcpy(profile.rp_config.profile.selinux_domain, KSU_DEFAULT_SELINUX_DOMAIN);
|
||||
ksu_set_app_profile(&profile, false);
|
||||
}
|
||||
#endif
|
||||
|
||||
bool ksu_get_app_profile(struct app_profile *profile)
|
||||
{
|
||||
struct perm_data *p = NULL;
|
||||
struct list_head *pos = NULL;
|
||||
bool found = false;
|
||||
|
||||
list_for_each (pos, &allow_list) {
|
||||
p = list_entry(pos, struct perm_data, list);
|
||||
bool uid_match = profile->current_uid == p->profile.current_uid;
|
||||
if (uid_match) {
|
||||
// found it, override it with ours
|
||||
memcpy(profile, &p->profile, sizeof(*profile));
|
||||
found = true;
|
||||
goto exit;
|
||||
}
|
||||
}
|
||||
|
||||
exit:
|
||||
return found;
|
||||
}
|
||||
|
||||
static inline bool forbid_system_uid(uid_t uid) {
|
||||
#define SHELL_UID 2000
|
||||
#define SYSTEM_UID 1000
|
||||
return uid < SHELL_UID && uid != SYSTEM_UID;
|
||||
}
|
||||
|
||||
static bool profile_valid(struct app_profile *profile)
|
||||
{
|
||||
if (!profile) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (forbid_system_uid(profile->current_uid)) {
|
||||
pr_err("uid lower than 2000 is unsupported: %d\n", profile->current_uid);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (profile->version < KSU_APP_PROFILE_VER) {
|
||||
pr_info("Unsupported profile version: %d\n", profile->version);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (profile->allow_su) {
|
||||
if (profile->rp_config.profile.groups_count > KSU_MAX_GROUPS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (strlen(profile->rp_config.profile.selinux_domain) == 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ksu_set_app_profile(struct app_profile *profile, bool persist)
|
||||
{
|
||||
// find the node first!
|
||||
struct perm_data *p = NULL;
|
||||
struct list_head *pos = NULL;
|
||||
bool result = false;
|
||||
|
||||
if (!profile_valid(profile)) {
|
||||
pr_err("Failed to set app profile: invalid profile!\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
list_for_each (pos, &allow_list) {
|
||||
p = list_entry(pos, struct perm_data, list);
|
||||
if (uid == p->uid) {
|
||||
p->allow = allow;
|
||||
// both uid and package must match, otherwise it will break multiple package with different user id
|
||||
if (profile->current_uid == p->profile.current_uid &&
|
||||
!strcmp(profile->key, p->profile.key)) {
|
||||
// found it, just override it all!
|
||||
memcpy(&p->profile, profile, sizeof(*profile));
|
||||
result = true;
|
||||
goto exit;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
// not found, alloc a new node!
|
||||
p = (struct perm_data *)kmalloc(sizeof(struct perm_data), GFP_KERNEL);
|
||||
if (!p) {
|
||||
pr_err("alloc allow node failed.\n");
|
||||
pr_err("ksu_set_app_profile alloc failed\n");
|
||||
return false;
|
||||
}
|
||||
p->uid = uid;
|
||||
p->allow = allow;
|
||||
|
||||
memcpy(&p->profile, profile, sizeof(*profile));
|
||||
if (profile->allow_su) {
|
||||
pr_info("set root profile, key: %s, uid: %d, gid: %d, context: %s\n",
|
||||
profile->key, profile->current_uid,
|
||||
profile->rp_config.profile.gid,
|
||||
profile->rp_config.profile.selinux_domain);
|
||||
} else {
|
||||
pr_info("set app profile, key: %s, uid: %d, umount modules: %d\n",
|
||||
profile->key, profile->current_uid,
|
||||
profile->nrp_config.profile.umount_modules);
|
||||
}
|
||||
list_add_tail(&p->list, &allow_list);
|
||||
|
||||
out:
|
||||
if (profile->current_uid <= BITMAP_UID_MAX) {
|
||||
if (profile->allow_su)
|
||||
allow_list_bitmap[profile->current_uid / BITS_PER_BYTE] |= 1 << (profile->current_uid % BITS_PER_BYTE);
|
||||
else
|
||||
allow_list_bitmap[profile->current_uid / BITS_PER_BYTE] &= ~(1 << (profile->current_uid % BITS_PER_BYTE));
|
||||
} else {
|
||||
if (profile->allow_su) {
|
||||
/*
|
||||
* 1024 apps with uid higher than BITMAP_UID_MAX
|
||||
* registered to request superuser?
|
||||
*/
|
||||
if (allow_list_pointer >= ARRAY_SIZE(allow_list_arr)) {
|
||||
pr_err("too many apps registered\n");
|
||||
WARN_ON(1);
|
||||
return false;
|
||||
}
|
||||
allow_list_arr[allow_list_pointer++] = profile->current_uid;
|
||||
} else {
|
||||
remove_uid_from_arr(profile->current_uid);
|
||||
}
|
||||
}
|
||||
result = true;
|
||||
|
||||
exit:
|
||||
// check if the default profiles is changed, cache it to a single struct to accelerate access.
|
||||
if (unlikely(!strcmp(profile->key, "$"))) {
|
||||
// set default non root profile
|
||||
memcpy(&default_non_root_profile, &profile->nrp_config.profile,
|
||||
sizeof(default_non_root_profile));
|
||||
}
|
||||
|
||||
if (unlikely(!strcmp(profile->key, "#"))) {
|
||||
// set default root profile
|
||||
memcpy(&default_root_profile, &profile->rp_config.profile,
|
||||
sizeof(default_root_profile));
|
||||
}
|
||||
|
||||
if (persist)
|
||||
persistent_allow_list();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool ksu_is_allow_uid(uid_t uid)
|
||||
bool __ksu_is_allow_uid(uid_t uid)
|
||||
{
|
||||
struct perm_data *p = NULL;
|
||||
struct list_head *pos = NULL;
|
||||
int i;
|
||||
|
||||
if (uid == 0) {
|
||||
if (unlikely(uid == 0)) {
|
||||
// already root, but only allow our domain.
|
||||
return is_ksu_domain();
|
||||
}
|
||||
|
||||
list_for_each (pos, &allow_list) {
|
||||
p = list_entry(pos, struct perm_data, list);
|
||||
// pr_info("is_allow_uid uid :%d, allow: %d\n", p->uid, p->allow);
|
||||
if (uid == p->uid) {
|
||||
return p->allow;
|
||||
if (forbid_system_uid(uid)) {
|
||||
// do not bother going through the list if it's system
|
||||
return false;
|
||||
}
|
||||
|
||||
if (likely(uid <= BITMAP_UID_MAX)) {
|
||||
return !!(allow_list_bitmap[uid / BITS_PER_BYTE] & (1 << (uid % BITS_PER_BYTE)));
|
||||
} else {
|
||||
for (i = 0; i < allow_list_pointer; i++) {
|
||||
if (allow_list_arr[i] == uid)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ksu_uid_should_umount(uid_t uid)
|
||||
{
|
||||
struct app_profile profile = { .current_uid = uid };
|
||||
bool found = ksu_get_app_profile(&profile);
|
||||
if (!found) {
|
||||
// no app profile found, it must be non root app
|
||||
return default_non_root_profile.umount_modules;
|
||||
}
|
||||
if (profile.allow_su) {
|
||||
// if found and it is granted to su, we shouldn't umount for it
|
||||
return false;
|
||||
} else {
|
||||
// found an app profile
|
||||
if (profile.nrp_config.use_default) {
|
||||
return default_non_root_profile.umount_modules;
|
||||
} else {
|
||||
return profile.nrp_config.profile.umount_modules;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct root_profile *ksu_get_root_profile(uid_t uid)
|
||||
{
|
||||
struct perm_data *p = NULL;
|
||||
struct list_head *pos = NULL;
|
||||
|
||||
list_for_each (pos, &allow_list) {
|
||||
p = list_entry(pos, struct perm_data, list);
|
||||
if (uid == p->profile.current_uid && p->profile.allow_su) {
|
||||
if (!p->profile.rp_config.use_default) {
|
||||
return &p->profile.rp_config.profile;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// use default profile
|
||||
return &default_root_profile;
|
||||
}
|
||||
|
||||
bool ksu_get_allow_list(int *array, int *length, bool allow)
|
||||
{
|
||||
struct perm_data *p = NULL;
|
||||
@@ -102,9 +332,9 @@ bool ksu_get_allow_list(int *array, int *length, bool allow)
|
||||
int i = 0;
|
||||
list_for_each (pos, &allow_list) {
|
||||
p = list_entry(pos, struct perm_data, list);
|
||||
pr_info("get_allow_list uid: %d allow: %d\n", p->uid, p->allow);
|
||||
if (p->allow == allow) {
|
||||
array[i++] = p->uid;
|
||||
// pr_info("get_allow_list uid: %d allow: %d\n", p->uid, p->allow);
|
||||
if (p->profile.allow_su == allow) {
|
||||
array[i++] = p->profile.current_uid;
|
||||
}
|
||||
}
|
||||
*length = i;
|
||||
@@ -112,7 +342,7 @@ bool ksu_get_allow_list(int *array, int *length, bool allow)
|
||||
return true;
|
||||
}
|
||||
|
||||
void do_persistent_allow_list(struct work_struct *work)
|
||||
void do_save_allow_list(struct work_struct *work)
|
||||
{
|
||||
u32 magic = FILE_MAGIC;
|
||||
u32 version = FILE_FORMAT_VERSION;
|
||||
@@ -124,12 +354,13 @@ void do_persistent_allow_list(struct work_struct *work)
|
||||
filp_open(KERNEL_SU_ALLOWLIST, O_WRONLY | O_CREAT, 0644);
|
||||
|
||||
if (IS_ERR(fp)) {
|
||||
pr_err("save_allow_list creat file failed: %d\n", PTR_ERR(fp));
|
||||
pr_err("save_allow_list create file failed: %ld\n", PTR_ERR(fp));
|
||||
return;
|
||||
}
|
||||
|
||||
// store magic and version
|
||||
if (ksu_kernel_write_compat(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;
|
||||
}
|
||||
@@ -142,10 +373,12 @@ void do_persistent_allow_list(struct work_struct *work)
|
||||
|
||||
list_for_each (pos, &allow_list) {
|
||||
p = list_entry(pos, struct perm_data, list);
|
||||
pr_info("save allow list uid :%d, allow: %d\n", p->uid,
|
||||
p->allow);
|
||||
ksu_kernel_write_compat(fp, &p->uid, sizeof(p->uid), &off);
|
||||
ksu_kernel_write_compat(fp, &p->allow, sizeof(p->allow), &off);
|
||||
pr_info("save allow list, name: %s uid :%d, allow: %d\n",
|
||||
p->profile.key, p->profile.current_uid,
|
||||
p->profile.allow_su);
|
||||
|
||||
ksu_kernel_write_compat(fp, &p->profile, sizeof(p->profile),
|
||||
&off);
|
||||
}
|
||||
|
||||
exit:
|
||||
@@ -161,27 +394,21 @@ void do_load_allow_list(struct work_struct *work)
|
||||
u32 version;
|
||||
KWORKER_INSTALL_KEYRING();
|
||||
|
||||
#ifdef CONFIG_KSU_DEBUG
|
||||
// always allow adb shell by default
|
||||
ksu_grant_root_to_shell();
|
||||
#endif
|
||||
// load allowlist now!
|
||||
fp = filp_open(KERNEL_SU_ALLOWLIST, O_RDONLY, 0);
|
||||
|
||||
if (IS_ERR(fp)) {
|
||||
#ifdef CONFIG_KSU_DEBUG
|
||||
int errno = PTR_ERR(fp);
|
||||
if (errno == -ENOENT) {
|
||||
ksu_allow_uid(2000, true,
|
||||
true); // allow adb shell by default
|
||||
} else {
|
||||
pr_err("load_allow_list open file failed: %d\n",
|
||||
PTR_ERR(fp));
|
||||
}
|
||||
#else
|
||||
pr_err("load_allow_list open file failed: %d\n", PTR_ERR(fp));
|
||||
#endif
|
||||
pr_err("load_allow_list open file failed: %ld\n", PTR_ERR(fp));
|
||||
return;
|
||||
}
|
||||
|
||||
// verify magic
|
||||
if (ksu_kernel_read_compat(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;
|
||||
@@ -196,18 +423,19 @@ void do_load_allow_list(struct work_struct *work)
|
||||
pr_info("allowlist version: %d\n", version);
|
||||
|
||||
while (true) {
|
||||
u32 uid;
|
||||
bool allow = false;
|
||||
ret = ksu_kernel_read_compat(fp, &uid, sizeof(uid), &off);
|
||||
struct app_profile profile;
|
||||
|
||||
ret = ksu_kernel_read_compat(fp, &profile, sizeof(profile),
|
||||
&off);
|
||||
|
||||
if (ret <= 0) {
|
||||
pr_info("load_allow_list read err: %d\n", ret);
|
||||
pr_info("load_allow_list read err: %zd\n", ret);
|
||||
break;
|
||||
}
|
||||
ret = ksu_kernel_read_compat(fp, &allow, sizeof(allow), &off);
|
||||
|
||||
pr_info("load_allow_uid: %d, allow: %d\n", uid, allow);
|
||||
|
||||
ksu_allow_uid(uid, allow, false);
|
||||
pr_info("load_allow_uid, name: %s, uid: %d, allow: %d\n",
|
||||
profile.key, profile.current_uid, profile.allow_su);
|
||||
ksu_set_app_profile(&profile, false);
|
||||
}
|
||||
|
||||
exit:
|
||||
@@ -224,11 +452,16 @@ void ksu_prune_allowlist(bool (*is_uid_exist)(uid_t, void *), void *data)
|
||||
// TODO: use RCU!
|
||||
mutex_lock(&allowlist_mutex);
|
||||
list_for_each_entry_safe (np, n, &allow_list, list) {
|
||||
uid_t uid = np->uid;
|
||||
if (!is_uid_exist(uid, data)) {
|
||||
uid_t uid = np->profile.current_uid;
|
||||
// we use this uid for special cases, don't prune it!
|
||||
bool is_preserved_uid = uid == KSU_APP_PROFILE_PRESERVE_UID;
|
||||
if (!is_preserved_uid && !is_uid_exist(uid, data)) {
|
||||
modified = true;
|
||||
pr_info("prune uid: %d\n", uid);
|
||||
list_del(&np->list);
|
||||
allow_list_bitmap[uid / BITS_PER_BYTE] &= ~(1 << (uid % BITS_PER_BYTE));
|
||||
remove_uid_from_arr(uid);
|
||||
smp_mb();
|
||||
kfree(np);
|
||||
}
|
||||
}
|
||||
@@ -252,10 +485,20 @@ bool ksu_load_allow_list(void)
|
||||
|
||||
void ksu_allowlist_init(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
BUILD_BUG_ON(sizeof(allow_list_bitmap) != PAGE_SIZE);
|
||||
BUILD_BUG_ON(sizeof(allow_list_arr) != PAGE_SIZE);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(allow_list_arr); i++)
|
||||
allow_list_arr[i] = -1;
|
||||
|
||||
INIT_LIST_HEAD(&allow_list);
|
||||
|
||||
INIT_WORK(&ksu_save_work, do_persistent_allow_list);
|
||||
INIT_WORK(&ksu_save_work, do_save_allow_list);
|
||||
INIT_WORK(&ksu_load_work, do_load_allow_list);
|
||||
|
||||
init_default_profiles();
|
||||
}
|
||||
|
||||
void ksu_allowlist_exit(void)
|
||||
@@ -263,7 +506,7 @@ void ksu_allowlist_exit(void)
|
||||
struct perm_data *np = NULL;
|
||||
struct perm_data *n = NULL;
|
||||
|
||||
do_persistent_allow_list(NULL);
|
||||
do_save_allow_list(NULL);
|
||||
|
||||
// free allowlist
|
||||
mutex_lock(&allowlist_mutex);
|
||||
@@ -272,4 +515,4 @@ void ksu_allowlist_exit(void)
|
||||
kfree(np);
|
||||
}
|
||||
mutex_unlock(&allowlist_mutex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#define __KSU_H_ALLOWLIST
|
||||
|
||||
#include "linux/types.h"
|
||||
#include "ksu.h"
|
||||
|
||||
void ksu_allowlist_init(void);
|
||||
|
||||
@@ -11,12 +12,16 @@ bool ksu_load_allow_list(void);
|
||||
|
||||
void ksu_show_allow_list(void);
|
||||
|
||||
bool ksu_is_allow_uid(uid_t uid);
|
||||
|
||||
bool ksu_allow_uid(uid_t uid, bool allow, bool persist);
|
||||
bool __ksu_is_allow_uid(uid_t uid);
|
||||
#define ksu_is_allow_uid(uid) unlikely(__ksu_is_allow_uid(uid))
|
||||
|
||||
bool ksu_get_allow_list(int *array, int *length, bool allow);
|
||||
|
||||
void ksu_prune_allowlist(bool (*is_uid_exist)(uid_t, void *), void *data);
|
||||
|
||||
#endif
|
||||
bool ksu_get_app_profile(struct app_profile *);
|
||||
bool ksu_set_app_profile(struct app_profile *, bool persist);
|
||||
|
||||
bool ksu_uid_should_umount(uid_t uid);
|
||||
struct root_profile *ksu_get_root_profile(uid_t uid);
|
||||
#endif
|
||||
|
||||
@@ -4,4 +4,4 @@
|
||||
// return 0 if signature match
|
||||
int is_manager_apk(char *path);
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include "linux/capability.h"
|
||||
#include "linux/cred.h"
|
||||
#include "linux/dcache.h"
|
||||
#include "linux/err.h"
|
||||
@@ -5,6 +6,7 @@
|
||||
#include "linux/kernel.h"
|
||||
#include "linux/kprobes.h"
|
||||
#include "linux/lsm_hooks.h"
|
||||
#include "linux/nsproxy.h"
|
||||
#include "linux/path.h"
|
||||
#include "linux/printk.h"
|
||||
#include "linux/uaccess.h"
|
||||
@@ -38,36 +40,99 @@ static inline bool is_allow_su()
|
||||
return ksu_is_allow_uid(current_uid().val);
|
||||
}
|
||||
|
||||
static inline bool is_isolated_uid(uid_t uid) {
|
||||
#define FIRST_ISOLATED_UID 99000
|
||||
#define LAST_ISOLATED_UID 99999
|
||||
#define FIRST_APP_ZYGOTE_ISOLATED_UID 90000
|
||||
#define LAST_APP_ZYGOTE_ISOLATED_UID 98999
|
||||
uid_t appid = uid % 100000;
|
||||
return (appid >= FIRST_ISOLATED_UID && appid <= LAST_ISOLATED_UID)
|
||||
|| (appid >= FIRST_APP_ZYGOTE_ISOLATED_UID && appid <= LAST_APP_ZYGOTE_ISOLATED_UID);
|
||||
static inline bool is_isolated_uid(uid_t uid)
|
||||
{
|
||||
#define FIRST_ISOLATED_UID 99000
|
||||
#define LAST_ISOLATED_UID 99999
|
||||
#define FIRST_APP_ZYGOTE_ISOLATED_UID 90000
|
||||
#define LAST_APP_ZYGOTE_ISOLATED_UID 98999
|
||||
uid_t appid = uid % 100000;
|
||||
return (appid >= FIRST_ISOLATED_UID && appid <= LAST_ISOLATED_UID) ||
|
||||
(appid >= FIRST_APP_ZYGOTE_ISOLATED_UID &&
|
||||
appid <= LAST_APP_ZYGOTE_ISOLATED_UID);
|
||||
}
|
||||
|
||||
static struct group_info root_groups = { .usage = ATOMIC_INIT(2) };
|
||||
|
||||
static void setup_groups(struct root_profile *profile, struct cred *cred)
|
||||
{
|
||||
if (profile->groups_count > KSU_MAX_GROUPS) {
|
||||
pr_warn("Failed to setgroups, too large group: %d!\n",
|
||||
profile->uid);
|
||||
return;
|
||||
}
|
||||
|
||||
if (profile->groups_count == 1 && profile->groups[0] == 0) {
|
||||
// setgroup to root and return early.
|
||||
if (cred->group_info)
|
||||
put_group_info(cred->group_info);
|
||||
cred->group_info = get_group_info(&root_groups);
|
||||
return;
|
||||
}
|
||||
|
||||
u32 ngroups = profile->groups_count;
|
||||
struct group_info *group_info = groups_alloc(ngroups);
|
||||
if (!group_info) {
|
||||
pr_warn("Failed to setgroups, ENOMEM for: %d\n", profile->uid);
|
||||
return;
|
||||
}
|
||||
|
||||
int i;
|
||||
for (i = 0; i < ngroups; i++) {
|
||||
gid_t gid = profile->groups[i];
|
||||
kgid_t kgid = make_kgid(current_user_ns(), gid);
|
||||
if (!gid_valid(kgid)) {
|
||||
pr_warn("Failed to setgroups, invalid gid: %d\n", gid);
|
||||
put_group_info(group_info);
|
||||
return;
|
||||
}
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0)
|
||||
group_info->gid[i] = kgid;
|
||||
#else
|
||||
GROUP_AT(group_info, i) = kgid;
|
||||
#endif
|
||||
}
|
||||
|
||||
groups_sort(group_info);
|
||||
set_groups(cred, group_info);
|
||||
}
|
||||
|
||||
void escape_to_root(void)
|
||||
{
|
||||
struct cred *cred;
|
||||
|
||||
cred = (struct cred *)__task_cred(current);
|
||||
|
||||
memset(&cred->uid, 0, sizeof(cred->uid));
|
||||
memset(&cred->gid, 0, sizeof(cred->gid));
|
||||
memset(&cred->suid, 0, sizeof(cred->suid));
|
||||
memset(&cred->euid, 0, sizeof(cred->euid));
|
||||
memset(&cred->egid, 0, sizeof(cred->egid));
|
||||
memset(&cred->fsuid, 0, sizeof(cred->fsuid));
|
||||
memset(&cred->fsgid, 0, sizeof(cred->fsgid));
|
||||
memset(&cred->cap_inheritable, 0xff, sizeof(cred->cap_inheritable));
|
||||
memset(&cred->cap_permitted, 0xff, sizeof(cred->cap_permitted));
|
||||
memset(&cred->cap_effective, 0xff, sizeof(cred->cap_effective));
|
||||
memset(&cred->cap_bset, 0xff, sizeof(cred->cap_bset));
|
||||
memset(&cred->cap_ambient, 0xff, sizeof(cred->cap_ambient));
|
||||
if (cred->euid.val == 0) {
|
||||
pr_warn("Already root, don't escape!\n");
|
||||
return;
|
||||
}
|
||||
struct root_profile *profile = ksu_get_root_profile(cred->uid.val);
|
||||
|
||||
cred->uid.val = profile->uid;
|
||||
cred->suid.val = profile->uid;
|
||||
cred->euid.val = profile->uid;
|
||||
cred->fsuid.val = profile->uid;
|
||||
|
||||
cred->gid.val = profile->gid;
|
||||
cred->fsgid.val = profile->gid;
|
||||
cred->sgid.val = profile->gid;
|
||||
cred->egid.val = profile->gid;
|
||||
|
||||
BUILD_BUG_ON(sizeof(profile->capabilities.effective) !=
|
||||
sizeof(kernel_cap_t));
|
||||
|
||||
// capabilities
|
||||
memcpy(&cred->cap_effective, &profile->capabilities.effective,
|
||||
sizeof(cred->cap_effective));
|
||||
memcpy(&cred->cap_inheritable, &profile->capabilities.effective,
|
||||
sizeof(cred->cap_inheritable));
|
||||
memcpy(&cred->cap_permitted, &profile->capabilities.effective,
|
||||
sizeof(cred->cap_permitted));
|
||||
memcpy(&cred->cap_bset, &profile->capabilities.effective,
|
||||
sizeof(cred->cap_bset));
|
||||
memcpy(&cred->cap_ambient, &profile->capabilities.effective,
|
||||
sizeof(cred->cap_ambient));
|
||||
|
||||
// disable seccomp
|
||||
#if defined(CONFIG_GENERIC_ENTRY) && \
|
||||
@@ -83,12 +148,9 @@ void escape_to_root(void)
|
||||
#else
|
||||
#endif
|
||||
|
||||
// setgroup to root
|
||||
if (cred->group_info)
|
||||
put_group_info(cred->group_info);
|
||||
cred->group_info = get_group_info(&root_groups);
|
||||
setup_groups(profile, cred);
|
||||
|
||||
setup_selinux();
|
||||
setup_selinux(profile->selinux_domain);
|
||||
}
|
||||
|
||||
int ksu_handle_rename(struct dentry *old_dentry, struct dentry *new_dentry)
|
||||
@@ -168,14 +230,26 @@ int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3,
|
||||
}
|
||||
|
||||
// someone wants to be root manager, just check it!
|
||||
// arg3 should be `/data/data/<manager_package_name>`
|
||||
// arg3 should be `/data/user/<userId>/<manager_package_name>`
|
||||
char param[128];
|
||||
const char *prefix = "/data/data/";
|
||||
if (copy_from_user(param, arg3, sizeof(param))) {
|
||||
pr_err("become_manager: copy param err\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// for user 0, it is /data/data
|
||||
// for user 999, it is /data/user/999
|
||||
const char *prefix;
|
||||
char prefixTmp[64];
|
||||
int userId = current_uid().val / 100000;
|
||||
if (userId == 0) {
|
||||
prefix = "/data/data";
|
||||
} else {
|
||||
snprintf(prefixTmp, sizeof(prefixTmp), "/data/user/%d",
|
||||
userId);
|
||||
prefix = prefixTmp;
|
||||
}
|
||||
|
||||
if (startswith(param, (char *)prefix) != 0) {
|
||||
pr_info("become_manager: invalid param: %s\n", param);
|
||||
return 0;
|
||||
@@ -213,10 +287,6 @@ int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3,
|
||||
if (copy_to_user(result, &reply_ok, sizeof(reply_ok))) {
|
||||
pr_err("grant_root: prctl reply error\n");
|
||||
}
|
||||
} else {
|
||||
pr_info("deny root for: %d\n", current_uid());
|
||||
// add it to deny list!
|
||||
ksu_allow_uid(current_uid().val, false, true);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -311,6 +381,30 @@ int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3,
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (arg2 == CMD_UID_GRANTED_ROOT || arg2 == CMD_UID_SHOULD_UMOUNT) {
|
||||
if (is_manager() || 0 == current_uid().val) {
|
||||
uid_t target_uid = (uid_t)arg3;
|
||||
bool allow = false;
|
||||
if (arg2 == CMD_UID_GRANTED_ROOT) {
|
||||
allow = ksu_is_allow_uid(target_uid);
|
||||
} else if (arg2 == CMD_UID_SHOULD_UMOUNT) {
|
||||
allow = ksu_uid_should_umount(target_uid);
|
||||
} else {
|
||||
pr_err("unknown cmd: %d\n", arg2);
|
||||
}
|
||||
if (!copy_to_user(arg4, &allow, sizeof(allow))) {
|
||||
if (copy_to_user(result, &reply_ok,
|
||||
sizeof(reply_ok))) {
|
||||
pr_err("prctl reply error, cmd: %d\n",
|
||||
arg2);
|
||||
}
|
||||
} else {
|
||||
pr_err("prctl copy err, cmd: %d\n", arg2);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// all other cmds are for 'root manager'
|
||||
if (!is_manager()) {
|
||||
last_failed_uid = current_uid().val;
|
||||
@@ -318,45 +412,76 @@ int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3,
|
||||
}
|
||||
|
||||
// we are already manager
|
||||
if (arg2 == CMD_ALLOW_SU || arg2 == CMD_DENY_SU) {
|
||||
bool allow = arg2 == CMD_ALLOW_SU;
|
||||
bool success = false;
|
||||
uid_t uid = (uid_t)arg3;
|
||||
success = ksu_allow_uid(uid, allow, true);
|
||||
if (arg2 == CMD_GET_APP_PROFILE) {
|
||||
struct app_profile profile;
|
||||
if (copy_from_user(&profile, arg3, sizeof(profile))) {
|
||||
pr_err("copy profile failed\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool success = ksu_get_app_profile(&profile);
|
||||
if (success) {
|
||||
if (copy_to_user(arg3, &profile, sizeof(profile))) {
|
||||
pr_err("copy profile failed\n");
|
||||
return 0;
|
||||
}
|
||||
if (copy_to_user(result, &reply_ok, sizeof(reply_ok))) {
|
||||
pr_err("prctl reply error, cmd: %d\n", arg2);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (arg2 == CMD_SET_APP_PROFILE) {
|
||||
struct app_profile profile;
|
||||
if (copy_from_user(&profile, arg3, sizeof(profile))) {
|
||||
pr_err("copy profile failed\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// todo: validate the params
|
||||
if (ksu_set_app_profile(&profile, true)) {
|
||||
if (copy_to_user(result, &reply_ok, sizeof(reply_ok))) {
|
||||
pr_err("prctl reply error, cmd: %d\n", arg2);
|
||||
}
|
||||
}
|
||||
ksu_show_allow_list();
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool is_appuid(kuid_t uid) {
|
||||
#define PER_USER_RANGE 100000
|
||||
#define FIRST_APPLICATION_UID 10000
|
||||
#define LAST_APPLICATION_UID 19999
|
||||
static bool is_appuid(kuid_t uid)
|
||||
{
|
||||
#define PER_USER_RANGE 100000
|
||||
#define FIRST_APPLICATION_UID 10000
|
||||
#define LAST_APPLICATION_UID 19999
|
||||
|
||||
uid_t appid = uid.val % PER_USER_RANGE;
|
||||
return appid >= FIRST_APPLICATION_UID && appid <= LAST_APPLICATION_UID;
|
||||
}
|
||||
|
||||
static bool should_umount(struct path* path) {
|
||||
static bool should_umount(struct path *path)
|
||||
{
|
||||
if (!path) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (current->nsproxy->mnt_ns == init_nsproxy.mnt_ns) {
|
||||
pr_info("ignore global mnt namespace process: %d\n",
|
||||
current_uid().val);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (path->mnt && path->mnt->mnt_sb && path->mnt->mnt_sb->s_type) {
|
||||
const char* fstype = path->mnt->mnt_sb->s_type->name;
|
||||
const char *fstype = path->mnt->mnt_sb->s_type->name;
|
||||
return strcmp(fstype, "overlay") == 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void try_umount(const char *mnt) {
|
||||
static void try_umount(const char *mnt)
|
||||
{
|
||||
struct path path;
|
||||
int err = kern_path(mnt, 0, &path);
|
||||
if (err) {
|
||||
@@ -376,7 +501,8 @@ static void try_umount(const char *mnt) {
|
||||
#endif
|
||||
}
|
||||
|
||||
int ksu_handle_setuid(struct cred *new, const struct cred *old) {
|
||||
int ksu_handle_setuid(struct cred *new, const struct cred *old)
|
||||
{
|
||||
if (!new || !old) {
|
||||
return 0;
|
||||
}
|
||||
@@ -401,6 +527,14 @@ int ksu_handle_setuid(struct cred *new, const struct cred *old) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!ksu_uid_should_umount(new_uid.val)) {
|
||||
return 0;
|
||||
} else {
|
||||
#ifdef CONFIG_KSU_DEBUG
|
||||
pr_info("uid: %d should not umount!\n", current_uid().val);
|
||||
#endif
|
||||
}
|
||||
|
||||
// umount the target mnt
|
||||
pr_info("handle umount for uid: %d\n", new_uid.val);
|
||||
|
||||
@@ -509,7 +643,8 @@ static int ksu_inode_rename(struct inode *old_inode, struct dentry *old_dentry,
|
||||
}
|
||||
|
||||
static int ksu_task_fix_setuid(struct cred *new, const struct cred *old,
|
||||
int flags) {
|
||||
int flags)
|
||||
{
|
||||
return ksu_handle_setuid(new, old);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
// This file will be regenerated by CI
|
||||
|
||||
unsigned int ksud_size = 0;
|
||||
const char ksud[0] = {};
|
||||
const char ksud[0] = {};
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
register_kprobe
|
||||
unregister_kprobe
|
||||
unregister_kprobe
|
||||
|
||||
@@ -31,4 +31,4 @@ ssize_t ksu_kernel_write_compat(struct file *p, const void *buf, size_t count, l
|
||||
}
|
||||
return result;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,4 +39,4 @@ static inline int install_session_keyring(struct key *keyring)
|
||||
#define KWORKER_INSTALL_KEYRING()
|
||||
#endif
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -8,4 +8,4 @@
|
||||
#define pr_fmt(fmt) "KernelSU: " fmt
|
||||
#endif
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -57,7 +57,7 @@ int __init kernelsu_init(void)
|
||||
ksu_enable_sucompat();
|
||||
ksu_enable_ksud();
|
||||
#else
|
||||
#warning("KPROBES is disabled, KernelSU may not work, please check https://kernelsu.org/guide/how-to-integrate-for-non-gki.html")
|
||||
pr_alert("KPROBES is disabled, KernelSU may not work, please check https://kernelsu.org/guide/how-to-integrate-for-non-gki.html");
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
|
||||
66
kernel/ksu.h
66
kernel/ksu.h
@@ -1,13 +1,17 @@
|
||||
#ifndef __KSU_H_KSU
|
||||
#define __KSU_H_KSU
|
||||
|
||||
#include "linux/types.h"
|
||||
#include "linux/workqueue.h"
|
||||
|
||||
#ifndef KSU_GIT_VERSION
|
||||
#warning "KSU_GIT_VERSION not defined! It is better to make KernelSU a git submodule!"
|
||||
#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
|
||||
#define KERNEL_SU_VERSION \
|
||||
(10000 + KSU_GIT_VERSION + \
|
||||
200) // major * 10000 + git version + 200 for historical reasons
|
||||
#endif
|
||||
|
||||
#define KERNEL_SU_OPTION 0xDEADBEEF
|
||||
@@ -22,10 +26,68 @@
|
||||
#define CMD_REPORT_EVENT 7
|
||||
#define CMD_SET_SEPOLICY 8
|
||||
#define CMD_CHECK_SAFEMODE 9
|
||||
#define CMD_GET_APP_PROFILE 10
|
||||
#define CMD_SET_APP_PROFILE 11
|
||||
#define CMD_UID_GRANTED_ROOT 12
|
||||
#define CMD_UID_SHOULD_UMOUNT 13
|
||||
|
||||
#define EVENT_POST_FS_DATA 1
|
||||
#define EVENT_BOOT_COMPLETED 2
|
||||
|
||||
#define KSU_APP_PROFILE_VER 2
|
||||
#define KSU_MAX_PACKAGE_NAME 256
|
||||
// NGROUPS_MAX for Linux is 65535 generally, but we only supports 32 groups.
|
||||
#define KSU_MAX_GROUPS 32
|
||||
#define KSU_SELINUX_DOMAIN 64
|
||||
|
||||
struct root_profile {
|
||||
int32_t uid;
|
||||
int32_t gid;
|
||||
|
||||
int32_t groups_count;
|
||||
int32_t groups[KSU_MAX_GROUPS];
|
||||
|
||||
// kernel_cap_t is u32[2] for capabilities v3
|
||||
struct {
|
||||
u64 effective;
|
||||
u64 permitted;
|
||||
u64 inheritable;
|
||||
} capabilities;
|
||||
|
||||
char selinux_domain[KSU_SELINUX_DOMAIN];
|
||||
|
||||
int32_t namespaces;
|
||||
};
|
||||
|
||||
struct non_root_profile {
|
||||
bool umount_modules;
|
||||
};
|
||||
|
||||
struct app_profile {
|
||||
// It may be utilized for backward compatibility, although we have never explicitly made any promises regarding this.
|
||||
u32 version;
|
||||
|
||||
// this is usually the package of the app, but can be other value for special apps
|
||||
char key[KSU_MAX_PACKAGE_NAME];
|
||||
int32_t current_uid;
|
||||
bool allow_su;
|
||||
|
||||
union {
|
||||
struct {
|
||||
bool use_default;
|
||||
char template_name[KSU_MAX_PACKAGE_NAME];
|
||||
|
||||
struct root_profile profile;
|
||||
} rp_config;
|
||||
|
||||
struct {
|
||||
bool use_default;
|
||||
|
||||
struct non_root_profile profile;
|
||||
} nrp_config;
|
||||
};
|
||||
};
|
||||
|
||||
bool ksu_queue_work(struct work_struct *work);
|
||||
|
||||
static inline int startswith(char *s, char *prefix)
|
||||
|
||||
133
kernel/ksud.c
133
kernel/ksud.c
@@ -1,5 +1,6 @@
|
||||
#include "asm/current.h"
|
||||
#include "linux/string.h"
|
||||
#include "linux/compat.h"
|
||||
#include "linux/cred.h"
|
||||
#include "linux/dcache.h"
|
||||
#include "linux/err.h"
|
||||
@@ -23,6 +24,7 @@ static const char KERNEL_SU_RC[] =
|
||||
"\n"
|
||||
|
||||
"on post-fs-data\n"
|
||||
" start logd\n"
|
||||
// We should wait for the post-fs-data finish
|
||||
" exec u:r:su:s0 root -- " KSUD_PATH " post-fs-data\n"
|
||||
"\n"
|
||||
@@ -50,9 +52,9 @@ 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;
|
||||
bool ksu_vfs_read_hook __read_mostly = true;
|
||||
bool ksu_execveat_hook __read_mostly = true;
|
||||
bool ksu_input_hook __read_mostly = true;
|
||||
#endif
|
||||
|
||||
void on_post_fs_data(void)
|
||||
@@ -69,11 +71,80 @@ void on_post_fs_data(void)
|
||||
stop_input_hook();
|
||||
}
|
||||
|
||||
#define MAX_ARG_STRINGS 0x7FFFFFFF
|
||||
struct user_arg_ptr {
|
||||
#ifdef CONFIG_COMPAT
|
||||
bool is_compat;
|
||||
#endif
|
||||
union {
|
||||
const char __user *const __user *native;
|
||||
#ifdef CONFIG_COMPAT
|
||||
const compat_uptr_t __user *compat;
|
||||
#endif
|
||||
} ptr;
|
||||
};
|
||||
|
||||
static const char __user *get_user_arg_ptr(struct user_arg_ptr argv, int nr)
|
||||
{
|
||||
const char __user *native;
|
||||
|
||||
#ifdef CONFIG_COMPAT
|
||||
if (unlikely(argv.is_compat)) {
|
||||
compat_uptr_t compat;
|
||||
|
||||
if (get_user(compat, argv.ptr.compat + nr))
|
||||
return ERR_PTR(-EFAULT);
|
||||
|
||||
return compat_ptr(compat);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (get_user(native, argv.ptr.native + nr))
|
||||
return ERR_PTR(-EFAULT);
|
||||
|
||||
return native;
|
||||
}
|
||||
|
||||
/*
|
||||
* count() counts the number of strings in array ARGV.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Make sure old GCC compiler can use __maybe_unused,
|
||||
* Test passed in 4.4.x ~ 4.9.x when use GCC.
|
||||
*/
|
||||
|
||||
static int __maybe_unused count(struct user_arg_ptr argv, int max)
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
if (argv.ptr.native != NULL) {
|
||||
for (;;) {
|
||||
const char __user *p = get_user_arg_ptr(argv, i);
|
||||
|
||||
if (!p)
|
||||
break;
|
||||
|
||||
if (IS_ERR(p))
|
||||
return -EFAULT;
|
||||
|
||||
if (i >= max)
|
||||
return -E2BIG;
|
||||
++i;
|
||||
|
||||
if (fatal_signal_pending(current))
|
||||
return -ERESTARTNOHAND;
|
||||
cond_resched();
|
||||
}
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
int ksu_handle_execveat_ksud(int *fd, struct filename **filename_ptr,
|
||||
void *argv, void *envp, int *flags)
|
||||
{
|
||||
#ifndef CONFIG_KPROBES
|
||||
if (!execveat_hook) {
|
||||
if (!ksu_execveat_hook) {
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
@@ -82,7 +153,7 @@ int ksu_handle_execveat_ksud(int *fd, struct filename **filename_ptr,
|
||||
static const char app_process[] = "/system/bin/app_process";
|
||||
static bool first_app_process = true;
|
||||
static const char system_bin_init[] = "/system/bin/init";
|
||||
static int init_count = 0;
|
||||
static bool init_second_stage_executed = false;
|
||||
|
||||
if (!filename_ptr)
|
||||
return 0;
|
||||
@@ -92,21 +163,53 @@ int ksu_handle_execveat_ksud(int *fd, struct filename **filename_ptr,
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!memcmp(filename->name, system_bin_init,
|
||||
sizeof(system_bin_init) - 1)) {
|
||||
if (unlikely(!memcmp(filename->name, system_bin_init,
|
||||
sizeof(system_bin_init) - 1))) {
|
||||
#ifdef __aarch64__
|
||||
// /system/bin/init executed
|
||||
struct user_arg_ptr *ptr = (struct user_arg_ptr*) argv;
|
||||
int argc = count(*ptr, MAX_ARG_STRINGS);
|
||||
pr_info("/system/bin/init argc: %d\n", argc);
|
||||
if (argc > 1 && !init_second_stage_executed) {
|
||||
const char __user *p = get_user_arg_ptr(*ptr, 1);
|
||||
if (p && !IS_ERR(p)) {
|
||||
char first_arg[16];
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 8, 0)
|
||||
strncpy_from_user_nofault(first_arg, p, sizeof(first_arg));
|
||||
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(5, 3, 0)
|
||||
strncpy_from_unsafe_user(first_arg, p, sizeof(first_arg));
|
||||
#else
|
||||
strncpy_from_user(first_arg, p, sizeof(first_arg));
|
||||
#endif
|
||||
pr_info("first arg: %s\n", first_arg);
|
||||
if (!strcmp(first_arg, "second_stage")) {
|
||||
pr_info("/system/bin/init second_stage executed\n");
|
||||
apply_kernelsu_rules();
|
||||
init_second_stage_executed = true;
|
||||
}
|
||||
} else {
|
||||
pr_err("/system/bin/init parse args err!\n");
|
||||
}
|
||||
}
|
||||
#else
|
||||
// The argument parse is incorrect becuase of the struct user_arg_ptr has 16bytes
|
||||
// and it is passed by value(not pointer), in arm64, it is correct becuase the register
|
||||
// is just arranged correct accidentally, but is not correct in x86_64
|
||||
// i have no device to test, so revert it for x86_64
|
||||
static int init_count = 0;
|
||||
if (++init_count == 2) {
|
||||
// 1: /system/bin/init selinux_setup
|
||||
// 2: /system/bin/init second_stage
|
||||
pr_info("/system/bin/init second_stage executed\n");
|
||||
apply_kernelsu_rules();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
if (first_app_process &&
|
||||
!memcmp(filename->name, app_process, sizeof(app_process) - 1)) {
|
||||
if (unlikely(first_app_process &&
|
||||
!memcmp(filename->name, app_process, sizeof(app_process) - 1))) {
|
||||
first_app_process = false;
|
||||
pr_info("exec app_process, /data prepared!\n");
|
||||
pr_info("exec app_process, /data prepared, second_stage: %d\n", init_second_stage_executed);
|
||||
on_post_fs_data(); // we keep this for old ksud
|
||||
stop_execve_hook();
|
||||
}
|
||||
@@ -147,7 +250,7 @@ int ksu_handle_vfs_read(struct file **file_ptr, char __user **buf_ptr,
|
||||
size_t *count_ptr, loff_t **pos)
|
||||
{
|
||||
#ifndef CONFIG_KPROBES
|
||||
if (!vfs_read_hook) {
|
||||
if (!ksu_vfs_read_hook) {
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
@@ -248,7 +351,7 @@ int ksu_handle_input_handle_event(unsigned int *type, unsigned int *code,
|
||||
int *value)
|
||||
{
|
||||
#ifndef CONFIG_KPROBES
|
||||
if (!input_hook) {
|
||||
if (!ksu_input_hook) {
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
@@ -366,7 +469,7 @@ static void stop_vfs_read_hook()
|
||||
bool ret = schedule_work(&stop_vfs_read_work);
|
||||
pr_info("unregister vfs_read kprobe: %d!\n", ret);
|
||||
#else
|
||||
vfs_read_hook = false;
|
||||
ksu_vfs_read_hook = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -376,7 +479,7 @@ static void stop_execve_hook()
|
||||
bool ret = schedule_work(&stop_execve_hook_work);
|
||||
pr_info("unregister execve kprobe: %d!\n", ret);
|
||||
#else
|
||||
execveat_hook = false;
|
||||
ksu_execveat_hook = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -391,7 +494,7 @@ static void stop_input_hook()
|
||||
bool ret = schedule_work(&stop_input_hook_work);
|
||||
pr_info("unregister input kprobe: %d!\n", ret);
|
||||
#else
|
||||
input_hook = false;
|
||||
ksu_input_hook = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -7,4 +7,4 @@ void on_post_fs_data(void);
|
||||
|
||||
bool ksu_is_safe_mode(void);
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
#include "ksu.h"
|
||||
#include "manager.h"
|
||||
|
||||
uid_t ksu_manager_uid = INVALID_UID;
|
||||
uid_t ksu_manager_uid = KSU_INVALID_UID;
|
||||
|
||||
bool become_manager(char *pkg)
|
||||
{
|
||||
@@ -39,39 +39,51 @@ bool become_manager(char *pkg)
|
||||
|
||||
files_table = files_fdtable(current->files);
|
||||
|
||||
int pkg_len = strlen(pkg);
|
||||
// todo: use iterate_fd
|
||||
while (files_table->fd[i] != NULL) {
|
||||
for (i = 0; files_table->fd[i] != NULL; i++) {
|
||||
files_path = files_table->fd[i]->f_path;
|
||||
if (!d_is_reg(files_path.dentry)) {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
cwd = d_path(&files_path, buf, PATH_MAX);
|
||||
if (startswith(cwd, "/data/app/") == 0 &&
|
||||
endswith(cwd, "/base.apk") == 0) {
|
||||
// we have found the apk!
|
||||
pr_info("found apk: %s", cwd);
|
||||
if (!strstr(cwd, pkg)) {
|
||||
pr_info("apk path not match package name!\n");
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
if (is_manager_apk(cwd) == 0) {
|
||||
// check passed
|
||||
uid_t uid = current_uid().val;
|
||||
pr_info("manager uid: %d\n", uid);
|
||||
|
||||
ksu_set_manager_uid(uid);
|
||||
|
||||
result = true;
|
||||
goto clean;
|
||||
} else {
|
||||
pr_info("manager signature invalid!");
|
||||
}
|
||||
|
||||
break;
|
||||
if (startswith(cwd, "/data/app/") != 0 ||
|
||||
endswith(cwd, "/base.apk") != 0) {
|
||||
continue;
|
||||
}
|
||||
i++;
|
||||
// we have found the apk!
|
||||
pr_info("found apk: %s", cwd);
|
||||
char *pkg_index = strstr(cwd, pkg);
|
||||
if (!pkg_index) {
|
||||
pr_info("apk path not match package name!\n");
|
||||
continue;
|
||||
}
|
||||
char *next_char = pkg_index + pkg_len;
|
||||
// because we ensure the cwd must startswith `/data/app` and endswith `base.apk`
|
||||
// we don't need to check if the pointer is out of bounds
|
||||
if (*next_char != '-') {
|
||||
// from android 8.1: http://aospxref.com/android-8.1.0_r81/xref/frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java#17612
|
||||
// to android 13: http://aospxref.com/android-13.0.0_r3/xref/frameworks/base/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java#1208
|
||||
// /data/app/~~[randomStringA]/[packageName]-[randomStringB]
|
||||
// the previous char must be `/` and the next char must be `-`
|
||||
// because we use strstr instead of equals, this is a strong verfication.
|
||||
pr_info("invalid pkg: %s\n", pkg);
|
||||
continue;
|
||||
}
|
||||
if (is_manager_apk(cwd) == 0) {
|
||||
// check passed
|
||||
uid_t uid = current_uid().val;
|
||||
pr_info("manager uid: %d\n", uid);
|
||||
|
||||
ksu_set_manager_uid(uid);
|
||||
|
||||
result = true;
|
||||
goto clean;
|
||||
} else {
|
||||
pr_info("manager signature invalid!");
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
clean:
|
||||
|
||||
@@ -4,18 +4,18 @@
|
||||
#include "linux/cred.h"
|
||||
#include "linux/types.h"
|
||||
|
||||
#define INVALID_UID -1
|
||||
#define KSU_INVALID_UID -1
|
||||
|
||||
extern uid_t ksu_manager_uid; // DO NOT DIRECT USE
|
||||
|
||||
static inline bool ksu_is_manager_uid_valid()
|
||||
{
|
||||
return ksu_manager_uid != INVALID_UID;
|
||||
return ksu_manager_uid != KSU_INVALID_UID;
|
||||
}
|
||||
|
||||
static inline bool is_manager()
|
||||
{
|
||||
return ksu_manager_uid == current_uid().val;
|
||||
return unlikely(ksu_manager_uid == current_uid().val);
|
||||
}
|
||||
|
||||
static inline uid_t ksu_get_manager_uid()
|
||||
@@ -30,7 +30,7 @@ static inline void ksu_set_manager_uid(uid_t uid)
|
||||
|
||||
static inline void ksu_invalidate_manager_uid()
|
||||
{
|
||||
ksu_manager_uid = INVALID_UID;
|
||||
ksu_manager_uid = KSU_INVALID_UID;
|
||||
}
|
||||
|
||||
bool become_manager(char *pkg);
|
||||
|
||||
@@ -30,4 +30,4 @@ RE_EXPORT_SYMBOL1(unsigned long, kallsyms_lookup_name, const char *, name)
|
||||
// int ksu_register_kretprobe(struct kretprobe *rp);
|
||||
// void unregister_kretprobe(struct kretprobe *rp);
|
||||
// int register_kretprobes(struct kretprobe **rps, int num);
|
||||
// void unregister_kretprobes(struct kretprobe **rps, int num);
|
||||
// void unregister_kretprobes(struct kretprobe **rps, int num);
|
||||
|
||||
@@ -2,8 +2,15 @@ obj-y += selinux.o
|
||||
obj-y += sepolicy.o
|
||||
obj-y += rules.o
|
||||
|
||||
ifeq ($(shell grep -q " current_sid(void)" $(srctree)/security/selinux/include/objsec.h; echo $$?),0)
|
||||
ccflags-y += -DKSU_COMPAT_HAS_CURRENT_SID
|
||||
endif
|
||||
|
||||
ifeq ($(shell grep -q "struct selinux_state " $(srctree)/security/selinux/include/security.h; echo $$?),0)
|
||||
ccflags-y += -DKSU_COMPAT_HAS_SELINUX_STATE
|
||||
endif
|
||||
|
||||
ccflags-y += -Wno-implicit-function-declaration -Wno-strict-prototypes -Wno-int-conversion
|
||||
ccflags-y += -Wno-macro-redefined -Wno-declaration-after-statement -Wno-unused-function
|
||||
ccflags-y += -Wno-declaration-after-statement -Wno-unused-function
|
||||
ccflags-y += -I$(srctree)/security/selinux -I$(srctree)/security/selinux/include
|
||||
ccflags-y += -I$(objtree)/security/selinux
|
||||
ccflags-y += -I$(objtree)/security/selinux -include $(srctree)/include/uapi/asm-generic/errno.h
|
||||
|
||||
@@ -22,7 +22,7 @@ static struct policydb *get_policydb(void)
|
||||
{
|
||||
struct policydb *db;
|
||||
// selinux_state does not exists before 4.19
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 337)
|
||||
#ifdef KSU_COMPAT_USE_SELINUX_STATE
|
||||
#ifdef SELINUX_POLICY_INSTEAD_SELINUX_SS
|
||||
struct selinux_policy *policy = rcu_dereference(selinux_state.policy);
|
||||
db = &policy->policydb;
|
||||
@@ -31,7 +31,7 @@ static struct policydb *get_policydb(void)
|
||||
db = &ss->policydb;
|
||||
#endif
|
||||
#else
|
||||
db = &policydb;
|
||||
db = &policydb;
|
||||
#endif
|
||||
return db;
|
||||
}
|
||||
@@ -39,8 +39,7 @@ static struct policydb *get_policydb(void)
|
||||
void apply_kernelsu_rules()
|
||||
{
|
||||
if (!getenforce()) {
|
||||
pr_info("SELinux permissive or disabled, don't apply rules.");
|
||||
return;
|
||||
pr_info("SELinux permissive or disabled, apply rules!");
|
||||
}
|
||||
|
||||
rcu_read_lock();
|
||||
@@ -169,8 +168,9 @@ static int get_object(char *buf, char __user *user_object, size_t buf_sz,
|
||||
}
|
||||
|
||||
// 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, 337))
|
||||
static void reset_avc_cache()
|
||||
{
|
||||
#ifndef KSU_COMPAT_USE_SELINUX_STATE
|
||||
avc_ss_reset(0);
|
||||
selnl_notify_policyload(0);
|
||||
selinux_status_update_policyload(0);
|
||||
@@ -190,8 +190,7 @@ int handle_sepolicy(unsigned long arg3, void __user *arg4)
|
||||
}
|
||||
|
||||
if (!getenforce()) {
|
||||
pr_info("SELinux permissive or disabled, don't apply policies.");
|
||||
return 0;
|
||||
pr_info("SELinux permissive or disabled when handle policy!\n");
|
||||
}
|
||||
|
||||
struct sepol_data data;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#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, 337))
|
||||
#ifndef KSU_COMPAT_USE_SELINUX_STATE
|
||||
#include "avc.h"
|
||||
#endif
|
||||
|
||||
@@ -39,9 +39,9 @@ static int transive_to_domain(const char *domain)
|
||||
return error;
|
||||
}
|
||||
|
||||
void setup_selinux()
|
||||
void setup_selinux(const char *domain)
|
||||
{
|
||||
if (transive_to_domain(KERNEL_SU_DOMAIN)) {
|
||||
if (transive_to_domain(domain)) {
|
||||
pr_err("transive domain failed.");
|
||||
return;
|
||||
}
|
||||
@@ -57,7 +57,7 @@ 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, 337)))
|
||||
#ifdef KSU_COMPAT_USE_SELINUX_STATE
|
||||
selinux_state.enforcing = enforce;
|
||||
#else
|
||||
selinux_enforcing = enforce;
|
||||
@@ -68,7 +68,7 @@ void setenforce(bool enforce)
|
||||
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, 337)))
|
||||
#ifdef KSU_COMPAT_USE_SELINUX_STATE
|
||||
if (selinux_state.disabled) {
|
||||
#else
|
||||
if (selinux_disabled) {
|
||||
@@ -78,7 +78,7 @@ bool getenforce()
|
||||
#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, 337)))
|
||||
#ifdef KSU_COMPAT_USE_SELINUX_STATE
|
||||
return selinux_state.enforcing;
|
||||
#else
|
||||
return selinux_enforcing;
|
||||
@@ -88,7 +88,8 @@ bool getenforce()
|
||||
#endif
|
||||
}
|
||||
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 9, 337)
|
||||
#if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 10, 0)) && \
|
||||
!defined(KSU_COMPAT_HAS_CURRENT_SID)
|
||||
/*
|
||||
* get the subjective security ID of the current task
|
||||
*/
|
||||
|
||||
@@ -2,8 +2,13 @@
|
||||
#define __KSU_H_SELINUX
|
||||
|
||||
#include "linux/types.h"
|
||||
#include "linux/version.h"
|
||||
|
||||
void setup_selinux();
|
||||
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0)) || defined(KSU_COMPAT_HAS_SELINUX_STATE)
|
||||
#define KSU_COMPAT_USE_SELINUX_STATE
|
||||
#endif
|
||||
|
||||
void setup_selinux(const char *);
|
||||
|
||||
void setenforce(bool);
|
||||
|
||||
@@ -13,4 +18,4 @@ bool is_ksu_domain();
|
||||
|
||||
void apply_kernelsu_rules();
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -9,6 +9,18 @@
|
||||
|
||||
#define KSU_SUPPORT_ADD_TYPE
|
||||
|
||||
/*
|
||||
* Adapt to Huawei HISI kernel without affecting other kernels ,
|
||||
* Huawei Hisi Kernel EBITMAP Enable or Disable Flag ,
|
||||
* From ss/ebitmap.h
|
||||
*/
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0) && \
|
||||
LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0)
|
||||
#ifdef HISI_SELINUX_EBITMAP_RO
|
||||
#define CONFIG_IS_HW_HISI
|
||||
#endif
|
||||
#endif
|
||||
|
||||
//////////////////////////////////////////////////////
|
||||
// Declaration
|
||||
//////////////////////////////////////////////////////
|
||||
@@ -61,7 +73,7 @@ static bool add_typeattribute(struct policydb *db, const char *type,
|
||||
// rules
|
||||
#define strip_av(effect, invert) ((effect == AVTAB_AUDITDENY) == !invert)
|
||||
|
||||
#define hash_for_each(node_ptr, n_slot, cur) \
|
||||
#define ksu_hash_for_each(node_ptr, n_slot, cur) \
|
||||
int i; \
|
||||
for (i = 0; i < n_slot; ++i) \
|
||||
for (cur = node_ptr[i]; cur; cur = cur->next)
|
||||
@@ -69,10 +81,10 @@ static bool add_typeattribute(struct policydb *db, const char *type,
|
||||
// htable is a struct instead of pointer above 5.8.0:
|
||||
// https://elixir.bootlin.com/linux/v5.8-rc1/source/security/selinux/ss/symtab.h
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 8, 0)
|
||||
#define hashtab_for_each(htab, cur) hash_for_each (htab.htable, htab.size, cur)
|
||||
#define ksu_hashtab_for_each(htab, cur) ksu_hash_for_each (htab.htable, htab.size, cur)
|
||||
#else
|
||||
#define hashtab_for_each(htab, cur) \
|
||||
hash_for_each (htab->htable, htab->size, cur)
|
||||
#define ksu_hashtab_for_each(htab, cur) \
|
||||
ksu_hash_for_each (htab->htable, htab->size, cur)
|
||||
#endif
|
||||
|
||||
// symtab_search is introduced on 5.9.0:
|
||||
@@ -83,7 +95,7 @@ static bool add_typeattribute(struct policydb *db, const char *type,
|
||||
#endif
|
||||
|
||||
#define avtab_for_each(avtab, cur) \
|
||||
hash_for_each (avtab.htable, avtab.nslot, cur) \
|
||||
ksu_hash_for_each (avtab.htable, avtab.nslot, cur) \
|
||||
;
|
||||
|
||||
static struct avtab_node *get_avtab_node(struct policydb *db,
|
||||
@@ -198,14 +210,14 @@ static void add_rule_raw(struct policydb *db, struct type_datum *src,
|
||||
if (src == NULL) {
|
||||
struct hashtab_node *node;
|
||||
if (strip_av(effect, invert)) {
|
||||
hashtab_for_each(db->p_types.table, node)
|
||||
ksu_hashtab_for_each(db->p_types.table, node)
|
||||
{
|
||||
add_rule_raw(db,
|
||||
(struct type_datum *)node->datum,
|
||||
tgt, cls, perm, effect, invert);
|
||||
};
|
||||
} else {
|
||||
hashtab_for_each(db->p_types.table, node)
|
||||
ksu_hashtab_for_each(db->p_types.table, node)
|
||||
{
|
||||
struct type_datum *type =
|
||||
(struct type_datum *)(node->datum);
|
||||
@@ -218,14 +230,14 @@ static void add_rule_raw(struct policydb *db, struct type_datum *src,
|
||||
} else if (tgt == NULL) {
|
||||
struct hashtab_node *node;
|
||||
if (strip_av(effect, invert)) {
|
||||
hashtab_for_each(db->p_types.table, node)
|
||||
ksu_hashtab_for_each(db->p_types.table, node)
|
||||
{
|
||||
add_rule_raw(db, src,
|
||||
(struct type_datum *)node->datum,
|
||||
cls, perm, effect, invert);
|
||||
};
|
||||
} else {
|
||||
hashtab_for_each(db->p_types.table, node)
|
||||
ksu_hashtab_for_each(db->p_types.table, node)
|
||||
{
|
||||
struct type_datum *type =
|
||||
(struct type_datum *)(node->datum);
|
||||
@@ -237,7 +249,7 @@ static void add_rule_raw(struct policydb *db, struct type_datum *src,
|
||||
}
|
||||
} else if (cls == NULL) {
|
||||
struct hashtab_node *node;
|
||||
hashtab_for_each(db->p_classes.table, node)
|
||||
ksu_hashtab_for_each(db->p_classes.table, node)
|
||||
{
|
||||
add_rule_raw(db, src, tgt,
|
||||
(struct class_datum *)node->datum, perm,
|
||||
@@ -280,7 +292,7 @@ static void add_xperm_rule_raw(struct policydb *db, struct type_datum *src,
|
||||
{
|
||||
if (src == NULL) {
|
||||
struct hashtab_node *node;
|
||||
hashtab_for_each(db->p_types.table, node)
|
||||
ksu_hashtab_for_each(db->p_types.table, node)
|
||||
{
|
||||
struct type_datum *type =
|
||||
(struct type_datum *)(node->datum);
|
||||
@@ -291,7 +303,7 @@ static void add_xperm_rule_raw(struct policydb *db, struct type_datum *src,
|
||||
};
|
||||
} else if (tgt == NULL) {
|
||||
struct hashtab_node *node;
|
||||
hashtab_for_each(db->p_types.table, node)
|
||||
ksu_hashtab_for_each(db->p_types.table, node)
|
||||
{
|
||||
struct type_datum *type =
|
||||
(struct type_datum *)(node->datum);
|
||||
@@ -302,7 +314,7 @@ static void add_xperm_rule_raw(struct policydb *db, struct type_datum *src,
|
||||
};
|
||||
} else if (cls == NULL) {
|
||||
struct hashtab_node *node;
|
||||
hashtab_for_each(db->p_classes.table, node)
|
||||
ksu_hashtab_for_each(db->p_classes.table, node)
|
||||
{
|
||||
add_xperm_rule_raw(db, src, tgt,
|
||||
(struct class_datum *)(node->datum),
|
||||
@@ -453,8 +465,9 @@ static bool add_type_rule(struct policydb *db, const char *s, const char *t,
|
||||
return true;
|
||||
}
|
||||
|
||||
// 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);
|
||||
// 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)
|
||||
{
|
||||
@@ -486,7 +499,6 @@ static int filenametr_cmp(const void *k1, const void *k2)
|
||||
return v;
|
||||
|
||||
return strcmp(ft1->name, ft2->name);
|
||||
|
||||
}
|
||||
|
||||
static const struct hashtab_key_params filenametr_key_params = {
|
||||
@@ -531,13 +543,13 @@ static bool add_filename_trans(struct policydb *db, const char *s,
|
||||
|
||||
struct filename_trans_datum *last = NULL;
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 9, 0)
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 9, 0)
|
||||
struct filename_trans_datum *trans =
|
||||
policydb_filenametr_search(db, &key);
|
||||
#else
|
||||
#else
|
||||
struct filename_trans_datum *trans =
|
||||
hashtab_search(&db->filename_trans, &key);
|
||||
#endif
|
||||
#endif
|
||||
while (trans) {
|
||||
if (ebitmap_get_bit(&trans->stypes, src->value - 1)) {
|
||||
// Duplicate, overwrite existing data and return
|
||||
@@ -551,15 +563,17 @@ static bool add_filename_trans(struct policydb *db, const char *s,
|
||||
}
|
||||
|
||||
if (trans == NULL) {
|
||||
trans = (struct filename_trans_datum*) kcalloc(sizeof(*trans), 1, GFP_ATOMIC);
|
||||
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);
|
||||
(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);
|
||||
hashtab_insert(&db->filename_trans, new_key, trans,
|
||||
filenametr_key_params);
|
||||
}
|
||||
|
||||
db->compat_filename_trans_count++;
|
||||
@@ -575,13 +589,15 @@ static bool add_filename_trans(struct policydb *db, const char *s,
|
||||
hashtab_search(db->filename_trans, &key);
|
||||
|
||||
if (trans == NULL) {
|
||||
trans = (struct filename_trans_datum*) kcalloc(sizeof(*trans), 1, GFP_ATOMIC);
|
||||
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);
|
||||
(struct filename_trans *)kmalloc(sizeof(*new_key),
|
||||
GFP_ATOMIC);
|
||||
if (!new_key) {
|
||||
pr_err("add_filename_trans: Failed to alloc new_key");
|
||||
return false;
|
||||
@@ -592,7 +608,8 @@ static bool add_filename_trans(struct policydb *db, const char *s,
|
||||
hashtab_insert(db->filename_trans, new_key, trans);
|
||||
}
|
||||
|
||||
return ebitmap_set_bit(&db->filename_trans_ttypes, src->value - 1, 1) == 0;
|
||||
return ebitmap_set_bit(&db->filename_trans_ttypes, src->value - 1, 1) ==
|
||||
0;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -679,17 +696,70 @@ static bool add_type(struct policydb *db, const char *type_name, bool attr)
|
||||
0);
|
||||
}
|
||||
|
||||
return true;
|
||||
#elif defined(CONFIG_IS_HW_HISI)
|
||||
/*
|
||||
* Huawei use type_attr_map and type_val_to_struct.
|
||||
* And use ebitmap not flex_array.
|
||||
*/
|
||||
size_t new_size = sizeof(struct ebitmap) * db->p_types.nprim;
|
||||
struct ebitmap *new_type_attr_map =
|
||||
(krealloc(db->type_attr_map, new_size, GFP_ATOMIC));
|
||||
|
||||
struct type_datum **new_type_val_to_struct =
|
||||
krealloc(db->type_val_to_struct,
|
||||
sizeof(*db->type_val_to_struct) * db->p_types.nprim,
|
||||
GFP_ATOMIC);
|
||||
|
||||
if (!new_type_attr_map) {
|
||||
pr_err("add_type: alloc type_attr_map failed\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!new_type_val_to_struct) {
|
||||
pr_err("add_type: alloc type_val_to_struct failed\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
char **new_val_to_name_types =
|
||||
krealloc(db->sym_val_to_name[SYM_TYPES],
|
||||
sizeof(char *) * db->symtab[SYM_TYPES].nprim,
|
||||
GFP_KERNEL);
|
||||
if (!new_val_to_name_types) {
|
||||
pr_err("add_type: alloc val_to_name failed\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
db->type_attr_map = new_type_attr_map;
|
||||
ebitmap_init(&db->type_attr_map[value - 1], HISI_SELINUX_EBITMAP_RO);
|
||||
ebitmap_set_bit(&db->type_attr_map[value - 1], value - 1, 1);
|
||||
|
||||
db->type_val_to_struct = new_type_val_to_struct;
|
||||
db->type_val_to_struct[value - 1] = type;
|
||||
|
||||
db->sym_val_to_name[SYM_TYPES] = new_val_to_name_types;
|
||||
db->sym_val_to_name[SYM_TYPES][value - 1] = key;
|
||||
|
||||
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;
|
||||
#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_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);
|
||||
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");
|
||||
@@ -707,20 +777,21 @@ static bool add_type(struct policydb *db, const char *type_name, bool attr)
|
||||
}
|
||||
|
||||
// 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)) {
|
||||
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)) {
|
||||
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)) {
|
||||
db->symtab[SYM_TYPES].nprim,
|
||||
GFP_ATOMIC | __GFP_ZERO)) {
|
||||
pr_err("add_type: prealloc val_to_name_types failed\n");
|
||||
return false;
|
||||
}
|
||||
@@ -731,25 +802,27 @@ static bool add_type(struct policydb *db, const char *type_name, bool attr)
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
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
|
||||
// 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;
|
||||
@@ -767,16 +840,16 @@ static bool add_type(struct policydb *db, const char *type_name, bool attr)
|
||||
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);
|
||||
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);
|
||||
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) {
|
||||
@@ -797,7 +870,7 @@ static bool set_type_state(struct policydb *db, const char *type_name,
|
||||
struct type_datum *type;
|
||||
if (type_name == NULL) {
|
||||
struct hashtab_node *node;
|
||||
hashtab_for_each(db->p_types.table, node)
|
||||
ksu_hashtab_for_each(db->p_types.table, node)
|
||||
{
|
||||
type = (struct type_datum *)(node->datum);
|
||||
if (ebitmap_set_bit(&db->permissive_map, type->value,
|
||||
@@ -825,6 +898,12 @@ static void add_typeattribute_raw(struct policydb *db, struct type_datum *type,
|
||||
{
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 1, 0)
|
||||
struct ebitmap *sattr = &db->type_attr_map_array[type->value - 1];
|
||||
#elif defined(CONFIG_IS_HW_HISI)
|
||||
/*
|
||||
* HISI_SELINUX_EBITMAP_RO is Huawei's unique features.
|
||||
*/
|
||||
struct ebitmap *sattr = &db->type_attr_map[type->value - 1],
|
||||
HISI_SELINUX_EBITMAP_RO;
|
||||
#else
|
||||
struct ebitmap *sattr =
|
||||
flex_array_get(db->type_attr_map_array, type->value - 1);
|
||||
@@ -834,7 +913,7 @@ static void add_typeattribute_raw(struct policydb *db, struct type_datum *type,
|
||||
struct hashtab_node *node;
|
||||
struct constraint_node *n;
|
||||
struct constraint_expr *e;
|
||||
hashtab_for_each(db->p_classes.table, node)
|
||||
ksu_hashtab_for_each(db->p_classes.table, node)
|
||||
{
|
||||
struct class_datum *cls = (struct class_datum *)(node->datum);
|
||||
for (n = cls->constraints; n; n = n->next) {
|
||||
|
||||
@@ -11,23 +11,40 @@ 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 '[+] You should modify this script by yourself.'
|
||||
exit 127
|
||||
fi
|
||||
|
||||
test -d "$GKI_ROOT/KernelSU" || git clone https://github.com/tiann/KernelSU
|
||||
cd "$GKI_ROOT/KernelSU"
|
||||
git stash && git pull
|
||||
git stash
|
||||
if [ "$(git status | grep -Po 'v\d+(\.\d+)*' | head -n1)" ]; then
|
||||
git checkout main
|
||||
fi
|
||||
git pull
|
||||
if [ -z "${1-}" ]; then
|
||||
git checkout "$(git describe --abbrev=0 --tags)"
|
||||
else
|
||||
git checkout "$1"
|
||||
fi
|
||||
cd "$GKI_ROOT"
|
||||
|
||||
echo "[+] GKI_ROOT: $GKI_ROOT"
|
||||
echo "[+] Copy kernel su driver to $DRIVER_DIR"
|
||||
|
||||
test -e "$DRIVER_DIR/kernelsu" || ln -sf "$GKI_ROOT/KernelSU/kernel" "$DRIVER_DIR/kernelsu"
|
||||
cd "$DRIVER_DIR"
|
||||
if test -d "$GKI_ROOT/common/drivers"; then
|
||||
ln -sf "../../KernelSU/kernel" "kernelsu"
|
||||
elif test -d "$GKI_ROOT/drivers"; then
|
||||
ln -sf "../KernelSU/kernel" "kernelsu"
|
||||
fi
|
||||
cd "$GKI_ROOT"
|
||||
|
||||
echo '[+] Add kernel su driver to Makefile'
|
||||
|
||||
DRIVER_MAKEFILE=$DRIVER_DIR/Makefile
|
||||
grep -q "kernelsu" "$DRIVER_MAKEFILE" || printf "\nobj-y += kernelsu/\n" >> "$DRIVER_MAKEFILE"
|
||||
DRIVER_KCONFIG=$DRIVER_DIR/Kconfig
|
||||
grep -q "kernelsu" "$DRIVER_MAKEFILE" || printf "obj-\$(CONFIG_KSU) += kernelsu/\n" >> "$DRIVER_MAKEFILE"
|
||||
grep -q "kernelsu" "$DRIVER_KCONFIG" || sed -i "/endmenu/i\\source \"drivers/kernelsu/Kconfig\"" "$DRIVER_KCONFIG"
|
||||
|
||||
echo '[+] Done.'
|
||||
echo '[+] Done.'
|
||||
@@ -53,7 +53,7 @@ int ksu_handle_faccessat(int *dfd, const char __user **filename_user, int *mode,
|
||||
if (IS_ERR(filename)) {
|
||||
return 0;
|
||||
}
|
||||
if (!memcmp(filename->name, su, sizeof(su))) {
|
||||
if (unlikely(!memcmp(filename->name, su, sizeof(su)))) {
|
||||
pr_info("faccessat su->sh!\n");
|
||||
*filename_user = sh_user_path();
|
||||
}
|
||||
@@ -73,7 +73,7 @@ int ksu_handle_stat(int *dfd, const char __user **filename_user, int *flags)
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!filename_user) {
|
||||
if (unlikely(!filename_user)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ int ksu_handle_stat(int *dfd, const char __user **filename_user, int *flags)
|
||||
if (IS_ERR(filename)) {
|
||||
return 0;
|
||||
}
|
||||
if (!memcmp(filename->name, su, sizeof(su))) {
|
||||
if (unlikely(!memcmp(filename->name, su, sizeof(su)))) {
|
||||
pr_info("newfstatat su->sh!\n");
|
||||
*filename_user = sh_user_path();
|
||||
}
|
||||
@@ -99,7 +99,7 @@ int ksu_handle_execveat_sucompat(int *fd, struct filename **filename_ptr,
|
||||
const char sh[] = KSUD_PATH;
|
||||
const char su[] = SU_PATH;
|
||||
|
||||
if (!filename_ptr)
|
||||
if (unlikely(!filename_ptr))
|
||||
return 0;
|
||||
|
||||
filename = *filename_ptr;
|
||||
@@ -107,16 +107,16 @@ int ksu_handle_execveat_sucompat(int *fd, struct filename **filename_ptr,
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!ksu_is_allow_uid(current_uid().val)) {
|
||||
if (likely(memcmp(filename->name, su, sizeof(su))))
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!memcmp(filename->name, su, sizeof(su))) {
|
||||
pr_info("do_execveat_common su found\n");
|
||||
memcpy((void *)filename->name, sh, sizeof(sh));
|
||||
if (!ksu_is_allow_uid(current_uid().val))
|
||||
return 0;
|
||||
|
||||
escape_to_root();
|
||||
}
|
||||
pr_info("do_execveat_common su found\n");
|
||||
memcpy((void *)filename->name, sh, sizeof(sh));
|
||||
|
||||
escape_to_root();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ static bool is_uid_exist(uid_t uid, void *data)
|
||||
|
||||
bool exist = false;
|
||||
list_for_each_entry (np, list, list) {
|
||||
if (np->uid == uid) {
|
||||
if (np->uid == uid % 100000) {
|
||||
exist = true;
|
||||
break;
|
||||
}
|
||||
@@ -44,7 +44,7 @@ static void do_update_uid(struct work_struct *work)
|
||||
if (IS_ERR(fp)) {
|
||||
pr_err("do_update_uid, open " SYSTEM_PACKAGES_LIST_PATH
|
||||
" failed: %d\n",
|
||||
ERR_PTR(fp));
|
||||
PTR_ERR(fp));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -56,13 +56,15 @@ static void do_update_uid(struct work_struct *work)
|
||||
loff_t line_start = 0;
|
||||
char buf[128];
|
||||
for (;;) {
|
||||
ssize_t count = ksu_kernel_read_compat(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 = ksu_kernel_read_compat(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);
|
||||
@@ -97,7 +99,10 @@ static void do_update_uid(struct work_struct *work)
|
||||
// first, check if manager_uid exist!
|
||||
bool manager_exist = false;
|
||||
list_for_each_entry (np, &uid_list, list) {
|
||||
if (np->uid == ksu_get_manager_uid()) {
|
||||
// if manager is installed in work profile, the uid in packages.list is still equals main profile
|
||||
// don't delete it in this case!
|
||||
int manager_uid = ksu_get_manager_uid() % 100000;
|
||||
if (np->uid == manager_uid) {
|
||||
manager_exist = true;
|
||||
break;
|
||||
}
|
||||
@@ -133,4 +138,4 @@ int ksu_uid_observer_init()
|
||||
int ksu_uid_observer_exit()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,4 +7,4 @@ int ksu_uid_observer_exit();
|
||||
|
||||
void update_uid();
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
18
manager/.gitignore
vendored
18
manager/.gitignore
vendored
@@ -1,17 +1,9 @@
|
||||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea/caches
|
||||
/.idea/libraries
|
||||
/.idea/modules.xml
|
||||
/.idea/workspace.xml
|
||||
/.idea/navEditor.xml
|
||||
/.idea/assetWizardSettings.xml
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
.cxx
|
||||
local.properties
|
||||
sign.properties
|
||||
.idea
|
||||
.DS_Store
|
||||
build
|
||||
captures
|
||||
.cxx
|
||||
key.jks
|
||||
|
||||
3
manager/.idea/.gitignore
generated
vendored
3
manager/.idea/.gitignore
generated
vendored
@@ -1,3 +0,0 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
1
manager/.idea/.name
generated
1
manager/.idea/.name
generated
@@ -1 +0,0 @@
|
||||
KernelSU
|
||||
6
manager/.idea/compiler.xml
generated
6
manager/.idea/compiler.xml
generated
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<bytecodeTargetLevel target="11" />
|
||||
</component>
|
||||
</project>
|
||||
20
manager/.idea/gradle.xml
generated
20
manager/.idea/gradle.xml
generated
@@ -1,20 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GradleMigrationSettings" migrationVersion="1" />
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
<option name="testRunner" value="GRADLE" />
|
||||
<option name="distributionType" value="DEFAULT_WRAPPED" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="gradleJvm" value="Embedded JDK" />
|
||||
<option name="modules">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
<option value="$PROJECT_DIR$/app" />
|
||||
</set>
|
||||
</option>
|
||||
</GradleProjectSettings>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
37
manager/.idea/inspectionProfiles/Project_Default.xml
generated
37
manager/.idea/inspectionProfiles/Project_Default.xml
generated
@@ -1,37 +0,0 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="PreviewAnnotationInFunctionWithParameters" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PreviewFontScaleMustBeGreaterThanZero" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PreviewMultipleParameterProviders" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
</profile>
|
||||
</component>
|
||||
10
manager/.idea/misc.xml
generated
10
manager/.idea/misc.xml
generated
@@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||
</component>
|
||||
<component name="ProjectType">
|
||||
<option name="id" value="Android" />
|
||||
</component>
|
||||
</project>
|
||||
6
manager/.idea/vcs.xml
generated
6
manager/.idea/vcs.xml
generated
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -1,37 +1,52 @@
|
||||
import com.android.build.gradle.internal.api.BaseVariantOutputImpl
|
||||
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
id("com.google.devtools.ksp")
|
||||
kotlin("android")
|
||||
alias(libs.plugins.agp.app)
|
||||
alias(libs.plugins.kotlin)
|
||||
alias(libs.plugins.ksp)
|
||||
alias(libs.plugins.lsplugin.apksign)
|
||||
id("kotlin-parcelize")
|
||||
}
|
||||
|
||||
val managerVersionCode: Int by rootProject.extra
|
||||
val managerVersionName: String by rootProject.extra
|
||||
|
||||
apksign {
|
||||
storeFileProperty = "KEYSTORE_FILE"
|
||||
storePasswordProperty = "KEYSTORE_PASSWORD"
|
||||
keyAliasProperty = "KEY_ALIAS"
|
||||
keyPasswordProperty = "KEY_PASSWORD"
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "me.weishu.kernelsu"
|
||||
|
||||
ndkVersion = "25.1.8937393"
|
||||
|
||||
defaultConfig {
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
ndk {
|
||||
abiFilters += listOf("arm64-v8a", "x86_64")
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = true
|
||||
isShrinkResources = true
|
||||
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
|
||||
}
|
||||
}
|
||||
|
||||
lint {
|
||||
checkReleaseBuilds = false
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
aidl = true
|
||||
buildConfig = true
|
||||
compose = true
|
||||
}
|
||||
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion = "1.3.2"
|
||||
kotlinOptions {
|
||||
jvmTarget = "17"
|
||||
}
|
||||
|
||||
packagingOptions {
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion = "1.4.3"
|
||||
}
|
||||
|
||||
packaging {
|
||||
jniLibs {
|
||||
useLegacyPackaging = true
|
||||
}
|
||||
resources {
|
||||
excludes += "/META-INF/{AL2.0,LGPL2.1}"
|
||||
}
|
||||
@@ -39,15 +54,14 @@ android {
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
path(file("src/main/cpp/CMakeLists.txt"))
|
||||
version = "3.18.1"
|
||||
path("src/main/cpp/CMakeLists.txt")
|
||||
}
|
||||
}
|
||||
|
||||
applicationVariants.all {
|
||||
outputs.forEach {
|
||||
val output = it as BaseVariantOutputImpl
|
||||
output.outputFileName = "KernelSU_$versionName-${buildType.name}.apk"
|
||||
output.outputFileName = "KernelSU_${managerVersionName}_${managerVersionCode}-$name.apk"
|
||||
}
|
||||
|
||||
kotlin.sourceSets {
|
||||
@@ -59,34 +73,42 @@ android {
|
||||
}
|
||||
|
||||
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")
|
||||
implementation("androidx.compose.ui:ui-tooling-preview")
|
||||
implementation("androidx.core:core-ktx:1.9.0")
|
||||
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.5.1")
|
||||
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-systemuicontroller:$accompanistVersion")
|
||||
implementation("io.github.raamcosta.compose-destinations:animations-core:$composeDestinationsVersion")
|
||||
implementation(libs.androidx.activity.compose)
|
||||
implementation(libs.androidx.navigation.compose)
|
||||
|
||||
implementation("io.coil-kt:coil-compose:2.2.2")
|
||||
implementation("me.zhanghai.android.appiconloader:appiconloader-coil:1.5.0")
|
||||
implementation(platform(libs.androidx.compose.bom))
|
||||
implementation(libs.androidx.compose.material.icons.extended)
|
||||
implementation(libs.androidx.compose.material)
|
||||
implementation(libs.androidx.compose.material3)
|
||||
implementation(libs.androidx.compose.ui)
|
||||
implementation(libs.androidx.compose.ui.tooling.preview)
|
||||
|
||||
implementation("com.github.topjohnwu.libsu:core:5.0.3")
|
||||
implementation("com.github.alorma:compose-settings-ui-m3:0.22.0")
|
||||
debugImplementation(libs.androidx.compose.ui.test.manifest)
|
||||
debugImplementation(libs.androidx.compose.ui.tooling)
|
||||
|
||||
ksp("io.github.raamcosta.compose-destinations:ksp:$composeDestinationsVersion")
|
||||
implementation(libs.androidx.lifecycle.runtime.compose)
|
||||
implementation(libs.androidx.lifecycle.runtime.ktx)
|
||||
implementation(libs.androidx.lifecycle.viewmodel.compose)
|
||||
|
||||
testImplementation("junit:junit:4.13.2")
|
||||
androidTestImplementation("androidx.test.ext:junit:1.1.4")
|
||||
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.0")
|
||||
implementation(libs.com.google.accompanist.drawablepainter)
|
||||
implementation(libs.com.google.accompanist.navigation.animation)
|
||||
implementation(libs.com.google.accompanist.systemuicontroller)
|
||||
|
||||
implementation(libs.compose.destinations.animations.core)
|
||||
ksp(libs.compose.destinations.ksp)
|
||||
|
||||
implementation(libs.com.github.topjohnwu.libsu.core)
|
||||
implementation(libs.com.github.topjohnwu.libsu.service)
|
||||
|
||||
implementation(libs.dev.rikka.rikkax.parcelablelist)
|
||||
|
||||
implementation(libs.io.coil.kt.coil.compose)
|
||||
|
||||
implementation(libs.kotlinx.coroutines.core)
|
||||
|
||||
implementation(libs.me.zhanghai.android.appiconloader.coil)
|
||||
|
||||
implementation(libs.sheet.compose.dialogs.core)
|
||||
implementation(libs.sheet.compose.dialogs.list)
|
||||
implementation(libs.sheet.compose.dialogs.input)
|
||||
}
|
||||
|
||||
30
manager/app/proguard-rules.pro
vendored
30
manager/app/proguard-rules.pro
vendored
@@ -1,21 +1,9 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
-dontwarn org.bouncycastle.jsse.BCSSLParameters
|
||||
-dontwarn org.bouncycastle.jsse.BCSSLSocket
|
||||
-dontwarn org.bouncycastle.jsse.provider.BouncyCastleJsseProvider
|
||||
-dontwarn org.conscrypt.Conscrypt$Version
|
||||
-dontwarn org.conscrypt.Conscrypt
|
||||
-dontwarn org.conscrypt.ConscryptHostnameVerifier
|
||||
-dontwarn org.openjsse.javax.net.ssl.SSLParameters
|
||||
-dontwarn org.openjsse.javax.net.ssl.SSLSocket
|
||||
-dontwarn org.openjsse.net.ssl.OpenJSSE
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
package me.weishu.kernelsu
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ExampleInstrumentedTest {
|
||||
@Test
|
||||
fun useAppContext() {
|
||||
// Context of the app under test.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
assertEquals("me.weishu.kernelsu", appContext.packageName)
|
||||
}
|
||||
}
|
||||
@@ -2,22 +2,19 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<uses-permission
|
||||
android:name="android.permission.QUERY_ALL_PACKAGES"
|
||||
tools:ignore="QueryAllPackagesPermission" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<application
|
||||
android:name=".KernelSUApplication"
|
||||
android:allowBackup="true"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:fullBackupContent="@xml/backup_rules"
|
||||
android:enableOnBackInvokedCallback="true"
|
||||
android:extractNativeLibs="true"
|
||||
android:fullBackupContent="@xml/backup_rules"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.KernelSU"
|
||||
tools:targetApi="31">
|
||||
tools:targetApi="33">
|
||||
<activity
|
||||
android:name=".ui.MainActivity"
|
||||
android:exported="true"
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
// IKsuInterface.aidl
|
||||
package me.weishu.kernelsu;
|
||||
|
||||
import android.content.pm.PackageInfo;
|
||||
import rikka.parcelablelist.ParcelableListSlice;
|
||||
|
||||
interface IKsuInterface {
|
||||
ParcelableListSlice<PackageInfo> getPackages(int flags);
|
||||
}
|
||||
@@ -3,15 +3,16 @@
|
||||
#include <sys/prctl.h>
|
||||
|
||||
#include <android/log.h>
|
||||
#include <cstring>
|
||||
|
||||
#include "ksu.h"
|
||||
|
||||
#define LOG_TAG "KernelSu"
|
||||
#define LOG_TAG "KernelSU"
|
||||
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_me_weishu_kernelsu_Natives_becomeManager(JNIEnv *env, jclass clazz, jstring pkg) {
|
||||
Java_me_weishu_kernelsu_Natives_becomeManager(JNIEnv *env, jobject, jstring pkg) {
|
||||
auto cpkg = env->GetStringUTFChars(pkg, nullptr);
|
||||
auto result = become_manager(cpkg);
|
||||
env->ReleaseStringUTFChars(pkg, cpkg);
|
||||
@@ -20,13 +21,13 @@ Java_me_weishu_kernelsu_Natives_becomeManager(JNIEnv *env, jclass clazz, jstring
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_me_weishu_kernelsu_Natives_getVersion(JNIEnv *env, jclass clazz) {
|
||||
Java_me_weishu_kernelsu_Natives_getVersion(JNIEnv *env, jobject) {
|
||||
return get_version();
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jintArray JNICALL
|
||||
Java_me_weishu_kernelsu_Natives_getAllowList(JNIEnv *env, jclass clazz) {
|
||||
Java_me_weishu_kernelsu_Natives_getAllowList(JNIEnv *env, jobject) {
|
||||
int uids[1024];
|
||||
int size = 0;
|
||||
bool result = get_allow_list(uids, &size);
|
||||
@@ -39,31 +40,260 @@ Java_me_weishu_kernelsu_Natives_getAllowList(JNIEnv *env, jclass clazz) {
|
||||
return env->NewIntArray(0);
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jintArray JNICALL
|
||||
Java_me_weishu_kernelsu_Natives_getDenyList(JNIEnv *env, jclass clazz) {
|
||||
int uids[1024];
|
||||
int size = 0;
|
||||
bool result = get_deny_list(uids, &size);
|
||||
if (result) {
|
||||
// success!
|
||||
auto array = env->NewIntArray(size);
|
||||
env->SetIntArrayRegion(array, 0, size, uids);
|
||||
return array;
|
||||
}
|
||||
return env->NewIntArray(0);
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_me_weishu_kernelsu_Natives_allowRoot(JNIEnv *env, jclass clazz, jint uid, jboolean allow) {
|
||||
return allow_su(uid, allow);
|
||||
}
|
||||
|
||||
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_me_weishu_kernelsu_Natives_isSafeMode(JNIEnv *env, jclass clazz) {
|
||||
return is_safe_mode();
|
||||
}
|
||||
|
||||
static void fillIntArray(JNIEnv *env, jobject list, int *data, int count) {
|
||||
auto cls = env->GetObjectClass(list);
|
||||
auto add = env->GetMethodID(cls, "add", "(Ljava/lang/Object;)Z");
|
||||
auto integerCls = env->FindClass("java/lang/Integer");
|
||||
auto constructor = env->GetMethodID(integerCls, "<init>", "(I)V");
|
||||
for (int i = 0; i < count; ++i) {
|
||||
auto integer = env->NewObject(integerCls, constructor, data[i]);
|
||||
env->CallBooleanMethod(list, add, integer);
|
||||
}
|
||||
}
|
||||
|
||||
static void addIntToList(JNIEnv *env, jobject list, int ele) {
|
||||
auto cls = env->GetObjectClass(list);
|
||||
auto add = env->GetMethodID(cls, "add", "(Ljava/lang/Object;)Z");
|
||||
auto integerCls = env->FindClass("java/lang/Integer");
|
||||
auto constructor = env->GetMethodID(integerCls, "<init>", "(I)V");
|
||||
auto integer = env->NewObject(integerCls, constructor, ele);
|
||||
env->CallBooleanMethod(list, add, integer);
|
||||
}
|
||||
|
||||
static uint64_t capListToBits(JNIEnv *env, jobject list) {
|
||||
auto cls = env->GetObjectClass(list);
|
||||
auto get = env->GetMethodID(cls, "get", "(I)Ljava/lang/Object;");
|
||||
auto size = env->GetMethodID(cls, "size", "()I");
|
||||
auto listSize = env->CallIntMethod(list, size);
|
||||
auto integerCls = env->FindClass("java/lang/Integer");
|
||||
auto intValue = env->GetMethodID(integerCls, "intValue", "()I");
|
||||
uint64_t result = 0;
|
||||
for (int i = 0; i < listSize; ++i) {
|
||||
auto integer = env->CallObjectMethod(list, get, i);
|
||||
int data = env->CallIntMethod(integer, intValue);
|
||||
|
||||
if (cap_valid(data)) {
|
||||
result |= (1ULL << data);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static int getListSize(JNIEnv *env, jobject list) {
|
||||
auto cls = env->GetObjectClass(list);
|
||||
auto size = env->GetMethodID(cls, "size", "()I");
|
||||
return env->CallIntMethod(list, size);
|
||||
}
|
||||
|
||||
static void fillArrayWithList(JNIEnv *env, jobject list, int *data, int count) {
|
||||
auto cls = env->GetObjectClass(list);
|
||||
auto get = env->GetMethodID(cls, "get", "(I)Ljava/lang/Object;");
|
||||
auto integerCls = env->FindClass("java/lang/Integer");
|
||||
auto intValue = env->GetMethodID(integerCls, "intValue", "()I");
|
||||
for (int i = 0; i < count; ++i) {
|
||||
auto integer = env->CallObjectMethod(list, get, i);
|
||||
data[i] = env->CallIntMethod(integer, intValue);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jobject JNICALL
|
||||
Java_me_weishu_kernelsu_Natives_getAppProfile(JNIEnv *env, jobject, jstring pkg, jint uid) {
|
||||
if (env->GetStringLength(pkg) > KSU_MAX_PACKAGE_NAME) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
p_key_t key = {};
|
||||
auto cpkg = env->GetStringUTFChars(pkg, nullptr);
|
||||
strcpy(key, cpkg);
|
||||
env->ReleaseStringUTFChars(pkg, cpkg);
|
||||
|
||||
app_profile profile = {};
|
||||
profile.version = KSU_APP_PROFILE_VER;
|
||||
|
||||
strcpy(profile.key, key);
|
||||
profile.current_uid = uid;
|
||||
|
||||
bool useDefaultProfile = !get_app_profile(key, &profile);
|
||||
|
||||
auto cls = env->FindClass("me/weishu/kernelsu/Natives$Profile");
|
||||
auto constructor = env->GetMethodID(cls, "<init>", "()V");
|
||||
auto obj = env->NewObject(cls, constructor);
|
||||
auto keyField = env->GetFieldID(cls, "name", "Ljava/lang/String;");
|
||||
auto currentUidField = env->GetFieldID(cls, "currentUid", "I");
|
||||
auto allowSuField = env->GetFieldID(cls, "allowSu", "Z");
|
||||
|
||||
auto rootUseDefaultField = env->GetFieldID(cls, "rootUseDefault", "Z");
|
||||
auto rootTemplateField = env->GetFieldID(cls, "rootTemplate", "Ljava/lang/String;");
|
||||
|
||||
auto uidField = env->GetFieldID(cls, "uid", "I");
|
||||
auto gidField = env->GetFieldID(cls, "gid", "I");
|
||||
auto groupsField = env->GetFieldID(cls, "groups", "Ljava/util/List;");
|
||||
auto capabilitiesField = env->GetFieldID(cls, "capabilities", "Ljava/util/List;");
|
||||
auto domainField = env->GetFieldID(cls, "context", "Ljava/lang/String;");
|
||||
auto namespacesField = env->GetFieldID(cls, "namespace", "I");
|
||||
|
||||
auto nonRootUseDefaultField = env->GetFieldID(cls, "nonRootUseDefault", "Z");
|
||||
auto umountModulesField = env->GetFieldID(cls, "umountModules", "Z");
|
||||
|
||||
env->SetObjectField(obj, keyField, env->NewStringUTF(profile.key));
|
||||
env->SetIntField(obj, currentUidField, profile.current_uid);
|
||||
|
||||
if (useDefaultProfile) {
|
||||
// no profile found, so just use default profile:
|
||||
// don't allow root and use default profile!
|
||||
LOGD("use default profile for: %s, %d", key, uid);
|
||||
|
||||
// allow_su = false
|
||||
// non root use default = true
|
||||
env->SetBooleanField(obj, allowSuField, false);
|
||||
env->SetBooleanField(obj, nonRootUseDefaultField, true);
|
||||
|
||||
jobject capList = env->GetObjectField(obj, capabilitiesField);
|
||||
int DEFAULT_CAPS[] = {CAP_DAC_READ_SEARCH};
|
||||
|
||||
for (auto i: DEFAULT_CAPS) {
|
||||
addIntToList(env, capList, i);
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
auto allowSu = profile.allow_su;
|
||||
|
||||
if (allowSu) {
|
||||
env->SetBooleanField(obj, rootUseDefaultField, (jboolean) profile.rp_config.use_default);
|
||||
if (strlen(profile.rp_config.template_name) > 0) {
|
||||
env->SetObjectField(obj, rootTemplateField,
|
||||
env->NewStringUTF(profile.rp_config.template_name));
|
||||
}
|
||||
|
||||
env->SetIntField(obj, uidField, profile.rp_config.profile.uid);
|
||||
env->SetIntField(obj, gidField, profile.rp_config.profile.gid);
|
||||
|
||||
jobject groupList = env->GetObjectField(obj, groupsField);
|
||||
int groupCount = profile.rp_config.profile.groups_count;
|
||||
if (groupCount > KSU_MAX_GROUPS) {
|
||||
LOGD("kernel group count too large: %d???", groupCount);
|
||||
groupCount = KSU_MAX_GROUPS;
|
||||
}
|
||||
fillIntArray(env, groupList, profile.rp_config.profile.groups, groupCount);
|
||||
|
||||
jobject capList = env->GetObjectField(obj, capabilitiesField);
|
||||
for (int i = 0; i <= CAP_LAST_CAP; i++) {
|
||||
if (profile.rp_config.profile.capabilities.effective & (1ULL << i)) {
|
||||
addIntToList(env, capList, i);
|
||||
}
|
||||
}
|
||||
|
||||
env->SetObjectField(obj, domainField,
|
||||
env->NewStringUTF(profile.rp_config.profile.selinux_domain));
|
||||
env->SetIntField(obj, namespacesField, profile.rp_config.profile.namespaces);
|
||||
env->SetBooleanField(obj, allowSuField, profile.allow_su);
|
||||
} else {
|
||||
env->SetBooleanField(obj, nonRootUseDefaultField,
|
||||
(jboolean) profile.nrp_config.use_default);
|
||||
env->SetBooleanField(obj, umountModulesField, profile.nrp_config.profile.umount_modules);
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_me_weishu_kernelsu_Natives_setAppProfile(JNIEnv *env, jobject clazz, jobject profile) {
|
||||
auto cls = env->FindClass("me/weishu/kernelsu/Natives$Profile");
|
||||
|
||||
auto keyField = env->GetFieldID(cls, "name", "Ljava/lang/String;");
|
||||
auto currentUidField = env->GetFieldID(cls, "currentUid", "I");
|
||||
auto allowSuField = env->GetFieldID(cls, "allowSu", "Z");
|
||||
|
||||
auto rootUseDefaultField = env->GetFieldID(cls, "rootUseDefault", "Z");
|
||||
auto rootTemplateField = env->GetFieldID(cls, "rootTemplate", "Ljava/lang/String;");
|
||||
|
||||
auto uidField = env->GetFieldID(cls, "uid", "I");
|
||||
auto gidField = env->GetFieldID(cls, "gid", "I");
|
||||
auto groupsField = env->GetFieldID(cls, "groups", "Ljava/util/List;");
|
||||
auto capabilitiesField = env->GetFieldID(cls, "capabilities", "Ljava/util/List;");
|
||||
auto domainField = env->GetFieldID(cls, "context", "Ljava/lang/String;");
|
||||
auto namespacesField = env->GetFieldID(cls, "namespace", "I");
|
||||
|
||||
auto nonRootUseDefaultField = env->GetFieldID(cls, "nonRootUseDefault", "Z");
|
||||
auto umountModulesField = env->GetFieldID(cls, "umountModules", "Z");
|
||||
|
||||
auto key = env->GetObjectField(profile, keyField);
|
||||
if (!key) {
|
||||
return false;
|
||||
}
|
||||
if (env->GetStringLength((jstring) key) > KSU_MAX_PACKAGE_NAME) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto cpkg = env->GetStringUTFChars((jstring) key, nullptr);
|
||||
p_key_t p_key = {};
|
||||
strcpy(p_key, cpkg);
|
||||
env->ReleaseStringUTFChars((jstring) key, cpkg);
|
||||
|
||||
auto currentUid = env->GetIntField(profile, currentUidField);
|
||||
|
||||
auto uid = env->GetIntField(profile, uidField);
|
||||
auto gid = env->GetIntField(profile, gidField);
|
||||
auto groups = env->GetObjectField(profile, groupsField);
|
||||
auto capabilities = env->GetObjectField(profile, capabilitiesField);
|
||||
auto domain = env->GetObjectField(profile, domainField);
|
||||
auto allowSu = env->GetBooleanField(profile, allowSuField);
|
||||
auto umountModules = env->GetBooleanField(profile, umountModulesField);
|
||||
|
||||
app_profile p = {};
|
||||
p.version = KSU_APP_PROFILE_VER;
|
||||
|
||||
strcpy(p.key, p_key);
|
||||
p.allow_su = allowSu;
|
||||
p.current_uid = currentUid;
|
||||
|
||||
if (allowSu) {
|
||||
p.rp_config.use_default = env->GetBooleanField(profile, rootUseDefaultField);
|
||||
auto templateName = env->GetObjectField(profile, rootTemplateField);
|
||||
if (templateName) {
|
||||
auto ctemplateName = env->GetStringUTFChars((jstring) templateName, nullptr);
|
||||
strcpy(p.rp_config.template_name, ctemplateName);
|
||||
env->ReleaseStringUTFChars((jstring) templateName, ctemplateName);
|
||||
}
|
||||
|
||||
p.rp_config.profile.uid = uid;
|
||||
p.rp_config.profile.gid = gid;
|
||||
|
||||
int groups_count = getListSize(env, groups);
|
||||
if (groups_count > KSU_MAX_GROUPS) {
|
||||
LOGD("groups count too large: %d", groups_count);
|
||||
return false;
|
||||
}
|
||||
p.rp_config.profile.groups_count = groups_count;
|
||||
fillArrayWithList(env, groups, p.rp_config.profile.groups, groups_count);
|
||||
|
||||
p.rp_config.profile.capabilities.effective = capListToBits(env, capabilities);
|
||||
|
||||
auto cdomain = env->GetStringUTFChars((jstring) domain, nullptr);
|
||||
strcpy(p.rp_config.profile.selinux_domain, cdomain);
|
||||
env->ReleaseStringUTFChars((jstring) domain, cdomain);
|
||||
|
||||
p.rp_config.profile.namespaces = env->GetIntField(profile, namespacesField);
|
||||
} else {
|
||||
p.nrp_config.use_default = env->GetBooleanField(profile, nonRootUseDefaultField);
|
||||
p.nrp_config.profile.umount_modules = umountModules;
|
||||
}
|
||||
|
||||
return set_app_profile(&p);
|
||||
}
|
||||
extern "C"
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_me_weishu_kernelsu_Natives_uidShouldUmount(JNIEnv *env, jobject thiz, jint uid) {
|
||||
return uid_should_umount(uid);
|
||||
}
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "ksu.h"
|
||||
|
||||
@@ -17,10 +18,16 @@
|
||||
#define CMD_GET_VERSION 2
|
||||
#define CMD_ALLOW_SU 3
|
||||
#define CMD_DENY_SU 4
|
||||
#define CMD_GET_ALLOW_LIST 5
|
||||
#define CMD_GET_SU_LIST 5
|
||||
#define CMD_GET_DENY_LIST 6
|
||||
#define CMD_CHECK_SAFEMODE 9
|
||||
|
||||
#define CMD_GET_APP_PROFILE 10
|
||||
#define CMD_SET_APP_PROFILE 11
|
||||
|
||||
#define CMD_IS_UID_GRANTED_ROOT 12
|
||||
#define CMD_IS_UID_SHOULD_UMOUNT 13
|
||||
|
||||
static bool ksuctl(int cmd, void* arg1, void* arg2) {
|
||||
int32_t result = 0;
|
||||
prctl(KERNEL_SU_OPTION, cmd, arg1, arg2, &result);
|
||||
@@ -29,7 +36,14 @@ static bool ksuctl(int cmd, void* arg1, void* arg2) {
|
||||
|
||||
bool become_manager(const char* pkg) {
|
||||
char param[128];
|
||||
sprintf(param, "/data/data/%s", pkg);
|
||||
uid_t uid = getuid();
|
||||
uint32_t userId = uid / 100000;
|
||||
if (userId == 0) {
|
||||
sprintf(param, "/data/data/%s", pkg);
|
||||
} else {
|
||||
snprintf(param, sizeof(param), "/data/user/%d/%s", userId, pkg);
|
||||
}
|
||||
|
||||
return ksuctl(CMD_BECOME_MANAGER, param, nullptr);
|
||||
}
|
||||
|
||||
@@ -41,19 +55,23 @@ int get_version() {
|
||||
return version;
|
||||
}
|
||||
|
||||
bool allow_su(int uid, bool allow) {
|
||||
int cmd = allow ? CMD_ALLOW_SU : CMD_DENY_SU;
|
||||
return ksuctl(cmd, (void*) uid, nullptr);
|
||||
}
|
||||
|
||||
bool get_allow_list(int *uids, int *size) {
|
||||
return ksuctl(CMD_GET_ALLOW_LIST, uids, size);
|
||||
}
|
||||
|
||||
bool get_deny_list(int *uids, int *size) {
|
||||
return ksuctl(CMD_GET_DENY_LIST, uids, size);
|
||||
return ksuctl(CMD_GET_SU_LIST, uids, size);
|
||||
}
|
||||
|
||||
bool is_safe_mode() {
|
||||
return ksuctl(CMD_CHECK_SAFEMODE, nullptr, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
bool uid_should_umount(int uid) {
|
||||
bool should;
|
||||
return ksuctl(CMD_IS_UID_SHOULD_UMOUNT, reinterpret_cast<void*>(uid), &should) && should;
|
||||
}
|
||||
|
||||
bool set_app_profile(const app_profile *profile) {
|
||||
return ksuctl(CMD_SET_APP_PROFILE, (void*) profile, nullptr);
|
||||
}
|
||||
|
||||
bool get_app_profile(p_key_t key, app_profile *profile) {
|
||||
return ksuctl(CMD_GET_APP_PROFILE, (void*) profile, nullptr);
|
||||
}
|
||||
|
||||
@@ -5,16 +5,76 @@
|
||||
#ifndef KERNELSU_KSU_H
|
||||
#define KERNELSU_KSU_H
|
||||
|
||||
bool become_manager(const char*);
|
||||
#include <linux/capability.h>
|
||||
|
||||
bool become_manager(const char *);
|
||||
|
||||
int get_version();
|
||||
|
||||
bool allow_su(int uid, bool allow);
|
||||
|
||||
bool get_allow_list(int *uids, int *size);
|
||||
|
||||
bool get_deny_list(int *uids, int *size);
|
||||
bool uid_should_umount(int uid);
|
||||
|
||||
bool is_safe_mode();
|
||||
|
||||
#define KSU_APP_PROFILE_VER 2
|
||||
#define KSU_MAX_PACKAGE_NAME 256
|
||||
// NGROUPS_MAX for Linux is 65535 generally, but we only supports 32 groups.
|
||||
#define KSU_MAX_GROUPS 32
|
||||
#define KSU_SELINUX_DOMAIN 64
|
||||
|
||||
using p_key_t = char[KSU_MAX_PACKAGE_NAME];
|
||||
|
||||
struct root_profile {
|
||||
int32_t uid;
|
||||
int32_t gid;
|
||||
|
||||
int32_t groups_count;
|
||||
int32_t groups[KSU_MAX_GROUPS];
|
||||
|
||||
// kernel_cap_t is u32[2] for capabilities v3
|
||||
struct {
|
||||
uint64_t effective;
|
||||
uint64_t permitted;
|
||||
uint64_t inheritable;
|
||||
} capabilities;
|
||||
|
||||
char selinux_domain[KSU_SELINUX_DOMAIN];
|
||||
|
||||
int32_t namespaces;
|
||||
};
|
||||
|
||||
struct non_root_profile {
|
||||
bool umount_modules;
|
||||
};
|
||||
|
||||
struct app_profile {
|
||||
// It may be utilized for backward compatibility, although we have never explicitly made any promises regarding this.
|
||||
uint32_t version;
|
||||
|
||||
// this is usually the package of the app, but can be other value for special apps
|
||||
char key[KSU_MAX_PACKAGE_NAME];
|
||||
int32_t current_uid;
|
||||
bool allow_su;
|
||||
|
||||
union {
|
||||
struct {
|
||||
bool use_default;
|
||||
char template_name[KSU_MAX_PACKAGE_NAME];
|
||||
|
||||
struct root_profile profile;
|
||||
} rp_config;
|
||||
|
||||
struct {
|
||||
bool use_default;
|
||||
|
||||
struct non_root_profile profile;
|
||||
} nrp_config;
|
||||
};
|
||||
};
|
||||
|
||||
bool set_app_profile(const app_profile *profile);
|
||||
|
||||
bool get_app_profile(p_key_t key, app_profile *profile);
|
||||
|
||||
#endif //KERNELSU_KSU_H
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
package me.weishu.kernelsu;
|
||||
|
||||
/**
|
||||
* @author weishu
|
||||
* @date 2022/12/8.
|
||||
*/
|
||||
public final class Natives {
|
||||
|
||||
static {
|
||||
System.loadLibrary("kernelsu");
|
||||
}
|
||||
|
||||
// become root manager, return true if success.
|
||||
public static native boolean becomeManager(String pkg);
|
||||
|
||||
public static native int getVersion();
|
||||
|
||||
// get the uid list of allowed su processes.
|
||||
public static native int[] getAllowList();
|
||||
|
||||
public static native int[] getDenyList();
|
||||
|
||||
public static native boolean allowRoot(int uid, boolean allow);
|
||||
|
||||
public static native boolean isSafeMode();
|
||||
}
|
||||
105
manager/app/src/main/java/me/weishu/kernelsu/Natives.kt
Normal file
105
manager/app/src/main/java/me/weishu/kernelsu/Natives.kt
Normal file
@@ -0,0 +1,105 @@
|
||||
package me.weishu.kernelsu
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.annotation.Keep
|
||||
import androidx.compose.runtime.Immutable
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
/**
|
||||
* @author weishu
|
||||
* @date 2022/12/8.
|
||||
*/
|
||||
object Natives {
|
||||
// minimal supported kernel version
|
||||
// 10915: allowlist breaking change, add app profile
|
||||
// 10931: app profile struct add 'version' field
|
||||
// 10946: add capabilities
|
||||
// 10977: change groups_count and groups to avoid overflow write
|
||||
const val MINIMAL_SUPPORTED_KERNEL = 10977
|
||||
|
||||
init {
|
||||
System.loadLibrary("kernelsu")
|
||||
}
|
||||
|
||||
// become root manager, return true if success.
|
||||
external fun becomeManager(pkg: String?): Boolean
|
||||
val version: Int
|
||||
external get
|
||||
|
||||
// get the uid list of allowed su processes.
|
||||
val allowList: IntArray
|
||||
external get
|
||||
|
||||
val isSafeMode: Boolean
|
||||
external get
|
||||
|
||||
external fun uidShouldUmount(uid: Int): Boolean
|
||||
|
||||
/**
|
||||
* Get the profile of the given package.
|
||||
* @param key usually the package name
|
||||
* @return return null if failed.
|
||||
*/
|
||||
external fun getAppProfile(key: String?, uid: Int): Profile
|
||||
external fun setAppProfile(profile: Profile?): Boolean
|
||||
|
||||
private const val NON_ROOT_DEFAULT_PROFILE_KEY = "$"
|
||||
private const val ROOT_DEFAULT_PROFILE_KEY = "#"
|
||||
private const val NOBODY_UID = 9999
|
||||
|
||||
fun setDefaultUmountModules(umountModules: Boolean): Boolean {
|
||||
Profile(
|
||||
NON_ROOT_DEFAULT_PROFILE_KEY,
|
||||
NOBODY_UID,
|
||||
false,
|
||||
umountModules = umountModules
|
||||
).let {
|
||||
return setAppProfile(it)
|
||||
}
|
||||
}
|
||||
|
||||
fun isDefaultUmountModules(): Boolean {
|
||||
getAppProfile(NON_ROOT_DEFAULT_PROFILE_KEY, NOBODY_UID).let {
|
||||
return it.umountModules
|
||||
}
|
||||
}
|
||||
|
||||
fun requireNewKernel(): Boolean {
|
||||
return version < MINIMAL_SUPPORTED_KERNEL
|
||||
}
|
||||
|
||||
@Immutable
|
||||
@Parcelize
|
||||
@Keep
|
||||
data class Profile(
|
||||
// and there is a default profile for root and non-root
|
||||
val name: String,
|
||||
// current uid for the package, this is convivent for kernel to check
|
||||
// if the package name doesn't match uid, then it should be invalidated.
|
||||
val currentUid: Int = 0,
|
||||
|
||||
// if this is true, kernel will grant root permission to this package
|
||||
val allowSu: Boolean = false,
|
||||
|
||||
// these are used for root profile
|
||||
val rootUseDefault: Boolean = true,
|
||||
val rootTemplate: String? = null,
|
||||
val uid: Int = 0,
|
||||
val gid: Int = 0,
|
||||
val groups: List<Int> = mutableListOf(),
|
||||
val capabilities: List<Int> = mutableListOf(),
|
||||
val context: String = "u:r:su:s0",
|
||||
val namespace: Int = Namespace.Inherited.ordinal,
|
||||
|
||||
val nonRootUseDefault: Boolean = true,
|
||||
val umountModules: Boolean = true,
|
||||
) : Parcelable {
|
||||
enum class Namespace {
|
||||
Inherited,
|
||||
Global,
|
||||
Individual,
|
||||
}
|
||||
|
||||
constructor() : this("")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package me.weishu.kernelsu.profile
|
||||
|
||||
/**
|
||||
* @author weishu
|
||||
* @date 2023/6/3.
|
||||
*/
|
||||
enum class Capabilities(val cap: Int, val display: String, val desc: String) {
|
||||
CAP_CHOWN(0, "CHOWN", "Make arbitrary changes to file UIDs and GIDs (see chown(2))"),
|
||||
CAP_DAC_OVERRIDE(1, "DAC_OVERRIDE", "Bypass file read, write, and execute permission checks"),
|
||||
CAP_DAC_READ_SEARCH(2, "DAC_READ_SEARCH", "Bypass file read permission checks and directory read and execute permission checks"),
|
||||
CAP_FOWNER(3, "FOWNER", "Bypass permission checks on operations that normally require the filesystem UID of the process to match the UID of the file (e.g., chmod(2), utime(2)), excluding those operations covered by CAP_DAC_OVERRIDE and CAP_DAC_READ_SEARCH"),
|
||||
CAP_FSETID(4, "FSETID", "Don’t clear set-user-ID and set-group-ID permission bits when a file is modified; set the set-group-ID bit for a file whose GID does not match the filesystem or any of the supplementary GIDs of the calling process"),
|
||||
CAP_KILL(5, "KILL", "Bypass permission checks for sending signals (see kill(2))."),
|
||||
CAP_SETGID(6, "SETGID", "Make arbitrary manipulations of process GIDs and supplementary GID list; allow setgid(2) manipulation of the caller’s effective and real group IDs"),
|
||||
CAP_SETUID(7, "SETUID", "Make arbitrary manipulations of process UIDs (setuid(2), setreuid(2), setresuid(2), setfsuid(2)); allow changing the current process user IDs; allow changing of the current process group ID to any value in the system’s range of legal group IDs"),
|
||||
CAP_SETPCAP(8, "SETPCAP", "If file capabilities are supported: grant or remove any capability in the caller’s permitted capability set to or from any other process. (This property supersedes the obsolete notion of giving a process all capabilities by granting all capabilities in its permitted set, and of removing all capabilities from a process by granting no capabilities in its permitted set. It does not permit any actions that were not permitted before.)"),
|
||||
CAP_LINUX_IMMUTABLE(9, "LINUX_IMMUTABLE", "Set the FS_APPEND_FL and FS_IMMUTABLE_FL inode flags (see chattr(1))."),
|
||||
CAP_NET_BIND_SERVICE(10, "NET_BIND_SERVICE", "Bind a socket to Internet domain"),
|
||||
CAP_NET_BROADCAST(11, "NET_BROADCAST", "Make socket broadcasts, and listen to multicasts"),
|
||||
CAP_NET_ADMIN(12, "NET_ADMIN", "Perform various network-related operations: interface configuration, administration of IP firewall, masquerading, and accounting, modify routing tables, bind to any address for transparent proxying, set type-of-service (TOS), clear driver statistics, set promiscuous mode, enabling multicasting, use setsockopt(2) to set the following socket options: SO_DEBUG, SO_MARK, SO_PRIORITY (for a priority outside the range 0 to 6), SO_RCVBUFFORCE, and SO_SNDBUFFORCE"),
|
||||
CAP_NET_RAW(13, "NET_RAW", "Use RAW and PACKET sockets"),
|
||||
CAP_IPC_LOCK(14, "IPC_LOCK", "Lock memory (mlock(2), mlockall(2), mmap(2), shmctl(2))"),
|
||||
CAP_IPC_OWNER(15, "IPC_OWNER", "Bypass permission checks for operations on System V IPC objects"),
|
||||
CAP_SYS_MODULE(16, "SYS_MODULE", "Load and unload kernel modules (see init_module(2) and delete_module(2)); in kernels before 2.6.25, this also granted rights for various other operations related to kernel modules"),
|
||||
CAP_SYS_RAWIO(17, "SYS_RAWIO", "Perform I/O port operations (iopl(2) and ioperm(2)); access /proc/kcore"),
|
||||
CAP_SYS_CHROOT(18, "SYS_CHROOT", "Use chroot(2)"),
|
||||
CAP_SYS_PTRACE(19, "SYS_PTRACE", "Trace arbitrary processes using ptrace(2)"),
|
||||
CAP_SYS_PACCT(20, "SYS_PACCT", "Use acct(2)"),
|
||||
CAP_SYS_ADMIN(21, "SYS_ADMIN", "Perform a range of system administration operations including: quotactl(2), mount(2), umount(2), swapon(2), swapoff(2), sethostname(2), and setdomainname(2); set and modify process resource limits (setrlimit(2)); perform various network-related operations (e.g., setting privileged socket options, enabling multicasting, interface configuration); perform various IPC operations (e.g., SysV semaphores, POSIX message queues, System V shared memory); allow reboot and kexec_load(2); override /proc/sys kernel tunables; perform ptrace(2) PTRACE_SECCOMP_GET_FILTER operation; perform some tracing and debugging operations (see ptrace(2)); administer the lifetime of kernel tracepoints (tracefs(5)); perform the KEYCTL_CHOWN and KEYCTL_SETPERM keyctl(2) operations; perform the following keyctl(2) operations: KEYCTL_CAPABILITIES, KEYCTL_CAPSQUASH, and KEYCTL_PKEY_ OPERATIONS; set state for the Extensible Authentication Protocol (EAP) kernel module; and override the RLIMIT_NPROC resource limit; allow ioperm/iopl access to I/O ports"),
|
||||
CAP_SYS_BOOT(22, "SYS_BOOT", "Use reboot(2) and kexec_load(2), reboot and load a new kernel for later execution"),
|
||||
CAP_SYS_NICE(23, "SYS_NICE", "Raise process nice value (nice(2), setpriority(2)) and change the nice value for arbitrary processes; set real-time scheduling policies for calling process, and set scheduling policies and priorities for arbitrary processes (sched_setscheduler(2), sched_setparam(2)"),
|
||||
CAP_SYS_RESOURCE(24, "SYS_RESOURCE", "Override resource Limits. Set resource limits (setrlimit(2), prlimit(2)), override quota limits (quota(2), quotactl(2)), override reserved space on ext2 filesystem (ext2_ioctl(2)), override size restrictions on IPC message queues (msg(2)) and system V shared memory segments (shmget(2)), and override the /proc/sys/fs/pipe-size-max limit"),
|
||||
CAP_SYS_TIME(25, "SYS_TIME", "Set system clock (settimeofday(2), stime(2), adjtimex(2)); set real-time (hardware) clock"),
|
||||
CAP_SYS_TTY_CONFIG(26, "SYS_TTY_CONFIG", "Use vhangup(2); employ various privileged ioctl(2) operations on virtual terminals"),
|
||||
CAP_MKNOD(27, "MKNOD", "Create special files using mknod(2)"),
|
||||
CAP_LEASE(28, "LEASE", "Establish leases on arbitrary files (see fcntl(2))"),
|
||||
CAP_AUDIT_WRITE(29, "AUDIT_WRITE", "Write records to kernel auditing log"),
|
||||
CAP_AUDIT_CONTROL(30, "AUDIT_CONTROL", "Enable and disable kernel auditing; change auditing filter rules; retrieve auditing status and filtering rules"),
|
||||
CAP_SETFCAP(31, "SETFCAP", "If file capabilities are supported: grant or remove any capability in any capability set to any file"),
|
||||
CAP_MAC_OVERRIDE(32, "MAC_OVERRIDE", "Override Mandatory Access Control (MAC). Implemented for the Smack Linux Security Module (LSM)"),
|
||||
CAP_MAC_ADMIN(33, "MAC_ADMIN", "Allow MAC configuration or state changes. Implemented for the Smack LSM"),
|
||||
CAP_SYSLOG(34, "SYSLOG", "Perform privileged syslog(2) operations. See syslog(2) for information on which operations require privilege"),
|
||||
CAP_WAKE_ALARM(35, "WAKE_ALARM", "Trigger something that will wake up the system"),
|
||||
CAP_BLOCK_SUSPEND(36, "BLOCK_SUSPEND", "Employ features that can block system suspend"),
|
||||
CAP_AUDIT_READ(37, "AUDIT_READ", "Allow reading the audit log via a multicast netlink socket"),
|
||||
CAP_PERFMON(38, "PERFMON", "Allow performance monitoring via perf_event_open(2)"),
|
||||
CAP_BPF(39, "BPF", "Allow BPF operations via bpf(2)"),
|
||||
CAP_CHECKPOINT_RESTORE(40, "CHECKPOINT_RESTORE", "Allow processes to be checkpointed via checkpoint/restore in user namespace(2)"),
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package me.weishu.kernelsu.profile
|
||||
|
||||
/**
|
||||
* @author weishu
|
||||
* @date 2023/6/3.
|
||||
*/
|
||||
enum class Groups(val gid: Int, val display: String, val desc: String) {
|
||||
ROOT(0, "root", "traditional unix root user"),
|
||||
DAEMON(1, "daemon", "Traditional unix daemon owner."),
|
||||
BIN(2, "bin", "Traditional unix binaries owner."),
|
||||
SYS(3, "sys", "A group with the same gid on Linux/macOS/Android."),
|
||||
SYSTEM(1000, "system", "system server"),
|
||||
RADIO(1001, "radio", "telephony subsystem, RIL"),
|
||||
BLUETOOTH(1002, "bluetooth", "bluetooth subsystem"),
|
||||
GRAPHICS(1003, "graphics", "graphics devices"),
|
||||
INPUT(1004, "input", "input devices"),
|
||||
AUDIO(1005, "audio", "audio devices"),
|
||||
CAMERA(1006, "camera", "camera devices"),
|
||||
LOG(1007, "log", "log devices"),
|
||||
COMPASS(1008, "compass", "compass device"),
|
||||
MOUNT(1009, "mount", "mountd socket"),
|
||||
WIFI(1010, "wifi", "wifi subsystem"),
|
||||
ADB(1011, "adb", "android debug bridge (adbd)"),
|
||||
INSTALL(1012, "install", "group for installing packages"),
|
||||
MEDIA(1013, "media", "mediaserver process"),
|
||||
DHCP(1014, "dhcp", "dhcp client"),
|
||||
SDCARD_RW(1015, "sdcard_rw", "external storage write access"),
|
||||
VPN(1016, "vpn", "vpn system"),
|
||||
KEYSTORE(1017, "keystore", "keystore subsystem"),
|
||||
USB(1018, "usb", "USB devices"),
|
||||
DRM(1019, "drm", "DRM server"),
|
||||
MDNSR(1020, "mdnsr", "MulticastDNSResponder (service discovery)"),
|
||||
GPS(1021, "gps", "GPS daemon"),
|
||||
UNUSED1(1022, "unused1", "deprecated, DO NOT USE"),
|
||||
MEDIA_RW(1023, "media_rw", "internal media storage write access"),
|
||||
MTP(1024, "mtp", "MTP USB driver access"),
|
||||
UNUSED2(1025, "unused2", "deprecated, DO NOT USE"),
|
||||
DRMRPC(1026, "drmrpc", "group for drm rpc"),
|
||||
NFC(1027, "nfc", "nfc subsystem"),
|
||||
SDCARD_R(1028, "sdcard_r", "external storage read access"),
|
||||
CLAT(1029, "clat", "clat part of nat464"),
|
||||
LOOP_RADIO(1030, "loop_radio", "loop radio devices"),
|
||||
MEDIA_DRM(1031, "media_drm", "MediaDrm plugins"),
|
||||
PACKAGE_INFO(1032, "package_info", "access to installed package details"),
|
||||
SDCARD_PICS(1033, "sdcard_pics", "external storage photos access"),
|
||||
SDCARD_AV(1034, "sdcard_av", "external storage audio/video access"),
|
||||
SDCARD_ALL(1035, "sdcard_all", "access all users external storage"),
|
||||
LOGD(1036, "logd", "log daemon"),
|
||||
SHARED_RELRO(1037, "shared_relro", "creator of shared GNU RELRO files"),
|
||||
DBUS(1038, "dbus", "dbus-daemon IPC broker process"),
|
||||
TLSDATE(1039, "tlsdate", "tlsdate unprivileged user"),
|
||||
MEDIA_EX(1040, "media_ex", "mediaextractor process"),
|
||||
AUDIOSERVER(1041, "audioserver", "audioserver process"),
|
||||
METRICS_COLL(1042, "metrics_coll", "metrics_collector process"),
|
||||
METRICSD(1043, "metricsd", "metricsd process"),
|
||||
WEBSERV(1044, "webserv", "webservd process"),
|
||||
DEBUGGERD(1045, "debuggerd", "debuggerd unprivileged user"),
|
||||
MEDIA_CODEC(1046, "media_codec", "media_codec process"),
|
||||
CAMERASERVER(1047, "cameraserver", "cameraserver process"),
|
||||
FIREWALL(1048, "firewall", "firewall process"),
|
||||
TRUNKS(1049, "trunks", "trunksd process"),
|
||||
NVRAM(1050, "nvram", "nvram daemon"),
|
||||
DNS_TETHER(1051, "dns_tether", "dns_tether device"),
|
||||
DNS_TETHER_RESERVED(1052, "dns_tether_reserved", "Reserved range for dns_tether"),
|
||||
WEBVIEW_ZYGOTE(1053, "webview_zygote", "zygote process"),
|
||||
WEBVIEW_USER(1054, "webview_user", "webview chromium user"),
|
||||
ETHERNET(1055, "ethernet", "Ethernet"),
|
||||
TOMBSTONED(1056, "tombstoned", "tombstoned process"),
|
||||
GRAPHICS_RW(1057, "graphics_rw", "graphics devices"),
|
||||
|
||||
SHELL(2000, "shell", "adb and debug shell user"),
|
||||
CACHE(2001, "cache", "cache access"),
|
||||
DIAG(2002, "diag", "diagnostics"),
|
||||
NET_BT_ADMIN(3001, "net_bt_admin", "bluetooth: create any socket"),
|
||||
NET_BT(3002, "net_bt", "bluetooth: create sco, rfcomm or l2cap sockets"),
|
||||
INET(3003, "inet", "can create AF_INET and AF_INET6 sockets"),
|
||||
NET_RAW(3004, "net_raw", "can create raw INET sockets"),
|
||||
NET_ADMIN(3005, "net_admin", "can configure interfaces and routing tables."),
|
||||
NET_BW_STATS(3006, "net_bw_stats", "read bandwidth statistics"),
|
||||
NET_BW_ACCT(3007, "net_bw_acct", "change bandwidth statistics accounting"),
|
||||
NET_BT_STACK(3008, "net_bt_stack", "access to various bluetooth management functions"),
|
||||
QCOM_DIAG(3009, "qcom_diag", "allow msm specific diag commands"),
|
||||
EVERYBODY(9997, "everybody", "Shared external storage read/write"),
|
||||
MISC(9998, "misc", "Access to misc storage"),
|
||||
NOBODY(9999, "nobody", "Reserved"),
|
||||
APP(10000, "app", "Access to app data"),
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package me.weishu.kernelsu.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.IBinder;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.topjohnwu.superuser.ipc.RootService;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import me.weishu.kernelsu.IKsuInterface;
|
||||
import rikka.parcelablelist.ParcelableListSlice;
|
||||
|
||||
/**
|
||||
* @author weishu
|
||||
* @date 2023/4/18.
|
||||
*/
|
||||
|
||||
public class KsuService extends RootService {
|
||||
|
||||
private static final String TAG = "KsuService";
|
||||
|
||||
class Stub extends IKsuInterface.Stub {
|
||||
@Override
|
||||
public ParcelableListSlice<PackageInfo> getPackages(int flags) {
|
||||
List<PackageInfo> list = getInstalledPackagesAll(flags);
|
||||
Log.i(TAG, "getPackages: " + list.size());
|
||||
return new ParcelableListSlice<>(list);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(@NonNull Intent intent) {
|
||||
return new Stub();
|
||||
}
|
||||
|
||||
List<Integer> getUserIds() {
|
||||
List<Integer> result = new ArrayList<>();
|
||||
UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
|
||||
List<UserHandle> userProfiles = um.getUserProfiles();
|
||||
for (UserHandle userProfile : userProfiles) {
|
||||
int userId = userProfile.hashCode();
|
||||
result.add(userProfile.hashCode());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
ArrayList<PackageInfo> getInstalledPackagesAll(int flags) {
|
||||
ArrayList<PackageInfo> packages = new ArrayList<>();
|
||||
for (Integer userId : getUserIds()) {
|
||||
Log.i(TAG, "getInstalledPackagesAll: " + userId);
|
||||
packages.addAll(getInstalledPackagesAsUser(flags, userId));
|
||||
}
|
||||
return packages;
|
||||
}
|
||||
|
||||
List<PackageInfo> getInstalledPackagesAsUser(int flags, int userId) {
|
||||
try {
|
||||
PackageManager pm = getPackageManager();
|
||||
Method getInstalledPackagesAsUser = pm.getClass().getDeclaredMethod("getInstalledPackagesAsUser", int.class, int.class);
|
||||
return (List<PackageInfo>) getInstalledPackagesAsUser.invoke(pm, flags, userId);
|
||||
} catch (Throwable e) {
|
||||
Log.e(TAG, "err", e);
|
||||
}
|
||||
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
@@ -5,30 +5,37 @@ import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.compose.animation.ExperimentalAnimationApi
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.NavigationBar
|
||||
import androidx.compose.material3.NavigationBarItem
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavGraph.Companion.findStartDestination
|
||||
import androidx.navigation.NavHostController
|
||||
import com.google.accompanist.navigation.animation.rememberAnimatedNavController
|
||||
import com.ramcosta.composedestinations.DestinationsNavHost
|
||||
import com.ramcosta.composedestinations.navigation.popBackStack
|
||||
import com.ramcosta.composedestinations.utils.isRouteOnBackStackAsState
|
||||
import me.weishu.kernelsu.Natives
|
||||
import me.weishu.kernelsu.ksuApp
|
||||
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() {
|
||||
|
||||
@OptIn(ExperimentalAnimationApi::class, ExperimentalMaterial3Api::class)
|
||||
@OptIn(ExperimentalAnimationApi::class)
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
@@ -58,31 +65,29 @@ class MainActivity : ComponentActivity() {
|
||||
|
||||
@Composable
|
||||
private fun BottomBar(navController: NavHostController) {
|
||||
val topDestination: Destination = navController.appCurrentDestinationAsState().value
|
||||
?: NavGraphs.root.startAppDestination
|
||||
val bottomBarRoutes = remember {
|
||||
BottomBarDestination.values().map { it.direction.route }
|
||||
}
|
||||
|
||||
val isManager = Natives.becomeManager(ksuApp.packageName)
|
||||
val fullFeatured = isManager && !Natives.requireNewKernel()
|
||||
NavigationBar(tonalElevation = 8.dp) {
|
||||
BottomBarDestination.values().forEach { destination ->
|
||||
if (!fullFeatured && destination.rootRequired) return@forEach
|
||||
val isCurrentDestOnBackStack by navController.isRouteOnBackStackAsState(destination.direction)
|
||||
NavigationBarItem(
|
||||
selected = topDestination.route == destination.direction.route,
|
||||
selected = isCurrentDestOnBackStack,
|
||||
onClick = {
|
||||
val firstRoute = navController.backQueue.reversed().first {
|
||||
it.destination.route in bottomBarRoutes
|
||||
}.destination.route
|
||||
if (isCurrentDestOnBackStack) {
|
||||
navController.popBackStack(destination.direction, false)
|
||||
}
|
||||
|
||||
navController.navigate(destination.direction.route) {
|
||||
popUpTo(navController.graph.findStartDestination().id) {
|
||||
saveState = firstRoute != destination.direction.route
|
||||
popUpTo(NavGraphs.root.route) {
|
||||
saveState = true
|
||||
}
|
||||
launchSingleTop = true
|
||||
restoreState = true
|
||||
}
|
||||
},
|
||||
icon = {
|
||||
if (topDestination.route == destination.direction.route) {
|
||||
if (isCurrentDestOnBackStack) {
|
||||
Icon(destination.iconSelected, stringResource(destination.label))
|
||||
} else {
|
||||
Icon(destination.iconNotSelected, stringResource(destination.label))
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
package me.weishu.kernelsu.ui.component
|
||||
|
||||
import android.text.method.LinkMovementMethod
|
||||
import android.widget.TextView
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.ElevatedCard
|
||||
import androidx.compose.material3.LocalContentColor
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.core.text.HtmlCompat
|
||||
import com.google.accompanist.drawablepainter.rememberDrawablePainter
|
||||
import me.weishu.kernelsu.BuildConfig
|
||||
import me.weishu.kernelsu.R
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun AboutCard() {
|
||||
ElevatedCard(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(),
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(24.dp)
|
||||
) {
|
||||
AboutCardContent()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun AboutDialog(showAboutDialog: MutableState<Boolean>) {
|
||||
if (showAboutDialog.value) {
|
||||
Dialog(onDismissRequest = { showAboutDialog.value = false }) {
|
||||
AboutCard()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AboutCardContent() {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
val drawable = ResourcesCompat.getDrawable(
|
||||
LocalContext.current.resources,
|
||||
R.mipmap.ic_launcher,
|
||||
LocalContext.current.theme
|
||||
)
|
||||
|
||||
Row {
|
||||
Image(
|
||||
painter = rememberDrawablePainter(drawable),
|
||||
contentDescription = "icon",
|
||||
modifier = Modifier.size(40.dp)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(12.dp))
|
||||
|
||||
Column {
|
||||
|
||||
Text(
|
||||
stringResource(id = R.string.app_name),
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
fontSize = 18.sp
|
||||
)
|
||||
Text(
|
||||
BuildConfig.VERSION_NAME,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
fontSize = 14.sp
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
HtmlText(
|
||||
html = stringResource(
|
||||
id = R.string.about_source_code,
|
||||
"<b><a href=\"https://github.com/tiann/KernelSU\">GitHub</a></b>",
|
||||
"<b><a href=\"https://t.me/KernelSU\">Telegram</a></b>"
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun HtmlText(html: String, modifier: Modifier = Modifier) {
|
||||
val contentColor = LocalContentColor.current
|
||||
AndroidView(
|
||||
modifier = modifier,
|
||||
factory = { context ->
|
||||
TextView(context).also {
|
||||
it.movementMethod = LinkMovementMethod.getInstance()
|
||||
}
|
||||
},
|
||||
update = {
|
||||
it.text = HtmlCompat.fromHtml(html, HtmlCompat.FROM_HTML_MODE_COMPACT)
|
||||
it.setTextColor(contentColor.toArgb())
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -1,56 +1,113 @@
|
||||
package me.weishu.kernelsu.ui.component
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import kotlinx.coroutines.CancellableContinuation
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
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
|
||||
|
||||
interface DialogVisuals {
|
||||
interface LoadingDialogVisuals : DialogVisuals
|
||||
|
||||
interface PromptDialogVisuals : DialogVisuals {
|
||||
val title: String
|
||||
val content: String
|
||||
}
|
||||
|
||||
interface ConfirmDialogVisuals : PromptDialogVisuals {
|
||||
val confirm: String?
|
||||
val dismiss: String?
|
||||
}
|
||||
|
||||
interface DialogData {
|
||||
|
||||
sealed interface DialogData {
|
||||
val visuals: DialogVisuals
|
||||
}
|
||||
|
||||
fun confirm()
|
||||
|
||||
interface LoadingDialogData : DialogData {
|
||||
override val visuals: LoadingDialogVisuals
|
||||
fun dismiss()
|
||||
}
|
||||
|
||||
interface PromptDialogData : DialogData {
|
||||
override val visuals: PromptDialogVisuals
|
||||
fun dismiss()
|
||||
}
|
||||
|
||||
interface ConfirmDialogData : PromptDialogData {
|
||||
override val visuals: ConfirmDialogVisuals
|
||||
fun confirm()
|
||||
}
|
||||
|
||||
sealed interface ConfirmResult {
|
||||
object Confirmed : ConfirmResult
|
||||
object Canceled : ConfirmResult
|
||||
}
|
||||
|
||||
class DialogHostState {
|
||||
|
||||
private data class DialogVisualsImpl(
|
||||
private object LoadingDialogVisualsImpl : LoadingDialogVisuals
|
||||
|
||||
private data class PromptDialogVisualsImpl(
|
||||
override val title: String,
|
||||
override val content: String
|
||||
) : PromptDialogVisuals
|
||||
|
||||
private data class ConfirmDialogVisualsImpl(
|
||||
override val title: String,
|
||||
override val content: String,
|
||||
override val confirm: String?,
|
||||
override val dismiss: String?
|
||||
) : DialogVisuals
|
||||
) : ConfirmDialogVisuals
|
||||
|
||||
private data class DialogDataImpl(
|
||||
override val visuals: DialogVisuals,
|
||||
val continuation: CancellableContinuation<DialogResult>
|
||||
) : DialogData {
|
||||
private data class LoadingDialogDataImpl(
|
||||
override val visuals: LoadingDialogVisuals,
|
||||
private val continuation: CancellableContinuation<Unit>,
|
||||
) : LoadingDialogData {
|
||||
override fun dismiss() {
|
||||
if (continuation.isActive) continuation.resume(Unit)
|
||||
}
|
||||
}
|
||||
|
||||
private data class PromptDialogDataImpl(
|
||||
override val visuals: PromptDialogVisuals,
|
||||
private val continuation: CancellableContinuation<Unit>,
|
||||
) : PromptDialogData {
|
||||
override fun dismiss() {
|
||||
if (continuation.isActive) continuation.resume(Unit)
|
||||
}
|
||||
}
|
||||
|
||||
private data class ConfirmDialogDataImpl(
|
||||
override val visuals: ConfirmDialogVisuals,
|
||||
private val continuation: CancellableContinuation<ConfirmResult>
|
||||
) : ConfirmDialogData {
|
||||
|
||||
override fun confirm() {
|
||||
if (continuation.isActive) continuation.resume(DialogResult.Confirmed)
|
||||
if (continuation.isActive) continuation.resume(ConfirmResult.Confirmed)
|
||||
}
|
||||
|
||||
override fun dismiss() {
|
||||
if (continuation.isActive) continuation.resume(DialogResult.Dismissed)
|
||||
if (continuation.isActive) continuation.resume(ConfirmResult.Canceled)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,16 +116,58 @@ class DialogHostState {
|
||||
var currentDialogData by mutableStateOf<DialogData?>(null)
|
||||
private set
|
||||
|
||||
suspend fun showDialog(
|
||||
suspend fun showLoading() {
|
||||
try {
|
||||
mutex.withLock {
|
||||
suspendCancellableCoroutine { continuation ->
|
||||
currentDialogData = LoadingDialogDataImpl(
|
||||
visuals = LoadingDialogVisualsImpl,
|
||||
continuation = continuation
|
||||
)
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
currentDialogData = null
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun <R> withLoading(block: suspend () -> R) = coroutineScope {
|
||||
val showLoading = launch {
|
||||
showLoading()
|
||||
}
|
||||
|
||||
val result = block()
|
||||
|
||||
showLoading.cancel()
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
suspend fun showPrompt(title: String, content: String) {
|
||||
try {
|
||||
mutex.withLock {
|
||||
suspendCancellableCoroutine { continuation ->
|
||||
currentDialogData = PromptDialogDataImpl(
|
||||
visuals = PromptDialogVisualsImpl(title, content),
|
||||
continuation = continuation
|
||||
)
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
currentDialogData = null
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun showConfirm(
|
||||
title: String,
|
||||
content: String,
|
||||
confirm: String? = null,
|
||||
dismiss: String? = null
|
||||
): DialogResult = mutex.withLock {
|
||||
): ConfirmResult = mutex.withLock {
|
||||
try {
|
||||
return@withLock suspendCancellableCoroutine { continuation ->
|
||||
currentDialogData = DialogDataImpl(
|
||||
visuals = DialogVisualsImpl(title, content, confirm, dismiss),
|
||||
currentDialogData = ConfirmDialogDataImpl(
|
||||
visuals = ConfirmDialogVisualsImpl(title, content, confirm, dismiss),
|
||||
continuation = continuation
|
||||
)
|
||||
}
|
||||
@@ -85,82 +184,85 @@ fun rememberDialogHostState(): 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)
|
||||
}
|
||||
)
|
||||
private inline fun <reified T : DialogData> DialogData?.tryInto(): T? {
|
||||
return when (this) {
|
||||
is T -> this
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SimpleDialog(
|
||||
fun LoadingDialog(
|
||||
state: DialogHostState = LocalDialogHost.current,
|
||||
content: @Composable (String) -> Unit
|
||||
) {
|
||||
BaseDialog(
|
||||
state = state,
|
||||
state.currentDialogData.tryInto<LoadingDialogData>() ?: return
|
||||
val dialogProperties = remember {
|
||||
DialogProperties(dismissOnClickOutside = false, dismissOnBackPress = false)
|
||||
}
|
||||
Dialog(onDismissRequest = {}, properties = dialogProperties) {
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.size(100.dp),
|
||||
shape = RoundedCornerShape(8.dp)
|
||||
) {
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
CircularProgressIndicator()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun PromptDialog(
|
||||
state: DialogHostState = LocalDialogHost.current,
|
||||
) {
|
||||
val promptDialogData = state.currentDialogData.tryInto<PromptDialogData>() ?: return
|
||||
|
||||
val visuals = promptDialogData.visuals
|
||||
AlertDialog(
|
||||
onDismissRequest = {
|
||||
promptDialogData.dismiss()
|
||||
},
|
||||
title = {
|
||||
Text(text = it)
|
||||
Text(text = visuals.title)
|
||||
},
|
||||
confirmButton = { text, confirm ->
|
||||
text?.let {
|
||||
TextButton(onClick = confirm) {
|
||||
Text(text = it)
|
||||
}
|
||||
text = {
|
||||
Text(text = visuals.content)
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(onClick = { promptDialogData.dismiss() }) {
|
||||
Text(text = stringResource(id = android.R.string.ok))
|
||||
}
|
||||
},
|
||||
dismissButton = { text, dismiss ->
|
||||
text?.let {
|
||||
TextButton(onClick = dismiss) {
|
||||
Text(text = it)
|
||||
}
|
||||
}
|
||||
},
|
||||
content = content
|
||||
dismissButton = null,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ConfirmDialog(state: DialogHostState = LocalDialogHost.current) {
|
||||
BaseDialog(
|
||||
state = state,
|
||||
title = {
|
||||
Text(text = it)
|
||||
val confirmDialogData = state.currentDialogData.tryInto<ConfirmDialogData>() ?: return
|
||||
|
||||
val visuals = confirmDialogData.visuals
|
||||
AlertDialog(
|
||||
onDismissRequest = {
|
||||
confirmDialogData.dismiss()
|
||||
},
|
||||
confirmButton = { text, confirm ->
|
||||
text?.let {
|
||||
TextButton(onClick = confirm) {
|
||||
Text(text = it)
|
||||
}
|
||||
title = {
|
||||
Text(text = visuals.title)
|
||||
},
|
||||
text = {
|
||||
Text(text = visuals.content)
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(onClick = { confirmDialogData.confirm() }) {
|
||||
Text(text = visuals.confirm ?: stringResource(id = android.R.string.ok))
|
||||
}
|
||||
},
|
||||
dismissButton = { text, dismiss ->
|
||||
text?.let {
|
||||
TextButton(onClick = dismiss) {
|
||||
Text(text = it)
|
||||
}
|
||||
dismissButton = {
|
||||
TextButton(onClick = { confirmDialogData.dismiss() }) {
|
||||
Text(text = visuals.dismiss ?: stringResource(id = android.R.string.cancel))
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
@@ -10,10 +10,22 @@ 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.*
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material.icons.filled.Search
|
||||
import androidx.compose.material.icons.outlined.ArrowBack
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -21,11 +33,9 @@ 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"
|
||||
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package me.weishu.kernelsu.ui.component
|
||||
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.ListItem
|
||||
import androidx.compose.material3.RadioButton
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
|
||||
@Composable
|
||||
fun SwitchItem(
|
||||
icon: ImageVector? = null,
|
||||
title: String,
|
||||
summary: String? = null,
|
||||
checked: Boolean,
|
||||
enabled: Boolean = true,
|
||||
onCheckedChange: (Boolean) -> Unit
|
||||
) {
|
||||
ListItem(
|
||||
headlineContent = {
|
||||
Text(title)
|
||||
},
|
||||
leadingContent = icon?.let {
|
||||
{ Icon(icon, title) }
|
||||
},
|
||||
trailingContent = {
|
||||
Switch(checked = checked, enabled = enabled, onCheckedChange = onCheckedChange)
|
||||
},
|
||||
supportingContent = {
|
||||
if (summary != null) {
|
||||
Text(summary)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun RadioItem(
|
||||
title: String,
|
||||
selected: Boolean,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
ListItem(
|
||||
headlineContent = {
|
||||
Text(title)
|
||||
},
|
||||
leadingContent = {
|
||||
RadioButton(selected = selected, onClick = onClick)
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package me.weishu.kernelsu.ui.component.profile
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import me.weishu.kernelsu.Natives
|
||||
import me.weishu.kernelsu.R
|
||||
import me.weishu.kernelsu.ui.component.SwitchItem
|
||||
|
||||
@Composable
|
||||
fun AppProfileConfig(
|
||||
modifier: Modifier = Modifier,
|
||||
fixedName: Boolean,
|
||||
enabled: Boolean,
|
||||
profile: Natives.Profile,
|
||||
onProfileChange: (Natives.Profile) -> Unit,
|
||||
) {
|
||||
Column(modifier = modifier) {
|
||||
if (!fixedName) {
|
||||
OutlinedTextField(
|
||||
label = { Text(stringResource(R.string.profile_name)) },
|
||||
value = profile.name,
|
||||
onValueChange = { onProfileChange(profile.copy(name = it)) }
|
||||
)
|
||||
}
|
||||
|
||||
SwitchItem(
|
||||
title = stringResource(R.string.profile_umount_modules),
|
||||
summary = stringResource(R.string.profile_umount_modules_summary),
|
||||
checked = if (enabled) {
|
||||
profile.umountModules
|
||||
} else {
|
||||
Natives.isDefaultUmountModules()
|
||||
},
|
||||
enabled = enabled,
|
||||
onCheckedChange = {
|
||||
onProfileChange(
|
||||
profile.copy(
|
||||
umountModules = it,
|
||||
nonRootUseDefault = false
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun AppProfileConfigPreview() {
|
||||
var profile by remember { mutableStateOf(Natives.Profile("")) }
|
||||
AppProfileConfig(fixedName = true, enabled = false, profile = profile) {
|
||||
profile = it
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,461 @@
|
||||
@file:OptIn(ExperimentalMaterial3Api::class)
|
||||
|
||||
package me.weishu.kernelsu.ui.component.profile
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||
import androidx.compose.foundation.layout.FlowRow
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
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.ArrowDropDown
|
||||
import androidx.compose.material.icons.filled.ArrowDropUp
|
||||
import androidx.compose.material3.AssistChip
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ExposedDropdownMenuBox
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.ListItem
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedCard
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextFieldDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.text.isDigitsOnly
|
||||
import com.maxkeppeker.sheets.core.models.base.Header
|
||||
import com.maxkeppeker.sheets.core.models.base.rememberUseCaseState
|
||||
import com.maxkeppeler.sheets.input.InputDialog
|
||||
import com.maxkeppeler.sheets.input.models.InputHeader
|
||||
import com.maxkeppeler.sheets.input.models.InputSelection
|
||||
import com.maxkeppeler.sheets.input.models.InputTextField
|
||||
import com.maxkeppeler.sheets.input.models.InputTextFieldType
|
||||
import com.maxkeppeler.sheets.input.models.ValidationResult
|
||||
import com.maxkeppeler.sheets.list.ListDialog
|
||||
import com.maxkeppeler.sheets.list.models.ListOption
|
||||
import com.maxkeppeler.sheets.list.models.ListSelection
|
||||
import me.weishu.kernelsu.Natives
|
||||
import me.weishu.kernelsu.R
|
||||
import me.weishu.kernelsu.profile.Capabilities
|
||||
import me.weishu.kernelsu.profile.Groups
|
||||
import me.weishu.kernelsu.ui.util.isSepolicyValid
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun RootProfileConfig(
|
||||
modifier: Modifier = Modifier,
|
||||
fixedName: Boolean,
|
||||
profile: Natives.Profile,
|
||||
onProfileChange: (Natives.Profile) -> Unit,
|
||||
) {
|
||||
Column(modifier = modifier) {
|
||||
if (!fixedName) {
|
||||
OutlinedTextField(
|
||||
label = { Text(stringResource(R.string.profile_name)) },
|
||||
value = profile.name,
|
||||
onValueChange = { onProfileChange(profile.copy(name = it)) }
|
||||
)
|
||||
}
|
||||
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
val currentNamespace = when (profile.namespace) {
|
||||
Natives.Profile.Namespace.Inherited.ordinal -> stringResource(R.string.profile_namespace_inherited)
|
||||
Natives.Profile.Namespace.Global.ordinal -> stringResource(R.string.profile_namespace_global)
|
||||
Natives.Profile.Namespace.Individual.ordinal -> stringResource(R.string.profile_namespace_individual)
|
||||
else -> stringResource(R.string.profile_namespace_inherited)
|
||||
}
|
||||
ListItem(headlineContent = {
|
||||
ExposedDropdownMenuBox(
|
||||
expanded = expanded,
|
||||
onExpandedChange = { expanded = !expanded }
|
||||
) {
|
||||
OutlinedTextField(
|
||||
modifier = Modifier
|
||||
.menuAnchor()
|
||||
.fillMaxWidth(),
|
||||
readOnly = true,
|
||||
label = { Text(stringResource(R.string.profile_namespace)) },
|
||||
value = currentNamespace,
|
||||
onValueChange = {},
|
||||
trailingIcon = {
|
||||
if (expanded) Icon(Icons.Filled.ArrowDropUp, null)
|
||||
else Icon(Icons.Filled.ArrowDropDown, null)
|
||||
},
|
||||
)
|
||||
ExposedDropdownMenu(
|
||||
expanded = expanded,
|
||||
onDismissRequest = { expanded = false }
|
||||
) {
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.profile_namespace_inherited)) },
|
||||
onClick = {
|
||||
onProfileChange(profile.copy(namespace = Natives.Profile.Namespace.Inherited.ordinal))
|
||||
expanded = false
|
||||
},
|
||||
)
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.profile_namespace_global)) },
|
||||
onClick = {
|
||||
onProfileChange(profile.copy(namespace = Natives.Profile.Namespace.Global.ordinal))
|
||||
expanded = false
|
||||
},
|
||||
)
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.profile_namespace_individual)) },
|
||||
onClick = {
|
||||
onProfileChange(profile.copy(namespace = Natives.Profile.Namespace.Individual.ordinal))
|
||||
expanded = false
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
UidPanel(uid = profile.uid, label = "uid", onUidChange = {
|
||||
onProfileChange(
|
||||
profile.copy(
|
||||
uid = it,
|
||||
rootUseDefault = false
|
||||
)
|
||||
)
|
||||
})
|
||||
|
||||
UidPanel(uid = profile.gid, label = "gid", onUidChange = {
|
||||
onProfileChange(
|
||||
profile.copy(
|
||||
gid = it,
|
||||
rootUseDefault = false
|
||||
)
|
||||
)
|
||||
})
|
||||
|
||||
val selectedGroups = profile.groups.ifEmpty { listOf(0) }.let { e ->
|
||||
e.mapNotNull { g ->
|
||||
Groups.values().find { it.gid == g }
|
||||
}
|
||||
}
|
||||
GroupsPanel(selectedGroups) {
|
||||
onProfileChange(
|
||||
profile.copy(
|
||||
groups = it.map { group -> group.gid }.ifEmpty { listOf(0) },
|
||||
rootUseDefault = false
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
val selectedCaps = profile.capabilities.mapNotNull { e ->
|
||||
Capabilities.values().find { it.cap == e }
|
||||
}
|
||||
|
||||
CapsPanel(selectedCaps) {
|
||||
onProfileChange(
|
||||
profile.copy(
|
||||
capabilities = it.map { cap -> cap.cap },
|
||||
rootUseDefault = false
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
SELinuxPanel(profile = profile, onSELinuxChange = { domain, rules ->
|
||||
onProfileChange(
|
||||
profile.copy(
|
||||
context = domain,
|
||||
rootUseDefault = false
|
||||
)
|
||||
)
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
fun GroupsPanel(selected: List<Groups>, closeSelection: (selection: Set<Groups>) -> Unit) {
|
||||
|
||||
var showDialog by remember { mutableStateOf(false) }
|
||||
|
||||
if (showDialog) {
|
||||
val groups = Groups.values()
|
||||
val options = groups.map { value ->
|
||||
ListOption(
|
||||
titleText = value.display,
|
||||
subtitleText = value.desc,
|
||||
selected = selected.contains(value),
|
||||
)
|
||||
}
|
||||
|
||||
val selection = HashSet(selected)
|
||||
ListDialog(
|
||||
state = rememberUseCaseState(visible = true, onFinishedRequest = {
|
||||
closeSelection(selection)
|
||||
}, onCloseRequest = {
|
||||
showDialog = false
|
||||
}),
|
||||
header = Header.Default(
|
||||
title = stringResource(R.string.profile_groups),
|
||||
),
|
||||
selection = ListSelection.Multiple(
|
||||
showCheckBoxes = true,
|
||||
options = options,
|
||||
maxChoices = 32, // Kernel only supports 32 groups at most
|
||||
) { indecies, _ ->
|
||||
// Handle selection
|
||||
selection.clear()
|
||||
indecies.forEach { index ->
|
||||
val group = groups[index]
|
||||
selection.add(group)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
OutlinedCard(modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
.clickable {
|
||||
showDialog = true
|
||||
}) {
|
||||
|
||||
Column(modifier = Modifier.padding(16.dp)) {
|
||||
Text(stringResource(R.string.profile_groups))
|
||||
FlowRow {
|
||||
selected.forEach { group ->
|
||||
AssistChip(
|
||||
modifier = Modifier.padding(3.dp),
|
||||
onClick = { /*TODO*/ },
|
||||
label = { Text(group.display) })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
fun CapsPanel(
|
||||
selected: Collection<Capabilities>,
|
||||
closeSelection: (selection: Set<Capabilities>) -> Unit
|
||||
) {
|
||||
|
||||
var showDialog by remember { mutableStateOf(false) }
|
||||
|
||||
if (showDialog) {
|
||||
val caps = Capabilities.values()
|
||||
val options = caps.map { value ->
|
||||
ListOption(
|
||||
titleText = value.display,
|
||||
subtitleText = value.desc,
|
||||
selected = selected.contains(value),
|
||||
)
|
||||
}
|
||||
|
||||
val selection = HashSet(selected)
|
||||
ListDialog(
|
||||
state = rememberUseCaseState(visible = true, onFinishedRequest = {
|
||||
closeSelection(selection)
|
||||
}, onCloseRequest = {
|
||||
showDialog = false
|
||||
}),
|
||||
header = Header.Default(
|
||||
title = stringResource(R.string.profile_capabilities),
|
||||
),
|
||||
selection = ListSelection.Multiple(
|
||||
showCheckBoxes = true,
|
||||
options = options
|
||||
) { indecies, _ ->
|
||||
// Handle selection
|
||||
selection.clear()
|
||||
indecies.forEach { index ->
|
||||
val group = caps[index]
|
||||
selection.add(group)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
OutlinedCard(modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
.clickable {
|
||||
showDialog = true
|
||||
}) {
|
||||
|
||||
Column(modifier = Modifier.padding(16.dp)) {
|
||||
Text(stringResource(R.string.profile_capabilities))
|
||||
FlowRow {
|
||||
selected.forEach { group ->
|
||||
AssistChip(
|
||||
modifier = Modifier.padding(3.dp),
|
||||
onClick = { /*TODO*/ },
|
||||
label = { Text(group.display) })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalComposeUiApi::class)
|
||||
@Composable
|
||||
private fun UidPanel(uid: Int, label: String, onUidChange: (Int) -> Unit) {
|
||||
|
||||
ListItem(headlineContent = {
|
||||
var isError by remember {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
var lastValidUid by remember {
|
||||
mutableStateOf(uid)
|
||||
}
|
||||
|
||||
val keyboardController = LocalSoftwareKeyboardController.current
|
||||
OutlinedTextField(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
label = { Text(label) },
|
||||
value = uid.toString(),
|
||||
isError = isError,
|
||||
keyboardOptions = KeyboardOptions(
|
||||
keyboardType = KeyboardType.Number,
|
||||
imeAction = ImeAction.Done
|
||||
),
|
||||
keyboardActions = KeyboardActions(onDone = {
|
||||
keyboardController?.hide()
|
||||
}),
|
||||
onValueChange = {
|
||||
if (it.isEmpty()) {
|
||||
onUidChange(0)
|
||||
return@OutlinedTextField
|
||||
}
|
||||
val valid = isTextValidUid(it)
|
||||
|
||||
val targetUid = if (valid) it.toInt() else lastValidUid
|
||||
if (valid) {
|
||||
lastValidUid = it.toInt()
|
||||
}
|
||||
|
||||
onUidChange(targetUid)
|
||||
|
||||
isError = !valid
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SELinuxPanel(profile: Natives.Profile, onSELinuxChange: (domain: String, rules: String) -> Unit) {
|
||||
var showDialog by remember { mutableStateOf(false) }
|
||||
if (showDialog) {
|
||||
var domain by remember { mutableStateOf(profile.context) }
|
||||
var rules by remember { mutableStateOf("") }
|
||||
|
||||
val inputOptions = listOf(
|
||||
InputTextField(
|
||||
text = domain,
|
||||
header = InputHeader(
|
||||
title = stringResource(id = R.string.profile_selinux_domain),
|
||||
),
|
||||
type = InputTextFieldType.OUTLINED,
|
||||
required = true,
|
||||
keyboardOptions = KeyboardOptions(
|
||||
keyboardType = KeyboardType.Ascii,
|
||||
imeAction = ImeAction.Next
|
||||
),
|
||||
resultListener = {
|
||||
domain = it ?: ""
|
||||
},
|
||||
validationListener = { value ->
|
||||
// value can be a-zA-Z0-9_
|
||||
val regex = Regex("^[a-z_]+:[a-z0-9_]+:[a-z0-9_]+(:[a-z0-9_]+)?$")
|
||||
if (value?.matches(regex) == true) ValidationResult.Valid
|
||||
else ValidationResult.Invalid("Domain must be valid sepolicy")
|
||||
}
|
||||
),
|
||||
InputTextField(
|
||||
text = rules,
|
||||
header = InputHeader(
|
||||
title = stringResource(id = R.string.profile_selinux_rules),
|
||||
),
|
||||
type = InputTextFieldType.OUTLINED,
|
||||
keyboardOptions = KeyboardOptions(
|
||||
keyboardType = KeyboardType.Ascii,
|
||||
imeAction = ImeAction.Done
|
||||
),
|
||||
singleLine = false,
|
||||
resultListener = {
|
||||
rules = it ?: ""
|
||||
},
|
||||
validationListener = { value ->
|
||||
if (isSepolicyValid(value)) ValidationResult.Valid
|
||||
else ValidationResult.Invalid("Rules must be valid sepolicy")
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
InputDialog(
|
||||
state = rememberUseCaseState(visible = true,
|
||||
onFinishedRequest = {
|
||||
onSELinuxChange(domain, rules)
|
||||
},
|
||||
onCloseRequest = {
|
||||
showDialog = false
|
||||
}),
|
||||
header = Header.Default(
|
||||
title = stringResource(R.string.profile_selinux_context),
|
||||
),
|
||||
selection = InputSelection(
|
||||
input = inputOptions,
|
||||
onPositiveClick = { result ->
|
||||
// Handle selection
|
||||
},
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
ListItem(headlineContent = {
|
||||
OutlinedTextField(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable {
|
||||
showDialog = true
|
||||
},
|
||||
enabled = false,
|
||||
colors = TextFieldDefaults.outlinedTextFieldColors(
|
||||
disabledTextColor = MaterialTheme.colorScheme.onSurface,
|
||||
disabledBorderColor = MaterialTheme.colorScheme.outline,
|
||||
disabledPlaceholderColor = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
disabledLabelColor = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
),
|
||||
label = { Text(text = stringResource(R.string.profile_selinux_context)) },
|
||||
value = profile.context,
|
||||
onValueChange = { },
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun RootProfileConfigPreview() {
|
||||
var profile by remember { mutableStateOf(Natives.Profile("")) }
|
||||
RootProfileConfig(fixedName = true, profile = profile) {
|
||||
profile = it
|
||||
}
|
||||
}
|
||||
|
||||
private fun isTextValidUid(text: String): Boolean {
|
||||
return text.isNotEmpty() && text.isDigitsOnly() && text.toInt() >= 0 && text.toInt() <= Int.MAX_VALUE
|
||||
}
|
||||
@@ -0,0 +1,308 @@
|
||||
package me.weishu.kernelsu.ui.screen
|
||||
|
||||
import android.util.Log
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.animation.Crossfade
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.AccountCircle
|
||||
import androidx.compose.material.icons.filled.Android
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.ArrowDropDown
|
||||
import androidx.compose.material.icons.filled.ArrowDropUp
|
||||
import androidx.compose.material.icons.filled.Security
|
||||
import androidx.compose.material3.Divider
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ExposedDropdownMenuBox
|
||||
import androidx.compose.material3.FilterChip
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.ListItem
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import coil.compose.AsyncImage
|
||||
import coil.request.ImageRequest
|
||||
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.SwitchItem
|
||||
import me.weishu.kernelsu.ui.component.profile.AppProfileConfig
|
||||
import me.weishu.kernelsu.ui.component.profile.RootProfileConfig
|
||||
import me.weishu.kernelsu.ui.util.LocalSnackbarHost
|
||||
import me.weishu.kernelsu.ui.viewmodel.SuperUserViewModel
|
||||
|
||||
/**
|
||||
* @author weishu
|
||||
* @date 2023/5/16.
|
||||
*/
|
||||
@Destination
|
||||
@Composable
|
||||
fun AppProfileScreen(
|
||||
navigator: DestinationsNavigator,
|
||||
appInfo: SuperUserViewModel.AppInfo,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val snackbarHost = LocalSnackbarHost.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val failToUpdateAppProfile =
|
||||
stringResource(R.string.failed_to_update_app_profile).format(appInfo.label)
|
||||
|
||||
val packageName = appInfo.packageName
|
||||
var profile by rememberSaveable {
|
||||
mutableStateOf(Natives.getAppProfile(packageName, appInfo.uid))
|
||||
}
|
||||
|
||||
Log.i("mylog", "profile: $profile")
|
||||
|
||||
Scaffold(
|
||||
topBar = { TopBar { navigator.popBackStack() } }
|
||||
) { paddingValues ->
|
||||
AppProfileInner(
|
||||
modifier = Modifier
|
||||
.padding(paddingValues)
|
||||
.verticalScroll(rememberScrollState()),
|
||||
packageName = appInfo.packageName,
|
||||
appLabel = appInfo.label,
|
||||
appIcon = {
|
||||
AsyncImage(
|
||||
model = ImageRequest.Builder(context)
|
||||
.data(appInfo.packageInfo)
|
||||
.crossfade(true)
|
||||
.build(),
|
||||
contentDescription = appInfo.label,
|
||||
modifier = Modifier
|
||||
.padding(4.dp)
|
||||
.width(48.dp)
|
||||
.height(48.dp)
|
||||
)
|
||||
},
|
||||
profile = profile,
|
||||
onProfileChange = {
|
||||
scope.launch {
|
||||
if (!Natives.setAppProfile(it)) {
|
||||
snackbarHost.showSnackbar(failToUpdateAppProfile.format(appInfo.uid))
|
||||
} else {
|
||||
profile = it
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun AppProfileInner(
|
||||
modifier: Modifier = Modifier,
|
||||
packageName: String,
|
||||
appLabel: String,
|
||||
appIcon: @Composable () -> Unit,
|
||||
profile: Natives.Profile,
|
||||
onProfileChange: (Natives.Profile) -> Unit,
|
||||
) {
|
||||
val isRootGranted = profile.allowSu
|
||||
|
||||
Column(modifier = modifier) {
|
||||
ListItem(
|
||||
headlineContent = { Text(appLabel) },
|
||||
supportingContent = { Text(packageName) },
|
||||
leadingContent = appIcon,
|
||||
)
|
||||
|
||||
SwitchItem(
|
||||
icon = Icons.Filled.Security,
|
||||
title = stringResource(id = R.string.superuser),
|
||||
checked = isRootGranted,
|
||||
onCheckedChange = { onProfileChange(profile.copy(allowSu = it)) },
|
||||
)
|
||||
|
||||
Crossfade(targetState = isRootGranted, label = "") { current ->
|
||||
Column {
|
||||
if (current) {
|
||||
val initialMode = if (profile.rootUseDefault) {
|
||||
Mode.Default
|
||||
} else if (profile.rootTemplate != null) {
|
||||
Mode.Template
|
||||
} else {
|
||||
Mode.Custom
|
||||
}
|
||||
var mode by remember {
|
||||
mutableStateOf(initialMode)
|
||||
}
|
||||
ProfileBox(mode, false) {
|
||||
// template mode shouldn't change profile here!
|
||||
if (it == Mode.Default || it == Mode.Custom) {
|
||||
onProfileChange(profile.copy(rootUseDefault = it == Mode.Default))
|
||||
}
|
||||
mode = it
|
||||
}
|
||||
Crossfade(targetState = mode, label = "") { currentMode ->
|
||||
if (currentMode == Mode.Template) {
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
val templateNone = "None"
|
||||
var template by rememberSaveable {
|
||||
mutableStateOf(
|
||||
profile.rootTemplate
|
||||
?: templateNone
|
||||
)
|
||||
}
|
||||
ListItem(headlineContent = {
|
||||
ExposedDropdownMenuBox(
|
||||
expanded = expanded,
|
||||
onExpandedChange = { expanded = it },
|
||||
) {
|
||||
OutlinedTextField(
|
||||
modifier = Modifier.menuAnchor(),
|
||||
readOnly = true,
|
||||
label = { Text(stringResource(R.string.profile_template)) },
|
||||
value = template,
|
||||
onValueChange = {
|
||||
if (template != templateNone) {
|
||||
onProfileChange(
|
||||
profile.copy(
|
||||
rootTemplate = it,
|
||||
rootUseDefault = false
|
||||
)
|
||||
)
|
||||
template = it
|
||||
}
|
||||
},
|
||||
trailingIcon = {
|
||||
if (expanded) Icon(Icons.Filled.ArrowDropUp, null)
|
||||
else Icon(Icons.Filled.ArrowDropDown, null)
|
||||
},
|
||||
)
|
||||
// TODO: Template
|
||||
}
|
||||
})
|
||||
} else if (mode == Mode.Custom) {
|
||||
RootProfileConfig(
|
||||
fixedName = true,
|
||||
profile = profile,
|
||||
onProfileChange = onProfileChange
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val mode = if (profile.nonRootUseDefault) Mode.Default else Mode.Custom
|
||||
ProfileBox(mode, false) {
|
||||
onProfileChange(profile.copy(nonRootUseDefault = (it == Mode.Default)))
|
||||
}
|
||||
Crossfade(targetState = mode, label = "") { currentMode ->
|
||||
val modifyEnabled = currentMode == Mode.Custom
|
||||
AppProfileConfig(
|
||||
fixedName = true,
|
||||
profile = profile,
|
||||
enabled = modifyEnabled,
|
||||
onProfileChange = onProfileChange
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum class Mode(@StringRes private val res: Int) {
|
||||
Default(R.string.profile_default),
|
||||
Template(R.string.profile_template),
|
||||
Custom(R.string.profile_custom);
|
||||
|
||||
val text: String
|
||||
@Composable get() = stringResource(res)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun TopBar(onBack: () -> Unit) {
|
||||
TopAppBar(
|
||||
title = {
|
||||
Text(stringResource(R.string.profile))
|
||||
},
|
||||
navigationIcon = {
|
||||
IconButton(
|
||||
onClick = onBack
|
||||
) { Icon(Icons.Filled.ArrowBack, contentDescription = null) }
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun ProfileBox(
|
||||
mode: Mode,
|
||||
hasTemplate: Boolean,
|
||||
onModeChange: (Mode) -> Unit,
|
||||
) {
|
||||
ListItem(
|
||||
headlineContent = { Text(stringResource(R.string.profile)) },
|
||||
supportingContent = { Text(mode.text) },
|
||||
leadingContent = { Icon(Icons.Filled.AccountCircle, null) },
|
||||
)
|
||||
Divider(thickness = Dp.Hairline)
|
||||
ListItem(headlineContent = {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceEvenly
|
||||
) {
|
||||
FilterChip(
|
||||
selected = mode == Mode.Default,
|
||||
label = { Text(stringResource(R.string.profile_default)) },
|
||||
onClick = { onModeChange(Mode.Default) },
|
||||
)
|
||||
if (hasTemplate) {
|
||||
FilterChip(
|
||||
selected = mode == Mode.Template,
|
||||
label = { Text(stringResource(R.string.profile_template)) },
|
||||
onClick = { onModeChange(Mode.Template) },
|
||||
)
|
||||
}
|
||||
FilterChip(
|
||||
selected = mode == Mode.Custom,
|
||||
label = { Text(stringResource(R.string.profile_custom)) },
|
||||
onClick = { onModeChange(Mode.Custom) },
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun AppProfilePreview() {
|
||||
var profile by remember { mutableStateOf(Natives.Profile("")) }
|
||||
AppProfileInner(
|
||||
packageName = "icu.nullptr.test",
|
||||
appLabel = "Test",
|
||||
appIcon = { Icon(Icons.Filled.Android, null) },
|
||||
profile = profile,
|
||||
onProfileChange = {
|
||||
profile = it
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -15,9 +15,10 @@ enum class BottomBarDestination(
|
||||
val direction: DirectionDestinationSpec,
|
||||
@StringRes val label: Int,
|
||||
val iconSelected: ImageVector,
|
||||
val iconNotSelected: ImageVector
|
||||
val iconNotSelected: ImageVector,
|
||||
val rootRequired: Boolean,
|
||||
) {
|
||||
Home(HomeScreenDestination, R.string.home, Icons.Filled.Home, Icons.Outlined.Home),
|
||||
SuperUser(SuperUserScreenDestination, R.string.superuser, Icons.Filled.Security, Icons.Outlined.Security),
|
||||
Module(ModuleScreenDestination, R.string.module, Icons.Filled.Apps, Icons.Outlined.Apps)
|
||||
Home(HomeScreenDestination, R.string.home, Icons.Filled.Home, Icons.Outlined.Home, false),
|
||||
SuperUser(SuperUserScreenDestination, R.string.superuser, Icons.Filled.Security, Icons.Outlined.Security, true),
|
||||
Module(ModuleScreenDestination, R.string.module, Icons.Filled.Apps, Icons.Outlined.Apps, true)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package me.weishu.kernelsu.ui.screen
|
||||
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.os.PowerManager
|
||||
@@ -21,6 +19,7 @@ import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import androidx.compose.ui.res.stringResource
|
||||
@@ -30,27 +29,22 @@ import androidx.compose.ui.unit.dp
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.annotation.RootNavGraph
|
||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import me.weishu.kernelsu.*
|
||||
import me.weishu.kernelsu.R
|
||||
import me.weishu.kernelsu.ui.screen.destinations.SettingScreenDestination
|
||||
import me.weishu.kernelsu.ui.util.LocalSnackbarHost
|
||||
import me.weishu.kernelsu.ui.util.reboot
|
||||
import me.weishu.kernelsu.ui.util.getSELinuxStatus
|
||||
import me.weishu.kernelsu.ui.util.install
|
||||
import me.weishu.kernelsu.ui.util.*
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@RootNavGraph(start = true)
|
||||
@Destination
|
||||
@Composable
|
||||
fun HomeScreen(navigator: DestinationsNavigator) {
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopBar(onSettingsClick = {
|
||||
navigator.navigate(SettingScreenDestination)
|
||||
})
|
||||
}
|
||||
) { innerPadding ->
|
||||
Scaffold(topBar = {
|
||||
TopBar(onSettingsClick = {
|
||||
navigator.navigate(SettingScreenDestination)
|
||||
})
|
||||
}) { innerPadding ->
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(innerPadding)
|
||||
@@ -63,15 +57,47 @@ fun HomeScreen(navigator: DestinationsNavigator) {
|
||||
SideEffect {
|
||||
if (isManager) install()
|
||||
}
|
||||
val ksuVersion = if (isManager) Natives.getVersion() else null
|
||||
val ksuVersion = if (isManager) Natives.version else null
|
||||
|
||||
StatusCard(kernelVersion, ksuVersion)
|
||||
if (isManager && Natives.requireNewKernel()) {
|
||||
WarningCard(
|
||||
stringResource(id = R.string.require_kernel_version).format(
|
||||
ksuVersion, Natives.MINIMAL_SUPPORTED_KERNEL
|
||||
)
|
||||
)
|
||||
}
|
||||
UpdateCard()
|
||||
InfoCard()
|
||||
DonateCard()
|
||||
LearnMoreCard()
|
||||
Spacer(Modifier)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun UpdateCard() {
|
||||
val context = LocalContext.current
|
||||
val newVersion by produceState(initialValue = 0 to "") {
|
||||
value = withContext(Dispatchers.IO) { checkNewVersion() }
|
||||
}
|
||||
val currentVersionCode = getManagerVersion(context).second
|
||||
val newVersionCode = newVersion.first
|
||||
val newVersionUrl = newVersion.second
|
||||
if (newVersionCode <= currentVersionCode) {
|
||||
return
|
||||
}
|
||||
|
||||
val uriHandler = LocalUriHandler.current
|
||||
WarningCard(
|
||||
message = stringResource(id = R.string.new_version_available).format(newVersionCode),
|
||||
MaterialTheme.colorScheme.outlineVariant
|
||||
) {
|
||||
uriHandler.openUri(newVersionUrl)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun RebootDropdownItem(@StringRes id: Int, reason: String = "") {
|
||||
DropdownMenuItem(text = {
|
||||
@@ -84,43 +110,41 @@ fun RebootDropdownItem(@StringRes id: Int, reason: String = "") {
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun TopBar(onSettingsClick: () -> Unit) {
|
||||
TopAppBar(
|
||||
title = { Text(stringResource(R.string.app_name)) },
|
||||
actions = {
|
||||
var showDropdown by remember { mutableStateOf(false) }
|
||||
IconButton(onClick = {
|
||||
showDropdown = true
|
||||
TopAppBar(title = { Text(stringResource(R.string.app_name)) }, actions = {
|
||||
var showDropdown by remember { mutableStateOf(false) }
|
||||
IconButton(onClick = {
|
||||
showDropdown = true
|
||||
}) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Refresh,
|
||||
contentDescription = stringResource(id = R.string.reboot)
|
||||
)
|
||||
|
||||
DropdownMenu(expanded = showDropdown, onDismissRequest = {
|
||||
showDropdown = false
|
||||
}) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Refresh,
|
||||
contentDescription = stringResource(id = R.string.reboot)
|
||||
)
|
||||
|
||||
DropdownMenu(expanded = showDropdown, onDismissRequest = {
|
||||
showDropdown = false
|
||||
}) {
|
||||
RebootDropdownItem(id = R.string.reboot)
|
||||
|
||||
RebootDropdownItem(id = R.string.reboot)
|
||||
|
||||
val pm = LocalContext.current.getSystemService(Context.POWER_SERVICE) as PowerManager?
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && pm?.isRebootingUserspaceSupported == true) {
|
||||
RebootDropdownItem(id = R.string.reboot_userspace, reason = "userspace")
|
||||
}
|
||||
RebootDropdownItem(id = R.string.reboot_recovery, reason = "recovery")
|
||||
RebootDropdownItem(id = R.string.reboot_bootloader, reason = "bootloader")
|
||||
RebootDropdownItem(id = R.string.reboot_download, reason = "download")
|
||||
RebootDropdownItem(id = R.string.reboot_edl, reason = "edl")
|
||||
val pm =
|
||||
LocalContext.current.getSystemService(Context.POWER_SERVICE) as PowerManager?
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && pm?.isRebootingUserspaceSupported == true) {
|
||||
RebootDropdownItem(id = R.string.reboot_userspace, reason = "userspace")
|
||||
}
|
||||
}
|
||||
|
||||
IconButton(onClick = onSettingsClick) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Settings,
|
||||
contentDescription = stringResource(id = R.string.settings)
|
||||
)
|
||||
RebootDropdownItem(id = R.string.reboot_recovery, reason = "recovery")
|
||||
RebootDropdownItem(id = R.string.reboot_bootloader, reason = "bootloader")
|
||||
RebootDropdownItem(id = R.string.reboot_download, reason = "download")
|
||||
RebootDropdownItem(id = R.string.reboot_edl, reason = "edl")
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
IconButton(onClick = onSettingsClick) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Settings,
|
||||
contentDescription = stringResource(id = R.string.settings)
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -132,20 +156,17 @@ private fun StatusCard(kernelVersion: KernelVersion, ksuVersion: Int?) {
|
||||
})
|
||||
) {
|
||||
val uriHandler = LocalUriHandler.current
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable {
|
||||
if (kernelVersion.isGKI() && ksuVersion == null) {
|
||||
uriHandler.openUri("https://kernelsu.org/guide/installation.html")
|
||||
}
|
||||
Row(modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable {
|
||||
if (kernelVersion.isGKI() && ksuVersion == null) {
|
||||
uriHandler.openUri("https://kernelsu.org/guide/installation.html")
|
||||
}
|
||||
.padding(24.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
}
|
||||
.padding(24.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
when {
|
||||
ksuVersion != null -> {
|
||||
val appendText = if (Natives.isSafeMode()) {
|
||||
val appendText = if (Natives.isSafeMode) {
|
||||
" [${stringResource(id = R.string.safe_mode)}]"
|
||||
} else {
|
||||
""
|
||||
@@ -161,8 +182,20 @@ private fun StatusCard(kernelVersion: KernelVersion, ksuVersion: Int?) {
|
||||
text = stringResource(R.string.home_working_version, ksuVersion),
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
Spacer(Modifier.height(4.dp))
|
||||
Text(
|
||||
text = stringResource(
|
||||
R.string.home_superuser_count, getSuperuserCount()
|
||||
), style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
Spacer(Modifier.height(4.dp))
|
||||
Text(
|
||||
text = stringResource(R.string.home_module_count, getModuleCount()),
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
kernelVersion.isGKI() -> {
|
||||
Icon(Icons.Outlined.Warning, stringResource(R.string.home_not_installed))
|
||||
Column(Modifier.padding(start = 20.dp)) {
|
||||
@@ -177,6 +210,7 @@ private fun StatusCard(kernelVersion: KernelVersion, ksuVersion: Int?) {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
Icon(Icons.Outlined.Block, stringResource(R.string.home_unsupported))
|
||||
Column(Modifier.padding(start = 20.dp)) {
|
||||
@@ -197,11 +231,90 @@ private fun StatusCard(kernelVersion: KernelVersion, ksuVersion: Int?) {
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun WarningCard(
|
||||
message: String, color: Color = MaterialTheme.colorScheme.error, onClick: () -> Unit = {}
|
||||
) {
|
||||
ElevatedCard(
|
||||
colors = CardDefaults.elevatedCardColors(
|
||||
containerColor = color
|
||||
)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(24.dp)
|
||||
.clickable {
|
||||
onClick()
|
||||
}, verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Column() {
|
||||
Text(
|
||||
text = message, style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun LearnMoreCard() {
|
||||
val uriHandler = LocalUriHandler.current
|
||||
val url = stringResource(R.string.home_learn_kernelsu_url)
|
||||
|
||||
ElevatedCard {
|
||||
|
||||
Row(modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable {
|
||||
uriHandler.openUri(url)
|
||||
}
|
||||
.padding(24.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
Column() {
|
||||
Text(
|
||||
text = stringResource(R.string.home_learn_kernelsu),
|
||||
style = MaterialTheme.typography.titleSmall
|
||||
)
|
||||
Spacer(Modifier.height(4.dp))
|
||||
Text(
|
||||
text = stringResource(R.string.home_click_to_learn_kernelsu),
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DonateCard() {
|
||||
val uriHandler = LocalUriHandler.current
|
||||
|
||||
ElevatedCard {
|
||||
|
||||
Row(modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable {
|
||||
uriHandler.openUri("https://patreon.com/weishu")
|
||||
}
|
||||
.padding(24.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
Column() {
|
||||
Text(
|
||||
text = stringResource(R.string.home_support_title),
|
||||
style = MaterialTheme.typography.titleSmall
|
||||
)
|
||||
Spacer(Modifier.height(4.dp))
|
||||
Text(
|
||||
text = stringResource(R.string.home_support_content),
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun InfoCard() {
|
||||
val context = LocalContext.current
|
||||
val snackbarHost = LocalSnackbarHost.current
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
ElevatedCard {
|
||||
Column(
|
||||
@@ -221,41 +334,25 @@ private fun InfoCard() {
|
||||
|
||||
InfoCardItem(stringResource(R.string.home_kernel), uname.release)
|
||||
|
||||
Spacer(Modifier.height(24.dp))
|
||||
InfoCardItem(stringResource(R.string.home_manager_version), getManagerVersion(context))
|
||||
Spacer(Modifier.height(16.dp))
|
||||
val managerVersion = getManagerVersion(context)
|
||||
InfoCardItem(
|
||||
stringResource(R.string.home_manager_version),
|
||||
"${managerVersion.first} (${managerVersion.second})"
|
||||
)
|
||||
|
||||
Spacer(Modifier.height(24.dp))
|
||||
InfoCardItem(stringResource(R.string.home_api), Build.VERSION.SDK_INT.toString())
|
||||
|
||||
Spacer(Modifier.height(24.dp))
|
||||
InfoCardItem(stringResource(R.string.home_abi), Build.SUPPORTED_ABIS.joinToString(", "))
|
||||
|
||||
Spacer(Modifier.height(24.dp))
|
||||
Spacer(Modifier.height(16.dp))
|
||||
InfoCardItem(stringResource(R.string.home_fingerprint), Build.FINGERPRINT)
|
||||
|
||||
Spacer(Modifier.height(24.dp))
|
||||
InfoCardItem(stringResource(R.string.home_securitypatch), Build.VERSION.SECURITY_PATCH)
|
||||
|
||||
Spacer(Modifier.height(24.dp))
|
||||
Spacer(Modifier.height(16.dp))
|
||||
InfoCardItem(stringResource(R.string.home_selinux_status), getSELinuxStatus())
|
||||
|
||||
val copiedMessage = stringResource(R.string.home_copied_to_clipboard)
|
||||
TextButton(
|
||||
modifier = Modifier.align(Alignment.End),
|
||||
onClick = {
|
||||
val cm = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
cm.setPrimaryClip(ClipData.newPlainText("KernelSU", contents.toString()))
|
||||
scope.launch { snackbarHost.showSnackbar(copiedMessage) }
|
||||
},
|
||||
content = { Text(stringResource(android.R.string.copy)) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getManagerVersion(context: Context) : String {
|
||||
fun getManagerVersion(context: Context): Pair<String, Int> {
|
||||
val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0)
|
||||
return "${packageInfo.versionName} (${packageInfo.versionCode})"
|
||||
return Pair(packageInfo.versionName, packageInfo.versionCode)
|
||||
}
|
||||
|
||||
@Preview
|
||||
|
||||
@@ -13,7 +13,6 @@ import androidx.compose.material.icons.filled.Refresh
|
||||
import androidx.compose.material.icons.filled.Save
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
@@ -35,13 +34,12 @@ import java.util.*
|
||||
* @author weishu
|
||||
* @date 2023/1/1.
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
@Destination
|
||||
fun InstallScreen(navigator: DestinationsNavigator, uri: Uri) {
|
||||
|
||||
var text by rememberSaveable { mutableStateOf("") }
|
||||
var showFloatAction by rememberSaveable { mutableStateOf(false) }
|
||||
var text by remember { mutableStateOf("") }
|
||||
var showFloatAction by remember { mutableStateOf(false) }
|
||||
|
||||
val snackBarHost = LocalSnackbarHost.current
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
@@ -2,12 +2,15 @@ package me.weishu.kernelsu.ui.screen
|
||||
|
||||
import android.app.Activity.RESULT_OK
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
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.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
@@ -16,9 +19,9 @@ 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.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
@@ -34,12 +37,11 @@ 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.component.ConfirmResult
|
||||
import me.weishu.kernelsu.ui.screen.destinations.InstallScreenDestination
|
||||
import me.weishu.kernelsu.ui.util.*
|
||||
import me.weishu.kernelsu.ui.viewmodel.ModuleViewModel
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Destination
|
||||
@Composable
|
||||
fun ModuleScreen(navigator: DestinationsNavigator) {
|
||||
@@ -51,11 +53,10 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
|
||||
}
|
||||
}
|
||||
|
||||
val isSafeMode = Natives.isSafeMode()
|
||||
val isKSUVersionInvalid = Natives.getVersion() < 0
|
||||
val isSafeMode = Natives.isSafeMode
|
||||
val hasMagisk = hasMagisk()
|
||||
|
||||
val hideInstallButton = isSafeMode || isKSUVersionInvalid || hasMagisk
|
||||
val hideInstallButton = isSafeMode || hasMagisk
|
||||
|
||||
Scaffold(topBar = {
|
||||
TopBar()
|
||||
@@ -94,11 +95,6 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
|
||||
ConfirmDialog()
|
||||
|
||||
when {
|
||||
isKSUVersionInvalid -> {
|
||||
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||
Text(stringResource(R.string.require_kernel_version_8))
|
||||
}
|
||||
}
|
||||
hasMagisk -> {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
@@ -112,13 +108,15 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
ModuleList(
|
||||
viewModel = viewModel,
|
||||
modifier = Modifier
|
||||
viewModel = viewModel, modifier = Modifier
|
||||
.padding(innerPadding)
|
||||
.fillMaxSize()
|
||||
)
|
||||
) {
|
||||
navigator.navigate(InstallScreenDestination(it))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -126,7 +124,9 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
private fun ModuleList(viewModel: ModuleViewModel, modifier: Modifier = Modifier) {
|
||||
private fun ModuleList(
|
||||
viewModel: ModuleViewModel, modifier: Modifier = Modifier, onInstallModule: (Uri) -> Unit
|
||||
) {
|
||||
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)
|
||||
@@ -136,20 +136,19 @@ private fun ModuleList(viewModel: ModuleViewModel, modifier: Modifier = Modifier
|
||||
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 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(
|
||||
val confirmResult = dialogHost.showConfirm(
|
||||
moduleStr,
|
||||
content = moduleUninstallConfirm.format(module.name),
|
||||
confirm = uninstall,
|
||||
dismiss = cancel
|
||||
)
|
||||
if (dialogResult != DialogResult.Confirmed) {
|
||||
if (confirmResult != ConfirmResult.Confirmed) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -173,12 +172,12 @@ private fun ModuleList(viewModel: ModuleViewModel, modifier: Modifier = Modifier
|
||||
}
|
||||
}
|
||||
|
||||
val refreshState = rememberPullRefreshState(
|
||||
refreshing = viewModel.isRefreshing,
|
||||
onRefresh = { viewModel.fetchModuleList() }
|
||||
)
|
||||
val refreshState = rememberPullRefreshState(refreshing = viewModel.isRefreshing,
|
||||
onRefresh = { viewModel.fetchModuleList() })
|
||||
Box(modifier.pullRefresh(refreshState)) {
|
||||
if (viewModel.isOverlayAvailable) {
|
||||
val context = LocalContext.current
|
||||
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
@@ -187,8 +186,7 @@ private fun ModuleList(viewModel: ModuleViewModel, modifier: Modifier = Modifier
|
||||
start = 16.dp,
|
||||
top = 16.dp,
|
||||
end = 16.dp,
|
||||
bottom = 16.dp
|
||||
+ 16.dp + 56.dp /* Scaffold Fab Spacing + Fab container height */
|
||||
bottom = 16.dp + 16.dp + 56.dp /* Scaffold Fab Spacing + Fab container height */
|
||||
)
|
||||
},
|
||||
) {
|
||||
@@ -196,17 +194,23 @@ private fun ModuleList(viewModel: ModuleViewModel, modifier: Modifier = Modifier
|
||||
if (isEmpty) {
|
||||
item {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentAlignment = Alignment.Center
|
||||
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) }
|
||||
var isChecked by remember(module) { mutableStateOf(module.enabled) }
|
||||
val scope = rememberCoroutineScope()
|
||||
ModuleItem(module, isChecked, onUninstall = {
|
||||
val updateUrl by produceState(initialValue = "") {
|
||||
viewModel.checkUpdate(module) { value = it.orEmpty() }
|
||||
}
|
||||
|
||||
val downloadingText = stringResource(R.string.module_downloading)
|
||||
val startDownloadingText = stringResource(R.string.module_start_downloading)
|
||||
|
||||
ModuleItem(module, isChecked, updateUrl, onUninstall = {
|
||||
scope.launch { onModuleUninstall(module) }
|
||||
}, onCheckChanged = {
|
||||
val success = toggleModule(module.id, !isChecked)
|
||||
@@ -226,12 +230,37 @@ private fun ModuleList(viewModel: ModuleViewModel, modifier: Modifier = Modifier
|
||||
val message = if (isChecked) failedDisable else failedEnable
|
||||
snackBarHost.showSnackbar(message.format(module.name))
|
||||
}
|
||||
}, onUpdate = {
|
||||
|
||||
scope.launch {
|
||||
Toast.makeText(
|
||||
context,
|
||||
startDownloadingText.format(module.name),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
|
||||
val downloading = downloadingText.format(module.name)
|
||||
download(
|
||||
context,
|
||||
updateUrl,
|
||||
"${module.name}-${module.version}.zip",
|
||||
downloading,
|
||||
onDownloaded = onInstallModule,
|
||||
onDownloading = {
|
||||
Toast.makeText(context, downloading, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
// fix last item shadow incomplete in LazyColumn
|
||||
Spacer(Modifier.height(1.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DownloadListener(context, onInstallModule)
|
||||
|
||||
} else {
|
||||
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||
Text(stringResource(R.string.module_overlay_fs_not_available))
|
||||
@@ -239,9 +268,7 @@ private fun ModuleList(viewModel: ModuleViewModel, modifier: Modifier = Modifier
|
||||
}
|
||||
|
||||
PullRefreshIndicator(
|
||||
refreshing = viewModel.isRefreshing,
|
||||
state = refreshState,
|
||||
modifier = Modifier.align(
|
||||
refreshing = viewModel.isRefreshing, state = refreshState, modifier = Modifier.align(
|
||||
Alignment.TopCenter
|
||||
)
|
||||
)
|
||||
@@ -258,8 +285,10 @@ private fun TopBar() {
|
||||
private fun ModuleItem(
|
||||
module: ModuleViewModel.ModuleInfo,
|
||||
isChecked: Boolean,
|
||||
updateUrl: String,
|
||||
onUninstall: (ModuleViewModel.ModuleInfo) -> Unit,
|
||||
onCheckChanged: (Boolean) -> Unit
|
||||
onCheckChanged: (Boolean) -> Unit,
|
||||
onUpdate: (ModuleViewModel.ModuleInfo) -> Unit,
|
||||
) {
|
||||
ElevatedCard(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
@@ -341,6 +370,23 @@ private fun ModuleItem(
|
||||
) {
|
||||
Spacer(modifier = Modifier.weight(1f, true))
|
||||
|
||||
if (updateUrl.isNotEmpty()) {
|
||||
Button(
|
||||
modifier = Modifier
|
||||
.padding(0.dp)
|
||||
.defaultMinSize(52.dp, 32.dp),
|
||||
onClick = { onUpdate(module) },
|
||||
shape = RoundedCornerShape(6.dp),
|
||||
contentPadding = PaddingValues(0.dp)
|
||||
) {
|
||||
Text(
|
||||
fontFamily = MaterialTheme.typography.labelMedium.fontFamily,
|
||||
fontSize = MaterialTheme.typography.labelMedium.fontSize,
|
||||
text = stringResource(R.string.module_update),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
TextButton(
|
||||
enabled = !module.remove,
|
||||
onClick = { onUninstall(module) },
|
||||
@@ -369,6 +415,7 @@ fun ModuleItemPreview() {
|
||||
enabled = true,
|
||||
update = true,
|
||||
remove = true,
|
||||
updateJson = ""
|
||||
)
|
||||
ModuleItem(module, true, {}, {})
|
||||
ModuleItem(module, true, "", {}, {}, {})
|
||||
}
|
||||
@@ -2,33 +2,38 @@ package me.weishu.kernelsu.ui.screen
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.BugReport
|
||||
import androidx.compose.material.icons.filled.ContactPage
|
||||
import androidx.compose.material.icons.filled.RemoveModerator
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
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.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import me.weishu.kernelsu.BuildConfig
|
||||
import me.weishu.kernelsu.Natives
|
||||
import me.weishu.kernelsu.R
|
||||
import me.weishu.kernelsu.ui.component.SimpleDialog
|
||||
import me.weishu.kernelsu.ui.util.LinkifyText
|
||||
import me.weishu.kernelsu.ui.component.AboutDialog
|
||||
import me.weishu.kernelsu.ui.component.LoadingDialog
|
||||
import me.weishu.kernelsu.ui.component.SwitchItem
|
||||
import me.weishu.kernelsu.ui.util.LocalDialogHost
|
||||
import me.weishu.kernelsu.ui.util.getBugreportFile
|
||||
|
||||
|
||||
/**
|
||||
* @author weishu
|
||||
* @date 2023/1/1.
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Destination
|
||||
@Composable
|
||||
fun SettingScreen(navigator: DestinationsNavigator) {
|
||||
@@ -40,46 +45,70 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
||||
})
|
||||
}
|
||||
) { paddingValues ->
|
||||
LoadingDialog()
|
||||
|
||||
SimpleDialog {
|
||||
SupportCard()
|
||||
}
|
||||
val showAboutDialog = remember { mutableStateOf(false) }
|
||||
AboutDialog(showAboutDialog)
|
||||
|
||||
Column(modifier = Modifier.padding(paddingValues)) {
|
||||
|
||||
val context = LocalContext.current
|
||||
SettingsMenuLink(title = {
|
||||
Text(stringResource(id = R.string.send_log))
|
||||
},
|
||||
onClick = {
|
||||
val bugreport = getBugreportFile(context)
|
||||
val uri: Uri = FileProvider.getUriForFile(context, "${BuildConfig.APPLICATION_ID}.fileprovider", bugreport)
|
||||
val scope = rememberCoroutineScope()
|
||||
val dialogHost = LocalDialogHost.current
|
||||
|
||||
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)
|
||||
var umountChecked by rememberSaveable {
|
||||
mutableStateOf(Natives.isDefaultUmountModules())
|
||||
}
|
||||
SwitchItem(
|
||||
icon = Icons.Filled.RemoveModerator,
|
||||
title = stringResource(id = R.string.settings_umount_modules_default),
|
||||
summary = stringResource(id = R.string.settings_umount_modules_default_summary),
|
||||
checked = umountChecked
|
||||
) {
|
||||
if (Natives.setDefaultUmountModules(it)) {
|
||||
umountChecked = it
|
||||
}
|
||||
}
|
||||
|
||||
context.startActivity(
|
||||
Intent.createChooser(
|
||||
shareIntent,
|
||||
context.getString(R.string.send_log)
|
||||
ListItem(
|
||||
leadingContent = { Icon(Icons.Filled.BugReport, stringResource(id = R.string.send_log)) },
|
||||
headlineContent = { Text(stringResource(id = R.string.send_log)) },
|
||||
modifier = Modifier.clickable {
|
||||
scope.launch {
|
||||
val bugreport = dialogHost.withLoading {
|
||||
withContext(Dispatchers.IO) {
|
||||
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)
|
||||
}
|
||||
ListItem(
|
||||
leadingContent = { Icon(Icons.Filled.ContactPage, stringResource(id = R.string.about)) },
|
||||
headlineContent = { Text(about) },
|
||||
modifier = Modifier.clickable {
|
||||
showAboutDialog.value = true
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -98,19 +127,3 @@ private fun TopBar(onBack: () -> Unit = {}) {
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun SupportCard() {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
CompositionLocalProvider(LocalTextStyle provides MaterialTheme.typography.bodyMedium) {
|
||||
LinkifyText("Author: weishu")
|
||||
LinkifyText("Github: https://github.com/tiann/KernelSU")
|
||||
LinkifyText("Telegram: https://t.me/KernelSU")
|
||||
LinkifyText("QQ: https://pd.qq.com/s/8lipl1brp")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,11 @@
|
||||
package me.weishu.kernelsu.ui.screen
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.MoreVert
|
||||
@@ -11,30 +14,32 @@ 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.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import coil.compose.AsyncImage
|
||||
import coil.request.ImageRequest
|
||||
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.SearchAppBar
|
||||
import me.weishu.kernelsu.ui.util.LocalSnackbarHost
|
||||
import me.weishu.kernelsu.ui.screen.destinations.AppProfileScreenDestination
|
||||
import me.weishu.kernelsu.ui.viewmodel.SuperUserViewModel
|
||||
import java.util.*
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class)
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Destination
|
||||
@Composable
|
||||
fun SuperUserScreen() {
|
||||
fun SuperUserScreen(navigator: DestinationsNavigator) {
|
||||
val viewModel = viewModel<SuperUserViewModel>()
|
||||
val snackbarHost = LocalSnackbarHost.current
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
@@ -91,6 +96,8 @@ fun SuperUserScreen() {
|
||||
}
|
||||
) { innerPadding ->
|
||||
|
||||
ConfirmDialog()
|
||||
|
||||
val refreshState = rememberPullRefreshState(
|
||||
refreshing = viewModel.isRefreshing,
|
||||
onRefresh = { scope.launch { viewModel.fetchAppList() } },
|
||||
@@ -100,19 +107,12 @@ fun SuperUserScreen() {
|
||||
.padding(innerPadding)
|
||||
.pullRefresh(refreshState)
|
||||
) {
|
||||
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)
|
||||
if (success) {
|
||||
isChecked = checked
|
||||
} else scope.launch {
|
||||
snackbarHost.showSnackbar(failMessage.format(app.uid))
|
||||
}
|
||||
items(viewModel.appList, key = { it.packageName + it.uid }) { app ->
|
||||
AppItem(app) {
|
||||
navigator.navigate(AppProfileScreenDestination(app))
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,20 +125,36 @@ fun SuperUserScreen() {
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
private fun AppItem(
|
||||
app: SuperUserViewModel.AppInfo,
|
||||
isChecked: Boolean,
|
||||
onCheckedChange: (Boolean) -> Unit
|
||||
onClickListener: () -> Unit,
|
||||
) {
|
||||
ListItem(
|
||||
headlineText = { Text(app.label) },
|
||||
supportingText = { Text(app.packageName) },
|
||||
modifier = Modifier.clickable(onClick = onClickListener),
|
||||
headlineContent = { Text(app.label) },
|
||||
supportingContent = {
|
||||
Column {
|
||||
Text(app.packageName)
|
||||
FlowRow {
|
||||
if (app.allowSu) {
|
||||
LabelText(label = "ROOT")
|
||||
} else {
|
||||
if (Natives.uidShouldUmount(app.uid)) {
|
||||
LabelText(label = "UMOUNT")
|
||||
}
|
||||
}
|
||||
if (app.hasCustomProfile) {
|
||||
LabelText(label = "CUSTOM")
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
leadingContent = {
|
||||
AsyncImage(
|
||||
model = ImageRequest.Builder(LocalContext.current)
|
||||
.data(app.icon)
|
||||
.data(app.packageInfo)
|
||||
.crossfade(true)
|
||||
.build(),
|
||||
contentDescription = app.label,
|
||||
@@ -148,12 +164,26 @@ private fun AppItem(
|
||||
.height(48.dp)
|
||||
)
|
||||
},
|
||||
trailingContent = {
|
||||
Switch(
|
||||
checked = isChecked,
|
||||
onCheckedChange = onCheckedChange,
|
||||
modifier = Modifier.padding(4.dp)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun LabelText(label: String) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(top = 4.dp, end = 4.dp)
|
||||
.background(
|
||||
Color.Black,
|
||||
shape = RoundedCornerShape(4.dp)
|
||||
)
|
||||
) {
|
||||
Text(
|
||||
text = label,
|
||||
modifier = Modifier.padding(vertical = 2.dp, horizontal = 5.dp),
|
||||
style = TextStyle(
|
||||
fontSize = 8.sp,
|
||||
color = Color.White,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
package me.weishu.kernelsu.ui.util
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.DownloadManager
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.net.Uri
|
||||
import android.os.Environment
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
|
||||
/**
|
||||
* @author weishu
|
||||
* @date 2023/6/22.
|
||||
*/
|
||||
@SuppressLint("Range")
|
||||
fun download(
|
||||
context: Context,
|
||||
url: String,
|
||||
fileName: String,
|
||||
description: String,
|
||||
onDownloaded: (Uri) -> Unit = {},
|
||||
onDownloading: () -> Unit = {}
|
||||
) {
|
||||
val downloadManager =
|
||||
context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
||||
|
||||
val query = DownloadManager.Query()
|
||||
query.setFilterByStatus(DownloadManager.STATUS_RUNNING or DownloadManager.STATUS_PAUSED or DownloadManager.STATUS_PENDING)
|
||||
downloadManager.query(query).use { cursor ->
|
||||
while (cursor.moveToNext()) {
|
||||
val uri = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_URI))
|
||||
val localUri = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI))
|
||||
val status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS))
|
||||
val columnTitle = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_TITLE))
|
||||
if (url == uri || fileName == columnTitle) {
|
||||
if (status == DownloadManager.STATUS_RUNNING || status == DownloadManager.STATUS_PENDING) {
|
||||
onDownloading()
|
||||
return
|
||||
} else if (status == DownloadManager.STATUS_SUCCESSFUL) {
|
||||
onDownloaded(Uri.parse(localUri))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val request = DownloadManager.Request(Uri.parse(url))
|
||||
.setDestinationInExternalPublicDir(
|
||||
Environment.DIRECTORY_DOWNLOADS,
|
||||
fileName
|
||||
)
|
||||
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
|
||||
.setMimeType("application/zip")
|
||||
.setTitle(fileName)
|
||||
.setDescription(description)
|
||||
|
||||
downloadManager.enqueue(request)
|
||||
}
|
||||
|
||||
fun checkNewVersion(): Pair<Int, String> {
|
||||
val url = "https://api.github.com/repos/tiann/KernelSU/releases/latest"
|
||||
val defaultValue = 0 to ""
|
||||
runCatching {
|
||||
okhttp3.OkHttpClient().newCall(okhttp3.Request.Builder().url(url).build()).execute()
|
||||
.use { response ->
|
||||
if (!response.isSuccessful) {
|
||||
return defaultValue
|
||||
}
|
||||
val body = response.body?.string() ?: return defaultValue
|
||||
val json = org.json.JSONObject(body)
|
||||
|
||||
val assets = json.getJSONArray("assets")
|
||||
for (i in 0 until assets.length()) {
|
||||
val asset = assets.getJSONObject(i)
|
||||
val name = asset.getString("name")
|
||||
if (!name.endsWith(".apk")) {
|
||||
continue
|
||||
}
|
||||
|
||||
val regex = Regex("v(.+?)_(\\d+)-")
|
||||
val matchResult = regex.find(name) ?: continue
|
||||
val versionName = matchResult.groupValues[1]
|
||||
val versionCode = matchResult.groupValues[2].toInt()
|
||||
val downloadUrl = asset.getString("browser_download_url")
|
||||
|
||||
return versionCode to downloadUrl
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DownloadListener(context: Context, onDownloaded: (Uri) -> Unit) {
|
||||
DisposableEffect(context) {
|
||||
val receiver = object : BroadcastReceiver() {
|
||||
@SuppressLint("Range")
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
if (intent?.action == DownloadManager.ACTION_DOWNLOAD_COMPLETE) {
|
||||
val id = intent.getLongExtra(
|
||||
DownloadManager.EXTRA_DOWNLOAD_ID, -1
|
||||
)
|
||||
val query = DownloadManager.Query().setFilterById(id)
|
||||
val downloadManager =
|
||||
context?.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
||||
val cursor = downloadManager.query(query)
|
||||
if (cursor.moveToFirst()) {
|
||||
val status = cursor.getInt(
|
||||
cursor.getColumnIndex(DownloadManager.COLUMN_STATUS)
|
||||
)
|
||||
if (status == DownloadManager.STATUS_SUCCESSFUL) {
|
||||
val uri = cursor.getString(
|
||||
cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI)
|
||||
)
|
||||
onDownloaded(Uri.parse(uri))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
context.registerReceiver(
|
||||
receiver,
|
||||
IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)
|
||||
)
|
||||
onDispose {
|
||||
context.unregisterReceiver(receiver)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,10 @@ import com.topjohnwu.superuser.CallbackList
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import com.topjohnwu.superuser.ShellUtils
|
||||
import me.weishu.kernelsu.BuildConfig
|
||||
import me.weishu.kernelsu.Natives
|
||||
import me.weishu.kernelsu.ksuApp
|
||||
import me.weishu.kernelsu.ui.viewmodel.ModuleViewModel
|
||||
import org.json.JSONArray
|
||||
import java.io.File
|
||||
|
||||
|
||||
@@ -21,6 +24,14 @@ private fun getKsuDaemonPath(): String {
|
||||
return ksuApp.applicationInfo.nativeLibraryDir + File.separator + "libksud.so"
|
||||
}
|
||||
|
||||
object KsuCli {
|
||||
val SHELL: Shell = createRootShell()
|
||||
}
|
||||
|
||||
fun getRootShell(): Shell {
|
||||
return KsuCli.SHELL
|
||||
}
|
||||
|
||||
fun createRootShell(): Shell {
|
||||
Shell.enableVerboseLogging = BuildConfig.DEBUG
|
||||
val builder = Shell.Builder.create()
|
||||
@@ -33,7 +44,7 @@ fun createRootShell(): Shell {
|
||||
}
|
||||
|
||||
fun execKsud(args: String): Boolean {
|
||||
val shell = createRootShell()
|
||||
val shell = getRootShell()
|
||||
return ShellUtils.fastCmdResult(shell, "${getKsuDaemonPath()} $args")
|
||||
}
|
||||
|
||||
@@ -44,12 +55,25 @@ fun install() {
|
||||
}
|
||||
|
||||
fun listModules(): String {
|
||||
val shell = createRootShell()
|
||||
val shell = getRootShell()
|
||||
|
||||
val out = shell.newJob().add("${getKsuDaemonPath()} module list").to(ArrayList(), null).exec().out
|
||||
val out =
|
||||
shell.newJob().add("${getKsuDaemonPath()} module list").to(ArrayList(), null).exec().out
|
||||
return out.joinToString("\n").ifBlank { "[]" }
|
||||
}
|
||||
|
||||
fun getModuleCount(): Int {
|
||||
val result = listModules()
|
||||
runCatching {
|
||||
val array = JSONArray(result)
|
||||
return array.length()
|
||||
}.getOrElse { return 0 }
|
||||
}
|
||||
|
||||
fun getSuperuserCount(): Int {
|
||||
return Natives.allowList.size
|
||||
}
|
||||
|
||||
fun toggleModule(id: String, enable: Boolean): Boolean {
|
||||
val cmd = if (enable) {
|
||||
"module enable $id"
|
||||
@@ -61,14 +85,14 @@ fun toggleModule(id: String, enable: Boolean): Boolean {
|
||||
return result
|
||||
}
|
||||
|
||||
fun uninstallModule(id: String) : Boolean {
|
||||
fun uninstallModule(id: String): Boolean {
|
||||
val cmd = "module uninstall $id"
|
||||
val result = execKsud(cmd)
|
||||
Log.i(TAG, "uninstall module $id result: $result")
|
||||
return result
|
||||
}
|
||||
|
||||
fun installModule(uri: Uri, onFinish: (Boolean)->Unit, onOutput: (String) -> Unit) : Boolean {
|
||||
fun installModule(uri: Uri, onFinish: (Boolean) -> Unit, onOutput: (String) -> Unit): Boolean {
|
||||
val resolver = ksuApp.contentResolver
|
||||
with(resolver.openInputStream(uri)) {
|
||||
val file = File(ksuApp.cacheDir, "module.zip")
|
||||
@@ -77,7 +101,7 @@ fun installModule(uri: Uri, onFinish: (Boolean)->Unit, onOutput: (String) -> Uni
|
||||
}
|
||||
val cmd = "module install ${file.absolutePath}"
|
||||
|
||||
val shell = createRootShell()
|
||||
val shell = getRootShell()
|
||||
|
||||
val callbackList: CallbackList<String?> = object : CallbackList<String?>() {
|
||||
override fun onAddElement(s: String?) {
|
||||
@@ -85,7 +109,8 @@ fun installModule(uri: Uri, onFinish: (Boolean)->Unit, onOutput: (String) -> Uni
|
||||
}
|
||||
}
|
||||
|
||||
val result = shell.newJob().add("${getKsuDaemonPath()} $cmd").to(callbackList, callbackList).exec()
|
||||
val result =
|
||||
shell.newJob().add("${getKsuDaemonPath()} $cmd").to(callbackList, callbackList).exec()
|
||||
Log.i("KernelSU", "install module $uri result: $result")
|
||||
|
||||
file.delete()
|
||||
@@ -96,7 +121,7 @@ fun installModule(uri: Uri, onFinish: (Boolean)->Unit, onOutput: (String) -> Uni
|
||||
}
|
||||
|
||||
fun reboot(reason: String = "") {
|
||||
val shell = createRootShell()
|
||||
val shell = getRootShell()
|
||||
if (reason == "recovery") {
|
||||
// KEYCODE_POWER = 26, hide incorrect "Factory data reset" message
|
||||
ShellUtils.fastCmd(shell, "/system/bin/input keyevent 26")
|
||||
@@ -105,14 +130,24 @@ fun reboot(reason: String = "") {
|
||||
}
|
||||
|
||||
fun overlayFsAvailable(): Boolean {
|
||||
val shell = createRootShell()
|
||||
val shell = getRootShell()
|
||||
// check /proc/filesystems
|
||||
return ShellUtils.fastCmdResult(shell, "cat /proc/filesystems | grep overlay")
|
||||
}
|
||||
|
||||
fun hasMagisk(): Boolean {
|
||||
val shell = createRootShell()
|
||||
val shell = getRootShell()
|
||||
val result = shell.newJob().add("nsenter --mount=/proc/1/ns/mnt which magisk").exec()
|
||||
Log.i(TAG, "has magisk: ${result.isSuccess}")
|
||||
return result.isSuccess
|
||||
}
|
||||
|
||||
fun isSepolicyValid(rules: String?): Boolean {
|
||||
if (rules == null) {
|
||||
return true
|
||||
}
|
||||
val shell = getRootShell()
|
||||
val result =
|
||||
shell.newJob().add("ksud sepolicy check '$rules'").to(ArrayList(), null).exec()
|
||||
return result.isSuccess
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package me.weishu.kernelsu.ui.util
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.system.Os
|
||||
import com.topjohnwu.superuser.ShellUtils
|
||||
import me.weishu.kernelsu.Natives
|
||||
import me.weishu.kernelsu.ui.screen.getManagerVersion
|
||||
import java.io.File
|
||||
@@ -22,26 +23,32 @@ fun getBugreportFile(context: Context): File {
|
||||
val dropboxFile = File(bugreportDir, "dropbox.tar.gz")
|
||||
val pstoreFile = File(bugreportDir, "pstore.tar.gz")
|
||||
val diagFile = File(bugreportDir, "diag.tar.gz")
|
||||
val bootlogFile = File(bugreportDir, "bootlog.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 appListFile = File(bugreportDir, "packages.txt")
|
||||
val propFile = File(bugreportDir, "props.txt")
|
||||
val allowListFile = File(bugreportDir, "allowlist.bin")
|
||||
|
||||
val shell = createRootShell()
|
||||
val shell = getRootShell()
|
||||
|
||||
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("tar -czf ${tombstonesFile.absolutePath} -C /data/tombstones .").exec()
|
||||
shell.newJob().add("tar -czf ${dropboxFile.absolutePath} -C /data/system/dropbox .").exec()
|
||||
shell.newJob().add("tar -czf ${pstoreFile.absolutePath} -C /sys/fs/pstore .").exec()
|
||||
shell.newJob().add("tar -czf ${diagFile.absolutePath} -C /data/vendor/diag .").exec()
|
||||
shell.newJob().add("tar -czf ${bootlogFile.absolutePath} -C /data/adb/ksu/log .").exec()
|
||||
|
||||
shell.newJob().add("cat /proc/mounts > ${mountsFile.absolutePath}").exec()
|
||||
shell.newJob().add("cat /proc/1/mountinfo > ${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("cp /data/system/packages.list ${appListFile.absolutePath}").exec()
|
||||
shell.newJob().add("getprop > ${propFile.absolutePath}").exec()
|
||||
shell.newJob().add("cp /data/adb/ksu/.allowlist ${allowListFile.absolutePath}").exec()
|
||||
|
||||
val selinux = ShellUtils.fastCmd(shell, "getenforce");
|
||||
|
||||
// basic information
|
||||
val buildInfo = File(bugreportDir, "basic.txt")
|
||||
@@ -56,6 +63,7 @@ fun getBugreportFile(context: Context): File {
|
||||
pw.println("FINGERPRINT: " + Build.FINGERPRINT)
|
||||
pw.println("DEVICE: " + Build.DEVICE)
|
||||
pw.println("Manager: " + getManagerVersion(context))
|
||||
pw.println("SELinux: $selinux")
|
||||
|
||||
val uname = Os.uname()
|
||||
pw.println("KernelRelease: ${uname.release}")
|
||||
@@ -64,9 +72,9 @@ fun getBugreportFile(context: Context): File {
|
||||
pw.println("Nodename: ${uname.nodename}")
|
||||
pw.println("Sysname: ${uname.sysname}")
|
||||
|
||||
val ksuKernel = Natives.getVersion()
|
||||
val ksuKernel = Natives.version
|
||||
pw.println("KernelSU: $ksuKernel")
|
||||
val safeMode = Natives.isSafeMode()
|
||||
val safeMode = Natives.isSafeMode
|
||||
pw.println("SafeMode: $safeMode")
|
||||
}
|
||||
|
||||
@@ -84,4 +92,4 @@ fun getBugreportFile(context: Context): File {
|
||||
shell.newJob().add("chmod 0644 ${targetFile.absolutePath}").exec()
|
||||
|
||||
return targetFile
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package me.weishu.kernelsu.ui.viewmodel
|
||||
|
||||
import android.net.Uri
|
||||
import android.os.SystemClock
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
@@ -13,6 +14,7 @@ import kotlinx.coroutines.launch
|
||||
import me.weishu.kernelsu.ui.util.listModules
|
||||
import me.weishu.kernelsu.ui.util.overlayFsAvailable
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
import java.text.Collator
|
||||
import java.util.*
|
||||
|
||||
@@ -33,6 +35,14 @@ class ModuleViewModel : ViewModel() {
|
||||
val enabled: Boolean,
|
||||
val update: Boolean,
|
||||
val remove: Boolean,
|
||||
val updateJson: String,
|
||||
)
|
||||
|
||||
data class ModuleUpdateInfo(
|
||||
val version: String,
|
||||
val versionCode: Int,
|
||||
val zipUrl: String,
|
||||
val changelog: String,
|
||||
)
|
||||
|
||||
var isRefreshing by mutableStateOf(false)
|
||||
@@ -78,6 +88,7 @@ class ModuleViewModel : ViewModel() {
|
||||
obj.getBoolean("enabled"),
|
||||
obj.getBoolean("update"),
|
||||
obj.getBoolean("remove"),
|
||||
obj.optString("updateJson", "")
|
||||
)
|
||||
}.toList()
|
||||
}.onFailure { e ->
|
||||
@@ -94,4 +105,57 @@ class ModuleViewModel : ViewModel() {
|
||||
Log.i(TAG, "load cost: ${SystemClock.elapsedRealtime() - start}, modules: $modules")
|
||||
}
|
||||
}
|
||||
|
||||
fun checkUpdate(m: ModuleInfo, callback: (String?) -> Unit) {
|
||||
if (m.updateJson.isEmpty()) {
|
||||
callback(null)
|
||||
return
|
||||
}
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
// download updateJson
|
||||
val result = kotlin.runCatching {
|
||||
val url = m.updateJson
|
||||
Log.i(TAG, "checkUpdate url: $url")
|
||||
val response = okhttp3.OkHttpClient()
|
||||
.newCall(
|
||||
okhttp3.Request.Builder()
|
||||
.url(url)
|
||||
.build()
|
||||
).execute()
|
||||
Log.d(TAG, "checkUpdate code: ${response.code}")
|
||||
if (response.isSuccessful) {
|
||||
response.body?.string() ?: ""
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}.getOrDefault("")
|
||||
Log.i(TAG, "checkUpdate result: $result")
|
||||
|
||||
if (result.isEmpty()) {
|
||||
callback(null)
|
||||
return@launch
|
||||
}
|
||||
|
||||
val updateJson = kotlin.runCatching {
|
||||
JSONObject(result)
|
||||
}.getOrNull()
|
||||
|
||||
if (updateJson == null) {
|
||||
callback(null)
|
||||
return@launch
|
||||
}
|
||||
|
||||
val version = updateJson.optString("version", "")
|
||||
val versionCode = updateJson.optInt("versionCode", 0)
|
||||
val zipUrl = updateJson.optString("zipUrl", "")
|
||||
val changelog = updateJson.optString("changelog", "")
|
||||
if (versionCode <= m.versionCode || zipUrl.isEmpty()) {
|
||||
callback(null)
|
||||
return@launch
|
||||
}
|
||||
|
||||
callback(zipUrl)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
package me.weishu.kernelsu.ui.viewmodel
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.PackageInfo
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.IBinder
|
||||
import android.os.Parcelable
|
||||
import android.os.SystemClock
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
@@ -10,13 +14,20 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import me.weishu.kernelsu.IKsuInterface
|
||||
import me.weishu.kernelsu.Natives
|
||||
import me.weishu.kernelsu.ksuApp
|
||||
import me.weishu.kernelsu.ui.KsuService
|
||||
import me.weishu.kernelsu.ui.util.HanziToPinyin
|
||||
import me.weishu.kernelsu.ui.util.KsuCli
|
||||
import java.text.Collator
|
||||
import java.util.*
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
class SuperUserViewModel : ViewModel() {
|
||||
|
||||
@@ -25,14 +36,32 @@ class SuperUserViewModel : ViewModel() {
|
||||
private var apps by mutableStateOf<List<AppInfo>>(emptyList())
|
||||
}
|
||||
|
||||
class AppInfo(
|
||||
@Parcelize
|
||||
data class AppInfo(
|
||||
val label: String,
|
||||
val packageName: String,
|
||||
val icon: PackageInfo,
|
||||
val uid: Int,
|
||||
val onAllowList: Boolean,
|
||||
val onDenyList: Boolean
|
||||
)
|
||||
val packageInfo: PackageInfo,
|
||||
val profile: Natives.Profile?,
|
||||
) : Parcelable {
|
||||
val packageName: String
|
||||
get() = packageInfo.packageName
|
||||
val uid: Int
|
||||
get() = packageInfo.applicationInfo.uid
|
||||
|
||||
val allowSu: Boolean
|
||||
get() = profile != null && profile.allowSu
|
||||
val hasCustomProfile: Boolean
|
||||
get() {
|
||||
if (profile == null) {
|
||||
return false
|
||||
}
|
||||
|
||||
return if (profile.allowSu) {
|
||||
!profile.rootUseDefault
|
||||
} else {
|
||||
!profile.nonRootUseDefault
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var search by mutableStateOf("")
|
||||
var showSystemApps by mutableStateOf(false)
|
||||
@@ -42,8 +71,8 @@ class SuperUserViewModel : ViewModel() {
|
||||
private val sortedList by derivedStateOf {
|
||||
val comparator = compareBy<AppInfo> {
|
||||
when {
|
||||
it.onAllowList -> 0
|
||||
it.onDenyList -> 1
|
||||
it.allowSu -> 0
|
||||
it.hasCustomProfile -> 1
|
||||
else -> 2
|
||||
}
|
||||
}.then(compareBy(Collator.getInstance(Locale.getDefault()), AppInfo::label))
|
||||
@@ -58,29 +87,68 @@ class SuperUserViewModel : ViewModel() {
|
||||
.toPinyinString(it.label).contains(search)
|
||||
}.filter {
|
||||
it.uid == 2000 // Always show shell
|
||||
|| showSystemApps || it.icon.applicationInfo.flags.and(ApplicationInfo.FLAG_SYSTEM) == 0
|
||||
|| showSystemApps || it.packageInfo.applicationInfo.flags.and(ApplicationInfo.FLAG_SYSTEM) == 0
|
||||
}
|
||||
}
|
||||
|
||||
private suspend inline fun connectKsuService(
|
||||
crossinline onDisconnect: () -> Unit = {}
|
||||
): Pair<IBinder, ServiceConnection> = suspendCoroutine {
|
||||
val connection = object : ServiceConnection {
|
||||
override fun onServiceDisconnected(name: ComponentName?) {
|
||||
onDisconnect()
|
||||
}
|
||||
|
||||
override fun onServiceConnected(name: ComponentName?, binder: IBinder?) {
|
||||
it.resume(binder as IBinder to this)
|
||||
}
|
||||
}
|
||||
|
||||
val intent = Intent(ksuApp, KsuService::class.java);
|
||||
|
||||
val task = KsuService.bindOrTask(
|
||||
intent,
|
||||
Shell.EXECUTOR,
|
||||
connection,
|
||||
)
|
||||
val shell = KsuCli.SHELL
|
||||
task?.let { it1 -> shell.execTask(it1) }
|
||||
}
|
||||
|
||||
private fun stopKsuService() {
|
||||
val intent = Intent(ksuApp, KsuService::class.java);
|
||||
KsuService.stop(intent)
|
||||
}
|
||||
|
||||
suspend fun fetchAppList() {
|
||||
|
||||
isRefreshing = true
|
||||
|
||||
val result = connectKsuService {
|
||||
Log.w(TAG, "KsuService disconnected")
|
||||
}
|
||||
|
||||
withContext(Dispatchers.IO) {
|
||||
isRefreshing = true
|
||||
val pm = ksuApp.packageManager
|
||||
val allowList = Natives.getAllowList().toSet()
|
||||
val denyList = Natives.getDenyList().toSet()
|
||||
Log.i(TAG, "allowList: $allowList")
|
||||
Log.i(TAG, "denyList: $denyList")
|
||||
val start = SystemClock.elapsedRealtime()
|
||||
apps = pm.getInstalledPackages(0).map {
|
||||
|
||||
val binder = result.first
|
||||
val allPackages = IKsuInterface.Stub.asInterface(binder).getPackages(0)
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
stopKsuService()
|
||||
}
|
||||
|
||||
val packages = allPackages.list
|
||||
|
||||
apps = packages.map {
|
||||
val appInfo = it.applicationInfo
|
||||
val uid = appInfo.uid
|
||||
val profile = Natives.getAppProfile(it.packageName, uid)
|
||||
AppInfo(
|
||||
label = appInfo.loadLabel(pm).toString(),
|
||||
packageName = it.packageName,
|
||||
icon = it,
|
||||
uid = uid,
|
||||
onAllowList = uid in allowList,
|
||||
onDenyList = uid in denyList
|
||||
packageInfo = it,
|
||||
profile = profile,
|
||||
)
|
||||
}.filter { it.packageName != ksuApp.packageName }
|
||||
Log.i(TAG, "load cost: ${SystemClock.elapsedRealtime() - start}")
|
||||
|
||||
61
manager/app/src/main/res/values-ar/strings.xml
Normal file
61
manager/app/src/main/res/values-ar/strings.xml
Normal file
@@ -0,0 +1,61 @@
|
||||
<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_superuser_count">مستخدمين الجذر: %d</string>
|
||||
<string name="home_module_count">الوحدات: %d</string>
|
||||
<string name="home_unsupported">غير مدعوم</string>
|
||||
<string name="home_unsupported_reason">KernelSU يدعم GKI kernels فقط</string>
|
||||
|
||||
<string name="home_kernel">إصدار النواة</string>
|
||||
<string name="home_manager_version">إصدار المدير</string>
|
||||
<string name="home_fingerprint">البصمة</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">مستخدم خارق</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="about">من نحن</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">التراكبات غير متوفرة ، لا يمكن للوحدة أن تعمل!</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>
|
||||
<string name="home_learn_kernelsu">تعلم KernelSU</string>
|
||||
<string name="home_learn_kernelsu_url">https://kernelsu.org/guide/what-is-kernelsu.html</string>
|
||||
<string name="home_click_to_learn_kernelsu">تعرف على كيفية تثبيت KernelSU واستخدام الوحدات</string>
|
||||
<string name="home_support_title">إدعمنا</string>
|
||||
<string name="home_support_content">KernelSU سيظل دائماً مجانياً ومفتوح المصدر. مع ذلك، يمكنك أن تظهر لنا أنك تهتم بالتبرع.</string>
|
||||
<string name="about_source_code"><![CDATA[أنظر إلى مصدر البرمجة في %1$s<br/>إنضم إلى قناتنا في %2$s ]]></string>
|
||||
</resources>
|
||||
|
||||
|
||||
60
manager/app/src/main/res/values-bn/strings.xml
Normal file
60
manager/app/src/main/res/values-bn/strings.xml
Normal file
@@ -0,0 +1,60 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name" translatable="false">কার্নেলএসইউ</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_superuser_count">সুপার ইউজার: %d</string>
|
||||
<string name="home_module_count">মডিউল: %d</string>
|
||||
<string name="home_unsupported">অসমর্থিত</string>
|
||||
<string name="home_unsupported_reason">কার্নেলএসইউ শুধুমাত্র জিকেআই কার্নেল সমর্থন করে</string>
|
||||
|
||||
<string name="home_kernel">কার্নেল</string>
|
||||
<string name="home_manager_version">ম্যানেজার সংস্করণ</string>
|
||||
<string name="home_fingerprint">ফিঙ্গারপ্রিন্ট</string>
|
||||
|
||||
<string name="home_selinux_status">সেলিনাক্স স্ট্যাটাস</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="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">রিবুট বুটলোডার</string>
|
||||
<string name="reboot_download">রিবুট ডাউনলোড</string>
|
||||
<string name="reboot_edl">রিবুট ইডিএল</string>
|
||||
<string name="about">এবাউট</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">ওভারলেএফএস উপলব্ধ নয়, মডিউল কাজ করতে পারে না!</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">মডিউলগুলি অক্ষম কারণ তারা ম্যাজিস্কের সাথে বিরোধিতা করে!</string>
|
||||
<string name="home_learn_kernelsu">লার্ন কার্নেলএসইউ</string>
|
||||
<string name="home_learn_kernelsu_url">https://kernelsu.org/guide/what-is-kernelsu.html</string>
|
||||
<string name="home_click_to_learn_kernelsu">কিভাবে কার্নেলএসইউ ইনস্টল করতে হয় এবং মডিউল ব্যবহার করতে হয় তা শিখুন</string>
|
||||
<string name="home_support_title">সাপোর্ট টাইটেল</string>
|
||||
<string name="home_support_content">কার্নেলএসইউ বিনামূল্যে এবং ওপেন সোর্স, এবং সবসময় থাকবে। আপনি সবসময় একটি অনুদান দিয়ে আপনার কৃতজ্ঞতা প্রদর্শন করতে পারেন.</string>
|
||||
<string name="about_source_code"><![CDATA[Bekijk source code op %1$s<br/>আমাদের %2$s চ্যানেল মার্জ করুন]]></string>
|
||||
</resources>
|
||||
67
manager/app/src/main/res/values-fa/strings.xml
Normal file
67
manager/app/src/main/res/values-fa/strings.xml
Normal file
@@ -0,0 +1,67 @@
|
||||
<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_superuser_count">برنامه های با دسترسی روت: %d</string>
|
||||
<string name="home_module_count">ماژولها: %d</string>
|
||||
<string name="home_unsupported">پشتیبانی نشده</string>
|
||||
<string name="home_unsupported_reason">کرنل اس یو فقط هسته های gki را پشتیبانی میکند</string>
|
||||
<string name="home_kernel">هسته</string>
|
||||
<string name="home_manager_version">نسخه برنامه</string>
|
||||
<string name="home_fingerprint">اثرانگشت</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">دسترسی روت</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">راه اندازی به بوتلودر</string>
|
||||
<string name="reboot_download">راه اندازی به حالت دانلود</string>
|
||||
<string name="reboot_edl">راه اندازی به EDL</string>
|
||||
<string name="about">درباره</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">مازول به دلیل تعارض با مجیسک غیرفعال شده اند\'s!</string>
|
||||
<string name="home_learn_kernelsu">یادگیری کرنل اس یو</string>
|
||||
<string name="home_learn_kernelsu_url">https://kernelsu.org/guide/what-is-kernelsu.html</string>
|
||||
<string name="home_click_to_learn_kernelsu">یاد بگیرید چگونه از کرنل اس یو و ماژول ها استفاده کنید</string>
|
||||
<string name="home_support_title">از ما حمایت کنید</string>
|
||||
<string name="home_support_content">KernelSU رایگان است و همیشه خواهد بود و منبع باز است. با این حال، می توانید با اهدای کمک مالی به ما نشان دهید که برایتان مهم است.</string>
|
||||
<string name="about_source_code">
|
||||
<![CDATA[ View source code at %1$s<br/>Join our %2$s channel ]]>
|
||||
</string>
|
||||
<string name="profile">پروفایل برنامه</string>
|
||||
<string name="profile_default">پیشفرض</string>
|
||||
<string name="profile_template">قالب</string>
|
||||
<string name="profile_custom">شخصی سازی شده</string>
|
||||
<string name="profile_name">اسم پروفایل</string>
|
||||
<string name="profile_namespace">Mount namespace</string>
|
||||
<string name="profile_namespace_inherited">اثر گرفته</string>
|
||||
<string name="profile_namespace_global">گلوبال</string>
|
||||
<string name="profile_namespace_individual">تکی</string>
|
||||
<string name="profile_umount_modules">جداکردن ماژول ها</string>
|
||||
</resources>
|
||||
@@ -1,55 +1,77 @@
|
||||
<?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">Memasang module</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>
|
||||
<resources>
|
||||
<string name="app_name" translatable="false">KernelSU</string>
|
||||
|
||||
<string name="home">Beranda</string>
|
||||
<string name="home_not_installed">Tidak terpasang</string>
|
||||
<string name="home_click_to_install">Klik untuk memasang</string>
|
||||
<string name="home_working">Bekerja</string>
|
||||
<string name="home_working_version">Versi: %d</string>
|
||||
<string name="home_superuser_count">Superusers: %d</string>
|
||||
<string name="home_module_count">Modul: %d</string>
|
||||
<string name="home_unsupported">Tidak didukung</string>
|
||||
<string name="home_unsupported_reason">KernelSU hanya mendukung kernel GKI saat ini</string>
|
||||
|
||||
<string name="home_kernel">Kernel</string>
|
||||
<string name="home_manager_version">Versi Manager</string>
|
||||
<string name="home_fingerprint">Sidik jari</string>
|
||||
|
||||
<string name="home_selinux_status">status SELinux</string>
|
||||
<string name="selinux_status_disabled">Dinonaktifkan</string>
|
||||
<string name="selinux_status_enforcing">Enforcing</string>
|
||||
<string name="selinux_status_permissive">Permissive</string>
|
||||
<string name="selinux_status_unknown">Tidak dikenal</string>
|
||||
<string name="superuser">Superuser</string>
|
||||
<string name="module_failed_to_enable">Gagal mengaktifkan modul: %s</string>
|
||||
<string name="module_failed_to_disable">Gagal menonaktifkan modul: %s</string>
|
||||
<string name="module_empty">Tidak ada modul terpasang</string>
|
||||
|
||||
<string name="module">Modul</string>
|
||||
<string name="uninstall">Copot</string>
|
||||
<string name="module_install">Pasang</string>
|
||||
<string name="install">Pasang</string>
|
||||
<string name="reboot">Reboot</string>
|
||||
<string name="settings">Pengaturan</string>
|
||||
<string name="reboot_userspace">Reboot Lembut</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="module_uninstall_confirm">Apakah Anda yakin ingin mencopot modul %s?</string>
|
||||
<string name="module_uninstall_success">%s Tercopot</string>
|
||||
<string name="module_uninstall_failed">Gagal untuk mencopot: %s</string>
|
||||
<string name="module_version">Versi</string>
|
||||
<string name="module_author">Pembuat</string>
|
||||
<string name="module_overlay_fs_not_available">overlayfs tidak tersedia, modul tidak dapat bekerja!</string>
|
||||
<string name="refresh">Segarkan</string>
|
||||
<string name="show_system_apps">Tampilkan apl sistem</string>
|
||||
<string name="hide_system_apps">Sembunyikan apl sistem</string>
|
||||
<string name="send_log">Kirim Log</string>
|
||||
<string name="safe_mode">Mode aman</string>
|
||||
<string name="reboot_to_apply">Reboot untuk menerapkan</string>
|
||||
<string name="module_magisk_conflict">Modul dinonaktifkan karena bertentangan dengan Magisk!</string>
|
||||
<string name="home_learn_kernelsu">Pelajari KernelSU</string>
|
||||
<string name="home_learn_kernelsu_url">https://kernelsu.org/guide/what-is-kernelsu.html</string>
|
||||
<string name="home_click_to_learn_kernelsu">Pelajari cara memasang KernelSU dan menggunakan modul</string>
|
||||
<string name="home_support_title">Dukung Kami</string>
|
||||
<string name="home_support_content">KernelSU gratis dan bersumber terbuka, dan akan selalu seperti itu. Bagaimanapun juga Anda dapat menunjukan kepedulian Anda kepada kami dengan mengirimkan sedikit donasi.</string>
|
||||
<string name="about_source_code"><![CDATA[Lihat sumber code di %1$s<br/>Gabung kanal %2$s kami]]></string>
|
||||
<string name="profile">Profil Apl</string>
|
||||
<string name="profile_default">Bawaan</string>
|
||||
<string name="profile_template">Templat</string>
|
||||
<string name="profile_custom">Khusus</string>
|
||||
<string name="profile_name">Nama profil</string>
|
||||
<string name="profile_namespace">Ikat ruang-nama</string>
|
||||
<string name="profile_namespace_inherited">Diwariskan</string>
|
||||
<string name="profile_namespace_global">Universal</string>
|
||||
<string name="profile_namespace_individual">Personal</string>
|
||||
<string name="profile_groups">Kelompok</string>
|
||||
<string name="profile_capabilities">Kemampuan</string>
|
||||
<string name="profile_selinux_context">Konteks SELinux</string>
|
||||
<string name="profile_umount_modules">Lepas modul</string>
|
||||
<string name="failed_to_update_app_profile">Gagal memperbarui Profil Apl untuk %s</string>
|
||||
<string name="require_kernel_version">Versi kernel saat ini %d terlalu rendah bagi manager untuk berfungsi dengan baik. Tolong tingkatkan ke versi %d atau lebih tinggi!</string>
|
||||
<string name="settings_umount_modules_default">Lepas modul secara bawaan</string>
|
||||
<string name="settings_umount_modules_default_summary">Nilai bawaan universal untuk \"Lepas modul\" di Profil-profil Apl. Jika diaktifkan, ini akan menghapus semua modifikasi modul pada sistem untuk aplikasi yang tidak memiliki set Profil.</string>
|
||||
<string name="profile_umount_modules_summary">Mengaktifkan opsi ini akan mengizinkan KernelSU memulihkan file-file yang dimodifikasi oleh modul untuk aplikasi ini.</string>
|
||||
</resources>
|
||||
|
||||
57
manager/app/src/main/res/values-it/strings.xml
Normal file
57
manager/app/src/main/res/values-it/strings.xml
Normal file
@@ -0,0 +1,57 @@
|
||||
<resources>
|
||||
<string name="home">Home</string>
|
||||
<string name="home_not_installed">Non installato</string>
|
||||
<string name="home_click_to_install">Clicca per installare</string>
|
||||
<string name="home_working">In esecuzione</string>
|
||||
<string name="home_working_version">Versione: %d</string>
|
||||
<string name="home_superuser_count">Superuser: %d</string>
|
||||
<string name="home_module_count">Moduli: %d</string>
|
||||
<string name="home_unsupported">Non supportato</string>
|
||||
<string name="home_unsupported_reason">KernelSU ora supporta solo i kernel GKI</string>
|
||||
|
||||
<string name="home_kernel">Kernel</string>
|
||||
<string name="home_manager_version">Versione del manager</string>
|
||||
<string name="home_fingerprint">Fingerprint</string>
|
||||
|
||||
<string name="home_selinux_status">Stato SELinux</string>
|
||||
<string name="selinux_status_disabled">Disabilitato</string>
|
||||
<string name="selinux_status_enforcing">Enforcing</string>
|
||||
<string name="selinux_status_permissive">Permissive</string>
|
||||
<string name="selinux_status_unknown">Sconosciuto</string>
|
||||
<string name="superuser">Accesso root</string>
|
||||
<string name="module_failed_to_enable">Impossibile abilitare il modulo: %s</string>
|
||||
<string name="module_failed_to_disable">Impossibile disabilitare il modulo: %s</string>
|
||||
<string name="module_empty">Nessun modulo installato</string>
|
||||
|
||||
<string name="module">Moduli</string>
|
||||
<string name="uninstall">Disinstalla</string>
|
||||
<string name="module_install">Installa</string>
|
||||
<string name="install">Installa</string>
|
||||
<string name="reboot">Riavvia</string>
|
||||
<string name="settings">Impostazioni</string>
|
||||
<string name="reboot_userspace">Riavvio veloce</string>
|
||||
<string name="reboot_recovery">Riavvia in Recovery</string>
|
||||
<string name="reboot_bootloader">Riavvia in modalità Bootloader</string>
|
||||
<string name="reboot_download">Riavvia in modalità Download</string>
|
||||
<string name="reboot_edl">Riavvia in modalità EDL</string>
|
||||
<string name="about">Informazioni</string>
|
||||
<string name="module_uninstall_confirm">Sei sicuro di voler disinstallare il modulo %s?</string>
|
||||
<string name="module_uninstall_success">%s disinstallato</string>
|
||||
<string name="module_uninstall_failed">Impossibile disinstallare: %s</string>
|
||||
<string name="module_version">Versione</string>
|
||||
<string name="module_author">Autore</string>
|
||||
<string name="module_overlay_fs_not_available">overlayfs non è disponibile, il modulo non può funzionare!</string>
|
||||
<string name="refresh">Aggiorna</string>
|
||||
<string name="show_system_apps">Mostra app di sistema</string>
|
||||
<string name="hide_system_apps">Nascondi app di sistema</string>
|
||||
<string name="send_log">Invia log</string>
|
||||
<string name="safe_mode">Modalità provvisoria</string>
|
||||
<string name="reboot_to_apply">Riavvia per applicare la modifica</string>
|
||||
<string name="module_magisk_conflict">I moduli sono disabilitati perché in conflitto con quelli di Magisk!</string>
|
||||
<string name="home_learn_kernelsu">Scopri KernelSU</string>
|
||||
<string name="home_learn_kernelsu_url">https://kernelsu.org/guide/what-is-kernelsu.html</string>
|
||||
<string name="home_click_to_learn_kernelsu">Scopri come installare KernelSU e utilizzare i moduli</string>
|
||||
<string name="home_support_title">Supportaci</string>
|
||||
<string name="home_support_content">KernelSU è, e sempre sarà, gratuito e open source. Puoi comunque mostrarci il tuo apprezzamento facendo una donazione.</string>
|
||||
<string name="about_source_code"><![CDATA[Visualizza il codice sorgente su %1$s<br/>Unisciti al nostro canale %2$s]]></string>
|
||||
</resources>
|
||||
@@ -1,54 +1,68 @@
|
||||
<?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>
|
||||
|
||||
<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_superuser_count">スーパーユーザー: %d</string>
|
||||
<string name="home_module_count">モジュール: %d</string>
|
||||
<string name="home_unsupported">非対応</string>
|
||||
<string name="home_unsupported_reason">KernelSU は現在、GKI カーネルにのみ対応しています</string>
|
||||
|
||||
<string name="home_kernel">カーネル</string>
|
||||
<string name="home_manager_version">バージョン</string>
|
||||
<string name="home_fingerprint">フィンガープリント</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="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="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>
|
||||
<string name="home_learn_kernelsu">KernelSU の詳細</string>
|
||||
<string name="home_learn_kernelsu_url">https://kernelsu.org/ja_JP/guide/what-is-kernelsu.html</string>
|
||||
<string name="home_click_to_learn_kernelsu">KernelSU のインストール方法やモジュールの使い方はこちら</string>
|
||||
<string name="home_support_title">支援する</string>
|
||||
<string name="home_support_content">KernelSU は無料かつオープンソースです。寄付していただくことで開発を支援できます。</string>
|
||||
<string name="about_source_code"><![CDATA[%1$s でソースコードを表示<br/>%2$s チャンネルに参加]]></string>
|
||||
<string name="profile">アプリのプロファイル</string>
|
||||
<string name="profile_default">デフォルト</string>
|
||||
<string name="profile_template">テンプレート</string>
|
||||
<string name="profile_custom">カスタム</string>
|
||||
<string name="profile_name">プロファイル名</string>
|
||||
<string name="profile_namespace">マウント名前空間</string>
|
||||
<string name="profile_namespace_inherited">継承</string>
|
||||
<string name="profile_namespace_global">グローバル</string>
|
||||
<string name="profile_namespace_individual">分離</string>
|
||||
<string name="profile_umount_modules">モジュールのアンマウント</string>
|
||||
</resources>
|
||||
|
||||
56
manager/app/src/main/res/values-ko/strings.xml
Normal file
56
manager/app/src/main/res/values-ko/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_superuser_count">루트 권한: %d개</string>
|
||||
<string name="home_module_count">설치된 모듈: %d개</string>
|
||||
<string name="home_unsupported">지원되지 않음</string>
|
||||
<string name="home_unsupported_reason">KernelSU는 현재 GKI 커널만 지원합니다</string>
|
||||
|
||||
<string name="home_kernel">커널</string>
|
||||
<string name="home_manager_version">매니저 버전</string>
|
||||
<string name="home_fingerprint">빌드 정보</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">슈퍼유저</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">부트로더로 다시 시작</string>
|
||||
<string name="reboot_download">다운로드 모드로 다시 시작</string>
|
||||
<string name="reboot_edl">EDL 모드로 다시 시작</string>
|
||||
<string name="about">정보</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>
|
||||
<string name="home_learn_kernelsu">KernelSU 알아보기</string>
|
||||
<string name="home_click_to_learn_kernelsu">KernelSU 설치 방법과 모듈 사용 방법을 확인합니다.</string>
|
||||
<string name="home_support_title">지원이 필요해요!</string>
|
||||
<string name="home_support_content">KernelSU는 지금도, 앞으로도 항상 무료이며 오픈 소스로 유지됩니다. 기부를 통해 여러분의 관심을 보여주세요.</string>
|
||||
<string name="about_source_code"><![CDATA[%1$s에서 소스 코드 보기<br/>%2$s 채널 참가하기]]></string>
|
||||
</resources>
|
||||
82
manager/app/src/main/res/values-nl/strings.xml
Normal file
82
manager/app/src/main/res/values-nl/strings.xml
Normal file
@@ -0,0 +1,82 @@
|
||||
<resources>
|
||||
<string name="app_name" translatable="false">KernelSU</string>
|
||||
|
||||
<string name="home">Home</string>
|
||||
<string name="home_not_installed">Niet geïnstalleerd</string>
|
||||
<string name="home_click_to_install">Klik om te installeren</string>
|
||||
<string name="home_working">Werkend</string>
|
||||
<string name="home_working_version">Versie: %d</string>
|
||||
<string name="home_superuser_count">Superusers: %d</string>
|
||||
<string name="home_module_count">Modules: %d</string>
|
||||
<string name="home_unsupported">Niet ondersteund</string>
|
||||
<string name="home_unsupported_reason">KernelSU ondersteunt alleen GKI kernels</string>
|
||||
|
||||
<string name="home_kernel">Kernel</string>
|
||||
<string name="home_manager_version">Manager Versie</string>
|
||||
<string name="home_fingerprint">Fingerprint</string>
|
||||
|
||||
<string name="home_selinux_status">SELinux status</string>
|
||||
<string name="selinux_status_disabled">Uitgeschakeld</string>
|
||||
<string name="selinux_status_enforcing">Afgedwongen</string>
|
||||
<string name="selinux_status_permissive">Permissief</string>
|
||||
<string name="selinux_status_unknown">Niet gekend</string>
|
||||
<string name="superuser">Superuser</string>
|
||||
<string name="module_failed_to_enable">Mislukt om module in te schakelen: %s</string>
|
||||
<string name="module_failed_to_disable">Mislukt om module uit te schakelen: %s</string>
|
||||
<string name="module_empty">Geen module geïnstalleerd</string>
|
||||
|
||||
<string name="module">Module</string>
|
||||
<string name="uninstall">Verwijderen</string>
|
||||
<string name="module_install">Installeren</string>
|
||||
<string name="install">Installeren</string>
|
||||
<string name="reboot">Herstart</string>
|
||||
<string name="settings">Instellingen</string>
|
||||
<string name="reboot_userspace">Soft herstart</string>
|
||||
<string name="reboot_recovery">Herstart naar Recovery</string>
|
||||
<string name="reboot_bootloader">Herstart naar Bootloader</string>
|
||||
<string name="reboot_download">Herstart om te downloaden</string>
|
||||
<string name="reboot_edl">Herstart naar EDL</string>
|
||||
<string name="about">Over</string>
|
||||
<string name="module_uninstall_confirm">Zeker van het verwijderen van module %s?</string>
|
||||
<string name="module_uninstall_success">%s verwijderd</string>
|
||||
<string name="module_uninstall_failed">Mislukt om te verwijderen: %s</string>
|
||||
<string name="module_version">Versie</string>
|
||||
<string name="module_author">Auteur</string>
|
||||
<string name="module_overlay_fs_not_available">overlayfs is niet beschikbaar, module kan niet werken!</string>
|
||||
<string name="refresh">Vernieuwen</string>
|
||||
<string name="show_system_apps">Toon systeem apps</string>
|
||||
<string name="hide_system_apps">Verberg systeem apps</string>
|
||||
<string name="send_log">Stuur Log</string>
|
||||
<string name="safe_mode">Safe mode</string>
|
||||
<string name="reboot_to_apply">Herstart om effect te hebben</string>
|
||||
<string name="module_magisk_conflict">Modules zijn uitgeschakeld omdat ze in conflict zijn met magisk!</string>
|
||||
<string name="home_learn_kernelsu">Leer KernelSU</string>
|
||||
<string name="home_learn_kernelsu_url">https://kernelsu.org/guide/what-is-kernelsu.html</string>
|
||||
<string name="home_click_to_learn_kernelsu">Leer hoe KernelSU te installeren en modules te gebruiken</string>
|
||||
<string name="home_support_title">Ondersteun ons</string>
|
||||
<string name="home_support_content">KernelSU is, en zal altijd, vrij en open source zijn. Je kan altijd je appreciatie tonen met een donatie.</string>
|
||||
<string name="about_source_code"><![CDATA[Bekijk source code op %1$s<br/>Vervoeg ons %2$s kanaal]]></string>
|
||||
<string name="profile" translatable="false">App profiel</string>
|
||||
<string name="profile_default">Standaard</string>
|
||||
<string name="profile_template">Sjabloon</string>
|
||||
<string name="profile_custom">Aangepast</string>
|
||||
<string name="profile_name">Profiel naam</string>
|
||||
<string name="profile_namespace">Koppel naamruimte</string>
|
||||
<string name="profile_namespace_inherited">Overgenomen</string>
|
||||
<string name="profile_namespace_global">Globaal</string>
|
||||
<string name="profile_namespace_individual">Individuëel</string>
|
||||
<string name="profile_groups">Groepen</string>
|
||||
<string name="profile_capabilities">Mogelijkheden</string>
|
||||
<string name="profile_selinux_context">SELinux context</string>
|
||||
<string name="profile_umount_modules">Ontkoppel modules</string>
|
||||
<string name="failed_to_update_app_profile">Mislukt om App Profiel te updaten voor %s</string>
|
||||
<string name="require_kernel_version">De bestaande kernel versie %d is te laag voor de manager om goed te werken. Upgrade best tot versie %d of hoger!</string>
|
||||
<string name="settings_umount_modules_default">Ontkoppel standaard de modules</string>
|
||||
<string name="settings_umount_modules_default_summary">De globale standaard waarde voor \"Ontkoppel modules\" in App Profielen. Indien geactiveerd, zal het alle module wijzigingen tot het systeem verwijderen voor applicaties die geen Profiel ingesteld hebben.</string>
|
||||
<string name="profile_umount_modules_summary">Met deze optie ingeschakeld zal KernelSU toelaten om alle gewijzigde bestanden door de modules voor deze applicatie te herstellen.</string>
|
||||
<string name="profile_selinux_domain">Domein</string>
|
||||
<string name="profile_selinux_rules">Regels</string>
|
||||
<string name="module_update">Update</string>
|
||||
<string name="module_downloading">Downloaden van module: %s</string>
|
||||
<string name="new_version_available">Nieuwe versie: %s is beschikbaar, klik om te downloaden</string>
|
||||
</resources>
|
||||
77
manager/app/src/main/res/values-pt-rBR/strings.xml
Normal file
77
manager/app/src/main/res/values-pt-rBR/strings.xml
Normal file
@@ -0,0 +1,77 @@
|
||||
<resources>
|
||||
<string name="app_name" translatable="false">KernelSU</string>
|
||||
|
||||
<string name="home">Início</string>
|
||||
<string name="home_not_installed">Não instalado</string>
|
||||
<string name="home_click_to_install">Clique para instalar</string>
|
||||
<string name="home_working">Em Execução</string>
|
||||
<string name="home_working_version">Versão: %d</string>
|
||||
<string name="home_superuser_count">Superusuários: %d</string>
|
||||
<string name="home_module_count">Módulos: %d</string>
|
||||
<string name="home_unsupported">Sem Suporte</string>
|
||||
<string name="home_unsupported_reason">Por enquanto, KernelSU suporta apenas kernels GKI</string>
|
||||
|
||||
<string name="home_kernel">Kernel</string>
|
||||
<string name="home_manager_version">Versão do gerenciador</string>
|
||||
<string name="home_fingerprint">Impressão digital</string>
|
||||
|
||||
<string name="home_selinux_status">Status do SELinux</string>
|
||||
<string name="selinux_status_disabled">Desabilitado</string>
|
||||
<string name="selinux_status_enforcing">Enforcing</string>
|
||||
<string name="selinux_status_permissive">Permissive</string>
|
||||
<string name="selinux_status_unknown">Desconhecido</string>
|
||||
<string name="superuser">Superusuário</string>
|
||||
<string name="module_failed_to_enable">Falha ao ativar o módulo: %s</string>
|
||||
<string name="module_failed_to_disable">Falha ao desativar o módulo: %s</string>
|
||||
<string name="module_empty">Nenhum módulo instalado</string>
|
||||
|
||||
<string name="module">Módulo</string>
|
||||
<string name="uninstall">Desinstalar</string>
|
||||
<string name="module_install">Instalar</string>
|
||||
<string name="install">Instalar</string>
|
||||
<string name="reboot">Reiniciar</string>
|
||||
<string name="settings">Configurações</string>
|
||||
<string name="reboot_userspace">Reinicialização suave</string>
|
||||
<string name="reboot_recovery">Reiniciar para recuperação</string>
|
||||
<string name="reboot_bootloader">Reiniciar para bootloader</string>
|
||||
<string name="reboot_download">Reiniciar para download</string>
|
||||
<string name="reboot_edl">Reiniciar para EDL</string>
|
||||
<string name="about">Sobre</string>
|
||||
<string name="module_uninstall_confirm">Tem certeza de que deseja desinstalar o módulo %s?</string>
|
||||
<string name="module_uninstall_success">%s desinstalado</string>
|
||||
<string name="module_uninstall_failed">Falha ao desinstalar: %s</string>
|
||||
<string name="module_version">Versão</string>
|
||||
<string name="module_author">Autor</string>
|
||||
<string name="module_overlay_fs_not_available">overlayfs não está disponível, o módulo não funcionará!</string>
|
||||
<string name="refresh">Atualizar</string>
|
||||
<string name="show_system_apps">Mostrar aplicativos do sistema</string>
|
||||
<string name="hide_system_apps">Ocultar aplicativos do sistema</string>
|
||||
<string name="send_log">Enviar log</string>
|
||||
<string name="safe_mode">Modo de segurança</string>
|
||||
<string name="reboot_to_apply">Reinicie para entrar em vigor</string>
|
||||
<string name="module_magisk_conflict">Os módulos estão desativados porque entraram em conflito com o Magisk\'s!</string>
|
||||
<string name="home_learn_kernelsu">Leia mais sobre KernelSU</string>
|
||||
<string name="home_learn_kernelsu_url">https://kernelsu.org/guide/what-is-kernelsu.html</string>
|
||||
<string name="home_click_to_learn_kernelsu">Saiba como instalar o KernelSU e usar os módulos</string>
|
||||
<string name="home_support_title">Apoie-nos</string>
|
||||
<string name="home_support_content">O KernelSU é, e sempre será, gratuito e de código aberto. No entanto, você pode mostrar seu apoio fazendo uma doação.</string>
|
||||
<string name="about_source_code"><![CDATA[Veja o código-fonte no %1$s<br/>Junte-se ao nosso canal do %2$s]]></string>
|
||||
<string name="profile" translatable="false">Perfil do Aplicativo</string>
|
||||
<string name="profile_default">Padrão</string>
|
||||
<string name="profile_template">Modelo</string>
|
||||
<string name="profile_custom">Personalizado</string>
|
||||
<string name="profile_name">Nome do perfil</string>
|
||||
<string name="profile_namespace">Montar namespace</string>
|
||||
<string name="profile_namespace_inherited">Padrão</string>
|
||||
<string name="profile_namespace_global">Global</string>
|
||||
<string name="profile_namespace_individual">Individual</string>
|
||||
<string name="profile_groups">Grupos</string>
|
||||
<string name="profile_capabilities">Permissões</string>
|
||||
<string name="profile_selinux_context">Contexto do SELinux</string>
|
||||
<string name="profile_umount_modules">Módulos não montados</string>
|
||||
<string name="failed_to_update_app_profile">Falha ao atualizar o perfil do aplicativo para %s</string>
|
||||
<string name="require_kernel_version">A versão atual do kernel %d é muito baixa para o gerenciador funcionar corretamente. Atualize para a versão %d ou superior!</string>
|
||||
<string name="settings_umount_modules_default">Não montar módulos por padrão</string>
|
||||
<string name="settings_umount_modules_default_summary">O valor padrão global para \"Módulos não montados\" em perfis de aplicativos. Se ativado, removerá todas as modificações do módulo do sistema para aplicativos que não possuem um perfil definido.</string>
|
||||
<string name="profile_umount_modules_summary">Ativar esta opção permitirá que o KernelSU restaure quaisquer arquivos modificados pelos módulos para este aplicativo.</string>
|
||||
</resources>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user