Compare commits

...

256 Commits

Author SHA1 Message Date
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
topjohnwu
b62835cbeb Release new canary build 2025-01-31 02:36:58 +08:00
Wang Han
6ea740b5ab Skip clearing install dir if not needed 2025-01-27 12:14:55 +08:00
topjohnwu
7ab98dd5ac Make ioctl not a special token 2025-01-27 03:01:00 +08:00
anonymix007
fc8b3400fc Fix sterm parsing logic for ioctl 2025-01-27 03:01:00 +08:00
topjohnwu
54428ba415 Fix Android Studio C++ indexing 2025-01-26 02:24:35 +08:00
topjohnwu
95d1e69d8e Update to ONDK r28.2 2025-01-21 18:50:12 +08:00
topjohnwu
a0f13ab49f Move lambda to static function 2025-01-19 18:59:17 +08:00
topjohnwu
c3e8405020 Update libcxx 2025-01-19 18:51:17 +08:00
Pesh4waaa
a93593ea66
Kurdish Language For Magisk 2025-01-19 15:24:03 +08:00
Wang Han
23eff70883
Fix repeated binding of first argument
Co-authored-by: LoveSy <shana@zju.edu.cn>
2025-01-19 11:57:09 +08:00
vvb2060
110dd4a8b9 Update dependencies 2025-01-19 11:55:44 +08:00
Wang Han
d9c2bffc9f
Avoid hardcoding max fd size
Android changed max fd limit to 32768 since Android 9:
cb5fccc83c

Co-authored-by: LoveSy <shana@zju.edu.cn>
2025-01-19 11:54:26 +08:00
topjohnwu
049db49dc8 Use preprocessor for 64bit detection 2025-01-11 00:15:10 +08:00
Wang Han
7c1d2ec61e zygisk: Let client send arch info 2025-01-11 00:15:10 +08:00
topjohnwu
a1b2830c06 Address clippy warnings 2025-01-11 00:11:48 +08:00
topjohnwu
82d1d19267 Migrate uid_granted_root to Rust 2025-01-11 00:11:48 +08:00
topjohnwu
4d4195c02d Migrate prune_su_access to Rust 2025-01-11 00:11:48 +08:00
topjohnwu
5637a258fc Migrate package detection to Rust 2025-01-11 00:11:48 +08:00
topjohnwu
ee6810f417 Rewrite magisk logging implementation 2025-01-11 00:11:48 +08:00
topjohnwu
7098248c64 Add more functionality into Rust 2025-01-11 00:11:48 +08:00
topjohnwu
0d31d356ef Use SQLite's internal time function 2025-01-05 05:04:04 -08:00
topjohnwu
b782e7dcb7 Fetch policy table from Rust 2025-01-05 05:04:04 -08:00
Softastur
a4671b4698 Update Asturian translations
Fixing and updating
2025-01-05 03:15:31 -08:00
topjohnwu
7edd8be169 Minor changes 2025-01-04 02:24:08 -08:00
topjohnwu
24650eefe4 Bind SQLite to Rust 2025-01-04 02:24:08 -08:00
topjohnwu
8e1a44e7eb Use argument binding for query 2025-01-04 02:24:08 -08:00
topjohnwu
2722875190 Use better C++ SQL APIs 2025-01-04 02:24:08 -08:00
topjohnwu
3ca6d06f69 Cleanup database code 2025-01-04 02:24:08 -08:00
topjohnwu
10e47248de Use finer grain sqlite3 APIs 2025-01-04 02:24:08 -08:00
Pzqqt
e73ff679ac scripts: flash_script.sh: Avoid overly dangerous code 2024-12-27 16:02:24 -08:00
Wang Han
53e401fa2d
Perform authentication if needed for AutomaticResponse setting 2024-12-27 16:00:02 -08:00
LoveSy
d2768357da Support systemlessly deleting files or folders
After we refactor the magic mount and always mount folder as tmpfs,
we can easily support deleting files or folders now. We recognize
dummy devices with major number 0 and minor number 0 as an indicator
for removing files and folders. This indicator is borrowed from
overlayfs.
2024-12-27 15:57:54 -08:00
LoveSy
a6c2ba7c1e Allow kernel to relabel 2024-12-27 12:35:29 -08:00
LoveSy
aae5b466fb Use rust to implement collect/reset overlay context 2024-12-27 12:35:29 -08:00
5ec1cff
2b7be8b949 init: reset overlay.d files context after sepolicy loaded 2024-12-27 12:35:29 -08:00
5ec1cff
b6511a510d Revert "Allow all domains to access tmpfs files"
This reverts commit da43ac89a07c7b13b4cf4ae1539c95363ecd1f9f.
2024-12-27 12:35:29 -08:00
Wang Han
704541aef2 Use /metadata/watchdog as preinit dir if exists
Since Android 15, all domains are allowed to search /metadata so preinit
dir will be exposed. Use /metadata/watchdog when /metadata is chosen as
preinit device, and the dir is available (since Android 11).
2024-12-27 10:35:05 -08:00
topjohnwu
005560a4c5 Always rescan manager APK when database is updated 2024-12-26 12:18:38 -08:00
topjohnwu
231a5d1853 Cleanup test code 2024-12-25 22:26:30 -08:00
topjohnwu
9e2b59060d Drive app migration tests through instrumentation
Make tests less flaky
2024-12-25 22:26:30 -08:00
topjohnwu
08ea937f7c Test su request via instrumentation 2024-12-25 22:26:30 -08:00
topjohnwu
2baedf74d1 Install and test LSPosed through test app 2024-12-25 22:26:30 -08:00
topjohnwu
32faa4ced6 Redesign test APK architecture
The test APK and the main APK share the same process and classloader,
and in the non-hidden case, the test APK's classes take precedence over
the ones in the main APK. This causes issues because the test APK and
main APK share some dependencies, but don't always use the same
version. This is especially problematic for the Kotlin stdlib and
AndroidX dependencies.

The solution here is to rely on R8's obfuscation feature and repackage
all potentially conflicting classes into a separate package in the test
APK. To ensure that the test classes are always using the same classes
as the main APK, we have to directly implement all tests inside the main
APK, making the test APK purely a "test runner with test dependencies".

As a result, the test APK can only be used when built in release mode,
because R8 no longer allow class obfuscation to be enabled when building
for debug versions.
2024-12-25 20:17:57 -08:00
topjohnwu
ccdb0b5d13 Add ability to skip certain test variants 2024-12-25 20:11:21 -08:00
topjohnwu
8506b672ad Update CI operating system 2024-12-23 22:52:30 -08:00
topjohnwu
ce2e33bb20 Cleanup test scripts 2024-12-23 20:42:54 -08:00
topjohnwu
6707b72260 Fix AVD tests 2024-12-23 20:41:42 -08:00
topjohnwu
5885b8c20d Add new tests for app hiding 2024-12-18 17:22:31 -08:00
topjohnwu
820710c086 Fix incorrect SQLite syntax 2024-12-18 17:22:31 -08:00
topjohnwu
51cf196bf7 Always use root to hide/restore app 2024-12-18 17:22:31 -08:00
Wang Han
dadba44cf9 Update module installer guide about META-INF 2024-12-17 16:36:40 -08:00
topjohnwu
2ce4a5543b Make ndk-build happy when Rust libs are missing 2024-12-13 17:00:40 -08:00
topjohnwu
9112a3a4f5 Introduce instrumentation tests 2024-12-13 12:07:42 -08:00
topjohnwu
24615afda1 Remove usage of ProcessLifecycle 2024-12-13 12:07:42 -08:00
topjohnwu
c5778f398b Cleanup imports 2024-12-13 12:07:42 -08:00
topjohnwu
4eb4097b9b Split file processing into its own class 2024-12-13 12:07:42 -08:00
vvb2060
c512496847 install_module: simplify script 2024-12-12 10:08:09 -08:00
vvb2060
506961a10d flash module: ignore META-INF 2024-12-12 10:07:47 -08:00
topjohnwu
3414415907 Support zip files with unsupported compresssion method 2024-12-12 02:50:19 -08:00
topjohnwu
dc2ae7cfd1 Disable CI on master push
Changes should be done through PRs for CI
2024-12-12 02:50:19 -08:00
topjohnwu
2e86d21c29 16k pages only work on Android B on x64 2024-12-09 20:13:27 -08:00
topjohnwu
2654382c43 Address clippy warnings 2024-12-09 18:26:39 -08:00
topjohnwu
9e26b73813 Update rust dependencies 2024-12-09 18:26:39 -08:00
topjohnwu
10cd13bf80 Update ONDK r28.1 2024-12-09 18:26:39 -08:00
topjohnwu
f10ee5f887 Test 16K pages with AVD instead of Cuttlefish 2024-12-09 14:16:08 -08:00
topjohnwu
47cc532d96 Release new canary build 2024-12-06 18:19:06 -08:00
topjohnwu
218327f92b Release Magisk v28.1 2024-12-06 17:45:41 -08:00
topjohnwu
4eae66a1a7 Add v28.1 release notes 2024-12-06 17:38:43 -08:00
vvb2060
b09ceeb43c scripts: sync avd_magisk.sh with mgiskinit 2024-12-06 02:21:17 -08:00
vvb2060
4fb539c110 core: use a new tmpfs as worker 2024-12-06 02:19:43 -08:00
vvb2060
849b284da5 core: insert symlink magisk_node 2024-12-06 02:19:32 -08:00
topjohnwu
895b5f6cbf Release new canary build 2024-12-04 01:28:31 -08:00
SonyaMedved
cb3d4ea514
strings.xml
The strings have been translated into Ukrainian.
2024-12-04 01:26:39 -08:00
topjohnwu
0d89a2a97d Update AGP 2024-12-04 01:25:44 -08:00
nedokaka
3ca5913055 Update Russian Translation 2024-12-03 19:52:53 -08:00
topjohnwu
df6b808f49 Cleanup DesugarClassVisitorFactory 2024-12-03 19:52:39 -08:00
topjohnwu
09c7ac754b Simplify MagiskD Rust/C++ FFI 2024-12-03 15:51:17 -08:00
topjohnwu
805da67c23 Update cxx-rs 2024-12-03 14:16:14 -08:00
topjohnwu
3c6889505b Stop using polymorphism in magiskinit 2024-12-03 02:18:22 -08:00
topjohnwu
c8e9ce7627 Cleanup mount code in magiskinit 2024-12-03 02:18:22 -08:00
topjohnwu
837c679a31 Update avd_test API versions 2024-12-03 02:18:22 -08:00
LoveSy
06616659b8 Only desugar ZipEntry's methods 2024-12-02 19:55:28 -08:00
topjohnwu
a34c04f999 Release new canary build 2024-12-01 14:59:57 -08:00
topjohnwu
da43ac89a0 Allow all domains to access tmpfs files
Fix #8457
2024-11-30 23:21:33 -08:00
vvb2060
830fc758b9 init: Use apex dir to determine whether 2SI 2024-11-30 23:03:29 -08:00
vvb2060
0f3cfef278 Revert "init: support 2SI devices with skip_initramfs"
This reverts commit b38fd1ca5f15ed22858c7737879c38d360e78eac.
2024-11-30 23:03:29 -08:00
topjohnwu
b32d7bfafd Update gradle version 2024-11-21 21:05:35 -08:00
topjohnwu
c0899f2939 Update dependencies 2024-11-19 20:29:15 -08:00
topjohnwu
082330808f Fix building APK 2024-11-19 20:25:10 -08:00
topjohnwu
024da05888 Move several stuff into buildSrc 2024-11-09 20:08:12 -08:00
LoveSy
377b6d0cc2 avoid desugar the Desugar class 2024-11-09 19:41:06 -08:00
Georgi Boiko
c661009b31 docs(ci): update setup action to state correct jdk version 2024-11-04 11:12:01 -08:00
vvb2060
613f2d31c5 app: auto close action fragment only when focus lost 2024-11-04 11:11:41 -08:00
vvb2060
7dbb973db5 daemon: some samsung devices using incorrect mediatek-res path 2024-11-04 11:09:53 -08:00
topjohnwu
f4502f8be8 Add our own API desugaring
Fix #8452
2024-10-29 12:13:22 -07:00
topjohnwu
455b13b83c Fix download URL in stub.apk 2024-10-17 19:42:10 -07:00
topjohnwu
8b98709743 Update dependencies 2024-10-17 13:17:46 -07:00
tzagim
1b12f45f39 Update Hebrew Translation 2024-10-15 15:23:21 -07:00
vvb2060
a5cad532ff ui: fix lock screen orientation 2024-10-12 01:16:24 -07:00
topjohnwu
070719db50 Release new canary build 2024-10-10 02:10:50 -07:00
236 changed files with 10870 additions and 8248 deletions

6
.gitattributes vendored
View File

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

View File

@ -6,7 +6,7 @@ inputs:
runs: runs:
using: "composite" using: "composite"
steps: steps:
- name: Set up JDK 17 - name: Set up JDK 21
uses: actions/setup-java@v4 uses: actions/setup-java@v4
with: with:
distribution: "temurin" distribution: "temurin"
@ -26,6 +26,15 @@ runs:
- name: Cache sccache - name: Cache sccache
uses: actions/cache@v4 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: with:
path: .sccache path: .sccache
key: sccache-${{ runner.os }}-${{ github.sha }} key: sccache-${{ runner.os }}-${{ github.sha }}
@ -55,7 +64,7 @@ runs:
- name: Cache Gradle dependencies - name: Cache Gradle dependencies
uses: actions/cache@v4 uses: actions/cache@v4
if: inputs.is-asset-build == 'true' if: ${{ inputs.is-asset-build == 'true' && github.event_name != 'pull_request' }}
with: with:
path: | path: |
.gradle/caches .gradle/caches
@ -66,7 +75,7 @@ runs:
- name: Restore Gradle dependencies - name: Restore Gradle dependencies
uses: actions/cache/restore@v4 uses: actions/cache/restore@v4
if: inputs.is-asset-build == 'false' if: ${{ inputs.is-asset-build == 'false' || github.event_name == 'pull_request' }}
with: with:
path: | path: |
.gradle/caches .gradle/caches
@ -78,19 +87,17 @@ runs:
- name: Cache Gradle build cache - name: Cache Gradle build cache
uses: actions/cache@v4 uses: actions/cache@v4
if: inputs.is-asset-build == 'true' if: ${{ inputs.is-asset-build == 'true' && github.event_name != 'pull_request' }}
with: with:
path: | path: .gradle/caches/build-cache-*
.gradle/caches/build-cache-*
key: gradle-build-cache-${{ github.sha }} key: gradle-build-cache-${{ github.sha }}
restore-keys: gradle-build-cache- restore-keys: gradle-build-cache-
- name: Restore Gradle build cache - name: Restore Gradle build cache
uses: actions/cache/restore@v4 uses: actions/cache/restore@v4
if: inputs.is-asset-build == 'false' if: ${{ inputs.is-asset-build == 'false' || github.event_name == 'pull_request' }}
with: with:
path: | path: .gradle/caches/build-cache-*
.gradle/caches/build-cache-*
key: gradle-build-cache-${{ github.sha }} key: gradle-build-cache-${{ github.sha }}
restore-keys: gradle-build-cache- restore-keys: gradle-build-cache-
enableCrossOsArchive: true enableCrossOsArchive: true

View File

@ -9,6 +9,7 @@ on:
- "buildSrc/**" - "buildSrc/**"
- "build.py" - "build.py"
- "gradle.properties" - "gradle.properties"
- "gradle/libs.versions.toml"
- ".github/workflows/build.yml" - ".github/workflows/build.yml"
pull_request: pull_request:
branches: [master] branches: [master]
@ -17,7 +18,7 @@ on:
jobs: jobs:
build: build:
name: Build Magisk artifacts name: Build Magisk artifacts
runs-on: macos-14 runs-on: macos-15
strategy: strategy:
fail-fast: false fail-fast: false
steps: steps:
@ -60,7 +61,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
os: [windows-latest, ubuntu-latest] os: [windows-2025, ubuntu-24.04]
steps: steps:
- name: Check out - name: Check out
uses: actions/checkout@v4 uses: actions/checkout@v4
@ -78,16 +79,19 @@ jobs:
avd-test: avd-test:
name: Test API ${{ matrix.version }} (x86_64) name: Test API ${{ matrix.version }} (x86_64)
runs-on: ubuntu-latest runs-on: ubuntu-24.04
needs: build needs: build
if: ${{ github.event_name != 'push' }}
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
version: [23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34] version: [23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35]
type: [""] type: [""]
include: include:
- version: 35 - version: 36
type: "google_apis" type: "google_apis"
- version: 36
type: "google_apis_ps16k"
steps: steps:
- name: Check out - name: Check out
@ -122,8 +126,9 @@ jobs:
avd-test-32: avd-test-32:
name: Test API ${{ matrix.version }} (x86) name: Test API ${{ matrix.version }} (x86)
runs-on: ubuntu-latest runs-on: ubuntu-24.04
needs: build needs: build
if: ${{ github.event_name != 'push' }}
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
@ -161,20 +166,19 @@ jobs:
kernel.log kernel.log
logcat.log logcat.log
cf_test: cf-test:
name: Test ${{ matrix.device }} name: Test ${{ matrix.device }}
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
needs: build needs: build
if: ${{ github.event_name != 'push' }}
env: env:
CF_HOME: /home/runner/aosp_cf_phone CF_HOME: /home/runner/aosp_cf_phone
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
include: include:
- branch: "aosp-main" - branch: "aosp-android-latest-release"
device: "aosp_cf_x86_64_phone" device: "aosp_cf_x86_64_only_phone"
- branch: "aosp-main-throttled"
device: "aosp_cf_x86_64_phone_pgagnostic"
steps: steps:
- name: Check out - name: Check out

1
.gitignore vendored
View File

@ -14,6 +14,7 @@ native/out
*.iml *.iml
.gradle .gradle
.idea .idea
.kotlin
/local.properties /local.properties
/build /build
/captures /captures

9
.gitmodules vendored
View File

@ -4,18 +4,12 @@
[submodule "lz4"] [submodule "lz4"]
path = native/src/external/lz4 path = native/src/external/lz4
url = https://github.com/lz4/lz4.git url = https://github.com/lz4/lz4.git
[submodule "bzip2"]
path = native/src/external/bzip2
url = https://github.com/nemequ/bzip2.git
[submodule "xz"] [submodule "xz"]
path = native/src/external/xz path = native/src/external/xz
url = https://github.com/xz-mirror/xz.git url = https://github.com/xz-mirror/xz.git
[submodule "libcxx"] [submodule "libcxx"]
path = native/src/external/libcxx path = native/src/external/libcxx
url = https://github.com/topjohnwu/libcxx.git url = https://github.com/topjohnwu/libcxx.git
[submodule "zlib"]
path = native/src/external/zlib
url = https://android.googlesource.com/platform/external/zlib
[submodule "zopfli"] [submodule "zopfli"]
path = native/src/external/zopfli path = native/src/external/zopfli
url = https://github.com/google/zopfli.git url = https://github.com/google/zopfli.git
@ -31,6 +25,3 @@
[submodule "crt0"] [submodule "crt0"]
path = native/src/external/crt0 path = native/src/external/crt0
url = https://github.com/topjohnwu/crt0.git 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

@ -20,9 +20,9 @@ Some highlight features:
Click the icon below to download Magisk apk. Click the icon below to download Magisk apk.
[![](https://img.shields.io/badge/Magisk-v27.0-blue)](https://github.com/topjohnwu/Magisk/releases/tag/v27.0) [![](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.0-blue)](https://github.com/topjohnwu/Magisk/releases/tag/v28.0) [![](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-27008) [![](https://img.shields.io/badge/Magisk-Canary-red)](https://github.com/topjohnwu/Magisk/releases/tag/canary-28104)
## Useful Links ## Useful Links

View File

@ -6,7 +6,7 @@ plugins {
id("androidx.navigation.safeargs.kotlin") id("androidx.navigation.safeargs.kotlin")
} }
setupAppCommon() setupMainApk()
kapt { kapt {
correctErrorTypes = true correctErrorTypes = true
@ -18,27 +18,6 @@ kapt {
} }
android { android {
namespace = "com.topjohnwu.magisk"
defaultConfig {
applicationId = "com.topjohnwu.magisk"
vectorDrawables.useSupportLibrary = true
versionName = Config.version
versionCode = Config.versionCode
ndk {
abiFilters += listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64", "riscv64")
debugSymbolLevel = "FULL"
}
}
buildTypes {
release {
isMinifyEnabled = true
isShrinkResources = true
proguardFiles("proguard-rules.pro")
}
}
buildFeatures { buildFeatures {
dataBinding = true dataBinding = true
} }
@ -46,6 +25,13 @@ android {
compileOptions { compileOptions {
isCoreLibraryDesugaringEnabled = true isCoreLibraryDesugaringEnabled = true
} }
buildTypes {
release {
isMinifyEnabled = true
isShrinkResources = true
}
}
} }
dependencies { dependencies {

View File

@ -1,10 +1,13 @@
package com.topjohnwu.magisk.arch package com.topjohnwu.magisk.arch
import android.content.ContentResolver
import android.view.KeyEvent import android.view.KeyEvent
import androidx.databinding.ViewDataBinding import androidx.databinding.ViewDataBinding
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.navigation.NavDirections import androidx.navigation.NavDirections
import androidx.navigation.fragment.NavHostFragment import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.navOptions
import com.topjohnwu.magisk.utils.AccessibilityUtils
abstract class NavigationActivity<Binding : ViewDataBinding> : UIActivity<Binding>() { 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() { fun NavDirections.navigate() {
navigation.navigate(this) navigate(this, navigation, contentResolver)
} }
} }

View File

@ -71,7 +71,7 @@ class FlashFragment : BaseFragment<FragmentFlashMd2Binding>(), MenuProvider {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
defaultOrientation = activity?.requestedOrientation ?: -1 defaultOrientation = activity?.requestedOrientation ?: -1
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LOCKED
if (savedInstanceState == null) { if (savedInstanceState == null) {
viewModel.startFlashing() viewModel.startFlashing()
} }

View File

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

View File

@ -41,7 +41,7 @@ object RebootMenu {
activity.getSystemService<PowerManager>()?.isRebootingUserspaceSupported == true) { activity.getSystemService<PowerManager>()?.isRebootingUserspaceSupported == true) {
menu.menu.findItem(R.id.action_reboot_userspace).isVisible = 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 menu.menu.findItem(R.id.action_reboot_safe_mode).isChecked = Config.bootloop >= 2
} else { } else {
menu.menu.findItem(R.id.action_reboot_safe_mode).isVisible = false 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.MenuInflater
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.widget.HorizontalScrollView
import androidx.core.view.MenuProvider import androidx.core.view.MenuProvider
import androidx.core.view.isVisible import androidx.core.view.isVisible
import com.topjohnwu.magisk.R 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.arch.viewModel
import com.topjohnwu.magisk.databinding.FragmentLogMd2Binding import com.topjohnwu.magisk.databinding.FragmentLogMd2Binding
import com.topjohnwu.magisk.ui.MainActivity import com.topjohnwu.magisk.ui.MainActivity
import com.topjohnwu.magisk.utils.AccessibilityUtils
import com.topjohnwu.magisk.utils.MotionRevealHelper import com.topjohnwu.magisk.utils.MotionRevealHelper
import rikka.recyclerview.addEdgeSpacing import rikka.recyclerview.addEdgeSpacing
import rikka.recyclerview.addItemSpacing import rikka.recyclerview.addItemSpacing
@ -56,6 +58,11 @@ class LogFragment : BaseFragment<FragmentLogMd2Binding>(), MenuProvider {
addItemSpacing(R.dimen.l1, R.dimen.l_50, R.dimen.l1) addItemSpacing(R.dimen.l1, R.dimen.l_50, R.dimen.l1)
fixEdgeEffect() fixEdgeEffect()
} }
if (!AccessibilityUtils.isAnimationEnabled(requireContext().contentResolver)) {
val scrollView = view.findViewById<HorizontalScrollView>(R.id.log_scroll_magisk)
scrollView.setOverScrollMode(View.OVER_SCROLL_NEVER)
}
} }

View File

@ -8,6 +8,7 @@ import android.view.Menu
import android.view.MenuInflater import android.view.MenuInflater
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewTreeObserver
import android.widget.Toast import android.widget.Toast
import androidx.core.view.MenuProvider import androidx.core.view.MenuProvider
import androidx.core.view.isVisible import androidx.core.view.isVisible
@ -16,8 +17,6 @@ import com.topjohnwu.magisk.arch.BaseFragment
import com.topjohnwu.magisk.arch.viewModel import com.topjohnwu.magisk.arch.viewModel
import com.topjohnwu.magisk.core.ktx.toast import com.topjohnwu.magisk.core.ktx.toast
import com.topjohnwu.magisk.databinding.FragmentActionMd2Binding import com.topjohnwu.magisk.databinding.FragmentActionMd2Binding
import com.topjohnwu.magisk.ui.flash.FlashViewModel
import timber.log.Timber
import com.topjohnwu.magisk.core.R as CoreR import com.topjohnwu.magisk.core.R as CoreR
class ActionFragment : BaseFragment<FragmentActionMd2Binding>(), MenuProvider { class ActionFragment : BaseFragment<FragmentActionMd2Binding>(), MenuProvider {
@ -37,39 +36,32 @@ class ActionFragment : BaseFragment<FragmentActionMd2Binding>(), MenuProvider {
super.onStart() super.onStart()
activity?.setTitle(viewModel.args.name) activity?.setTitle(viewModel.args.name)
binding.closeBtn.setOnClickListener { binding.closeBtn.setOnClickListener {
activity?.onBackPressed(); activity?.onBackPressed()
} }
viewModel.state.observe(this) { viewModel.state.observe(this) {
activity?.supportActionBar?.setSubtitle( if (it != ActionViewModel.State.RUNNING) {
when (it) {
ActionViewModel.State.RUNNING -> CoreR.string.running
ActionViewModel.State.SUCCESS -> CoreR.string.done
ActionViewModel.State.FAILED -> CoreR.string.failure
}
)
when (it) {
ActionViewModel.State.SUCCESS -> {
activity?.apply {
toast(
getString(
com.topjohnwu.magisk.core.R.string.done_action,
this@ActionFragment.viewModel.args.name
), Toast.LENGTH_LONG
)
onBackPressed()
}
}
ActionViewModel.State.FAILED -> {
binding.closeBtn.apply { binding.closeBtn.apply {
if (!this.isVisible) this.show() if (!this.isVisible) this.show()
if (!this.isFocused) this.requestFocus() if (!this.isFocused) this.requestFocus()
} }
} }
if (it != ActionViewModel.State.SUCCESS) return@observe
else -> {} view?.viewTreeObserver?.addOnWindowFocusChangeListener(
object : ViewTreeObserver.OnWindowFocusChangeListener {
override fun onWindowFocusChanged(hasFocus: Boolean) {
if (hasFocus) return
view?.viewTreeObserver?.removeOnWindowFocusChangeListener(this)
view?.context?.apply {
toast(
getString(CoreR.string.done_action, viewModel.args.name),
Toast.LENGTH_SHORT
)
} }
viewModel.back()
}
}
)
} }
} }
@ -85,7 +77,7 @@ class ActionFragment : BaseFragment<FragmentActionMd2Binding>(), MenuProvider {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
defaultOrientation = activity?.requestedOrientation ?: -1 defaultOrientation = activity?.requestedOrientation ?: -1
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LOCKED
if (savedInstanceState == null) { if (savedInstanceState == null) {
viewModel.startRunAction() viewModel.startRunAction()
} }

View File

@ -1,28 +1,26 @@
package com.topjohnwu.magisk.ui.module package com.topjohnwu.magisk.ui.module
import android.view.MenuItem import android.view.MenuItem
import androidx.databinding.Bindable
import androidx.databinding.ObservableArrayList import androidx.databinding.ObservableArrayList
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.map
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.arch.BaseViewModel import com.topjohnwu.magisk.arch.BaseViewModel
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.ktx.synchronized import com.topjohnwu.magisk.core.ktx.synchronized
import com.topjohnwu.magisk.core.ktx.timeFormatStandard import com.topjohnwu.magisk.core.ktx.timeFormatStandard
import com.topjohnwu.magisk.core.ktx.toTime import com.topjohnwu.magisk.core.ktx.toTime
import com.topjohnwu.magisk.core.tasks.RunAction
import com.topjohnwu.magisk.core.utils.MediaStoreUtils import com.topjohnwu.magisk.core.utils.MediaStoreUtils
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
import com.topjohnwu.magisk.databinding.set
import com.topjohnwu.magisk.events.SnackbarEvent import com.topjohnwu.magisk.events.SnackbarEvent
import com.topjohnwu.magisk.ui.flash.ConsoleItem import com.topjohnwu.magisk.ui.flash.ConsoleItem
import com.topjohnwu.superuser.CallbackList import com.topjohnwu.superuser.CallbackList
import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import timber.log.Timber
import java.io.IOException
class ActionViewModel : BaseViewModel() { class ActionViewModel : BaseViewModel() {
@ -32,7 +30,6 @@ class ActionViewModel : BaseViewModel() {
private val _state = MutableLiveData(State.RUNNING) private val _state = MutableLiveData(State.RUNNING)
val state: LiveData<State> get() = _state val state: LiveData<State> get() = _state
val running = state.map { it == State.RUNNING }
val items = ObservableArrayList<ConsoleItem>() val items = ObservableArrayList<ConsoleItem>()
lateinit var args: ActionFragmentArgs lateinit var args: ActionFragmentArgs
@ -46,10 +43,17 @@ class ActionViewModel : BaseViewModel() {
} }
} }
fun startRunAction() { fun startRunAction() = viewModelScope.launch {
viewModelScope.launch { onResult(withContext(Dispatchers.IO) {
onResult(RunAction(args.id, outItems, logItems).exec()) try {
Shell.cmd("run_action \'${args.id}\'")
.to(outItems, logItems)
.exec().isSuccess
} catch (e: IOException) {
Timber.e(e)
false
} }
})
} }
private fun onResult(success: Boolean) { private fun onResult(success: Boolean) {

View File

@ -13,6 +13,7 @@ import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.arch.BaseViewModel import com.topjohnwu.magisk.arch.BaseViewModel
import com.topjohnwu.magisk.core.AppContext import com.topjohnwu.magisk.core.AppContext
import com.topjohnwu.magisk.core.BuildConfig import com.topjohnwu.magisk.core.BuildConfig
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.Const import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.Info import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.R import com.topjohnwu.magisk.core.R
@ -92,7 +93,7 @@ class SettingsViewModel : BaseViewModel(), BaseSettingsItem.Handler {
DownloadPath -> withExternalRW(doAction) DownloadPath -> withExternalRW(doAction)
UpdateChecker -> withPostNotificationPermission(doAction) UpdateChecker -> withPostNotificationPermission(doAction)
Authentication -> AuthEvent(doAction).publish() Authentication -> AuthEvent(doAction).publish()
Hide, Restore -> withInstallPermission(doAction) AutomaticResponse -> if (Config.suAuth) AuthEvent(doAction).publish() else doAction()
else -> doAction() else -> doAction()
} }
} }

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"> android:layout_height="match_parent">
<HorizontalScrollView <HorizontalScrollView
android:id="@+id/log_scroll_magisk"
gone="@{viewModel.loading}" gone="@{viewModel.loading}"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"

View File

@ -19,6 +19,7 @@ android {
buildConfigField("int", "APP_VERSION_CODE", "${Config.versionCode}") buildConfigField("int", "APP_VERSION_CODE", "${Config.versionCode}")
buildConfigField("String", "APP_VERSION_NAME", "\"${Config.version}\"") buildConfigField("String", "APP_VERSION_NAME", "\"${Config.version}\"")
buildConfigField("int", "STUB_VERSION", Config.stubVersion) buildConfigField("int", "STUB_VERSION", Config.stubVersion)
consumerProguardFile("proguard-rules.pro")
} }
buildFeatures { buildFeatures {
@ -59,5 +60,10 @@ dependencies {
implementation(libs.activity) implementation(libs.activity)
implementation(libs.collection.ktx) implementation(libs.collection.ktx)
implementation(libs.profileinstaller) implementation(libs.profileinstaller)
implementation(libs.lifecycle.process)
// We also implement all our tests in this module.
// However, we don't want to bundle test dependencies.
// That's why we make it compileOnly.
compileOnly(libs.test.junit)
compileOnly(libs.test.uiautomator)
} }

View File

@ -22,42 +22,19 @@
int mActivityHandlesConfigFlags; int mActivityHandlesConfigFlags;
} }
# main
-keep,allowoptimization public class com.topjohnwu.magisk.signing.SignBoot {
public static void main(java.lang.String[]);
}
# Strip Timber verbose and debug logging # Strip Timber verbose and debug logging
-assumenosideeffects class timber.log.Timber$Tree { -assumenosideeffects class timber.log.Timber$Tree {
public void v(**); public void v(**);
public void d(**); public void d(**);
} }
# https://github.com/square/retrofit/issues/3751#issuecomment-1192043644
# Keep generic signature of Call, Response (R8 full mode strips signatures from non-kept items).
-keep,allowobfuscation,allowshrinking interface retrofit2.Call
-keep,allowobfuscation,allowshrinking class retrofit2.Response
# With R8 full mode generic signatures are stripped for classes that are not # With R8 full mode generic signatures are stripped for classes that are not
# kept. Suspend functions are wrapped in continuations where the type argument # kept. Suspend functions are wrapped in continuations where the type argument
# is used. # is used.
-keep,allowobfuscation,allowshrinking class kotlin.coroutines.Continuation -keep,allowobfuscation,allowshrinking class kotlin.coroutines.Continuation
# Excessive obfuscation # Excessive obfuscation
-repackageclasses 'a' -flattenpackagehierarchy
-allowaccessmodification -allowaccessmodification
-obfuscationdictionary ../dict.txt -dontwarn org.junit.**
-classobfuscationdictionary ../dict.txt
-packageobfuscationdictionary ../dict.txt
-dontwarn org.bouncycastle.jsse.BCSSLParameters
-dontwarn org.bouncycastle.jsse.BCSSLSocket
-dontwarn org.bouncycastle.jsse.provider.BouncyCastleJsseProvider
-dontwarn org.commonmark.ext.gfm.strikethrough.Strikethrough
-dontwarn org.conscrypt.Conscrypt*
-dontwarn org.conscrypt.ConscryptHostnameVerifier
-dontwarn org.openjsse.javax.net.ssl.SSLParameters
-dontwarn org.openjsse.javax.net.ssl.SSLSocket
-dontwarn org.openjsse.net.ssl.OpenJSSE

View File

@ -16,7 +16,6 @@ import com.topjohnwu.magisk.StubApk
import com.topjohnwu.magisk.core.base.UntrackedActivity import com.topjohnwu.magisk.core.base.UntrackedActivity
import com.topjohnwu.magisk.core.utils.LocaleSetting import com.topjohnwu.magisk.core.utils.LocaleSetting
import com.topjohnwu.magisk.core.utils.NetworkObserver import com.topjohnwu.magisk.core.utils.NetworkObserver
import com.topjohnwu.magisk.core.utils.ProcessLifecycle
import com.topjohnwu.magisk.core.utils.RootUtils import com.topjohnwu.magisk.core.utils.RootUtils
import com.topjohnwu.magisk.core.utils.ShellInit import com.topjohnwu.magisk.core.utils.ShellInit
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
@ -40,6 +39,7 @@ object AppContext : ContextWrapper(null),
private var ref = WeakReference<Activity>(null) private var ref = WeakReference<Activity>(null)
private lateinit var application: Application private lateinit var application: Application
private lateinit var networkObserver: NetworkObserver
init { init {
// Always log full stack trace with Timber // Always log full stack trace with Timber
@ -56,6 +56,10 @@ object AppContext : ContextWrapper(null),
LocaleSetting.instance.updateResource(resources) LocaleSetting.instance.updateResource(resources)
} }
override fun onActivityStarted(activity: Activity) {
networkObserver.postCurrentState()
}
override fun onActivityResumed(activity: Activity) { override fun onActivityResumed(activity: Activity) {
if (activity is UntrackedActivity) return if (activity is UntrackedActivity) return
ref = WeakReference(activity) ref = WeakReference(activity)
@ -102,8 +106,7 @@ object AppContext : ContextWrapper(null),
val lm = getSystemService(LocaleManager::class.java) val lm = getSystemService(LocaleManager::class.java)
lm.overrideLocaleConfig = LocaleSetting.localeConfig lm.overrideLocaleConfig = LocaleSetting.localeConfig
} }
ProcessLifecycle.init(this) networkObserver = NetworkObserver.init(this)
NetworkObserver.init(this)
if (!BuildConfig.DEBUG && !isRunningAsStub) { if (!BuildConfig.DEBUG && !isRunningAsStub) {
GlobalScope.launch(Dispatchers.IO) { GlobalScope.launch(Dispatchers.IO) {
ProfileInstaller.writeProfile(this@AppContext) ProfileInstaller.writeProfile(this@AppContext)
@ -120,7 +123,6 @@ object AppContext : ContextWrapper(null),
} }
override fun onActivityCreated(activity: Activity, bundle: Bundle?) {} override fun onActivityCreated(activity: Activity, bundle: Bundle?) {}
override fun onActivityStarted(activity: Activity) {}
override fun onActivityStopped(activity: Activity) {} override fun onActivityStopped(activity: Activity) {}
override fun onActivitySaveInstanceState(activity: Activity, bundle: Bundle) {} override fun onActivitySaveInstanceState(activity: Activity, bundle: Bundle) {}
override fun onActivityDestroyed(activity: Activity) {} override fun onActivityDestroyed(activity: Activity) {}

View File

@ -83,7 +83,7 @@ object Config : PreferenceConfig, DBConfig {
const val SU_AUTO_ALLOW = 2 const val SU_AUTO_ALLOW = 2
// su timeout // su timeout
val TIMEOUT_LIST = intArrayOf(0, -1, 10, 20, 30, 60) val TIMEOUT_LIST = longArrayOf(0, -1, 10, 20, 30, 60)
} }
private val defaultChannel = private val defaultChannel =

View File

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

View File

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

View File

@ -11,6 +11,7 @@ import androidx.core.content.getSystemService
import com.topjohnwu.magisk.core.base.BaseJobService import com.topjohnwu.magisk.core.base.BaseJobService
import com.topjohnwu.magisk.core.di.ServiceLocator import com.topjohnwu.magisk.core.di.ServiceLocator
import com.topjohnwu.magisk.core.download.DownloadEngine import com.topjohnwu.magisk.core.download.DownloadEngine
import com.topjohnwu.magisk.core.download.DownloadSession
import com.topjohnwu.magisk.core.download.Subject import com.topjohnwu.magisk.core.download.Subject
import com.topjohnwu.magisk.view.Notifications import com.topjohnwu.magisk.view.Notifications
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -25,7 +26,7 @@ class JobService : BaseJobService() {
@TargetApi(value = 34) @TargetApi(value = 34)
inner class Session( inner class Session(
private var params: JobParameters private var params: JobParameters
) : DownloadEngine.Session { ) : DownloadSession {
override val context get() = this@JobService override val context get() = this@JobService
val engine = DownloadEngine(this) val engine = DownloadEngine(this)

View File

@ -3,7 +3,6 @@ package com.topjohnwu.magisk.core
import android.os.Bundle import android.os.Bundle
import com.topjohnwu.magisk.core.base.BaseProvider import com.topjohnwu.magisk.core.base.BaseProvider
import com.topjohnwu.magisk.core.su.SuCallbackHandler import com.topjohnwu.magisk.core.su.SuCallbackHandler
import com.topjohnwu.magisk.core.su.TestHandler
class Provider : BaseProvider() { class Provider : BaseProvider() {
@ -13,7 +12,7 @@ class Provider : BaseProvider() {
SuCallbackHandler.run(context!!, method, extras) SuCallbackHandler.run(context!!, method, extras)
Bundle.EMPTY Bundle.EMPTY
} }
else -> TestHandler.run(method) else -> Bundle.EMPTY
} }
} }
} }

View File

@ -7,9 +7,10 @@ import androidx.core.app.ServiceCompat
import androidx.core.content.IntentCompat import androidx.core.content.IntentCompat
import com.topjohnwu.magisk.core.base.BaseService import com.topjohnwu.magisk.core.base.BaseService
import com.topjohnwu.magisk.core.download.DownloadEngine import com.topjohnwu.magisk.core.download.DownloadEngine
import com.topjohnwu.magisk.core.download.DownloadSession
import com.topjohnwu.magisk.core.download.Subject import com.topjohnwu.magisk.core.download.Subject
class Service : BaseService(), DownloadEngine.Session { class Service : BaseService(), DownloadSession {
private var mEngine: DownloadEngine? = null private var mEngine: DownloadEngine? = null
override val context get() = this override val context get() = this

View File

@ -7,9 +7,13 @@ import kotlinx.coroutines.withContext
open class MagiskDB { open class MagiskDB {
suspend fun <R> exec( class Literal(
val str: String
)
suspend inline fun <R> exec(
query: String, query: String,
mapper: suspend (Map<String, String>) -> R crossinline mapper: (Map<String, String>) -> R
): List<R> { ): List<R> {
return withContext(Dispatchers.IO) { return withContext(Dispatchers.IO) {
val out = Shell.cmd("magisk --sqlite '$query'").await().out val out = Shell.cmd("magisk --sqlite '$query'").await().out
@ -18,13 +22,15 @@ open class MagiskDB {
.map { it.split("=", limit = 2) } .map { it.split("=", limit = 2) }
.filter { it.size == 2 } .filter { it.size == 2 }
.associate { it[0] to it[1] } .associate { it[0] to it[1] }
.let { mapper(it) } .let(mapper)
} }
} }
} }
suspend inline fun exec(query: String) { suspend fun exec(query: String) {
exec(query) {} withContext(Dispatchers.IO) {
Shell.cmd("magisk --sqlite '$query'").await()
}
} }
fun Map<String, Any>.toQuery(): String { fun Map<String, Any>.toQuery(): String {
@ -33,6 +39,7 @@ open class MagiskDB {
when (it) { when (it) {
is Boolean -> if (it) "1" else "0" is Boolean -> if (it) "1" else "0"
is Number -> it.toString() is Number -> it.toString()
is Literal -> it.str
else -> "\"$it\"" else -> "\"$it\""
} }
} }

View File

@ -3,24 +3,24 @@ package com.topjohnwu.magisk.core.data.magiskdb
import com.topjohnwu.magisk.core.AppContext import com.topjohnwu.magisk.core.AppContext
import com.topjohnwu.magisk.core.Const import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.model.su.SuPolicy import com.topjohnwu.magisk.core.model.su.SuPolicy
import java.util.concurrent.TimeUnit
private const val SELECT_QUERY = "SELECT (until - strftime(\"%s\", \"now\")) AS remain, *"
class PolicyDao : MagiskDB() { class PolicyDao : MagiskDB() {
suspend fun deleteOutdated() { suspend fun deleteOutdated() {
val nowSeconds = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis())
val query = "DELETE FROM ${Table.POLICY} WHERE " + val query = "DELETE FROM ${Table.POLICY} WHERE " +
"(until > 0 AND until < $nowSeconds) OR until < 0" "(until > 0 AND until < strftime(\"%s\", \"now\")) OR until < 0"
exec(query) exec(query)
} }
suspend fun delete(uid: Int) { suspend fun delete(uid: Int) {
val query = "DELETE FROM ${Table.POLICY} WHERE uid == $uid" val query = "DELETE FROM ${Table.POLICY} WHERE uid=$uid"
exec(query) exec(query)
} }
suspend fun fetch(uid: Int): SuPolicy? { suspend fun fetch(uid: Int): SuPolicy? {
val query = "SELECT * FROM ${Table.POLICY} WHERE uid == $uid LIMIT = 1" val query = "$SELECT_QUERY FROM ${Table.POLICY} WHERE uid=$uid LIMIT 1"
return exec(query, ::toPolicy).firstOrNull() return exec(query, ::toPolicy).firstOrNull()
} }
@ -35,7 +35,7 @@ class PolicyDao : MagiskDB() {
} }
suspend fun fetchAll(): List<SuPolicy> { suspend fun fetchAll(): List<SuPolicy> {
val query = "SELECT * FROM ${Table.POLICY} WHERE uid/100000 == ${Const.USER_ID}" val query = "$SELECT_QUERY FROM ${Table.POLICY} WHERE uid/100000=${Const.USER_ID}"
return exec(query, ::toPolicy).filterNotNull() return exec(query, ::toPolicy).filterNotNull()
} }
@ -43,8 +43,15 @@ class PolicyDao : MagiskDB() {
val uid = map["uid"]?.toInt() ?: return null val uid = map["uid"]?.toInt() ?: return null
val policy = SuPolicy(uid) val policy = SuPolicy(uid)
map["until"]?.toLong()?.let { until ->
if (until <= 0) {
policy.remain = until
} else {
map["remain"]?.toLong()?.let { policy.remain = it }
}
}
map["policy"]?.toInt()?.let { policy.policy = it } map["policy"]?.toInt()?.let { policy.policy = it }
map["until"]?.toLong()?.let { policy.until = it }
map["logging"]?.toInt()?.let { policy.logging = it != 0 } map["logging"]?.toInt()?.let { policy.logging = it != 0 }
map["notification"]?.toInt()?.let { policy.notification = it != 0 } map["notification"]?.toInt()?.let { policy.notification = it != 0 }
return policy return policy

View File

@ -3,7 +3,7 @@ package com.topjohnwu.magisk.core.data.magiskdb
class SettingsDao : MagiskDB() { class SettingsDao : MagiskDB() {
suspend fun delete(key: String) { suspend fun delete(key: String) {
val query = "DELETE FROM ${Table.SETTINGS} WHERE key == \"$key\"" val query = "DELETE FROM ${Table.SETTINGS} WHERE key=\"$key\""
exec(query) exec(query)
} }
@ -14,7 +14,7 @@ class SettingsDao : MagiskDB() {
} }
suspend fun fetch(key: String, default: Int = -1): Int { suspend fun fetch(key: String, default: Int = -1): Int {
val query = "SELECT value FROM ${Table.SETTINGS} WHERE key == \"$key\" LIMIT 1" val query = "SELECT value FROM ${Table.SETTINGS} WHERE key=\"$key\" LIMIT 1"
return exec(query) { it["value"]?.toInt() }.firstOrNull() ?: default return exec(query) { it["value"]?.toInt() }.firstOrNull() ?: default
} }
} }

View File

@ -3,7 +3,7 @@ package com.topjohnwu.magisk.core.data.magiskdb
class StringDao : MagiskDB() { class StringDao : MagiskDB() {
suspend fun delete(key: String) { suspend fun delete(key: String) {
val query = "DELETE FROM ${Table.STRINGS} WHERE key == \"$key\"" val query = "DELETE FROM ${Table.STRINGS} WHERE key=\"$key\""
exec(query) exec(query)
} }
@ -14,7 +14,7 @@ class StringDao : MagiskDB() {
} }
suspend fun fetch(key: String, default: String = ""): String { suspend fun fetch(key: String, default: String = ""): String {
val query = "SELECT value FROM ${Table.STRINGS} WHERE key == \"$key\" LIMIT 1" val query = "SELECT value FROM ${Table.STRINGS} WHERE key=\"$key\" LIMIT 1"
return exec(query) { it["value"] }.firstOrNull() ?: default return exec(query) { it["value"] }.firstOrNull() ?: default
} }
} }

View File

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

View File

@ -7,7 +7,6 @@ import android.app.PendingIntent
import android.app.job.JobInfo import android.app.job.JobInfo
import android.app.job.JobScheduler import android.app.job.JobScheduler
import android.content.Context import android.content.Context
import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
@ -16,7 +15,6 @@ import androidx.collection.isNotEmpty
import androidx.core.content.getSystemService import androidx.core.content.getSystemService
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import com.topjohnwu.magisk.StubApk
import com.topjohnwu.magisk.core.AppContext import com.topjohnwu.magisk.core.AppContext
import com.topjohnwu.magisk.core.Const import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.JobService import com.topjohnwu.magisk.core.JobService
@ -25,18 +23,8 @@ import com.topjohnwu.magisk.core.base.IActivityExtension
import com.topjohnwu.magisk.core.cmp import com.topjohnwu.magisk.core.cmp
import com.topjohnwu.magisk.core.di.ServiceLocator import com.topjohnwu.magisk.core.di.ServiceLocator
import com.topjohnwu.magisk.core.intent import com.topjohnwu.magisk.core.intent
import com.topjohnwu.magisk.core.isRunningAsStub
import com.topjohnwu.magisk.core.ktx.cachedFile
import com.topjohnwu.magisk.core.ktx.copyAll
import com.topjohnwu.magisk.core.ktx.copyAndClose
import com.topjohnwu.magisk.core.ktx.forEach
import com.topjohnwu.magisk.core.ktx.set import com.topjohnwu.magisk.core.ktx.set
import com.topjohnwu.magisk.core.ktx.withStreams
import com.topjohnwu.magisk.core.ktx.writeTo
import com.topjohnwu.magisk.core.tasks.AppMigration
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
import com.topjohnwu.magisk.core.utils.ProgressInputStream import com.topjohnwu.magisk.core.utils.ProgressInputStream
import com.topjohnwu.magisk.utils.APKInstall
import com.topjohnwu.magisk.view.Notifications import com.topjohnwu.magisk.view.Notifications
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -44,13 +32,7 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import okhttp3.ResponseBody import okhttp3.ResponseBody
import timber.log.Timber import timber.log.Timber
import java.io.IOException
import java.io.InputStream import java.io.InputStream
import java.io.OutputStream
import java.util.zip.ZipEntry
import java.util.zip.ZipFile
import java.util.zip.ZipInputStream
import java.util.zip.ZipOutputStream
/** /**
* This class drives the execution of file downloads and notification management. * This class drives the execution of file downloads and notification management.
@ -69,16 +51,7 @@ import java.util.zip.ZipOutputStream
* For API 23 - 33, we use a foreground service as a session. * For API 23 - 33, we use a foreground service as a session.
* For API 34 and higher, we use user-initiated job services as a session. * For API 34 and higher, we use user-initiated job services as a session.
*/ */
class DownloadEngine( class DownloadEngine(session: DownloadSession) : DownloadSession by session, DownloadNotifier {
private val session: Session
) {
interface Session {
val context: Context
fun attachNotification(id: Int, builder: Notification.Builder)
fun onDownloadComplete()
}
companion object { companion object {
const val ACTION = "com.topjohnwu.magisk.DOWNLOAD" const val ACTION = "com.topjohnwu.magisk.DOWNLOAD"
@ -99,35 +72,37 @@ class DownloadEngine(
} }
} }
private fun createIntent(context: Context, subject: Subject) = private fun createBroadcastIntent(context: Context, subject: Subject) =
if (Build.VERSION.SDK_INT >= 34) {
context.intent<com.topjohnwu.magisk.core.Receiver>() context.intent<com.topjohnwu.magisk.core.Receiver>()
.setAction(ACTION) .setAction(ACTION)
.putExtra(SUBJECT_KEY, subject) .putExtra(SUBJECT_KEY, subject)
} else {
private fun createServiceIntent(context: Context, subject: Subject) =
context.intent<com.topjohnwu.magisk.core.Service>() context.intent<com.topjohnwu.magisk.core.Service>()
.setAction(ACTION) .setAction(ACTION)
.putExtra(SUBJECT_KEY, subject) .putExtra(SUBJECT_KEY, subject)
}
@SuppressLint("InlinedApi") @SuppressLint("InlinedApi")
fun getPendingIntent(context: Context, subject: Subject): PendingIntent { fun getPendingIntent(context: Context, subject: Subject): PendingIntent {
val flag = PendingIntent.FLAG_IMMUTABLE or val flag = PendingIntent.FLAG_IMMUTABLE or
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_UPDATE_CURRENT or
PendingIntent.FLAG_ONE_SHOT PendingIntent.FLAG_ONE_SHOT
val intent = createIntent(context, subject)
return if (Build.VERSION.SDK_INT >= 34) { return if (Build.VERSION.SDK_INT >= 34) {
// On API 34+, download tasks are handled with a user-initiated job. // On API 34+, download tasks are handled with a user-initiated job.
// However, there is no way to schedule a new job directly with a pending intent. // However, there is no way to schedule a new job directly with a pending intent.
// As a workaround, we send the subject to a broadcast receiver and have it // As a workaround, we send the subject to a broadcast receiver and have it
// schedule the job for us. // schedule the job for us.
val intent = createBroadcastIntent(context, subject)
PendingIntent.getBroadcast(context, REQUEST_CODE, intent, flag) PendingIntent.getBroadcast(context, REQUEST_CODE, intent, flag)
} else if (Build.VERSION.SDK_INT >= 26) { } else {
val intent = createServiceIntent(context, subject)
if (Build.VERSION.SDK_INT >= 26) {
PendingIntent.getForegroundService(context, REQUEST_CODE, intent, flag) PendingIntent.getForegroundService(context, REQUEST_CODE, intent, flag)
} else { } else {
PendingIntent.getService(context, REQUEST_CODE, intent, flag) PendingIntent.getService(context, REQUEST_CODE, intent, flag)
} }
} }
}
@SuppressLint("InlinedApi") @SuppressLint("InlinedApi")
fun <T> startWithActivity( fun <T> startWithActivity(
@ -140,6 +115,7 @@ class DownloadEngine(
} }
} }
@SuppressLint("MissingPermission")
fun start(context: Context, subject: Subject) { fun start(context: Context, subject: Subject) {
if (Build.VERSION.SDK_INT >= 34) { if (Build.VERSION.SDK_INT >= 34) {
val scheduler = context.getSystemService<JobScheduler>()!! val scheduler = context.getSystemService<JobScheduler>()!!
@ -152,24 +128,29 @@ class DownloadEngine(
.setTransientExtras(extras) .setTransientExtras(extras)
.build() .build()
scheduler.schedule(info) scheduler.schedule(info)
} else if (Build.VERSION.SDK_INT >= 26) {
context.startForegroundService(createIntent(context, subject))
} else { } else {
context.startService(createIntent(context, subject)) val intent = createServiceIntent(context, subject)
if (Build.VERSION.SDK_INT >= 26) {
context.startForegroundService(intent)
} else {
context.startService(intent)
} }
} }
} }
}
private val notifications = SparseArrayCompat<Notification.Builder>()
private var attachedId = -1
private val job = Job()
private val processor = DownloadProcessor(this)
private val network get() = ServiceLocator.networkService
fun download(subject: Subject) { fun download(subject: Subject) {
notifyUpdate(subject.notifyId) notifyUpdate(subject.notifyId)
CoroutineScope(job + Dispatchers.IO).launch { CoroutineScope(job + Dispatchers.IO).launch {
try { try {
val stream = network.fetchFile(subject.url).toProgressStream(subject) val stream = network.fetchFile(subject.url).toProgressStream(subject)
when (subject) { processor.handle(stream, subject)
is Subject.App -> handleApp(stream, subject)
is Subject.Module -> handleModule(stream, subject.file)
else -> stream.copyAndClose(subject.file.outputStream())
}
val activity = AppContext.foregroundActivity val activity = AppContext.foregroundActivity
if (activity != null && subject.autoLaunch) { if (activity != null && subject.autoLaunch) {
notifyRemove(subject.notifyId) notifyRemove(subject.notifyId)
@ -187,16 +168,13 @@ class DownloadEngine(
@Synchronized @Synchronized
fun reattach() { fun reattach() {
val builder = notifications[attachedId] ?: return val builder = notifications[attachedId] ?: return
session.attachNotification(attachedId, builder) attachNotification(attachedId, builder)
} }
private val notifications = SparseArrayCompat<Notification.Builder>() private fun attach(id: Int, notification: Notification.Builder) {
private var attachedId = -1 attachedId = id
attachNotification(id, notification)
private val job = Job() }
private val context get() = session.context
private val network get() = ServiceLocator.networkService
private fun finalNotify(id: Int, editor: (Notification.Builder) -> Unit): Int { private fun finalNotify(id: Int, editor: (Notification.Builder) -> Unit): Int {
val notification = notifyRemove(id)?.also(editor) ?: return -1 val notification = notifyRemove(id)?.also(editor) ?: return -1
@ -223,19 +201,14 @@ class DownloadEngine(
subject.pendingIntent(context)?.let { intent -> it.setContentIntent(intent) } subject.pendingIntent(context)?.let { intent -> it.setContentIntent(intent) }
} }
private fun attachNotification(id: Int, notification: Notification.Builder) {
attachedId = id
session.attachNotification(id, notification)
}
@Synchronized @Synchronized
private fun notifyUpdate(id: Int, editor: (Notification.Builder) -> Unit = {}) { override fun notifyUpdate(id: Int, editor: (Notification.Builder) -> Unit) {
val notification = (notifications[id] ?: Notifications.startProgress("").also { val notification = (notifications[id] ?: Notifications.startProgress("").also {
notifications[id] = it notifications[id] = it
}).apply(editor) }).apply(editor)
if (attachedId < 0) if (attachedId < 0)
attachNotification(id, notification) attach(id, notification)
else else
Notifications.mgr.notify(id, notification.build()) Notifications.mgr.notify(id, notification.build())
} }
@ -255,11 +228,11 @@ class DownloadEngine(
// There are still remaining notifications, pick one and attach to the session // There are still remaining notifications, pick one and attach to the session
val anotherId = notifications.keyAt(0) val anotherId = notifications.keyAt(0)
val notification = notifications.valueAt(0) val notification = notifications.valueAt(0)
attachNotification(anotherId, notification) attach(anotherId, notification)
} else { } else {
// No more notifications left, terminate the session // No more notifications left, terminate the session
attachedId = -1 attachedId = -1
session.onDownloadComplete() onDownloadComplete()
} }
} }
} }
@ -268,90 +241,6 @@ class DownloadEngine(
return n return n
} }
private suspend fun handleApp(stream: InputStream, subject: Subject.App) {
val external = subject.file.outputStream()
if (isRunningAsStub) {
val updateApk = StubApk.update(context)
try {
// Download full APK to stub update path
stream.copyAndClose(TeeOutputStream(external, updateApk.outputStream()))
// Also upgrade stub
notifyUpdate(subject.notifyId) {
it.setProgress(0, 0, true)
.setContentTitle(context.getString(R.string.hide_app_title))
.setContentText("")
}
// Extract stub
val zf = ZipFile(updateApk)
val apk = context.cachedFile("stub.apk")
apk.delete()
zf.getInputStream(zf.getEntry("assets/stub.apk")).writeTo(apk)
zf.close()
// Patch and install
subject.intent = AppMigration.upgradeStub(context, apk)
?: throw IOException("HideAPK patch error")
apk.delete()
} catch (e: Exception) {
// If any error occurred, do not let stub load the new APK
updateApk.delete()
throw e
}
} else {
val session = APKInstall.startSession(context)
stream.copyAndClose(TeeOutputStream(external, session.openStream(context)))
subject.intent = session.waitIntent()
}
}
private suspend fun handleModule(src: InputStream, file: Uri) {
val input = ZipInputStream(src)
val output = ZipOutputStream(file.outputStream())
withStreams(input, output) { zin, zout ->
zout.putNextEntry(ZipEntry("META-INF/"))
zout.putNextEntry(ZipEntry("META-INF/com/"))
zout.putNextEntry(ZipEntry("META-INF/com/google/"))
zout.putNextEntry(ZipEntry("META-INF/com/google/android/"))
zout.putNextEntry(ZipEntry("META-INF/com/google/android/update-binary"))
context.assets.open("module_installer.sh").use { it.copyAll(zout) }
zout.putNextEntry(ZipEntry("META-INF/com/google/android/updater-script"))
zout.write("#MAGISK\n".toByteArray())
zin.forEach { entry ->
val path = entry.name
if (path.isNotEmpty() && !path.startsWith("META-INF")) {
zout.putNextEntry(ZipEntry(path))
if (!entry.isDirectory) {
zin.copyAll(zout)
}
}
}
}
}
private class TeeOutputStream(
private val o1: OutputStream,
private val o2: OutputStream
) : OutputStream() {
override fun write(b: Int) {
o1.write(b)
o2.write(b)
}
override fun write(b: ByteArray?, off: Int, len: Int) {
o1.write(b, off, len)
o2.write(b, off, len)
}
override fun close() {
o1.close()
o2.close()
}
}
private fun ResponseBody.toProgressStream(subject: Subject): InputStream { private fun ResponseBody.toProgressStream(subject: Subject): InputStream {
val max = contentLength() val max = contentLength()
val total = max.toFloat() / 1048576 val total = max.toFloat() / 1048576

View File

@ -0,0 +1,122 @@
package com.topjohnwu.magisk.core.download
import android.net.Uri
import com.topjohnwu.magisk.StubApk
import com.topjohnwu.magisk.core.R
import com.topjohnwu.magisk.core.isRunningAsStub
import com.topjohnwu.magisk.core.ktx.cachedFile
import com.topjohnwu.magisk.core.ktx.copyAll
import com.topjohnwu.magisk.core.ktx.copyAndClose
import com.topjohnwu.magisk.core.ktx.withInOut
import com.topjohnwu.magisk.core.ktx.writeTo
import com.topjohnwu.magisk.core.tasks.AppMigration
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
import com.topjohnwu.magisk.utils.APKInstall
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream
import org.apache.commons.compress.archivers.zip.ZipFile
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
class DownloadProcessor(notifier: DownloadNotifier) : DownloadNotifier by notifier {
suspend fun handle(stream: InputStream, subject: Subject) {
when (subject) {
is Subject.App -> handleApp(stream, subject)
is Subject.Module -> handleModule(stream, subject.file)
else -> stream.copyAndClose(subject.file.outputStream())
}
}
suspend fun handleApp(stream: InputStream, subject: Subject.App) {
val external = subject.file.outputStream()
if (isRunningAsStub) {
val updateApk = StubApk.update(context)
try {
// Download full APK to stub update path
stream.copyAndClose(TeeOutputStream(external, updateApk.outputStream()))
// Also upgrade stub
notifyUpdate(subject.notifyId) {
it.setProgress(0, 0, true)
.setContentTitle(context.getString(R.string.hide_app_title))
.setContentText("")
}
// Extract stub
val apk = context.cachedFile("stub.apk")
ZipFile.Builder().setFile(updateApk).get().use { zf ->
apk.delete()
zf.getInputStream(zf.getEntry("assets/stub.apk")).writeTo(apk)
}
// Patch and install
subject.intent = AppMigration.upgradeStub(context, apk)
?: throw IOException("HideAPK patch error")
apk.delete()
} catch (e: Exception) {
// If any error occurred, do not let stub load the new APK
updateApk.delete()
throw e
}
} else {
val session = APKInstall.startSession(context)
stream.copyAndClose(TeeOutputStream(external, session.openStream(context)))
subject.intent = session.waitIntent()
}
}
suspend fun handleModule(src: InputStream, file: Uri) {
val tmp = context.cachedFile("module.zip")
try {
// First download the entire zip into cache so we can process it
src.writeTo(tmp)
val input = ZipFile.Builder().setFile(tmp).get()
val output = ZipArchiveOutputStream(file.outputStream())
withInOut(input, output) { zin, zout ->
zout.putArchiveEntry(ZipArchiveEntry("META-INF/"))
zout.closeArchiveEntry()
zout.putArchiveEntry(ZipArchiveEntry("META-INF/com/"))
zout.closeArchiveEntry()
zout.putArchiveEntry(ZipArchiveEntry("META-INF/com/google/"))
zout.closeArchiveEntry()
zout.putArchiveEntry(ZipArchiveEntry("META-INF/com/google/android/"))
zout.closeArchiveEntry()
zout.putArchiveEntry(ZipArchiveEntry("META-INF/com/google/android/update-binary"))
context.assets.open("module_installer.sh").use { it.copyAll(zout) }
zout.closeArchiveEntry()
zout.putArchiveEntry(ZipArchiveEntry("META-INF/com/google/android/updater-script"))
zout.write("#MAGISK\n".toByteArray())
zout.closeArchiveEntry()
// Then simply copy all entries to output
zin.copyRawEntries(zout) { entry -> !entry.name.startsWith("META-INF") }
}
} finally {
tmp.delete()
}
}
private class TeeOutputStream(
private val o1: OutputStream,
private val o2: OutputStream
) : OutputStream() {
override fun write(b: Int) {
o1.write(b)
o2.write(b)
}
override fun write(b: ByteArray?, off: Int, len: Int) {
o1.write(b, off, len)
o2.write(b, off, len)
}
override fun close() {
o1.close()
o2.close()
}
}
}

View File

@ -0,0 +1,15 @@
package com.topjohnwu.magisk.core.download
import android.app.Notification
import android.content.Context
interface DownloadSession {
val context: Context
fun attachNotification(id: Int, builder: Notification.Builder)
fun onDownloadComplete()
}
interface DownloadNotifier {
val context: Context
fun notifyUpdate(id: Int, editor: (Notification.Builder) -> Unit = {})
}

View File

@ -2,7 +2,11 @@ package com.topjohnwu.magisk.core.ktx
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Activity import android.app.Activity
import android.content.* import android.content.BroadcastReceiver
import android.content.Context
import android.content.ContextWrapper
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.ApplicationInfo import android.content.pm.ApplicationInfo
import android.content.pm.PackageInfo import android.content.pm.PackageInfo
import android.content.pm.PackageManager import android.content.pm.PackageManager
@ -23,7 +27,6 @@ import com.topjohnwu.magisk.core.utils.RootUtils
import com.topjohnwu.magisk.utils.APKInstall import com.topjohnwu.magisk.utils.APKInstall
import com.topjohnwu.superuser.internal.UiThreadHandler import com.topjohnwu.superuser.internal.UiThreadHandler
import java.io.File import java.io.File
import kotlin.String
fun Context.getBitmap(id: Int): Bitmap { fun Context.getBitmap(id: Int): Bitmap {
var drawable = getDrawable(id)!! var drawable = getDrawable(id)!!

View File

@ -8,6 +8,7 @@ import kotlinx.coroutines.flow.flatMapMerge
import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.isActive import kotlinx.coroutines.isActive
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.io.Closeable
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
@ -17,24 +18,14 @@ import java.text.DateFormat
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Collections import java.util.Collections
import java.util.Locale import java.util.Locale
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
inline fun ZipInputStream.forEach(callback: (ZipEntry) -> Unit) { inline fun <In : Closeable, Out : Closeable> withInOut(
var entry: ZipEntry? = nextEntry input: In,
while (entry != null) { output: Out,
callback(entry)
entry = nextEntry
}
}
inline fun <In : InputStream, Out : OutputStream> withStreams(
inStream: In,
outStream: Out,
withBoth: (In, Out) -> Unit withBoth: (In, Out) -> Unit
) { ) {
inStream.use { reader -> input.use { reader ->
outStream.use { writer -> output.use { writer ->
withBoth(reader, writer) withBoth(reader, writer)
} }
} }
@ -64,7 +55,7 @@ suspend inline fun InputStream.copyAndClose(
out: OutputStream, out: OutputStream,
bufferSize: Int = DEFAULT_BUFFER_SIZE, bufferSize: Int = DEFAULT_BUFFER_SIZE,
dispatcher: CoroutineDispatcher = Dispatchers.IO dispatcher: CoroutineDispatcher = Dispatchers.IO
) = withStreams(this, out) { i, o -> i.copyAll(o, bufferSize, dispatcher) } ) = withInOut(this, out) { i, o -> i.copyAll(o, bufferSize, dispatcher) }
@Throws(IOException::class) @Throws(IOException::class)
suspend inline fun InputStream.writeTo( suspend inline fun InputStream.writeTo(

View File

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

View File

@ -1,22 +1,32 @@
package com.topjohnwu.magisk.core.model.su package com.topjohnwu.magisk.core.model.su
class SuPolicy(val uid: Int) { import com.topjohnwu.magisk.core.data.magiskdb.MagiskDB
class SuPolicy(
val uid: Int,
var policy: Int = INTERACTIVE,
var remain: Long = -1L,
var logging: Boolean = true,
var notification: Boolean = true,
) {
companion object { companion object {
const val INTERACTIVE = 0 const val INTERACTIVE = 0
const val DENY = 1 const val DENY = 1
const val ALLOW = 2 const val ALLOW = 2
} }
var policy: Int = INTERACTIVE fun toMap(): MutableMap<String, Any> {
var until: Long = -1L val until = if (remain <= 0) {
var logging: Boolean = true remain
var notification: Boolean = true } else {
MagiskDB.Literal("(strftime(\"%s\", \"now\") + $remain)")
fun toMap(): MutableMap<String, Any> = mutableMapOf( }
return mutableMapOf(
"uid" to uid, "uid" to uid,
"policy" to policy, "policy" to policy,
"until" to until, "until" to until,
"logging" to logging, "logging" to logging,
"notification" to notification "notification" to notification
) )
}
} }

View File

@ -4,6 +4,7 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.ByteBuffer;
public class ByteArrayStream extends ByteArrayOutputStream { public class ByteArrayStream extends ByteArrayOutputStream {
@ -27,4 +28,8 @@ public class ByteArrayStream extends ByteArrayOutputStream {
public ByteArrayInputStream getInputStream() { public ByteArrayInputStream getInputStream() {
return new ByteArrayInputStream(buf, 0, count); 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; privateKey[0] = key;
// Generate, in memory, an APK signed using standard JAR Signature Scheme. // Generate, in memory, an APK signed using standard JAR Signature Scheme.
ByteArrayOutputStream v1SignedApkBuf = new ByteArrayOutputStream(); ByteArrayStream v1SignedApkBuf = new ByteArrayStream();
JarOutputStream outputJar = new JarOutputStream(v1SignedApkBuf); JarOutputStream outputJar = new JarOutputStream(v1SignedApkBuf);
// Use maximum compression for compressed entries because the APK lives forever on // Use maximum compression for compressed entries because the APK lives forever on
// the system partition. // the system partition.
@ -519,8 +519,7 @@ public class SignApk {
copyFiles(manifest, inputJar, outputJar, timestamp, alignment); copyFiles(manifest, inputJar, outputJar, timestamp, alignment);
signFile(manifest, publicKey, privateKey, timestamp, outputJar); signFile(manifest, publicKey, privateKey, timestamp, outputJar);
outputJar.close(); outputJar.close();
ByteBuffer v1SignedApk = ByteBuffer.wrap(v1SignedApkBuf.toByteArray()); ByteBuffer v1SignedApk = v1SignedApkBuf.toByteBuffer();
v1SignedApkBuf.reset();
ByteBuffer[] outputChunks; ByteBuffer[] outputChunks;
List<ApkSignerV2.SignerConfig> signerConfigs = createV2SignerConfigs(privateKey, publicKey, List<ApkSignerV2.SignerConfig> signerConfigs = createV2SignerConfigs(privateKey, publicKey,

View File

@ -62,7 +62,7 @@ class SuRequestHandler(
return false return false
} }
output = File(fifo) output = File(fifo)
policy = SuPolicy(uid) policy = policyDB.fetch(uid) ?: SuPolicy(uid)
try { try {
pkgInfo = pm.getPackageInfo(uid, pid) ?: PackageInfo().apply { pkgInfo = pm.getPackageInfo(uid, pid) ?: PackageInfo().apply {
val name = pm.getNameForUid(uid) ?: throw PackageManager.NameNotFoundException() val name = pm.getNameForUid(uid) ?: throw PackageManager.NameNotFoundException()
@ -81,15 +81,13 @@ class SuRequestHandler(
return true return true
} }
suspend fun respond(action: Int, time: Int) { suspend fun respond(action: Int, time: Long) {
val until = if (time > 0)
TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()) +
TimeUnit.MINUTES.toSeconds(time.toLong())
else
time.toLong()
policy.policy = action policy.policy = action
policy.until = until if (time >= 0) {
policy.remain = TimeUnit.MINUTES.toSeconds(time)
} else {
policy.remain = time
}
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
try { try {
@ -100,7 +98,7 @@ class SuRequestHandler(
} catch (e: IOException) { } catch (e: IOException) {
Timber.e(e) Timber.e(e)
} }
if (until >= 0) { if (time >= 0) {
policyDB.update(policy) policyDB.update(policy)
} }
} }

View File

@ -1,80 +0,0 @@
package com.topjohnwu.magisk.core.su
import android.os.Bundle
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.di.ServiceLocator
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 kotlinx.coroutines.Runnable
import kotlinx.coroutines.runBlocking
import timber.log.Timber
object TestHandler {
object LogList : CallbackList<String>(Runnable::run) {
override fun onAddElement(e: String) {
Timber.i(e)
}
}
fun run(method: String): Bundle {
var reason: String? = null
fun prerequisite(): Boolean {
// Make sure the Magisk app can get root
val shell = Shell.getShell()
if (!shell.isRoot) {
reason = "shell not root"
return false
}
// Make sure the root service is running
RootUtils.Connection.await()
return true
}
fun setup(): Boolean {
return runBlocking {
MagiskInstaller.Emulator(LogList, LogList).exec()
}
}
fun test(): Boolean {
// Make sure Zygisk works correctly
if (!Info.isZygiskEnabled) {
reason = "zygisk not enabled"
return false
}
// Clear existing grant for ADB shell
runBlocking {
ServiceLocator.policyDB.delete(2000)
Config.suAutoResponse = Config.Value.SU_AUTO_ALLOW
Config.prefs.edit().commit()
}
return true
}
val result = prerequisite() && runCatching {
when (method) {
"setup" -> setup()
"test" -> test()
else -> {
reason = "unknown method"
false
}
}
}.getOrElse {
reason = it.stackTraceToString()
false
}
return Bundle().apply {
putBoolean("result", result)
if (reason != null) putString("reason", reason)
}
}
}

View File

@ -4,6 +4,7 @@ import android.app.Activity
import android.app.ActivityOptions import android.app.ActivityOptions
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build import android.os.Build
import android.widget.Toast import android.widget.Toast
import com.topjohnwu.magisk.StubApk import com.topjohnwu.magisk.StubApk
@ -13,7 +14,6 @@ import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.Const import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.R import com.topjohnwu.magisk.core.R
import com.topjohnwu.magisk.core.ktx.await import com.topjohnwu.magisk.core.ktx.await
import com.topjohnwu.magisk.core.ktx.copyAndClose
import com.topjohnwu.magisk.core.ktx.toast import com.topjohnwu.magisk.core.ktx.toast
import com.topjohnwu.magisk.core.ktx.writeTo import com.topjohnwu.magisk.core.ktx.writeTo
import com.topjohnwu.magisk.core.signing.JarMap import com.topjohnwu.magisk.core.signing.JarMap
@ -23,11 +23,9 @@ import com.topjohnwu.magisk.core.utils.Keygen
import com.topjohnwu.magisk.utils.APKInstall import com.topjohnwu.magisk.utils.APKInstall
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Runnable
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import timber.log.Timber import timber.log.Timber
import java.io.File import java.io.File
import java.io.FileOutputStream
import java.io.IOException import java.io.IOException
import java.io.OutputStream import java.io.OutputStream
import java.security.SecureRandom import java.security.SecureRandom
@ -38,6 +36,7 @@ object AppMigration {
private const val ALPHA = "abcdefghijklmnopqrstuvwxyz" private const val ALPHA = "abcdefghijklmnopqrstuvwxyz"
private const val ALPHADOTS = "$ALPHA....." private const val ALPHADOTS = "$ALPHA....."
private const val ANDROID_MANIFEST = "AndroidManifest.xml" private const val ANDROID_MANIFEST = "AndroidManifest.xml"
private const val TEST_PKG_NAME = "$APP_PACKAGE_NAME.test"
// Some arbitrary limit // Some arbitrary limit
const val MAX_LABEL_LENGTH = 32 const val MAX_LABEL_LENGTH = 32
@ -133,21 +132,15 @@ object AppMigration {
val je = jar.getJarEntry(ANDROID_MANIFEST) val je = jar.getJarEntry(ANDROID_MANIFEST)
val xml = AXML(jar.getRawData(je)) val xml = AXML(jar.getRawData(je))
val generator = classNameGenerator() val generator = classNameGenerator()
val p = xml.patchStrings {
if (!xml.patchStrings { when {
for (i in it.indices) { it.contains(APP_PACKAGE_NAME) -> it.replace(APP_PACKAGE_NAME, pkg)
val s = it[i] it.contains(PLACEHOLDER) -> generator.next()
if (s.contains(APP_PACKAGE_NAME)) { it == origLabel -> label.toString()
it[i] = s.replace(APP_PACKAGE_NAME, pkg) else -> it
} else if (s.contains(PLACEHOLDER)) {
it[i] = generator.next()
} else if (s == origLabel) {
it[i] = label.toString()
} }
} }
}) { if (!p) return false
return false
}
// Write apk changes // Write apk changes
jar.getOutputStream(je).use { it.write(xml.bytes) } jar.getOutputStream(je).use { it.write(xml.bytes) }
@ -161,52 +154,87 @@ object AppMigration {
} }
} }
private fun launchApp(activity: Activity, pkg: String) { private fun patchTest(apk: File, out: File, pkg: String): Boolean {
val intent = activity.packageManager.getLaunchIntentForPackage(pkg) ?: return try {
JarMap.open(apk, true).use { jar ->
val je = jar.getJarEntry(ANDROID_MANIFEST)
val xml = AXML(jar.getRawData(je))
val p = xml.patchStrings {
when (it) {
APP_PACKAGE_NAME -> pkg
TEST_PKG_NAME -> "$pkg.test"
else -> it
}
}
if (!p) return false
// Write apk changes
jar.getOutputStream(je).use { it.write(xml.bytes) }
val keys = Keygen()
out.outputStream().use { SignApk.sign(keys.cert, keys.key, jar, it) }
return true
}
} catch (e: Exception) {
Timber.e(e)
return false
}
}
private fun launchApp(context: Context, pkg: String) {
val intent = context.packageManager.getLaunchIntentForPackage(pkg) ?: return
intent.putExtra(Const.Key.PREV_CONFIG, Config.toBundle()) intent.putExtra(Const.Key.PREV_CONFIG, Config.toBundle())
val options = ActivityOptions.makeBasic() val options = ActivityOptions.makeBasic()
if (Build.VERSION.SDK_INT >= 34) { if (Build.VERSION.SDK_INT >= 34) {
options.setShareIdentityEnabled(true) options.setShareIdentityEnabled(true)
} }
activity.startActivity(intent, options.toBundle()) context.startActivity(intent, options.toBundle())
activity.finish() if (context is Activity) {
context.finish()
}
} }
private suspend fun patchAndHide(activity: Activity, label: String, onFailure: Runnable): Boolean { suspend fun patchAndHide(context: Context, label: String, pkg: String? = null): Boolean {
val stub = File(activity.cacheDir, "stub.apk") val stub = File(context.cacheDir, "stub.apk")
try { try {
activity.assets.open("stub.apk").writeTo(stub) context.assets.open("stub.apk").writeTo(stub)
} catch (e: IOException) { } catch (e: IOException) {
Timber.e(e) Timber.e(e)
return false return false
} }
// Generate a new random package name and signature // Generate a new random signature and package name if needed
val repack = File(activity.cacheDir, "patched.apk") val pkg = pkg ?: genPackageName()
val pkg = genPackageName()
Config.keyStoreRaw = "" Config.keyStoreRaw = ""
if (!patch(activity, stub, FileOutputStream(repack), pkg, label)) // Check and patch the test APK
try {
val info = context.packageManager.getApplicationInfo(TEST_PKG_NAME, 0)
val testApk = File(info.sourceDir)
val testRepack = File(context.cacheDir, "test.apk")
if (!patchTest(testApk, testRepack, pkg))
return false return false
val cmd = "adb_pm_install $testRepack $pkg.test"
if (!Shell.cmd(cmd).exec().isSuccess)
return false
} catch (e: PackageManager.NameNotFoundException) {
}
val repack = File(context.cacheDir, "patched.apk")
repack.outputStream().use {
if (!patch(context, stub, it, pkg, label))
return false
}
// Install and auto launch app // Install and auto launch app
val session = APKInstall.startSession(activity, pkg, onFailure) { val cmd = "adb_pm_install $repack $pkg"
if (Shell.cmd(cmd).exec().isSuccess) {
Config.suManager = pkg Config.suManager = pkg
Shell.cmd("touch $AppApkPath").exec() Shell.cmd("touch $AppApkPath").exec()
launchApp(activity, pkg) launchApp(context, pkg)
} return true
} else {
val cmd = "adb_pm_install $repack $pkg"
if (Shell.cmd(cmd).exec().isSuccess) return true
try {
repack.inputStream().copyAndClose(session.openStream(activity))
} catch (e: IOException) {
Timber.e(e)
return false return false
} }
session.waitIntent()?.let { activity.startActivity(it) } ?: return false
return true
} }
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
@ -217,14 +245,25 @@ object AppMigration {
setCancelable(false) setCancelable(false)
show() show()
} }
val onFailure = Runnable { val success = withContext(Dispatchers.IO) {
patchAndHide(activity, label)
}
if (!success) {
dialog.dismiss() dialog.dismiss()
activity.toast(R.string.failure, Toast.LENGTH_LONG) activity.toast(R.string.failure, Toast.LENGTH_LONG)
} }
val success = withContext(Dispatchers.IO) {
patchAndHide(activity, label, onFailure)
} }
if (!success) onFailure.run()
suspend fun restoreApp(context: Context): Boolean {
val apk = StubApk.current(context)
val cmd = "adb_pm_install $apk $APP_PACKAGE_NAME"
if (Shell.cmd(cmd).await().isSuccess) {
Config.suManager = ""
Shell.cmd("touch $AppApkPath").exec()
launchApp(context, APP_PACKAGE_NAME)
return true
}
return false
} }
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
@ -235,31 +274,11 @@ object AppMigration {
setCancelable(false) setCancelable(false)
show() show()
} }
val onFailure = Runnable { if (!restoreApp(activity)) {
dialog.dismiss()
activity.toast(R.string.failure, Toast.LENGTH_LONG) activity.toast(R.string.failure, Toast.LENGTH_LONG)
} }
val apk = StubApk.current(activity)
val session = APKInstall.startSession(activity, APP_PACKAGE_NAME, onFailure) {
Config.suManager = ""
Shell.cmd("touch $AppApkPath").exec()
launchApp(activity, APP_PACKAGE_NAME)
dialog.dismiss() dialog.dismiss()
} }
val cmd = "adb_pm_install $apk $APP_PACKAGE_NAME"
if (Shell.cmd(cmd).await().isSuccess) return
val success = withContext(Dispatchers.IO) {
try {
apk.inputStream().copyAndClose(session.openStream(activity))
} catch (e: IOException) {
Timber.e(e)
return@withContext false
}
session.waitIntent()?.let { activity.startActivity(it) } ?: return@withContext false
return@withContext true
}
if (!success) onFailure.run()
}
suspend fun upgradeStub(context: Context, apk: File): Intent? { suspend fun upgradeStub(context: Context, apk: File): Intent? {
val label = context.applicationInfo.nonLocalizedLabel val label = context.applicationInfo.nonLocalizedLabel

View File

@ -7,7 +7,6 @@ import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.ktx.writeTo import com.topjohnwu.magisk.core.ktx.writeTo
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.displayName import com.topjohnwu.magisk.core.utils.MediaStoreUtils.displayName
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.inputStream import com.topjohnwu.magisk.core.utils.MediaStoreUtils.inputStream
import com.topjohnwu.magisk.core.utils.unzip
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -47,20 +46,14 @@ open class FlashZip(
} }
} }
val isValid = try { try {
zipFile.unzip(installDir, "META-INF/com/google/android", true) val binary = File(installDir, "update-binary")
val script = File(installDir, "updater-script") AppContext.assets.open("module_installer.sh").use { it.writeTo(binary) }
script.readText().contains("#MAGISK")
} catch (e: IOException) { } catch (e: IOException) {
console.add("! Unzip error") console.add("! Unzip error")
throw e throw e
} }
if (!isValid) {
console.add("! This zip is not a Magisk module!")
return false
}
console.add("- Installing ${mUri.displayName}") console.add("- Installing ${mUri.displayName}")
return Shell.cmd("sh $installDir/update-binary dummy 1 \'$zipFile\'") return Shell.cmd("sh $installDir/update-binary dummy 1 \'$zipFile\'")

View File

@ -17,7 +17,6 @@ import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.di.ServiceLocator import com.topjohnwu.magisk.core.di.ServiceLocator
import com.topjohnwu.magisk.core.isRunningAsStub import com.topjohnwu.magisk.core.isRunningAsStub
import com.topjohnwu.magisk.core.ktx.copyAll 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.ktx.writeTo
import com.topjohnwu.magisk.core.utils.DummyList import com.topjohnwu.magisk.core.utils.DummyList
import com.topjohnwu.magisk.core.utils.MediaStoreUtils import com.topjohnwu.magisk.core.utils.MediaStoreUtils
@ -47,7 +46,6 @@ import java.io.OutputStream
import java.io.PushbackInputStream import java.io.PushbackInputStream
import java.nio.ByteBuffer import java.nio.ByteBuffer
import java.security.SecureRandom import java.security.SecureRandom
import java.util.Arrays
import java.util.Locale import java.util.Locale
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
@ -191,7 +189,8 @@ abstract class MagiskInstallImpl protected constructor(
return true 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) { private class NoAvailableStream(s: InputStream) : FilterInputStream(s) {
// Make sure available is never called on the actual stream and always return 0 // Make sure available is never called on the actual stream and always return 0
@ -225,8 +224,8 @@ abstract class MagiskInstallImpl protected constructor(
console.add("- Processing tar file") console.add("- Processing tar file")
var entry: TarArchiveEntry? = tarIn.nextEntry var entry: TarArchiveEntry? = tarIn.nextEntry
fun TarArchiveEntry.decompressedStream(): InputStream { fun decompressedStream(): InputStream {
val stream = if (name.endsWith(".lz4")) val stream = if (tarIn.currentEntry.name.endsWith(".lz4"))
FramedLZ4CompressorInputStream(tarIn, true) else tarIn FramedLZ4CompressorInputStream(tarIn, true) else tarIn
return NoAvailableStream(stream) return NoAvailableStream(stream)
} }
@ -252,9 +251,9 @@ abstract class MagiskInstallImpl protected constructor(
if (bootItem != null) { if (bootItem != null) {
console.add("-- Extracting: ${bootItem.name}") console.add("-- Extracting: ${bootItem.name}")
entry.decompressedStream().copyAndCloseOut(bootItem.file.newOutputStream()) decompressedStream().copyAndCloseOut(bootItem.file.newOutputStream())
} else if (entry.name.contains("vbmeta.img")) { } 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 // Valid vbmeta.img should be at least 256 bytes
if (rawData.size < 256) if (rawData.size < 256)
continue continue
@ -287,7 +286,7 @@ abstract class MagiskInstallImpl protected constructor(
} else { } else {
console.add("-- Copying : ${entry.name}") console.add("-- Copying : ${entry.name}")
tarOut.putArchiveEntry(entry) tarOut.putArchiveEntry(entry)
tarIn.copyAll(tarOut, bufferSize = 1024 * 1024) tarIn.copyAll(tarOut)
tarOut.closeArchiveEntry() tarOut.closeArchiveEntry()
} }
entry = tarIn.nextEntry ?: break entry = tarIn.nextEntry ?: break
@ -429,7 +428,7 @@ abstract class MagiskInstallImpl protected constructor(
// Process input file // Process input file
try { try {
PushbackInputStream(uri.inputStream(), 512).use { src -> PushbackInputStream(uri.inputStream().buffered(1024 * 1024), 512).use { src ->
val head = ByteArray(512) val head = ByteArray(512)
if (src.read(head) != head.size) { if (src.read(head) != head.size) {
console.add("! Invalid input file") console.add("! Invalid input file")
@ -438,12 +437,13 @@ abstract class MagiskInstallImpl protected constructor(
src.unread(head) src.unread(head)
val magic = head.copyOf(4) val magic = head.copyOf(4)
val tarMagic = Arrays.copyOfRange(head, 257, 262) val tarMagic = head.copyOfRange(257, 262)
srcBoot = if (tarMagic.contentEquals("ustar".toByteArray())) { srcBoot = if (tarMagic.contentEquals("ustar".toByteArray())) {
// tar file // tar file
outFile = MediaStoreUtils.getFile("$destName.tar") 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.setBigNumberMode(TarArchiveOutputStream.BIGNUMBER_STAR)
it.setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU) it.setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU)
} }
@ -500,7 +500,7 @@ abstract class MagiskInstallImpl protected constructor(
bootItem.file = newBoot bootItem.file = newBoot
bootItem.copyTo(outStream as TarArchiveOutputStream) bootItem.copyTo(outStream as TarArchiveOutputStream)
} else { } else {
newBoot.newInputStream().copyAndClose(outStream) newBoot.newInputStream().use { it.copyAll(outStream, 1024 * 1024) }
} }
newBoot.delete() newBoot.delete()
@ -514,6 +514,8 @@ abstract class MagiskInstallImpl protected constructor(
outFile.delete() outFile.delete()
Timber.e(e) Timber.e(e)
return false return false
} finally {
outStream.close()
} }
// Fix up binaries // Fix up binaries
@ -597,6 +599,8 @@ abstract class MagiskInstallImpl protected constructor(
if (result) if (result)
return true return true
// Not every operation initializes installDir
if (::installDir.isInitialized)
Shell.cmd("rm -rf $installDir").submit() Shell.cmd("rm -rf $installDir").submit()
return false return false
} }

View File

@ -1,42 +0,0 @@
package com.topjohnwu.magisk.core.tasks
import android.net.Uri
import androidx.core.net.toFile
import com.topjohnwu.magisk.core.AppContext
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.ktx.writeTo
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.displayName
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.inputStream
import com.topjohnwu.magisk.core.utils.unzip
import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import timber.log.Timber
import java.io.File
import java.io.FileNotFoundException
import java.io.IOException
open class RunAction(
private val module: String,
private val console: MutableList<String>,
private val logs: MutableList<String>
) {
@Throws(IOException::class)
private suspend fun run(): Boolean {
return Shell.cmd("run_action \'$module\'").to(console, logs).exec().isSuccess
}
open suspend fun exec() = withContext(Dispatchers.IO) {
try {
if (!run()) {
console.add("! Run action failed")
false
} else {
true
}
} catch (e: IOException) {
Timber.e(e)
false
}
}
}

View File

@ -29,7 +29,7 @@ class AXML(b: ByteArray) {
* Followed by an array of uint32_t with size = number of strings * Followed by an array of uint32_t with size = number of strings
* Each entry points to an offset into the string data * Each entry points to an offset into the string data
*/ */
fun patchStrings(patchFn: (Array<String>) -> Unit): Boolean { fun patchStrings(mapFn: (String) -> String): Boolean {
val buffer = ByteBuffer.wrap(bytes).order(LITTLE_ENDIAN) val buffer = ByteBuffer.wrap(bytes).order(LITTLE_ENDIAN)
fun findStringPool(): Int { fun findStringPool(): Int {
@ -65,7 +65,9 @@ class AXML(b: ByteArray) {
} }
val strArr = strList.toTypedArray() val strArr = strList.toTypedArray()
patchFn(strArr) for (i in strArr.indices) {
strArr[i] = mapFn(strArr[i])
}
// Write everything before string data, will patch values later // Write everything before string data, will patch values later
val baos = RawByteStream() val baos = RawByteStream()

View File

@ -0,0 +1,47 @@
package com.topjohnwu.magisk.core.utils;
import android.os.Build;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
import org.apache.commons.compress.archivers.zip.ZipUtil;
import java.nio.file.attribute.FileTime;
import java.util.zip.ZipEntry;
public class Desugar {
public static FileTime getLastModifiedTime(ZipEntry entry) {
if (Build.VERSION.SDK_INT >= 26) {
return entry.getLastModifiedTime();
} else {
return FileTime.fromMillis(entry.getTime());
}
}
public static FileTime getLastAccessTime(ZipEntry entry) {
if (Build.VERSION.SDK_INT >= 26) {
return entry.getLastAccessTime();
} else {
return null;
}
}
public static FileTime getCreationTime(ZipEntry entry) {
if (Build.VERSION.SDK_INT >= 26) {
return entry.getCreationTime();
} else {
return null;
}
}
/**
* Within {@link ZipArchiveOutputStream#copyFromZipInputStream}, we redirect the method call
* {@link ZipUtil#checkRequestedFeatures} to this method. This is safe because the only usage
* of copyFromZipInputStream is in {@link ZipArchiveOutputStream#addRawArchiveEntry},
* which does not need to actually understand the content of the zip entry. By removing
* this feature check, we can modify zip files using unsupported compression methods.
*/
public static void checkRequestedFeatures(final ZipArchiveEntry ze) {
// No-op
}
}

View File

@ -11,13 +11,10 @@ import android.net.NetworkRequest
import android.os.PowerManager import android.os.PowerManager
import androidx.collection.ArraySet import androidx.collection.ArraySet
import androidx.core.content.getSystemService import androidx.core.content.getSystemService
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ProcessLifecycleOwner
import com.topjohnwu.magisk.core.Info import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.ktx.registerRuntimeReceiver import com.topjohnwu.magisk.core.ktx.registerRuntimeReceiver
class NetworkObserver(context: Context): DefaultLifecycleObserver { class NetworkObserver(context: Context) {
private val manager = context.getSystemService<ConnectivityManager>()!! private val manager = context.getSystemService<ConnectivityManager>()!!
private val networkCallback = object : ConnectivityManager.NetworkCallback() { private val networkCallback = object : ConnectivityManager.NetworkCallback() {
@ -55,16 +52,13 @@ class NetworkObserver(context: Context): DefaultLifecycleObserver {
manager.registerNetworkCallback(request, networkCallback) manager.registerNetworkCallback(request, networkCallback)
val filter = IntentFilter(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED) val filter = IntentFilter(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED)
context.applicationContext.registerRuntimeReceiver(receiver, filter) context.applicationContext.registerRuntimeReceiver(receiver, filter)
ProcessLifecycleOwner.get().lifecycle.addObserver(this)
} }
override fun onStart(owner: LifecycleOwner) { fun postCurrentState() {
postCurrentState() postValue(
} manager.getNetworkCapabilities(manager.activeNetwork)
?.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) == true
private fun postCurrentState() { )
postValue(manager.getNetworkCapabilities(manager.activeNetwork)
?.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) ?: false)
} }
private fun postValue(b: Boolean) { private fun postValue(b: Boolean) {

View File

@ -1,15 +0,0 @@
package com.topjohnwu.magisk.core.utils;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.lifecycle.LifecycleDispatcher;
import androidx.lifecycle.ProcessLifecycleOwner;
// Use Java to bypass Kotlin internal visibility modifier
public class ProcessLifecycle {
public static void init(@NonNull Context context) {
LifecycleDispatcher.init(context);
ProcessLifecycleOwner.init$lifecycle_process_release(context);
}
}

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

@ -0,0 +1,114 @@
package com.topjohnwu.magisk.test
import android.os.ParcelFileDescriptor.AutoCloseInputStream
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 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
import java.util.regex.Pattern
@Keep
@RunWith(AndroidJUnit4::class)
class AdditionalTest : BaseTest {
companion object {
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
fun teardown() {
device.pressHome()
}
@Test
fun testModuleCount() {
var expected = 0
if (Environment.testModules()) expected +=2
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() } }
val pattern = Pattern.compile("$LSPOSED_PKG:id/.*")
assertNotNull(
"LSPosed manager launch failed",
device.wait(Until.hasObject(By.res(pattern)), TimeUnit.SECONDS.toMillis(10))
)
}
@Test
fun testModule01() {
assumeTrue(Environment.testModules())
val module = modules.find { it.id == "test_01" }
assertNotNull("test_01 is not installed", module)
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()
)
module!!
assertTrue("test_01 should be zygisk unloaded", module.zygiskUnloaded)
}
@Test
fun testModule02() {
assumeTrue(Environment.testModules())
val module = modules.find { it.id == "test_02" }
assertNotNull("test_02 is not installed", module)
module!!
assertTrue("test_02 should be zygisk unloaded", module.zygiskUnloaded)
}
@Test
fun testModule03() {
assumeTrue(Environment.testModules())
assertNull("test_03 should be removed", modules.find { it.id == "test_03" })
}
}

View File

@ -0,0 +1,27 @@
package com.topjohnwu.magisk.test
import android.app.Instrumentation
import android.app.UiAutomation
import android.content.Context
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.topjohnwu.magisk.core.utils.RootUtils
import com.topjohnwu.superuser.Shell
import org.junit.Assert.assertTrue
interface BaseTest {
val instrumentation: Instrumentation
get() = InstrumentationRegistry.getInstrumentation()
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)
companion object {
fun prerequisite() {
assertTrue("Should have root access", Shell.getShell().isRoot)
// Make sure the root service is running
RootUtils.Connection.await()
}
}
}

View File

@ -0,0 +1,209 @@
package com.topjohnwu.magisk.test
import android.app.Notification
import android.os.Build
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.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.BeforeClass
import org.junit.Test
import org.junit.runner.RunWith
import timber.log.Timber
import java.io.File
@Keep
@RunWith(AndroidJUnit4::class)
class Environment : BaseTest {
companion object {
@BeforeClass
@JvmStatic
fun before() = BaseTest.prerequisite()
// The kernel running on emulators < API 26 does not play well with
// magic mount. Skip module tests on those legacy platforms.
fun testModules(): Boolean {
return Build.VERSION.SDK_INT >= 26
}
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"
}
object TimberLog : CallbackList<String>(Runnable::run) {
override fun onAddElement(e: String) {
Timber.i(e)
}
}
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 setupModule01(root: ExtendedFile) {
val error = "test_01 setup failed"
val path = root.getChildFile("test_01")
// Create /system/etc/newfile
val etc = path.getChildFile("system").getChildFile("etc")
assertTrue(error, etc.mkdirs())
assertTrue(error, etc.getChildFile("newfile").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)
// Create an empty zygisk folder
val module = LocalModule(path)
assertTrue(error, module.zygiskFolder.mkdir())
assertTrue(error, Shell.cmd("set_default_perm $path").exec().isSuccess)
}
private fun setupModule02(root: ExtendedFile) {
val error = "test_02 setup failed"
val path = root.getChildFile("test_02")
// 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 setupModule03(root: ExtendedFile) {
val error = "test_03 setup failed"
val path = root.getChildFile("test_03")
// 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 setupEnvironment() {
runBlocking {
assertTrue(
"Magisk setup failed",
MagiskInstaller.Emulator(TimberLog, TimberLog).exec()
)
}
val notify = object : DownloadNotifier {
override val context = appContext
override fun notifyUpdate(id: Int, editor: (Notification.Builder) -> Unit) {}
}
val processor = DownloadProcessor(notify)
val shamiko = appContext.cachedFile("shamiko.zip")
runBlocking {
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()
)
}
}
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()
)
}
}
if (testModules()) {
val root = RootUtils.fs.getFile(Const.MODULE_PATH)
setupModule01(root)
setupModule02(root)
setupModule03(root)
}
}
@Test
fun setupAppHide() {
runBlocking {
assertTrue(
"App hiding failed",
AppMigration.patchAndHide(
context = appContext,
label = "Settings",
pkg = "repackaged.$APP_PACKAGE_NAME"
)
)
}
}
@Test
fun setupAppRestore() {
runBlocking {
assertTrue(
"App restoration failed",
AppMigration.restoreApp(appContext)
)
}
}
}

View File

@ -0,0 +1,86 @@
package com.topjohnwu.magisk.test
import android.content.Intent
import android.content.IntentFilter
import android.os.Build
import android.os.ParcelFileDescriptor.AutoCloseInputStream
import androidx.annotation.Keep
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.di.ServiceLocator
import com.topjohnwu.magisk.core.model.su.SuPolicy
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertTrue
import org.junit.BeforeClass
import org.junit.Test
import org.junit.runner.RunWith
import java.util.concurrent.TimeUnit
@Keep
@RunWith(AndroidJUnit4::class)
class MagiskAppTest : BaseTest {
companion object {
@BeforeClass
@JvmStatic
fun before() = BaseTest.prerequisite()
}
@Test
fun testZygisk() {
assertTrue("Zygisk should be enabled", Info.isZygiskEnabled)
}
@Test
fun testSuRequest() {
// Bypass the need to actually show a dialog
Config.suAutoResponse = Config.Value.SU_AUTO_ALLOW
Config.prefs.edit().commit()
// Inject an undetermined + mute logging policy for ADB shell
val policy = SuPolicy(
uid = 2000,
logging = false,
notification = false,
remain = 0L
)
runBlocking {
ServiceLocator.policyDB.update(policy)
}
val filter = IntentFilter(Intent.ACTION_VIEW)
filter.addCategory(Intent.CATEGORY_DEFAULT)
val monitor = instrumentation.addMonitor(filter, null, false)
// Try to call su from ADB shell
val cmd = if (Build.VERSION.SDK_INT < 24) {
// API 23 runs executeShellCommand as root
"/system/xbin/su 2000 su -c id"
} else {
"su -c id"
}
val pfd = uiAutomation.executeShellCommand(cmd)
// Make sure SuRequestActivity is launched
val suRequest = monitor.waitForActivityWithTimeout(TimeUnit.SECONDS.toMillis(10))
assertNotNull("SuRequestActivity is not launched", suRequest)
// Check that the request went through
AutoCloseInputStream(pfd).reader().use {
assertTrue(
"Cannot grant root permission from shell",
it.readText().contains("uid=0")
)
}
// Check that the database is updated
runBlocking {
val policy = ServiceLocator.policyDB.fetch(2000)
?: throw AssertionError("PolicyDB is invalid")
assertEquals("Policy for shell is incorrect", SuPolicy.ALLOW, policy.policy)
}
}
}

View File

@ -4,7 +4,7 @@
<string name="modules">Módulos</string> <string name="modules">Módulos</string>
<string name="superuser">Superusuariu</string> <string name="superuser">Superusuariu</string>
<string name="logs">Rexistru</string> <string name="logs">Rexistru</string>
<string name="settings">Axustes</string> <string name="settings">Configuración</string>
<string name="install">Instalar</string> <string name="install">Instalar</string>
<string name="section_home">Aniciu</string> <string name="section_home">Aniciu</string>
<string name="section_theme">Estilos</string> <string name="section_theme">Estilos</string>
@ -15,7 +15,7 @@
<string name="loading">Cargando…</string> <string name="loading">Cargando…</string>
<string name="update">Anovar</string> <string name="update">Anovar</string>
<string name="not_available">N/D</string> <string name="not_available">N/D</string>
<string name="hide">Anubrir</string> <string name="hide">Esconder</string>
<string name="home_package">Paquete</string> <string name="home_package">Paquete</string>
<string name="home_app_title">Aplicación</string> <string name="home_app_title">Aplicación</string>
<string name="home_notice_content">Baxa Magisk NAMÁS dende la páxina oficial de GitHub. ¡Los ficheros de fontes desconocíes puen ser maliciosos!</string> <string name="home_notice_content">Baxa Magisk NAMÁS dende la páxina oficial de GitHub. ¡Los ficheros de fontes desconocíes puen ser maliciosos!</string>
@ -39,10 +39,10 @@
<string name="manager_download_install">Primi equí pa baxalu ya instalalu</string> <string name="manager_download_install">Primi equí pa baxalu ya instalalu</string>
<string name="direct_install">Instalación direuta (aconséyase)</string> <string name="direct_install">Instalación direuta (aconséyase)</string>
<string name="install_inactive_slot">Instalar na ralura inactiva (darréu del OTA)</string> <string name="install_inactive_slot">Instalar na ralura inactiva (darréu del OTA)</string>
<string name="install_inactive_slot_msg">¡El preséu va arrincar OBLIGATORIAMENTE na ralura inactiva darréu de reaniciar!\nUsa esta opción namás dempués d\'acabar l\'anovamientu per OTA.\n¿Quies siguir?</string> <string name="install_inactive_slot_msg">¡El preséu va arrincar OBLIGATORIAMENTE na ralura inactiva dempués de reaniciar!\nUsa esta opción namás dempués d\'acabar l\'anovamientu per OTA.\n¿Quies siguir?</string>
<string name="setup_title">Configuración adicional</string> <string name="setup_title">Configuración adicional</string>
<string name="select_patch_file">Seleicionar y parchiar un ficheru</string> <string name="select_patch_file">Seleicionar y parchiar un ficheru</string>
<string name="patch_file_msg">Seleiciona una imaxe en bruto (*.img) o un archivu d\'ODIN (*.tar)</string> <string name="patch_file_msg">Seleiciona una imaxe en bruto (*.img), un archivu d\'ODIN (*.tar) o un ficheru payload.bin (*.bin)</string>
<string name="reboot_delay_toast">Reaniciando en 5 segundos…</string> <string name="reboot_delay_toast">Reaniciando en 5 segundos…</string>
<string name="flash_screen_title">Instalación</string> <string name="flash_screen_title">Instalación</string>
<!--Superuser--> <!--Superuser-->
@ -81,6 +81,9 @@
<string name="logs_cleared">El rexistru borróse correutamente</string> <string name="logs_cleared">El rexistru borróse correutamente</string>
<string name="pid">PID: %1$d</string> <string name="pid">PID: %1$d</string>
<string name="target_uid">UID de destín: %1$d</string> <string name="target_uid">UID de destín: %1$d</string>
<string name="target_pid">Mount ns target PID: %s</string>
<string name="selinux_context">Contestu de SELinux: %s</string>
<string name="supp_group">Grupu suplementariu: %s</string>
<!--SafetyNet--> <!--SafetyNet-->
<!--MagiskHide--> <!--MagiskHide-->
<string name="show_system_app">Aplicaciones del sistema</string> <string name="show_system_app">Aplicaciones del sistema</string>
@ -94,15 +97,19 @@
<string name="reboot_bootloader">Reaniciar al cargador d\'arrinque</string> <string name="reboot_bootloader">Reaniciar al cargador d\'arrinque</string>
<string name="reboot_download">Reaniciar al mou de descarga</string> <string name="reboot_download">Reaniciar al mou de descarga</string>
<string name="reboot_edl">Reaniciar al mou EDL</string> <string name="reboot_edl">Reaniciar al mou EDL</string>
<string name="reboot_safe_mode">Mou seguru</string>
<string name="module_version_author">%1$s por %2$s</string> <string name="module_version_author">%1$s por %2$s</string>
<string name="module_state_remove">Quitar</string> <string name="module_state_remove">Quitar</string>
<string name="module_action">Aición</string>
<string name="module_state_restore">Restaurar</string> <string name="module_state_restore">Restaurar</string>
<string name="module_action_install_external">Instalar dende l\'almacenamientu</string> <string name="module_action_install_external">Instalar dende l\'almacenamientu</string>
<string name="update_available">Hai un anovamientu disponible</string> <string name="update_available">Hai un anovamientu disponible</string>
<string name="suspend_text_riru">Suspendióse\'l módulu porque s\'activó «%1$s»</string> <string name="suspend_text_riru">Suspendióse\'l módulu porque s\'activó «%1$s»</string>
<string name="suspend_text_zygisk">Suspendióse\'l módulu porque nun s\'activó «%1$s»</string> <string name="suspend_text_zygisk">Suspendióse\'l módulu porque nun s\'activó «%1$s»</string>
<string name="zygisk_module_unloaded">El módulu de Zygisk nun cargó pola mor d\'haber incompatibilidaes</string> <string name="zygisk_module_unloaded">El módulu de Zygisk nun cargó por haber incompatibilidaes</string>
<string name="module_empty">Nun hai nengún módulu instaláu</string> <string name="module_empty">Nun hai nengún módulu instaláu</string>
<string name="confirm_install">¿Quies instalar el módulu «%1$s»?</string>
<string name="confirm_install_title">Confirmación de la instalación</string>
<!--Settings--> <!--Settings-->
<string name="settings_dark_mode_title">Mou del estilu</string> <string name="settings_dark_mode_title">Mou del estilu</string>
<string name="settings_dark_mode_message">¡Seleiciona\'l mou que meyor s\'adaute al to estilu!</string> <string name="settings_dark_mode_message">¡Seleiciona\'l mou que meyor s\'adaute al to estilu!</string>
@ -111,7 +118,7 @@
<string name="settings_dark_mode_dark">Escuridá</string> <string name="settings_dark_mode_dark">Escuridá</string>
<string name="settings_download_path_title">Camín de les descargues</string> <string name="settings_download_path_title">Camín de les descargues</string>
<string name="settings_download_path_message">Los ficheros van guardase en «%1$s»</string> <string name="settings_download_path_message">Los ficheros van guardase en «%1$s»</string>
<string name="settings_hide_app_title">Anubrir Magisk</string> <string name="settings_hide_app_title">Esconder Magisk</string>
<string name="settings_hide_app_summary">Instala una aplicación intermedia con una ID y una etiqueta al debalu</string> <string name="settings_hide_app_summary">Instala una aplicación intermedia con una ID y una etiqueta al debalu</string>
<string name="settings_restore_app_title">Restaurar el mou visible</string> <string name="settings_restore_app_title">Restaurar el mou visible</string>
<string name="settings_restore_app_summary">Fai que l\'aplicación orixinal vuelva ser visible</string> <string name="settings_restore_app_summary">Fai que l\'aplicación orixinal vuelva ser visible</string>
@ -149,14 +156,19 @@
<string name="auto_response">Rempuesta automática</string> <string name="auto_response">Rempuesta automática</string>
<string name="request_timeout">Tiempu d\'espera de les solicitúes</string> <string name="request_timeout">Tiempu d\'espera de les solicitúes</string>
<string name="superuser_notification">Avisu de superusuariu</string> <string name="superuser_notification">Avisu de superusuariu</string>
<string name="settings_su_reauth_title">Volver autenticar darréu d\'anovar</string> <string name="settings_su_reauth_title">Volver autenticar dempués d\'anovar</string>
<string name="settings_su_reauth_summary">Vuelve pidir los permisos de superusuariu dempués d\'anovar les aplicaciones</string> <string name="settings_su_reauth_summary">Vuelve pidir los permisos de superusuariu dempués d\'anovar les aplicaciones</string>
<string name="settings_su_tapjack_title">Proteición escontra\'l tapjacking</string> <string name="settings_su_tapjack_title">Proteición escontra\'l tapjacking</string>
<string name="settings_su_tapjack_summary">El diálogu de concesión de permisos de superusuariu nun respuende a la entrada mentanto lu torgue otra ventana o superposición</string> <string name="settings_su_tapjack_summary">El diálogu de concesión de permisos de superusuariu nun respuende a la entrada mentanto lu torgue otra ventana o superposición</string>
<string name="settings_su_auth_title">Autenticación d\'usuariu</string>
<string name="settings_su_auth_summary">Pide l\'autenticación demientres les solicitúes de superusuariu</string>
<string name="settings_su_auth_insecure">Nun se configuró nengún métodu d\'autenticación nel preséu</string>
<string name="settings_customization">Personalización</string> <string name="settings_customization">Personalización</string>
<string name="setting_add_shortcut_summary">Amiesta un atayu a la pantalla d\'aniciu en casu de que\'l nome y l\'iconu seyan difíciles de reconocer darréu d\'anubrir l\'aplicación</string> <string name="setting_add_shortcut_summary">Amiesta un atayu a la pantalla d\'aniciu en casu de que\'l nome y l\'iconu seyan difíciles de reconocer dempués d\'esconder l\'aplicación</string>
<string name="settings_doh_title">DNS per HTTPS</string> <string name="settings_doh_title">DNS per HTTPS</string>
<string name="settings_doh_description">Una igua alternativa pal envelenamientu de DNS en dalgunos países</string> <string name="settings_doh_description">Una igua alternativa pal envelenamientu de DNS en dalgunos países</string>
<string name="settings_random_name_title">Nome de la salida aleatoriu</string>
<string name="settings_random_name_description">Fai que\'l nome de ficheru de la salida de les imáxenes parchiaes y los ficheros tar seya aleatoriu pa impidir la deteición</string>
<string name="multiuser_mode">Mou de multiusuariu</string> <string name="multiuser_mode">Mou de multiusuariu</string>
<string name="settings_owner_only">Namás el propietariu del preséu</string> <string name="settings_owner_only">Namás el propietariu del preséu</string>
<string name="settings_owner_manage">El propietariu xestionáu del preséu</string> <string name="settings_owner_manage">El propietariu xestionáu del preséu</string>
@ -186,11 +198,14 @@
<string name="repo_install_title">Instalación de: %1$s %2$s (%3$d)</string> <string name="repo_install_title">Instalación de: %1$s %2$s (%3$d)</string>
<string name="download">Baxar</string> <string name="download">Baxar</string>
<string name="reboot">Reaniciar</string> <string name="reboot">Reaniciar</string>
<string name="close">Zarrar</string>
<string name="release_notes">Notes de la versión</string> <string name="release_notes">Notes de la versión</string>
<string name="flashing">Flaxando…</string> <string name="flashing">Flaxando…</string>
<string name="running">Executando…</string>
<string name="done">¡Fecho!</string> <string name="done">¡Fecho!</string>
<string name="done_action">Completóse l\'aición de: %1$s</string>
<string name="failure">¡Falló!</string> <string name="failure">¡Falló!</string>
<string name="hide_app_title">Anubriendo l\'aplicación Magisk…</string> <string name="hide_app_title">Escondiendo l\'aplicación Magisk…</string>
<string name="open_link_failed_toast">Nun s\'atopó nenguna aplicación p\'abrir l\'enllaz</string> <string name="open_link_failed_toast">Nun s\'atopó nenguna aplicación p\'abrir l\'enllaz</string>
<string name="complete_uninstall">Desinstalar dafechu</string> <string name="complete_uninstall">Desinstalar dafechu</string>
<string name="restore_img">Restaurar les imáxenes</string> <string name="restore_img">Restaurar les imáxenes</string>
@ -200,6 +215,7 @@
<string name="setup_fail">La configuración falló</string> <string name="setup_fail">La configuración falló</string>
<string name="env_fix_title">Configuración adicional</string> <string name="env_fix_title">Configuración adicional</string>
<string name="env_fix_msg">El preséu precisa una configuración adicional pa que Magisk funcione afayadizamente. ¿Quies siguir y reaniciar?</string> <string name="env_fix_msg">El preséu precisa una configuración adicional pa que Magisk funcione afayadizamente. ¿Quies siguir y reaniciar?</string>
<string name="env_full_fix_msg">El preséu precisa volver flaxar Magisk pa que funcione afayadizamente. Volvi instalar Magisk dientro de l\'aplicación porque\'l mou de recuperación nun pue consiguir la información correuta del preséu.</string>
<string name="setup_msg">Executando la configuración del entornu…</string> <string name="setup_msg">Executando la configuración del entornu…</string>
<string name="unsupport_magisk_title">Versión non compatible</string> <string name="unsupport_magisk_title">Versión non compatible</string>
<string name="unsupport_magisk_msg">Esta versión de l\'aplicación nun ye compatible coles versiones de Magisk anteriores a la %1$s.\n\nL\'aplicación va comportase como si Magisk nun tuviere instaláu, anueva Magisk namás que puedas.</string> <string name="unsupport_magisk_msg">Esta versión de l\'aplicación nun ye compatible coles versiones de Magisk anteriores a la %1$s.\n\nL\'aplicación va comportase como si Magisk nun tuviere instaláu, anueva Magisk namás que puedas.</string>
@ -207,12 +223,13 @@
<string name="unsupport_system_app_msg">Esta aplicación nun se pue executar nel espaciu del sistema. Volvi instalala mas nel espaciu del usuariu.</string> <string name="unsupport_system_app_msg">Esta aplicación nun se pue executar nel espaciu del sistema. Volvi instalala mas nel espaciu del usuariu.</string>
<string name="unsupport_other_su_msg">Detectóse un binariu «su» que nun ye de Magisk. Quita cualesquier solución de root y/o volvi instalar Magisk.</string> <string name="unsupport_other_su_msg">Detectóse un binariu «su» que nun ye de Magisk. Quita cualesquier solución de root y/o volvi instalar Magisk.</string>
<string name="unsupport_external_storage_msg">Magisk ta instaláu nel almacenamientu esternu. Movi l\'aplicación al almacenamientu internu, por favor.</string> <string name="unsupport_external_storage_msg">Magisk ta instaláu nel almacenamientu esternu. Movi l\'aplicación al almacenamientu internu, por favor.</string>
<string name="unsupport_nonroot_stub_msg">Magisk nun pue siguir funcionando nel mou anubríu darréu que se perdió\'l root. Restaura\'l mou visible de l\'aplicación.</string> <string name="unsupport_nonroot_stub_msg">Magisk nun pue siguir funcionando nel mou escondíu darréu que se perdió\'l root. Restaura\'l mou visible de l\'aplicación.</string>
<string name="unsupport_nonroot_stub_title">@string/settings_restore_app_title</string> <string name="unsupport_nonroot_stub_title">@string/settings_restore_app_title</string>
<string name="external_rw_permission_denied">Concede\'l permisu d\'almacenamientu p\'activar esta funcionalidá</string> <string name="external_rw_permission_denied">Concede\'l permisu d\'almacenamientu p\'activar esta funcionalidá</string>
<string name="post_notifications_denied">Concede\'l permisu de los avisos p\'activar esta función</string>
<string name="install_unknown_denied">Permite la instalación d\'aplicaciones desconocíes p\'activar esta funcionalidá</string> <string name="install_unknown_denied">Permite la instalación d\'aplicaciones desconocíes p\'activar esta funcionalidá</string>
<string name="add_shortcut_title">Amestar un atayu a la pantalla d\'aniciu</string> <string name="add_shortcut_title">Amestar un atayu a la pantalla d\'aniciu</string>
<string name="add_shortcut_msg">Darréu d\'anubrir esta aplicación, el so nome ya iconu van ser difíciles de reconocer. ¿Quies amestar un atayu a la pantalla d\'aniciu?</string> <string name="add_shortcut_msg">Dempués d\'esconder esta aplicación, el so nome ya iconu van ser difíciles de reconocer. ¿Quies amestar un atayu a la pantalla d\'aniciu?</string>
<string name="app_not_found">Nun s\'atopó nenguna aplicación pa remanar esta aición</string> <string name="app_not_found">Nun s\'atopó nenguna aplicación pa remanar esta aición</string>
<string name="reboot_apply_change">Reanicia p\'aplicar los cambeos</string> <string name="reboot_apply_change">Reanicia p\'aplicar los cambeos</string>
<string name="restore_app_confirmation">Esta aición va restaurar l\'aplicación orixinal y desanicia la intermedia. ¿De xuru que quies facelo?</string> <string name="restore_app_confirmation">Esta aición va restaurar l\'aplicación orixinal y desanicia la intermedia. ¿De xuru que quies facelo?</string>

View File

@ -10,10 +10,9 @@
<string name="section_theme">עיצוב</string> <string name="section_theme">עיצוב</string>
<string name="denylist">רשימת דחייה</string> <string name="denylist">רשימת דחייה</string>
<!--Home--> <!--Home-->
<string name="no_connection">אין חיבור זמין</string> <string name="no_connection">אין חיבור זמין</string>
<string name="app_changelog">רשימת שינויים</string> <string name="app_changelog">יומן שינויים</string>
<string name="loading">טוען…</string> <string name="loading">טוען…</string>
<string name="update">עדכון</string> <string name="update">עדכון</string>
<string name="not_available">ל/ז</string> <string name="not_available">ל/ז</string>
@ -45,16 +44,16 @@
<string name="install_inactive_slot_msg">ההתקן שלך ייאלץ אתחול לחריץ הלא פעיל הנוכחי שלך לאחר הפעלה מחדש!\nיש להשתמש באפשרות זו רק לאחר ביצוע OTA בלבד.\nלהמשיך?</string> <string name="install_inactive_slot_msg">ההתקן שלך ייאלץ אתחול לחריץ הלא פעיל הנוכחי שלך לאחר הפעלה מחדש!\nיש להשתמש באפשרות זו רק לאחר ביצוע OTA בלבד.\nלהמשיך?</string>
<string name="setup_title">התקנה נוספת</string> <string name="setup_title">התקנה נוספת</string>
<string name="select_patch_file">בחירה והתקנת קובץ</string> <string name="select_patch_file">בחירה והתקנת קובץ</string>
<string name="patch_file_msg">בחירת תמונה גולמית (*.img) או ODIN קובץ tar (*.tar)</string> <string name="patch_file_msg">בחירת תמונה גולמית (*.img) או ODIN tarfile (*.tar) או payload.bin (*.bin)</string>
<string name="reboot_delay_toast">מאתחל בעוד 5 שניות…</string> <string name="reboot_delay_toast">מאתחל בעוד 5 שניות…</string>
<string name="flash_screen_title">התקנה</string> <string name="flash_screen_title">התקנה</string>
<!--Superuser--> <!--Superuser-->
<string name="su_request_title">בקשות משתמש על</string> <string name="su_request_title">בקשות משתמש על</string>
<string name="touch_filtered_warning">מכיוון שיישום מסתיר בקשה של משתמש על, Magisk לא יכול לאמת את תגובתך</string> <string name="touch_filtered_warning">מכיוון שיישום מסתיר בקשה של משתמש על, Magisk לא יכול לאמת את תגובתך</string>
<string name="deny">דחה</string> <string name="deny">דחייה</string>
<string name="prompt">מיידי</string> <string name="prompt">מיידי</string>
<string name="grant">הענק</string> <string name="grant">הענקה</string>
<string name="su_warning">מעניק גישה מלאה להתקן שלך.\nיש לדחות באי וודאות!</string> <string name="su_warning">מעניק גישה מלאה להתקן שלך.\nיש לדחות באי וודאות!</string>
<string name="forever">לצמיתות</string> <string name="forever">לצמיתות</string>
<string name="once">פעם אחת</string> <string name="once">פעם אחת</string>
@ -62,24 +61,24 @@
<string name="twentymin">20 דקות</string> <string name="twentymin">20 דקות</string>
<string name="thirtymin">חצי שעה</string> <string name="thirtymin">חצי שעה</string>
<string name="sixtymin">שעה</string> <string name="sixtymin">שעה</string>
<string name="su_allow_toast">%1$s הוענקו הרשאות משתמש עבור</string> <string name="su_allow_toast">%1$s קיבל הרשאות משתמש על</string>
<string name="su_deny_toast">%1$s נשללו הרשאות משתמש עבור</string> <string name="su_deny_toast">%1$s נשללו הרשאות משתמש על</string>
<string name="su_snack_grant">הרשאות משתמש על עבור %1$s הוענקו</string> <string name="su_snack_grant">הרשאות משתמש על עבור %1$s הוענקו</string>
<string name="su_snack_deny">הרשאות משתמש על עבור %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_on">התראות של %1$s מופעלות</string>
<string name="su_snack_notif_off">התראות עבור %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_on">יומני רישום עבור %1$s פועלות</string>
<string name="su_snack_log_off">יומני רישום עבור %1$s כבויות</string> <string name="su_snack_log_off">יומני רישום עבור %1$s מושבתות</string>
<string name="su_revoke_title">להסיר?</string> <string name="su_revoke_title">להסיר?</string>
<string name="su_revoke_msg">נא לאשר שלילת הרשאות עבור %1$s?</string> <string name="su_revoke_msg">נא לאשר שלילת הרשאות עבור %1$s?</string>
<string name="toast">הרמת כוסית</string> <string name="toast">הרמת כוסית</string>
<string name="none">ללא</string> <string name="none">ללא</string>
<string name="superuser_toggle_notification">התראות</string> <string name="superuser_toggle_notification">התראות</string>
<string name="superuser_toggle_revoke">הסרה</string> <string name="superuser_toggle_revoke">הסרה</string>
<string name="superuser_policy_none">לא נתבקשו הרשאות משתמש על על ידי שום יישום</string> <string name="superuser_policy_none">טרם נתבקשו הרשאות משתמש על על ידי יישומים</string>
<!--Logs--> <!--Logs-->
<string name="log_data_none">הינך ללא יומן רישום, יש לנסות להשתמש ביישומים מותאמים יותר למשתמש העל שלך</string> <string name="log_data_none">הינך ללא יומן, יש לנסות להשתמש יותר ביישומי השורש שלך</string>
<string name="log_data_magisk_none">יומני רישום Magisk ריקים, זה מוזר</string> <string name="log_data_magisk_none">יומני רישום Magisk ריקים, זה מוזר</string>
<string name="menuSaveLog">שמירת יומן רישום</string> <string name="menuSaveLog">שמירת יומן רישום</string>
<string name="menuClearLog">ניקוי יומן רישום כעת</string> <string name="menuClearLog">ניקוי יומן רישום כעת</string>
@ -92,7 +91,7 @@
<!--SafetyNet--> <!--SafetyNet-->
<!-- MagiskHide --> <!--MagiskHide-->
<string name="show_system_app">הצגת יישומי מערכת</string> <string name="show_system_app">הצגת יישומי מערכת</string>
<string name="show_os_app">הצגת יישומי מערכת הפעלה</string> <string name="show_os_app">הצגת יישומי מערכת הפעלה</string>
<string name="hide_filter_hint">סינון לפי שם</string> <string name="hide_filter_hint">סינון לפי שם</string>
@ -100,14 +99,16 @@
<!--Module--> <!--Module-->
<string name="no_info_provided">(לא סופק מידע)</string> <string name="no_info_provided">(לא סופק מידע)</string>
<string name="reboot_userspace">אתחול מהיר</string> <string name="reboot_userspace">אתחול רך</string>
<string name="reboot_recovery">אתחול למצב שחזור</string> <string name="reboot_recovery">אתחול למצב שחזור</string>
<string name="reboot_bootloader">אתחול מצב מנהל האתחול</string> <string name="reboot_bootloader">אתחול לטוען האתחול</string>
<string name="reboot_download">אתחול מצב הורדה</string> <string name="reboot_download">אתחול למצב הורדה</string>
<string name="reboot_edl">אתחול למצב EDL</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_version_author">%1$s מאת %2$s</string>
<string name="module_state_remove">הסרה</string> <string name="module_state_remove">הסרה</string>
<string name="module_state_restore">שיחזור</string> <string name="module_action">פעולה</string>
<string name="module_state_restore">שחזור</string>
<string name="module_action_install_external">התקנה מהאחסון</string> <string name="module_action_install_external">התקנה מהאחסון</string>
<string name="update_available">עדכונים זמינים</string> <string name="update_available">עדכונים זמינים</string>
<string name="suspend_text_riru">מודול מושעה כי %1$s מופעל</string> <string name="suspend_text_riru">מודול מושעה כי %1$s מופעל</string>
@ -117,7 +118,7 @@
<string name="confirm_install">להתקין מודול %1$s?</string> <string name="confirm_install">להתקין מודול %1$s?</string>
<string name="confirm_install_title">אישור התקנה</string> <string name="confirm_install_title">אישור התקנה</string>
<!--Settings --> <!--Settings-->
<string name="settings_dark_mode_title">מצב עיצוב</string> <string name="settings_dark_mode_title">מצב עיצוב</string>
<string name="settings_dark_mode_message">נא לבחור מצב המתאים ביותר לסגנון שלך!</string> <string name="settings_dark_mode_message">נא לבחור מצב המתאים ביותר לסגנון שלך!</string>
<string name="settings_dark_mode_light">תמיד בהיר</string> <string name="settings_dark_mode_light">תמיד בהיר</string>
@ -128,11 +129,11 @@
<string name="settings_hide_app_title">הסתרת היישום Magisk</string> <string name="settings_hide_app_title">הסתרת היישום Magisk</string>
<string name="settings_hide_app_summary">התקנת יישום מתווך עם מזהה חבילה אקראי ותווית שם מותאמת אישית</string> <string name="settings_hide_app_summary">התקנת יישום מתווך עם מזהה חבילה אקראי ותווית שם מותאמת אישית</string>
<string name="settings_restore_app_title">שיחזור היישום Magisk</string> <string name="settings_restore_app_title">שיחזור היישום Magisk</string>
<string name="settings_restore_app_summary">יש לבטל את הסתרת היישום ולשחזור אותו ל-APK המקורי</string> <string name="settings_restore_app_summary">ביטול הסתרת היישום ושחזור אל ה-APK המקורי</string>
<string name="language">שפה</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_title">בדיקת עדכונים</string>
<string name="settings_check_update_summary">בדוק מעת לעת ברקע אם יש עדכונים</string> <string name="settings_check_update_summary">בדיקה מעת לעת ברקע אם יש עדכונים</string>
<string name="settings_update_channel_title">ערוץ עדכון</string> <string name="settings_update_channel_title">ערוץ עדכון</string>
<string name="settings_update_stable">יציב</string> <string name="settings_update_stable">יציב</string>
<string name="settings_update_beta">בטא</string> <string name="settings_update_beta">בטא</string>
@ -161,12 +162,12 @@
<string name="settings_su_request_60">60 שניות</string> <string name="settings_su_request_60">60 שניות</string>
<string name="superuser_access">גישת משתמש על</string> <string name="superuser_access">גישת משתמש על</string>
<string name="auto_response">תגובה אוטומטית</string> <string name="auto_response">תגובה אוטומטית</string>
<string name="request_timeout">בקש פסק זמן</string> <string name="request_timeout">בקשת פסק זמן</string>
<string name="superuser_notification">התראות משתמש על</string> <string name="superuser_notification">התראות משתמש על</string>
<string name="settings_su_reauth_title">אימות מחדש לאחר שדרוג</string> <string name="settings_su_reauth_title">אימות מחדש לאחר שדרוג</string>
<string name="settings_su_reauth_summary">אימות מחדש הרשאות של משתמש על לאחר שדרוג יישום</string> <string name="settings_su_reauth_summary">אימות מחדש הרשאות של משתמש על לאחר שדרוג יישום</string>
<string name="settings_su_tapjack_title">הפעלת הגנת Tapjacking</string> <string name="settings_su_tapjack_title">הגנת Tapjacking</string>
<string name="settings_su_tapjack_summary">תיבת הדו שיח של משתמש העל לא תגיב לקלט כשהיא מוסתרת על ידי חלון או כיסוי אחר</string> <string name="settings_su_tapjack_summary">תיבת הדו שיח של משתמש העל לא תגיב לקלט כשהיא מוסתרת על ידי חלון או שכבת על אחרת</string>
<string name="settings_su_auth_title">אימות משתמש</string> <string name="settings_su_auth_title">אימות משתמש</string>
<string name="settings_su_auth_summary">בקשת אימות משתמש במהלך בקשות משתמש על</string> <string name="settings_su_auth_summary">בקשת אימות משתמש במהלך בקשות משתמש על</string>
<string name="settings_su_auth_insecure">לא מוגדרת שיטת אימות בהתקן</string> <string name="settings_su_auth_insecure">לא מוגדרת שיטת אימות בהתקן</string>
@ -174,6 +175,8 @@
<string name="setting_add_shortcut_summary">הוספת קיצור דרך יפה במסך הבית למקרה שקשה לזהות את השם ואת הסמל לאחר הסתרת היישום</string> <string name="setting_add_shortcut_summary">הוספת קיצור דרך יפה במסך הבית למקרה שקשה לזהות את השם ואת הסמל לאחר הסתרת היישום</string>
<string name="settings_doh_title">DNS על HTTPS</string> <string name="settings_doh_title">DNS על HTTPS</string>
<string name="settings_doh_description">עקיפת DNS מורעל במדינות מסוימות</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="multiuser_mode">מצב מרובה משתמשים</string>
<string name="settings_owner_only">בעל ההתקן בלבד</string> <string name="settings_owner_only">בעל ההתקן בלבד</string>
<string name="settings_owner_manage">אחראי ניהול ההתקן</string> <string name="settings_owner_manage">אחראי ניהול ההתקן</string>
@ -205,10 +208,13 @@
<string name="repo_install_title">מתקין %1$s %2$s(%3$d)</string> <string name="repo_install_title">מתקין %1$s %2$s(%3$d)</string>
<string name="download">הורדה</string> <string name="download">הורדה</string>
<string name="reboot">הפעלה מחדש</string> <string name="reboot">הפעלה מחדש</string>
<string name="close">סגירה</string>
<string name="release_notes">הערות שחרור</string> <string name="release_notes">הערות שחרור</string>
<string name="flashing">צורב…</string> <string name="flashing">צורב…</string>
<string name="running">רץ…</string>
<string name="done">בוצע!</string> <string name="done">בוצע!</string>
<string name="failure">נכשל</string> <string name="done_action">בוצעה ריצת פעולה של %1$s</string>
<string name="failure">נכשל!</string>
<string name="hide_app_title">מסתיר את יישום Magisk…</string> <string name="hide_app_title">מסתיר את יישום Magisk…</string>
<string name="open_link_failed_toast">לא נמצאו יישומים לפתיחת קישור זה</string> <string name="open_link_failed_toast">לא נמצאו יישומים לפתיחת קישור זה</string>
<string name="complete_uninstall">הסרה מלאה</string> <string name="complete_uninstall">הסרה מלאה</string>
@ -237,4 +243,5 @@
<string name="app_not_found">לא נמצא יישום לטיפול בפעולה זו</string> <string name="app_not_found">לא נמצא יישום לטיפול בפעולה זו</string>
<string name="reboot_apply_change">ייש להפעיל מחדש כדי להחיל שינויים</string> <string name="reboot_apply_change">ייש להפעיל מחדש כדי להחיל שינויים</string>
<string name="restore_app_confirmation">פעולה זו תשחזר את היישום המוסתר חזרה ליישום המקורי. האם בוודאות ברצונך לעשות את זה?</string> <string name="restore_app_confirmation">פעולה זו תשחזר את היישום המוסתר חזרה ליישום המקורי. האם בוודאות ברצונך לעשות את זה?</string>
</resources> </resources>

View File

@ -0,0 +1,252 @@
<resources>
<!--Sections-->
<string name="modules">زیادکراوەکان</string>
<string name="superuser">سوپەر یوسەر</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>
<!--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="hide">شاردنەوە</string>
<string name="home_package">پاکێج</string>
<string name="home_app_title">ئەپ</string>
<string name="home_notice_content">تەنها لە گیتهەبی فەرمی ماجیسک دابگرە، لە شوێنی تر لەوانەیە زیانبەخش بێت</string>
<string name="home_support_title">پشتگیریمان بکە</string>
<string name="home_follow_title">شوێنمان بکەوە</string>
<string name="home_item_source">سەرچاوە</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">سڕینەوەی ماجیسک</string>
<string name="uninstall_magisk_msg">هەموو زیادکراوەکان دەسڕێنەوە، ڕۆت دەسڕێتەوە، و هەر ڕەمزێنراوێک بەهۆی ماجیسک کرابێ لادەچێت!</string>
<!--Install-->
<string name="keep_force_encryption">ڕەمزاندنی بەزۆر بهێڵەوە</string>
<string name="keep_dm_verity">بهێڵەوە AVB 2.0/dm-verity</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="direct_install">داگرتنی ڕاستەوخۆ(پێشنیارکراوە)</string>
<string name="install_inactive_slot">دایبگرە بۆ خانەی ناچالاک(پاش OTA)</string>
<string name="install_inactive_slot_msg">ئێستا ئامێرەکەت دەچێتە خانە ناچالاکەکە، ئەمە بەکاربهێنە تەنها دوای ئەپدەیت کردن لە ڕێگەی OTA، بەردەوام دەبیت؟</string>
<string name="setup_title">ڕێکخستنی زیاتر</string>
<string name="select_patch_file">فایلێک هەڵبژێرە و پینەی بکە</string>
<string name="patch_file_msg">تکایە فایلێکی Tar یان img یان payload.bin هەڵبژێرە</string>
<string name="reboot_delay_toast">ڕێستارت کردنەوە لە ماوەی ٥ چرکە…</string>
<string name="flash_screen_title">ڕێکخستن</string>
<!--Superuser-->
<string name="su_request_title">داواکاری سوپەریوسەر</string>
<string name="touch_filtered_warning">ئەپێک لە سەر شاشەکەیە، ناتوانین دڵنیا بینەوە</string>
<string name="deny">ڕەتکردنەوە</string>
<string name="prompt">داواکاری</string>
<string name="grant">ڕێگەپێدان</string>
<string name="su_warning">ڕێگەپێدان بۆ تەواوی ئامێرەکەت، گەر دڵنیا نیت ڕەتی بکەوە</string>
<string name="forever">بۆ هەمیشە</string>
<string name="once">بۆ یەکجار</string>
<string name="tenmin">بۆ ١٠ خولەک</string>
<string name="twentymin">بۆ ٢٠ خولەک</string>
<string name="thirtymin">بۆ ٣٠ خولەک</string>
<string name="sixtymin">بۆ ٦٠ خولەک</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_revoke_title">لابردن؟</string>
<string name="su_revoke_msg">دڵنیابەوە بۆ لابردنی سوپەریوسەر بۆ %1$s </string>
<string name="toast">هێنانەسەر</string>
<string name="none">هیچ</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">تۆمارەکانی ماجیسک بەتاڵن، باشە بۆ؟</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 target PID: %s</string>
<string name="selinux_context">SELinux context: %s</string>
<string name="supp_group">Supplementary 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">چوونە ناو بووتلۆدەر</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 by %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">شاردنەوەی ئەپی ماجیسک</string>
<string name="settings_hide_app_summary">داگرتنی ماجیسک بە ناوی جیاوە</string>
<string name="settings_restore_app_title">ئەپە ڕەسەنەکە بهێنەوە</string>
<string name="settings_restore_app_summary">ئەپەکە دەربخەوە و ڕەسەنڵ</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">بەستەرێکی تایبەت دابنێ</string>
<string name="settings_zygisk_summary">کارپێکردنی بەشێکی ماجیسک لە zygote daemon</string>
<string name="settings_denylist_title">پێڕستی نەرێنی کراوەکان</string>
<string name="settings_denylist_summary">هەر ئەپێک لە پێڕستی نەرێنییەکان کاریگەریەکانی ماجیسکی لەسەر نییە</string>
<string name="settings_denylist_config_title">دەستکاریکردنی پێڕستی نەرێنیکراوەکان</string>
<string name="settings_denylist_config_summary">ئەو ئەپە هەڵبژێرە کە دەتەوێت نەرێنیی بکەیت</string>
<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_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">پارێزگاری کردن لە ئەگەری دەستلێدانی تر</string>
<string name="settings_su_tapjack_summary"> کاتێک ئەپێکی تر بەسەر شاشەکەوەیە، سوپەر یوسەر وەڵام ناداتەوە لە کردنی هەر بژاردەیەک لەبەر پارێزراوی</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">Workaround DNS خراپە لە هەندێک شوێن</string>
<string name="settings_random_name_title">ناوێک لەخۆیەوە</string>
<string name="settings_random_name_description">دانانی ناوێک لەخۆوە تاوەکوو ئاشکرا نەبێت</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>
<!--Notifications-->
<string name="update_channel">نوێکردنەوەکانی ماجیسک</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">وەشانی نوێی ماجیسک ئامادەیە!</string>
<string name="updated_title">ماجیسک نوێکراوە!</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">نێبینییەکان</string>
<string name="flashing">فلاش کردن</string>
<string name="running">کار کردن...</string>
<string name="done">تەواو!</string>
<string name="done_action">کارکردنی %1$s تەواو بوو</string>
<string name="failure">Failed!</string>
<string name="hide_app_title">شاردنەوەی ئەپی ماجیسک…</string>
<string name="open_link_failed_toast">هیچ ئەپێک تییە تا لینکەکەی پێ بکرێتەوە</string>
<string name="complete_uninstall">سڕینەوەی تەواوی</string>
<string name="restore_img">هێنانەوەی 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">مۆبایلەکەت پێویستی بە ڕێکخستنی زیاترە، ئایا ئەتەوێت بەردەوام بیت و ڕێستارتی بکەیتەوە؟</string>
<string name="env_full_fix_msg"> پێویستە دوبارە ماجیسک دابگریتەوە بۆ ئەوەی بەباشی کاربکات تکایە ماجیسک دابگرەوە لەناو ئەپەکە خۆی چونکە لە ڕیکەڤەرییەوە ناتوانرێ زانیاری تەواو لەسەر ئامێرەکە دەستبخرێت </string>
<string name="setup_msg">دەستپێکردن....</string>
<string name="unsupport_magisk_title">وەشانی ماجیسک پاڵپشتینەکراوە</string>
<string name="unsupport_magisk_msg">وەشانی ماجیسکەکەت زۆر کۆنە وەک ئەوە وایە هەر نەبێت، تکایە نوێی بکەوە بە زوترین کات</string>
<string name="unsupport_general_title">باری نائاسایی</string>
<string name="unsupport_system_app_msg">ئەم ئەپە وەکو ئەپی سیستەم کارناکات، تکایە بیگۆڕەوە بۆ ئەپی ئاسایی</string>
<string name="unsupport_other_su_msg"> \"su\" binary یەکی بێگانە دۆزرایەوە، تکایە جگە لە ماجیسک ئەپی تر بەکارمەهێنە بۆ ڕۆت کردن </string>
<string name="unsupport_external_storage_msg">ماجیسک لە بیرگەی دەرەکی داگیراوە، تکایە بیبەوە بۆ ناوەکی</string>
<string name="unsupport_nonroot_stub_msg">ئەپە شاراوەکە کار ناکات چونکە ڕۆتەکە نەماوە، تکایە ئەپە ڕەسەنەکە بگەڕێنەوە</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">ڕەزامەندی "install unknown apps" تاوەکوو کار بکات</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

@ -87,6 +87,9 @@
<string name="logs_cleared">Логи успешно очищены</string> <string name="logs_cleared">Логи успешно очищены</string>
<string name="pid">PID: %1$d</string> <string name="pid">PID: %1$d</string>
<string name="target_uid">Целевой UID: %1$d</string> <string name="target_uid">Целевой UID: %1$d</string>
<string name="target_pid">Целевой PID пространства имён: %s</string>
<string name="selinux_context">Контекст SELinux: %s</string>
<string name="supp_group">Дополнительная группа: %s</string>
<!--SafetyNet--> <!--SafetyNet-->
@ -164,11 +167,16 @@
<string name="settings_su_reauth_title">Повторная аутентификация</string> <string name="settings_su_reauth_title">Повторная аутентификация</string>
<string name="settings_su_reauth_summary">Повторный запрос прав суперпользователя после обновления приложений</string> <string name="settings_su_reauth_summary">Повторный запрос прав суперпользователя после обновления приложений</string>
<string name="settings_su_tapjack_title">Защита от перехвата нажатий</string> <string name="settings_su_tapjack_title">Защита от перехвата нажатий</string>
<string name="settings_su_auth_title">Аутентификация пользователя</string>
<string name="settings_su_auth_summary">Требовать аутентификацию пользователя при запросах Superuser</string>
<string name="settings_su_auth_insecure">На устройстве не настроен метод аутентификации</string>
<string name="settings_su_tapjack_summary">Окно запроса прав суперпользователя будет неактивно пока активированы наложения экрана</string> <string name="settings_su_tapjack_summary">Окно запроса прав суперпользователя будет неактивно пока активированы наложения экрана</string>
<string name="settings_customization">Персонализация</string> <string name="settings_customization">Персонализация</string>
<string name="setting_add_shortcut_summary">Добавить ярлык на рабочий стол для удобного восприятия приложения после его скрытия</string> <string name="setting_add_shortcut_summary">Добавить ярлык на рабочий стол для удобного восприятия приложения после его скрытия</string>
<string name="settings_doh_title">DNS поверх HTTPS</string> <string name="settings_doh_title">DNS поверх HTTPS</string>
<string name="settings_doh_description">Активировать DoH (используйте при проблемах с подключением к сети)</string> <string name="settings_doh_description">Активировать DoH (используйте при проблемах с подключением к сети)</string>
<string name="settings_random_name_title">Случайное имя образа</string>
<string name="settings_random_name_description">Генерировать случайные имена для патченных образов и tar-файлов для предотвращения обнаружения</string>
<string name="multiuser_mode">Многопользовательский режим</string> <string name="multiuser_mode">Многопользовательский режим</string>
<string name="settings_owner_only">Только администратор</string> <string name="settings_owner_only">Только администратор</string>

View File

@ -130,8 +130,8 @@
<string name="settings_update_custom">Власний</string> <string name="settings_update_custom">Власний</string>
<string name="settings_update_custom_msg">Вставте власний URL</string> <string name="settings_update_custom_msg">Вставте власний URL</string>
<string name="settings_zygisk_summary">Запускати частини Magisk в сервісі zygote</string> <string name="settings_zygisk_summary">Запускати частини Magisk в сервісі zygote</string>
<string name="settings_denylist_title">Enforce DenyList</string> <string name="settings_denylist_title">Увімкнути DenyList</string>
<string name="settings_denylist_summary">Processes on the denylist will have all Magisk modifications reverted</string> <string name="settings_denylist_summary">Всі зміни, внесені Magisk, будуть приховані від процесів, позначених у DenyList</string>
<string name="settings_denylist_config_title">Налаштувати DenyList</string> <string name="settings_denylist_config_title">Налаштувати DenyList</string>
<string name="settings_denylist_config_summary">Вибрати процеси, які будуть додані до denylist</string> <string name="settings_denylist_config_summary">Вибрати процеси, які будуть додані до denylist</string>
<string name="settings_hosts_title">Позасистемні хости</string> <string name="settings_hosts_title">Позасистемні хости</string>
@ -156,7 +156,7 @@
<string name="superuser_notification">Сповіщення суперкористувача</string> <string name="superuser_notification">Сповіщення суперкористувача</string>
<string name="settings_su_reauth_title">Повторна автентифікація</string> <string name="settings_su_reauth_title">Повторна автентифікація</string>
<string name="settings_su_reauth_summary">Перевидача прав суперкористувача після оновлення застосунку</string> <string name="settings_su_reauth_summary">Перевидача прав суперкористувача після оновлення застосунку</string>
<string name="settings_su_tapjack_title">Увімкнути захист від Tapjack</string> <string name="settings_su_tapjack_title">Увімкнути захист від підміни натискань</string>
<string name="settings_su_tapjack_summary">Діалогове вікно суперкористувача не буде отримувати ввід від користувача, коли вікно перекрито іншим застосунком чи вікном</string> <string name="settings_su_tapjack_summary">Діалогове вікно суперкористувача не буде отримувати ввід від користувача, коли вікно перекрито іншим застосунком чи вікном</string>
<string name="settings_customization">Оформлення</string> <string name="settings_customization">Оформлення</string>
<string name="setting_add_shortcut_summary">Додати ярлик на домашній екран для зручного сприйняття застосунку після його приховування</string> <string name="setting_add_shortcut_summary">Додати ярлик на домашній екран для зручного сприйняття застосунку після його приховування</string>

View File

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

View File

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

View File

@ -12,7 +12,7 @@ import dalvik.system.BaseDexClassLoader;
public class DynamicClassLoader extends BaseDexClassLoader { public class DynamicClassLoader extends BaseDexClassLoader {
public DynamicClassLoader(File apk) { public DynamicClassLoader(File apk) {
this(apk, getSystemClassLoader()); this(apk, DynamicClassLoader.class.getClassLoader());
} }
public DynamicClassLoader(File apk, ClassLoader parent) { public DynamicClassLoader(File apk, ClassLoader parent) {

View File

@ -15,7 +15,7 @@ android {
val canary = !Config.version.contains(".") val canary = !Config.version.contains(".")
val url = if (canary) null val url = if (canary) null
else "https://cdn.jsdelivr.net/gh/topjohnwu/magisk-files@${Config.version}/app-release.apk" else "https://github.com/topjohnwu/Magisk/releases/download/v${Config.version}/Magisk-v${Config.version}.apk"
defaultConfig { defaultConfig {
applicationId = "com.topjohnwu.magisk" applicationId = "com.topjohnwu.magisk"
@ -27,9 +27,9 @@ android {
buildTypes { buildTypes {
release { release {
proguardFiles("proguard-rules.pro")
isMinifyEnabled = true isMinifyEnabled = true
isShrinkResources = false isShrinkResources = false
proguardFiles("proguard-rules.pro")
} }
} }
@ -38,7 +38,7 @@ android {
} }
} }
setupStub() setupStubApk()
dependencies { dependencies {
implementation(project(":app:shared")) implementation(project(":app:shared"))

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="upgrade_msg">ماجیسکەکەت بەرزبکەوە بۆ وەشانی تەواوەتی، دەتەوێت دایبگریت و ڕێکیبخەیت؟</string>
<string name="no_internet_msg">تکایە پەیوەست ببە بە ئینتەرنێتەوە، پێویستە ماجیسکەکەت ڕێک بخەیت.</string>
<string name="dling">داگرتن</string>
<string name="relaunch_app">تکایە دووبارە ئەپەکە بکەوە</string>
</resources>

1
app/test/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

30
app/test/build.gradle.kts Normal file
View File

@ -0,0 +1,30 @@
plugins {
id("com.android.application")
kotlin("android")
}
android {
namespace = "com.topjohnwu.magisk.test"
defaultConfig {
applicationId = "com.topjohnwu.magisk.test"
versionCode = 1
versionName = "1.0"
proguardFile("proguard-rules.pro")
}
buildTypes {
release {
isMinifyEnabled = true
}
}
}
setupTestApk()
dependencies {
implementation(libs.test.runner)
implementation(libs.test.rules)
implementation(libs.test.junit)
implementation(libs.test.uiautomator)
}

13
app/test/proguard-rules.pro vendored Normal file
View File

@ -0,0 +1,13 @@
# Keep all test dependencies
-keep class org.junit.** { *; }
-keep class androidx.test.** { *; }
# Make sure the classloader constructor is kept
-keepclassmembers class com.topjohnwu.magisk.test.TestClassLoader { <init>(); }
# Repackage dependencies
-repackageclasses 'deps'
-allowaccessmodification
# Keep attributes for stacktrace
-keepattributes *

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission
android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="QueryAllPackagesPermission" />
<queries tools:node="removeAll" />
<application tools:node="replace">
<uses-library android:name="android.test.runner" />
</application>
<instrumentation
android:name="com.topjohnwu.magisk.test.AppTestRunner"
android:targetPackage="com.topjohnwu.magisk" />
<instrumentation
android:name="com.topjohnwu.magisk.test.TestRunner"
android:targetPackage="com.topjohnwu.magisk.test" />
</manifest>

View File

@ -0,0 +1,88 @@
package com.topjohnwu.magisk.test
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.ParcelFileDescriptor.AutoCloseInputStream
import androidx.annotation.Keep
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.After
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
@Keep
@RunWith(AndroidJUnit4::class)
class AppMigrationTest {
companion object {
private const val APP_PKG = "com.topjohnwu.magisk"
private const val STUB_PKG = "repackaged.$APP_PKG"
private const val RECEIVER_TIMEOUT = 20L
}
private val instrumentation get() = InstrumentationRegistry.getInstrumentation()
private val context get() = instrumentation.context
private val uiAutomation get() = instrumentation.uiAutomation
private val registeredReceivers = mutableListOf<BroadcastReceiver>()
class PackageRemoveMonitor(
context: Context,
private val packageName: String
) : BroadcastReceiver() {
val latch = CountDownLatch(1)
init {
val filter = IntentFilter(Intent.ACTION_PACKAGE_REMOVED)
filter.addDataScheme("package")
context.registerReceiver(this, filter)
}
override fun onReceive(context: Context, intent: Intent) {
if (intent.action != Intent.ACTION_PACKAGE_REMOVED)
return
val data = intent.data ?: return
val pkg = data.schemeSpecificPart
if (pkg == packageName) latch.countDown()
}
}
@After
fun tearDown() {
registeredReceivers.forEach(context::unregisterReceiver)
}
private fun testAppMigration(pkg: String, method: String) {
val receiver = PackageRemoveMonitor(context, pkg)
registeredReceivers.add(receiver)
// Trigger the test to run migration
val pfd = uiAutomation.executeShellCommand(
"am instrument -w --user 0 -e class .Environment#$method " +
"$pkg.test/${AppTestRunner::class.java.name}"
)
val output = AutoCloseInputStream(pfd).reader().use { it.readText() }
assertTrue("$method failed, inst out: $output", output.contains("OK ("))
// Wait for migration to complete
assertTrue(
"$pkg uninstallation failed",
receiver.latch.await(RECEIVER_TIMEOUT, TimeUnit.SECONDS)
)
}
@Test
fun testAppHide() {
testAppMigration(APP_PKG, "setupAppHide")
}
@Test
fun testAppRestore() {
testAppMigration(STUB_PKG, "setupAppRestore")
}
}

View File

@ -0,0 +1,35 @@
package com.topjohnwu.magisk.test
import android.os.Bundle
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.runner.AndroidJUnitRunner
open class TestRunner : AndroidJUnitRunner() {
override fun onCreate(arguments: Bundle) {
// Support short-hand ".ClassName"
arguments.getString("class")?.let {
val classArg = it.split(",").joinToString(separator = ",") { clz ->
if (clz.startsWith(".")) {
"com.topjohnwu.magisk.test$clz"
} else {
clz
}
}
arguments.putString("class", classArg)
}
super.onCreate(arguments)
}
}
class AppTestRunner : TestRunner() {
override fun onCreate(arguments: Bundle) {
// Force using the target context's classloader to run tests
arguments.putString("classLoader", TestClassLoader::class.java.name)
super.onCreate(arguments)
}
}
private val targetClassLoader inline get() =
InstrumentationRegistry.getInstrumentation().targetContext.classLoader
class TestClassLoader : ClassLoader(targetClassLoader)

267
build.py
View File

@ -1,7 +1,7 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import argparse import argparse
import copy
import glob import glob
import lzma
import multiprocessing import multiprocessing
import os import os
import platform import platform
@ -56,14 +56,6 @@ if is_windows:
if not sys.version_info >= (3, 8): if not sys.version_info >= (3, 8):
error("Requires Python 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() cpu_count = multiprocessing.cpu_count()
os_name = platform.system().lower() os_name = platform.system().lower()
@ -79,17 +71,6 @@ default_targets = {"magisk", "magiskinit", "magiskboot", "magiskpolicy"}
support_targets = default_targets | {"resetprop"} support_targets = default_targets | {"resetprop"}
rust_targets = {"magisk", "magiskinit", "magiskboot", "magiskpolicy"} 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()
# Global vars # Global vars
config = {} config = {}
args = {} args = {}
@ -161,37 +142,22 @@ def cmd_out(cmds: list):
) )
def xz(data):
return lzma.compress(data, preset=9, check=lzma.CHECK_NONE)
############### ###############
# Build Native # Build Native
############### ###############
def clean_elf(): def clean_elf():
if is_windows: cargo_toml = Path("tools", "elf-cleaner", "Cargo.toml")
elf_cleaner = Path("tools", "elf-cleaner.exe") cmds = ["run", "--release", "--manifest-path", cargo_toml]
else: if args.verbose == 0:
elf_cleaner = Path("native", "out", "elf-cleaner") cmds.append("-q")
if not elf_cleaner.exists(): elif args.verbose > 1:
execv( cmds.append("--verbose")
[ cmds.append("--")
"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"]
cmds.extend(glob.glob("native/out/*/magisk")) cmds.extend(glob.glob("native/out/*/magisk"))
cmds.extend(glob.glob("native/out/*/magiskpolicy")) cmds.extend(glob.glob("native/out/*/magiskpolicy"))
execv(cmds) run_cargo(cmds)
def run_ndk_build(cmds: list): def run_ndk_build(cmds: list):
@ -218,8 +184,6 @@ def run_ndk_build(cmds: list):
def build_cpp_src(targets: set): def build_cpp_src(targets: set):
dump_flag_header()
cmds = [] cmds = []
clean = False clean = False
@ -257,11 +221,11 @@ def build_cpp_src(targets: set):
def run_cargo(cmds): def run_cargo(cmds):
ensure_paths()
env = os.environ.copy() env = os.environ.copy()
env["PATH"] = f'{rust_bin}{os.pathsep}{env["PATH"]}' env["RUSTUP_TOOLCHAIN"] = str(rust_sysroot)
env["CARGO_BUILD_RUSTC"] = str(rust_bin / f"rustc{EXE_EXT}")
env["CARGO_BUILD_RUSTFLAGS"] = f"-Z threads={min(8, cpu_count)}" 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): def build_rust_src(targets: set):
@ -334,11 +298,18 @@ def dump_flag_header():
flag_txt += f'#define MAGISK_VER_CODE {config["versionCode"]}\n' flag_txt += f'#define MAGISK_VER_CODE {config["versionCode"]}\n'
flag_txt += f"#define MAGISK_DEBUG {0 if args.release else 1}\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) native_gen_path.mkdir(mode=0o755, parents=True, exist_ok=True)
write_if_diff(Path(native_gen_path, "flags.h"), flag_txt) write_if_diff(native_gen_path / "flags.h", flag_txt)
rust_flag_txt = f'pub const MAGISK_VERSION: &str = "{config["version"]}";\n'
rust_flag_txt += f'pub const MAGISK_VER_CODE: i32 = {config["versionCode"]};\n'
write_if_diff(native_gen_path / "flags.rs", rust_flag_txt)
def build_native(): def build_native():
ensure_paths()
# Verify NDK install # Verify NDK install
try: try:
with open(Path(ndk_path, "ONDK_VERSION"), "r") as ondk_ver: with open(Path(ndk_path, "ONDK_VERSION"), "r") as ondk_ver:
@ -362,6 +333,7 @@ def build_native():
if ccache := shutil.which("ccache"): if ccache := shutil.which("ccache"):
os.environ["NDK_CCACHE"] = ccache os.environ["NDK_CCACHE"] = ccache
dump_flag_header()
build_rust_src(targets) build_rust_src(targets)
build_cpp_src(targets) build_cpp_src(targets)
@ -397,13 +369,14 @@ def find_jdk():
if no_jdk: if no_jdk:
error( error(
"Please set Android Studio's path to environment variable ANDROID_STUDIO,\n" "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 return env
def build_apk(module: str): def build_apk(module: str):
ensure_paths()
env = find_jdk() env = find_jdk()
build_type = "Release" if args.release else "Debug" build_type = "Release" if args.release else "Debug"
@ -426,19 +399,20 @@ def build_apk(module: str):
source = Path(*paths, "build", "outputs", "apk", build_type, apk) source = Path(*paths, "build", "outputs", "apk", build_type, apk)
target = config["outdir"] / apk target = config["outdir"] / apk
mv(source, target) mv(source, target)
header(f"Output: {target}") return target
def build_app(): def build_app():
header("* Building the Magisk app") header("* Building the Magisk app")
build_apk(":app:apk") apk = build_apk(":app:apk")
build_type = "release" if args.release else "debug" build_type = "release" if args.release else "debug"
# Rename apk-variant.apk to app-variant.apk # Rename apk-variant.apk to app-variant.apk
source = config["outdir"] / f"apk-{build_type}.apk" source = apk
target = config["outdir"] / f"app-{build_type}.apk" target = apk.parent / apk.name.replace("apk-", "app-")
mv(source, target) mv(source, target)
header(f"Output: {target}")
# Stub building is directly integrated into the main app # Stub building is directly integrated into the main app
# build process. Copy the stub APK into output directory. # build process. Copy the stub APK into output directory.
@ -449,7 +423,23 @@ def build_app():
def build_stub(): def build_stub():
header("* Building the stub app") header("* Building the stub app")
build_apk(":app:stub") apk = build_apk(":app:stub")
header(f"Output: {apk}")
def build_test():
global args
args_bak = copy.copy(args)
# Test APK has to be built as release to prevent classname clash
args.release = True
try:
header("* Building the test app")
source = build_apk(":app:test")
target = source.parent / "test.apk"
mv(source, target)
header(f"Output: {target}")
finally:
args = args_bak
################ ################
@ -458,6 +448,7 @@ def build_stub():
def cleanup(): def cleanup():
ensure_paths()
support_targets = {"native", "cpp", "rust", "app"} support_targets = {"native", "cpp", "rust", "app"}
if args.targets: if args.targets:
targets = set(args.targets) & support_targets targets = set(args.targets) & support_targets
@ -482,6 +473,7 @@ def cleanup():
if "native" in targets: if "native" in targets:
rm_rf(Path("native", "out")) rm_rf(Path("native", "out"))
rm_rf(Path("tools", "elf-cleaner", "target"))
if "app" in targets: if "app" in targets:
header("* Cleaning app") header("* Cleaning app")
@ -491,6 +483,7 @@ def cleanup():
def build_all(): def build_all():
build_native() build_native()
build_app() build_app()
build_test()
############ ############
@ -498,6 +491,16 @@ def build_all():
############ ############
def clippy_cli():
args.force_out = True
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(): def cargo_cli():
args.force_out = True args.force_out = True
if len(args.commands) >= 1 and args.commands[0] == "--": if len(args.commands) >= 1 and args.commands[0] == "--":
@ -508,6 +511,7 @@ def cargo_cli():
def setup_ndk(): def setup_ndk():
ensure_paths()
ndk_ver = config["ondkVersion"] ndk_ver = config["ondkVersion"]
url = f"https://github.com/topjohnwu/ondk/releases/download/{ndk_ver}/ondk-{ndk_ver}-{os_name}.tar.xz" url = f"https://github.com/topjohnwu/ondk/releases/download/{ndk_ver}/ondk-{ndk_ver}-{os_name}.tar.xz"
ndk_archive = url.split("/")[-1] ndk_archive = url.split("/")[-1]
@ -526,11 +530,51 @@ def setup_ndk():
mv(ondk_path, ndk_path) 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): def push_files(script):
if args.build:
build_all()
ensure_adb()
abi = cmd_out([adb_path, "shell", "getprop", "ro.product.cpu.abi"]) abi = cmd_out([adb_path, "shell", "getprop", "ro.product.cpu.abi"])
if not abi: if not abi:
error("Cannot detect emulator ABI") error("Cannot detect emulator ABI")
if args.apk:
apk = Path(args.apk)
else:
apk = Path( apk = Path(
config["outdir"], ("app-release.apk" if args.release else "app-debug.apk") config["outdir"], ("app-release.apk" if args.release else "app-debug.apk")
) )
@ -555,9 +599,6 @@ def push_files(script):
def setup_avd(): def setup_avd():
if not args.skip:
build_all()
header("* Setting up emulator") header("* Setting up emulator")
push_files(Path("scripts", "avd_magisk.sh")) push_files(Path("scripts", "avd_magisk.sh"))
@ -568,17 +609,8 @@ def setup_avd():
def patch_avd_file(): def patch_avd_file():
if not args.skip:
build_all()
input = Path(args.image) input = Path(args.image)
if args.output:
output = Path(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"
header(f"* Patching {input.name}") header(f"* Patching {input.name}")
@ -588,6 +620,9 @@ def patch_avd_file():
if proc.returncode != 0: if proc.returncode != 0:
error("adb push failed!") 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]) proc = execv([adb_path, "shell", "sh", "/data/local/tmp/avd_patch.sh", src_file])
if proc.returncode != 0: if proc.returncode != 0:
error("avd_patch.sh failed!") error("avd_patch.sh failed!")
@ -599,37 +634,47 @@ def patch_avd_file():
header(f"Output: {output}") header(f"Output: {output}")
def setup_rustup(): ##########################
wrapper_dir = Path(args.wrapper_dir) # Config, paths, argparse
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"]) 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() / "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: else:
cargo_home = Path.home() / ".cargo" adb_path = Path(adb_path)
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 and args
##################
def parse_props(file): def parse_props(file):
@ -719,6 +764,8 @@ def parse_args():
stub_parser = subparsers.add_parser("stub", help="build the stub app") stub_parser = subparsers.add_parser("stub", help="build the stub app")
test_parser = subparsers.add_parser("test", help="build the test app")
clean_parser = subparsers.add_parser("clean", help="cleanup") clean_parser = subparsers.add_parser("clean", help="cleanup")
clean_parser.add_argument( clean_parser.add_argument(
"targets", nargs="*", help="native, cpp, rust, java, or empty to clean all" "targets", nargs="*", help="native, cpp, rust, java, or empty to clean all"
@ -727,17 +774,19 @@ def parse_args():
ndk_parser = subparsers.add_parser("ndk", help="setup Magisk NDK") ndk_parser = subparsers.add_parser("ndk", help="setup Magisk NDK")
emu_parser = subparsers.add_parser("emulator", help="setup AVD for development") 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( 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_parser = subparsers.add_parser(
"avd_patch", help="patch AVD ramdisk.img or init_boot.img" "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("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( 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( cargo_parser = subparsers.add_parser(
@ -745,6 +794,8 @@ def parse_args():
) )
cargo_parser.add_argument("commands", nargs=argparse.REMAINDER) 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 = subparsers.add_parser("rustup", help="setup rustup wrapper")
rustup_parser.add_argument( rustup_parser.add_argument(
"wrapper_dir", help="path to setup rustup wrapper binaries" "wrapper_dir", help="path to setup rustup wrapper binaries"
@ -754,9 +805,11 @@ def parse_args():
all_parser.set_defaults(func=build_all) all_parser.set_defaults(func=build_all)
native_parser.set_defaults(func=build_native) native_parser.set_defaults(func=build_native)
cargo_parser.set_defaults(func=cargo_cli) cargo_parser.set_defaults(func=cargo_cli)
clippy_parser.set_defaults(func=clippy_cli)
rustup_parser.set_defaults(func=setup_rustup) rustup_parser.set_defaults(func=setup_rustup)
app_parser.set_defaults(func=build_app) app_parser.set_defaults(func=build_app)
stub_parser.set_defaults(func=build_stub) stub_parser.set_defaults(func=build_stub)
test_parser.set_defaults(func=build_test)
emu_parser.set_defaults(func=setup_avd) emu_parser.set_defaults(func=setup_avd)
avd_patch_parser.set_defaults(func=patch_avd_file) avd_patch_parser.set_defaults(func=patch_avd_file)
clean_parser.set_defaults(func=cleanup) clean_parser.set_defaults(func=cleanup)
@ -769,7 +822,13 @@ def parse_args():
return parser.parse_args() return parser.parse_args()
args = parse_args() def main():
load_config() global args
vars(args)["force_out"] = False args = parse_args()
args.func() load_config()
vars(args)["force_out"] = False
args.func()
if __name__ == "__main__":
main()

View File

@ -1,4 +1,4 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
plugins { plugins {
`kotlin-dsl` `kotlin-dsl`
@ -18,9 +18,9 @@ gradlePlugin {
} }
} }
tasks.withType<KotlinCompile>().configureEach { kotlin {
kotlinOptions { compilerOptions {
languageVersion = "2.0" languageVersion = KotlinVersion.KOTLIN_2_0
} }
} }

View File

@ -0,0 +1,122 @@
import com.android.build.api.instrumentation.AsmClassVisitorFactory
import com.android.build.api.instrumentation.ClassContext
import com.android.build.api.instrumentation.ClassData
import com.android.build.api.instrumentation.InstrumentationParameters
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.MethodVisitor
import org.objectweb.asm.Opcodes
import org.objectweb.asm.Opcodes.ASM9
private const val DESUGAR_CLASS_NAME = "com.topjohnwu.magisk.core.utils.Desugar"
private const val ZIP_ENTRY_CLASS_NAME = "java.util.zip.ZipEntry"
private const val ZIP_OUT_STREAM_CLASS_NAME = "org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream"
private const val ZIP_UTIL_CLASS_NAME = "org/apache/commons/compress/archivers/zip/ZipUtil"
private const val ZIP_ENTRY_GET_TIME_DESC = "()Ljava/nio/file/attribute/FileTime;"
private const val DESUGAR_GET_TIME_DESC =
"(Ljava/util/zip/ZipEntry;)Ljava/nio/file/attribute/FileTime;"
private fun ClassData.isTypeOf(name: String) = className == name || superClasses.contains(name)
abstract class DesugarClassVisitorFactory : AsmClassVisitorFactory<InstrumentationParameters.None> {
override fun createClassVisitor(
classContext: ClassContext,
nextClassVisitor: ClassVisitor
): ClassVisitor {
return if (classContext.currentClassData.className == ZIP_OUT_STREAM_CLASS_NAME) {
ZipEntryPatcher(classContext, ZipOutputStreamPatcher(nextClassVisitor))
} else {
ZipEntryPatcher(classContext, nextClassVisitor)
}
}
override fun isInstrumentable(classData: ClassData) = classData.className != DESUGAR_CLASS_NAME
// Patch ALL references to ZipEntry#getXXXTime
class ZipEntryPatcher(
private val classContext: ClassContext,
cv: ClassVisitor
) : ClassVisitor(ASM9, cv) {
override fun visitMethod(
access: Int,
name: String?,
descriptor: String?,
signature: String?,
exceptions: Array<out String>?
) = MethodPatcher(super.visitMethod(access, name, descriptor, signature, exceptions))
inner class MethodPatcher(mv: MethodVisitor?) : MethodVisitor(ASM9, mv) {
override fun visitMethodInsn(
opcode: Int,
owner: String,
name: String,
descriptor: String,
isInterface: Boolean
) {
if (!process(owner, name, descriptor)) {
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface)
}
}
private fun process(owner: String, name: String, descriptor: String): Boolean {
val classData = classContext.loadClassData(owner.replace("/", ".")) ?: return false
if (!classData.isTypeOf(ZIP_ENTRY_CLASS_NAME))
return false
if (descriptor != ZIP_ENTRY_GET_TIME_DESC)
return false
return when (name) {
"getLastModifiedTime", "getLastAccessTime", "getCreationTime" -> {
mv.visitMethodInsn(
Opcodes.INVOKESTATIC,
DESUGAR_CLASS_NAME.replace('.', '/'),
name,
DESUGAR_GET_TIME_DESC,
false
)
true
}
else -> false
}
}
}
}
// Patch ZipArchiveOutputStream#copyFromZipInputStream
class ZipOutputStreamPatcher(cv: ClassVisitor) : ClassVisitor(ASM9, cv) {
override fun visitMethod(
access: Int,
name: String,
descriptor: String,
signature: String?,
exceptions: Array<out String?>?
): MethodVisitor? {
return if (name == "copyFromZipInputStream") {
MethodPatcher(super.visitMethod(access, name, descriptor, signature, exceptions))
} else {
super.visitMethod(access, name, descriptor, signature, exceptions)
}
}
class MethodPatcher(mv: MethodVisitor?) : MethodVisitor(ASM9, mv) {
override fun visitMethodInsn(
opcode: Int,
owner: String,
name: String,
descriptor: String?,
isInterface: Boolean
) {
if (owner == ZIP_UTIL_CLASS_NAME && name == "checkRequestedFeatures") {
// Redirect
mv.visitMethodInsn(
Opcodes.INVOKESTATIC,
DESUGAR_CLASS_NAME.replace('.', '/'),
name,
descriptor,
false
)
} else {
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface)
}
}
}
}
}

View File

@ -1,6 +1,8 @@
import com.android.build.api.artifact.ArtifactTransformationRequest import com.android.build.api.artifact.ArtifactTransformationRequest
import com.android.build.api.artifact.SingleArtifact import com.android.build.api.artifact.SingleArtifact
import com.android.build.api.dsl.ApkSigningConfig import com.android.build.api.dsl.ApkSigningConfig
import com.android.build.api.instrumentation.FramesComputationMode.COMPUTE_FRAMES_FOR_INSTRUMENTED_METHODS
import com.android.build.api.instrumentation.InstrumentationScope
import com.android.build.api.variant.ApplicationAndroidComponentsExtension import com.android.build.api.variant.ApplicationAndroidComponentsExtension
import com.android.build.gradle.BaseExtension import com.android.build.gradle.BaseExtension
import com.android.build.gradle.LibraryExtension import com.android.build.gradle.LibraryExtension
@ -69,10 +71,10 @@ private val Project.androidComponents
fun Project.setupCommon() { fun Project.setupCommon() {
androidBase { androidBase {
compileSdkVersion(35) compileSdkVersion(36)
buildToolsVersion = "34.0.0" buildToolsVersion = "36.0.0"
ndkPath = "$sdkDirectory/ndk/magisk" ndkPath = "$sdkDirectory/ndk/magisk"
ndkVersion = "27.0.12077973" ndkVersion = "29.0.13113456"
defaultConfig { defaultConfig {
minSdk = 23 minSdk = 23
@ -113,6 +115,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 = const val BUSYBOX_DOWNLOAD_URL =
"https://github.com/topjohnwu/magisk-files/releases/download/files/busybox-1.36.1.1.zip" "https://github.com/topjohnwu/magisk-files/releases/download/files/busybox-1.36.1.1.zip"
const val BUSYBOX_ZIP_CHECKSUM = const val BUSYBOX_ZIP_CHECKSUM =
@ -142,24 +165,7 @@ fun Project.setupCoreLib() {
val downloadBusybox by tasks.registering(Copy::class) { val downloadBusybox by tasks.registering(Copy::class) {
dependsOn(syncLibs) dependsOn(syncLibs)
val bb = layout.buildDirectory.file(BUSYBOX_ZIP_CHECKSUM).get().asFile from(zipTree(downloadFile(BUSYBOX_DOWNLOAD_URL, BUSYBOX_ZIP_CHECKSUM)))
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))
include(abiList.map { "$it/libbusybox.so" }) include(abiList.map { "$it/libbusybox.so" })
into("src/main/jniLibs") into("src/main/jniLibs")
} }
@ -296,7 +302,10 @@ fun Project.setupAppCommon() {
} }
defaultConfig { defaultConfig {
targetSdk = 35 targetSdk = 36
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt")
)
} }
buildTypes { buildTypes {
@ -348,7 +357,34 @@ fun Project.setupAppCommon() {
} }
} }
fun Project.setupStub() { fun Project.setupMainApk() {
setupAppCommon()
android {
namespace = "com.topjohnwu.magisk"
defaultConfig {
applicationId = "com.topjohnwu.magisk"
vectorDrawables.useSupportLibrary = true
versionName = Config.version
versionCode = Config.versionCode
ndk {
abiFilters += listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64", "riscv64")
debugSymbolLevel = "FULL"
}
}
androidComponents.onVariants { variant ->
variant.instrumentation.apply {
setAsmFramesComputationMode(COMPUTE_FRAMES_FOR_INSTRUMENTED_METHODS)
transformClassesWith(
DesugarClassVisitorFactory::class.java, InstrumentationScope.ALL) {}
}
}
}
}
fun Project.setupStubApk() {
setupAppCommon() setupAppCommon()
androidComponents.onVariants { variant -> androidComponents.onVariants { variant ->
@ -430,3 +466,31 @@ fun Project.setupStub() {
delete.addAll(listOf("src/debug/AndroidManifest.xml", "src/release/AndroidManifest.xml")) 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

@ -40,12 +40,9 @@
### Developing Rust ### 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: 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:
- Install [rustup](https://rustup.rs/), the official Rust toolchain manager
- Link the ONDK Rust toolchain and set it as default:
```bash ```bash
# Link the ONDK toolchain with the name "magisk" # 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 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. - 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,13 @@
# Magisk Changelog # Magisk Changelog
### v28.1
- [App] Fix stub APK download link
- [App] Fix support for Android lower than 8.0
- [General] Fix support for MTK Samsung devices
- [MagiskInit] Fix a regression for 2SI devices
- [MagiskPolicy] Fix a regression causing `overlay.d` replaced files to be not accessible
### v28.0 ### v28.0
- [General] Support 16k page size - [General] Support 16k page size

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.) 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: 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. 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.
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. 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? ### 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 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. 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

@ -124,6 +124,8 @@ If you place a file named `.replace` in any of the folders, instead of merging i
If you want to replace files in `/vendor`, `/product`, or `/system_ext`, please place them under `system/vendor`, `system/product`, and `system/system_ext` respectively. Magisk will transparently handle whether these partitions are in a separate partition or not. If you want to replace files in `/vendor`, `/product`, or `/system_ext`, please place them under `system/vendor`, `system/product`, and `system/system_ext` respectively. Magisk will transparently handle whether these partitions are in a separate partition or not.
If you want to remove a specific file or folder, please place a dummy character device with major number 0 and minor number 0 in the same path. For example, if you want to remove `/system/app/GoogleCamera`, you can `mknod GoogleCamera c 0 0` in `$MODDIR/system/app`.
#### Zygisk #### Zygisk
Zygisk is a feature of Magisk that allows advanced module developers to run code directly in every Android applications' processes before they are specialized and running. For more details about the Zygisk API and building a Zygisk module, please checkout the [Zygisk Module Sample](https://github.com/topjohnwu/zygisk-module-sample) project. Zygisk is a feature of Magisk that allows advanced module developers to run code directly in every Android applications' processes before they are specialized and running. For more details about the Zygisk API and building a Zygisk module, please checkout the [Zygisk Module Sample](https://github.com/topjohnwu/zygisk-module-sample) project.
@ -138,7 +140,7 @@ If your module requires some additional sepolicy patches, please add those rules
## Magisk Module Installer ## Magisk Module Installer
A Magisk module installer is a Magisk module packaged in a zip file that can be flashed in the Magisk app or custom recoveries such as TWRP. The simplest Magisk module installer is just a Magisk module packed as a zip file, in addition to the following files: A Magisk module installer is a Magisk module packaged in a zip file that can be flashed in the Magisk app or custom recoveries such as TWRP. The simplest Magisk module installer is just a Magisk module packed as a zip file, in addition to the following files only if the module supports flashing in recovery:
- `update-binary`: Download the latest [module_installer.sh](https://github.com/topjohnwu/Magisk/blob/master/scripts/module_installer.sh) and rename/copy that script as `update-binary` - `update-binary`: Download the latest [module_installer.sh](https://github.com/topjohnwu/Magisk/blob/master/scripts/module_installer.sh) and rename/copy that script as `update-binary`
- `updater-script`: This file should only contain the string `#MAGISK` - `updater-script`: This file should only contain the string `#MAGISK`
@ -148,7 +150,7 @@ The module installer script will setup the environment, extract the module files
``` ```
module.zip module.zip
├── META-INF ├── META-INF <--- Only needed for flashing in recovery
│ └── com │ └── com
│ └── google │ └── google
│ └── android │ └── android
@ -212,7 +214,7 @@ set_perm_recursive <directory> <owner> <group> <dirpermission> <filepermission>
For convenience, you can also declare a list of folders you want to replace in the variable name `REPLACE`. The module installer script will create the `.replace` file into the folders listed in `REPLACE`. For example: For convenience, you can also declare a list of folders you want to replace in the variable name `REPLACE`. The module installer script will create the `.replace` file into the folders listed in `REPLACE`. For example:
``` ```sh
REPLACE=" REPLACE="
/system/app/YouTube /system/app/YouTube
/system/app/Bloatware /system/app/Bloatware
@ -221,6 +223,17 @@ REPLACE="
The list above will result in the following files being created: `$MODPATH/system/app/YouTube/.replace` and `$MODPATH/system/app/Bloatware/.replace`. The list above will result in the following files being created: `$MODPATH/system/app/YouTube/.replace` and `$MODPATH/system/app/Bloatware/.replace`.
For convenience, you can also declare a list of files/folders you want to remove in the variable name `REMOVE`. The module installer script will create the corresponding dummy devices. For example:
```sh
REMOVE="
/system/app/YouTube
/system/fonts/Roboto.ttf
"
```
The list above will result in the following dummy devices being created: `$MODPATH/system/app/YouTube` and `$MODPATH/system/fonts/Roboto.ttf`.
#### Notes #### Notes
- When your module is downloaded with the Magisk app, `update-binary` will be **forcefully** replaced with the latest [`module_installer.sh`](https://github.com/topjohnwu/Magisk/blob/master/scripts/module_installer.sh). **DO NOT** try to add any custom logic in `update-binary`. - When your module is downloaded with the Magisk app, `update-binary` will be **forcefully** replaced with the latest [`module_installer.sh`](https://github.com/topjohnwu/Magisk/blob/master/scripts/module_installer.sh). **DO NOT** try to add any custom logic in `update-binary`.

View File

@ -76,7 +76,7 @@ As a summary, after installing Magisk in recovery **(starting from power off)**:
Before proceeding, please acknowledge that: Before proceeding, please acknowledge that:
- Installing Magisk **WILL** trip your Knox Warranty Bit, this action is not reversible in any way. - 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 ### Flashing Tools
@ -86,7 +86,7 @@ Before proceeding, please acknowledge that:
### Requirements ### 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: Possible OEM Lock values are the following:
- **ON (L)**: fully locked. - **ON (L)**: fully locked.

33
docs/releases/28100.md Normal file
View File

@ -0,0 +1,33 @@
## 2024.12.6 Magisk v28.1
- [App] Fix stub APK download link
- [App] Fix support for Android lower than 8.0
- [General] Fix support for MTK Samsung devices
- [MagiskInit] Fix a regression for 2SI devices
- [MagiskPolicy] Fix a regression causing `overlay.d` replaced files to be not accessible
## Magisk v28.0 Changes
- [General] Support 16k page size
- [General] Add basic support for RISC-V (not built in releases)
- [General] Use a minimal libc to build static executables (`magiskinit` and `magiskboot`) for smaller sizes
- [Core] Remove unnecessary mirror for magic mount
- [Core] Update boot image detection logic to support more devices
- [MagiskInit] Rewrite 2SI logic for injecting `magiskinit` as `init`
- [MagiskInit] Update preinit partition detection
- [Zygisk] Update internal JNI hooking implementation
- [MagiskPolicy] Preserve sepolicy config flag after patching
- [MagiskPolicy] Optimize patching rules to reduce the amount of new rules being injected
- [DenyList] Support enforcing denylist when Zygisk is disabled
- [Resetprop] Improve implementation to workaround several property modification detections
- [Resetprop] Update to properly work with property overlays
- [App] Major internal code refactoring
- [App] Support patching Samsung firmware with images larger than 8GiB
- [App] Use user-initiated job instead of foreground services on Android 14
- [App] Support Android 13+ built-in per-app language preferences
- [App] Add `action.sh` support to allow modules to define an action triggered from UI
- [MagiskBoot] Support spliting kernel images without decompression
- [MagiskBoot] Properly support vendor boot images
- [MagiskBoot] Disable Samsung PROCA from kernel image
### Full Changelog: [here](https://topjohnwu.github.io/Magisk/changes.html)

View File

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

View File

@ -30,5 +30,5 @@ android.nonFinalResIds=false
# Magisk # Magisk
magisk.stubVersion=40 magisk.stubVersion=40
magisk.versionCode=28000 magisk.versionCode=28104
magisk.ondkVersion=r27.4 magisk.ondkVersion=r29.1

View File

@ -1,17 +1,17 @@
[versions] [versions]
kotlin = "2.0.20" kotlin = "2.1.20"
android = "8.7.0" android = "8.9.2"
ksp = "2.0.20-1.0.25" ksp = "2.1.20-1.0.31"
rikka = "1.3.0" rikka = "1.3.0"
navigation = "2.8.2" navigation = "2.8.9"
libsu = "6.0.0" libsu = "6.0.0"
moshi = "1.15.1" moshi = "1.15.2"
okhttp = "4.12.0" okhttp = "4.12.0"
retrofit = "2.11.0" retrofit = "2.11.0"
room = "2.6.1" room = "2.7.1"
[libraries] [libraries]
bcpkix = { module = "org.bouncycastle:bcpkix-jdk18on", version = "1.78.1" } bcpkix = { module = "org.bouncycastle:bcpkix-jdk18on", version = "1.80" }
commons-compress = { module = "org.apache.commons:commons-compress", version = "1.27.1" } commons-compress = { module = "org.apache.commons:commons-compress", version = "1.27.1" }
retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" } retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
retrofit-moshi = { module = "com.squareup.retrofit2:converter-moshi", version.ref = "retrofit" } retrofit-moshi = { module = "com.squareup.retrofit2:converter-moshi", version.ref = "retrofit" }
@ -23,28 +23,31 @@ okhttp-logging = { module = "com.squareup.okhttp3:logging-interceptor", version.
moshi = { module = "com.squareup.moshi:moshi", version.ref = "moshi" } moshi = { module = "com.squareup.moshi:moshi", version.ref = "moshi" }
moshi-codegen = { module = "com.squareup.moshi:moshi-kotlin-codegen", version.ref = "moshi" } moshi-codegen = { module = "com.squareup.moshi:moshi-kotlin-codegen", version.ref = "moshi" }
timber = { module = "com.jakewharton.timber:timber", version = "5.0.1" } timber = { module = "com.jakewharton.timber:timber", version = "5.0.1" }
jgit = { module = "org.eclipse.jgit:org.eclipse.jgit", version = "6.10.0.202406032230-r" } jgit = { module = "org.eclipse.jgit:org.eclipse.jgit", version = "7.1.0.202411261347-r" }
# AndroidX # AndroidX
activity = { module = "androidx.activity:activity", version = "1.9.2" } activity = { module = "androidx.activity:activity", version = "1.10.1" }
appcompat = { module = "androidx.appcompat:appcompat", version = "1.7.0" } appcompat = { module = "androidx.appcompat:appcompat", version = "1.7.0" }
core-ktx = { module = "androidx.core:core-ktx", version = "1.13.1" } core-ktx = { module = "androidx.core:core-ktx", version = "1.16.0" }
core-splashscreen = { module = "androidx.core:core-splashscreen", version = "1.0.1" } core-splashscreen = { module = "androidx.core:core-splashscreen", version = "1.0.1" }
constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version = "2.1.4" } constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version = "2.2.1" }
fragment-ktx = { module = "androidx.fragment:fragment-ktx", version = "1.8.4" } fragment-ktx = { module = "androidx.fragment:fragment-ktx", version = "1.8.6" }
navigation-fragment-ktx = { module = "androidx.navigation:navigation-fragment-ktx", version.ref = "navigation" } navigation-fragment-ktx = { module = "androidx.navigation:navigation-fragment-ktx", version.ref = "navigation" }
navigation-ui-ktx = { module = "androidx.navigation:navigation-ui-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" } profileinstaller = { module = "androidx.profileinstaller:profileinstaller", version = "1.4.1" }
recyclerview = { module = "androidx.recyclerview:recyclerview", version = "1.3.2" } recyclerview = { module = "androidx.recyclerview:recyclerview", version = "1.4.0" }
room-ktx = { module = "androidx.room:room-ktx", version.ref = "room" } room-ktx = { module = "androidx.room:room-ktx", version.ref = "room" }
room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" } room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" }
room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" } room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" }
swiperefreshlayout = { module = "androidx.swiperefreshlayout:swiperefreshlayout", version = "1.1.0" } swiperefreshlayout = { module = "androidx.swiperefreshlayout:swiperefreshlayout", version = "1.1.0" }
transition = { module = "androidx.transition:transition", version = "1.5.1" } transition = { module = "androidx.transition:transition", version = "1.6.0" }
collection-ktx = { module = "androidx.collection:collection-ktx", version = "1.4.4" } collection-ktx = { module = "androidx.collection:collection-ktx", version = "1.5.0" }
lifecycle-process = { module = "androidx.lifecycle:lifecycle-process", version = "2.8.6" }
material = { module = "com.google.android.material:material", version = "1.12.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.2" } 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" }
test-uiautomator = { module = "androidx.test.uiautomator:uiautomator", version = "2.3.0" }
# topjohnwu # topjohnwu
indeterminate-checkbox = { module = "com.github.topjohnwu:indeterminate-checkbox", version = "1.0.7" } indeterminate-checkbox = { module = "com.github.topjohnwu:indeterminate-checkbox", version = "1.0.7" }

View File

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

3
gradlew vendored
View File

@ -86,8 +86,7 @@ done
# shellcheck disable=SC2034 # shellcheck disable=SC2034
APP_BASE_NAME=${0##*/} APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value. # Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum MAX_FD=maximum

View File

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

View File

@ -4,35 +4,43 @@ LOCAL_PATH := $(call my-dir)
# Rust compilation outputs # Rust compilation outputs
########################### ###########################
LIBRARY_PATH = ../out/$(TARGET_ARCH_ABI)/libmagisk-rs.a
ifneq (,$(wildcard $(LOCAL_PATH)/$(LIBRARY_PATH)))
include $(CLEAR_VARS) include $(CLEAR_VARS)
LOCAL_MODULE := magisk-rs LOCAL_MODULE := magisk-rs
LOCAL_EXPORT_C_INCLUDES := src/core/include LOCAL_EXPORT_C_INCLUDES := src/core/include
LOCAL_SRC_FILES := $(LIBRARY_PATH) LOCAL_LIB = ../out/$(TARGET_ARCH_ABI)/libmagisk-rs.a
ifneq (,$(wildcard $(LOCAL_PATH)/$(LOCAL_LIB)))
LOCAL_SRC_FILES := $(LOCAL_LIB)
include $(PREBUILT_STATIC_LIBRARY) include $(PREBUILT_STATIC_LIBRARY)
else
include $(BUILD_STATIC_LIBRARY)
endif endif
LIBRARY_PATH = ../out/$(TARGET_ARCH_ABI)/libmagiskboot-rs.a
ifneq (,$(wildcard $(LOCAL_PATH)/$(LIBRARY_PATH)))
include $(CLEAR_VARS) include $(CLEAR_VARS)
LOCAL_MODULE := boot-rs LOCAL_MODULE := boot-rs
LOCAL_SRC_FILES := $(LIBRARY_PATH) LOCAL_LIB = ../out/$(TARGET_ARCH_ABI)/libmagiskboot-rs.a
ifneq (,$(wildcard $(LOCAL_PATH)/$(LOCAL_LIB)))
LOCAL_SRC_FILES := $(LOCAL_LIB)
include $(PREBUILT_STATIC_LIBRARY) include $(PREBUILT_STATIC_LIBRARY)
else
include $(BUILD_STATIC_LIBRARY)
endif endif
LIBRARY_PATH = ../out/$(TARGET_ARCH_ABI)/libmagiskinit-rs.a
ifneq (,$(wildcard $(LOCAL_PATH)/$(LIBRARY_PATH)))
include $(CLEAR_VARS) include $(CLEAR_VARS)
LOCAL_MODULE := init-rs LOCAL_MODULE := init-rs
LOCAL_SRC_FILES := $(LIBRARY_PATH) LOCAL_LIB = ../out/$(TARGET_ARCH_ABI)/libmagiskinit-rs.a
ifneq (,$(wildcard $(LOCAL_PATH)/$(LOCAL_LIB)))
LOCAL_SRC_FILES := $(LOCAL_LIB)
include $(PREBUILT_STATIC_LIBRARY) include $(PREBUILT_STATIC_LIBRARY)
else
include $(BUILD_STATIC_LIBRARY)
endif endif
LIBRARY_PATH = ../out/$(TARGET_ARCH_ABI)/libmagiskpolicy-rs.a
ifneq (,$(wildcard $(LOCAL_PATH)/$(LIBRARY_PATH)))
include $(CLEAR_VARS) include $(CLEAR_VARS)
LOCAL_MODULE := policy-rs LOCAL_MODULE := policy-rs
LOCAL_SRC_FILES := $(LIBRARY_PATH) LOCAL_LIB = ../out/$(TARGET_ARCH_ABI)/libmagiskpolicy-rs.a
ifneq (,$(wildcard $(LOCAL_PATH)/$(LOCAL_LIB)))
LOCAL_SRC_FILES := $(LOCAL_LIB)
include $(PREBUILT_STATIC_LIBRARY) include $(PREBUILT_STATIC_LIBRARY)
else
include $(BUILD_STATIC_LIBRARY)
endif endif

View File

@ -18,22 +18,15 @@ LOCAL_SRC_FILES := \
core/applets.cpp \ core/applets.cpp \
core/magisk.cpp \ core/magisk.cpp \
core/daemon.cpp \ core/daemon.cpp \
core/bootstages.cpp \
core/socket.cpp \
core/db.cpp \
core/package.cpp \
core/scripting.cpp \ core/scripting.cpp \
core/selinux.cpp \ core/sqlite.cpp \
core/module.cpp \ core/module.cpp \
core/thread.cpp \ core/thread.cpp \
core/core-rs.cpp \ core/core-rs.cpp \
core/resetprop/resetprop.cpp \ core/resetprop/resetprop.cpp \
core/su/su.cpp \ core/su/su.cpp \
core/su/connect.cpp \ core/su/connect.cpp \
core/su/pts.cpp \
core/su/su_daemon.cpp \
core/zygisk/entry.cpp \ core/zygisk/entry.cpp \
core/zygisk/main.cpp \
core/zygisk/module.cpp \ core/zygisk/module.cpp \
core/zygisk/hook.cpp \ core/zygisk/hook.cpp \
core/deny/cli.cpp \ core/deny/cli.cpp \
@ -68,18 +61,16 @@ LOCAL_STATIC_LIBRARIES := \
libinit-rs libinit-rs
LOCAL_SRC_FILES := \ LOCAL_SRC_FILES := \
init/init.cpp \
init/mount.cpp \ init/mount.cpp \
init/rootdir.cpp \ init/rootdir.cpp \
init/getinfo.cpp \ init/getinfo.cpp \
init/twostage.cpp \
init/selinux.cpp \
init/init-rs.cpp init/init-rs.cpp
LOCAL_LDFLAGS := -static LOCAL_LDFLAGS := -static
ifdef B_CRT0 ifdef B_CRT0
LOCAL_STATIC_LIBRARIES += crt0 LOCAL_STATIC_LIBRARIES += crt0
LOCAL_LDFLAGS += -Wl,--defsym=vfprintf=tiny_vfprintf
endif endif
include $(BUILD_EXECUTABLE) include $(BUILD_EXECUTABLE)
@ -94,8 +85,6 @@ LOCAL_STATIC_LIBRARIES := \
libbase \ libbase \
liblzma \ liblzma \
liblz4 \ liblz4 \
libbz2 \
libz \
libzopfli \ libzopfli \
libboot-rs libboot-rs
@ -126,8 +115,6 @@ LOCAL_STATIC_LIBRARIES := \
libpolicy \ libpolicy \
libpolicy-rs libpolicy-rs
LOCAL_SRC_FILES := sepolicy/main.cpp
include $(BUILD_EXECUTABLE) include $(BUILD_EXECUTABLE)
endif endif
@ -169,6 +156,7 @@ LOCAL_SRC_FILES := \
sepolicy/policy-rs.cpp sepolicy/policy-rs.cpp
include $(BUILD_STATIC_LIBRARY) include $(BUILD_STATIC_LIBRARY)
include src/Android-rs.mk CWD := $(LOCAL_PATH)
include src/base/Android.mk include $(CWD)/Android-rs.mk
include src/external/Android.mk include $(CWD)/base/Android.mk
include $(CWD)/external/Android.mk

View File

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

490
native/src/Cargo.lock generated
View File

@ -1,20 +1,29 @@
# This file is automatically @generated by Cargo. # This file is automatically @generated by Cargo.
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 version = 4
[[package]]
name = "anstyle"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
[[package]] [[package]]
name = "argh" name = "argh"
version = "0.1.12" version = "0.1.13"
source = "git+https://github.com/google/argh.git?rev=1c632b046d084e7bde86b82dfc969b30b4647c8c#1c632b046d084e7bde86b82dfc969b30b4647c8c" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34ff18325c8a36b82f992e533ece1ec9f9a9db446bd1c14d4f936bac88fcd240"
dependencies = [ dependencies = [
"argh_derive", "argh_derive",
"argh_shared", "argh_shared",
"rust-fuzzy-search",
] ]
[[package]] [[package]]
name = "argh_derive" name = "argh_derive"
version = "0.1.12" version = "0.1.13"
source = "git+https://github.com/google/argh.git?rev=1c632b046d084e7bde86b82dfc969b30b4647c8c#1c632b046d084e7bde86b82dfc969b30b4647c8c" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adb7b2b83a50d329d5d8ccc620f5c7064028828538bdf5646acd60dc1f767803"
dependencies = [ dependencies = [
"argh_shared", "argh_shared",
"proc-macro2", "proc-macro2",
@ -24,17 +33,15 @@ dependencies = [
[[package]] [[package]]
name = "argh_shared" name = "argh_shared"
version = "0.1.12" version = "0.1.13"
source = "git+https://github.com/google/argh.git?rev=1c632b046d084e7bde86b82dfc969b30b4647c8c#1c632b046d084e7bde86b82dfc969b30b4647c8c" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ checksum = "a464143cc82dedcdc3928737445362466b7674b5db4e2eb8e869846d6d84f4f6"
"serde",
]
[[package]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.3.0" version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]] [[package]]
name = "base" name = "base"
@ -60,33 +67,48 @@ checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf"
[[package]] [[package]]
name = "base64ct" name = "base64ct"
version = "1.6.0" version = "1.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" checksum = "89e25b6adfb930f02d1981565a6e5d9c547ac15a96606256d3b59040e5cd4ca3"
[[package]]
name = "bit-set"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3"
dependencies = [
"bit-vec",
]
[[package]]
name = "bit-vec"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
[[package]] [[package]]
name = "block-buffer" name = "block-buffer"
version = "0.11.0-rc.0" version = "0.11.0-rc.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17092d478f4fadfb35a7e082f62e49f0907fdf048801d9d706277e34f9df8a78" checksum = "3fd016a0ddc7cb13661bf5576073ce07330a693f8608a1320b4e20561cc12cdc"
dependencies = [ dependencies = [
"crypto-common", "hybrid-array",
] ]
[[package]] [[package]]
name = "bytemuck" name = "bytemuck"
version = "1.16.3" version = "1.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "102087e286b4677862ea56cf8fc58bb2cdfa8725c40ffb80fe3a008eb7f2fc83" checksum = "b6b1fc10dbac614ebc03540c9dbd60e83887fda27794998c6528f1782047d540"
dependencies = [ dependencies = [
"bytemuck_derive", "bytemuck_derive",
] ]
[[package]] [[package]]
name = "bytemuck_derive" name = "bytemuck_derive"
version = "1.7.0" version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ee891b04274a59bd38b412188e24b849617b2e45a0fd8d057deb63e7403761b" checksum = "7ecc273b49b3205b83d648f0690daa588925572cc5063745bfe547fe7ec8e1a1"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -101,9 +123,12 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.1.7" version = "1.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26a5c3fd7bfa1ce3897a3a3501d362b2d87b7f2583ebcb4a949ec25911025cbc" checksum = "8e3a13707ac958681c13b39b458c073d0d9bc8a22cb1b2f4c8e55eb72c13f362"
dependencies = [
"shlex",
]
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
@ -111,6 +136,32 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "4.5.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8aa86934b44c19c50f87cc2790e19f54f7a67aedb64101c2e1a2e5ecfb73944"
dependencies = [
"clap_builder",
]
[[package]]
name = "clap_builder"
version = "4.5.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2414dbb2dd0695280da6ea9261e327479e9d37b0630f6b53ba2a11c60c679fd9"
dependencies = [
"anstyle",
"clap_lex",
"strsim",
]
[[package]]
name = "clap_lex"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
[[package]] [[package]]
name = "codespan-reporting" name = "codespan-reporting"
version = "0.11.1" version = "0.11.1"
@ -123,24 +174,24 @@ dependencies = [
[[package]] [[package]]
name = "const-oid" name = "const-oid"
version = "0.10.0-rc.0" version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9adcf94f05e094fca3005698822ec791cb4433ced416afda1c5ca3b8dfc05a2f" checksum = "0dabb6555f92fb9ee4140454eb5dcd14c7960e1225c6d1a6cc361f032947713e"
[[package]] [[package]]
name = "const_format" name = "const_format"
version = "0.2.32" version = "0.2.34"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3a214c7af3d04997541b18d432afaff4c455e79e2029079647e72fc2bd27673" checksum = "126f97965c8ad46d6d9163268ff28432e8f6a1196a55578867832e3049df63dd"
dependencies = [ dependencies = [
"const_format_proc_macros", "const_format_proc_macros",
] ]
[[package]] [[package]]
name = "const_format_proc_macros" name = "const_format_proc_macros"
version = "0.2.32" version = "0.2.34"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7f6ff08fd20f4f299298a28e2dfa8a8ba1036e6cd2460ac1de7b425d76f2500" checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -149,49 +200,62 @@ dependencies = [
[[package]] [[package]]
name = "cpufeatures" name = "cpufeatures"
version = "0.2.12" version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
dependencies = [ dependencies = [
"libc", "libc",
] ]
[[package]] [[package]]
name = "crypto-bigint" name = "crypto-bigint"
version = "0.6.0-rc.2" version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e43027691f1c055da3da4f7d96af09fcec420d435d5616e51f29afd0811c56a7" checksum = "96272c2ff28b807e09250b180ad1fb7889a3258f7455759b5c3c58b719467130"
dependencies = [ dependencies = [
"hybrid-array", "hybrid-array",
"num-traits", "num-traits",
"rand_core", "rand_core",
"serdect",
"subtle", "subtle",
"zeroize", "zeroize",
] ]
[[package]] [[package]]
name = "crypto-common" name = "crypto-common"
version = "0.2.0-rc.0" version = "0.2.0-rc.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c070b79a496dccd931229780ad5bbedd535ceff6c3565605a8e440e18e1aa2b" checksum = "b0b8ce8218c97789f16356e7896b3714f26c2ee1079b79c0b7ae7064bb9089fa"
dependencies = [ dependencies = [
"getrandom", "getrandom",
"hybrid-array", "hybrid-array",
"rand_core", "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]] [[package]]
name = "cxx" name = "cxx"
version = "1.0.124" version = "1.0.137"
dependencies = [ dependencies = [
"cc", "cc",
"cxxbridge-cmd",
"cxxbridge-flags", "cxxbridge-flags",
"cxxbridge-macro", "cxxbridge-macro",
"foldhash",
] ]
[[package]] [[package]]
name = "cxx-gen" name = "cxx-gen"
version = "0.7.124" version = "0.7.137"
dependencies = [ dependencies = [
"codespan-reporting", "codespan-reporting",
"proc-macro2", "proc-macro2",
@ -200,23 +264,35 @@ dependencies = [
] ]
[[package]] [[package]]
name = "cxxbridge-flags" name = "cxxbridge-cmd"
version = "1.0.124" version = "1.0.137"
[[package]]
name = "cxxbridge-macro"
version = "1.0.124"
dependencies = [ dependencies = [
"clap",
"codespan-reporting",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn",
] ]
[[package]]
name = "cxxbridge-flags"
version = "1.0.137"
[[package]]
name = "cxxbridge-macro"
version = "1.0.137"
dependencies = [
"proc-macro2",
"quote",
"rustversion",
"syn",
]
[[package]] [[package]]
name = "der" name = "der"
version = "0.8.0-rc.0" version = "0.8.0-rc.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05d9c07d3bd80cf0935ce478d07edf7e7a5b158446757f988f3e62082227b700" checksum = "82db698b33305f0134faf590b9d1259dc171b5481ac41d5c8146c3b3ee7d4319"
dependencies = [ dependencies = [
"const-oid", "const-oid",
"der_derive", "der_derive",
@ -227,9 +303,18 @@ dependencies = [
[[package]] [[package]]
name = "der_derive" name = "der_derive"
version = "0.8.0-rc.0" version = "0.8.0-rc.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c46c0d3c8dba679a95cc7caf42fe6220338e28c9d7c09e0a458e6f6156c06e7" checksum = "211bea8bb45f5f61bc857104606913ef8ac8b5ec698143aa2aa96a7ffdc94991"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "derive"
version = "0.0.0"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -250,9 +335,9 @@ dependencies = [
[[package]] [[package]]
name = "ecdsa" name = "ecdsa"
version = "0.17.0-pre.7" version = "0.17.0-pre.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fad051af2b2d2f356d716138c76775929be913deb5b4ea217cd2613535936bef" checksum = "7e62f2041a28c40b8884b79fbd19bc7457d76c6397767831e9ff4029fc0473a9"
dependencies = [ dependencies = [
"der", "der",
"digest", "digest",
@ -264,9 +349,9 @@ dependencies = [
[[package]] [[package]]
name = "elliptic-curve" name = "elliptic-curve"
version = "0.14.0-pre.6" version = "0.14.0-rc.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ed8e96bb573517f42470775f8ef1b9cd7595de52ba7a8e19c48325a92c8fe4f" checksum = "cc43715037532dc2d061e5c97e81b684c28993d52a4fa4eb7d2ce2826d78f2f2"
dependencies = [ dependencies = [
"base16ct", "base16ct",
"crypto-bigint", "crypto-bigint",
@ -291,9 +376,9 @@ checksum = "784a4df722dc6267a04af36895398f59d21d07dce47232adf31ec0ff2fa45e67"
[[package]] [[package]]
name = "ff" name = "ff"
version = "0.13.0" version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393"
dependencies = [ dependencies = [
"rand_core", "rand_core",
"subtle", "subtle",
@ -301,9 +386,15 @@ dependencies = [
[[package]] [[package]]
name = "flagset" name = "flagset"
version = "0.4.6" version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3ea1ec5f8307826a5b71094dd91fc04d4ae75d5709b20ad351c7fb4815c86ec" checksum = "b7ac824320a75a52197e8f2d787f6a38b6718bb6897a35142d749af3c0e8f4fe"
[[package]]
name = "foldhash"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]] [[package]]
name = "getrandom" name = "getrandom"
@ -347,53 +438,59 @@ dependencies = [
[[package]] [[package]]
name = "hybrid-array" name = "hybrid-array"
version = "0.2.0-rc.9" version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d306b679262030ad8813a82d4915fc04efff97776e4db7f8eb5137039d56400" checksum = "f2d35805454dc9f8662a98d6d61886ffe26bd465f5960e0e55345c70d5c0d2a9"
dependencies = [ dependencies = [
"typenum", "typenum",
"zeroize", "zeroize",
] ]
[[package]] [[package]]
name = "lazy_static" name = "libbz2-rs-sys"
version = "1.5.0" version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" checksum = "0864a00c8d019e36216b69c2c4ce50b83b7bd966add3cf5ba554ec44f8bebcf5"
dependencies = [ dependencies = [
"spin", "libc",
] ]
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.155" version = "0.2.171"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6"
[[package]] [[package]]
name = "libm" name = "libz-rs-sys"
version = "0.2.8" version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" checksum = "6489ca9bd760fe9642d7644e827b0c9add07df89857b0416ee15c1cc1a3b8c5a"
dependencies = [
"zlib-rs",
]
[[package]] [[package]]
name = "log" name = "log"
version = "0.4.22" version = "0.4.27"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
[[package]] [[package]]
name = "magisk" name = "magisk"
version = "0.0.0" version = "0.0.0"
dependencies = [ dependencies = [
"base", "base",
"bit-set",
"bytemuck", "bytemuck",
"cxx", "cxx",
"cxx-gen", "cxx-gen",
"derive",
"num-derive", "num-derive",
"num-traits", "num-traits",
"pb-rs", "pb-rs",
"quick-protobuf", "quick-protobuf",
"thiserror",
] ]
[[package]] [[package]]
@ -402,6 +499,7 @@ version = "0.0.0"
dependencies = [ dependencies = [
"argh", "argh",
"base", "base",
"block-buffer",
"bytemuck", "bytemuck",
"byteorder", "byteorder",
"cxx", "cxx",
@ -409,6 +507,8 @@ dependencies = [
"der", "der",
"digest", "digest",
"fdt", "fdt",
"libbz2-rs-sys",
"libz-rs-sys",
"num-traits", "num-traits",
"p256", "p256",
"p384", "p384",
@ -416,6 +516,7 @@ dependencies = [
"pb-rs", "pb-rs",
"quick-protobuf", "quick-protobuf",
"rsa", "rsa",
"sec1",
"sha1", "sha1",
"sha2", "sha2",
"size", "size",
@ -436,6 +537,7 @@ dependencies = [
name = "magiskpolicy" name = "magiskpolicy"
version = "0.0.0" version = "0.0.0"
dependencies = [ dependencies = [
"argh",
"base", "base",
"cxx", "cxx",
"cxx-gen", "cxx-gen",
@ -463,23 +565,6 @@ dependencies = [
"minimal-lexical", "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]] [[package]]
name = "num-derive" name = "num-derive"
version = "0.4.2" version = "0.4.2"
@ -491,26 +576,6 @@ dependencies = [
"syn", "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]] [[package]]
name = "num-traits" name = "num-traits"
version = "0.2.19" version = "0.2.19"
@ -518,14 +583,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [ dependencies = [
"autocfg", "autocfg",
"libm",
] ]
[[package]] [[package]]
name = "p256" name = "p256"
version = "0.14.0-pre.1" version = "0.14.0-pre.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c32c18a74d9dda1314d2f945fb3e274848822f63f264a9e4d3f783e29b3bc1f" checksum = "71f3fd64a9cad9c26ed7f734b152196d5e56376b9957c832bcca0de48a708080"
dependencies = [ dependencies = [
"ecdsa", "ecdsa",
"elliptic-curve", "elliptic-curve",
@ -535,9 +599,9 @@ dependencies = [
[[package]] [[package]]
name = "p384" name = "p384"
version = "0.14.0-pre.1" version = "0.14.0-pre.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99acc40dbfad9cc3dc102828f5678c8ca14f0cbf3a1f56f74c2875b5a84427af" checksum = "1e19554fe6ee269c860a0f231cbba714e5cbef26a927c75d8e30ac9040a4b32e"
dependencies = [ dependencies = [
"ecdsa", "ecdsa",
"elliptic-curve", "elliptic-curve",
@ -547,9 +611,9 @@ dependencies = [
[[package]] [[package]]
name = "p521" name = "p521"
version = "0.14.0-pre.1" version = "0.14.0-pre.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ec5d919bea930a34a522bb1c95a89f559925deab255db2c2ffa174fc48df664" checksum = "957df9b5e6a7542f6430ec5187a4cb66d8498946c38b23fd14562bce6e32e6de"
dependencies = [ dependencies = [
"base16ct", "base16ct",
"ecdsa", "ecdsa",
@ -563,7 +627,7 @@ dependencies = [
[[package]] [[package]]
name = "pb-rs" name = "pb-rs"
version = "0.10.0" 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 = [ dependencies = [
"log", "log",
"nom", "nom",
@ -571,18 +635,18 @@ dependencies = [
[[package]] [[package]]
name = "pem-rfc7468" name = "pem-rfc7468"
version = "1.0.0-rc.1" version = "1.0.0-rc.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6c1cde4770761bf6bd336f947b9ac1fe700b0a4ec5867cf66cf08597fe89e8c" checksum = "c2dfbfa5c6f0906884269722c5478e72fd4d6c0e24fe600332c6d62359567ce1"
dependencies = [ dependencies = [
"base64ct", "base64ct",
] ]
[[package]] [[package]]
name = "pkcs1" name = "pkcs1"
version = "0.8.0-rc.0" version = "0.8.0-rc.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d2f4c73d459a85331915baebd5082dce5ee8ef16fd9a1ca75559ac91e66a9ee" checksum = "226eb25e2c46c166ce498ac0f606ac623142d640064879ff445938accddff1e2"
dependencies = [ dependencies = [
"der", "der",
"pkcs8", "pkcs8",
@ -591,23 +655,14 @@ dependencies = [
[[package]] [[package]]
name = "pkcs8" name = "pkcs8"
version = "0.11.0-rc.0" version = "0.11.0-rc.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66180445f1dce533620a7743467ef85fe1c5e80cdaf7c7053609d7a2fbcdae20" checksum = "f22636de7c995e997ed3d8d2949b7414d4faba3efa7312a6c0e75d875a14bdd4"
dependencies = [ dependencies = [
"der", "der",
"spki", "spki",
] ]
[[package]]
name = "ppv-lite86"
version = "0.2.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
dependencies = [
"zerocopy",
]
[[package]] [[package]]
name = "primefield" name = "primefield"
version = "0.14.0-pre.0" version = "0.14.0-pre.0"
@ -616,18 +671,18 @@ checksum = "d3f2ce0fa9cccdaf216230d151ce51a15298aef50ad76081a830128ecbc6428a"
[[package]] [[package]]
name = "primeorder" name = "primeorder"
version = "0.14.0-pre.1" version = "0.14.0-pre.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9bed0c431186675ad845922b903d28c7faa2b634a6d130fb7b50bb289f5a4d52" checksum = "b794117b388378d55629f78f61e64e182baa200bf59c1a8205e0c46508ce5873"
dependencies = [ dependencies = [
"elliptic-curve", "elliptic-curve",
] ]
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.86" version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
@ -635,40 +690,20 @@ dependencies = [
[[package]] [[package]]
name = "quick-protobuf" name = "quick-protobuf"
version = "0.8.1" 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 = [ dependencies = [
"byteorder", "byteorder",
] ]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.36" version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [ dependencies = [
"proc-macro2", "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]] [[package]]
name = "rand_core" name = "rand_core"
version = "0.6.4" version = "0.6.4"
@ -690,15 +725,14 @@ dependencies = [
[[package]] [[package]]
name = "rsa" name = "rsa"
version = "0.10.0-pre.2" version = "0.10.0-pre.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57e864e43f5d003321ab452feea6450f9611d7be6726489b4ec051da34774c62" checksum = "e82e90f434676d49758cab5b3ff2cc1fd8f12bf7d79b70c6088bc5a4e7c63270"
dependencies = [ dependencies = [
"const-oid", "const-oid",
"crypto-bigint",
"crypto-primes",
"digest", "digest",
"num-bigint-dig",
"num-integer",
"num-traits",
"pkcs1", "pkcs1",
"pkcs8", "pkcs8",
"rand_core", "rand_core",
@ -710,10 +744,22 @@ dependencies = [
] ]
[[package]] [[package]]
name = "sec1" name = "rust-fuzzy-search"
version = "0.8.0-rc.0" version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32c98827dc6ed0ea1707286a3d14b4ad4e25e2643169cbf111568a46ff5b09f5" checksum = "a157657054ffe556d8858504af8a672a054a6e0bd9e8ee531059100c0fa11bb2"
[[package]]
name = "rustversion"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2"
[[package]]
name = "sec1"
version = "0.8.0-rc.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1988446eff153796413a73669dfaa4caa3f5ce8b25fac89e3821a39c611772e"
dependencies = [ dependencies = [
"base16ct", "base16ct",
"der", "der",
@ -725,24 +771,34 @@ dependencies = [
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.204" version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.204" version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn",
] ]
[[package]]
name = "serdect"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f42f67da2385b51a5f9652db9c93d78aeaf7610bf5ec366080b6de810604af53"
dependencies = [
"base16ct",
"serde",
]
[[package]] [[package]]
name = "sha1" name = "sha1"
version = "0.11.0-pre.4" version = "0.11.0-pre.4"
@ -765,6 +821,12 @@ dependencies = [
"digest", "digest",
] ]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]] [[package]]
name = "signature" name = "signature"
version = "2.3.0-pre.4" version = "2.3.0-pre.4"
@ -777,32 +839,26 @@ dependencies = [
[[package]] [[package]]
name = "size" name = "size"
version = "0.4.1" version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fed904c7fb2856d868b92464fc8fa597fce366edea1a9cbfaa8cb5fe080bd6d" checksum = "1b6709c7b6754dca1311b3c73e79fcce40dd414c782c66d88e8823030093b02b"
[[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"
[[package]] [[package]]
name = "spki" name = "spki"
version = "0.8.0-rc.0" version = "0.8.0-rc.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee3fb1c675852398475928637b3ebbdd7e1d0cc24d27b3bbc81788b4eb51e310" checksum = "37ac66481418fd7afdc584adcf3be9aa572cf6c2858814494dc2a01755f050bc"
dependencies = [ dependencies = [
"base64ct", "base64ct",
"der", "der",
] ]
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]] [[package]]
name = "subtle" name = "subtle"
version = "2.6.1" version = "2.6.1"
@ -811,9 +867,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.72" version = "2.0.100"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -831,18 +887,18 @@ dependencies = [
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.63" version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
dependencies = [ dependencies = [
"thiserror-impl", "thiserror-impl",
] ]
[[package]] [[package]]
name = "thiserror-impl" name = "thiserror-impl"
version = "1.0.63" version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -851,9 +907,9 @@ dependencies = [
[[package]] [[package]]
name = "tls_codec" name = "tls_codec"
version = "0.4.1" version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e78c9c330f8c85b2bae7c8368f2739157db9991235123aa1b15ef9502bfb6a" checksum = "0de2e01245e2bb89d6f05801c564fa27624dbd7b1846859876c7dad82e90bf6b"
dependencies = [ dependencies = [
"tls_codec_derive", "tls_codec_derive",
"zeroize", "zeroize",
@ -861,9 +917,9 @@ dependencies = [
[[package]] [[package]]
name = "tls_codec_derive" name = "tls_codec_derive"
version = "0.4.1" version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d9ef545650e79f30233c0003bcc2504d7efac6dad25fca40744de773fe2049c" checksum = "2d2e76690929402faae40aebdda620a2c0e25dd6d3b9afe48867dfd95991f4bd"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -872,27 +928,27 @@ dependencies = [
[[package]] [[package]]
name = "typenum" name = "typenum"
version = "1.17.0" version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.12" version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]] [[package]]
name = "unicode-width" name = "unicode-width"
version = "0.1.13" version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
[[package]] [[package]]
name = "unicode-xid" name = "unicode-xid"
version = "0.2.4" version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
[[package]] [[package]]
name = "wasi" name = "wasi"
@ -984,8 +1040,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]] [[package]]
name = "x509-cert" name = "x509-cert"
version = "0.3.0-pre" version = "0.3.0-pre.0"
source = "git+https://github.com/RustCrypto/formats.git?rev=9c0e851c6db9c2c8a2601840d46375afde2663fb#9c0e851c6db9c2c8a2601840d46375afde2663fb" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2db382aa43c1fb5c419a960f72c3847ab0f383f635fc2e25f0bd6c5fb94371d1"
dependencies = [ dependencies = [
"const-oid", "const-oid",
"der", "der",
@ -993,27 +1050,6 @@ dependencies = [
"tls_codec", "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]] [[package]]
name = "zeroize" name = "zeroize"
version = "1.8.1" version = "1.8.1"
@ -1033,3 +1069,9 @@ dependencies = [
"quote", "quote",
"syn", "syn",
] ]
[[package]]
name = "zlib-rs"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "868b928d7949e09af2f6086dfc1e01936064cc7a819253bce650d4e2a2d63ba8"

View File

@ -1,48 +1,53 @@
[workspace] [workspace]
exclude = ["external"] exclude = ["external"]
members = ["base", "boot", "core", "init", "sepolicy"] members = ["base", "boot", "core", "core/derive", "init", "sepolicy"]
resolver = "2" resolver = "2"
[workspace.package]
version = "0.0.0"
edition = "2024"
[workspace.dependencies] [workspace.dependencies]
cxx = { path = "external/cxx-rs" } cxx = { path = "external/cxx-rs" }
cxx-gen = { path = "external/cxx-rs/gen/lib" } cxx-gen = { path = "external/cxx-rs/gen/lib" }
libc = "0.2" libc = "0.2.171"
cfg-if = "1.0" cfg-if = "1.0.0"
num-traits = "0.2" num-traits = "0.2.19"
num-derive = "0.4" num-derive = "0.4.2"
thiserror = "1.0" thiserror = "2.0.12"
byteorder = "1" byteorder = "1.5.0"
size = "0.4" size = "0.5.0"
sha1 = "0.11.0-pre.4" 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" sha2 = "=0.11.0-pre.4"
digest = "0.11.0-pre.9" digest = "=0.11.0-pre.9"
p256 = "0.14.0-pre.1" p256 = "0.14.0-pre.2"
p384 = "0.14.0-pre.1" p384 = "0.14.0-pre.2"
p521 = "0.14.0-pre.1" p521 = "0.14.0-pre.2"
rsa = "0.10.0-pre.2" rsa = "0.10.0-pre.4"
#x509-cert = "0.3" x509-cert = "0.3.0-pre.0"
der = "0.8.0-rc.0" der = "0.8.0-rc.1"
bytemuck = "1.16"
fdt = "0.1"
const_format = "0.2"
[workspace.dependencies.argh] # Pin version to prevent cargo update breaking builds
git = "https://github.com/google/argh.git" block-buffer = "=0.11.0-rc.3"
rev = "1c632b046d084e7bde86b82dfc969b30b4647c8c" sec1 = "=0.8.0-rc.3"
default-features = false
[workspace.dependencies.pb-rs] [patch.crates-io]
git = "https://github.com/tafia/quick-protobuf.git" pb-rs = { git = "https://github.com/tafia/quick-protobuf.git" }
rev = "2f37d5a65504de7d716b5b28fd82219501a901a9" quick-protobuf = { git = "https://github.com/tafia/quick-protobuf.git" }
default-features = false
[workspace.dependencies.quick-protobuf]
git = "https://github.com/tafia/quick-protobuf.git"
rev = "2f37d5a65504de7d716b5b28fd82219501a901a9"
[workspace.dependencies.x509-cert]
git = "https://github.com/RustCrypto/formats.git"
rev = "9c0e851c6db9c2c8a2601840d46375afde2663fb"
[profile.dev] [profile.dev]
opt-level = "z" opt-level = "z"
@ -54,3 +59,4 @@ opt-level = "z"
lto = "fat" lto = "fat"
codegen-units = 1 codegen-units = 1
panic = "abort" panic = "abort"
strip = true

View File

@ -1,7 +1,7 @@
[package] [package]
name = "base" name = "base"
version = "0.0.0" version.workspace = true
edition = "2021" edition.workspace = true
[lib] [lib]
path = "lib.rs" 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"] #[path = "../include/codegen.rs"]
mod gen; mod codegen;
fn main() { fn main() {
gen_cxx_binding("base-rs"); gen_cxx_binding("base-rs");

View File

@ -1,14 +1,14 @@
use cxx::{ExternType, type_id};
use libc::c_char;
use std::borrow::Borrow;
use std::cmp::min; use std::cmp::min;
use std::ffi::{CStr, FromBytesWithNulError, OsStr}; use std::ffi::{CStr, FromBytesWithNulError, OsStr};
use std::fmt::{Arguments, Debug, Display, Formatter, Write}; use std::fmt::{Debug, Display, Formatter, Write};
use std::ops::{Deref, DerefMut}; use std::ops::Deref;
use std::os::unix::ffi::OsStrExt; use std::os::unix::ffi::OsStrExt;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::str::Utf8Error; use std::str::Utf8Error;
use std::{fmt, mem, slice, str}; use std::{fmt, mem, slice, str};
use cxx::{type_id, ExternType};
use libc::c_char;
use thiserror::Error; use thiserror::Error;
use crate::slice_from_ptr_mut; use crate::slice_from_ptr_mut;
@ -18,88 +18,81 @@ use crate::slice_from_ptr_mut;
// Several Utf8CStr types: // Several Utf8CStr types:
// //
// Utf8CStr: can only exist as reference, similar to &str // 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 // Utf8CStrBufRef: reference to a fixed sized buffer
// Utf8CStrBufArr<N>: fixed sized buffer allocated on the stack // 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 // In most cases, these are the types being used
// //
// &Utf8CStr: whenever a printable null terminated string is needed // &Utf8CStr: whenever a printable null terminated string is needed
// &mut dyn Utf8CStrWrite: whenever we need a buffer that only needs to support appending // &mut dyn Utf8CStrBuf: whenever we need a buffer that needs to support appending
// strings to the end, and has to be null terminated // 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 // &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 // in the result, and has to be null terminated
// //
// All types dereferences to &Utf8CStr. // All types dereferences to &Utf8CStr.
// Utf8CString, Utf8CStrBufRef, and Utf8CStrBufArr<N> implements Utf8CStrWrite. // Utf8CString, Utf8CStrBufRef, and Utf8CStrBufArr<N> implements Utf8CStrBuf.
// Utf8CStrBufRef and Utf8CStrBufArr<N> implements Utf8CStrBuf.
fn utf8_cstr_buf_append(buf: &mut dyn Utf8CStrBuf, s: &[u8]) -> usize { // Public helper functions
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
}
fn utf8_cstr_append_lossy(buf: &mut dyn Utf8CStrWrite, s: &[u8]) -> usize { pub mod buf {
let mut len = 0_usize; use super::{Utf8CStrBufArr, Utf8CStrBufRef, Utf8CString};
for chunk in s.utf8_chunks() {
len += buf.push_str(chunk.valid()); #[inline(always)]
if !chunk.invalid().is_empty() { pub fn dynamic(capacity: usize) -> Utf8CString {
len += buf.push_str(char::REPLACEMENT_CHARACTER.encode_utf8(&mut [0; 4])); 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 // Trait definitions
pub trait Utf8CStrWrite: pub trait Utf8CStrBuf: Write + AsRef<Utf8CStr> + Deref<Target = Utf8CStr> {
Write + AsRef<Utf8CStr> + AsMut<Utf8CStr> + Deref<Target = Utf8CStr> + DerefMut // The length of the string without the terminating null character.
{ // assert_true(len <= capacity - 1)
fn len(&self) -> usize; 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)] #[inline(always)]
fn is_empty(&self) -> bool { fn is_empty(&self) -> bool {
self.len() == 0 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 { pub trait StringExt {
fn nul_terminate(&mut self) -> &mut [u8]; fn nul_terminate(&mut self) -> &mut [u8];
} }
@ -132,50 +125,70 @@ impl StringExt for PathBuf {
} }
} }
#[derive(Default)]
pub struct Utf8CString(String); pub struct Utf8CString(String);
impl Utf8CString { impl Default for Utf8CString {
#[inline(always)] fn default() -> Self {
pub fn new() -> Self { Utf8CString::with_capacity(256)
Self::default()
} }
} }
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)] #[inline(always)]
fn as_utf8_cstr(&self) -> &Utf8CStr { fn as_ref(&self) -> &Utf8CStr {
// SAFETY: the internal string is always null terminated // SAFETY: the internal string is always null terminated
unsafe { mem::transmute(slice::from_raw_parts(self.0.as_ptr(), self.0.len() + 1)) } 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)] #[inline(always)]
fn len(&self) -> usize { fn len(&self) -> usize {
self.0.len() 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 { fn push_str(&mut self, s: &str) -> usize {
self.0.push_str(s); self.0.push_str(s);
self.0.nul_terminate(); self.0.nul_terminate();
s.len() s.len()
} }
#[inline(always)] fn capacity(&self) -> usize {
fn push_lossy(&mut self, s: &[u8]) -> usize { self.0.capacity()
utf8_cstr_append_lossy(self, s)
} }
fn clear(&mut self) { fn clear(&mut self) {
self.0.clear(); self.0.clear();
self.0.nul_terminate(); 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 { impl From<String> for Utf8CString {
@ -185,24 +198,15 @@ impl From<String> for Utf8CString {
} }
} }
// Implementations for Utf8CStrBuf impl From<&str> for Utf8CString {
fn from(value: &str) -> Self {
impl<T: Utf8CStrBuf> AsUtf8CStr for T { value.to_string().into()
#[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))) }
} }
}
#[inline(always)] impl Borrow<Utf8CStr> for Utf8CString {
fn as_utf8_cstr_mut(&mut self) -> &mut Utf8CStr { fn borrow(&self) -> &Utf8CStr {
// SAFETY: the internal buffer is always UTF-8 checked self.deref()
// 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))
}
} }
} }
@ -214,7 +218,7 @@ pub struct Utf8CStrBufRef<'a> {
impl<'a> Utf8CStrBufRef<'a> { impl<'a> Utf8CStrBufRef<'a> {
pub unsafe fn from_ptr(buf: *mut u8, len: usize) -> 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 // UTF-8 validated + null terminated buffer on the stack
pub struct Utf8CStrBufArr<const N: usize> { pub struct Utf8CStrBufArr<const N: usize> {
used: 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> { impl Default for Utf8CStrBufArr<4096> {
fn default() -> Self { fn default() -> Self {
Utf8CStrBufArr::<4096>::new() Utf8CStrBufArr::<4096>::new()
@ -310,27 +275,15 @@ impl Utf8CStr {
Self::from_cstr(CStr::from_bytes_with_nul(buf)?) Self::from_cstr(CStr::from_bytes_with_nul(buf)?)
} }
pub fn from_bytes_mut(buf: &mut [u8]) -> Result<&mut Utf8CStr, StrErr> { pub fn from_string(s: &mut String) -> &Utf8CStr {
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 {
let buf = s.nul_terminate(); let buf = s.nul_terminate();
// SAFETY: the null byte is explicitly added to the buffer // SAFETY: the null byte is explicitly added to the buffer
unsafe { mem::transmute(buf) } unsafe { mem::transmute(buf) }
} }
#[inline(always)] #[inline(always)]
pub unsafe fn from_bytes_unchecked(buf: &[u8]) -> &Utf8CStr { pub const unsafe fn from_bytes_unchecked(buf: &[u8]) -> &Utf8CStr {
mem::transmute(buf) unsafe { mem::transmute(buf) }
}
#[inline(always)]
pub unsafe fn from_bytes_unchecked_mut(buf: &mut [u8]) -> &mut Utf8CStr {
mem::transmute(buf)
} }
pub unsafe fn from_ptr<'a>(ptr: *const c_char) -> Result<&'a Utf8CStr, StrErr> { pub unsafe fn from_ptr<'a>(ptr: *const c_char) -> Result<&'a Utf8CStr, StrErr> {
@ -340,6 +293,13 @@ impl Utf8CStr {
Self::from_cstr(unsafe { CStr::from_ptr(ptr) }) Self::from_cstr(unsafe { CStr::from_ptr(ptr) })
} }
pub unsafe fn from_ptr_unchecked<'a>(ptr: *const c_char) -> &'a Utf8CStr {
unsafe {
let cstr = CStr::from_ptr(ptr);
Self::from_bytes_unchecked(cstr.to_bytes_with_nul())
}
}
#[inline(always)] #[inline(always)]
pub fn as_bytes_with_nul(&self) -> &[u8] { pub fn as_bytes_with_nul(&self) -> &[u8] {
&self.0 &self.0
@ -350,11 +310,6 @@ impl Utf8CStr {
self.0.as_ptr().cast() 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)] #[inline(always)]
pub fn as_cstr(&self) -> &CStr { pub fn as_cstr(&self) -> &CStr {
// SAFETY: Already validated as null terminated during construction // SAFETY: Already validated as null terminated during construction
@ -367,13 +322,6 @@ impl Utf8CStr {
// SAFETY: The length of the slice is at least 1 due to null termination check // 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)) } 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 { impl Deref for Utf8CStr {
@ -385,10 +333,19 @@ impl Deref for Utf8CStr {
} }
} }
impl DerefMut for Utf8CStr { impl ToOwned for Utf8CStr {
#[inline(always)] type Owned = Utf8CString;
fn deref_mut(&mut self) -> &mut Self::Target {
self.as_str_mut() 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
} }
} }
@ -405,96 +362,45 @@ macro_rules! const_assert_eq {
} }
// Assert ABI layout // Assert ABI layout
const_assert_eq!(mem::size_of::<&Utf8CStr>(), mem::size_of::<[usize; 2]>()); const_assert_eq!(size_of::<&Utf8CStr>(), size_of::<[usize; 2]>());
const_assert_eq!(mem::align_of::<&Utf8CStr>(), mem::align_of::<[usize; 2]>()); const_assert_eq!(align_of::<&Utf8CStr>(), align_of::<[usize; 2]>());
// File system path extensions types // File system path extensions types
#[repr(transparent)] #[repr(transparent)]
pub struct FsPath(Utf8CStr); pub struct FsPathFollow(Utf8CStr);
impl FsPath { impl AsRef<Utf8CStr> for FsPathFollow {
#[inline(always)] #[inline(always)]
pub fn from<T: AsRef<Utf8CStr> + ?Sized>(value: &T) -> &FsPath { fn as_ref(&self) -> &Utf8CStr {
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 {
&self.0 &self.0
} }
} }
impl DerefMut for FsPath { // impl<T: AsRef<Utf8CStr>> Deref<Target = Utf8CStr> for T { ... }
#[inline(always)] macro_rules! impl_cstr_deref {
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<'a> Deref for FsPathBuf<'a> {
type Target = FsPath;
fn deref(&self) -> &FsPath {
FsPath::from(&self.0)
}
}
impl<'a> DerefMut for FsPathBuf<'a> {
fn deref_mut(&mut self) -> &mut Self::Target {
FsPath::from_mut(&mut self.0)
}
}
// Boilerplate trait implementations
macro_rules! impl_str {
($( ($t:ty, $($g:tt)*) )*) => {$( ($( ($t:ty, $($g:tt)*) )*) => {$(
impl<$($g)*> AsRef<Utf8CStr> for $t { impl<$($g)*> Deref for $t {
type Target = Utf8CStr;
#[inline(always)] #[inline(always)]
fn as_ref(&self) -> &Utf8CStr { fn deref(&self) -> &Utf8CStr {
self 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 { impl<$($g)*> AsRef<str> for $t {
#[inline(always)] #[inline(always)]
fn as_ref(&self) -> &str { fn as_ref(&self) -> &str {
@ -555,7 +461,7 @@ macro_rules! impl_str {
self == other.as_cstr() 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)] #[inline(always)]
fn eq(&self, other: &T) -> bool { fn eq(&self, other: &T) -> bool {
self.as_bytes_with_nul() == other.as_ref().as_bytes_with_nul() self.as_bytes_with_nul() == other.as_ref().as_bytes_with_nul()
@ -564,16 +470,87 @@ macro_rules! impl_str {
)*} )*}
} }
impl_str!( impl_cstr_misc!(
(Utf8CStr,) (Utf8CStr,)
(FsPath,)
(FsPathBuf<'_>,)
(Utf8CStrBufRef<'_>,) (Utf8CStrBufRef<'_>,)
(Utf8CStrBufArr<N>, const N: usize) (Utf8CStrBufArr<N>, const N: usize)
(Utf8CString,) (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)*) )*) => {$( ($( ($t:ty, $($g:tt)*) )*) => {$(
impl<$($g)*> Write for $t { impl<$($g)*> Write for $t {
#[inline(always)] #[inline(always)]
@ -582,84 +559,27 @@ macro_rules! impl_str_write {
Ok(()) 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<'_>,) (Utf8CStrBufRef<'_>,)
(Utf8CStrBufArr<N>, const N: usize) (Utf8CStrBufArr<N>, const N: usize)
(Utf8CString,) (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_export]
macro_rules! cstr { macro_rules! cstr {
($($str:tt)*) => {{ ($str:expr) => {{
assert!( const NULL_STR: &str = $crate::const_format::concatcp!($str, "\0");
!($($str)*).bytes().any(|b| b == b'\0'),
"cstr argument contains embedded NUL bytes",
);
#[allow(unused_unsafe)] #[allow(unused_unsafe)]
unsafe { unsafe {
$crate::Utf8CStr::from_bytes_unchecked($crate::const_format::concatcp!($($str)*, "\0") $crate::Utf8CStr::from_bytes_unchecked(NULL_STR.as_bytes())
.as_bytes())
} }
}}; }};
} }
#[macro_export] #[macro_export]
macro_rules! raw_cstr { macro_rules! raw_cstr {
($($str:tt)*) => {{ ($str:expr) => {{ $crate::cstr!($str).as_ptr() }};
$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 // 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 std::os::fd::{BorrowedFd, OwnedFd, RawFd};
use cfg_if::cfg_if; use cfg_if::cfg_if;
@ -9,49 +8,55 @@ use libc::{c_char, mode_t};
use crate::files::map_file_at; use crate::files::map_file_at;
pub(crate) use crate::xwrap::*; pub(crate) use crate::xwrap::*;
use crate::{ use crate::{
clone_attr, cstr, fclone_attr, fd_path, map_fd, map_file, slice_from_ptr, CxxResultExt, CxxResultExt, Directory, OsResultStatic, Utf8CStr, clone_attr, cstr, fclone_attr, fd_path,
Directory, FsPath, Utf8CStr, Utf8CStrBufRef, map_fd, map_file, slice_from_ptr,
}; };
pub(crate) fn fd_path_for_cxx(fd: RawFd, buf: &mut [u8]) -> isize { 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) 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) .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 { unsafe extern "C" fn canonical_path(path: *const c_char, buf: *mut u8, bufsz: usize) -> isize {
unsafe {
match Utf8CStr::from_ptr(path) { match Utf8CStr::from_ptr(path) {
Ok(p) => { Ok(path) => {
let mut buf = Utf8CStrBufRef::from_ptr(buf, bufsz); let mut buf = cstr::buf::wrap_ptr(buf, bufsz);
FsPath::from(p) path.realpath(&mut buf)
.realpath(&mut buf) .log_cxx()
.map_or(-1, |_| buf.len() as isize) .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 { unsafe extern "C" fn mkdirs_for_cxx(path: *const c_char, mode: mode_t) -> i32 {
unsafe {
match Utf8CStr::from_ptr(path) { match Utf8CStr::from_ptr(path) {
Ok(p) => FsPath::from(p).mkdirs(mode).map_or(-1, |_| 0), Ok(path) => path.mkdirs(mode).map_or(-1, |_| 0),
Err(_) => -1, Err(_) => -1,
} }
}
} }
#[export_name = "rm_rf"] #[unsafe(export_name = "rm_rf")]
unsafe extern "C" fn rm_rf_for_cxx(path: *const c_char) -> bool { unsafe extern "C" fn rm_rf_for_cxx(path: *const c_char) -> bool {
unsafe {
match Utf8CStr::from_ptr(path) { match Utf8CStr::from_ptr(path) {
Ok(p) => FsPath::from(p).remove_all().is_ok(), Ok(path) => path.remove_all().is_ok(),
Err(_) => false, Err(_) => false,
} }
}
} }
#[no_mangle] #[unsafe(no_mangle)]
unsafe extern "C" fn frm_rf(fd: OwnedFd) -> bool { 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() Directory::try_from(fd)?.remove_all()
} }
inner(fd).is_ok() inner(fd).is_ok()
@ -77,12 +82,13 @@ 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, dirfd: RawFd,
path: *const c_char, path: *const c_char,
buf: *mut u8, buf: *mut u8,
bufsz: usize, bufsz: usize,
) -> isize { ) -> isize {
unsafe {
// readlinkat() may fail on x86 platform, returning random value // readlinkat() may fail on x86 platform, returning random value
// instead of number of bytes placed in buf (length of link) // instead of number of bytes placed in buf (length of link)
cfg_if! { cfg_if! {
@ -100,93 +106,75 @@ pub(crate) unsafe fn readlinkat_for_cxx(
} }
} }
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 { unsafe extern "C" fn cp_afc_for_cxx(src: *const c_char, dest: *const c_char) -> bool {
unsafe {
if let Ok(src) = Utf8CStr::from_ptr(src) { if let Ok(src) = Utf8CStr::from_ptr(src) {
if let Ok(dest) = Utf8CStr::from_ptr(dest) { if let Ok(dest) = Utf8CStr::from_ptr(dest) {
let src = FsPath::from(src); return src.copy_to(dest).log_cxx().is_ok();
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();
} }
} }
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 { unsafe extern "C" fn mv_path_for_cxx(src: *const c_char, dest: *const c_char) -> bool {
unsafe {
if let Ok(src) = Utf8CStr::from_ptr(src) { if let Ok(src) = Utf8CStr::from_ptr(src) {
if let Ok(dest) = Utf8CStr::from_ptr(dest) { if let Ok(dest) = Utf8CStr::from_ptr(dest) {
let src = FsPath::from(src); return src.move_to(dest).log_cxx().is_ok();
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();
} }
} }
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 { unsafe extern "C" fn link_path_for_cxx(src: *const c_char, dest: *const c_char) -> bool {
unsafe {
if let Ok(src) = Utf8CStr::from_ptr(src) { if let Ok(src) = Utf8CStr::from_ptr(src) {
if let Ok(dest) = Utf8CStr::from_ptr(dest) { if let Ok(dest) = Utf8CStr::from_ptr(dest) {
let src = FsPath::from(src); return src.link_to(dest).log_cxx().is_ok();
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();
} }
} }
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 { unsafe extern "C" fn clone_attr_for_cxx(src: *const c_char, dest: *const c_char) -> bool {
unsafe {
if let Ok(src) = Utf8CStr::from_ptr(src) { if let Ok(src) = Utf8CStr::from_ptr(src) {
if let Ok(dest) = Utf8CStr::from_ptr(dest) { if let Ok(dest) = Utf8CStr::from_ptr(dest) {
let src = FsPath::from(src); return clone_attr(src, dest).log_cxx().is_ok();
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();
} }
} }
false false
}
} }
#[export_name = "fclone_attr"] #[unsafe(export_name = "fclone_attr")]
unsafe extern "C" fn fclone_attr_for_cxx(a: RawFd, b: RawFd) -> bool { unsafe extern "C" fn fclone_attr_for_cxx(a: RawFd, b: RawFd) -> bool {
fclone_attr(a, b) fclone_attr(a, b).log_cxx().is_ok()
.log_cxx_with_msg(|w| w.write_str("fclone_attr failed"))
.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) { unsafe extern "C" fn str_new(this: &mut &Utf8CStr, s: *const u8, len: usize) {
unsafe {
*this = Utf8CStr::from_bytes(slice_from_ptr(s, len)).unwrap_or(cstr!("")); *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 { unsafe extern "C" fn str_ptr(this: &&Utf8CStr) -> *const u8 {
this.as_ptr().cast() this.as_ptr().cast()
} }
#[export_name = "cxx$utf8str$len"] #[unsafe(export_name = "cxx$utf8str$len")]
unsafe extern "C" fn str_len(this: &&Utf8CStr) -> usize { unsafe extern "C" fn str_len(this: &&Utf8CStr) -> usize {
this.len() 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

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

File diff suppressed because it is too large Load Diff

View File

@ -8,3 +8,4 @@
using rust::xpipe2; using rust::xpipe2;
using rust::fd_path; using rust::fd_path;
using kv_pairs = std::vector<std::pair<std::string, std::string>>;

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