Compare commits

...

129 Commits

Author SHA1 Message Date
LoveSy c6f0762510 Use pidfd_open for setns
which is more efficient on newer kernel
2024-05-20 03:26:55 +08:00
LoveSy 941a363c5a
Support waiting on non-exist prop 2024-05-18 13:55:33 +08:00
Arbri çoçka 2afcdc64a0 Update strings.xml sq 2024-05-18 13:52:31 +08:00
VD $ VD171 @ Priv8 3c66c4bbc5 Update PORTUGUESE translation 2024-05-18 13:52:14 +08:00
VD $ VD171 @ Priv8 9f5cd5e1cc Update PORTUGUESE translation 2024-05-18 13:52:14 +08:00
kubalav a35f2bb73b Update Slovak translation 2024-05-18 13:51:59 +08:00
topjohnwu 6cf00130f4 Check Magisk version instead of app version 2024-05-15 12:42:55 +08:00
topjohnwu 6c27ba6b88 Rename db entry name 2024-05-15 12:42:55 +08:00
vvb2060 dd3b9980e7 app: add safe mode config to menu 2024-05-15 12:42:55 +08:00
vvb2060 02e189a029 core: add safe mode config to db 2024-05-15 12:42:55 +08:00
topjohnwu 72b8d12ee4 Update development guide 2024-05-11 20:40:33 -07:00
topjohnwu eed03080c1 Update to ONDK r27.1 2024-05-09 09:42:40 -07:00
LoveSy 090cb4b0f9 Upgrade AGP to 8.4.0 2024-05-09 09:42:27 -07:00
topjohnwu 6f2c76b898 Fix build script 2024-05-09 02:19:24 -07:00
topjohnwu f61827cbec Switch rustup_wrapper to Rust implementation
For better Windows portability
2024-05-09 02:19:07 -07:00
topjohnwu 3f2264f2c7 Support rustup wrapper 2024-05-09 00:34:12 -07:00
topjohnwu c1cadf4bdc Update build.py to use pathlib 2024-05-09 00:31:41 -07:00
Rodrigo Martínez 0e56991369 Improve and add missing strings for Spanish 2024-04-29 22:23:02 -07:00
LoveSy 4dc1c59040 add missing `xz_dec_end` 2024-04-29 22:22:47 -07:00
topjohnwu 33b7b8b297 Update resetprop 2024-04-26 16:42:24 -07:00
topjohnwu e6af5ed460 Address Rust warnings 2024-04-26 16:28:46 -07:00
topjohnwu b678afa4b6 Update to ONDK r27.0
Co-authored-by: LoveSy <shana@zju.edu.cn>
2024-04-26 03:09:44 -07:00
WINZORT 4bac2df4e7
Improve turkish locales and add the missing strings 2024-04-18 02:14:21 -07:00
igor 50416eee09 Improve Portuguese translation 2024-04-18 02:13:50 -07:00
igor 73cf501d33 Improve Brazilian Portuguese translation 2024-04-18 02:13:50 -07:00
Hen_Ry d2b7907bed
Update german strings.xml 2024-04-18 02:13:28 -07:00
topjohnwu 99d5dd5ea8 Update crt0 2024-04-17 10:12:27 -07:00
cloudchamb3r 5fdb841fa8 Fix typo
Fix typo in values-ko/strings.xml
2024-04-17 09:18:14 -07:00
topjohnwu 7c88484d64 Fix #7988 2024-04-16 19:45:01 -07:00
topjohnwu b22b6a4204 Refactor cpio 2024-04-10 22:46:21 -07:00
topjohnwu 2a3d34c812 Fix mkdirs 2024-04-10 22:36:47 -07:00
topjohnwu c50ee722a1 Use memmem for finding needle in haystack 2024-04-10 14:57:44 -07:00
topjohnwu ffc1e38e48 Add 32 bit CI 2024-04-10 02:56:23 -07:00
topjohnwu 6219d5fcbf Update crt0 for 32 bit 2024-04-10 02:43:37 -07:00
topjohnwu 2e4440b702 Support 32-bit magiskboot 2024-04-09 19:34:14 -07:00
topjohnwu 0d9ec0931b Code cleanup 2024-04-08 23:00:59 -07:00
vvb2060 60e8415369 Make denylist work when zygisk is disabled
Co-authored-by: topjohnwu <topjohnwu@gmail.com>
2024-04-08 23:00:50 -07:00
LoveSy 652a26d5d9 Fix comment of sepolicy 2024-04-03 21:03:41 -07:00
topjohnwu f57839379a Update RustCrypto dependencies 2024-04-03 21:03:05 -07:00
LoveSy 36bd00a046 Add p521 to magiskboot 2024-04-03 21:03:05 -07:00
topjohnwu fb5ee86615 Install one single ABI in ramdisk 2024-03-31 22:01:22 -07:00
topjohnwu 30bf5c8448 Fix typos 2024-03-31 14:17:25 -07:00
topjohnwu 2051836a73 Remove unused code 2024-03-30 21:03:51 -07:00
topjohnwu 2cb0af1ff3 Move revert_unmount into Rust 2024-03-30 02:51:39 -07:00
topjohnwu a1b6568226 Implement preinit related features in Rust 2024-03-28 14:11:03 -07:00
topjohnwu 1eddbfd72c Use const_format for const strings 2024-03-26 18:03:40 -07:00
topjohnwu 21ed095601 Update crt0 2024-03-25 16:11:02 -07:00
Js0n 000a2e4d59 Upgrade AGP to 8.3.1 2024-03-22 17:02:18 -07:00
Js0n 7abe635de9 fix: AGP 8.3.X 2024-03-22 17:02:18 -07:00
topjohnwu 9a008c17ba Optimize for binary size 2024-03-22 16:53:44 -07:00
topjohnwu 08dbf728a4 Allow platform_app to access MagiskSU 2024-03-22 16:53:44 -07:00
topjohnwu 4670f762d3 Disable debug only features 2024-03-22 16:53:44 -07:00
topjohnwu efa49567fa Fix parsing logic for term and sterm 2024-03-21 18:17:28 -07:00
topjohnwu 0ffc4527a7 Better error reporting 2024-03-21 15:10:34 -07:00
topjohnwu dd9d43be96 Move sepolicy parsing error message into Rust 2024-03-21 14:07:28 -07:00
topjohnwu 865fca71a5 Optimize sepolicy rules
Close #7916

Co-authored-by: vvb2060 <vvb2060@gmail.com>
2024-03-21 01:51:35 -07:00
topjohnwu 6b4baa3bcd Change a little parsing handling 2024-03-21 00:04:09 -07:00
topjohnwu a9ee2d7d18 Fix xperm parsing logic 2024-03-20 23:13:54 -07:00
topjohnwu d654b9cb97 Several code cleanups in sepolicy 2024-03-20 23:09:22 -07:00
LoveSy 4d2921e742 Rewrite sepolicy statement parsing in Rust 2024-03-20 10:12:23 -07:00
vvb2060 ecc74d45d1 Let magic mount optional 2024-03-19 23:21:41 -07:00
vvb2060 5de597f079 No need to unshare 2024-03-19 23:21:41 -07:00
LoveSy 156b0e67ca No need extra tmpfs for worker 2024-03-19 23:21:41 -07:00
vvb2060 10069215f4 Rename dir name 2024-03-19 23:21:41 -07:00
LoveSy 92b305a389 Remove unnecessary mirror for magic mount
Mirror was previously used for accessing the original file during
magic mount when we are using a tmpfs to cover the target. However,
since we introduce atomic mount, we switch all tmpfs mount in
worker and then move to the target at once. It means that we can
still access the original file when we are constructing the tmpfs
mount point. Thus we no longer need mirror.
2024-03-19 23:21:41 -07:00
topjohnwu d20b30c771 Update libsepol
Close #7915
2024-03-19 02:54:01 -07:00
topjohnwu 83209b21ff Release new canary build 2024-03-19 00:51:07 -07:00
topjohnwu 81658d45f7 Support tar with files larger than 8GiB
Fix #7838
2024-03-14 16:54:46 -07:00
topjohnwu c951b208a1 Always update stub APK when upgrade 2024-03-14 14:31:02 -07:00
topjohnwu 050a073771 Make all I/O suspendable 2024-03-12 03:24:42 -07:00
topjohnwu 21d374214f Minor cleanup of DownloadEngine 2024-03-12 03:14:06 -07:00
LoveSy 19ea25a9d0
Upgrade AGP 2024-03-08 17:12:46 -08:00
topjohnwu dbf6e40dfe Ensure stub APK is expected
Fix #7884
2024-03-08 17:09:54 -08:00
topjohnwu d56f4fbc90 Fix stub on API 34 2024-03-08 15:57:49 -08:00
topjohnwu 73c3d741a7 Reorganize some code 2024-03-06 18:07:01 -08:00
pndwal 2b5fc75127
Update faq.md
Since system Safe Mode may activate without Magisk 'Safe Mode', this clarification will prevent users from erroneously concluding either that Safe Mode doesn't work or that modules are not the issue since 'Safe Mode' was apparently triggered...

Fixes this: #4624
2024-03-06 17:48:54 -08:00
osm0sis 991802ab82 Add no decompression flag to magiskboot split 2024-03-06 16:15:30 -08:00
WindowsFan9600 7f6b5305ba Improve Turkish language translation
Updated "reboot_download" string
2024-03-06 02:37:58 -08:00
canyie 825c6c4316 Reverse format template argument order 2024-03-06 01:39:16 -08:00
canyie f00408c793 Fix zygote restart monitor 2024-03-06 01:39:16 -08:00
topjohnwu a6ff3672af Update crt0 2024-03-04 16:42:25 -08:00
LoveSy 2290ddeb89 Fix segfault when sepolicy.rule has empty line 2024-03-02 06:15:45 -08:00
topjohnwu 74af79ad03 Update crt0 2024-03-02 05:57:48 -08:00
LoveSy b6c24a3a8a No more sony `init.real` tricks
Co-authored-by: canyie <a1364259@163.com>
Co-authored-by: vvb2060 <vvb2060@gmail.com>
2024-02-29 23:40:00 -08:00
LoveSy a8c2ae223a Avoid hexpatch /init for 2SI when possible
Previous we hexpatch /init from /system/bin/init to /data/magiskinit
to redirect the second stage init. However, some devices like sony
has /init that does not directly invoke /system/bin/init, and thus
the hexpatch fails.

In this patch, we further make use of AOSP `SwitchRoot` to help us
bind mount /data/magisk to /system/bin/init after `SwitchRoot`.

Two important assumption about 2SI are i) that the second stage init
is always /system/bin/init and ii) that the /sdcard (path after
`SwitchRoot`) is always a symlink to `/storage/self/primary`. When
these assumptions hold, during first stage init (before `SwitchRoot`)
we can bind mount magiskinit to /sdcard, and create a symlink
/storage/self/primary to /system/system/bin/init. By these steps,
during `SwitchRoot`, AOSP init will try to mount move /sdcard to
/system/sdcard. And /system/sdcard is symlink to /storage/self/primary,
it will try to mount move /sdcard to /storage/self/primary. And
/storage/self/primary in ramfs is now a symlink that points to
/system/system/bin/init, thus AOSP will try to mount move /sdcard
(which is a bind mount to magiskinit) to /system/system/bin/init.
After chroot done by AOSP init, we then have a magiskinit bind mount
on /system/bin/init, which is the second stage init.

An edge case is that some devices (like meizu) use 2SI but
does not switch root. In this case, they must already have a /sdcard
in the ramfs, thus we can check if /sdcard exists and fallback to
hexpatch.
2024-02-29 23:40:00 -08:00
topjohnwu 953d44302c Remove ancient NDK binaries 2024-02-29 23:26:58 -08:00
topjohnwu 24e46a5971 Build magiskboot with crt0 2024-02-29 02:36:05 -08:00
topjohnwu b1297c4192 Less usage of C stdio 2024-02-28 15:52:03 -08:00
topjohnwu 9ae328fd84 Further reduce code size 2024-02-28 11:19:56 -08:00
topjohnwu 625a1d6f44 Remove seek support from streams 2024-02-28 11:07:53 -08:00
topjohnwu 987e5f5413 Address clippy warnings 2024-02-27 21:03:34 -08:00
topjohnwu 715284b70d Reorganize code 2024-02-27 18:14:30 -08:00
LoveSy 62fc7868ac Use self implemented parse_mount_info 2024-02-27 17:03:22 -08:00
topjohnwu 1a70796339 Replace all parse_mount_info usage with Rust 2024-02-27 03:49:17 -08:00
topjohnwu af6965eefa Update init logging implementation
Use less std::fs
2024-02-26 17:49:11 -08:00
topjohnwu 8f7d2e38f7 Make crt0 an external submodule 2024-02-26 17:34:17 -08:00
topjohnwu be433fa667 Use Rust for formatting
The fprintf implementation included in crt0 is too rudimental
2024-02-26 00:26:23 -08:00
topjohnwu 0ccd6e7381 Fix fread and fwrite implementation 2024-02-25 23:20:30 -08:00
topjohnwu 907bbbda41 Remove usage of patched static lib 2024-02-25 22:11:34 -08:00
topjohnwu 4393bc077d Implement string routines 2024-02-25 21:12:19 -08:00
topjohnwu 365b373480 Make it easy to build without crt0 2024-02-24 22:32:22 -08:00
topjohnwu 47e6dd286d Minor fixes 2024-02-24 22:00:09 -08:00
topjohnwu 0dbaf52566 Make all platforms build properly 2024-02-24 05:10:54 -08:00
topjohnwu 66f49dfab5 Remove unnecessary lock usage 2024-02-24 04:20:28 -08:00
topjohnwu f8967e9274 Implement strerror 2024-02-24 04:02:46 -08:00
topjohnwu a4f008fde5 Reorganize files 2024-02-24 03:41:22 -08:00
topjohnwu e9980c778b Implement stub functions 2024-02-24 03:41:03 -08:00
topjohnwu 06b6fb0c33 Add setenv 2024-02-24 03:27:09 -08:00
topjohnwu 38cb3d4105 Add dirent implementation 2024-02-24 03:26:53 -08:00
topjohnwu db99caf258 Use execve directly 2024-02-24 01:47:11 -08:00
topjohnwu 39dbffadfe Complete stdio 2024-02-24 01:28:58 -08:00
topjohnwu b7505c3c9c Remove fopen usage in magiskinit 2024-02-24 00:45:07 -08:00
topjohnwu 3185e5a7ca Introduce string/mem functions 2024-02-23 23:56:31 -08:00
topjohnwu e0cbe28711 Add the generic syscall function 2024-02-23 18:41:39 -08:00
topjohnwu 66cee19cea Add printf and sscanf family 2024-02-23 17:44:12 -08:00
topjohnwu 2ec29ade79 Add all missing syscalls 2024-02-23 14:35:12 -08:00
topjohnwu c865d4e187 Add memory allocator 2024-02-22 21:22:27 -08:00
topjohnwu a42a0a53ce Declare more symbols 2024-02-22 21:22:27 -08:00
topjohnwu 6d79de7d71 Initial crt0 implementation
Builds but cannot link, missing a lot of symbols
2024-02-22 21:22:27 -08:00
topjohnwu 7e9abe6e90 Update ONDK 2024-02-22 20:58:40 -08:00
残页 4d5510be4f Prompt users to use reboot button in System Updates
So the update engine can write verify info of partitions which fixes bootloop on newer Pixel devices
2024-02-19 02:14:12 -08:00
topjohnwu b04e1394c0 Update README 2024-02-07 14:39:53 -08:00
topjohnwu 2aa923191e Rename DownloadManager to DownloadEngine
Also add some documentation
2024-02-06 17:54:15 -08:00
topjohnwu 4bf1c74164 Disable foreground service on API 34+ 2024-02-06 17:29:42 -08:00
topjohnwu 472c7878b2 Update AGP 2024-02-06 17:04:48 -08:00
topjohnwu 38ad871e33 Use user-initiated jobs for download tasks on API 34+ 2024-02-06 17:04:39 -08:00
topjohnwu c5d34670c4 Isolate download logic from service lifecycle 2024-02-06 00:56:14 -08:00
topjohnwu 154121f3dd Release new canary build 2024-02-02 23:51:35 -08:00
topjohnwu 3d91a561fe Update README 2024-02-02 23:35:14 -08:00
175 changed files with 4500 additions and 3643 deletions

View File

@ -82,7 +82,7 @@ jobs:
run: ./gradlew --stop
test:
name: Test on API ${{ matrix.api }}
name: Test x86_64 on API ${{ matrix.api }}
runs-on: ubuntu-latest
needs: build
strategy:
@ -115,3 +115,40 @@ jobs:
- name: AVD test
run: scripts/avd_test.sh ${{ matrix.api }}
test-32:
name: Test x86 on API ${{ matrix.api }}
runs-on: ubuntu-latest
needs: build
strategy:
fail-fast: false
matrix:
api: [23, 24, 25, 26, 27, 28, 29, 30]
steps:
- name: Check out
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Python 3
uses: actions/setup-python@v5
with:
python-version: "3.x"
- name: Download build artifacts
uses: actions/download-artifact@v4
with:
name: ${{ github.sha }}
path: out
- name: Enable KVM group perms
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
- name: AVD test
env:
FORCE_32_BIT: 1
run: scripts/avd_test.sh ${{ matrix.api }}

3
.gitmodules vendored
View File

@ -34,6 +34,9 @@
[submodule "system_properties"]
path = native/src/external/system_properties
url = https://github.com/topjohnwu/system_properties.git
[submodule "crt0"]
path = native/src/external/crt0
url = https://github.com/topjohnwu/crt0.git
[submodule "termux-elf-cleaner"]
path = tools/termux-elf-cleaner
url = https://github.com/termux/termux-elf-cleaner.git

View File

@ -18,8 +18,8 @@ Some highlight features:
[Github](https://github.com/topjohnwu/Magisk/) is the only source where you can get official Magisk information and downloads.
[![](https://img.shields.io/badge/Magisk-v26.4-blue)](https://github.com/topjohnwu/Magisk/releases/tag/v26.4)
[![](https://img.shields.io/badge/Magisk%20Beta-v26.4-blue)](https://github.com/topjohnwu/Magisk/releases/tag/v26.4)
[![](https://img.shields.io/badge/Magisk-v27.0-blue)](https://github.com/topjohnwu/Magisk/releases/tag/v27.0)
[![](https://img.shields.io/badge/Magisk%20Beta-v27.0-blue)](https://github.com/topjohnwu/Magisk/releases/tag/v27.0)
[![](https://img.shields.io/badge/Magisk-Canary-red)](https://raw.githubusercontent.com/topjohnwu/magisk-files/canary/app-release.apk)
[![](https://img.shields.io/badge/Magisk-Debug-red)](https://raw.githubusercontent.com/topjohnwu/magisk-files/canary/app-debug.apk)

View File

@ -70,7 +70,7 @@ configurations.all {
dependencies {
implementation(project(":app:shared"))
implementation("com.github.topjohnwu:jtar:1.0.0")
implementation("com.github.topjohnwu:jtar:1.1.0")
implementation("com.github.topjohnwu:indeterminate-checkbox:1.0.7")
implementation("com.github.topjohnwu:lz4-java:1.7.1")
implementation("com.jakewharton.timber:timber:5.0.1")
@ -104,7 +104,7 @@ dependencies {
implementation("androidx.room:room-ktx:${vRoom}")
kapt("androidx.room:room-compiler:${vRoom}")
val vNav = "2.7.6"
val vNav = "2.7.7"
implementation("androidx.navigation:navigation-fragment-ktx:${vNav}")
implementation("androidx.navigation:navigation-ui-ktx:${vNav}")

View File

@ -4,12 +4,14 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.HIDE_OVERLAY_WINDOWS" />
<uses-permission android:name="android.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.RUN_USER_INITIATED_JOBS" />
<uses-permission
android:name="android.permission.FOREGROUND_SERVICE"
android:maxSdkVersion="33" />
<uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="29" />

View File

@ -67,8 +67,6 @@ public final class APKInstall {
public interface Session {
// @WorkerThread
OutputStream openStream(Context context) throws IOException;
// @WorkerThread
void install(Context context, File apk) throws IOException;
// @WorkerThread @Nullable
Intent waitIntent();
}
@ -167,13 +165,5 @@ public final class APKInstall {
}
};
}
@Override
public void install(Context context, File apk) throws IOException {
try (var src = new FileInputStream(apk);
var out = openStream(context)) {
transfer(src, out);
}
}
}
}

View File

@ -61,8 +61,9 @@
</receiver>
<service
android:name=".core.download.DownloadService"
android:name=".core.Service"
android:exported="false"
android:enabled="@bool/enable_fg_service"
android:foregroundServiceType="dataSync" />
<service

View File

@ -1,6 +1,8 @@
package com.topjohnwu.magisk.arch
import android.Manifest.permission.*
import android.Manifest.permission.POST_NOTIFICATIONS
import android.Manifest.permission.REQUEST_INSTALL_PACKAGES
import android.Manifest.permission.WRITE_EXTERNAL_STORAGE
import android.annotation.SuppressLint
import android.os.Bundle
import androidx.databinding.PropertyChangeRegistry

View File

@ -5,6 +5,7 @@ import android.app.Application
import android.content.Context
import android.content.res.Configuration
import android.os.Bundle
import android.system.Os
import androidx.profileinstaller.ProfileInstaller
import com.topjohnwu.magisk.BuildConfig
import com.topjohnwu.magisk.StubApk
@ -46,6 +47,8 @@ open class App() : Application() {
Timber.e(e)
exitProcess(1)
}
Os.setenv("PATH", "${Os.getenv("PATH")}:/debug_ramdisk:/sbin", true)
}
override fun attachBaseContext(context: Context) {

View File

@ -12,8 +12,11 @@ import com.topjohnwu.magisk.core.repository.DBConfig
import com.topjohnwu.magisk.core.repository.PreferenceConfig
import com.topjohnwu.magisk.core.utils.refreshLocale
import com.topjohnwu.magisk.ui.theme.Theme
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.runBlocking
import java.io.File
import java.io.IOException
object Config : PreferenceConfig, DBConfig {
@ -37,6 +40,7 @@ object Config : PreferenceConfig, DBConfig {
const val SU_MNT_NS = "mnt_ns"
const val SU_BIOMETRIC = "su_biometric"
const val ZYGISK = "zygisk"
const val BOOTLOOP = "bootloop"
const val DENYLIST = "denylist"
const val SU_MANAGER = "requester"
const val KEYSTORE = "keystore"
@ -163,6 +167,7 @@ object Config : PreferenceConfig, DBConfig {
suBiometric = value
}
var zygisk by dbSettings(Key.ZYGISK, false)
var bootloop by dbSettings(Key.BOOTLOOP, 0)
var denyList by BoolDBPropertyNoWrite(Key.DENYLIST, false)
var suManager by dbStrings(Key.SU_MANAGER, "", true)
var keyStoreRaw by dbStrings(Key.KEYSTORE, "", true)
@ -171,8 +176,14 @@ object Config : PreferenceConfig, DBConfig {
fun load(pkg: String?) {
// Only try to load prefs when fresh install and a previous package name is set
if (pkg != null && prefs.all.isEmpty()) runCatching {
context.contentResolver.openInputStream(Provider.preferencesUri(pkg))?.writeTo(prefsFile)
if (pkg != null && prefs.all.isEmpty()) {
runBlocking {
try {
context.contentResolver
.openInputStream(Provider.preferencesUri(pkg))
?.writeTo(prefsFile, dispatcher = Dispatchers.Unconfined)
} catch (ignored: IOException) {}
}
return
}

View File

@ -35,7 +35,8 @@ object Const {
}
object ID {
const val JOB_SERVICE_ID = 7
const val DOWNLOAD_JOB_ID = 6
const val CHECK_UPDATE_JOB_ID = 7
}
object Url {

View File

@ -1,5 +1,8 @@
package com.topjohnwu.magisk.core
import android.annotation.SuppressLint
import android.annotation.TargetApi
import android.app.Notification
import android.app.job.JobInfo
import android.app.job.JobParameters
import android.app.job.JobScheduler
@ -8,38 +11,78 @@ import androidx.core.content.getSystemService
import com.topjohnwu.magisk.BuildConfig
import com.topjohnwu.magisk.core.base.BaseJobService
import com.topjohnwu.magisk.core.di.ServiceLocator
import com.topjohnwu.magisk.core.download.DownloadEngine
import com.topjohnwu.magisk.core.download.Subject
import com.topjohnwu.magisk.view.Notifications
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import java.util.concurrent.TimeUnit
class JobService : BaseJobService() {
private val job = Job()
private val svc get() = ServiceLocator.networkService
private var mSession: Session? = null
override fun onStartJob(params: JobParameters): Boolean {
val coroutineScope = CoroutineScope(Dispatchers.IO + job)
coroutineScope.launch {
doWork()
@TargetApi(value = 34)
inner class Session(
private var params: JobParameters
) : DownloadEngine.Session {
override val context get() = this@JobService
val engine = DownloadEngine(this)
fun updateParams(params: JobParameters) {
this.params = params
engine.reattach()
}
override fun attachNotification(id: Int, builder: Notification.Builder) {
setNotification(params, id, builder.build(), JOB_END_NOTIFICATION_POLICY_REMOVE)
}
override fun onDownloadComplete() {
jobFinished(params, false)
}
return false
}
private suspend fun doWork() {
svc.fetchUpdate()?.let {
Info.remote = it
if (Info.env.isActive && BuildConfig.VERSION_CODE < it.magisk.versionCode)
Notifications.updateAvailable()
@SuppressLint("NewApi")
override fun onStartJob(params: JobParameters): Boolean {
return when (params.jobId) {
Const.ID.CHECK_UPDATE_JOB_ID -> checkUpdate(params)
Const.ID.DOWNLOAD_JOB_ID -> downloadFile(params)
else -> false
}
}
override fun onStopJob(params: JobParameters): Boolean {
job.cancel()
return false
override fun onStopJob(params: JobParameters?) = false
@TargetApi(value = 34)
private fun downloadFile(params: JobParameters): Boolean {
params.transientExtras.classLoader = Subject::class.java.classLoader
val subject = params.transientExtras
.getParcelable(DownloadEngine.SUBJECT_KEY, Subject::class.java) ?:
return false
val session = mSession?.also {
it.updateParams(params)
} ?: run {
Session(params).also { mSession = it }
}
session.engine.download(subject)
return true
}
private fun checkUpdate(params: JobParameters): Boolean {
GlobalScope.launch(Dispatchers.IO) {
ServiceLocator.networkService.fetchUpdate()?.let {
Info.remote = it
if (Info.env.isActive && BuildConfig.VERSION_CODE < it.magisk.versionCode)
Notifications.updateAvailable()
jobFinished(params, false)
}
}
return true
}
companion object {
@ -47,14 +90,14 @@ class JobService : BaseJobService() {
val scheduler = context.getSystemService<JobScheduler>() ?: return
if (Config.checkUpdate) {
val cmp = JobService::class.java.cmp(context.packageName)
val info = JobInfo.Builder(Const.ID.JOB_SERVICE_ID, cmp)
val info = JobInfo.Builder(Const.ID.CHECK_UPDATE_JOB_ID, cmp)
.setPeriodic(TimeUnit.HOURS.toMillis(12))
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
.setRequiresDeviceIdle(true)
.build()
scheduler.schedule(info)
} else {
scheduler.cancel(Const.ID.JOB_SERVICE_ID)
scheduler.cancel(Const.ID.CHECK_UPDATE_JOB_ID)
}
}
}

View File

@ -3,8 +3,11 @@ package com.topjohnwu.magisk.core
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import androidx.core.content.IntentCompat
import com.topjohnwu.magisk.core.base.BaseReceiver
import com.topjohnwu.magisk.core.di.ServiceLocator
import com.topjohnwu.magisk.core.download.DownloadEngine
import com.topjohnwu.magisk.core.download.Subject
import com.topjohnwu.magisk.view.Notifications
import com.topjohnwu.magisk.view.Shortcuts
import com.topjohnwu.superuser.Shell
@ -35,6 +38,12 @@ open class Receiver : BaseReceiver() {
}
when (intent.action ?: return) {
DownloadEngine.ACTION -> {
IntentCompat.getParcelableExtra(
intent, DownloadEngine.SUBJECT_KEY, Subject::class.java)?.let {
DownloadEngine.start(context, it)
}
}
Intent.ACTION_PACKAGE_REPLACED -> {
// This will only work pre-O
if (Config.suReAuth)

View File

@ -0,0 +1,38 @@
package com.topjohnwu.magisk.core
import android.app.Notification
import android.content.Intent
import android.os.Build
import androidx.core.app.ServiceCompat
import androidx.core.content.IntentCompat
import com.topjohnwu.magisk.core.base.BaseService
import com.topjohnwu.magisk.core.download.DownloadEngine
import com.topjohnwu.magisk.core.download.Subject
class Service : BaseService(), DownloadEngine.Session {
private var mEngine: DownloadEngine? = null
override val context get() = this
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
if (intent.action == DownloadEngine.ACTION) {
IntentCompat
.getParcelableExtra(intent, DownloadEngine.SUBJECT_KEY, Subject::class.java)
?.let { subject ->
val engine = mEngine ?: DownloadEngine(this).also { mEngine = it }
engine.download(subject)
}
}
return START_NOT_STICKY
}
override fun attachNotification(id: Int, builder: Notification.Builder) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
builder.setForegroundServiceBehavior(Notification.FOREGROUND_SERVICE_IMMEDIATE)
startForeground(id, builder.build())
}
override fun onDownloadComplete() {
ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE)
}
}

View File

@ -4,7 +4,11 @@ import com.topjohnwu.magisk.core.model.BranchInfo
import com.topjohnwu.magisk.core.model.ModuleJson
import com.topjohnwu.magisk.core.model.UpdateInfo
import okhttp3.ResponseBody
import retrofit2.http.*
import retrofit2.http.GET
import retrofit2.http.Headers
import retrofit2.http.Path
import retrofit2.http.Streaming
import retrofit2.http.Url
private const val BRANCH = "branch"
private const val REPO = "repo"

View File

@ -1,12 +1,16 @@
package com.topjohnwu.magisk.core.data
import androidx.room.*
import androidx.room.Dao
import androidx.room.Database
import androidx.room.Insert
import androidx.room.Query
import androidx.room.RoomDatabase
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import com.topjohnwu.magisk.core.model.su.SuLog
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.util.*
import java.util.Calendar
@Database(version = 2, entities = [SuLog::class], exportSchema = false)
abstract class SuLogDatabase : RoomDatabase() {

View File

@ -0,0 +1,373 @@
package com.topjohnwu.magisk.core.download
import android.Manifest
import android.annotation.SuppressLint
import android.app.Notification
import android.app.PendingIntent
import android.app.job.JobInfo
import android.app.job.JobScheduler
import android.content.Context
import android.net.Uri
import android.os.Build
import android.os.Bundle
import androidx.collection.SparseArrayCompat
import androidx.collection.isNotEmpty
import androidx.core.content.getSystemService
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.MutableLiveData
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.StubApk
import com.topjohnwu.magisk.core.ActivityTracker
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.JobService
import com.topjohnwu.magisk.core.base.BaseActivity
import com.topjohnwu.magisk.core.cmp
import com.topjohnwu.magisk.core.di.ServiceLocator
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.withStreams
import com.topjohnwu.magisk.core.ktx.writeTo
import com.topjohnwu.magisk.core.tasks.HideAPK
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
import com.topjohnwu.magisk.core.utils.ProgressInputStream
import com.topjohnwu.magisk.utils.APKInstall
import com.topjohnwu.magisk.view.Notifications
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import okhttp3.ResponseBody
import timber.log.Timber
import java.io.IOException
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.
*
* Each download engine instance has to be paired with a "session" that is managed by the operating
* system. A session is an Android component that allows executing long lasting operations and
* have its state tied to a notification to show progress.
*
* A session can only have one single notification representing its state, and the operating system
* also uses the notification to manage the lifecycle of a session. One goal of this class is
* to support concurrent download tasks using only one single session, so internally it manages
* all active tasks and notifications and properly re-assign notifications to be attached to
* the session to make sure all download operations can be completed without the operating system
* killing the 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.
*/
class DownloadEngine(
private val session: Session
) {
interface Session {
val context: Context
fun attachNotification(id: Int, builder: Notification.Builder)
fun onDownloadComplete()
}
companion object {
const val ACTION = "com.topjohnwu.magisk.DOWNLOAD"
const val SUBJECT_KEY = "subject"
private const val REQUEST_CODE = 1
private val progressBroadcast = MutableLiveData<Pair<Float, Subject>?>()
private fun broadcast(progress: Float, subject: Subject) {
progressBroadcast.postValue(progress to subject)
}
fun observeProgress(owner: LifecycleOwner, callback: (Float, Subject) -> Unit) {
progressBroadcast.value = null
progressBroadcast.observe(owner) {
val (progress, subject) = it ?: return@observe
callback(progress, subject)
}
}
private fun createIntent(context: Context, subject: Subject) =
if (Build.VERSION.SDK_INT >= 34) {
context.intent<com.topjohnwu.magisk.core.Receiver>()
.setAction(ACTION)
.putExtra(SUBJECT_KEY, subject)
} else {
context.intent<com.topjohnwu.magisk.core.Service>()
.setAction(ACTION)
.putExtra(SUBJECT_KEY, subject)
}
@SuppressLint("InlinedApi")
fun getPendingIntent(context: Context, subject: Subject): PendingIntent {
val flag = PendingIntent.FLAG_IMMUTABLE or
PendingIntent.FLAG_UPDATE_CURRENT or
PendingIntent.FLAG_ONE_SHOT
val intent = createIntent(context, subject)
return if (Build.VERSION.SDK_INT >= 34) {
// 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.
// As a workaround, we send the subject to a broadcast receiver and have it
// schedule the job for us.
PendingIntent.getBroadcast(context, REQUEST_CODE, intent, flag)
} else if (Build.VERSION.SDK_INT >= 26) {
PendingIntent.getForegroundService(context, REQUEST_CODE, intent, flag)
} else {
PendingIntent.getService(context, REQUEST_CODE, intent, flag)
}
}
@SuppressLint("InlinedApi")
fun startWithActivity(activity: BaseActivity, subject: Subject) {
activity.withPermission(Manifest.permission.POST_NOTIFICATIONS) {
// Always download regardless of notification permission status
start(activity.applicationContext, subject)
}
}
fun start(context: Context, subject: Subject) {
if (Build.VERSION.SDK_INT >= 34) {
val scheduler = context.getSystemService<JobScheduler>()!!
val cmp = JobService::class.java.cmp(context.packageName)
val extras = Bundle()
extras.putParcelable(SUBJECT_KEY, subject)
val info = JobInfo.Builder(Const.ID.DOWNLOAD_JOB_ID, cmp)
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
.setUserInitiated(true)
.setTransientExtras(extras)
.build()
scheduler.schedule(info)
} else if (Build.VERSION.SDK_INT >= 26) {
context.startForegroundService(createIntent(context, subject))
} else {
context.startService(createIntent(context, subject))
}
}
}
fun download(subject: Subject) {
notifyUpdate(subject.notifyId)
CoroutineScope(job + Dispatchers.IO).launch {
try {
val stream = network.fetchFile(subject.url).toProgressStream(subject)
when (subject) {
is Subject.App -> handleApp(stream, subject)
is Subject.Module -> handleModule(stream, subject.file)
else -> stream.copyAndClose(subject.file.outputStream())
}
val activity = ActivityTracker.foreground
if (activity != null && subject.autoLaunch) {
notifyRemove(subject.notifyId)
subject.pendingIntent(activity)?.send()
} else {
notifyFinish(subject)
}
} catch (e: IOException) {
Timber.e(e)
notifyFail(subject)
}
}
}
@Synchronized
fun reattach() {
val builder = notifications[attachedId] ?: return
session.attachNotification(attachedId, builder)
}
private val notifications = SparseArrayCompat<Notification.Builder>()
private var attachedId = -1
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 {
val notification = notifyRemove(id)?.also(editor) ?: return -1
val newId = Notifications.nextId()
Notifications.mgr.notify(newId, notification.build())
return newId
}
private fun notifyFail(subject: Subject) = finalNotify(subject.notifyId) {
broadcast(-2f, subject)
it.setContentText(context.getString(R.string.download_file_error))
.setSmallIcon(android.R.drawable.stat_notify_error)
.setOngoing(false)
}
private fun notifyFinish(subject: Subject) = finalNotify(subject.notifyId) {
broadcast(1f, subject)
it.setContentTitle(subject.title)
.setContentText(context.getString(R.string.download_complete))
.setSmallIcon(android.R.drawable.stat_sys_download_done)
.setProgress(0, 0, false)
.setOngoing(false)
.setAutoCancel(true)
subject.pendingIntent(context)?.let { intent -> it.setContentIntent(intent) }
}
private fun attachNotification(id: Int, notification: Notification.Builder) {
attachedId = id
session.attachNotification(id, notification)
}
@Synchronized
private fun notifyUpdate(id: Int, editor: (Notification.Builder) -> Unit = {}) {
val notification = (notifications[id] ?: Notifications.startProgress("").also {
notifications[id] = it
}).apply(editor)
if (attachedId < 0)
attachNotification(id, notification)
else
Notifications.mgr.notify(id, notification.build())
}
@Synchronized
private fun notifyRemove(id: Int): Notification.Builder? {
val idx = notifications.indexOfKey(id)
var n: Notification.Builder? = null
if (idx >= 0) {
n = notifications.valueAt(idx)
notifications.removeAt(idx)
// The cancelled notification is the one attached to the session, need special handling
if (attachedId == id) {
if (notifications.isNotEmpty()) {
// There are still remaining notifications, pick one and attach to the session
val anotherId = notifications.keyAt(0)
val notification = notifications.valueAt(0)
attachNotification(anotherId, notification)
} else {
// No more notifications left, terminate the session
attachedId = -1
session.onDownloadComplete()
}
}
}
Notifications.mgr.cancel(id)
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 = HideAPK.upgrade(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 {
val max = contentLength()
val total = max.toFloat() / 1048576
val id = subject.notifyId
notifyUpdate(id) { it.setContentTitle(subject.title) }
return ProgressInputStream(byteStream()) {
val progress = it.toFloat() / 1048576
notifyUpdate(id) { notification ->
if (max > 0) {
broadcast(progress / total, subject)
notification
.setProgress(max.toInt(), it.toInt(), false)
.setContentText("%.2f / %.2f MB".format(progress, total))
} else {
broadcast(-1f, subject)
notification.setContentText("%.2f MB / ??".format(progress))
}
}
}
}
}

View File

@ -1,220 +0,0 @@
package com.topjohnwu.magisk.core.download
import android.Manifest
import android.annotation.SuppressLint
import android.app.PendingIntent
import android.app.PendingIntent.*
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import androidx.core.net.toFile
import androidx.lifecycle.LifecycleOwner
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.StubApk
import com.topjohnwu.magisk.core.ActivityTracker
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.base.BaseActivity
import com.topjohnwu.magisk.core.intent
import com.topjohnwu.magisk.core.isRunningAsStub
import com.topjohnwu.magisk.core.ktx.*
import com.topjohnwu.magisk.core.tasks.HideAPK
import com.topjohnwu.magisk.core.utils.MediaStoreUtils
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
import com.topjohnwu.magisk.utils.APKInstall
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import timber.log.Timber
import java.io.ByteArrayInputStream
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
import java.util.Properties
import java.util.zip.ZipEntry
import java.util.zip.ZipFile
import java.util.zip.ZipInputStream
import java.util.zip.ZipOutputStream
class DownloadService : NotificationService() {
private val job = Job()
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
intent.getParcelableExtra<Subject>(SUBJECT_KEY)?.let { download(it) }
return START_NOT_STICKY
}
override fun onDestroy() {
job.cancel()
}
private fun download(subject: Subject) {
notifyUpdate(subject.notifyId)
CoroutineScope(job + Dispatchers.IO).launch {
try {
val stream = service.fetchFile(subject.url).toProgressStream(subject)
when (subject) {
is Subject.App -> handleApp(stream, subject)
is Subject.Module -> handleModule(stream, subject.file)
}
val activity = ActivityTracker.foreground
if (activity != null && subject.autoLaunch) {
notifyRemove(subject.notifyId)
subject.pendingIntent(activity)?.send()
} else {
notifyFinish(subject)
}
subject.postDownload?.invoke()
if (!hasNotifications)
stopSelf()
} catch (e: Exception) {
Timber.e(e)
notifyFail(subject)
}
}
}
private fun handleApp(stream: InputStream, subject: Subject.App) {
fun writeTee(output: OutputStream) {
val uri = MediaStoreUtils.getFile("${subject.title}.apk").uri
val external = uri.outputStream()
stream.copyAndClose(TeeOutputStream(external, output))
}
if (isRunningAsStub) {
val updateApk = StubApk.update(this)
try {
// Download full APK to stub update path
writeTee(updateApk.outputStream())
val zf = ZipFile(updateApk)
val prop = Properties()
prop.load(ByteArrayInputStream(zf.comment.toByteArray()))
val stubVersion = prop.getProperty("stubVersion").toIntOrNull() ?: -1
if (Info.stub!!.version < stubVersion) {
// Also upgrade stub
notifyUpdate(subject.notifyId) {
it.setProgress(0, 0, true)
.setContentTitle(getString(R.string.hide_app_title))
.setContentText("")
}
// Extract stub
val apk = subject.file.toFile()
zf.getInputStream(zf.getEntry("assets/stub.apk")).writeTo(apk)
zf.close()
// Patch and install
subject.intent = HideAPK.upgrade(this, apk)
?: throw IOException("HideAPK patch error")
apk.delete()
} else {
ActivityTracker.foreground?.let {
// Relaunch the process if we are foreground
StubApk.restartProcess(it)
} ?: run {
// Or else kill the current process after posting notification
subject.intent = selfLaunchIntent()
subject.postDownload = { Runtime.getRuntime().exit(0) }
}
return
}
} catch (e: Exception) {
// If any error occurred, do not let stub load the new APK
updateApk.delete()
throw e
}
} else {
val session = APKInstall.startSession(this)
writeTee(session.openStream(this))
subject.intent = session.waitIntent()
}
}
private fun handleModule(src: InputStream, file: Uri) {
val input = ZipInputStream(src.buffered())
val output = ZipOutputStream(file.outputStream().buffered())
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"))
assets.open("module_installer.sh").copyTo(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.copyTo(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()
}
}
companion object {
private const val SUBJECT_KEY = "subject"
private const val REQUEST_CODE = 1
fun observeProgress(owner: LifecycleOwner, callback: (Float, Subject) -> Unit) {
progressBroadcast.value = null
progressBroadcast.observe(owner) {
val (progress, subject) = it ?: return@observe
callback(progress, subject)
}
}
private fun intent(context: Context, subject: Subject) =
context.intent<DownloadService>().putExtra(SUBJECT_KEY, subject)
@SuppressLint("InlinedApi")
fun getPendingIntent(context: Context, subject: Subject): PendingIntent {
val flag = FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT or FLAG_ONE_SHOT
val intent = intent(context, subject)
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
getForegroundService(context, REQUEST_CODE, intent, flag)
} else {
getService(context, REQUEST_CODE, intent, flag)
}
}
@SuppressLint("InlinedApi")
fun start(activity: BaseActivity, subject: Subject) {
activity.withPermission(Manifest.permission.POST_NOTIFICATIONS) {
// Always download regardless of notification permission status
val app = activity.applicationContext
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
app.startForegroundService(intent(app, subject))
} else {
app.startService(intent(app, subject))
}
}
}
}
}

View File

@ -1,128 +0,0 @@
package com.topjohnwu.magisk.core.download
import android.app.Notification
import android.content.Intent
import android.os.Build
import androidx.lifecycle.MutableLiveData
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.base.BaseService
import com.topjohnwu.magisk.core.di.ServiceLocator
import com.topjohnwu.magisk.core.ktx.synchronized
import com.topjohnwu.magisk.core.utils.ProgressInputStream
import com.topjohnwu.magisk.view.Notifications
import okhttp3.ResponseBody
import java.io.InputStream
open class NotificationService : BaseService() {
private val notifications = HashMap<Int, Notification.Builder>().synchronized()
protected val hasNotifications get() = notifications.isNotEmpty()
protected val service get() = ServiceLocator.networkService
private var attachedNotificationId = 0
override fun onTaskRemoved(rootIntent: Intent?) {
super.onTaskRemoved(rootIntent)
notifications.forEach { Notifications.mgr.cancel(it.key) }
notifications.clear()
}
protected fun ResponseBody.toProgressStream(subject: Subject): InputStream {
val max = contentLength()
val total = max.toFloat() / 1048576
val id = subject.notifyId
notifyUpdate(id) { it.setContentTitle(subject.title) }
return ProgressInputStream(byteStream()) {
val progress = it.toFloat() / 1048576
notifyUpdate(id) { notification ->
if (max > 0) {
broadcast(progress / total, subject)
notification
.setProgress(max.toInt(), it.toInt(), false)
.setContentText("%.2f / %.2f MB".format(progress, total))
} else {
broadcast(-1f, subject)
notification.setContentText("%.2f MB / ??".format(progress))
}
}
}
}
private fun finalNotify(id: Int, editor: (Notification.Builder) -> Unit): Int {
val notification = notifyRemove(id)?.also(editor) ?: return -1
val newId = Notifications.nextId()
Notifications.mgr.notify(newId, notification.build())
return newId
}
protected fun notifyFail(subject: Subject) = finalNotify(subject.notifyId) {
broadcast(-2f, subject)
it.setContentText(getString(R.string.download_file_error))
.setSmallIcon(android.R.drawable.stat_notify_error)
.setOngoing(false)
}
protected fun notifyFinish(subject: Subject) = finalNotify(subject.notifyId) {
broadcast(1f, subject)
it.setContentTitle(subject.title)
.setContentText(getString(R.string.download_complete))
.setSmallIcon(android.R.drawable.stat_sys_download_done)
.setProgress(0, 0, false)
.setOngoing(false)
.setAutoCancel(true)
subject.pendingIntent(this)?.let { intent -> it.setContentIntent(intent) }
}
private fun attachNotification(id: Int, notification: Notification) {
attachedNotificationId = id
startForeground(id, notification)
}
private fun maybeDetachNotification(id: Int) : Boolean {
if (attachedNotificationId != id) return false
if (hasNotifications) {
val (anotherId, notification) = notifications.entries.first()
// Attaching a new notification will remove the current showing one
attachNotification(anotherId, notification.build())
return true
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
stopForeground(STOP_FOREGROUND_REMOVE)
} else {
@Suppress("DEPRECATION")
stopForeground(true)
}
attachedNotificationId = 0
return true
}
protected fun notifyUpdate(id: Int, editor: (Notification.Builder) -> Unit = {}) {
fun create() = Notifications.startProgress("")
val wasEmpty = !hasNotifications
val notification = notifications.getOrPut(id, ::create).also(editor).build()
if (wasEmpty)
attachNotification(id, notification)
else
Notifications.mgr.notify(id, notification)
}
protected fun notifyRemove(id: Int): Notification.Builder? {
val n = notifications.remove(id)
if (n == null || !maybeDetachNotification(id))
Notifications.mgr.cancel(id)
return n
}
companion object {
@JvmStatic
protected val progressBroadcast = MutableLiveData<Pair<Float, Subject>?>()
private fun broadcast(progress: Float, subject: Subject) {
progressBroadcast.postValue(progress to subject)
}
}
}

View File

@ -17,13 +17,8 @@ import com.topjohnwu.magisk.ui.flash.FlashFragment
import com.topjohnwu.magisk.view.Notifications
import kotlinx.parcelize.IgnoredOnParcel
import kotlinx.parcelize.Parcelize
private fun cachedFile(name: String) = AppContext.cachedFile(name).apply { delete() }.toUri()
enum class Action {
Flash,
Download
}
import java.io.File
import java.util.UUID
sealed class Subject : Parcelable {
@ -32,19 +27,17 @@ sealed class Subject : Parcelable {
abstract val title: String
abstract val notifyId: Int
open val autoLaunch: Boolean get() = true
open val postDownload: (() -> Unit)? get() = null
abstract fun pendingIntent(context: Context): PendingIntent?
open fun pendingIntent(context: Context): PendingIntent? = null
@Parcelize
class Module(
val module: OnlineModule,
val action: Action,
private val module: OnlineModule,
override val autoLaunch: Boolean,
override val notifyId: Int = Notifications.nextId()
) : Subject() {
override val url: String get() = module.zipUrl
override val title: String get() = module.downloadFilename
override val autoLaunch: Boolean get() = action == Action.Flash
@IgnoredOnParcel
override val file by lazy {
@ -65,17 +58,24 @@ sealed class Subject : Parcelable {
@IgnoredOnParcel
override val file by lazy {
cachedFile("manager.apk")
MediaStoreUtils.getFile("${title}.apk").uri
}
@IgnoredOnParcel
override var postDownload: (() -> Unit)? = null
@IgnoredOnParcel
var intent: Intent? = null
override fun pendingIntent(context: Context) = intent?.toPending(context)
}
@Parcelize
class Test(
override val notifyId: Int = Notifications.nextId(),
override val title: String = UUID.randomUUID().toString().substring(0, 6)
) : Subject() {
override val url get() = "https://link.testfile.org/250MB"
override val file get() = File("/dev/null").toUri()
override val autoLaunch get() = false
}
@SuppressLint("InlinedApi")
protected fun Intent.toPending(context: Context): PendingIntent {
return PendingIntent.getActivity(context, notifyId, this,

View File

@ -2,10 +2,15 @@ package com.topjohnwu.magisk.core.ktx
import androidx.collection.SparseArrayCompat
import com.topjohnwu.magisk.core.utils.currentLocale
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flatMapMerge
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.isActive
import kotlinx.coroutines.withContext
import java.io.File
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
import java.lang.reflect.Field
@ -35,9 +40,38 @@ inline fun <In : InputStream, Out : OutputStream> withStreams(
}
}
fun InputStream.copyAndClose(out: OutputStream) = withStreams(this, out) { i, o -> i.copyTo(o) }
@Throws(IOException::class)
suspend fun InputStream.copyAll(
out: OutputStream,
bufferSize: Int = DEFAULT_BUFFER_SIZE,
dispatcher: CoroutineDispatcher = Dispatchers.IO
): Long {
return withContext(dispatcher) {
var bytesCopied: Long = 0
val buffer = ByteArray(bufferSize)
var bytes = read(buffer)
while (isActive && bytes >= 0) {
out.write(buffer, 0, bytes)
bytesCopied += bytes
bytes = read(buffer)
}
bytesCopied
}
}
fun InputStream.writeTo(file: File) = copyAndClose(file.outputStream())
@Throws(IOException::class)
suspend inline fun InputStream.copyAndClose(
out: OutputStream,
bufferSize: Int = DEFAULT_BUFFER_SIZE,
dispatcher: CoroutineDispatcher = Dispatchers.IO
) = withStreams(this, out) { i, o -> i.copyAll(o, bufferSize, dispatcher) }
@Throws(IOException::class)
suspend inline fun InputStream.writeTo(
file: File,
bufferSize: Int = DEFAULT_BUFFER_SIZE,
dispatcher: CoroutineDispatcher = Dispatchers.IO
) = copyAndClose(file.outputStream(), bufferSize, dispatcher)
operator fun <E> SparseArrayCompat<E>.set(key: Int, value: E) {
put(key, value)

View File

@ -26,7 +26,7 @@ open class FlashZip(
private lateinit var zipFile: File
@Throws(IOException::class)
private fun flash(): Boolean {
private suspend fun flash(): Boolean {
installDir.deleteRecursively()
installDir.mkdirs()
@ -47,13 +47,13 @@ open class FlashZip(
}
}
val isValid = runCatching {
val isValid = try {
zipFile.unzip(installDir, "META-INF/com/google/android", true)
val script = File(installDir, "updater-script")
script.readText().contains("#MAGISK")
}.getOrElse {
} catch (e: IOException) {
console.add("! Unzip error")
throw it
throw e
}
if (!isValid) {

View File

@ -12,6 +12,7 @@ import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.Provider
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.writeTo
import com.topjohnwu.magisk.core.utils.AXML
@ -168,7 +169,7 @@ object HideAPK {
activity.finish()
}
private fun patchAndHide(activity: Activity, label: String, onFailure: Runnable): Boolean {
private suspend fun patchAndHide(activity: Activity, label: String, onFailure: Runnable): Boolean {
val stub = File(activity.cacheDir, "stub.apk")
try {
activity.assets.open("stub.apk").writeTo(stub)
@ -195,7 +196,7 @@ object HideAPK {
if (Shell.cmd(cmd).exec().isSuccess) return true
try {
session.install(activity, repack)
repack.inputStream().copyAndClose(session.openStream(activity))
} catch (e: IOException) {
Timber.e(e)
return false
@ -244,7 +245,7 @@ object HideAPK {
if (Shell.cmd(cmd).await().isSuccess) return
val success = withContext(Dispatchers.IO) {
try {
session.install(activity, apk)
apk.inputStream().copyAndClose(session.openStream(activity))
} catch (e: IOException) {
Timber.e(e)
return@withContext false

View File

@ -1,6 +1,7 @@
package com.topjohnwu.magisk.core.tasks
import android.net.Uri
import android.os.Process
import android.system.ErrnoException
import android.system.Os
import android.system.OsConstants
@ -17,6 +18,7 @@ import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.di.ServiceLocator
import com.topjohnwu.magisk.core.isRunningAsStub
import com.topjohnwu.magisk.core.ktx.copyAll
import com.topjohnwu.magisk.core.ktx.copyAndClose
import com.topjohnwu.magisk.core.ktx.reboot
import com.topjohnwu.magisk.core.ktx.toast
@ -93,7 +95,7 @@ abstract class MagiskInstallImpl protected constructor(
return true
}
private fun extractFiles(): Boolean {
private suspend fun extractFiles(): Boolean {
console.add("- Device platform: ${Const.CPU_ABI}")
console.add("- Installing: ${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})")
@ -104,40 +106,41 @@ abstract class MagiskInstallImpl protected constructor(
try {
// Extract binaries
if (isRunningAsStub) {
val zf = ZipFile(StubApk.current(context))
ZipFile(StubApk.current(context)).use { zf ->
zf.entries().asSequence().filter {
!it.isDirectory && it.name.startsWith("/lib/${Const.CPU_ABI}/")
}.forEach {
val n = it.name.substring(it.name.lastIndexOf('/') + 1)
val name = n.substring(3, n.length - 3)
val dest = File(installDir, name)
zf.getInputStream(it).writeTo(dest)
dest.setExecutable(true)
}
// Also extract magisk32 on non 64-bit only 64-bit devices
val is32lib = Const.CPU_ABI_32?.let {
{ entry: ZipEntry -> entry.name == "lib/$it/libmagisk32.so" }
} ?: { false }
zf.entries().asSequence().filter {
!it.isDirectory && (it.name.startsWith("lib/${Const.CPU_ABI}/") || is32lib(it))
}.forEach {
val n = it.name.substring(it.name.lastIndexOf('/') + 1)
val name = n.substring(3, n.length - 3)
val dest = File(installDir, name)
zf.getInputStream(it).writeTo(dest)
dest.setExecutable(true)
val abi32 = Const.CPU_ABI_32
if (Process.is64Bit() && abi32 != null) {
val magisk32 = File(installDir, "magisk32")
zf.getInputStream(ZipEntry("lib/$abi32/libmagisk.so")).writeTo(magisk32)
magisk32.setExecutable(true)
}
}
zf.close()
} else {
val info = context.applicationInfo
var libs = File(info.nativeLibraryDir).listFiles { _, name ->
name.startsWith("lib") && name.endsWith(".so")
} ?: emptyArray()
// Also symlink magisk32 on non 64-bit only 64-bit devices
val lib32 = info.javaClass.getDeclaredField("secondaryNativeLibraryDir")
.get(info) as String?
if (lib32 != null) {
libs += File(lib32, "libmagisk32.so")
}
for (lib in libs) {
val name = lib.name.substring(3, lib.name.length - 3)
Os.symlink(lib.path, "$installDir/$name")
}
// Also symlink magisk32 on 64-bit devices that supports 32-bit
val lib32 = info.javaClass.getDeclaredField("secondaryNativeLibraryDir")
.get(info) as String?
if (lib32 != null) {
Os.symlink("$lib32/libmagisk.so", "$installDir/magisk32");
}
}
// Extract scripts
@ -174,7 +177,7 @@ abstract class MagiskInstallImpl protected constructor(
return true
}
private fun InputStream.copyAndCloseOut(out: OutputStream) = out.use { copyTo(it) }
private suspend fun InputStream.copyAndCloseOut(out: OutputStream) = out.use { copyAll(it) }
private fun newTarEntry(name: String, size: Long): TarEntry {
console.add("-- Writing: $name")
@ -191,7 +194,7 @@ abstract class MagiskInstallImpl protected constructor(
private class NoBootException : IOException()
@Throws(IOException::class)
private fun processTar(tarIn: TarInputStream, tarOut: TarOutputStream): ExtendedFile {
private suspend fun processTar(tarIn: TarInputStream, tarOut: TarOutputStream): ExtendedFile {
console.add("- Processing tar file")
lateinit var entry: TarEntry
@ -228,7 +231,7 @@ abstract class MagiskInstallImpl protected constructor(
} else {
console.add("-- Copying: ${entry.name}")
tarOut.putNextEntry(entry)
tarIn.copyTo(tarOut, bufferSize = 1024 * 1024)
tarIn.copyAll(tarOut, bufferSize = 1024 * 1024)
}
}
@ -236,10 +239,10 @@ abstract class MagiskInstallImpl protected constructor(
val initBoot = installDir.getChildFile("init_boot.img")
val recovery = installDir.getChildFile("recovery.img")
fun ExtendedFile.copyToTar() {
suspend fun ExtendedFile.copyToTar() {
newInputStream().use {
tarOut.putNextEntry(newTarEntry(name, length()))
it.copyTo(tarOut)
it.copyAll(tarOut)
}
delete()
}
@ -273,7 +276,7 @@ abstract class MagiskInstallImpl protected constructor(
}
@Throws(IOException::class)
private fun processZip(zipIn: ZipInputStream): ExtendedFile {
private suspend fun processZip(zipIn: ZipInputStream): ExtendedFile {
console.add("- Processing zip file")
val boot = installDir.getChildFile("boot.img")
val initBoot = installDir.getChildFile("init_boot.img")
@ -373,23 +376,22 @@ abstract class MagiskInstallImpl protected constructor(
}
}
private fun handleFile(uri: Uri): Boolean {
private suspend fun handleFile(uri: Uri): Boolean {
val outStream: OutputStream
val outFile: MediaStoreUtils.UriFile
// Process input file
try {
uri.inputStream().buffered().use { src ->
src.mark(500)
val magic = ByteArray(4)
val tarMagic = ByteArray(5)
if (src.read(magic) != magic.size || src.skip(253) != 253L ||
src.read(tarMagic) != tarMagic.size
) {
PushbackInputStream(uri.inputStream(), 512).use { src ->
val head = ByteArray(512)
if (src.read(head) != head.size) {
console.add("! Invalid input file")
return false
}
src.reset()
src.unread(head)
val magic = head.copyOf(4)
val tarMagic = Arrays.copyOfRange(head, 257, 262)
val alpha = "abcdefghijklmnopqrstuvwxyz"
val alphaNum = "$alpha${alpha.uppercase(Locale.ROOT)}0123456789"
@ -510,7 +512,7 @@ abstract class MagiskInstallImpl protected constructor(
private fun flashBoot() = "direct_install $installDir $srcBoot".sh().isSuccess
private fun postOTA(): Boolean {
private suspend fun postOTA(): Boolean {
try {
val bootctl = File.createTempFile("bootctl", null, context.cacheDir)
context.assets.open("bootctl").writeTo(bootctl)
@ -521,9 +523,10 @@ abstract class MagiskInstallImpl protected constructor(
return false
}
console.add("***************************************")
console.add("*************************************************************")
console.add(" Next reboot will boot to second slot!")
console.add("***************************************")
console.add(" Go back to System Updates and press Restart to complete OTA")
console.add("*************************************************************")
return true
}
@ -533,14 +536,14 @@ abstract class MagiskInstallImpl protected constructor(
private fun String.fsh() = ShellUtils.fastCmd(shell, this)
private fun Array<String>.fsh() = ShellUtils.fastCmd(shell, *this)
protected fun patchFile(file: Uri) = extractFiles() && handleFile(file)
protected suspend fun patchFile(file: Uri) = extractFiles() && handleFile(file)
protected fun direct() = findImage() && extractFiles() && patchBoot() && flashBoot()
protected suspend fun direct() = findImage() && extractFiles() && patchBoot() && flashBoot()
protected fun secondSlot() =
protected suspend fun secondSlot() =
findSecondary() && extractFiles() && patchBoot() && flashBoot() && postOTA()
protected fun fixEnv() = extractFiles() && "fix_env $installDir".sh().isSuccess
protected suspend fun fixEnv() = extractFiles() && "fix_env $installDir".sh().isSuccess
protected fun uninstall() = "run_uninstaller $AppApkPath".sh().isSuccess

View File

@ -12,7 +12,7 @@ import com.topjohnwu.magisk.core.createNewResources
import com.topjohnwu.magisk.core.di.AppContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.util.*
import java.util.Locale
var currentLocale: Locale = Locale.getDefault()

View File

@ -15,9 +15,6 @@ import com.topjohnwu.magisk.core.di.AppContext
import java.io.File
import java.io.FileNotFoundException
import java.io.IOException
import java.io.OutputStream
import java.security.MessageDigest
import kotlin.experimental.and
@Suppress("DEPRECATION")
object MediaStoreUtils {
@ -120,24 +117,6 @@ object MediaStoreUtils {
return this.toString()
}
fun Uri.checkSum(alg: String, reference: String) = runCatching {
this.inputStream().use {
val digest = MessageDigest.getInstance(alg)
it.copyTo(object : OutputStream() {
override fun write(b: Int) {
digest.update(b.toByte())
}
override fun write(b: ByteArray, off: Int, len: Int) {
digest.update(b, off, len)
}
})
val sb = StringBuilder()
digest.digest().forEach { b -> sb.append("%02x".format(b and 0xff.toByte())) }
sb.toString() == reference
}
}.getOrElse { false }
interface UriFile {
val uri: Uri
fun delete(): Boolean

View File

@ -13,6 +13,8 @@ import com.topjohnwu.magisk.core.ktx.rawResource
import com.topjohnwu.magisk.core.ktx.writeTo
import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.ShellUtils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import java.io.File
import java.util.jar.JarFile
@ -34,7 +36,9 @@ class ShellInit : Shell.Initializer() {
val bb = jar.getJarEntry("lib/${Const.CPU_ABI}/libbusybox.so")
localBB = context.deviceProtectedContext.cachedFile("busybox")
localBB.delete()
jar.getInputStream(bb).writeTo(localBB)
runBlocking {
jar.getInputStream(bb).writeTo(localBB, dispatcher = Dispatchers.Unconfined)
}
localBB.setExecutable(true)
} else {
localBB = File(context.applicationInfo.nativeLibraryDir, "libbusybox.so")

View File

@ -1,5 +1,6 @@
package com.topjohnwu.magisk.core.utils
import com.topjohnwu.magisk.core.ktx.copyAll
import java.io.File
import java.io.IOException
import java.io.InputStream
@ -7,14 +8,14 @@ import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
@Throws(IOException::class)
fun File.unzip(folder: File, path: String = "", junkPath: Boolean = false) {
suspend fun File.unzip(folder: File, path: String = "", junkPath: Boolean = false) {
inputStream().buffered().use {
it.unzip(folder, path, junkPath)
}
}
@Throws(IOException::class)
fun InputStream.unzip(folder: File, path: String, junkPath: Boolean) {
suspend fun InputStream.unzip(folder: File, path: String, junkPath: Boolean) {
try {
val zin = ZipInputStream(this)
var entry: ZipEntry
@ -34,7 +35,7 @@ fun InputStream.unzip(folder: File, path: String, junkPath: Boolean) {
if (!it.exists())
it.mkdirs()
}
dest.outputStream().use { out -> zin.copyTo(out) }
dest.outputStream().use { out -> zin.copyAll(out) }
}
} catch (e: IllegalArgumentException) {
throw IOException(e)

View File

@ -8,7 +8,12 @@ import android.text.Spanned
import android.util.TypedValue
import android.view.View
import android.view.ViewGroup
import android.widget.*
import android.widget.ArrayAdapter
import android.widget.Button
import android.widget.ImageView
import android.widget.ProgressBar
import android.widget.Spinner
import android.widget.TextView
import androidx.annotation.DrawableRes
import androidx.appcompat.widget.Toolbar
import androidx.cardview.widget.CardView
@ -20,7 +25,11 @@ import androidx.databinding.BindingAdapter
import androidx.databinding.InverseBindingAdapter
import androidx.databinding.InverseBindingListener
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
import androidx.recyclerview.widget.*
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.StaggeredGridLayoutManager
import com.google.android.material.button.MaterialButton
import com.google.android.material.card.MaterialCardView
import com.google.android.material.chip.Chip

View File

@ -3,7 +3,7 @@ package com.topjohnwu.magisk.databinding
import androidx.databinding.ListChangeRegistry
import androidx.databinding.ObservableList
import androidx.databinding.ObservableList.OnListChangedCallback
import java.util.*
import java.util.AbstractList
@Suppress("UNCHECKED_CAST")
class MergeObservableList<T> : AbstractList<T>(), ObservableList<T> {

View File

@ -4,7 +4,7 @@ import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.di.AppContext
import com.topjohnwu.magisk.core.di.ServiceLocator
import com.topjohnwu.magisk.core.download.DownloadService
import com.topjohnwu.magisk.core.download.DownloadEngine
import com.topjohnwu.magisk.core.download.Subject
import com.topjohnwu.magisk.view.MagiskDialog
import java.io.File
@ -29,7 +29,7 @@ class ManagerInstallDialog : MarkDownDialog() {
setCancelable(true)
setButton(MagiskDialog.ButtonType.POSITIVE) {
text = R.string.install
onClick { DownloadService.start(activity, Subject.App()) }
onClick { DownloadEngine.startWithActivity(activity, Subject.App()) }
}
setButton(MagiskDialog.ButtonType.NEGATIVE) {
text = android.R.string.cancel

View File

@ -2,8 +2,7 @@ package com.topjohnwu.magisk.dialog
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.di.ServiceLocator
import com.topjohnwu.magisk.core.download.Action
import com.topjohnwu.magisk.core.download.DownloadService
import com.topjohnwu.magisk.core.download.DownloadEngine
import com.topjohnwu.magisk.core.download.Subject
import com.topjohnwu.magisk.core.model.module.OnlineModule
import com.topjohnwu.magisk.view.MagiskDialog
@ -22,9 +21,7 @@ class OnlineModuleInstallDialog(private val item: OnlineModule) : MarkDownDialog
dialog.apply {
fun download(install: Boolean) {
val action = if (install) Action.Flash else Action.Download
val subject = Subject.Module(item, action)
DownloadService.start(activity, subject)
DownloadEngine.startWithActivity(activity, Subject.Module(item, install))
}
val title = context.getString(R.string.repo_install_title,

View File

@ -12,7 +12,6 @@ import androidx.core.content.pm.ShortcutManagerCompat
import androidx.core.view.forEach
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavDirections
import com.topjohnwu.magisk.MainDirections
import com.topjohnwu.magisk.R
@ -24,13 +23,10 @@ import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.isRunningAsStub
import com.topjohnwu.magisk.core.model.module.LocalModule
import com.topjohnwu.magisk.core.tasks.HideAPK
import com.topjohnwu.magisk.databinding.ActivityMainMd2Binding
import com.topjohnwu.magisk.ui.home.HomeFragmentDirections
import com.topjohnwu.magisk.view.MagiskDialog
import com.topjohnwu.magisk.view.Shortcuts
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.io.File
class MainViewModel : BaseViewModel()
@ -62,7 +58,6 @@ class MainActivity : SplashActivity<ActivityMainMd2Binding>() {
setContentView()
showUnsupportedMessage()
askForHomeShortcut()
checkStubComponent()
// Ask permission to post notifications for background update check
if (Config.checkUpdate) {
@ -231,22 +226,4 @@ class MainActivity : SplashActivity<ActivityMainMd2Binding>() {
}.show()
}
}
@SuppressLint("InlinedApi")
private fun checkStubComponent() {
if (intent.component?.className?.contains(HideAPK.PLACEHOLDER) == true) {
// The stub APK was not properly patched, re-apply our changes
withPermission(Manifest.permission.REQUEST_INSTALL_PACKAGES) { granted ->
if (granted) {
lifecycleScope.launch(Dispatchers.IO) {
val apk = File(applicationInfo.sourceDir)
HideAPK.upgrade(this@MainActivity, apk)?.let {
startActivity(it)
}
}
}
}
}
}
}

View File

@ -8,23 +8,30 @@ import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.databinding.ViewDataBinding
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import com.topjohnwu.magisk.BuildConfig
import com.topjohnwu.magisk.BuildConfig.APPLICATION_ID
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.StubApk
import com.topjohnwu.magisk.arch.NavigationActivity
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.JobService
import com.topjohnwu.magisk.core.di.ServiceLocator
import com.topjohnwu.magisk.core.isRunningAsStub
import com.topjohnwu.magisk.core.ktx.toast
import com.topjohnwu.magisk.core.ktx.writeTo
import com.topjohnwu.magisk.core.tasks.HideAPK
import com.topjohnwu.magisk.core.utils.RootUtils
import com.topjohnwu.magisk.ui.theme.Theme
import com.topjohnwu.magisk.view.MagiskDialog
import com.topjohnwu.magisk.view.Shortcuts
import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import timber.log.Timber
import java.io.File
import java.io.IOException
@SuppressLint("CustomSplashScreen")
abstract class SplashActivity<Binding : ViewDataBinding> : NavigationActivity<Binding>() {
@ -58,7 +65,7 @@ abstract class SplashActivity<Binding : ViewDataBinding> : NavigationActivity<Bi
showInvalidStateMessage()
return@getShell
}
preLoad(savedInstanceState)
initialize(savedInstanceState)
}
}
}
@ -102,7 +109,7 @@ abstract class SplashActivity<Binding : ViewDataBinding> : NavigationActivity<Bi
}
}
private fun preLoad(savedState: Bundle?) {
private fun initialize(savedState: Bundle?) {
val prevPkg = intent.getStringExtra(Const.Key.PREV_PKG)?.let {
// Make sure the calling package matches (prevent DoS)
if (it == realCallingPackage)
@ -112,7 +119,21 @@ abstract class SplashActivity<Binding : ViewDataBinding> : NavigationActivity<Bi
}
Config.load(prevPkg)
handleRepackage(prevPkg)
if (packageName != APPLICATION_ID) {
runCatching {
// Hidden, remove com.topjohnwu.magisk if exist as it could be malware
packageManager.getApplicationInfo(APPLICATION_ID, 0)
Shell.cmd("(pm uninstall $APPLICATION_ID)& >/dev/null 2>&1").exec()
}
} else {
if (Config.suManager.isNotEmpty())
Config.suManager = ""
if (prevPkg != null) {
Shell.cmd("(pm uninstall $prevPkg)& >/dev/null 2>&1").exec()
}
}
if (prevPkg != null) {
runOnUiThread {
// Relaunch the process after package migration
@ -121,6 +142,31 @@ abstract class SplashActivity<Binding : ViewDataBinding> : NavigationActivity<Bi
return
}
// Validate stub APK
if (isRunningAsStub && (
// Version mismatch
Info.stub!!.version != BuildConfig.STUB_VERSION ||
// Not properly patched
intent.component!!.className.contains(HideAPK.PLACEHOLDER)
)) {
withPermission(REQUEST_INSTALL_PACKAGES) { granted ->
if (granted) {
lifecycleScope.launch(Dispatchers.IO) {
val apk = File(cacheDir, "stub.apk")
try {
assets.open("stub.apk").writeTo(apk)
HideAPK.upgrade(this@SplashActivity, apk)?.let {
startActivity(it)
}
} catch (e: IOException) {
Timber.e(e)
}
}
}
}
return
}
JobService.schedule(this)
Shortcuts.setupDynamic(this)
@ -144,19 +190,4 @@ abstract class SplashActivity<Binding : ViewDataBinding> : NavigationActivity<Bi
}
}
}
private fun handleRepackage(pkg: String?) {
if (packageName != APPLICATION_ID) {
runCatching {
// Hidden, remove com.topjohnwu.magisk if exist as it could be malware
packageManager.getApplicationInfo(APPLICATION_ID, 0)
Shell.cmd("(pm uninstall $APPLICATION_ID)& >/dev/null 2>&1").exec()
}
} else {
if (Config.suManager.isNotEmpty())
Config.suManager = ""
pkg ?: return
Shell.cmd("(pm uninstall $pkg)& >/dev/null 2>&1").exec()
}
}
}

View File

@ -4,7 +4,12 @@ import android.annotation.SuppressLint
import android.content.pm.ApplicationInfo
import android.content.pm.ComponentInfo
import android.content.pm.PackageManager
import android.content.pm.PackageManager.*
import android.content.pm.PackageManager.GET_ACTIVITIES
import android.content.pm.PackageManager.GET_PROVIDERS
import android.content.pm.PackageManager.GET_RECEIVERS
import android.content.pm.PackageManager.GET_SERVICES
import android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS
import android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES
import android.content.pm.ServiceInfo
import android.graphics.drawable.Drawable
import android.os.Build
@ -12,7 +17,7 @@ import android.os.Build.VERSION.SDK_INT
import androidx.core.os.ProcessCompat
import com.topjohnwu.magisk.core.ktx.getLabel
import com.topjohnwu.magisk.core.utils.currentLocale
import java.util.*
import java.util.TreeSet
class CmdlineListItem(line: String) {
val packageName: String

View File

@ -5,7 +5,11 @@ import android.content.Context
import android.content.pm.ActivityInfo
import android.net.Uri
import android.os.Bundle
import android.view.*
import android.view.KeyEvent
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import androidx.core.view.MenuProvider
import androidx.core.view.isVisible
import androidx.navigation.NavDeepLinkBuilder

View File

@ -72,6 +72,7 @@ class FlashViewModel : BaseViewModel() {
MagiskInstaller.Direct(outItems, logItems).exec()
}
Const.Value.FLASH_INACTIVE_SLOT -> {
showReboot = false
MagiskInstaller.SecondSlot(outItems, logItems).exec()
}
Const.Value.PATCH_FILE -> {

View File

@ -14,7 +14,7 @@ import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.arch.BaseFragment
import com.topjohnwu.magisk.arch.viewModel
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.download.DownloadService
import com.topjohnwu.magisk.core.download.DownloadEngine
import com.topjohnwu.magisk.databinding.FragmentHomeMd2Binding
class HomeFragment : BaseFragment<FragmentHomeMd2Binding>(), MenuProvider {
@ -25,7 +25,7 @@ class HomeFragment : BaseFragment<FragmentHomeMd2Binding>(), MenuProvider {
override fun onStart() {
super.onStart()
activity?.setTitle(R.string.section_home)
DownloadService.observeProgress(this, viewModel::onProgressUpdate)
DownloadEngine.observeProgress(this, viewModel::onProgressUpdate)
}
private fun checkTitle(text: TextView, icon: ImageView) {

View File

@ -7,6 +7,8 @@ import android.view.MenuItem
import android.widget.PopupMenu
import androidx.core.content.getSystemService
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.base.BaseActivity
import com.topjohnwu.magisk.core.ktx.reboot as systemReboot
@ -20,6 +22,11 @@ object RebootMenu {
R.id.action_reboot_download -> systemReboot("download")
R.id.action_reboot_edl -> systemReboot("edl")
R.id.action_reboot_recovery -> systemReboot("recovery")
R.id.action_reboot_safe_mode -> {
val status = !item.isChecked
item.isChecked = status
Config.bootloop = if (status) 2 else 0
}
else -> Unit
}
return true
@ -29,10 +36,16 @@ object RebootMenu {
val themeWrapper = ContextThemeWrapper(activity, R.style.Foundation_PopupMenu)
val menu = PopupMenu(themeWrapper, activity.findViewById(R.id.action_reboot))
activity.menuInflater.inflate(R.menu.menu_reboot, menu.menu)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R &&
activity.getSystemService<PowerManager>()?.isRebootingUserspaceSupported == true)
menu.menu.findItem(R.id.action_reboot_userspace).isVisible = true
menu.setOnMenuItemClickListener(RebootMenu::reboot)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R &&
activity.getSystemService<PowerManager>()?.isRebootingUserspaceSupported == true) {
menu.menu.findItem(R.id.action_reboot_userspace).isVisible = true
}
if (Const.Version.isCanary()) {
menu.menu.findItem(R.id.action_reboot_safe_mode).isChecked = Config.bootloop >= 2
} else {
menu.menu.findItem(R.id.action_reboot_safe_mode).isVisible = false
}
return menu
}

View File

@ -227,25 +227,14 @@ object Zygisk : BaseSettingsItem.Toggle() {
get() = Config.zygisk
set(value) {
Config.zygisk = value
DenyList.isEnabled = value
DenyListConfig.isEnabled = value
notifyPropertyChanged(BR.description)
DenyList.notifyPropertyChanged(BR.description)
}
val mismatch get() = value != Info.isZygiskEnabled
}
object DenyList : BaseSettingsItem.Toggle() {
override val title = R.string.settings_denylist_title.asText()
override val description get() =
if (isEnabled) {
if (Zygisk.mismatch)
R.string.reboot_apply_change.asText()
else
R.string.settings_denylist_summary.asText()
} else {
R.string.settings_denylist_error.asText(R.string.zygisk.asText())
}
override val description get() = R.string.settings_denylist_summary.asText()
override var value = Config.denyList
set(value) {
@ -260,18 +249,11 @@ object DenyList : BaseSettingsItem.Toggle() {
}
}
}
override fun refresh() {
isEnabled = Zygisk.value
}
}
object DenyListConfig : BaseSettingsItem.Blank() {
override val title = R.string.settings_denylist_config_title.asText()
override val description = R.string.settings_denylist_config_summary.asText()
override fun refresh() {
isEnabled = Zygisk.value
}
}
// --- Superuser

View File

@ -11,7 +11,7 @@ import androidx.core.content.getSystemService
import androidx.core.graphics.drawable.toIcon
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.di.AppContext
import com.topjohnwu.magisk.core.download.DownloadService
import com.topjohnwu.magisk.core.download.DownloadEngine
import com.topjohnwu.magisk.core.download.Subject
import com.topjohnwu.magisk.core.ktx.getBitmap
import com.topjohnwu.magisk.core.ktx.selfLaunchIntent
@ -67,7 +67,7 @@ object Notifications {
fun updateAvailable() {
AppContext.apply {
val intent = DownloadService.getPendingIntent(this, Subject.App())
val intent = DownloadEngine.getPendingIntent(this, Subject.App())
val bitmap = getBitmap(R.drawable.ic_magisk_outline)
val builder = if (SDK_INT >= Build.VERSION_CODES.O) {
Notification.Builder(this, UPDATE_CHANNEL)

View File

@ -26,4 +26,9 @@
android:id="@+id/action_reboot_edl"
android:title="@string/reboot_edl" />
</menu>
<item
android:id="@+id/action_reboot_safe_mode"
android:checkable="true"
android:title="@string/reboot_safe_mode" />
</menu>

View File

@ -14,7 +14,7 @@ env_check() {
[ -f "$MAGISKBIN/magiskpolicy" ] || return 1
fi
if [ "$2" -ge 25210 ]; then
[ -b "$MAGISKTMP/.magisk/block/preinit" ] || return 2
[ -b "$MAGISKTMP/.magisk/device/preinit" ] || [ -b "$MAGISKTMP/.magisk/block/preinit" ] || return 2
fi
grep -xqF "MAGISK_VER='$1'" "$MAGISKBIN/util_functions.sh" || return 3
grep -xqF "MAGISK_VER_CODE=$2" "$MAGISKBIN/util_functions.sh" || return 3

View File

@ -8,14 +8,14 @@
<string name="install">Installieren</string>
<string name="section_home">Startseite</string>
<string name="section_theme">Themen</string>
<string name="denylist">Verweigerungsliste</string>
<string name="denylist">Ausnahmeliste</string>
<!--Home-->
<string name="no_connection">Keine Verbindung verfügbar</string>
<string name="app_changelog">Änderungen</string>
<string name="loading">Laden …</string>
<string name="update">Aktualisieren</string>
<string name="not_available">N/A</string>
<string name="not_available">Nicht verfügbar</string>
<string name="hide">Verstecken</string>
<string name="home_package">Paket</string>
<string name="home_app_title">App</string>
@ -88,6 +88,9 @@
<string name="logs_cleared">Protokoll erfolgreich gelöscht.</string>
<string name="pid">PID: %1$d</string>
<string name="target_uid">Ziel-UID: %1$d</string>
<string name="target_pid">NS Target PID einbinden: %s</string>
<string name="selinux_context">SELinux Kontext: %s</string>
<string name="supp_group">Ergänzende Gruppe: %s</string>
<!--SafetyNet-->
@ -138,11 +141,11 @@
<string name="settings_update_custom">Benutzerdefiniert</string>
<string name="settings_update_custom_msg">Benutzerdefinierte URL einfügen</string>
<string name="settings_zygisk_summary">Teile von Magisk im Zygoten-Daemon ausführen</string>
<string name="settings_denylist_title">Verweigerungsliste erzwingen</string>
<string name="settings_denylist_summary">Bei Prozessen, die auf der Verweigerungsliste stehen, werden alle Magisk-Änderungen rückgängig gemacht.</string>
<string name="settings_denylist_title">Ausnahmeliste erzwingen</string>
<string name="settings_denylist_summary">Bei Prozessen, die auf der Ausnahmeliste stehen, werden alle Magisk-Änderungen rückgängig gemacht.</string>
<string name="settings_denylist_error">Für diese Funktion muss %1$s aktiviert sein</string>
<string name="settings_denylist_config_title">Verweigerungsliste konfigurieren</string>
<string name="settings_denylist_config_summary">Auswahl der Prozesse, die in die Verweigerungsliste aufgenommen werden sollen</string>
<string name="settings_denylist_config_title">Ausnahmeliste konfigurieren</string>
<string name="settings_denylist_config_summary">Auswahl der Prozesse, die in die Ausnahmeliste aufgenommen werden sollen</string>
<string name="settings_hosts_title">Systemlose Hosts-Datei</string>
<string name="settings_hosts_summary">Systemlose Unterstützung für Werbeblocker</string>
<string name="settings_hosts_toast">Systemloses Hosts-Modul hinzugefügt</string>
@ -167,6 +170,9 @@
<string name="settings_su_reauth_summary">Superuser-Berechtigungen nach App-Aktualisierung erneut authentifizieren</string>
<string name="settings_su_tapjack_title">Tapjacking-Schutz aktivieren</string>
<string name="settings_su_tapjack_summary">Das Dialogfeld der Superuser-Eingabeaufforderung reagiert nicht auf Eingaben, wenn es durch ein anderes Fenster oder Überlagerung verdeckt wird</string>
<string name="settings_su_auth_title">Benutzerauthentifizierung</string>
<string name="settings_su_auth_summary">Nachfrage nach Benutzerauthentifizierung bei Superuser-Anfragen</string>
<string name="settings_su_auth_insecure">Auf dem Gerät ist keine Authentifizierungsmethode konfiguriert</string>>
<string name="settings_customization">Personalisierung</string>
<string name="setting_add_shortcut_summary">Hinzufügen einer hübschen Startbildschirm-Verknüpfung, falls der Name und das Symbol nach dem Ausblenden der App schwer zu erkennen sind</string>
<string name="settings_doh_title">DNS über HTTPS</string>

View File

@ -19,7 +19,6 @@
<string name="hide">Ocultar</string>
<string name="home_package">Nombre de paquete:</string>
<string name="home_app_title">App</string>
<string name="home_notice_content">Descarga Magisk únicamente desde la página oficial de GitHub. ¡Las descargas desde fuentes desconocidas pueden ser maliciosas!</string>
<string name="home_support_title">Apóyanos</string>
<string name="home_follow_title">Síguenos</string>
@ -75,7 +74,6 @@
<string name="su_revoke_msg">Confirma para revocarle los privilegios de superusuario a %1$s</string>
<string name="toast">Mensaje emergente</string>
<string name="none">Ninguno</string>
<string name="superuser_toggle_notification">Notificaciones</string>
<string name="superuser_toggle_revoke">Revocar</string>
<string name="superuser_policy_none">Ninguna app ha solicitado privilegios de superusuario aún.</string>
@ -87,7 +85,10 @@
<string name="menuClearLog">Limpiar registro</string>
<string name="logs_cleared">Registros limpiados correctamente</string>
<string name="pid">PID: %1$d</string>
<string name="target_uid">Target UID: %1$d</string>
<string name="target_uid">Objetivo UID: %1$d</string>
<string name="target_pid">Montar ns objetivo PID: %s</string>
<string name="selinux_context">Contexto SELinux: %s</string>
<string name="supp_group">Grupo suplementario: %s</string>
<!--SafetyNet/PlayIntegrity-->
@ -167,11 +168,13 @@
<string name="settings_su_reauth_summary">Las apps volverán a solicitar privilegios de superusuario tras actualizarse</string>
<string name="settings_su_tapjack_title">Protección contra tapjacking</string>
<string name="settings_su_tapjack_summary">No podrás interactuar con las solicitudes de superusuario mientras estén tapadas por cualquier otra ventana o superposición</string>
<string name="settings_su_auth_title">Autenticación de usuario</string>
<string name="settings_su_auth_summary">Preguntar por la autenticación de usuario durante las peticiones de Superusuario</string>
<string name="settings_su_auth_insecure">No hay método de autenticación configurado en el dispositivo</string>
<string name="settings_customization">Personalización</string>
<string name="setting_add_shortcut_summary">Añade un atajo a la app con el ícono de Magisk a la pantalla de inicio en caso de que te resulte difícil reconocerla tras haberle cambiado el nombre y el ícono</string>
<string name="settings_doh_title">DNS sobre HTTPS</string>
<string name="settings_doh_description">Proporciona una solución alternativa (workaround) contra el envenenamiento de DNS en algunos países</string>
<string name="multiuser_mode">Modo multiusuario</string>
<string name="settings_owner_only">Propietario del dispositivo</string>
<string name="settings_owner_manage">Administrado por el propietario del dispositivo</string>
@ -179,7 +182,6 @@
<string name="owner_only_summary">Solo el administrador tiene acceso root</string>
<string name="owner_manage_summary">Solo el administrador puede manipular y recibir solicitudes de superusuario</string>
<string name="user_independent_summary">Cada usuario tiene sus propias reglas y acceso al root</string>
<string name="mount_namespace_mode">Modo del namespace de montaje</string>
<string name="settings_ns_global">Namespace global</string>
<string name="settings_ns_requester">Namespace heredado</string>
@ -238,4 +240,4 @@
<string name="reboot_apply_change">Reinicia para aplicar los cambios</string>
<string name="restore_app_confirmation">Se restaurará la app oculta de vuelta a la original. ¿Quieres continuar?</string>
</resources>
</resources>

View File

@ -182,7 +182,7 @@
<string name="settings_ns_isolate">격리된 네임스페이스</string>
<string name="global_summary">모든 루트 세션이 전역 마운트 네임스페이스를 사용합니다.</string>
<string name="requester_summary">루트 세션은 요청자의 네임스페이스를 상속합니다.</string>
<string name="isolate_summary">각각의 루트 세션은 자신만의 독립된 네임스페이를 사용합니다.</string>
<string name="isolate_summary">각각의 루트 세션은 자신만의 독립된 네임스페이를 사용합니다.</string>
<!--Notifications-->
<string name="update_channel">Magisk 업데이트</string>

View File

@ -19,11 +19,11 @@
<string name="hide">Ocultar</string>
<string name="home_package">Pacote</string>
<string name="home_app_title">App</string>
<string name="home_notice_content">Baixe o Magisk SOMENTE pela página oficial do GitHub. Arquivos de fontes desconhecidas podem ser maliciosos e podem danificar seu aparelho!</string>
<string name="home_support_title">Apoie</string>
<string name="home_follow_title">Siga</string>
<string name="home_notice_content">Baixe o Magisk SOMENTE pela página oficial do GitHub. Arquivos de fontes desconhecidas podem ser maliciosos!</string>
<string name="home_support_title">Apoie-nos</string>
<string name="home_follow_title">Siga-nos</string>
<string name="home_item_source">Fonte</string>
<string name="home_support_content">Magisk sempre foi e sempre será, gratuito e de código aberto. No entanto, você pode agradecer enviando uma pequena doação.</string>
<string name="home_support_content">Magisk sempre foi e sempre será, gratuito e de código aberto. No entanto, você pode nos ajudar enviando uma pequena doação.</string>
<string name="home_installed_version">Instalado</string>
<string name="home_latest_version">Recente</string>
<string name="invalid_update_channel">Canal de atualização inválido</string>
@ -64,7 +64,7 @@
<string name="sixtymin">60 mins</string>
<string name="su_allow_toast">%1$s foi permitido o acesso de SuperUsuário</string>
<string name="su_deny_toast">%1$s foi negado o acesso de SuperUsuário</string>
<string name="su_snack_grant">Os acessos de SuperUsuário de %1$s foram permitidos</string>
<string name="su_snack_grant">Os acessos de SuperUsuário de %1$s foram concedidos</string>
<string name="su_snack_deny">Os acessos de SuperUsuário de %1$s foram negados</string>
<string name="su_snack_notif_on">As notificações de %1$s foram ativadas</string>
<string name="su_snack_notif_off">As notificações de %1$s foram desativadas</string>
@ -98,11 +98,12 @@
<!--Module-->
<string name="no_info_provided">(Nenhuma informação fornecida)</string>
<string name="reboot_userspace">Reinício rápido</string>
<string name="reboot_userspace">Reinicialização suave</string>
<string name="reboot_recovery">Reiniciar em modo Recovery</string>
<string name="reboot_bootloader">Reiniciar em modo Bootloader</string>
<string name="reboot_download">Reiniciar em modo Download</string>
<string name="reboot_edl">Reiniciar em modo EDL</string>
<string name="reboot_safe_mode">Modo de segurança</string>
<string name="module_version_author">%1$s por %2$s</string>
<string name="module_state_remove">Remover</string>
<string name="module_state_restore">Restaurar</string>
@ -117,34 +118,34 @@
<!--Settings-->
<string name="settings_dark_mode_title">Modo do tema</string>
<string name="settings_dark_mode_message">Selecione o modo mais adequado ao seu estilo!</string>
<string name="settings_dark_mode_message">Selecione o modo mais adequado para você!</string>
<string name="settings_dark_mode_light">Sempre claro</string>
<string name="settings_dark_mode_system">Mesmo do sistema</string>
<string name="settings_dark_mode_system">Seguir sistema</string>
<string name="settings_dark_mode_dark">Sempre escuro</string>
<string name="settings_download_path_title">Caminho para baixar</string>
<string name="settings_download_path_message">Os arquivos serão salvos em %1$s</string>
<string name="settings_hide_app_title">Ocultar o app do Magisk</string>
<string name="settings_hide_app_title">Ocultar app do Magisk</string>
<string name="settings_hide_app_summary">Instala o app oculto com ID aleatório e nome personalizado</string>
<string name="settings_restore_app_title">Restaurar o app do Magisk</string>
<string name="settings_restore_app_title">Restaurar app do Magisk</string>
<string name="settings_restore_app_summary">Desoculta o app do Magisk e restaura o APK original</string>
<string name="language">Idioma</string>
<string name="system_default">(Padrão do sistema)</string>
<string name="settings_check_update_title">Verificar atualizações</string>
<string name="settings_check_update_summary">Verifica periodicamente se há atualizações em segundo plano</string>
<string name="settings_check_update_title">Verificar por atualizações</string>
<string name="settings_check_update_summary">Verifique automaticamente se há atualizações ao abrir o app</string>
<string name="settings_update_channel_title">Canal de atualização</string>
<string name="settings_update_stable">Estável</string>
<string name="settings_update_beta">Beta</string>
<string name="settings_update_custom">Personalizado</string>
<string name="settings_update_custom_msg">Insira um URL de canal personalizado</string>
<string name="settings_zygisk_summary">Executa partes do Magisk no Zygote</string>
<string name="settings_denylist_title">Aplicar Lista de negação</string>
<string name="settings_denylist_summary">Os processos na Lista de negação terão todas as modificações do Magisk revertidas</string>
<string name="settings_denylist_error">Isso requer %1$s para ser ativado</string>
<string name="settings_denylist_config_title">Configurar Lista de negação</string>
<string name="settings_denylist_config_summary">Selecione os processos a serem incluídos na Lista de negação</string>
<string name="settings_hosts_title">Hosts fora do sistema</string>
<string name="settings_hosts_summary">Suporte de /etc/hosts fora do sistema para apps Adblock</string>
<string name="settings_hosts_toast">Adicionado módulo de /etc/hosts fora do sistema</string>
<string name="settings_denylist_title">Aplicar lista de negação</string>
<string name="settings_denylist_summary">Os processos na lista de negação terão todas as modificações do Magisk revertidas</string>
<string name="settings_denylist_error">Este recurso requer que %1$s esteja ativado</string>
<string name="settings_denylist_config_title">Configurar lista de negação</string>
<string name="settings_denylist_config_summary">Selecione os processos a serem incluídos na lista de negação</string>
<string name="settings_hosts_title">Hosts sem sistema</string>
<string name="settings_hosts_summary">Suporte de hosts sem sistema para apps AdBlock</string>
<string name="settings_hosts_toast">Adicionado módulo de hosts sem sistema</string>
<string name="settings_app_name_hint">Novo nome</string>
<string name="settings_app_name_helper">O app do Magisk será reinstalado com este nome</string>
<string name="settings_app_name_error">Formato inválido</string>
@ -163,7 +164,7 @@
<string name="request_timeout">Tempo limite da solicitação</string>
<string name="superuser_notification">Notificação de SuperUsuário</string>
<string name="settings_su_reauth_title">Reautenticar após atualização</string>
<string name="settings_su_reauth_summary">Reautenticar permissões de SuperUsuário após atualizar os apps</string>
<string name="settings_su_reauth_summary">Solicite permissões de SuperUsuário novamente após atualizar os apps</string>
<string name="settings_su_tapjack_title">Proteção contra atividades sobrepostas</string>
<string name="settings_su_tapjack_summary">A caixa de diálogo do SuperUsuário não responderá à entrada enquanto estiver oculta por qualquer outra janela ou sobreposição</string>
<string name="settings_su_auth_title">Autenticação de usuário</string>
@ -180,13 +181,13 @@
<string name="owner_only_summary">Somente o proprietário tem acesso root</string>
<string name="owner_manage_summary">Somente o proprietário pode gerenciar o acesso root e receber pedidos de solicitação</string>
<string name="user_independent_summary">Cada usuário tem suas próprias regras de root separadas</string>
<string name="mount_namespace_mode">Montar modo Namespace</string>
<string name="settings_ns_global">Namespace global</string>
<string name="settings_ns_requester">Herdar Namespace</string>
<string name="settings_ns_isolate">Namespace isolado</string>
<string name="mount_namespace_mode">Montar namespace</string>
<string name="settings_ns_global">Global</string>
<string name="settings_ns_requester">Herdado</string>
<string name="settings_ns_isolate">Individual</string>
<string name="global_summary">Todas as sessões root usam o namespace de montagem global</string>
<string name="requester_summary">As sessões root herdarão o namespace do solicitante</string>
<string name="isolate_summary">Cada sessão root terá seu próprio namespace isolado</string>
<string name="isolate_summary">Cada sessão root terá seu próprio namespace individual</string>
<!--Notifications-->
<string name="update_channel">Atualizações do Magisk</string>
@ -207,7 +208,7 @@
<string name="release_notes">Notas da atualização</string>
<string name="flashing">Flashando…</string>
<string name="done">Concluído!</string>
<string name="failure">Falha!</string>
<string name="failure">Falhou!</string>
<string name="hide_app_title">Ocultando o app do Magisk…</string>
<string name="open_link_failed_toast">Nenhum app encontrado para abrir o link</string>
<string name="complete_uninstall">Desinstalação completa</string>

View File

@ -19,11 +19,11 @@
<string name="hide">Ocultar</string>
<string name="home_package">Pacote</string>
<string name="home_app_title">App</string>
<string name="home_notice_content">Baixe o Magisk SOMENTE pela página oficial do GitHub. Arquivos de fontes desconhecidas podem ser maliciosos e podem danificar seu aparelho!</string>
<string name="home_support_title">Apoie</string>
<string name="home_follow_title">Siga</string>
<string name="home_notice_content">Baixe o Magisk SOMENTE pela página oficial do GitHub. Arquivos de fontes desconhecidas podem ser maliciosos!</string>
<string name="home_support_title">Apoie-nos</string>
<string name="home_follow_title">Siga-nos</string>
<string name="home_item_source">Fonte</string>
<string name="home_support_content">Magisk sempre foi e sempre será, gratuito e de código aberto. No entanto, você pode agradecer enviando uma pequena doação.</string>
<string name="home_support_content">Magisk sempre foi e sempre será, gratuito e de código aberto. No entanto, você pode nos ajudar enviando uma pequena doação.</string>
<string name="home_installed_version">Instalado</string>
<string name="home_latest_version">Recente</string>
<string name="invalid_update_channel">Canal de atualização inválido</string>
@ -64,7 +64,7 @@
<string name="sixtymin">60 mins</string>
<string name="su_allow_toast">%1$s foi permitido o acesso de SuperUsuário</string>
<string name="su_deny_toast">%1$s foi negado o acesso de SuperUsuário</string>
<string name="su_snack_grant">Os acessos de SuperUsuário de %1$s foram permitidos</string>
<string name="su_snack_grant">Os acessos de SuperUsuário de %1$s foram concedidos</string>
<string name="su_snack_deny">Os acessos de SuperUsuário de %1$s foram negados</string>
<string name="su_snack_notif_on">As notificações de %1$s foram ativadas</string>
<string name="su_snack_notif_off">As notificações de %1$s foram desativadas</string>
@ -98,11 +98,12 @@
<!--Module-->
<string name="no_info_provided">(Nenhuma informação fornecida)</string>
<string name="reboot_userspace">Reinício rápido</string>
<string name="reboot_userspace">Reinicialização suave</string>
<string name="reboot_recovery">Reiniciar em modo Recovery</string>
<string name="reboot_bootloader">Reiniciar em modo Bootloader</string>
<string name="reboot_download">Reiniciar em modo Download</string>
<string name="reboot_edl">Reiniciar em modo EDL</string>
<string name="reboot_safe_mode">Modo de segurança</string>
<string name="module_version_author">%1$s por %2$s</string>
<string name="module_state_remove">Remover</string>
<string name="module_state_restore">Restaurar</string>
@ -117,34 +118,34 @@
<!--Settings-->
<string name="settings_dark_mode_title">Modo do tema</string>
<string name="settings_dark_mode_message">Selecione o modo mais adequado ao seu estilo!</string>
<string name="settings_dark_mode_message">Selecione o modo mais adequado para você!</string>
<string name="settings_dark_mode_light">Sempre claro</string>
<string name="settings_dark_mode_system">Mesmo do sistema</string>
<string name="settings_dark_mode_system">Seguir sistema</string>
<string name="settings_dark_mode_dark">Sempre escuro</string>
<string name="settings_download_path_title">Caminho para baixar</string>
<string name="settings_download_path_message">Os arquivos serão salvos em %1$s</string>
<string name="settings_hide_app_title">Ocultar o app do Magisk</string>
<string name="settings_hide_app_title">Ocultar app do Magisk</string>
<string name="settings_hide_app_summary">Instala o app oculto com ID aleatório e nome personalizado</string>
<string name="settings_restore_app_title">Restaurar o app do Magisk</string>
<string name="settings_restore_app_title">Restaurar app do Magisk</string>
<string name="settings_restore_app_summary">Desoculta o app do Magisk e restaura o APK original</string>
<string name="language">Idioma</string>
<string name="system_default">(Padrão do sistema)</string>
<string name="settings_check_update_title">Verificar atualizações</string>
<string name="settings_check_update_summary">Verifica periodicamente se há atualizações em segundo plano</string>
<string name="settings_check_update_title">Verificar por atualizações</string>
<string name="settings_check_update_summary">Verifique automaticamente se há atualizações ao abrir o app</string>
<string name="settings_update_channel_title">Canal de atualização</string>
<string name="settings_update_stable">Estável</string>
<string name="settings_update_beta">Beta</string>
<string name="settings_update_custom">Personalizado</string>
<string name="settings_update_custom_msg">Insira um URL de canal personalizado</string>
<string name="settings_zygisk_summary">Executa partes do Magisk no Zygote</string>
<string name="settings_denylist_title">Aplicar Lista de negação</string>
<string name="settings_denylist_summary">Os processos na Lista de negação terão todas as modificações do Magisk revertidas</string>
<string name="settings_denylist_error">Isso requer %1$s para ser ativado</string>
<string name="settings_denylist_config_title">Configurar Lista de negação</string>
<string name="settings_denylist_config_summary">Selecione os processos a serem incluídos na Lista de negação</string>
<string name="settings_hosts_title">Hosts fora do sistema</string>
<string name="settings_hosts_summary">Suporte de /etc/hosts fora do sistema para apps Adblock</string>
<string name="settings_hosts_toast">Adicionado módulo de /etc/hosts fora do sistema</string>
<string name="settings_denylist_title">Aplicar lista de negação</string>
<string name="settings_denylist_summary">Os processos na lista de negação terão todas as modificações do Magisk revertidas</string>
<string name="settings_denylist_error">Este recurso requer que %1$s esteja ativado</string>
<string name="settings_denylist_config_title">Configurar lista de negação</string>
<string name="settings_denylist_config_summary">Selecione os processos a serem incluídos na lista de negação</string>
<string name="settings_hosts_title">Hosts sem sistema</string>
<string name="settings_hosts_summary">Suporte de hosts sem sistema para apps AdBlock</string>
<string name="settings_hosts_toast">Adicionado módulo de hosts sem sistema</string>
<string name="settings_app_name_hint">Novo nome</string>
<string name="settings_app_name_helper">O app do Magisk será reinstalado com este nome</string>
<string name="settings_app_name_error">Formato inválido</string>
@ -163,7 +164,7 @@
<string name="request_timeout">Tempo limite da solicitação</string>
<string name="superuser_notification">Notificação de SuperUsuário</string>
<string name="settings_su_reauth_title">Reautenticar após atualização</string>
<string name="settings_su_reauth_summary">Reautenticar permissões de SuperUsuário após atualizar os apps</string>
<string name="settings_su_reauth_summary">Solicite permissões de SuperUsuário novamente após atualizar os apps</string>
<string name="settings_su_tapjack_title">Proteção contra atividades sobrepostas</string>
<string name="settings_su_tapjack_summary">A caixa de diálogo do SuperUsuário não responderá à entrada enquanto estiver oculta por qualquer outra janela ou sobreposição</string>
<string name="settings_su_auth_title">Autenticação de usuário</string>
@ -180,13 +181,13 @@
<string name="owner_only_summary">Somente o proprietário tem acesso root</string>
<string name="owner_manage_summary">Somente o proprietário pode gerenciar o acesso root e receber pedidos de solicitação</string>
<string name="user_independent_summary">Cada usuário tem suas próprias regras de root separadas</string>
<string name="mount_namespace_mode">Montar modo Namespace</string>
<string name="settings_ns_global">Namespace global</string>
<string name="settings_ns_requester">Herdar Namespace</string>
<string name="settings_ns_isolate">Namespace isolado</string>
<string name="mount_namespace_mode">Montar namespace</string>
<string name="settings_ns_global">Global</string>
<string name="settings_ns_requester">Herdado</string>
<string name="settings_ns_isolate">Individual</string>
<string name="global_summary">Todas as sessões root usam o namespace de montagem global</string>
<string name="requester_summary">As sessões root herdarão o namespace do solicitante</string>
<string name="isolate_summary">Cada sessão root terá seu próprio namespace isolado</string>
<string name="isolate_summary">Cada sessão root terá seu próprio namespace individual</string>
<!--Notifications-->
<string name="update_channel">Atualizações do Magisk</string>
@ -207,7 +208,7 @@
<string name="release_notes">Notas da atualização</string>
<string name="flashing">Flashando…</string>
<string name="done">Concluído!</string>
<string name="failure">Falha!</string>
<string name="failure">Falhou!</string>
<string name="hide_app_title">Ocultando o app do Magisk…</string>
<string name="open_link_failed_toast">Nenhum app encontrado para abrir o link</string>
<string name="complete_uninstall">Desinstalação completa</string>

View File

@ -107,6 +107,7 @@
<string name="reboot_bootloader">Reštartovať do Bootloader</string>
<string name="reboot_download">Reštartovať do Download</string>
<string name="reboot_edl">Reštartovať do EDL</string>
<string name="reboot_safe_mode">Núdzový režim</string>
<string name="module_version_author">%1$s od %2$s</string>
<string name="module_state_remove">Odstrániť</string>
<string name="module_state_restore">Obnoviť</string>

View File

@ -107,6 +107,7 @@
<string name="reboot_bootloader">Rinis te Bootloader</string>
<string name="reboot_download">Rinis te Download</string>
<string name="reboot_edl">Rinis te EDL</string>
<string name="reboot_safe_mode">Rinis në safe mode</string>
<string name="module_version_author">%1$s nga %2$s</string>
<string name="module_state_remove">Hiqe</string>
<string name="module_state_restore">Rikëthe</string>

View File

@ -15,7 +15,7 @@
<string name="app_changelog">Değişiklik günlüğü</string>
<string name="loading">Yükleniyor…</string>
<string name="update">Güncelle</string>
<string name="not_available">Mevcut değil</string>
<string name="not_available">Yüklü değil</string>
<string name="hide">Gizle</string>
<string name="home_package">Paket</string>
<string name="home_app_title">Uygulama</string>
@ -24,29 +24,29 @@
<string name="home_support_title">Bizi Destekleyin</string>
<string name="home_follow_title">Bizi Takip Edin</string>
<string name="home_item_source">Kaynak</string>
<string name="home_support_content">Magisk ücretsiz ve açık kaynaktır ve her zaman öyle kalacaktır. Ancak bış yaparak bize değer verdiğinizi gösterebilirsiniz.</string>
<string name="home_installed_version">Yüklendi</string>
<string name="home_support_content">Magisk ücretsiz ve açık kaynaktır ve her zaman öyle kalacaktır. Ancak bir bağış yaparak bize destek olduğunuzu gösterebilirsiniz.</string>
<string name="home_installed_version">Durum</string>
<string name="home_latest_version">En son sürüm</string>
<string name="invalid_update_channel">Geçersiz Güncelleme Kanalı</string>
<string name="uninstall_magisk_title">Magisk\'i Kaldırın</string>
<string name="uninstall_magisk_title">Magisk\'i Kaldır</string>
<string name="uninstall_magisk_msg">Tüm modüller devre dışı bırakılacak/kaldırılacak!\nKök kaldırılacak!\nMagisk kullanılarak şifrelenmemiş herhangi bir dahili depolama yeniden şifrelenecek!</string>
<!--Install-->
<string name="keep_force_encryption">Zorla şifrelemeyi koru</string>
<string name="keep_dm_verity">AVB 2.0/dm-verity\'yi koruyun</string>
<string name="keep_dm_verity">AVB 2.0/dm-verity\'yi koru</string>
<string name="patch_vbmeta">Önyükleme görüntüsünde vbmeta yamasını uygula</string>
<string name="recovery_mode">Kurtarma Modu</string>
<string name="install_options_title">Seçenekler</string>
<string name="install_method_title">Yöntem</string>
<string name="install_next">Sonraki</string>
<string name="install_start">Hadi gidelim</string>
<string name="install_start">Haydi başlayalım</string>
<string name="manager_download_install">İndirmek ve yüklemek için basın</string>
<string name="direct_install">Doğrudan Kurulum (Önerilir)</string>
<string name="install_inactive_slot">Etkin Olmayan Yuvaya Kur (OTA\'dan Sonra)</string>
<string name="install_inactive_slot">Etkin Olmayan Yuvaya Yükle (OTA\'dan Sonra)</string>
<string name="install_inactive_slot_msg">Yeniden başlatmanın ardından cihazınız mevcut etkin olmayan yuvaya önyükleme yapmaya ZORLANACAK!\nBu seçeneği yalnızca OTA tamamlandıktan sonra kullanın.\nDevam edilsin mi?</string>
<string name="setup_title">Ek Kurulum</string>
<string name="select_patch_file">Bir Dosya Seçin ve Yamalayın</string>
<string name="patch_file_msg">Bir ham görüntü (*.img) veya bir ODIN tar dosyası (*.tar) seçin</string>
<string name="select_patch_file">Bir Dosya Seç ve Yama Yap</string>
<string name="patch_file_msg">Bir ham görüntü (*.img) veya bir ODIN tar dosyası (*.tar) veya bir payload.bin (*.bin) seçin</string>
<string name="reboot_delay_toast">5 saniye içinde yeniden başlatılıyor…</string>
<string name="flash_screen_title">Yükleniyor</string>
@ -54,11 +54,11 @@
<string name="su_request_title">Süper Kullanıcı İsteği</string>
<string name="touch_filtered_warning">Bir uygulama bir Süper Kullanıcı isteğini engellediği için Magisk yanıtınızı doğrulayamıyor</string>
<string name="deny">Reddet</string>
<string name="prompt">Hemen</string>
<string name="prompt">Sor</string>
<string name="grant">İzin ver</string>
<string name="su_warning">Cihazınıza tam erişim sağlar.\nEğer emin değilseniz reddedin!</string>
<string name="forever">Daima</string>
<string name="once">Bir kere</string>
<string name="once">Bir kez</string>
<string name="tenmin">10 dakika</string>
<string name="twentymin">20 dakika</string>
<string name="thirtymin">30 dakika</string>
@ -85,9 +85,12 @@
<string name="log_data_magisk_none">Magisk günlükleri boş, bu garip</string>
<string name="menuSaveLog">Günlüğü kaydet</string>
<string name="menuClearLog">Günlüğü şimdi temizle</string>
<string name="logs_cleared">Günlük başarıyla temizlendi</string>
<string name="logs_cleared">Günlük kaydı başarıyla temizlendi</string>
<string name="pid">PID: %1$d</string>
<string name="target_uid">Hedef UID: %1$d</string>
<string name="target_pid">Ns hedef PID\'sini bağla: %s</string>
<string name="selinux_context">SELinux içeriği: %s</string>
<string name="supp_group">Ek grup: %s</string>
<!--SafetyNet-->
@ -99,18 +102,18 @@
<!--Module-->
<string name="no_info_provided">(Bilgi verilmedi)</string>
<string name="reboot_userspace">Yazılımsal olarak yeniden başlat</string>
<string name="reboot_recovery">Kurtarma modunda Yeniden Başlat</string>
<string name="reboot_bootloader">Önyükleyici modunda Yeniden Başlat</string>
<string name="reboot_download">Download modu için Yeniden Başlat</string>
<string name="reboot_edl">EDL modunda Yeniden Başlat</string>
<string name="reboot_userspace">Hızlı yeniden başlat</string>
<string name="reboot_recovery">Kurtarma modunda yeniden başlat</string>
<string name="reboot_bootloader">Önyükleyici modunda yeniden başlat</string>
<string name="reboot_download">İndirme modunda yeniden başlat</string>
<string name="reboot_edl">EDL modunda yeniden başlat</string>
<string name="module_version_author">%1$s / %2$s</string>
<string name="module_state_remove">Kaldır</string>
<string name="module_state_restore">Geri yükle</string>
<string name="module_action_install_external">Depolamadan yükle</string>
<string name="update_available">Güncelleme Mevcut</string>
<string name="suspend_text_riru">%1$s etkinleştirildiği için modül askıya alındı</string>
<string name="suspend_text_zygisk">%1$s etkin olmadığı için modül askıya alındı</string>
<string name="suspend_text_zygisk">%1$s etkinleştirilmediği için modül askıya alındı</string>
<string name="zygisk_module_unloaded">Uyumsuzluk nedeniyle Zygisk modülü yüklenmedi</string>
<string name="module_empty">Yüklü modül yok</string>
<string name="confirm_install">%1$s modülü yüklensin mi?</string>
@ -119,33 +122,33 @@
<!--Settings-->
<string name="settings_dark_mode_title">Tema Modu</string>
<string name="settings_dark_mode_message">Tarzınıza en uygun modu seçin!</string>
<string name="settings_dark_mode_light">Her zamanık</string>
<string name="settings_dark_mode_light">Daimaık</string>
<string name="settings_dark_mode_system">Sistemi Takip Et</string>
<string name="settings_dark_mode_dark">Her zaman Koyu</string>
<string name="settings_dark_mode_dark">Daima Koyu</string>
<string name="settings_download_path_title">İndirme yolu</string>
<string name="settings_download_path_message">Dosyalar %1$s konumuna kaydedilecek</string>
<string name="settings_hide_app_title">Magisk uygulamasını gizleyin</string>
<string name="settings_hide_app_summary">Rastgele bir paket kimliğine ve özel uygulama etiketine sahip bir proxy uygulaması yükleyin</string>
<string name="settings_restore_app_title">Magisk uygulamasını geri yükleyin</string>
<string name="settings_restore_app_summary">Uygulamayı gösterin ve orijinal APK\'yı geri yükleyin</string>
<string name="settings_hide_app_title">Magisk uygulamasını gizle</string>
<string name="settings_hide_app_summary">Rastgele bir paket kimliği ve özel uygulama etiketi olan bir proxy uygulaması yükleyin</string>
<string name="settings_restore_app_title">Magisk uygulamasını geri yükle</string>
<string name="settings_restore_app_summary">Uygulamayı göster ve orijinal APK\'yı geri yükle</string>
<string name="language">Dil</string>
<string name="system_default">(Sistem Varsayılanı)</string>
<string name="settings_check_update_title">Güncellemeleri Kontrol Et</string>
<string name="settings_check_update_summary">Arka planda güncellemeleri düzenli olarak kontrol edin</string>
<string name="settings_check_update_summary">Arka plandaki güncellemeleri düzenli olarak kontrol et</string>
<string name="settings_update_channel_title">Güncelleme Kanalı</string>
<string name="settings_update_stable">Stabil</string>
<string name="settings_update_beta">Beta</string>
<string name="settings_update_custom">Özel</string>
<string name="settings_update_custom_msg">Özel bir kanal URL\'si ekleyin</string>
<string name="settings_zygisk_summary">Zygisk arka plan programında Magisk\'in bazı bölümlerini çalıştırın</string>
<string name="settings_update_custom_msg">Özel bir kanal bağlantısı ekle</string>
<string name="settings_zygisk_summary">Zygisk arka plan programında Magisk\'in bazı bölümlerini çalıştır</string>
<string name="settings_denylist_title">Reddetme Listesini Zorla</string>
<string name="settings_denylist_summary">Reddetme listesindeki işlemlerde tüm Magisk değişiklikleri geri alınır</string>
<string name="settings_denylist_error">Bu özelliğin etkinleştirilmesi için %1$s gerekir</string>
<string name="settings_denylist_error">Bu özelliğin etkinleştirilmesi için %1$s gerekiyor</string>
<string name="settings_denylist_config_title">Reddetme Listesini Yapılandır</string>
<string name="settings_denylist_config_summary">Reddetme listesine dahil edilecek işlemleri seçin</string>
<string name="settings_hosts_title">Sistemsiz host</string>
<string name="settings_hosts_summary">Reklam engelleme uygulamaları için sistemsiz host desteği</string>
<string name="settings_hosts_toast">Sistemsiz host modülü eklendi</string>
<string name="settings_denylist_config_summary">Reddetme listesine dahil edilecek işlemleri seç</string>
<string name="settings_hosts_title">Sistemsiz ana makineler (systemless hosts)</string>
<string name="settings_hosts_summary">Reklam engelleme uygulamaları için sistemsiz ana makineler (systemless hosts) desteği</string>
<string name="settings_hosts_toast">Sistemsiz ana makineler (systemless hosts) modülü eklendi</string>
<string name="settings_app_name_hint">Yeni ad</string>
<string name="settings_app_name_helper">Uygulama bu adla yeniden paketlenecek</string>
<string name="settings_app_name_error">Geçersiz format</string>
@ -160,15 +163,18 @@
<string name="settings_su_request_45">45 saniye</string>
<string name="settings_su_request_60">60 saniye</string>
<string name="superuser_access">Süper Kullanıcı Erişimi</string>
<string name="auto_response">Otomatik Cevap</string>
<string name="request_timeout">İstek Zaman Aşımına Uğradı</string>
<string name="auto_response">Otomatik Yanıt</string>
<string name="request_timeout">İstek Zaman Aşımı</string>
<string name="superuser_notification">Süper Kullanıcı Bildirimi</string>
<string name="settings_su_reauth_title">Yükseltmeden sonra yeniden kimlik doğrulaması yapın</string>
<string name="settings_su_reauth_summary">Uygulamaları yükselttikten sonra Süper Kullanıcı izinlerini tekrar isteyin</string>
<string name="settings_su_reauth_title">Yükseltmeden sonra yeniden kimlik doğrulaması yap</string>
<string name="settings_su_reauth_summary">Uygulamaları yükselttikten sonra Süper Kullanıcı izinlerini tekrar iste</string>
<string name="settings_su_tapjack_title">Sahte Ekran (Tapjacking) Koruması</string>
<string name="settings_su_tapjack_summary">Süper Kullanıcı bilgi istemi iletişim kutusu, herhangi bir başka pencere veya yer paylaşımı tarafından engellendiğinde girişe yanıt vermeyecektir.</string>
<string name="settings_su_auth_title">Kullanıcı Kimlik Doğrulaması</string>
<string name="settings_su_auth_summary">Süper Kullanıcı istekleri sırasında kullanıcı kimlik doğrulaması iste</string>
<string name="settings_su_auth_insecure">Cihazda hiçbir kimlik doğrulama yöntemi yapılandırılmamış</string>
<string name="settings_customization">Özelleştir</string>
<string name="setting_add_shortcut_summary">Uygulamayı gizledikten sonra adın ve simgenin tanınmasının zor olması durumunda ana ekrana güzel bir kısayol ekleyin</string>
<string name="setting_add_shortcut_summary">Uygulamayı gizledikten sonra adın ve simgenin tanınmasının zor olması durumunda ana ekrana güzel bir kısayol ekle</string>
<string name="settings_doh_title">HTTPS üzerinden DNS</string>
<string name="settings_doh_description">Bazı ülkelerde DNS zehirlenmesine geçici çözüm</string>
@ -176,31 +182,31 @@
<string name="settings_owner_only">Yalnızca Cihaz Sahibi</string>
<string name="settings_owner_manage">Cihaz Sahibi Tarafından Yönetildi</string>
<string name="settings_user_independent">Kullanıcıdan Bağımsız</string>
<string name="owner_only_summary">Yalnızca sahibinin kök erişimi vardır</string>
<string name="owner_manage_summary">Yalnızca sahip, kök erişimini yönetebilir ve istek istemleri alabilir</string>
<string name="owner_only_summary">Kök erişimi yalnızca sahibine aittir</string>
<string name="owner_manage_summary">Kök erişimini yalnızca sahip yönetebilir ve istek istemlerini alabilir</string>
<string name="user_independent_summary">Her kullanıcının kendi ayrı kök kuralları vardır</string>
<string name="mount_namespace_mode">Bağlama Ad Alanı Modu</string>
<string name="settings_ns_global">Küresel Ad Alanı</string>
<string name="settings_ns_requester">Ad Alanını Devral</string>
<string name="settings_ns_isolate">İzole Ad Alanı</string>
<string name="settings_ns_isolate">İzole Edilmiş Ad Alanı</string>
<string name="global_summary">Tüm kök oturumları, global bağlama ad alanını kullanır</string>
<string name="requester_summary">Kök oturumları, istek sahibinin ad alanını devralır</string>
<string name="isolate_summary">Her kök oturumun kendi yalıtılmış ad alanı olacaktır</string>
<string name="isolate_summary">Her kök oturumun kendi izole edilmiş ad alanı olacaktır</string>
<!--Notifications-->
<string name="update_channel">Magisk Güncellemeleri</string>
<string name="progress_channel">İlerleme Bildirimleri</string>
<string name="updated_channel">Güncelleme Tamamlandı</string>
<string name="download_complete">İndirme tamamlandı</string>
<string name="download_complete">İndirme Tamamlandı</string>
<string name="download_file_error">Dosya indirilirken hata oluştu</string>
<string name="magisk_update_title">Magisk Güncellemesi Mevcut!</string>
<string name="updated_title">Magisk Güncellendi</string>
<string name="updated_text">Uygulamayı açmak için dokunun</string>
<!--Toasts, Dialogs-->
<string name="yes">Evet</string>
<string name="no">Hayır</string>
<string name="yes">Mevcut</string>
<string name="no">Mevcut değil</string>
<string name="repo_install_title">%1$s %2$s(%3$d) yükle</string>
<string name="download">İndir</string>
<string name="reboot">Yeniden başlat</string>
@ -218,22 +224,22 @@
<string name="setup_fail">Kurulum başarısız oldu</string>
<string name="env_fix_title">Ek Kurulum Gerekiyor</string>
<string name="env_fix_msg">Magisk\'in düzgün çalışması için cihazınızın ek kuruluma ihtiyacı var. Devam etmek ve yeniden başlatmak istiyor musunuz?</string>
<string name="env_full_fix_msg">Cihazınızın düzgün çalışması için Magisk\'in yeniden yüklenmeye ihtiyacı var. Lütfen Magisk\'i uygulama içinde yeniden yükleyin, kurtarma modu doğru cihaz bilgilerini alamıyor.</string>
<string name="env_full_fix_msg">Cihazınızın düzgün çalışması için Magisk\'in yeniden başlatılması gerekiyor. Lütfen Magisk\'i uygulama içinden yeniden yükleyin, kurtarma modu doğru cihaz bilgilerini alamıyor.</string>
<string name="setup_msg">Ortam kurulumu çalıştırılıyor…</string>
<string name="authenticate">Kimlik Doğrula</string>
<string name="authenticate">Kimlik doğrulaması yap</string>
<string name="unsupport_magisk_title">Desteklenmeyen Magisk Sürümü</string>
<string name="unsupport_magisk_msg">Uygulamanın bu sürümü, %1$s\'den daha düşük Magisk sürümlerini desteklemiyor.\n\nUygulama, Magisk kurulu değilmiş gibi davranacak, lütfen Magisk\'i mümkün olan en kısa sürede yükseltin.</string>
<string name="unsupport_magisk_msg">Uygulamanın bu sürümü %1$s altındaki Magisk sürümlerini desteklemiyor.\n\nUygulama Magisk yüklenmemiş gibi davranacak, lütfen en kısa sürede Magisk\'i yükseltin.</string>
<string name="unsupport_general_title">Anormal Durum</string>
<string name="unsupport_system_app_msg">Bu uygulamayı bir sistem uygulaması olarak çalıştırma desteklenmiyor. Lütfen uygulamayı bir kullanıcı uygulamasına geri döndürün.</string>
<string name="unsupport_other_su_msg">Magisk\'ten olmayan bir \"su\" ikili dosyası algılandı. Lütfen başka kök çözümlerini kaldırın ve/veya Magisk\'i yeniden yükleyin.</string>
<string name="unsupport_external_storage_msg">Magisk, harici depolama birimine kurulur. Lütfen uygulamayı dahili depolamaya taşıyın.</string>
<string name="unsupport_nonroot_stub_msg">Kök kaybolduğu için gizli Magisk uygulaması çalışmaya devam edemiyor. Lütfen orijinal APK\'yı geri yükleyin.</string>
<string name="unsupport_system_app_msg">Bu uygulamanın sistem uygulaması olarak çalıştırılması desteklenmiyor. Lütfen uygulamayı bir kullanıcı uygulamasına geri yükleyin.</string>
<string name="unsupport_other_su_msg">Magisk\'ten olmayan bir \"su\" ikilisi algılandı. Lütfen rakip kök çözümlerini kaldırın ve/veya Magisk\'i yeniden yükleyin.</string>
<string name="unsupport_external_storage_msg">Magisk harici depolamaya yüklenmiş. Lütfen uygulamayı dahili depolamaya taşıyın.</string>
<string name="unsupport_nonroot_stub_msg">Kök (root) kaybolduğu için gizli Magisk uygulaması çalışmaya devam edemiyor. Lütfen orijinal APK\'yı geri yükleyin.</string>
<string name="unsupport_nonroot_stub_title">@string/settings_restore_app_title</string>
<string name="external_rw_permission_denied">Bu işlevi etkinleştirmek için depolama izni veriniz.</string>
<string name="post_notifications_denied">Bu işlevi etkinleştirmek için bildirim izni veriniz.</string>
<string name="install_unknown_denied">Bu işlevi etkinleştirmek için "Bilinmeyen uygulamaları yükle" ayarına izin veriniz.</string>
<string name="add_shortcut_title">Ana ekrana kısayol ekle</string>
<string name="add_shortcut_msg">Bu uygulamayı gizledikten sonra adını ve simgesini tanımak zorlaşabilir. Ana ekrana hoş bir kısayol eklemek ister misiniz?</string>
<string name="add_shortcut_msg">Bu uygulamayı gizledikten sonra adını ve simgesini tanımak zorlaşabilir. Ana ekrana güzel bir kısayol eklemek ister misiniz?</string>
<string name="app_not_found">Bu eylemi gerçekleştirecek uygulama bulunamadı</string>
<string name="reboot_apply_change">Değişiklikleri uygulamak için yeniden başlatın</string>
<string name="restore_app_confirmation">Bu işlem, gizli uygulamayı orijinal uygulama ile değiştirecektir. Bu işlemi yapmak istediğinizden emin misiniz?</string>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<bool name="enable_fg_service">false</bool>
</resources>

View File

@ -15,4 +15,6 @@
<drawable name="ic_launcher">@drawable/ic_logo</drawable>
<bool name="enable_fg_service">true</bool>
</resources>

View File

@ -107,6 +107,7 @@
<string name="reboot_bootloader">Reboot to Bootloader</string>
<string name="reboot_download">Reboot to Download</string>
<string name="reboot_edl">Reboot to EDL</string>
<string name="reboot_safe_mode">Safe mode</string>
<string name="module_version_author">%1$s by %2$s</string>
<string name="module_state_remove">Remove</string>
<string name="module_state_restore">Restore</string>

230
build.py
View File

@ -4,7 +4,6 @@ import glob
import lzma
import multiprocessing
import os
import os.path as op
import platform
import shutil
import stat
@ -13,6 +12,7 @@ import sys
import tarfile
import textwrap
import urllib.request
from pathlib import Path
from zipfile import ZipFile
@ -79,18 +79,16 @@ default_targets = ["magisk", "magiskinit", "magiskboot", "magiskpolicy", "busybo
support_targets = default_targets + ["resetprop"]
rust_targets = ["magisk", "magiskinit", "magiskboot", "magiskpolicy"]
sdk_path = os.environ["ANDROID_SDK_ROOT"]
ndk_root = op.join(sdk_path, "ndk")
ndk_path = op.join(ndk_root, "magisk")
ndk_build = op.join(ndk_path, "ndk-build")
rust_bin = op.join(ndk_path, "toolchains", "rust", "bin")
llvm_bin = op.join(
ndk_path, "toolchains", "llvm", "prebuilt", f"{os_name}-x86_64", "bin"
)
cargo = op.join(rust_bin, "cargo" + EXE_EXT)
gradlew = op.join(".", "gradlew" + (".bat" if is_windows else ""))
adb_path = op.join(sdk_path, "platform-tools", "adb" + EXE_EXT)
native_gen_path = op.realpath(op.join("native", "out", "generated"))
sdk_path = Path(os.environ["ANDROID_SDK_ROOT"])
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 / f"cargo{EXE_EXT}"
gradlew = Path("gradlew" + (".bat" if is_windows else "")).resolve()
adb_path = sdk_path / "platform-tools" / f"adb{EXE_EXT}"
native_gen_path = Path("native", "out", "generated").resolve()
# Global vars
config = {}
@ -98,7 +96,7 @@ STDOUT = None
build_tools = None
def mv(source, target):
def mv(source: Path, target: Path):
try:
shutil.move(source, target)
vprint(f"mv {source} -> {target}")
@ -106,7 +104,7 @@ def mv(source, target):
pass
def cp(source, target):
def cp(source: Path, target: Path):
try:
shutil.copyfile(source, target)
vprint(f"cp {source} -> {target}")
@ -114,7 +112,7 @@ def cp(source, target):
pass
def rm(file):
def rm(file: Path):
try:
os.remove(file)
vprint(f"rm {file}")
@ -132,22 +130,11 @@ def rm_on_error(func, path, _):
pass
def rm_rf(path):
def rm_rf(path: Path):
vprint(f"rm -rf {path}")
shutil.rmtree(path, ignore_errors=False, onerror=rm_on_error)
def mkdir(path, mode=0o755):
try:
os.mkdir(path, mode)
except:
pass
def mkdir_p(path, mode=0o755):
os.makedirs(path, mode, exist_ok=True)
def execv(cmd, env=None):
return subprocess.run(cmd, stdout=STDOUT, env=env)
@ -192,11 +179,13 @@ def load_config(args):
config["versionCode"] = 1000000
config["outdir"] = "out"
args.config = Path(args.config)
# Load prop files
if op.exists(args.config):
if args.config.exists():
config.update(parse_props(args.config))
if op.exists("gradle.properties"):
if Path("gradle.properties").exists():
for key, value in parse_props("gradle.properties").items():
if key.startswith("magisk."):
config[key[7:]] = value
@ -206,17 +195,19 @@ def load_config(args):
except ValueError:
error('Config error: "versionCode" is required to be an integer')
mkdir_p(config["outdir"])
config["outdir"] = Path(config["outdir"])
config["outdir"].mkdir(mode=0o755, parents=True, exist_ok=True)
global STDOUT
STDOUT = None if args.verbose else subprocess.DEVNULL
def clean_elf():
if is_windows:
elf_cleaner = op.join("tools", "elf-cleaner.exe")
elf_cleaner = Path("tools", "elf-cleaner.exe")
else:
elf_cleaner = op.join("native", "out", "elf-cleaner")
if not op.exists(elf_cleaner):
elf_cleaner = Path("native", "out", "elf-cleaner")
if not elf_cleaner.exists():
execv(
[
"gcc",
@ -231,7 +222,7 @@ def clean_elf():
)
args = [elf_cleaner, "--api-level", "23"]
args.extend(
op.join("native", "out", arch, bin)
Path("native", "out", arch, bin)
for arch in archs
for bin in ["magisk", "magiskpolicy"]
)
@ -247,24 +238,22 @@ def run_ndk_build(flags):
os.chdir("..")
for arch in archs:
for tgt in support_targets + ["libinit-ld.so"]:
source = op.join("native", "libs", arch, tgt)
target = op.join("native", "out", arch, tgt)
source = Path("native", "libs", arch, tgt)
target = Path("native", "out", arch, tgt)
mv(source, target)
def run_cargo(cmds, triple="aarch64-linux-android"):
env = os.environ.copy()
env["PATH"] = f'{rust_bin}{os.pathsep}{env["PATH"]}'
env["CARGO_BUILD_RUSTC"] = op.join(rust_bin, "rustc" + EXE_EXT)
env["CARGO_BUILD_RUSTC"] = str(rust_bin / f"rustc{EXE_EXT}")
env["RUSTFLAGS"] = f"-Clinker-plugin-lto -Zthreads={min(8, cpu_count)}"
env["TARGET_CC"] = op.join(llvm_bin, "clang" + EXE_EXT)
env["TARGET_CFLAGS"] = f"--target={triple}23"
return execv([cargo, *cmds], env)
def run_cargo_build(args):
native_out = op.join("..", "out")
mkdir(native_out)
native_out = Path("..", "out")
native_out.mkdir(mode=0o755, exist_ok=True)
targets = set(args.target) & set(rust_targets)
if "resetprop" in args.target:
@ -297,11 +286,11 @@ def run_cargo_build(args):
if proc.returncode != 0:
error("Build binary failed!")
arch_out = op.join(native_out, arch)
mkdir(arch_out)
arch_out = native_out / arch
arch_out.mkdir(mode=0o755, exist_ok=True)
for tgt in targets:
source = op.join("target", rust_triple, rust_out, f"lib{tgt}.a")
target = op.join(arch_out, f"lib{tgt}-rs.a")
source = Path("target", rust_triple, rust_out, f"lib{tgt}.a")
target = arch_out / f"lib{tgt}-rs.a"
mv(source, target)
@ -310,14 +299,14 @@ def run_cargo_cmd(args):
STDOUT = None
if len(args.commands) >= 1 and args.commands[0] == "--":
args.commands = args.commands[1:]
os.chdir(op.join("native", "src"))
os.chdir(Path("native", "src"))
run_cargo(args.commands)
os.chdir(op.join("..", ".."))
os.chdir(Path("..", ".."))
def write_if_diff(file_name, text):
def write_if_diff(file_name: Path, text: str):
do_write = True
if op.exists(file_name):
if file_name.exists():
with open(file_name, "r") as f:
orig = f.read()
do_write = orig != text
@ -337,12 +326,12 @@ def binary_dump(src, var_name, compressor=xz):
def dump_bin_header(args):
mkdir_p(native_gen_path)
native_gen_path.mkdir(mode=0o755, parents=True, exist_ok=True)
for arch in archs:
preload = op.join("native", "out", arch, "libinit-ld.so")
preload = Path("native", "out", arch, "libinit-ld.so")
with open(preload, "rb") as src:
text = binary_dump(src, "init_ld_xz")
write_if_diff(op.join(native_gen_path, f"{arch}_binaries.h"), text)
write_if_diff(Path(native_gen_path, f"{arch}_binaries.h"), text)
def dump_flag_header():
@ -359,14 +348,14 @@ def dump_flag_header():
flag_txt += f'#define MAGISK_VER_CODE {config["versionCode"]}\n'
flag_txt += f"#define MAGISK_DEBUG {0 if args.release else 1}\n"
mkdir_p(native_gen_path)
write_if_diff(op.join(native_gen_path, "flags.h"), flag_txt)
native_gen_path.mkdir(mode=0o755, parents=True, exist_ok=True)
write_if_diff(Path(native_gen_path, "flags.h"), flag_txt)
def build_binary(args):
# Verify NDK install
try:
with open(op.join(ndk_path, "ONDK_VERSION"), "r") as ondk_ver:
with open(Path(ndk_path, "ONDK_VERSION"), "r") as ondk_ver:
assert ondk_ver.read().strip(" \t\r\n") == config["ondkVersion"]
except:
error('Unmatched NDK. Please install/upgrade NDK with "build.py ndk"')
@ -383,9 +372,9 @@ def build_binary(args):
header("* Building binaries: " + " ".join(args.target))
os.chdir(op.join("native", "src"))
os.chdir(Path("native", "src"))
run_cargo_build(args)
os.chdir(op.join("..", ".."))
os.chdir(Path("..", ".."))
dump_flag_header()
@ -400,36 +389,33 @@ def build_binary(args):
flag += " B_POLICY=1"
clean = True
if "test" in args.target:
flag += " B_TEST=1"
if "magiskinit" in args.target:
flag += " B_PRELOAD=1"
if "resetprop" in args.target:
flag += " B_PROP=1"
if "magiskboot" in args.target:
flag += " B_BOOT=1"
if flag:
run_ndk_build(flag)
# magiskinit embeds preload.so
flag = ""
if "magiskinit" in args.target:
# magiskinit embeds preload.so
dump_bin_header(args)
flag += " B_INIT=1"
if "magiskboot" in args.target:
flag += " B_BOOT=1"
if flag:
dump_bin_header(args)
flag += " B_CRT0=1"
run_ndk_build(flag)
if clean:
clean_elf()
# BusyBox is built with different libc
# BusyBox is built with different API level
if "busybox" in args.target:
run_ndk_build("B_BB=1")
@ -439,10 +425,10 @@ def find_jdk():
env = os.environ.copy()
if "ANDROID_STUDIO" in env:
studio = env["ANDROID_STUDIO"]
jbr = op.join(studio, "jbr", "bin")
if not op.exists(jbr):
jbr = op.join(studio, "Contents", "jbr", "Contents", "Home", "bin")
if op.exists(jbr):
jbr = Path(studio, "jbr", "bin")
if not jbr.exists():
jbr = Path(studio, "Contents", "jbr", "Contents", "Home", "bin")
if jbr.exists():
env["PATH"] = f'{jbr}{os.pathsep}{env["PATH"]}'
no_jdk = False
@ -475,7 +461,7 @@ def build_apk(args, module):
[
gradlew,
f"{module}:assemble{build_type}",
"-PconfigPath=" + op.abspath(args.config),
f"-PconfigPath={args.config.resolve()}",
],
env=env,
)
@ -485,10 +471,10 @@ def build_apk(args, module):
build_type = build_type.lower()
apk = f"{module}-{build_type}.apk"
source = op.join(module, "build", "outputs", "apk", build_type, apk)
target = op.join(config["outdir"], apk)
source = Path(module, "build", "outputs", "apk", build_type, apk)
target = config["outdir"] / apk
mv(source, target)
header("Output: " + target)
header(f"Output: {target}")
def build_app(args):
@ -499,8 +485,8 @@ def build_app(args):
# build process. Copy the stub APK into output directory.
build_type = "release" if args.release else "debug"
apk = f"stub-{build_type}.apk"
source = op.join("app", "src", build_type, "assets", "stub.apk")
target = op.join(config["outdir"], apk)
source = Path("app", "src", build_type, "assets", "stub.apk")
target = config["outdir"] / apk
cp(source, target)
@ -521,30 +507,30 @@ def cleanup(args):
if "cpp" in args.target:
header("* Cleaning C++")
rm_rf(op.join("native", "libs"))
rm_rf(op.join("native", "obj"))
rm_rf(op.join("native", "out"))
rm_rf(Path("native", "libs"))
rm_rf(Path("native", "obj"))
rm_rf(Path("native", "out"))
if "rust" in args.target:
header("* Cleaning Rust")
rm_rf(op.join("native", "src", "target"))
rm(op.join("native", "src", "boot", "proto", "mod.rs"))
rm(op.join("native", "src", "boot", "proto", "update_metadata.rs"))
rm_rf(Path("native", "src", "target"))
rm(Path("native", "src", "boot", "proto", "mod.rs"))
rm(Path("native", "src", "boot", "proto", "update_metadata.rs"))
for rs_gen in glob.glob("native/**/*-rs.*pp", recursive=True):
rm(rs_gen)
if "java" in args.target:
header("* Cleaning java")
execv([gradlew, "app:clean", "app:shared:clean", "stub:clean"], env=find_jdk())
rm_rf(op.join("app", "src", "debug"))
rm_rf(op.join("app", "src", "release"))
rm_rf(Path("app", "src", "debug"))
rm_rf(Path("app", "src", "release"))
def setup_ndk(args):
ndk_ver = config["ondkVersion"]
url = f"https://github.com/topjohnwu/ondk/releases/download/{ndk_ver}/ondk-{ndk_ver}-{os_name}.tar.xz"
ndk_archive = url.split("/")[-1]
ondk_path = op.join(ndk_root, f"ondk-{ndk_ver}")
ondk_path = Path(ndk_root, f"ondk-{ndk_ver}")
header(f"* Downloading and extracting {ndk_archive}")
rm_rf(ondk_path)
@ -555,34 +541,15 @@ def setup_ndk(args):
rm_rf(ndk_path)
mv(ondk_path, ndk_path)
header("* Patching static libs")
for target in ["arm-linux-androideabi", "i686-linux-android"]:
arch = target.split("-")[0]
lib_dir = op.join(
ndk_path,
"toolchains",
"llvm",
"prebuilt",
f"{os_name}-x86_64",
"sysroot",
"usr",
"lib",
f"{target}",
"23",
)
if not op.exists(lib_dir):
continue
src_dir = op.join("tools", "ndk-bins", arch)
rm(op.join(src_dir, ".DS_Store"))
shutil.copytree(src_dir, lib_dir, copy_function=cp, dirs_exist_ok=True)
def push_files(args, script):
abi = cmd_out([adb_path, "shell", "getprop", "ro.product.cpu.abi"])
apk = config["outdir"] + ("/app-release.apk" if args.release else "/app-debug.apk")
apk = Path(
config["outdir"], ("app-release.apk" if args.release else "app-debug.apk")
)
# Extract busybox from APK
busybox = f'{config["outdir"]}/busybox'
busybox = Path(config["outdir"], "busybox")
with ZipFile(apk) as zf:
with zf.open(f"lib/{abi}/libbusybox.so") as libbb:
with open(busybox, "wb") as bb:
@ -606,7 +573,7 @@ def setup_avd(args):
header("* Setting up emulator")
push_files(args, "scripts/avd_magisk.sh")
push_files(args, Path("scripts", "avd_magisk.sh"))
proc = execv([adb_path, "shell", "sh", "/data/local/tmp/avd_magisk.sh"])
if proc.returncode != 0:
@ -618,26 +585,28 @@ def patch_avd_ramdisk(args):
args.release = False
build_all(args)
args.ramdisk = Path(args.ramdisk)
header("* Patching emulator ramdisk.img")
# Create a backup to prevent accidental overwrites
backup = args.ramdisk + ".bak"
if not op.exists(backup):
backup = args.ramdisk.parent / f"{args.ramdisk.name}.bak"
if not backup.exists():
cp(args.ramdisk, backup)
ini = op.join(op.dirname(args.ramdisk), "advancedFeatures.ini")
ini = args.ramdisk.parent / "advancedFeatures.ini"
with open(ini, "r") as f:
adv_ft = f.read()
# Need to turn off system as root
if "SystemAsRoot = on" in adv_ft:
# Create a backup
cp(ini, ini + ".bak")
cp(ini, ini.parent / f"{ini.name}.bak")
adv_ft = adv_ft.replace("SystemAsRoot = on", "SystemAsRoot = off")
with open(ini, "w") as f:
f.write(adv_ft)
push_files(args, "scripts/avd_patch.sh")
push_files(args, Path("scripts", "avd_patch.sh"))
proc = execv([adb_path, "push", backup, "/data/local/tmp/ramdisk.cpio.tmp"])
if proc.returncode != 0:
@ -657,6 +626,31 @@ def build_all(args):
build_app(args)
def setup_rustup(args):
wrapper_dir = Path(args.wrapper_dir)
rm_rf(wrapper_dir)
wrapper_dir.mkdir(mode=0o755, parents=True, exist_ok=True)
if "CARGO_HOME" in os.environ:
cargo_home = Path(os.environ["CARGO_HOME"])
else:
cargo_home = Path.home() / ".cargo"
cargo_bin = cargo_home / "bin"
for src in cargo_bin.iterdir():
tgt = wrapper_dir / src.name
tgt.symlink_to(src)
# Build rustup_wrapper
wrapper_src = Path("tools", "rustup_wrapper")
cargo_toml = wrapper_src / "Cargo.toml"
execv([cargo, "build", "--release", f"--manifest-path={cargo_toml}"])
# 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)
parser = argparse.ArgumentParser(description="Magisk build script")
parser.set_defaults(func=lambda x: None)
parser.add_argument(
@ -687,6 +681,10 @@ cargo_parser = subparsers.add_parser("cargo", help="run cargo with proper enviro
cargo_parser.add_argument("commands", nargs=argparse.REMAINDER)
cargo_parser.set_defaults(func=run_cargo_cmd)
rustup_parser = subparsers.add_parser("rustup", help="setup rustup wrapper")
rustup_parser.add_argument("wrapper_dir", help="path to setup rustup wrapper binaries")
rustup_parser.set_defaults(func=setup_rustup)
app_parser = subparsers.add_parser("app", help="build the Magisk app")
app_parser.set_defaults(func=build_app)

View File

@ -17,8 +17,8 @@ gradlePlugin {
dependencies {
implementation(embeddedKotlin("gradle-plugin"))
implementation("com.android.tools.build:gradle:8.2.1")
implementation("androidx.navigation:navigation-safe-args-gradle-plugin:2.7.6")
implementation("com.android.tools.build:gradle:8.4.0")
implementation("androidx.navigation:navigation-safe-args-gradle-plugin:2.7.7")
implementation("org.lsposed.lsparanoid:gradle-plugin:0.5.2")
implementation("org.eclipse.jgit:org.eclipse.jgit:6.7.0.202309050840-r")
}

View File

@ -72,7 +72,7 @@ fun Project.setupCommon() {
compileSdkVersion(34)
buildToolsVersion = "34.0.0"
ndkPath = "$sdkDirectory/ndk/magisk"
ndkVersion = "26.1.10909125"
ndkVersion = "27.0.11718014"
defaultConfig {
minSdk = 23
@ -166,6 +166,10 @@ private fun Project.setupAppCommon() {
}
}
defaultConfig {
buildConfigField("int", "STUB_VERSION", Config.stubVersion)
}
buildTypes {
signingConfigs["config"].also {
debug {
@ -227,25 +231,25 @@ fun Project.setupApp() {
into("armeabi-v7a") {
from(rootProject.file("native/out/armeabi-v7a")) {
include("busybox", "magiskboot", "magiskinit", "magiskpolicy", "magisk")
rename { if (it == "magisk") "libmagisk32.so" else "lib$it.so" }
rename { "lib$it.so" }
}
}
into("x86") {
from(rootProject.file("native/out/x86")) {
include("busybox", "magiskboot", "magiskinit", "magiskpolicy", "magisk")
rename { if (it == "magisk") "libmagisk32.so" else "lib$it.so" }
rename { "lib$it.so" }
}
}
into("arm64-v8a") {
from(rootProject.file("native/out/arm64-v8a")) {
include("busybox", "magiskboot", "magiskinit", "magiskpolicy", "magisk")
rename { if (it == "magisk") "libmagisk64.so" else "lib$it.so" }
rename { "lib$it.so" }
}
}
into("x86_64") {
from(rootProject.file("native/out/x86_64")) {
include("busybox", "magiskboot", "magiskinit", "magiskpolicy", "magisk")
rename { if (it == "magisk") "libmagisk64.so" else "lib$it.so" }
rename { "lib$it.so" }
}
}
onlyIf {
@ -337,7 +341,7 @@ fun Project.setupStub() {
val outResDir = layout.buildDirectory.dir("generated/source/res/${variantLowered}").get().asFile
val aapt = File(android.sdkDirectory, "build-tools/${android.buildToolsVersion}/aapt2")
val apk = layout.buildDirectory.file("intermediates/processed_res/" +
"${variantLowered}/out/resources-${variantLowered}.ap_").get().asFile
"${variantLowered}/process${variantCapped}Resources/out/resources-${variantLowered}.ap_").get().asFile
val genManifestTask = tasks.register("generate${variantCapped}ObfuscatedClass") {
inputs.property("seed", RAND_SEED)
@ -379,9 +383,9 @@ fun Project.setupStub() {
}
// Override optimizeReleaseResources task
val apk = layout.buildDirectory.file("intermediates/processed_res/" +
"release/out/resources-release.ap_").get().asFile
"release/processReleaseResources/out/resources-release.ap_").get().asFile
val optRes = layout.buildDirectory.file("intermediates/optimized_processed_res/" +
"release/resources-release-optimize.ap_").get().asFile
"release/optimizeReleaseResources/resources-release-optimize.ap_").get().asFile
afterEvaluate {
tasks.named("optimizeReleaseResources") {
doLast { apk.copyTo(optRes, true) }

View File

@ -34,13 +34,15 @@
## IDE Support
- The repository can be directly opened with Android Studio as a project.
- The Kotlin, Java, C++, and C code in the project should be properly supported in Android Studio out of the box.
- Run `./build.py binary` before working on native code, as some generated code is only created during the build process.
- Kotlin, Java, C++, and C code in the project should be supported in Android Studio out of the box. This repository can be directly opened with Android Studio as a project.
- For Rust development, see the next section.
- Before working on any native code, build all native code first with `./build.py binary`, as some generated code is only created during the build process.
### Developing Rust in Android Studio
### Developing Rust
Because the Magisk NDK package, [ONDK](https://github.com/topjohnwu/ondk) (the one installed with `./build.py ndk`), contains a fully self contained Clang + Rust toolchain, building the Magisk project alone does not require configuring toolchains. However, due to the way the IntelliJ Rust plugin works, you'll have to go through some additional setup to make Android Studio work with Magisk's Rust codebase:
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`.
Let's first setup `rustup` to use our custom ONDK Rust toolchain by default:
- Install [rustup](https://rustup.rs/), the official Rust toolchain manager
- Link the ONDK Rust toolchain and set it as default:
@ -48,13 +50,30 @@ Because the Magisk NDK package, [ONDK](https://github.com/topjohnwu/ondk) (the o
```bash
# Link the ONDK toolchain with the name "magisk"
rustup toolchain link magisk "$ANDROID_SDK_ROOT/ndk/magisk/toolchains/rust"
# Set as default
# Set magisk as default
rustup default magisk
```
- Install the [Intellij Rust plugin](https://www.jetbrains.com/rust/) in Android Studio
- In Preferences > Languages & Frameworks > Rust, set `$ANDROID_SDK_ROOT/ndk/magisk/toolchains/rust/bin` as the toolchain location
- Open `native/src/Cargo.toml`, and select "Attach" in the "No Cargo projects found" banner
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:
- 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.
```bash
rustup toolchain install nightly
# Add some components that is also included in ONDK
rustup +nightly component add rust-src clippy
```
- Create a wrapper cargo bin directory to workaround `rustup` limitations
```bash
# We choose ~/.cargo/wrapper here as an example (and a good recommendation)
# Pick any path you like, you just need to use this path in the next step
./build.py rustup ~/.cargo/wrapper
```
- In Settings > Rust > Toolchain location, set this to the path of the wrapper directory we just created.
- The IDE should now be fully functional, and you are able to enable `rustfmt` and use `Clippy` as the external linter.
## Signing and Distribution

View File

@ -4,7 +4,12 @@
If you have USB debugging enabled in developer options, connect your phone to the PC. If your device is detected (check by `adb devices`), enter ADB shell and run the command `magisk --remove-modules`. This will remove all your modules and automatically reboot the device.
If unfortunately you do not have USB debugging enabled, reboot into Safe Mode. Most modern Android devices support pressing a special key combo at boot to enter Safe Mode as an emergency option. Magisk will detect Safe Mode being activated, and all modules will be disabled. Then reboot back to normal mode (the module disable state persists) and manage your modules through the Magisk app.
If unfortunately you do not have USB debugging enabled you can boot using the Safe Mode key combo to cause Magisk to create an empty file named 'disable' in modules directories which disables modules when next booted with Magisk. Most modern Android devices support such a special key combo at boot to enter system Safe Mode as an emergency option, but **please note** that Magisk's key combo detection occurs _earlier_ than system detection so the key combo timing indicated by many online guides may need to be altered to activate Magisk's Safe Mode. (It's possible to activate system Safe Mode but not Magisk Safe Mode and vice versa.)
The following details should ensure that modules are properly disabled:
1) Many online guides for entering Safe Mode say 'When the animated logo appears, press and hold the volume down button until the system boots' or similar. This may actually be _too late_ for Magisk detection however and result in activating system Safe Mode but modules are not disabled.
2) By pressing the volume down button some seconds before the animation and releasing it as soon as the boot animation appears, Magisk's Safe Mode should be activated without activating system Safe Mode (thus avoiding disabling other device and app settings) and the device should then simply boot to normal system with modules disabled.
3) By pressing the volume down button some seconds before the animation and holding it until the system boots, both Magisk's Safe Mode and system Safe Mode should be activated. Next, after booting back to normal system, modules will be disabled.
### Q: Why is X app detecting root?

View File

@ -97,7 +97,7 @@ Supported actions:
test
Test the cpio's status
Return value is 0 or bitwise or-ed of following values:
0x1:Magisk 0x2:unsupported 0x4:Sony
0x1:Magisk 0x2:unsupported
patch
Apply ramdisk patches
Configure with env variables: KEEPVERITY KEEPFORCEENCRYPT

View File

@ -26,6 +26,6 @@ android.injected.testOnly=false
android.nonFinalResIds=false
# Magisk
magisk.stubVersion=38
magisk.versionCode=27000
magisk.ondkVersion=r26.3
magisk.stubVersion=39
magisk.versionCode=27002
magisk.ondkVersion=r27.1

View File

@ -5,7 +5,7 @@ plugins {
setupCommon()
android {
namespace = "com.topjohnwu.magisk.native"
namespace = "com.topjohnwu.magisk.binary"
externalNativeBuild {
ndkBuild {
@ -22,7 +22,8 @@ android {
ndkBuild {
// Pass arguments to ndk-build.
arguments(
"B_MAGISK=1", "B_INIT=1", "B_BOOT=1", "B_TEST=1", "B_POLICY=1", "B_PRELOAD=1", "B_PROP=1"
"B_MAGISK=1", "B_INIT=1", "B_BOOT=1", "B_POLICY=1",
"B_PRELOAD=1", "B_PROP=1", "B_CRT0=1"
)
}
}

View File

@ -38,7 +38,7 @@ LOCAL_SRC_FILES := \
core/zygisk/hook.cpp \
core/deny/cli.cpp \
core/deny/utils.cpp \
core/deny/revert.cpp
core/deny/logcat.cpp
LOCAL_LDLIBS := -llog
LOCAL_LDFLAGS := -Wl,--dynamic-list=src/exported_sym.txt
@ -63,7 +63,6 @@ include $(CLEAR_VARS)
LOCAL_MODULE := magiskinit
LOCAL_STATIC_LIBRARIES := \
libbase \
libcompat \
libpolicy \
libxz \
libinit-rs
@ -77,6 +76,12 @@ LOCAL_SRC_FILES := \
init/selinux.cpp \
init/init-rs.cpp
LOCAL_LDFLAGS := -static
ifdef B_CRT0
LOCAL_STATIC_LIBRARIES += crt0
endif
include $(BUILD_EXECUTABLE)
endif
@ -87,7 +92,6 @@ include $(CLEAR_VARS)
LOCAL_MODULE := magiskboot
LOCAL_STATIC_LIBRARIES := \
libbase \
libcompat \
liblzma \
liblz4 \
libbz2 \
@ -102,6 +106,13 @@ LOCAL_SRC_FILES := \
boot/format.cpp \
boot/boot-rs.cpp
LOCAL_LDFLAGS := -static
ifdef B_CRT0
LOCAL_STATIC_LIBRARIES += crt0
LOCAL_LDFLAGS += -lm
endif
include $(BUILD_EXECUTABLE)
endif
@ -154,9 +165,7 @@ LOCAL_EXPORT_C_INCLUDES := $(LOCAL_C_INCLUDES)
LOCAL_SRC_FILES := \
sepolicy/api.cpp \
sepolicy/sepolicy.cpp \
sepolicy/rules.cpp \
sepolicy/policydb.cpp \
sepolicy/statement.cpp \
sepolicy/policy-rs.cpp
include $(BUILD_STATIC_LIBRARY)

View File

@ -8,10 +8,18 @@ APP_PLATFORM := android-23
APP_THIN_ARCHIVE := true
APP_STRIP_MODE := none
# Busybox should use stock libc.a
ifdef B_CRT0
# Disable all security and debugging features
APP_CFLAGS += -fno-unwind-tables -fno-asynchronous-unwind-tables -fno-stack-protector -U_FORTIFY_SOURCE
# Override output folder to make sure all dependencies are rebuilt with new CFLAGS
NDK_APP_OUT := ./obj/nolibc
endif
# Busybox should use a newer libc.a
ifdef B_BB
APP_PLATFORM := android-26
APP_LDFLAGS += -T src/lto_fix.lds
ifeq ($(OS),Windows_NT)
APP_SHORT_COMMANDS := true
endif

284
native/src/Cargo.lock generated
View File

@ -4,9 +4,9 @@ version = 3
[[package]]
name = "aho-corasick"
version = "1.1.2"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
@ -61,9 +61,9 @@ dependencies = [
[[package]]
name = "autocfg"
version = "1.1.0"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80"
[[package]]
name = "base"
@ -72,6 +72,7 @@ dependencies = [
"argh",
"bytemuck",
"cfg-if",
"const_format",
"cxx",
"cxx-gen",
"libc",
@ -100,27 +101,27 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "block-buffer"
version = "0.10.4"
version = "0.11.0-pre.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
checksum = "3ded684142010808eb980d9974ef794da2bcf97d13396143b1515e9f0fb4a10e"
dependencies = [
"generic-array",
"crypto-common",
]
[[package]]
name = "bytemuck"
version = "1.14.1"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed2490600f404f2b94c167e31d3ed1d5f3c225a0f3b80230053b3e0b7b962bd9"
checksum = "5d6d68c57235a3a081186990eca2867354726650f42f7516ca50c28d6281fd15"
dependencies = [
"bytemuck_derive",
]
[[package]]
name = "bytemuck_derive"
version = "1.5.0"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "965ab7eb5f8f97d2a083c799f3a1b994fc397b2fe2da5d1da1626ce15a39f2b1"
checksum = "4da9a32f3fed317401fa3c862968128267c3106685286e15d5aaa3d7389c2f60"
dependencies = [
"proc-macro2",
"quote",
@ -135,12 +136,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "cc"
version = "1.0.83"
version = "1.0.90"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
dependencies = [
"libc",
]
checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5"
[[package]]
name = "cfg-if"
@ -175,9 +173,29 @@ dependencies = [
[[package]]
name = "const-oid"
version = "0.9.6"
version = "0.10.0-pre.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
checksum = "f7e3352a27098ba6b09546e5f13b15165e6a88b5c2723afecb3ea9576b27e3ea"
[[package]]
name = "const_format"
version = "0.2.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3a214c7af3d04997541b18d432afaff4c455e79e2029079647e72fc2bd27673"
dependencies = [
"const_format_proc_macros",
]
[[package]]
name = "const_format_proc_macros"
version = "0.2.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7f6ff08fd20f4f299298a28e2dfa8a8ba1036e6cd2460ac1de7b425d76f2500"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "cpufeatures"
@ -190,11 +208,12 @@ dependencies = [
[[package]]
name = "crypto-bigint"
version = "0.5.5"
version = "0.6.0-pre.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76"
checksum = "1943d7beadd9ce2b25f3bae73b9e9336fccc1edf38bdec1ed58d3aa183989e11"
dependencies = [
"generic-array",
"hybrid-array",
"num-traits",
"rand_core",
"subtle",
"zeroize",
@ -202,12 +221,13 @@ dependencies = [
[[package]]
name = "crypto-common"
version = "0.1.6"
version = "0.2.0-pre.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
checksum = "b7aa2ec04f5120b830272a481e8d9d8ba4dda140d2cda59b0f1110d5eb93c38e"
dependencies = [
"generic-array",
"typenum",
"getrandom",
"hybrid-array",
"rand_core",
]
[[package]]
@ -244,9 +264,9 @@ dependencies = [
[[package]]
name = "der"
version = "0.7.8"
version = "0.8.0-pre.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c"
checksum = "b489fd2221710c1dd46637d66b984161fb66134f81437a8489800306bcc2ecea"
dependencies = [
"const-oid",
"der_derive",
@ -257,9 +277,9 @@ dependencies = [
[[package]]
name = "der_derive"
version = "0.7.2"
version = "0.8.0-pre.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fe87ce4529967e0ba1dcf8450bab64d97dfd5010a6256187ffe2e43e6f0e049"
checksum = "dd1ee9778ac378876dc78f546d2821fae40a1b69ec8d82f3745392d69ff89ce6"
dependencies = [
"proc-macro2",
"quote",
@ -268,9 +288,9 @@ dependencies = [
[[package]]
name = "digest"
version = "0.10.7"
version = "0.11.0-pre.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
checksum = "065d93ead7c220b85d5b4be4795d8398eac4ff68b5ee63895de0a3c1fb6edf25"
dependencies = [
"block-buffer",
"const-oid",
@ -280,9 +300,9 @@ dependencies = [
[[package]]
name = "ecdsa"
version = "0.16.9"
version = "0.17.0-pre.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca"
checksum = "d7e045ee5c360512162782f3d4cb07d2f4ce8c4ef9bf7c77ec16d1cf60b3d5ca"
dependencies = [
"der",
"digest",
@ -294,17 +314,17 @@ dependencies = [
[[package]]
name = "elliptic-curve"
version = "0.13.8"
version = "0.14.0-pre.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47"
checksum = "4a1775af172997a40c14854c3a9fde9e03e5772084b334b6a0bb18bf7f93ac16"
dependencies = [
"base16ct",
"crypto-bigint",
"digest",
"ff",
"generic-array",
"group",
"hkdf",
"hybrid-array",
"pem-rfc7468",
"pkcs8",
"rand_core",
@ -344,20 +364,9 @@ dependencies = [
[[package]]
name = "flagset"
version = "0.4.4"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d52a7e408202050813e6f1d9addadcaafef3dca7530c7ddfb005d4081cce6779"
[[package]]
name = "generic-array"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check",
"zeroize",
]
checksum = "cdeb3aa5e95cf9aabc17f060cfa0ced7b83f042390760ca53bf09df9968acaa1"
[[package]]
name = "getrandom"
@ -392,18 +401,18 @@ dependencies = [
[[package]]
name = "hkdf"
version = "0.12.4"
version = "0.13.0-pre.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7"
checksum = "fd5d615ab5c462f96c309b3a00b19f373025a4981312f717f9df5bbd0201530c"
dependencies = [
"hmac",
]
[[package]]
name = "hmac"
version = "0.12.1"
version = "0.13.0-pre.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
checksum = "ffd790a0795ee332ed3e8959e5b177beb70d7112eb7d345428ec17427897d5ce"
dependencies = [
"digest",
]
@ -417,6 +426,16 @@ dependencies = [
"quick-error",
]
[[package]]
name = "hybrid-array"
version = "0.2.0-rc.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53668f5da5a41d9eaf4bf7064be46d1ebe6a4e1ceed817f387587b18f2b51047"
dependencies = [
"typenum",
"zeroize",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
@ -440,9 +459,9 @@ checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058"
[[package]]
name = "log"
version = "0.4.20"
version = "0.4.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
[[package]]
name = "magisk"
@ -474,6 +493,7 @@ dependencies = [
"num-traits",
"p256",
"p384",
"p521",
"pb-rs",
"quick-protobuf",
"rsa",
@ -504,9 +524,9 @@ dependencies = [
[[package]]
name = "memchr"
version = "2.7.1"
version = "2.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
[[package]]
name = "minimal-lexical"
@ -543,9 +563,9 @@ dependencies = [
[[package]]
name = "num-derive"
version = "0.4.1"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfb77679af88f8b125209d354a202862602672222e7f2313fdd6dc349bad4712"
checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
dependencies = [
"proc-macro2",
"quote",
@ -554,19 +574,18 @@ dependencies = [
[[package]]
name = "num-integer"
version = "0.1.45"
version = "0.1.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
dependencies = [
"autocfg",
"num-traits",
]
[[package]]
name = "num-iter"
version = "0.1.43"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252"
checksum = "d869c01cc0c455284163fd0092f1f93835385ccab5a98a0dcc497b2f8bf055a9"
dependencies = [
"autocfg",
"num-integer",
@ -575,9 +594,9 @@ dependencies = [
[[package]]
name = "num-traits"
version = "0.2.17"
version = "0.2.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c"
checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a"
dependencies = [
"autocfg",
"libm",
@ -585,9 +604,8 @@ dependencies = [
[[package]]
name = "p256"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b"
version = "0.14.0-pre.0"
source = "git+https://github.com/RustCrypto/elliptic-curves.git?rev=5d1c252c2defb5808f55329f3e2955ca72d7f8b5#5d1c252c2defb5808f55329f3e2955ca72d7f8b5"
dependencies = [
"ecdsa",
"elliptic-curve",
@ -597,9 +615,8 @@ dependencies = [
[[package]]
name = "p384"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70786f51bcc69f6a4c0360e063a4cac5419ef7c5cd5b3c99ad70f3be5ba79209"
version = "0.14.0-pre"
source = "git+https://github.com/RustCrypto/elliptic-curves.git?rev=5d1c252c2defb5808f55329f3e2955ca72d7f8b5#5d1c252c2defb5808f55329f3e2955ca72d7f8b5"
dependencies = [
"ecdsa",
"elliptic-curve",
@ -607,6 +624,20 @@ dependencies = [
"sha2",
]
[[package]]
name = "p521"
version = "0.14.0-pre"
source = "git+https://github.com/RustCrypto/elliptic-curves.git?rev=5d1c252c2defb5808f55329f3e2955ca72d7f8b5#5d1c252c2defb5808f55329f3e2955ca72d7f8b5"
dependencies = [
"base16ct",
"ecdsa",
"elliptic-curve",
"primefield",
"primeorder",
"rand_core",
"sha2",
]
[[package]]
name = "pb-rs"
version = "0.10.0"
@ -620,18 +651,18 @@ dependencies = [
[[package]]
name = "pem-rfc7468"
version = "0.7.0"
version = "1.0.0-pre.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412"
checksum = "76a65e1c27d1680f8805b3f8c9949f08d6aa5d6cbd088c9896e64a53821dc27d"
dependencies = [
"base64ct",
]
[[package]]
name = "pkcs1"
version = "0.7.5"
version = "0.8.0-pre.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f"
checksum = "4f6af6e88ac39402f67488e22faa9eb15cf065f520cf4a09419393691a6d0133"
dependencies = [
"der",
"pkcs8",
@ -640,9 +671,9 @@ dependencies = [
[[package]]
name = "pkcs8"
version = "0.10.2"
version = "0.11.0-pre.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
checksum = "935c09e0aecb0cb8f8907b57438b19a068cb74a25189b06724f061170b2465ff"
dependencies = [
"der",
"spki",
@ -654,20 +685,24 @@ version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "primefield"
version = "0.14.0-pre"
source = "git+https://github.com/RustCrypto/elliptic-curves.git?rev=5d1c252c2defb5808f55329f3e2955ca72d7f8b5#5d1c252c2defb5808f55329f3e2955ca72d7f8b5"
[[package]]
name = "primeorder"
version = "0.13.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6"
version = "0.14.0-pre.0"
source = "git+https://github.com/RustCrypto/elliptic-curves.git?rev=5d1c252c2defb5808f55329f3e2955ca72d7f8b5#5d1c252c2defb5808f55329f3e2955ca72d7f8b5"
dependencies = [
"elliptic-curve",
]
[[package]]
name = "proc-macro2"
version = "1.0.78"
version = "1.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e"
dependencies = [
"unicode-ident",
]
@ -726,9 +761,9 @@ dependencies = [
[[package]]
name = "regex"
version = "1.10.3"
version = "1.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15"
checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c"
dependencies = [
"aho-corasick",
"memchr",
@ -738,9 +773,9 @@ dependencies = [
[[package]]
name = "regex-automata"
version = "0.4.5"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd"
checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea"
dependencies = [
"aho-corasick",
"memchr",
@ -749,15 +784,15 @@ dependencies = [
[[package]]
name = "regex-syntax"
version = "0.8.2"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56"
[[package]]
name = "rfc6979"
version = "0.4.0"
version = "0.5.0-pre.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2"
checksum = "045972f2f66b9467a2f6834b7fd0f9b23ca214b4a8700b880c36edb726e96da6"
dependencies = [
"hmac",
"subtle",
@ -765,9 +800,9 @@ dependencies = [
[[package]]
name = "rsa"
version = "0.9.6"
version = "0.10.0-pre.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d0e5124fcb30e76a7e79bfee683a2746db83784b86289f6251b54b7950a0dfc"
checksum = "43e0089f12e510517c97e1adc17d0f8374efbabdd021dfb7645d6619f85633e9"
dependencies = [
"const-oid",
"digest",
@ -786,13 +821,13 @@ dependencies = [
[[package]]
name = "sec1"
version = "0.7.3"
version = "0.8.0-pre.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc"
checksum = "02dc081ed777a3bab68583b52ffb8221677b6e90d483b320963a247e2c07f328"
dependencies = [
"base16ct",
"der",
"generic-array",
"hybrid-array",
"pkcs8",
"subtle",
"zeroize",
@ -800,18 +835,18 @@ dependencies = [
[[package]]
name = "serde"
version = "1.0.196"
version = "1.0.197"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32"
checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.196"
version = "1.0.197"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67"
checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
dependencies = [
"proc-macro2",
"quote",
@ -820,9 +855,9 @@ dependencies = [
[[package]]
name = "sha1"
version = "0.10.6"
version = "0.11.0-pre.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
checksum = "3885de8cb916f223718c1ccd47a840b91f806333e76002dc5cb3862154b4fed3"
dependencies = [
"cfg-if",
"cpufeatures",
@ -831,9 +866,9 @@ dependencies = [
[[package]]
name = "sha2"
version = "0.10.8"
version = "0.11.0-pre.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
checksum = "8f33549bf3064b62478926aa89cbfc7c109aab66ae8f0d5d2ef839e482cc30d6"
dependencies = [
"cfg-if",
"cpufeatures",
@ -842,9 +877,9 @@ dependencies = [
[[package]]
name = "signature"
version = "2.2.0"
version = "2.3.0-pre.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
checksum = "1700c22ba9ce32c7b0a1495068a906c3552e7db386af7cf865162e0dea498523"
dependencies = [
"digest",
"rand_core",
@ -858,9 +893,9 @@ checksum = "9fed904c7fb2856d868b92464fc8fa597fce366edea1a9cbfaa8cb5fe080bd6d"
[[package]]
name = "smallvec"
version = "1.13.1"
version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7"
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
[[package]]
name = "spin"
@ -870,9 +905,9 @@ checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
[[package]]
name = "spki"
version = "0.7.3"
version = "0.8.0-pre.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
checksum = "cb2b56670f5ef52934c97efad30bf42585de0c33ec3e2a886e38b80d2db67243"
dependencies = [
"base64ct",
"der",
@ -892,9 +927,9 @@ checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
[[package]]
name = "syn"
version = "2.0.48"
version = "2.0.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687"
dependencies = [
"proc-macro2",
"quote",
@ -921,18 +956,18 @@ dependencies = [
[[package]]
name = "thiserror"
version = "1.0.56"
version = "1.0.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad"
checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.56"
version = "1.0.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471"
checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7"
dependencies = [
"proc-macro2",
"quote",
@ -978,18 +1013,18 @@ version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
[[package]]
name = "unicode-xid"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
[[package]]
name = "vec_map"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
@ -1029,9 +1064,8 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "x509-cert"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1301e935010a701ae5f8655edc0ad17c44bad3ac5ce8c39185f75453b720ae94"
version = "0.3.0-pre"
source = "git+https://github.com/RustCrypto/formats.git?rev=809df65b20d61e88afb7f514b5cfdd3d1958a40f#809df65b20d61e88afb7f514b5cfdd3d1958a40f"
dependencies = [
"const-oid",
"der",

View File

@ -13,16 +13,18 @@ num-derive = "0.4"
thiserror = "1.0"
byteorder = "1"
size = "0.4"
sha1 = "0.10"
sha2 = "0.10"
digest = "0.10"
p256 = "0.13"
p384 = "0.13"
rsa = "0.9"
x509-cert = "0.2"
der = "0.7"
sha1 = "0.11.0-pre.3"
sha2 = "=0.11.0-pre.3"
digest = "0.11.0-pre.8"
#p256 = "0.14"
#p384 = "0.14"
#p521 = "0.14"
rsa = "0.10.0-pre.1"
#x509-cert = "0.3"
der = "0.8.0-pre.0"
bytemuck = "1.14"
fdt = "0.1"
const_format = "0.2"
[workspace.dependencies.argh]
git = "https://github.com/google/argh.git"
@ -37,6 +39,22 @@ rev = "2f37d5a65504de7d716b5b28fd82219501a901a9"
git = "https://github.com/tafia/quick-protobuf.git"
rev = "2f37d5a65504de7d716b5b28fd82219501a901a9"
[workspace.dependencies.p256]
git = "https://github.com/RustCrypto/elliptic-curves.git"
rev = "5d1c252c2defb5808f55329f3e2955ca72d7f8b5"
[workspace.dependencies.p384]
git = "https://github.com/RustCrypto/elliptic-curves.git"
rev = "5d1c252c2defb5808f55329f3e2955ca72d7f8b5"
[workspace.dependencies.p521]
git = "https://github.com/RustCrypto/elliptic-curves.git"
rev = "5d1c252c2defb5808f55329f3e2955ca72d7f8b5"
[workspace.dependencies.x509-cert]
git = "https://github.com/RustCrypto/formats.git"
rev = "809df65b20d61e88afb7f514b5cfdd3d1958a40f"
[profile.dev]
opt-level = "z"
lto = true

View File

@ -22,18 +22,3 @@ LOCAL_SRC_FILES := \
base-rs.cpp \
../external/cxx-rs/src/cxx.cc
include $(BUILD_STATIC_LIBRARY)
# All static executables should link with libcompat
include $(CLEAR_VARS)
LOCAL_MODULE := libcompat
LOCAL_SRC_FILES := compat/compat.cpp
# Fix static variables' ctor/dtor when using LTO
# See: https://github.com/android/ndk/issues/1461
LOCAL_EXPORT_LDFLAGS := -static -T src/lto_fix.lds -Wl,--wrap=rename -Wl,--wrap=renameat
# For some reason, using the hacky libc.a with x86 will trigger stack protection violation
# when mixing Rust and C++ code. Disable stack protector to bypass this issue.
ifeq ($(TARGET_ARCH), x86)
LOCAL_EXPORT_CFLAGS := -fno-stack-protector
endif
include $(BUILD_STATIC_LIBRARY)

View File

@ -22,3 +22,4 @@ argh = { workspace = true }
bytemuck = { workspace = true }
num-traits = { workspace = true }
num-derive = { workspace = true }
const_format = { workspace = true }

View File

@ -1,144 +0,0 @@
// This file implements all missing symbols that should exist in normal API 23
// libc.a but missing in our extremely lean libc.a replacements.
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/syscall.h>
#include <sys/xattr.h>
extern "C" {
#if !defined(__LP64__)
// Add "hacky" libc.a missing symbols back
// All symbols in this file are weak, so a vanilla NDK should still link properly
#include "fortify.hpp"
// Original source: https://github.com/freebsd/freebsd/blob/master/contrib/file/src/getline.c
// License: BSD, full copyright notice please check original source
[[gnu::weak]]
ssize_t getdelim(char **buf, size_t *bufsiz, int delimiter, FILE *fp) {
char *ptr, *eptr;
if (*buf == nullptr || *bufsiz == 0) {
*bufsiz = BUFSIZ;
if ((*buf = (char *) malloc(*bufsiz)) == nullptr)
return -1;
}
for (ptr = *buf, eptr = *buf + *bufsiz;;) {
int c = fgetc(fp);
if (c == -1) {
if (feof(fp))
return ptr == *buf ? -1 : ptr - *buf;
else
return -1;
}
*ptr++ = c;
if (c == delimiter) {
*ptr = '\0';
return ptr - *buf;
}
if (ptr + 2 >= eptr) {
char *nbuf;
size_t nbufsiz = *bufsiz * 2;
ssize_t d = ptr - *buf;
if ((nbuf = (char *) realloc(*buf, nbufsiz)) == nullptr)
return -1;
*buf = nbuf;
*bufsiz = nbufsiz;
eptr = nbuf + nbufsiz;
ptr = nbuf + d;
}
}
}
[[gnu::weak]]
ssize_t getline(char **buf, size_t *bufsiz, FILE *fp) {
return getdelim(buf, bufsiz, '\n', fp);
}
// Missing system call wrappers
[[gnu::weak]]
ssize_t readlinkat(int dirfd, const char *pathname, char *buf, size_t bufsiz) {
return syscall(__NR_readlinkat, dirfd, pathname, buf, bufsiz);
}
[[gnu::weak]]
int symlinkat(const char *target, int newdirfd, const char *linkpath) {
return syscall(__NR_symlinkat, target, newdirfd, linkpath);
}
[[gnu::weak]]
int linkat(int olddirfd, const char *oldpath,
int newdirfd, const char *newpath, int flags) {
return syscall(__NR_linkat, olddirfd, oldpath, newdirfd, newpath, flags);
}
[[gnu::weak]]
int faccessat(int dirfd, const char *pathname, int mode, int flags) {
return syscall(__NR_faccessat, dirfd, pathname, mode, flags);
}
[[gnu::weak]]
int mkfifo(const char *path, mode_t mode) {
return mknod(path, (mode & ~S_IFMT) | S_IFIFO, 0);
}
[[gnu::weak]]
int fsetxattr(int fd, const char *name, const void *value, size_t size, int flags) {
return syscall(__NR_fsetxattr, fd, name, value, size, flags);
}
[[gnu::weak]]
int lsetxattr(const char *path, const char *name, const void *value, size_t size, int flags) {
return syscall(__NR_lsetxattr, path, name, value, size, flags);
}
#define SPLIT_64(v) (unsigned)((v) & 0xFFFFFFFF), (unsigned)((v) >> 32)
#if defined(__arm__)
// Why the additional 0 is required: https://man7.org/linux/man-pages/man2/syscall.2.html
[[gnu::weak]]
int ftruncate64(int fd, off64_t length) {
return syscall(__NR_ftruncate64, fd, 0, SPLIT_64(length));
}
#elif defined(__i386__)
[[gnu::weak]]
int ftruncate64(int fd, off64_t length) {
return syscall(__NR_ftruncate64, fd, SPLIT_64(length));
}
#endif
[[gnu::weak]]
void android_set_abort_message(const char *) {}
extern FILE __sF[];
[[gnu::weak]] FILE* stdin = &__sF[0];
[[gnu::weak]] FILE* stdout = &__sF[1];
[[gnu::weak]] FILE* stderr = &__sF[2];
#endif // !defined(__LP64__)
[[maybe_unused]]
int __wrap_renameat(int old_dir_fd, const char *old_path, int new_dir_fd, const char *new_path) {
long out = syscall(__NR_renameat, old_dir_fd, old_path, new_dir_fd, new_path);
if (out == -1 && errno == ENOSYS) {
out = syscall(__NR_renameat2, old_dir_fd, old_path, new_dir_fd, new_path, 0);
}
return static_cast<int>(out);
}
[[maybe_unused]]
int __wrap_rename(const char *old_path, const char *new_path) {
return __wrap_renameat(AT_FDCWD, old_path, AT_FDCWD, new_path);
}
} // extern "C"

View File

@ -1,144 +0,0 @@
// Original source: https://android.googlesource.com/platform/bionic/+/master/libc/bionic/fortify.cpp
// License: AOSP, full copyright notice please check original source
#include <sys/stat.h>
#include <fcntl.h>
#undef _FORTIFY_SOURCE
extern void __vloge(const char* fmt, va_list ap);
static inline __noreturn __printflike(1, 2) void __fortify_fatal(const char* fmt, ...) {
va_list args;
va_start(args, fmt);
__vloge(fmt, args);
va_end(args);
abort();
}
static inline void __check_count(const char* fn, const char* identifier, size_t value) {
if (__predict_false(value > SSIZE_MAX)) {
__fortify_fatal("%s: %s %zu > SSIZE_MAX", fn, identifier, value);
}
}
static inline void __check_buffer_access(const char* fn, const char* action,
size_t claim, size_t actual) {
if (__predict_false(claim > actual)) {
__fortify_fatal("%s: prevented %zu-byte %s %zu-byte buffer", fn, claim, action, actual);
}
}
[[gnu::weak]]
void* __memcpy_chk(void* dst, const void* src, size_t count, size_t dst_len) {
__check_count("memcpy", "count", count);
__check_buffer_access("memcpy", "write into", count, dst_len);
return __call_bypassing_fortify(memcpy)(dst, src, count);
}
[[gnu::weak]]
char* __strcpy_chk(char* dst, const char* src, size_t dst_len) {
// TODO: optimize so we don't scan src twice.
size_t src_len = __builtin_strlen(src) + 1;
__check_buffer_access("strcpy", "write into", src_len, dst_len);
return __builtin_strcpy(dst, src);
}
[[gnu::weak]]
size_t __strlcpy_chk(char* dst, const char* src,
size_t supplied_size, size_t dst_len_from_compiler) {
__check_buffer_access("strlcpy", "write into", supplied_size, dst_len_from_compiler);
return __call_bypassing_fortify(strlcpy)(dst, src, supplied_size);
}
[[gnu::weak]]
char* __strchr_chk(const char* p, int ch, size_t s_len) {
for (;; ++p, s_len--) {
if (__predict_false(s_len == 0)) {
__fortify_fatal("strchr: prevented read past end of buffer");
}
if (*p == static_cast<char>(ch)) {
return const_cast<char*>(p);
}
if (*p == '\0') {
return nullptr;
}
}
}
[[gnu::weak]]
char* __strcat_chk(char* dst, const char* src, size_t dst_buf_size) {
char* save = dst;
size_t dst_len = __strlen_chk(dst, dst_buf_size);
dst += dst_len;
dst_buf_size -= dst_len;
while ((*dst++ = *src++) != '\0') {
dst_buf_size--;
if (__predict_false(dst_buf_size == 0)) {
__fortify_fatal("strcat: prevented write past end of %zu-byte buffer", dst_buf_size);
}
}
return save;
}
[[gnu::weak]]
size_t __strlen_chk(const char* s, size_t s_len) {
// TODO: "prevented" here would be a lie because this strlen can run off the end.
// strlen is too important to be expensive, so we wanted to be able to call the optimized
// implementation, but I think we need to implement optimized assembler __strlen_chk routines.
size_t ret = __builtin_strlen(s);
if (__predict_false(ret >= s_len)) {
__fortify_fatal("strlen: detected read past end of buffer");
}
return ret;
}
[[gnu::weak]]
int __vsprintf_chk(char* dst, int /*flags*/,
size_t dst_len_from_compiler, const char* format, va_list va) {
// The compiler uses SIZE_MAX to mean "no idea", but our vsnprintf rejects sizes that large.
int result = __call_bypassing_fortify(vsnprintf)(dst,
dst_len_from_compiler == SIZE_MAX ? SSIZE_MAX : dst_len_from_compiler,
format, va);
// Try to catch failures after the fact...
__check_buffer_access("vsprintf", "write into", result + 1, dst_len_from_compiler);
return result;
}
[[gnu::weak]]
mode_t __umask_chk(mode_t mode) {
if (__predict_false((mode & 0777) != mode)) {
__fortify_fatal("umask: called with invalid mask %o", mode);
}
return __umask_real(mode);
}
[[gnu::weak]]
ssize_t __read_chk(int fd, void* buf, size_t count, size_t buf_size) {
__check_count("read", "count", count);
__check_buffer_access("read", "write into", count, buf_size);
return __call_bypassing_fortify(read)(fd, buf, count);
}
static inline bool needs_mode(int flags) {
return ((flags & O_CREAT) == O_CREAT) || ((flags & O_TMPFILE) == O_TMPFILE);
}
static inline int force_O_LARGEFILE(int flags) {
return flags | O_LARGEFILE;
}
[[gnu::weak]]
int __open_2(const char* pathname, int flags) {
if (needs_mode(flags)) __fortify_fatal("open: called with O_CREAT/O_TMPFILE but no mode");
return __openat_real(AT_FDCWD, pathname, force_O_LARGEFILE(flags), 0);
}
[[gnu::weak]]
int __openat_2(int fd, const char* pathname, int flags) {
if (needs_mode(flags)) __fortify_fatal("open: called with O_CREAT/O_TMPFILE but no mode");
return __openat_real(fd, pathname, force_O_LARGEFILE(flags), 0);
}
[[gnu::weak]]
int __vsnprintf_chk(char* dst, size_t supplied_size, int /*flags*/,
size_t dst_len_from_compiler, const char* format, va_list va) {
__check_buffer_access("vsnprintf", "write into", supplied_size, dst_len_from_compiler);
return __call_bypassing_fortify(vsnprintf)(dst, supplied_size, format, va);
}

View File

@ -2,7 +2,8 @@ use std::cmp::min;
use std::ffi::{CStr, FromBytesWithNulError, OsStr};
use std::fmt::{Arguments, Debug, Display, Formatter, Write};
use std::ops::{Deref, DerefMut};
use std::path::Path;
use std::os::unix::ffi::OsStrExt;
use std::path::{Path, PathBuf};
use std::str::{Utf8Chunks, Utf8Error};
use std::{fmt, mem, slice, str};
@ -33,13 +34,6 @@ use crate::slice_from_ptr_mut;
// Utf8CString, Utf8CStrBufRef, and Utf8CStrBufArr<N> implements Utf8CStrWrite.
// Utf8CStrBufRef and Utf8CStrBufArr<N> implements Utf8CStrBuf.
pub fn copy_cstr<T: AsRef<CStr> + ?Sized>(dest: &mut [u8], src: &T) -> usize {
let src = src.as_ref().to_bytes_with_nul();
let len = min(src.len(), dest.len());
dest[..len].copy_from_slice(&src[..len]);
len - 1
}
fn utf8_cstr_buf_append(buf: &mut dyn Utf8CStrBuf, s: &[u8]) -> usize {
let mut used = buf.len();
if used >= buf.capacity() - 1 {
@ -48,7 +42,9 @@ fn utf8_cstr_buf_append(buf: &mut dyn Utf8CStrBuf, s: &[u8]) -> usize {
}
let dest = unsafe { &mut buf.mut_buf()[used..] };
let len = min(s.len(), dest.len() - 1);
dest[..len].copy_from_slice(&s[..len]);
if len > 0 {
dest[..len].copy_from_slice(&s[..len]);
}
dest[len] = b'\0';
used += len;
unsafe { buf.set_len(used) };
@ -105,7 +101,7 @@ trait AsUtf8CStr {
// Implementation for Utf8CString
trait StringExt {
pub trait StringExt {
fn nul_terminate(&mut self) -> &mut [u8];
}
@ -122,6 +118,21 @@ impl StringExt for String {
}
}
impl StringExt for PathBuf {
#[allow(mutable_transmutes)]
fn nul_terminate(&mut self) -> &mut [u8] {
self.reserve(1);
// SAFETY: the PathBuf is reserved to have enough capacity to fit in the null byte
// SAFETY: the null byte is explicitly added outside of the PathBuf's length
unsafe {
let bytes: &mut [u8] = mem::transmute(self.as_mut_os_str().as_bytes());
let buf = slice::from_raw_parts_mut(bytes.as_mut_ptr(), bytes.len() + 1);
*buf.get_unchecked_mut(bytes.len()) = b'\0';
buf
}
}
}
#[derive(Default)]
pub struct Utf8CString(String);
@ -435,6 +446,7 @@ 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)
}
@ -640,7 +652,8 @@ macro_rules! cstr {
);
#[allow(unused_unsafe)]
unsafe {
$crate::Utf8CStr::from_bytes_unchecked(concat!($($str)*, "\0").as_bytes())
$crate::Utf8CStr::from_bytes_unchecked($crate::const_format::concatcp!($($str)*, "\0")
.as_bytes())
}
}};
}
@ -648,6 +661,6 @@ macro_rules! cstr {
#[macro_export]
macro_rules! raw_cstr {
($($str:tt)*) => {{
cstr!($($str)*).as_ptr()
$crate::cstr!($($str)*).as_ptr()
}};
}

View File

@ -7,11 +7,10 @@ use cfg_if::cfg_if;
use cxx::private::c_char;
use libc::mode_t;
use crate::logging::CxxResultExt;
pub(crate) use crate::xwrap::*;
use crate::{
clone_attr, cstr, fclone_attr, fd_path, map_fd, map_file, slice_from_ptr, Directory, FsPath,
Utf8CStr, Utf8CStrBufRef,
clone_attr, cstr, fclone_attr, fd_path, map_fd, map_file, slice_from_ptr, CxxResultExt,
Directory, FsPath, Utf8CStr, Utf8CStrBufRef,
};
pub(crate) fn fd_path_for_cxx(fd: RawFd, buf: &mut [u8]) -> isize {

View File

@ -78,6 +78,7 @@ void file_readline(bool trim, const char *file, const function<bool(string_view)
if (auto fp = open_file(file, "re"))
file_readline(trim, fp.get(), fn);
}
void file_readline(const char *file, const function<bool(string_view)> &fn) {
file_readline(false, file, fn);
}
@ -100,86 +101,6 @@ void parse_prop_file(const char *file, const function<bool(string_view, string_v
parse_prop_file(fp.get(), fn);
}
std::vector<mount_info> parse_mount_info(const char *pid) {
char buf[PATH_MAX] = {};
ssprintf(buf, sizeof(buf), "/proc/%s/mountinfo", pid);
std::vector<mount_info> result;
file_readline(buf, [&result](string_view line) -> bool {
int root_start = 0, root_end = 0;
int target_start = 0, target_end = 0;
int vfs_option_start = 0, vfs_option_end = 0;
int type_start = 0, type_end = 0;
int source_start = 0, source_end = 0;
int fs_option_start = 0, fs_option_end = 0;
int optional_start = 0, optional_end = 0;
unsigned int id, parent, maj, min;
sscanf(line.data(),
"%u " // (1) id
"%u " // (2) parent
"%u:%u " // (3) maj:min
"%n%*s%n " // (4) mountroot
"%n%*s%n " // (5) target
"%n%*s%n" // (6) vfs options (fs-independent)
"%n%*[^-]%n - " // (7) optional fields
"%n%*s%n " // (8) FS type
"%n%*s%n " // (9) source
"%n%*s%n", // (10) fs options (fs specific)
&id, &parent, &maj, &min, &root_start, &root_end, &target_start,
&target_end, &vfs_option_start, &vfs_option_end,
&optional_start, &optional_end, &type_start, &type_end,
&source_start, &source_end, &fs_option_start, &fs_option_end);
auto root = line.substr(root_start, root_end - root_start);
auto target = line.substr(target_start, target_end - target_start);
auto vfs_option =
line.substr(vfs_option_start, vfs_option_end - vfs_option_start);
++optional_start;
--optional_end;
auto optional = line.substr(
optional_start,
optional_end - optional_start > 0 ? optional_end - optional_start : 0);
auto type = line.substr(type_start, type_end - type_start);
auto source = line.substr(source_start, source_end - source_start);
auto fs_option =
line.substr(fs_option_start, fs_option_end - fs_option_start);
unsigned int shared = 0;
unsigned int master = 0;
unsigned int propagate_from = 0;
if (auto pos = optional.find("shared:"); pos != std::string_view::npos) {
shared = parse_int(optional.substr(pos + 7));
}
if (auto pos = optional.find("master:"); pos != std::string_view::npos) {
master = parse_int(optional.substr(pos + 7));
}
if (auto pos = optional.find("propagate_from:");
pos != std::string_view::npos) {
propagate_from = parse_int(optional.substr(pos + 15));
}
result.emplace_back(mount_info {
.id = id,
.parent = parent,
.device = static_cast<dev_t>(makedev(maj, min)),
.root {root},
.target {target},
.vfs_option {vfs_option},
.optional {
.shared = shared,
.master = master,
.propagate_from = propagate_from,
},
.type {type},
.source {source},
.fs_option {fs_option},
});
return true;
});
return result;
}
sDIR make_dir(DIR *dp) {
return sDIR(dp, [](DIR *dp){ return dp ? closedir(dp) : 1; });
}

View File

@ -20,23 +20,6 @@ static inline T align_padding(T v, int a) {
return align_to(v, a) - v;
}
struct mount_info {
unsigned int id;
unsigned int parent;
dev_t device;
std::string root;
std::string target;
std::string vfs_option;
struct {
unsigned int shared;
unsigned int master;
unsigned int propagate_from;
} optional;
std::string type;
std::string source;
std::string fs_option;
};
struct mmap_data : public byte_data {
static_assert((sizeof(void *) == 8 && BLKGETSIZE64 == 0x80081272) ||
(sizeof(void *) == 4 && BLKGETSIZE64 == 0x80041272));
@ -77,7 +60,6 @@ void file_readline(const char *file, const std::function<bool(std::string_view)>
void parse_prop_file(FILE *fp, const std::function<bool(std::string_view, std::string_view)> &fn);
void parse_prop_file(const char *file,
const std::function<bool(std::string_view, std::string_view)> &fn);
std::vector<mount_info> parse_mount_info(const char *pid);
std::string resolve_preinit_dir(const char *base_dir);
using sFILE = std::unique_ptr<FILE, decltype(&fclose)>;

View File

@ -2,25 +2,25 @@ use mem::MaybeUninit;
use std::cmp::min;
use std::ffi::CStr;
use std::fs::File;
use std::io::{BufRead, Read, Seek, SeekFrom, Write};
use std::io::{BufRead, BufReader, Read, Seek, SeekFrom, Write};
use std::ops::Deref;
use std::os::android::fs::MetadataExt;
use std::os::fd::{AsFd, BorrowedFd, IntoRawFd};
use std::os::unix::fs::FileTypeExt;
use std::os::unix::ffi::OsStrExt;
use std::os::unix::io::{AsRawFd, FromRawFd, OwnedFd, RawFd};
use std::path::Path;
use std::{io, mem, ptr, slice};
use bytemuck::{bytes_of_mut, Pod};
use libc::{
c_uint, dirent, mode_t, EEXIST, ENOENT, F_OK, O_CLOEXEC, O_CREAT, O_PATH, O_RDONLY, O_RDWR,
O_TRUNC, O_WRONLY, S_IFDIR, S_IFLNK, S_IFREG,
c_uint, dirent, makedev, mode_t, EEXIST, ENOENT, F_OK, O_CLOEXEC, O_CREAT, O_PATH, O_RDONLY,
O_RDWR, O_TRUNC, O_WRONLY,
};
use num_traits::AsPrimitive;
use crate::cxx_extern::readlinkat_for_cxx;
use crate::{
copy_cstr, cstr, errno, error, FsPath, FsPathBuf, LibcReturn, Utf8CStr, Utf8CStrBuf,
Utf8CStrBufArr,
cstr, errno, error, FsPath, FsPathBuf, LibcReturn, Utf8CStr, Utf8CStrBuf, Utf8CStrBufArr,
Utf8CStrWrite,
};
pub fn __open_fd_impl(path: &Utf8CStr, flags: i32, mode: mode_t) -> io::Result<OwnedFd> {
@ -151,6 +151,40 @@ impl FileAttr {
con: Utf8CStrBufArr::new(),
}
}
#[inline(always)]
#[allow(clippy::unnecessary_cast)]
fn is(&self, mode: mode_t) -> bool {
(self.st.st_mode & libc::S_IFMT as c_uint) as mode_t == mode
}
pub fn is_dir(&self) -> bool {
self.is(libc::S_IFDIR)
}
pub fn is_file(&self) -> bool {
self.is(libc::S_IFREG)
}
pub fn is_symlink(&self) -> bool {
self.is(libc::S_IFLNK)
}
pub fn is_block_device(&self) -> bool {
self.is(libc::S_IFBLK)
}
pub fn is_char_device(&self) -> bool {
self.is(libc::S_IFCHR)
}
pub fn is_fifo(&self) -> bool {
self.is(libc::S_IFIFO)
}
pub fn is_socket(&self) -> bool {
self.is(libc::S_IFSOCK)
}
}
#[cfg(feature = "selinux")]
@ -187,10 +221,26 @@ impl DirEntry<'_> {
self.d_type == libc::DT_REG
}
pub fn is_lnk(&self) -> bool {
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) -> io::Result<()> {
let flag = if self.is_dir() { libc::AT_REMOVEDIR } else { 0 };
unsafe {
@ -372,7 +422,7 @@ impl Directory {
};
std::io::copy(&mut src, &mut dest)?;
fd_set_attr(dest.as_raw_fd(), &attr)?;
} else if e.is_lnk() {
} else if e.is_symlink() {
let mut path = Utf8CStrBufArr::default();
e.read_link(&mut path)?;
unsafe {
@ -550,10 +600,9 @@ impl FsPath {
}
pub fn remove_all(&self) -> io::Result<()> {
let f = self.open(O_RDONLY | O_CLOEXEC)?;
let st = f.metadata()?;
if st.is_dir() {
let mut dir = Directory::try_from(OwnedFd::from(f))?;
let attr = self.get_attr()?;
if attr.is_dir() {
let mut dir = Directory::try_from(open_fd!(self, O_RDONLY | O_CLOEXEC)?)?;
dir.remove_all()?;
}
self.remove()
@ -584,11 +633,14 @@ impl FsPath {
}
pub fn mkdirs(&self, mode: mode_t) -> io::Result<()> {
let mut buf = [0_u8; 4096];
let len = copy_cstr(&mut buf, self);
let buf = &mut buf[..len];
if self.is_empty() {
return Ok(());
}
let mut arr = Utf8CStrBufArr::default();
arr.push_str(self);
let mut off = 1;
unsafe {
let buf = arr.as_bytes_mut();
while let Some(p) = buf[off..].iter().position(|c| *c == b'/') {
buf[off + p] = b'\0';
if libc::mkdir(buf.as_ptr().cast(), mode) < 0 && *errno() != EEXIST {
@ -652,7 +704,7 @@ impl FsPath {
pub fn set_attr(&self, attr: &FileAttr) -> io::Result<()> {
unsafe {
if (attr.st.st_mode & libc::S_IFMT as c_uint) != S_IFLNK.as_() {
if !attr.is_symlink() {
libc::chmod(self.as_ptr(), (attr.st.st_mode & 0o777).as_()).as_os_err()?;
}
libc::lchown(self.as_ptr(), attr.st.st_uid, attr.st.st_gid).as_os_err()?;
@ -674,7 +726,7 @@ impl FsPath {
pub fn copy_to(&self, path: &FsPath) -> io::Result<()> {
let attr = self.get_attr()?;
if (attr.st.st_mode & libc::S_IFMT as c_uint) == S_IFDIR.as_() {
if attr.is_dir() {
path.mkdir(0o777)?;
let mut src = Directory::open(self)?;
let dest = Directory::open(path)?;
@ -682,11 +734,11 @@ impl FsPath {
} else {
// It's OK if remove failed
path.remove().ok();
if (attr.st.st_mode & libc::S_IFMT as c_uint) == S_IFREG.as_() {
if attr.is_file() {
let mut src = self.open(O_RDONLY | O_CLOEXEC)?;
let mut dest = path.create(O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0o777)?;
std::io::copy(&mut src, &mut dest)?;
} else if (attr.st.st_mode & libc::S_IFMT as c_uint) == S_IFLNK.as_() {
} else if attr.is_symlink() {
let mut buf = Utf8CStrBufArr::default();
self.read_link(&mut buf)?;
unsafe {
@ -701,7 +753,7 @@ impl FsPath {
pub fn move_to(&self, path: &FsPath) -> io::Result<()> {
if path.exists() {
let attr = path.get_attr()?;
if (attr.st.st_mode & libc::S_IFMT as c_uint) == S_IFDIR.as_() {
if attr.is_dir() {
let mut src = Directory::open(self)?;
let dest = Directory::open(path)?;
return src.move_into(&dest);
@ -714,7 +766,7 @@ impl FsPath {
pub fn link_to(&self, path: &FsPath) -> io::Result<()> {
let attr = self.get_attr()?;
if (attr.st.st_mode & libc::S_IFMT as c_uint) == S_IFDIR.as_() {
if attr.is_dir() {
path.mkdir(0o777)?;
path.set_attr(&attr)?;
let mut src = Directory::open(self)?;
@ -724,6 +776,23 @@ impl FsPath {
unsafe { libc::link(self.as_ptr(), path.as_ptr()).as_os_err() }
}
}
pub fn symlink_to(&self, path: &FsPath) -> io::Result<()> {
unsafe { libc::symlink(self.as_ptr(), path.as_ptr()).as_os_err() }
}
pub fn parent(&self, buf: &mut dyn Utf8CStrWrite) -> bool {
buf.clear();
if let Some(parent) = Path::new(self.as_str()).parent() {
let bytes = parent.as_os_str().as_bytes();
// SAFETY: all substring of self is valid UTF-8
let parent = unsafe { std::str::from_utf8_unchecked(bytes) };
buf.push_str(parent);
true
} else {
false
}
}
}
pub fn fd_get_attr(fd: RawFd) -> io::Result<FileAttr> {
@ -826,18 +895,18 @@ pub(crate) fn map_file(path: &Utf8CStr, rw: bool) -> io::Result<&'static mut [u8
const BLKGETSIZE64: u32 = 0x80041272;
let flag = if rw { O_RDWR } else { O_RDONLY };
let f = File::from(open_fd!(path, flag | O_CLOEXEC)?);
let file = FsPath::from(path).open(flag | O_CLOEXEC)?;
let st = f.metadata()?;
let sz = if st.file_type().is_block_device() {
let attr = fd_get_attr(file.as_raw_fd())?;
let sz = if attr.is_block_device() {
let mut sz = 0_u64;
unsafe { ioctl(f.as_raw_fd(), BLKGETSIZE64, &mut sz) }.as_os_err()?;
unsafe { ioctl(file.as_raw_fd(), BLKGETSIZE64, &mut sz) }.as_os_err()?;
sz
} else {
st.st_size()
attr.st.st_size as u64
};
map_fd(f.as_fd(), sz as usize, rw)
map_fd(file.as_fd(), sz as usize, rw)
}
pub(crate) fn map_fd(fd: BorrowedFd, sz: usize, rw: bool) -> io::Result<&'static mut [u8]> {
@ -861,3 +930,83 @@ pub(crate) fn map_fd(fd: BorrowedFd, sz: usize, rw: bool) -> io::Result<&'static
Ok(slice::from_raw_parts_mut(ptr.cast(), sz))
}
}
#[allow(dead_code)]
pub struct MountInfo {
pub id: u32,
pub parent: u32,
pub device: u64,
pub root: String,
pub target: String,
pub vfs_option: String,
pub shared: u32,
pub master: u32,
pub propagation_from: u32,
pub unbindable: bool,
pub fs_type: String,
pub source: String,
pub fs_option: String,
}
#[allow(clippy::useless_conversion)]
fn parse_mount_info_line(line: &str) -> Option<MountInfo> {
let mut iter = line.split_whitespace();
let id = iter.next()?.parse().ok()?;
let parent = iter.next()?.parse().ok()?;
let (maj, min) = iter.next()?.split_once(':')?;
let maj = maj.parse().ok()?;
let min = min.parse().ok()?;
let device = makedev(maj, min).into();
let root = iter.next()?.to_string();
let target = iter.next()?.to_string();
let vfs_option = iter.next()?.to_string();
let mut optional = iter.next()?;
let mut shared = 0;
let mut master = 0;
let mut propagation_from = 0;
let mut unbindable = false;
while optional != "-" {
if let Some(peer) = optional.strip_prefix("master:") {
master = peer.parse().ok()?;
} else if let Some(peer) = optional.strip_prefix("shared:") {
shared = peer.parse().ok()?;
} else if let Some(peer) = optional.strip_prefix("propagate_from:") {
propagation_from = peer.parse().ok()?;
} else if optional == "unbindable" {
unbindable = true;
}
optional = iter.next()?;
}
let fs_type = iter.next()?.to_string();
let source = iter.next()?.to_string();
let fs_option = iter.next()?.to_string();
Some(MountInfo {
id,
parent,
device,
root,
target,
vfs_option,
shared,
master,
propagation_from,
unbindable,
fs_type,
source,
fs_option,
})
}
pub fn parse_mount_info(pid: &str) -> Vec<MountInfo> {
let mut res = vec![];
let mut path = format!("/proc/{}/mountinfo", pid);
if let Ok(fd) = open_fd!(Utf8CStr::from_string(&mut path), O_RDONLY | O_CLOEXEC) {
let file = File::from(fd);
BufReader::new(file).foreach_lines(|line| {
parse_mount_info_line(line)
.map(|info| res.push(info))
.is_some()
});
}
res
}

View File

@ -6,9 +6,13 @@
#include "../files.hpp"
#define ENABLE_IOV 0
struct out_stream {
virtual bool write(const void *buf, size_t len) = 0;
#if ENABLE_IOV
virtual ssize_t writev(const iovec *iov, int iovcnt);
#endif
virtual ~out_stream() = default;
};
@ -48,27 +52,25 @@ private:
struct in_stream {
virtual ssize_t read(void *buf, size_t len) = 0;
virtual ssize_t readFully(void *buf, size_t len);
ssize_t readFully(void *buf, size_t len);
#if ENABLE_IOV
virtual ssize_t readv(const iovec *iov, int iovcnt);
#endif
virtual ~in_stream() = default;
};
// A channel is something that is writable, readable, and seekable
struct channel : public out_stream, public in_stream {
virtual off_t seek(off_t off, int whence) = 0;
virtual ~channel() = default;
};
// A stream is something that is writable and readable
struct stream : public out_stream, public in_stream {};
using channel_ptr = std::unique_ptr<channel>;
using stream_ptr = std::unique_ptr<stream>;
// Byte channel that dynamically allocates memory
class byte_channel : public channel {
// Byte stream that dynamically allocates memory
class byte_stream : public stream {
public:
byte_channel(heap_data &data) : _data(data) {}
byte_stream(heap_data &data) : _data(data) {}
ssize_t read(void *buf, size_t len) override;
bool write(const void *buf, size_t len) override;
off_t seek(off_t off, int whence) override;
private:
heap_data &_data;
@ -78,13 +80,12 @@ private:
void resize(size_t new_sz, bool zero = false);
};
class rust_vec_channel : public channel {
class rust_vec_stream : public stream {
public:
rust_vec_channel(rust::Vec<uint8_t> &data) : _data(data) {}
rust_vec_stream(rust::Vec<uint8_t> &data) : _data(data) {}
ssize_t read(void *buf, size_t len) override;
bool write(const void *buf, size_t len) override;
off_t seek(off_t off, int whence) override;
private:
rust::Vec<uint8_t> &_data;
@ -93,21 +94,22 @@ private:
void ensure_size(size_t sz, bool zero = false);
};
class file_channel : public channel {
class file_stream : public stream {
public:
bool write(const void *buf, size_t len) final;
protected:
virtual ssize_t do_write(const void *buf, size_t len) = 0;
};
// File channel but does not close the file descriptor at any time
class fd_channel : public file_channel {
// File stream but does not close the file descriptor at any time
class fd_stream : public file_stream {
public:
fd_channel(int fd) : fd(fd) {}
fd_stream(int fd) : fd(fd) {}
ssize_t read(void *buf, size_t len) override;
#if ENABLE_IOV
ssize_t readv(const iovec *iov, int iovcnt) override;
ssize_t writev(const iovec *iov, int iovcnt) override;
off_t seek(off_t off, int whence) override;
#endif
protected:
ssize_t do_write(const void *buf, size_t len) override;
private:
@ -115,26 +117,13 @@ private:
};
/* ****************************************
* Bridge between channel class and C stdio
* Bridge between stream class and C stdio
* ****************************************/
// sFILE -> channel_ptr
class fp_channel final : public file_channel {
public:
fp_channel(FILE *fp = nullptr) : fp(fp, fclose) {}
fp_channel(sFILE &&fp) : fp(std::move(fp)) {}
ssize_t read(void *buf, size_t len) override;
off_t seek(off_t off, int whence) override;
protected:
ssize_t do_write(const void *buf, size_t len) override;
private:
sFILE fp;
};
// channel_ptr -> sFILE
sFILE make_channel_fp(channel_ptr &&strm);
// stream_ptr -> sFILE
sFILE make_stream_fp(stream_ptr &&strm);
template <class T, class... Args>
sFILE make_channel_fp(Args &&... args) {
return make_channel_fp(channel_ptr(new T(std::forward<Args>(args)...)));
sFILE make_stream_fp(Args &&... args) {
return make_stream_fp(stream_ptr(new T(std::forward<Args>(args)...)));
}

View File

@ -3,6 +3,7 @@
#![feature(io_error_more)]
#![feature(utf8_chunks)]
pub use const_format;
pub use libc;
use num_traits::FromPrimitive;
@ -11,12 +12,14 @@ use cxx_extern::*;
pub use files::*;
pub use logging::*;
pub use misc::*;
pub use result::*;
mod cstr;
mod cxx_extern;
mod files;
mod logging;
mod misc;
mod result;
mod xwrap;
#[cxx::bridge]

View File

@ -8,6 +8,10 @@
using namespace std;
#ifndef __call_bypassing_fortify
#define __call_bypassing_fortify(fn) (&fn)
#endif
#undef vsnprintf
static int fmt_and_log_with_rs(LogLevel level, const char *fmt, va_list ap) {
constexpr int sz = 4096;

View File

@ -1,27 +1,14 @@
use std::fmt;
use std::fmt::Arguments;
use std::io::{stderr, stdout, Write};
use std::process::exit;
use num_derive::{FromPrimitive, ToPrimitive};
use num_traits::FromPrimitive;
use std::fmt;
use std::fmt::{Arguments, Display};
use std::io::{stderr, stdout, Write};
use std::panic::Location;
use std::process::exit;
use crate::ffi::LogLevelCxx;
use crate::{Utf8CStr, Utf8CStrBufArr};
// Error handling and logging throughout the Rust codebase in Magisk:
//
// All errors should be logged and consumed as soon as possible and converted into LoggedError.
// For `Result` with errors that implement the `Display` trait, use the `?` operator to
// log and convert to LoggedResult.
//
// To log an error with more information, use `ResultExt::log_with_msg()`.
//
// The "cxx" method variants in `CxxResultExt` are only used for C++ interop and
// should not be used directly in any Rust code.
//
// For general logging, use the <level>!(...) macros.
// Ugly hack to avoid using enum
#[allow(non_snake_case, non_upper_case_globals)]
mod LogFlag {
@ -50,7 +37,7 @@ pub static mut LOGGER: Logger = Logger {
};
type LogWriter = fn(level: LogLevel, msg: &Utf8CStr);
type Formatter<'a> = &'a mut dyn fmt::Write;
pub(crate) type Formatter<'a> = &'a mut dyn fmt::Write;
#[derive(Copy, Clone)]
pub struct Logger {
@ -171,188 +158,3 @@ macro_rules! debug {
macro_rules! debug {
($($args:tt)+) => {};
}
#[derive(Default)]
pub struct LoggedError {}
// Automatically handle all printable errors
impl<T: Display> From<T> for LoggedError {
#[cfg(not(debug_assertions))]
fn from(e: T) -> Self {
log_with_args(LogLevel::Error, format_args_nl!("{:#}", e));
LoggedError::default()
}
#[track_caller]
#[cfg(debug_assertions)]
fn from(e: T) -> Self {
let caller = Location::caller();
log_with_args(
LogLevel::Error,
format_args_nl!("[{}:{}] {:#}", caller.file(), caller.line(), e),
);
LoggedError::default()
}
}
pub type LoggedResult<T> = Result<T, LoggedError>;
#[macro_export]
macro_rules! log_err {
($msg:literal $(,)?) => {{
$crate::log_with_args($crate::LogLevel::Error, format_args_nl!($msg));
$crate::LoggedError::default()
}};
($err:expr $(,)?) => {{
$crate::log_with_args($crate::LogLevel::Error, format_args_nl!("{}", $err));
$crate::LoggedError::default()
}};
($($args:tt)+) => {{
$crate::log_with_args($crate::LogLevel::Error, format_args_nl!($($args)+));
$crate::LoggedError::default()
}};
}
pub trait ResultExt<T> {
fn log(self) -> LoggedResult<T>;
fn log_with_msg<F: FnOnce(Formatter) -> fmt::Result>(self, f: F) -> LoggedResult<T>;
}
pub trait ResultNoLog<T> {
fn no_log(self) -> LoggedResult<T>;
}
// Internal C++ bridging logging routines
pub(crate) trait CxxResultExt<T> {
fn log_cxx(self) -> LoggedResult<T>;
fn log_cxx_with_msg<F: FnOnce(Formatter) -> fmt::Result>(self, f: F) -> LoggedResult<T>;
}
trait LogImpl<T> {
fn log_impl(self, level: LogLevel, caller: Option<&'static Location>) -> LoggedResult<T>;
fn log_with_msg_impl<F: FnOnce(Formatter) -> fmt::Result>(
self,
level: LogLevel,
caller: Option<&'static Location>,
f: F,
) -> LoggedResult<T>;
}
impl<T, E> ResultNoLog<T> for Result<T, E> {
fn no_log(self) -> LoggedResult<T> {
match self {
Ok(v) => Ok(v),
Err(_) => Err(LoggedError::default()),
}
}
}
impl<T> ResultNoLog<T> for Option<T> {
fn no_log(self) -> LoggedResult<T> {
match self {
Some(v) => Ok(v),
None => Err(LoggedError::default()),
}
}
}
impl<T, R: LogImpl<T>> CxxResultExt<T> for R {
fn log_cxx(self) -> LoggedResult<T> {
self.log_impl(LogLevel::ErrorCxx, None)
}
fn log_cxx_with_msg<F: FnOnce(Formatter) -> fmt::Result>(self, f: F) -> LoggedResult<T> {
self.log_with_msg_impl(LogLevel::ErrorCxx, None, f)
}
}
impl<T, R: LogImpl<T>> ResultExt<T> for R {
#[cfg(not(debug_assertions))]
fn log(self) -> LoggedResult<T> {
self.log_impl(LogLevel::Error, None)
}
#[track_caller]
#[cfg(debug_assertions)]
fn log(self) -> LoggedResult<T> {
self.log_impl(LogLevel::Error, Some(Location::caller()))
}
#[cfg(not(debug_assertions))]
fn log_with_msg<F: FnOnce(Formatter) -> fmt::Result>(self, f: F) -> LoggedResult<T> {
self.log_with_msg_impl(LogLevel::Error, None, f)
}
#[track_caller]
#[cfg(debug_assertions)]
fn log_with_msg<F: FnOnce(Formatter) -> fmt::Result>(self, f: F) -> LoggedResult<T> {
self.log_with_msg_impl(LogLevel::Error, Some(Location::caller()), f)
}
}
impl<T> LogImpl<T> for LoggedResult<T> {
fn log_impl(self, _: LogLevel, _: Option<&'static Location>) -> LoggedResult<T> {
self
}
fn log_with_msg_impl<F: FnOnce(Formatter) -> fmt::Result>(
self,
level: LogLevel,
caller: Option<&'static Location>,
f: F,
) -> LoggedResult<T> {
match self {
Ok(v) => Ok(v),
Err(_) => {
log_with_formatter(level, |w| {
if let Some(caller) = caller {
write!(w, "[{}:{}] ", caller.file(), caller.line())?;
}
f(w)?;
w.write_char('\n')
});
Err(LoggedError::default())
}
}
}
}
impl<T, E: Display> LogImpl<T> for Result<T, E> {
fn log_impl(self, level: LogLevel, caller: Option<&'static Location>) -> LoggedResult<T> {
match self {
Ok(v) => Ok(v),
Err(e) => {
if let Some(caller) = caller {
log_with_args(
level,
format_args_nl!("[{}:{}] {:#}", caller.file(), caller.line(), e),
);
} else {
log_with_args(level, format_args_nl!("{:#}", e));
}
Err(LoggedError::default())
}
}
}
fn log_with_msg_impl<F: FnOnce(Formatter) -> fmt::Result>(
self,
level: LogLevel,
caller: Option<&'static Location>,
f: F,
) -> LoggedResult<T> {
match self {
Ok(v) => Ok(v),
Err(e) => {
log_with_formatter(level, |w| {
if let Some(caller) = caller {
write!(w, "[{}:{}] ", caller.file(), caller.line())?;
}
f(w)?;
writeln!(w, ": {:#}", e)
});
Err(LoggedError::default())
}
}
}
}

View File

@ -208,16 +208,22 @@ uint32_t binary_gcd(uint32_t u, uint32_t v) {
}
int switch_mnt_ns(int pid) {
char mnt[32];
ssprintf(mnt, sizeof(mnt), "/proc/%d/ns/mnt", pid);
if (access(mnt, R_OK) == -1) return 1; // Maybe process died..
int ret = -1;
int fd = syscall(__NR_pidfd_open, pid, 0);
if (fd > 0) {
ret = setns(fd, CLONE_NEWNS);
close(fd);
}
if (ret < 0) {
char mnt[32];
ssprintf(mnt, sizeof(mnt), "/proc/%d/ns/mnt", pid);
fd = open(mnt, O_RDONLY);
if (fd < 0) return 1; // Maybe process died..
int fd, ret;
fd = xopen(mnt, O_RDONLY);
if (fd < 0) return 1;
// Switch to its namespace
ret = xsetns(fd, 0);
close(fd);
// Switch to its namespace
ret = xsetns(fd, 0);
close(fd);
}
return ret;
}

View File

@ -205,7 +205,7 @@ private:
uint8_t arr[N];
};
class byte_channel;
class byte_stream;
struct heap_data : public byte_data {
ALLOW_MOVE_ONLY(heap_data)
@ -214,8 +214,8 @@ struct heap_data : public byte_data {
explicit heap_data(size_t sz) : byte_data(calloc(sz, 1), sz) {}
~heap_data() { free(_buf); }
// byte_channel needs to reallocate the internal buffer
friend byte_channel;
// byte_stream needs to reallocate the internal buffer
friend byte_stream;
};
struct owned_fd {

View File

@ -1,5 +1,7 @@
use std::fmt::Arguments;
use std::io::Write;
use std::process::exit;
use std::{io, slice, str};
use std::{fmt, io, slice, str};
use argh::EarlyExit;
use libc::c_char;
@ -76,11 +78,40 @@ impl<T> LibcReturn for *mut T {
}
}
pub trait BytesExt {
fn find(&self, needle: &[u8]) -> Option<usize>;
fn contains(&self, needle: &[u8]) -> bool {
self.find(needle).is_some()
}
}
impl<T: AsRef<[u8]> + ?Sized> BytesExt for T {
fn find(&self, needle: &[u8]) -> Option<usize> {
fn inner(haystack: &[u8], needle: &[u8]) -> Option<usize> {
unsafe {
let ptr: *const u8 = libc::memmem(
haystack.as_ptr().cast(),
haystack.len(),
needle.as_ptr().cast(),
needle.len(),
)
.cast();
if ptr.is_null() {
None
} else {
Some(ptr.offset_from(haystack.as_ptr()) as usize)
}
}
}
inner(self.as_ref(), needle)
}
}
pub trait MutBytesExt {
fn patch(&mut self, from: &[u8], to: &[u8]) -> Vec<usize>;
}
impl<T: AsMut<[u8]>> MutBytesExt for T {
impl<T: AsMut<[u8]> + ?Sized> MutBytesExt for T {
fn patch(&mut self, from: &[u8], to: &[u8]) -> Vec<usize> {
ffi::mut_u8_patch(self.as_mut(), from, to)
}
@ -117,3 +148,16 @@ impl<T> EarlyExitExt<T> for Result<T, EarlyExit> {
}
}
}
pub struct FmtAdaptor<'a, T>(pub &'a mut T)
where
T: Write;
impl<T: Write> fmt::Write for FmtAdaptor<'_, T> {
fn write_str(&mut self, s: &str) -> fmt::Result {
self.0.write_all(s.as_bytes()).map_err(|_| fmt::Error)
}
fn write_fmt(&mut self, args: Arguments<'_>) -> fmt::Result {
self.0.write_fmt(args).map_err(|_| fmt::Error)
}
}

195
native/src/base/result.rs Normal file
View File

@ -0,0 +1,195 @@
use std::fmt;
use std::fmt::Display;
use std::panic::Location;
use crate::logging::Formatter;
use crate::{log_with_args, log_with_formatter, LogLevel};
// Error handling throughout the Rust codebase in Magisk:
//
// All errors should be logged and consumed as soon as possible and converted into LoggedError.
// For `Result` with errors that implement the `Display` trait, use the `?` operator to
// log and convert to LoggedResult.
//
// To log an error with more information, use `ResultExt::log_with_msg()`.
//
// The "cxx" method variants in `CxxResultExt` are only used for C++ interop and
// should not be used directly in any Rust code.
#[derive(Default)]
pub struct LoggedError {}
pub type LoggedResult<T> = Result<T, LoggedError>;
#[macro_export]
macro_rules! log_err {
($($args:tt)+) => {{
$crate::log_with_args($crate::LogLevel::Error, format_args_nl!($($args)+));
$crate::LoggedError::default()
}};
}
// Any result or option can be silenced
pub trait SilentResultExt<T> {
fn silent(self) -> LoggedResult<T>;
}
impl<T, E> SilentResultExt<T> for Result<T, E> {
fn silent(self) -> LoggedResult<T> {
match self {
Ok(v) => Ok(v),
Err(_) => Err(LoggedError::default()),
}
}
}
impl<T> SilentResultExt<T> for Option<T> {
fn silent(self) -> LoggedResult<T> {
match self {
Some(v) => Ok(v),
None => Err(LoggedError::default()),
}
}
}
// Public API for logging results
pub trait ResultExt<T> {
fn log(self) -> LoggedResult<T>;
fn log_with_msg<F: FnOnce(Formatter) -> fmt::Result>(self, f: F) -> LoggedResult<T>;
}
// Internal C++ bridging logging routines
pub(crate) trait CxxResultExt<T> {
fn log_cxx(self) -> LoggedResult<T>;
fn log_cxx_with_msg<F: FnOnce(Formatter) -> fmt::Result>(self, f: F) -> LoggedResult<T>;
}
trait Loggable<T> {
fn do_log(self, level: LogLevel, caller: Option<&'static Location>) -> LoggedResult<T>;
fn do_log_msg<F: FnOnce(Formatter) -> fmt::Result>(
self,
level: LogLevel,
caller: Option<&'static Location>,
f: F,
) -> LoggedResult<T>;
}
impl<T, R: Loggable<T>> CxxResultExt<T> for R {
fn log_cxx(self) -> LoggedResult<T> {
self.do_log(LogLevel::ErrorCxx, None)
}
fn log_cxx_with_msg<F: FnOnce(Formatter) -> fmt::Result>(self, f: F) -> LoggedResult<T> {
self.do_log_msg(LogLevel::ErrorCxx, None, f)
}
}
impl<T, R: Loggable<T>> ResultExt<T> for R {
#[cfg(not(debug_assertions))]
fn log(self) -> LoggedResult<T> {
self.do_log(LogLevel::Error, None)
}
#[track_caller]
#[cfg(debug_assertions)]
fn log(self) -> LoggedResult<T> {
self.do_log(LogLevel::Error, Some(Location::caller()))
}
#[cfg(not(debug_assertions))]
fn log_with_msg<F: FnOnce(Formatter) -> fmt::Result>(self, f: F) -> LoggedResult<T> {
self.do_log_msg(LogLevel::Error, None, f)
}
#[track_caller]
#[cfg(debug_assertions)]
fn log_with_msg<F: FnOnce(Formatter) -> fmt::Result>(self, f: F) -> LoggedResult<T> {
self.do_log_msg(LogLevel::Error, Some(Location::caller()), f)
}
}
impl<T> Loggable<T> for LoggedResult<T> {
fn do_log(self, _: LogLevel, _: Option<&'static Location>) -> LoggedResult<T> {
self
}
fn do_log_msg<F: FnOnce(Formatter) -> fmt::Result>(
self,
level: LogLevel,
caller: Option<&'static Location>,
f: F,
) -> LoggedResult<T> {
match self {
Ok(v) => Ok(v),
Err(_) => {
log_with_formatter(level, |w| {
if let Some(caller) = caller {
write!(w, "[{}:{}] ", caller.file(), caller.line())?;
}
f(w)?;
w.write_char('\n')
});
Err(LoggedError::default())
}
}
}
}
impl<T, E: Display> Loggable<T> for Result<T, E> {
fn do_log(self, level: LogLevel, caller: Option<&'static Location>) -> LoggedResult<T> {
match self {
Ok(v) => Ok(v),
Err(e) => {
if let Some(caller) = caller {
log_with_args(
level,
format_args_nl!("[{}:{}] {:#}", caller.file(), caller.line(), e),
);
} else {
log_with_args(level, format_args_nl!("{:#}", e));
}
Err(LoggedError::default())
}
}
}
fn do_log_msg<F: FnOnce(Formatter) -> fmt::Result>(
self,
level: LogLevel,
caller: Option<&'static Location>,
f: F,
) -> LoggedResult<T> {
match self {
Ok(v) => Ok(v),
Err(e) => {
log_with_formatter(level, |w| {
if let Some(caller) = caller {
write!(w, "[{}:{}] ", caller.file(), caller.line())?;
}
f(w)?;
writeln!(w, ": {:#}", e)
});
Err(LoggedError::default())
}
}
}
}
// Automatically convert all printable errors to LoggedError to support `?` operator
impl<T: Display> From<T> for LoggedError {
#[cfg(not(debug_assertions))]
fn from(e: T) -> Self {
log_with_args(LogLevel::Error, format_args_nl!("{:#}", e));
LoggedError::default()
}
#[track_caller]
#[cfg(debug_assertions)]
fn from(e: T) -> Self {
let caller = Location::caller();
log_with_args(
LogLevel::Error,
format_args_nl!("[{}:{}] {:#}", caller.file(), caller.line(), e),
);
LoggedError::default()
}
}

View File

@ -7,30 +7,25 @@
using namespace std;
static int strm_read(void *v, char *buf, int len) {
auto strm = static_cast<channel *>(v);
auto strm = static_cast<stream *>(v);
return strm->read(buf, len);
}
static int strm_write(void *v, const char *buf, int len) {
auto strm = static_cast<channel *>(v);
auto strm = static_cast<stream *>(v);
if (!strm->write(buf, len))
return -1;
return len;
}
static fpos_t strm_seek(void *v, fpos_t off, int whence) {
auto strm = static_cast<channel *>(v);
return strm->seek(off, whence);
}
static int strm_close(void *v) {
auto strm = static_cast<channel *>(v);
auto strm = static_cast<stream *>(v);
delete strm;
return 0;
}
sFILE make_channel_fp(channel_ptr &&strm) {
auto fp = make_file(funopen(strm.release(), strm_read, strm_write, strm_seek, strm_close));
sFILE make_stream_fp(stream_ptr &&strm) {
auto fp = make_file(funopen(strm.release(), strm_read, strm_write, nullptr, strm_close));
setbuf(fp.get(), nullptr);
return fp;
}
@ -50,40 +45,6 @@ ssize_t in_stream::readFully(void *buf, size_t len) {
return read_sz;
}
ssize_t in_stream::readv(const iovec *iov, int iovcnt) {
size_t read_sz = 0;
for (int i = 0; i < iovcnt; ++i) {
auto ret = readFully(iov[i].iov_base, iov[i].iov_len);
if (ret < 0)
return ret;
read_sz += ret;
}
return read_sz;
}
ssize_t out_stream::writev(const iovec *iov, int iovcnt) {
size_t write_sz = 0;
for (int i = 0; i < iovcnt; ++i) {
if (!write(iov[i].iov_base, iov[i].iov_len))
return write_sz;
write_sz += iov[i].iov_len;
}
return write_sz;
}
ssize_t fp_channel::read(void *buf, size_t len) {
auto ret = fread(buf, 1, len, fp.get());
return ret ? ret : (ferror(fp.get()) ? -1 : 0);
}
ssize_t fp_channel::do_write(const void *buf, size_t len) {
return fwrite(buf, 1, len, fp.get());
}
off_t fp_channel::seek(off_t off, int whence) {
return fseek(fp.get(), off, whence);
}
bool filter_out_stream::write(const void *buf, size_t len) {
return base->write(buf, len);
}
@ -131,14 +92,14 @@ void chunk_out_stream::finalize() {
}
}
ssize_t byte_channel::read(void *buf, size_t len) {
ssize_t byte_stream::read(void *buf, size_t len) {
len = std::min((size_t) len, _data._sz- _pos);
memcpy(buf, _data.buf() + _pos, len);
_pos += len;
return len;
}
bool byte_channel::write(const void *buf, size_t len) {
bool byte_stream::write(const void *buf, size_t len) {
resize(_pos + len);
memcpy(_data.buf() + _pos, buf, len);
_pos += len;
@ -146,27 +107,7 @@ bool byte_channel::write(const void *buf, size_t len) {
return true;
}
off_t byte_channel::seek(off_t off, int whence) {
off_t np;
switch (whence) {
case SEEK_CUR:
np = _pos + off;
break;
case SEEK_END:
np = _data._sz+ off;
break;
case SEEK_SET:
np = off;
break;
default:
return -1;
}
resize(np, true);
_pos = np;
return np;
}
void byte_channel::resize(size_t new_sz, bool zero) {
void byte_stream::resize(size_t new_sz, bool zero) {
bool resize = false;
size_t old_cap = _cap;
while (new_sz > _cap) {
@ -180,41 +121,21 @@ void byte_channel::resize(size_t new_sz, bool zero) {
}
}
ssize_t rust_vec_channel::read(void *buf, size_t len) {
ssize_t rust_vec_stream::read(void *buf, size_t len) {
len = std::min<size_t>(len, _data.size() - _pos);
memcpy(buf, _data.data() + _pos, len);
_pos += len;
return len;
}
bool rust_vec_channel::write(const void *buf, size_t len) {
bool rust_vec_stream::write(const void *buf, size_t len) {
ensure_size(_pos + len);
memcpy(_data.data() + _pos, buf, len);
_pos += len;
return true;
}
off_t rust_vec_channel::seek(off_t off, int whence) {
off_t np;
switch (whence) {
case SEEK_CUR:
np = _pos + off;
break;
case SEEK_END:
np = _data.size() + off;
break;
case SEEK_SET:
np = off;
break;
default:
return -1;
}
ensure_size(np, true);
_pos = np;
return np;
}
void rust_vec_channel::ensure_size(size_t sz, bool zero) {
void rust_vec_stream::ensure_size(size_t sz, bool zero) {
size_t old_sz = _data.size();
if (sz > old_sz) {
resize_vec(_data, sz);
@ -223,27 +144,15 @@ void rust_vec_channel::ensure_size(size_t sz, bool zero) {
}
}
ssize_t fd_channel::read(void *buf, size_t len) {
ssize_t fd_stream::read(void *buf, size_t len) {
return ::read(fd, buf, len);
}
ssize_t fd_channel::readv(const iovec *iov, int iovcnt) {
return ::readv(fd, iov, iovcnt);
}
ssize_t fd_channel::do_write(const void *buf, size_t len) {
ssize_t fd_stream::do_write(const void *buf, size_t len) {
return ::write(fd, buf, len);
}
ssize_t fd_channel::writev(const iovec *iov, int iovcnt) {
return ::writev(fd, iov, iovcnt);
}
off_t fd_channel::seek(off_t off, int whence) {
return lseek(fd, off, whence);
}
bool file_channel::write(const void *buf, size_t len) {
bool file_stream::write(const void *buf, size_t len) {
size_t write_sz = 0;
ssize_t ret;
do {
@ -257,3 +166,36 @@ bool file_channel::write(const void *buf, size_t len) {
} while (write_sz != len && ret != 0);
return true;
}
#if ENABLE_IOV
ssize_t in_stream::readv(const iovec *iov, int iovcnt) {
size_t read_sz = 0;
for (int i = 0; i < iovcnt; ++i) {
auto ret = readFully(iov[i].iov_base, iov[i].iov_len);
if (ret < 0)
return ret;
read_sz += ret;
}
return read_sz;
}
ssize_t out_stream::writev(const iovec *iov, int iovcnt) {
size_t write_sz = 0;
for (int i = 0; i < iovcnt; ++i) {
if (!write(iov[i].iov_base, iov[i].iov_len))
return write_sz;
write_sz += iov[i].iov_len;
}
return write_sz;
}
ssize_t fd_stream::readv(const iovec *iov, int iovcnt) {
return ::readv(fd, iov, iovcnt);
}
ssize_t fd_stream::writev(const iovec *iov, int iovcnt) {
return ::writev(fd, iov, iovcnt);
}
#endif // ENABLE_IOV

View File

@ -10,7 +10,7 @@ use libc::{
};
use crate::cxx_extern::readlinkat_for_cxx;
use crate::{cstr, errno, raw_cstr, CxxResultExt, FsPath, Utf8CStr, Utf8CStrBufRef};
use crate::{errno, raw_cstr, CxxResultExt, FsPath, Utf8CStr, Utf8CStrBufRef};
fn ptr_to_str<'a, T>(ptr: *const T) -> &'a str {
if ptr.is_null() {

View File

@ -23,9 +23,10 @@ sha2 = { workspace = true }
digest = { workspace = true }
p256 = { workspace = true }
p384 = { workspace = true }
p521 = { workspace = true }
rsa = { workspace = true, features = ["sha2"] }
x509-cert = { workspace = true }
der = { workspace = true, features = ["derive"] }
der = { workspace = true, features = ["derive", "pem"] }
fdt = { workspace = true }
bytemuck = { workspace = true, features = ["derive", "min_const_generics"] }
num-traits = { workspace = true }

View File

@ -16,14 +16,14 @@ using namespace std;
#define SHA_DIGEST_SIZE 20
static void decompress(format_t type, int fd, const void *in, size_t size) {
auto ptr = get_decoder(type, make_unique<fd_channel>(fd));
auto ptr = get_decoder(type, make_unique<fd_stream>(fd));
ptr->write(in, size);
}
static off_t compress(format_t type, int fd, const void *in, size_t size) {
auto prev = lseek(fd, 0, SEEK_CUR);
{
auto strm = get_encoder(type, make_unique<fd_channel>(fd));
auto strm = get_encoder(type, make_unique<fd_stream>(fd));
strm->write(in, size);
}
auto now = lseek(fd, 0, SEEK_CUR);
@ -520,12 +520,12 @@ bool boot_img::verify(const char *cert) const {
return rust::verify_boot_image(*this, cert);
}
int split_image_dtb(const char *filename) {
int split_image_dtb(const char *filename, bool skip_decomp) {
mmap_data img(filename);
if (int off = find_dtb_offset(img.buf(), img.sz()); off > 0) {
format_t fmt = check_fmt_lg(img.buf(), img.sz());
if (COMPRESSED(fmt)) {
if (!skip_decomp && COMPRESSED(fmt)) {
int fd = creat(KERNEL_FILE, 0644);
decompress(fmt, fd, img.buf(), off);
close(fd);

View File

@ -627,12 +627,13 @@ void decompress(char *infile, const char *outfile) {
bool in_std = infile == "-"sv;
bool rm_in = false;
FILE *in_fp = in_std ? stdin : xfopen(infile, "re");
int in_fd = in_std ? STDIN_FILENO : xopen(infile, O_RDONLY);
int out_fd = -1;
out_strm_ptr strm;
char buf[4096];
size_t len;
while ((len = fread(buf, 1, sizeof(buf), in_fp))) {
while ((len = read(in_fd, buf, sizeof(buf)))) {
if (!strm) {
format_t type = check_fmt(buf, len);
@ -660,8 +661,10 @@ void decompress(char *infile, const char *outfile) {
}
}
FILE *out_fp = outfile == "-"sv ? stdout : xfopen(outfile, "we");
strm = get_decoder(type, make_unique<fp_channel>(out_fp));
out_fd = outfile == "-"sv ?
STDOUT_FILENO :
xopen(outfile, O_WRONLY | O_CREAT | O_TRUNC, 0644);
strm = get_decoder(type, make_unique<fd_stream>(out_fd));
if (ext) *ext = '.';
}
if (!strm->write(buf, len))
@ -669,7 +672,8 @@ void decompress(char *infile, const char *outfile) {
}
strm.reset(nullptr);
fclose(in_fp);
if (in_fd != STDIN_FILENO) close(in_fd);
if (out_fd != STDOUT_FILENO) close(out_fd);
if (rm_in)
unlink(infile);
@ -683,36 +687,39 @@ void compress(const char *method, const char *infile, const char *outfile) {
bool in_std = infile == "-"sv;
bool rm_in = false;
FILE *in_fp = in_std ? stdin : xfopen(infile, "re");
FILE *out_fp;
int in_fd = in_std ? STDIN_FILENO : xopen(infile, O_RDONLY);
int out_fd = -1;
if (outfile == nullptr) {
if (in_std) {
out_fp = stdout;
out_fd = STDOUT_FILENO;
} else {
/* If user does not provide outfile and infile is not
* STDIN, output to <infile>.[ext] */
string tmp(infile);
tmp += fmt2ext[fmt];
out_fp = xfopen(tmp.data(), "we");
out_fd = xopen(tmp.data(), O_WRONLY | O_CREAT | O_TRUNC, 0644);
fprintf(stderr, "Compressing to [%s]\n", tmp.data());
rm_in = true;
}
} else {
out_fp = outfile == "-"sv ? stdout : xfopen(outfile, "we");
out_fd = outfile == "-"sv ?
STDOUT_FILENO :
xopen(outfile, O_WRONLY | O_CREAT | O_TRUNC, 0644);
}
auto strm = get_encoder(fmt, make_unique<fp_channel>(out_fp));
auto strm = get_encoder(fmt, make_unique<fd_stream>(out_fd));
char buf[4096];
size_t len;
while ((len = fread(buf, 1, sizeof(buf), in_fp))) {
while ((len = read(in_fd, buf, sizeof(buf)))) {
if (!strm->write(buf, len))
LOGE("Compression error!\n");
}
strm.reset(nullptr);
fclose(in_fp);
if (in_fd != STDIN_FILENO) close(in_fd);
if (out_fd != STDOUT_FILENO) close(out_fd);
if (rm_in)
unlink(infile);
@ -726,7 +733,7 @@ bool decompress(rust::Slice<const uint8_t> buf, int fd) {
return false;
}
auto strm = get_decoder(type, make_unique<fd_channel>(fd));
auto strm = get_decoder(type, make_unique<fd_stream>(fd));
if (!strm->write(buf.data(), buf.length())) {
return false;
}
@ -734,7 +741,7 @@ bool decompress(rust::Slice<const uint8_t> buf, int fd) {
}
bool xz(rust::Slice<const uint8_t> buf, rust::Vec<uint8_t> &out) {
auto strm = get_encoder(XZ, make_unique<rust_vec_channel>(out));
auto strm = get_encoder(XZ, make_unique<rust_vec_stream>(out));
if (!strm->write(buf.data(), buf.length())) {
return false;
}
@ -747,7 +754,7 @@ bool unxz(rust::Slice<const uint8_t> buf, rust::Vec<uint8_t> &out) {
LOGE("Input file is not in xz format!\n");
return false;
}
auto strm = get_decoder(XZ, make_unique<rust_vec_channel>(out));
auto strm = get_decoder(XZ, make_unique<rust_vec_stream>(out));
if (!strm->write(buf.data(), buf.length())) {
return false;
}

View File

@ -1,12 +1,11 @@
#![allow(clippy::useless_conversion)]
use std::collections::BTreeMap;
use std::cmp::Ordering;
use std::collections::{BTreeMap, HashMap};
use std::fmt::{Display, Formatter};
use std::fs::{metadata, read, DirBuilder, File};
use std::io::Write;
use std::fs::File;
use std::io::{Read, Write};
use std::mem::size_of;
use std::os::unix::fs::{symlink, DirBuilderExt, FileTypeExt, MetadataExt};
use std::path::Path;
use std::process::exit;
use std::str;
@ -15,17 +14,19 @@ use bytemuck::{from_bytes, Pod, Zeroable};
use num_traits::cast::AsPrimitive;
use size::{Base, Size, Style};
use crate::ffi::{unxz, xz};
use base::libc::{
c_char, dev_t, gid_t, major, makedev, minor, mknod, mode_t, uid_t, S_IFBLK, S_IFCHR, S_IFDIR,
S_IFLNK, S_IFMT, S_IFREG, S_IRGRP, S_IROTH, S_IRUSR, S_IWGRP, S_IWOTH, S_IWUSR, S_IXGRP,
S_IXOTH, S_IXUSR,
c_char, dev_t, gid_t, major, makedev, minor, mknod, mode_t, uid_t, O_CLOEXEC, O_CREAT,
O_RDONLY, O_TRUNC, O_WRONLY, S_IFBLK, S_IFCHR, S_IFDIR, S_IFLNK, S_IFMT, S_IFREG, S_IRGRP,
S_IROTH, S_IRUSR, S_IWGRP, S_IWOTH, S_IWUSR, S_IXGRP, S_IXOTH, S_IXUSR,
};
use base::{
log_err, map_args, EarlyExitExt, LoggedResult, MappedFile, ResultExt, Utf8CStr, WriteExt,
log_err, map_args, BytesExt, EarlyExitExt, FsPath, LoggedResult, MappedFile, ResultExt,
Utf8CStr, Utf8CStrBufArr, Utf8CStrWrite, WriteExt,
};
use crate::ramdisk::MagiskCpio;
use crate::check_env;
use crate::ffi::{unxz, xz};
use crate::patch::{patch_encryption, patch_verity};
#[derive(FromArgs)]
struct CpioCli {
@ -38,12 +39,12 @@ struct CpioCli {
#[derive(FromArgs)]
struct CpioCommand {
#[argh(subcommand)]
command: CpioSubCommand,
action: CpioAction,
}
#[derive(FromArgs)]
#[argh(subcommand)]
enum CpioSubCommand {
enum CpioAction {
Test(Test),
Restore(Restore),
Patch(Patch),
@ -176,7 +177,7 @@ Supported commands:
test
Test the cpio's status
Return value is 0 or bitwise or-ed of following values:
0x1:Magisk 0x2:unsupported 0x4:Sony
0x1:Magisk 0x2:unsupported
patch
Apply ramdisk patches
Configure with env variables: KEEPVERITY KEEPFORCEENCRYPT
@ -207,17 +208,17 @@ struct CpioHeader {
check: [u8; 8],
}
pub(crate) struct Cpio {
pub(crate) entries: BTreeMap<String, Box<CpioEntry>>,
struct Cpio {
entries: BTreeMap<String, Box<CpioEntry>>,
}
pub(crate) struct CpioEntry {
pub(crate) mode: mode_t,
pub(crate) uid: uid_t,
pub(crate) gid: gid_t,
pub(crate) rdevmajor: dev_t,
pub(crate) rdevminor: dev_t,
pub(crate) data: Vec<u8>,
struct CpioEntry {
mode: mode_t,
uid: uid_t,
gid: gid_t,
rdevmajor: dev_t,
rdevminor: dev_t,
data: Vec<u8>,
}
impl Cpio {
@ -245,7 +246,7 @@ impl Cpio {
continue;
}
if name == "TRAILER!!!" {
match data[pos..].windows(6).position(|x| x == b"070701") {
match data[pos..].find(b"070701") {
Some(x) => pos += x,
None => break,
}
@ -267,7 +268,7 @@ impl Cpio {
Ok(cpio)
}
pub(crate) fn load_from_file(path: &Utf8CStr) -> LoggedResult<Self> {
fn load_from_file(path: &Utf8CStr) -> LoggedResult<Self> {
eprintln!("Loading cpio: [{}]", path);
let file = MappedFile::open(path)?;
Self::load_from_data(file.as_ref())
@ -316,7 +317,7 @@ impl Cpio {
Ok(())
}
pub(crate) fn rm(&mut self, path: &str, recursive: bool) {
fn rm(&mut self, path: &str, recursive: bool) {
let path = norm_path(path);
if self.entries.remove(&path).is_some() {
eprintln!("Removed entry [{}]", path);
@ -334,41 +335,39 @@ impl Cpio {
}
}
fn extract_entry(&self, path: &str, out: &Path) -> LoggedResult<()> {
fn extract_entry(&self, path: &str, out: &mut String) -> LoggedResult<()> {
let entry = self
.entries
.get(path)
.ok_or_else(|| log_err!("No such file"))?;
eprintln!("Extracting entry [{}] to [{}]", path, out.to_string_lossy());
if let Some(parent) = out.parent() {
DirBuilder::new()
.mode(0o755)
.recursive(true)
.create(parent)?;
eprintln!("Extracting entry [{}] to [{}]", path, out);
let out = Utf8CStr::from_string(out);
let out = FsPath::from(out);
let mut buf = Utf8CStrBufArr::default();
// Make sure its parent directories exist
if out.parent(&mut buf) {
FsPath::from(&buf).mkdirs(0o755)?;
}
let mode: mode_t = (entry.mode & 0o777).into();
match entry.mode & S_IFMT {
S_IFDIR => {
DirBuilder::new()
.mode((entry.mode & 0o777).into())
.recursive(true) // avoid error if existing
.create(out)?;
}
S_IFDIR => out.mkdir(mode)?,
S_IFREG => {
let mut file = File::create(out)?;
let mut file = out.create(O_CREAT | O_TRUNC | O_WRONLY | O_CLOEXEC, mode)?;
file.write_all(&entry.data)?;
}
S_IFLNK => {
symlink(Path::new(&str::from_utf8(entry.data.as_slice())?), out)?;
buf.clear();
buf.push_str(str::from_utf8(entry.data.as_slice())?);
FsPath::from(&buf).symlink_to(out)?;
}
S_IFBLK | S_IFCHR => {
let dev = makedev(entry.rdevmajor.try_into()?, entry.rdevminor.try_into()?);
unsafe {
mknod(
out.to_str().unwrap().as_ptr() as *const c_char,
entry.mode,
dev,
)
};
unsafe { mknod(out.as_ptr().cast(), entry.mode, dev) };
}
_ => {
return Err(log_err!("unknown entry type"));
@ -377,48 +376,55 @@ impl Cpio {
Ok(())
}
fn extract(&self, path: Option<&str>, out: Option<&str>) -> LoggedResult<()> {
let path = path.map(norm_path);
let out = out.map(Path::new);
if let (Some(path), Some(out)) = (&path, &out) {
fn extract(&self, path: Option<&mut String>, out: Option<&mut String>) -> LoggedResult<()> {
let path = path.map(|s| norm_path(s.as_str()));
if let (Some(path), Some(out)) = (&path, out) {
return self.extract_entry(path, out);
} else {
for path in self.entries.keys() {
if path == "." || path == ".." {
continue;
}
self.extract_entry(path, Path::new(path))?;
self.extract_entry(path, &mut path.clone())?;
}
}
Ok(())
}
pub(crate) fn exists(&self, path: &str) -> bool {
fn exists(&self, path: &str) -> bool {
self.entries.contains_key(&norm_path(path))
}
fn add(&mut self, mode: &mode_t, path: &str, file: &str) -> LoggedResult<()> {
fn add(&mut self, mode: mode_t, path: &str, file: &mut String) -> LoggedResult<()> {
if path.ends_with('/') {
return Err(log_err!("path cannot end with / for add"));
}
let file = Path::new(file);
let content = read(file)?;
let metadata = metadata(file)?;
let mut rdevmajor: dev_t = 0;
let mut rdevminor: dev_t = 0;
let mode = if metadata.file_type().is_file() {
let file = Utf8CStr::from_string(file);
let file = FsPath::from(&file);
let attr = file.get_attr()?;
let mut content = Vec::<u8>::new();
let rdevmajor: dev_t;
let rdevminor: dev_t;
// Treat symlinks as regular files as symlinks are created by the 'ln TARGET ENTRY' command
let mode = if attr.is_file() || attr.is_symlink() {
rdevmajor = 0;
rdevminor = 0;
file.open(O_RDONLY | O_CLOEXEC)?.read_to_end(&mut content)?;
mode | S_IFREG
} else {
rdevmajor = unsafe { major(metadata.rdev().try_into()?).try_into()? };
rdevminor = unsafe { minor(metadata.rdev().try_into()?).try_into()? };
if metadata.file_type().is_block_device() {
rdevmajor = unsafe { major(attr.st.st_rdev.as_()) }.as_();
rdevminor = unsafe { minor(attr.st.st_rdev.as_()) }.as_();
if attr.is_block_device() {
mode | S_IFBLK
} else if metadata.file_type().is_char_device() {
} else if attr.is_char_device() {
mode | S_IFCHR
} else {
return Err(log_err!("unsupported file type"));
}
};
self.entries.insert(
norm_path(path),
Box::new(CpioEntry {
@ -434,11 +440,11 @@ impl Cpio {
Ok(())
}
fn mkdir(&mut self, mode: &mode_t, dir: &str) {
fn mkdir(&mut self, mode: mode_t, dir: &str) {
self.entries.insert(
norm_path(dir),
Box::new(CpioEntry {
mode: *mode | S_IFDIR,
mode: mode | S_IFDIR,
uid: 0,
gid: 0,
rdevmajor: 0,
@ -498,6 +504,196 @@ impl Cpio {
}
}
const MAGISK_PATCHED: i32 = 1 << 0;
const UNSUPPORTED_CPIO: i32 = 1 << 1;
impl Cpio {
fn patch(&mut self) {
let keep_verity = check_env("KEEPVERITY");
let keep_force_encrypt = check_env("KEEPFORCEENCRYPT");
eprintln!(
"Patch with flag KEEPVERITY=[{}] KEEPFORCEENCRYPT=[{}]",
keep_verity, keep_force_encrypt
);
self.entries.retain(|name, entry| {
let fstab = (!keep_verity || !keep_force_encrypt)
&& entry.mode & S_IFMT == S_IFREG
&& !name.starts_with(".backup")
&& !name.starts_with("twrp")
&& !name.starts_with("recovery")
&& name.starts_with("fstab");
if !keep_verity {
if fstab {
eprintln!("Found fstab file [{}]", name);
let len = patch_verity(entry.data.as_mut_slice());
if len != entry.data.len() {
entry.data.resize(len, 0);
}
} else if name == "verity_key" {
return false;
}
}
if !keep_force_encrypt && fstab {
let len = patch_encryption(entry.data.as_mut_slice());
if len != entry.data.len() {
entry.data.resize(len, 0);
}
}
true
});
}
fn test(&self) -> i32 {
let mut ret = 0;
for file in [
"sbin/launch_daemonsu.sh",
"sbin/su",
"init.xposed.rc",
"boot/sbin/launch_daemonsu.sh",
] {
if self.exists(file) {
return UNSUPPORTED_CPIO;
}
}
for file in [
".backup/.magisk",
"init.magisk.rc",
"overlay/init.magisk.rc",
] {
if self.exists(file) {
ret |= MAGISK_PATCHED;
break;
}
}
ret
}
fn restore(&mut self) -> LoggedResult<()> {
let mut backups = HashMap::<String, Box<CpioEntry>>::new();
let mut rm_list = String::new();
self.entries
.extract_if(|name, _| name.starts_with(".backup/"))
.for_each(|(name, mut entry)| {
if name == ".backup/.rmlist" {
if let Ok(data) = str::from_utf8(&entry.data) {
rm_list.push_str(data);
}
} else if name != ".backup/.magisk" {
let new_name = if name.ends_with(".xz") && entry.decompress() {
&name[8..name.len() - 3]
} else {
&name[8..]
};
eprintln!("Restore [{}] -> [{}]", name, new_name);
backups.insert(new_name.to_string(), entry);
}
});
self.rm(".backup", false);
if rm_list.is_empty() && backups.is_empty() {
self.entries.clear();
return Ok(());
}
for rm in rm_list.split('\0') {
if !rm.is_empty() {
self.rm(rm, false);
}
}
self.entries.extend(backups);
Ok(())
}
fn backup(&mut self, origin: &mut String, skip_compress: bool) -> LoggedResult<()> {
let mut backups = HashMap::<String, Box<CpioEntry>>::new();
let mut rm_list = String::new();
backups.insert(
".backup".to_string(),
Box::new(CpioEntry {
mode: S_IFDIR,
uid: 0,
gid: 0,
rdevmajor: 0,
rdevminor: 0,
data: vec![],
}),
);
let origin = Utf8CStr::from_string(origin);
let mut o = Cpio::load_from_file(origin)?;
o.rm(".backup", true);
self.rm(".backup", true);
let mut lhs = o.entries.into_iter().peekable();
let mut rhs = self.entries.iter().peekable();
loop {
enum Action<'a> {
Backup(String, Box<CpioEntry>),
Record(&'a String),
Noop,
}
let action = match (lhs.peek(), rhs.peek()) {
(Some((l, _)), Some((r, re))) => match l.as_str().cmp(r.as_str()) {
Ordering::Less => {
let (l, le) = lhs.next().unwrap();
Action::Backup(l, le)
}
Ordering::Greater => Action::Record(rhs.next().unwrap().0),
Ordering::Equal => {
let (l, le) = lhs.next().unwrap();
let action = if re.data != le.data {
Action::Backup(l, le)
} else {
Action::Noop
};
rhs.next();
action
}
},
(Some(_), None) => {
let (l, le) = lhs.next().unwrap();
Action::Backup(l, le)
}
(None, Some(_)) => Action::Record(rhs.next().unwrap().0),
(None, None) => {
break;
}
};
match action {
Action::Backup(name, mut entry) => {
let backup = if !skip_compress && entry.compress() {
format!(".backup/{}.xz", name)
} else {
format!(".backup/{}", name)
};
eprintln!("Backup [{}] -> [{}]", name, backup);
backups.insert(backup, entry);
}
Action::Record(name) => {
eprintln!("Record new entry: [{}] -> [.backup/.rmlist]", name);
rm_list.push_str(&format!("{}\0", name));
}
Action::Noop => {}
}
}
if !rm_list.is_empty() {
backups.insert(
".backup/.rmlist".to_string(),
Box::new(CpioEntry {
mode: S_IFREG,
uid: 0,
gid: 0,
rdevmajor: 0,
rdevminor: 0,
data: rm_list.as_bytes().to_vec(),
}),
);
}
self.entries.extend(backups);
Ok(())
}
}
impl CpioEntry {
pub(crate) fn compress(&mut self) -> bool {
if self.mode & S_IFMT != S_IFREG {
@ -573,7 +769,7 @@ pub fn cpio_commands(argc: i32, argv: *const *const c_char) -> bool {
CpioCli::from_args(&["magiskboot", "cpio"], &cmds).on_early_exit(print_cpio_usage);
let file = Utf8CStr::from_string(&mut cli.file);
let mut cpio = if Path::new(file).exists() {
let mut cpio = if FsPath::from(file).exists() {
Cpio::load_from_file(file)?
} else {
Cpio::new()
@ -592,36 +788,34 @@ pub fn cpio_commands(argc: i32, argv: *const *const c_char) -> bool {
)
.on_early_exit(print_cpio_usage);
match &mut cli.command {
CpioSubCommand::Test(_) => exit(cpio.test()),
CpioSubCommand::Restore(_) => cpio.restore()?,
CpioSubCommand::Patch(_) => cpio.patch(),
CpioSubCommand::Exists(Exists { path }) => {
match &mut cli.action {
CpioAction::Test(_) => exit(cpio.test()),
CpioAction::Restore(_) => cpio.restore()?,
CpioAction::Patch(_) => cpio.patch(),
CpioAction::Exists(Exists { path }) => {
if cpio.exists(path) {
exit(0);
} else {
exit(1);
}
}
CpioSubCommand::Backup(Backup {
CpioAction::Backup(Backup {
origin,
skip_compress,
}) => cpio.backup(Utf8CStr::from_string(origin), *skip_compress)?,
CpioSubCommand::Remove(Remove { path, recursive }) => cpio.rm(path, *recursive),
CpioSubCommand::Move(Move { from, to }) => cpio.mv(from, to)?,
CpioSubCommand::MakeDir(MakeDir { mode, dir }) => cpio.mkdir(mode, dir),
CpioSubCommand::Link(Link { src, dst }) => cpio.ln(src, dst),
CpioSubCommand::Add(Add { mode, path, file }) => cpio.add(mode, path, file)?,
CpioSubCommand::Extract(Extract { paths }) => {
}) => cpio.backup(origin, *skip_compress)?,
CpioAction::Remove(Remove { path, recursive }) => cpio.rm(path, *recursive),
CpioAction::Move(Move { from, to }) => cpio.mv(from, to)?,
CpioAction::MakeDir(MakeDir { mode, dir }) => cpio.mkdir(*mode, dir),
CpioAction::Link(Link { src, dst }) => cpio.ln(src, dst),
CpioAction::Add(Add { mode, path, file }) => cpio.add(*mode, path, file)?,
CpioAction::Extract(Extract { paths }) => {
if !paths.is_empty() && paths.len() != 2 {
return Err(log_err!("invalid arguments"));
}
cpio.extract(
paths.get(0).map(|x| x.as_str()),
paths.get(1).map(|x| x.as_str()),
)?;
let mut it = paths.iter_mut();
cpio.extract(it.next(), it.next())?;
}
CpioSubCommand::List(List { path, recursive }) => {
CpioAction::List(List { path, recursive }) => {
cpio.ls(path.as_str(), *recursive);
exit(0);
}

View File

@ -17,7 +17,6 @@ mod payload;
// Suppress warnings in generated code
#[allow(warnings)]
mod proto;
mod ramdisk;
mod sign;
#[cxx::bridge]

View File

@ -18,7 +18,7 @@ int unpack(const char *image, bool skip_decomp = false, bool hdr = false);
void repack(const char *src_img, const char *out_img, bool skip_comp = false);
int verify(const char *image, const char *cert);
int sign(const char *image, const char *name, const char *cert, const char *key);
int split_image_dtb(const char *filename);
int split_image_dtb(const char *filename, bool skip_decomp = false);
int dtb_commands(int argc, char *argv[]);
static inline bool check_env(const char *name) {

View File

@ -6,6 +6,11 @@
using namespace std;
#ifdef USE_CRT0
__asm__(".global vfprintf \n vfprintf = musl_vfprintf");
__asm__(".global vsscanf \n vsscanf = tfp_vsscanf");
#endif
static void print_formats() {
for (int fmt = GZIP; fmt < LZOP; ++fmt) {
fprintf(stderr, "%s ", fmt2name[(format_t) fmt]);
@ -79,8 +84,10 @@ Supported actions:
Do dtb related actions to <file>.
See "dtb --help" for supported actions.
split <file>
Split image.*-dtb into kernel + kernel_dtb
split [-n] <file>
Split image.*-dtb into kernel + kernel_dtb.
If '-n' is provided, decompression operations will be skipped;
the kernel will remain untouched, split in its original format.
sha1 <file>
Print the SHA1 checksum for <file>
@ -145,7 +152,13 @@ int main(int argc, char *argv[]) {
printf("%02x", i);
printf("\n");
} else if (argc > 2 && action == "split") {
return split_image_dtb(argv[2]);
if (argv[2] == "-n"sv) {
if (argc == 3)
usage(argv[0]);
return split_image_dtb(argv[3], true);
} else {
return split_image_dtb(argv[2]);
}
} else if (argc > 2 && action == "unpack") {
int idx = 2;
bool nodecomp = false;

View File

@ -140,7 +140,7 @@ fn do_extract_boot_from_payload(
let out_offset = operation
.dst_extents
.get(0)
.first()
.ok_or_else(|| bad_payload!("dst extents not found"))?
.start_block
.ok_or_else(|| bad_payload!("start block not found"))?

View File

@ -1,210 +0,0 @@
use std::cmp::Ordering;
use std::collections::HashMap;
use std::str::from_utf8;
use base::libc::{S_IFDIR, S_IFMT, S_IFREG};
use base::{LoggedResult, Utf8CStr};
use crate::check_env;
use crate::cpio::{Cpio, CpioEntry};
use crate::patch::{patch_encryption, patch_verity};
pub trait MagiskCpio {
fn patch(&mut self);
fn test(&self) -> i32;
fn restore(&mut self) -> LoggedResult<()>;
fn backup(&mut self, origin: &Utf8CStr, skip_compress: bool) -> LoggedResult<()>;
}
const MAGISK_PATCHED: i32 = 1 << 0;
const UNSUPPORTED_CPIO: i32 = 1 << 1;
const SONY_INIT: i32 = 1 << 2;
impl MagiskCpio for Cpio {
fn patch(&mut self) {
let keep_verity = check_env("KEEPVERITY");
let keep_force_encrypt = check_env("KEEPFORCEENCRYPT");
eprintln!(
"Patch with flag KEEPVERITY=[{}] KEEPFORCEENCRYPT=[{}]",
keep_verity, keep_force_encrypt
);
self.entries.retain(|name, entry| {
let fstab = (!keep_verity || !keep_force_encrypt)
&& entry.mode & S_IFMT == S_IFREG
&& !name.starts_with(".backup")
&& !name.starts_with("twrp")
&& !name.starts_with("recovery")
&& name.starts_with("fstab");
if !keep_verity {
if fstab {
eprintln!("Found fstab file [{}]", name);
let len = patch_verity(entry.data.as_mut_slice());
if len != entry.data.len() {
entry.data.resize(len, 0);
}
} else if name == "verity_key" {
return false;
}
}
if !keep_force_encrypt && fstab {
let len = patch_encryption(entry.data.as_mut_slice());
if len != entry.data.len() {
entry.data.resize(len, 0);
}
}
true
});
}
fn test(&self) -> i32 {
let mut ret = 0;
for file in [
"sbin/launch_daemonsu.sh",
"sbin/su",
"init.xposed.rc",
"boot/sbin/launch_daemonsu.sh",
] {
if self.exists(file) {
return UNSUPPORTED_CPIO;
}
}
for file in [
".backup/.magisk",
"init.magisk.rc",
"overlay/init.magisk.rc",
] {
if self.exists(file) {
ret |= MAGISK_PATCHED;
break;
}
}
if self.exists("init.real") {
ret |= SONY_INIT;
}
ret
}
fn restore(&mut self) -> LoggedResult<()> {
let mut backups = HashMap::<String, Box<CpioEntry>>::new();
let mut rm_list = String::new();
self.entries
.extract_if(|name, _| name.starts_with(".backup/"))
.for_each(|(name, mut entry)| {
if name == ".backup/.rmlist" {
if let Ok(data) = from_utf8(&entry.data) {
rm_list.push_str(data);
}
} else if name != ".backup/.magisk" {
let new_name = if name.ends_with(".xz") && entry.decompress() {
&name[8..name.len() - 3]
} else {
&name[8..]
};
eprintln!("Restore [{}] -> [{}]", name, new_name);
backups.insert(new_name.to_string(), entry);
}
});
self.rm(".backup", false);
if rm_list.is_empty() && backups.is_empty() {
self.entries.clear();
return Ok(());
}
for rm in rm_list.split('\0') {
if !rm.is_empty() {
self.rm(rm, false);
}
}
self.entries.extend(backups);
Ok(())
}
fn backup(&mut self, origin: &Utf8CStr, skip_compress: bool) -> LoggedResult<()> {
let mut backups = HashMap::<String, Box<CpioEntry>>::new();
let mut rm_list = String::new();
backups.insert(
".backup".to_string(),
Box::new(CpioEntry {
mode: S_IFDIR,
uid: 0,
gid: 0,
rdevmajor: 0,
rdevminor: 0,
data: vec![],
}),
);
let mut o = Cpio::load_from_file(origin)?;
o.rm(".backup", true);
self.rm(".backup", true);
let mut lhs = o.entries.into_iter().peekable();
let mut rhs = self.entries.iter().peekable();
loop {
enum Action<'a> {
Backup(String, Box<CpioEntry>),
Record(&'a String),
Noop,
}
let action = match (lhs.peek(), rhs.peek()) {
(Some((l, _)), Some((r, re))) => match l.as_str().cmp(r.as_str()) {
Ordering::Less => {
let (l, le) = lhs.next().unwrap();
Action::Backup(l, le)
}
Ordering::Greater => Action::Record(rhs.next().unwrap().0),
Ordering::Equal => {
let (l, le) = lhs.next().unwrap();
let action = if re.data != le.data {
Action::Backup(l, le)
} else {
Action::Noop
};
rhs.next();
action
}
},
(Some(_), None) => {
let (l, le) = lhs.next().unwrap();
Action::Backup(l, le)
}
(None, Some(_)) => Action::Record(rhs.next().unwrap().0),
(None, None) => {
break;
}
};
match action {
Action::Backup(name, mut entry) => {
let backup = if !skip_compress && entry.compress() {
format!(".backup/{}.xz", name)
} else {
format!(".backup/{}", name)
};
eprintln!("Backup [{}] -> [{}]", name, backup);
backups.insert(backup, entry);
}
Action::Record(name) => {
eprintln!("Record new entry: [{}] -> [.backup/.rmlist]", name);
rm_list.push_str(&format!("{}\0", name));
}
Action::Noop => {}
}
}
if !rm_list.is_empty() {
backups.insert(
".backup/.rmlist".to_string(),
Box::new(CpioEntry {
mode: S_IFREG,
uid: 0,
gid: 0,
rdevmajor: 0,
rdevminor: 0,
data: rm_list.as_bytes().to_vec(),
}),
);
}
self.entries.extend(backups);
Ok(())
}
}

View File

@ -8,6 +8,9 @@ use p256::pkcs8::DecodePrivateKey;
use p384::ecdsa::{
Signature as P384Signature, SigningKey as P384SigningKey, VerifyingKey as P384VerifyingKey,
};
use p521::ecdsa::{
Signature as P521Signature, SigningKey as P521SigningKey, VerifyingKey as P521VerifyingKey,
};
use rsa::pkcs1v15::{
Signature as RsaSignature, SigningKey as RsaSigningKey, VerifyingKey as RsaVerifyingKey,
};
@ -16,7 +19,7 @@ use rsa::signature::hazmat::{PrehashSigner, PrehashVerifier};
use rsa::signature::SignatureEncoding;
use rsa::{RsaPrivateKey, RsaPublicKey};
use sha1::Sha1;
use sha2::{Sha256, Sha384};
use sha2::{Sha256, Sha384, Sha512};
use x509_cert::der::asn1::{OctetString, PrintableString};
use x509_cert::der::Any;
use x509_cert::spki::AlgorithmIdentifier;
@ -82,6 +85,7 @@ enum SigningKey {
SHA256withRSA(RsaSigningKey<Sha256>),
SHA256withECDSA(P256SigningKey),
SHA384withECDSA(P384SigningKey),
SHA521withECDSA(P521SigningKey),
}
#[allow(clippy::large_enum_variant)]
@ -89,6 +93,7 @@ enum VerifyingKey {
SHA256withRSA(RsaVerifyingKey<Sha256>),
SHA256withECDSA(P256VerifyingKey),
SHA384withECDSA(P384VerifyingKey),
SHA521withECDSA(P521VerifyingKey),
}
struct Verifier {
@ -108,6 +113,9 @@ impl Verifier {
} else if let Ok(ec) = P384VerifyingKey::try_from(key.clone()) {
digest = Box::<Sha384>::default();
VerifyingKey::SHA384withECDSA(ec)
} else if let Ok(ec) = P521VerifyingKey::try_from(key.clone()) {
digest = Box::<Sha512>::default();
VerifyingKey::SHA521withECDSA(ec)
} else {
return Err(log_err!("Unsupported private key"));
};
@ -133,6 +141,10 @@ impl Verifier {
let sig = P384Signature::from_slice(signature)?;
key.verify_prehash(hash.as_ref(), &sig).log()
}
VerifyingKey::SHA521withECDSA(key) => {
let sig = P521Signature::from_slice(signature)?;
key.verify_prehash(hash.as_ref(), &sig).log()
}
};
}
}
@ -154,6 +166,9 @@ impl Signer {
} else if let Ok(ec) = P384SigningKey::from_pkcs8_der(key) {
digest = Box::<Sha384>::default();
SigningKey::SHA384withECDSA(ec)
} else if let Ok(ec) = P521SigningKey::from_pkcs8_der(key) {
digest = Box::<Sha512>::default();
SigningKey::SHA521withECDSA(ec)
} else {
return Err(log_err!("Unsupported private key"));
};
@ -179,6 +194,10 @@ impl Signer {
let sig: P384Signature = key.sign_prehash(hash.as_ref())?;
sig.to_vec()
}
SigningKey::SHA521withECDSA(key) => {
let sig: P521Signature = key.sign_prehash(hash.as_ref())?;
sig.to_vec()
}
};
Ok(v)
}

View File

@ -20,198 +20,15 @@ bool zygisk_enabled = false;
* Setup *
*********/
static bool rec_mount(const std::string_view from, const std::string_view to) {
return !xmkdirs(to.data(), 0755) &&
// recursively bind mount to mirror dir, rootfs will fail before 3.12 kernel
// because of MS_NOUSER
!mount(from.data(), to.data(), nullptr, MS_BIND | MS_REC, nullptr);
}
static void mount_mirrors() {
LOGI("* Mounting mirrors\n");
auto self_mount_info = parse_mount_info("self");
char path[64];
// Bind remount module root to clear nosuid
if (access(SECURE_DIR, F_OK) == 0 || SDK_INT < 24) {
ssprintf(path, sizeof(path), "%s/" MODULEMNT, get_magisk_tmp());
xmkdir(SECURE_DIR, 0700);
xmkdir(MODULEROOT, 0755);
xmkdir(path, 0755);
xmount(MODULEROOT, path, nullptr, MS_BIND, nullptr);
xmount(nullptr, path, nullptr, MS_REMOUNT | MS_BIND | MS_RDONLY, nullptr);
xmount(nullptr, path, nullptr, MS_PRIVATE, nullptr);
chmod(SECURE_DIR, 0700);
}
// Check and mount preinit mirror
char dev_path[64];
ssprintf(dev_path, sizeof(dev_path), "%s/" PREINITDEV, get_magisk_tmp());
if (struct stat st{}; stat(dev_path, &st) == 0 && S_ISBLK(st.st_mode)) {
// DO NOT mount the block device directly, as we do not know the flags and configs
// to properly mount the partition; mounting block devices directly as rw could cause
// crashes if the filesystem driver is crap (e.g. some broken F2FS drivers).
// What we do instead is to scan through the current mountinfo and find a pre-existing
// mount point mounting our desired partition, and then bind mount the target folder.
dev_t preinit_dev = st.st_rdev;
bool mounted = false;
ssprintf(path, sizeof(path), "%s/" PREINITMIRR, get_magisk_tmp());
for (const auto &info: self_mount_info) {
if (info.root == "/" && info.device == preinit_dev) {
auto flags = split_view(info.fs_option, ",");
auto rw = std::any_of(flags.begin(), flags.end(), [](const auto &flag) {
return flag == "rw"sv;
});
if (!rw) continue;
string preinit_dir = resolve_preinit_dir(info.target.data());
xmkdir(preinit_dir.data(), 0700);
if ((mounted = rec_mount(preinit_dir, path))) {
xmount(nullptr, path, nullptr, MS_UNBINDABLE, nullptr);
break;
}
}
}
if (!mounted) {
LOGW("preinit mirror not mounted %u:%u\n", major(preinit_dev), minor(preinit_dev));
unlink(dev_path);
}
}
// Prepare worker
ssprintf(path, sizeof(path), "%s/" WORKERDIR, get_magisk_tmp());
xmount("worker", path, "tmpfs", 0, "mode=755");
xmount(nullptr, path, nullptr, MS_PRIVATE, nullptr);
// Recursively bind mount / to mirror dir
// Keep mirror shared so that mounting during post-fs-data will be propagated
if (auto mirror_dir = get_magisk_tmp() + "/"s MIRRDIR; !rec_mount("/", mirror_dir)) {
LOGI("fallback to mount subtree\n");
// create new a bind mount for easy make private
xmount(mirror_dir.data(), mirror_dir.data(), nullptr, MS_BIND, nullptr);
// rootfs may fail, fallback to bind mount each mount point
set<string, greater<>> mounted_dirs {{ get_magisk_tmp() }};
for (const auto &info: self_mount_info) {
if (info.type == "rootfs"sv) continue;
// the greatest mount point that less than info.target, which is possibly a parent
if (auto last_mount = mounted_dirs.upper_bound(info.target);
last_mount != mounted_dirs.end() && info.target.starts_with(*last_mount + '/')) {
continue;
}
if (rec_mount(info.target, mirror_dir + info.target)) {
LOGD("%-8s: %s <- %s\n", "rbind", (mirror_dir + info.target).data(), info.target.data());
mounted_dirs.insert(info.target);
}
}
}
}
string find_preinit_device() {
enum part_t {
UNKNOWN,
PERSIST,
METADATA,
CACHE,
DATA,
};
part_t ext4_type = UNKNOWN;
part_t f2fs_type = UNKNOWN;
bool encrypted = get_prop("ro.crypto.state") == "encrypted";
bool mount = getuid() == 0 && getenv("MAGISKTMP");
bool make_dev = mount && getenv("MAKEDEV");
string preinit_source;
string preinit_dir;
dev_t preinit_dev;
for (const auto &info: parse_mount_info("self")) {
if (info.target.ends_with(PREINITMIRR))
return basename(info.source.data());
if (info.root != "/" || info.source[0] != '/' || info.source.find("/dm-") != string::npos)
continue;
// Skip all non ext4 partitions once we found a matching ext4 partition
if (ext4_type != UNKNOWN && info.type != "ext4")
continue;
if (info.type != "ext4" && info.type != "f2fs")
continue;
auto flags = split_view(info.fs_option, ",");
auto rw = std::any_of(flags.begin(), flags.end(), [](const auto &flag) {
return flag == "rw"sv;
});
if (!rw) continue;
if (auto base = std::string_view(info.source).substr(0, info.source.find_last_of('/'));
!base.ends_with("/by-name") && !base.ends_with("/block")) {
continue;
}
part_t &matched = (info.type == "f2fs") ? f2fs_type : ext4_type;
switch (matched) {
case UNKNOWN:
if (info.target == "/persist" || info.target == "/mnt/vendor/persist") {
matched = PERSIST;
break;
}
[[fallthrough]];
case PERSIST:
if (info.target == "/metadata") {
matched = METADATA;
break;
}
[[fallthrough]];
case METADATA:
if (info.target == "/cache") {
matched = CACHE;
break;
}
[[fallthrough]];
case CACHE:
if (info.target == "/data") {
if (!encrypted || access("/data/unencrypted", F_OK) == 0) {
matched = DATA;
break;
}
}
[[fallthrough]];
default:
continue;
}
if (mount) {
preinit_dir = resolve_preinit_dir(info.target.data());
preinit_dev = info.device;
}
preinit_source = info.source;
// Cannot find any better partition, stop finding
if (ext4_type == DATA)
break;
}
if (preinit_source.empty())
return "";
if (!preinit_dir.empty()) {
auto mirror_dir = string(getenv("MAGISKTMP")) + "/" PREINITMIRR;
mkdirs(preinit_dir.data(), 0700);
mkdirs(mirror_dir.data(), 0700);
xmount(preinit_dir.data(), mirror_dir.data(), nullptr, MS_BIND, nullptr);
if (make_dev) {
auto dev_path = string(getenv("MAGISKTMP")) + "/" PREINITDEV;
xmknod(dev_path.data(), S_IFBLK | 0600, preinit_dev);
}
}
return basename(preinit_source.data());
}
static bool magisk_env() {
char buf[4096];
LOGI("* Initializing Magisk environment\n");
preserve_stub_apk();
// Directories in /data/adb
chmod(SECURE_DIR, 0700);
xmkdir(DATABIN, 0755);
xmkdir(MODULEROOT, 0755);
xmkdir(SECURE_DIR "/post-fs-data.d", 0755);
xmkdir(SECURE_DIR "/service.d", 0755);
restorecon();
@ -224,6 +41,12 @@ static bool magisk_env() {
cp_afc(DATABIN "/busybox", buf);
exec_command_async(buf, "--install", "-s", dirname(buf));
// magisk32 and magiskpolicy are not installed into ramdisk and has to be copied
// from data to magisk tmp
if (access(DATABIN "/magisk32", X_OK) == 0) {
ssprintf(buf, sizeof(buf), "%s/magisk32", get_magisk_tmp());
cp_afc(DATABIN "/magisk32", buf);
}
if (access(DATABIN "/magiskpolicy", X_OK) == 0) {
ssprintf(buf, sizeof(buf), "%s/magiskpolicy", get_magisk_tmp());
cp_afc(DATABIN "/magiskpolicy", buf);
@ -298,58 +121,64 @@ static bool check_key_combo() {
return true;
}
static bool check_safe_mode() {
int bootloop_cnt;
db_settings dbs;
get_db_settings(dbs, BOOTLOOP_COUNT);
bootloop_cnt = dbs[BOOTLOOP_COUNT];
// Increment the bootloop counter
set_db_settings(BOOTLOOP_COUNT, bootloop_cnt + 1);
return bootloop_cnt >= 2 || get_prop("persist.sys.safemode", true) == "1" ||
get_prop("ro.sys.safemode") == "1" || check_key_combo();
}
/***********************
* Boot Stage Handlers *
***********************/
extern int disable_deny();
bool MagiskD::post_fs_data() const {
as_rust().setup_logfile();
LOGI("** post-fs-data mode running\n");
unlock_blocks();
mount_mirrors();
preserve_stub_apk();
prune_su_access();
bool safe_mode = false;
if (access(SECURE_DIR, F_OK) != 0) {
LOGE(SECURE_DIR " is not present, abort\n");
goto early_abort;
if (SDK_INT < 24) {
xmkdir(SECURE_DIR, 0700);
} else {
LOGE(SECURE_DIR " is not present, abort\n");
safe_mode = true;
return safe_mode;
}
}
if (!magisk_env()) {
LOGE("* Magisk environment incomplete, abort\n");
goto early_abort;
}
if (get_prop("persist.sys.safemode", true) == "1" ||
get_prop("ro.sys.safemode") == "1" || check_key_combo()) {
safe_mode = true;
// Disable all modules and denylist so next boot will be clean
disable_modules();
disable_deny();
} else {
exec_common_scripts("post-fs-data");
db_settings dbs;
get_db_settings(dbs, ZYGISK_CONFIG);
zygisk_enabled = dbs[ZYGISK_CONFIG];
initialize_denylist();
handle_modules();
return safe_mode;
}
early_abort:
auto mirror_dir = get_magisk_tmp() + "/"s MIRRDIR;
// make mirror dir as a private mount so that it won't be affected by magic mount
LOGD("make %s private\n", mirror_dir.data());
xmount(nullptr, mirror_dir.data(), nullptr, MS_PRIVATE | MS_REC, nullptr);
// We still do magic mount because root itself might need it
if (check_safe_mode()) {
LOGI("* Safe mode triggered\n");
safe_mode = true;
// Disable all modules and zygisk so next boot will be clean
disable_modules();
set_db_settings(ZYGISK_CONFIG, false);
return safe_mode;
}
exec_common_scripts("post-fs-data");
db_settings dbs;
get_db_settings(dbs, ZYGISK_CONFIG);
zygisk_enabled = dbs[ZYGISK_CONFIG];
initialize_denylist();
setup_mounts();
handle_modules();
load_modules();
// make mirror dir as a shared mount to make magisk --stop work for other ns
xmount(nullptr, mirror_dir.data(), nullptr, MS_SHARED | MS_REC, nullptr);
LOGD("make %s shared\n", mirror_dir.data());
return safe_mode;
}
@ -367,6 +196,9 @@ void MagiskD::boot_complete() const {
LOGI("** boot-complete triggered\n");
// Reset the bootloop counter once we have boot-complete
set_db_settings(BOOTLOOP_COUNT, 0);
// At this point it's safe to create the folder
if (access(SECURE_DIR, F_OK) != 0)
xmkdir(SECURE_DIR, 0700);

View File

@ -391,13 +391,6 @@ static void daemon_entry() {
ssprintf(path, sizeof(path), "%s/" ROOTOVL, tmp);
rm_rf(path);
// Unshare magiskd
xunshare(CLONE_NEWNS);
// Hide magisk internal mount point
xmount(nullptr, tmp, nullptr, MS_PRIVATE | MS_REC, nullptr);
// Fix sdcardfs bug on old kernel
xmount(nullptr, "/mnt", nullptr, MS_SLAVE | MS_REC, nullptr);
// Use isolated devpts if kernel support
if (access("/dev/pts/ptmx", F_OK) == 0) {
ssprintf(path, sizeof(path), "%s/" SHELLPTS, tmp);

View File

@ -9,9 +9,10 @@ use base::{
Utf8CStrBufArr, Utf8CStrBufRef, WalkResult,
};
use crate::consts::MAIN_CONFIG;
use crate::ffi::{get_magisk_tmp, CxxMagiskD, RequestCode};
use crate::get_prop;
use crate::logging::magisk_logging;
use crate::{get_prop, MAIN_CONFIG};
// Global magiskd singleton
pub static MAGISKD: OnceLock<MagiskD> = OnceLock::new();
@ -80,7 +81,7 @@ impl MagiskD {
}
RequestCode::BOOT_COMPLETE => {
unsafe { libc::close(client) };
if !state.contains(BootState::SafeMode) {
if state.contains(BootState::PostFsDataDone) {
state.set(BootState::BootComplete);
self.as_cxx().boot_complete()
}
@ -108,7 +109,7 @@ pub fn daemon_entry() {
let mut buf = Utf8CStrBufArr::<64>::new();
let path = FsPathBuf::new(&mut buf)
.join(get_magisk_tmp())
.join(MAIN_CONFIG!());
.join(MAIN_CONFIG);
let mut is_recovery = false;
if let Ok(file) = path.open(O_RDONLY | O_CLOEXEC) {
let mut file = BufReader::new(file);

View File

@ -117,6 +117,7 @@ db_settings::db_settings() {
data[SU_MNT_NS] = NAMESPACE_MODE_REQUESTER;
data[DENYLIST_CONFIG] = false;
data[ZYGISK_CONFIG] = MagiskD::get()->is_emulator();
data[BOOTLOOP_COUNT] = 0;
}
int db_settings::get_idx(string_view key) const {
@ -341,6 +342,16 @@ int get_db_settings(db_settings &cfg, int key) {
return 0;
}
int set_db_settings(int key, int value) {
char *err;
char sql[128];
ssprintf(sql, sizeof(sql), "INSERT OR REPLACE INTO settings VALUES ('%s', %d)",
DB_SETTING_KEYS[key], value);
err = db_exec(sql);
db_err_cmd(err, return 1)
return 0;
}
int get_db_strings(db_strings &str, int key) {
char *err = nullptr;
auto string_cb = [&](db_row &row) -> bool {

View File

@ -53,8 +53,7 @@ void denylist_handler(int client, const sock_cred *cred) {
ls_list(client);
return;
case DenyRequest::STATUS:
res = (zygisk_enabled && denylist_enforced)
? DenyResponse::ENFORCED : DenyResponse::NOT_ENFORCED;
res = denylist_enforced ? DenyResponse::ENFORCED : DenyResponse::NOT_ENFORCED;
break;
default:
// Unknown request code

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