Compare commits

..

181 Commits

Author SHA1 Message Date
topjohnwu
62b1310d97 Release Magisk v29.0
[skip ci]
2025-05-14 01:26:05 -07:00
topjohnwu
0a86916d3a Fix cleanup 2025-05-14 01:23:54 -07:00
topjohnwu
9907ce57aa Add Magisk r29.0 release notes 2025-05-14 01:13:37 -07:00
topjohnwu
b92626cacc Use sudo instead of su 2025-05-13 17:26:09 -07:00
topjohnwu
5a762f0a8e Move all gradle files into folder app
Decouple java and native projects
2025-05-13 17:04:41 -07:00
topjohnwu
5dd7a7d804 Better ABI support for build.py 2025-05-13 14:35:57 -07:00
topjohnwu
7831f40691 Make tests more granular 2025-05-13 14:08:54 -07:00
topjohnwu
4f4b1ff885 Add sepolicy.rule patching tests 2025-05-13 14:08:54 -07:00
topjohnwu
97901979dd Test replace functionality 2025-05-13 14:08:54 -07:00
topjohnwu
287316842c Fix Android M sepolicy reading 2025-05-13 12:28:10 -07:00
topjohnwu
608786e8f3 Print verbose logs in avd_test.sh and cuttlefish.sh 2025-05-11 02:18:36 -07:00
topjohnwu
9684a35cab Use rust::String::c_str to ensure nil termination 2025-05-09 16:02:46 -07:00
Radoš Milićev
e3e4202954 Update translation of core strings 2025-05-09 11:06:03 -07:00
Radoš Milićev
23c2054d46 Translate stub strings 2025-05-09 11:06:03 -07:00
Wang Han
a20a2a8fa0 Recognize Samsung custom policy version path 2025-05-09 11:03:58 -07:00
topjohnwu
a2896be4a6 Cargo fmt
[skip ci]
2025-05-08 23:29:27 -07:00
LoveSy
e9220a28d9 Use splice to pump tty to avoid userspace copying 2025-05-08 23:20:46 -07:00
vvb2060
cf12087e21 app: disable multiArch 2025-05-08 21:02:26 -07:00
topjohnwu
00c1b36837 Support generating files for C++ IDE 2025-05-07 22:18:44 -07:00
topjohnwu
03e034795d Implement Ord and PartialOrd for Utf8CStr familiy 2025-05-05 11:33:33 -07:00
LoveSy
79c0fafe43 Fix cleanup pre-init mount 2025-05-05 11:00:39 -07:00
vvb2060
d499819ba0 app: ignore androidx meta prop file 2025-05-02 16:40:37 -07:00
vvb2060
86da917174 core: fix mkdirs 2025-05-02 16:39:46 -07:00
ZGX089
30bd7d6555 feat: Improve Arabic translation (stub)
Fixed typos, Grammatical errors and changed/fixed other strings.
2025-05-01 13:47:46 -07:00
ZGX089
e5a12f0f5f feat: Improve Arabic translation
Fix Grammatical errors, typos, and changed/fixed other strings.
2025-05-01 13:47:46 -07:00
topjohnwu
c85a8434c6 Update dependencies 2025-04-29 16:22:45 -07:00
topjohnwu
427a1ca4e5 Release new canary build
[skip ci]
2025-04-29 11:54:17 -07:00
topjohnwu
22884e173a Implement reboot in Rust 2025-04-28 17:22:14 -07:00
topjohnwu
d1829308e9 Move more daemon_start code into Rust 2025-04-28 17:22:14 -07:00
topjohnwu
73840f8721 Migrate selinux.cpp to selinux.rs 2025-04-28 17:22:14 -07:00
topjohnwu
c7d1af9805 Stop using PathBuf in package.rs 2025-04-28 17:22:14 -07:00
topjohnwu
4ad26d3dfb Better path methods 2025-04-28 17:22:14 -07:00
topjohnwu
0c70b7670c Cleanup dir implementations 2025-04-28 17:22:14 -07:00
topjohnwu
f44d044095 Remove Utf8CStrBuffer 2025-04-28 17:22:14 -07:00
topjohnwu
5c1cb13472 Remove AsUtf8CStr trait 2025-04-28 17:22:14 -07:00
topjohnwu
3327fc668e Remove FsPath and FsPathMnt trait
Directly use Utf8CStr
2025-04-28 17:22:14 -07:00
topjohnwu
610945ac54 Remove open_fd macro 2025-04-28 17:22:14 -07:00
Howard Wu
ddf5474917 apt-get update before install to fix ci 2025-04-28 11:16:14 -07:00
Howard Wu
6ba1685ade Fix some seopt log 2025-04-22 03:25:21 -07:00
topjohnwu
e02b5f7868 Rename cstr_buf to cstr::buf 2025-04-22 03:21:00 -07:00
topjohnwu
ab2e5d1e7e Make FsPathBuf a trait and rename to FsPathBuilder 2025-04-22 03:21:00 -07:00
topjohnwu
f3fef7bfe4 Make FsPath a trait 2025-04-22 03:21:00 -07:00
topjohnwu
c34c7838bb Cleanup cstr implementation 2025-04-22 03:21:00 -07:00
topjohnwu
c8a16b0e0c Remove unused code 2025-04-16 17:13:03 -07:00
topjohnwu
14f9ed91a1 Remove unused methods 2025-04-15 11:35:31 -07:00
topjohnwu
7a207d4ccf Only accept UTF-8 directory entries 2025-04-15 10:26:22 -07:00
topjohnwu
92a42d901f Move most implementation into Directory 2025-04-15 10:26:22 -07:00
topjohnwu
084d89fcce Create Utf8CStrBuffer type 2025-04-15 10:26:22 -07:00
topjohnwu
55b036c071 Introduce BorrowedDirectory 2025-04-15 10:26:22 -07:00
topjohnwu
30e79310ab Make pointers NonNull after error check 2025-04-15 00:18:48 -07:00
topjohnwu
f063fa5054 Cleanup xwrap implementation 2025-04-15 00:18:48 -07:00
topjohnwu
7bd901273c Provide richer error messages
Make sure most syscall/libc calls results are mapped to OsResult
that can produce more detailed error messages.
2025-04-15 00:18:48 -07:00
topjohnwu
c1e061603b Specify ADB_SERIAL for emulator 2025-04-13 21:43:11 -07:00
topjohnwu
cb08504fe5 Update cargo dependencies 2025-04-11 14:48:16 -07:00
topjohnwu
c0a1fb77be Code cleanup 2025-04-11 14:48:01 -07:00
LoveSy
4864c1112a no pty for -c by default, and add -i to force pty 2025-04-11 13:21:10 -07:00
LoveSy
9ddeab034b Fix wrong tty pump
See #1463
2025-04-11 13:21:10 -07:00
LoveSy
c4847ed288 Move pts to rust, and avoid using thread 2025-04-11 13:21:10 -07:00
topjohnwu
b8f1523fb2 Minor code reorg
[skip ci]
2025-04-08 17:20:22 -07:00
topjohnwu
fb7fa8a6b3 Update to ONDK r29.1 2025-04-08 12:11:59 -07:00
topjohnwu
9c7d359093 Optimize and format imports
[skip ci]
2025-04-08 09:57:09 -07:00
topjohnwu
eb54bc1fd7 Cleanup unused code 2025-04-08 02:33:52 -07:00
topjohnwu
d4a0286e13 Migrate magiskinit selinux.cpp to Rust 2025-04-08 02:33:52 -07:00
Steven Xu
83e66767ff refactor: use empty navOptions 2025-04-02 09:13:32 -07:00
Steven Xu
7dc010749b feat: remove animation settings button transition 2025-04-02 09:13:32 -07:00
Steven Xu
8e8d013b1b feat: remove log overscroll 2025-04-02 09:13:32 -07:00
Steven Xu
bba0373808 feat: remove navigation transition when clicking buttons on the bottom bar 2025-04-02 09:13:32 -07:00
topjohnwu
1fa318dc8c Use Rust elf-cleaner implementation 2025-04-01 18:32:54 -07:00
cheesetosti
6edc5e2037 Update install.md
fixed grammar n stuff
2025-04-01 12:13:29 -07:00
topjohnwu
1523ed9f78 Always go through rustup proxies 2025-04-01 10:01:35 -07:00
topjohnwu
8e604d2ab8 Update cuttlefish CI 2025-03-28 00:12:31 -07:00
topjohnwu
2aba7247a9 Skip stub APK install on emulator
Reduce test flakiness
2025-03-26 13:15:12 -07:00
topjohnwu
e66fe8533e API 36 does not support wait_for_bootanim 2025-03-26 13:15:12 -07:00
vvb2060
b03fbb3917 avd_test: upgrade to android16 beta3 2025-03-26 13:15:12 -07:00
vvb2060
c2ece62e4c native: delete global 16k option
NDK 28 enable 16 KiB page size compatibility option by default, delete the global option to restore 4k alignment for 32-bit arch.
2025-03-26 13:15:12 -07:00
vvb2060
8c972dcf34 app: target sdk 36 2025-03-26 13:15:12 -07:00
topjohnwu
50af14f2a3 Move all MagiskInit entrypoints into init.rs 2025-03-24 17:26:03 -07:00
topjohnwu
e0a356b319 Introduce mount helper methods 2025-03-24 17:26:03 -07:00
topjohnwu
c09a792958 Reorganize magiskinit code 2025-03-24 17:26:03 -07:00
topjohnwu
0bbfe7f44d Fix 2SI on legacy SAR devices 2025-03-24 17:26:03 -07:00
topjohnwu
a396abf565 Minor changes
[skip ci]
2025-03-22 01:16:51 -07:00
topjohnwu
1e3edb8883 Release new canary build
[skip ci]
2025-03-21 10:37:49 -07:00
topjohnwu
3b8b61bf35 Remove ZipUtils.kt 2025-03-20 14:54:25 -07:00
topjohnwu
6f90456036 Properly buffer I/O 2025-03-20 13:16:39 -07:00
topjohnwu
f56fd4e215 Always close outputStream in processFile
Fix #8735
2025-03-19 17:57:31 -07:00
topjohnwu
aa35aac5d5 Update dependencies 2025-03-19 15:55:06 -07:00
topjohnwu
1f162b819d Update ONDK r29.0 2025-03-19 10:55:08 -07:00
Wang Han
52ef1d1cb2 Simplify matching selinux context of child zygote (#8845) 2025-03-11 01:27:15 -07:00
John Wu
f14e3a89cc Enable optimize_for_size for Rust std (#8844) 2025-03-10 15:50:43 -07:00
topjohnwu
95d3eac2e0 Cleanup xwrap functions 2025-03-09 01:10:41 -08:00
Wang Han
8e73536e02 Remove unused hasGMS variable 2025-03-09 00:16:25 -08:00
LoveSy
12a0870bc9 Replace bzip2 with bz2-rs 2025-03-08 14:37:17 -08:00
topjohnwu
6ff82c4e86 Introduce FsPathFollow
Make sure all operations of FsPath do not follow symlinks, and provide
a way to explicitly switch over to a set of operations that DO follow
symlinks by FsPath::follow_link.
2025-03-07 15:51:51 -08:00
LoveSy
c64de35375 Move magiskpolicy cli to argh 2025-03-07 14:29:30 -08:00
topjohnwu
ee5283f4e8 Update release script 2025-03-07 14:14:06 -08:00
LoveSy
bd0e954fea Replace zlib with zlib-rs 2025-03-07 11:40:02 -08:00
topjohnwu
675471a49e Upgrade argh to stable release 2025-03-07 02:38:36 -08:00
topjohnwu
c90e73ccec Migration to Edition 2024 2025-03-07 02:35:25 -08:00
topjohnwu
a43c1267d8 Update Cargo.toml 2025-03-07 02:35:25 -08:00
vvb2060
e8958c6b5c get_secontext: ignore ENODATA 2025-03-06 20:03:36 -08:00
LoveSy
e8a3bf82c6 set exit code of log ExitOnError to -1 2025-03-06 20:03:15 -08:00
topjohnwu
27fd79176a Update ONDK to r28.3 2025-03-06 17:41:28 -08:00
topjohnwu
28d86a3454 Update rustup_wrapper
[skip ci]
2025-03-06 16:12:35 -08:00
topjohnwu
c6c1a17ae6 Address several clippy warnings 2025-03-03 02:15:14 -08:00
topjohnwu
2b47d47215 Also run clippy with release mode
[skip ci]
2025-03-02 23:14:46 -08:00
David K.
0e82df9e10 Support zImage compression types other than gzip.
Instead of just searching for the gzip magic, it now incrementally searches the kernel for the first thing that `check_fmt_lg` doesn't report as `UNKNOWN`.
2025-03-02 13:35:13 -08:00
topjohnwu
893821ad88 Skip all tests on master push 2025-03-02 02:30:11 -08:00
topjohnwu
6b80fbfa99 Fix cache save condition 2025-03-02 02:30:11 -08:00
topjohnwu
8c3c7d0194 Build on master push 2025-03-02 01:51:42 -08:00
topjohnwu
b94a3d9f2f Do not store cache on pull request 2025-03-02 01:51:42 -08:00
Wang Han
442d0b5ddc Delete bootctl binary if execution fails
New devices may use AIDL bootctrl HAL, so if bootctl hal-info fails,
simply remove the temp file and return.
2025-03-01 22:43:55 -08:00
topjohnwu
494615d9a0 Support ./build.py emulator with an APK argument 2025-02-28 17:17:24 -08:00
vvb2060
afbfb81837 docs: add avd_magisk.sh to faq 2025-02-28 17:17:24 -08:00
vvb2060
3ed4e258a3 avd_magisk: add general usage 2025-02-28 17:17:24 -08:00
vvb2060
dddd41c95b avd_magisk: support rootfs without sbin 2025-02-28 17:17:24 -08:00
topjohnwu
5f2ca81e86 Update AGP 2025-02-28 13:19:16 -08:00
topjohnwu
c9eac0c438 Introduce new sepolicy strategy for legacy devices
The existing sepolicy patching strategy looks like this:

1. 2SI: use LD_PRELOAD to hijack `security_load_policy`
2. Split policy: devices using split policy implies it also needs to
   do early mount, which means fstab is stored in device tree.
   So we do the following:
   - Hijack the fstab node in the device tree in sysfs
   - Wait for init to mount selinuxfs for us
   - Hijack selinuxfs to intercept sepolicy loading
3. Monolithic policy: directly patch `/sepolicy`

Method #1 and #2 both has the magiskinit pre-init daemon handling
the sepolicy patching and loading process, while method #3 gives us
zero control over sepolicy loading process. Downsides:

a. Pre-init daemon bypasses the need to guess which sepolicy init
   will load, because the original init will literally send the stock
   sepolicy file directly to us with this approach.
b. If we want to add more features/functionalities during the sepolicy
   patching process, we will leave out devices using method #3

In order to solve these issues, we completely redesign the sepolicy
patching strategy for non-2SI devices. Instead of limiting usage of
pre-init daemon to early mount devices, we always intercept the
sepolicy loading process regardless of the Android version and device
setup. This will give us a unified implementation for sepolicy patching,
and will make it easier to develop further new features down the line.
2025-02-28 09:39:10 -08:00
topjohnwu
b6b34f7612 Fix overlay.d context preservation 2025-02-27 01:57:25 -08:00
LoveSy
e55c413261 Correctly handle truncated dtb 2025-02-23 20:31:55 -08:00
topjohnwu
0399cde50a Cleanup logcat.log for each invocation 2025-02-18 01:04:19 -08:00
topjohnwu
019eb03823 Hide or remove mut constructors for Utf8CStr 2025-02-17 11:38:11 -08:00
topjohnwu
363410e1c0 Introduce cstr_buf helper functions 2025-02-17 11:32:21 -08:00
topjohnwu
fc2ef21660 Introduce path! macro for FsPath 2025-02-17 01:46:19 -08:00
topjohnwu
18cb659ff3 Run clippy through build.py 2025-02-17 01:31:59 -08:00
topjohnwu
63231d97ce Properly handle db downgrades 2025-02-16 17:01:36 -08:00
topjohnwu
9ac81a8a25 Skip module tests on API < 26 2025-02-16 16:20:09 -08:00
topjohnwu
79af2787ae Workaround potential OOM when signing APKs 2025-02-16 16:20:09 -08:00
topjohnwu
f5f9b285c0 Add module tests 2025-02-16 16:20:09 -08:00
topjohnwu
6c05f2ae85 Test processing Shamiko module zip 2025-02-16 16:20:09 -08:00
topjohnwu
29043e1684 Consolidate setup methods 2025-02-16 16:20:09 -08:00
topjohnwu
b73d4a7022 Fix log_ok() 2025-02-16 12:01:25 -08:00
topjohnwu
ad95e8951b Skip download in lsposed setup test
Download the zip during build time
2025-02-16 01:26:40 -08:00
topjohnwu
bf591fca12 Fix Utf8CString constructor and add more comments 2025-02-16 01:17:48 -08:00
topjohnwu
dcf027884d Update FsPathBuf 2025-02-15 18:27:45 -08:00
topjohnwu
584f3820fe Make all Utf8CStrWrite Utf8CStrBuf 2025-02-15 18:27:45 -08:00
topjohnwu
3c7c46307a Partially cleanup MagiskInit code 2025-02-15 18:27:45 -08:00
vvb2060
4d80361805 core: search for first available dir in PATH 2025-02-15 15:08:26 -08:00
LoveSy
9a74e19117 Add log_ok() for log().ok() 2025-02-14 14:24:13 -08:00
LoveSy
b1e17706a4 Format code 2025-02-14 14:24:13 -08:00
LoveSy
caad129d69 Move MagiskInit::patch_sepolicy to rust 2025-02-14 14:24:13 -08:00
LoveSy
da58571ce5 Remove redundant rust export 2025-02-14 14:24:13 -08:00
LoveSy
2aa7f1c094 Move MagiskInit::check_two_stage to rust 2025-02-14 14:24:13 -08:00
LoveSy
823e31a91b Use linker to link vfprintf as tiny_vfprintf 2025-02-14 14:24:13 -08:00
LoveSy
fb926ae302 Move MagiskInit::redirect_second_stage to rust 2025-02-14 14:24:13 -08:00
LoveSy
e0489eeffd Move MagiskInit::first_stage to rust 2025-02-14 14:24:13 -08:00
LoveSy
dc9d5a4cac Move MagiskInit::second_stage to rust 2025-02-14 14:24:13 -08:00
LoveSy
143743d0b0 Refactor init.cpp to init.rs 2025-02-14 14:24:13 -08:00
LoveSy
563f0d5ad5 Move BootConfig::print to rust 2025-02-14 14:24:13 -08:00
LoveSy
c99f4a591b Move MagiskInit::exec_init to rust 2025-02-14 14:24:13 -08:00
LoveSy
449204e380 Move MagiskInit::prepare_data to rust 2025-02-14 14:24:13 -08:00
LoveSy
a85c4c6528 Move MagiskInit::MagiskInit to rust 2025-02-14 14:24:13 -08:00
LoveSy
d203a6fff6 Move MagiskInit to rust 2025-02-14 14:24:13 -08:00
LoveSy
6c612d66d7 Move BootConfig to rust 2025-02-14 14:24:13 -08:00
topjohnwu
540253a55b Remove unnecessary FFI 2025-02-14 11:24:46 -08:00
topjohnwu
15b7c4ccd1 Fix tmpfs mounts in avd_magisk.sh 2025-02-14 10:17:31 -08:00
topjohnwu
442d5335ea Consolidate get_module_fds implementation
Close #8767
2025-02-12 02:55:27 +08:00
topjohnwu
8a80eea597 Directly deal with Rust &str in sepolicy.cpp 2025-02-12 01:26:06 +08:00
Wang Han
5e35703091 Ensure target path exists before mknod
Co-authored-by: LoveSy <shana@zju.edu.cn>
2025-02-12 01:13:56 +08:00
topjohnwu
b7ca73f431 Remove an additional unique_ptr indirection 2025-02-05 14:18:16 +08:00
Wang Han
a14fc90f07 Fix fetching notification settings from db (#8761)
Co-authored-by: LoveSy <shana@zju.edu.cn>
2025-02-04 17:42:21 +08:00
LoveSy
c913f7ec74 Make sepolicy a shared type between rust and cxx 2025-02-04 00:36:11 +08:00
topjohnwu
7f6c9e8411 Fix zygisk module load 2025-02-03 23:21:51 +08:00
topjohnwu
bb02ea3a20 Fix file descriptor IPC 2025-02-03 18:21:03 +08:00
LoveSy
3981c9665e Replace rust inner functions to try blocks 2025-02-02 22:09:55 +08:00
topjohnwu
88628fdf3c Make sure IPC is arch agnostic 2025-02-02 22:08:41 +08:00
topjohnwu
0469817781 Cleanup code and bindings 2025-02-02 22:08:41 +08:00
topjohnwu
a786801141 Implement su_daemon in Rust 2025-02-02 22:08:41 +08:00
topjohnwu
ab86732c89 Implement simple serialization over IPC 2025-02-02 22:08:41 +08:00
topjohnwu
59622d1688 Use static methods in cxx-rs 2025-02-02 02:46:33 +08:00
LoveSy
58a25a3e2b Fix su with tty 2025-02-01 16:50:53 +08:00
topjohnwu
15dca29a87 Update cxx-rs 2025-02-01 02:02:29 +08:00
Wang Han
46980819c0 Expose safe mode option on 28+ 2025-01-31 12:20:02 +08:00
topjohnwu
4fb6a7268c Fix SDK 27 and 28 tests 2025-01-31 02:52:27 +08:00
topjohnwu
c05e963f37 Address clippy warnings 2025-01-31 02:52:27 +08:00
topjohnwu
7f7f625864 Code reorganization 2025-01-31 02:52:27 +08:00
topjohnwu
b25aa8295a Move bootstage into Rust 2025-01-31 02:52:27 +08:00
topjohnwu
15a605765c Fully implement daemon side of Zygisk in Rust 2025-01-31 02:52:27 +08:00
topjohnwu
b575c95710 Implement fd I/O on Rust side 2025-01-31 02:52:27 +08:00
topjohnwu
a48a9c858a Migrate zygisk handler to Rust 2025-01-31 02:52:27 +08:00
topjohnwu
0d8d6290a3 Move module list into MagiskD 2025-01-31 02:52:27 +08:00
topjohnwu
4dcd733ddd Minor code cleanup 2025-01-31 02:52:27 +08:00
198 changed files with 8255 additions and 6894 deletions

6
.gitattributes vendored
View File

@@ -12,13 +12,11 @@
# Denote all files that are truly binary and should not be modified.
tools/** binary
tools/rustup-wrapper/** -binary
tools/elf-cleaner/** -binary
*.jar binary
*.exe binary
*.apk binary
*.png binary
*.jpg binary
*.ttf binary
# Help GitHub detect languages
native/jni/external/** linguist-vendored
native/jni/systemproperties/** linguist-language=C++

View File

@@ -26,6 +26,15 @@ runs:
- name: Cache sccache
uses: actions/cache@v4
if: ${{ github.event_name != 'pull_request' }}
with:
path: .sccache
key: sccache-${{ runner.os }}-${{ github.sha }}
restore-keys: sccache-${{ runner.os }}-
- name: Restore sccache
uses: actions/cache/restore@v4
if: ${{ github.event_name == 'pull_request' }}
with:
path: .sccache
key: sccache-${{ runner.os }}-${{ github.sha }}
@@ -55,7 +64,7 @@ runs:
- name: Cache Gradle dependencies
uses: actions/cache@v4
if: inputs.is-asset-build == 'true'
if: ${{ inputs.is-asset-build == 'true' && github.event_name != 'pull_request' }}
with:
path: |
.gradle/caches
@@ -66,7 +75,7 @@ runs:
- name: Restore Gradle dependencies
uses: actions/cache/restore@v4
if: inputs.is-asset-build == 'false'
if: ${{ inputs.is-asset-build == 'false' || github.event_name == 'pull_request' }}
with:
path: |
.gradle/caches
@@ -78,19 +87,17 @@ runs:
- name: Cache Gradle build cache
uses: actions/cache@v4
if: inputs.is-asset-build == 'true'
if: ${{ inputs.is-asset-build == 'true' && github.event_name != 'pull_request' }}
with:
path: |
.gradle/caches/build-cache-*
path: .gradle/caches/build-cache-*
key: gradle-build-cache-${{ github.sha }}
restore-keys: gradle-build-cache-
- name: Restore Gradle build cache
uses: actions/cache/restore@v4
if: inputs.is-asset-build == 'false'
if: ${{ inputs.is-asset-build == 'false' || github.event_name == 'pull_request' }}
with:
path: |
.gradle/caches/build-cache-*
path: .gradle/caches/build-cache-*
key: gradle-build-cache-${{ github.sha }}
restore-keys: gradle-build-cache-
enableCrossOsArchive: true

View File

@@ -1,6 +1,13 @@
name: Magisk Build
on:
push:
branches: [master]
paths:
- "app/**"
- "native/**"
- "build.py"
- ".github/workflows/build.yml"
pull_request:
branches: [master]
workflow_dispatch:
@@ -29,7 +36,7 @@ jobs:
run: ./build.py -v all
- name: Stop gradle daemon
run: ./gradlew --stop
run: ./app/gradlew --stop
- name: Upload build artifact
uses: actions/upload-artifact@v4
@@ -65,21 +72,22 @@ jobs:
run: python build.py -v -c .github/ci.prop all
- name: Stop gradle daemon
run: ./gradlew --stop
run: ./app/gradlew --stop
avd-test:
name: Test API ${{ matrix.version }} (x86_64)
runs-on: ubuntu-24.04
needs: build
if: ${{ github.event_name != 'push' }}
strategy:
fail-fast: false
matrix:
version: [23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35]
type: [""]
include:
- version: "Baklava"
- version: 36
type: "google_apis"
- version: "Baklava"
- version: 36
type: "google_apis_ps16k"
steps:
@@ -117,6 +125,7 @@ jobs:
name: Test API ${{ matrix.version }} (x86)
runs-on: ubuntu-24.04
needs: build
if: ${{ github.event_name != 'push' }}
strategy:
fail-fast: false
matrix:
@@ -158,14 +167,15 @@ jobs:
name: Test ${{ matrix.device }}
runs-on: ubuntu-24.04
needs: build
if: ${{ github.event_name != 'push' }}
env:
CF_HOME: /home/runner/aosp_cf_phone
strategy:
fail-fast: false
matrix:
include:
- branch: "aosp-main"
device: "aosp_cf_x86_64_phone"
- branch: "aosp-android-latest-release"
device: "aosp_cf_x86_64_only_phone"
steps:
- name: Check out
@@ -184,7 +194,7 @@ jobs:
- name: Run Cuttlefish test
timeout-minutes: 10
run: su $USER -c 'scripts/cuttlefish.sh test'
run: sudo -E -u $USER scripts/cuttlefish.sh test
- name: Upload logs on error
if: ${{ failure() }}

10
.gitignore vendored
View File

@@ -2,19 +2,13 @@ out
*.zip
*.jks
*.apk
*.log
/config.prop
/notes.md
/update.sh
/app/dict.txt
# Built binaries
native/out
# Android Studio / Gradle
# Android Studio
*.iml
.gradle
.idea
.kotlin
/local.properties
/build
/captures

9
.gitmodules vendored
View File

@@ -4,18 +4,12 @@
[submodule "lz4"]
path = native/src/external/lz4
url = https://github.com/lz4/lz4.git
[submodule "bzip2"]
path = native/src/external/bzip2
url = https://github.com/nemequ/bzip2.git
[submodule "xz"]
path = native/src/external/xz
url = https://github.com/xz-mirror/xz.git
[submodule "libcxx"]
path = native/src/external/libcxx
url = https://github.com/topjohnwu/libcxx.git
[submodule "zlib"]
path = native/src/external/zlib
url = https://android.googlesource.com/platform/external/zlib
[submodule "zopfli"]
path = native/src/external/zopfli
url = https://github.com/google/zopfli.git
@@ -31,6 +25,3 @@
[submodule "crt0"]
path = native/src/external/crt0
url = https://github.com/topjohnwu/crt0.git
[submodule "termux-elf-cleaner"]
path = tools/termux-elf-cleaner
url = https://github.com/termux/termux-elf-cleaner.git

View File

@@ -21,8 +21,8 @@ Some highlight features:
Click the icon below to download Magisk apk.
[![](https://img.shields.io/badge/Magisk-v28.1-blue)](https://github.com/topjohnwu/Magisk/releases/tag/v28.1)
[![](https://img.shields.io/badge/Magisk%20Beta-v28.1-blue)](https://github.com/topjohnwu/Magisk/releases/tag/v28.1)
[![](https://img.shields.io/badge/Magisk-Canary-red)](https://github.com/topjohnwu/Magisk/releases/tag/canary-28102)
[![](https://img.shields.io/badge/Magisk%20Beta-v29.0-blue)](https://github.com/topjohnwu/Magisk/releases/tag/v29.0)
[![](https://img.shields.io/badge/Magisk-Canary-red)](https://github.com/topjohnwu/Magisk/releases/tag/canary-28104)
## Useful Links

7
app/.gitignore vendored Normal file
View File

@@ -0,0 +1,7 @@
/dict.txt
# Gradle
.gradle
.kotlin
/local.properties
/build

View File

@@ -35,7 +35,7 @@ android {
}
dependencies {
implementation(project(":app:core"))
implementation(project(":core"))
coreLibraryDesugaring(libs.jdk.libs)
implementation(libs.indeterminate.checkbox)

View File

@@ -1,10 +1,13 @@
package com.topjohnwu.magisk.arch
import android.content.ContentResolver
import android.view.KeyEvent
import androidx.databinding.ViewDataBinding
import androidx.navigation.NavController
import androidx.navigation.NavDirections
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.navOptions
import com.topjohnwu.magisk.utils.AccessibilityUtils
abstract class NavigationActivity<Binding : ViewDataBinding> : UIActivity<Binding>() {
@@ -31,7 +34,17 @@ abstract class NavigationActivity<Binding : ViewDataBinding> : UIActivity<Bindin
}
}
companion object {
fun navigate(directions: NavDirections, navigation: NavController, cr: ContentResolver) {
if (AccessibilityUtils.isAnimationEnabled(cr)) {
navigation.navigate(directions)
} else {
navigation.navigate(directions, navOptions {})
}
}
}
fun NavDirections.navigate() {
navigation.navigate(this)
navigate(this, navigation, contentResolver)
}
}

View File

@@ -17,6 +17,8 @@ import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.download.DownloadEngine
import com.topjohnwu.magisk.databinding.FragmentHomeMd2Binding
import com.topjohnwu.magisk.core.R as CoreR
import androidx.navigation.findNavController
import com.topjohnwu.magisk.arch.NavigationActivity
class HomeFragment : BaseFragment<FragmentHomeMd2Binding>(), MenuProvider {
@@ -68,7 +70,13 @@ class HomeFragment : BaseFragment<FragmentHomeMd2Binding>(), MenuProvider {
override fun onMenuItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.action_settings ->
HomeFragmentDirections.actionHomeFragmentToSettingsFragment().navigate()
activity?.let {
NavigationActivity.navigate(
HomeFragmentDirections.actionHomeFragmentToSettingsFragment(),
it.findNavController(R.id.main_nav_host),
it.contentResolver,
)
}
R.id.action_reboot -> activity?.let { RebootMenu.inflate(it).show() }
else -> return super.onOptionsItemSelected(item)
}

View File

@@ -41,7 +41,7 @@ object RebootMenu {
activity.getSystemService<PowerManager>()?.isRebootingUserspaceSupported == true) {
menu.menu.findItem(R.id.action_reboot_userspace).isVisible = true
}
if (Const.Version.isCanary()) {
if (Const.Version.atLeast_28_0()) {
menu.menu.findItem(R.id.action_reboot_safe_mode).isChecked = Config.bootloop >= 2
} else {
menu.menu.findItem(R.id.action_reboot_safe_mode).isVisible = false

View File

@@ -5,6 +5,7 @@ import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.widget.HorizontalScrollView
import androidx.core.view.MenuProvider
import androidx.core.view.isVisible
import com.topjohnwu.magisk.R
@@ -12,6 +13,7 @@ import com.topjohnwu.magisk.arch.BaseFragment
import com.topjohnwu.magisk.arch.viewModel
import com.topjohnwu.magisk.databinding.FragmentLogMd2Binding
import com.topjohnwu.magisk.ui.MainActivity
import com.topjohnwu.magisk.utils.AccessibilityUtils
import com.topjohnwu.magisk.utils.MotionRevealHelper
import rikka.recyclerview.addEdgeSpacing
import rikka.recyclerview.addItemSpacing
@@ -56,6 +58,11 @@ class LogFragment : BaseFragment<FragmentLogMd2Binding>(), MenuProvider {
addItemSpacing(R.dimen.l1, R.dimen.l_50, R.dimen.l1)
fixEdgeEffect()
}
if (!AccessibilityUtils.isAnimationEnabled(requireContext().contentResolver)) {
val scrollView = view.findViewById<HorizontalScrollView>(R.id.log_scroll_magisk)
scrollView.setOverScrollMode(View.OVER_SCROLL_NEVER)
}
}

View File

@@ -0,0 +1,14 @@
package com.topjohnwu.magisk.utils
import android.content.ContentResolver
import android.provider.Settings
class AccessibilityUtils {
companion object {
fun isAnimationEnabled(cr: ContentResolver): Boolean {
return !(Settings.Global.getFloat(cr, Settings.Global.ANIMATOR_DURATION_SCALE, 1.0f) == 0.0f
&& Settings.Global.getFloat(cr, Settings.Global.TRANSITION_ANIMATION_SCALE, 1.0f) == 0.0f
&& Settings.Global.getFloat(cr, Settings.Global.WINDOW_ANIMATION_SCALE, 1.0f) == 0.0f)
}
}
}

View File

@@ -16,6 +16,7 @@
android:layout_height="match_parent">
<HorizontalScrollView
android:id="@+id/log_scroll_magisk"
gone="@{viewModel.loading}"
android:layout_width="match_parent"
android:layout_height="match_parent"

View File

@@ -1,5 +1,11 @@
tasks.register("clean") {
plugins {
id("MagiskPlugin")
}
tasks.register("clean", Delete::class) {
delete(rootProject.layout.buildDirectory)
subprojects.forEach {
dependsOn(":app:${it.name}:clean")
dependsOn(":${it.name}:clean")
}
}

View File

@@ -14,7 +14,7 @@ private val defaultAbis = setOf("armeabi-v7a", "x86", "arm64-v8a", "x86_64")
object Config {
operator fun get(key: String): String? {
val v = props[key] as? String ?: return null
return if (v.isBlank()) null else v
return v.ifBlank { null }
}
fun contains(key: String) = get(key) != null
@@ -28,19 +28,21 @@ object Config {
}
}
val Project.baseDir: File get() = rootProject.file("..")
class MagiskPlugin : Plugin<Project> {
override fun apply(project: Project) = project.applyPlugin()
private fun Project.applyPlugin() {
initRandom(rootProject.file("app/dict.txt"))
initRandom(rootProject.file("dict.txt"))
props.clear()
rootProject.file("gradle.properties").inputStream().use { props.load(it) }
val configPath: String? by this
val config = configPath?.let { File(it) } ?: rootProject.file("config.prop")
val config = configPath?.let { File(it) } ?: File(baseDir, "config.prop")
if (config.exists())
config.inputStream().use { props.load(it) }
val repo = FileRepository(rootProject.file(".git"))
val repo = FileRepository(File(baseDir, ".git"))
val refId = repo.refDatabase.exactRef("HEAD").objectId
commitHash = repo.newObjectReader().abbreviate(refId, 8).name()
}

View File

@@ -71,10 +71,10 @@ private val Project.androidComponents
fun Project.setupCommon() {
androidBase {
compileSdkVersion(35)
buildToolsVersion = "35.0.1"
compileSdkVersion(36)
buildToolsVersion = "36.0.0"
ndkPath = "$sdkDirectory/ndk/magisk"
ndkVersion = "28.0.12674087"
ndkVersion = "29.0.13113456"
defaultConfig {
minSdk = 23
@@ -89,6 +89,7 @@ fun Project.setupCommon() {
resources {
excludes += arrayOf(
"/META-INF/*",
"/META-INF/androidx/**",
"/META-INF/versions/**",
"/org/bouncycastle/**",
"/org/apache/commons/**",
@@ -115,6 +116,27 @@ fun Project.setupCommon() {
}
}
private fun Project.downloadFile(url: String, checksum: String): File {
val file = layout.buildDirectory.file(checksum).get().asFile
if (file.exists()) {
val md = MessageDigest.getInstance("SHA-256")
file.inputStream().use { md.update(it.readAllBytes()) }
val hash = HexFormat.of().formatHex(md.digest())
if (hash != checksum) {
file.delete()
}
}
if (!file.exists()) {
file.parentFile.mkdirs()
URI(url).toURL().openStream().use { dl ->
file.outputStream().use {
dl.copyTo(it)
}
}
}
return file
}
const val BUSYBOX_DOWNLOAD_URL =
"https://github.com/topjohnwu/magisk-files/releases/download/files/busybox-1.36.1.1.zip"
const val BUSYBOX_ZIP_CHECKSUM =
@@ -129,7 +151,7 @@ fun Project.setupCoreLib() {
into("src/main/jniLibs")
for (abi in abiList) {
into(abi) {
from(rootProject.file("native/out/$abi")) {
from(File(baseDir, "native/out/$abi")) {
include("magiskboot", "magiskinit", "magiskpolicy", "magisk", "libinit-ld.so")
rename { if (it.endsWith(".so")) it else "lib$it.so" }
}
@@ -144,34 +166,17 @@ fun Project.setupCoreLib() {
val downloadBusybox by tasks.registering(Copy::class) {
dependsOn(syncLibs)
val bb = layout.buildDirectory.file(BUSYBOX_ZIP_CHECKSUM).get().asFile
if (bb.exists()) {
val md = MessageDigest.getInstance("SHA-256")
bb.inputStream().use { md.update(it.readAllBytes()) }
val hash = HexFormat.of().formatHex(md.digest())
if (hash != BUSYBOX_ZIP_CHECKSUM) {
bb.delete()
}
}
if (!bb.exists()) {
bb.parentFile.mkdirs()
URI(BUSYBOX_DOWNLOAD_URL).toURL().openStream().use { dl ->
bb.outputStream().use {
dl.copyTo(it)
}
}
}
from(zipTree(bb))
from(zipTree(downloadFile(BUSYBOX_DOWNLOAD_URL, BUSYBOX_ZIP_CHECKSUM)))
include(abiList.map { "$it/libbusybox.so" })
into("src/main/jniLibs")
}
val syncResources by tasks.registering(Sync::class) {
into("src/main/resources/META-INF/com/google/android")
from(rootProject.file("scripts/update_binary.sh")) {
from(File(baseDir, "scripts/update_binary.sh")) {
rename { "update-binary" }
}
from(rootProject.file("scripts/flash_script.sh")) {
from(File(baseDir, "scripts/flash_script.sh")) {
rename { "updater-script" }
}
}
@@ -182,7 +187,7 @@ fun Project.setupCoreLib() {
tasks.getByPath("merge${variantCapped}JniLibFolders").dependsOn(downloadBusybox)
processJavaResourcesProvider.configure { dependsOn(syncResources) }
val stubTask = tasks.getByPath(":app:stub:comment$variantCapped")
val stubTask = tasks.getByPath(":stub:comment$variantCapped")
val stubApk = stubTask.outputs.files.asFileTree.filter {
it.name.endsWith(".apk")
}
@@ -192,14 +197,14 @@ fun Project.setupCoreLib() {
inputs.property("version", Config.version)
inputs.property("versionCode", Config.versionCode)
into("src/${this@all.name}/assets")
from(rootProject.file("scripts")) {
from(File(baseDir, "scripts")) {
include("util_functions.sh", "boot_patch.sh", "addon.d.sh",
"app_functions.sh", "uninstaller.sh", "module_installer.sh")
}
from(rootProject.file("tools/bootctl"))
from(File(baseDir, "tools/bootctl"))
into("chromeos") {
from(rootProject.file("tools/futility"))
from(rootProject.file("tools/keys")) {
from(File(baseDir, "tools/futility"))
from(File(baseDir, "tools/keys")) {
include("kernel_data_key.vbprivk", "kernel.keyblock")
}
}
@@ -289,7 +294,7 @@ fun Project.setupAppCommon() {
signingConfigs {
create("config") {
Config["keyStore"]?.also {
storeFile = rootProject.file(it)
storeFile = File(baseDir, it)
storePassword = Config["keyStorePass"]
keyAlias = Config["keyAlias"]
keyPassword = Config["keyPass"]
@@ -298,7 +303,7 @@ fun Project.setupAppCommon() {
}
defaultConfig {
targetSdk = 35
targetSdk = 36
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt")
)
@@ -462,3 +467,31 @@ fun Project.setupStubApk() {
delete.addAll(listOf("src/debug/AndroidManifest.xml", "src/release/AndroidManifest.xml"))
}
}
const val LSPOSED_DOWNLOAD_URL =
"https://github.com/LSPosed/LSPosed/releases/download/v1.9.2/LSPosed-v1.9.2-7024-zygisk-release.zip"
const val LSPOSED_CHECKSUM =
"0ebc6bcb465d1c4b44b7220ab5f0252e6b4eb7fe43da74650476d2798bb29622"
const val SHAMIKO_DOWNLOAD_URL =
"https://github.com/LSPosed/LSPosed.github.io/releases/download/shamiko-383/Shamiko-v1.2.1-383-release.zip"
const val SHAMIKO_CHECKSUM =
"93754a038c2d8f0e985bad45c7303b96f70a93d8335060e50146f028d3a9b13f"
fun Project.setupTestApk() {
setupAppCommon()
androidApp.applicationVariants.all {
val variantCapped = name.replaceFirstChar { it.uppercase() }
val dlTask by tasks.register("download${variantCapped}Lsposed", Sync::class) {
from(downloadFile(LSPOSED_DOWNLOAD_URL, LSPOSED_CHECKSUM)) {
rename { "lsposed.zip" }
}
from(downloadFile(SHAMIKO_DOWNLOAD_URL, SHAMIKO_CHECKSUM)) {
rename { "shamiko.zip" }
}
into("src/${this@all.name}/assets")
}
mergeAssetsProvider.configure { dependsOn(dlTask) }
}
}

View File

@@ -29,7 +29,7 @@ android {
}
dependencies {
api(project(":app:shared"))
api(project(":shared"))
api(libs.timber)
api(libs.markwon.core)

View File

@@ -14,7 +14,6 @@
<application
android:name=".App"
android:icon="@drawable/ic_launcher"
android:multiArch="true"
tools:ignore="UnusedAttribute,GoogleAppIndexingWarning"
tools:remove="android:appComponentFactory">

View File

@@ -14,7 +14,7 @@ object Const {
else Build.SUPPORTED_32_BIT_ABIS.firstOrNull()
// Paths
const val MAGISK_PATH = "/data/adb/modules"
const val MODULE_PATH = "/data/adb/modules"
const val TMPDIR = "/dev/tmp"
const val MAGISK_LOG = "/cache/magisk.log"
@@ -28,6 +28,7 @@ object Const {
fun atLeast_24_0() = Info.env.versionCode >= 24000 || isCanary()
fun atLeast_25_0() = Info.env.versionCode >= 25000 || isCanary()
fun atLeast_28_0() = Info.env.versionCode >= 28000 || isCanary()
fun isCanary() = isCanary(Info.env.versionCode)
fun isCanary(ver: Int) = ver > 0 && ver % 100 != 0

View File

@@ -47,7 +47,6 @@ object Info {
private set
private var crypto = ""
var hasGMS = true
val isEmulator =
Build.DEVICE.contains("vsoc")
|| getProperty("ro.kernel.qemu", "0") == "1"

View File

@@ -5,7 +5,6 @@ import com.squareup.moshi.Moshi
import com.topjohnwu.magisk.ProviderInstaller
import com.topjohnwu.magisk.core.BuildConfig
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.utils.LocaleSetting
import okhttp3.Cache
import okhttp3.ConnectionSpec
@@ -72,9 +71,7 @@ fun createOkHttpClient(context: Context): OkHttpClient {
chain.proceed(request.build())
}
if (!ProviderInstaller.install(context)) {
Info.hasGMS = false
}
ProviderInstaller.install(context)
return builder.build()
}

View File

@@ -5,6 +5,7 @@ import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.di.ServiceLocator
import com.topjohnwu.magisk.core.utils.RootUtils
import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.nio.ExtendedFile
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import timber.log.Timber
@@ -12,7 +13,7 @@ import java.io.IOException
import java.util.Locale
data class LocalModule(
private val path: String,
private val base: ExtendedFile,
) : Module() {
private val svc get() = ServiceLocator.networkService
@@ -24,20 +25,18 @@ data class LocalModule(
var description: String = ""
var updateInfo: OnlineModule? = null
var outdated = false
private var updateUrl: String = ""
private val removeFile = RootUtils.fs.getFile(path, "remove")
private val disableFile = RootUtils.fs.getFile(path, "disable")
private val updateFile = RootUtils.fs.getFile(path, "update")
private val riruFolder = RootUtils.fs.getFile(path, "riru")
private val zygiskFolder = RootUtils.fs.getFile(path, "zygisk")
private val unloaded = RootUtils.fs.getFile(zygiskFolder, "unloaded")
val updated: Boolean get() = updateFile.exists()
val isRiru: Boolean get() = (id == "riru-core") || riruFolder.exists()
val isZygisk: Boolean get() = zygiskFolder.exists()
val zygiskUnloaded: Boolean get() = unloaded.exists()
val hasAction: Boolean;
private val removeFile = base.getChildFile("remove")
private val disableFile = base.getChildFile("disable")
private val updateFile = base.getChildFile("update")
val zygiskFolder = base.getChildFile("zygisk")
val updated get() = updateFile.exists()
val isRiru = (id == "riru-core") || base.getChildFile("riru").exists()
val isZygisk = zygiskFolder.exists()
val zygiskUnloaded = zygiskFolder.getChildFile("unloaded").exists()
val hasAction = base.getChildFile("action.sh").exists()
var enable: Boolean
get() = !disableFile.exists()
@@ -90,19 +89,16 @@ data class LocalModule(
init {
runCatching {
parseProps(Shell.cmd("dos2unix < $path/module.prop").exec().out)
parseProps(Shell.cmd("dos2unix < $base/module.prop").exec().out)
}
if (id.isEmpty()) {
val sep = path.lastIndexOf('/')
id = path.substring(sep + 1)
id = base.name
}
if (name.isEmpty()) {
name = id
}
hasAction = RootUtils.fs.getFile(path, "action.sh").exists()
}
suspend fun fetch(): Boolean {
@@ -125,14 +121,14 @@ data class LocalModule(
companion object {
fun loaded() = RootUtils.fs.getFile(Const.MAGISK_PATH).exists()
fun loaded() = RootUtils.fs.getFile(Const.MODULE_PATH).exists()
suspend fun installed() = withContext(Dispatchers.IO) {
RootUtils.fs.getFile(Const.MAGISK_PATH)
RootUtils.fs.getFile(Const.MODULE_PATH)
.listFiles()
.orEmpty()
.filter { !it.isFile && !it.isHidden }
.map { LocalModule("${Const.MAGISK_PATH}/${it.name}") }
.map { LocalModule(it) }
.sortedBy { it.name.lowercase(Locale.ROOT) }
}
}

View File

@@ -4,6 +4,7 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
public class ByteArrayStream extends ByteArrayOutputStream {
@@ -27,4 +28,8 @@ public class ByteArrayStream extends ByteArrayOutputStream {
public ByteArrayInputStream getInputStream() {
return new ByteArrayInputStream(buf, 0, count);
}
public ByteBuffer toByteBuffer() {
return ByteBuffer.wrap(buf, 0, count);
}
}

View File

@@ -510,7 +510,7 @@ public class SignApk {
privateKey[0] = key;
// Generate, in memory, an APK signed using standard JAR Signature Scheme.
ByteArrayOutputStream v1SignedApkBuf = new ByteArrayOutputStream();
ByteArrayStream v1SignedApkBuf = new ByteArrayStream();
JarOutputStream outputJar = new JarOutputStream(v1SignedApkBuf);
// Use maximum compression for compressed entries because the APK lives forever on
// the system partition.
@@ -519,8 +519,7 @@ public class SignApk {
copyFiles(manifest, inputJar, outputJar, timestamp, alignment);
signFile(manifest, publicKey, privateKey, timestamp, outputJar);
outputJar.close();
ByteBuffer v1SignedApk = ByteBuffer.wrap(v1SignedApkBuf.toByteArray());
v1SignedApkBuf.reset();
ByteBuffer v1SignedApk = v1SignedApkBuf.toByteBuffer();
ByteBuffer[] outputChunks;
List<ApkSignerV2.SignerConfig> signerConfigs = createV2SignerConfigs(privateKey, publicKey,

View File

@@ -17,7 +17,6 @@ import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.di.ServiceLocator
import com.topjohnwu.magisk.core.isRunningAsStub
import com.topjohnwu.magisk.core.ktx.copyAll
import com.topjohnwu.magisk.core.ktx.copyAndClose
import com.topjohnwu.magisk.core.ktx.writeTo
import com.topjohnwu.magisk.core.utils.DummyList
import com.topjohnwu.magisk.core.utils.MediaStoreUtils
@@ -47,7 +46,6 @@ import java.io.OutputStream
import java.io.PushbackInputStream
import java.nio.ByteBuffer
import java.security.SecureRandom
import java.util.Arrays
import java.util.Locale
import java.util.concurrent.atomic.AtomicBoolean
@@ -134,7 +132,6 @@ abstract class MagiskInstallImpl protected constructor(
if (entry != null) {
val magisk32 = File(installDir, "magisk32")
zf.getInputStream(entry).writeTo(magisk32)
magisk32.setExecutable(true)
}
}
}
@@ -149,11 +146,15 @@ abstract class MagiskInstallImpl protected constructor(
Os.symlink(lib.path, "$installDir/$name")
}
// Also symlink magisk32 on 64-bit devices that supports 32-bit
val lib32 = info.javaClass.getDeclaredField("secondaryNativeLibraryDir")
.get(info) as String?
if (lib32 != null) {
Os.symlink("$lib32/libmagisk.so", "$installDir/magisk32");
// Also extract magisk32 on 64-bit devices that supports 32-bit
val abi32 = Const.CPU_ABI_32
if (Process.is64Bit() && abi32 != null) {
val name = "lib/$abi32/libmagisk.so"
val entry = javaClass.classLoader!!.getResourceAsStream(name)
if (entry != null) {
val magisk32 = File(installDir, "magisk32")
entry.writeTo(magisk32)
}
}
}
@@ -191,7 +192,8 @@ abstract class MagiskInstallImpl protected constructor(
return true
}
private suspend fun InputStream.copyAndCloseOut(out: OutputStream) = out.use { copyAll(it) }
private suspend fun InputStream.copyAndCloseOut(out: OutputStream) =
out.use { copyAll(it, 1024 * 1024) }
private class NoAvailableStream(s: InputStream) : FilterInputStream(s) {
// Make sure available is never called on the actual stream and always return 0
@@ -225,8 +227,8 @@ abstract class MagiskInstallImpl protected constructor(
console.add("- Processing tar file")
var entry: TarArchiveEntry? = tarIn.nextEntry
fun TarArchiveEntry.decompressedStream(): InputStream {
val stream = if (name.endsWith(".lz4"))
fun decompressedStream(): InputStream {
val stream = if (tarIn.currentEntry.name.endsWith(".lz4"))
FramedLZ4CompressorInputStream(tarIn, true) else tarIn
return NoAvailableStream(stream)
}
@@ -252,9 +254,9 @@ abstract class MagiskInstallImpl protected constructor(
if (bootItem != null) {
console.add("-- Extracting: ${bootItem.name}")
entry.decompressedStream().copyAndCloseOut(bootItem.file.newOutputStream())
decompressedStream().copyAndCloseOut(bootItem.file.newOutputStream())
} else if (entry.name.contains("vbmeta.img")) {
val rawData = entry.decompressedStream().readBytes()
val rawData = decompressedStream().readBytes()
// Valid vbmeta.img should be at least 256 bytes
if (rawData.size < 256)
continue
@@ -287,7 +289,7 @@ abstract class MagiskInstallImpl protected constructor(
} else {
console.add("-- Copying : ${entry.name}")
tarOut.putArchiveEntry(entry)
tarIn.copyAll(tarOut, bufferSize = 1024 * 1024)
tarIn.copyAll(tarOut)
tarOut.closeArchiveEntry()
}
entry = tarIn.nextEntry ?: break
@@ -429,7 +431,7 @@ abstract class MagiskInstallImpl protected constructor(
// Process input file
try {
PushbackInputStream(uri.inputStream(), 512).use { src ->
PushbackInputStream(uri.inputStream().buffered(1024 * 1024), 512).use { src ->
val head = ByteArray(512)
if (src.read(head) != head.size) {
console.add("! Invalid input file")
@@ -438,12 +440,13 @@ abstract class MagiskInstallImpl protected constructor(
src.unread(head)
val magic = head.copyOf(4)
val tarMagic = Arrays.copyOfRange(head, 257, 262)
val tarMagic = head.copyOfRange(257, 262)
srcBoot = if (tarMagic.contentEquals("ustar".toByteArray())) {
// tar file
outFile = MediaStoreUtils.getFile("$destName.tar")
outStream = TarArchiveOutputStream(outFile.uri.outputStream()).also {
val os = outFile.uri.outputStream().buffered(1024 * 1024)
outStream = TarArchiveOutputStream(os).also {
it.setBigNumberMode(TarArchiveOutputStream.BIGNUMBER_STAR)
it.setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU)
}
@@ -500,7 +503,7 @@ abstract class MagiskInstallImpl protected constructor(
bootItem.file = newBoot
bootItem.copyTo(outStream as TarArchiveOutputStream)
} else {
newBoot.newInputStream().copyAndClose(outStream)
newBoot.newInputStream().use { it.copyAll(outStream, 1024 * 1024) }
}
newBoot.delete()
@@ -514,6 +517,8 @@ abstract class MagiskInstallImpl protected constructor(
outFile.delete()
Timber.e(e)
return false
} finally {
outStream.close()
}
// Fix up binaries

View File

@@ -1,50 +0,0 @@
package com.topjohnwu.magisk.core.utils
import com.topjohnwu.magisk.core.ktx.copyAll
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry
import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream
import org.apache.commons.compress.archivers.zip.ZipFile
import java.io.File
import java.io.IOException
import java.io.InputStream
@Throws(IOException::class)
suspend fun File.unzip(folder: File, path: String = "", junkPath: Boolean = false) {
ZipFile.Builder().setFile(this).get().use { zip ->
for (entry in zip.entries) {
if (!entry.name.startsWith(path) || entry.isDirectory) {
// Ignore directories, only create files
continue
}
val name = if (junkPath)
entry.name.substring(entry.name.lastIndexOf('/') + 1)
else
entry.name
val dest = File(folder, name)
dest.parentFile?.mkdirs()
dest.outputStream().use { out -> zip.getInputStream(entry).copyAll(out) }
}
}
}
@Throws(IOException::class)
suspend fun InputStream.unzip(folder: File, path: String = "", junkPath: Boolean = false) {
ZipArchiveInputStream(this).use { zin ->
var entry: ZipArchiveEntry
while (true) {
entry = zin.nextEntry ?: break
if (!entry.name.startsWith(path) || entry.isDirectory) {
// Ignore directories, only create files
continue
}
val name = if (junkPath)
entry.name.substring(entry.name.lastIndexOf('/') + 1)
else
entry.name
val dest = File(folder, name)
dest.parentFile?.mkdirs()
dest.outputStream().use { out -> zin.copyAll(out) }
}
}
}

View File

@@ -5,9 +5,23 @@ import androidx.annotation.Keep
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.uiautomator.By
import androidx.test.uiautomator.Until
import com.topjohnwu.magisk.core.model.module.LocalModule
import com.topjohnwu.magisk.core.utils.RootUtils
import com.topjohnwu.magisk.test.Environment.Companion.EMPTY_ZYGISK
import com.topjohnwu.magisk.test.Environment.Companion.INVALID_ZYGISK
import com.topjohnwu.magisk.test.Environment.Companion.MOUNT_TEST
import com.topjohnwu.magisk.test.Environment.Companion.REMOVE_TEST
import com.topjohnwu.magisk.test.Environment.Companion.SEPOLICY_RULE
import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.runBlocking
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Assume.assumeTrue
import org.junit.BeforeClass
import org.junit.Test
import org.junit.runner.RunWith
import java.util.concurrent.TimeUnit
@@ -21,6 +35,17 @@ class AdditionalTest : BaseTest {
private const val SHELL_PKG = "com.android.shell"
private const val LSPOSED_CATEGORY = "org.lsposed.manager.LAUNCH_MANAGER"
private const val LSPOSED_PKG = "org.lsposed.manager"
private lateinit var modules: List<LocalModule>
@BeforeClass
@JvmStatic
fun before() {
BaseTest.prerequisite()
runBlocking {
modules = LocalModule.installed()
}
}
}
@After
@@ -29,9 +54,25 @@ class AdditionalTest : BaseTest {
}
@Test
fun testLaunchLsposedManager() {
fun testModuleCount() {
var expected = 2
if (Environment.mount()) expected++
if (Environment.preinit()) expected++
if (Environment.lsposed()) expected++
if (Environment.shamiko()) expected++
assertEquals("Module count incorrect", expected, modules.size)
}
@Test
fun testLsposed() {
assumeTrue(Environment.lsposed())
val module = modules.find { it.id == "zygisk_lsposed" }
assertNotNull("zygisk_lsposed is not installed", module)
module!!
assertFalse("zygisk_lsposed is not enabled", module.zygiskUnloaded)
// Launch lsposed manager to ensure the module is active
uiAutomation.executeShellCommand(
"am start -c $LSPOSED_CATEGORY $SHELL_PKG/.BugreportWarningActivity"
).let { pfd -> AutoCloseInputStream(pfd).use { it.readBytes() } }
@@ -42,4 +83,56 @@ class AdditionalTest : BaseTest {
device.wait(Until.hasObject(By.res(pattern)), TimeUnit.SECONDS.toMillis(10))
)
}
@Test
fun testModuleMount() {
assumeTrue(Environment.mount())
assertNotNull("$MOUNT_TEST is not installed", modules.find { it.id == MOUNT_TEST })
assertTrue(
"/system/etc/newfile should exist",
RootUtils.fs.getFile("/system/etc/newfile").exists()
)
assertFalse(
"/system/bin/screenrecord should not exist",
RootUtils.fs.getFile("/system/bin/screenrecord").exists()
)
val egg = RootUtils.fs.getFile("/system/app/EasterEgg").list() ?: arrayOf()
assertTrue(
"/system/app/EasterEgg should be empty",
egg.isEmpty()
)
}
@Test
fun testSepolicyRule() {
assumeTrue(Environment.preinit())
assertNotNull("$SEPOLICY_RULE is not installed", modules.find { it.id == SEPOLICY_RULE })
assertTrue(
"Module sepolicy.rule is not applied",
Shell.cmd("magiskpolicy --print-rules | grep -q magisk_test").exec().isSuccess
)
}
@Test
fun testEmptyZygiskModule() {
val module = modules.find { it.id == EMPTY_ZYGISK }
assertNotNull("$EMPTY_ZYGISK is not installed", module)
module!!
assertTrue("$EMPTY_ZYGISK should be zygisk unloaded", module.zygiskUnloaded)
}
@Test
fun testInvalidZygiskModule() {
val module = modules.find { it.id == INVALID_ZYGISK }
assertNotNull("$INVALID_ZYGISK is not installed", module)
module!!
assertTrue("$INVALID_ZYGISK should be zygisk unloaded", module.zygiskUnloaded)
}
@Test
fun testRemoveModule() {
assertNull("$REMOVE_TEST should be removed", modules.find { it.id == REMOVE_TEST })
}
}

View File

@@ -12,7 +12,8 @@ import org.junit.Assert.assertTrue
interface BaseTest {
val instrumentation: Instrumentation
get() = InstrumentationRegistry.getInstrumentation()
val context: Context get() = instrumentation.targetContext
val appContext: Context get() = instrumentation.targetContext
val testContext: Context get() = instrumentation.context
val uiAutomation: UiAutomation get() = instrumentation.uiAutomation
val device: UiDevice get() = UiDevice.getInstance(instrumentation)

View File

@@ -6,21 +6,29 @@ import androidx.annotation.Keep
import androidx.core.net.toUri
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.topjohnwu.magisk.core.BuildConfig.APP_PACKAGE_NAME
import com.topjohnwu.magisk.core.di.ServiceLocator
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.download.DownloadNotifier
import com.topjohnwu.magisk.core.download.DownloadProcessor
import com.topjohnwu.magisk.core.ktx.cachedFile
import com.topjohnwu.magisk.core.model.module.LocalModule
import com.topjohnwu.magisk.core.tasks.AppMigration
import com.topjohnwu.magisk.core.tasks.FlashZip
import com.topjohnwu.magisk.core.tasks.MagiskInstaller
import com.topjohnwu.magisk.core.utils.RootUtils
import com.topjohnwu.superuser.CallbackList
import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.nio.ExtendedFile
import kotlinx.coroutines.runBlocking
import org.apache.commons.compress.archivers.zip.ZipFile
import org.junit.Assert.assertArrayEquals
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Assume.assumeTrue
import org.junit.BeforeClass
import org.junit.Test
import org.junit.runner.RunWith
import timber.log.Timber
import java.io.File
import java.io.PrintStream
@Keep
@RunWith(AndroidJUnit4::class)
@@ -31,12 +39,31 @@ class Environment : BaseTest {
@JvmStatic
fun before() = BaseTest.prerequisite()
fun lsposed(): Boolean {
return Build.VERSION.SDK_INT >= 27 && Build.VERSION.SDK_INT <= 34
// The kernel running on emulators < API 26 does not play well with
// magic mount. Skip mount_test on those legacy platforms.
fun mount(): Boolean {
return Build.VERSION.SDK_INT >= 26
}
private const val LSPOSED_URL =
"https://github.com/LSPosed/LSPosed/releases/download/v1.9.2/LSPosed-v1.9.2-7024-zygisk-release.zip"
// It is possible that there are no suitable preinit partition to use
fun preinit(): Boolean {
return Shell.cmd("magisk --preinit-device").exec().isSuccess
}
fun lsposed(): Boolean {
return Build.VERSION.SDK_INT in 27..34
}
fun shamiko(): Boolean {
return Build.VERSION.SDK_INT >= 27
}
private const val MODULE_ERROR = "Module zip processing incorrect"
const val MOUNT_TEST = "mount_test"
const val SEPOLICY_RULE = "sepolicy_rule"
const val INVALID_ZYGISK = "invalid_zygisk"
const val REMOVE_TEST = "remove_test"
const val EMPTY_ZYGISK = "empty_zygisk"
}
object TimberLog : CallbackList<String>(Runnable::run) {
@@ -45,35 +72,152 @@ class Environment : BaseTest {
}
}
private fun checkModuleZip(file: File) {
// Make sure module processing is correct
ZipFile.Builder().setFile(file).get().use { zip ->
val meta = zip.entries
.asSequence()
.filter { it.name.startsWith("META-INF") }
.toMutableList()
assertEquals(MODULE_ERROR, 6, meta.size)
val binary = zip.getInputStream(
zip.getEntry("META-INF/com/google/android/update-binary")
).use { it.readBytes() }
val ref = appContext.assets.open("module_installer.sh").use { it.readBytes() }
assertArrayEquals(MODULE_ERROR, ref, binary)
val script = zip.getInputStream(
zip.getEntry("META-INF/com/google/android/updater-script")
).use { it.readBytes() }
assertArrayEquals(MODULE_ERROR, "#MAGISK\n".toByteArray(), script)
}
}
private fun setupMountTest(root: ExtendedFile) {
val error = "$MOUNT_TEST setup failed"
val path = root.getChildFile(MOUNT_TEST)
// Create /system/etc/newfile
val etc = path.getChildFile("system").getChildFile("etc")
assertTrue(error, etc.mkdirs())
assertTrue(error, etc.getChildFile("newfile").createNewFile())
// Create /system/app/EasterEgg/.replace
val egg = path.getChildFile("system").getChildFile("app").getChildFile("EasterEgg")
assertTrue(error, egg.mkdirs())
assertTrue(error, egg.getChildFile(".replace").createNewFile())
// Delete /system/bin/screenrecord
val bin = path.getChildFile("system").getChildFile("bin")
assertTrue(error, bin.mkdirs())
assertTrue(error, Shell.cmd("mknod $bin/screenrecord c 0 0").exec().isSuccess)
assertTrue(error, Shell.cmd("set_default_perm $path").exec().isSuccess)
}
private fun setupSepolicyRuleModule(root: ExtendedFile) {
val error = "$SEPOLICY_RULE setup failed"
val path = root.getChildFile(SEPOLICY_RULE)
assertTrue(error, path.mkdirs())
// Add sepolicy patch
PrintStream(path.getChildFile("sepolicy.rule").newOutputStream()).use {
it.println("type magisk_test domain")
}
assertTrue(error, Shell.cmd(
"set_default_perm $path",
"copy_preinit_files"
).exec().isSuccess)
}
private fun setupEmptyZygiskModule(root: ExtendedFile) {
val error = "$EMPTY_ZYGISK setup failed"
val path = root.getChildFile(EMPTY_ZYGISK)
// Create an empty zygisk folder
val module = LocalModule(path)
assertTrue(error, module.zygiskFolder.mkdirs())
}
private fun setupInvalidZygiskModule(root: ExtendedFile) {
val error = "$INVALID_ZYGISK setup failed"
val path = root.getChildFile(INVALID_ZYGISK)
// Create invalid zygisk libraries
val module = LocalModule(path)
assertTrue(error, module.zygiskFolder.mkdirs())
assertTrue(error, module.zygiskFolder.getChildFile("armeabi-v7a.so").createNewFile())
assertTrue(error, module.zygiskFolder.getChildFile("arm64-v8a.so").createNewFile())
assertTrue(error, module.zygiskFolder.getChildFile("x86.so").createNewFile())
assertTrue(error, module.zygiskFolder.getChildFile("x86_64.so").createNewFile())
assertTrue(error, Shell.cmd("set_default_perm $path").exec().isSuccess)
}
private fun setupRemoveModule(root: ExtendedFile) {
val error = "$REMOVE_TEST setup failed"
val path = root.getChildFile(REMOVE_TEST)
// Create a new module but mark is as "remove"
val module = LocalModule(path)
assertTrue(error, path.mkdirs())
assertTrue(error, path.getChildFile("service.sh").createNewFile())
module.remove = true
assertTrue(error, Shell.cmd("set_default_perm $path").exec().isSuccess)
}
@Test
fun setupMagisk() {
fun setupEnvironment() {
runBlocking {
assertTrue(
"Magisk setup failed",
MagiskInstaller.Emulator(TimberLog, TimberLog).exec()
)
}
}
@Test
fun setupLsposed() {
assumeTrue(lsposed())
val notify = object : DownloadNotifier {
override val context = this@Environment.context
override val context = appContext
override fun notifyUpdate(id: Int, editor: (Notification.Builder) -> Unit) {}
}
val processor = DownloadProcessor(notify)
val zip = context.cachedFile("lsposed.zip")
val shamiko = appContext.cachedFile("shamiko.zip")
runBlocking {
ServiceLocator.networkService.fetchFile(LSPOSED_URL).byteStream().use {
processor.handleModule(it, zip.toUri())
testContext.assets.open("shamiko.zip").use {
processor.handleModule(it, shamiko.toUri())
}
checkModuleZip(shamiko)
if (shamiko()) {
assertTrue(
"Shamiko installation failed",
FlashZip(shamiko.toUri(), TimberLog, TimberLog).exec()
)
}
assertTrue(
"LSPosed installation failed",
FlashZip(zip.toUri(), TimberLog, TimberLog).exec()
)
}
val lsp = appContext.cachedFile("lsposed.zip")
runBlocking {
testContext.assets.open("lsposed.zip").use {
processor.handleModule(it, lsp.toUri())
}
checkModuleZip(lsp)
if (lsposed()) {
assertTrue(
"LSPosed installation failed",
FlashZip(lsp.toUri(), TimberLog, TimberLog).exec()
)
}
}
val root = RootUtils.fs.getFile(Const.MODULE_PATH)
if (mount()) { setupMountTest(root) }
if (preinit()) { setupSepolicyRuleModule(root) }
setupEmptyZygiskModule(root)
setupInvalidZygiskModule(root)
setupRemoveModule(root)
}
@Test
@@ -82,7 +226,7 @@ class Environment : BaseTest {
assertTrue(
"App hiding failed",
AppMigration.patchAndHide(
context = context,
context = appContext,
label = "Settings",
pkg = "repackaged.$APP_PACKAGE_NAME"
)
@@ -95,7 +239,7 @@ class Environment : BaseTest {
runBlocking {
assertTrue(
"App restoration failed",
AppMigration.restoreApp(context)
AppMigration.restoreApp(appContext)
)
}
}

View File

@@ -6,25 +6,25 @@
<string name="logs">السجلات</string>
<string name="settings">الإعدادات</string>
<string name="install">تثبيت</string>
<string name="section_home">الأساسي</string>
<string name="section_theme">السِمات</string>
<string name="section_home">الصفحة الرئيسية</string>
<string name="section_theme">المظهر</string>
<!--Home-->
<string name="no_connection">لا يوجد إتصال</string>
<string name="app_changelog">تفاصيل التحديث</string>
<string name="loading">جارٍ التحميل...</string>
<string name="update">تحديث</string>
<string name="not_available">غير/متوفر</string>
<string name="not_available">غير متوفر</string>
<string name="hide">إخفاء</string>
<string name="home_package">الحزمة</string>
<string name="home_support_title">تبرع لنا</string>
<string name="home_item_source">الكود المصدري للتطبيق</string>
<string name="home_support_content">مـاجـيسك هي، وستظل دوماً، مجانيةً و مفتوحة المصدر، اظهر اهتمامك لنا لكي نبقيها هكذا بدعم مالي صغير</string>
<string name="home_support_content">ماجيسك هو، وسيظل دوماً، مجانياّ و مفتوح المصدر، اظهر اهتمامك لنا لكي نبقيه هكذا بدعم مالي صغير</string>
<string name="home_installed_version">تم التثبيت</string>
<string name="home_latest_version">آخر إصدار</string>
<string name="invalid_update_channel">مصدر التحديث غير صالح</string>
<string name="uninstall_magisk_title">إلغاء تثبيت Magisk</string>
<string name="uninstall_magisk_title">إلغاء تثبيت ماجيسك</string>
<string name="uninstall_magisk_msg">ستُعطل/ستُحذف جميع الإضافات. سيُحذف الروت، وربما ستشفر بياناتك إذا لم تكن غير مشفرة حالياً.</string>
<!--Install-->
@@ -34,7 +34,7 @@
<string name="install_options_title">الخيارات</string>
<string name="install_method_title">الطريقة</string>
<string name="install_next">التالي</string>
<string name="install_start">هيا، بنا</string>
<string name="install_start">هيا بنا</string>
<string name="manager_download_install">اضغط للتنزيل و التثبيت</string>
<string name="direct_install">تثبيت مباشر (موصى بها)</string>
<string name="install_inactive_slot">التثبيت على المنطقة الغير نشطة (بعد OTA)</string>
@@ -87,15 +87,15 @@
<!-- MagiskHide -->
<string name="show_system_app">إظهار برامج النظام</string>
<string name="hide_filter_hint">البحث بالإسم</string>
<string name="hide_filter_hint">البحث بالاسم</string>
<string name="hide_search">ابحث</string>
<!--Module Fragment-->
<string name="no_info_provided">م توفر معلومات)</string>
<string name="no_info_provided">ا تتوفر معلومات)</string>
<string name="reboot_recovery">إعادة التشغيل إلى Recovery</string>
<string name="reboot_bootloader">إعادة التشغيل إلى Bootloader</string>
<string name="reboot_download">إعادة التشغيل إلى وضـع Odin</string>
<string name="reboot_edl">إعادة التشغيل إلى وضـعية EDL</string>
<string name="reboot_edl">إعادة التشغيل إلى EDL</string>
<string name="module_version_author">%1$sبواسطة%2$s</string>
<string name="module_state_remove">إزالة </string>
<string name="module_state_restore">إسترجاع</string>
@@ -104,15 +104,15 @@
<string name="external_rw_permission_denied">امنحني إذن الولوج للذاكرة الداخلية</string>
<!--Settings -->
<string name="settings_dark_mode_title">وضـعية الِسمات</string>
<string name="settings_dark_mode_message">حدد الوضـع الذي يناسب ذوقك</string>
<string name="settings_dark_mode_title">المظهر</string>
<string name="settings_dark_mode_message">حدد المظهر الذي يناسب ذوقك</string>
<string name="settings_dark_mode_light">الوضـع المضيء</string>
<string name="settings_dark_mode_system">اتبّع النظام</string>
<string name="settings_dark_mode_dark">وضـع الظلام</string>
<string name="settings_dark_mode_dark">الوضع المظلم</string>
<string name="settings_download_path_title">مسار التحميل</string>
<string name="settings_download_path_message">ستحمل الملفات إلى %1$s</string>
<string name="language">اللغة</string>
<string name="system_default">(الأفتراضي)</string>
<string name="system_default">(الإفتراضي)</string>
<string name="settings_check_update_title">تحقق من التحديثات</string>
<string name="settings_check_update_summary">التحقق من التحديثات في الخلفية بشكل دوري</string>
<string name="settings_update_channel_title">مصدر التحديثات</string>
@@ -123,8 +123,8 @@
<string name="settings_hosts_title">موانع الاعلانات</string>
<string name="settings_hosts_summary">حجب الاعلانات دون تعديل النظام</string>
<string name="settings_hosts_toast">تم تمكين خاصية حجب الاعلانات</string>
<string name="settings_app_name_hint">الإسم الجديد</string>
<string name="settings_app_name_helper">التطبيق الجديد سوف يملك هذا الإسم</string>
<string name="settings_app_name_hint">الاسم الجديد</string>
<string name="settings_app_name_helper">التطبيق الجديد سوف يملك هذا الاسم</string>
<string name="settings_app_name_error">الصيغة غير مقبولة</string>
<string name="settings_su_app_adb">التطبيقات و ADB</string>
<string name="settings_su_app">التطبيقات فقط</string>
@@ -140,51 +140,51 @@
<string name="auto_response">الفعل التلقائي</string>
<string name="request_timeout">المهلة قبل الفعل التلقائي</string>
<string name="superuser_notification">إشعارات طلبات الروت</string>
<string name="settings_su_reauth_title">إعادة المصادقة بعد الترقية</string>
<string name="settings_su_reauth_summary">أعد مصادقة صلاحيات الروت بعد إجراء ترقيات للتطبيق</string>
<string name="settings_su_reauth_title">إعادة المصادقة بعد التحديث</string>
<string name="settings_su_reauth_summary">أعد مصادقة صلاحيات الروت بعد تحديث التطبيق</string>
<string name="settings_customization">تخصيص</string>
<string name="multiuser_mode">نمط مستخدمين متعددين</string>
<string name="multiuser_mode">نمط المستخدم المزدوج</string>
<string name="settings_owner_only">مالك الجهاز فقط</string>
<string name="settings_owner_manage">المالك هو من يحدد</string>
<string name="settings_user_independent">مستقل </string>
<string name="settings_user_independent">مستقل</string>
<string name="owner_only_summary">للمالك فقط له صلاحيات الروت</string>
<string name="owner_manage_summary">فقط المالك من يرفض و يمنح صلاحيات الروت</string>
<string name="user_independent_summary">كل مستخدم له قواعد روت خاصة به</string>
<string name="mount_namespace_mode">نمط Mount Namespace</string>
<string name="settings_ns_global">نمط Namespace العام</string>
<string name="settings_ns_requester">نمط NameSpace المتوارث</string>
<string name="settings_ns_isolate">نمط NameSpace المعزول</string>
<string name="settings_ns_global">نمط Namespace عام</string>
<string name="settings_ns_requester">نمط NameSpace متوارث</string>
<string name="settings_ns_isolate">نمط NameSpace معزول</string>
<string name="global_summary">جميع الجلسات الروت تستخدم NameSpace العام</string>
<string name="requester_summary">جميع الجلسات الروت تستخدم NameSpace المتوارث</string>
<string name="isolate_summary">جميع الجلسات الروت تستخدم NameSpace المعزول</string>
<!--Notifications-->
<string name="update_channel">تحديثات Magisk</string>
<string name="update_channel">تحديثات ماجيسك</string>
<string name="progress_channel">إشعارات التقدم</string>
<string name="download_complete">اكتمل التنزيل</string>
<string name="download_file_error">فشل تنزيل الملف</string>
<string name="magisk_update_title">تحديث مـاجـيسك متوفر!</string>
<string name="magisk_update_title">تحديث ماجيسك متوفر!</string>
<!--Toasts, Dialogs-->
<string name="yes">نعم</string>
<string name="no">لا</string>
<string name="download">تنزيل</string>
<string name="reboot">إعادة التشغيل</string>
<string name="release_notes">معلومات الأصدار الجديد</string>
<string name="flashing">يتم الحرق...</string>
<string name="release_notes">معلومات الإصدار الجديد</string>
<string name="flashing">يتم التثبيت...</string>
<string name="done">تم!</string>
<string name="failure">فشل!</string>
<string name="open_link_failed_toast">لم يُعثر على تطبيق لفتح الرابط …</string>
<string name="complete_uninstall">إلغاء التثبيت بالكامل</string>
<string name="restore_img">استعادة الصور</string>
<string name="restore_img_msg">جار الأستعادة…</string>
<string name="restore_done">تم الأستعادة</string>
<string name="restore_fail">النسخة الاحتياطية الأصلية غير موجودة!</string>
<string name="setup_fail">فشل التضبيط</string>
<string name="env_fix_title"> الإعداد الأضافي مطلوب</string>
<string name="setup_msg">جار تضبيت البيئة</string>
<string name="unsupport_magisk_title">إصدار مـاجـيسك غير مدعوم</string>
<string name="restore_img_msg">جار الإستعادة…</string>
<string name="restore_done">تم الإستعادة</string>
<string name="restore_fail">النسخة الإحتياطية الأصلية غير موجودة!</string>
<string name="setup_fail">فشل الإعداد</string>
<string name="env_fix_title">الإعداد الإضافي مطلوب</string>
<string name="setup_msg">جار إعداد البيئة</string>
<string name="unsupport_magisk_title">إصدار ماجيسك غير مدعوم</string>
</resources>

View File

@@ -1,99 +1,57 @@
<resources>
<!--Universal-->
<!--Welcome Activity-->
<!--Sections-->
<string name="modules">Модули</string>
<string name="superuser">Супер-корисник</string>
<string name="logs">Дневник</string>
<string name="logs">Логови</string>
<string name="settings">Подешавања</string>
<string name="install">Инсталација</string>
<string name="section_home">Почетно</string>
<string name="section_theme">Теме</string>
<string name="denylist">Листа забрана</string>
<!--Status Fragment-->
<string name="invalid_update_channel">Непостојећи Канал Ажурирања</string>
<!--Home-->
<string name="no_connection">Недоступна конекција</string>
<string name="app_changelog">Промене у апликацији</string>
<string name="loading">Учитавање…</string>
<string name="update">Ажурирање</string>
<string name="not_available">N/A</string>
<string name="hide">Сакриј</string>
<string name="home_package">Пакет</string>
<string name="home_app_title">Апл.</string>
<!--Install Fragment-->
<string name="home_notice_content">Преузмите Magisk САМО са званичне GitHub странице. Фајлови из непознатих извора могу бити малициозни!</string>
<string name="home_support_title">Подржите нас</string>
<string name="home_follow_title">Запратите нас</string>
<string name="home_item_source">Извор</string>
<string name="home_support_content">Magisk јесте и увек ће бити бесплатан и open source. Можете показати да вам је стало својом донацијом.</string>
<string name="home_installed_version">Инсталирано</string>
<string name="home_latest_version">Најновије</string>
<string name="invalid_update_channel">Невалидан канал ажурирања</string>
<string name="uninstall_magisk_title">Деинсталирај Magisk</string>
<string name="uninstall_magisk_msg">Сви модули ће бити онемогућени/уклоњени!\nКорен ће бити уклоњен!\nСвако неенкриптовано интерно складиште ће употребом Magisk-а бити поново енкриптовано!</string>
<!--Install-->
<string name="keep_force_encryption">Задржи форсирану енкрипцију</string>
<string name="keep_dm_verity">Задржи AVB 2.0/dm-verity</string>
<string name="uninstall_magisk_title">Унинсталирај Магиск</string>
<string name="uninstall_magisk_msg">Сви модули ће бити онеспособљени/уклоњени. Корен ће бити уклоњен, и потенцијално енкриптовати твоје податке уколико већ нису енкриптовани</string>
<string name="update">Ажурирање</string>
<!--Module Fragment-->
<string name="no_info_provided">(Без информација)</string>
<!--Repo Fragment-->
<string name="update_available">Ажурирање доступно</string>
<string name="home_installed_version">Инсталирано</string>
<!--Log Fragment-->
<string name="menuSaveLog">Сачувај дневник</string>
<string name="menuClearLog">Обриши дневник</string>
<string name="logs_cleared">Дневник успешно креиран</string>
<!--About Activity-->
<string name="app_changelog">Дневник промена апликације</string>
<!--Toasts, Dialogs-->
<string name="repo_install_title">Инсталирај %1$s %2$s(%3$d)</string>
<string name="download">Преузми</string>
<string name="reboot">Рестартуј</string>
<string name="magisk_update_title">Нови Адбејт Магиска Доступан!</string>
<string name="release_notes">Белешке обљављивања</string>
<string name="recovery_mode">Режим опоравка</string>
<string name="install_options_title">Опције</string>
<string name="install_method_title">Метод</string>
<string name="install_next">Наредно</string>
<string name="install_start">Почнимо</string>
<string name="manager_download_install">Притисни да преузмеш и инсталираш</string>
<string name="update_channel">Магиск Ажурирање</string>
<string name="flashing">Флешовање</string>
<string name="direct_install">Директна Инсталација (Препоручено)</string>
<string name="complete_uninstall">Комплетна Унинсталација</string>
<string name="restore_done">Повратак успешан!</string>
<string name="restore_fail">Фабрички бекап не постоји!</string>
<string name="download_file_error">Грешка при преузимању фајла</string>
<!--Settings Activity -->
<string name="language">Језик</string>
<string name="system_default">(Фабрички Система)</string>
<string name="settings_update_channel_title">Канал Ажурирања</string>
<string name="settings_update_stable">Стабилан</string>
<string name="settings_update_beta">Бета</string>
<string name="settings_update_custom">По наруџби</string>
<string name="settings_update_custom_msg">Унеси наруџбени УРЛ</string>
<string name="settings_hosts_title">Без-системски домаћини (hosts)</string>
<string name="settings_hosts_summary">Подршка без-системских домаћина за апликације за блокирање реклама</string>
<string name="settings_su_app_adb">Апликације и АДБ</string>
<string name="settings_su_app">Само Апликације</string>
<string name="settings_su_adb">Само АДБ</string>
<string name="settings_su_disable">Онемогућено</string>
<string name="settings_su_request_10">10 секунди</string>
<string name="settings_su_request_15">15 секунди</string>
<string name="settings_su_request_20">20 секунди</string>
<string name="settings_su_request_30">30 секунди</string>
<string name="settings_su_request_45">45 секунди</string>
<string name="settings_su_request_60">60 секунди</string>
<string name="superuser_access">Приступ Супер-кориснику</string>
<string name="auto_response">Аутоматски одговор</string>
<string name="request_timeout">Истек захтева</string>
<string name="superuser_notification">Нотификације Супер-корисника</string>
<string name="settings_su_reauth_title">Поново одобри после ажурирања</string>
<string name="settings_su_reauth_summary">Поново одобри дозволу Супер-корисника после ажурирања апликације</string>
<string name="multiuser_mode">Више-кориснички режим</string>
<string name="settings_owner_only">Власник уређаја само</string>
<string name="settings_owner_manage">Одређено од стране власника</string>
<string name="settings_user_independent">Независно од корисника</string>
<string name="owner_only_summary">Само власник има приступ корену</string>
<string name="owner_manage_summary">Само власник може да приступа корену и да прими захтеве за њега</string>
<string name="user_independent_summary">Сваки корисник има своја правила корена</string>
<string name="mount_namespace_mode">Постоље режима имена простора</string>
<string name="settings_ns_global">Глобално име простора</string>
<string name="settings_ns_requester">Наслеђено име простора</string>
<string name="settings_ns_isolate">Ограђено име простора</string>
<string name="global_summary">Све коренске сесије користе глобално име простора</string>
<string name="requester_summary">Коренске сесије ће наследити свој простор именовања</string>
<string name="isolate_summary">Свака коренска сесија ће имати свој изоловани простор именовања</string>
<string name="direct_install">Директна инсталација (Препоручено)</string>
<string name="install_inactive_slot">Инсталација на неактиван слот (Након OTA)</string>
<string name="install_inactive_slot_msg">Ваш уређај ће бити ФОРСИРАН да се покрене на тренутно неактивном слоту након поновног покретања!\nКористите опцију само кад се OTA заврши.\nНастави?</string>
<string name="setup_title">Додатне поставке</string>
<string name="select_patch_file">Изаберите фајл</string>
<string name="patch_file_msg">Изаберите слику (*.img) или ODIN tarfile (*.tar) или payload.bin (*.bin)</string>
<string name="reboot_delay_toast">Поново покретање за 5 секунди…</string>
<string name="flash_screen_title">Инсталација</string>
<!--Superuser-->
<string name="su_request_title">Супер-кориснички захтев</string>
<string name="touch_filtered_warning">Magisk не може да верификује ваш одговор, јер апликација прикрива супер-кориснички захтев</string>
<string name="deny">Забрани</string>
<string name="prompt">Захтев</string>
<string name="grant">Дозволи</string>
@@ -104,20 +62,190 @@
<string name="twentymin">20 мин</string>
<string name="thirtymin">30 мин</string>
<string name="sixtymin">60 мин</string>
<string name="su_allow_toast">%1$s је добио права на Супер-корисника</string>
<string name="su_deny_toast">%1$s није добио права на Супер-корисника</string>
<string name="su_allow_toast">%1$s је добио права на супер-корисника</string>
<string name="su_deny_toast">%1$s није добио права на супер-корисника</string>
<string name="su_snack_grant">Супер-корисничка права од %1$s су пружена</string>
<string name="su_snack_deny">Супер-корисничка права од %1$s су одбијена</string>
<string name="su_snack_notif_on">Нотификације од %1$s су омогућене</string>
<string name="su_snack_notif_off">Нотификације од %1$s су онемогућене</string>
<string name="su_snack_log_on">Записивање у дневник за %1$s је омогућено</string>
<string name="su_snack_log_off">Записивање у дневник за %1$s је онемогућено</string>
<string name="su_snack_log_on">Логовање за %1$s је омогућено</string>
<string name="su_snack_log_off">Логовање за %1$s је онемогућено</string>
<string name="su_revoke_title">Опозови?</string>
<string name="su_revoke_msg">Потврди да опозовеш права од %1$s?</string>
<string name="toast">Тост</string>
<string name="none">Ниједан</string>
<string name="su_revoke_msg">Потврди да опозовеш права на супер-корисника од %1$s?</string>
<string name="toast">Toast</string>
<string name="none">Ништа</string>
<!--Superuser logs-->
<string name="target_uid">Циљани УИД: %1$d</string>
<string name="superuser_toggle_notification">Нотификације</string>
<string name="superuser_toggle_revoke">Опозови</string>
<string name="superuser_policy_none">Ниједна апликација није тражила пермисије за супер-корисника још увек.</string>
<!--Logs-->
<string name="log_data_none">Немате логова, покушајте користити коренске апликације више</string>
<string name="log_data_magisk_none">Magisk логови су празни, то је чудно</string>
<string name="menuSaveLog">Сачувај лог</string>
<string name="menuClearLog">Уклони лог</string>
<string name="logs_cleared">Лог успешно уклоњен</string>
<string name="pid">PID: %1$d</string>
<string name="target_uid">Циљани UID: %1$d</string>
<string name="target_pid">Mount ns цињани PID: %s</string>
<string name="selinux_context">SELinux контекст: %s</string>
<string name="supp_group">Допунска група: %s</string>
<!--SafetyNet-->
<!--MagiskHide-->
<string name="show_system_app">Прикажи системске апл.</string>
<string name="show_os_app">Прикажи апл. ОС-а</string>
<string name="hide_filter_hint">Филтрирај по имену</string>
<string name="hide_search">Претрага</string>
<!--Module-->
<string name="no_info_provided">(Без информација)</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="reboot_safe_mode">Сигуран мод</string>
<string name="module_version_author">%1$s од %2$s</string>
<string name="module_state_remove">Уклони</string>
<string name="module_action">Акција</string>
<string name="module_state_restore">Поврати</string>
<string name="module_action_install_external">Инсталирај из складишта</string>
<string name="update_available">Ажурирање доступно</string>
<string name="suspend_text_riru">Модул је суспендован јер је %1$s омогућено</string>
<string name="suspend_text_zygisk">Модул је суспендован јер %1$s није омогућено</string>
<string name="zygisk_module_unloaded">Zygisk модул није учитан због некомпатибилности</string>
<string name="module_empty">Ниједан модул није инсталиран</string>
<string name="confirm_install">Инсталирај модул %1$s?</string>
<string name="confirm_install_title">Потврда инсталације</string>
<!--Settings-->
<string name="settings_dark_mode_title">Тема</string>
<string name="settings_dark_mode_message">Изаберите тему која вам највише одговара!</string>
<string name="settings_dark_mode_light">Увек светло</string>
<string name="settings_dark_mode_system">Прати систем</string>
<string name="settings_dark_mode_dark">Увек тамно</string>
<string name="settings_download_path_title">Путања за преузимање</string>
<string name="settings_download_path_message">Фајлови ће бити сачувани на %1$s</string>
<string name="settings_hide_app_title">Сакриј Magisk апл.</string>
<string name="settings_hide_app_summary">Инсталирај proxy апликацију са насумичним ID-јем пакета и прилагођеном лабелом</string>
<string name="settings_restore_app_title">Поврати Magisk апл.</string>
<string name="settings_restore_app_summary">Откриј апл. и поврати оригинални APK</string>
<string name="language">Језик</string>
<string name="system_default">(Подразумевано системски)</string>
<string name="settings_check_update_title">Провери ажурирања</string>
<string name="settings_check_update_summary">Периодично провери ажурирања у позадини</string>
<string name="settings_update_channel_title">Канал ажурирања</string>
<string name="settings_update_stable">Стабилно</string>
<string name="settings_update_beta">Бета</string>
<string name="settings_update_custom">Прилагођено</string>
<string name="settings_update_custom_msg">Унеси прилагођени URL канала</string>
<string name="settings_zygisk_summary">Покрени делове Magisk-а у zygote daemon-у</string>
<string name="settings_denylist_title">Спроведи листу забрана</string>
<string name="settings_denylist_summary">Процеси на листи забрана ће повратити све Magisk измене</string>
<string name="settings_denylist_config_title">Конфигуриши листу забрана</string>
<string name="settings_denylist_config_summary">Изабери процесе који ће бити на листи забрана</string>
<string name="settings_hosts_title">Безсистемски домаћини (hosts)</string>
<string name="settings_hosts_summary">Подршка безсистемских домаћина за апликације блокирања реклама</string>
<string name="settings_hosts_toast">Модул безсистемских домаћина додат</string>
<string name="settings_app_name_hint">Ново име</string>
<string name="settings_app_name_helper">Апл. ће бити спакована под овим именом</string>
<string name="settings_app_name_error">Невалидан формат</string>
<string name="settings_su_app_adb">Апликације и ADB</string>
<string name="settings_su_app">Само апликације</string>
<string name="settings_su_adb">Само ADB</string>
<string name="settings_su_disable">Онемогућено</string>
<string name="settings_su_request_10">10 секунди</string>
<string name="settings_su_request_15">15 секунди</string>
<string name="settings_su_request_20">20 секунди</string>
<string name="settings_su_request_30">30 секунди</string>
<string name="settings_su_request_45">45 секунди</string>
<string name="settings_su_request_60">60 секунди</string>
<string name="superuser_access">Приступ супер-корисника</string>
<string name="auto_response">Аутоматски одговор</string>
<string name="request_timeout">Истек захтева</string>
<string name="superuser_notification">Нотификације супер-корисника</string>
<string name="settings_su_reauth_title">Поново одобри након ажурирања</string>
<string name="settings_su_reauth_summary">Поново тражи пермисије супер-корисника након ажурирања апликација</string>
<string name="settings_su_tapjack_title">Заштита од tapjacking-а</string>
<string name="settings_su_tapjack_summary">Prompt дијалог супер-корисника неће реаговати док је прикривен другим прозором или overlay-ем</string>
<string name="settings_su_auth_title">Аутентификација корисника</string>
<string name="settings_su_auth_summary">Тражи аутентификацију корисника током захтева супер-корисника</string>
<string name="settings_su_auth_insecure">Ниједан метод аутентификације није подешен на уређају</string>
<string name="settings_customization">Прилагођавање</string>
<string name="setting_add_shortcut_summary">Додај лепу пречицу на почетни екран у случају да се име и иконица не препознају лако након скривања апликације</string>
<string name="settings_doh_title">DNS преко HTTPS-а</string>
<string name="settings_doh_description">Заобилазно решење DNS тровања у неким нацијама</string>
<string name="settings_random_name_title">Насумично име на излазу</string>
<string name="settings_random_name_description">Насумично име излазног фајла слика и tar фајлова ради спречавања детекције</string>
<string name="multiuser_mode">Вишекориснички режим</string>
<string name="settings_owner_only">Само власник уређаја</string>
<string name="settings_owner_manage">Одређено од стране власника</string>
<string name="settings_user_independent">Независно од корисника</string>
<string name="owner_only_summary">Само власник има приступ корену</string>
<string name="owner_manage_summary">Само власник може да приступа корену и да прима захтеве за њега</string>
<string name="user_independent_summary">Сваки корисник има своја правила корена</string>
<string name="mount_namespace_mode">Mount режим namespace-а</string>
<string name="settings_ns_global">Глобални namespace</string>
<string name="settings_ns_requester">Наслеђени namespace</string>
<string name="settings_ns_isolate">Изоловани namespace</string>
<string name="global_summary">Све коренске сесије користе глобални mount namespace</string>
<string name="requester_summary">Коренске сесије ће наследити namespace од подносиоца захтева</string>
<string name="isolate_summary">Свака коренска сесија ће имати свој изоловани namespace</string>
<!--Notifications-->
<string name="update_channel">Ажурирања Magisk-а</string>
<string name="progress_channel">Нотификације о прогресу</string>
<string name="updated_channel">Ажурирање завршено</string>
<string name="download_complete">Преузимање завршено</string>
<string name="download_file_error">Грешка при преузимању фајла</string>
<string name="magisk_update_title">Ажурирање Magisk-а доступно!</string>
<string name="updated_title">Magisk је ажуриран</string>
<string name="updated_text">Кликни да отвориш апликацију</string>
<!--Toasts, Dialogs-->
<string name="yes">Да</string>
<string name="no">Не</string>
<string name="repo_install_title">Инсталирај %1$s %2$s(%3$d)</string>
<string name="download">Преузми</string>
<string name="reboot">Поново покрени</string>
<string name="close">Затвори</string>
<string name="release_notes">Release notes</string>
<string name="flashing">Флешовање…</string>
<string name="running">Покретање…</string>
<string name="done">Завршено!</string>
<string name="done_action">Покретање акције %1$s завршено</string>
<string name="failure">Неуспешно!</string>
<string name="hide_app_title">Скривање Magisk апликације…</string>
<string name="open_link_failed_toast">Није пронађена апликација за отварање линка</string>
<string name="complete_uninstall">Комплетна деинсталација</string>
<string name="restore_img">Поврати слике</string>
<string name="restore_img_msg">Повратак…</string>
<string name="restore_done">Повратак успешан!</string>
<string name="restore_fail">Фабрички бекап не постоји!</string>
<string name="setup_fail">Неуспешна поставка</string>
<string name="env_fix_title">Потребно додатно подешавање</string>
<string name="env_fix_msg">Ваш уређај захтева додатно подешавање да би Magisk радио како треба. Да ли желите наставити и покренути поново?</string>
<string name="env_full_fix_msg">Ваш уређај захтева поновно флешовање да би Magisk радио како треба. Реинсталирајте Magisk кроз апликацију, режим опоравка не може добити тачне информације о уређају.</string>
<string name="setup_msg">Покретање подешавања окружења…</string>
<string name="unsupport_magisk_title">Неподржана верзија Magisk-а</string>
<string name="unsupport_magisk_msg">Ова верзија апликације не подржава Magisk верзије мање од %1$s.\n\nАпликација ће се понашати као да Magisk није инсталиран, молимо ажурирајте Magisk што пре.</string>
<string name="unsupport_general_title">Ненормално стање</string>
<string name="unsupport_system_app_msg">Покретање апликације као системске није подржано. Молимо поставите апликацију да буде корисничка.</string>
<string name="unsupport_other_su_msg">Детектован \"su\" binary који није Magisk-ов. Молимо уклоните конкурентно коренско решење и/или реинсталирајте Magisk.</string>
<string name="unsupport_external_storage_msg">Magisk је инсталиран на екстерно складиште. Молимо померите апл. у интерно складиште.</string>
<string name="unsupport_nonroot_stub_msg">Скривена Magisk апликација не може наставити са радом јер је корен изгубљен. Молимо повратите оригинални APK.</string>
<string name="unsupport_nonroot_stub_title">@string/settings_restore_app_title</string>
<string name="external_rw_permission_denied">Дозволите пермисију за складиште да бисте омогућили ову функционалност</string>
<string name="post_notifications_denied">Дозволите пермисију за нотификације да бисте омогућили ову функционалност</string>
<string name="install_unknown_denied">Дозволите "инсталирање непознатих апликација" да бисте омогућили ову функционалност</string>
<string name="add_shortcut_title">Додај пречицу на почетни екран</string>
<string name="add_shortcut_msg">Након скривања апликације, њено име и иконицу ћете тешко препознати. Желите ли додати лепу пречицу на почетни екран?</string>
<string name="app_not_found">Није пронађена апликација за ову акцију</string>
<string name="reboot_apply_change">Поново покрени да примениш измене</string>
<string name="restore_app_confirmation">Ово ће вратити скривену апликацију на оригиналну. Да ли стварно то желите?</string>
</resources>

View File

@@ -30,5 +30,4 @@ android.nonFinalResIds=false
# Magisk
magisk.stubVersion=40
magisk.versionCode=28102
magisk.ondkVersion=r28.2
magisk.versionCode=29000

View File

@@ -1,14 +1,14 @@
[versions]
kotlin = "2.1.0"
android = "8.8.0"
ksp = "2.1.0-1.0.29"
kotlin = "2.1.20"
android = "8.9.2"
ksp = "2.1.20-1.0.31"
rikka = "1.3.0"
navigation = "2.8.4"
navigation = "2.8.9"
libsu = "6.0.0"
moshi = "1.15.1"
moshi = "1.15.2"
okhttp = "4.12.0"
retrofit = "2.11.0"
room = "2.6.1"
room = "2.7.1"
[libraries]
bcpkix = { module = "org.bouncycastle:bcpkix-jdk18on", version = "1.80" }
@@ -26,12 +26,12 @@ timber = { module = "com.jakewharton.timber:timber", version = "5.0.1" }
jgit = { module = "org.eclipse.jgit:org.eclipse.jgit", version = "7.1.0.202411261347-r" }
# AndroidX
activity = { module = "androidx.activity:activity", version = "1.10.0" }
activity = { module = "androidx.activity:activity", version = "1.10.1" }
appcompat = { module = "androidx.appcompat:appcompat", version = "1.7.0" }
core-ktx = { module = "androidx.core:core-ktx", version = "1.15.0" }
core-ktx = { module = "androidx.core:core-ktx", version = "1.16.0" }
core-splashscreen = { module = "androidx.core:core-splashscreen", version = "1.0.1" }
constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version = "2.2.0" }
fragment-ktx = { module = "androidx.fragment:fragment-ktx", version = "1.8.5" }
constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version = "2.2.1" }
fragment-ktx = { module = "androidx.fragment:fragment-ktx", version = "1.8.6" }
navigation-fragment-ktx = { module = "androidx.navigation:navigation-fragment-ktx", version.ref = "navigation" }
navigation-ui-ktx = { module = "androidx.navigation:navigation-ui-ktx", version.ref = "navigation" }
profileinstaller = { module = "androidx.profileinstaller:profileinstaller", version = "1.4.1" }
@@ -40,10 +40,10 @@ room-ktx = { module = "androidx.room:room-ktx", version.ref = "room" }
room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" }
room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" }
swiperefreshlayout = { module = "androidx.swiperefreshlayout:swiperefreshlayout", version = "1.1.0" }
transition = { module = "androidx.transition:transition", version = "1.5.1" }
collection-ktx = { module = "androidx.collection:collection-ktx", version = "1.4.5" }
transition = { module = "androidx.transition:transition", version = "1.6.0" }
collection-ktx = { module = "androidx.collection:collection-ktx", version = "1.5.0" }
material = { module = "com.google.android.material:material", version = "1.12.0" }
jdk-libs = { module = "com.android.tools:desugar_jdk_libs_nio", version = "2.1.3" }
jdk-libs = { module = "com.android.tools:desugar_jdk_libs_nio", version = "2.1.5" }
test-runner = { module = "androidx.test:runner", version = "1.6.2" }
test-rules = { module = "androidx.test:rules", version = "1.6.1" }
test-junit = { module = "androidx.test.ext:junit", version = "1.2.1" }

View File

@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME

View File

View File

View File

@@ -8,4 +8,4 @@ dependencyResolutionManagement {
}
}
rootProject.name = "Magisk"
include(":app:apk", ":app:core", ":app:shared", ":app:stub", ":app:test", ":native")
include(":apk", ":core", ":shared", ":stub", ":test")

View File

@@ -25,6 +25,7 @@
<application
android:allowBackup="false"
android:enableOnBackInvokedCallback="false"
android:label="Magisk"
android:requestLegacyExternalStorage="true"
android:supportsRtl="true"

View File

@@ -7,12 +7,12 @@ public class ProviderInstaller {
private static final String GMS_PACKAGE_NAME = "com.google.android.gms";
public static boolean install(Context context) {
public static void install(Context context) {
try {
// Check if gms is a system app
ApplicationInfo appInfo = context.getPackageManager().getApplicationInfo(GMS_PACKAGE_NAME, 0);
if ((appInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
return false;
return;
}
// Try installing new SSL provider from Google Play Service
@@ -22,9 +22,7 @@ public class ProviderInstaller {
.loadClass("com.google.android.gms.common.security.ProviderInstallerImpl")
.getMethod("insertProvider", Context.class)
.invoke(null, gms);
} catch (Exception e) {
return false;
} catch (Exception ignored) {
}
return true;
}
}

View File

@@ -41,5 +41,5 @@ android {
setupStubApk()
dependencies {
implementation(project(":app:shared"))
implementation(project(":shared"))
}

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="upgrade_msg">عليك الترقية Magisk لإكمال تهيئة التطبيق. هل تريد التنزيل والتثبيت؟</string>
<string name="no_internet_msg">يرجى اللإتصال بالإنترنت! ترقية Magisk مطلوبة.</string>
<string name="upgrade_msg">عليك بتحديث ماجيسك لإكمال تهيئة التطبيق. هل تريد التنزيل والتثبيت؟</string>
<string name="no_internet_msg">يرجى الإتصال بالإنترنت! تحديث ماجيسك مطلوب.</string>
<string name="dling">جارٍ التنزيل</string>
<string name="relaunch_app">يرجى إعادة تشغيل التطبيق يدوياً</string>
</resources>

View File

@@ -1 +1,7 @@
<resources></resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="upgrade_msg">Ажурирајте Magisk да бисте завршили постављање. Преузми и инсталирај?</string>
<string name="no_internet_msg">Молимо повежите се на интернет! Неопходно је ажурирање Magisk-а.</string>
<string name="dling">Преузимање</string>
<string name="relaunch_app">Молимо покрените апликацију поново</string>
</resources>

View File

@@ -20,7 +20,7 @@ android {
}
}
setupAppCommon()
setupTestApk()
dependencies {
implementation(libs.test.runner)

View File

@@ -1,7 +0,0 @@
plugins {
id("MagiskPlugin")
}
tasks.register("clean", Delete::class) {
delete(rootProject.layout.buildDirectory)
}

329
build.py
View File

@@ -2,7 +2,6 @@
import argparse
import copy
import glob
import lzma
import multiprocessing
import os
import platform
@@ -57,14 +56,6 @@ if is_windows:
if not sys.version_info >= (3, 8):
error("Requires Python 3.8+")
try:
sdk_path = Path(os.environ["ANDROID_HOME"])
except KeyError:
try:
sdk_path = Path(os.environ["ANDROID_SDK_ROOT"])
except KeyError:
error("Please set Android SDK path to environment variable ANDROID_HOME")
cpu_count = multiprocessing.cpu_count()
os_name = platform.system().lower()
@@ -76,20 +67,11 @@ support_abis = {
"x86_64": "x86_64-linux-android",
"riscv64": "riscv64-linux-android",
}
default_archs = {"armeabi-v7a", "x86", "arm64-v8a", "x86_64"}
default_targets = {"magisk", "magiskinit", "magiskboot", "magiskpolicy"}
support_targets = default_targets | {"resetprop"}
rust_targets = {"magisk", "magiskinit", "magiskboot", "magiskpolicy"}
# Common paths
ndk_root = sdk_path / "ndk"
ndk_path = ndk_root / "magisk"
ndk_build = ndk_path / "ndk-build"
rust_bin = ndk_path / "toolchains" / "rust" / "bin"
llvm_bin = ndk_path / "toolchains" / "llvm" / "prebuilt" / f"{os_name}-x86_64" / "bin"
cargo = rust_bin / "cargo"
gradlew = Path.cwd() / "gradlew"
adb_path = sdk_path / "platform-tools" / "adb"
native_gen_path = Path("native", "out", "generated").resolve()
ondk_version = "r29.1"
# Global vars
config = {}
@@ -162,37 +144,31 @@ def cmd_out(cmds: list):
)
def xz(data):
return lzma.compress(data, preset=9, check=lzma.CHECK_NONE)
###############
# Build Native
###############
def clean_elf():
if is_windows:
elf_cleaner = Path("tools", "elf-cleaner.exe")
else:
elf_cleaner = Path("native", "out", "elf-cleaner")
if not elf_cleaner.exists():
execv(
[
"gcc",
'-DPACKAGE_NAME="termux-elf-cleaner"',
'-DPACKAGE_VERSION="2.1.1"',
'-DCOPYRIGHT="Copyright (C) 2022 Termux."',
"tools/termux-elf-cleaner/elf-cleaner.cpp",
"tools/termux-elf-cleaner/arghandling.c",
"-o",
elf_cleaner,
]
)
cmds = [elf_cleaner, "--api-level", "23"]
cargo_toml = Path("tools", "elf-cleaner", "Cargo.toml")
cmds = ["run", "--release", "--manifest-path", cargo_toml]
if args.verbose == 0:
cmds.append("-q")
elif args.verbose > 1:
cmds.append("--verbose")
cmds.append("--")
cmds.extend(glob.glob("native/out/*/magisk"))
cmds.extend(glob.glob("native/out/*/magiskpolicy"))
execv(cmds)
run_cargo(cmds)
def collect_ndk_build():
for arch in build_abis.keys():
arch_dir = Path("native", "libs", arch)
out_dir = Path("native", "out", arch)
for source in arch_dir.iterdir():
target = out_dir / source.name
mv(source, target)
def run_ndk_build(cmds: list):
@@ -210,13 +186,6 @@ def run_ndk_build(cmds: list):
error("Build binary failed!")
os.chdir("..")
for arch in build_abis.keys():
arch_dir = Path("native", "libs", arch)
out_dir = Path("native", "out", arch)
for source in arch_dir.iterdir():
target = out_dir / source.name
mv(source, target)
def build_cpp_src(targets: set):
cmds = []
@@ -238,6 +207,7 @@ def build_cpp_src(targets: set):
if cmds:
run_ndk_build(cmds)
collect_ndk_build()
cmds.clear()
@@ -250,17 +220,18 @@ def build_cpp_src(targets: set):
if cmds:
cmds.append("B_CRT0=1")
run_ndk_build(cmds)
collect_ndk_build()
if clean:
clean_elf()
def run_cargo(cmds):
ensure_paths()
env = os.environ.copy()
env["PATH"] = f'{rust_bin}{os.pathsep}{env["PATH"]}'
env["CARGO_BUILD_RUSTC"] = str(rust_bin / f"rustc{EXE_EXT}")
env["RUSTUP_TOOLCHAIN"] = str(rust_sysroot)
env["CARGO_BUILD_RUSTFLAGS"] = f"-Z threads={min(8, cpu_count)}"
return execv([cargo, *cmds], env)
return execv(["cargo", *cmds], env)
def build_rust_src(targets: set):
@@ -333,6 +304,7 @@ def dump_flag_header():
flag_txt += f'#define MAGISK_VER_CODE {config["versionCode"]}\n'
flag_txt += f"#define MAGISK_DEBUG {0 if args.release else 1}\n"
native_gen_path = Path("native", "out", "generated")
native_gen_path.mkdir(mode=0o755, parents=True, exist_ok=True)
write_if_diff(native_gen_path / "flags.h", flag_txt)
@@ -342,10 +314,12 @@ def dump_flag_header():
def build_native():
ensure_paths()
# Verify NDK install
try:
with open(Path(ndk_path, "ONDK_VERSION"), "r") as ondk_ver:
assert ondk_ver.read().strip(" \t\r\n") == config["ondkVersion"]
assert ondk_ver.read().strip(" \t\r\n") == ondk_version
except:
error('Unmatched NDK. Please install/upgrade NDK with "build.py ndk"')
@@ -401,24 +375,28 @@ def find_jdk():
if no_jdk:
error(
"Please set Android Studio's path to environment variable ANDROID_STUDIO,\n"
+ "or install JDK 17 and make sure 'javac' is available in PATH"
+ "or install JDK 21 and make sure 'javac' is available in PATH"
)
return env
def build_apk(module: str):
ensure_paths()
env = find_jdk()
props = args.config.resolve()
os.chdir("app")
build_type = "Release" if args.release else "Debug"
proc = execv(
[
gradlew,
f"{module}:assemble{build_type}",
f"-PconfigPath={args.config.resolve()}",
f"-PconfigPath={props}",
],
env=env,
)
os.chdir("..")
if proc.returncode != 0:
error(f"Build {module} failed!")
@@ -427,7 +405,7 @@ def build_apk(module: str):
paths = module.split(":")
apk = f"{paths[-1]}-{build_type}.apk"
source = Path(*paths, "build", "outputs", "apk", build_type, apk)
source = Path("app", *paths, "build", "outputs", "apk", build_type, apk)
target = config["outdir"] / apk
mv(source, target)
return target
@@ -435,7 +413,7 @@ def build_apk(module: str):
def build_app():
header("* Building the Magisk app")
apk = build_apk(":app:apk")
apk = build_apk(":apk")
build_type = "release" if args.release else "debug"
@@ -454,7 +432,7 @@ def build_app():
def build_stub():
header("* Building the stub app")
apk = build_apk(":app:stub")
apk = build_apk(":stub")
header(f"Output: {apk}")
@@ -465,7 +443,7 @@ def build_test():
args.release = True
try:
header("* Building the test app")
source = build_apk(":app:test")
source = build_apk(":test")
target = source.parent / "test.apk"
mv(source, target)
header(f"Output: {target}")
@@ -479,6 +457,7 @@ def build_test():
def cleanup():
ensure_paths()
support_targets = {"native", "cpp", "rust", "app"}
if args.targets:
targets = set(args.targets) & support_targets
@@ -502,11 +481,15 @@ def cleanup():
rm(rs_gen)
if "native" in targets:
header("* Cleaning native")
rm_rf(Path("native", "out"))
rm_rf(Path("tools", "elf-cleaner", "target"))
if "app" in targets:
header("* Cleaning app")
execv([gradlew, ":app:clean"], env=find_jdk())
os.chdir("app")
execv([gradlew, ":clean"], env=find_jdk())
os.chdir("..")
def build_all():
@@ -520,6 +503,46 @@ def build_all():
############
def gen_ide():
ensure_paths()
set_archs({args.abi})
# Dump flags for both C++ and Rust code
dump_flag_header()
# Run build.rs to generate Rust/C++ FFI bindings
os.chdir(Path("native", "src"))
run_cargo(["check"])
os.chdir(Path("..", ".."))
# Generate compilation database
rm_rf(Path("native", "compile_commands.json"))
run_ndk_build(
[
"B_MAGISK=1",
"B_INIT=1",
"B_BOOT=1",
"B_POLICY=1",
"B_PRELOAD=1",
"B_PROP=1",
"B_CRT0=1",
"compile_commands.json",
]
)
def clippy_cli():
args.force_out = True
set_archs(default_archs)
os.chdir(Path("native", "src"))
cmds = ["clippy", "--no-deps", "--target"]
for triple in build_abis.values():
run_cargo(cmds + [triple])
run_cargo(cmds + [triple, "--release"])
os.chdir(Path("..", ".."))
def cargo_cli():
args.force_out = True
if len(args.commands) >= 1 and args.commands[0] == "--":
@@ -530,10 +553,10 @@ def cargo_cli():
def setup_ndk():
ndk_ver = config["ondkVersion"]
url = f"https://github.com/topjohnwu/ondk/releases/download/{ndk_ver}/ondk-{ndk_ver}-{os_name}.tar.xz"
ensure_paths()
url = f"https://github.com/topjohnwu/ondk/releases/download/{ondk_version}/ondk-{ondk_version}-{os_name}.tar.xz"
ndk_archive = url.split("/")[-1]
ondk_path = Path(ndk_root, f"ondk-{ndk_ver}")
ondk_path = Path(ndk_root, f"ondk-{ondk_version}")
header(f"* Downloading and extracting {ndk_archive}")
rm_rf(ondk_path)
@@ -548,14 +571,54 @@ def setup_ndk():
mv(ondk_path, ndk_path)
def setup_rustup():
wrapper_dir = Path(args.wrapper_dir)
rm_rf(wrapper_dir)
wrapper_dir.mkdir(mode=0o755, parents=True, exist_ok=True)
if "CARGO_HOME" in os.environ:
cargo_home = Path(os.environ["CARGO_HOME"])
else:
cargo_home = Path.home() / ".cargo"
cargo_bin = cargo_home / "bin"
for src in cargo_bin.iterdir():
tgt = wrapper_dir / src.name
tgt.symlink_to(f"rustup{EXE_EXT}")
# Build rustup-wrapper
wrapper_src = Path("tools", "rustup-wrapper")
cargo_toml = wrapper_src / "Cargo.toml"
cmds = ["build", "--release", f"--manifest-path={cargo_toml}"]
if args.verbose > 1:
cmds.append("--verbose")
run_cargo(cmds)
# Replace rustup with wrapper
wrapper = wrapper_dir / (f"rustup{EXE_EXT}")
wrapper.unlink(missing_ok=True)
cp(wrapper_src / "target" / "release" / (f"rustup-wrapper{EXE_EXT}"), wrapper)
wrapper.chmod(0o755)
##################
# AVD and testing
##################
def push_files(script):
if args.build:
build_all()
ensure_adb()
abi = cmd_out([adb_path, "shell", "getprop", "ro.product.cpu.abi"])
if not abi:
error("Cannot detect emulator ABI")
apk = Path(
config["outdir"], ("app-release.apk" if args.release else "app-debug.apk")
)
if args.apk:
apk = Path(args.apk)
else:
apk = Path(
config["outdir"], ("app-release.apk" if args.release else "app-debug.apk")
)
# Extract busybox from APK
busybox = Path(config["outdir"], "busybox")
@@ -577,9 +640,6 @@ def push_files(script):
def setup_avd():
if not args.skip:
build_all()
header("* Setting up emulator")
push_files(Path("scripts", "avd_magisk.sh"))
@@ -590,17 +650,8 @@ def setup_avd():
def patch_avd_file():
if not args.skip:
build_all()
input = Path(args.image)
if args.output:
output = Path(args.output)
else:
output = input.parent / f"{input.name}.magisk"
src_file = f"/data/local/tmp/{input.name}"
out_file = f"{src_file}.magisk"
output = Path(args.output)
header(f"* Patching {input.name}")
@@ -610,6 +661,9 @@ def patch_avd_file():
if proc.returncode != 0:
error("adb push failed!")
src_file = f"/data/local/tmp/{input.name}"
out_file = f"{src_file}.magisk"
proc = execv([adb_path, "shell", "sh", "/data/local/tmp/avd_patch.sh", src_file])
if proc.returncode != 0:
error("avd_patch.sh failed!")
@@ -621,37 +675,47 @@ def patch_avd_file():
header(f"Output: {output}")
def setup_rustup():
wrapper_dir = Path(args.wrapper_dir)
rm_rf(wrapper_dir)
wrapper_dir.mkdir(mode=0o755, parents=True, exist_ok=True)
if "CARGO_HOME" in os.environ:
cargo_home = Path(os.environ["CARGO_HOME"])
else:
cargo_home = Path.home() / ".cargo"
cargo_bin = cargo_home / "bin"
for src in cargo_bin.iterdir():
tgt = wrapper_dir / src.name
tgt.symlink_to(src)
# Build rustup_wrapper
wrapper_src = Path("tools", "rustup_wrapper")
cargo_toml = wrapper_src / "Cargo.toml"
cmds = ["build", "--release", f"--manifest-path={cargo_toml}"]
if args.verbose > 1:
cmds.append("--verbose")
run_cargo(cmds)
# Replace rustup with wrapper
wrapper = wrapper_dir / (f"rustup{EXE_EXT}")
wrapper.unlink(missing_ok=True)
cp(wrapper_src / "target" / "release" / (f"rustup_wrapper{EXE_EXT}"), wrapper)
wrapper.chmod(0o755)
##########################
# Config, paths, argparse
##########################
##################
# Config and args
##################
def ensure_paths():
global sdk_path, ndk_root, ndk_path, ndk_build, rust_sysroot
global llvm_bin, gradlew, adb_path, native_gen_path
# Skip if already initialized
if "sdk_path" in globals():
return
try:
sdk_path = Path(os.environ["ANDROID_HOME"])
except KeyError:
try:
sdk_path = Path(os.environ["ANDROID_SDK_ROOT"])
except KeyError:
error("Please set Android SDK path to environment variable ANDROID_HOME")
ndk_root = sdk_path / "ndk"
ndk_path = ndk_root / "magisk"
ndk_build = ndk_path / "ndk-build"
rust_sysroot = ndk_path / "toolchains" / "rust"
llvm_bin = (
ndk_path / "toolchains" / "llvm" / "prebuilt" / f"{os_name}-x86_64" / "bin"
)
adb_path = sdk_path / "platform-tools" / "adb"
gradlew = Path.cwd() / "app" / "gradlew"
# We allow using several functionality with only ADB
def ensure_adb():
global adb_path
if "adb_path" not in globals():
adb_path = shutil.which("adb")
if not adb_path:
error("Command 'adb' cannot be found in PATH")
else:
adb_path = Path(adb_path)
def parse_props(file):
@@ -671,6 +735,12 @@ def parse_props(file):
return props
def set_archs(archs: set):
triples = map(support_abis.get, archs)
global build_abis
build_abis = dict(zip(archs, triples))
def load_config():
commit_hash = cmd_out(["git", "rev-parse", "--short=8", "HEAD"])
@@ -685,8 +755,9 @@ def load_config():
if args.config.exists():
config.update(parse_props(args.config))
if Path("gradle.properties").exists():
for key, value in parse_props("gradle.properties").items():
gradle_props = Path("app", "gradle.properties")
if gradle_props.exists():
for key, value in parse_props(gradle_props).items():
if key.startswith("magisk."):
config[key[7:]] = value
@@ -702,12 +773,9 @@ def load_config():
abiList = re.split("\\s*,\\s*", config["abiList"])
archs = set(abiList) & support_abis.keys()
else:
archs = {"armeabi-v7a", "x86", "arm64-v8a", "x86_64"}
archs = default_archs
triples = map(support_abis.get, archs)
global build_abis
build_abis = dict(zip(archs, triples))
set_archs(archs)
def parse_args():
@@ -751,17 +819,19 @@ def parse_args():
ndk_parser = subparsers.add_parser("ndk", help="setup Magisk NDK")
emu_parser = subparsers.add_parser("emulator", help="setup AVD for development")
emu_parser.add_argument("apk", help="a Magisk APK to use", nargs="?")
emu_parser.add_argument(
"-s", "--skip", action="store_true", help="skip building binaries and the app"
"-b", "--build", action="store_true", help="build before patching"
)
avd_patch_parser = subparsers.add_parser(
"avd_patch", help="patch AVD ramdisk.img or init_boot.img"
)
avd_patch_parser.add_argument("image", help="path to ramdisk.img or init_boot.img")
avd_patch_parser.add_argument("output", help="optional output file name", nargs="?")
avd_patch_parser.add_argument("output", help="output file name")
avd_patch_parser.add_argument("--apk", help="a Magisk APK to use")
avd_patch_parser.add_argument(
"-s", "--skip", action="store_true", help="skip building binaries and the app"
"-b", "--build", action="store_true", help="build before patching"
)
cargo_parser = subparsers.add_parser(
@@ -769,16 +839,23 @@ def parse_args():
)
cargo_parser.add_argument("commands", nargs=argparse.REMAINDER)
clippy_parser = subparsers.add_parser("clippy", help="run clippy on Rust sources")
rustup_parser = subparsers.add_parser("rustup", help="setup rustup wrapper")
rustup_parser.add_argument(
"wrapper_dir", help="path to setup rustup wrapper binaries"
)
gen_parser = subparsers.add_parser("gen", help="generate files for IDE")
gen_parser.add_argument("--abi", default="arm64-v8a", help="target ABI to generate")
# Set callbacks
all_parser.set_defaults(func=build_all)
native_parser.set_defaults(func=build_native)
cargo_parser.set_defaults(func=cargo_cli)
clippy_parser.set_defaults(func=clippy_cli)
rustup_parser.set_defaults(func=setup_rustup)
gen_parser.set_defaults(func=gen_ide)
app_parser.set_defaults(func=build_app)
stub_parser.set_defaults(func=build_stub)
test_parser.set_defaults(func=build_test)
@@ -794,7 +871,13 @@ def parse_args():
return parser.parse_args()
args = parse_args()
load_config()
vars(args)["force_out"] = False
args.func()
def main():
global args
args = parse_args()
load_config()
vars(args)["force_out"] = False
args.func()
if __name__ == "__main__":
main()

View File

@@ -40,12 +40,9 @@
### Developing Rust
The Magisk NDK package [ONDK](https://github.com/topjohnwu/ondk) (the one installed with `./build.py ndk`) bundles a complete Rust toolchain, so _building_ the Magisk project itself does not require any further configuration. However, if you'd like to work on the Rust codebase with proper support, you'd need some setup as most development tools are built around `rustup`.
First, install [rustup](https://www.rust-lang.org/tools/install), the official Rust toolchain manager. The Magisk NDK package [ONDK](https://github.com/topjohnwu/ondk) (the one installed with `./build.py ndk`) bundles a complete Rust toolchain, so _building_ the Magisk project itself does not require any further configuration.
Let's first setup `rustup` to use our custom ONDK Rust toolchain by default:
- Install [rustup](https://rustup.rs/), the official Rust toolchain manager
- Link the ONDK Rust toolchain and set it as default:
However, if you'd like to work on the Rust codebase, it'll be easier if you link ONDK's Rust toolchain in `rustup` and set it as default so several development tools and IDEs will work properly:
```bash
# Link the ONDK toolchain with the name "magisk"
@@ -54,7 +51,7 @@ rustup toolchain link magisk "$ANDROID_HOME/ndk/magisk/toolchains/rust"
rustup default magisk
```
If you plan to use VSCode, you can then install the [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer) plugin and everything should be good to go. If you plan to use Jetbrain IDEs (e.g. [Rustrover](https://www.jetbrains.com/rust/), or its Rust Plugin), due to its poor support with custom toolchains, we need some additional setup:
If you plan to use VSCode, you can then install the [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer) plugin and everything should be good to go. If you plan to use Jetbrain IDEs (e.g. [Rustrover](https://www.jetbrains.com/rust/), or its Rust Plugin), we need some additional setup:
- Install the official nightly toolchain and add some components. We won't actually use the nightly toolchain for anything other than tricking the IDE to cooperate; the magic happens in the wrapper we setup in the next step.

View File

@@ -1,5 +1,14 @@
# Magisk Changelog
### v29.0
- [General] Massive internal refactoring and code migration
- [App] Support downloading module zip files with XZ compression
- [App] Disable app animations when system animations are disabled
- [MagiskMount] Support systemlessly deleting files with modules using blank file nodes
- [MagiskInit] Redesign sepolicy patching and injection logic
- [MagiskSU] Better TTY/PTY support
### v28.1
- [App] Fix stub APK download link

View File

@@ -7,9 +7,10 @@ If you have USB debugging enabled in developer options, connect your phone to th
If unfortunately you do not have USB debugging enabled you can boot using the Safe Mode key combo to cause Magisk to create an empty file named 'disable' in modules directories which disables modules when next booted with Magisk. Most modern Android devices support such a special key combo at boot to enter system Safe Mode as an emergency option, but **please note** that Magisk's key combo detection occurs _earlier_ than system detection so the key combo timing indicated by many online guides may need to be altered to activate Magisk's Safe Mode. (It's possible to activate system Safe Mode but not Magisk Safe Mode and vice versa.)
The following details should ensure that modules are properly disabled:
1) Many online guides for entering Safe Mode say 'When the animated logo appears, press and hold the volume down button until the system boots' or similar. This may actually be _too late_ for Magisk detection however and result in activating system Safe Mode but modules are not disabled.
2) By pressing the volume down button some seconds before the animation and releasing it as soon as the boot animation appears, Magisk's Safe Mode should be activated without activating system Safe Mode (thus avoiding disabling other device and app settings) and the device should then simply boot to normal system with modules disabled.
3) By pressing the volume down button some seconds before the animation and holding it until the system boots, both Magisk's Safe Mode and system Safe Mode should be activated. Next, after booting back to normal system, modules will be disabled.
1. Many online guides for entering Safe Mode say 'When the animated logo appears, press and hold the volume down button until the system boots' or similar. This may actually be _too late_ for Magisk detection however and result in activating system Safe Mode but modules are not disabled.
2. By pressing the volume down button some seconds before the animation and releasing it as soon as the boot animation appears, Magisk's Safe Mode should be activated without activating system Safe Mode (thus avoiding disabling other device and app settings) and the device should then simply boot to normal system with modules disabled.
3. By pressing the volume down button some seconds before the animation and holding it until the system boots, both Magisk's Safe Mode and system Safe Mode should be activated. Next, after booting back to normal system, modules will be disabled.
### Q: Why is X app detecting root?
@@ -20,3 +21,9 @@ Magisk no longer handles root hiding. There are plenty of Magisk/Zygisk modules
When hiding the Magisk app, it will install a "stub" APK that has nothing in it. The only functionality this stub app has is downloading the full Magisk app APK into its internal storage and dynamically loading it. Due to the fact that the APK is literally _empty_, it does not contain the image resource for the app icon.
When you open the hidden Magisk app, it will offer you the option to create a shortcut in the homescreen (which has both the correct app name and icon) for your convenience. You can also manually ask the app to create the icon in app settings.
### Q: How to use Magisk in the emulator?
With the emulator running and accessible via ADB, run `./build.py emulator <path to Magisk APK>` to temporarily install Magisk on to the emulator. The patch is not persistent, meaning Magisk will be lost after a reboot, so re-execute the script to emulate a reboot if required.
The script is only tested on the official Android Virtual Device (AVD) shipped alongside Android Studio; other emulators may work, but the emulator must have SELinux enabled.

View File

@@ -76,7 +76,7 @@ As a summary, after installing Magisk in recovery **(starting from power off)**:
Before proceeding, please acknowledge that:
- Installing Magisk **WILL** trip your Knox Warranty Bit, this action is not reversible in any way.
- Installing Magisk for the first time **REQUIRES** a full data wipe (this is **NOT** counting the data wipe when unlocking bootloader). Please make a backup your data.
- Installing Magisk for the first time **REQUIRES** a full data wipe (this is **NOT** counting the data wipe when unlocking bootloader). Please make a backup of your data.
### Flashing Tools
@@ -86,7 +86,7 @@ Before proceeding, please acknowledge that:
### Requirements
To verify whether or not Magisk can be installed in your Samsung device, you first must check the OEM Lock and KnoxGuard (RMM) status, to do so boot your device in Download mode with its key combo.
To verify whether or not Magisk can be installed in your Samsung device, you first must check the OEM Lock and KnoxGuard (RMM) status. To do so, boot your device in Download mode with its key combo.
Possible OEM Lock values are the following:
- **ON (L)**: fully locked.

16
docs/releases/29000.md Normal file
View File

@@ -0,0 +1,16 @@
## 2025.5.14 Magisk v29.0
This release looks minor at the surface, however, the entire codebase has gone through significant refactoring and migration. The native code in Magisk used to be mainly C++, but several contributors and I have been steadily rewriting parts of the code in Rust since April 2022. After years of effort, the Rust-ification of the project slowly began picking up steam, and at the moment of this release, over 40% of the native code has been rewritten in Rust, with several major subsystem rewrites in the PR queue, planned to be merged for the next release.
Many might wonder, why introduce a new language to the project? My reason is actually not to reduce memory safety issues (although it is a nice side benefit), but to be able to develop Magisk using a more modern programming language. After using Rust for a while, it's clear to me that using Rust allows me to write more correct code and makes me happier compared to dealing with C++. People share the [same sentiment as I do](https://threadreaderapp.com/thread/1577667445719912450.html).
## Changelog
- [General] Massive internal refactoring and code migration
- [App] Support downloading module zip files with XZ compression
- [App] Disable app animations when system animations are disabled
- [MagiskMount] Support systemlessly deleting files with modules using blank file nodes
- [MagiskInit] Redesign sepolicy patching and injection logic
- [MagiskSU] Better TTY/PTY support
### Full Changelog: [here](https://topjohnwu.github.io/Magisk/changes.html)

View File

@@ -1,5 +1,6 @@
# Release Notes
- [v29.0](29000.md)
- [v28.1](28100.md)
- [v28.0](28000.md)
- [v27.0](27000.md)

1
native/.gitignore vendored
View File

@@ -5,3 +5,4 @@ libs
/.cxx
*-rs.cpp
*-rs.hpp
/compile_commands.json

View File

@@ -8,5 +8,5 @@ target-dir = "../out/rust"
[unstable]
build-std = ["std", "panic_abort"]
build-std-features = ["panic_immediate_abort"]
build-std-features = ["panic_immediate_abort", "optimize_for_size"]
profile-rustflags = true

View File

@@ -18,10 +18,7 @@ LOCAL_SRC_FILES := \
core/applets.cpp \
core/magisk.cpp \
core/daemon.cpp \
core/bootstages.cpp \
core/socket.cpp \
core/scripting.cpp \
core/selinux.cpp \
core/sqlite.cpp \
core/module.cpp \
core/thread.cpp \
@@ -29,10 +26,7 @@ LOCAL_SRC_FILES := \
core/resetprop/resetprop.cpp \
core/su/su.cpp \
core/su/connect.cpp \
core/su/pts.cpp \
core/su/su_daemon.cpp \
core/zygisk/entry.cpp \
core/zygisk/main.cpp \
core/zygisk/module.cpp \
core/zygisk/hook.cpp \
core/deny/cli.cpp \
@@ -67,18 +61,16 @@ LOCAL_STATIC_LIBRARIES := \
libinit-rs
LOCAL_SRC_FILES := \
init/init.cpp \
init/mount.cpp \
init/rootdir.cpp \
init/getinfo.cpp \
init/twostage.cpp \
init/selinux.cpp \
init/init-rs.cpp
LOCAL_LDFLAGS := -static
ifdef B_CRT0
LOCAL_STATIC_LIBRARIES += crt0
LOCAL_LDFLAGS += -Wl,--defsym=vfprintf=tiny_vfprintf
endif
include $(BUILD_EXECUTABLE)
@@ -93,8 +85,6 @@ LOCAL_STATIC_LIBRARIES := \
libbase \
liblzma \
liblz4 \
libbz2 \
libz \
libzopfli \
libboot-rs
@@ -125,8 +115,6 @@ LOCAL_STATIC_LIBRARIES := \
libpolicy \
libpolicy-rs
LOCAL_SRC_FILES := sepolicy/main.cpp
include $(BUILD_EXECUTABLE)
endif

View File

@@ -5,7 +5,6 @@ APP_STL := none
APP_PLATFORM := android-23
APP_THIN_ARCHIVE := true
APP_STRIP_MODE := none
APP_SUPPORT_FLEXIBLE_PAGE_SIZES := true
ifdef MAGISK_DEBUG

321
native/src/Cargo.lock generated
View File

@@ -10,17 +10,20 @@ checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
[[package]]
name = "argh"
version = "0.1.12"
source = "git+https://github.com/google/argh.git?rev=1c632b046d084e7bde86b82dfc969b30b4647c8c#1c632b046d084e7bde86b82dfc969b30b4647c8c"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34ff18325c8a36b82f992e533ece1ec9f9a9db446bd1c14d4f936bac88fcd240"
dependencies = [
"argh_derive",
"argh_shared",
"rust-fuzzy-search",
]
[[package]]
name = "argh_derive"
version = "0.1.12"
source = "git+https://github.com/google/argh.git?rev=1c632b046d084e7bde86b82dfc969b30b4647c8c#1c632b046d084e7bde86b82dfc969b30b4647c8c"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adb7b2b83a50d329d5d8ccc620f5c7064028828538bdf5646acd60dc1f767803"
dependencies = [
"argh_shared",
"proc-macro2",
@@ -30,11 +33,9 @@ dependencies = [
[[package]]
name = "argh_shared"
version = "0.1.12"
source = "git+https://github.com/google/argh.git?rev=1c632b046d084e7bde86b82dfc969b30b4647c8c#1c632b046d084e7bde86b82dfc969b30b4647c8c"
dependencies = [
"serde",
]
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a464143cc82dedcdc3928737445362466b7674b5db4e2eb8e869846d6d84f4f6"
[[package]]
name = "autocfg"
@@ -66,9 +67,9 @@ checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf"
[[package]]
name = "base64ct"
version = "1.6.0"
version = "1.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
checksum = "89e25b6adfb930f02d1981565a6e5d9c547ac15a96606256d3b59040e5cd4ca3"
[[package]]
name = "bit-set"
@@ -96,18 +97,18 @@ dependencies = [
[[package]]
name = "bytemuck"
version = "1.20.0"
version = "1.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b37c88a63ffd85d15b406896cc343916d7cf57838a847b3a6f2ca5d39a5695a"
checksum = "b6b1fc10dbac614ebc03540c9dbd60e83887fda27794998c6528f1782047d540"
dependencies = [
"bytemuck_derive",
]
[[package]]
name = "bytemuck_derive"
version = "1.8.0"
version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bcfcc3cd946cb52f0bbfdbbcfa2f4e24f75ebb6c0e1002f7c25904fada18b9ec"
checksum = "7ecc273b49b3205b83d648f0690daa588925572cc5063745bfe547fe7ec8e1a1"
dependencies = [
"proc-macro2",
"quote",
@@ -122,9 +123,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "cc"
version = "1.2.3"
version = "1.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27f657647bcff5394bf56c7317665bbf790a137a50eaaa5c6bfbb9e27a518f2d"
checksum = "8e3a13707ac958681c13b39b458c073d0d9bc8a22cb1b2f4c8e55eb72c13f362"
dependencies = [
"shlex",
]
@@ -137,18 +138,18 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "4.5.23"
version = "4.5.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84"
checksum = "d8aa86934b44c19c50f87cc2790e19f54f7a67aedb64101c2e1a2e5ecfb73944"
dependencies = [
"clap_builder",
]
[[package]]
name = "clap_builder"
version = "4.5.23"
version = "4.5.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838"
checksum = "2414dbb2dd0695280da6ea9261e327479e9d37b0630f6b53ba2a11c60c679fd9"
dependencies = [
"anstyle",
"clap_lex",
@@ -173,9 +174,9 @@ dependencies = [
[[package]]
name = "const-oid"
version = "0.10.0-rc.3"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68ff6be19477a1bd5441f382916a89bc2a0b2c35db6d41e0f6e8538bf6d6463f"
checksum = "0dabb6555f92fb9ee4140454eb5dcd14c7960e1225c6d1a6cc361f032947713e"
[[package]]
name = "const_format"
@@ -199,22 +200,23 @@ dependencies = [
[[package]]
name = "cpufeatures"
version = "0.2.16"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3"
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
dependencies = [
"libc",
]
[[package]]
name = "crypto-bigint"
version = "0.6.0-rc.6"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d748d1f5b807ee6d0df5a548d0130417295c3aaed1dcbbb3d6a2e7106e11fcca"
checksum = "96272c2ff28b807e09250b180ad1fb7889a3258f7455759b5c3c58b719467130"
dependencies = [
"hybrid-array",
"num-traits",
"rand_core",
"serdect",
"subtle",
"zeroize",
]
@@ -230,9 +232,19 @@ dependencies = [
"rand_core",
]
[[package]]
name = "crypto-primes"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2acbaf157961745008b5a80ee1cc974150691304fe9177edf69747142bfd9878"
dependencies = [
"crypto-bigint",
"rand_core",
]
[[package]]
name = "cxx"
version = "1.0.133"
version = "1.0.137"
dependencies = [
"cc",
"cxxbridge-cmd",
@@ -243,7 +255,7 @@ dependencies = [
[[package]]
name = "cxx-gen"
version = "0.7.133"
version = "0.7.137"
dependencies = [
"codespan-reporting",
"proc-macro2",
@@ -253,7 +265,7 @@ dependencies = [
[[package]]
name = "cxxbridge-cmd"
version = "1.0.133"
version = "1.0.137"
dependencies = [
"clap",
"codespan-reporting",
@@ -264,11 +276,11 @@ dependencies = [
[[package]]
name = "cxxbridge-flags"
version = "1.0.133"
version = "1.0.137"
[[package]]
name = "cxxbridge-macro"
version = "1.0.133"
version = "1.0.137"
dependencies = [
"proc-macro2",
"quote",
@@ -300,6 +312,15 @@ dependencies = [
"syn",
]
[[package]]
name = "derive"
version = "0.0.0"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "digest"
version = "0.11.0-pre.9"
@@ -355,9 +376,9 @@ checksum = "784a4df722dc6267a04af36895398f59d21d07dce47232adf31ec0ff2fa45e67"
[[package]]
name = "ff"
version = "0.13.0"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449"
checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393"
dependencies = [
"rand_core",
"subtle",
@@ -365,15 +386,15 @@ dependencies = [
[[package]]
name = "flagset"
version = "0.4.6"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3ea1ec5f8307826a5b71094dd91fc04d4ae75d5709b20ad351c7fb4815c86ec"
checksum = "b7ac824320a75a52197e8f2d787f6a38b6718bb6897a35142d749af3c0e8f4fe"
[[package]]
name = "foldhash"
version = "0.1.3"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
name = "getrandom"
@@ -426,31 +447,34 @@ dependencies = [
]
[[package]]
name = "lazy_static"
version = "1.5.0"
name = "libbz2-rs-sys"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
checksum = "0864a00c8d019e36216b69c2c4ce50b83b7bd966add3cf5ba554ec44f8bebcf5"
dependencies = [
"spin",
"libc",
]
[[package]]
name = "libc"
version = "0.2.168"
version = "0.2.171"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d"
checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6"
[[package]]
name = "libm"
version = "0.2.11"
name = "libz-rs-sys"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa"
checksum = "6489ca9bd760fe9642d7644e827b0c9add07df89857b0416ee15c1cc1a3b8c5a"
dependencies = [
"zlib-rs",
]
[[package]]
name = "log"
version = "0.4.22"
version = "0.4.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
[[package]]
name = "magisk"
@@ -461,6 +485,7 @@ dependencies = [
"bytemuck",
"cxx",
"cxx-gen",
"derive",
"num-derive",
"num-traits",
"pb-rs",
@@ -474,6 +499,7 @@ version = "0.0.0"
dependencies = [
"argh",
"base",
"block-buffer",
"bytemuck",
"byteorder",
"cxx",
@@ -481,6 +507,8 @@ dependencies = [
"der",
"digest",
"fdt",
"libbz2-rs-sys",
"libz-rs-sys",
"num-traits",
"p256",
"p384",
@@ -488,6 +516,7 @@ dependencies = [
"pb-rs",
"quick-protobuf",
"rsa",
"sec1",
"sha1",
"sha2",
"size",
@@ -508,6 +537,7 @@ dependencies = [
name = "magiskpolicy"
version = "0.0.0"
dependencies = [
"argh",
"base",
"cxx",
"cxx-gen",
@@ -535,23 +565,6 @@ dependencies = [
"minimal-lexical",
]
[[package]]
name = "num-bigint-dig"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151"
dependencies = [
"byteorder",
"lazy_static",
"libm",
"num-integer",
"num-iter",
"num-traits",
"rand",
"smallvec",
"zeroize",
]
[[package]]
name = "num-derive"
version = "0.4.2"
@@ -563,26 +576,6 @@ dependencies = [
"syn",
]
[[package]]
name = "num-integer"
version = "0.1.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
dependencies = [
"num-traits",
]
[[package]]
name = "num-iter"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.19"
@@ -590,7 +583,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
"libm",
]
[[package]]
@@ -635,7 +627,7 @@ dependencies = [
[[package]]
name = "pb-rs"
version = "0.10.0"
source = "git+https://github.com/tafia/quick-protobuf.git?rev=2f37d5a65504de7d716b5b28fd82219501a901a9#2f37d5a65504de7d716b5b28fd82219501a901a9"
source = "git+https://github.com/tafia/quick-protobuf.git#54e7d6c5d981c6f7cec2e9a2167c10ed0f9392b4"
dependencies = [
"log",
"nom",
@@ -663,23 +655,14 @@ dependencies = [
[[package]]
name = "pkcs8"
version = "0.11.0-rc.1"
version = "0.11.0-rc.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eacd2c7141f32aef1cfd1ad0defb5287a3d94592d7ab57c1ae20e3f9f1f0db1f"
checksum = "f22636de7c995e997ed3d8d2949b7414d4faba3efa7312a6c0e75d875a14bdd4"
dependencies = [
"der",
"spki",
]
[[package]]
name = "ppv-lite86"
version = "0.2.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
dependencies = [
"zerocopy",
]
[[package]]
name = "primefield"
version = "0.14.0-pre.0"
@@ -697,9 +680,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.92"
version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84"
dependencies = [
"unicode-ident",
]
@@ -707,40 +690,20 @@ dependencies = [
[[package]]
name = "quick-protobuf"
version = "0.8.1"
source = "git+https://github.com/tafia/quick-protobuf.git?rev=2f37d5a65504de7d716b5b28fd82219501a901a9#2f37d5a65504de7d716b5b28fd82219501a901a9"
source = "git+https://github.com/tafia/quick-protobuf.git#54e7d6c5d981c6f7cec2e9a2167c10ed0f9392b4"
dependencies = [
"byteorder",
]
[[package]]
name = "quote"
version = "1.0.37"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.4"
@@ -762,15 +725,14 @@ dependencies = [
[[package]]
name = "rsa"
version = "0.10.0-pre.3"
version = "0.10.0-pre.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07058e83b684989ab0559f9e22322f4e3f7e49147834ed0bae40486b9e70473c"
checksum = "e82e90f434676d49758cab5b3ff2cc1fd8f12bf7d79b70c6088bc5a4e7c63270"
dependencies = [
"const-oid",
"crypto-bigint",
"crypto-primes",
"digest",
"num-bigint-dig",
"num-integer",
"num-traits",
"pkcs1",
"pkcs8",
"rand_core",
@@ -782,10 +744,16 @@ dependencies = [
]
[[package]]
name = "rustversion"
version = "1.0.18"
name = "rust-fuzzy-search"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248"
checksum = "a157657054ffe556d8858504af8a672a054a6e0bd9e8ee531059100c0fa11bb2"
[[package]]
name = "rustversion"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2"
[[package]]
name = "sec1"
@@ -803,24 +771,34 @@ dependencies = [
[[package]]
name = "serde"
version = "1.0.215"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f"
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.215"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0"
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serdect"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f42f67da2385b51a5f9652db9c93d78aeaf7610bf5ec366080b6de810604af53"
dependencies = [
"base16ct",
"serde",
]
[[package]]
name = "sha1"
version = "0.11.0-pre.4"
@@ -861,21 +839,9 @@ dependencies = [
[[package]]
name = "size"
version = "0.4.1"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fed904c7fb2856d868b92464fc8fa597fce366edea1a9cbfaa8cb5fe080bd6d"
[[package]]
name = "smallvec"
version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
[[package]]
name = "spin"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
checksum = "1b6709c7b6754dca1311b3c73e79fcce40dd414c782c66d88e8823030093b02b"
[[package]]
name = "spki"
@@ -901,9 +867,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "syn"
version = "2.0.90"
version = "2.0.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31"
checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0"
dependencies = [
"proc-macro2",
"quote",
@@ -921,18 +887,18 @@ dependencies = [
[[package]]
name = "thiserror"
version = "2.0.6"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fec2a1820ebd077e2b90c4df007bebf344cd394098a13c563957d0afc83ea47"
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "2.0.6"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d65750cab40f4ff1929fb1ba509e9914eb756131cef4210da8d5d700d26f6312"
checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
dependencies = [
"proc-macro2",
"quote",
@@ -941,9 +907,9 @@ dependencies = [
[[package]]
name = "tls_codec"
version = "0.4.1"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e78c9c330f8c85b2bae7c8368f2739157db9991235123aa1b15ef9502bfb6a"
checksum = "0de2e01245e2bb89d6f05801c564fa27624dbd7b1846859876c7dad82e90bf6b"
dependencies = [
"tls_codec_derive",
"zeroize",
@@ -951,9 +917,9 @@ dependencies = [
[[package]]
name = "tls_codec_derive"
version = "0.4.1"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d9ef545650e79f30233c0003bcc2504d7efac6dad25fca40744de773fe2049c"
checksum = "2d2e76690929402faae40aebdda620a2c0e25dd6d3b9afe48867dfd95991f4bd"
dependencies = [
"proc-macro2",
"quote",
@@ -962,15 +928,15 @@ dependencies = [
[[package]]
name = "typenum"
version = "1.17.0"
version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
[[package]]
name = "unicode-ident"
version = "1.0.14"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]]
name = "unicode-width"
@@ -1084,27 +1050,6 @@ dependencies = [
"tls_codec",
]
[[package]]
name = "zerocopy"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
dependencies = [
"byteorder",
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "zeroize"
version = "1.8.1"
@@ -1124,3 +1069,9 @@ dependencies = [
"quote",
"syn",
]
[[package]]
name = "zlib-rs"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "868b928d7949e09af2f6086dfc1e01936064cc7a819253bce650d4e2a2d63ba8"

View File

@@ -1,45 +1,53 @@
[workspace]
exclude = ["external"]
members = ["base", "boot", "core", "init", "sepolicy"]
members = ["base", "boot", "core", "core/derive", "init", "sepolicy"]
resolver = "2"
[workspace.package]
version = "0.0.0"
edition = "2024"
[workspace.dependencies]
cxx = { path = "external/cxx-rs" }
cxx-gen = { path = "external/cxx-rs/gen/lib" }
libc = "0.2"
cfg-if = "1.0"
num-traits = "0.2"
num-derive = "0.4"
thiserror = "2.0"
byteorder = "1"
size = "0.4"
sha1 = "0.11.0-pre.4"
libc = "0.2.171"
cfg-if = "1.0.0"
num-traits = "0.2.19"
num-derive = "0.4.2"
thiserror = "2.0.12"
byteorder = "1.5.0"
size = "0.5.0"
bytemuck = "1.22.0"
fdt = "0.1.5"
const_format = "0.2.34"
bit-set = "0.8.0"
syn = "2.0.100"
quote = "1.0.40"
proc-macro2 = "1.0.94"
argh = { version = "0.1.13", default-features = false }
libz-rs-sys = { version = "0.5.0", features = ["export-symbols"] }
libbz2-rs-sys = { version = "0.1.3" }
pb-rs = { version = "0.10.0", default-features = false }
quick-protobuf = "0.8.1"
# Rust crypto crates are tied together
sha1 = "=0.11.0-pre.4"
sha2 = "=0.11.0-pre.4"
digest = "0.11.0-pre.9"
digest = "=0.11.0-pre.9"
p256 = "0.14.0-pre.2"
p384 = "0.14.0-pre.2"
p521 = "0.14.0-pre.2"
rsa = "0.10.0-pre.3"
rsa = "0.10.0-pre.4"
x509-cert = "0.3.0-pre.0"
der = "0.8.0-rc.1"
bytemuck = "1.16"
fdt = "0.1"
const_format = "0.2"
bit-set = "0.8"
[workspace.dependencies.argh]
git = "https://github.com/google/argh.git"
rev = "1c632b046d084e7bde86b82dfc969b30b4647c8c"
default-features = false
# Pin version to prevent cargo update breaking builds
block-buffer = "=0.11.0-rc.3"
sec1 = "=0.8.0-rc.3"
[workspace.dependencies.pb-rs]
git = "https://github.com/tafia/quick-protobuf.git"
rev = "2f37d5a65504de7d716b5b28fd82219501a901a9"
default-features = false
[workspace.dependencies.quick-protobuf]
git = "https://github.com/tafia/quick-protobuf.git"
rev = "2f37d5a65504de7d716b5b28fd82219501a901a9"
[patch.crates-io]
pb-rs = { git = "https://github.com/tafia/quick-protobuf.git" }
quick-protobuf = { git = "https://github.com/tafia/quick-protobuf.git" }
[profile.dev]
opt-level = "z"
@@ -51,3 +59,4 @@ opt-level = "z"
lto = "fat"
codegen-units = 1
panic = "abort"
strip = true

View File

@@ -1,7 +1,7 @@
[package]
name = "base"
version = "0.0.0"
edition = "2021"
version.workspace = true
edition.workspace = true
[lib]
path = "lib.rs"

View File

@@ -1,7 +1,7 @@
use crate::gen::gen_cxx_binding;
use crate::codegen::gen_cxx_binding;
#[path = "../include/gen.rs"]
mod gen;
#[path = "../include/codegen.rs"]
mod codegen;
fn main() {
gen_cxx_binding("base-rs");

View File

@@ -1,14 +1,14 @@
use std::cmp::min;
use cxx::{ExternType, type_id};
use libc::c_char;
use std::borrow::Borrow;
use std::cmp::{Ordering, min};
use std::ffi::{CStr, FromBytesWithNulError, OsStr};
use std::fmt::{Arguments, Debug, Display, Formatter, Write};
use std::ops::{Deref, DerefMut};
use std::fmt::{Debug, Display, Formatter, Write};
use std::ops::Deref;
use std::os::unix::ffi::OsStrExt;
use std::path::{Path, PathBuf};
use std::str::Utf8Error;
use std::{fmt, mem, slice, str};
use cxx::{type_id, ExternType};
use libc::c_char;
use thiserror::Error;
use crate::slice_from_ptr_mut;
@@ -18,88 +18,81 @@ use crate::slice_from_ptr_mut;
// Several Utf8CStr types:
//
// Utf8CStr: can only exist as reference, similar to &str
// Utf8CString: dynamically sized buffer allocated on the heap
// Utf8CString: dynamically sized buffer allocated on the heap, similar to String
// Utf8CStrBufRef: reference to a fixed sized buffer
// Utf8CStrBufArr<N>: fixed sized buffer allocated on the stack
//
// For easier usage, please use the helper functions in cstr::buf.
//
// In most cases, these are the types being used
//
// &Utf8CStr: whenever a printable null terminated string is needed
// &mut dyn Utf8CStrWrite: whenever we need a buffer that only needs to support appending
// strings to the end, and has to be null terminated
// &mut dyn Utf8CStrBuf: whenever we need a buffer that needs to support appending
// strings to the end, and has to be null terminated
// &mut dyn Utf8CStrBuf: whenever we need a pre-allocated buffer that is large enough to fit
// in the result, and has to be null terminated
//
// All types dereferences to &Utf8CStr.
// Utf8CString, Utf8CStrBufRef, and Utf8CStrBufArr<N> implements Utf8CStrWrite.
// Utf8CStrBufRef and Utf8CStrBufArr<N> implements Utf8CStrBuf.
// Utf8CString, Utf8CStrBufRef, and Utf8CStrBufArr<N> implements Utf8CStrBuf.
fn utf8_cstr_buf_append(buf: &mut dyn Utf8CStrBuf, s: &[u8]) -> usize {
let mut used = buf.len();
if used >= buf.capacity() - 1 {
// Truncate
return 0;
}
let dest = unsafe { &mut buf.mut_buf()[used..] };
let len = min(s.len(), dest.len() - 1);
if len > 0 {
dest[..len].copy_from_slice(&s[..len]);
}
dest[len] = b'\0';
used += len;
unsafe { buf.set_len(used) };
len
}
// Public helper functions
fn utf8_cstr_append_lossy(buf: &mut dyn Utf8CStrWrite, s: &[u8]) -> usize {
let mut len = 0_usize;
for chunk in s.utf8_chunks() {
len += buf.push_str(chunk.valid());
if !chunk.invalid().is_empty() {
len += buf.push_str(char::REPLACEMENT_CHARACTER.encode_utf8(&mut [0; 4]));
}
pub mod buf {
use super::{Utf8CStrBufArr, Utf8CStrBufRef, Utf8CString};
#[inline(always)]
pub fn dynamic(capacity: usize) -> Utf8CString {
Utf8CString::with_capacity(capacity)
}
#[inline(always)]
pub fn default() -> Utf8CStrBufArr<4096> {
Utf8CStrBufArr::default()
}
#[inline(always)]
pub fn new<const N: usize>() -> Utf8CStrBufArr<N> {
Utf8CStrBufArr::new()
}
#[inline(always)]
pub fn wrap(buf: &mut [u8]) -> Utf8CStrBufRef {
Utf8CStrBufRef::from(buf)
}
#[inline(always)]
pub unsafe fn wrap_ptr<'a>(buf: *mut u8, len: usize) -> Utf8CStrBufRef<'a> {
unsafe { Utf8CStrBufRef::from_ptr(buf, len) }
}
len
}
// Trait definitions
pub trait Utf8CStrWrite:
Write + AsRef<Utf8CStr> + AsMut<Utf8CStr> + Deref<Target = Utf8CStr> + DerefMut
{
pub trait Utf8CStrBuf: Write + AsRef<Utf8CStr> + Deref<Target = Utf8CStr> {
// The length of the string without the terminating null character.
// assert_true(len <= capacity - 1)
fn len(&self) -> usize;
// Set the length of the string
//
// It is your responsibility to:
// 1. Null terminate the string by setting the next byte after len to null
// 2. Ensure len <= capacity - 1
// 3. All bytes from 0 to len is valid UTF-8 and does not contain null
unsafe fn set_len(&mut self, len: usize);
fn push_str(&mut self, s: &str) -> usize;
// The capacity of the internal buffer. The maximum string length this buffer can contain
// is capacity - 1, because the last byte is reserved for the terminating null character.
fn capacity(&self) -> usize;
fn clear(&mut self);
fn as_mut_ptr(&mut self) -> *mut c_char;
fn truncate(&mut self, new_len: usize);
#[inline(always)]
fn is_empty(&self) -> bool {
self.len() == 0
}
fn push_str(&mut self, s: &str) -> usize;
fn push_lossy(&mut self, s: &[u8]) -> usize;
fn clear(&mut self);
}
pub trait Utf8CStrBuf: Utf8CStrWrite {
fn buf(&self) -> &[u8];
// Modifying the underlying buffer or length is unsafe because it can either:
// 1. Break null termination
// 2. Break UTF-8 validation
// 3. Introduce inner null byte in the string
unsafe fn mut_buf(&mut self) -> &mut [u8];
unsafe fn set_len(&mut self, len: usize);
#[inline(always)]
fn capacity(&self) -> usize {
self.buf().len()
}
}
trait AsUtf8CStr {
fn as_utf8_cstr(&self) -> &Utf8CStr;
fn as_utf8_cstr_mut(&mut self) -> &mut Utf8CStr;
}
// Implementation for Utf8CString
pub trait StringExt {
fn nul_terminate(&mut self) -> &mut [u8];
}
@@ -132,50 +125,70 @@ impl StringExt for PathBuf {
}
}
#[derive(Default)]
pub struct Utf8CString(String);
impl Utf8CString {
#[inline(always)]
pub fn new() -> Self {
Self::default()
impl Default for Utf8CString {
fn default() -> Self {
Utf8CString::with_capacity(256)
}
}
impl AsUtf8CStr for Utf8CString {
impl Utf8CString {
pub fn with_capacity(capacity: usize) -> Utf8CString {
Utf8CString::from(String::with_capacity(capacity))
}
pub fn ensure_capacity(&mut self, capacity: usize) {
if self.capacity() >= capacity {
return;
}
self.0.reserve(capacity - self.0.len())
}
}
impl AsRef<Utf8CStr> for Utf8CString {
#[inline(always)]
fn as_utf8_cstr(&self) -> &Utf8CStr {
fn as_ref(&self) -> &Utf8CStr {
// SAFETY: the internal string is always null terminated
unsafe { mem::transmute(slice::from_raw_parts(self.0.as_ptr(), self.0.len() + 1)) }
}
#[inline(always)]
fn as_utf8_cstr_mut(&mut self) -> &mut Utf8CStr {
Utf8CStr::from_string(&mut self.0)
}
}
impl Utf8CStrWrite for Utf8CString {
impl Utf8CStrBuf for Utf8CString {
#[inline(always)]
fn len(&self) -> usize {
self.0.len()
}
unsafe fn set_len(&mut self, len: usize) {
unsafe {
self.0.as_mut_vec().set_len(len);
}
}
fn push_str(&mut self, s: &str) -> usize {
self.0.push_str(s);
self.0.nul_terminate();
s.len()
}
#[inline(always)]
fn push_lossy(&mut self, s: &[u8]) -> usize {
utf8_cstr_append_lossy(self, s)
fn capacity(&self) -> usize {
self.0.capacity()
}
fn clear(&mut self) {
self.0.clear();
self.0.nul_terminate();
}
fn as_mut_ptr(&mut self) -> *mut c_char {
self.0.as_mut_ptr().cast()
}
fn truncate(&mut self, new_len: usize) {
self.0.truncate(new_len);
self.0.nul_terminate();
}
}
impl From<String> for Utf8CString {
@@ -185,24 +198,15 @@ impl From<String> for Utf8CString {
}
}
// Implementations for Utf8CStrBuf
impl<T: Utf8CStrBuf> AsUtf8CStr for T {
#[inline(always)]
fn as_utf8_cstr(&self) -> &Utf8CStr {
// SAFETY: the internal buffer is always UTF-8 checked
// SAFETY: self.used is guaranteed to always <= SIZE - 1
unsafe { Utf8CStr::from_bytes_unchecked(self.buf().get_unchecked(..(self.len() + 1))) }
impl From<&str> for Utf8CString {
fn from(value: &str) -> Self {
value.to_string().into()
}
}
#[inline(always)]
fn as_utf8_cstr_mut(&mut self) -> &mut Utf8CStr {
// SAFETY: the internal buffer is always UTF-8 checked
// SAFETY: self.used is guaranteed to always <= SIZE - 1
unsafe {
let len = self.len() + 1;
Utf8CStr::from_bytes_unchecked_mut(self.mut_buf().get_unchecked_mut(..len))
}
impl Borrow<Utf8CStr> for Utf8CString {
fn borrow(&self) -> &Utf8CStr {
self.deref()
}
}
@@ -214,7 +218,7 @@ pub struct Utf8CStrBufRef<'a> {
impl<'a> Utf8CStrBufRef<'a> {
pub unsafe fn from_ptr(buf: *mut u8, len: usize) -> Utf8CStrBufRef<'a> {
Self::from(slice_from_ptr_mut(buf, len))
unsafe { Self::from(slice_from_ptr_mut(buf, len)) }
}
}
@@ -225,23 +229,6 @@ impl<'a> From<&'a mut [u8]> for Utf8CStrBufRef<'a> {
}
}
impl Utf8CStrBuf for Utf8CStrBufRef<'_> {
#[inline(always)]
fn buf(&self) -> &[u8] {
self.buf
}
#[inline(always)]
unsafe fn mut_buf(&mut self) -> &mut [u8] {
self.buf
}
#[inline(always)]
unsafe fn set_len(&mut self, len: usize) {
self.used = len;
}
}
// UTF-8 validated + null terminated buffer on the stack
pub struct Utf8CStrBufArr<const N: usize> {
used: usize,
@@ -257,28 +244,6 @@ impl<const N: usize> Utf8CStrBufArr<N> {
}
}
impl<const N: usize> Utf8CStrBuf for Utf8CStrBufArr<N> {
#[inline(always)]
fn buf(&self) -> &[u8] {
&self.buf
}
#[inline(always)]
unsafe fn mut_buf(&mut self) -> &mut [u8] {
&mut self.buf
}
#[inline(always)]
unsafe fn set_len(&mut self, len: usize) {
self.used = len;
}
#[inline(always)]
fn capacity(&self) -> usize {
N
}
}
impl Default for Utf8CStrBufArr<4096> {
fn default() -> Self {
Utf8CStrBufArr::<4096>::new()
@@ -310,27 +275,15 @@ impl Utf8CStr {
Self::from_cstr(CStr::from_bytes_with_nul(buf)?)
}
pub fn from_bytes_mut(buf: &mut [u8]) -> Result<&mut Utf8CStr, StrErr> {
CStr::from_bytes_with_nul(buf)?;
str::from_utf8(buf)?;
// Both condition checked
unsafe { Ok(mem::transmute::<&mut [u8], &mut Utf8CStr>(buf)) }
}
pub fn from_string(s: &mut String) -> &mut Utf8CStr {
pub fn from_string(s: &mut String) -> &Utf8CStr {
let buf = s.nul_terminate();
// SAFETY: the null byte is explicitly added to the buffer
unsafe { mem::transmute(buf) }
}
#[inline(always)]
pub unsafe fn from_bytes_unchecked(buf: &[u8]) -> &Utf8CStr {
mem::transmute(buf)
}
#[inline(always)]
pub unsafe fn from_bytes_unchecked_mut(buf: &mut [u8]) -> &mut Utf8CStr {
mem::transmute(buf)
pub const unsafe fn from_bytes_unchecked(buf: &[u8]) -> &Utf8CStr {
unsafe { mem::transmute(buf) }
}
pub unsafe fn from_ptr<'a>(ptr: *const c_char) -> Result<&'a Utf8CStr, StrErr> {
@@ -341,8 +294,10 @@ impl Utf8CStr {
}
pub unsafe fn from_ptr_unchecked<'a>(ptr: *const c_char) -> &'a Utf8CStr {
let cstr = CStr::from_ptr(ptr);
Self::from_bytes_unchecked(cstr.to_bytes_with_nul())
unsafe {
let cstr = CStr::from_ptr(ptr);
Self::from_bytes_unchecked(cstr.to_bytes_with_nul())
}
}
#[inline(always)]
@@ -355,11 +310,6 @@ impl Utf8CStr {
self.0.as_ptr().cast()
}
#[inline(always)]
pub fn as_mut_ptr(&mut self) -> *mut c_char {
self.0.as_mut_ptr().cast()
}
#[inline(always)]
pub fn as_cstr(&self) -> &CStr {
// SAFETY: Already validated as null terminated during construction
@@ -372,13 +322,6 @@ impl Utf8CStr {
// SAFETY: The length of the slice is at least 1 due to null termination check
unsafe { str::from_utf8_unchecked(self.0.get_unchecked(..self.0.len() - 1)) }
}
#[inline(always)]
pub fn as_str_mut(&mut self) -> &mut str {
// SAFETY: Already UTF-8 validated during construction
// SAFETY: The length of the slice is at least 1 due to null termination check
unsafe { str::from_utf8_unchecked_mut(self.0.get_unchecked_mut(..self.0.len() - 1)) }
}
}
impl Deref for Utf8CStr {
@@ -390,10 +333,19 @@ impl Deref for Utf8CStr {
}
}
impl DerefMut for Utf8CStr {
#[inline(always)]
fn deref_mut(&mut self) -> &mut Self::Target {
self.as_str_mut()
impl ToOwned for Utf8CStr {
type Owned = Utf8CString;
fn to_owned(&self) -> Utf8CString {
let mut s = Utf8CString::with_capacity(self.len() + 1);
s.push_str(self.as_str());
s
}
}
impl AsRef<Utf8CStr> for Utf8CStr {
fn as_ref(&self) -> &Utf8CStr {
self
}
}
@@ -416,90 +368,39 @@ const_assert_eq!(align_of::<&Utf8CStr>(), align_of::<[usize; 2]>());
// File system path extensions types
#[repr(transparent)]
pub struct FsPath(Utf8CStr);
pub struct FsPathFollow(Utf8CStr);
impl FsPath {
impl AsRef<Utf8CStr> for FsPathFollow {
#[inline(always)]
pub fn from<T: AsRef<Utf8CStr> + ?Sized>(value: &T) -> &FsPath {
unsafe { mem::transmute(value.as_ref()) }
}
#[inline(always)]
pub fn from_mut<T: AsMut<Utf8CStr> + ?Sized>(value: &mut T) -> &mut FsPath {
unsafe { mem::transmute(value.as_mut()) }
}
}
impl Deref for FsPath {
type Target = Utf8CStr;
#[inline(always)]
fn deref(&self) -> &Utf8CStr {
fn as_ref(&self) -> &Utf8CStr {
&self.0
}
}
impl DerefMut for FsPath {
#[inline(always)]
fn deref_mut(&mut self) -> &mut Utf8CStr {
&mut self.0
}
}
pub struct FsPathBuf<'a>(&'a mut dyn Utf8CStrWrite);
impl<'a> FsPathBuf<'a> {
pub fn new(value: &'a mut dyn Utf8CStrWrite) -> Self {
value.clear();
FsPathBuf(value)
}
pub fn join<T: AsRef<str>>(self, path: T) -> Self {
fn inner(buf: &mut dyn Utf8CStrWrite, path: &str) {
if path.starts_with('/') {
buf.clear();
} else {
buf.push_str("/");
}
buf.push_str(path);
}
inner(self.0, path.as_ref());
self
}
pub fn join_fmt<T: Display>(self, name: T) -> Self {
fn inner(buf: &mut dyn Utf8CStrWrite, path: Arguments) {
buf.write_fmt(path).ok();
}
inner(self.0, format_args!("/{}", name));
self
}
}
impl Deref for FsPathBuf<'_> {
type Target = FsPath;
fn deref(&self) -> &FsPath {
FsPath::from(&self.0)
}
}
impl DerefMut for FsPathBuf<'_> {
fn deref_mut(&mut self) -> &mut Self::Target {
FsPath::from_mut(&mut self.0)
}
}
// Boilerplate trait implementations
macro_rules! impl_str {
// impl<T: AsRef<Utf8CStr>> Deref<Target = Utf8CStr> for T { ... }
macro_rules! impl_cstr_deref {
($( ($t:ty, $($g:tt)*) )*) => {$(
impl<$($g)*> AsRef<Utf8CStr> for $t {
impl<$($g)*> Deref for $t {
type Target = Utf8CStr;
#[inline(always)]
fn as_ref(&self) -> &Utf8CStr {
self
fn deref(&self) -> &Utf8CStr {
self.as_ref()
}
}
)*}
}
impl_cstr_deref!(
(Utf8CStrBufRef<'_>,)
(Utf8CStrBufArr<N>, const N: usize)
(Utf8CString,)
(FsPathFollow,)
);
// impl<T: Deref<Target = Utf8CStr>> BoilerPlate for T { ... }
macro_rules! impl_cstr_misc {
($( ($t:ty, $($g:tt)*) )*) => {$(
impl<$($g)*> AsRef<str> for $t {
#[inline(always)]
fn as_ref(&self) -> &str {
@@ -560,25 +461,107 @@ macro_rules! impl_str {
self == other.as_cstr()
}
}
impl<T: AsRef<Utf8CStr>, $($g)*> PartialEq<T> for $t {
impl<T: AsRef<Utf8CStr> + ?Sized, $($g)*> PartialEq<T> for $t {
#[inline(always)]
fn eq(&self, other: &T) -> bool {
self.as_bytes_with_nul() == other.as_ref().as_bytes_with_nul()
}
}
impl<$($g)*> Eq for $t {}
impl<$($g)*> PartialOrd for $t {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl<$($g)*> Ord for $t {
fn cmp(&self, other: &Self) -> Ordering {
self.as_str().cmp(other.as_str())
}
}
)*}
}
impl_str!(
impl_cstr_misc!(
(Utf8CStr,)
(FsPath,)
(FsPathBuf<'_>,)
(Utf8CStrBufRef<'_>,)
(Utf8CStrBufArr<N>, const N: usize)
(Utf8CString,)
(FsPathFollow,)
);
macro_rules! impl_str_write {
fn copy_cstr_truncate(dest: &mut [u8], src: &[u8]) -> usize {
if dest.len() <= 1 {
// Truncate
return 0;
}
let len = min(src.len(), dest.len() - 1);
if len > 0 {
dest[..len].copy_from_slice(&src[..len]);
}
dest[len] = b'\0';
len
}
// impl<T> AsRef<Utf8CStr> for T { ... }
// impl<T> Utf8CStrBuf for T { ... }
macro_rules! impl_cstr_buf {
($( ($t:ty, $($g:tt)*) )*) => {$(
impl<$($g)*> AsRef<Utf8CStr> for $t {
#[inline(always)]
fn as_ref(&self) -> &Utf8CStr {
// SAFETY: the internal buffer is always UTF-8 checked
// SAFETY: self.used is guaranteed to always <= SIZE - 1
unsafe { Utf8CStr::from_bytes_unchecked(self.buf.get_unchecked(..(self.used + 1))) }
}
}
impl<$($g)*> Utf8CStrBuf for $t {
#[inline(always)]
fn len(&self) -> usize {
self.used
}
#[inline(always)]
unsafe fn set_len(&mut self, len: usize) {
self.used = len;
}
#[inline(always)]
fn push_str(&mut self, s: &str) -> usize {
// SAFETY: self.used is guaranteed to always <= SIZE - 1
let dest = unsafe { self.buf.get_unchecked_mut(self.used..) };
let len = copy_cstr_truncate(dest, s.as_bytes());
self.used += len;
len
}
#[inline(always)]
fn capacity(&self) -> usize {
self.buf.len()
}
#[inline(always)]
fn clear(&mut self) {
self.buf[0] = b'\0';
self.used = 0;
}
#[inline(always)]
fn as_mut_ptr(&mut self) -> *mut c_char {
self.buf.as_mut_ptr().cast()
}
fn truncate(&mut self, new_len: usize) {
if self.used <= new_len {
return;
}
self.buf[new_len] = b'\0';
self.used = new_len;
}
}
)*}
}
impl_cstr_buf!(
(Utf8CStrBufRef<'_>,)
(Utf8CStrBufArr<N>, const N: usize)
);
// impl<T: Utf8CStrBuf> Write for T { ... }
macro_rules! impl_cstr_buf_write {
($( ($t:ty, $($g:tt)*) )*) => {$(
impl<$($g)*> Write for $t {
#[inline(always)]
@@ -587,84 +570,27 @@ macro_rules! impl_str_write {
Ok(())
}
}
impl<$($g)*> Deref for $t {
type Target = Utf8CStr;
#[inline(always)]
fn deref(&self) -> &Utf8CStr {
self.as_utf8_cstr()
}
}
impl<$($g)*> DerefMut for $t {
#[inline(always)]
fn deref_mut(&mut self) -> &mut Utf8CStr {
self.as_utf8_cstr_mut()
}
}
impl<$($g)*> AsMut<Utf8CStr> for $t {
#[inline(always)]
fn as_mut(&mut self) -> &mut Utf8CStr {
self.as_utf8_cstr_mut()
}
}
)*}
}
impl_str_write!(
impl_cstr_buf_write!(
(Utf8CStrBufRef<'_>,)
(Utf8CStrBufArr<N>, const N: usize)
(Utf8CString,)
);
macro_rules! impl_str_buf {
($( ($t:ty, $($g:tt)*) )*) => {$(
impl<$($g)*> Utf8CStrWrite for $t {
#[inline(always)]
fn len(&self) -> usize {
self.used
}
#[inline(always)]
fn push_str(&mut self, s: &str) -> usize {
utf8_cstr_buf_append(self, s.as_bytes())
}
#[inline(always)]
fn push_lossy(&mut self, s: &[u8]) -> usize {
utf8_cstr_append_lossy(self, s)
}
#[inline(always)]
fn clear(&mut self) {
self.buf[0] = b'\0';
self.used = 0;
}
}
)*}
}
impl_str_buf!(
(Utf8CStrBufRef<'_>,)
(Utf8CStrBufArr<N>, const N: usize)
);
// The cstr! macro is copied from https://github.com/bytecodealliance/rustix/blob/main/src/cstr.rs
#[macro_export]
macro_rules! cstr {
($($str:tt)*) => {{
assert!(
!($($str)*).bytes().any(|b| b == b'\0'),
"cstr argument contains embedded NUL bytes",
);
($str:expr) => {{
const NULL_STR: &str = $crate::const_format::concatcp!($str, "\0");
#[allow(unused_unsafe)]
unsafe {
$crate::Utf8CStr::from_bytes_unchecked($crate::const_format::concatcp!($($str)*, "\0")
.as_bytes())
$crate::Utf8CStr::from_bytes_unchecked(NULL_STR.as_bytes())
}
}};
}
#[macro_export]
macro_rules! raw_cstr {
($($str:tt)*) => {{
$crate::cstr!($($str)*).as_ptr()
}};
($str:expr) => {{ $crate::cstr!($str).as_ptr() }};
}

View File

@@ -1,6 +1,5 @@
// Functions in this file are only for exporting to C++, DO NOT USE IN RUST
use std::io;
use std::os::fd::{BorrowedFd, OwnedFd, RawFd};
use cfg_if::cfg_if;
@@ -9,49 +8,55 @@ use libc::{c_char, mode_t};
use crate::files::map_file_at;
pub(crate) use crate::xwrap::*;
use crate::{
clone_attr, cstr, fclone_attr, fd_path, map_fd, map_file, slice_from_ptr, CxxResultExt,
Directory, FsPath, Utf8CStr, Utf8CStrBufRef,
CxxResultExt, Directory, OsResultStatic, Utf8CStr, clone_attr, cstr, fclone_attr, fd_path,
map_fd, map_file, slice_from_ptr,
};
pub(crate) fn fd_path_for_cxx(fd: RawFd, buf: &mut [u8]) -> isize {
let mut buf = Utf8CStrBufRef::from(buf);
let mut buf = cstr::buf::wrap(buf);
fd_path(fd, &mut buf)
.log_cxx_with_msg(|w| w.write_str("fd_path failed"))
.log_cxx()
.map_or(-1_isize, |_| buf.len() as isize)
}
#[no_mangle]
#[unsafe(no_mangle)]
unsafe extern "C" fn canonical_path(path: *const c_char, buf: *mut u8, bufsz: usize) -> isize {
match Utf8CStr::from_ptr(path) {
Ok(p) => {
let mut buf = Utf8CStrBufRef::from_ptr(buf, bufsz);
FsPath::from(p)
.realpath(&mut buf)
.map_or(-1, |_| buf.len() as isize)
unsafe {
match Utf8CStr::from_ptr(path) {
Ok(path) => {
let mut buf = cstr::buf::wrap_ptr(buf, bufsz);
path.realpath(&mut buf)
.log_cxx()
.map_or(-1_isize, |_| buf.len() as isize)
}
Err(_) => -1,
}
Err(_) => -1,
}
}
#[export_name = "mkdirs"]
#[unsafe(export_name = "mkdirs")]
unsafe extern "C" fn mkdirs_for_cxx(path: *const c_char, mode: mode_t) -> i32 {
match Utf8CStr::from_ptr(path) {
Ok(p) => FsPath::from(p).mkdirs(mode).map_or(-1, |_| 0),
Err(_) => -1,
unsafe {
match Utf8CStr::from_ptr(path) {
Ok(path) => path.mkdirs(mode).map_or(-1, |_| 0),
Err(_) => -1,
}
}
}
#[export_name = "rm_rf"]
#[unsafe(export_name = "rm_rf")]
unsafe extern "C" fn rm_rf_for_cxx(path: *const c_char) -> bool {
match Utf8CStr::from_ptr(path) {
Ok(p) => FsPath::from(p).remove_all().is_ok(),
Err(_) => false,
unsafe {
match Utf8CStr::from_ptr(path) {
Ok(path) => path.remove_all().is_ok(),
Err(_) => false,
}
}
}
#[no_mangle]
#[unsafe(no_mangle)]
unsafe extern "C" fn frm_rf(fd: OwnedFd) -> bool {
fn inner(fd: OwnedFd) -> io::Result<()> {
fn inner(fd: OwnedFd) -> OsResultStatic<()> {
Directory::try_from(fd)?.remove_all()
}
inner(fd).is_ok()
@@ -77,116 +82,99 @@ pub(crate) fn map_fd_for_cxx(fd: RawFd, sz: usize, rw: bool) -> &'static mut [u8
}
}
pub(crate) unsafe fn readlinkat_for_cxx(
pub(crate) unsafe fn readlinkat(
dirfd: RawFd,
path: *const c_char,
buf: *mut u8,
bufsz: usize,
) -> isize {
// readlinkat() may fail on x86 platform, returning random value
// instead of number of bytes placed in buf (length of link)
cfg_if! {
if #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] {
libc::memset(buf.cast(), 0, bufsz);
let mut r = libc::readlinkat(dirfd, path, buf.cast(), bufsz - 1);
if r > 0 {
r = libc::strlen(buf.cast()) as isize;
}
} else {
let r = libc::readlinkat(dirfd, path, buf.cast(), bufsz - 1);
if r >= 0 {
*buf.offset(r) = b'\0';
unsafe {
// readlinkat() may fail on x86 platform, returning random value
// instead of number of bytes placed in buf (length of link)
cfg_if! {
if #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] {
libc::memset(buf.cast(), 0, bufsz);
let mut r = libc::readlinkat(dirfd, path, buf.cast(), bufsz - 1);
if r > 0 {
r = libc::strlen(buf.cast()) as isize;
}
} else {
let r = libc::readlinkat(dirfd, path, buf.cast(), bufsz - 1);
if r >= 0 {
*buf.offset(r) = b'\0';
}
}
}
r
}
r
}
#[export_name = "cp_afc"]
#[unsafe(export_name = "cp_afc")]
unsafe extern "C" fn cp_afc_for_cxx(src: *const c_char, dest: *const c_char) -> bool {
if let Ok(src) = Utf8CStr::from_ptr(src) {
if let Ok(dest) = Utf8CStr::from_ptr(dest) {
let src = FsPath::from(src);
let dest = FsPath::from(dest);
return src
.copy_to(dest)
.log_cxx_with_msg(|w| {
w.write_fmt(format_args!("cp_afc {} -> {} failed", src, dest))
})
.is_ok();
unsafe {
if let Ok(src) = Utf8CStr::from_ptr(src) {
if let Ok(dest) = Utf8CStr::from_ptr(dest) {
return src.copy_to(dest).log_cxx().is_ok();
}
}
false
}
false
}
#[export_name = "mv_path"]
#[unsafe(export_name = "mv_path")]
unsafe extern "C" fn mv_path_for_cxx(src: *const c_char, dest: *const c_char) -> bool {
if let Ok(src) = Utf8CStr::from_ptr(src) {
if let Ok(dest) = Utf8CStr::from_ptr(dest) {
let src = FsPath::from(src);
let dest = FsPath::from(dest);
return src
.move_to(dest)
.log_cxx_with_msg(|w| {
w.write_fmt(format_args!("mv_path {} -> {} failed", src, dest))
})
.is_ok();
unsafe {
if let Ok(src) = Utf8CStr::from_ptr(src) {
if let Ok(dest) = Utf8CStr::from_ptr(dest) {
return src.move_to(dest).log_cxx().is_ok();
}
}
false
}
false
}
#[export_name = "link_path"]
#[unsafe(export_name = "link_path")]
unsafe extern "C" fn link_path_for_cxx(src: *const c_char, dest: *const c_char) -> bool {
if let Ok(src) = Utf8CStr::from_ptr(src) {
if let Ok(dest) = Utf8CStr::from_ptr(dest) {
let src = FsPath::from(src);
let dest = FsPath::from(dest);
return src
.link_to(dest)
.log_cxx_with_msg(|w| {
w.write_fmt(format_args!("link_path {} -> {} failed", src, dest))
})
.is_ok();
unsafe {
if let Ok(src) = Utf8CStr::from_ptr(src) {
if let Ok(dest) = Utf8CStr::from_ptr(dest) {
return src.link_to(dest).log_cxx().is_ok();
}
}
false
}
false
}
#[export_name = "clone_attr"]
#[unsafe(export_name = "clone_attr")]
unsafe extern "C" fn clone_attr_for_cxx(src: *const c_char, dest: *const c_char) -> bool {
if let Ok(src) = Utf8CStr::from_ptr(src) {
if let Ok(dest) = Utf8CStr::from_ptr(dest) {
let src = FsPath::from(src);
let dest = FsPath::from(dest);
return clone_attr(src, dest)
.log_cxx_with_msg(|w| {
w.write_fmt(format_args!("clone_attr {} -> {} failed", src, dest))
})
.is_ok();
unsafe {
if let Ok(src) = Utf8CStr::from_ptr(src) {
if let Ok(dest) = Utf8CStr::from_ptr(dest) {
return clone_attr(src, dest).log_cxx().is_ok();
}
}
false
}
false
}
#[export_name = "fclone_attr"]
#[unsafe(export_name = "fclone_attr")]
unsafe extern "C" fn fclone_attr_for_cxx(a: RawFd, b: RawFd) -> bool {
fclone_attr(a, b)
.log_cxx_with_msg(|w| w.write_str("fclone_attr failed"))
.is_ok()
fclone_attr(a, b).log_cxx().is_ok()
}
#[export_name = "cxx$utf8str$new"]
#[unsafe(export_name = "cxx$utf8str$new")]
unsafe extern "C" fn str_new(this: &mut &Utf8CStr, s: *const u8, len: usize) {
*this = Utf8CStr::from_bytes(slice_from_ptr(s, len)).unwrap_or(cstr!(""));
unsafe {
*this = Utf8CStr::from_bytes(slice_from_ptr(s, len)).unwrap_or(cstr!(""));
}
}
#[export_name = "cxx$utf8str$ptr"]
#[unsafe(export_name = "cxx$utf8str$ptr")]
unsafe extern "C" fn str_ptr(this: &&Utf8CStr) -> *const u8 {
this.as_ptr().cast()
}
#[export_name = "cxx$utf8str$len"]
#[unsafe(export_name = "cxx$utf8str$len")]
unsafe extern "C" fn str_len(this: &&Utf8CStr) -> usize {
this.len()
}

495
native/src/base/dir.rs Normal file
View File

@@ -0,0 +1,495 @@
use crate::cxx_extern::readlinkat;
use crate::{
FsPathBuilder, LibcReturn, OsError, OsResult, OsResultStatic, Utf8CStr, Utf8CStrBuf, cstr,
errno, fd_path, fd_set_attr,
};
use libc::{EEXIST, O_CLOEXEC, O_CREAT, O_RDONLY, O_TRUNC, O_WRONLY, dirent, mode_t};
use std::fs::File;
use std::marker::PhantomData;
use std::ops::{Deref, DerefMut};
use std::os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd};
use std::ptr::NonNull;
use std::{mem, slice};
pub struct DirEntry<'a> {
dir: BorrowedDirectory<'a>,
entry: NonNull<dirent>,
d_name_len: usize,
}
impl DirEntry<'_> {
pub fn as_ptr(&self) -> *mut dirent {
self.entry.as_ptr()
}
pub fn name(&self) -> &Utf8CStr {
unsafe {
Utf8CStr::from_bytes_unchecked(slice::from_raw_parts(
self.d_name.as_ptr().cast(),
self.d_name_len,
))
}
}
pub fn resolve_path(&self, buf: &mut dyn Utf8CStrBuf) -> OsResult<'static, ()> {
self.dir.path_at(self.name(), buf)
}
pub fn is_dir(&self) -> bool {
self.d_type == libc::DT_DIR
}
pub fn is_file(&self) -> bool {
self.d_type == libc::DT_REG
}
pub fn is_symlink(&self) -> bool {
self.d_type == libc::DT_LNK
}
pub fn is_block_device(&self) -> bool {
self.d_type == libc::DT_BLK
}
pub fn is_char_device(&self) -> bool {
self.d_type == libc::DT_CHR
}
pub fn is_fifo(&self) -> bool {
self.d_type == libc::DT_FIFO
}
pub fn is_socket(&self) -> bool {
self.d_type == libc::DT_SOCK
}
pub fn unlink(&self) -> OsResult<()> {
let flag = if self.is_dir() { libc::AT_REMOVEDIR } else { 0 };
self.dir.unlink_at(self.name(), flag)
}
pub fn read_link(&self, buf: &mut dyn Utf8CStrBuf) -> OsResult<()> {
self.dir.read_link_at(self.name(), buf)
}
pub fn open_as_dir(&self) -> OsResult<Directory> {
if !self.is_dir() {
return Err(OsError::with_os_error(
libc::ENOTDIR,
"fdopendir",
Some(self.name()),
None,
));
}
self.dir.open_as_dir_at(self.name())
}
pub fn open_as_file(&self, flags: i32) -> OsResult<File> {
if self.is_dir() {
return Err(OsError::with_os_error(
libc::EISDIR,
"open_as_file",
Some(self.name()),
None,
));
}
self.dir.open_as_file_at(self.name(), flags, 0)
}
}
impl Deref for DirEntry<'_> {
type Target = dirent;
fn deref(&self) -> &dirent {
unsafe { self.entry.as_ref() }
}
}
#[repr(transparent)]
pub struct Directory {
inner: NonNull<libc::DIR>,
}
#[repr(transparent)]
pub struct BorrowedDirectory<'a> {
inner: NonNull<libc::DIR>,
phantom: PhantomData<&'a Directory>,
}
impl Deref for BorrowedDirectory<'_> {
type Target = Directory;
fn deref(&self) -> &Directory {
// SAFETY: layout of NonNull<libc::DIR> is the same as Directory
// SAFETY: the lifetime of the raw pointer is tracked in the PhantomData
unsafe { mem::transmute(&self.inner) }
}
}
impl DerefMut for BorrowedDirectory<'_> {
fn deref_mut(&mut self) -> &mut Directory {
// SAFETY: layout of NonNull<libc::DIR> is the same as Directory
// SAFETY: the lifetime of the raw pointer is tracked in the PhantomData
unsafe { mem::transmute(&mut self.inner) }
}
}
pub enum WalkResult {
Continue,
Abort,
Skip,
}
impl Directory {
fn borrow(&self) -> BorrowedDirectory {
BorrowedDirectory {
inner: self.inner,
phantom: PhantomData,
}
}
fn openat<'a>(&self, name: &'a Utf8CStr, flags: i32, mode: u32) -> OsResult<'a, OwnedFd> {
unsafe {
libc::openat(self.as_raw_fd(), name.as_ptr(), flags | O_CLOEXEC, mode)
.as_os_result("openat", Some(name), None)
.map(|fd| OwnedFd::from_raw_fd(fd))
}
}
fn path_at(&self, name: &Utf8CStr, buf: &mut dyn Utf8CStrBuf) -> OsResult<'static, ()> {
self.resolve_path(buf)?;
buf.append_path(name);
Ok(())
}
}
impl Directory {
pub fn open(path: &Utf8CStr) -> OsResult<Directory> {
let dirp = unsafe { libc::opendir(path.as_ptr()) };
let dirp = dirp.as_os_result("opendir", Some(path), None)?;
Ok(Directory { inner: dirp })
}
pub fn read(&mut self) -> OsResult<'static, Option<DirEntry>> {
*errno() = 0;
let e = unsafe { libc::readdir(self.inner.as_ptr()) };
if e.is_null() {
return if *errno() != 0 {
Err(OsError::last_os_error("readdir", None, None))
} else {
Ok(None)
};
}
// Skip non UTF-8 entries, ".", and ".."
unsafe {
let entry = &*e;
let Ok(name) = Utf8CStr::from_ptr(entry.d_name.as_ptr()) else {
return self.read();
};
if name == "." || name == ".." {
self.read()
} else {
let e = DirEntry {
dir: self.borrow(),
entry: NonNull::from(entry),
d_name_len: name.as_bytes_with_nul().len(),
};
Ok(Some(e))
}
}
}
pub fn rewind(&mut self) {
unsafe { libc::rewinddir(self.inner.as_ptr()) };
}
pub fn open_as_dir_at<'a>(&self, name: &'a Utf8CStr) -> OsResult<'a, Directory> {
let fd = self.openat(name, O_RDONLY, 0)?;
Directory::try_from(fd).map_err(|e| e.set_args(Some(name), None))
}
pub fn open_as_file_at<'a>(
&self,
name: &'a Utf8CStr,
flags: i32,
mode: u32,
) -> OsResult<'a, File> {
let fd = self.openat(name, flags, mode)?;
Ok(File::from(fd))
}
pub fn read_link_at<'a>(
&self,
name: &'a Utf8CStr,
buf: &mut dyn Utf8CStrBuf,
) -> OsResult<'a, ()> {
buf.clear();
unsafe {
let r = readlinkat(
self.as_raw_fd(),
name.as_ptr(),
buf.as_mut_ptr().cast(),
buf.capacity(),
)
.as_os_result("readlinkat", Some(name), None)? as usize;
buf.set_len(r);
}
Ok(())
}
pub fn mkdir_at<'a>(&self, name: &'a Utf8CStr, mode: mode_t) -> OsResult<'a, ()> {
unsafe {
if libc::mkdirat(self.as_raw_fd(), name.as_ptr(), mode as mode_t) < 0
&& *errno() != EEXIST
{
return Err(OsError::last_os_error("mkdirat", Some(name), None));
}
}
Ok(())
}
// ln -s target self/name
pub fn create_symlink_at<'a>(
&self,
name: &'a Utf8CStr,
target: &'a Utf8CStr,
) -> OsResult<'a, ()> {
unsafe {
libc::symlinkat(target.as_ptr(), self.as_raw_fd(), name.as_ptr()).check_os_err(
"symlinkat",
Some(target),
Some(name),
)
}
}
pub fn unlink_at<'a>(&self, name: &'a Utf8CStr, flag: i32) -> OsResult<'a, ()> {
unsafe {
libc::unlinkat(self.as_raw_fd(), name.as_ptr(), flag).check_os_err(
"unlinkat",
Some(name),
None,
)?;
}
Ok(())
}
pub fn contains_path(&self, path: &Utf8CStr) -> bool {
// WARNING: Using faccessat is incorrect, because the raw linux kernel syscall
// does not support the flag AT_SYMLINK_NOFOLLOW until 5.8 with faccessat2.
// Use fstatat to check the existence of a file instead.
unsafe {
let mut st: libc::stat = mem::zeroed();
libc::fstatat(
self.as_raw_fd(),
path.as_ptr(),
&mut st,
libc::AT_SYMLINK_NOFOLLOW,
) == 0
}
}
pub fn resolve_path(&self, buf: &mut dyn Utf8CStrBuf) -> OsResult<'static, ()> {
fd_path(self.as_raw_fd(), buf)
}
pub fn post_order_walk<F: FnMut(&DirEntry) -> OsResultStatic<WalkResult>>(
&mut self,
mut f: F,
) -> OsResultStatic<WalkResult> {
self.post_order_walk_impl(&mut f)
}
pub fn pre_order_walk<F: FnMut(&DirEntry) -> OsResultStatic<WalkResult>>(
&mut self,
mut f: F,
) -> OsResultStatic<WalkResult> {
self.pre_order_walk_impl(&mut f)
}
pub fn remove_all(&mut self) -> OsResultStatic<()> {
self.post_order_walk(|e| {
e.unlink()?;
Ok(WalkResult::Continue)
})?;
Ok(())
}
pub fn copy_into(&mut self, dir: &Directory) -> OsResultStatic<()> {
let mut buf = cstr::buf::default();
self.copy_into_impl(dir, &mut buf)
}
pub fn move_into(&mut self, dir: &Directory) -> OsResultStatic<()> {
let dir_fd = self.as_raw_fd();
while let Some(ref e) = self.read()? {
if e.is_dir() && dir.contains_path(e.name()) {
// Destination folder exists, needs recursive move
let mut src = e.open_as_dir()?;
let dest = dir.open_as_dir_at(e.name())?;
src.move_into(&dest)?;
return Ok(e.unlink()?);
}
unsafe {
libc::renameat(
dir_fd,
e.d_name.as_ptr(),
dir.as_raw_fd(),
e.d_name.as_ptr(),
)
.check_os_err("renameat", Some(e.name()), None)?;
}
}
Ok(())
}
pub fn link_into(&mut self, dir: &Directory) -> OsResultStatic<()> {
let mut buf = cstr::buf::default();
self.link_into_impl(dir, &mut buf)
}
}
impl Directory {
fn post_order_walk_impl<F: FnMut(&DirEntry) -> OsResultStatic<WalkResult>>(
&mut self,
f: &mut F,
) -> OsResultStatic<WalkResult> {
use WalkResult::*;
loop {
match self.read()? {
None => return Ok(Continue),
Some(ref e) => {
if e.is_dir() {
let mut dir = e.open_as_dir()?;
if let Abort = dir.post_order_walk_impl(f)? {
return Ok(Abort);
}
}
match f(e)? {
Abort => return Ok(Abort),
Skip => return Ok(Continue),
Continue => {}
}
}
}
}
}
fn pre_order_walk_impl<F: FnMut(&DirEntry) -> OsResultStatic<WalkResult>>(
&mut self,
f: &mut F,
) -> OsResultStatic<WalkResult> {
use WalkResult::*;
loop {
match self.read()? {
None => return Ok(Continue),
Some(ref e) => match f(e)? {
Abort => return Ok(Abort),
Skip => continue,
Continue => {
if e.is_dir() {
let mut dir = e.open_as_dir()?;
if let Abort = dir.pre_order_walk_impl(f)? {
return Ok(Abort);
}
}
}
},
}
}
}
fn copy_into_impl(
&mut self,
dest_dir: &Directory,
buf: &mut dyn Utf8CStrBuf,
) -> OsResultStatic<()> {
while let Some(ref e) = self.read()? {
e.resolve_path(buf)?;
let attr = buf.get_attr()?;
if e.is_dir() {
dest_dir.mkdir_at(e.name(), 0o777)?;
let mut src = e.open_as_dir()?;
let dest = dest_dir.open_as_dir_at(e.name())?;
src.copy_into_impl(&dest, buf)?;
fd_set_attr(dest.as_raw_fd(), &attr)?;
} else if e.is_file() {
let mut src = e.open_as_file(O_RDONLY)?;
let mut dest =
dest_dir.open_as_file_at(e.name(), O_WRONLY | O_CREAT | O_TRUNC, 0o777)?;
std::io::copy(&mut src, &mut dest)?;
fd_set_attr(dest.as_raw_fd(), &attr)?;
} else if e.is_symlink() {
e.read_link(buf)?;
dest_dir.create_symlink_at(e.name(), buf)?;
dest_dir.path_at(e.name(), buf)?;
buf.set_attr(&attr)?;
}
}
Ok(())
}
fn link_into_impl(
&mut self,
dest_dir: &Directory,
buf: &mut dyn Utf8CStrBuf,
) -> OsResultStatic<()> {
let dir_fd = self.as_raw_fd();
while let Some(ref e) = self.read()? {
if e.is_dir() {
dest_dir.mkdir_at(e.name(), 0o777)?;
e.resolve_path(buf)?;
let attr = buf.get_attr()?;
let mut src = e.open_as_dir()?;
let dest = dest_dir.open_as_dir_at(e.name())?;
src.link_into_impl(&dest, buf)?;
fd_set_attr(dest.as_raw_fd(), &attr)?;
} else {
unsafe {
libc::linkat(
dir_fd,
e.d_name.as_ptr(),
dest_dir.as_raw_fd(),
e.d_name.as_ptr(),
0,
)
.check_os_err("linkat", Some(e.name()), None)?;
}
}
}
Ok(())
}
}
impl TryFrom<OwnedFd> for Directory {
type Error = OsError<'static>;
fn try_from(fd: OwnedFd) -> OsResult<'static, Self> {
let dirp = unsafe { libc::fdopendir(fd.into_raw_fd()) };
let dirp = dirp.as_os_result("fdopendir", None, None)?;
Ok(Directory { inner: dirp })
}
}
impl AsRawFd for Directory {
fn as_raw_fd(&self) -> RawFd {
unsafe { libc::dirfd(self.inner.as_ptr()) }
}
}
impl AsFd for Directory {
fn as_fd(&self) -> BorrowedFd {
unsafe { BorrowedFd::borrow_raw(self.as_raw_fd()) }
}
}
impl Drop for Directory {
fn drop(&mut self) {
unsafe {
libc::closedir(self.inner.as_ptr());
}
}
}

View File

@@ -143,7 +143,7 @@ string resolve_preinit_dir(const char *base_dir) {
if (access((dir + "/unencrypted").data(), F_OK) == 0) {
dir += "/unencrypted/magisk";
} else if (access((dir + "/adb").data(), F_OK) == 0) {
dir += "/adb/modules";
dir += "/adb";
} else if (access((dir + "/watchdog").data(), F_OK) == 0) {
dir += "/watchdog/magisk";
} else {

File diff suppressed because it is too large Load Diff

View File

@@ -7,4 +7,5 @@
#include "../base-rs.hpp"
using rust::xpipe2;
using rust::fd_path;
using rust::fd_path;
using kv_pairs = std::vector<std::pair<std::string, std::string>>;

View File

@@ -6,18 +6,24 @@ pub use const_format;
pub use libc;
use num_traits::FromPrimitive;
pub use cstr::*;
pub use cstr::{
FsPathFollow, StrErr, Utf8CStr, Utf8CStrBuf, Utf8CStrBufArr, Utf8CStrBufRef, Utf8CString,
};
use cxx_extern::*;
pub use dir::*;
pub use ffi::fork_dont_care;
pub use files::*;
pub use logging::*;
pub use misc::*;
pub use result::*;
mod cstr;
pub mod cstr;
mod cxx_extern;
mod dir;
mod files;
mod logging;
mod misc;
mod mount;
mod result;
mod xwrap;
@@ -42,6 +48,7 @@ pub mod ffi {
type Utf8CStrRef<'a> = &'a crate::cstr::Utf8CStr;
fn mut_u8_patch(buf: &mut [u8], from: &[u8], to: &[u8]) -> Vec<usize>;
fn fork_dont_care() -> i32;
}
extern "Rust" {

View File

@@ -1,13 +1,13 @@
use std::fmt;
use std::fmt::Arguments;
use std::io::{stderr, stdout, Write};
use std::io::{Write, stderr, stdout};
use std::process::exit;
use num_derive::{FromPrimitive, ToPrimitive};
use num_traits::FromPrimitive;
use crate::ffi::LogLevelCxx;
use crate::{Utf8CStr, Utf8CStrBufArr};
use crate::{Utf8CStr, cstr};
// Ugly hack to avoid using enum
#[allow(non_snake_case, non_upper_case_globals)]
@@ -84,7 +84,7 @@ fn log_with_writer<F: FnOnce(LogWriter)>(level: LogLevel, f: F) {
}
f(logger.write);
if matches!(level, LogLevel::ErrorCxx) && (logger.flags & LogFlag::ExitOnError) != 0 {
exit(1);
exit(-1);
}
}
@@ -96,7 +96,7 @@ pub fn log_from_cxx(level: LogLevelCxx, msg: &Utf8CStr) {
pub fn log_with_formatter<F: FnOnce(Formatter) -> fmt::Result>(level: LogLevel, f: F) {
log_with_writer(level, |write| {
let mut buf = Utf8CStrBufArr::default();
let mut buf = cstr::buf::default();
f(&mut buf).ok();
write(level, &buf);
});

View File

@@ -186,27 +186,6 @@ int parse_int(string_view s) {
return parse_num<int, 10>(s);
}
uint64_t parse_uint64_hex(string_view s) {
return parse_num<uint64_t, 16>(s);
}
uint32_t binary_gcd(uint32_t u, uint32_t v) {
if (u == 0) return v;
if (v == 0) return u;
auto shift = __builtin_ctz(u | v);
u >>= __builtin_ctz(u);
do {
v >>= __builtin_ctz(v);
if (u > v) {
auto t = v;
v = u;
u = t;
}
v -= u;
} while (v != 0);
return u << shift;
}
int switch_mnt_ns(int pid) {
int ret = -1;
int fd = syscall(__NR_pidfd_open, pid, 0);
@@ -255,10 +234,6 @@ vector<string> split(string_view s, string_view delims) {
return split_impl<string>(s, delims);
}
vector<string_view> split_view(string_view s, string_view delims) {
return split_impl<string_view>(s, delims);
}
#undef vsnprintf
int vssprintf(char *dest, size_t size, const char *fmt, va_list ap) {
if (size > 0) {

View File

@@ -71,57 +71,6 @@ static inline void default_new(T *&p) { p = new T(); }
template<class T>
static inline void default_new(std::unique_ptr<T> &p) { p.reset(new T()); }
template<typename T, typename Impl>
class stateless_allocator {
public:
using value_type = T;
T *allocate(size_t num) { return static_cast<T*>(Impl::allocate(sizeof(T) * num)); }
void deallocate(T *ptr, size_t num) { Impl::deallocate(ptr, sizeof(T) * num); }
stateless_allocator() = default;
stateless_allocator(const stateless_allocator&) = default;
stateless_allocator(stateless_allocator&&) = default;
template <typename U>
stateless_allocator(const stateless_allocator<U, Impl>&) {}
bool operator==(const stateless_allocator&) { return true; }
bool operator!=(const stateless_allocator&) { return false; }
};
class dynamic_bitset_impl {
public:
using slot_type = unsigned long;
constexpr static int slot_size = sizeof(slot_type) * 8;
using slot_bits = std::bitset<slot_size>;
size_t slots() const { return slot_list.size(); }
slot_type get_slot(size_t slot) const {
return slot_list.size() > slot ? slot_list[slot].to_ulong() : 0ul;
}
void emplace_back(slot_type l) {
slot_list.emplace_back(l);
}
protected:
slot_bits::reference get(size_t pos) {
size_t slot = pos / slot_size;
size_t index = pos % slot_size;
if (slot_list.size() <= slot) {
slot_list.resize(slot + 1);
}
return slot_list[slot][index];
}
bool get(size_t pos) const {
size_t slot = pos / slot_size;
size_t index = pos % slot_size;
return slot_list.size() > slot && slot_list[slot][index];
}
private:
std::vector<slot_bits> slot_list;
};
struct dynamic_bitset : public dynamic_bitset_impl {
slot_bits::reference operator[] (size_t pos) { return get(pos); }
bool operator[] (size_t pos) const { return get(pos); }
};
struct StringCmp {
using is_transparent = void;
bool operator()(std::string_view a, std::string_view b) const { return a < b; }
@@ -198,13 +147,6 @@ struct byte_data : public byte_view {
rust::Vec<size_t> patch(byte_view from, byte_view to);
};
template<size_t N>
struct byte_array : public byte_data {
byte_array() : byte_data(arr, N), arr{0} {}
private:
uint8_t arr[N];
};
class byte_stream;
struct heap_data : public byte_data {
@@ -238,7 +180,6 @@ rust::Vec<size_t> mut_u8_patch(
rust::Slice<const uint8_t> from,
rust::Slice<const uint8_t> to);
uint64_t parse_uint64_hex(std::string_view s);
int parse_int(std::string_view s);
using thread_entry = void *(*)(void *);
@@ -270,11 +211,9 @@ int fork_dont_care();
int fork_no_orphan();
void init_argv0(int argc, char **argv);
void set_nice_name(const char *name);
uint32_t binary_gcd(uint32_t u, uint32_t v);
int switch_mnt_ns(int pid);
std::string &replace_all(std::string &str, std::string_view from, std::string_view to);
std::vector<std::string> split(std::string_view s, std::string_view delims);
std::vector<std::string_view> split_view(std::string_view, std::string_view delims);
// Similar to vsnprintf, but the return value is the written number of bytes
__printflike(3, 0) int vssprintf(char *dest, size_t size, const char *fmt, va_list ap);

View File

@@ -1,12 +1,13 @@
use std::fmt::Arguments;
use std::io::Write;
use std::process::exit;
use std::{fmt, io, slice, str};
use crate::{StrErr, Utf8CStr, ffi};
use argh::EarlyExit;
use libc::c_char;
use crate::{ffi, StrErr, Utf8CStr};
use std::fmt::Arguments;
use std::io::Write;
use std::mem::ManuallyDrop;
use std::process::exit;
use std::sync::Arc;
use std::sync::atomic::{AtomicPtr, Ordering};
use std::{fmt, slice, str};
pub fn errno() -> &'static mut i32 {
unsafe { &mut *libc::__errno() }
@@ -15,67 +16,25 @@ pub fn errno() -> &'static mut i32 {
// When len is 0, don't care whether buf is null or not
#[inline]
pub unsafe fn slice_from_ptr<'a, T>(buf: *const T, len: usize) -> &'a [T] {
if len == 0 {
&[]
} else {
slice::from_raw_parts(buf, len)
unsafe {
if len == 0 {
&[]
} else {
slice::from_raw_parts(buf, len)
}
}
}
// When len is 0, don't care whether buf is null or not
#[inline]
pub unsafe fn slice_from_ptr_mut<'a, T>(buf: *mut T, len: usize) -> &'a mut [T] {
if len == 0 {
&mut []
} else {
slice::from_raw_parts_mut(buf, len)
}
}
// Check libc return value and map to Result
pub trait LibcReturn
where
Self: Copy,
{
fn is_error(&self) -> bool;
fn check_os_err(self) -> io::Result<Self> {
if self.is_error() {
Err(io::Error::last_os_error())
unsafe {
if len == 0 {
&mut []
} else {
Ok(self)
slice::from_raw_parts_mut(buf, len)
}
}
fn as_os_err(self) -> io::Result<()> {
self.check_os_err()?;
Ok(())
}
}
macro_rules! impl_libc_return {
($($t:ty)*) => ($(
impl LibcReturn for $t {
#[inline]
fn is_error(&self) -> bool {
*self < 0
}
}
)*)
}
impl_libc_return! { i8 i16 i32 i64 isize }
impl<T> LibcReturn for *const T {
#[inline]
fn is_error(&self) -> bool {
self.is_null()
}
}
impl<T> LibcReturn for *mut T {
#[inline]
fn is_error(&self) -> bool {
self.is_null()
}
}
pub trait BytesExt {
@@ -161,3 +120,52 @@ impl<T: Write> fmt::Write for FmtAdaptor<'_, T> {
self.0.write_fmt(args).map_err(|_| fmt::Error)
}
}
pub struct AtomicArc<T> {
ptr: AtomicPtr<T>,
}
impl<T> AtomicArc<T> {
pub fn new(arc: Arc<T>) -> AtomicArc<T> {
let raw = Arc::into_raw(arc);
Self {
ptr: AtomicPtr::new(raw as *mut _),
}
}
pub fn load(&self) -> Arc<T> {
let raw = self.ptr.load(Ordering::Acquire);
// SAFETY: the raw pointer is always created from Arc::into_raw
let arc = ManuallyDrop::new(unsafe { Arc::from_raw(raw) });
ManuallyDrop::into_inner(arc.clone())
}
fn swap_ptr(&self, raw: *const T) -> Arc<T> {
let prev = self.ptr.swap(raw as *mut _, Ordering::AcqRel);
// SAFETY: the raw pointer is always created from Arc::into_raw
unsafe { Arc::from_raw(prev) }
}
pub fn swap(&self, arc: Arc<T>) -> Arc<T> {
let raw = Arc::into_raw(arc);
self.swap_ptr(raw)
}
pub fn store(&self, arc: Arc<T>) {
// Drop the previous value
let _ = self.swap(arc);
}
}
impl<T> Drop for AtomicArc<T> {
fn drop(&mut self) {
// Drop the internal value
let _ = self.swap_ptr(std::ptr::null());
}
}
impl<T: Default> Default for AtomicArc<T> {
fn default() -> Self {
Self::new(Default::default())
}
}

90
native/src/base/mount.rs Normal file
View File

@@ -0,0 +1,90 @@
use crate::{LibcReturn, OsResult, Utf8CStr};
use libc::c_ulong;
use std::ptr;
impl Utf8CStr {
pub fn bind_mount_to<'a>(&'a self, path: &'a Utf8CStr) -> OsResult<'a, ()> {
unsafe {
libc::mount(
self.as_ptr(),
path.as_ptr(),
ptr::null(),
libc::MS_BIND,
ptr::null(),
)
.check_os_err("bind_mount", Some(self), Some(path))
}
}
pub fn remount_mount_point_flags(&self, flags: c_ulong) -> OsResult<()> {
unsafe {
libc::mount(
ptr::null(),
self.as_ptr(),
ptr::null(),
libc::MS_BIND | libc::MS_REMOUNT | flags,
ptr::null(),
)
.check_os_err("remount", Some(self), None)
}
}
pub fn remount_mount_flags(&self, flags: c_ulong) -> OsResult<()> {
unsafe {
libc::mount(
ptr::null(),
self.as_ptr(),
ptr::null(),
libc::MS_REMOUNT | flags,
ptr::null(),
)
.check_os_err("remount", Some(self), None)
}
}
pub fn remount_with_data(&self, data: &Utf8CStr) -> OsResult<()> {
unsafe {
libc::mount(
ptr::null(),
self.as_ptr(),
ptr::null(),
libc::MS_REMOUNT,
data.as_ptr().cast(),
)
.check_os_err("remount", Some(self), None)
}
}
pub fn move_mount_to<'a>(&'a self, path: &'a Utf8CStr) -> OsResult<'a, ()> {
unsafe {
libc::mount(
self.as_ptr(),
path.as_ptr(),
ptr::null(),
libc::MS_MOVE,
ptr::null(),
)
.check_os_err("move_mount", Some(self), Some(path))
}
}
pub fn unmount(&self) -> OsResult<()> {
unsafe {
libc::umount2(self.as_ptr(), libc::MNT_DETACH).check_os_err("unmount", Some(self), None)
}
}
pub fn set_mount_private(&self, recursive: bool) -> OsResult<()> {
let flag = if recursive { libc::MS_REC } else { 0 };
unsafe {
libc::mount(
ptr::null(),
self.as_ptr(),
ptr::null(),
libc::MS_PRIVATE | flag,
ptr::null(),
)
.check_os_err("set_mount_private", Some(self), None)
}
}
}

View File

@@ -1,9 +1,10 @@
use std::fmt;
use crate::logging::Formatter;
use crate::{LogLevel, errno, log_with_args, log_with_formatter};
use std::fmt::Display;
use std::panic::Location;
use crate::logging::Formatter;
use crate::{log_with_args, log_with_formatter, LogLevel};
use std::ptr::NonNull;
use std::{fmt, io};
use thiserror::Error;
// Error handling throughout the Rust codebase in Magisk:
//
@@ -55,12 +56,12 @@ impl<T> SilentResultExt<T> for Option<T> {
pub trait ResultExt<T> {
fn log(self) -> LoggedResult<T>;
fn log_with_msg<F: FnOnce(Formatter) -> fmt::Result>(self, f: F) -> LoggedResult<T>;
fn log_ok(self);
}
// Internal C++ bridging logging routines
pub(crate) trait CxxResultExt<T> {
fn log_cxx(self) -> LoggedResult<T>;
fn log_cxx_with_msg<F: FnOnce(Formatter) -> fmt::Result>(self, f: F) -> LoggedResult<T>;
}
trait Loggable<T> {
@@ -77,10 +78,6 @@ impl<T, R: Loggable<T>> CxxResultExt<T> for R {
fn log_cxx(self) -> LoggedResult<T> {
self.do_log(LogLevel::ErrorCxx, None)
}
fn log_cxx_with_msg<F: FnOnce(Formatter) -> fmt::Result>(self, f: F) -> LoggedResult<T> {
self.do_log_msg(LogLevel::ErrorCxx, None, f)
}
}
impl<T, R: Loggable<T>> ResultExt<T> for R {
@@ -105,6 +102,17 @@ impl<T, R: Loggable<T>> ResultExt<T> for R {
fn log_with_msg<F: FnOnce(Formatter) -> fmt::Result>(self, f: F) -> LoggedResult<T> {
self.do_log_msg(LogLevel::Error, Some(Location::caller()), f)
}
#[cfg(not(debug_assertions))]
fn log_ok(self) {
self.log().ok();
}
#[track_caller]
#[cfg(debug_assertions)]
fn log_ok(self) {
self.do_log(LogLevel::Error, Some(Location::caller())).ok();
}
}
impl<T> Loggable<T> for LoggedResult<T> {
@@ -193,3 +201,205 @@ impl<T: Display> From<T> for LoggedError {
LoggedError::default()
}
}
// Check libc return value and map to Result
pub trait LibcReturn
where
Self: Copy,
{
type Value;
fn is_error(&self) -> bool;
fn map_val(self) -> Self::Value;
fn as_os_result<'a>(
self,
name: &'static str,
arg1: Option<&'a str>,
arg2: Option<&'a str>,
) -> OsResult<'a, Self::Value> {
if self.is_error() {
Err(OsError::last_os_error(name, arg1, arg2))
} else {
Ok(self.map_val())
}
}
fn check_os_err<'a>(
self,
name: &'static str,
arg1: Option<&'a str>,
arg2: Option<&'a str>,
) -> OsResult<'a, ()> {
if self.is_error() {
Err(OsError::last_os_error(name, arg1, arg2))
} else {
Ok(())
}
}
fn check_io_err(self) -> io::Result<()> {
if self.is_error() {
Err(io::Error::last_os_error())
} else {
Ok(())
}
}
}
macro_rules! impl_libc_return {
($($t:ty)*) => ($(
impl LibcReturn for $t {
type Value = Self;
#[inline(always)]
fn is_error(&self) -> bool {
*self < 0
}
#[inline(always)]
fn map_val(self) -> Self::Value {
self
}
}
)*)
}
impl_libc_return! { i8 i16 i32 i64 isize }
impl<T> LibcReturn for *mut T {
type Value = NonNull<T>;
#[inline(always)]
fn is_error(&self) -> bool {
self.is_null()
}
#[inline(always)]
fn map_val(self) -> NonNull<T> {
// SAFETY: pointer is null checked by is_error
unsafe { NonNull::new_unchecked(self.cast()) }
}
}
#[derive(Debug)]
enum OwnableStr<'a> {
None,
Borrowed(&'a str),
Owned(Box<str>),
}
impl OwnableStr<'_> {
fn into_owned(self) -> OwnableStr<'static> {
match self {
OwnableStr::None => OwnableStr::None,
OwnableStr::Borrowed(s) => OwnableStr::Owned(Box::from(s)),
OwnableStr::Owned(s) => OwnableStr::Owned(s),
}
}
fn ok(&self) -> Option<&str> {
match self {
OwnableStr::None => None,
OwnableStr::Borrowed(s) => Some(*s),
OwnableStr::Owned(s) => Some(s),
}
}
}
impl<'a> From<Option<&'a str>> for OwnableStr<'a> {
fn from(value: Option<&'a str>) -> Self {
value.map(OwnableStr::Borrowed).unwrap_or(OwnableStr::None)
}
}
#[derive(Debug)]
pub struct OsError<'a> {
code: i32,
name: &'static str,
arg1: OwnableStr<'a>,
arg2: OwnableStr<'a>,
}
impl OsError<'_> {
pub fn with_os_error<'a>(
code: i32,
name: &'static str,
arg1: Option<&'a str>,
arg2: Option<&'a str>,
) -> OsError<'a> {
OsError {
code,
name,
arg1: OwnableStr::from(arg1),
arg2: OwnableStr::from(arg2),
}
}
pub fn last_os_error<'a>(
name: &'static str,
arg1: Option<&'a str>,
arg2: Option<&'a str>,
) -> OsError<'a> {
Self::with_os_error(*errno(), name, arg1, arg2)
}
pub fn set_args<'a>(self, arg1: Option<&'a str>, arg2: Option<&'a str>) -> OsError<'a> {
Self::with_os_error(self.code, self.name, arg1, arg2)
}
pub fn into_owned(self) -> OsError<'static> {
OsError {
code: *errno(),
name: self.name,
arg1: self.arg1.into_owned(),
arg2: self.arg2.into_owned(),
}
}
fn as_io_error(&self) -> io::Error {
io::Error::from_raw_os_error(self.code)
}
}
impl Display for OsError<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let error = self.as_io_error();
if self.name.is_empty() {
write!(f, "{:#}", error)
} else {
match (self.arg1.ok(), self.arg2.ok()) {
(Some(arg1), Some(arg2)) => {
write!(f, "{} '{}' '{}': {:#}", self.name, arg1, arg2, error)
}
(Some(arg1), None) => {
write!(f, "{} '{}': {:#}", self.name, arg1, error)
}
_ => {
write!(f, "{}: {:#}", self.name, error)
}
}
}
}
}
impl std::error::Error for OsError<'_> {}
pub type OsResult<'a, T> = Result<T, OsError<'a>>;
#[derive(Debug, Error)]
pub enum OsErrorStatic {
#[error(transparent)]
Os(OsError<'static>),
#[error(transparent)]
Io(#[from] io::Error),
}
// Convert non-static OsError to static
impl<'a> From<OsError<'a>> for OsErrorStatic {
fn from(value: OsError<'a>) -> Self {
OsErrorStatic::Os(value.into_owned())
}
}
pub type OsResultStatic<T> = Result<T, OsErrorStatic>;

View File

@@ -15,7 +15,6 @@ int xopenat(int dirfd, const char *pathname, int flags, mode_t mode = 0);
ssize_t xwrite(int fd, const void *buf, size_t count);
ssize_t xread(int fd, void *buf, size_t count);
ssize_t xxread(int fd, void *buf, size_t count);
off64_t xlseek64(int fd, off64_t offset, int whence);
int xsetns(int fd, int nstype);
int xunshare(int flags);
DIR *xopendir(const char *name);
@@ -26,33 +25,21 @@ int xsocket(int domain, int type, int protocol);
int xbind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
int xlisten(int sockfd, int backlog);
int xaccept4(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int flags);
ssize_t xsendmsg(int sockfd, const struct msghdr *msg, int flags);
ssize_t xrecvmsg(int sockfd, struct msghdr *msg, int flags);
int xaccess(const char *path, int mode);
int xfaccessat(int dirfd, const char *pathname, int mode, int flags);
int xstat(const char *pathname, struct stat *buf);
int xlstat(const char *pathname, struct stat *buf);
int xfstat(int fd, struct stat *buf);
int xfstatat(int dirfd, const char *pathname, struct stat *buf, int flags);
int xdup(int fd);
int xdup2(int oldfd, int newfd);
int xdup3(int oldfd, int newfd, int flags);
ssize_t xreadlink(const char * __restrict__ pathname, char * __restrict__ buf, size_t bufsiz);
ssize_t xreadlinkat(
int dirfd, const char * __restrict__ pathname, char * __restrict__ buf, size_t bufsiz);
int xsymlink(const char *target, const char *linkpath);
int xsymlinkat(const char *target, int newdirfd, const char *linkpath);
int xlinkat(int olddirfd, const char *oldpath, int newdirfd, const char *newpath, int flags);
int xmount(const char *source, const char *target,
const char *filesystemtype, unsigned long mountflags,
const void *data);
int xumount(const char *target);
int xumount2(const char *target, int flags);
int xrename(const char *oldpath, const char *newpath);
int xmkdir(const char *pathname, mode_t mode);
int xmkdirs(const char *pathname, mode_t mode);
int xmkdirat(int dirfd, const char *pathname, mode_t mode);
void *xmmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
ssize_t xsendfile(int out_fd, int in_fd, off_t *offset, size_t count);
pid_t xfork();
int xpoll(pollfd *fds, nfds_t nfds, int timeout);

View File

@@ -1,509 +1,306 @@
// Functions in this file are only for exporting to C++, DO NOT USE IN RUST
use std::ffi::CStr;
use std::os::unix::io::RawFd;
use std::ptr;
use crate::cxx_extern::readlinkat;
use crate::{
BorrowedDirectory, CxxResultExt, LibcReturn, Utf8CStr, cstr, slice_from_ptr, slice_from_ptr_mut,
};
use libc::{
c_char, c_uint, c_ulong, c_void, dev_t, mode_t, nfds_t, off_t, pollfd, sockaddr, socklen_t,
ssize_t, SYS_dup3,
};
use std::ffi::CStr;
use std::fs::File;
use std::io::{Read, Write};
use std::mem::ManuallyDrop;
use std::os::fd::FromRawFd;
use std::os::unix::io::RawFd;
use std::ptr;
use std::ptr::NonNull;
use crate::cxx_extern::readlinkat_for_cxx;
use crate::{errno, raw_cstr, CxxResultExt, FsPath, Utf8CStr, Utf8CStrBufRef};
fn ptr_to_str<'a, T>(ptr: *const T) -> &'a str {
fn ptr_to_str<'a>(ptr: *const c_char) -> Option<&'a str> {
if ptr.is_null() {
"(null)"
None
} else {
unsafe { CStr::from_ptr(ptr.cast()) }.to_str().unwrap_or("")
unsafe { CStr::from_ptr(ptr) }.to_str().ok()
}
}
fn error_str() -> &'static str {
unsafe { ptr_to_str(libc::strerror(*errno())) }
}
macro_rules! error_cxx {
($($args:tt)+) => {
($crate::log_with_args($crate::LogLevel::ErrorCxx, format_args_nl!($($args)+)))
}
}
macro_rules! perror {
($fmt:expr) => {
$crate::log_with_formatter($crate::LogLevel::ErrorCxx, |w| {
w.write_str($fmt)?;
w.write_fmt(format_args_nl!(" failed with {}: {}", $crate::errno(), error_str()))
})
};
($fmt:expr, $($args:tt)*) => {
$crate::log_with_formatter($crate::LogLevel::ErrorCxx, |w| {
w.write_fmt(format_args!($fmt, $($args)*))?;
w.write_fmt(format_args_nl!(" failed with {}: {}", $crate::errno(), error_str()))
})
};
}
mod c_export {
use std::os::unix::io::RawFd;
use crate::{slice_from_ptr, slice_from_ptr_mut};
#[no_mangle]
unsafe extern "C" fn xwrite(fd: RawFd, buf: *const u8, bufsz: usize) -> isize {
super::xwrite(fd, slice_from_ptr(buf, bufsz))
}
#[no_mangle]
unsafe extern "C" fn xxread(fd: RawFd, buf: *mut u8, bufsz: usize) -> isize {
super::xxread(fd, slice_from_ptr_mut(buf, bufsz))
}
}
#[no_mangle]
#[unsafe(no_mangle)]
unsafe extern "C" fn xrealpath(path: *const c_char, buf: *mut u8, bufsz: usize) -> isize {
match Utf8CStr::from_ptr(path) {
Ok(p) => {
let mut buf = Utf8CStrBufRef::from_ptr(buf, bufsz);
FsPath::from(p)
.realpath(&mut buf)
.log_cxx_with_msg(|w| w.write_fmt(format_args!("realpath {} failed", p)))
.map_or(-1, |_| buf.len() as isize)
unsafe {
match Utf8CStr::from_ptr(path) {
Ok(path) => {
let mut buf = cstr::buf::wrap_ptr(buf, bufsz);
path.realpath(&mut buf)
.log_cxx()
.map_or(-1, |_| buf.len() as isize)
}
Err(_) => -1,
}
Err(_) => -1,
}
}
#[no_mangle]
#[unsafe(no_mangle)]
unsafe extern "C" fn xreadlink(path: *const c_char, buf: *mut u8, bufsz: usize) -> isize {
match Utf8CStr::from_ptr(path) {
Ok(p) => {
let mut buf = Utf8CStrBufRef::from_ptr(buf, bufsz);
FsPath::from(p)
.read_link(&mut buf)
.log_cxx_with_msg(|w| w.write_fmt(format_args!("readlink {} failed", p)))
.map_or(-1, |_| buf.len() as isize)
unsafe {
match Utf8CStr::from_ptr(path) {
Ok(path) => {
let mut buf = cstr::buf::wrap_ptr(buf, bufsz);
path.read_link(&mut buf)
.log_cxx()
.map_or(-1, |_| buf.len() as isize)
}
Err(_) => -1,
}
Err(_) => -1,
}
}
#[no_mangle]
#[unsafe(no_mangle)]
unsafe extern "C" fn xreadlinkat(
dirfd: RawFd,
path: *const c_char,
buf: *mut u8,
bufsz: usize,
) -> isize {
let r = readlinkat_for_cxx(dirfd, path, buf, bufsz);
if r < 0 {
perror!("readlinkat {}", ptr_to_str(path))
}
r
}
#[no_mangle]
unsafe extern "C" fn xfopen(path: *const c_char, mode: *const c_char) -> *mut libc::FILE {
let fp = libc::fopen(path, mode);
if fp.is_null() {
perror!("fopen {}", ptr_to_str(path));
}
fp
}
#[no_mangle]
unsafe extern "C" fn xfdopen(fd: RawFd, mode: *const c_char) -> *mut libc::FILE {
let fp = libc::fdopen(fd, mode);
if fp.is_null() {
perror!("fdopen");
}
fp
}
#[no_mangle]
unsafe extern "C" fn xopen(path: *const c_char, flags: i32, mode: mode_t) -> RawFd {
let r = libc::open(path, flags, mode as c_uint);
if r < 0 {
perror!("open {}", ptr_to_str(path));
}
r
}
#[no_mangle]
unsafe extern "C" fn xopenat(dirfd: RawFd, path: *const c_char, flags: i32, mode: mode_t) -> RawFd {
let r = libc::openat(dirfd, path, flags, mode as c_uint);
if r < 0 {
perror!("openat {}", ptr_to_str(path));
}
r
}
// Fully write data slice
fn xwrite(fd: RawFd, data: &[u8]) -> isize {
unsafe {
let mut write_sz: usize = 0;
let mut r: ssize_t;
let mut remain: &[u8] = data;
loop {
r = libc::write(fd, remain.as_ptr().cast(), remain.len());
if r < 0 {
if *errno() == libc::EINTR {
continue;
}
perror!("write");
return r;
}
let r = r as usize;
write_sz += r;
remain = &remain[r..];
if r == 0 || remain.is_empty() {
break;
}
}
if !remain.is_empty() {
error_cxx!("write ({} != {})", write_sz, data.len())
}
write_sz as isize
readlinkat(dirfd, path, buf, bufsz)
.as_os_result("readlinkat", ptr_to_str(path), None)
.log_cxx()
.unwrap_or(-1)
}
}
#[no_mangle]
#[unsafe(no_mangle)]
unsafe extern "C" fn xfopen(path: *const c_char, mode: *const c_char) -> *mut libc::FILE {
unsafe {
libc::fopen(path, mode)
.as_os_result("fopen", ptr_to_str(path), None)
.log_cxx()
.map_or(ptr::null_mut(), NonNull::as_ptr)
}
}
#[unsafe(no_mangle)]
unsafe extern "C" fn xfdopen(fd: RawFd, mode: *const c_char) -> *mut libc::FILE {
unsafe {
libc::fdopen(fd, mode)
.as_os_result("fdopen", None, None)
.log_cxx()
.map_or(ptr::null_mut(), NonNull::as_ptr)
}
}
#[unsafe(no_mangle)]
unsafe extern "C" fn xopen(path: *const c_char, flags: i32, mode: mode_t) -> RawFd {
unsafe {
libc::open(path, flags, mode as c_uint)
.as_os_result("open", ptr_to_str(path), None)
.log_cxx()
.unwrap_or(-1)
}
}
#[unsafe(no_mangle)]
unsafe extern "C" fn xopenat(dirfd: RawFd, path: *const c_char, flags: i32, mode: mode_t) -> RawFd {
unsafe {
libc::openat(dirfd, path, flags, mode as c_uint)
.as_os_result("openat", ptr_to_str(path), None)
.log_cxx()
.unwrap_or(-1)
}
}
#[unsafe(no_mangle)]
unsafe extern "C" fn xwrite(fd: RawFd, buf: *const u8, bufsz: usize) -> isize {
let mut file = unsafe { ManuallyDrop::new(File::from_raw_fd(fd)) };
let data = unsafe { slice_from_ptr(buf, bufsz) };
file.write_all(data)
.log_cxx()
.map_or(-1, |_| data.len() as isize)
}
#[unsafe(no_mangle)]
unsafe extern "C" fn xread(fd: RawFd, buf: *mut c_void, bufsz: usize) -> isize {
unsafe {
let r = libc::read(fd, buf, bufsz);
if r < 0 {
perror!("read");
}
r
libc::read(fd, buf, bufsz)
.as_os_result("read", None, None)
.log_cxx()
.unwrap_or(-1)
}
}
// Fully read size of data slice
fn xxread(fd: RawFd, data: &mut [u8]) -> isize {
unsafe {
let mut read_sz: usize = 0;
let mut r: ssize_t;
let mut remain: &mut [u8] = data;
loop {
r = libc::read(fd, remain.as_mut_ptr().cast(), remain.len());
if r < 0 {
if *errno() == libc::EINTR {
continue;
}
perror!("read");
return r;
}
let r = r as usize;
read_sz += r;
remain = &mut remain[r..];
if r == 0 || remain.is_empty() {
break;
}
}
if !remain.is_empty() {
error_cxx!("read ({} != {})", read_sz, data.len())
}
read_sz as isize
}
}
#[no_mangle]
extern "C" fn xlseek64(fd: RawFd, offset: i64, whence: i32) -> i64 {
unsafe {
let r = libc::lseek64(fd, offset, whence);
if r < 0 {
perror!("lseek64");
}
r
}
#[unsafe(no_mangle)]
unsafe extern "C" fn xxread(fd: RawFd, buf: *mut u8, bufsz: usize) -> isize {
let mut file = unsafe { ManuallyDrop::new(File::from_raw_fd(fd)) };
let data = unsafe { slice_from_ptr_mut(buf, bufsz) };
file.read_exact(data)
.log_cxx()
.map_or(-1, |_| data.len() as isize)
}
pub(crate) fn xpipe2(fds: &mut [i32; 2], flags: i32) -> i32 {
unsafe {
let r = libc::pipe2(fds.as_mut_ptr(), flags);
if r < 0 {
perror!("pipe2");
}
r
libc::pipe2(fds.as_mut_ptr(), flags)
.as_os_result("pipe2", None, None)
.log_cxx()
.unwrap_or(-1)
}
}
#[no_mangle]
#[unsafe(no_mangle)]
extern "C" fn xsetns(fd: RawFd, nstype: i32) -> i32 {
unsafe {
let r = libc::setns(fd, nstype);
if r < 0 {
perror!("setns");
}
r
libc::setns(fd, nstype)
.as_os_result("setns", None, None)
.log_cxx()
.unwrap_or(-1)
}
}
#[no_mangle]
#[unsafe(no_mangle)]
extern "C" fn xunshare(flags: i32) -> i32 {
unsafe {
let r = libc::unshare(flags);
if r < 0 {
perror!("unshare");
}
r
libc::unshare(flags)
.as_os_result("unshare", None, None)
.log_cxx()
.unwrap_or(-1)
}
}
#[no_mangle]
#[unsafe(no_mangle)]
unsafe extern "C" fn xopendir(path: *const c_char) -> *mut libc::DIR {
let dp = libc::opendir(path);
if dp.is_null() {
perror!("opendir {}", ptr_to_str(path));
unsafe {
libc::opendir(path)
.as_os_result("opendir", ptr_to_str(path), None)
.log_cxx()
.map_or(ptr::null_mut(), NonNull::as_ptr)
}
dp
}
#[no_mangle]
#[unsafe(no_mangle)]
extern "C" fn xfdopendir(fd: RawFd) -> *mut libc::DIR {
unsafe {
let dp = libc::fdopendir(fd);
if dp.is_null() {
perror!("fdopendir");
}
dp
libc::fdopendir(fd)
.as_os_result("fdopendir", None, None)
.log_cxx()
.map_or(ptr::null_mut(), NonNull::as_ptr)
}
}
#[no_mangle]
unsafe extern "C" fn xreaddir(dirp: *mut libc::DIR) -> *mut libc::dirent {
*errno() = 0;
loop {
let e = libc::readdir(dirp);
if e.is_null() {
if *errno() != 0 {
perror!("readdir")
}
} else {
// Filter out . and ..
let s = (*e).d_name.as_ptr();
if libc::strcmp(s, raw_cstr!(".")) == 0 || libc::strcmp(s, raw_cstr!("..")) == 0 {
continue;
}
};
return e;
}
#[unsafe(no_mangle)]
unsafe extern "C" fn xreaddir(mut dir: BorrowedDirectory) -> *mut libc::dirent {
dir.read()
.log_cxx()
.ok()
.flatten()
.map_or(ptr::null_mut(), |entry| entry.as_ptr())
}
#[no_mangle]
#[unsafe(no_mangle)]
extern "C" fn xsetsid() -> i32 {
unsafe {
let r = libc::setsid();
if r < 0 {
perror!("setsid");
}
r
libc::setsid()
.as_os_result("setsid", None, None)
.log_cxx()
.unwrap_or(-1)
}
}
#[no_mangle]
#[unsafe(no_mangle)]
extern "C" fn xsocket(domain: i32, ty: i32, protocol: i32) -> RawFd {
unsafe {
let fd = libc::socket(domain, ty, protocol);
if fd < 0 {
perror!("socket");
}
fd
libc::socket(domain, ty, protocol)
.as_os_result("socket", None, None)
.log_cxx()
.unwrap_or(-1)
}
}
#[no_mangle]
#[unsafe(no_mangle)]
unsafe extern "C" fn xbind(socket: i32, address: *const sockaddr, len: socklen_t) -> i32 {
let r = libc::bind(socket, address, len);
if r < 0 {
perror!("bind");
unsafe {
libc::bind(socket, address, len)
.as_os_result("bind", None, None)
.log_cxx()
.unwrap_or(-1)
}
r
}
#[no_mangle]
#[unsafe(no_mangle)]
extern "C" fn xlisten(socket: i32, backlog: i32) -> i32 {
unsafe {
let r = libc::listen(socket, backlog);
if r < 0 {
perror!("listen");
}
r
libc::listen(socket, backlog)
.as_os_result("listen", None, None)
.log_cxx()
.unwrap_or(-1)
}
}
#[no_mangle]
#[unsafe(no_mangle)]
unsafe extern "C" fn xaccept4(
sockfd: RawFd,
addr: *mut sockaddr,
len: *mut socklen_t,
flg: i32,
) -> RawFd {
let fd = libc::accept4(sockfd, addr, len, flg);
if fd < 0 {
perror!("accept4");
unsafe {
libc::accept4(sockfd, addr, len, flg)
.as_os_result("accept4", None, None)
.log_cxx()
.unwrap_or(-1)
}
fd
}
#[no_mangle]
unsafe extern "C" fn xsendmsg(fd: RawFd, msg: *const libc::msghdr, flags: i32) -> ssize_t {
let r = libc::sendmsg(fd, msg, flags);
if r < 0 {
perror!("sendmsg");
}
r
}
#[no_mangle]
unsafe extern "C" fn xrecvmsg(fd: RawFd, msg: *mut libc::msghdr, flags: i32) -> ssize_t {
let r = libc::recvmsg(fd, msg, flags);
if r < 0 {
perror!("recvmsg");
}
r
}
#[no_mangle]
unsafe extern "C" fn xaccess(path: *const c_char, mode: i32) -> i32 {
let r = libc::access(path, mode);
if r < 0 {
perror!("access {}", ptr_to_str(path));
}
r
}
#[no_mangle]
unsafe extern "C" fn xfaccessat(dirfd: RawFd, path: *const c_char, mode: i32, flags: i32) -> i32 {
#[allow(unused_mut)]
let mut r = libc::faccessat(dirfd, path, mode, flags);
if r < 0 {
perror!("faccessat {}", ptr_to_str(path));
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
if r > 0 && *errno() == 0 {
r = 0
}
r
}
#[no_mangle]
#[unsafe(no_mangle)]
unsafe extern "C" fn xstat(path: *const c_char, buf: *mut libc::stat) -> i32 {
let r = libc::stat(path, buf);
if r < 0 {
perror!("stat {}", ptr_to_str(path));
unsafe {
libc::stat(path, buf)
.as_os_result("stat", ptr_to_str(path), None)
.log_cxx()
.unwrap_or(-1)
}
r
}
#[no_mangle]
unsafe extern "C" fn xlstat(path: *const c_char, buf: *mut libc::stat) -> i32 {
let r = libc::lstat(path, buf);
if r < 0 {
perror!("lstat {}", ptr_to_str(path));
}
r
}
#[no_mangle]
#[unsafe(no_mangle)]
unsafe extern "C" fn xfstat(fd: RawFd, buf: *mut libc::stat) -> i32 {
let r = libc::fstat(fd, buf);
if r < 0 {
perror!("fstat");
unsafe {
libc::fstat(fd, buf)
.as_os_result("fstat", None, None)
.log_cxx()
.unwrap_or(-1)
}
r
}
#[no_mangle]
unsafe extern "C" fn xfstatat(
dirfd: RawFd,
path: *const c_char,
buf: *mut libc::stat,
flags: i32,
) -> i32 {
let r = libc::fstatat(dirfd, path, buf, flags);
if r < 0 {
perror!("fstatat {}", ptr_to_str(path));
}
r
}
#[no_mangle]
#[unsafe(no_mangle)]
extern "C" fn xdup(oldfd: RawFd) -> RawFd {
unsafe {
let fd = libc::dup(oldfd);
if fd < 0 {
perror!("dup");
}
fd
libc::dup(oldfd)
.as_os_result("dup", None, None)
.log_cxx()
.unwrap_or(-1)
}
}
#[no_mangle]
#[unsafe(no_mangle)]
extern "C" fn xdup2(oldfd: RawFd, newfd: RawFd) -> RawFd {
unsafe {
let fd = libc::dup2(oldfd, newfd);
if fd < 0 {
perror!("dup2");
}
fd
libc::dup2(oldfd, newfd)
.as_os_result("dup2", None, None)
.log_cxx()
.unwrap_or(-1)
}
}
#[no_mangle]
extern "C" fn xdup3(oldfd: RawFd, newfd: RawFd, flags: i32) -> RawFd {
unsafe {
let fd = libc::syscall(SYS_dup3, oldfd, newfd, flags) as RawFd;
if fd < 0 {
perror!("dup3");
}
fd
}
}
#[no_mangle]
#[unsafe(no_mangle)]
unsafe extern "C" fn xsymlink(target: *const c_char, linkpath: *const c_char) -> i32 {
let r = libc::symlink(target, linkpath);
if r < 0 {
perror!("symlink {} -> {}", ptr_to_str(target), ptr_to_str(linkpath));
unsafe {
libc::symlink(target, linkpath)
.as_os_result("symlink", ptr_to_str(target), ptr_to_str(linkpath))
.log_cxx()
.unwrap_or(-1)
}
r
}
#[no_mangle]
unsafe extern "C" fn xsymlinkat(
target: *const c_char,
dirfd: RawFd,
linkpath: *const c_char,
) -> i32 {
let r = libc::symlinkat(target, dirfd, linkpath);
if r < 0 {
perror!(
"symlinkat {} -> {}",
ptr_to_str(target),
ptr_to_str(linkpath)
);
}
r
}
#[no_mangle]
unsafe extern "C" fn xlinkat(
olddirfd: RawFd,
target: *const c_char,
newdirfd: RawFd,
linkpath: *const c_char,
flags: i32,
) -> i32 {
let r = libc::linkat(olddirfd, target, newdirfd, linkpath, flags);
if r < 0 {
perror!("linkat {} -> {}", ptr_to_str(target), ptr_to_str(linkpath));
}
r
}
#[no_mangle]
#[unsafe(no_mangle)]
unsafe extern "C" fn xmount(
src: *const c_char,
target: *const c_char,
@@ -511,125 +308,95 @@ unsafe extern "C" fn xmount(
flags: c_ulong,
data: *const c_void,
) -> i32 {
let r = libc::mount(src, target, fstype, flags, data);
if r < 0 {
perror!("mount {} -> {}", ptr_to_str(src), ptr_to_str(target));
unsafe {
libc::mount(src, target, fstype, flags, data)
.as_os_result("mount", ptr_to_str(src), ptr_to_str(target))
.log_cxx()
.unwrap_or(-1)
}
r
}
#[no_mangle]
unsafe extern "C" fn xumount(target: *const c_char) -> i32 {
let r = libc::umount(target);
if r < 0 {
perror!("umount {}", ptr_to_str(target));
}
r
}
#[no_mangle]
#[unsafe(no_mangle)]
unsafe extern "C" fn xumount2(target: *const c_char, flags: i32) -> i32 {
let r = libc::umount2(target, flags);
if r < 0 {
perror!("umount2 {}", ptr_to_str(target));
unsafe {
libc::umount2(target, flags)
.as_os_result("umount2", ptr_to_str(target), None)
.log_cxx()
.unwrap_or(-1)
}
r
}
#[no_mangle]
#[unsafe(no_mangle)]
unsafe extern "C" fn xrename(oldname: *const c_char, newname: *const c_char) -> i32 {
let r = libc::rename(oldname, newname);
if r < 0 {
perror!("rename {} -> {}", ptr_to_str(oldname), ptr_to_str(newname));
unsafe {
libc::rename(oldname, newname)
.as_os_result("rename", ptr_to_str(oldname), ptr_to_str(newname))
.log_cxx()
.unwrap_or(-1)
}
r
}
#[no_mangle]
#[unsafe(no_mangle)]
unsafe extern "C" fn xmkdir(path: *const c_char, mode: mode_t) -> i32 {
let r = libc::mkdir(path, mode);
if r < 0 && *errno() != libc::EEXIST {
perror!("mkdir {}", ptr_to_str(path));
unsafe {
match Utf8CStr::from_ptr(path) {
Ok(path) => path.mkdir(mode).log_cxx().map_or(-1, |_| 0),
Err(_) => -1,
}
}
r
}
#[no_mangle]
#[unsafe(no_mangle)]
unsafe extern "C" fn xmkdirs(path: *const c_char, mode: mode_t) -> i32 {
match Utf8CStr::from_ptr(path) {
Ok(p) => FsPath::from(p)
.mkdirs(mode)
.log_cxx_with_msg(|w| w.write_fmt(format_args!("mkdirs {} failed", p)))
.map_or(-1, |_| 0),
Err(_) => -1,
unsafe {
match Utf8CStr::from_ptr(path) {
Ok(path) => path.mkdirs(mode).log_cxx().map_or(-1, |_| 0),
Err(_) => -1,
}
}
}
#[no_mangle]
unsafe extern "C" fn xmkdirat(dirfd: RawFd, path: *const c_char, mode: mode_t) -> i32 {
let r = libc::mkdirat(dirfd, path, mode);
if r < 0 && *errno() != libc::EEXIST {
perror!("mkdirat {}", ptr_to_str(path));
}
r
}
#[no_mangle]
#[unsafe(no_mangle)]
unsafe extern "C" fn xsendfile(
out_fd: RawFd,
in_fd: RawFd,
offset: *mut off_t,
count: usize,
) -> isize {
let r = libc::sendfile(out_fd, in_fd, offset, count);
if r < 0 {
perror!("sendfile");
unsafe {
libc::sendfile(out_fd, in_fd, offset, count)
.as_os_result("sendfile", None, None)
.log_cxx()
.unwrap_or(-1)
}
r
}
#[no_mangle]
unsafe extern "C" fn xmmap(
addr: *mut c_void,
len: usize,
prot: i32,
flags: i32,
fd: RawFd,
offset: off_t,
) -> *mut c_void {
let r = libc::mmap(addr, len, prot, flags, fd, offset);
if r == libc::MAP_FAILED {
perror!("mmap");
return ptr::null_mut();
}
r
}
#[no_mangle]
#[unsafe(no_mangle)]
extern "C" fn xfork() -> i32 {
unsafe {
let r = libc::fork();
if r < 0 {
perror!("fork");
}
r
libc::fork()
.as_os_result("fork", None, None)
.log_cxx()
.unwrap_or(-1)
}
}
#[no_mangle]
#[unsafe(no_mangle)]
unsafe extern "C" fn xpoll(fds: *mut pollfd, nfds: nfds_t, timeout: i32) -> i32 {
let r = libc::poll(fds, nfds, timeout);
if r < 0 {
perror!("poll");
unsafe {
libc::poll(fds, nfds, timeout)
.as_os_result("poll", None, None)
.log_cxx()
.unwrap_or(-1)
}
r
}
#[no_mangle]
#[unsafe(no_mangle)]
unsafe extern "C" fn xmknod(pathname: *const c_char, mode: mode_t, dev: dev_t) -> i32 {
let r = libc::mknod(pathname, mode, dev);
if r < 0 {
perror!("mknod {}", ptr_to_str(pathname));
unsafe {
libc::mknod(pathname, mode, dev)
.as_os_result("mknod", ptr_to_str(pathname), None)
.log_cxx()
.unwrap_or(-1)
}
r
}

View File

@@ -1,7 +1,7 @@
[package]
name = "magiskboot"
version = "0.0.0"
edition = "2021"
version.workspace = true
edition.workspace = true
[lib]
crate-type = ["staticlib"]
@@ -13,7 +13,7 @@ pb-rs = { workspace = true }
[dependencies]
base = { path = "../base" }
cxx = { path = "../external/cxx-rs" }
cxx = { workspace = true }
byteorder = { workspace = true }
size = { workspace = true }
quick-protobuf = { workspace = true }
@@ -30,3 +30,9 @@ der = { workspace = true, features = ["derive", "pem"] }
fdt = { workspace = true }
bytemuck = { workspace = true, features = ["derive", "min_const_generics"] }
num-traits = { workspace = true }
libz-rs-sys = { workspace = true }
libbz2-rs-sys = { workspace = true }
# Pin version to prevent cargo update break builds
block-buffer = { workspace = true }
sec1 = { workspace = true }

View File

@@ -431,9 +431,20 @@ bool boot_img::parse_image(const uint8_t *p, format_t type) {
}
if (k_fmt == ZIMAGE) {
z_hdr = reinterpret_cast<const zimage_hdr *>(kernel);
if (const void *gzip = memmem(kernel, hdr->kernel_size(), GZIP1_MAGIC "\x08\x00", 4)) {
const uint8_t* found_pos = 0;
for (const uint8_t* search_pos = kernel + 0x28; search_pos < kernel + hdr->kernel_size(); search_pos++) {
// ^^^^^^ +0x28 to search after zimage header and magic
if (check_fmt_lg(search_pos, hdr->kernel_size() - (search_pos - kernel)) != UNKNOWN) {
found_pos = search_pos;
search_pos = kernel + hdr->kernel_size();
}
}
if (found_pos != 0) {
fprintf(stderr, "ZIMAGE_KERNEL\n");
z_info.hdr_sz = (const uint8_t *) gzip - kernel;
z_info.hdr_sz = (const uint8_t *) found_pos - kernel;
// Find end of piggy
uint32_t zImage_size = z_hdr->end - z_hdr->start;
@@ -457,7 +468,7 @@ bool boot_img::parse_image(const uint8_t *p, format_t type) {
k_fmt = check_fmt_lg(kernel, hdr->kernel_size());
}
} else {
fprintf(stderr, "! Could not find zImage gzip piggy, keeping raw kernel\n");
fprintf(stderr, "! Could not find zImage piggy, keeping raw kernel\n");
}
}
fprintf(stderr, "%-*s [%s]\n", PADDING, "KERNEL_FMT", fmt2name[k_fmt]);

View File

@@ -1,9 +1,9 @@
use pb_rs::{types::FileDescriptor, ConfigBuilder};
use pb_rs::{ConfigBuilder, types::FileDescriptor};
use crate::gen::gen_cxx_binding;
use crate::codegen::gen_cxx_binding;
#[path = "../include/gen.rs"]
mod gen;
#[path = "../include/codegen.rs"]
mod codegen;
fn main() {
println!("cargo:rerun-if-changed=proto/update_metadata.proto");

287
native/src/boot/bzlib.h Normal file
View File

@@ -0,0 +1,287 @@
/*-------------------------------------------------------------*/
/*--- Public header file for the library. ---*/
/*--- bzlib.h ---*/
/*-------------------------------------------------------------*/
/* ------------------------------------------------------------------
This file is part of bzip2/libbzip2, a program and library for
lossless, block-sorting data compression.
bzip2/libbzip2 version 1.1.0 of 6 September 2010
Copyright (C) 1996-2010 Julian Seward <jseward@acm.org>
Please read the WARNING, DISCLAIMER and PATENTS sections in the
README file.
This program is released under the terms of the license contained
in the file LICENSE.
------------------------------------------------------------------ */
#ifndef _BZLIB_H
#define _BZLIB_H
#ifdef __cplusplus
extern "C" {
#endif
#define BZ_RUN 0
#define BZ_FLUSH 1
#define BZ_FINISH 2
#define BZ_OK 0
#define BZ_RUN_OK 1
#define BZ_FLUSH_OK 2
#define BZ_FINISH_OK 3
#define BZ_STREAM_END 4
#define BZ_SEQUENCE_ERROR (-1)
#define BZ_PARAM_ERROR (-2)
#define BZ_MEM_ERROR (-3)
#define BZ_DATA_ERROR (-4)
#define BZ_DATA_ERROR_MAGIC (-5)
#define BZ_IO_ERROR (-6)
#define BZ_UNEXPECTED_EOF (-7)
#define BZ_OUTBUFF_FULL (-8)
#define BZ_CONFIG_ERROR (-9)
typedef
struct {
char *next_in;
unsigned int avail_in;
unsigned int total_in_lo32;
unsigned int total_in_hi32;
char *next_out;
unsigned int avail_out;
unsigned int total_out_lo32;
unsigned int total_out_hi32;
void *state;
void *(*bzalloc)(void *,int,int);
void (*bzfree)(void *,void *);
void *opaque;
}
bz_stream;
#ifndef BZ_IMPORT
#define BZ_EXPORT
#endif
#ifndef BZ_NO_STDIO
/* Need a definitition for FILE */
#include <stdio.h>
#endif
#ifdef _WIN32
# include <windows.h>
# ifdef small
/* windows.h define small to char */
# undef small
# endif
# ifndef WINAPI
# define WINAPI
# endif
# ifdef BZ_EXPORT
# define BZ_API(func) WINAPI func
# define BZ_EXTERN extern
# else
/* import windows dll dynamically */
# define BZ_API(func) (WINAPI * func)
# define BZ_EXTERN
# endif
#else
# define BZ_API(func) func
#endif
#ifndef BZ_EXTERN
#define BZ_EXTERN extern
#endif
/*-- Core (low-level) library functions --*/
BZ_EXTERN int BZ_API(BZ2_bzCompressInit) (
bz_stream* strm,
int blockSize100k,
int verbosity,
int workFactor
);
BZ_EXTERN int BZ_API(BZ2_bzCompress) (
bz_stream* strm,
int action
);
BZ_EXTERN int BZ_API(BZ2_bzCompressEnd) (
bz_stream* strm
);
BZ_EXTERN int BZ_API(BZ2_bzDecompressInit) (
bz_stream *strm,
int verbosity,
int small
);
BZ_EXTERN int BZ_API(BZ2_bzDecompress) (
bz_stream* strm
);
BZ_EXTERN int BZ_API(BZ2_bzDecompressEnd) (
bz_stream *strm
);
/*-- High(er) level library functions --*/
#ifndef BZ_NO_STDIO
#define BZ_MAX_UNUSED 5000
typedef void BZFILE;
BZ_EXTERN BZFILE* BZ_API(BZ2_bzReadOpen) (
int* bzerror,
FILE* f,
int verbosity,
int small,
void* unused,
int nUnused
);
BZ_EXTERN void BZ_API(BZ2_bzReadClose) (
int* bzerror,
BZFILE* b
);
BZ_EXTERN void BZ_API(BZ2_bzReadGetUnused) (
int* bzerror,
BZFILE* b,
void** unused,
int* nUnused
);
BZ_EXTERN int BZ_API(BZ2_bzRead) (
int* bzerror,
BZFILE* b,
void* buf,
int len
);
BZ_EXTERN BZFILE* BZ_API(BZ2_bzWriteOpen) (
int* bzerror,
FILE* f,
int blockSize100k,
int verbosity,
int workFactor
);
BZ_EXTERN void BZ_API(BZ2_bzWrite) (
int* bzerror,
BZFILE* b,
void* buf,
int len
);
BZ_EXTERN void BZ_API(BZ2_bzWriteClose) (
int* bzerror,
BZFILE* b,
int abandon,
unsigned int* nbytes_in,
unsigned int* nbytes_out
);
BZ_EXTERN void BZ_API(BZ2_bzWriteClose64) (
int* bzerror,
BZFILE* b,
int abandon,
unsigned int* nbytes_in_lo32,
unsigned int* nbytes_in_hi32,
unsigned int* nbytes_out_lo32,
unsigned int* nbytes_out_hi32
);
#endif
/*-- Utility functions --*/
BZ_EXTERN int BZ_API(BZ2_bzBuffToBuffCompress) (
char* dest,
unsigned int* destLen,
char* source,
unsigned int sourceLen,
int blockSize100k,
int verbosity,
int workFactor
);
BZ_EXTERN int BZ_API(BZ2_bzBuffToBuffDecompress) (
char* dest,
unsigned int* destLen,
char* source,
unsigned int sourceLen,
int small,
int verbosity
);
/*--
Code contributed by Yoshioka Tsuneo (tsuneo@rr.iij4u.or.jp)
to support better zlib compatibility.
This code is not _officially_ part of libbzip2 (yet);
I haven't tested it, documented it, or considered the
threading-safeness of it.
If this code breaks, please contact both Yoshioka and me.
--*/
BZ_EXTERN const char * BZ_API(BZ2_bzlibVersion) (
void
);
#ifndef BZ_NO_STDIO
BZ_EXTERN BZFILE * BZ_API(BZ2_bzopen) (
const char *path,
const char *mode
);
BZ_EXTERN BZFILE * BZ_API(BZ2_bzdopen) (
int fd,
const char *mode
);
BZ_EXTERN int BZ_API(BZ2_bzread) (
BZFILE* b,
void* buf,
int len
);
BZ_EXTERN int BZ_API(BZ2_bzwrite) (
BZFILE* b,
void* buf,
int len
);
BZ_EXTERN int BZ_API(BZ2_bzflush) (
BZFILE* b
);
BZ_EXTERN void BZ_API(BZ2_bzclose) (
BZFILE* b
);
BZ_EXTERN const char * BZ_API(BZ2_bzerror) (
BZFILE *b,
int *errnum
);
#endif
#ifdef __cplusplus
}
#endif
#endif
/*-------------------------------------------------------------*/
/*--- end bzlib.h ---*/
/*-------------------------------------------------------------*/

View File

@@ -2,7 +2,7 @@
#include <functional>
#include <zlib.h>
#include <bzlib.h>
#include "bzlib.h"
#include <lzma.h>
#include <lz4.h>
#include <lz4frame.h>
@@ -153,7 +153,7 @@ class zopfli_encoder : public chunk_out_stream {
public:
explicit zopfli_encoder(out_strm_ptr &&base) :
chunk_out_stream(std::move(base), ZOPFLI_MASTER_BLOCK_SIZE),
zo{}, out(nullptr), outsize(0), crc(crc32_z(0L, Z_NULL, 0)), in_total(0), bp(0) {
zo{}, out(nullptr), outsize(0), crc(crc32(0L, Z_NULL, 0)), in_total(0), bp(0) {
ZopfliInitOptions(&zo);
// This config is already better than gzip -9
@@ -198,7 +198,7 @@ protected:
auto in = static_cast<const unsigned char *>(buf);
in_total += len;
crc = crc32_z(crc, in, len);
crc = crc32(crc, in, len);
ZopfliDeflatePart(&zo, 2, final, in, 0, len, &bp, &out, &outsize);

View File

@@ -10,18 +10,18 @@ use std::process::exit;
use std::str;
use argh::FromArgs;
use bytemuck::{from_bytes, Pod, Zeroable};
use bytemuck::{Pod, Zeroable, from_bytes};
use num_traits::cast::AsPrimitive;
use size::{Base, Size, Style};
use base::libc::{
c_char, dev_t, gid_t, major, makedev, minor, mknod, mode_t, uid_t, O_CLOEXEC, O_CREAT,
O_RDONLY, O_TRUNC, O_WRONLY, S_IFBLK, S_IFCHR, S_IFDIR, S_IFLNK, S_IFMT, S_IFREG, S_IRGRP,
S_IROTH, S_IRUSR, S_IWGRP, S_IWOTH, S_IWUSR, S_IXGRP, S_IXOTH, S_IXUSR,
O_CLOEXEC, O_CREAT, O_RDONLY, O_TRUNC, O_WRONLY, S_IFBLK, S_IFCHR, S_IFDIR, S_IFLNK, S_IFMT,
S_IFREG, S_IRGRP, S_IROTH, S_IRUSR, S_IWGRP, S_IWOTH, S_IWUSR, S_IXGRP, S_IXOTH, S_IXUSR,
c_char, dev_t, gid_t, major, makedev, minor, mknod, mode_t, uid_t,
};
use base::{
log_err, map_args, BytesExt, EarlyExitExt, FsPath, LoggedResult, MappedFile, ResultExt,
Utf8CStr, Utf8CStrBufArr, Utf8CStrWrite, WriteExt,
BytesExt, EarlyExitExt, LoggedResult, MappedFile, ResultExt, Utf8CStr, Utf8CStrBuf, WriteExt,
cstr, log_err, map_args,
};
use crate::check_env;
@@ -342,13 +342,13 @@ impl Cpio {
eprintln!("Extracting entry [{}] to [{}]", path, out);
let out = Utf8CStr::from_string(out);
let out = FsPath::from(out);
let mut buf = Utf8CStrBufArr::default();
let mut buf = cstr::buf::default();
// Make sure its parent directories exist
if out.parent(&mut buf) {
FsPath::from(&buf).mkdirs(0o755)?;
if let Some(dir) = out.parent_dir() {
buf.push_str(dir);
buf.mkdirs(0o755)?;
}
let mode: mode_t = (entry.mode & 0o777).into();
@@ -362,7 +362,7 @@ impl Cpio {
S_IFLNK => {
buf.clear();
buf.push_str(str::from_utf8(entry.data.as_slice())?);
FsPath::from(&buf).symlink_to(out)?;
out.create_symlink_to(&buf)?;
}
S_IFBLK | S_IFCHR => {
let dev = makedev(entry.rdevmajor.try_into()?, entry.rdevminor.try_into()?);
@@ -399,7 +399,6 @@ impl Cpio {
return Err(log_err!("path cannot end with / for add"));
}
let file = Utf8CStr::from_string(file);
let file = FsPath::from(&file);
let attr = file.get_attr()?;
let mut content = Vec::<u8>::new();
@@ -413,8 +412,8 @@ impl Cpio {
file.open(O_RDONLY | O_CLOEXEC)?.read_to_end(&mut content)?;
mode | S_IFREG
} else {
rdevmajor = unsafe { major(attr.st.st_rdev.as_()) }.as_();
rdevminor = unsafe { minor(attr.st.st_rdev.as_()) }.as_();
rdevmajor = major(attr.st.st_rdev.as_()).as_();
rdevminor = minor(attr.st.st_rdev.as_()).as_();
if attr.is_block_device() {
mode | S_IFBLK
} else if attr.is_char_device() {
@@ -746,8 +745,7 @@ impl Display for CpioEntry {
Size::from_bytes(self.data.len())
.format()
.with_style(Style::Abbreviated)
.with_base(Base::Base10)
.to_string(),
.with_base(Base::Base10),
self.rdevmajor,
self.rdevminor,
)
@@ -755,9 +753,9 @@ impl Display for CpioEntry {
}
pub fn cpio_commands(argc: i32, argv: *const *const c_char) -> bool {
fn inner(argc: i32, argv: *const *const c_char) -> LoggedResult<()> {
let res: LoggedResult<()> = try {
if argc < 1 {
return Err(log_err!("No arguments"));
Err(log_err!("No arguments"))?;
}
let cmds = map_args(argc, argv)?;
@@ -766,7 +764,7 @@ pub fn cpio_commands(argc: i32, argv: *const *const c_char) -> bool {
CpioCli::from_args(&["magiskboot", "cpio"], &cmds).on_early_exit(print_cpio_usage);
let file = Utf8CStr::from_string(&mut cli.file);
let mut cpio = if FsPath::from(file).exists() {
let mut cpio = if file.exists() {
Cpio::load_from_file(file)?
} else {
Cpio::new()
@@ -807,7 +805,7 @@ pub fn cpio_commands(argc: i32, argv: *const *const c_char) -> bool {
CpioAction::Add(Add { mode, path, file }) => cpio.add(*mode, path, file)?,
CpioAction::Extract(Extract { paths }) => {
if !paths.is_empty() && paths.len() != 2 {
return Err(log_err!("invalid arguments"));
Err(log_err!("invalid arguments"))?;
}
let mut it = paths.iter_mut();
cpio.extract(it.next(), it.next())?;
@@ -819,10 +817,8 @@ pub fn cpio_commands(argc: i32, argv: *const *const c_char) -> bool {
};
}
cpio.dump(file)?;
Ok(())
}
inner(argc, argv)
.log_with_msg(|w| w.write_str("Failed to process cpio"))
};
res.log_with_msg(|w| w.write_str("Failed to process cpio"))
.is_ok()
}

View File

@@ -2,12 +2,12 @@ use std::{cell::UnsafeCell, process::exit};
use argh::FromArgs;
use fdt::{
Fdt, FdtError,
node::{FdtNode, NodeProperty},
Fdt,
};
use base::{
libc::c_char, log_err, map_args, EarlyExitExt, LoggedResult, MappedFile, ResultExt, Utf8CStr,
EarlyExitExt, LoggedResult, MappedFile, ResultExt, Utf8CStr, libc::c_char, log_err, map_args,
};
use crate::{check_env, patch::patch_verity};
@@ -171,15 +171,17 @@ fn for_each_fdt<F: FnMut(usize, Fdt) -> LoggedResult<()>>(
if slice.len() < 40 {
break;
}
let fdt = Fdt::new(slice)?;
let fdt = match Fdt::new(slice) {
Err(FdtError::BufferTooSmall) => {
eprintln!("dtb.{:04} is truncated", dtb_num);
break;
}
Ok(fdt) => fdt,
e => e?,
};
let size = fdt.total_size();
if size > slice.len() {
eprintln!("dtb.{:04} is truncated", dtb_num);
break;
}
f(dtb_num, fdt)?;
dtb_num += 1;
@@ -273,9 +275,9 @@ fn dtb_patch(file: &Utf8CStr) -> LoggedResult<bool> {
}
pub fn dtb_commands(argc: i32, argv: *const *const c_char) -> bool {
fn inner(argc: i32, argv: *const *const c_char) -> LoggedResult<()> {
let res: LoggedResult<()> = try {
if argc < 1 {
return Err(log_err!("No arguments"));
Err(log_err!("No arguments"))?;
}
let cmds = map_args(argc, argv)?;
@@ -299,9 +301,7 @@ pub fn dtb_commands(argc: i32, argv: *const *const c_char) -> bool {
}
}
}
Ok(())
}
inner(argc, argv)
.log_with_msg(|w| w.write_str("Failed to process dtb"))
};
res.log_with_msg(|w| w.write_str("Failed to process dtb"))
.is_ok()
}

View File

@@ -1,13 +1,16 @@
#![feature(format_args_nl)]
#![feature(btree_extract_if)]
#![feature(iter_intersperse)]
#![feature(try_blocks)]
pub use base;
use cpio::cpio_commands;
use dtb::dtb_commands;
pub use libbz2_rs_sys::*;
pub use libz_rs_sys::*;
use patch::hexpatch;
use payload::extract_boot_from_payload;
use sign::{get_sha, sha1_hash, sha256_hash, sign_boot_image, verify_boot_image, SHA};
use sign::{SHA, get_sha, sha1_hash, sha256_hash, sign_boot_image, verify_boot_image};
use std::env;
mod cpio;
@@ -21,6 +24,14 @@ mod sign;
#[cxx::bridge]
pub mod ffi {
unsafe extern "C++" {
include!("../base/include/base.hpp");
#[namespace = "rust"]
#[cxx_name = "Utf8CStr"]
type Utf8CStrRef<'a> = base::ffi::Utf8CStrRef<'a>;
}
unsafe extern "C++" {
include!("compress.hpp");
fn decompress(buf: &[u8], fd: i32) -> bool;
@@ -51,10 +62,10 @@ pub mod ffi {
#[namespace = "rust"]
#[allow(unused_unsafe)]
extern "Rust" {
unsafe fn extract_boot_from_payload(
partition: *const c_char,
in_path: *const c_char,
out_path: *const c_char,
fn extract_boot_from_payload(
partition: Utf8CStrRef,
in_path: Utf8CStrRef,
out_path: Utf8CStrRef,
) -> bool;
unsafe fn cpio_commands(argc: i32, argv: *const *const c_char) -> bool;
unsafe fn verify_boot_image(img: &BootImage, cert: *const c_char) -> bool;
@@ -70,5 +81,5 @@ pub mod ffi {
#[inline(always)]
pub(crate) fn check_env(env: &str) -> bool {
env::var(env).map_or(false, |var| var == "true")
env::var(env).is_ok_and(|var| var == "true")
}

View File

@@ -217,8 +217,8 @@ int main(int argc, char *argv[]) {
} else if (argc > 2 && action == "extract") {
return rust::extract_boot_from_payload(
argv[2],
argc > 3 ? argv[3] : nullptr,
argc > 4 ? argv[4] : nullptr
argc > 3 ? argv[3] : "",
argc > 4 ? argv[4] : ""
) ? 0 : 1;
} else {
usage(argv[0]);

View File

@@ -63,15 +63,17 @@ fn remove_pattern(buf: &mut [u8], pattern_matcher: unsafe fn(&[u8]) -> Option<us
pub fn patch_verity(buf: &mut [u8]) -> usize {
unsafe fn match_verity_pattern(buf: &[u8]) -> Option<usize> {
match_patterns!(
buf,
b"verifyatboot",
b"verify",
b"avb_keys",
b"avb",
b"support_scfs",
b"fsverity"
)
unsafe {
match_patterns!(
buf,
b"verifyatboot",
b"verify",
b"avb_keys",
b"avb",
b"support_scfs",
b"fsverity"
)
}
}
remove_pattern(buf, match_verity_pattern)
@@ -79,7 +81,7 @@ pub fn patch_verity(buf: &mut [u8]) -> usize {
pub fn patch_encryption(buf: &mut [u8]) -> usize {
unsafe fn match_encryption_pattern(buf: &[u8]) -> Option<usize> {
match_patterns!(buf, b"forceencrypt", b"forcefdeorfbe", b"fileencryption")
unsafe { match_patterns!(buf, b"forceencrypt", b"forcefdeorfbe", b"fileencryption") }
}
remove_pattern(buf, match_encryption_pattern)
@@ -95,13 +97,13 @@ fn hex2byte(hex: &[u8]) -> Vec<u8> {
let low = bytes[1].to_ascii_uppercase() - b'0';
let h = if high > 9 { high - 7 } else { high };
let l = if low > 9 { low - 7 } else { low };
v.push(h << 4 | l);
v.push((h << 4) | l);
}
v
}
pub fn hexpatch(file: &[u8], from: &[u8], to: &[u8]) -> bool {
fn inner(file: &[u8], from: &[u8], to: &[u8]) -> LoggedResult<bool> {
let res: LoggedResult<bool> = try {
let file = Utf8CStr::from_bytes(file)?;
let from = Utf8CStr::from_bytes(from)?;
let to = Utf8CStr::from_bytes(to)?;
@@ -114,8 +116,7 @@ pub fn hexpatch(file: &[u8], from: &[u8], to: &[u8]) -> bool {
for off in &v {
eprintln!("Patch @ {:#010X} [{}] -> [{}]", off, from, to);
}
Ok(!v.is_empty())
}
inner(file, from, to).unwrap_or(false)
!v.is_empty()
};
res.unwrap_or(false)
}

View File

@@ -1,17 +1,19 @@
use std::fs::File;
use std::io::{BufReader, Read, Seek, SeekFrom, Write};
use std::os::fd::{AsRawFd, FromRawFd};
use std::{
fs::File,
io::{BufReader, Read, Seek, SeekFrom, Write},
os::fd::{AsRawFd, FromRawFd},
};
use byteorder::{BigEndian, ReadBytesExt};
use quick_protobuf::{BytesReader, MessageRead};
use base::libc::c_char;
use base::{error, LoggedError, LoggedResult, ReadSeekExt, StrErr, Utf8CStr};
use base::{ResultExt, WriteExt};
use crate::ffi;
use crate::proto::update_metadata::mod_InstallOperation::Type;
use crate::proto::update_metadata::DeltaArchiveManifest;
use crate::{
ffi,
proto::update_metadata::{DeltaArchiveManifest, mod_InstallOperation::Type},
};
use base::{
LoggedError, LoggedResult, ReadSeekExt, ResultExt, Utf8CStr, WriteExt, error, ffi::Utf8CStrRef,
};
macro_rules! bad_payload {
($msg:literal) => {{
@@ -178,28 +180,23 @@ fn do_extract_boot_from_payload(
}
pub fn extract_boot_from_payload(
in_path: *const c_char,
partition: *const c_char,
out_path: *const c_char,
in_path: Utf8CStrRef,
partition: Utf8CStrRef,
out_path: Utf8CStrRef,
) -> bool {
fn inner(
in_path: *const c_char,
partition: *const c_char,
out_path: *const c_char,
) -> LoggedResult<()> {
let in_path = unsafe { Utf8CStr::from_ptr(in_path) }?;
let partition = match unsafe { Utf8CStr::from_ptr(partition) } {
Ok(s) => Some(s),
Err(StrErr::NullPointerError) => None,
Err(e) => Err(e)?,
let res: LoggedResult<()> = try {
let partition = if partition.is_empty() {
None
} else {
Some(partition)
};
let out_path = match unsafe { Utf8CStr::from_ptr(out_path) } {
Ok(s) => Some(s),
Err(StrErr::NullPointerError) => None,
Err(e) => Err(e)?,
let out_path = if out_path.is_empty() {
None
} else {
Some(out_path)
};
do_extract_boot_from_payload(in_path, partition, out_path)
.log_with_msg(|w| w.write_str("Failed to extract from payload"))
}
inner(in_path, partition, out_path).is_ok()
do_extract_boot_from_payload(in_path, partition, out_path)?
};
res.log_with_msg(|w| w.write_str("Failed to extract from payload"))
.is_ok()
}

View File

@@ -15,18 +15,18 @@ use rsa::pkcs1v15::{
Signature as RsaSignature, SigningKey as RsaSigningKey, VerifyingKey as RsaVerifyingKey,
};
use rsa::pkcs8::SubjectPublicKeyInfoRef;
use rsa::signature::hazmat::{PrehashSigner, PrehashVerifier};
use rsa::signature::SignatureEncoding;
use rsa::signature::hazmat::{PrehashSigner, PrehashVerifier};
use rsa::{RsaPrivateKey, RsaPublicKey};
use sha1::Sha1;
use sha2::{Sha256, Sha384, Sha512};
use x509_cert::der::asn1::{OctetString, PrintableString};
use x509_cert::der::Any;
use x509_cert::spki::AlgorithmIdentifier;
use x509_cert::Certificate;
use x509_cert::der::Any;
use x509_cert::der::asn1::{OctetString, PrintableString};
use x509_cert::spki::AlgorithmIdentifier;
use base::libc::c_char;
use base::{log_err, LoggedResult, MappedFile, ResultExt, StrErr, Utf8CStr};
use base::{LoggedResult, MappedFile, ResultExt, StrErr, Utf8CStr, log_err};
use crate::ffi::BootImage;
@@ -157,20 +157,32 @@ struct Signer {
impl Signer {
fn from_private_key(key: &[u8]) -> LoggedResult<Signer> {
let digest: Box<dyn DynDigest>;
let key = if let Ok(rsa) = RsaPrivateKey::from_pkcs8_der(key) {
digest = Box::<Sha256>::default();
SigningKey::SHA256withRSA(RsaSigningKey::<Sha256>::new(rsa))
} else if let Ok(ec) = P256SigningKey::from_pkcs8_der(key) {
digest = Box::<Sha256>::default();
SigningKey::SHA256withECDSA(ec)
} else if let Ok(ec) = P384SigningKey::from_pkcs8_der(key) {
digest = Box::<Sha384>::default();
SigningKey::SHA384withECDSA(ec)
} else if let Ok(ec) = P521SigningKey::from_pkcs8_der(key) {
digest = Box::<Sha512>::default();
SigningKey::SHA521withECDSA(ec)
} else {
return Err(log_err!("Unsupported private key"));
let key = match RsaPrivateKey::from_pkcs8_der(key) {
Ok(rsa) => {
digest = Box::<Sha256>::default();
SigningKey::SHA256withRSA(RsaSigningKey::<Sha256>::new(rsa))
}
_ => match P256SigningKey::from_pkcs8_der(key) {
Ok(ec) => {
digest = Box::<Sha256>::default();
SigningKey::SHA256withECDSA(ec)
}
_ => match P384SigningKey::from_pkcs8_der(key) {
Ok(ec) => {
digest = Box::<Sha384>::default();
SigningKey::SHA384withECDSA(ec)
}
_ => match P521SigningKey::from_pkcs8_der(key) {
Ok(ec) => {
digest = Box::<Sha512>::default();
SigningKey::SHA521withECDSA(ec)
}
_ => {
return Err(log_err!("Unsupported private key"));
}
},
},
},
};
Ok(Signer { digest, key })
}
@@ -254,7 +266,7 @@ impl BootSignature {
}
pub fn verify_boot_image(img: &BootImage, cert: *const c_char) -> bool {
fn inner(img: &BootImage, cert: *const c_char) -> LoggedResult<()> {
let res: LoggedResult<()> = try {
let tail = img.tail();
// Don't use BootSignature::from_der because tail might have trailing zeros
let mut reader = SliceReader::new(tail)?;
@@ -268,9 +280,8 @@ pub fn verify_boot_image(img: &BootImage, cert: *const c_char) -> bool {
Err(e) => Err(e)?,
};
sig.verify(img.payload())?;
Ok(())
}
inner(img, cert).is_ok()
};
res.is_ok()
}
enum Bytes {
@@ -296,12 +307,7 @@ pub fn sign_boot_image(
cert: *const c_char,
key: *const c_char,
) -> Vec<u8> {
fn inner(
payload: &[u8],
name: *const c_char,
cert: *const c_char,
key: *const c_char,
) -> LoggedResult<Vec<u8>> {
let res: LoggedResult<Vec<u8>> = try {
// Process arguments
let name = unsafe { Utf8CStr::from_ptr(name) }?;
let cert = match unsafe { Utf8CStr::from_ptr(cert) } {
@@ -337,7 +343,7 @@ pub fn sign_boot_image(
authenticated_attributes: attr,
signature: OctetString::new(sig)?,
};
sig.to_der().log()
}
inner(payload, name, cert, key).unwrap_or_default()
sig.to_der()?
};
res.unwrap_or_default()
}

View File

@@ -1,7 +1,7 @@
[package]
name = "magisk"
version = "0.0.0"
edition = "2021"
version.workspace = true
edition.workspace = true
[lib]
crate-type = ["staticlib"]
@@ -17,6 +17,7 @@ pb-rs = { workspace = true }
[dependencies]
base = { path = "../base", features = ["selinux"] }
derive = { path = "derive" }
cxx = { workspace = true }
num-traits = { workspace = true }
num-derive = { workspace = true }

View File

@@ -1,7 +1,6 @@
#include <sys/stat.h>
#include <consts.hpp>
#include <selinux.hpp>
#include <base.hpp>
int main(int argc, char *argv[]) {

View File

@@ -3,7 +3,6 @@
#include <sys/stat.h>
#include <consts.hpp>
#include <selinux.hpp>
#include <base.hpp>
using namespace std;

View File

@@ -1,211 +0,0 @@
#include <sys/mount.h>
#include <sys/wait.h>
#include <sys/sysmacros.h>
#include <linux/input.h>
#include <libgen.h>
#include <set>
#include <string>
#include <consts.hpp>
#include <base.hpp>
#include <core.hpp>
#include <selinux.hpp>
using namespace std;
bool zygisk_enabled = false;
/*********
* Setup *
*********/
static bool magisk_env() {
char buf[4096];
LOGI("* Initializing Magisk environment\n");
ssprintf(buf, sizeof(buf), "%s/0/%s/install", APP_DATA_DIR, JAVA_PACKAGE_NAME);
// Alternative binaries paths
const char *alt_bin[] = { "/cache/data_adb/magisk", "/data/magisk", buf };
for (auto alt : alt_bin) {
if (access(alt, F_OK) == 0) {
rm_rf(DATABIN);
cp_afc(alt, DATABIN);
rm_rf(alt);
}
}
rm_rf("/cache/data_adb");
// Directories in /data/adb
chmod(SECURE_DIR, 0700);
xmkdir(DATABIN, 0755);
xmkdir(MODULEROOT, 0755);
xmkdir(SECURE_DIR "/post-fs-data.d", 0755);
xmkdir(SECURE_DIR "/service.d", 0755);
restorecon();
if (access(DATABIN "/busybox", X_OK))
return false;
ssprintf(buf, sizeof(buf), "%s/" BBPATH "/busybox", get_magisk_tmp());
mkdir(dirname(buf), 0755);
cp_afc(DATABIN "/busybox", buf);
exec_command_async(buf, "--install", "-s", dirname(buf));
// magisk32 and magiskpolicy are not installed into ramdisk and has to be copied
// from data to magisk tmp
if (access(DATABIN "/magisk32", X_OK) == 0) {
ssprintf(buf, sizeof(buf), "%s/magisk32", get_magisk_tmp());
cp_afc(DATABIN "/magisk32", buf);
}
if (access(DATABIN "/magiskpolicy", X_OK) == 0) {
ssprintf(buf, sizeof(buf), "%s/magiskpolicy", get_magisk_tmp());
cp_afc(DATABIN "/magiskpolicy", buf);
}
return true;
}
void unlock_blocks() {
int fd, dev, OFF = 0;
auto dir = xopen_dir("/dev/block");
if (!dir)
return;
dev = dirfd(dir.get());
for (dirent *entry; (entry = readdir(dir.get()));) {
if (entry->d_type == DT_BLK) {
if ((fd = openat(dev, entry->d_name, O_RDONLY | O_CLOEXEC)) < 0)
continue;
if (ioctl(fd, BLKROSET, &OFF) < 0)
PLOGE("unlock %s", entry->d_name);
close(fd);
}
}
}
#define test_bit(bit, array) (array[bit / 8] & (1 << (bit % 8)))
static bool check_key_combo() {
uint8_t bitmask[(KEY_MAX + 1) / 8];
vector<int> events;
constexpr char name[] = "/dev/.ev";
// First collect candidate events that accepts volume down
for (int minor = 64; minor < 96; ++minor) {
if (xmknod(name, S_IFCHR | 0444, makedev(13, minor)))
continue;
int fd = open(name, O_RDONLY | O_CLOEXEC);
unlink(name);
if (fd < 0)
continue;
memset(bitmask, 0, sizeof(bitmask));
ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(bitmask)), bitmask);
if (test_bit(KEY_VOLUMEDOWN, bitmask))
events.push_back(fd);
else
close(fd);
}
if (events.empty())
return false;
run_finally fin([&]{ std::for_each(events.begin(), events.end(), close); });
// Check if volume down key is held continuously for more than 3 seconds
for (int i = 0; i < 300; ++i) {
bool pressed = false;
for (const int &fd : events) {
memset(bitmask, 0, sizeof(bitmask));
ioctl(fd, EVIOCGKEY(sizeof(bitmask)), bitmask);
if (test_bit(KEY_VOLUMEDOWN, bitmask)) {
pressed = true;
break;
}
}
if (!pressed)
return false;
// Check every 10ms
usleep(10000);
}
LOGD("KEY_VOLUMEDOWN detected: enter safe mode\n");
return true;
}
/***********************
* Boot Stage Handlers *
***********************/
bool MagiskD::post_fs_data() const noexcept {
setup_logfile();
LOGI("** post-fs-data mode running\n");
preserve_stub_apk();
if (access(SECURE_DIR, F_OK) != 0) {
if (SDK_INT < 24) {
xmkdir(SECURE_DIR, 0700);
} else {
LOGE(SECURE_DIR " is not present, abort\n");
return true;
}
}
prune_su_access();
if (!magisk_env()) {
LOGE("* Magisk environment incomplete, abort\n");
return true;
}
// Check safe mode
int bootloop_cnt = get_db_setting(DbEntryKey::BootloopCount);
// Increment the boot counter
set_db_setting(DbEntryKey::BootloopCount, bootloop_cnt + 1);
bool safe_mode = bootloop_cnt >= 2 || get_prop("persist.sys.safemode", true) == "1" ||
get_prop("ro.sys.safemode") == "1" || check_key_combo();
if (safe_mode) {
LOGI("* Safe mode triggered\n");
// Disable all modules and zygisk so next boot will be clean
disable_modules();
set_db_setting(DbEntryKey::ZygiskConfig, false);
return true;
}
exec_common_scripts("post-fs-data");
zygisk_enabled = get_db_setting(DbEntryKey::ZygiskConfig);
initialize_denylist();
setup_mounts();
handle_modules();
load_modules();
return false;
}
void MagiskD::late_start() const noexcept {
setup_logfile();
LOGI("** late_start service mode running\n");
exec_common_scripts("service");
exec_module_scripts("service");
}
void MagiskD::boot_complete() const noexcept {
setup_logfile();
LOGI("** boot-complete triggered\n");
// Reset the bootloop counter once we have boot-complete
set_db_setting(DbEntryKey::BootloopCount, 0);
// At this point it's safe to create the folder
if (access(SECURE_DIR, F_OK) != 0)
xmkdir(SECURE_DIR, 0700);
// Ensure manager exists
get_manager(0, nullptr, true);
reset_zygisk(true);
}

View File

@@ -1,9 +1,9 @@
use pb_rs::{types::FileDescriptor, ConfigBuilder};
use pb_rs::{ConfigBuilder, types::FileDescriptor};
use crate::gen::gen_cxx_binding;
use crate::codegen::gen_cxx_binding;
#[path = "../include/gen.rs"]
mod gen;
#[path = "../include/codegen.rs"]
mod codegen;
fn main() {
println!("cargo:rerun-if-changed=resetprop/proto/persistent_properties.proto");

View File

@@ -2,11 +2,12 @@
#include <libgen.h>
#include <sys/un.h>
#include <sys/mount.h>
#include <sys/sysmacros.h>
#include <linux/input.h>
#include <consts.hpp>
#include <base.hpp>
#include <core.hpp>
#include <selinux.hpp>
#include <flags.h>
using namespace std;
@@ -127,30 +128,57 @@ static void poll_ctrl_handler(pollfd *pfd) {
}
}
void MagiskD::reboot() const noexcept {
if (is_recovery())
exec_command_sync("/system/bin/reboot", "recovery");
else
exec_command_sync("/system/bin/reboot");
bool get_client_cred(int fd, sock_cred *cred) {
socklen_t len = sizeof(ucred);
if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, cred, &len) != 0)
return false;
char buf[4096];
len = sizeof(buf);
if (getsockopt(fd, SOL_SOCKET, SO_PEERSEC, buf, &len) != 0)
len = 0;
buf[len] = '\0';
cred->context = buf;
return true;
}
bool read_string(int fd, std::string &str) {
str.clear();
int len = read_int(fd);
str.resize(len);
return xxread(fd, str.data(), len) == len;
}
string read_string(int fd) {
string str;
read_string(fd, str);
return str;
}
void write_string(int fd, string_view str) {
if (fd < 0) return;
write_int(fd, str.size());
xwrite(fd, str.data(), str.size());
}
static void handle_request_async(int client, int code, const sock_cred &cred) {
auto &daemon = MagiskD::Get();
switch (code) {
case +RequestCode::DENYLIST:
denylist_handler(client, &cred);
break;
case +RequestCode::SUPERUSER:
su_daemon_handler(client, &cred);
daemon.su_daemon_handler(client, cred);
break;
case +RequestCode::ZYGOTE_RESTART:
case +RequestCode::ZYGOTE_RESTART: {
LOGI("** zygote restarted\n");
MagiskD().prune_su_access();
daemon.prune_su_access();
scan_deny_apps();
reset_zygisk(false);
daemon.zygisk_reset(false);
close(client);
break;
}
case +RequestCode::SQLITE_CMD:
MagiskD().db_exec(client);
daemon.db_exec(client);
break;
case +RequestCode::REMOVE_MODULES: {
int do_reboot = read_int(client);
@@ -158,12 +186,12 @@ static void handle_request_async(int client, int code, const sock_cred &cred) {
write_int(client, 0);
close(client);
if (do_reboot) {
MagiskD().reboot();
daemon.reboot();
}
break;
}
case +RequestCode::ZYGISK:
zygisk_handler(client, &cred);
daemon.zygisk_handler(client);
break;
default:
__builtin_unreachable();
@@ -285,24 +313,11 @@ static void handle_request(pollfd *pfd) {
exec_task([=, fd = client.release()] { handle_request_async(fd, code, cred); });
} else {
exec_task([=, fd = client.release()] {
MagiskD().boot_stage_handler(fd, code);
MagiskD::Get().boot_stage_handler(fd, code);
});
}
}
static void switch_cgroup(const char *cgroup, int pid) {
char buf[32];
ssprintf(buf, sizeof(buf), "%s/cgroup.procs", cgroup);
if (access(buf, F_OK) != 0)
return;
int fd = xopen(buf, O_WRONLY | O_APPEND | O_CLOEXEC);
if (fd == -1)
return;
ssprintf(buf, sizeof(buf), "%d\n", pid);
xwrite(fd, buf, strlen(buf));
close(fd);
}
static void daemon_entry() {
android_logging();
@@ -324,51 +339,15 @@ static void daemon_entry() {
if (fd > STDERR_FILENO)
close(fd);
setsid();
setcon(MAGISK_PROC_CON);
rust::daemon_entry();
SDK_INT = MagiskD().sdk_int();
// Escape from cgroup
int pid = getpid();
switch_cgroup("/acct", pid);
switch_cgroup("/dev/cg2_bpf", pid);
switch_cgroup("/sys/fs/cgroup", pid);
if (get_prop("ro.config.per_app_memcg") != "false") {
switch_cgroup("/dev/memcg/apps", pid);
}
SDK_INT = MagiskD::Get().sdk_int();
// Get self stat
xstat("/proc/self/exe", &self_st);
// Samsung workaround #7887
if (access("/system_ext/app/mediatek-res/mediatek-res.apk", F_OK) == 0) {
set_prop("ro.vendor.mtk_model", "0");
}
restore_tmpcon();
// Cleanups
const char *tmp = get_magisk_tmp();
char path[64];
ssprintf(path, sizeof(path), "%s/" ROOTMNT, tmp);
if (access(path, F_OK) == 0) {
file_readline(true, path, [](string_view line) -> bool {
umount2(line.data(), MNT_DETACH);
return true;
});
}
if (getenv("REMOUNT_ROOT")) {
xmount(nullptr, "/", nullptr, MS_REMOUNT | MS_RDONLY, nullptr);
unsetenv("REMOUNT_ROOT");
}
ssprintf(path, sizeof(path), "%s/" ROOTOVL, tmp);
rm_rf(path);
fd = xsocket(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0);
sockaddr_un addr = {.sun_family = AF_LOCAL};
ssprintf(addr.sun_path, sizeof(addr.sun_path), "%s/" MAIN_SOCKET, tmp);
ssprintf(addr.sun_path, sizeof(addr.sun_path), "%s/" MAIN_SOCKET, get_magisk_tmp());
unlink(addr.sun_path);
if (xbind(fd, (sockaddr *) &addr, sizeof(addr)))
exit(1);
@@ -378,7 +357,6 @@ static void daemon_entry() {
default_new(poll_map);
default_new(poll_fds);
default_new(module_list);
// Register handler for main socket
pollfd main_socket_pfd = { fd, POLLIN, 0 };
@@ -455,3 +433,116 @@ int connect_daemon(int req, bool create) {
}
return fd;
}
bool setup_magisk_env() {
char buf[4096];
LOGI("* Initializing Magisk environment\n");
ssprintf(buf, sizeof(buf), "%s/0/%s/install", APP_DATA_DIR, JAVA_PACKAGE_NAME);
// Alternative binaries paths
const char *alt_bin[] = { "/cache/data_adb/magisk", "/data/magisk", buf };
for (auto alt : alt_bin) {
if (access(alt, F_OK) == 0) {
rm_rf(DATABIN);
cp_afc(alt, DATABIN);
rm_rf(alt);
}
}
rm_rf("/cache/data_adb");
// Directories in /data/adb
chmod(SECURE_DIR, 0700);
xmkdir(DATABIN, 0755);
xmkdir(MODULEROOT, 0755);
xmkdir(SECURE_DIR "/post-fs-data.d", 0755);
xmkdir(SECURE_DIR "/service.d", 0755);
restorecon();
if (access(DATABIN "/busybox", X_OK))
return false;
ssprintf(buf, sizeof(buf), "%s/" BBPATH "/busybox", get_magisk_tmp());
mkdir(dirname(buf), 0755);
cp_afc(DATABIN "/busybox", buf);
exec_command_async(buf, "--install", "-s", dirname(buf));
// magisk32 and magiskpolicy are not installed into ramdisk and has to be copied
// from data to magisk tmp
if (access(DATABIN "/magisk32", X_OK) == 0) {
ssprintf(buf, sizeof(buf), "%s/magisk32", get_magisk_tmp());
cp_afc(DATABIN "/magisk32", buf);
}
if (access(DATABIN "/magiskpolicy", X_OK) == 0) {
ssprintf(buf, sizeof(buf), "%s/magiskpolicy", get_magisk_tmp());
cp_afc(DATABIN "/magiskpolicy", buf);
}
return true;
}
void unlock_blocks() {
int fd, dev, OFF = 0;
auto dir = xopen_dir("/dev/block");
if (!dir)
return;
dev = dirfd(dir.get());
for (dirent *entry; (entry = readdir(dir.get()));) {
if (entry->d_type == DT_BLK) {
if ((fd = openat(dev, entry->d_name, O_RDONLY | O_CLOEXEC)) < 0)
continue;
if (ioctl(fd, BLKROSET, &OFF) < 0)
PLOGE("unlock %s", entry->d_name);
close(fd);
}
}
}
#define test_bit(bit, array) (array[bit / 8] & (1 << (bit % 8)))
bool check_key_combo() {
uint8_t bitmask[(KEY_MAX + 1) / 8];
vector<int> events;
constexpr char name[] = "/dev/.ev";
// First collect candidate events that accepts volume down
for (int minor = 64; minor < 96; ++minor) {
if (xmknod(name, S_IFCHR | 0444, makedev(13, minor)))
continue;
int fd = open(name, O_RDONLY | O_CLOEXEC);
unlink(name);
if (fd < 0)
continue;
memset(bitmask, 0, sizeof(bitmask));
ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(bitmask)), bitmask);
if (test_bit(KEY_VOLUMEDOWN, bitmask))
events.push_back(fd);
else
close(fd);
}
if (events.empty())
return false;
run_finally fin([&]{ std::for_each(events.begin(), events.end(), close); });
// Check if volume down key is held continuously for more than 3 seconds
for (int i = 0; i < 300; ++i) {
bool pressed = false;
for (const int &fd : events) {
memset(bitmask, 0, sizeof(bitmask));
ioctl(fd, EVIOCGKEY(sizeof(bitmask)), bitmask);
if (test_bit(KEY_VOLUMEDOWN, bitmask)) {
pressed = true;
break;
}
}
if (!pressed)
return false;
// Check every 10ms
usleep(10000);
}
LOGD("KEY_VOLUMEDOWN detected: enter safe mode\n");
return true;
}

View File

@@ -1,19 +1,24 @@
use crate::consts::{MAGISK_FULL_VER, MAIN_CONFIG};
use crate::consts::{MAGISK_FULL_VER, MAGISK_PROC_CON, MAIN_CONFIG, ROOTMNT, ROOTOVL, SECURE_DIR};
use crate::db::Sqlite3;
use crate::ffi::{get_magisk_tmp, RequestCode};
use crate::get_prop;
use crate::logging::{magisk_logging, start_log_daemon};
use crate::package::ManagerInfo;
use base::libc::{O_CLOEXEC, O_RDONLY};
use base::{
cstr, info, libc, open_fd, BufReadExt, Directory, FsPath, FsPathBuf, LoggedResult, ReadExt,
Utf8CStr, Utf8CStrBufArr,
use crate::ffi::{
DbEntryKey, ModuleInfo, RequestCode, check_key_combo, disable_modules, exec_common_scripts,
exec_module_scripts, get_magisk_tmp, initialize_denylist, setup_magisk_env,
};
use bit_set::BitSet;
use bytemuck::bytes_of;
use std::fs::File;
use std::io;
use std::io::{BufReader, Read, Write};
use crate::logging::{magisk_logging, setup_logfile, start_log_daemon};
use crate::mount::{clean_mounts, setup_module_mount, setup_preinit_dir};
use crate::package::ManagerInfo;
use crate::selinux::restore_tmpcon;
use crate::su::SuInfo;
use crate::{get_prop, set_prop};
use base::libc::{O_APPEND, O_CLOEXEC, O_RDONLY, O_WRONLY};
use base::{
AtomicArc, BufReadExt, FsPathBuilder, ResultExt, Utf8CStr, Utf8CStrBuf, cstr, error, info, libc,
};
use std::fmt::Write as FmtWrite;
use std::io::{BufReader, Write};
use std::os::unix::net::UnixStream;
use std::process::Command;
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
use std::sync::{Mutex, OnceLock};
// Global magiskd singleton
@@ -60,14 +65,23 @@ pub struct MagiskD {
pub sql_connection: Mutex<Option<Sqlite3>>,
pub manager_info: Mutex<ManagerInfo>,
boot_stage_lock: Mutex<BootStateFlags>,
pub module_list: OnceLock<Vec<ModuleInfo>>,
pub zygiskd_sockets: Mutex<(Option<UnixStream>, Option<UnixStream>)>,
pub zygisk_enabled: AtomicBool,
pub zygote_start_count: AtomicU32,
pub cached_su_info: AtomicArc<SuInfo>,
sdk_int: i32,
pub is_emulator: bool,
is_recovery: bool,
}
impl MagiskD {
pub fn is_recovery(&self) -> bool {
self.is_recovery
pub fn get() -> &'static MagiskD {
unsafe { MAGISKD.get().unwrap_unchecked() }
}
pub fn zygisk_enabled(&self) -> bool {
self.zygisk_enabled.load(Ordering::Acquire)
}
pub fn sdk_int(&self) -> i32 {
@@ -82,39 +96,88 @@ impl MagiskD {
}
}
// app_id = app_no + AID_APP_START
// app_no range: [0, 9999]
pub fn get_app_no_list(&self) -> BitSet {
let mut list = BitSet::new();
let _: LoggedResult<()> = try {
let mut app_data_dir = Directory::open(self.app_data_dir())?;
// For each user
loop {
let entry = match app_data_dir.read()? {
None => break,
Some(e) => e,
};
let mut user_dir = match entry.open_as_dir() {
Err(_) => continue,
Ok(dir) => dir,
};
// For each package
loop {
match user_dir.read()? {
None => break,
Some(e) => {
let attr = e.get_attr()?;
let app_id = to_app_id(attr.st.st_uid as i32);
if (AID_APP_START..=AID_APP_END).contains(&app_id) {
let app_no = app_id - AID_APP_START;
list.insert(app_no as usize);
}
}
}
}
fn post_fs_data(&self) -> bool {
setup_logfile();
info!("** post-fs-data mode running");
self.preserve_stub_apk();
// Check secure dir
let secure_dir = cstr!(SECURE_DIR);
if !secure_dir.exists() {
if self.sdk_int < 24 {
secure_dir.mkdir(0o700).log_ok();
} else {
error!("* {} is not present, abort", SECURE_DIR);
return true;
}
};
list
}
self.prune_su_access();
if !setup_magisk_env() {
error!("* Magisk environment incomplete, abort");
return true;
}
// Check safe mode
let boot_cnt = self.get_db_setting(DbEntryKey::BootloopCount);
self.set_db_setting(DbEntryKey::BootloopCount, boot_cnt + 1)
.log()
.ok();
let safe_mode = boot_cnt >= 2
|| get_prop(cstr!("persist.sys.safemode"), true) == "1"
|| get_prop(cstr!("ro.sys.safemode"), false) == "1"
|| check_key_combo();
if safe_mode {
info!("* Safe mode triggered");
// Disable all modules and zygisk so next boot will be clean
disable_modules();
self.set_db_setting(DbEntryKey::ZygiskConfig, 0).log_ok();
return true;
}
exec_common_scripts(cstr!("post-fs-data"));
self.zygisk_enabled.store(
self.get_db_setting(DbEntryKey::ZygiskConfig) != 0,
Ordering::Release,
);
initialize_denylist();
setup_module_mount();
let modules = self.handle_modules();
self.module_list.set(modules).ok();
clean_mounts();
false
}
fn late_start(&self) {
setup_logfile();
info!("** late_start service mode running");
exec_common_scripts(cstr!("service"));
if let Some(module_list) = self.module_list.get() {
exec_module_scripts(cstr!("service"), module_list);
}
}
fn boot_complete(&self) {
setup_logfile();
info!("** boot-complete triggered");
// Reset the bootloop counter once we have boot-complete
self.set_db_setting(DbEntryKey::BootloopCount, 0).log_ok();
// At this point it's safe to create the folder
let secure_dir = cstr!(SECURE_DIR);
if !secure_dir.exists() {
secure_dir.mkdir(0o700).log_ok();
}
setup_preinit_dir();
self.ensure_manager();
self.zygisk_reset(true)
}
pub fn boot_stage_handler(&self, client: i32, code: i32) {
@@ -152,9 +215,26 @@ impl MagiskD {
}
}
}
pub fn reboot(&self) {
if self.is_recovery {
Command::new("/system/bin/reboot").arg("recovery").status()
} else {
Command::new("/system/bin/reboot").status()
}
.ok();
}
}
pub fn daemon_entry() {
unsafe { libc::setsid() };
// Make sure the current context is magisk
if let Ok(mut current) = cstr!("/proc/self/attr/current").open(O_WRONLY | O_CLOEXEC) {
let con = cstr!(MAGISK_PROC_CON);
current.write_all(con.as_bytes_with_nul()).log_ok();
}
start_log_daemon();
magisk_logging();
info!("Magisk {} daemon started", MAGISK_FULL_VER);
@@ -164,14 +244,13 @@ pub fn daemon_entry() {
|| get_prop(cstr!("ro.product.device"), false).contains("vsoc");
// Load config status
let mut buf = Utf8CStrBufArr::<64>::new();
let path = FsPathBuf::new(&mut buf)
.join(get_magisk_tmp())
.join(MAIN_CONFIG);
let magisk_tmp = get_magisk_tmp();
let mut tmp_path = cstr::buf::new::<64>()
.join_path(magisk_tmp)
.join_path(MAIN_CONFIG);
let mut is_recovery = false;
if let Ok(file) = path.open(O_RDONLY | O_CLOEXEC) {
let mut file = BufReader::new(file);
file.foreach_props(|key, val| {
if let Ok(main_config) = tmp_path.open(O_RDONLY | O_CLOEXEC) {
BufReader::new(main_config).foreach_props(|key, val| {
if key == "RECOVERYMODE" {
is_recovery = val == "true";
return false;
@@ -179,11 +258,11 @@ pub fn daemon_entry() {
true
});
}
tmp_path.truncate(magisk_tmp.len());
let mut sdk_int = -1;
if let Ok(file) = FsPath::from(cstr!("/system/build.prop")).open(O_RDONLY | O_CLOEXEC) {
let mut file = BufReader::new(file);
file.foreach_props(|key, val| {
if let Ok(build_prop) = cstr!("/system/build.prop").open(O_RDONLY | O_CLOEXEC) {
BufReader::new(build_prop).foreach_props(|key, val| {
if key == "ro.build.version.sdk" {
sdk_int = val.parse::<i32>().unwrap_or(-1);
return false;
@@ -199,18 +278,71 @@ pub fn daemon_entry() {
}
info!("* Device API level: {}", sdk_int);
restore_tmpcon().log_ok();
// Escape from cgroup
let pid = unsafe { libc::getpid() };
switch_cgroup("/acct", pid);
switch_cgroup("/dev/cg2_bpf", pid);
switch_cgroup("/sys/fs/cgroup", pid);
if get_prop(cstr!("ro.config.per_app_memcg"), false) != "false" {
switch_cgroup("/dev/memcg/apps", pid);
}
// Samsung workaround #7887
if cstr!("/system_ext/app/mediatek-res/mediatek-res.apk").exists() {
set_prop(cstr!("ro.vendor.mtk_model"), cstr!("0"), false);
}
// Cleanup pre-init mounts
tmp_path.append_path(ROOTMNT);
if let Ok(mount_list) = tmp_path.open(O_RDONLY | O_CLOEXEC) {
BufReader::new(mount_list).foreach_lines(|line| {
line.truncate(line.trim_end().len());
let item = Utf8CStr::from_string(line);
item.unmount().log_ok();
true
})
}
tmp_path.truncate(magisk_tmp.len());
// Remount rootfs as read-only if requested
if std::env::var_os("REMOUNT_ROOT").is_some() {
cstr!("/").remount_mount_flags(libc::MS_RDONLY).log_ok();
unsafe { std::env::remove_var("REMOUNT_ROOT") };
}
// Remove all pre-init overlay files to free-up memory
tmp_path.append_path(ROOTOVL);
tmp_path.remove_all().log_ok();
tmp_path.truncate(magisk_tmp.len());
let magiskd = MagiskD {
sdk_int,
is_emulator,
is_recovery,
zygote_start_count: AtomicU32::new(1),
..Default::default()
};
MAGISKD.set(magiskd).ok();
}
fn switch_cgroup(cgroup: &str, pid: i32) {
let mut buf = cstr::buf::new::<64>()
.join_path(cgroup)
.join_path("cgroup.procs");
if !buf.exists() {
return;
}
if let Ok(mut file) = buf.open(O_WRONLY | O_APPEND | O_CLOEXEC) {
buf.clear();
buf.write_fmt(format_args!("{}", pid)).ok();
file.write_all(buf.as_bytes()).log_ok();
}
}
fn check_data() -> bool {
if let Ok(fd) = open_fd!(cstr!("/proc/mounts"), O_RDONLY | O_CLOEXEC) {
let file = File::from(fd);
if let Ok(file) = cstr!("/proc/mounts").open(O_RDONLY | O_CLOEXEC) {
let mut mnt = false;
BufReader::new(file).foreach_lines(|line| {
if line.contains(" /data ") && !line.contains("tmpfs") {
@@ -238,43 +370,3 @@ fn check_data() -> bool {
}
false
}
pub fn get_magiskd() -> &'static MagiskD {
unsafe { MAGISKD.get().unwrap_unchecked() }
}
pub trait IpcRead {
fn ipc_read_int(&mut self) -> io::Result<i32>;
fn ipc_read_string(&mut self) -> io::Result<String>;
}
impl<T: Read> IpcRead for T {
fn ipc_read_int(&mut self) -> io::Result<i32> {
let mut val: i32 = 0;
self.read_pod(&mut val)?;
Ok(val)
}
fn ipc_read_string(&mut self) -> io::Result<String> {
let len = self.ipc_read_int()?;
let mut val = "".to_string();
self.take(len as u64).read_to_string(&mut val)?;
Ok(val)
}
}
pub trait IpcWrite {
fn ipc_write_int(&mut self, val: i32) -> io::Result<()>;
fn ipc_write_string(&mut self, val: &str) -> io::Result<()>;
}
impl<T: Write> IpcWrite for T {
fn ipc_write_int(&mut self, val: i32) -> io::Result<()> {
self.write_all(bytes_of(&val))
}
fn ipc_write_string(&mut self, val: &str) -> io::Result<()> {
self.ipc_write_int(val.len() as i32)?;
self.write_all(val.as_bytes())
}
}

Some files were not shown because too many files have changed in this diff Show More