Compare commits

..

126 Commits
v30.2 ... v30.6

Author SHA1 Message Date
topjohnwu
ccb264f33a Release Magisk v30.6
[skip ci]
2025-12-01 15:46:33 -08:00
topjohnwu
84f7d75d30 Update release.sh
Strip out all canary build logic
2025-12-01 15:27:01 -08:00
南宫雪珊
9a776c22d9 Revert "Use rootfs for magisktmp if possible" 2025-12-01 11:45:34 -08:00
topjohnwu
363566d0d5 Release Magisk v30.5
[skip ci]
2025-12-01 01:52:46 -08:00
topjohnwu
d9dc459bf4 Update system_properties
Fix #9408
2025-12-01 00:25:39 -08:00
topjohnwu
5d6b703622 Move occupy and unoccupy out of base crate 2025-11-29 00:13:41 -08:00
topjohnwu
f7ce9c38e1 Run through clippy and rustfmt 2025-11-29 00:13:41 -08:00
LoveSy
bdbfb40383 Use rootfs for magisktmp if possible 2025-11-29 00:13:41 -08:00
topjohnwu
283fc0f46f Update cargo dependencies 2025-11-28 21:00:48 -08:00
topjohnwu
2c24a41bf2 Update gradle dependencies 2025-11-27 03:36:49 -08:00
topjohnwu
97c93a1f4d Smaller release binary size 2025-11-27 02:00:54 -08:00
topjohnwu
8d534e6de8 Update cxx-rs 2025-11-25 02:29:45 -08:00
topjohnwu
3a60ef2039 Update to ONDK r29.3 2025-11-21 13:28:46 -08:00
Wang Han
52d7eff03f Fix splice direction for ptmx out stream 2025-11-19 15:14:59 -08:00
topjohnwu
020e23ea13 Disable help triggers on subcommands 2025-11-03 16:16:49 -08:00
topjohnwu
1599bfc2c5 Update dependencies 2025-11-02 13:52:32 -08:00
Wang Han
c8d51b38ba Enhance fdt_header validation for empty dtb 2025-11-02 02:42:48 -08:00
Wang Han
f741a4aeb8 Free regex resources in plt_hook_commit
Free regex resources for registered and ignored hooks before clearing the lists.
2025-11-02 01:59:03 -08:00
topjohnwu
4ee2235961 Update dependencies 2025-10-20 10:30:09 -07:00
topjohnwu
536e50c6e0 Support optional trailing positional arguments 2025-10-19 17:15:30 -07:00
topjohnwu
57d9fc6099 Support short only options and switches 2025-10-19 17:15:30 -07:00
topjohnwu
52d8910bdd Cleanup code for EarlyExit during help triggers 2025-10-19 17:15:30 -07:00
topjohnwu
c94bd49a89 Update default help triggers 2025-10-19 17:15:30 -07:00
topjohnwu
b72ba6759e Vendor argh sources
Further customization will come in future commits
2025-10-19 17:15:30 -07:00
topjohnwu
5bcb55b7fc Format Rust imports with rustfmt 2025-10-19 17:15:30 -07:00
topjohnwu
0dc8231585 Make all dependencies workspace = true 2025-10-19 17:15:30 -07:00
Wang Han
470acc93c9 Remove clickable attribute from item_module_md2.xml 2025-10-19 14:02:02 -07:00
Wang Han
0edb80b10f Set module card as non clickable
It's so easy to mis-click card.
2025-10-19 14:02:02 -07:00
topjohnwu
bcc6296d94 Build debug without debug-info 2025-10-03 00:16:17 -07:00
topjohnwu
c3db2e368d Release Magisk v30.4
[skip ci]
2025-10-02 04:30:47 -07:00
topjohnwu
d37da5ca66 Cleanup code 2025-10-02 04:18:20 -07:00
topjohnwu
aac52176ed Support API level as floating point 2025-10-02 04:10:22 -07:00
topjohnwu
78e2fc37e5 Add easy knobs to disable security checks 2025-10-02 04:09:46 -07:00
Wang Han
ca2e40593f Make fetchUpdate safe 2025-10-02 04:03:44 -07:00
LoveSy
c07fdc87e3 Handle second splice() failure gracefully 2025-10-02 04:03:27 -07:00
topjohnwu
7270f5e413 Several minor fixes/improvements 2025-10-02 04:03:08 -07:00
topjohnwu
07cc85ccb1 Default initialize before swap in move constructor
Fix #9373, fix #9384, fix #9400, fix #9404
2025-10-02 04:03:08 -07:00
topjohnwu
d6f17c42d5 Fix logging implementation error 2025-10-02 04:03:08 -07:00
Wang Han
d60806f429 Only reset NB prop when zygisk is enabled 2025-10-02 03:19:32 -07:00
Mohammad Hasan Keramat J
8836a09c8c core: Update Persian translation 2025-09-30 00:21:44 -07:00
topjohnwu
f16e93c7db Release Magisk v30.3
[skip ci]
2025-09-29 21:35:45 -07:00
Thonsi
1b0ddec66e Remove unused code 2025-09-29 02:33:02 -07:00
topjohnwu
cd8820f563 Refactor code for more readability 2025-09-29 01:41:55 -07:00
topjohnwu
b70192ca3e Sync libsepol with upstream AOSP 2025-09-29 01:18:52 -07:00
LoveSy
d42ec5da9a Fix pattern matching for CANARY version 2025-09-29 01:18:52 -07:00
topjohnwu
742913ebcb Support installing Magisk on vendor_boot
Close #9238, fix #8835
2025-09-28 01:10:11 -07:00
topjohnwu
ed206c6480 Upgrade cargo dependencies 2025-09-26 23:37:45 -07:00
topjohnwu
f9a8052583 Improve build.py
Close #8988
2025-09-26 17:00:58 -07:00
topjohnwu
f4fdd516f9 Upgrade gradle dependencies 2025-09-24 03:18:35 -07:00
topjohnwu
5925a71f94 Upgrade cargo dependencies 2025-09-24 03:05:18 -07:00
topjohnwu
3cda9beb93 Cleanup unused bindings 2025-09-24 02:38:18 -07:00
topjohnwu
8b7d1ffcdd Migrate magisk_main to Rust 2025-09-18 03:22:44 -07:00
topjohnwu
8d02d0632e Fix comments 2025-09-18 03:22:44 -07:00
topjohnwu
dd743f6f7e Improve Encodable/Decodable impls 2025-09-18 01:17:28 -07:00
topjohnwu
cf483ad4d2 Migrate connect_daemon to Rust 2025-09-15 14:25:18 -07:00
topjohnwu
4aed644e08 Directly accept RequestCode for connect_daemon 2025-09-15 14:25:18 -07:00
topjohnwu
0acc39cec0 Use bitflags to implement BootState 2025-09-15 14:25:18 -07:00
topjohnwu
8b3a44344f Move bootstages into its own module 2025-09-15 14:25:18 -07:00
topjohnwu
8b49eda85a Migrate daemon_entry to Rust 2025-09-15 14:25:18 -07:00
topjohnwu
7057d4c7f1 Migrate setup_magisk_env to Rust 2025-09-15 14:25:18 -07:00
Radoš Milićev
aab8344058 Update Serbian 2025-09-14 18:42:22 -07:00
topjohnwu
7cccf83b37 Remove unused poll_ctrl implementation 2025-09-14 01:59:04 -07:00
topjohnwu
f10ad93c4e Move more code of daemon_entry into Rust 2025-09-13 01:21:33 -07:00
topjohnwu
f143b5df15 Do not mount directories as mirror
Mounting real directories into worker will cause init to start tracking
the mount point through dev.mnt. This causes issues, so we are forced
to recursively reconstruct the mirror directory structure from scratch.

Fix #9316
2025-09-12 22:01:08 -07:00
topjohnwu
71213cc6f4 Fix path tracking in module.rs 2025-09-12 22:01:08 -07:00
topjohnwu
e2a1774e5b Make logging.rs use nix 2025-09-11 01:17:34 -07:00
topjohnwu
0222527a1e Use bitflags macro 2025-09-11 01:17:34 -07:00
topjohnwu
312bfe1bab Do not leak base::ffi to external crates 2025-09-11 01:17:34 -07:00
topjohnwu
48c62a1dae Disable exit on error for cmdline_logging 2025-09-11 01:17:34 -07:00
rikka
cfc2bcb665 Fix zygisk native bridge library name concatenation order 2025-09-11 01:16:54 -07:00
topjohnwu
94b1ff674f Allow calling remove_all on non-existence file 2025-09-10 03:44:39 -07:00
topjohnwu
111136733a Migrate away from unsafe set_len of Utf8CStr 2025-09-09 22:19:05 -07:00
topjohnwu
c8caaa98f5 Enable mount for nix 2025-09-09 20:17:09 -07:00
topjohnwu
8d28f10a3f Enable zerocopy for nix 2025-09-09 12:04:46 -07:00
topjohnwu
177a456d8b Enable term for nix 2025-09-09 12:04:31 -07:00
topjohnwu
ef4e230258 Use nix for libc functions 2025-09-08 23:59:29 -07:00
topjohnwu
17082af438 Simplify OsError 2025-09-08 11:25:20 -07:00
topjohnwu
1df5b34175 Stop differentiate Error vs ErrorCxx 2025-09-08 11:25:18 -07:00
topjohnwu
ea5fe7525d Simplify LibcReturn 2025-09-08 10:55:57 -07:00
topjohnwu
a75c335261 Update cargo dependencies 2025-09-08 02:24:01 -07:00
topjohnwu
3903f42cf6 Support specify ABI for clippy 2025-09-08 02:23:49 -07:00
topjohnwu
fb0c4ea838 Fallback to userspace copy if splice failed
Fix #9032
2025-09-03 16:10:18 -07:00
topjohnwu
bc89c60977 Run cargo fmt 2025-09-02 22:06:08 -07:00
topjohnwu
bd657c354c Reduce FFI across C++/Rust 2025-09-02 22:06:08 -07:00
MONA
675b5f9565 feat(i18n): Add Hinglish translation 2025-09-02 01:27:58 -07:00
MONA
1b2c43268e feat(i18n): Add Hinglish translation 2025-09-02 01:27:58 -07:00
topjohnwu
653730d75e Make cxx binding generate less code 2025-08-29 01:44:06 -07:00
topjohnwu
d472e9c36e Update cargo dependencies 2025-08-28 22:01:35 -07:00
topjohnwu
484d53ef7e Update to ONDK r29.2 2025-08-28 16:15:59 -07:00
topjohnwu
c4e2985677 Migrate resetprop to Rust 2025-08-27 22:48:48 -07:00
topjohnwu
42d9f87bc9 Cleanup resetprop code 2025-08-27 22:48:48 -07:00
topjohnwu
2e4fa6864c Make Utf8CStr a first class citizen in C++ codebase
Utf8CStr is in many cases a better string view class than
std::string_view, because it provides "view" access to a string buffer
that is guaranteed to be null terminated. It also has the additional
benefit of being UTF-8 verified and can seemlessly cross FFI boundaries.

We would want to start use more Utf8CStr in our existing C++ codebase.
2025-08-27 22:48:48 -07:00
topjohnwu
e2abb648ac Update system_properties 2025-08-27 10:12:51 -07:00
topjohnwu
3599dcedfb Make argh directly parse into Utf8CString 2025-08-27 01:26:41 -07:00
topjohnwu
ea72666df8 Only specify ADB port for tests 2025-08-25 15:34:04 -07:00
topjohnwu
bd2a47ba18 Merge libbase cpp files 2025-08-25 01:31:47 -07:00
topjohnwu
b861671391 Cleanup libbase 2025-08-25 01:31:47 -07:00
topjohnwu
e91fc75d86 Consolidate for_each implementation into Rust 2025-08-25 01:31:47 -07:00
LoveSy
78f5cd55c7 Use lzma-rust2 for xz and lzma compression and decompression 2025-08-24 00:23:55 -07:00
topjohnwu
9787a69528 Make all decoders Read instead of Write
Most libraries only implement Read for decoders
2025-08-24 00:23:55 -07:00
topjohnwu
87b8fe374d Fix magiskboot cli parsing 2025-08-23 20:31:15 -07:00
topjohnwu
7b706bb0cb Cleanup and fix compress/decompress command 2025-08-23 20:31:15 -07:00
topjohnwu
c1491b8d2b Fix LoggedResult implementation error 2025-08-23 15:25:52 -07:00
LoveSy
5cbaf2ae11 Use super let to simplify code 2025-08-22 12:05:44 -07:00
topjohnwu
8ebc6207b4 Merge headers 2025-08-22 12:03:47 -07:00
topjohnwu
7848ee616b Cleanup magiskboot main function 2025-08-22 12:03:47 -07:00
topjohnwu
fd193c3cae Simplify ResultExt implementation
Also introduce OptionExt
2025-08-22 12:03:47 -07:00
topjohnwu
36d33c7a85 Make log_err directly return LoggedResult 2025-08-22 12:03:47 -07:00
topjohnwu
5caf28d27c Hide harmless error reporting 2025-08-22 12:03:47 -07:00
topjohnwu
2c39d0234d Fix compression format detection 2025-08-21 12:21:22 -07:00
topjohnwu
c313812129 Simplify magiskboot FFI 2025-08-21 12:21:22 -07:00
topjohnwu
af51880a81 Introduce CmdArgs for argument parsing in Rust 2025-08-21 12:21:22 -07:00
LoveSy
db8d832707 Move magiskboot cli to argh 2025-08-20 21:40:34 -07:00
Wang Han
8dc23d0ead Avoid triggering magisk --zygote-restart twice
We have already used on restart keyword to inject zygote restart, so
triggering it here on prop is not needed.
2025-08-20 12:34:39 -07:00
topjohnwu
b4287700d5 Increase timeout to 15 minutes 2025-08-20 11:23:18 -07:00
topjohnwu
8d10ab89f2 Set zygisk properties in Rust 2025-08-20 11:23:18 -07:00
topjohnwu
49fdc1addb Prevent setting zygisk prop twice 2025-08-20 11:23:18 -07:00
topjohnwu
1333d3b986 Fix canary emulator 2025-08-18 11:25:47 -07:00
残页
335146a6a2 Update supported API levels 2025-08-17 23:58:43 -07:00
topjohnwu
eaf9527971 Use AOSP ATD for API 36
[skip ci]
2025-08-15 17:25:41 -07:00
LoveSy
da937a88c8 if !restore { set_zygisk_prop(); } 2025-08-15 16:45:01 -07:00
topjohnwu
9476e7282d More borrowing, less copying 2025-08-08 21:06:41 -07:00
topjohnwu
251c3c3e0e Remove old ffi data structure 2025-08-08 21:06:41 -07:00
topjohnwu
cd0eca20b0 Migrate connect.cpp to Rust 2025-08-08 21:06:41 -07:00
topjohnwu
6839cb9ab2 Keep /system/xbin/su on emulators 2025-08-08 21:06:41 -07:00
topjohnwu
d11a3397d8 Reduce verbose logging in Zygisk 2025-08-08 21:06:41 -07:00
155 changed files with 9957 additions and 7078 deletions

View File

@@ -82,10 +82,10 @@ jobs:
strategy:
fail-fast: false
matrix:
version: [23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, "36.0-CANARY"]
version: [23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, "CANARY"]
type: [""]
include:
- version: "36.0-CANARY"
- version: "CANARY"
type: "google_apis_ps16k"
steps:
@@ -105,7 +105,7 @@ jobs:
sudo udevadm trigger --name-match=kvm
- name: Run AVD test
timeout-minutes: 10
timeout-minutes: 15
env:
AVD_TEST_LOG: 1
run: scripts/avd.sh test ${{ matrix.version }} ${{ matrix.type }}
@@ -146,7 +146,7 @@ jobs:
sudo udevadm trigger --name-match=kvm
- name: Run AVD test
timeout-minutes: 10
timeout-minutes: 15
env:
FORCE_32_BIT: 1
AVD_TEST_LOG: 1
@@ -191,7 +191,7 @@ jobs:
scripts/cuttlefish.sh download ${{ matrix.branch }} ${{ matrix.device }}
- name: Run Cuttlefish test
timeout-minutes: 10
timeout-minutes: 15
run: sudo -E -u $USER scripts/cuttlefish.sh test
- name: Upload logs on error

3
.gitmodules vendored
View File

@@ -4,9 +4,6 @@
[submodule "lz4"]
path = native/src/external/lz4
url = https://github.com/lz4/lz4.git
[submodule "xz"]
path = native/src/external/xz
url = https://github.com/xz-mirror/xz.git
[submodule "libcxx"]
path = native/src/external/libcxx
url = https://github.com/topjohnwu/libcxx.git

View File

@@ -73,7 +73,8 @@ class InstallViewModel(svc: NetworkService, markwon: Markwon) : BaseViewModel()
val noteText = when {
noteFile.exists() -> noteFile.readText()
else -> {
val note = svc.fetchUpdate(APP_VERSION_CODE).note
val note = svc.fetchUpdate(APP_VERSION_CODE)?.note.orEmpty()
if (note.isEmpty()) return@launch
noteFile.writeText(note)
note
}

View File

@@ -43,10 +43,6 @@ enum class Theme(
val isSelected get() = Config.themeOrdinal == ordinal
fun select() {
Config.themeOrdinal = ordinal
}
companion object {
val selected get() = values().getOrNull(Config.themeOrdinal) ?: Piplup
}

View File

@@ -27,10 +27,8 @@
isEnabled="@{!item.removed && item.enabled && !item.showNotice}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="@{!item.removed && item.enabled && !item.showNotice}"
android:focusable="@{!item.removed && item.enabled && !item.showNotice}"
android:nextFocusRight="@id/module_indicator"
android:onClick="@{() -> item.setEnabled(!item.enabled)}"
app:cardBackgroundColor="@color/color_card_background_color_selector"
tools:isEnabled="false"
tools:layout_gravity="center"

View File

@@ -55,7 +55,7 @@ fun Project.setupCommon() {
compileSdkVersion(36)
buildToolsVersion = "36.0.0"
ndkPath = "$sdkDirectory/ndk/magisk"
ndkVersion = "28.1.13356709"
ndkVersion = "29.0.14206865"
defaultConfig {
minSdk = 23

View File

@@ -47,6 +47,8 @@ object Info {
private set
var slot = ""
private set
var isVendorBoot = false
private set
@JvmField val isZygiskEnabled = System.getenv("ZYGISK_ENABLED") == "1"
@JvmStatic val isFDE get() = crypto == "block"
@JvmStatic var ramdisk = false
@@ -113,6 +115,7 @@ object Info {
crypto = getVar("CRYPTOTYPE")
slot = getVar("SLOT")
legacySAR = getBool("LEGACYSAR")
isVendorBoot = getBool("VENDORBOOT")
// Default presets
Config.recovery = getBool("RECOVERYMODE")

View File

@@ -19,7 +19,7 @@ abstract class SuLogDatabase : RoomDatabase() {
companion object {
val MIGRATION_1_2 = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) = with(database) {
override fun migrate(db: SupportSQLiteDatabase) = with(db) {
execSQL("ALTER TABLE logs ADD COLUMN target INTEGER NOT NULL DEFAULT -1")
execSQL("ALTER TABLE logs ADD COLUMN context TEXT NOT NULL DEFAULT ''")
execSQL("ALTER TABLE logs ADD COLUMN gids TEXT NOT NULL DEFAULT ''")

View File

@@ -44,7 +44,7 @@ object ServiceLocator {
private fun createSuLogDatabase(context: Context) =
Room.databaseBuilder(context, SuLogDatabase::class.java, "sulogs.db")
.addMigrations(SuLogDatabase.MIGRATION_1_2)
.fallbackToDestructiveMigration()
.fallbackToDestructiveMigration(true)
.build()
private fun createMarkwon(context: Context) =

View File

@@ -34,7 +34,7 @@ data class ModuleJson(
@JsonClass(generateAdapter = true)
data class ReleaseAssets(
val name: String,
@Json(name = "browser_download_url") val url: String,
@param:Json(name = "browser_download_url") val url: String,
)
class DateTimeAdapter {
@@ -51,12 +51,12 @@ class DateTimeAdapter {
@JsonClass(generateAdapter = true)
data class Release(
@Json(name = "tag_name") val tag: String,
@param:Json(name = "tag_name") val tag: String,
val name: String,
val prerelease: Boolean,
val assets: List<ReleaseAssets>,
val body: String,
@Json(name = "created_at") val createdTime: Instant,
@param:Json(name = "created_at") val createdTime: Instant,
) {
val versionCode: Int get() {
return if (tag[0] == 'v') {

View File

@@ -41,7 +41,9 @@ class NetworkService(
info
}
suspend fun fetchUpdate(version: Int) = findRelease { it.versionCode == version }.asInfo()
suspend fun fetchUpdate(version: Int) = safe {
findRelease { it.versionCode == version }.asInfo()
}
// Keep going through all release pages until we find a match
private suspend inline fun findRelease(predicate: (Release) -> Boolean): Release? {

View File

@@ -81,10 +81,12 @@ abstract class MagiskInstallImpl protected constructor(
}
private fun findImage(slot: String): Boolean {
val bootPath = (
"(RECOVERYMODE=${Config.recovery} " +
"SLOT=$slot find_boot_image; " +
"echo \$BOOTIMAGE)").fsh()
val cmd =
"RECOVERYMODE=${Config.recovery} " +
"VENDORBOOT=${Info.isVendorBoot} " +
"SLOT=$slot " +
"find_boot_image; echo \$BOOTIMAGE"
val bootPath = ("($cmd)").fsh()
if (bootPath.isEmpty()) {
console.add("! Unable to detect target image")
return false

View File

@@ -20,12 +20,11 @@
<string name="hide">Sakrij</string>
<string name="home_package">Paket</string>
<string name="home_app_title">Apl.</string>
<string name="home_notice_content">Preuzmite Magisk SAMO sa zvanične GitHub stranice. Fajlovi iz nepoznatih izvora mogu biti maliciozni!</string>
<string name="home_support_title">Podržite nas</string>
<string name="home_follow_title">Zapratite nas</string>
<string name="home_item_source">Izvor</string>
<string name="home_support_content">Magisk jeste i uvek će biti besplatan i open source. Možete pokazati da vam je stalo svojom donacijom.</string>
<string name="home_support_content">Magisk jeste i uvek će biti besplatan i open source. Međutim, možete pokazati da vam je stalo svojom donacijom.</string>
<string name="home_installed_version">Instalirano</string>
<string name="home_latest_version">Najnovije</string>
<string name="invalid_update_channel">Nevalidan kanal ažuriranja</string>
@@ -52,9 +51,10 @@
<!--Superuser-->
<string name="su_request_title">Super-korisnički zahtev</string>
<string name="touch_filtered_warning">Magisk ne može da verifikuje vaš odgovor, jer aplikacija prikriva super-korisnički zahtev</string>
<string name="touch_filtered_warning">Magisk ne može da verifikuje vaš odgovor, jer aplikacija prikriva super-korisnički zahtev.</string>
<string name="deny">Zabrani</string>
<string name="prompt">Zahtev</string>
<string name="restrict">Ograniči</string>
<string name="grant">Dozvoli</string>
<string name="su_warning">Pruža potpun pristup vašem uređaju.\nZabranite ako niste sigurni!</string>
<string name="forever">Zauvek</string>
@@ -75,25 +75,22 @@
<string name="su_revoke_msg">Potvrdi da opozoveš prava na super-korisnika od %1$s?</string>
<string name="toast">Toast</string>
<string name="none">Ništa</string>
<string name="superuser_toggle_notification">Notifikacije</string>
<string name="superuser_toggle_revoke">Opozovi</string>
<string name="superuser_policy_none">Nijedna aplikacija nije tražila permisije za super-korisnika još uvek.</string>
<!--Logs-->
<string name="log_data_none">Nemate logova, pokušajte koristiti korenske aplikacije više</string>
<string name="log_data_magisk_none">Magisk logovi su prazni, to je čudno</string>
<string name="log_data_none">Nemate logova. Pokušajte koristiti korenske aplikacije više.</string>
<string name="log_data_magisk_none">Magisk logovi su prazni, to je čudno.</string>
<string name="menuSaveLog">Sačuvaj log</string>
<string name="menuClearLog">Ukloni log</string>
<string name="logs_cleared">Log uspešno uklonjen</string>
<string name="pid">PID: %1$d</string>
<string name="target_uid">Ciljani UID: %1$d</string>
<string name="target_pid">Mount ns cinjani PID: %s</string>
<string name="target_pid">Ciljani PID: %s</string>
<string name="selinux_context">SELinux kontekst: %s</string>
<string name="supp_group">Dopunska grupa: %s</string>
<!--SafetyNet-->
<!--MagiskHide-->
<string name="show_system_app">Prikaži sistemske apl.</string>
<string name="show_os_app">Prikaži apl. OS-a</string>
@@ -140,9 +137,10 @@
<string name="settings_update_channel_title">Kanal ažuriranja</string>
<string name="settings_update_stable">Stabilno</string>
<string name="settings_update_beta">Beta</string>
<string name="settings_update_debug">Debug</string>
<string name="settings_update_custom">Prilagođeno</string>
<string name="settings_update_custom_msg">Unesi prilagođeni URL kanala</string>
<string name="settings_zygisk_summary">Pokreni delove Magisk-a u zygote daemon-u</string>
<string name="settings_zygisk_summary">Pokreni delove Magisk-a u Zygote daemon-u</string>
<string name="settings_denylist_title">Sprovedi listu zabrana</string>
<string name="settings_denylist_summary">Procesi na listi zabrana će povratiti sve Magisk izmene</string>
<string name="settings_denylist_config_title">Konfiguriši listu zabrana</string>
@@ -174,13 +172,14 @@
<string name="settings_su_auth_title">Autentifikacija korisnika</string>
<string name="settings_su_auth_summary">Traži autentifikaciju korisnika tokom zahteva super-korisnika</string>
<string name="settings_su_auth_insecure">Nijedan metod autentifikacije nije podešen na uređaju</string>
<string name="settings_su_restrict_title">Ograniči korenske sposobnosti</string>
<string name="settings_su_restrict_summary">Podrazumevano ograničava apl. super-korisnika. Upozorenje: ovo će većinu aplikacija skršiti. Ne omogućavaj, osim ako znaš šta radiš.</string>
<string name="settings_customization">Prilagođavanje</string>
<string name="setting_add_shortcut_summary">Dodaj lepu prečicu na početni ekran u slučaju da se ime i ikonica ne prepoznaju lako nakon skrivanja aplikacije</string>
<string name="settings_doh_title">DNS preko HTTPS-a</string>
<string name="settings_doh_description">Zaobilazno rešenje DNS trovanja u nekim nacijama</string>
<string name="settings_random_name_title">Nasumično ime na izlazu</string>
<string name="settings_random_name_description">Nasumično ime izlaznog fajla slika i tar fajlova radi sprečavanja detekcije</string>
<string name="multiuser_mode">Višekorisnički režim</string>
<string name="settings_owner_only">Samo vlasnik uređaja</string>
<string name="settings_owner_manage">Određeno od strane vlasnika</string>
@@ -188,7 +187,6 @@
<string name="owner_only_summary">Samo vlasnik ima pristup korenu</string>
<string name="owner_manage_summary">Samo vlasnik može da pristupa korenu i da prima zahteve za njega</string>
<string name="user_independent_summary">Svaki korisnik ima svoja pravila korena</string>
<string name="mount_namespace_mode">Mount režim namespace-a</string>
<string name="settings_ns_global">Globalni namespace</string>
<string name="settings_ns_requester">Nasleđeni namespace</string>
@@ -233,7 +231,7 @@
<string name="env_full_fix_msg">Vaš uređaj zahteva ponovno flešovanje da bi Magisk radio kako treba. Reinstalirajte Magisk kroz aplikaciju, režim oporavka ne može dobiti tačne informacije o uređaju.</string>
<string name="setup_msg">Pokretanje podešavanja okruženja…</string>
<string name="unsupport_magisk_title">Nepodržana verzija Magisk-a</string>
<string name="unsupport_magisk_msg">Ova verzija aplikacije ne podržava Magisk verzije manje od %1$s.\n\nAplikacija će se ponašati kao da Magisk nije instaliran, molimo ažurirajte Magisk što pre.</string>
<string name="unsupport_magisk_msg">Ova verzija aplikacije ne podržava Magisk verzije manje od %1$s.\n\nAplikacija će se ponašati kao da Magisk nije instaliran. Molimo ažurirajte Magisk što pre.</string>
<string name="unsupport_general_title">Nenormalno stanje</string>
<string name="unsupport_system_app_msg">Pokretanje aplikacije kao sistemske nije podržano. Molimo postavite aplikaciju da bude korisnička.</string>
<string name="unsupport_other_su_msg">Detektovan \"su\" binary koji nije Magisk-ov. Molimo uklonite konkurentno korensko rešenje i/ili reinstalirajte Magisk.</string>
@@ -242,7 +240,7 @@
<string name="unsupport_nonroot_stub_title">@string/settings_restore_app_title</string>
<string name="external_rw_permission_denied">Dozvolite permisiju za skladište da biste omogućili ovu funkcionalnost</string>
<string name="post_notifications_denied">Dozvolite permisiju za notifikacije da biste omogućili ovu funkcionalnost</string>
<string name="install_unknown_denied">Dozvolite "instaliranje nepoznatih aplikacija" da biste omogućili ovu funkcionalnost</string>
<string name="install_unknown_denied">Dozvolite \"instaliranje nepoznatih aplikacija\" da biste omogućili ovu funkcionalnost</string>
<string name="add_shortcut_title">Dodaj prečicu na početni ekran</string>
<string name="add_shortcut_msg">Nakon skrivanja aplikacije, njeno ime i ikonicu ćete teško prepoznati. Želite li dodati lepu prečicu na početni ekran?</string>
<string name="app_not_found">Nije pronađena aplikacija za ovu akciju</string>

View File

@@ -8,6 +8,7 @@
<string name="install">نصب</string>
<string name="section_home">خانه</string>
<string name="section_theme">تم ها</string>
<string name="denylist">لیست منع</string>
<!--Home-->
<string name="no_connection">هیچ اتصالی وجود ندارد</string>
@@ -17,7 +18,9 @@
<string name="not_available">غیر/قابل دسترسی</string>
<string name="hide">پنهان کردن</string>
<string name="home_package">پکیج</string>
<string name="home_app_title">برنامه</string>
<string name="home_notice_content">Magisk را فقط از صفحه رسمی GitHub دانلود کنید. فایل‌ها از منابع ناشناس می‌توانند مخرب باشند!</string>
<string name="home_follow_title">ما را دنبال کنید</string>
<string name="home_support_title">حمایت ما</string>
<string name="home_item_source">منبع</string>
<string name="home_support_content">این برنامه (Magisk) رایگان و متن باز است و همیشه خواهد ماند. اگرچه شما میتواند با دونیت خود نشان دهد که به ما اهمیت می دهید.</string>
@@ -47,8 +50,10 @@
<!--Superuser-->
<string name="su_request_title">درخواست کاربر روت</string>
<string name="touch_filtered_warning">به دلیل اینکه یک برنامه در حال پوشاندن درخواست Superuser است، Magisk نمی‌تواند پاسخ شما را تأیید کند.</string>
<string name="deny">رد کردن</string>
<string name="prompt">درخواست کردن</string>
<string name="restrict">محدود کردن</string>
<string name="grant">اجازه دادن</string>
<string name="su_warning">دسترسی کامل به دستگاه شما را اعطا می کند. \nاگر مطمئن نیستید رد کنید!</string>
<string name="forever">همیشه</string>
@@ -67,37 +72,50 @@
<string name="su_snack_log_off">ورود به سیستم از %1$s غییر فعال است</string>
<string name="su_revoke_title">باطل بشه؟</string>
<string name="su_revoke_msg">تایید کنید که %1$s باطل بشه؟</string>
<string name="toast">پیام کوتاه</string>
<string name="none">هیچ کدام</string>
<string name="superuser_toggle_notification">اعلان ها</string>
<string name="superuser_toggle_revoke">ابطال</string>
<string name="superuser_policy_none">هنوز هیچ برنامه ای مجوز روت درخواست نکرده است.</string>
<!--Logs-->
<string name="log_data_none">شما هیچ لاگی ندارید, سعی کنید برنامه های که به روت دسترسی میگیرند را استفاده کنید.</string>
<string name="log_data_magisk_none">لاگ های مربوط به Magisk خالی است. پنا بر خدا.</string>
<string name="log_data_magisk_none">لاگ های مربوط به Magisk خالی است.</string>
<string name="menuSaveLog">ذخیره کردن لاگ</string>
<string name="menuClearLog">پاک کردن لاگ</string>
<string name="logs_cleared">لاگ با موفقیت پاک شد.</string>
<!--SafetyNet-->
<string name="pid">شناسه پردازش: %1$d</string>
<string name="target_uid">شناسه کاربر هدف: %1$d</string>
<string name="target_pid">شناسه پردازش هدف: %s</string>
<string name="selinux_context">متن SELinux: %s</string>
<string name="supp_group">گروه تکمیلی: %s</string>
<!-- MagiskHide -->
<string name="show_system_app">نشان دادن برنامه های سیستمی</string>
<string name="show_os_app">نمایش برنامه‌های سیستم عامل</string>
<string name="hide_filter_hint">فیلتر کردن با نام</string>
<string name="hide_search">سرچ کردن</string>
<!--Module -->
<string name="no_info_provided">(هیچ اطلاعاتی ارائه نشده است)</string>
<string name="reboot_userspace">راه اندازی مججد</string>
<string name="reboot_recovery">راه اندازی مججد برای رفتن به ریکاوری</string>
<string name="reboot_bootloader">راه اندازی مججد برای رفتن به بوت لودر</string>
<string name="reboot_download">راه اندازی مججد برای دانلود کردن</string>
<string name="reboot_edl">راه اندازی مججد برای رفتن به EDL</string>
<string name="reboot_safe_mode">راه اندازی مججد برای رفتن به حالت امن</string>
<string name="module_version_author">%1$s با %2$s</string>
<string name="module_state_remove">حذف کردن</string>
<string name="module_state_restore">بازگرداندن</string>
<string name="module_action_install_external">نصب از حافظه</string>
<string name="update_available">بروزرسانی در دسترس است</string>
<string name="suspend_text_riru">ماژول به دلیل فعال بودن %1$s متوقف شد</string>
<string name="suspend_text_zygisk">ماژول به دلیل غیرفعال بودن %1$s متوقف شد</string>
<string name="zygisk_module_unloaded">ماژول Zygisk به دلیل ناسازگاری بارگذاری نشد</string>
<string name="module_empty">هیچ ماژولی نصب نشده است</string>
<string name="confirm_install">نصب ماژول %1$s؟</string>
<string name="confirm_install_title">تأیید نصب</string>
<!--Settings -->
<string name="settings_dark_mode_title">حالت تم</string>
@@ -107,6 +125,10 @@
<string name="settings_dark_mode_dark">همیشه تاریک</string>
<string name="settings_download_path_title">مسیر دانلود</string>
<string name="settings_download_path_message">فایل ها در %1$s ذخیره خواهند شد.</string>
<string name="settings_hide_app_title">مخفی کردن برنامه Magisk</string>
<string name="settings_hide_app_summary">نصب یک برنامه پروکسی با شناسه بسته تصادفی و برچسب سفارشی</string>
<string name="settings_restore_app_title">بازگردانی برنامه Magisk</string>
<string name="settings_restore_app_summary">آشکار کردن برنامه و بازگرداندن APK اصلی</string>
<string name="language">زبان</string>
<string name="system_default">(پیش فرض سیستم)</string>
<string name="settings_check_update_title">چک کردن بروز رسانی ها</string>
@@ -114,8 +136,14 @@
<string name="settings_update_channel_title">کانال بروزرسانی</string>
<string name="settings_update_stable">پایدار</string>
<string name="settings_update_beta">آزمایشی</string>
<string name="settings_update_debug">اشکال‌زدایی</string>
<string name="settings_update_custom">شخصی سازی شده</string>
<string name="settings_update_custom_msg">اضافه کردن یک URL سفارشی</string>
<string name="settings_zygisk_summary">اجرای بخش‌هایی از Magisk در سرویس Zygote</string>
<string name="settings_denylist_title">اعمال لیست منع</string>
<string name="settings_denylist_summary">فرآیندهای موجود در لیست منع تمام تغییرات Magisk را از دست خواهند داد</string>
<string name="settings_denylist_config_title">پیکربندی لیست منع</string>
<string name="settings_denylist_config_summary">انتخاب فرآیندهایی که باید در لیست منع قرار گیرند</string>
<string name="settings_hosts_title">نصب بدون حذف یا تغییر در فایل ها</string>
<string name="settings_hosts_summary">نصب بدون حذف یا تغییر در فایل ها رای ساپورت از برنامه های Adblock</string>
<string name="settings_hosts_toast">ماژول نصب بدون حذف یا تغییر در فایل ها اضافه شد</string>
@@ -138,9 +166,19 @@
<string name="superuser_notification">اعلان روت</string>
<string name="settings_su_reauth_title">احراز هویت دوباره پس از بروز رسانی</string>
<string name="settings_su_reauth_summary">تأیید کردندوباره مجوزهای روت پس از ارتقاء برنامه</string>
<string name="settings_su_tapjack_title">محافظت در برابر Tapjacking</string>
<string name="settings_su_tapjack_summary">پنجره درخواست Superuser زمانی که توسط پنجره یا لایه دیگری پوشانده شود، به ورودی پاسخ نخواهد داد</string>
<string name="settings_su_auth_title">احراز هویت کاربر</string>
<string name="settings_su_auth_summary">درخواست احراز هویت کاربر هنگام درخواست Superuser</string>
<string name="settings_su_auth_insecure">هیچ روش احراز هویتی روی دستگاه پیکربندی نشده است</string>
<string name="settings_su_restrict_title">محدود کردن دسترسی روت</string>
<string name="settings_su_restrict_summary">به طور پیش‌فرض برنامه‌های Superuser جدید را محدود می‌کند. هشدار: این کار بیشتر برنامه‌ها را از کار می‌اندازد. فقط اگر دقیقاً می‌دانید چه می‌کنید آن را فعال کنید.</string>
<string name="settings_customization">سفارشی سازی</string>
<string name="setting_add_shortcut_summary">اضافه کردن یک میانبر زیبا را در صفحه اصلی در صورت شناسایی نام و نماد پس از پنهان کردن برنامه</string>
<string name="settings_doh_title">DNS روی HTTPS</string>
<string name="settings_doh_description">دور زدن مسمومیت DNS در برخی کشورها</string>
<string name="settings_random_name_title">تغییر تصادفی نام خروجی</string>
<string name="settings_random_name_description">تغییر تصادفی نام فایل خروجی تصاویر و فایل‌های tar پچ‌شده برای جلوگیری از شناسایی</string>
<string name="multiuser_mode">حالت چند کاربره</string>
<string name="settings_owner_only">فقط صاحب دستگاه</string>
<string name="settings_owner_manage">صاحب دستگاه مدیریت شود</string>
@@ -148,7 +186,6 @@
<string name="owner_only_summary">فقط مالک دسترسی روت دارد</string>
<string name="owner_manage_summary">فقط مالک می تواند دسترسی روت را مدیریت کرده و درخواست های پرامپت را دریافت کند</string>
<string name="user_independent_summary">هر کاربر قوانین روت جداگانه خود را دارد</string>
<string name="mount_namespace_mode">نصب کردن Namespace Mode</string>
<string name="settings_ns_global">سراسری Namespace</string>
<string name="settings_ns_requester">وراثتی Namespace</string>
@@ -160,19 +197,27 @@
<!--Notifications-->
<string name="update_channel">Magisk بروزرسانی های</string>
<string name="progress_channel">اعلان پیشرفت</string>
<string name="updated_channel">به‌روزرسانی کامل شد</string>
<string name="download_complete">دانلود کامل شد</string>
<string name="download_file_error">خطا در دانلود فایل</string>
<string name="magisk_update_title">بروزرسانی Magisk در دسترس است!</string>
<string name="updated_title">Magisk به‌روزرسانی شد</string>
<string name="updated_text">برای باز کردن برنامه لمس کنید</string>
<!--Toasts, Dialogs-->
<string name="yes">بله</string>
<string name="no">نه</string>
<string name="download">انلود کردن</string>
<string name="repo_install_title">نصب %1$s %2$s(%3$d)</string>
<string name="download">دانلود کردن</string>
<string name="reboot">راه اندازی مجدد</string>
<string name="close">بستن</string>
<string name="release_notes">نکته های نسخه</string>
<string name="flashing">ر حال فلش کردن…</string>
<string name="running">در حال اجرا…</string>
<string name="done">تمام!</string>
<string name="done_action">انجام عملیات %1$s به پایان رسید</string>
<string name="failure">ناموفق</string>
<string name="hide_app_title">در حال مخفی کردن برنامه Magisk…</string>
<string name="open_link_failed_toast">هیچ برنامه ای برای باز کردن لینک یافت نشد</string>
<string name="complete_uninstall">کامل کردن حذف</string>
<string name="restore_img">بازیابی تصاویر</string>
@@ -181,9 +226,24 @@
<string name="restore_fail">نسخه پشتیبان استک موجود نیست!</string>
<string name="setup_fail">نصب انجام نشد</string>
<string name="env_fix_title">به تنظیمات اضافی نیاز دارد</string>
<string name="env_fix_msg">دستگاه شما به پیکربندی اضافی نیاز دارد تا Magisk به درستی کار کند. آیا می‌خواهید ادامه دهید و راه‌اندازی مجدد انجام شود؟</string>
<string name="env_full_fix_msg">دستگاه شما نیاز به نصب دوباره Magisk دارد تا به درستی کار کند. لطفاً Magisk را از داخل برنامه دوباره نصب کنید، حالت Recovery نمی‌تواند اطلاعات دستگاه را به درستی بگیرد.</string>
<string name="setup_msg">راه اندازی محیط نصب…</string>
<string name="unsupport_magisk_title">نسخه پشتیبانی نشده Magisk</string>
<string name="unsupport_magisk_msg">این نسخه از برنامه از نسخه‌های Magisk پایین‌تر از %1$s پشتیبانی نمی‌کند.\n\nبرنامه طوری رفتار می‌کند که انگار Magisk نصب نشده است. لطفاً هرچه سریع‌تر Magisk را به‌روزرسانی کنید.</string>
<string name="unsupport_general_title">وضعیت غیرعادی</string>
<string name="unsupport_system_app_msg">اجرای این برنامه به عنوان برنامه سیستمی پشتیبانی نمی‌شود. لطفاً آن را به برنامه کاربری بازگردانید.</string>
<string name="unsupport_other_su_msg">یک باینری "su" غیر از Magisk شناسایی شد. لطفاً هر راهکار روت دیگری را حذف کنید و/یا Magisk را دوباره نصب کنید.</string>
<string name="unsupport_external_storage_msg">Magisk روی حافظه خارجی نصب شده است. لطفاً برنامه را به حافظه داخلی منتقل کنید.</string>
<string name="unsupport_nonroot_stub_msg">برنامه مخفی Magisk نمی‌تواند ادامه دهد زیرا دسترسی روت از بین رفته است. لطفاً APK اصلی را بازگردانید.</string>
<string name="unsupport_nonroot_stub_title">@string/settings_restore_app_title</string>
<string name="external_rw_permission_denied">برای فعال کردن این قابلیت ، اجازه دسترسی به حافظه بدهید</string>
<string name="post_notifications_denied">برای فعال‌سازی این قابلیت، مجوز اعلان‌ها را بدهید</string>
<string name="install_unknown_denied">برای فعال‌سازی این قابلیت، «نصب برنامه‌های ناشناخته» را مجاز کنید</string>
<string name="add_shortcut_title">اضافه کردن میانبر را به صفحه</string>
<string name="add_shortcut_msg">بعد از مخفی کردن این برنامه، ممکن است نام و آیکون آن سخت قابل شناسایی شود. آیا می‌خواهید یک میانبر زیبا به صفحه اصلی اضافه کنید؟</string>
<string name="app_not_found">هیچ برنامه‌ای برای انجام این عملیات یافت نشد</string>
<string name="reboot_apply_change">برای اعمال تغییرات، دستگاه را دوباره راه‌اندازی کنید</string>
<string name="restore_app_confirmation">این کار برنامه مخفی شده را به نسخه اصلی بازمی‌گرداند. آیا واقعاً می‌خواهید این کار را انجام دهید؟</string>
</resources>

View File

@@ -0,0 +1,249 @@
<resources>
<!--Sections-->
<string name="modules">Modules</string>
<string name="superuser">Superuser</string>
<string name="logs">Logs</string>
<string name="settings">Settings</string>
<string name="install">Install</string>
<string name="section_home">Home</string>
<string name="section_theme">Themes</string>
<string name="denylist">DenyList</string>
<!--Home-->
<string name="no_connection">Net nahi chal rha hai</string>
<string name="app_changelog">Kya naga hai</string>
<string name="loading">Loading ho rha hai…</string>
<string name="update">Update</string>
<string name="not_available">Available nahi hai</string>
<string name="hide">Chhupao</string>
<string name="home_package">Package</string>
<string name="home_app_title">App</string>
<string name="home_notice_content">Hamesha Magisk ko uske official github release source se download karein. Unofficial source ki file khatarnak ho sakti hai.</string>
<string name="home_support_title">Humein Support karo</string>
<string name="home_follow_title">Humein Karo</string>
<string name="home_item_source">Source</string>
<string name="home_support_content">Magisk hamesha free aur open source rahega. Agar aap support karna chahte ho, toh donation de sakte ho.</string>
<string name="home_installed_version">Jo version install hai</string>
<string name="home_latest_version">Latest</string>
<string name="invalid_update_channel">Galat update channel</string>
<string name="uninstall_magisk_title">Magisk uninstall karo</string>
<string name="uninstall_magisk_msg">Saare modules disable ya remove ho jayenge!\nRoot khatam ho jayega!\nAgar Magisk ne storage ko decrypt kiya tha toh wo phir se encrypt ho jayega!</string>
<!--Install-->
<string name="keep_force_encryption">Force encryption ko preserve karo</string>
<string name="keep_dm_verity">AVB 2.0/dm-verity ko preserve karo</string>
<string name="recovery_mode">Recovery mode</string>
<string name="install_options_title">Options</string>
<string name="install_method_title">Method</string>
<string name="install_next">Aage badho</string>
<string name="install_start">Chalo shuru karein</string>
<string name="manager_download_install">Download aur install karne ke liye dabao</string>
<string name="direct_install">Direct install (Recommended)</string>
<string name="install_inactive_slot">Inactive slot par install karo (OTA ke baad)</string>
<string name="install_inactive_slot_msg">Reboot ke baad device ko inactive slot se boot karna padega!\nSirf tab use karo jab OTA complete ho chuka ho.\nAage badhna hai?</string>
<string name="setup_title">Extra setup</string>
<string name="select_patch_file">File select karke patch karo</string>
<string name="patch_file_msg">Raw image (*.img), ODIN tar file (*.tar), ya payload.bin (*.bin) select karo</string>
<string name="reboot_delay_toast">Phone 5 second mein reboot hoga…</string>
<string name="flash_screen_title">Installation ho rahi hai</string>
<!--Superuser-->
<string name="su_request_title">ROOT access maang rha hai</string>
<string name="touch_filtered_warning">Ek app Superuser request ko cover kar raha hai, isliye Magisk aapka response verify nahi kar sakta.</string>
<string name="deny">Nahi</string>
<string name="prompt">Poocho</string>
<string name="restrict">Restrict</string>
<string name="grant">Haan Theek hai</string>
<string name="su_warning">Yeh full access dega device ko.\nAgar sure nahi ho toh deny kar do!</string>
<string name="forever">Hamesha ke liye</string>
<string name="once">Ek baar ke liye</string>
<string name="tenmin">10 mins</string>
<string name="twentymin">20 mins</string>
<string name="thirtymin">30 mins</string>
<string name="sixtymin">60 mins</string>
<string name="su_allow_toast">%1$s ko ROOT access de diya gaya</string>
<string name="su_deny_toast">%1$s ko ROOT access nahi diya gaya</string>
<string name="su_snack_grant">%1$s ko ROOT access mila</string>
<string name="su_snack_deny">%1$s ka ROOT access deny hua</string>
<string name="su_snack_notif_on">%1$s ke notifications ON hain</string>
<string name="su_snack_notif_off">%1$s ke notifications OFF hain</string>
<string name="su_snack_log_on">%1$s ka logging ON hai</string>
<string name="su_snack_log_off">%1$s ka logging OFF hai</string>
<string name="su_revoke_title">Access wapas lena hai?</string>
<string name="su_revoke_msg">Kya aap confirm karte ho ki %1$s ka ROOT access hata diya jaye?</string>
<string name="toast">Toast</string>
<string name="none">Kuch nahi</string>
<string name="superuser_toggle_notification">Notifications</string>
<string name="superuser_toggle_revoke">Wapas lelo</string>
<string name="superuser_policy_none">Ab tak kisi bhi app ne ROOT access nahi manga hai</string>
<!--Logs-->
<string name="log_data_none">Koi logs nahi mile. Shayad tumhe root apps ka zyada use krna chahiye.</string>
<string name="log_data_magisk_none">Ajeeb baat hai, Yaha toh logs hain hi nahi.</string>
<string name="menuSaveLog">Log save karo</string>
<string name="menuClearLog">Log clear karo</string>
<string name="logs_cleared">Logs clear ho gaye</string>
<string name="pid">PID: %1$d</string>
<string name="target_uid">Target UID: %1$d</string>
<string name="target_pid">Target PID: %s</string>
<string name="selinux_context">SELinux context: %s</string>
<string name="supp_group">Supplementary group: %s</string>
<!--MagiskHide-->
<string name="show_system_app">System apps dikhao</string>
<string name="show_os_app">OS apps dikhao</string>
<string name="hide_filter_hint">Naam ke hisaab se filter karo</string>
<string name="hide_search">Search karo</string>
<!--Module-->
<string name="no_info_provided">(Koi info nahi di gayi)</string>
<string name="reboot_userspace">Soft reboot</string>
<string name="reboot_recovery">Recovery mode mein reboot karo</string>
<string name="reboot_bootloader">Bootloader mode mein reboot karo</string>
<string name="reboot_download">Download mode mein reboot karo</string>
<string name="reboot_edl">EDL mode mein reboot karo</string>
<string name="reboot_safe_mode">Safe mode</string>
<string name="module_version_author">%1$s ko %2$s ne banaya hai</string>
<string name="module_state_remove">Delete karo</string>
<string name="module_action">Action</string>
<string name="module_state_restore">Wapas laao</string>
<string name="module_action_install_external">Storage se install karo</string>
<string name="update_available">Naya update available hai</string>
<string name="suspend_text_riru">Module suspend kiya gaya kyunki %1$s enabled hai</string>
<string name="suspend_text_zygisk">Bhai %1$s ON kr tabb ye module chalega</string>
<string name="zygisk_module_unloaded">Zygisk module compatible nahi tha, isliye load nahi hua</string>
<string name="module_empty">Koi module install nahi hai</string>
<string name="confirm_install">>%1$s module install karna hai?</string>
<string name="confirm_install_title">Ek baar firse soch lo</string>
<!--Settings-->
<string name="settings_dark_mode_title">Theme mode</string>
<string name="settings_dark_mode_message">Apni hisaab se mode choose karo!</string>
<string name="settings_dark_mode_light">Hamesha light theme</string>
<string name="settings_dark_mode_system">System ke saath follow karo</string>
<string name="settings_dark_mode_dark">Hamesha dark theme</string>
<string name="settings_download_path_title">Download path</string>
<string name="settings_download_path_message">Files yahan save hongi: %1$s</string>
<string name="settings_hide_app_title">Magisk app ko hide karo</string>
<string name="settings_hide_app_summary">Magisk app ka name aur package ID change kro</string>
<string name="settings_restore_app_title">Magisk app ko unhide karo</string>
<string name="settings_restore_app_summary">App ko unhide karo aur original APK wapas lao</string>
<string name="language">Language</string>
<string name="system_default">(System ki default)</string>
<string name="settings_check_update_title">Updates check karo</string>
<string name="settings_check_update_summary">Background mein updates auto check honge</string>
<string name="settings_update_channel_title">Update channel</string>
<string name="settings_update_stable">Stable</string>
<string name="settings_update_beta">Beta</string>
<string name="settings_update_debug">Debug</string>
<string name="settings_update_custom">Custom</string>
<string name="settings_update_custom_msg">Apna custom channel URL daaloL</string>
<string name="settings_zygisk_summary">Magisk ke kuch parts ko Zygote daemon mein run karo</string>
<string name="settings_denylist_title">DenyList enforce karo</string>
<string name="settings_denylist_summary">DenyList mein jo processes hain, unpe Magisk ka effect hata diya jayega</string>
<string name="settings_denylist_config_title">DenyList set karo</string>
<string name="settings_denylist_config_summary">Kaunse process DenyList mein daalne hain, select karo</string>
<string name="settings_hosts_title">Systemless hosts</string>
<string name="settings_hosts_summary">Ad-blocking apps ke liye systemless hosts support</string>
<string name="settings_hosts_toast">Systemless hosts module add kar diya gaya</string>
<string name="settings_app_name_hint">Naya naam</string>
<string name="settings_app_name_helper">App is naam ke saath repack hoga</string>
<string name="settings_app_name_error">Naam ka format galat hai</string>
<string name="settings_su_app_adb">Apps aur ADB</string>
<string name="settings_su_app">Sirf apps</string>
<string name="settings_su_adb">Sirf ADB</string>
<string name="settings_su_disable">Disable kiya gaya</string>
<string name="settings_su_request_10">10 seconds</string>
<string name="settings_su_request_15">15 seconds</string>
<string name="settings_su_request_20">20 seconds</string>
<string name="settings_su_request_30">30 seconds</string>
<string name="settings_su_request_45">45 seconds</string>
<string name="settings_su_request_60">60 seconds</string>
<string name="superuser_access">ROOT access</string>
<string name="auto_response">Automatic response</string>
<string name="request_timeout">Request timeout</string>
<string name="superuser_notification">ROOT notification</string>
<string name="settings_su_reauth_title">Upgrade ke baad dobara permission puchho</string>
<string name="settings_su_reauth_summary">App upgrade hone ke baad Superuser permission firse maangna</string>
<string name="settings_su_tapjack_title">Tapjacking se protection</string>
<string name="settings_su_tapjack_summary">Agar Superuser prompt kisi aur window ya overlay ke neeche chhup gaya ho, toh uspar tap kaam nahi karega</string>
<string name="settings_su_auth_title">User authentication</string>
<string name="settings_su_auth_summary">Superuser request ke time user se authentication maango</string>
<string name="settings_su_auth_insecure">Device mein koi bhi authentication method set nahi hai</string>
<string name="settings_su_restrict_title">Root access ko limit karo</string>
<string name="settings_su_restrict_summary">Nayeapps ko ROOT access maangne se block karega. Warning: Isse zyada tarr apps kaam karna band kar denge. Sirf tab enable karo jab aapko pata ho aap kya kar rahe ho.</string>
<string name="settings_customization">Customization</string>
<string name="setting_add_shortcut_summary">Agar app hide karne ke baad uska naam ya icon samajhne mein dikkat ho rahi ho, toh home screen pe ek shortcut add kar lo</string>
<string name="settings_doh_title">DNS over HTTPS</string>
<string name="settings_doh_description">DNS poisoning se bachne ke liye (kuch countries mein zaroori padti hai)</string>
<string name="settings_random_name_title">Output file ka naam random karo</string>
<string name="settings_random_name_description">Patched images aur tar files ka naam random bana ke detection se bachao</string>
<string name="multiuser_mode">Multiuser mode</string>
<string name="settings_owner_only">Sirf device owner</string>
<string name="settings_owner_manage">Device owner manage karega</string>
<string name="settings_user_independent">Har user ke liye alag</string>
<string name="owner_only_summary">Sirf device owner ko root access milega</string>
<string name="owner_manage_summary">Sirf owner root access manage kar sakta hai aur requests receive karega</string>
<string name="user_independent_summary">Har user ke liye alag root rules honge</string>
<string name="mount_namespace_mode">Mount namespace mode</string>
<string name="settings_ns_global">Global namespace</string>
<string name="settings_ns_requester">Inherit namespace</string>
<string name="settings_ns_isolate">Isolated namespace</string>
<string name="global_summary">Sabhi root sessions ek hi global mount namespace use karenge</string>
<string name="requester_summary">Root session apne requester ka namespace inherit karega</string>
<string name="isolate_summary">Har root session ka apna alag isolated namespace hoga</string>
<!--Notifications-->
<string name="update_channel">Magisk updates</string>
<string name="progress_channel">Progress notifications</string>
<string name="updated_channel">Update ho gaya</string>
<string name="download_complete">Download ho gaya</string>
<string name="download_file_error">File download karne mein error aaya</string>
<string name="magisk_update_title">Magisk ka naya update available hai!</string>
<string name="updated_title">Magisk update ho gaya</string>
<string name="updated_text">App open karne ke liye tap karo</string>
<!--Toasts, Dialogs-->
<string name="yes">Haan</string>
<string name="no">Nahi</string>
<string name="repo_install_title">%1$s %2$s(%3$d) Install karo</string>
<string name="download">Download karo</string>
<string name="reboot">Reboot karo</string>
<string name="close">Close karo</string>
<string name="release_notes">Release notes</string>
<string name="flashing">Flash ho raha hai…</string>
<string name="running">Chal raha hai…</string>
<string name="done">Ho gaya!</string>
<string name="done_action">%1$s ka action complete ho gaya</string>
<string name="failure">Fail ho gaya!</string>
<string name="hide_app_title">Magisk app ko chhupa rahe hain…</string>
<string name="open_link_failed_toast">Link kholne ke liye koi app nahi mila</string>
<string name="complete_uninstall">Sab kuch uninstall karo</string>
<string name="restore_img">Images restore karo</string>
<string name="restore_img_msg">Restore kiya ja raha hai…</string>
<string name="restore_done">Restore complete ho gaya!</string>
<string name="restore_fail">Stock backup available nahi hai!</string>
<string name="setup_fail">Setup fail ho gaya</string>
<string name="env_fix_title">Iske liye thoda extra setup chahiye</string>
<string name="env_fix_msg">Magisk ko sahi se chalane ke liye thoda aur setup karna padega. Kya aap proceed karke device reboot karna chahte ho?</string>
<string name="env_full_fix_msg">Magisk ko properly chalane ke liye aapko usse dubara flash karna padega. App ke andar se Magisk reinstall karo, kyunki Recovery mode sahi device info nahi de pata.</string>
<string name="setup_msg">Environment setup chal raha hai…</string>
<string name="unsupport_magisk_title">Magisk version supported nahi hain</string>
<string name="unsupport_magisk_msg">Is app version ko %1$s se purani Magisk versions support nahi karti.\n\nApp aise behave karega jaise Magisk install hi nahi hai. Jaldi se Magisk ko update karo.</string>
<string name="unsupport_general_title">System thoda alag behave kar raha hai</string>
<string name="unsupport_system_app_msg">App ko system app bana ke chalana supported nahi hai. Please app ko wapas user app bana do.</string>
<string name="unsupport_other_su_msg">Pehle doosri root method hatao ya Magisk dobara install karo.</string>
<string name="unsupport_external_storage_msg">Magisk external storage pe installed hai. App ko internal storage mein move karo.</string>
<string name="unsupport_nonroot_stub_msg">Hidden Magisk app ab kaam nahi karega kyunki root chala gaya hai. Please original APK restore karo.</string>
<string name="unsupport_nonroot_stub_title">@string/settings_restore_app_title</string>
<string name="external_rw_permission_denied">Iss feature ko enable karne ke liye storage permission allow karo</string>
<string name="post_notifications_denied">Is feature ke liye notifications permission allow karo</string>
<string name="install_unknown_denied">\"Install unknown apps\" ki permission allow karo taaki ye feature kaam kar sakey</string>
<string name="add_shortcut_title">Shortcut home screen pe add karo</string>
<string name="add_shortcut_msg">App ko hide karne ke baad agar uska icon ya naam pehchanna mushkil ho, toh ek shortcut home screen pe add kar lein?</string>
<string name="app_not_found">Is action ko handle karne ke liye koi app nahi mila</string>
<string name="reboot_apply_change">Changes apply karne ke liye reboot krna zaroori hai</string>
<string name="restore_app_confirmation">Ye action hidden app ko original version mein wapas laayega. Kya aap sach mein ye karna chahte ho?</string>
</resources>

View File

@@ -20,12 +20,11 @@
<string name="hide">Сакриј</string>
<string name="home_package">Пакет</string>
<string name="home_app_title">Апл.</string>
<string name="home_notice_content">Преузмите Magisk САМО са званичне GitHub странице. Фајлови из непознатих извора могу бити малициозни!</string>
<string name="home_support_title">Подржите нас</string>
<string name="home_follow_title">Запратите нас</string>
<string name="home_item_source">Извор</string>
<string name="home_support_content">Magisk јесте и увек ће бити бесплатан и open source. Можете показати да вам је стало својом донацијом.</string>
<string name="home_support_content">Magisk јесте и увек ће бити бесплатан и open source. Међутим, можете показати да вам је стало својом донацијом.</string>
<string name="home_installed_version">Инсталирано</string>
<string name="home_latest_version">Најновије</string>
<string name="invalid_update_channel">Невалидан канал ажурирања</string>
@@ -52,9 +51,10 @@
<!--Superuser-->
<string name="su_request_title">Супер-кориснички захтев</string>
<string name="touch_filtered_warning">Magisk не може да верификује ваш одговор, јер апликација прикрива супер-кориснички захтев</string>
<string name="touch_filtered_warning">Magisk не може да верификује ваш одговор, јер апликација прикрива супер-кориснички захтев.</string>
<string name="deny">Забрани</string>
<string name="prompt">Захтев</string>
<string name="restrict">Ограничи</string>
<string name="grant">Дозволи</string>
<string name="su_warning">Пружа потпун приступ вашем уређају.\nЗабраните ако нисте сигурни!</string>
<string name="forever">Заувек</string>
@@ -75,25 +75,22 @@
<string name="su_revoke_msg">Потврди да опозовеш права на супер-корисника од %1$s?</string>
<string name="toast">Toast</string>
<string name="none">Ништа</string>
<string name="superuser_toggle_notification">Нотификације</string>
<string name="superuser_toggle_revoke">Опозови</string>
<string name="superuser_policy_none">Ниједна апликација није тражила пермисије за супер-корисника још увек.</string>
<!--Logs-->
<string name="log_data_none">Немате логова, покушајте користити коренске апликације више</string>
<string name="log_data_magisk_none">Magisk логови су празни, то је чудно</string>
<string name="log_data_none">Немате логова. Покушајте користити коренске апликације више.</string>
<string name="log_data_magisk_none">Magisk логови су празни, то је чудно.</string>
<string name="menuSaveLog">Сачувај лог</string>
<string name="menuClearLog">Уклони лог</string>
<string name="logs_cleared">Лог успешно уклоњен</string>
<string name="pid">PID: %1$d</string>
<string name="target_uid">Циљани UID: %1$d</string>
<string name="target_pid">Mount ns цињани PID: %s</string>
<string name="target_pid">Циљани PID: %s</string>
<string name="selinux_context">SELinux контекст: %s</string>
<string name="supp_group">Допунска група: %s</string>
<!--SafetyNet-->
<!--MagiskHide-->
<string name="show_system_app">Прикажи системске апл.</string>
<string name="show_os_app">Прикажи апл. ОС-а</string>
@@ -140,9 +137,10 @@
<string name="settings_update_channel_title">Канал ажурирања</string>
<string name="settings_update_stable">Стабилно</string>
<string name="settings_update_beta">Бета</string>
<string name="settings_update_debug">Debug</string>
<string name="settings_update_custom">Прилагођено</string>
<string name="settings_update_custom_msg">Унеси прилагођени URL канала</string>
<string name="settings_zygisk_summary">Покрени делове Magisk-а у zygote daemon-у</string>
<string name="settings_zygisk_summary">Покрени делове Magisk-а у Zygote daemon-у</string>
<string name="settings_denylist_title">Спроведи листу забрана</string>
<string name="settings_denylist_summary">Процеси на листи забрана ће повратити све Magisk измене</string>
<string name="settings_denylist_config_title">Конфигуриши листу забрана</string>
@@ -174,13 +172,14 @@
<string name="settings_su_auth_title">Аутентификација корисника</string>
<string name="settings_su_auth_summary">Тражи аутентификацију корисника током захтева супер-корисника</string>
<string name="settings_su_auth_insecure">Ниједан метод аутентификације није подешен на уређају</string>
<string name="settings_su_restrict_title">Ограничи коренске способности</string>
<string name="settings_su_restrict_summary">Подразумевано ограничава апл. супер-корисника. Упозорење: ово ће већину апликација скршити. Не омогућавај, осим ако знаш шта радиш.</string>
<string name="settings_customization">Прилагођавање</string>
<string name="setting_add_shortcut_summary">Додај лепу пречицу на почетни екран у случају да се име и иконица не препознају лако након скривања апликације</string>
<string name="settings_doh_title">DNS преко HTTPS-а</string>
<string name="settings_doh_description">Заобилазно решење DNS тровања у неким нацијама</string>
<string name="settings_random_name_title">Насумично име на излазу</string>
<string name="settings_random_name_description">Насумично име излазног фајла слика и tar фајлова ради спречавања детекције</string>
<string name="multiuser_mode">Вишекориснички режим</string>
<string name="settings_owner_only">Само власник уређаја</string>
<string name="settings_owner_manage">Одређено од стране власника</string>
@@ -188,7 +187,6 @@
<string name="owner_only_summary">Само власник има приступ корену</string>
<string name="owner_manage_summary">Само власник може да приступа корену и да прима захтеве за њега</string>
<string name="user_independent_summary">Сваки корисник има своја правила корена</string>
<string name="mount_namespace_mode">Mount режим namespace-а</string>
<string name="settings_ns_global">Глобални namespace</string>
<string name="settings_ns_requester">Наслеђени namespace</string>
@@ -233,7 +231,7 @@
<string name="env_full_fix_msg">Ваш уређај захтева поновно флешовање да би Magisk радио како треба. Реинсталирајте Magisk кроз апликацију, режим опоравка не може добити тачне информације о уређају.</string>
<string name="setup_msg">Покретање подешавања окружења…</string>
<string name="unsupport_magisk_title">Неподржана верзија Magisk-а</string>
<string name="unsupport_magisk_msg">Ова верзија апликације не подржава Magisk верзије мање од %1$s.\n\nАпликација ће се понашати као да Magisk није инсталиран, молимо ажурирајте Magisk што пре.</string>
<string name="unsupport_magisk_msg">Ова верзија апликације не подржава Magisk верзије мање од %1$s.\n\nАпликација ће се понашати као да Magisk није инсталиран. Молимо ажурирајте Magisk што пре.</string>
<string name="unsupport_general_title">Ненормално стање</string>
<string name="unsupport_system_app_msg">Покретање апликације као системске није подржано. Молимо поставите апликацију да буде корисничка.</string>
<string name="unsupport_other_su_msg">Детектован \"su\" binary који није Magisk-ов. Молимо уклоните конкурентно коренско решење и/или реинсталирајте Magisk.</string>
@@ -242,7 +240,7 @@
<string name="unsupport_nonroot_stub_title">@string/settings_restore_app_title</string>
<string name="external_rw_permission_denied">Дозволите пермисију за складиште да бисте омогућили ову функционалност</string>
<string name="post_notifications_denied">Дозволите пермисију за нотификације да бисте омогућили ову функционалност</string>
<string name="install_unknown_denied">Дозволите "инсталирање непознатих апликација" да бисте омогућили ову функционалност</string>
<string name="install_unknown_denied">Дозволите \"инсталирање непознатих апликација\" да бисте омогућили ову функционалност</string>
<string name="add_shortcut_title">Додај пречицу на почетни екран</string>
<string name="add_shortcut_msg">Након скривања апликације, њено име и иконицу ћете тешко препознати. Желите ли додати лепу пречицу на почетни екран?</string>
<string name="app_not_found">Није пронађена апликација за ову акцију</string>

View File

@@ -30,4 +30,4 @@ android.nonFinalResIds=false
# Magisk
magisk.stubVersion=40
magisk.versionCode=30200
magisk.versionCode=30600

View File

@@ -1,16 +1,16 @@
[versions]
kotlin = "2.2.0"
android = "8.12.0"
ksp = "2.2.0-2.0.2"
kotlin = "2.2.21"
android = "8.13.1"
ksp = "2.3.3"
rikka = "1.3.0"
navigation = "2.9.3"
navigation = "2.9.6"
libsu = "6.0.0"
okhttp = "5.1.0"
okhttp = "5.3.2"
retrofit = "3.0.0"
room = "2.7.2"
room = "2.8.4"
[libraries]
bcpkix = { module = "org.bouncycastle:bcpkix-jdk18on", version = "1.81" }
bcpkix = { module = "org.bouncycastle:bcpkix-jdk18on", version = "1.83" }
commons-compress = { module = "org.apache.commons:commons-compress", version = "1.28.0" }
retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
retrofit-moshi = { module = "com.squareup.retrofit2:converter-moshi", version.ref = "retrofit" }
@@ -23,12 +23,12 @@ timber = { module = "com.jakewharton.timber:timber", version = "5.0.1" }
jgit = { module = "org.eclipse.jgit:org.eclipse.jgit", version = "7.1.0.202411261347-r" }
# AndroidX
activity = { module = "androidx.activity:activity", version = "1.10.1" }
activity = { module = "androidx.activity:activity", version = "1.12.0" }
appcompat = { module = "androidx.appcompat:appcompat", version = "1.7.1" }
core-ktx = { module = "androidx.core:core-ktx", version = "1.16.0" }
core-splashscreen = { module = "androidx.core:core-splashscreen", version = "1.0.1" }
core-ktx = { module = "androidx.core:core-ktx", version = "1.17.0" }
core-splashscreen = { module = "androidx.core:core-splashscreen", version = "1.2.0" }
constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version = "2.2.1" }
fragment-ktx = { module = "androidx.fragment:fragment-ktx", version = "1.8.8" }
fragment-ktx = { module = "androidx.fragment:fragment-ktx", version = "1.8.9" }
navigation-fragment-ktx = { module = "androidx.navigation:navigation-fragment-ktx", version.ref = "navigation" }
navigation-ui-ktx = { module = "androidx.navigation:navigation-ui-ktx", version.ref = "navigation" }
profileinstaller = { module = "androidx.profileinstaller:profileinstaller", version = "1.4.1" }
@@ -39,7 +39,7 @@ room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" }
swiperefreshlayout = { module = "androidx.swiperefreshlayout:swiperefreshlayout", version = "1.1.0" }
transition = { module = "androidx.transition:transition", version = "1.6.0" }
collection-ktx = { module = "androidx.collection:collection-ktx", version = "1.5.0" }
material = { module = "com.google.android.material:material", version = "1.12.0" }
material = { module = "com.google.android.material:material", version = "1.13.0" }
jdk-libs = { module = "com.android.tools:desugar_jdk_libs_nio", version = "2.1.5" }
test-runner = { module = "androidx.test:runner", version = "1.7.0" }
test-rules = { module = "androidx.test:rules", version = "1.7.0" }
@@ -62,6 +62,6 @@ android-gradle-plugin = { module = "com.android.tools.build:gradle", version.ref
ksp-plugin = { module = "com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin", version.ref = "ksp" }
navigation-safe-args-plugin = { module = "androidx.navigation:navigation-safe-args-gradle-plugin", version.ref = "navigation" }
lsparanoid-plugin = { module = "org.lsposed.lsparanoid:gradle-plugin", version = "0.6.0" }
moshi-plugin = { module = "dev.zacsweers.moshix:dev.zacsweers.moshix.gradle.plugin", version = "0.31.0" }
moshi-plugin = { module = "dev.zacsweers.moshix:dev.zacsweers.moshix.gradle.plugin", version = "0.34.1" }
[plugins]

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="upgrade_msg">Setup complete karne ke liye full Magisk install karna hoga. Abhi download aur install karein?</string>
<string name="no_internet_msg">Full Magisk upgrade ke liye Internet se connect hona zaroori hai!</string>
<string name="dling">Download ho raha hai…</string>
<string name="relaunch_app">App ko reopen karo</string>
</resources>

139
build.py
View File

@@ -1,6 +1,5 @@
#!/usr/bin/env python3
import argparse
import copy
import glob
import multiprocessing
import os
@@ -11,7 +10,6 @@ import stat
import subprocess
import sys
import tarfile
import textwrap
import urllib.request
from pathlib import Path
from zipfile import ZipFile
@@ -39,8 +37,13 @@ def vprint(str):
print(str)
# Environment checks and detection
is_windows = os.name == "nt"
# OS detection
os_name = platform.system().lower()
is_windows = False
if os_name != "linux" and os_name != "darwin":
# It's possible we're using MSYS/Cygwin/MinGW, treat them all as Windows
is_windows = True
os_name = "windows"
EXE_EXT = ".exe" if is_windows else ""
no_color = False
@@ -57,7 +60,6 @@ if not sys.version_info >= (3, 8):
error("Requires Python 3.8+")
cpu_count = multiprocessing.cpu_count()
os_name = platform.system().lower()
# Common constants
support_abis = {
@@ -67,16 +69,24 @@ support_abis = {
"x86_64": "x86_64-linux-android",
"riscv64": "riscv64-linux-android",
}
default_archs = {"armeabi-v7a", "x86", "arm64-v8a", "x86_64"}
default_targets = {"magisk", "magiskinit", "magiskboot", "magiskpolicy"}
support_targets = default_targets | {"resetprop"}
rust_targets = {"magisk", "magiskinit", "magiskboot", "magiskpolicy"}
ondk_version = "r28.5"
abi_alias = {
"arm": "armeabi-v7a",
"arm32": "armeabi-v7a",
"arm64": "arm64-v8a",
"x64": "x86_64",
}
default_abis = support_abis.keys() - {"riscv64"}
support_targets = {"magisk", "magiskinit", "magiskboot", "magiskpolicy", "resetprop"}
default_targets = support_targets - {"resetprop"}
rust_targets = default_targets.copy()
clean_targets = {"native", "cpp", "rust", "app"}
ondk_version = "r29.3"
# Global vars
config = {}
args = {}
build_abis = {}
args: argparse.Namespace
build_abis: dict[str, str]
force_out = False
###################
# Helper functions
@@ -126,7 +136,7 @@ def rm_rf(path: Path):
def execv(cmds: list, env=None):
out = None if args.force_out or args.verbose > 0 else subprocess.DEVNULL
out = None if force_out or args.verbose > 0 else subprocess.DEVNULL
# Use shell on Windows to support PATHEXT
return subprocess.run(cmds, stdout=out, env=env, shell=is_windows)
@@ -171,7 +181,7 @@ def collect_ndk_build():
mv(source, target)
def run_ndk_build(cmds: list):
def run_ndk_build(cmds: list[str]):
os.chdir("native")
cmds.append("NDK_PROJECT_PATH=.")
cmds.append("NDK_APPLICATION_MK=src/Application.mk")
@@ -187,7 +197,7 @@ def run_ndk_build(cmds: list):
os.chdir("..")
def build_cpp_src(targets: set):
def build_cpp_src(targets: set[str]):
cmds = []
clean = False
@@ -226,15 +236,22 @@ def build_cpp_src(targets: set):
clean_elf()
def run_cargo(cmds):
def run_cargo(cmds: list[str]):
ensure_paths()
env = os.environ.copy()
env["RUSTUP_TOOLCHAIN"] = str(rust_sysroot)
env["PATH"] = f"{rust_sysroot / "bin"}{os.pathsep}{env["PATH"]}"
env["CARGO_BUILD_RUSTFLAGS"] = f"-Z threads={min(8, cpu_count)}"
# Cargo calls executables in $RUSTROOT/lib/rustlib/$TRIPLE/bin, we need
# to make sure the runtime linker also search $RUSTROOT/lib for libraries.
# This is only required on Unix, as Windows search dlls from PATH.
if os_name == "darwin":
env["DYLD_FALLBACK_LIBRARY_PATH"] = str(rust_sysroot / "lib")
elif os_name == "linux":
env["LD_LIBRARY_PATH"] = str(rust_sysroot / "lib")
return execv(["cargo", *cmds], env)
def build_rust_src(targets: set):
def build_rust_src(targets: set[str]):
targets = targets.copy()
if "resetprop" in targets:
targets.add("magisk")
@@ -433,8 +450,7 @@ def build_stub():
def build_test():
global args
args_bak = copy.copy(args)
old_release = args.release
# Test APK has to be built as release to prevent classname clash
args.release = True
try:
@@ -444,7 +460,7 @@ def build_test():
mv(source, target)
header(f"Output: {target}")
finally:
args = args_bak
args.release = old_release
################
@@ -454,14 +470,13 @@ def build_test():
def cleanup():
ensure_paths()
support_targets = {"native", "cpp", "rust", "app"}
if args.targets:
targets = set(args.targets) & support_targets
targets: set[str] = set(args.targets) & clean_targets
if "native" in targets:
targets.add("cpp")
targets.add("rust")
else:
targets = support_targets
targets = clean_targets
if "cpp" in targets:
header("* Cleaning C++")
@@ -470,11 +485,11 @@ def cleanup():
if "rust" in targets:
header("* Cleaning Rust")
rm_rf(Path("native", "src", "target"))
rm_rf(Path("native", "out", "rust"))
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)
rm(Path(rs_gen))
if "native" in targets:
header("* Cleaning native")
@@ -501,7 +516,7 @@ def build_all():
def gen_ide():
ensure_paths()
set_archs({args.abi})
set_build_abis({args.abi})
# Dump flags for both C++ and Rust code
dump_flag_header()
@@ -529,19 +544,31 @@ def gen_ide():
def clippy_cli():
ensure_toolchain()
args.force_out = True
set_archs(default_archs)
global force_out
force_out = True
if args.abi:
set_build_abis(set(args.abi))
else:
set_build_abis(default_abis)
if not args.release and not args.debug:
# If none is specified, run both
args.release = True
args.debug = True
os.chdir(Path("native", "src"))
cmds = ["clippy", "--no-deps", "--target"]
for triple in build_abis.values():
run_cargo(cmds + [triple])
run_cargo(cmds + [triple, "--release"])
if args.debug:
run_cargo(cmds + [triple])
if args.release:
run_cargo(cmds + [triple, "--release"])
os.chdir(Path("..", ".."))
def cargo_cli():
args.force_out = True
global force_out
force_out = True
if len(args.commands) >= 1 and args.commands[0] == "--":
args.commands = args.commands[1:]
os.chdir(Path("native", "src"))
@@ -601,7 +628,7 @@ def setup_rustup():
##################
def push_files(script):
def push_files(script: Path):
if args.build:
build_all()
ensure_adb()
@@ -678,8 +705,8 @@ def patch_avd_file():
def ensure_paths():
global sdk_path, ndk_root, ndk_path, ndk_build, rust_sysroot
global llvm_bin, gradlew, adb_path, native_gen_path
global sdk_path, ndk_root, ndk_path, rust_sysroot
global ndk_build, gradlew, adb_path
# Skip if already initialized
if "sdk_path" in globals():
@@ -697,9 +724,6 @@ def ensure_paths():
ndk_path = ndk_root / "magisk"
ndk_build = ndk_path / "ndk-build"
rust_sysroot = ndk_path / "toolchains" / "rust"
llvm_bin = (
ndk_path / "toolchains" / "llvm" / "prebuilt" / f"{os_name}-x86_64" / "bin"
)
adb_path = sdk_path / "platform-tools" / "adb"
gradlew = Path.cwd() / "app" / "gradlew"
@@ -708,14 +732,13 @@ def ensure_paths():
def ensure_adb():
global adb_path
if "adb_path" not in globals():
adb_path = shutil.which("adb")
if not adb_path:
error("Command 'adb' cannot be found in PATH")
if adb := shutil.which("adb"):
adb_path = Path(adb)
else:
adb_path = Path(adb_path)
error("Command 'adb' cannot be found in PATH")
def parse_props(file):
def parse_props(file: Path) -> dict[str, str]:
props = {}
with open(file, "r") as f:
for line in [l.strip(" \t\r\n") for l in f]:
@@ -732,10 +755,14 @@ def parse_props(file):
return props
def set_archs(archs: set):
triples = map(support_abis.get, archs)
def set_build_abis(abis: set[str]):
global build_abis
build_abis = dict(zip(archs, triples))
# Try to convert several aliases to real ABI
abis = {abi_alias.get(k, k) for k in abis}
# Check any unknown ABIs
for k in abis - support_abis.keys():
error(f"Unknown ABI: {k}")
build_abis = {k: support_abis[k] for k in abis if k in support_abis}
def load_config():
@@ -746,8 +773,6 @@ def load_config():
config["versionCode"] = 1000000
config["outdir"] = "out"
args.config = Path(args.config)
# Load prop files
if args.config.exists():
config.update(parse_props(args.config))
@@ -767,12 +792,11 @@ def load_config():
config["outdir"].mkdir(mode=0o755, parents=True, exist_ok=True)
if "abiList" in config:
abiList = re.split("\\s*,\\s*", config["abiList"])
archs = set(abiList) & support_abis.keys()
abis = set(re.split("\\s*,\\s*", config["abiList"]))
else:
archs = default_archs
abis = default_abis
set_archs(archs)
set_build_abis(abis)
def parse_args():
@@ -837,6 +861,15 @@ def parse_args():
cargo_parser.add_argument("commands", nargs=argparse.REMAINDER)
clippy_parser = subparsers.add_parser("clippy", help="run clippy on Rust sources")
clippy_parser.add_argument(
"--abi", action="append", help="target ABI(s) to run clippy"
)
clippy_parser.add_argument(
"-r", "--release", action="store_true", help="run clippy as release"
)
clippy_parser.add_argument(
"-d", "--debug", action="store_true", help="run clippy as debug"
)
rustup_parser = subparsers.add_parser("rustup", help="setup rustup wrapper")
rustup_parser.add_argument(
@@ -871,8 +904,8 @@ def parse_args():
def main():
global args
args = parse_args()
args.config = Path(args.config)
load_config()
vars(args)["force_out"] = False
args.func()

View File

@@ -1,5 +1,25 @@
# Magisk Changelog
### v30.6 (2025.12.1)
- [MagiskInit] Revert a change that could result in bootloops
### v30.5 (2025.12.1)
- [General] Improve commandline argument parsing logic
- [resetprop] Properly support Android versions with property overrides
### v30.4 (2025.10.2)
- [MagiskSU] Fix several implementation bugs
### v30.3 (2025.9.29)
- [General] Support installing Magisk into vendor_boot partition
- [MagiskPolicy] Support new sepolicy binary format introduced in Android 16 QPR2
- [Core] Migrate much more code into Rust
- [MagiskSU] Fallback to older implementation when the kernel doesn't support zero userspace copy APIs
### v30.2 (2025.8.6)
- [Core] Fix an edge case breaking modules when overlayfs is involved

View File

@@ -10,3 +10,6 @@ target-dir = "../out/rust"
build-std = ["std", "panic_abort"]
build-std-features = ["panic_immediate_abort", "optimize_for_size"]
profile-rustflags = true
[profile.release]
rustflags = ["-Z", "location-detail=none", "-Z", "fmt-debug=none"]

View File

@@ -16,15 +16,12 @@ LOCAL_STATIC_LIBRARIES := \
LOCAL_SRC_FILES := \
core/applets.cpp \
core/magisk.cpp \
core/daemon.cpp \
core/scripting.cpp \
core/sqlite.cpp \
core/thread.cpp \
core/utils.cpp \
core/core-rs.cpp \
core/resetprop/resetprop.cpp \
core/resetprop/sys.cpp \
core/su/su.cpp \
core/su/connect.cpp \
core/zygisk/entry.cpp \
core/zygisk/module.cpp \
core/zygisk/hook.cpp \
@@ -82,14 +79,11 @@ include $(CLEAR_VARS)
LOCAL_MODULE := magiskboot
LOCAL_STATIC_LIBRARIES := \
libbase \
liblzma \
liblz4 \
libboot-rs
LOCAL_SRC_FILES := \
boot/main.cpp \
boot/bootimg.cpp \
boot/format.cpp \
boot/boot-rs.cpp
LOCAL_LDFLAGS := -static
@@ -127,7 +121,7 @@ LOCAL_STATIC_LIBRARIES := \
LOCAL_SRC_FILES := \
core/applet_stub.cpp \
core/resetprop/resetprop.cpp \
core/resetprop/sys.cpp \
core/core-rs.cpp
LOCAL_CFLAGS := -DAPPLET_STUB_MAIN=resetprop_main

664
native/src/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[workspace]
exclude = ["external"]
members = ["base", "boot", "core", "core/derive", "init", "sepolicy"]
members = ["base", "base/derive", "boot", "core", "init", "sepolicy"]
resolver = "2"
[workspace.package]
@@ -8,52 +8,56 @@ version = "0.0.0"
edition = "2024"
[workspace.dependencies]
base = { path = "base" }
derive = { path = "base/derive" }
magiskpolicy = { path = "sepolicy" }
cxx = { path = "external/cxx-rs" }
cxx-gen = { path = "external/cxx-rs/gen/lib" }
libc = "0.2.174"
cfg-if = "1.0.1"
libc = "0.2.177"
cfg-if = "1.0.4"
num-traits = "0.2.19"
num-derive = "0.4.2"
thiserror = "2.0.12"
thiserror = "2.0.17"
byteorder = "1.5.0"
size = "0.5.0"
bytemuck = "1.23.1"
bytemuck = "1.24.0"
fdt = "0.1.5"
const_format = "0.2.34"
const_format = "0.2.35"
bit-set = "0.8.0"
syn = "2.0.104"
quote = "1.0.40"
proc-macro2 = "1.0.95"
argh = { version = "0.1.13", default-features = false }
syn = "2.0.111"
quote = "1.0.42"
proc-macro2 = "1.0.103"
pb-rs = { version = "0.10.0", default-features = false }
quick-protobuf = "0.8.1"
flate2 = { version = "1.1.2", default-features = false }
bzip2 = { version = "0.6.0" }
zopfli = "0.8.2"
flate2 = { version = "1.1.5", default-features = false }
bzip2 = "0.6.1"
zopfli = "0.8.3"
lz4 = "1.28.1"
xz2 = "0.1.7"
lzma-rust2 = { version = "0.15.2", default-features = false }
nix = "0.30.1"
bitflags = "2.10.0"
# Rust crypto crates are tied together
sha1 = "0.11.0-rc.0"
sha2 = "0.11.0-rc.0"
digest = "0.11.0-rc.0"
p256 = "0.14.0-pre.9"
p384 = "0.14.0-pre.9"
p521 = "0.14.0-pre.9"
rsa = "0.10.0-rc.3"
x509-cert = "0.3.0-rc.1"
der = "0.8.0-rc.7"
sha1 = "0.11.0-rc.3"
sha2 = "0.11.0-rc.3"
digest = "0.11.0-rc.4"
p256 = "0.14.0-rc.1"
p384 = "0.14.0-rc.1"
p521 = "0.14.0-rc.1"
rsa = "0.10.0-rc.10"
x509-cert = "0.3.0-rc.2"
der = "0.8.0-rc.10"
[patch.crates-io]
pb-rs = { git = "https://github.com/tafia/quick-protobuf.git" }
quick-protobuf = { git = "https://github.com/tafia/quick-protobuf.git" }
pb-rs = { git = "https://github.com/topjohnwu/quick-protobuf.git" }
quick-protobuf = { git = "https://github.com/topjohnwu/quick-protobuf.git" }
lz4-sys = { path = "external/lz4-sys" }
lzma-sys = { path = "external/lzma-sys" }
[profile.dev]
opt-level = "z"
lto = "thin"
panic = "abort"
debug = "none"
[profile.release]
opt-level = "z"

View File

@@ -7,17 +7,12 @@ LOCAL_MODULE := libbase
LOCAL_C_INCLUDES := \
src/include \
$(LOCAL_PATH)/include \
src/external/cxx-rs/include \
out/generated
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_C_INCLUDES)
LOCAL_EXPORT_STATIC_LIBRARIES := libcxx
LOCAL_STATIC_LIBRARIES := libcxx
LOCAL_CFLAGS := -DRUST_CXX_NO_EXCEPTIONS
LOCAL_SRC_FILES := \
new.cpp \
files.cpp \
misc.cpp \
logging.cpp \
base.cpp \
base-rs.cpp \
../external/cxx-rs/src/cxx.cc
include $(BUILD_STATIC_LIBRARY)

View File

@@ -8,18 +8,19 @@ path = "lib.rs"
[features]
selinux = []
dyn_selinux = []
[build-dependencies]
cxx-gen = { workspace = true }
[dependencies]
derive = { workspace = true }
cxx = { workspace = true }
libc = { workspace = true }
cfg-if = { workspace = true }
thiserror = { workspace = true }
argh = { workspace = true }
bytemuck = { workspace = true }
num-traits = { workspace = true }
num-derive = { workspace = true }
const_format = { workspace = true }
nix = { workspace = true, features = ["fs", "mount", "user"] }
bitflags = { workspace = true }

1226
native/src/base/argh.rs Normal file

File diff suppressed because it is too large Load Diff

433
native/src/base/base.cpp Normal file
View File

@@ -0,0 +1,433 @@
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/prctl.h>
#include <sys/mman.h>
#include <android/log.h>
#include <fcntl.h>
#include <unistd.h>
#include <syscall.h>
#include <random>
#include <string>
#include <base.hpp>
#include <flags.h>
using namespace std;
#ifndef __call_bypassing_fortify
#define __call_bypassing_fortify(fn) (&fn)
#endif
// Override libc++ new implementation to optimize final build size
void* operator new(std::size_t s) { return std::malloc(s); }
void* operator new[](std::size_t s) { return std::malloc(s); }
void operator delete(void *p) { std::free(p); }
void operator delete[](void *p) { std::free(p); }
void* operator new(std::size_t s, const std::nothrow_t&) noexcept { return std::malloc(s); }
void* operator new[](std::size_t s, const std::nothrow_t&) noexcept { return std::malloc(s); }
void operator delete(void *p, const std::nothrow_t&) noexcept { std::free(p); }
void operator delete[](void *p, const std::nothrow_t&) noexcept { std::free(p); }
bool byte_view::contains(byte_view pattern) const {
return _buf != nullptr && memmem(_buf, _sz, pattern._buf, pattern._sz) != nullptr;
}
bool byte_view::operator==(byte_view rhs) const {
return _sz == rhs._sz && memcmp(_buf, rhs._buf, _sz) == 0;
}
void byte_data::swap(byte_data &o) {
std::swap(_buf, o._buf);
std::swap(_sz, o._sz);
}
rust::Vec<size_t> byte_data::patch(byte_view from, byte_view to) const {
rust::Vec<size_t> v;
if (_buf == nullptr)
return v;
auto p = _buf;
auto eof = _buf + _sz;
while (p < eof) {
p = static_cast<uint8_t *>(memmem(p, eof - p, from.data(), from.size()));
if (p == nullptr)
return v;
memset(p, 0, from.size());
memcpy(p, to.data(), to.size());
v.push_back(p - _buf);
p += from.size();
}
return v;
}
rust::Vec<size_t> mut_u8_patch(MutByteSlice buf, ByteSlice from, ByteSlice to) {
byte_data data(buf);
return data.patch(from, to);
}
int fork_dont_care() {
if (int pid = xfork()) {
waitpid(pid, nullptr, 0);
return pid;
} else if (xfork()) {
exit(0);
}
return 0;
}
int fork_no_orphan() {
int pid = xfork();
if (pid)
return pid;
prctl(PR_SET_PDEATHSIG, SIGKILL);
if (getppid() == 1)
exit(1);
return 0;
}
int exec_command(exec_t &exec) {
auto pipefd = array<int, 2>{-1, -1};
int outfd = -1;
if (exec.fd == -1) {
if (xpipe2(pipefd, O_CLOEXEC) == -1)
return -1;
outfd = pipefd[1];
} else if (exec.fd >= 0) {
outfd = exec.fd;
}
int pid = exec.fork();
if (pid < 0) {
close(pipefd[0]);
close(pipefd[1]);
return -1;
} else if (pid) {
if (exec.fd == -1) {
exec.fd = pipefd[0];
close(pipefd[1]);
}
return pid;
}
// Unblock all signals
sigset_t set;
sigfillset(&set);
pthread_sigmask(SIG_UNBLOCK, &set, nullptr);
if (outfd >= 0) {
xdup2(outfd, STDOUT_FILENO);
if (exec.err)
xdup2(outfd, STDERR_FILENO);
close(outfd);
}
// Call the pre-exec callback
if (exec.pre_exec)
exec.pre_exec();
execve(exec.argv[0], (char **) exec.argv, environ);
PLOGE("execve %s", exec.argv[0]);
exit(-1);
}
int exec_command_sync(exec_t &exec) {
int pid = exec_command(exec);
if (pid < 0)
return -1;
int status;
waitpid(pid, &status, 0);
return WEXITSTATUS(status);
}
int new_daemon_thread(thread_entry entry, void *arg) {
pthread_t thread;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
errno = pthread_create(&thread, &attr, entry, arg);
if (errno) {
PLOGE("pthread_create");
}
return errno;
}
static char *argv0;
static size_t name_len;
void init_argv0(int argc, char **argv) {
argv0 = argv[0];
name_len = (argv[argc - 1] - argv[0]) + strlen(argv[argc - 1]) + 1;
}
void set_nice_name(Utf8CStr name) {
memset(argv0, 0, name_len);
strscpy(argv0, name.c_str(), name_len);
prctl(PR_SET_NAME, name.c_str());
}
template<typename T, int base>
static T parse_num(string_view s) {
T val = 0;
for (char c : s) {
if (isdigit(c)) {
c -= '0';
} else if (base > 10 && isalpha(c)) {
c -= isupper(c) ? 'A' - 10 : 'a' - 10;
} else {
return -1;
}
if (c >= base) {
return -1;
}
val *= base;
val += c;
}
return val;
}
/*
* Bionic's atoi runs through strtol().
* Use our own implementation for faster conversion.
*/
int parse_int(string_view s) {
return parse_num<int, 10>(s);
}
uint32_t parse_uint32_hex(string_view s) {
return parse_num<uint32_t, 16>(s);
}
int switch_mnt_ns(int pid) {
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..
// Switch to its namespace
ret = xsetns(fd, 0);
close(fd);
}
return ret;
}
string &replace_all(string &str, string_view from, string_view to) {
size_t pos = 0;
while((pos = str.find(from, pos)) != string::npos) {
str.replace(pos, from.length(), to);
pos += to.length();
}
return str;
}
template <typename T>
static auto split_impl(string_view s, string_view delims) {
vector<T> result;
size_t base = 0;
size_t found;
while (true) {
found = s.find_first_of(delims, base);
result.emplace_back(s.substr(base, found - base));
if (found == string::npos)
break;
base = found + 1;
}
return result;
}
vector<string> split(string_view s, string_view delims) {
return split_impl<string>(s, delims);
}
#undef vsnprintf
int vssprintf(char *dest, size_t size, const char *fmt, va_list ap) {
if (size > 0) {
*dest = 0;
return std::min(vsnprintf(dest, size, fmt, ap), (int) size - 1);
}
return -1;
}
int ssprintf(char *dest, size_t size, const char *fmt, ...) {
va_list va;
va_start(va, fmt);
int r = vssprintf(dest, size, fmt, va);
va_end(va);
return r;
}
#undef strlcpy
size_t strscpy(char *dest, const char *src, size_t size) {
return std::min(strlcpy(dest, src, size), size - 1);
}
#undef vsnprintf
static int fmt_and_log_with_rs(LogLevel level, const char *fmt, va_list ap) {
constexpr int sz = 4096;
char buf[sz];
buf[0] = '\0';
// Fortify logs when a fatal error occurs. Do not run through fortify again
int len = std::min(__call_bypassing_fortify(vsnprintf)(buf, sz, fmt, ap), sz - 1);
log_with_rs(level, Utf8CStr(buf, len + 1));
return len;
}
// Used to override external C library logging
extern "C" int magisk_log_print(int prio, const char *tag, const char *fmt, ...) {
LogLevel level;
switch (prio) {
case ANDROID_LOG_DEBUG:
level = LogLevel::Debug;
break;
case ANDROID_LOG_INFO:
level = LogLevel::Info;
break;
case ANDROID_LOG_WARN:
level = LogLevel::Warn;
break;
case ANDROID_LOG_ERROR:
level = LogLevel::Error;
break;
default:
return 0;
}
char fmt_buf[4096];
auto len = strscpy(fmt_buf, tag, sizeof(fmt_buf) - 1);
// Prevent format specifications in the tag
std::replace(fmt_buf, fmt_buf + len, '%', '_');
len = ssprintf(fmt_buf + len, sizeof(fmt_buf) - len - 1, ": %s", fmt) + len;
// Ensure the fmt string always ends with newline
if (fmt_buf[len - 1] != '\n') {
fmt_buf[len] = '\n';
fmt_buf[len + 1] = '\0';
}
va_list argv;
va_start(argv, fmt);
int ret = fmt_and_log_with_rs(level, fmt_buf, argv);
va_end(argv);
return ret;
}
#define LOG_BODY(level) \
va_list argv; \
va_start(argv, fmt); \
fmt_and_log_with_rs(LogLevel::level, fmt, argv); \
va_end(argv); \
// LTO will optimize out the NOP function
#if MAGISK_DEBUG
void LOGD(const char *fmt, ...) { LOG_BODY(Debug) }
#else
void LOGD(const char *fmt, ...) {}
#endif
void LOGI(const char *fmt, ...) { LOG_BODY(Info) }
void LOGW(const char *fmt, ...) { LOG_BODY(Warn) }
void LOGE(const char *fmt, ...) { LOG_BODY(Error) }
// Export raw symbol to fortify compat
extern "C" void __vloge(const char* fmt, va_list ap) {
fmt_and_log_with_rs(LogLevel::Error, fmt, ap);
}
string full_read(int fd) {
string str;
char buf[4096];
for (ssize_t len; (len = xread(fd, buf, sizeof(buf))) > 0;)
str.insert(str.end(), buf, buf + len);
return str;
}
string full_read(const char *filename) {
string str;
if (int fd = xopen(filename, O_RDONLY | O_CLOEXEC); fd >= 0) {
str = full_read(fd);
close(fd);
}
return str;
}
void write_zero(int fd, size_t size) {
char buf[4096] = {0};
size_t len;
while (size > 0) {
len = sizeof(buf) > size ? size : sizeof(buf);
write(fd, buf, len);
size -= len;
}
}
sDIR make_dir(DIR *dp) {
return sDIR(dp, [](DIR *dp){ return dp ? closedir(dp) : 1; });
}
sFILE make_file(FILE *fp) {
return sFILE(fp, [](FILE *fp){ return fp ? fclose(fp) : 1; });
}
mmap_data::mmap_data(const char *name, bool rw) {
auto slice = rust::map_file(name, rw);
if (!slice.empty()) {
_buf = slice.data();
_sz = slice.size();
}
}
mmap_data::mmap_data(int dirfd, const char *name, bool rw) {
auto slice = rust::map_file_at(dirfd, name, rw);
if (!slice.empty()) {
_buf = slice.data();
_sz = slice.size();
}
}
mmap_data::mmap_data(int fd, size_t sz, bool rw) {
auto slice = rust::map_fd(fd, sz, rw);
if (!slice.empty()) {
_buf = slice.data();
_sz = slice.size();
}
}
mmap_data::~mmap_data() {
if (_buf)
munmap(_buf, _sz);
}
string resolve_preinit_dir(const char *base_dir) {
string dir = base_dir;
if (access((dir + "/unencrypted").data(), F_OK) == 0) {
dir += "/unencrypted/magisk";
} else if (access((dir + "/adb").data(), F_OK) == 0) {
dir += "/adb";
} else if (access((dir + "/watchdog").data(), F_OK) == 0) {
dir += "/watchdog/magisk";
} else {
dir += "/magisk";
}
return dir;
}
// FFI for Utf8CStr
extern "C" void cxx$utf8str$new(Utf8CStr *self, const void *s, size_t len);
extern "C" const char *cxx$utf8str$ptr(const Utf8CStr *self);
extern "C" size_t cxx$utf8str$len(const Utf8CStr *self);
Utf8CStr::Utf8CStr(const char *s, size_t len) {
cxx$utf8str$new(this, s, len);
}
const char *Utf8CStr::data() const {
return cxx$utf8str$ptr(this);
}
size_t Utf8CStr::length() const {
return cxx$utf8str$len(this);
}

View File

@@ -1,13 +1,14 @@
use cxx::{ExternType, type_id};
use libc::c_char;
use nix::NixPath;
use std::borrow::Borrow;
use std::cmp::{Ordering, min};
use std::ffi::{CStr, FromBytesWithNulError, OsStr};
use std::ffi::{CStr, FromBytesUntilNulError, FromBytesWithNulError, OsStr};
use std::fmt::{Debug, Display, Formatter, Write};
use std::ops::Deref;
use std::os::unix::ffi::OsStrExt;
use std::path::{Path, PathBuf};
use std::str::Utf8Error;
use std::str::{FromStr, Utf8Error};
use std::{fmt, mem, slice, str};
use thiserror::Error;
@@ -56,7 +57,7 @@ pub mod buf {
}
#[inline(always)]
pub fn wrap(buf: &mut [u8]) -> Utf8CStrBufRef {
pub fn wrap(buf: &mut [u8]) -> Utf8CStrBufRef<'_> {
Utf8CStrBufRef::from(buf)
}
@@ -72,13 +73,6 @@ pub trait Utf8CStrBuf: Display + Write + AsRef<Utf8CStr> + Deref<Target = Utf8CS
// The length of the string without the terminating null character.
// assert_true(len <= capacity - 1)
fn len(&self) -> usize;
// Set the length of the string
//
// It is your responsibility to:
// 1. Null terminate the string by setting the next byte after len to null
// 2. Ensure len <= capacity - 1
// 3. All bytes from 0 to len is valid UTF-8 and does not contain null
unsafe fn set_len(&mut self, len: usize);
fn push_str(&mut self, s: &str) -> usize;
// The capacity of the internal buffer. The maximum string length this buffer can contain
// is capacity - 1, because the last byte is reserved for the terminating null character.
@@ -86,6 +80,10 @@ pub trait Utf8CStrBuf: Display + Write + AsRef<Utf8CStr> + Deref<Target = Utf8CS
fn clear(&mut self);
fn as_mut_ptr(&mut self) -> *mut c_char;
fn truncate(&mut self, new_len: usize);
// Rebuild the Utf8CStr based on the contents of the internal buffer. Required after any
// unsafe modifications directly though the pointer obtained from self.as_mut_ptr().
// If an error is returned, the internal buffer will be reset, resulting in an empty string.
fn rebuild(&mut self) -> Result<(), StrErr>;
#[inline(always)]
fn is_empty(&self) -> bool {
@@ -160,12 +158,6 @@ impl Utf8CStrBuf for Utf8CString {
self.0.len()
}
unsafe fn set_len(&mut self, len: usize) {
unsafe {
self.0.as_mut_vec().set_len(len);
}
}
fn push_str(&mut self, s: &str) -> usize {
self.0.push_str(s);
self.0.nul_terminate();
@@ -189,6 +181,32 @@ impl Utf8CStrBuf for Utf8CString {
self.0.truncate(new_len);
self.0.nul_terminate();
}
fn rebuild(&mut self) -> Result<(), StrErr> {
// Temporarily move the internal String out
let mut tmp = String::new();
mem::swap(&mut tmp, &mut self.0);
let (ptr, _, capacity) = tmp.into_raw_parts();
unsafe {
// Validate the entire buffer, including the unused part
let bytes = slice::from_raw_parts(ptr, capacity);
match Utf8CStr::from_bytes_until_nul(bytes) {
Ok(s) => {
// Move the String with the new length back
self.0 = String::from_raw_parts(ptr, s.len(), capacity);
}
Err(e) => {
// Move the String with 0 length back
self.0 = String::from_raw_parts(ptr, 0, capacity);
self.0.nul_terminate();
return Err(e);
}
}
}
Ok(())
}
}
impl From<String> for Utf8CString {
@@ -200,7 +218,18 @@ impl From<String> for Utf8CString {
impl From<&str> for Utf8CString {
fn from(value: &str) -> Self {
value.to_string().into()
let mut s = String::with_capacity(value.len() + 1);
s.push_str(value);
s.nul_terminate();
Utf8CString(s)
}
}
impl FromStr for Utf8CString {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(s.into())
}
}
@@ -255,7 +284,9 @@ pub enum StrErr {
#[error(transparent)]
Utf8Error(#[from] Utf8Error),
#[error(transparent)]
CStrError(#[from] FromBytesWithNulError),
CStrWithNullError(#[from] FromBytesWithNulError),
#[error(transparent)]
CStrUntilNullError(#[from] FromBytesUntilNulError),
#[error("argument is null")]
NullPointerError,
}
@@ -271,8 +302,12 @@ impl Utf8CStr {
Ok(unsafe { Self::from_bytes_unchecked(cstr.to_bytes_with_nul()) })
}
pub fn from_bytes(buf: &[u8]) -> Result<&Utf8CStr, StrErr> {
Self::from_cstr(CStr::from_bytes_with_nul(buf)?)
fn from_bytes_until_nul(bytes: &[u8]) -> Result<&Utf8CStr, StrErr> {
Self::from_cstr(CStr::from_bytes_until_nul(bytes)?)
}
pub fn from_bytes(bytes: &[u8]) -> Result<&Utf8CStr, StrErr> {
Self::from_cstr(CStr::from_bytes_with_nul(bytes)?)
}
pub fn from_string(s: &mut String) -> &Utf8CStr {
@@ -282,8 +317,8 @@ impl Utf8CStr {
}
#[inline(always)]
pub const unsafe fn from_bytes_unchecked(buf: &[u8]) -> &Utf8CStr {
unsafe { mem::transmute(buf) }
pub const unsafe fn from_bytes_unchecked(bytes: &[u8]) -> &Utf8CStr {
unsafe { mem::transmute(bytes) }
}
pub unsafe fn from_ptr<'a>(ptr: *const c_char) -> Result<&'a Utf8CStr, StrErr> {
@@ -300,6 +335,13 @@ impl Utf8CStr {
}
}
pub unsafe fn from_raw_parts<'a>(ptr: *const c_char, len: usize) -> &'a Utf8CStr {
unsafe {
let bytes = slice::from_raw_parts(ptr.cast(), len);
Self::from_bytes_unchecked(bytes)
}
}
#[inline(always)]
pub fn as_bytes_with_nul(&self) -> &[u8] {
&self.0
@@ -316,6 +358,11 @@ impl Utf8CStr {
unsafe { CStr::from_bytes_with_nul_unchecked(&self.0) }
}
#[inline(always)]
pub fn as_utf8_cstr(&self) -> &Utf8CStr {
self
}
#[inline(always)]
pub fn as_str(&self) -> &str {
// SAFETY: Already UTF-8 validated during construction
@@ -349,9 +396,29 @@ impl AsRef<Utf8CStr> for Utf8CStr {
}
}
impl NixPath for Utf8CStr {
#[inline(always)]
fn is_empty(&self) -> bool {
self.as_str().is_empty()
}
#[inline(always)]
fn len(&self) -> usize {
self.as_str().len()
}
#[inline(always)]
fn with_nix_path<T, F>(&self, f: F) -> nix::Result<T>
where
F: FnOnce(&CStr) -> T,
{
Ok(f(self.as_cstr()))
}
}
// Notice that we only implement ExternType on Utf8CStr *reference*
unsafe impl ExternType for &Utf8CStr {
type Id = type_id!("rust::Utf8CStr");
type Id = type_id!("Utf8CStr");
type Kind = cxx::kind::Trivial;
}
@@ -520,10 +587,6 @@ macro_rules! impl_cstr_buf {
self.used
}
#[inline(always)]
unsafe fn set_len(&mut self, len: usize) {
self.used = len;
}
#[inline(always)]
fn push_str(&mut self, s: &str) -> usize {
// SAFETY: self.used is guaranteed to always <= SIZE - 1
let dest = unsafe { self.buf.get_unchecked_mut(self.used..) };
@@ -551,6 +614,18 @@ macro_rules! impl_cstr_buf {
self.buf[new_len] = b'\0';
self.used = new_len;
}
fn rebuild(&mut self) -> Result<(), StrErr> {
// Validate the entire buffer, including the unused part
match Utf8CStr::from_bytes_until_nul(&self.buf) {
Ok(s) => self.used = s.len(),
Err(e) => {
self.used = 0;
self.buf[0] = b'\0';
return Err(e);
}
}
Ok(())
}
}
)*}
}

View File

@@ -1,23 +1,21 @@
// Functions in this file are only for exporting to C++, DO NOT USE IN RUST
use std::os::fd::{BorrowedFd, OwnedFd, RawFd};
use cfg_if::cfg_if;
use libc::{c_char, mode_t};
use std::fs::File;
use std::io::BufReader;
use std::mem::ManuallyDrop;
use std::ops::DerefMut;
use std::os::fd::{BorrowedFd, FromRawFd, OwnedFd, RawFd};
use crate::ffi::{FnBoolStr, FnBoolStrStr};
use crate::files::map_file_at;
pub(crate) use crate::xwrap::*;
use crate::{
CxxResultExt, Directory, OsResultStatic, Utf8CStr, clone_attr, cstr, fclone_attr, fd_path,
BufReadExt, Directory, LoggedResult, ResultExt, Utf8CStr, clone_attr, cstr, fclone_attr,
map_fd, map_file, slice_from_ptr,
};
pub(crate) fn fd_path_for_cxx(fd: RawFd, buf: &mut [u8]) -> isize {
let mut buf = cstr::buf::wrap(buf);
fd_path(fd, &mut buf)
.log_cxx()
.map_or(-1_isize, |_| buf.len() as isize)
}
use cfg_if::cfg_if;
use libc::{c_char, mode_t};
use nix::fcntl::OFlag;
#[unsafe(no_mangle)]
unsafe extern "C" fn canonical_path(path: *const c_char, buf: *mut u8, bufsz: usize) -> isize {
@@ -26,7 +24,7 @@ unsafe extern "C" fn canonical_path(path: *const c_char, buf: *mut u8, bufsz: us
Ok(path) => {
let mut buf = cstr::buf::wrap_ptr(buf, bufsz);
path.realpath(&mut buf)
.log_cxx()
.log()
.map_or(-1_isize, |_| buf.len() as isize)
}
Err(_) => -1,
@@ -56,20 +54,20 @@ unsafe extern "C" fn rm_rf_for_cxx(path: *const c_char) -> bool {
#[unsafe(no_mangle)]
unsafe extern "C" fn frm_rf(fd: OwnedFd) -> bool {
fn inner(fd: OwnedFd) -> OsResultStatic<()> {
fn inner(fd: OwnedFd) -> LoggedResult<()> {
Directory::try_from(fd)?.remove_all()
}
inner(fd).is_ok()
}
pub(crate) fn map_file_for_cxx(path: &Utf8CStr, rw: bool) -> &'static mut [u8] {
map_file(path, rw).log_cxx().unwrap_or(&mut [])
map_file(path, rw).log().unwrap_or(&mut [])
}
pub(crate) fn map_file_at_for_cxx(fd: RawFd, path: &Utf8CStr, rw: bool) -> &'static mut [u8] {
unsafe {
map_file_at(BorrowedFd::borrow_raw(fd), path, rw)
.log_cxx()
.log()
.unwrap_or(&mut [])
}
}
@@ -77,7 +75,7 @@ pub(crate) fn map_file_at_for_cxx(fd: RawFd, path: &Utf8CStr, rw: bool) -> &'sta
pub(crate) fn map_fd_for_cxx(fd: RawFd, sz: usize, rw: bool) -> &'static mut [u8] {
unsafe {
map_fd(BorrowedFd::borrow_raw(fd), sz, rw)
.log_cxx()
.log()
.unwrap_or(&mut [])
}
}
@@ -112,10 +110,10 @@ pub(crate) unsafe fn readlinkat(
#[unsafe(export_name = "cp_afc")]
unsafe extern "C" fn cp_afc_for_cxx(src: *const c_char, dest: *const c_char) -> bool {
unsafe {
if let Ok(src) = Utf8CStr::from_ptr(src) {
if let Ok(dest) = Utf8CStr::from_ptr(dest) {
return src.copy_to(dest).log_cxx().is_ok();
}
if let Ok(src) = Utf8CStr::from_ptr(src)
&& let Ok(dest) = Utf8CStr::from_ptr(dest)
{
return src.copy_to(dest).is_ok();
}
false
}
@@ -124,10 +122,10 @@ unsafe extern "C" fn cp_afc_for_cxx(src: *const c_char, dest: *const c_char) ->
#[unsafe(export_name = "mv_path")]
unsafe extern "C" fn mv_path_for_cxx(src: *const c_char, dest: *const c_char) -> bool {
unsafe {
if let Ok(src) = Utf8CStr::from_ptr(src) {
if let Ok(dest) = Utf8CStr::from_ptr(dest) {
return src.move_to(dest).log_cxx().is_ok();
}
if let Ok(src) = Utf8CStr::from_ptr(src)
&& let Ok(dest) = Utf8CStr::from_ptr(dest)
{
return src.move_to(dest).is_ok();
}
false
}
@@ -136,10 +134,10 @@ unsafe extern "C" fn mv_path_for_cxx(src: *const c_char, dest: *const c_char) ->
#[unsafe(export_name = "link_path")]
unsafe extern "C" fn link_path_for_cxx(src: *const c_char, dest: *const c_char) -> bool {
unsafe {
if let Ok(src) = Utf8CStr::from_ptr(src) {
if let Ok(dest) = Utf8CStr::from_ptr(dest) {
return src.link_to(dest).log_cxx().is_ok();
}
if let Ok(src) = Utf8CStr::from_ptr(src)
&& let Ok(dest) = Utf8CStr::from_ptr(dest)
{
return src.link_to(dest).is_ok();
}
false
}
@@ -148,10 +146,10 @@ unsafe extern "C" fn link_path_for_cxx(src: *const c_char, dest: *const c_char)
#[unsafe(export_name = "clone_attr")]
unsafe extern "C" fn clone_attr_for_cxx(src: *const c_char, dest: *const c_char) -> bool {
unsafe {
if let Ok(src) = Utf8CStr::from_ptr(src) {
if let Ok(dest) = Utf8CStr::from_ptr(dest) {
return clone_attr(src, dest).log_cxx().is_ok();
}
if let Ok(src) = Utf8CStr::from_ptr(src)
&& let Ok(dest) = Utf8CStr::from_ptr(dest)
{
return clone_attr(src, dest).log().is_ok();
}
false
}
@@ -159,7 +157,7 @@ unsafe extern "C" fn clone_attr_for_cxx(src: *const c_char, dest: *const c_char)
#[unsafe(export_name = "fclone_attr")]
unsafe extern "C" fn fclone_attr_for_cxx(a: RawFd, b: RawFd) -> bool {
fclone_attr(a, b).log_cxx().is_ok()
fclone_attr(a, b).log().is_ok()
}
#[unsafe(export_name = "cxx$utf8str$new")]
@@ -178,3 +176,14 @@ unsafe extern "C" fn str_ptr(this: &&Utf8CStr) -> *const u8 {
unsafe extern "C" fn str_len(this: &&Utf8CStr) -> usize {
this.len()
}
pub(crate) fn parse_prop_file_rs(name: &Utf8CStr, f: &FnBoolStrStr) {
if let Ok(file) = name.open(OFlag::O_RDONLY) {
BufReader::new(file).for_each_prop(|key, value| f.call(key, value))
}
}
pub(crate) fn file_readline_for_cxx(fd: RawFd, f: &FnBoolStr) {
let mut fd = ManuallyDrop::new(unsafe { File::from_raw_fd(fd) });
BufReader::new(fd.deref_mut()).for_each_line(|line| f.call(Utf8CStr::from_string(line)));
}

View File

@@ -0,0 +1,185 @@
// Copyright (c) 2020 Google LLC All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
use proc_macro2::{Span, TokenStream};
use quote::ToTokens;
use std::cell::RefCell;
/// A type for collecting procedural macro errors.
#[derive(Default)]
pub struct Errors {
errors: RefCell<Vec<syn::Error>>,
}
/// Produce functions to expect particular literals in `syn::Expr`
macro_rules! expect_lit_fn {
($(($fn_name:ident, $syn_type:ident, $variant:ident, $lit_name:literal),)*) => {
$(
pub fn $fn_name<'a>(&self, e: &'a syn::Expr) -> Option<&'a syn::$syn_type> {
if let syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::$variant(inner), .. }) = e {
Some(inner)
} else {
self.unexpected_lit($lit_name, e);
None
}
}
)*
}
}
/// Produce functions to expect particular variants of `syn::Meta`
macro_rules! expect_meta_fn {
($(($fn_name:ident, $syn_type:ident, $variant:ident, $meta_name:literal),)*) => {
$(
pub fn $fn_name<'a>(&self, meta: &'a syn::Meta) -> Option<&'a syn::$syn_type> {
if let syn::Meta::$variant(inner) = meta {
Some(inner)
} else {
self.unexpected_meta($meta_name, meta);
None
}
}
)*
}
}
impl Errors {
/// Issue an error like:
///
/// Duplicate foo attribute
/// First foo attribute here
pub fn duplicate_attrs(
&self,
attr_kind: &str,
first: &impl syn::spanned::Spanned,
second: &impl syn::spanned::Spanned,
) {
self.duplicate_attrs_inner(attr_kind, first.span(), second.span())
}
fn duplicate_attrs_inner(&self, attr_kind: &str, first: Span, second: Span) {
self.err_span(second, &["Duplicate ", attr_kind, " attribute"].concat());
self.err_span(first, &["First ", attr_kind, " attribute here"].concat());
}
expect_lit_fn![
(expect_lit_str, LitStr, Str, "string"),
(expect_lit_char, LitChar, Char, "character"),
(expect_lit_int, LitInt, Int, "integer"),
];
expect_meta_fn![
(expect_meta_word, Path, Path, "path"),
(expect_meta_list, MetaList, List, "list"),
(
expect_meta_name_value,
MetaNameValue,
NameValue,
"name-value pair"
),
];
fn unexpected_lit(&self, expected: &str, found: &syn::Expr) {
fn lit_kind(lit: &syn::Lit) -> &'static str {
use syn::Lit::{Bool, Byte, ByteStr, Char, Float, Int, Str, Verbatim};
match lit {
Str(_) => "string",
ByteStr(_) => "bytestring",
Byte(_) => "byte",
Char(_) => "character",
Int(_) => "integer",
Float(_) => "float",
Bool(_) => "boolean",
Verbatim(_) => "unknown (possibly extra-large integer)",
_ => "unknown literal kind",
}
}
if let syn::Expr::Lit(syn::ExprLit { lit, .. }) = found {
self.err(
found,
&[
"Expected ",
expected,
" literal, found ",
lit_kind(lit),
" literal",
]
.concat(),
)
} else {
self.err(
found,
&[
"Expected ",
expected,
" literal, found non-literal expression.",
]
.concat(),
)
}
}
fn unexpected_meta(&self, expected: &str, found: &syn::Meta) {
fn meta_kind(meta: &syn::Meta) -> &'static str {
use syn::Meta::{List, NameValue, Path};
match meta {
Path(_) => "path",
List(_) => "list",
NameValue(_) => "name-value pair",
}
}
self.err(
found,
&[
"Expected ",
expected,
" attribute, found ",
meta_kind(found),
" attribute",
]
.concat(),
)
}
/// Issue an error relating to a particular `Spanned` structure.
pub fn err(&self, spanned: &impl syn::spanned::Spanned, msg: &str) {
self.err_span(spanned.span(), msg);
}
/// Issue an error relating to a particular `Span`.
pub fn err_span(&self, span: Span, msg: &str) {
self.push(syn::Error::new(span, msg));
}
/// Issue an error spanning over the given syntax tree node.
pub fn err_span_tokens<T: ToTokens>(&self, tokens: T, msg: &str) {
self.push(syn::Error::new_spanned(tokens, msg));
}
/// Push a `syn::Error` onto the list of errors to issue.
pub fn push(&self, err: syn::Error) {
self.errors.borrow_mut().push(err);
}
/// Convert a `syn::Result` to an `Option`, logging the error if present.
pub fn ok<T>(&self, r: syn::Result<T>) -> Option<T> {
match r {
Ok(v) => Some(v),
Err(e) => {
self.push(e);
None
}
}
}
}
impl ToTokens for Errors {
/// Convert the errors into tokens that, when emit, will cause
/// the user of the macro to receive compiler errors.
fn to_tokens(&self, tokens: &mut TokenStream) {
tokens.extend(self.errors.borrow().iter().map(|e| e.to_compile_error()));
}
}

View File

@@ -0,0 +1,912 @@
// Copyright (c) 2020 Google LLC All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
use syn::ext::IdentExt as _;
/// Implementation of the `FromArgs` and `argh(...)` derive attributes.
///
/// For more thorough documentation, see the `argh` crate itself.
extern crate proc_macro;
use errors::Errors;
use parse_attrs::{FieldAttrs, FieldKind, TypeAttrs, check_long_name};
use proc_macro2::{Span, TokenStream};
use quote::{ToTokens, quote, quote_spanned};
use std::collections::HashMap;
use std::str::FromStr;
use syn::spanned::Spanned;
use syn::{GenericArgument, LitStr, PathArguments, Type};
mod errors;
mod parse_attrs;
/// Transform the input into a token stream containing any generated implementations,
/// as well as all errors that occurred.
pub(crate) fn impl_from_args(input: &syn::DeriveInput) -> TokenStream {
let errors = &Errors::default();
let type_attrs = &TypeAttrs::parse(errors, input);
let mut output_tokens = match &input.data {
syn::Data::Struct(ds) => {
impl_from_args_struct(errors, &input.ident, type_attrs, &input.generics, ds)
}
syn::Data::Enum(de) => {
impl_from_args_enum(errors, &input.ident, type_attrs, &input.generics, de)
}
syn::Data::Union(_) => {
errors.err(input, "`#[derive(FromArgs)]` cannot be applied to unions");
TokenStream::new()
}
};
errors.to_tokens(&mut output_tokens);
output_tokens
}
/// The kind of optionality a parameter has.
enum Optionality {
None,
Defaulted(TokenStream),
Optional,
Repeating,
DefaultedRepeating(TokenStream),
}
impl PartialEq<Optionality> for Optionality {
fn eq(&self, other: &Optionality) -> bool {
use Optionality::*;
// NB: (Defaulted, Defaulted) can't contain the same token streams
matches!((self, other), (Optional, Optional) | (Repeating, Repeating))
}
}
impl Optionality {
/// Whether or not this is `Optionality::None`
fn is_required(&self) -> bool {
matches!(self, Optionality::None)
}
}
/// A field of a `#![derive(FromArgs)]` struct with attributes and some other
/// notable metadata appended.
struct StructField<'a> {
/// The original parsed field
field: &'a syn::Field,
/// The parsed attributes of the field
attrs: FieldAttrs,
/// The field name. This is contained optionally inside `field`,
/// but is duplicated non-optionally here to indicate that all field that
/// have reached this point must have a field name, and it no longer
/// needs to be unwrapped.
name: &'a syn::Ident,
/// Similar to `name` above, this is contained optionally inside `FieldAttrs`,
/// but here is fully present to indicate that we only have to consider fields
/// with a valid `kind` at this point.
kind: FieldKind,
// If `field.ty` is `Vec<T>` or `Option<T>`, this is `T`, otherwise it's `&field.ty`.
// This is used to enable consistent parsing code between optional and non-optional
// keyed and subcommand fields.
ty_without_wrapper: &'a syn::Type,
// Whether the field represents an optional value, such as an `Option` subcommand field
// or an `Option` or `Vec` keyed argument, or if it has a `default`.
optionality: Optionality,
// The `--`-prefixed name of the option, if one exists.
long_name: Option<String>,
}
impl<'a> StructField<'a> {
/// Attempts to parse a field of a `#[derive(FromArgs)]` struct, pulling out the
/// fields required for code generation.
fn new(errors: &Errors, field: &'a syn::Field, attrs: FieldAttrs) -> Option<Self> {
let name = field.ident.as_ref().expect("missing ident for named field");
// Ensure that one "kind" is present (switch, option, subcommand, positional)
let kind = if let Some(field_type) = &attrs.field_type {
field_type.kind
} else {
errors.err(
field,
concat!(
"Missing `argh` field kind attribute.\n",
"Expected one of: `switch`, `option`, `remaining`, `subcommand`, `positional`",
),
);
return None;
};
// Parse out whether a field is optional (`Option` or `Vec`).
let optionality;
let ty_without_wrapper;
match kind {
FieldKind::Switch => {
if !ty_expect_switch(errors, &field.ty) {
return None;
}
optionality = Optionality::Optional;
ty_without_wrapper = &field.ty;
}
FieldKind::Option | FieldKind::Positional => {
if let Some(default) = &attrs.default {
let tokens = match TokenStream::from_str(&default.value()) {
Ok(tokens) => tokens,
Err(_) => {
errors.err(&default, "Invalid tokens: unable to lex `default` value");
return None;
}
};
// Set the span of the generated tokens to the string literal
let tokens: TokenStream = tokens
.into_iter()
.map(|mut tree| {
tree.set_span(default.span());
tree
})
.collect();
let inner = if let Some(x) = ty_inner(&["Vec"], &field.ty) {
optionality = Optionality::DefaultedRepeating(tokens);
x
} else {
optionality = Optionality::Defaulted(tokens);
&field.ty
};
ty_without_wrapper = inner;
} else {
let mut inner = None;
optionality = if let Some(x) = ty_inner(&["Option"], &field.ty) {
inner = Some(x);
Optionality::Optional
} else if let Some(x) = ty_inner(&["Vec"], &field.ty) {
inner = Some(x);
Optionality::Repeating
} else {
Optionality::None
};
ty_without_wrapper = inner.unwrap_or(&field.ty);
}
}
FieldKind::SubCommand => {
let inner = ty_inner(&["Option"], &field.ty);
optionality = if inner.is_some() {
Optionality::Optional
} else {
Optionality::None
};
ty_without_wrapper = inner.unwrap_or(&field.ty);
}
}
// Determine the "long" name of options and switches.
// Defaults to the kebab-cased field name if `#[argh(long = "...")]` is omitted.
// If `#[argh(long = none)]` is explicitly set, no long name will be set.
let long_name = match kind {
FieldKind::Switch | FieldKind::Option => {
let long_name = match &attrs.long {
None => {
let kebab_name = to_kebab_case(&name.unraw().to_string());
check_long_name(errors, name, &kebab_name);
Some(kebab_name)
}
Some(None) => None,
Some(Some(long)) => Some(long.value()),
}
.map(|long_name| {
if long_name == "help" {
errors.err(field, "Custom `--help` flags are not supported.");
}
format!("--{}", long_name)
});
if let (None, None) = (&attrs.short, &long_name) {
errors.err(field, "At least one of `short` or `long` has to be set.")
};
long_name
}
FieldKind::SubCommand | FieldKind::Positional => None,
};
Some(StructField {
field,
attrs,
kind,
optionality,
ty_without_wrapper,
name,
long_name,
})
}
pub(crate) fn positional_arg_name(&self) -> String {
self.attrs
.arg_name
.as_ref()
.map(LitStr::value)
.unwrap_or_else(|| self.name.to_string().trim_matches('_').to_owned())
}
fn option_arg_name(&self) -> String {
match (&self.attrs.short, &self.long_name) {
(None, None) => unreachable!("short and long cannot both be None"),
(Some(short), None) => format!("-{}", short.value()),
(None, Some(long)) => long.clone(),
(Some(short), Some(long)) => format!("-{},{long}", short.value()),
}
}
}
fn to_kebab_case(s: &str) -> String {
let words = s.split('_').filter(|word| !word.is_empty());
let mut res = String::with_capacity(s.len());
for word in words {
if !res.is_empty() {
res.push('-')
}
res.push_str(word)
}
res
}
/// Implements `FromArgs` and `TopLevelCommand` or `SubCommand` for a `#[derive(FromArgs)]` struct.
fn impl_from_args_struct(
errors: &Errors,
name: &syn::Ident,
type_attrs: &TypeAttrs,
generic_args: &syn::Generics,
ds: &syn::DataStruct,
) -> TokenStream {
let fields = match &ds.fields {
syn::Fields::Named(fields) => fields,
syn::Fields::Unnamed(_) => {
errors.err(
&ds.struct_token,
"`#![derive(FromArgs)]` is not currently supported on tuple structs",
);
return TokenStream::new();
}
syn::Fields::Unit => {
errors.err(
&ds.struct_token,
"#![derive(FromArgs)]` cannot be applied to unit structs",
);
return TokenStream::new();
}
};
let fields: Vec<_> = fields
.named
.iter()
.filter_map(|field| {
let attrs = FieldAttrs::parse(errors, field);
StructField::new(errors, field, attrs)
})
.collect();
ensure_unique_names(errors, &fields);
ensure_only_trailing_positionals_are_optional(errors, &fields);
let impl_span = Span::call_site();
let from_args_method = impl_from_args_struct_from_args(errors, type_attrs, &fields);
let top_or_sub_cmd_impl = top_or_sub_cmd_impl(errors, name, type_attrs, generic_args);
let (impl_generics, ty_generics, where_clause) = generic_args.split_for_impl();
let trait_impl = quote_spanned! { impl_span =>
#[automatically_derived]
impl #impl_generics argh::FromArgs for #name #ty_generics #where_clause {
#from_args_method
}
#top_or_sub_cmd_impl
};
trait_impl
}
fn impl_from_args_struct_from_args<'a>(
errors: &Errors,
type_attrs: &TypeAttrs,
fields: &'a [StructField<'a>],
) -> TokenStream {
let init_fields = declare_local_storage_for_from_args_fields(fields);
let unwrap_fields = unwrap_from_args_fields(fields);
let positional_fields: Vec<&StructField<'_>> = fields
.iter()
.filter(|field| field.kind == FieldKind::Positional)
.collect();
let positional_field_idents = positional_fields.iter().map(|field| &field.field.ident);
let positional_field_names = positional_fields.iter().map(|field| field.name.to_string());
let last_positional_is_repeating = positional_fields
.last()
.map(|field| field.optionality == Optionality::Repeating)
.unwrap_or(false);
let last_positional_is_greedy = positional_fields
.last()
.map(|field| field.kind == FieldKind::Positional && field.attrs.greedy.is_some())
.unwrap_or(false);
let flag_output_table = fields.iter().filter_map(|field| {
let field_name = &field.field.ident;
match field.kind {
FieldKind::Option => Some(quote! { argh::ParseStructOption::Value(&mut #field_name) }),
FieldKind::Switch => Some(quote! { argh::ParseStructOption::Flag(&mut #field_name) }),
FieldKind::SubCommand | FieldKind::Positional => None,
}
});
let flag_str_to_output_table_map = flag_str_to_output_table_map_entries(fields);
let mut subcommands_iter = fields
.iter()
.filter(|field| field.kind == FieldKind::SubCommand)
.fuse();
let subcommand: Option<&StructField<'_>> = subcommands_iter.next();
for dup_subcommand in subcommands_iter {
errors.duplicate_attrs(
"subcommand",
subcommand.unwrap().field,
dup_subcommand.field,
);
}
let impl_span = Span::call_site();
let missing_requirements_ident = syn::Ident::new("__missing_requirements", impl_span);
let append_missing_requirements =
append_missing_requirements(&missing_requirements_ident, fields);
let parse_subcommands = if let Some(subcommand) = subcommand {
let name = subcommand.name;
let ty = subcommand.ty_without_wrapper;
quote_spanned! { impl_span =>
Some(argh::ParseStructSubCommand {
subcommands: <#ty as argh::SubCommands>::COMMANDS,
dynamic_subcommands: &<#ty as argh::SubCommands>::dynamic_commands(),
parse_func: &mut |__command, __remaining_args| {
#name = Some(<#ty as argh::FromArgs>::from_args(__command, __remaining_args)?);
Ok(())
},
})
}
} else {
quote_spanned! { impl_span => None }
};
let help_triggers = get_help_triggers(type_attrs);
let method_impl = quote_spanned! { impl_span =>
fn from_args(__cmd_name: &[&str], __args: &[&str])
-> std::result::Result<Self, argh::EarlyExit>
{
#![allow(clippy::unwrap_in_result)]
#( #init_fields )*
argh::parse_struct_args(
__cmd_name,
__args,
argh::ParseStructOptions {
arg_to_slot: &[ #( #flag_str_to_output_table_map ,)* ],
slots: &mut [ #( #flag_output_table, )* ],
help_triggers: &[ #( #help_triggers ),* ],
},
argh::ParseStructPositionals {
positionals: &mut [
#(
argh::ParseStructPositional {
name: #positional_field_names,
slot: &mut #positional_field_idents as &mut dyn argh::ParseValueSlot,
},
)*
],
last_is_repeating: #last_positional_is_repeating,
last_is_greedy: #last_positional_is_greedy,
},
#parse_subcommands,
)?;
let mut #missing_requirements_ident = argh::MissingRequirements::default();
#(
#append_missing_requirements
)*
#missing_requirements_ident.err_on_any()?;
Ok(Self {
#( #unwrap_fields, )*
})
}
};
method_impl
}
/// get help triggers vector from type_attrs.help_triggers as a [`Vec<String>`]
///
/// Defaults to vec!["-h", "--help"] if type_attrs.help_triggers is None
fn get_help_triggers(type_attrs: &TypeAttrs) -> Vec<String> {
if type_attrs.is_subcommand.is_some() {
// Subcommands should never have any help triggers
Vec::new()
} else {
type_attrs.help_triggers.as_ref().map_or_else(
|| vec!["-h".to_string(), "--help".to_string()],
|s| {
s.iter()
.filter_map(|s| {
let trigger = s.value();
let trigger_trimmed = trigger.trim().to_owned();
if trigger_trimmed.is_empty() {
None
} else {
Some(trigger_trimmed)
}
})
.collect::<Vec<_>>()
},
)
}
}
/// Ensures that only trailing positional args are non-required.
fn ensure_only_trailing_positionals_are_optional(errors: &Errors, fields: &[StructField<'_>]) {
let mut first_non_required_span = None;
for field in fields {
if field.kind == FieldKind::Positional {
if let Some(first) = first_non_required_span
&& field.optionality.is_required()
{
errors.err_span(
first,
"Only trailing positional arguments may be `Option`, `Vec`, or defaulted.",
);
errors.err(
&field.field,
"Later non-optional positional argument declared here.",
);
return;
}
if !field.optionality.is_required() {
first_non_required_span = Some(field.field.span());
}
}
}
}
/// Ensures that only one short or long name is used.
fn ensure_unique_names(errors: &Errors, fields: &[StructField<'_>]) {
let mut seen_short_names = HashMap::new();
let mut seen_long_names = HashMap::new();
for field in fields {
if let Some(short_name) = &field.attrs.short {
let short_name = short_name.value();
if let Some(first_use_field) = seen_short_names.get(&short_name) {
errors.err_span_tokens(
first_use_field,
&format!(
"The short name of \"-{}\" was already used here.",
short_name
),
);
errors.err_span_tokens(field.field, "Later usage here.");
}
seen_short_names.insert(short_name, &field.field);
}
if let Some(long_name) = &field.long_name {
if let Some(first_use_field) = seen_long_names.get(&long_name) {
errors.err_span_tokens(
*first_use_field,
&format!("The long name of \"{}\" was already used here.", long_name),
);
errors.err_span_tokens(field.field, "Later usage here.");
}
seen_long_names.insert(long_name, field.field);
}
}
}
/// Implement `argh::TopLevelCommand` or `argh::SubCommand` as appropriate.
fn top_or_sub_cmd_impl(
errors: &Errors,
name: &syn::Ident,
type_attrs: &TypeAttrs,
generic_args: &syn::Generics,
) -> TokenStream {
let description = String::new();
let (impl_generics, ty_generics, where_clause) = generic_args.split_for_impl();
if type_attrs.is_subcommand.is_none() {
// Not a subcommand
quote! {
#[automatically_derived]
impl #impl_generics argh::TopLevelCommand for #name #ty_generics #where_clause {}
}
} else {
let empty_str = syn::LitStr::new("", Span::call_site());
let subcommand_name = type_attrs.name.as_ref().unwrap_or_else(|| {
errors.err(
name,
"`#[argh(name = \"...\")]` attribute is required for subcommands",
);
&empty_str
});
quote! {
#[automatically_derived]
impl #impl_generics argh::SubCommand for #name #ty_generics #where_clause {
const COMMAND: &'static argh::CommandInfo = &argh::CommandInfo {
name: #subcommand_name,
description: #description,
};
}
}
}
}
/// Declare a local slots to store each field in during parsing.
///
/// Most fields are stored in `Option<FieldType>` locals.
/// `argh(option)` fields are stored in a `ParseValueSlotTy` along with a
/// function that knows how to decode the appropriate value.
fn declare_local_storage_for_from_args_fields<'a>(
fields: &'a [StructField<'a>],
) -> impl Iterator<Item = TokenStream> + 'a {
fields.iter().map(|field| {
let field_name = &field.field.ident;
let field_type = &field.ty_without_wrapper;
// Wrap field types in `Option` if they aren't already `Option` or `Vec`-wrapped.
let field_slot_type = match field.optionality {
Optionality::Optional | Optionality::Repeating => (&field.field.ty).into_token_stream(),
Optionality::None | Optionality::Defaulted(_) => {
quote! { std::option::Option<#field_type> }
}
Optionality::DefaultedRepeating(_) => {
quote! { std::option::Option<std::vec::Vec<#field_type>> }
}
};
match field.kind {
FieldKind::Option | FieldKind::Positional => {
let from_str_fn = match &field.attrs.from_str_fn {
Some(from_str_fn) => from_str_fn.into_token_stream(),
None => {
quote! {
<#field_type as argh::FromArgValue>::from_arg_value
}
}
};
quote! {
let mut #field_name: argh::ParseValueSlotTy<#field_slot_type, #field_type>
= argh::ParseValueSlotTy {
slot: std::default::Default::default(),
parse_func: |_, value| { #from_str_fn(value) },
};
}
}
FieldKind::SubCommand => {
quote! { let mut #field_name: #field_slot_type = None; }
}
FieldKind::Switch => {
quote! { let mut #field_name: #field_slot_type = argh::Flag::default(); }
}
}
})
}
/// Unwrap non-optional fields and take options out of their tuple slots.
fn unwrap_from_args_fields<'a>(
fields: &'a [StructField<'a>],
) -> impl Iterator<Item = TokenStream> + 'a {
fields.iter().map(|field| {
let field_name = field.name;
match field.kind {
FieldKind::Option | FieldKind::Positional => match &field.optionality {
Optionality::None => quote! {
#field_name: #field_name.slot.unwrap()
},
Optionality::Optional | Optionality::Repeating => {
quote! { #field_name: #field_name.slot }
}
Optionality::Defaulted(tokens) | Optionality::DefaultedRepeating(tokens) => {
quote! {
#field_name: #field_name.slot.unwrap_or_else(|| #tokens)
}
}
},
FieldKind::Switch => field_name.into_token_stream(),
FieldKind::SubCommand => match field.optionality {
Optionality::None => quote! { #field_name: #field_name.unwrap() },
Optionality::Optional | Optionality::Repeating => field_name.into_token_stream(),
Optionality::Defaulted(_) | Optionality::DefaultedRepeating(_) => unreachable!(),
},
}
})
}
/// Entries of tokens like `("--some-flag-key", 5)` that map from a flag key string
/// to an index in the output table.
fn flag_str_to_output_table_map_entries<'a>(fields: &'a [StructField<'a>]) -> Vec<TokenStream> {
let mut flag_str_to_output_table_map = vec![];
for (i, field) in fields.iter().enumerate() {
if let Some(short) = &field.attrs.short {
let short = format!("-{}", short.value());
flag_str_to_output_table_map.push(quote! { (#short, #i) });
}
if let Some(long) = &field.long_name {
flag_str_to_output_table_map.push(quote! { (#long, #i) });
}
}
flag_str_to_output_table_map
}
/// For each non-optional field, add an entry to the `argh::MissingRequirements`.
fn append_missing_requirements<'a>(
// missing_requirements_ident
mri: &syn::Ident,
fields: &'a [StructField<'a>],
) -> impl Iterator<Item = TokenStream> + 'a {
let mri = mri.clone();
fields
.iter()
.filter(|f| f.optionality.is_required())
.map(move |field| {
let field_name = field.name;
match field.kind {
FieldKind::Switch => unreachable!("switches are always optional"),
FieldKind::Positional => {
let name = field.positional_arg_name();
quote! {
if #field_name.slot.is_none() {
#mri.missing_positional_arg(#name)
}
}
}
FieldKind::Option => {
let name = field.option_arg_name();
quote! {
if #field_name.slot.is_none() {
#mri.missing_option(#name)
}
}
}
FieldKind::SubCommand => {
let ty = field.ty_without_wrapper;
quote! {
if #field_name.is_none() {
#mri.missing_subcommands(
<#ty as argh::SubCommands>::COMMANDS
.iter()
.cloned()
.chain(
<#ty as argh::SubCommands>::dynamic_commands()
.iter()
.copied()
),
)
}
}
}
}
})
}
/// Require that a type can be a `switch`.
/// Throws an error for all types except booleans and integers
fn ty_expect_switch(errors: &Errors, ty: &syn::Type) -> bool {
fn ty_can_be_switch(ty: &syn::Type) -> bool {
if let syn::Type::Path(path) = ty {
if path.qself.is_some() {
return false;
}
if path.path.segments.len() != 1 {
return false;
}
let ident = &path.path.segments[0].ident;
// `Option<bool>` can be used as a `switch`.
if ident == "Option"
&& let PathArguments::AngleBracketed(args) = &path.path.segments[0].arguments
&& let GenericArgument::Type(Type::Path(p)) = &args.args[0]
&& p.path.segments[0].ident == "bool"
{
return true;
}
[
"bool", "u8", "u16", "u32", "u64", "u128", "i8", "i16", "i32", "i64", "i128",
]
.iter()
.any(|path| ident == path)
} else {
false
}
}
let res = ty_can_be_switch(ty);
if !res {
errors.err(
ty,
"switches must be of type `bool`, `Option<bool>`, or integer type",
);
}
res
}
/// Returns `Some(T)` if a type is `wrapper_name<T>` for any `wrapper_name` in `wrapper_names`.
fn ty_inner<'a>(wrapper_names: &[&str], ty: &'a syn::Type) -> Option<&'a syn::Type> {
if let syn::Type::Path(path) = ty {
if path.qself.is_some() {
return None;
}
// Since we only check the last path segment, it isn't necessarily the case that
// we're referring to `std::vec::Vec` or `std::option::Option`, but there isn't
// a fool proof way to check these since name resolution happens after macro expansion,
// so this is likely "good enough" (so long as people don't have their own types called
// `Option` or `Vec` that take one generic parameter they're looking to parse).
let last_segment = path.path.segments.last()?;
if !wrapper_names.iter().any(|name| last_segment.ident == *name) {
return None;
}
if let syn::PathArguments::AngleBracketed(gen_args) = &last_segment.arguments {
let generic_arg = gen_args.args.first()?;
if let syn::GenericArgument::Type(ty) = &generic_arg {
return Some(ty);
}
}
}
None
}
/// Implements `FromArgs` and `SubCommands` for a `#![derive(FromArgs)]` enum.
fn impl_from_args_enum(
errors: &Errors,
name: &syn::Ident,
type_attrs: &TypeAttrs,
generic_args: &syn::Generics,
de: &syn::DataEnum,
) -> TokenStream {
parse_attrs::check_enum_type_attrs(errors, type_attrs, &de.enum_token.span);
// An enum variant like `<name>(<ty>)`
struct SubCommandVariant<'a> {
name: &'a syn::Ident,
ty: &'a syn::Type,
}
let mut dynamic_type_and_variant = None;
let variants: Vec<SubCommandVariant<'_>> = de
.variants
.iter()
.filter_map(|variant| {
let name = &variant.ident;
let ty = enum_only_single_field_unnamed_variants(errors, &variant.fields)?;
if parse_attrs::VariantAttrs::parse(errors, variant)
.is_dynamic
.is_some()
{
if dynamic_type_and_variant.is_some() {
errors.err(variant, "Only one variant can have the `dynamic` attribute");
}
dynamic_type_and_variant = Some((ty, name));
None
} else {
Some(SubCommandVariant { name, ty })
}
})
.collect();
let name_repeating = std::iter::repeat(name.clone());
let variant_ty = variants.iter().map(|x| x.ty).collect::<Vec<_>>();
let variant_names = variants.iter().map(|x| x.name).collect::<Vec<_>>();
let dynamic_from_args =
dynamic_type_and_variant
.as_ref()
.map(|(dynamic_type, dynamic_variant)| {
quote! {
if let Some(result) = <#dynamic_type as argh::DynamicSubCommand>::try_from_args(
command_name, args) {
return result.map(#name::#dynamic_variant);
}
}
});
let dynamic_commands = dynamic_type_and_variant.as_ref().map(|(dynamic_type, _)| {
quote! {
fn dynamic_commands() -> &'static [&'static argh::CommandInfo] {
<#dynamic_type as argh::DynamicSubCommand>::commands()
}
}
});
let (impl_generics, ty_generics, where_clause) = generic_args.split_for_impl();
quote! {
impl #impl_generics argh::FromArgs for #name #ty_generics #where_clause {
fn from_args(command_name: &[&str], args: &[&str])
-> std::result::Result<Self, argh::EarlyExit>
{
let subcommand_name = if let Some(subcommand_name) = command_name.last() {
*subcommand_name
} else {
return Err(argh::EarlyExit::from("no subcommand name".to_owned()));
};
#(
if subcommand_name == <#variant_ty as argh::SubCommand>::COMMAND.name {
return Ok(#name_repeating::#variant_names(
<#variant_ty as argh::FromArgs>::from_args(command_name, args)?
));
}
)*
#dynamic_from_args
Err(argh::EarlyExit::from("no subcommand matched".to_owned()))
}
}
impl #impl_generics argh::SubCommands for #name #ty_generics #where_clause {
const COMMANDS: &'static [&'static argh::CommandInfo] = &[#(
<#variant_ty as argh::SubCommand>::COMMAND,
)*];
#dynamic_commands
}
}
}
/// Returns `Some(Bar)` if the field is a single-field unnamed variant like `Foo(Bar)`.
/// Otherwise, generates an error.
fn enum_only_single_field_unnamed_variants<'a>(
errors: &Errors,
variant_fields: &'a syn::Fields,
) -> Option<&'a syn::Type> {
macro_rules! with_enum_suggestion {
($help_text:literal) => {
concat!(
$help_text,
"\nInstead, use a variant with a single unnamed field for each subcommand:\n",
" enum MyCommandEnum {\n",
" SubCommandOne(SubCommandOne),\n",
" SubCommandTwo(SubCommandTwo),\n",
" }",
)
};
}
match variant_fields {
syn::Fields::Named(fields) => {
errors.err(
fields,
with_enum_suggestion!(
"`#![derive(FromArgs)]` `enum`s do not support variants with named fields."
),
);
None
}
syn::Fields::Unit => {
errors.err(
variant_fields,
with_enum_suggestion!(
"`#![derive(FromArgs)]` does not support `enum`s with no variants."
),
);
None
}
syn::Fields::Unnamed(fields) => {
if fields.unnamed.len() != 1 {
errors.err(
fields,
with_enum_suggestion!(
"`#![derive(FromArgs)]` `enum` variants must only contain one field."
),
);
None
} else {
// `unwrap` is okay because of the length check above.
let first_field = fields.unnamed.first().unwrap();
Some(&first_field.ty)
}
}
}
}

View File

@@ -0,0 +1,688 @@
// Copyright (c) 2020 Google LLC All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
use syn::parse::Parser;
use syn::punctuated::Punctuated;
use super::errors::Errors;
use proc_macro2::Span;
use std::collections::hash_map::{Entry, HashMap};
/// Attributes applied to a field of a `#![derive(FromArgs)]` struct.
#[derive(Default)]
pub struct FieldAttrs {
pub default: Option<syn::LitStr>,
pub description: Option<Description>,
pub from_str_fn: Option<syn::ExprPath>,
pub field_type: Option<FieldType>,
pub long: Option<Option<syn::LitStr>>,
pub short: Option<syn::LitChar>,
pub arg_name: Option<syn::LitStr>,
pub greedy: Option<syn::Path>,
pub hidden_help: bool,
}
/// The purpose of a particular field on a `#![derive(FromArgs)]` struct.
#[derive(Copy, Clone, Eq, PartialEq)]
pub enum FieldKind {
/// Switches are booleans that are set to "true" by passing the flag.
Switch,
/// Options are `--key value`. They may be optional (using `Option`),
/// or repeating (using `Vec`), or required (neither `Option` nor `Vec`)
Option,
/// Subcommand fields (of which there can be at most one) refer to enums
/// containing one of several potential subcommands. They may be optional
/// (using `Option`) or required (no `Option`).
SubCommand,
/// Positional arguments are parsed literally if the input
/// does not begin with `-` or `--` and is not a subcommand.
/// They are parsed in declaration order, and only the last positional
/// argument in a type may be an `Option`, `Vec`, or have a default value.
Positional,
}
/// The type of a field on a `#![derive(FromArgs)]` struct.
///
/// This is a simple wrapper around `FieldKind` which includes the `syn::Ident`
/// of the attribute containing the field kind.
pub struct FieldType {
pub kind: FieldKind,
pub ident: syn::Ident,
}
/// A description of a `#![derive(FromArgs)]` struct.
///
/// Defaults to the docstring if one is present, or `#[argh(description = "...")]`
/// if one is provided.
pub struct Description {
/// Whether the description was an explicit annotation or whether it was a doc string.
pub explicit: bool,
pub content: syn::LitStr,
}
impl FieldAttrs {
pub fn parse(errors: &Errors, field: &syn::Field) -> Self {
let mut this = Self::default();
for attr in &field.attrs {
if is_doc_attr(attr) {
parse_attr_doc(errors, attr, &mut this.description);
continue;
}
let ml = if let Some(ml) = argh_attr_to_meta_list(errors, attr) {
ml
} else {
continue;
};
for meta in ml {
let name = meta.path();
if name.is_ident("arg_name") {
if let Some(m) = errors.expect_meta_name_value(&meta) {
this.parse_attr_arg_name(errors, m);
}
} else if name.is_ident("default") {
if let Some(m) = errors.expect_meta_name_value(&meta) {
this.parse_attr_default(errors, m);
}
} else if name.is_ident("description") {
if let Some(m) = errors.expect_meta_name_value(&meta) {
parse_attr_description(errors, m, &mut this.description);
}
} else if name.is_ident("from_str_fn") {
if let Some(m) = errors.expect_meta_list(&meta) {
this.parse_attr_from_str_fn(errors, m);
}
} else if name.is_ident("long") {
if let Some(m) = errors.expect_meta_name_value(&meta) {
this.parse_attr_long(errors, m);
}
} else if name.is_ident("option") {
parse_attr_field_type(errors, &meta, FieldKind::Option, &mut this.field_type);
} else if name.is_ident("short") {
if let Some(m) = errors.expect_meta_name_value(&meta) {
this.parse_attr_short(errors, m);
}
} else if name.is_ident("subcommand") {
parse_attr_field_type(
errors,
&meta,
FieldKind::SubCommand,
&mut this.field_type,
);
} else if name.is_ident("switch") {
parse_attr_field_type(errors, &meta, FieldKind::Switch, &mut this.field_type);
} else if name.is_ident("positional") {
parse_attr_field_type(
errors,
&meta,
FieldKind::Positional,
&mut this.field_type,
);
} else if name.is_ident("greedy") {
this.greedy = Some(name.clone());
} else if name.is_ident("hidden_help") {
this.hidden_help = true;
} else {
errors.err(
&meta,
concat!(
"Invalid field-level `argh` attribute\n",
"Expected one of: `arg_name`, `default`, `description`, `from_str_fn`, `greedy`, ",
"`long`, `option`, `short`, `subcommand`, `switch`, `hidden_help`",
),
);
}
}
}
if let (Some(default), Some(field_type)) = (&this.default, &this.field_type) {
match field_type.kind {
FieldKind::Option | FieldKind::Positional => {}
FieldKind::SubCommand | FieldKind::Switch => errors.err(
default,
"`default` may only be specified on `#[argh(option)]` \
or `#[argh(positional)]` fields",
),
}
}
match (&this.greedy, this.field_type.as_ref().map(|f| f.kind)) {
(Some(_), Some(FieldKind::Positional)) => {}
(Some(greedy), Some(_)) => errors.err(
&greedy,
"`greedy` may only be specified on `#[argh(positional)]` \
fields",
),
_ => {}
}
if let Some(d) = &this.description {
check_option_description(errors, d.content.value().trim(), d.content.span());
}
this
}
fn parse_attr_from_str_fn(&mut self, errors: &Errors, m: &syn::MetaList) {
parse_attr_fn_name(errors, m, "from_str_fn", &mut self.from_str_fn)
}
fn parse_attr_default(&mut self, errors: &Errors, m: &syn::MetaNameValue) {
parse_attr_single_string(errors, m, "default", &mut self.default);
}
fn parse_attr_arg_name(&mut self, errors: &Errors, m: &syn::MetaNameValue) {
parse_attr_single_string(errors, m, "arg_name", &mut self.arg_name);
}
fn parse_attr_long(&mut self, errors: &Errors, m: &syn::MetaNameValue) {
if let Some(first) = &self.long {
errors.duplicate_attrs("long", first, m);
} else if let syn::Expr::Path(syn::ExprPath { path, .. }) = &m.value
&& let Some(ident) = path.get_ident()
&& ident.to_string().eq_ignore_ascii_case("none")
{
self.long = Some(None);
} else if let Some(lit_str) = errors.expect_lit_str(&m.value) {
self.long = Some(Some(lit_str.clone()));
}
if let Some(Some(long)) = &self.long {
let value = long.value();
check_long_name(errors, long, &value);
}
}
fn parse_attr_short(&mut self, errors: &Errors, m: &syn::MetaNameValue) {
if let Some(first) = &self.short {
errors.duplicate_attrs("short", first, m);
} else if let Some(lit_char) = errors.expect_lit_char(&m.value) {
self.short = Some(lit_char.clone());
if !lit_char.value().is_ascii() {
errors.err(lit_char, "Short names must be ASCII");
}
}
}
}
pub(crate) fn check_long_name(errors: &Errors, spanned: &impl syn::spanned::Spanned, value: &str) {
if !value.is_ascii() {
errors.err(spanned, "Long names must be ASCII");
}
if !value
.chars()
.all(|c| c.is_lowercase() || c == '-' || c.is_ascii_digit())
{
errors.err(
spanned,
"Long names may only contain lowercase letters, digits, and dashes",
);
}
}
fn parse_attr_fn_name(
errors: &Errors,
m: &syn::MetaList,
attr_name: &str,
slot: &mut Option<syn::ExprPath>,
) {
if let Some(first) = slot {
errors.duplicate_attrs(attr_name, first, m);
}
*slot = errors.ok(m.parse_args());
}
fn parse_attr_field_type(
errors: &Errors,
meta: &syn::Meta,
kind: FieldKind,
slot: &mut Option<FieldType>,
) {
if let Some(path) = errors.expect_meta_word(meta) {
if let Some(first) = slot {
errors.duplicate_attrs("field kind", &first.ident, path);
} else if let Some(word) = path.get_ident() {
*slot = Some(FieldType {
kind,
ident: word.clone(),
});
}
}
}
// Whether the attribute is one like `#[<name> ...]`
fn is_matching_attr(name: &str, attr: &syn::Attribute) -> bool {
attr.path().segments.len() == 1 && attr.path().segments[0].ident == name
}
/// Checks for `#[doc ...]`, which is generated by doc comments.
fn is_doc_attr(attr: &syn::Attribute) -> bool {
is_matching_attr("doc", attr)
}
/// Checks for `#[argh ...]`
fn is_argh_attr(attr: &syn::Attribute) -> bool {
is_matching_attr("argh", attr)
}
/// Filters out non-`#[argh(...)]` attributes and converts to a sequence of `syn::Meta`.
fn argh_attr_to_meta_list(
errors: &Errors,
attr: &syn::Attribute,
) -> Option<impl IntoIterator<Item = syn::Meta>> {
if !is_argh_attr(attr) {
return None;
}
let ml = errors.expect_meta_list(&attr.meta)?;
errors.ok(ml.parse_args_with(
syn::punctuated::Punctuated::<syn::Meta, syn::Token![,]>::parse_terminated,
))
}
/// Represents a `#[derive(FromArgs)]` type's top-level attributes.
#[derive(Default)]
pub struct TypeAttrs {
pub is_subcommand: Option<syn::Ident>,
pub name: Option<syn::LitStr>,
pub description: Option<Description>,
pub examples: Vec<syn::LitStr>,
pub notes: Vec<syn::LitStr>,
pub error_codes: Vec<(syn::LitInt, syn::LitStr)>,
/// Arguments that trigger printing of the help message
pub help_triggers: Option<Vec<syn::LitStr>>,
}
impl TypeAttrs {
/// Parse top-level `#[argh(...)]` attributes
pub fn parse(errors: &Errors, derive_input: &syn::DeriveInput) -> Self {
let mut this = TypeAttrs::default();
for attr in &derive_input.attrs {
if is_doc_attr(attr) {
parse_attr_doc(errors, attr, &mut this.description);
continue;
}
let ml: Vec<syn::Meta> = if let Some(ml) = argh_attr_to_meta_list(errors, attr) {
ml.into_iter().collect()
} else {
continue;
};
for meta in ml.iter() {
let name = meta.path();
if name.is_ident("description") {
if let Some(m) = errors.expect_meta_name_value(meta) {
parse_attr_description(errors, m, &mut this.description);
}
} else if name.is_ident("error_code") {
if let Some(m) = errors.expect_meta_list(meta) {
this.parse_attr_error_code(errors, m);
}
} else if name.is_ident("example") {
if let Some(m) = errors.expect_meta_name_value(meta) {
this.parse_attr_example(errors, m);
}
} else if name.is_ident("name") {
if let Some(m) = errors.expect_meta_name_value(meta) {
this.parse_attr_name(errors, m);
}
} else if name.is_ident("note") {
if let Some(m) = errors.expect_meta_name_value(meta) {
this.parse_attr_note(errors, m);
}
} else if name.is_ident("subcommand") {
if let Some(ident) = errors.expect_meta_word(meta).and_then(|p| p.get_ident()) {
this.parse_attr_subcommand(errors, ident);
}
} else if name.is_ident("help_triggers") {
if let Some(m) = errors.expect_meta_list(meta) {
Self::parse_help_triggers(m, errors, &mut this);
}
} else {
errors.err(
meta,
concat!(
"Invalid type-level `argh` attribute\n",
"Expected one of: `description`, `error_code`, `example`, `name`, ",
"`note`, `subcommand`, `help_triggers`",
),
);
}
}
if this.is_subcommand.is_some() && this.help_triggers.is_some() {
let help_meta = ml
.iter()
.find(|meta| meta.path().is_ident("help_triggers"))
.unwrap();
errors.err(help_meta, "Cannot use `help_triggers` on a subcommand");
}
}
this.check_error_codes(errors);
this
}
/// Checks that error codes are within range for `i32` and that they are
/// never duplicated.
fn check_error_codes(&self, errors: &Errors) {
// map from error code to index
let mut map: HashMap<u64, usize> = HashMap::new();
for (index, (lit_int, _lit_str)) in self.error_codes.iter().enumerate() {
let value = match lit_int.base10_parse::<u64>() {
Ok(v) => v,
Err(e) => {
errors.push(e);
continue;
}
};
if value > (i32::MAX as u64) {
errors.err(lit_int, "Error code out of range for `i32`");
}
match map.entry(value) {
Entry::Occupied(previous) => {
let previous_index = *previous.get();
let (previous_lit_int, _previous_lit_str) = &self.error_codes[previous_index];
errors.err(lit_int, &format!("Duplicate error code {}", value));
errors.err(
previous_lit_int,
&format!("Error code {} previously defined here", value),
);
}
Entry::Vacant(slot) => {
slot.insert(index);
}
}
}
}
fn parse_attr_error_code(&mut self, errors: &Errors, ml: &syn::MetaList) {
errors.ok(ml.parse_args_with(|input: syn::parse::ParseStream| {
let err_code = input.parse()?;
input.parse::<syn::Token![,]>()?;
let err_msg = input.parse()?;
if let (Some(err_code), Some(err_msg)) = (
errors.expect_lit_int(&err_code),
errors.expect_lit_str(&err_msg),
) {
self.error_codes.push((err_code.clone(), err_msg.clone()));
}
Ok(())
}));
}
fn parse_attr_example(&mut self, errors: &Errors, m: &syn::MetaNameValue) {
parse_attr_multi_string(errors, m, &mut self.examples)
}
fn parse_attr_name(&mut self, errors: &Errors, m: &syn::MetaNameValue) {
parse_attr_single_string(errors, m, "name", &mut self.name);
if let Some(name) = &self.name
&& name.value() == "help"
{
errors.err(name, "Custom `help` commands are not supported.");
}
}
fn parse_attr_note(&mut self, errors: &Errors, m: &syn::MetaNameValue) {
parse_attr_multi_string(errors, m, &mut self.notes)
}
fn parse_attr_subcommand(&mut self, errors: &Errors, ident: &syn::Ident) {
if let Some(first) = &self.is_subcommand {
errors.duplicate_attrs("subcommand", first, ident);
} else {
self.is_subcommand = Some(ident.clone());
}
}
// get the list of arguments that trigger printing of the help message as a vector of strings (help_arguments("-h", "--help", "help"))
fn parse_help_triggers(m: &syn::MetaList, errors: &Errors, this: &mut TypeAttrs) {
let parser = Punctuated::<syn::Expr, syn::Token![,]>::parse_terminated;
match parser.parse(m.tokens.clone().into()) {
Ok(args) => {
let mut triggers = Vec::new();
for arg in args {
if let syn::Expr::Lit(syn::ExprLit {
lit: syn::Lit::Str(lit_str),
..
}) = arg
{
triggers.push(lit_str);
}
}
this.help_triggers = Some(triggers);
}
Err(err) => errors.push(err),
}
}
}
/// Represents an enum variant's attributes.
#[derive(Default)]
pub struct VariantAttrs {
pub is_dynamic: Option<syn::Path>,
}
impl VariantAttrs {
/// Parse enum variant `#[argh(...)]` attributes
pub fn parse(errors: &Errors, variant: &syn::Variant) -> Self {
let mut this = VariantAttrs::default();
let fields = match &variant.fields {
syn::Fields::Named(fields) => Some(&fields.named),
syn::Fields::Unnamed(fields) => Some(&fields.unnamed),
syn::Fields::Unit => None,
};
for field in fields.into_iter().flatten() {
for attr in &field.attrs {
if is_argh_attr(attr) {
err_unused_enum_attr(errors, attr);
}
}
}
for attr in &variant.attrs {
let ml = if let Some(ml) = argh_attr_to_meta_list(errors, attr) {
ml
} else {
continue;
};
for meta in ml {
let name = meta.path();
if name.is_ident("dynamic") {
if let Some(prev) = this.is_dynamic.as_ref() {
errors.duplicate_attrs("dynamic", prev, &meta);
} else {
this.is_dynamic = errors.expect_meta_word(&meta).cloned();
}
} else {
errors.err(
&meta,
"Invalid variant-level `argh` attribute\n\
Variants can only have the #[argh(dynamic)] attribute.",
);
}
}
}
this
}
}
fn check_option_description(errors: &Errors, desc: &str, span: Span) {
let chars = &mut desc.trim().chars();
match (chars.next(), chars.next()) {
(Some(x), _) if x.is_lowercase() => {}
// If both the first and second letter are not lowercase,
// this is likely an initialism which should be allowed.
(Some(x), Some(y)) if !x.is_lowercase() && (y.is_alphanumeric() && !y.is_lowercase()) => {}
_ => {
errors.err_span(span, "Descriptions must begin with a lowercase letter");
}
}
}
fn parse_attr_single_string(
errors: &Errors,
m: &syn::MetaNameValue,
name: &str,
slot: &mut Option<syn::LitStr>,
) {
if let Some(first) = slot {
errors.duplicate_attrs(name, first, m);
} else if let Some(lit_str) = errors.expect_lit_str(&m.value) {
*slot = Some(lit_str.clone());
}
}
fn parse_attr_multi_string(errors: &Errors, m: &syn::MetaNameValue, list: &mut Vec<syn::LitStr>) {
if let Some(lit_str) = errors.expect_lit_str(&m.value) {
list.push(lit_str.clone());
}
}
fn parse_attr_doc(errors: &Errors, attr: &syn::Attribute, slot: &mut Option<Description>) {
let nv = if let Some(nv) = errors.expect_meta_name_value(&attr.meta) {
nv
} else {
return;
};
// Don't replace an existing explicit description.
if slot.as_ref().map(|d| d.explicit).unwrap_or(false) {
return;
}
if let Some(lit_str) = errors.expect_lit_str(&nv.value) {
let lit_str = if let Some(previous) = slot {
let previous = &previous.content;
let previous_span = previous.span();
syn::LitStr::new(
&(previous.value() + &unescape_doc(lit_str.value())),
previous_span,
)
} else {
syn::LitStr::new(&unescape_doc(lit_str.value()), lit_str.span())
};
*slot = Some(Description {
explicit: false,
content: lit_str,
});
}
}
/// Replaces escape sequences in doc-comments with the characters they represent.
///
/// Rustdoc understands CommonMark escape sequences consisting of a backslash followed by an ASCII
/// punctuation character. Any other backslash is treated as a literal backslash.
fn unescape_doc(s: String) -> String {
let mut result = String::with_capacity(s.len());
let mut characters = s.chars().peekable();
while let Some(mut character) = characters.next() {
if character == '\\'
&& let Some(next_character) = characters.peek()
&& next_character.is_ascii_punctuation()
{
character = *next_character;
characters.next();
}
// Braces must be escaped as this string will be used as a format string
if character == '{' || character == '}' {
result.push(character);
}
result.push(character);
}
result
}
fn parse_attr_description(errors: &Errors, m: &syn::MetaNameValue, slot: &mut Option<Description>) {
let lit_str = if let Some(lit_str) = errors.expect_lit_str(&m.value) {
lit_str
} else {
return;
};
// Don't allow multiple explicit (non doc-comment) descriptions
if let Some(description) = slot
&& description.explicit
{
errors.duplicate_attrs("description", &description.content, lit_str);
}
*slot = Some(Description {
explicit: true,
content: lit_str.clone(),
});
}
/// Checks that a `#![derive(FromArgs)]` enum has an `#[argh(subcommand)]`
/// attribute and that it does not have any other type-level `#[argh(...)]` attributes.
pub fn check_enum_type_attrs(errors: &Errors, type_attrs: &TypeAttrs, type_span: &Span) {
let TypeAttrs {
is_subcommand,
name,
description,
examples,
notes,
error_codes,
help_triggers,
} = type_attrs;
// Ensure that `#[argh(subcommand)]` is present.
if is_subcommand.is_none() {
errors.err_span(
*type_span,
concat!(
"`#![derive(FromArgs)]` on `enum`s can only be used to enumerate subcommands.\n",
"Consider adding `#[argh(subcommand)]` to the `enum` declaration.",
),
);
}
// Error on all other type-level attributes.
if let Some(name) = name {
err_unused_enum_attr(errors, name);
}
if let Some(description) = description
&& description.explicit
{
err_unused_enum_attr(errors, &description.content);
}
if let Some(example) = examples.first() {
err_unused_enum_attr(errors, example);
}
if let Some(note) = notes.first() {
err_unused_enum_attr(errors, note);
}
if let Some(err_code) = error_codes.first() {
err_unused_enum_attr(errors, &err_code.0);
}
if let Some(triggers) = help_triggers
&& let Some(trigger) = triggers.first()
{
err_unused_enum_attr(errors, trigger);
}
}
fn err_unused_enum_attr(errors: &Errors, location: &impl syn::spanned::Spanned) {
errors.err(
location,
concat!(
"Unused `argh` attribute on `#![derive(FromArgs)]` enum. ",
"Such `enum`s can only be used to dispatch to subcommands, ",
"and should only contain the #[argh(subcommand)] attribute.",
),
);
}

View File

@@ -20,16 +20,12 @@ pub(crate) fn derive_decodable(input: proc_macro::TokenStream) -> proc_macro::To
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let sum = gen_size_sum(&input.data);
let encode = gen_encode(&input.data);
let decode = gen_decode(&input.data);
let expanded = quote! {
// The generated impl.
impl #impl_generics crate::socket::Encodable for #name #ty_generics #where_clause {
fn encoded_len(&self) -> usize {
#sum
}
fn encode(&self, w: &mut impl std::io::Write) -> std::io::Result<()> {
#encode
Ok(())
@@ -45,32 +41,6 @@ pub(crate) fn derive_decodable(input: proc_macro::TokenStream) -> proc_macro::To
proc_macro::TokenStream::from(expanded)
}
// Generate an expression to sum up the size of each field.
fn gen_size_sum(data: &Data) -> TokenStream {
match *data {
Data::Struct(ref data) => {
match data.fields {
Fields::Named(ref fields) => {
// Expands to an expression like
//
// 0 + self.x.encoded_len() + self.y.encoded_len() + self.z.encoded_len()
let recurse = fields.named.iter().map(|f| {
let name = &f.ident;
quote_spanned! { f.span() =>
crate::socket::Encodable::encoded_len(&self.#name)
}
});
quote! {
0 #(+ #recurse)*
}
}
_ => unimplemented!(),
}
}
Data::Enum(_) | Data::Union(_) => unimplemented!(),
}
}
// Generate an expression to encode each field.
fn gen_encode(data: &Data) -> TokenStream {
match *data {

View File

@@ -0,0 +1,19 @@
#![recursion_limit = "256"]
use proc_macro::TokenStream;
mod argh;
mod decodable;
#[proc_macro_derive(Decodable)]
pub fn derive_decodable(input: TokenStream) -> TokenStream {
decodable::derive_decodable(input)
}
/// Entrypoint for `#[derive(FromArgs)]`.
#[proc_macro_derive(FromArgs, attributes(argh))]
pub fn argh_derive(input: TokenStream) -> TokenStream {
let ast = syn::parse_macro_input!(input as syn::DeriveInput);
let token = argh::impl_from_args(&ast);
token.into()
}

View File

@@ -1,18 +1,21 @@
use crate::cxx_extern::readlinkat;
use crate::{
FsPathBuilder, LibcReturn, OsError, OsResult, OsResultStatic, Utf8CStr, Utf8CStrBuf, cstr,
errno, fd_path, fd_set_attr,
FsPathBuilder, LibcReturn, LoggedResult, OsError, OsResult, Utf8CStr, Utf8CStrBuf, cstr, errno,
fd_path, fd_set_attr,
};
use libc::{EEXIST, O_CLOEXEC, O_CREAT, O_RDONLY, O_TRUNC, O_WRONLY, dirent, mode_t};
use libc::{dirent, mode_t};
use nix::errno::Errno;
use nix::fcntl::{AtFlags, OFlag};
use nix::sys::stat::Mode;
use nix::unistd::UnlinkatFlags;
use std::fs::File;
use std::marker::PhantomData;
use std::ops::{Deref, DerefMut};
use std::os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd};
use std::ops::Deref;
use std::os::fd::{AsFd, AsRawFd, BorrowedFd, IntoRawFd, OwnedFd, RawFd};
use std::ptr::NonNull;
use std::{mem, slice};
use std::slice;
pub struct DirEntry<'a> {
dir: BorrowedDirectory<'a>,
dir: &'a Directory,
entry: NonNull<dirent>,
d_name_len: usize,
}
@@ -23,6 +26,7 @@ impl DirEntry<'_> {
}
pub fn name(&self) -> &Utf8CStr {
// SAFETY: Utf8CStr is already validated in Directory::read
unsafe {
Utf8CStr::from_bytes_unchecked(slice::from_raw_parts(
self.d_name.as_ptr().cast(),
@@ -63,19 +67,23 @@ impl DirEntry<'_> {
self.d_type == libc::DT_SOCK
}
pub fn unlink(&self) -> OsResult<()> {
let flag = if self.is_dir() { libc::AT_REMOVEDIR } else { 0 };
pub fn unlink(&self) -> OsResult<'_, ()> {
let flag = if self.is_dir() {
UnlinkatFlags::RemoveDir
} else {
UnlinkatFlags::NoRemoveDir
};
self.dir.unlink_at(self.name(), flag)
}
pub fn read_link(&self, buf: &mut dyn Utf8CStrBuf) -> OsResult<()> {
pub fn read_link(&self, buf: &mut dyn Utf8CStrBuf) -> OsResult<'_, ()> {
self.dir.read_link_at(self.name(), buf)
}
pub fn open_as_dir(&self) -> OsResult<Directory> {
pub fn open_as_dir(&self) -> OsResult<'_, Directory> {
if !self.is_dir() {
return Err(OsError::with_os_error(
libc::ENOTDIR,
return Err(OsError::new(
Errno::ENOTDIR,
"fdopendir",
Some(self.name()),
None,
@@ -84,10 +92,10 @@ impl DirEntry<'_> {
self.dir.open_as_dir_at(self.name())
}
pub fn open_as_file(&self, flags: i32) -> OsResult<File> {
pub fn open_as_file(&self, flags: OFlag) -> OsResult<'_, File> {
if self.is_dir() {
return Err(OsError::with_os_error(
libc::EISDIR,
return Err(OsError::new(
Errno::EISDIR,
"open_as_file",
Some(self.name()),
None,
@@ -95,6 +103,14 @@ impl DirEntry<'_> {
}
self.dir.open_as_file_at(self.name(), flags, 0)
}
pub fn rename_to<'a, 'entry: 'a>(
&'entry self,
new_dir: impl AsFd,
path: &'a Utf8CStr,
) -> OsResult<'a, ()> {
self.dir.rename_at(self.name(), new_dir, path)
}
}
impl Deref for DirEntry<'_> {
@@ -110,30 +126,6 @@ pub struct Directory {
inner: NonNull<libc::DIR>,
}
#[repr(transparent)]
pub struct BorrowedDirectory<'a> {
inner: NonNull<libc::DIR>,
phantom: PhantomData<&'a Directory>,
}
impl Deref for BorrowedDirectory<'_> {
type Target = Directory;
fn deref(&self) -> &Directory {
// SAFETY: layout of NonNull<libc::DIR> is the same as Directory
// SAFETY: the lifetime of the raw pointer is tracked in the PhantomData
unsafe { mem::transmute(&self.inner) }
}
}
impl DerefMut for BorrowedDirectory<'_> {
fn deref_mut(&mut self) -> &mut Directory {
// SAFETY: layout of NonNull<libc::DIR> is the same as Directory
// SAFETY: the lifetime of the raw pointer is tracked in the PhantomData
unsafe { mem::transmute(&mut self.inner) }
}
}
pub enum WalkResult {
Continue,
Abort,
@@ -141,19 +133,14 @@ pub enum WalkResult {
}
impl Directory {
fn borrow(&self) -> BorrowedDirectory {
BorrowedDirectory {
inner: self.inner,
phantom: PhantomData,
}
}
fn openat<'a>(&self, name: &'a Utf8CStr, flags: i32, mode: u32) -> OsResult<'a, OwnedFd> {
unsafe {
libc::openat(self.as_raw_fd(), name.as_ptr(), flags | O_CLOEXEC, mode)
.as_os_result("openat", Some(name), None)
.map(|fd| OwnedFd::from_raw_fd(fd))
}
fn open_at<'a>(&self, name: &'a Utf8CStr, flags: OFlag, mode: mode_t) -> OsResult<'a, OwnedFd> {
nix::fcntl::openat(
self,
name,
flags | OFlag::O_CLOEXEC,
Mode::from_bits_truncate(mode),
)
.into_os_result("openat", Some(name), None)
}
fn path_at(&self, name: &Utf8CStr, buf: &mut dyn Utf8CStrBuf) -> OsResult<'static, ()> {
@@ -163,14 +150,15 @@ impl Directory {
}
}
// Low-level methods, we should track the caller when error occurs, so return OsResult.
impl Directory {
pub fn open(path: &Utf8CStr) -> OsResult<Directory> {
pub fn open(path: &Utf8CStr) -> OsResult<'_, Directory> {
let dirp = unsafe { libc::opendir(path.as_ptr()) };
let dirp = dirp.as_os_result("opendir", Some(path), None)?;
let dirp = dirp.into_os_result("opendir", Some(path), None)?;
Ok(Directory { inner: dirp })
}
pub fn read(&mut self) -> OsResult<'static, Option<DirEntry>> {
pub fn read(&mut self) -> OsResult<'static, Option<DirEntry<'_>>> {
*errno() = 0;
let e = unsafe { libc::readdir(self.inner.as_ptr()) };
if e.is_null() {
@@ -192,7 +180,7 @@ impl Directory {
self.read()
} else {
let e = DirEntry {
dir: self.borrow(),
dir: self,
entry: NonNull::from(entry),
d_name_len: name.as_bytes_with_nul().len(),
};
@@ -206,17 +194,17 @@ impl Directory {
}
pub fn open_as_dir_at<'a>(&self, name: &'a Utf8CStr) -> OsResult<'a, Directory> {
let fd = self.openat(name, O_RDONLY, 0)?;
let fd = self.open_at(name, OFlag::O_RDONLY, 0)?;
Directory::try_from(fd).map_err(|e| e.set_args(Some(name), None))
}
pub fn open_as_file_at<'a>(
&self,
name: &'a Utf8CStr,
flags: i32,
mode: u32,
flags: OFlag,
mode: mode_t,
) -> OsResult<'a, File> {
let fd = self.openat(name, flags, mode)?;
let fd = self.open_at(name, flags, mode)?;
Ok(File::from(fd))
}
@@ -227,27 +215,23 @@ impl Directory {
) -> OsResult<'a, ()> {
buf.clear();
unsafe {
let r = readlinkat(
readlinkat(
self.as_raw_fd(),
name.as_ptr(),
buf.as_mut_ptr().cast(),
buf.capacity(),
)
.as_os_result("readlinkat", Some(name), None)? as usize;
buf.set_len(r);
.check_os_err("readlinkat", Some(name), None)?;
}
buf.rebuild().ok();
Ok(())
}
pub fn mkdir_at<'a>(&self, name: &'a Utf8CStr, mode: mode_t) -> OsResult<'a, ()> {
unsafe {
if libc::mkdirat(self.as_raw_fd(), name.as_ptr(), mode as mode_t) < 0
&& *errno() != EEXIST
{
return Err(OsError::last_os_error("mkdirat", Some(name), None));
}
match nix::sys::stat::mkdirat(self, name, Mode::from_bits_truncate(mode)) {
Ok(_) | Err(Errno::EEXIST) => Ok(()),
Err(e) => Err(OsError::new(e, "mkdirat", Some(name), None)),
}
Ok(())
}
// ln -s target self/name
@@ -256,60 +240,56 @@ impl Directory {
name: &'a Utf8CStr,
target: &'a Utf8CStr,
) -> OsResult<'a, ()> {
unsafe {
libc::symlinkat(target.as_ptr(), self.as_raw_fd(), name.as_ptr()).check_os_err(
"symlinkat",
Some(target),
Some(name),
)
}
nix::unistd::symlinkat(target, self, name).check_os_err(
"symlinkat",
Some(target),
Some(name),
)
}
pub fn unlink_at<'a>(&self, name: &'a Utf8CStr, flag: i32) -> OsResult<'a, ()> {
unsafe {
libc::unlinkat(self.as_raw_fd(), name.as_ptr(), flag).check_os_err(
"unlinkat",
Some(name),
None,
)?;
}
Ok(())
pub fn unlink_at<'a>(&self, name: &'a Utf8CStr, flag: UnlinkatFlags) -> OsResult<'a, ()> {
nix::unistd::unlinkat(self, name, flag).check_os_err("unlinkat", Some(name), None)
}
pub fn contains_path(&self, path: &Utf8CStr) -> bool {
// WARNING: Using faccessat is incorrect, because the raw linux kernel syscall
// does not support the flag AT_SYMLINK_NOFOLLOW until 5.8 with faccessat2.
// Use fstatat to check the existence of a file instead.
unsafe {
let mut st: libc::stat = mem::zeroed();
libc::fstatat(
self.as_raw_fd(),
path.as_ptr(),
&mut st,
libc::AT_SYMLINK_NOFOLLOW,
) == 0
}
nix::sys::stat::fstatat(self, path, AtFlags::AT_SYMLINK_NOFOLLOW).is_ok()
}
pub fn resolve_path(&self, buf: &mut dyn Utf8CStrBuf) -> OsResult<'static, ()> {
fd_path(self.as_raw_fd(), buf)
}
pub fn post_order_walk<F: FnMut(&DirEntry) -> OsResultStatic<WalkResult>>(
pub fn rename_at<'a>(
&self,
old: &'a Utf8CStr,
new_dir: impl AsFd,
new: &'a Utf8CStr,
) -> OsResult<'a, ()> {
nix::fcntl::renameat(self, old, new_dir, new).check_os_err("renameat", Some(old), Some(new))
}
}
// High-level helper methods, composed of multiple operations.
// We should treat these as application logic and log ASAP, so return LoggedResult.
impl Directory {
pub fn post_order_walk<F: FnMut(&DirEntry) -> LoggedResult<WalkResult>>(
&mut self,
mut f: F,
) -> OsResultStatic<WalkResult> {
) -> LoggedResult<WalkResult> {
self.post_order_walk_impl(&mut f)
}
pub fn pre_order_walk<F: FnMut(&DirEntry) -> OsResultStatic<WalkResult>>(
pub fn pre_order_walk<F: FnMut(&DirEntry) -> LoggedResult<WalkResult>>(
&mut self,
mut f: F,
) -> OsResultStatic<WalkResult> {
) -> LoggedResult<WalkResult> {
self.pre_order_walk_impl(&mut f)
}
pub fn remove_all(mut self) -> OsResultStatic<()> {
pub fn remove_all(mut self) -> LoggedResult<()> {
self.post_order_walk(|e| {
e.unlink()?;
Ok(WalkResult::Continue)
@@ -317,13 +297,12 @@ impl Directory {
Ok(())
}
pub fn copy_into(&mut self, dir: &Directory) -> OsResultStatic<()> {
pub fn copy_into(&mut self, dir: &Directory) -> LoggedResult<()> {
let mut buf = cstr::buf::default();
self.copy_into_impl(dir, &mut buf)
}
pub fn move_into(&mut self, dir: &Directory) -> OsResultStatic<()> {
let dir_fd = self.as_raw_fd();
pub fn move_into(&mut self, dir: &Directory) -> LoggedResult<()> {
while let Some(ref e) = self.read()? {
if e.is_dir() && dir.contains_path(e.name()) {
// Destination folder exists, needs recursive move
@@ -332,31 +311,22 @@ impl Directory {
src.move_into(&dest)?;
return Ok(e.unlink()?);
}
unsafe {
libc::renameat(
dir_fd,
e.d_name.as_ptr(),
dir.as_raw_fd(),
e.d_name.as_ptr(),
)
.check_os_err("renameat", Some(e.name()), None)?;
}
e.rename_to(dir, e.name())?;
}
Ok(())
}
pub fn link_into(&mut self, dir: &Directory) -> OsResultStatic<()> {
pub fn link_into(&mut self, dir: &Directory) -> LoggedResult<()> {
let mut buf = cstr::buf::default();
self.link_into_impl(dir, &mut buf)
}
}
impl Directory {
fn post_order_walk_impl<F: FnMut(&DirEntry) -> OsResultStatic<WalkResult>>(
fn post_order_walk_impl<F: FnMut(&DirEntry) -> LoggedResult<WalkResult>>(
&mut self,
f: &mut F,
) -> OsResultStatic<WalkResult> {
) -> LoggedResult<WalkResult> {
use WalkResult::*;
loop {
match self.read()? {
@@ -378,10 +348,10 @@ impl Directory {
}
}
fn pre_order_walk_impl<F: FnMut(&DirEntry) -> OsResultStatic<WalkResult>>(
fn pre_order_walk_impl<F: FnMut(&DirEntry) -> LoggedResult<WalkResult>>(
&mut self,
f: &mut F,
) -> OsResultStatic<WalkResult> {
) -> LoggedResult<WalkResult> {
use WalkResult::*;
loop {
match self.read()? {
@@ -406,7 +376,7 @@ impl Directory {
&mut self,
dest_dir: &Directory,
buf: &mut dyn Utf8CStrBuf,
) -> OsResultStatic<()> {
) -> LoggedResult<()> {
while let Some(ref e) = self.read()? {
e.resolve_path(buf)?;
let attr = buf.get_attr()?;
@@ -417,9 +387,12 @@ impl Directory {
src.copy_into_impl(&dest, buf)?;
fd_set_attr(dest.as_raw_fd(), &attr)?;
} else if e.is_file() {
let mut src = e.open_as_file(O_RDONLY)?;
let mut dest =
dest_dir.open_as_file_at(e.name(), O_WRONLY | O_CREAT | O_TRUNC, 0o777)?;
let mut src = e.open_as_file(OFlag::O_RDONLY)?;
let mut dest = dest_dir.open_as_file_at(
e.name(),
OFlag::O_WRONLY | OFlag::O_CREAT | OFlag::O_TRUNC,
0o777,
)?;
std::io::copy(&mut src, &mut dest)?;
fd_set_attr(dest.as_raw_fd(), &attr)?;
} else if e.is_symlink() {
@@ -436,8 +409,7 @@ impl Directory {
&mut self,
dest_dir: &Directory,
buf: &mut dyn Utf8CStrBuf,
) -> OsResultStatic<()> {
let dir_fd = self.as_raw_fd();
) -> LoggedResult<()> {
while let Some(ref e) = self.read()? {
if e.is_dir() {
dest_dir.mkdir_at(e.name(), 0o777)?;
@@ -448,16 +420,8 @@ impl Directory {
src.link_into_impl(&dest, buf)?;
fd_set_attr(dest.as_raw_fd(), &attr)?;
} else {
unsafe {
libc::linkat(
dir_fd,
e.d_name.as_ptr(),
dest_dir.as_raw_fd(),
e.d_name.as_ptr(),
0,
)
nix::unistd::linkat(e.dir, e.name(), dest_dir, e.name(), AtFlags::empty())
.check_os_err("linkat", Some(e.name()), None)?;
}
}
}
Ok(())
@@ -469,7 +433,7 @@ impl TryFrom<OwnedFd> for Directory {
fn try_from(fd: OwnedFd) -> OsResult<'static, Self> {
let dirp = unsafe { libc::fdopendir(fd.into_raw_fd()) };
let dirp = dirp.as_os_result("fdopendir", None, None)?;
let dirp = dirp.into_os_result("fdopendir", None, None)?;
Ok(Directory { inner: dirp })
}
}
@@ -481,7 +445,7 @@ impl AsRawFd for Directory {
}
impl AsFd for Directory {
fn as_fd(&self) -> BorrowedFd {
fn as_fd(&self) -> BorrowedFd<'_> {
unsafe { BorrowedFd::borrow_raw(self.as_raw_fd()) }
}
}

View File

@@ -1,151 +0,0 @@
#include <sys/mman.h>
#include <sys/sendfile.h>
#include <linux/fs.h>
#include <fcntl.h>
#include <unistd.h>
#include <base.hpp>
using namespace std;
int fd_pathat(int dirfd, const char *name, char *path, size_t size) {
if (fd_path(dirfd, byte_data(path, size)) < 0)
return -1;
auto len = strlen(path);
path[len] = '/';
strscpy(path + len + 1, name, size - len - 1);
return 0;
}
void full_read(int fd, string &str) {
char buf[4096];
for (ssize_t len; (len = xread(fd, buf, sizeof(buf))) > 0;)
str.insert(str.end(), buf, buf + len);
}
void full_read(const char *filename, string &str) {
if (int fd = xopen(filename, O_RDONLY | O_CLOEXEC); fd >= 0) {
full_read(fd, str);
close(fd);
}
}
string full_read(int fd) {
string str;
full_read(fd, str);
return str;
}
string full_read(const char *filename) {
string str;
full_read(filename, str);
return str;
}
void write_zero(int fd, size_t size) {
char buf[4096] = {0};
size_t len;
while (size > 0) {
len = sizeof(buf) > size ? size : sizeof(buf);
write(fd, buf, len);
size -= len;
}
}
void file_readline(bool trim, FILE *fp, const function<bool(string_view)> &fn) {
size_t len = 1024;
char *buf = (char *) malloc(len);
char *start;
ssize_t read;
while ((read = getline(&buf, &len, fp)) >= 0) {
start = buf;
if (trim) {
while (read && "\n\r "sv.find(buf[read - 1]) != string::npos)
--read;
buf[read] = '\0';
while (*start == ' ')
++start;
}
if (!fn(start))
break;
}
free(buf);
}
void file_readline(bool trim, const char *file, const function<bool(string_view)> &fn) {
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);
}
void parse_prop_file(FILE *fp, const function<bool(string_view, string_view)> &fn) {
file_readline(true, fp, [&](string_view line_view) -> bool {
char *line = (char *) line_view.data();
if (line[0] == '#')
return true;
char *eql = strchr(line, '=');
if (eql == nullptr || eql == line)
return true;
*eql = '\0';
return fn(line, eql + 1);
});
}
void parse_prop_file(const char *file, const function<bool(string_view, string_view)> &fn) {
if (auto fp = open_file(file, "re"))
parse_prop_file(fp.get(), fn);
}
sDIR make_dir(DIR *dp) {
return sDIR(dp, [](DIR *dp){ return dp ? closedir(dp) : 1; });
}
sFILE make_file(FILE *fp) {
return sFILE(fp, [](FILE *fp){ return fp ? fclose(fp) : 1; });
}
mmap_data::mmap_data(const char *name, bool rw) {
auto slice = rust::map_file(name, rw);
if (!slice.empty()) {
_buf = slice.data();
_sz = slice.size();
}
}
mmap_data::mmap_data(int dirfd, const char *name, bool rw) {
auto slice = rust::map_file_at(dirfd, name, rw);
if (!slice.empty()) {
_buf = slice.data();
_sz = slice.size();
}
}
mmap_data::mmap_data(int fd, size_t sz, bool rw) {
auto slice = rust::map_fd(fd, sz, rw);
if (!slice.empty()) {
_buf = slice.data();
_sz = slice.size();
}
}
mmap_data::~mmap_data() {
if (_buf)
munmap(_buf, _sz);
}
string resolve_preinit_dir(const char *base_dir) {
string dir = base_dir;
if (access((dir + "/unencrypted").data(), F_OK) == 0) {
dir += "/unencrypted/magisk";
} else if (access((dir + "/adb").data(), F_OK) == 0) {
dir += "/adb";
} else if (access((dir + "/watchdog").data(), F_OK) == 0) {
dir += "/watchdog/magisk";
} else {
dir += "/magisk";
}
return dir;
}

View File

@@ -1,30 +1,19 @@
#pragma once
#include <sys/stat.h>
#include <linux/fs.h>
#include <functional>
#include <string_view>
#include <string>
#include <vector>
#include <linux/fs.h>
#include "misc.hpp"
template <typename T>
static inline T align_to(T v, int a) {
static_assert(std::is_integral<T>::value);
return (v + a - 1) / a * a;
}
template <typename T>
static inline T align_padding(T v, int a) {
return align_to(v, a) - v;
}
#include "base-rs.hpp"
struct mmap_data : public byte_data {
static_assert((sizeof(void *) == 8 && BLKGETSIZE64 == 0x80081272) ||
(sizeof(void *) == 4 && BLKGETSIZE64 == 0x80041272));
ALLOW_MOVE_ONLY(mmap_data)
mmap_data() = default;
explicit mmap_data(const char *name, bool rw = false);
mmap_data(int dirfd, const char *name, bool rw = false);
mmap_data(int fd, size_t sz, bool rw = false);
@@ -45,24 +34,26 @@ bool fclone_attr(int src, int dest);
} // extern "C"
int fd_pathat(int dirfd, const char *name, char *path, size_t size);
static inline ssize_t realpath(
const char * __restrict__ path, char * __restrict__ buf, size_t bufsiz) {
return canonical_path(path, buf, bufsiz);
}
void full_read(int fd, std::string &str);
void full_read(const char *filename, std::string &str);
std::string full_read(int fd);
std::string full_read(const char *filename);
void write_zero(int fd, size_t size);
void file_readline(bool trim, FILE *fp, const std::function<bool(std::string_view)> &fn);
void file_readline(bool trim, const char *file, const std::function<bool(std::string_view)> &fn);
void file_readline(const char *file, const std::function<bool(std::string_view)> &fn);
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::string resolve_preinit_dir(const char *base_dir);
// Functor = function<bool(Utf8CStr, Utf8CStr)>
template <typename Functor>
void parse_prop_file(const char *file, Functor &&fn) {
parse_prop_file_rs(file, [&](rust::Str key, rust::Str val) -> bool {
// We perform the null termination here in C++ because it's very difficult to do it
// right in Rust due to pointer provenance. Trying to dereference a pointer without
// the correct provenance in Rust, even in unsafe code, is undefined behavior.
// However on the C++ side, there are fewer restrictions on pointers, so the const_cast here
// will not trigger UB in the compiler.
*(const_cast<char *>(key.data()) + key.size()) = '\0';
*(const_cast<char *>(val.data()) + val.size()) = '\0';
return fn(Utf8CStr(key.data(), key.size() + 1), Utf8CStr(val.data(), val.size() + 1));
});
}
using sFILE = std::unique_ptr<FILE, decltype(&fclose)>;
using sDIR = std::unique_ptr<DIR, decltype(&closedir)>;
sDIR make_dir(DIR *dp);

View File

@@ -1,22 +1,23 @@
use crate::{
Directory, FsPathFollow, LibcReturn, OsError, OsResult, OsResultStatic, Utf8CStr, Utf8CStrBuf,
Directory, FsPathFollow, LibcReturn, LoggedResult, OsError, OsResult, Utf8CStr, Utf8CStrBuf,
cstr, errno, error,
};
use bytemuck::{Pod, bytes_of, bytes_of_mut};
use libc::{
EEXIST, ENOENT, F_OK, O_CLOEXEC, O_CREAT, O_PATH, O_RDONLY, O_RDWR, O_TRUNC, O_WRONLY, c_uint,
makedev, mode_t, stat,
};
use mem::MaybeUninit;
use libc::{c_uint, makedev, mode_t};
use nix::errno::Errno;
use nix::fcntl::{AT_FDCWD, OFlag};
use nix::sys::stat::{FchmodatFlags, Mode};
use nix::unistd::{AccessFlags, Gid, Uid};
use num_traits::AsPrimitive;
use std::cmp::min;
use std::ffi::CStr;
use std::fmt::Display;
use std::fs::File;
use std::io::{BufRead, BufReader, Read, Seek, SeekFrom, Write};
use std::mem::MaybeUninit;
use std::os::fd::{AsFd, BorrowedFd};
use std::os::unix::ffi::OsStrExt;
use std::os::unix::io::{AsRawFd, FromRawFd, OwnedFd, RawFd};
use std::os::unix::io::{AsRawFd, OwnedFd, RawFd};
use std::path::Path;
use std::{io, mem, ptr, slice};
@@ -57,12 +58,12 @@ impl<T: Read + Seek> ReadSeekExt for T {
}
pub trait BufReadExt {
fn foreach_lines<F: FnMut(&mut String) -> bool>(&mut self, f: F);
fn foreach_props<F: FnMut(&str, &str) -> bool>(&mut self, f: F);
fn for_each_line<F: FnMut(&mut String) -> bool>(&mut self, f: F);
fn for_each_prop<F: FnMut(&str, &str) -> bool>(&mut self, f: F);
}
impl<T: BufRead> BufReadExt for T {
fn foreach_lines<F: FnMut(&mut String) -> bool>(&mut self, mut f: F) {
fn for_each_line<F: FnMut(&mut String) -> bool>(&mut self, mut f: F) {
let mut buf = String::new();
loop {
match self.read_line(&mut buf) {
@@ -81,8 +82,11 @@ impl<T: BufRead> BufReadExt for T {
}
}
fn foreach_props<F: FnMut(&str, &str) -> bool>(&mut self, mut f: F) {
self.foreach_lines(|line| {
fn for_each_prop<F: FnMut(&str, &str) -> bool>(&mut self, mut f: F) {
self.for_each_line(|line| {
// Reserve an additional byte, because this string will be manually
// null terminated on the C++ side, and it may need more space.
line.reserve(1);
let line = line.trim();
if line.starts_with('#') {
return true;
@@ -116,17 +120,34 @@ impl<T: Write> WriteExt for T {
}
}
fn open_fd(path: &Utf8CStr, flags: i32, mode: mode_t) -> OsResult<OwnedFd> {
unsafe {
let fd = libc::open(path.as_ptr(), flags, mode as c_uint).as_os_result(
"open",
Some(path),
None,
)?;
Ok(OwnedFd::from_raw_fd(fd))
pub enum FileOrStd {
StdIn,
StdOut,
StdErr,
File(File),
}
impl FileOrStd {
pub fn as_file(&self) -> &File {
let raw_fd_ref: &'static RawFd = match self {
FileOrStd::StdIn => &0,
FileOrStd::StdOut => &1,
FileOrStd::StdErr => &2,
FileOrStd::File(file) => return file,
};
// SAFETY: File is guaranteed to have the same ABI as RawFd
unsafe { mem::transmute(raw_fd_ref) }
}
}
fn open_fd(path: &Utf8CStr, flags: OFlag, mode: mode_t) -> OsResult<'_, OwnedFd> {
nix::fcntl::open(path, flags, Mode::from_bits_truncate(mode)).into_os_result(
"open",
Some(path),
None,
)
}
pub fn fd_path(fd: RawFd, buf: &mut dyn Utf8CStrBuf) -> OsResult<'static, ()> {
let path = cstr::buf::new::<64>()
.join_path("/proc/self/fd")
@@ -140,8 +161,14 @@ pub struct FileAttr {
pub con: crate::Utf8CStrBufArr<128>,
}
impl Default for FileAttr {
fn default() -> Self {
Self::new()
}
}
impl FileAttr {
fn new() -> Self {
pub fn new() -> Self {
FileAttr {
st: unsafe { mem::zeroed() },
#[cfg(feature = "selinux")]
@@ -190,180 +217,136 @@ impl FileAttr {
const XATTR_NAME_SELINUX: &CStr = c"security.selinux";
// Low-level methods, we should track the caller when error occurs, so return OsResult.
impl Utf8CStr {
pub fn follow_link(&self) -> &FsPathFollow {
unsafe { mem::transmute(self) }
}
pub fn open(&self, flags: i32) -> OsResult<File> {
pub fn open(&self, flags: OFlag) -> OsResult<'_, File> {
Ok(File::from(open_fd(self, flags, 0)?))
}
pub fn create(&self, flags: i32, mode: mode_t) -> OsResult<File> {
Ok(File::from(open_fd(self, O_CREAT | flags, mode)?))
pub fn create(&self, flags: OFlag, mode: mode_t) -> OsResult<'_, File> {
Ok(File::from(open_fd(self, OFlag::O_CREAT | flags, mode)?))
}
pub fn exists(&self) -> bool {
unsafe {
let mut st: stat = mem::zeroed();
libc::lstat(self.as_ptr(), &mut st) == 0
}
nix::sys::stat::lstat(self).is_ok()
}
pub fn rename_to<'a>(&'a self, name: &'a Utf8CStr) -> OsResult<'a, ()> {
unsafe {
libc::rename(self.as_ptr(), name.as_ptr()).check_os_err(
"rename",
Some(self),
Some(name),
)
}
nix::fcntl::renameat(AT_FDCWD, self, AT_FDCWD, name).check_os_err(
"rename",
Some(self),
Some(name),
)
}
pub fn remove(&self) -> OsResult<()> {
pub fn remove(&self) -> OsResult<'_, ()> {
unsafe { libc::remove(self.as_ptr()).check_os_err("remove", Some(self), None) }
}
pub fn remove_all(&self) -> OsResultStatic<()> {
let attr = self.get_attr()?;
if attr.is_dir() {
let dir = Directory::try_from(open_fd(self, O_RDONLY | O_CLOEXEC, 0)?)?;
dir.remove_all()?;
}
Ok(self.remove()?)
}
#[allow(clippy::unnecessary_cast)]
pub fn read_link(&self, buf: &mut dyn Utf8CStrBuf) -> OsResult<()> {
pub fn read_link(&self, buf: &mut dyn Utf8CStrBuf) -> OsResult<'_, ()> {
buf.clear();
unsafe {
let r = libc::readlink(self.as_ptr(), buf.as_mut_ptr(), buf.capacity() - 1)
.as_os_result("readlink", Some(self), None)? as isize;
.into_os_result("readlink", Some(self), None)? as isize;
*(buf.as_mut_ptr().offset(r) as *mut u8) = b'\0';
buf.set_len(r as usize);
}
buf.rebuild().ok();
Ok(())
}
pub fn mkdir(&self, mode: mode_t) -> OsResult<()> {
unsafe {
if libc::mkdir(self.as_ptr(), mode) < 0 {
if *errno() == EEXIST {
libc::chmod(self.as_ptr(), mode).check_os_err("chmod", Some(self), None)?;
} else {
return Err(OsError::last_os_error("mkdir", Some(self), None));
}
}
pub fn mkdir(&self, mode: mode_t) -> OsResult<'_, ()> {
match nix::unistd::mkdir(self, Mode::from_bits_truncate(mode)) {
Ok(_) | Err(Errno::EEXIST) => Ok(()),
Err(e) => Err(OsError::new(e, "mkdir", Some(self), None)),
}
Ok(())
}
pub fn mkdirs(&self, mode: mode_t) -> OsResultStatic<()> {
if self.is_empty() {
return Ok(());
}
let mut path = cstr::buf::default();
let mut components = self.split('/').filter(|s| !s.is_empty());
if self.starts_with('/') {
path.append_path("/");
}
loop {
let Some(s) = components.next() else {
break;
};
path.append_path(s);
unsafe {
if libc::mkdir(path.as_ptr(), mode) < 0 && *errno() != EEXIST {
return Err(OsError::last_os_error("mkdir", Some(&path), None))?;
}
}
}
*errno() = 0;
Ok(())
}
// Inspired by https://android.googlesource.com/platform/bionic/+/master/libc/bionic/realpath.cpp
pub fn realpath(&self, buf: &mut dyn Utf8CStrBuf) -> OsResult<()> {
let fd = self.open(O_PATH | O_CLOEXEC)?;
let mut st1: libc::stat;
let mut st2: libc::stat;
pub fn realpath(&self, buf: &mut dyn Utf8CStrBuf) -> OsResult<'_, ()> {
let fd = self.open(OFlag::O_PATH | OFlag::O_CLOEXEC)?;
let mut skip_check = false;
unsafe {
st1 = mem::zeroed();
if libc::fstat(fd.as_raw_fd(), &mut st1) < 0 {
let st1 = match nix::sys::stat::fstat(&fd) {
Ok(st) => st,
Err(_) => {
// This will only fail on Linux < 3.6
skip_check = true;
unsafe { mem::zeroed() }
}
}
};
fd_path(fd.as_raw_fd(), buf)?;
unsafe {
st2 = mem::zeroed();
libc::stat(buf.as_ptr(), &mut st2).check_os_err("stat", Some(self), None)?;
if !skip_check && (st2.st_dev != st1.st_dev || st2.st_ino != st1.st_ino) {
*errno() = ENOENT;
return Err(OsError::last_os_error("realpath", Some(self), None));
}
let st2 = nix::sys::stat::stat(buf.as_cstr()).into_os_result("stat", Some(self), None)?;
if !skip_check && (st2.st_dev != st1.st_dev || st2.st_ino != st1.st_ino) {
return Err(OsError::new(Errno::ENOENT, "realpath", Some(self), None));
}
Ok(())
}
pub fn get_attr(&self) -> OsResult<FileAttr> {
let mut attr = FileAttr::new();
unsafe {
libc::lstat(self.as_ptr(), &mut attr.st).check_os_err("lstat", Some(self), None)?;
pub fn get_attr(&self) -> OsResult<'_, FileAttr> {
#[allow(unused_mut)]
let mut attr = FileAttr {
st: nix::sys::stat::lstat(self).into_os_result("lstat", Some(self), None)?,
#[cfg(feature = "selinux")]
self.get_secontext(&mut attr.con)?;
}
con: cstr::buf::new(),
};
#[cfg(feature = "selinux")]
self.get_secontext(&mut attr.con)?;
Ok(attr)
}
pub fn set_attr<'a>(&'a self, attr: &'a FileAttr) -> OsResult<'a, ()> {
unsafe {
if !attr.is_symlink() && libc::chmod(self.as_ptr(), (attr.st.st_mode & 0o777).as_()) < 0
{
let self_attr = self.get_attr()?;
if !self_attr.is_symlink() {
return Err(OsError::last_os_error("chmod", Some(self), None));
}
if !attr.is_symlink()
&& let Err(e) = self.follow_link().chmod((attr.st.st_mode & 0o777).as_())
{
// Double check if self is symlink before reporting error
let self_attr = self.get_attr()?;
if !self_attr.is_symlink() {
return Err(e);
}
}
unsafe {
libc::lchown(self.as_ptr(), attr.st.st_uid, attr.st.st_gid).check_os_err(
"lchown",
Some(self),
None,
)?;
}
#[cfg(feature = "selinux")]
if !attr.con.is_empty() {
self.set_secontext(&attr.con)?;
}
#[cfg(feature = "selinux")]
if !attr.con.is_empty() {
self.set_secontext(&attr.con)?;
}
Ok(())
}
pub fn get_secontext(&self, con: &mut dyn Utf8CStrBuf) -> OsResult<()> {
unsafe {
let sz = libc::lgetxattr(
pub fn get_secontext(&self, con: &mut dyn Utf8CStrBuf) -> OsResult<'_, ()> {
con.clear();
let result = unsafe {
libc::lgetxattr(
self.as_ptr(),
XATTR_NAME_SELINUX.as_ptr(),
con.as_mut_ptr().cast(),
con.capacity(),
);
if sz < 1 {
con.clear();
if *errno() != libc::ENODATA {
return Err(OsError::last_os_error("lgetxattr", Some(self), None));
}
} else {
con.set_len((sz - 1) as usize);
)
.check_err()
};
match result {
Ok(_) => {
con.rebuild().ok();
Ok(())
}
Err(Errno::ENODATA) => Ok(()),
Err(e) => Err(OsError::new(e, "lgetxattr", Some(self), None)),
}
Ok(())
}
pub fn set_secontext<'a>(&'a self, con: &'a Utf8CStr) -> OsResult<'a, ()> {
@@ -379,51 +362,6 @@ impl Utf8CStr {
}
}
pub fn copy_to(&self, path: &Utf8CStr) -> OsResultStatic<()> {
let attr = self.get_attr()?;
if attr.is_dir() {
path.mkdir(0o777)?;
let mut src = Directory::open(self)?;
let dest = Directory::open(path)?;
src.copy_into(&dest)?;
} else {
// It's OK if remove failed
path.remove().ok();
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.is_symlink() {
let mut buf = cstr::buf::default();
self.read_link(&mut buf)?;
unsafe {
libc::symlink(buf.as_ptr(), path.as_ptr()).check_os_err(
"symlink",
Some(&buf),
Some(path),
)?;
}
}
}
path.set_attr(&attr)?;
Ok(())
}
pub fn move_to(&self, path: &Utf8CStr) -> OsResultStatic<()> {
if path.exists() {
let attr = path.get_attr()?;
if attr.is_dir() {
let mut src = Directory::open(self)?;
let dest = Directory::open(path)?;
return src.move_into(&dest);
} else {
path.remove()?;
}
}
self.rename_to(path)?;
Ok(())
}
pub fn parent_dir(&self) -> Option<&str> {
Path::new(self.as_str())
.parent()
@@ -439,8 +377,119 @@ impl Utf8CStr {
.map(|s| unsafe { std::str::from_utf8_unchecked(s.as_bytes()) })
}
// ln -s target self
pub fn create_symlink_to<'a>(&'a self, target: &'a Utf8CStr) -> OsResult<'a, ()> {
nix::unistd::symlinkat(target, AT_FDCWD, self).check_os_err(
"symlink",
Some(target),
Some(self),
)
}
pub fn mkfifo(&self, mode: mode_t) -> OsResult<'_, ()> {
nix::unistd::mkfifo(self, Mode::from_bits_truncate(mode)).check_os_err(
"mkfifo",
Some(self),
None,
)
}
}
// High-level helper methods, composed of multiple operations.
// We should treat these as application logic and log ASAP, so return LoggedResult.
impl Utf8CStr {
pub fn remove_all(&self) -> LoggedResult<()> {
let attr = match self.get_attr() {
Ok(attr) => attr,
Err(e) => {
return match e.errno {
// Allow calling remove_all on non-existence file
Errno::ENOENT => Ok(()),
_ => Err(e)?,
};
}
};
if attr.is_dir() {
let dir = Directory::open(self)?;
dir.remove_all()?;
}
Ok(self.remove()?)
}
pub fn mkdirs(&self, mode: mode_t) -> LoggedResult<()> {
if self.is_empty() {
return Ok(());
}
let mut path = cstr::buf::default();
let mut components = self.split('/').filter(|s| !s.is_empty());
if self.starts_with('/') {
path.append_path("/");
}
loop {
let Some(s) = components.next() else {
break;
};
path.append_path(s);
path.mkdir(mode)?;
}
*errno() = 0;
Ok(())
}
pub fn copy_to(&self, path: &Utf8CStr) -> LoggedResult<()> {
let attr = self.get_attr()?;
if attr.is_dir() {
path.mkdir(0o777)?;
let mut src = Directory::open(self)?;
let dest = Directory::open(path)?;
src.copy_into(&dest)?;
} else {
// It's OK if remove failed
path.remove().ok();
if attr.is_file() {
let mut src = self.open(OFlag::O_RDONLY | OFlag::O_CLOEXEC)?;
let mut dest = path.create(
OFlag::O_WRONLY | OFlag::O_CREAT | OFlag::O_TRUNC | OFlag::O_CLOEXEC,
0o777,
)?;
std::io::copy(&mut src, &mut dest)?;
} else if attr.is_symlink() {
let mut buf = cstr::buf::default();
self.read_link(&mut buf)?;
unsafe {
libc::symlink(buf.as_ptr(), path.as_ptr()).check_os_err(
"symlink",
Some(&buf),
Some(path),
)?;
}
}
}
path.set_attr(&attr)?;
Ok(())
}
pub fn move_to(&self, path: &Utf8CStr) -> LoggedResult<()> {
if path.exists() {
let attr = path.get_attr()?;
if attr.is_dir() {
let mut src = Directory::open(self)?;
let dest = Directory::open(path)?;
return src.move_into(&dest);
} else {
path.remove()?;
}
}
self.rename_to(path)?;
Ok(())
}
// ln self path
pub fn link_to(&self, path: &Utf8CStr) -> OsResultStatic<()> {
pub fn link_to(&self, path: &Utf8CStr) -> LoggedResult<()> {
let attr = self.get_attr()?;
if attr.is_dir() {
path.mkdir(0o777)?;
@@ -459,78 +508,76 @@ impl Utf8CStr {
Ok(())
}
}
// ln -s target self
pub fn create_symlink_to<'a>(&'a self, target: &'a Utf8CStr) -> OsResult<'a, ()> {
unsafe {
libc::symlink(target.as_ptr(), self.as_ptr()).check_os_err(
"symlink",
Some(target),
Some(self),
)
}
}
pub fn mkfifo(&self, mode: mode_t) -> OsResult<()> {
unsafe { libc::mkfifo(self.as_ptr(), mode).check_os_err("mkfifo", Some(self), None) }
}
}
impl FsPathFollow {
pub fn exists(&self) -> bool {
unsafe { libc::access(self.as_ptr(), F_OK) == 0 }
nix::unistd::access(self.as_utf8_cstr(), AccessFlags::F_OK).is_ok()
}
pub fn get_attr(&self) -> OsResult<FileAttr> {
let mut attr = FileAttr::new();
unsafe {
libc::stat(self.as_ptr(), &mut attr.st).check_os_err("stat", Some(self), None)?;
pub fn chmod(&self, mode: mode_t) -> OsResult<'_, ()> {
nix::sys::stat::fchmodat(
AT_FDCWD,
self.as_utf8_cstr(),
Mode::from_bits_truncate(mode),
FchmodatFlags::FollowSymlink,
)
.check_os_err("chmod", Some(self), None)
}
pub fn get_attr(&self) -> OsResult<'_, FileAttr> {
#[allow(unused_mut)]
let mut attr = FileAttr {
st: nix::sys::stat::stat(self.as_utf8_cstr()).into_os_result(
"lstat",
Some(self),
None,
)?,
#[cfg(feature = "selinux")]
self.get_secontext(&mut attr.con)?;
}
con: cstr::buf::new(),
};
#[cfg(feature = "selinux")]
self.get_secontext(&mut attr.con)?;
Ok(attr)
}
pub fn set_attr<'a>(&'a self, attr: &'a FileAttr) -> OsResult<'a, ()> {
unsafe {
libc::chmod(self.as_ptr(), (attr.st.st_mode & 0o777).as_()).check_os_err(
"chmod",
Some(self),
None,
)?;
libc::chown(self.as_ptr(), attr.st.st_uid, attr.st.st_gid).check_os_err(
"chown",
Some(self),
None,
)?;
self.chmod((attr.st.st_mode & 0o777).as_())?;
#[cfg(feature = "selinux")]
if !attr.con.is_empty() {
self.set_secontext(&attr.con)?;
}
nix::unistd::chown(
self.as_utf8_cstr(),
Some(Uid::from(attr.st.st_uid)),
Some(Gid::from(attr.st.st_gid)),
)
.check_os_err("chown", Some(self), None)?;
#[cfg(feature = "selinux")]
if !attr.con.is_empty() {
self.set_secontext(&attr.con)?;
}
Ok(())
}
pub fn get_secontext(&self, con: &mut dyn Utf8CStrBuf) -> OsResult<()> {
unsafe {
let sz = libc::getxattr(
pub fn get_secontext(&self, con: &mut dyn Utf8CStrBuf) -> OsResult<'_, ()> {
con.clear();
let result = unsafe {
libc::getxattr(
self.as_ptr(),
XATTR_NAME_SELINUX.as_ptr(),
con.as_mut_ptr().cast(),
con.capacity(),
);
if sz < 1 {
con.clear();
if *errno() != libc::ENODATA {
return Err(OsError::last_os_error("getxattr", Some(self), None));
}
} else {
con.set_len((sz - 1) as usize);
)
.check_err()
};
match result {
Ok(_) => {
con.rebuild().ok();
Ok(())
}
Err(Errno::ENODATA) => Ok(()),
Err(e) => Err(OsError::new(e, "getxattr", Some(self), None)),
}
Ok(())
}
pub fn set_secontext<'a>(&'a self, con: &'a Utf8CStr) -> OsResult<'a, ()> {
@@ -611,7 +658,7 @@ pub fn fd_get_attr(fd: RawFd) -> OsResult<'static, FileAttr> {
Ok(attr)
}
pub fn fd_set_attr(fd: RawFd, attr: &FileAttr) -> OsResult<()> {
pub fn fd_set_attr(fd: RawFd, attr: &FileAttr) -> OsResult<'_, ()> {
unsafe {
libc::fchmod(fd, (attr.st.st_mode & 0o777).as_()).check_os_err("fchmod", None, None)?;
libc::fchown(fd, attr.st.st_uid, attr.st.st_gid).check_os_err("fchown", None, None)?;
@@ -625,25 +672,28 @@ pub fn fd_set_attr(fd: RawFd, attr: &FileAttr) -> OsResult<()> {
}
pub fn fd_get_secontext(fd: RawFd, con: &mut dyn Utf8CStrBuf) -> OsResult<'static, ()> {
unsafe {
let sz = libc::fgetxattr(
con.clear();
let result = unsafe {
libc::fgetxattr(
fd,
XATTR_NAME_SELINUX.as_ptr(),
con.as_mut_ptr().cast(),
con.capacity(),
);
if sz < 1 {
if *errno() != libc::ENODATA {
return Err(OsError::last_os_error("fgetxattr", None, None));
}
} else {
con.set_len((sz - 1) as usize);
)
.check_err()
};
match result {
Ok(_) => {
con.rebuild().ok();
Ok(())
}
Err(Errno::ENODATA) => Ok(()),
Err(e) => Err(OsError::new(e, "fgetxattr", None, None)),
}
Ok(())
}
pub fn fd_set_secontext(fd: RawFd, con: &Utf8CStr) -> OsResult<()> {
pub fn fd_set_secontext(fd: RawFd, con: &Utf8CStr) -> OsResult<'_, ()> {
unsafe {
libc::fsetxattr(
fd,
@@ -669,11 +719,11 @@ pub fn fclone_attr(a: RawFd, b: RawFd) -> OsResult<'static, ()> {
pub struct MappedFile(&'static mut [u8]);
impl MappedFile {
pub fn open(path: &Utf8CStr) -> OsResult<MappedFile> {
pub fn open(path: &Utf8CStr) -> OsResult<'_, MappedFile> {
Ok(MappedFile(map_file(path, false)?))
}
pub fn open_rw(path: &Utf8CStr) -> OsResult<MappedFile> {
pub fn open_rw(path: &Utf8CStr) -> OsResult<'_, MappedFile> {
Ok(MappedFile(map_file(path, true)?))
}
@@ -716,8 +766,8 @@ unsafe extern "C" {
}
// We mark the returned slice static because it is valid until explicitly unmapped
pub(crate) fn map_file(path: &Utf8CStr, rw: bool) -> OsResult<&'static mut [u8]> {
unsafe { map_file_at(BorrowedFd::borrow_raw(libc::AT_FDCWD), path, rw) }
pub(crate) fn map_file(path: &Utf8CStr, rw: bool) -> OsResult<'_, &'static mut [u8]> {
map_file_at(AT_FDCWD, path, rw)
}
pub(crate) fn map_file_at<'a>(
@@ -731,17 +781,9 @@ pub(crate) fn map_file_at<'a>(
#[cfg(target_pointer_width = "32")]
const BLKGETSIZE64: u32 = 0x80041272;
let flag = if rw { O_RDWR } else { O_RDONLY };
let fd = unsafe {
OwnedFd::from_raw_fd(
libc::openat(dirfd.as_raw_fd(), path.as_ptr(), flag | O_CLOEXEC).as_os_result(
"openat",
Some(path),
None,
)?,
)
};
let flag = if rw { OFlag::O_RDWR } else { OFlag::O_RDONLY };
let fd = nix::fcntl::openat(dirfd, path, flag | OFlag::O_CLOEXEC, Mode::empty())
.into_os_result("openat", Some(path), None)?;
let attr = fd_get_attr(fd.as_raw_fd())?;
let sz = if attr.is_block_device() {
let mut sz = 0_u64;
@@ -847,8 +889,8 @@ fn parse_mount_info_line(line: &str) -> Option<MountInfo> {
pub fn parse_mount_info(pid: &str) -> Vec<MountInfo> {
let mut res = vec![];
let mut path = format!("/proc/{pid}/mountinfo");
if let Ok(file) = Utf8CStr::from_string(&mut path).open(O_RDONLY | O_CLOEXEC) {
BufReader::new(file).foreach_lines(|line| {
if let Ok(file) = Utf8CStr::from_string(&mut path).open(OFlag::O_RDONLY | OFlag::O_CLOEXEC) {
BufReader::new(file).for_each_line(|line| {
parse_mount_info_line(line)
.map(|info| res.push(info))
.is_some()

View File

@@ -1,11 +1,10 @@
#pragma once
#include "../xwrap.hpp"
#include "../files.hpp"
#include "../misc.hpp"
#include "../logging.hpp"
#include "../base-rs.hpp"
#include "../files.hpp"
#include "../logging.hpp"
using rust::xpipe2;
using rust::fd_path;
using kv_pairs = std::vector<std::pair<std::string, std::string>>;

View File

@@ -0,0 +1 @@
../../../external/cxx-rs/include/cxx.h

View File

@@ -1,20 +1,21 @@
#![feature(vec_into_raw_parts)]
#![allow(clippy::missing_safety_doc)]
pub use const_format;
pub use libc;
use num_traits::FromPrimitive;
pub use {const_format, libc, nix};
pub use cstr::{
FsPathFollow, StrErr, Utf8CStr, Utf8CStrBuf, Utf8CStrBufArr, Utf8CStrBufRef, Utf8CString,
};
use cxx_extern::*;
pub use derive;
pub use dir::*;
pub use ffi::fork_dont_care;
pub use ffi::{Utf8CStrRef, fork_dont_care, set_nice_name};
pub use files::*;
pub use logging::*;
pub use misc::*;
pub use result::*;
pub mod argh;
pub mod cstr;
mod cxx_extern;
mod dir;
@@ -26,12 +27,11 @@ mod result;
mod xwrap;
#[cxx::bridge]
pub mod ffi {
mod ffi {
#[derive(Copy, Clone)]
#[repr(i32)]
#[cxx_name = "LogLevel"]
pub(crate) enum LogLevelCxx {
ErrorCxx,
Error,
Warn,
Info,
@@ -41,28 +41,32 @@ pub mod ffi {
unsafe extern "C++" {
include!("misc.hpp");
#[namespace = "rust"]
#[cxx_name = "Utf8CStr"]
type Utf8CStrRef<'a> = &'a crate::cstr::Utf8CStr;
fn mut_u8_patch(buf: &mut [u8], from: &[u8], to: &[u8]) -> Vec<usize>;
fn fork_dont_care() -> i32;
fn set_nice_name(name: Utf8CStrRef);
type FnBoolStrStr;
fn call(self: &FnBoolStrStr, key: &str, value: &str) -> bool;
type FnBoolStr;
fn call(self: &FnBoolStr, key: Utf8CStrRef) -> bool;
}
extern "Rust" {
#[cxx_name = "log_with_rs"]
fn log_from_cxx(level: LogLevelCxx, msg: Utf8CStrRef);
#[cxx_name = "set_log_level_state"]
fn set_log_level_state_cxx(level: LogLevelCxx, enabled: bool);
fn exit_on_error(b: bool);
fn cmdline_logging();
fn parse_prop_file_rs(name: Utf8CStrRef, f: &FnBoolStrStr);
#[cxx_name = "file_readline"]
fn file_readline_for_cxx(fd: i32, f: &FnBoolStr);
}
#[namespace = "rust"]
extern "Rust" {
fn xpipe2(fds: &mut [i32; 2], flags: i32) -> i32;
#[cxx_name = "fd_path"]
fn fd_path_for_cxx(fd: i32, buf: &mut [u8]) -> isize;
#[cxx_name = "map_file"]
fn map_file_for_cxx(path: Utf8CStrRef, rw: bool) -> &'static mut [u8];
#[cxx_name = "map_file_at"]
@@ -72,8 +76,9 @@ pub mod ffi {
}
}
fn set_log_level_state_cxx(level: ffi::LogLevelCxx, enabled: bool) {
if let Some(level) = LogLevel::from_i32(level.repr) {
set_log_level_state(level, enabled)
}
// In Rust, we do not want to deal with raw pointers, so we change the
// signature of all *mut c_void to usize for new_daemon_thread.
pub type ThreadEntry = extern "C" fn(usize) -> usize;
unsafe extern "C" {
pub fn new_daemon_thread(entry: ThreadEntry, arg: usize);
}

View File

@@ -1,81 +0,0 @@
#include <cstdio>
#include <android/log.h>
#include <flags.h>
#include <base.hpp>
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;
char buf[sz];
buf[0] = '\0';
// Fortify logs when a fatal error occurs. Do not run through fortify again
int len = std::min(__call_bypassing_fortify(vsnprintf)(buf, sz, fmt, ap), sz - 1);
log_with_rs(level, rust::Utf8CStr(buf, len + 1));
return len;
}
// Used to override external C library logging
extern "C" int magisk_log_print(int prio, const char *tag, const char *fmt, ...) {
LogLevel level;
switch (prio) {
case ANDROID_LOG_DEBUG:
level = LogLevel::Debug;
break;
case ANDROID_LOG_INFO:
level = LogLevel::Info;
break;
case ANDROID_LOG_WARN:
level = LogLevel::Warn;
break;
case ANDROID_LOG_ERROR:
level = LogLevel::ErrorCxx;
break;
default:
return 0;
}
char fmt_buf[4096];
auto len = strscpy(fmt_buf, tag, sizeof(fmt_buf) - 1);
// Prevent format specifications in the tag
std::replace(fmt_buf, fmt_buf + len, '%', '_');
len = ssprintf(fmt_buf + len, sizeof(fmt_buf) - len - 1, ": %s", fmt) + len;
// Ensure the fmt string always ends with newline
if (fmt_buf[len - 1] != '\n') {
fmt_buf[len] = '\n';
fmt_buf[len + 1] = '\0';
}
va_list argv;
va_start(argv, fmt);
int ret = fmt_and_log_with_rs(level, fmt_buf, argv);
va_end(argv);
return ret;
}
#define LOG_BODY(level) \
va_list argv; \
va_start(argv, fmt); \
fmt_and_log_with_rs(LogLevel::level, fmt, argv); \
va_end(argv); \
// LTO will optimize out the NOP function
#if MAGISK_DEBUG
void LOGD(const char *fmt, ...) { LOG_BODY(Debug) }
#else
void LOGD(const char *fmt, ...) {}
#endif
void LOGI(const char *fmt, ...) { LOG_BODY(Info) }
void LOGW(const char *fmt, ...) { LOG_BODY(Warn) }
void LOGE(const char *fmt, ...) { LOG_BODY(ErrorCxx) }
// Export raw symbol to fortify compat
extern "C" void __vloge(const char* fmt, va_list ap) {
fmt_and_log_with_rs(LogLevel::ErrorCxx, fmt, ap);
}

View File

@@ -1,27 +1,26 @@
use crate::ffi::LogLevelCxx;
use crate::{Utf8CStr, cstr};
use bitflags::bitflags;
use num_derive::{FromPrimitive, ToPrimitive};
use num_traits::FromPrimitive;
use std::fmt;
use std::io::{Write, stderr, stdout};
use std::process::exit;
use num_derive::{FromPrimitive, ToPrimitive};
use num_traits::FromPrimitive;
use crate::ffi::LogLevelCxx;
use crate::{Utf8CStr, cstr};
// Ugly hack to avoid using enum
#[allow(non_snake_case, non_upper_case_globals)]
mod LogFlag {
pub const DisableError: u32 = 1 << 0;
pub const DisableWarn: u32 = 1 << 1;
pub const DisableInfo: u32 = 1 << 2;
pub const DisableDebug: u32 = 1 << 3;
pub const ExitOnError: u32 = 1 << 4;
bitflags! {
#[derive(Copy, Clone)]
struct LogFlag : u32 {
const DISABLE_ERROR = 1 << 0;
const DISABLE_WARN = 1 << 1;
const DISABLE_INFO = 1 << 2;
const DISABLE_DEBUG = 1 << 3;
const EXIT_ON_ERROR = 1 << 4;
}
}
#[derive(Copy, Clone, FromPrimitive, ToPrimitive)]
#[repr(i32)]
pub enum LogLevel {
ErrorCxx = LogLevelCxx::ErrorCxx.repr,
Error = LogLevelCxx::Error.repr,
Warn = LogLevelCxx::Warn.repr,
Info = LogLevelCxx::Info.repr,
@@ -32,7 +31,7 @@ pub enum LogLevel {
// logger changes will only happen on the main thread.
pub static mut LOGGER: Logger = Logger {
write: |_, _| {},
flags: 0,
flags: LogFlag::empty(),
};
type LogWriter = fn(level: LogLevel, msg: &Utf8CStr);
@@ -41,48 +40,43 @@ pub(crate) type Formatter<'a> = &'a mut dyn fmt::Write;
#[derive(Copy, Clone)]
pub struct Logger {
pub write: LogWriter,
pub flags: u32,
flags: LogFlag,
}
pub fn exit_on_error(b: bool) {
pub fn update_logger(f: impl FnOnce(&mut Logger)) {
let mut logger = unsafe { LOGGER };
f(&mut logger);
unsafe {
if b {
LOGGER.flags |= LogFlag::ExitOnError;
} else {
LOGGER.flags &= !LogFlag::ExitOnError;
}
LOGGER = logger;
}
}
pub fn exit_on_error(b: bool) {
update_logger(|logger| logger.flags.set(LogFlag::EXIT_ON_ERROR, b));
}
impl LogLevel {
fn as_disable_flag(&self) -> u32 {
fn as_disable_flag(&self) -> LogFlag {
match *self {
LogLevel::Error | LogLevel::ErrorCxx => LogFlag::DisableError,
LogLevel::Warn => LogFlag::DisableWarn,
LogLevel::Info => LogFlag::DisableInfo,
LogLevel::Debug => LogFlag::DisableDebug,
LogLevel::Error => LogFlag::DISABLE_ERROR,
LogLevel::Warn => LogFlag::DISABLE_WARN,
LogLevel::Info => LogFlag::DISABLE_INFO,
LogLevel::Debug => LogFlag::DISABLE_DEBUG,
}
}
}
pub fn set_log_level_state(level: LogLevel, enabled: bool) {
let flag = level.as_disable_flag();
unsafe {
if enabled {
LOGGER.flags &= !flag
} else {
LOGGER.flags |= flag
}
}
update_logger(|logger| logger.flags.set(level.as_disable_flag(), enabled));
}
fn log_with_writer<F: FnOnce(LogWriter)>(level: LogLevel, f: F) {
let logger = unsafe { LOGGER };
if (logger.flags & level.as_disable_flag()) != 0 {
if logger.flags.contains(level.as_disable_flag()) {
return;
}
f(logger.write);
if matches!(level, LogLevel::ErrorCxx) && (logger.flags & LogFlag::ExitOnError) != 0 {
if matches!(level, LogLevel::Error) && logger.flags.contains(LogFlag::EXIT_ON_ERROR) {
exit(-1);
}
}
@@ -109,14 +103,7 @@ pub fn cmdline_logging() {
stderr().write_all(msg.as_bytes()).ok();
}
}
let logger = Logger {
write: cmdline_write,
flags: LogFlag::ExitOnError,
};
unsafe {
LOGGER = logger;
}
update_logger(|logger| logger.write = cmdline_write);
}
#[macro_export]

View File

@@ -1,275 +0,0 @@
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/prctl.h>
#include <fcntl.h>
#include <unistd.h>
#include <syscall.h>
#include <random>
#include <string>
#include <base.hpp>
using namespace std;
bool byte_view::contains(byte_view pattern) const {
return _buf != nullptr && memmem(_buf, _sz, pattern._buf, pattern._sz) != nullptr;
}
bool byte_view::equals(byte_view o) const {
return _sz == o._sz && memcmp(_buf, o._buf, _sz) == 0;
}
heap_data byte_view::clone() const {
heap_data copy(_sz);
memcpy(copy._buf, _buf, _sz);
return copy;
}
void byte_data::swap(byte_data &o) {
std::swap(_buf, o._buf);
std::swap(_sz, o._sz);
}
rust::Vec<size_t> byte_data::patch(byte_view from, byte_view to) {
rust::Vec<size_t> v;
if (_buf == nullptr)
return v;
auto p = _buf;
auto eof = _buf + _sz;
while (p < eof) {
p = static_cast<uint8_t *>(memmem(p, eof - p, from.buf(), from.sz()));
if (p == nullptr)
return v;
memset(p, 0, from.sz());
memcpy(p, to.buf(), to.sz());
v.push_back(p - _buf);
p += from.sz();
}
return v;
}
rust::Vec<size_t> mut_u8_patch(
rust::Slice<uint8_t> buf,
rust::Slice<const uint8_t> from,
rust::Slice<const uint8_t> to) {
byte_data data(buf);
return data.patch(from, to);
}
int fork_dont_care() {
if (int pid = xfork()) {
waitpid(pid, nullptr, 0);
return pid;
} else if (xfork()) {
exit(0);
}
return 0;
}
int fork_no_orphan() {
int pid = xfork();
if (pid)
return pid;
prctl(PR_SET_PDEATHSIG, SIGKILL);
if (getppid() == 1)
exit(1);
return 0;
}
int exec_command(exec_t &exec) {
auto pipefd = array<int, 2>{-1, -1};
int outfd = -1;
if (exec.fd == -1) {
if (xpipe2(pipefd, O_CLOEXEC) == -1)
return -1;
outfd = pipefd[1];
} else if (exec.fd >= 0) {
outfd = exec.fd;
}
int pid = exec.fork();
if (pid < 0) {
close(pipefd[0]);
close(pipefd[1]);
return -1;
} else if (pid) {
if (exec.fd == -1) {
exec.fd = pipefd[0];
close(pipefd[1]);
}
return pid;
}
// Unblock all signals
sigset_t set;
sigfillset(&set);
pthread_sigmask(SIG_UNBLOCK, &set, nullptr);
if (outfd >= 0) {
xdup2(outfd, STDOUT_FILENO);
if (exec.err)
xdup2(outfd, STDERR_FILENO);
close(outfd);
}
// Call the pre-exec callback
if (exec.pre_exec)
exec.pre_exec();
execve(exec.argv[0], (char **) exec.argv, environ);
PLOGE("execve %s", exec.argv[0]);
exit(-1);
}
int exec_command_sync(exec_t &exec) {
int pid = exec_command(exec);
if (pid < 0)
return -1;
int status;
waitpid(pid, &status, 0);
return WEXITSTATUS(status);
}
int new_daemon_thread(thread_entry entry, void *arg) {
pthread_t thread;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
errno = pthread_create(&thread, &attr, entry, arg);
if (errno) {
PLOGE("pthread_create");
}
return errno;
}
static char *argv0;
static size_t name_len;
void init_argv0(int argc, char **argv) {
argv0 = argv[0];
name_len = (argv[argc - 1] - argv[0]) + strlen(argv[argc - 1]) + 1;
}
void set_nice_name(const char *name) {
memset(argv0, 0, name_len);
strscpy(argv0, name, name_len);
prctl(PR_SET_NAME, name);
}
template<typename T, int base>
static T parse_num(string_view s) {
T val = 0;
for (char c : s) {
if (isdigit(c)) {
c -= '0';
} else if (base > 10 && isalpha(c)) {
c -= isupper(c) ? 'A' - 10 : 'a' - 10;
} else {
return -1;
}
if (c >= base) {
return -1;
}
val *= base;
val += c;
}
return val;
}
/*
* Bionic's atoi runs through strtol().
* Use our own implementation for faster conversion.
*/
int parse_int(string_view s) {
return parse_num<int, 10>(s);
}
uint32_t parse_uint32_hex(string_view s) {
return parse_num<uint32_t, 16>(s);
}
int switch_mnt_ns(int pid) {
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..
// Switch to its namespace
ret = xsetns(fd, 0);
close(fd);
}
return ret;
}
string &replace_all(string &str, string_view from, string_view to) {
size_t pos = 0;
while((pos = str.find(from, pos)) != string::npos) {
str.replace(pos, from.length(), to);
pos += to.length();
}
return str;
}
template <typename T>
static auto split_impl(string_view s, string_view delims) {
vector<T> result;
size_t base = 0;
size_t found;
while (true) {
found = s.find_first_of(delims, base);
result.emplace_back(s.substr(base, found - base));
if (found == string::npos)
break;
base = found + 1;
}
return result;
}
vector<string> split(string_view s, string_view delims) {
return split_impl<string>(s, delims);
}
#undef vsnprintf
int vssprintf(char *dest, size_t size, const char *fmt, va_list ap) {
if (size > 0) {
*dest = 0;
return std::min(vsnprintf(dest, size, fmt, ap), (int) size - 1);
}
return -1;
}
int ssprintf(char *dest, size_t size, const char *fmt, ...) {
va_list va;
va_start(va, fmt);
int r = vssprintf(dest, size, fmt, va);
va_end(va);
return r;
}
#undef strlcpy
size_t strscpy(char *dest, const char *src, size_t size) {
return std::min(strlcpy(dest, src, size), size - 1);
}
extern "C" void cxx$utf8str$new(rust::Utf8CStr *self, const void *s, size_t len);
extern "C" const char *cxx$utf8str$ptr(const rust::Utf8CStr *self);
extern "C" size_t cxx$utf8str$len(const rust::Utf8CStr *self);
rust::Utf8CStr::Utf8CStr(const char *s, size_t len) {
cxx$utf8str$new(this, s, len);
}
const char *rust::Utf8CStr::data() const {
return cxx$utf8str$ptr(this);
}
size_t rust::Utf8CStr::length() const {
return cxx$utf8str$len(this);
}

View File

@@ -5,8 +5,7 @@
#include <functional>
#include <string_view>
#include <bitset>
#include <random>
#include <cxx.h>
#include <rust/cxx.h>
#include "xwrap.hpp"
@@ -16,9 +15,11 @@ clazz(clazz &&) = delete;
#define ALLOW_MOVE_ONLY(clazz) \
clazz(const clazz&) = delete; \
clazz(clazz &&o) { swap(o); } \
clazz(clazz &&o) : clazz() { swap(o); } \
clazz& operator=(clazz &&o) { swap(o); return *this; }
struct Utf8CStr;
class mutex_guard {
DISALLOW_COPY_AND_MOVE(mutex_guard)
public:
@@ -46,30 +47,11 @@ private:
Func fn;
};
template <typename T>
class reversed_container {
public:
reversed_container(T &base) : base(base) {}
decltype(std::declval<T>().rbegin()) begin() { return base.rbegin(); }
decltype(std::declval<T>().crbegin()) begin() const { return base.crbegin(); }
decltype(std::declval<T>().crbegin()) cbegin() const { return base.crbegin(); }
decltype(std::declval<T>().rend()) end() { return base.rend(); }
decltype(std::declval<T>().crend()) end() const { return base.crend(); }
decltype(std::declval<T>().crend()) cend() const { return base.crend(); }
private:
T &base;
};
template <typename T>
reversed_container<T> reversed(T &base) {
return reversed_container<T>(base);
}
template<class T>
static void default_new(T *&p) { p = new T(); }
template<class T>
static inline void default_new(T *&p) { p = new T(); }
template<class T>
static inline void default_new(std::unique_ptr<T> &p) { p.reset(new T()); }
static void default_new(std::unique_ptr<T> &p) { p.reset(new T()); }
struct StringCmp {
using is_transparent = void;
@@ -78,49 +60,32 @@ struct StringCmp {
struct heap_data;
using ByteSlice = rust::Slice<const uint8_t>;
using MutByteSlice = rust::Slice<uint8_t>;
// Interchangeable as `&[u8]` in Rust
struct byte_view {
byte_view() : _buf(nullptr), _sz(0) {}
byte_view(const void *buf, size_t sz) : _buf((uint8_t *) buf), _sz(sz) {}
// byte_view, or any of its subclass, can be copied as byte_view
// byte_view, or any of its subclasses, can be copied as byte_view
byte_view(const byte_view &o) : _buf(o._buf), _sz(o._sz) {}
// Bridging to Rust slice
byte_view(rust::Slice<const uint8_t> o) : byte_view(o.data(), o.size()) {}
operator rust::Slice<const uint8_t>() const { return rust::Slice<const uint8_t>(_buf, _sz); }
// Transparent conversion to Rust slice
byte_view(const ByteSlice o) : byte_view(o.data(), o.size()) {}
operator ByteSlice() const { return {_buf, _sz}; }
// String as bytes
byte_view(const char *s, bool with_nul = true)
: byte_view(std::string_view(s), with_nul, false) {}
byte_view(const std::string &s, bool with_nul = true)
: byte_view(std::string_view(s), with_nul, false) {}
byte_view(std::string_view s, bool with_nul = true)
: byte_view(s, with_nul, true /* string_view is not guaranteed to null terminate */ ) {}
// Vector as bytes
byte_view(const std::vector<uint8_t> &v) : byte_view(v.data(), v.size()) {}
const uint8_t *buf() const { return _buf; }
size_t sz() const { return _sz; }
// String as bytes, including null terminator
byte_view(const char *s) : byte_view(s, strlen(s) + 1) {}
const uint8_t *data() const { return _buf; }
size_t size() const { return _sz; }
bool contains(byte_view pattern) const;
bool equals(byte_view o) const;
heap_data clone() const;
bool operator==(byte_view rhs) const;
protected:
uint8_t *_buf;
size_t _sz;
private:
byte_view(std::string_view s, bool with_nul, bool check_nul)
: byte_view(static_cast<const void *>(s.data()), s.length()) {
if (with_nul) {
if (check_nul && s[s.length()] != '\0')
return;
++_sz;
}
}
};
// Interchangeable as `&mut [u8]` in Rust
@@ -128,23 +93,18 @@ struct byte_data : public byte_view {
byte_data() = default;
byte_data(void *buf, size_t sz) : byte_view(buf, sz) {}
// byte_data, or any of its subclass, can be copied as byte_data
// byte_data, or any of its subclasses, can be copied as byte_data
byte_data(const byte_data &o) : byte_data(o._buf, o._sz) {}
// Transparent conversion from common C++ types to mutable byte references
byte_data(std::string &s, bool with_nul = true)
: byte_data(s.data(), with_nul ? s.length() + 1 : s.length()) {}
byte_data(std::vector<uint8_t> &v) : byte_data(v.data(), v.size()) {}
// Transparent conversion to Rust slice
byte_data(const MutByteSlice o) : byte_data(o.data(), o.size()) {}
operator MutByteSlice() const { return {_buf, _sz}; }
// Bridging to Rust slice
byte_data(rust::Slice<uint8_t> o) : byte_data(o.data(), o.size()) {}
operator rust::Slice<uint8_t>() { return rust::Slice<uint8_t>(_buf, _sz); }
using byte_view::buf;
uint8_t *buf() { return _buf; }
using byte_view::data;
uint8_t *data() const { return _buf; }
void swap(byte_data &o);
rust::Vec<size_t> patch(byte_view from, byte_view to);
rust::Vec<size_t> patch(byte_view from, byte_view to) const;
};
struct heap_data : public byte_data {
@@ -170,10 +130,7 @@ private:
int fd;
};
rust::Vec<size_t> mut_u8_patch(
rust::Slice<uint8_t> buf,
rust::Slice<const uint8_t> from,
rust::Slice<const uint8_t> to);
rust::Vec<size_t> mut_u8_patch(MutByteSlice buf, ByteSlice from, ByteSlice to);
uint32_t parse_uint32_hex(std::string_view s);
int parse_int(std::string_view s);
@@ -181,21 +138,6 @@ int parse_int(std::string_view s);
using thread_entry = void *(*)(void *);
extern "C" int new_daemon_thread(thread_entry entry, void *arg = nullptr);
static inline bool str_contains(std::string_view s, std::string_view ss) {
return s.find(ss) != std::string::npos;
}
static inline bool str_starts(std::string_view s, std::string_view ss) {
return s.size() >= ss.size() && s.compare(0, ss.size(), ss) == 0;
}
static inline bool str_ends(std::string_view s, std::string_view ss) {
return s.size() >= ss.size() && s.compare(s.size() - ss.size(), std::string::npos, ss) == 0;
}
static inline std::string ltrim(std::string &&s) {
s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) {
return !std::isspace(ch);
}));
return std::move(s);
}
static inline std::string rtrim(std::string &&s) {
s.erase(std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) {
return !std::isspace(ch) && ch != '\0';
@@ -206,7 +148,7 @@ static inline std::string rtrim(std::string &&s) {
int fork_dont_care();
int fork_no_orphan();
void init_argv0(int argc, char **argv);
void set_nice_name(const char *name);
void set_nice_name(Utf8CStr name);
int switch_mnt_ns(int pid);
std::string &replace_all(std::string &str, std::string_view from, std::string_view to);
std::vector<std::string> split(std::string_view s, std::string_view delims);
@@ -267,8 +209,6 @@ constexpr auto operator+(T e) noexcept ->
return static_cast<std::underlying_type_t<T>>(e);
}
namespace rust {
struct Utf8CStr {
const char *data() const;
size_t length() const;
@@ -276,14 +216,14 @@ struct Utf8CStr {
Utf8CStr() : Utf8CStr("", 1) {};
Utf8CStr(const Utf8CStr &o) = default;
Utf8CStr(Utf8CStr &&o) = default;
Utf8CStr(const char *s) : Utf8CStr(s, strlen(s) + 1) {};
Utf8CStr(std::string_view s) : Utf8CStr(s.data(), s.length() + 1) {};
Utf8CStr(std::string s) : Utf8CStr(s.data(), s.length() + 1) {};
const char *c_str() const { return this->data(); }
size_t size() const { return this->length(); }
bool empty() const { return this->length() == 0 ; }
operator std::string_view() { return {data(), length()}; }
std::string_view sv() const { return {data(), length()}; }
operator std::string_view() const { return sv(); }
bool operator==(std::string_view rhs) const { return sv() == rhs; }
private:
#pragma clang diagnostic push
@@ -292,4 +232,19 @@ private:
#pragma clang diagnostic pop
};
} // namespace rust
// Bindings for std::function to be callable from Rust
using CxxFnBoolStrStr = std::function<bool(rust::Str, rust::Str)>;
struct FnBoolStrStr : public CxxFnBoolStrStr {
using CxxFnBoolStrStr::function;
bool call(rust::Str a, rust::Str b) const {
return operator()(a, b);
}
};
using CxxFnBoolStr = std::function<bool(Utf8CStr)>;
struct FnBoolStr : public CxxFnBoolStr {
using CxxFnBoolStr::function;
bool call(Utf8CStr s) const {
return operator()(s);
}
};

View File

@@ -1,5 +1,5 @@
use crate::{StrErr, Utf8CStr, ffi};
use argh::EarlyExit;
use super::argh::{EarlyExit, MissingRequirements};
use crate::{Utf8CStr, Utf8CString, cstr, ffi};
use libc::c_char;
use std::fmt::Arguments;
use std::io::Write;
@@ -76,15 +76,6 @@ impl<T: AsMut<[u8]> + ?Sized> MutBytesExt for T {
}
}
// SAFETY: libc guarantees argc and argv are properly setup and are static
#[allow(clippy::not_unsafe_ptr_arg_deref)]
pub fn map_args(argc: i32, argv: *const *const c_char) -> Result<Vec<&'static str>, StrErr> {
unsafe { slice::from_raw_parts(argv, argc as usize) }
.iter()
.map(|s| unsafe { Utf8CStr::from_ptr(*s) }.map(|s| s.as_str()))
.collect()
}
pub trait EarlyExitExt<T> {
fn on_early_exit<F: FnOnce()>(self, print_help_msg: F) -> T;
}
@@ -93,17 +84,61 @@ impl<T> EarlyExitExt<T> for Result<T, EarlyExit> {
fn on_early_exit<F: FnOnce()>(self, print_help_msg: F) -> T {
match self {
Ok(t) => t,
Err(EarlyExit { output, status }) => match status {
Ok(_) => {
Err(EarlyExit { output, is_help }) => {
if is_help {
print_help_msg();
exit(0)
}
Err(_) => {
} else {
eprintln!("{output}");
print_help_msg();
exit(1)
}
},
}
}
}
}
pub struct PositionalArgParser<'a>(pub slice::Iter<'a, &'a str>);
impl PositionalArgParser<'_> {
pub fn required(&mut self, field_name: &'static str) -> Result<Utf8CString, EarlyExit> {
if let Some(next) = self.0.next() {
Ok((*next).into())
} else {
let mut missing = MissingRequirements::default();
missing.missing_positional_arg(field_name);
missing.err_on_any()?;
unreachable!()
}
}
pub fn optional(&mut self) -> Option<Utf8CString> {
self.0.next().map(|s| (*s).into())
}
pub fn last_required(&mut self, field_name: &'static str) -> Result<Utf8CString, EarlyExit> {
let r = self.required(field_name)?;
self.ensure_end()?;
Ok(r)
}
pub fn last_optional(&mut self) -> Result<Option<Utf8CString>, EarlyExit> {
let r = self.optional();
if r.is_none() {
return Ok(r);
}
self.ensure_end()?;
Ok(r)
}
fn ensure_end(&mut self) -> Result<(), EarlyExit> {
if self.0.len() == 0 {
Ok(())
} else {
Err(EarlyExit::from(format!(
"Unrecognized argument: {}\n",
self.0.next().unwrap()
)))
}
}
}
@@ -227,3 +262,35 @@ impl Chunker {
chunk
}
}
pub struct CmdArgs(pub Vec<&'static str>);
impl CmdArgs {
#[allow(clippy::not_unsafe_ptr_arg_deref)]
pub fn new(argc: i32, argv: *const *const c_char) -> CmdArgs {
CmdArgs(
// SAFETY: libc guarantees argc and argv are properly setup and are static
unsafe { slice::from_raw_parts(argv, argc as usize) }
.iter()
.map(|s| unsafe { Utf8CStr::from_ptr(*s) })
.map(|r| r.unwrap_or(cstr!("<invalid>")))
.map(Utf8CStr::as_str)
.collect(),
)
}
pub fn as_slice(&self) -> &[&'static str] {
self.0.as_slice()
}
pub fn iter(&self) -> slice::Iter<'_, &'static str> {
self.0.iter()
}
pub fn cstr_iter(&self) -> impl Iterator<Item = &'static Utf8CStr> {
// SAFETY: libc guarantees null terminated strings
self.0
.iter()
.map(|s| unsafe { Utf8CStr::from_raw_parts(s.as_ptr().cast(), s.len() + 1) })
}
}

View File

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

View File

@@ -1,14 +0,0 @@
#include <new>
#include <cstdlib>
/* Override libc++ new implementation
* to optimize final build size */
void* operator new(std::size_t s) { return std::malloc(s); }
void* operator new[](std::size_t s) { return std::malloc(s); }
void operator delete(void *p) { std::free(p); }
void operator delete[](void *p) { std::free(p); }
void* operator new(std::size_t s, const std::nothrow_t&) noexcept { return std::malloc(s); }
void* operator new[](std::size_t s, const std::nothrow_t&) noexcept { return std::malloc(s); }
void operator delete(void *p, const std::nothrow_t&) noexcept { std::free(p); }
void operator delete[](void *p, const std::nothrow_t&) noexcept { std::free(p); }

View File

@@ -1,10 +1,10 @@
use crate::logging::Formatter;
use crate::{LogLevel, errno, log_with_args, log_with_formatter};
use crate::{LogLevel, log_with_args, log_with_formatter};
use nix::errno::Errno;
use std::fmt;
use std::fmt::Display;
use std::panic::Location;
use std::ptr::NonNull;
use std::{fmt, io};
use thiserror::Error;
// Error handling throughout the Rust codebase in Magisk:
//
@@ -13,9 +13,6 @@ use thiserror::Error;
// 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 {}
@@ -23,32 +20,29 @@ pub type LoggedResult<T> = Result<T, LoggedError>;
#[macro_export]
macro_rules! log_err {
() => {{
Err($crate::LoggedError::default())
}};
($($args:tt)+) => {{
$crate::error!($($args)+);
$crate::LoggedError::default()
Err($crate::LoggedError::default())
}};
}
// Any result or option can be silenced
pub trait SilentResultExt<T> {
pub trait SilentLogExt<T> {
fn silent(self) -> LoggedResult<T>;
}
impl<T, E> SilentResultExt<T> for Result<T, E> {
impl<T, E> SilentLogExt<T> for Result<T, E> {
fn silent(self) -> LoggedResult<T> {
match self {
Ok(v) => Ok(v),
Err(_) => Err(LoggedError::default()),
}
self.map_err(|_| LoggedError::default())
}
}
impl<T> SilentResultExt<T> for Option<T> {
impl<T> SilentLogExt<T> for Option<T> {
fn silent(self) -> LoggedResult<T> {
match self {
Some(v) => Ok(v),
None => Err(LoggedError::default()),
}
self.ok_or_else(LoggedError::default)
}
}
@@ -59,170 +53,181 @@ pub trait ResultExt<T> {
fn log_ok(self);
}
// Internal C++ bridging logging routines
pub(crate) trait CxxResultExt<T> {
fn log_cxx(self) -> LoggedResult<T>;
// Public API for converting Option to LoggedResult
pub trait OptionExt<T> {
fn ok_or_log(self) -> LoggedResult<T>;
fn ok_or_log_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>;
impl<T> OptionExt<T> for Option<T> {
#[inline(always)]
fn ok_or_log(self) -> LoggedResult<T> {
self.ok_or_else(LoggedError::default)
}
#[cfg(not(debug_assertions))]
fn ok_or_log_msg<F: FnOnce(Formatter) -> fmt::Result>(self, f: F) -> LoggedResult<T> {
self.ok_or_else(|| {
do_log_msg(LogLevel::Error, None, f);
LoggedError::default()
})
}
#[track_caller]
#[cfg(debug_assertions)]
fn ok_or_log_msg<F: FnOnce(Formatter) -> fmt::Result>(self, f: F) -> LoggedResult<T> {
let caller = Some(Location::caller());
self.ok_or_else(|| {
do_log_msg(LogLevel::Error, caller, f);
LoggedError::default()
})
}
}
trait Loggable {
fn do_log(self, level: LogLevel, caller: Option<&'static Location>) -> LoggedError;
fn do_log_msg<F: FnOnce(Formatter) -> fmt::Result>(
self,
level: LogLevel,
caller: Option<&'static Location>,
f: F,
) -> LoggedResult<T>;
) -> LoggedError;
}
impl<T, R: Loggable<T>> CxxResultExt<T> for R {
fn log_cxx(self) -> LoggedResult<T> {
self.do_log(LogLevel::ErrorCxx, None)
}
}
impl<T, R: Loggable<T>> ResultExt<T> for R {
impl<T, E: Loggable> ResultExt<T> for Result<T, E> {
#[cfg(not(debug_assertions))]
fn log(self) -> LoggedResult<T> {
self.do_log(LogLevel::Error, None)
self.map_err(|e| e.do_log(LogLevel::Error, None))
}
#[track_caller]
#[cfg(debug_assertions)]
fn log(self) -> LoggedResult<T> {
self.do_log(LogLevel::Error, Some(Location::caller()))
let caller = Some(Location::caller());
self.map_err(|e| e.do_log(LogLevel::Error, 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)
self.map_err(|e| e.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)
let caller = Some(Location::caller());
self.map_err(|e| e.do_log_msg(LogLevel::Error, caller, f))
}
#[cfg(not(debug_assertions))]
fn log_ok(self) {
self.log().ok();
self.map_err(|e| e.do_log(LogLevel::Error, None)).ok();
}
#[track_caller]
#[cfg(debug_assertions)]
fn log_ok(self) {
self.do_log(LogLevel::Error, Some(Location::caller())).ok();
let caller = Some(Location::caller());
self.map_err(|e| e.do_log(LogLevel::Error, caller)).ok();
}
}
impl<T> Loggable<T> for LoggedResult<T> {
fn do_log(self, _: LogLevel, _: Option<&'static Location>) -> LoggedResult<T> {
impl<T> ResultExt<T> for LoggedResult<T> {
fn log(self) -> 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())
}
}
#[cfg(not(debug_assertions))]
fn log_with_msg<F: FnOnce(Formatter) -> fmt::Result>(self, f: F) -> LoggedResult<T> {
self.inspect_err(|_| 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> {
let caller = Some(Location::caller());
self.inspect_err(|_| do_log_msg(LogLevel::Error, caller, f))
}
fn log_ok(self) {}
}
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, "[{}:{}] {:#}", caller.file(), caller.line(), e);
} else {
log_with_args!(level, "{:#}", 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 {
// Allow converting Loggable errors to LoggedError to support `?` operator
impl<T: Loggable> From<T> for LoggedError {
#[cfg(not(debug_assertions))]
fn from(e: T) -> Self {
log_with_args!(LogLevel::Error, "{:#}", e);
LoggedError::default()
e.do_log(LogLevel::Error, None)
}
#[track_caller]
#[cfg(debug_assertions)]
fn from(e: T) -> Self {
let caller = Location::caller();
log_with_args!(
LogLevel::Error,
"[{}:{}] {:#}",
caller.file(),
caller.line(),
e
);
let caller = Some(Location::caller());
e.do_log(LogLevel::Error, caller)
}
}
// Actual logging implementation
// Make all printable objects Loggable
impl<T: Display> Loggable for T {
fn do_log(self, level: LogLevel, caller: Option<&'static Location>) -> LoggedError {
if let Some(caller) = caller {
log_with_args!(level, "[{}:{}] {:#}", caller.file(), caller.line(), self);
} else {
log_with_args!(level, "{:#}", self);
}
LoggedError::default()
}
fn do_log_msg<F: FnOnce(Formatter) -> fmt::Result>(
self,
level: LogLevel,
caller: Option<&'static Location>,
f: F,
) -> LoggedError {
log_with_formatter(level, |w| {
if let Some(caller) = caller {
write!(w, "[{}:{}] ", caller.file(), caller.line())?;
}
f(w)?;
writeln!(w, ": {self:#}")
});
LoggedError::default()
}
}
fn do_log_msg<F: FnOnce(Formatter) -> fmt::Result>(
level: LogLevel,
caller: Option<&'static Location>,
f: F,
) {
log_with_formatter(level, |w| {
if let Some(caller) = caller {
write!(w, "[{}:{}] ", caller.file(), caller.line())?;
}
f(w)?;
w.write_char('\n')
});
}
// Check libc return value and map to Result
pub trait LibcReturn
where
Self: Copy,
Self: Sized,
{
type Value;
fn is_error(&self) -> bool;
fn map_val(self) -> Self::Value;
fn check_err(self) -> nix::Result<Self::Value>;
fn as_os_result<'a>(
fn into_os_result<'a>(
self,
name: &'static str,
arg1: Option<&'a str>,
arg2: Option<&'a str>,
) -> OsResult<'a, Self::Value> {
if self.is_error() {
Err(OsError::last_os_error(name, arg1, arg2))
} else {
Ok(self.map_val())
}
self.check_err()
.map_err(|e| OsError::new(e, name, arg1, arg2))
}
fn check_os_err<'a>(
@@ -231,19 +236,9 @@ where
arg1: Option<&'a str>,
arg2: Option<&'a str>,
) -> OsResult<'a, ()> {
if self.is_error() {
Err(OsError::last_os_error(name, arg1, arg2))
} else {
Ok(())
}
}
fn check_io_err(self) -> io::Result<()> {
if self.is_error() {
Err(io::Error::last_os_error())
} else {
Ok(())
}
self.check_err()
.map(|_| ())
.map_err(|e| OsError::new(e, name, arg1, arg2))
}
}
@@ -253,13 +248,12 @@ macro_rules! impl_libc_return {
type Value = Self;
#[inline(always)]
fn is_error(&self) -> bool {
*self < 0
}
#[inline(always)]
fn map_val(self) -> Self::Value {
self
fn check_err(self) -> nix::Result<Self::Value> {
if self < 0 {
Err(Errno::last())
} else {
Ok(self)
}
}
}
)*)
@@ -271,68 +265,40 @@ impl<T> LibcReturn for *mut T {
type Value = NonNull<T>;
#[inline(always)]
fn is_error(&self) -> bool {
self.is_null()
fn check_err(self) -> nix::Result<Self::Value> {
NonNull::new(self).ok_or_else(Errno::last)
}
}
impl<T> LibcReturn for nix::Result<T> {
type Value = T;
#[inline(always)]
fn map_val(self) -> NonNull<T> {
// SAFETY: pointer is null checked by is_error
unsafe { NonNull::new_unchecked(self.cast()) }
}
}
#[derive(Debug)]
enum OwnableStr<'a> {
None,
Borrowed(&'a str),
Owned(Box<str>),
}
impl OwnableStr<'_> {
fn into_owned(self) -> OwnableStr<'static> {
match self {
OwnableStr::None => OwnableStr::None,
OwnableStr::Borrowed(s) => OwnableStr::Owned(Box::from(s)),
OwnableStr::Owned(s) => OwnableStr::Owned(s),
}
}
fn ok(&self) -> Option<&str> {
match self {
OwnableStr::None => None,
OwnableStr::Borrowed(s) => Some(*s),
OwnableStr::Owned(s) => Some(s),
}
}
}
impl<'a> From<Option<&'a str>> for OwnableStr<'a> {
fn from(value: Option<&'a str>) -> Self {
value.map(OwnableStr::Borrowed).unwrap_or(OwnableStr::None)
fn check_err(self) -> Self {
self
}
}
#[derive(Debug)]
pub struct OsError<'a> {
code: i32,
pub errno: Errno,
name: &'static str,
arg1: OwnableStr<'a>,
arg2: OwnableStr<'a>,
arg1: Option<&'a str>,
arg2: Option<&'a str>,
}
impl OsError<'_> {
pub fn with_os_error<'a>(
code: i32,
pub fn new<'a>(
errno: Errno,
name: &'static str,
arg1: Option<&'a str>,
arg2: Option<&'a str>,
) -> OsError<'a> {
OsError {
code,
errno,
name,
arg1: OwnableStr::from(arg1),
arg2: OwnableStr::from(arg2),
arg1,
arg2,
}
}
@@ -341,42 +307,28 @@ impl OsError<'_> {
arg1: Option<&'a str>,
arg2: Option<&'a str>,
) -> OsError<'a> {
Self::with_os_error(*errno(), name, arg1, arg2)
Self::new(Errno::last(), name, arg1, arg2)
}
pub fn set_args<'a>(self, arg1: Option<&'a str>, arg2: Option<&'a str>) -> OsError<'a> {
Self::with_os_error(self.code, self.name, arg1, arg2)
}
pub fn into_owned(self) -> OsError<'static> {
OsError {
code: *errno(),
name: self.name,
arg1: self.arg1.into_owned(),
arg2: self.arg2.into_owned(),
}
}
fn as_io_error(&self) -> io::Error {
io::Error::from_raw_os_error(self.code)
Self::new(self.errno, self.name, arg1, arg2)
}
}
impl Display for OsError<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let error = self.as_io_error();
if self.name.is_empty() {
write!(f, "{error:#}")
write!(f, "{}", self.errno)
} else {
match (self.arg1.ok(), self.arg2.ok()) {
match (self.arg1, self.arg2) {
(Some(arg1), Some(arg2)) => {
write!(f, "{} '{}' '{}': {:#}", self.name, arg1, arg2, error)
write!(f, "{} '{arg1}' '{arg2}': {}", self.name, self.errno)
}
(Some(arg1), None) => {
write!(f, "{} '{}': {:#}", self.name, arg1, error)
write!(f, "{} '{arg1}': {}", self.name, self.errno)
}
_ => {
write!(f, "{}: {:#}", self.name, error)
write!(f, "{}: {}", self.name, self.errno)
}
}
}
@@ -386,20 +338,3 @@ impl Display for OsError<'_> {
impl std::error::Error for OsError<'_> {}
pub type OsResult<'a, T> = Result<T, OsError<'a>>;
#[derive(Debug, Error)]
pub enum OsErrorStatic {
#[error(transparent)]
Os(OsError<'static>),
#[error(transparent)]
Io(#[from] io::Error),
}
// Convert non-static OsError to static
impl<'a> From<OsError<'a>> for OsErrorStatic {
fn from(value: OsError<'a>) -> Self {
OsErrorStatic::Os(value.into_owned())
}
}
pub type OsResultStatic<T> = Result<T, OsErrorStatic>;

View File

@@ -21,13 +21,8 @@ DIR *xopendir(const char *name);
DIR *xfdopendir(int fd);
dirent *xreaddir(DIR *dirp);
pid_t xsetsid();
int xsocket(int domain, int type, int protocol);
int xbind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
int xlisten(int sockfd, int backlog);
int xaccept4(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int flags);
int xstat(const char *pathname, struct stat *buf);
int xfstat(int fd, struct stat *buf);
int xdup(int fd);
int xdup2(int oldfd, int newfd);
ssize_t xreadlink(const char * __restrict__ pathname, char * __restrict__ buf, size_t bufsiz);
ssize_t xreadlinkat(
@@ -42,7 +37,6 @@ int xmkdir(const char *pathname, mode_t mode);
int xmkdirs(const char *pathname, mode_t mode);
ssize_t xsendfile(int out_fd, int in_fd, off_t *offset, size_t count);
pid_t xfork();
int xpoll(pollfd *fds, nfds_t nfds, int timeout);
ssize_t xrealpath(const char * __restrict__ path, char * __restrict__ buf, size_t bufsiz);
int xmknod(const char * pathname, mode_t mode, dev_t dev);

View File

@@ -1,12 +1,8 @@
// Functions in this file are only for exporting to C++, DO NOT USE IN RUST
use crate::cxx_extern::readlinkat;
use crate::{
BorrowedDirectory, CxxResultExt, LibcReturn, Utf8CStr, cstr, slice_from_ptr, slice_from_ptr_mut,
};
use libc::{
c_char, c_uint, c_ulong, c_void, dev_t, mode_t, nfds_t, off_t, pollfd, sockaddr, socklen_t,
};
use crate::{Directory, LibcReturn, ResultExt, Utf8CStr, cstr, slice_from_ptr, slice_from_ptr_mut};
use libc::{c_char, c_uint, c_ulong, c_void, dev_t, mode_t, off_t};
use std::ffi::CStr;
use std::fs::File;
use std::io::{Read, Write};
@@ -31,7 +27,7 @@ unsafe extern "C" fn xrealpath(path: *const c_char, buf: *mut u8, bufsz: usize)
Ok(path) => {
let mut buf = cstr::buf::wrap_ptr(buf, bufsz);
path.realpath(&mut buf)
.log_cxx()
.log()
.map_or(-1, |_| buf.len() as isize)
}
Err(_) => -1,
@@ -46,7 +42,7 @@ unsafe extern "C" fn xreadlink(path: *const c_char, buf: *mut u8, bufsz: usize)
Ok(path) => {
let mut buf = cstr::buf::wrap_ptr(buf, bufsz);
path.read_link(&mut buf)
.log_cxx()
.log()
.map_or(-1, |_| buf.len() as isize)
}
Err(_) => -1,
@@ -63,8 +59,8 @@ unsafe extern "C" fn xreadlinkat(
) -> isize {
unsafe {
readlinkat(dirfd, path, buf, bufsz)
.as_os_result("readlinkat", ptr_to_str(path), None)
.log_cxx()
.into_os_result("readlinkat", ptr_to_str(path), None)
.log()
.unwrap_or(-1)
}
}
@@ -73,8 +69,8 @@ unsafe extern "C" fn xreadlinkat(
unsafe extern "C" fn xfopen(path: *const c_char, mode: *const c_char) -> *mut libc::FILE {
unsafe {
libc::fopen(path, mode)
.as_os_result("fopen", ptr_to_str(path), None)
.log_cxx()
.into_os_result("fopen", ptr_to_str(path), None)
.log()
.map_or(ptr::null_mut(), NonNull::as_ptr)
}
}
@@ -83,8 +79,8 @@ unsafe extern "C" fn xfopen(path: *const c_char, mode: *const c_char) -> *mut li
unsafe extern "C" fn xfdopen(fd: RawFd, mode: *const c_char) -> *mut libc::FILE {
unsafe {
libc::fdopen(fd, mode)
.as_os_result("fdopen", None, None)
.log_cxx()
.into_os_result("fdopen", None, None)
.log()
.map_or(ptr::null_mut(), NonNull::as_ptr)
}
}
@@ -93,8 +89,8 @@ unsafe extern "C" fn xfdopen(fd: RawFd, mode: *const c_char) -> *mut libc::FILE
unsafe extern "C" fn xopen(path: *const c_char, flags: i32, mode: mode_t) -> RawFd {
unsafe {
libc::open(path, flags, mode as c_uint)
.as_os_result("open", ptr_to_str(path), None)
.log_cxx()
.into_os_result("open", ptr_to_str(path), None)
.log()
.unwrap_or(-1)
}
}
@@ -103,8 +99,8 @@ unsafe extern "C" fn xopen(path: *const c_char, flags: i32, mode: mode_t) -> Raw
unsafe extern "C" fn xopenat(dirfd: RawFd, path: *const c_char, flags: i32, mode: mode_t) -> RawFd {
unsafe {
libc::openat(dirfd, path, flags, mode as c_uint)
.as_os_result("openat", ptr_to_str(path), None)
.log_cxx()
.into_os_result("openat", ptr_to_str(path), None)
.log()
.unwrap_or(-1)
}
}
@@ -114,7 +110,7 @@ unsafe extern "C" fn xwrite(fd: RawFd, buf: *const u8, bufsz: usize) -> isize {
let mut file = unsafe { ManuallyDrop::new(File::from_raw_fd(fd)) };
let data = unsafe { slice_from_ptr(buf, bufsz) };
file.write_all(data)
.log_cxx()
.log()
.map_or(-1, |_| data.len() as isize)
}
@@ -122,8 +118,8 @@ unsafe extern "C" fn xwrite(fd: RawFd, buf: *const u8, bufsz: usize) -> isize {
unsafe extern "C" fn xread(fd: RawFd, buf: *mut c_void, bufsz: usize) -> isize {
unsafe {
libc::read(fd, buf, bufsz)
.as_os_result("read", None, None)
.log_cxx()
.into_os_result("read", None, None)
.log()
.unwrap_or(-1)
}
}
@@ -133,15 +129,15 @@ unsafe extern "C" fn xxread(fd: RawFd, buf: *mut u8, bufsz: usize) -> isize {
let mut file = unsafe { ManuallyDrop::new(File::from_raw_fd(fd)) };
let data = unsafe { slice_from_ptr_mut(buf, bufsz) };
file.read_exact(data)
.log_cxx()
.log()
.map_or(-1, |_| data.len() as isize)
}
pub(crate) fn xpipe2(fds: &mut [i32; 2], flags: i32) -> i32 {
unsafe {
libc::pipe2(fds.as_mut_ptr(), flags)
.as_os_result("pipe2", None, None)
.log_cxx()
.into_os_result("pipe2", None, None)
.log()
.unwrap_or(-1)
}
}
@@ -150,8 +146,8 @@ pub(crate) fn xpipe2(fds: &mut [i32; 2], flags: i32) -> i32 {
extern "C" fn xsetns(fd: RawFd, nstype: i32) -> i32 {
unsafe {
libc::setns(fd, nstype)
.as_os_result("setns", None, None)
.log_cxx()
.into_os_result("setns", None, None)
.log()
.unwrap_or(-1)
}
}
@@ -160,8 +156,8 @@ extern "C" fn xsetns(fd: RawFd, nstype: i32) -> i32 {
extern "C" fn xunshare(flags: i32) -> i32 {
unsafe {
libc::unshare(flags)
.as_os_result("unshare", None, None)
.log_cxx()
.into_os_result("unshare", None, None)
.log()
.unwrap_or(-1)
}
}
@@ -170,8 +166,8 @@ extern "C" fn xunshare(flags: i32) -> i32 {
unsafe extern "C" fn xopendir(path: *const c_char) -> *mut libc::DIR {
unsafe {
libc::opendir(path)
.as_os_result("opendir", ptr_to_str(path), None)
.log_cxx()
.into_os_result("opendir", ptr_to_str(path), None)
.log()
.map_or(ptr::null_mut(), NonNull::as_ptr)
}
}
@@ -180,16 +176,16 @@ unsafe extern "C" fn xopendir(path: *const c_char) -> *mut libc::DIR {
extern "C" fn xfdopendir(fd: RawFd) -> *mut libc::DIR {
unsafe {
libc::fdopendir(fd)
.as_os_result("fdopendir", None, None)
.log_cxx()
.into_os_result("fdopendir", None, None)
.log()
.map_or(ptr::null_mut(), NonNull::as_ptr)
}
}
#[unsafe(no_mangle)]
unsafe extern "C" fn xreaddir(mut dir: BorrowedDirectory) -> *mut libc::dirent {
unsafe extern "C" fn xreaddir(mut dir: ManuallyDrop<Directory>) -> *mut libc::dirent {
dir.read()
.log_cxx()
.log()
.ok()
.flatten()
.map_or(ptr::null_mut(), |entry| entry.as_ptr())
@@ -199,53 +195,8 @@ unsafe extern "C" fn xreaddir(mut dir: BorrowedDirectory) -> *mut libc::dirent {
extern "C" fn xsetsid() -> i32 {
unsafe {
libc::setsid()
.as_os_result("setsid", None, None)
.log_cxx()
.unwrap_or(-1)
}
}
#[unsafe(no_mangle)]
extern "C" fn xsocket(domain: i32, ty: i32, protocol: i32) -> RawFd {
unsafe {
libc::socket(domain, ty, protocol)
.as_os_result("socket", None, None)
.log_cxx()
.unwrap_or(-1)
}
}
#[unsafe(no_mangle)]
unsafe extern "C" fn xbind(socket: i32, address: *const sockaddr, len: socklen_t) -> i32 {
unsafe {
libc::bind(socket, address, len)
.as_os_result("bind", None, None)
.log_cxx()
.unwrap_or(-1)
}
}
#[unsafe(no_mangle)]
extern "C" fn xlisten(socket: i32, backlog: i32) -> i32 {
unsafe {
libc::listen(socket, backlog)
.as_os_result("listen", None, None)
.log_cxx()
.unwrap_or(-1)
}
}
#[unsafe(no_mangle)]
unsafe extern "C" fn xaccept4(
sockfd: RawFd,
addr: *mut sockaddr,
len: *mut socklen_t,
flg: i32,
) -> RawFd {
unsafe {
libc::accept4(sockfd, addr, len, flg)
.as_os_result("accept4", None, None)
.log_cxx()
.into_os_result("setsid", None, None)
.log()
.unwrap_or(-1)
}
}
@@ -254,8 +205,8 @@ unsafe extern "C" fn xaccept4(
unsafe extern "C" fn xstat(path: *const c_char, buf: *mut libc::stat) -> i32 {
unsafe {
libc::stat(path, buf)
.as_os_result("stat", ptr_to_str(path), None)
.log_cxx()
.into_os_result("stat", ptr_to_str(path), None)
.log()
.unwrap_or(-1)
}
}
@@ -264,18 +215,8 @@ unsafe extern "C" fn xstat(path: *const c_char, buf: *mut libc::stat) -> i32 {
unsafe extern "C" fn xfstat(fd: RawFd, buf: *mut libc::stat) -> i32 {
unsafe {
libc::fstat(fd, buf)
.as_os_result("fstat", None, None)
.log_cxx()
.unwrap_or(-1)
}
}
#[unsafe(no_mangle)]
extern "C" fn xdup(oldfd: RawFd) -> RawFd {
unsafe {
libc::dup(oldfd)
.as_os_result("dup", None, None)
.log_cxx()
.into_os_result("fstat", None, None)
.log()
.unwrap_or(-1)
}
}
@@ -284,8 +225,8 @@ extern "C" fn xdup(oldfd: RawFd) -> RawFd {
extern "C" fn xdup2(oldfd: RawFd, newfd: RawFd) -> RawFd {
unsafe {
libc::dup2(oldfd, newfd)
.as_os_result("dup2", None, None)
.log_cxx()
.into_os_result("dup2", None, None)
.log()
.unwrap_or(-1)
}
}
@@ -294,8 +235,8 @@ extern "C" fn xdup2(oldfd: RawFd, newfd: RawFd) -> RawFd {
unsafe extern "C" fn xsymlink(target: *const c_char, linkpath: *const c_char) -> i32 {
unsafe {
libc::symlink(target, linkpath)
.as_os_result("symlink", ptr_to_str(target), ptr_to_str(linkpath))
.log_cxx()
.into_os_result("symlink", ptr_to_str(target), ptr_to_str(linkpath))
.log()
.unwrap_or(-1)
}
}
@@ -310,8 +251,8 @@ unsafe extern "C" fn xmount(
) -> i32 {
unsafe {
libc::mount(src, target, fstype, flags, data)
.as_os_result("mount", ptr_to_str(src), ptr_to_str(target))
.log_cxx()
.into_os_result("mount", ptr_to_str(src), ptr_to_str(target))
.log()
.unwrap_or(-1)
}
}
@@ -320,8 +261,8 @@ unsafe extern "C" fn xmount(
unsafe extern "C" fn xumount2(target: *const c_char, flags: i32) -> i32 {
unsafe {
libc::umount2(target, flags)
.as_os_result("umount2", ptr_to_str(target), None)
.log_cxx()
.into_os_result("umount2", ptr_to_str(target), None)
.log()
.unwrap_or(-1)
}
}
@@ -330,8 +271,8 @@ unsafe extern "C" fn xumount2(target: *const c_char, flags: i32) -> i32 {
unsafe extern "C" fn xrename(oldname: *const c_char, newname: *const c_char) -> i32 {
unsafe {
libc::rename(oldname, newname)
.as_os_result("rename", ptr_to_str(oldname), ptr_to_str(newname))
.log_cxx()
.into_os_result("rename", ptr_to_str(oldname), ptr_to_str(newname))
.log()
.unwrap_or(-1)
}
}
@@ -340,7 +281,7 @@ unsafe extern "C" fn xrename(oldname: *const c_char, newname: *const c_char) ->
unsafe extern "C" fn xmkdir(path: *const c_char, mode: mode_t) -> i32 {
unsafe {
match Utf8CStr::from_ptr(path) {
Ok(path) => path.mkdir(mode).log_cxx().map_or(-1, |_| 0),
Ok(path) => path.mkdir(mode).log().map_or(-1, |_| 0),
Err(_) => -1,
}
}
@@ -350,7 +291,7 @@ unsafe extern "C" fn xmkdir(path: *const c_char, mode: mode_t) -> i32 {
unsafe extern "C" fn xmkdirs(path: *const c_char, mode: mode_t) -> i32 {
unsafe {
match Utf8CStr::from_ptr(path) {
Ok(path) => path.mkdirs(mode).log_cxx().map_or(-1, |_| 0),
Ok(path) => path.mkdirs(mode).log().map_or(-1, |_| 0),
Err(_) => -1,
}
}
@@ -365,8 +306,8 @@ unsafe extern "C" fn xsendfile(
) -> isize {
unsafe {
libc::sendfile(out_fd, in_fd, offset, count)
.as_os_result("sendfile", None, None)
.log_cxx()
.into_os_result("sendfile", None, None)
.log()
.unwrap_or(-1)
}
}
@@ -375,18 +316,8 @@ unsafe extern "C" fn xsendfile(
extern "C" fn xfork() -> i32 {
unsafe {
libc::fork()
.as_os_result("fork", None, None)
.log_cxx()
.unwrap_or(-1)
}
}
#[unsafe(no_mangle)]
unsafe extern "C" fn xpoll(fds: *mut pollfd, nfds: nfds_t, timeout: i32) -> i32 {
unsafe {
libc::poll(fds, nfds, timeout)
.as_os_result("poll", None, None)
.log_cxx()
.into_os_result("fork", None, None)
.log()
.unwrap_or(-1)
}
}
@@ -395,8 +326,8 @@ unsafe extern "C" fn xpoll(fds: *mut pollfd, nfds: nfds_t, timeout: i32) -> i32
unsafe extern "C" fn xmknod(pathname: *const c_char, mode: mode_t, dev: dev_t) -> i32 {
unsafe {
libc::mknod(pathname, mode, dev)
.as_os_result("mknod", ptr_to_str(pathname), None)
.log_cxx()
.into_os_result("mknod", ptr_to_str(pathname), None)
.log()
.unwrap_or(-1)
}
}

View File

@@ -12,12 +12,11 @@ cxx-gen = { workspace = true }
pb-rs = { workspace = true }
[dependencies]
base = { path = "../base" }
base = { workspace = true }
cxx = { workspace = true }
byteorder = { workspace = true }
size = { workspace = true }
quick-protobuf = { workspace = true }
argh = { workspace = true }
sha1 = { workspace = true }
sha2 = { workspace = true }
digest = { workspace = true }
@@ -33,5 +32,5 @@ num-traits = { workspace = true }
flate2 = { workspace = true, features = ["zlib-rs"] }
bzip2 = { workspace = true }
lz4 = { workspace = true }
xz2 = { workspace = true }
lzma-rust2 = { workspace = true, features = ["xz", "std", "encoder", "optimization"] }
zopfli = { workspace = true, features = ["gzip"] }

View File

@@ -15,6 +15,11 @@ using namespace std;
#define SHA256_DIGEST_SIZE 32
#define SHA_DIGEST_SIZE 20
#define RETURN_OK 0
#define RETURN_ERROR 1
#define RETURN_CHROMEOS 2
#define RETURN_VENDOR 3
static void decompress(FileFormat type, int fd, const void *in, size_t size) {
decompress_bytes(type, byte_view { in, size }, fd);
}
@@ -48,6 +53,43 @@ static bool check_env(const char *name) {
return val != nullptr && val == "true"sv;
}
FileFormat check_fmt(const void *buf, size_t len) {
if (CHECKED_MATCH(CHROMEOS_MAGIC)) {
return FileFormat::CHROMEOS;
} else if (CHECKED_MATCH(BOOT_MAGIC)) {
return FileFormat::AOSP;
} else if (CHECKED_MATCH(VENDOR_BOOT_MAGIC)) {
return FileFormat::AOSP_VENDOR;
} else if (CHECKED_MATCH(GZIP1_MAGIC) || CHECKED_MATCH(GZIP2_MAGIC)) {
return FileFormat::GZIP;
} else if (CHECKED_MATCH(LZOP_MAGIC)) {
return FileFormat::LZOP;
} else if (CHECKED_MATCH(XZ_MAGIC)) {
return FileFormat::XZ;
} else if (len >= 13 && memcmp(buf, "\x5d\x00\x00", 3) == 0
&& (((char *)buf)[12] == '\xff' || ((char *)buf)[12] == '\x00')) {
return FileFormat::LZMA;
} else if (CHECKED_MATCH(BZIP_MAGIC)) {
return FileFormat::BZIP2;
} else if (CHECKED_MATCH(LZ41_MAGIC) || CHECKED_MATCH(LZ42_MAGIC)) {
return FileFormat::LZ4;
} else if (CHECKED_MATCH(LZ4_LEG_MAGIC)) {
return FileFormat::LZ4_LEGACY;
} else if (CHECKED_MATCH(MTK_MAGIC)) {
return FileFormat::MTK;
} else if (CHECKED_MATCH(DTB_MAGIC)) {
return FileFormat::DTB;
} else if (CHECKED_MATCH(DHTB_MAGIC)) {
return FileFormat::DHTB;
} else if (CHECKED_MATCH(TEGRABLOB_MAGIC)) {
return FileFormat::BLOB;
} else if (len >= 0x28 && memcmp(&((char *)buf)[0x24], ZIMAGE_MAGIC, 4) == 0) {
return FileFormat::ZIMAGE;
} else {
return FileFormat::UNKNOWN;
}
}
void dyn_img_hdr::print() const {
uint32_t ver = header_version();
fprintf(stderr, "%-*s [%u]\n", PADDING, "HEADER_VER", ver);
@@ -119,7 +161,7 @@ void dyn_img_hdr::dump_hdr_file() const {
}
void dyn_img_hdr::load_hdr_file() {
parse_prop_file(HEADER_FILE, [=, this](string_view key, string_view value) -> bool {
parse_prop_file(HEADER_FILE, [=, this](Utf8CStr key, Utf8CStr value) -> bool {
if (key == "name" && name()) {
memset(name(), 0, 16);
memcpy(name(), value.data(), value.length() > 15 ? 15 : value.length());
@@ -129,7 +171,7 @@ void dyn_img_hdr::load_hdr_file() {
if (value.length() > BOOT_ARGS_SIZE) {
memcpy(cmdline(), value.data(), BOOT_ARGS_SIZE);
auto len = std::min(value.length() - BOOT_ARGS_SIZE, (size_t) BOOT_EXTRA_ARGS_SIZE);
memcpy(extra_cmdline(), &value[BOOT_ARGS_SIZE], len);
memcpy(extra_cmdline(), value.data() + BOOT_ARGS_SIZE, len);
} else {
memcpy(cmdline(), value.data(), value.length());
}
@@ -152,8 +194,8 @@ void dyn_img_hdr::load_hdr_file() {
boot_img::boot_img(const char *image) :
map(image), k_fmt(FileFormat::UNKNOWN), r_fmt(FileFormat::UNKNOWN), e_fmt(FileFormat::UNKNOWN) {
fprintf(stderr, "Parsing boot image: [%s]\n", image);
for (const uint8_t *addr = map.buf(); addr < map.buf() + map.sz(); ++addr) {
FileFormat fmt = check_fmt(addr, map.sz());
for (const uint8_t *addr = map.data(); addr < map.data() + map.size(); ++addr) {
FileFormat fmt = check_fmt(addr, map.size());
switch (fmt) {
case FileFormat::CHROMEOS:
// chromeos require external signing
@@ -180,7 +222,7 @@ map(image), k_fmt(FileFormat::UNKNOWN), r_fmt(FileFormat::UNKNOWN), e_fmt(FileFo
break;
}
}
exit(1);
exit(RETURN_ERROR);
}
boot_img::~boot_img() {
@@ -227,7 +269,6 @@ struct [[gnu::packed]] fdt_header {
fdt32_t size_dt_struct; /* size of the structure block */
};
static int find_dtb_offset(const uint8_t *buf, unsigned sz) {
const uint8_t * const end = buf + sz;
@@ -238,9 +279,10 @@ static int find_dtb_offset(const uint8_t *buf, unsigned sz) {
auto fdt_hdr = reinterpret_cast<const fdt_header *>(curr);
// Check that fdt_header.totalsize does not overflow kernel image size
// Check that fdt_header.totalsize does not overflow kernel image size or is empty dtb
// https://github.com/torvalds/linux/commit/7b937cc243e5b1df8780a0aa743ce800df6c68d1
uint32_t totalsize = fdt_hdr->totalsize;
if (totalsize > end - curr)
if (totalsize > end - curr || totalsize <= 0x48)
continue;
// Check that fdt_header.off_dt_struct does not overflow kernel image size
@@ -277,62 +319,61 @@ static FileFormat check_fmt_lg(const uint8_t *buf, unsigned sz) {
#define CMD_MATCH(s) BUFFER_MATCH(h->cmdline, s)
pair<const uint8_t *, dyn_img_hdr *> boot_img::create_hdr(const uint8_t *addr, FileFormat type) {
const uint8_t *boot_img::parse_hdr(const uint8_t *addr, FileFormat type) {
if (type == FileFormat::AOSP_VENDOR) {
fprintf(stderr, "VENDOR_BOOT_HDR\n");
auto h = reinterpret_cast<const boot_img_hdr_vnd_v3*>(addr);
switch (h->header_version) {
case 4:
return make_pair(addr, new dyn_img_vnd_v4(addr));
hdr = new dyn_img_vnd_v4(addr);
break;
default:
return make_pair(addr, new dyn_img_vnd_v3(addr));
hdr = new dyn_img_vnd_v3(addr);
break;
}
return addr;
}
auto h = reinterpret_cast<const boot_img_hdr_v0*>(addr);
if (h->page_size >= 0x02000000) {
fprintf(stderr, "PXA_BOOT_HDR\n");
return make_pair(addr, new dyn_img_pxa(addr));
hdr = new dyn_img_pxa(addr);
return addr;
}
auto make_hdr = [](const uint8_t *ptr) -> dyn_img_hdr * {
auto make_aosp_hdr = [](const uint8_t *ptr, ssize_t size = -1) -> dyn_img_hdr * {
auto h = reinterpret_cast<const boot_img_hdr_v0*>(ptr);
if (memcmp(h->magic, BOOT_MAGIC, BOOT_MAGIC_SIZE) != 0)
return nullptr;
switch (h->header_version) {
case 1:
return new dyn_img_v1(ptr);
return new dyn_img_v1(ptr, size);
case 2:
return new dyn_img_v2(ptr);
return new dyn_img_v2(ptr, size);
case 3:
return new dyn_img_v3(ptr);
return new dyn_img_v3(ptr, size);
case 4:
return new dyn_img_v4(ptr);
return new dyn_img_v4(ptr, size);
default:
return new dyn_img_v0(ptr);
return new dyn_img_v0(ptr, size);
}
};
// For NOOKHD and ACCLAIM, the entire boot image is shifted by a fixed offset.
// For AMONET, only the header is internally shifted by a fixed offset.
// For AMONET, the header itself is internally shifted by a fixed offset.
if (BUFFER_CONTAIN(addr, AMONET_MICROLOADER_SZ, AMONET_MICROLOADER_MAGIC) &&
BUFFER_MATCH(addr + AMONET_MICROLOADER_SZ, BOOT_MAGIC)) {
flags[AMONET_FLAG] = true;
fprintf(stderr, "AMONET_MICROLOADER\n");
// The real header is shifted, copy to temporary buffer
// The real header is shifted
h = reinterpret_cast<const boot_img_hdr_v0*>(addr + AMONET_MICROLOADER_SZ);
if (memcmp(h->magic, BOOT_MAGIC, BOOT_MAGIC_SIZE) != 0)
return make_pair(addr, nullptr);
auto real_hdr_sz = h->page_size - AMONET_MICROLOADER_SZ;
heap_data copy(h->page_size);
memcpy(copy.buf(), h, real_hdr_sz);
return make_pair(addr, make_hdr(copy.buf()));
hdr = make_aosp_hdr(addr + AMONET_MICROLOADER_SZ, real_hdr_sz);
return addr;
}
if (CMD_MATCH(NOOKHD_RL_MAGIC) ||
@@ -349,7 +390,51 @@ pair<const uint8_t *, dyn_img_hdr *> boot_img::create_hdr(const uint8_t *addr, F
addr += ACCLAIM_PRE_HEADER_SZ;
}
return make_pair(addr, make_hdr(addr));
hdr = make_aosp_hdr(addr);
return addr;
}
void boot_img::parse_zimage() {
z_info.hdr = reinterpret_cast<const zimage_hdr *>(kernel);
const uint8_t* piggy = nullptr;
// Skip 0x28, which includes zimage header
for (const uint8_t* curr = kernel + 0x28; curr < kernel + hdr->kernel_size(); curr++) {
if (check_fmt_lg(curr, hdr->kernel_size() - (curr - kernel)) != FileFormat::UNKNOWN) {
piggy = curr;
break;
}
}
if (piggy != nullptr) {
fprintf(stderr, "ZIMAGE_KERNEL\n");
z_info.hdr_sz = piggy - kernel;
// Find end of piggy
uint32_t piggy_size = z_info.hdr->end - z_info.hdr->start;
uint32_t piggy_end = piggy_size;
uint32_t offsets[16];
memcpy(offsets, kernel + piggy_size - sizeof(offsets), sizeof(offsets));
for (int i = 15; i >= 0; --i) {
if (offsets[i] > (piggy_size - 0xFF) && offsets[i] < piggy_size) {
piggy_end = offsets[i];
break;
}
}
if (piggy_end == piggy_size) {
fprintf(stderr, "! Could not find end of zImage piggy, keeping raw kernel\n");
} else {
flags[ZIMAGE_KERNEL] = true;
z_info.tail = byte_view(kernel + piggy_end, hdr->kernel_size() - piggy_end);
// Shift the kernel pointer and resize
kernel += z_info.hdr_sz;
hdr->kernel_size() = piggy_end - z_info.hdr_sz;
k_fmt = check_fmt_lg(kernel, hdr->kernel_size());
}
} else {
fprintf(stderr, "! Could not find zImage piggy, keeping raw kernel\n");
}
}
static const char *vendor_ramdisk_type(int type) {
@@ -366,20 +451,37 @@ static const char *vendor_ramdisk_type(int type) {
}
}
std::span<const vendor_ramdisk_table_entry_v4> boot_img::vendor_ramdisk_tbl() const {
if (hdr->vendor_ramdisk_table_size() == 0) {
return {};
}
// v4 vendor boot contains multiple ramdisks
using table_entry = const vendor_ramdisk_table_entry_v4;
if (hdr->vendor_ramdisk_table_entry_size() != sizeof(table_entry)) {
fprintf(stderr,
"! Invalid vendor image: vendor_ramdisk_table_entry_size != %zu\n",
sizeof(table_entry));
exit(RETURN_ERROR);
}
return span(reinterpret_cast<table_entry *>(vendor_ramdisk_table), hdr->vendor_ramdisk_table_entry_num());
}
#define assert_off() \
if ((base_addr + off) > (map.buf() + map_end)) { \
if ((addr + off) > (map.data() + map_end)) { \
fprintf(stderr, "Corrupted boot image!\n"); \
return false; \
return false; \
}
#define get_block(name) \
name = base_addr + off; \
name = addr + off; \
off += hdr->name##_size(); \
off = align_to(off, hdr->page_size()); \
assert_off();
assert_off()
bool boot_img::parse_image(const uint8_t *p, FileFormat type) {
auto [base_addr, hdr] = create_hdr(p, type);
bool boot_img::parse_image(const uint8_t *addr, FileFormat type) {
addr = parse_hdr(addr, type);
if (hdr == nullptr) {
fprintf(stderr, "Invalid boot image header!\n");
return false;
@@ -396,7 +498,7 @@ bool boot_img::parse_image(const uint8_t *p, FileFormat type) {
hdr->print();
size_t map_end = align_to(map.sz(), getpagesize());
size_t map_end = align_to(map.size(), getpagesize());
size_t off = hdr->hdr_space();
get_block(kernel);
get_block(ramdisk);
@@ -408,15 +510,15 @@ bool boot_img::parse_image(const uint8_t *p, FileFormat type) {
get_block(vendor_ramdisk_table);
get_block(bootconfig);
payload = byte_view(base_addr, off);
auto tail_addr = base_addr + off;
tail = byte_view(tail_addr, map.buf() + map_end - tail_addr);
payload = byte_view(addr, off);
auto tail_addr = addr + off;
tail = byte_view(tail_addr, map.data() + map_end - tail_addr);
if (auto size = hdr->kernel_size()) {
if (int dtb_off = find_dtb_offset(kernel, size); dtb_off > 0) {
kernel_dtb = byte_view(kernel + dtb_off, size - dtb_off);
hdr->kernel_size() = dtb_off;
fprintf(stderr, "%-*s [%zu]\n", PADDING, "KERNEL_DTB_SZ", kernel_dtb.sz());
fprintf(stderr, "%-*s [%zu]\n", PADDING, "KERNEL_DTB_SZ", kernel_dtb.size());
}
k_fmt = check_fmt_lg(kernel, hdr->kernel_size());
@@ -431,69 +533,18 @@ bool boot_img::parse_image(const uint8_t *p, FileFormat type) {
k_fmt = check_fmt_lg(kernel, hdr->kernel_size());
}
if (k_fmt == FileFormat::ZIMAGE) {
z_hdr = reinterpret_cast<const zimage_hdr *>(kernel);
const uint8_t* found_pos = 0;
for (const uint8_t* search_pos = kernel + 0x28; search_pos < kernel + hdr->kernel_size(); search_pos++) {
// ^^^^^^ +0x28 to search after zimage header and magic
if (check_fmt_lg(search_pos, hdr->kernel_size() - (search_pos - kernel)) != FileFormat::UNKNOWN) {
found_pos = search_pos;
search_pos = kernel + hdr->kernel_size();
}
}
if (found_pos != 0) {
fprintf(stderr, "ZIMAGE_KERNEL\n");
z_info.hdr_sz = (const uint8_t *) found_pos - kernel;
// Find end of piggy
uint32_t zImage_size = z_hdr->end - z_hdr->start;
uint32_t piggy_end = zImage_size;
uint32_t offsets[16];
memcpy(offsets, kernel + zImage_size - sizeof(offsets), sizeof(offsets));
for (int i = 15; i >= 0; --i) {
if (offsets[i] > (zImage_size - 0xFF) && offsets[i] < zImage_size) {
piggy_end = offsets[i];
break;
}
}
if (piggy_end == zImage_size) {
fprintf(stderr, "! Could not find end of zImage piggy, keeping raw kernel\n");
} else {
flags[ZIMAGE_KERNEL] = true;
z_info.tail = byte_view(kernel + piggy_end, hdr->kernel_size() - piggy_end);
kernel += z_info.hdr_sz;
hdr->kernel_size() = piggy_end - z_info.hdr_sz;
k_fmt = check_fmt_lg(kernel, hdr->kernel_size());
}
} else {
fprintf(stderr, "! Could not find zImage piggy, keeping raw kernel\n");
}
parse_zimage();
}
fprintf(stderr, "%-*s [%s]\n", PADDING, "KERNEL_FMT", fmt2name[k_fmt]);
fprintf(stderr, "%-*s [%s]\n", PADDING, "KERNEL_FMT", fmt2name(k_fmt));
}
if (auto size = hdr->ramdisk_size()) {
if (hdr->vendor_ramdisk_table_size()) {
// v4 vendor boot contains multiple ramdisks
using table_entry = const vendor_ramdisk_table_entry_v4;
if (hdr->vendor_ramdisk_table_entry_size() != sizeof(table_entry)) {
fprintf(stderr,
"! Invalid vendor image: vendor_ramdisk_table_entry_size != %zu\n",
sizeof(table_entry));
exit(1);
}
span<table_entry> table(
reinterpret_cast<table_entry *>(vendor_ramdisk_table),
hdr->vendor_ramdisk_table_entry_num());
for (auto &it : table) {
for (auto &it : vendor_ramdisk_tbl()) {
FileFormat fmt = check_fmt_lg(ramdisk + it.ramdisk_offset, it.ramdisk_size);
fprintf(stderr,
"%-*s name=[%s] type=[%s] size=[%u] fmt=[%s]\n", PADDING, "VND_RAMDISK",
it.ramdisk_name, vendor_ramdisk_type(it.ramdisk_type),
it.ramdisk_size, fmt2name[fmt]);
it.ramdisk_size, fmt2name(fmt));
}
} else {
r_fmt = check_fmt_lg(ramdisk, size);
@@ -507,78 +558,72 @@ bool boot_img::parse_image(const uint8_t *p, FileFormat type) {
hdr->ramdisk_size() -= sizeof(mtk_hdr);
r_fmt = check_fmt_lg(ramdisk, hdr->ramdisk_size());
}
fprintf(stderr, "%-*s [%s]\n", PADDING, "RAMDISK_FMT", fmt2name[r_fmt]);
fprintf(stderr, "%-*s [%s]\n", PADDING, "RAMDISK_FMT", fmt2name(r_fmt));
}
}
if (auto size = hdr->extra_size()) {
e_fmt = check_fmt_lg(extra, size);
fprintf(stderr, "%-*s [%s]\n", PADDING, "EXTRA_FMT", fmt2name[e_fmt]);
fprintf(stderr, "%-*s [%s]\n", PADDING, "EXTRA_FMT", fmt2name(e_fmt));
}
if (tail.sz()) {
if (tail.size()) {
// Check special flags
if (tail.sz() >= 16 && BUFFER_MATCH(tail.buf(), SEANDROID_MAGIC)) {
if (tail.size() >= 16 && BUFFER_MATCH(tail.data(), SEANDROID_MAGIC)) {
fprintf(stderr, "SAMSUNG_SEANDROID\n");
flags[SEANDROID_FLAG] = true;
} else if (tail.sz() >= 16 && BUFFER_MATCH(tail.buf(), LG_BUMP_MAGIC)) {
} else if (tail.size() >= 16 && BUFFER_MATCH(tail.data(), LG_BUMP_MAGIC)) {
fprintf(stderr, "LG_BUMP_IMAGE\n");
flags[LG_BUMP_FLAG] = true;
} else if (!(tail.sz() >= 4 && BUFFER_MATCH(tail.buf(), AVB_MAGIC)) && verify()) {
// Check if the image is avb 1.0 signed
} else if (verify()) {
fprintf(stderr, "AVB1_SIGNED\n");
flags[AVB1_SIGNED_FLAG] = true;
}
// Find AVB footer
const void *footer = tail.buf() + tail.sz() - sizeof(AvbFooter);
const void *footer = tail.data() + tail.size() - sizeof(AvbFooter);
if (BUFFER_MATCH(footer, AVB_FOOTER_MAGIC)) {
avb_footer = reinterpret_cast<const AvbFooter*>(footer);
avb_footer = static_cast<const AvbFooter*>(footer);
// Double check if meta header exists
const void *meta = base_addr + __builtin_bswap64(avb_footer->vbmeta_offset);
const void *meta = payload.data() + __builtin_bswap64(avb_footer->vbmeta_offset);
if (BUFFER_MATCH(meta, AVB_MAGIC)) {
fprintf(stderr, "VBMETA\n");
flags[AVB_FLAG] = true;
vbmeta = reinterpret_cast<const AvbVBMetaImageHeader*>(meta);
vbmeta = static_cast<const AvbVBMetaImageHeader*>(meta);
}
}
}
this->hdr = hdr;
return true;
}
bool boot_img::verify(const char *cert) const {
return rust::verify_boot_image(*this, cert);
}
int split_image_dtb(Utf8CStr filename, bool skip_decomp) {
mmap_data img(filename.c_str());
int split_image_dtb(const char *filename, bool skip_decomp) {
mmap_data img(filename);
if (size_t off = find_dtb_offset(img.buf(), img.sz()); off > 0) {
FileFormat fmt = check_fmt_lg(img.buf(), img.sz());
if (!skip_decomp && COMPRESSED(fmt)) {
if (size_t off = find_dtb_offset(img.data(), img.size()); off > 0) {
FileFormat fmt = check_fmt_lg(img.data(), img.size());
if (!skip_decomp && fmt_compressed(fmt)) {
int fd = creat(KERNEL_FILE, 0644);
decompress(fmt, fd, img.buf(), off);
decompress(fmt, fd, img.data(), off);
close(fd);
} else {
dump(img.buf(), off, KERNEL_FILE);
dump(img.data(), off, KERNEL_FILE);
}
dump(img.buf() + off, img.sz() - off, KER_DTB_FILE);
dump(img.data() + off, img.size() - off, KER_DTB_FILE);
return 0;
} else {
fprintf(stderr, "Cannot find DTB in %s\n", filename);
fprintf(stderr, "Cannot find DTB in %s\n", filename.c_str());
return 1;
}
}
int unpack(const char *image, bool skip_decomp, bool hdr) {
const boot_img boot(image);
int unpack(Utf8CStr image, bool skip_decomp, bool hdr) {
const boot_img boot(image.c_str());
if (hdr)
boot.hdr->dump_hdr_file();
// Dump kernel
if (!skip_decomp && COMPRESSED(boot.k_fmt)) {
if (!skip_decomp && fmt_compressed(boot.k_fmt)) {
if (boot.hdr->kernel_size() != 0) {
int fd = creat(KERNEL_FILE, 0644);
decompress(boot.k_fmt, fd, boot.kernel, boot.hdr->kernel_size());
@@ -589,18 +634,13 @@ int unpack(const char *image, bool skip_decomp, bool hdr) {
}
// Dump kernel_dtb
dump(boot.kernel_dtb.buf(), boot.kernel_dtb.sz(), KER_DTB_FILE);
dump(boot.kernel_dtb.data(), boot.kernel_dtb.size(), KER_DTB_FILE);
// Dump ramdisk
if (boot.hdr->vendor_ramdisk_table_size()) {
using table_entry = const vendor_ramdisk_table_entry_v4;
span<table_entry> table(
reinterpret_cast<table_entry *>(boot.vendor_ramdisk_table),
boot.hdr->vendor_ramdisk_table_entry_num());
xmkdir(VND_RAMDISK_DIR, 0755);
owned_fd dirfd = xopen(VND_RAMDISK_DIR, O_RDONLY | O_CLOEXEC);
for (auto &it : table) {
for (auto &it : boot.vendor_ramdisk_tbl()) {
char file_name[40];
if (it.ramdisk_name[0] == '\0') {
strscpy(file_name, RAMDISK_FILE, sizeof(file_name));
@@ -609,13 +649,13 @@ int unpack(const char *image, bool skip_decomp, bool hdr) {
}
owned_fd fd = xopenat(dirfd, file_name, O_CREAT | O_TRUNC | O_WRONLY | O_CLOEXEC, 0644);
FileFormat fmt = check_fmt_lg(boot.ramdisk + it.ramdisk_offset, it.ramdisk_size);
if (!skip_decomp && COMPRESSED(fmt)) {
if (!skip_decomp && fmt_compressed(fmt)) {
decompress(fmt, fd, boot.ramdisk + it.ramdisk_offset, it.ramdisk_size);
} else {
xwrite(fd, boot.ramdisk + it.ramdisk_offset, it.ramdisk_size);
}
}
} else if (!skip_decomp && COMPRESSED(boot.r_fmt)) {
} else if (!skip_decomp && fmt_compressed(boot.r_fmt)) {
if (boot.hdr->ramdisk_size() != 0) {
int fd = creat(RAMDISK_FILE, 0644);
decompress(boot.r_fmt, fd, boot.ramdisk, boot.hdr->ramdisk_size());
@@ -629,7 +669,7 @@ int unpack(const char *image, bool skip_decomp, bool hdr) {
dump(boot.second, boot.hdr->second_size(), SECOND_FILE);
// Dump extra
if (!skip_decomp && COMPRESSED(boot.e_fmt)) {
if (!skip_decomp && fmt_compressed(boot.e_fmt)) {
if (boot.hdr->extra_size() != 0) {
int fd = creat(EXTRA_FILE, 0644);
decompress(boot.e_fmt, fd, boot.extra, boot.hdr->extra_size());
@@ -648,7 +688,9 @@ int unpack(const char *image, bool skip_decomp, bool hdr) {
// Dump bootconfig
dump(boot.bootconfig, boot.hdr->bootconfig_size(), BOOTCONFIG_FILE);
return boot.flags[CHROMEOS_FLAG] ? 2 : 0;
if (boot.flags[CHROMEOS_FLAG]) return RETURN_CHROMEOS;
if (boot.hdr->is_vendor()) return RETURN_VENDOR;
return RETURN_OK;
}
#define file_align_with(page_size) \
@@ -656,9 +698,9 @@ write_zero(fd, align_padding(lseek(fd, 0, SEEK_CUR) - off.header, page_size))
#define file_align() file_align_with(boot.hdr->page_size())
void repack(const char *src_img, const char *out_img, bool skip_comp) {
const boot_img boot(src_img);
fprintf(stderr, "Repack to boot image: [%s]\n", out_img);
void repack(Utf8CStr src_img, Utf8CStr out_img, bool skip_comp) {
const boot_img boot(src_img.c_str());
fprintf(stderr, "Repack to boot image: [%s]\n", out_img.c_str());
struct {
uint32_t header;
@@ -667,7 +709,7 @@ void repack(const char *src_img, const char *out_img, bool skip_comp) {
uint32_t second;
uint32_t extra;
uint32_t dtb;
uint32_t total;
uint32_t tail;
uint32_t vbmeta;
} off{};
@@ -687,22 +729,22 @@ void repack(const char *src_img, const char *out_img, bool skip_comp) {
***************/
// Create new image
int fd = creat(out_img, 0644);
int fd = open(out_img.c_str(), O_RDWR | O_CREAT | O_TRUNC, 0644);
// Copy non-standard headers
if (boot.flags[DHTB_FLAG]) {
// Skip DHTB header
write_zero(fd, sizeof(dhtb_hdr));
xwrite(fd, boot.map.data(), sizeof(dhtb_hdr));
} else if (boot.flags[BLOB_FLAG]) {
xwrite(fd, boot.map.buf(), sizeof(blob_hdr));
xwrite(fd, boot.map.data(), sizeof(blob_hdr));
} else if (boot.flags[NOOKHD_FLAG]) {
xwrite(fd, boot.map.buf(), NOOKHD_PRE_HEADER_SZ);
xwrite(fd, boot.map.data(), NOOKHD_PRE_HEADER_SZ);
} else if (boot.flags[ACCLAIM_FLAG]) {
xwrite(fd, boot.map.buf(), ACCLAIM_PRE_HEADER_SZ);
xwrite(fd, boot.map.data(), ACCLAIM_PRE_HEADER_SZ);
}
// Copy raw header
off.header = lseek(fd, 0, SEEK_CUR);
xwrite(fd, boot.payload.buf(), hdr->hdr_space());
xwrite(fd, boot.payload.data(), hdr->hdr_space());
// kernel
off.kernel = lseek(fd, 0, SEEK_CUR);
@@ -712,16 +754,16 @@ void repack(const char *src_img, const char *out_img, bool skip_comp) {
}
if (boot.flags[ZIMAGE_KERNEL]) {
// Copy zImage headers
xwrite(fd, boot.z_hdr, boot.z_info.hdr_sz);
xwrite(fd, boot.z_info.hdr, boot.z_info.hdr_sz);
}
if (access(KERNEL_FILE, R_OK) == 0) {
mmap_data m(KERNEL_FILE);
if (!skip_comp && !COMPRESSED_ANY(check_fmt(m.buf(), m.sz())) && COMPRESSED(boot.k_fmt)) {
if (!skip_comp && !fmt_compressed_any(check_fmt(m.data(), m.size())) && fmt_compressed(boot.k_fmt)) {
// Always use zopfli for zImage compression
auto fmt = (boot.flags[ZIMAGE_KERNEL] && boot.k_fmt == FileFormat::GZIP) ? FileFormat::ZOPFLI : boot.k_fmt;
hdr->kernel_size() = compress_len(fmt, m, fd);
} else {
hdr->kernel_size() = xwrite(fd, m.buf(), m.sz());
hdr->kernel_size() = xwrite(fd, m.data(), m.size());
}
if (boot.flags[ZIMAGE_KERNEL]) {
@@ -732,7 +774,7 @@ void repack(const char *src_img, const char *out_img, bool skip_comp) {
} else if (!skip_comp) {
// Pad zeros to make sure the zImage file size does not change
// Also ensure the last 4 bytes are the uncompressed vmlinux size
uint32_t sz = m.sz();
uint32_t sz = m.size();
write_zero(fd, boot.hdr->kernel_size() - hdr->kernel_size() - sizeof(sz));
xwrite(fd, &sz, sizeof(sz));
}
@@ -747,7 +789,7 @@ void repack(const char *src_img, const char *out_img, bool skip_comp) {
if (boot.flags[ZIMAGE_KERNEL]) {
// Copy zImage tail and adjust size accordingly
hdr->kernel_size() += boot.z_info.hdr_sz;
hdr->kernel_size() += xwrite(fd, boot.z_info.tail.buf(), boot.z_info.tail.sz());
hdr->kernel_size() += xwrite(fd, boot.z_info.tail.data(), boot.z_info.tail.size());
}
// kernel dtb
@@ -762,15 +804,11 @@ void repack(const char *src_img, const char *out_img, bool skip_comp) {
xwrite(fd, boot.r_hdr, sizeof(mtk_hdr));
}
using table_entry = vendor_ramdisk_table_entry_v4;
vector<table_entry> ramdisk_table;
vector<vendor_ramdisk_table_entry_v4> ramdisk_table;
if (boot.hdr->vendor_ramdisk_table_size()) {
// Create a copy so we can modify it
auto entry_start = reinterpret_cast<const table_entry *>(boot.vendor_ramdisk_table);
ramdisk_table.insert(
ramdisk_table.begin(),
entry_start, entry_start + boot.hdr->vendor_ramdisk_table_entry_num());
ramdisk_table.assign_range(boot.vendor_ramdisk_tbl());
owned_fd dirfd = xopen(VND_RAMDISK_DIR, O_RDONLY | O_CLOEXEC);
uint32_t ramdisk_offset = 0;
@@ -784,10 +822,10 @@ void repack(const char *src_img, const char *out_img, bool skip_comp) {
mmap_data m(dirfd, file_name);
FileFormat fmt = check_fmt_lg(boot.ramdisk + it.ramdisk_offset, it.ramdisk_size);
it.ramdisk_offset = ramdisk_offset;
if (!skip_comp && !COMPRESSED_ANY(check_fmt(m.buf(), m.sz())) && COMPRESSED(fmt)) {
if (!skip_comp && !fmt_compressed_any(check_fmt(m.data(), m.size())) && fmt_compressed(fmt)) {
it.ramdisk_size = compress_len(fmt, m, fd);
} else {
it.ramdisk_size = xwrite(fd, m.buf(), m.sz());
it.ramdisk_size = xwrite(fd, m.data(), m.size());
}
ramdisk_offset += it.ramdisk_size;
}
@@ -801,13 +839,13 @@ void repack(const char *src_img, const char *out_img, bool skip_comp) {
// A v4 boot image ramdisk will have to be merged with other vendor ramdisks,
// and they have to use the exact same compression method. v4 GKIs are required to
// use lz4 (legacy), so hardcode the format here.
fprintf(stderr, "RAMDISK_FMT: [%s] -> [%s]\n", fmt2name[r_fmt], fmt2name[FileFormat::LZ4_LEGACY]);
fprintf(stderr, "RAMDISK_FMT: [%s] -> [%s]\n", fmt2name(r_fmt), fmt2name(FileFormat::LZ4_LEGACY));
r_fmt = FileFormat::LZ4_LEGACY;
}
if (!skip_comp && !COMPRESSED_ANY(check_fmt(m.buf(), m.sz())) && COMPRESSED(r_fmt)) {
if (!skip_comp && !fmt_compressed_any(check_fmt(m.data(), m.size())) && fmt_compressed(r_fmt)) {
hdr->ramdisk_size() = compress_len(r_fmt, m, fd);
} else {
hdr->ramdisk_size() = xwrite(fd, m.buf(), m.sz());
hdr->ramdisk_size() = xwrite(fd, m.data(), m.size());
}
file_align();
}
@@ -823,10 +861,10 @@ void repack(const char *src_img, const char *out_img, bool skip_comp) {
off.extra = lseek(fd, 0, SEEK_CUR);
if (access(EXTRA_FILE, R_OK) == 0) {
mmap_data m(EXTRA_FILE);
if (!skip_comp && !COMPRESSED_ANY(check_fmt(m.buf(), m.sz())) && COMPRESSED(boot.e_fmt)) {
if (!skip_comp && !fmt_compressed_any(check_fmt(m.data(), m.size())) && fmt_compressed(boot.e_fmt)) {
hdr->extra_size() = compress_len(boot.e_fmt, m, fd);
} else {
hdr->extra_size() = xwrite(fd, m.buf(), m.sz());
hdr->extra_size() = xwrite(fd, m.data(), m.size());
}
file_align();
}
@@ -853,7 +891,7 @@ void repack(const char *src_img, const char *out_img, bool skip_comp) {
// vendor ramdisk table
if (!ramdisk_table.empty()) {
xwrite(fd, ramdisk_table.data(), sizeof(table_entry) * ramdisk_table.size());
xwrite(fd, ramdisk_table.data(), sizeof(*ramdisk_table.data()) * ramdisk_table.size());
file_align();
}
@@ -863,6 +901,8 @@ void repack(const char *src_img, const char *out_img, bool skip_comp) {
file_align();
}
off.tail = lseek(fd, 0, SEEK_CUR);
// Proprietary stuffs
if (boot.flags[SEANDROID_FLAG]) {
xwrite(fd, SEANDROID_MAGIC, 16);
@@ -873,7 +913,6 @@ void repack(const char *src_img, const char *out_img, bool skip_comp) {
xwrite(fd, LG_BUMP_MAGIC, 16);
}
off.total = lseek(fd, 0, SEEK_CUR);
file_align();
// vbmeta
@@ -889,8 +928,8 @@ void repack(const char *src_img, const char *out_img, bool skip_comp) {
// Pad image to original size if not chromeos (as it requires post processing)
if (!boot.flags[CHROMEOS_FLAG]) {
off_t current = lseek(fd, 0, SEEK_CUR);
if (current < boot.map.sz()) {
write_zero(fd, boot.map.sz() - current);
if (current < boot.map.size()) {
write_zero(fd, boot.map.size() - current);
}
}
@@ -898,17 +937,19 @@ void repack(const char *src_img, const char *out_img, bool skip_comp) {
* Patch the image
******************/
uint32_t aosp_img_size = off.tail - off.header;
// Map output image as rw
mmap_data out(out_img, true);
mmap_data out(fd, lseek(fd, 0, SEEK_END), true);
// MTK headers
if (boot.flags[MTK_KERNEL]) {
auto m_hdr = reinterpret_cast<mtk_hdr *>(out.buf() + off.kernel);
auto m_hdr = reinterpret_cast<mtk_hdr *>(out.data() + off.kernel);
m_hdr->size = hdr->kernel_size();
hdr->kernel_size() += sizeof(mtk_hdr);
}
if (boot.flags[MTK_RAMDISK]) {
auto m_hdr = reinterpret_cast<mtk_hdr *>(out.buf() + off.ramdisk);
auto m_hdr = reinterpret_cast<mtk_hdr *>(out.data() + off.ramdisk);
m_hdr->size = hdr->ramdisk_size();
hdr->ramdisk_size() += sizeof(mtk_hdr);
}
@@ -920,28 +961,28 @@ void repack(const char *src_img, const char *out_img, bool skip_comp) {
if (char *id = hdr->id()) {
auto ctx = get_sha(!boot.flags[SHA256_FLAG]);
uint32_t size = hdr->kernel_size();
ctx->update(byte_view(out.buf() + off.kernel, size));
ctx->update(byte_view(out.data() + off.kernel, size));
ctx->update(byte_view(&size, sizeof(size)));
size = hdr->ramdisk_size();
ctx->update(byte_view(out.buf() + off.ramdisk, size));
ctx->update(byte_view(out.data() + off.ramdisk, size));
ctx->update(byte_view(&size, sizeof(size)));
size = hdr->second_size();
ctx->update(byte_view(out.buf() + off.second, size));
ctx->update(byte_view(out.data() + off.second, size));
ctx->update(byte_view(&size, sizeof(size)));
size = hdr->extra_size();
if (size) {
ctx->update(byte_view(out.buf() + off.extra, size));
ctx->update(byte_view(out.data() + off.extra, size));
ctx->update(byte_view(&size, sizeof(size)));
}
uint32_t ver = hdr->header_version();
if (ver == 1 || ver == 2) {
size = hdr->recovery_dtbo_size();
ctx->update(byte_view(out.buf() + hdr->recovery_dtbo_offset(), size));
ctx->update(byte_view(out.data() + hdr->recovery_dtbo_offset(), size));
ctx->update(byte_view(&size, sizeof(size)));
}
if (ver == 2) {
size = hdr->dtb_size();
ctx->update(byte_view(out.buf() + off.dtb, size));
ctx->update(byte_view(out.data() + off.dtb, size));
ctx->update(byte_view(&size, sizeof(size)));
}
memset(id, 0, BOOT_ID_SIZE);
@@ -954,42 +995,41 @@ void repack(const char *src_img, const char *out_img, bool skip_comp) {
// Copy main header
if (boot.flags[AMONET_FLAG]) {
auto real_hdr_sz = std::min(hdr->hdr_space() - AMONET_MICROLOADER_SZ, hdr->hdr_size());
memcpy(out.buf() + off.header + AMONET_MICROLOADER_SZ, hdr->raw_hdr(), real_hdr_sz);
memcpy(out.data() + off.header + AMONET_MICROLOADER_SZ, hdr->raw_hdr(), real_hdr_sz);
} else {
memcpy(out.buf() + off.header, hdr->raw_hdr(), hdr->hdr_size());
memcpy(out.data() + off.header, hdr->raw_hdr(), hdr->hdr_size());
}
if (boot.flags[AVB_FLAG]) {
// Copy and patch AVB structures
auto footer = reinterpret_cast<AvbFooter*>(out.buf() + out.sz() - sizeof(AvbFooter));
auto footer = reinterpret_cast<AvbFooter*>(out.data() + out.size() - sizeof(AvbFooter));
memcpy(footer, boot.avb_footer, sizeof(AvbFooter));
footer->original_image_size = __builtin_bswap64(off.total);
footer->original_image_size = __builtin_bswap64(aosp_img_size);
footer->vbmeta_offset = __builtin_bswap64(off.vbmeta);
if (check_env("PATCHVBMETAFLAG")) {
auto vbmeta = reinterpret_cast<AvbVBMetaImageHeader*>(out.buf() + off.vbmeta);
auto vbmeta = reinterpret_cast<AvbVBMetaImageHeader*>(out.data() + off.vbmeta);
vbmeta->flags = __builtin_bswap32(3);
}
}
if (boot.flags[DHTB_FLAG]) {
// DHTB header
auto d_hdr = reinterpret_cast<dhtb_hdr *>(out.buf());
memcpy(d_hdr, DHTB_MAGIC, 8);
d_hdr->size = off.total - sizeof(dhtb_hdr);
sha256_hash(byte_view(out.buf() + sizeof(dhtb_hdr), d_hdr->size),
byte_data(d_hdr->checksum, 32));
auto d_hdr = reinterpret_cast<dhtb_hdr *>(out.data());
d_hdr->size = aosp_img_size + 16 /* SEANDROID_MAGIC */ + 4 /* DHTB trailer */;
sha256_hash(byte_view(out.data() + sizeof(dhtb_hdr), d_hdr->size),
byte_data(d_hdr->checksum, SHA256_DIGEST_SIZE));
} else if (boot.flags[BLOB_FLAG]) {
// Blob header
auto b_hdr = reinterpret_cast<blob_hdr *>(out.buf());
b_hdr->size = off.total - sizeof(blob_hdr);
auto b_hdr = reinterpret_cast<blob_hdr *>(out.data());
b_hdr->size = aosp_img_size;
}
// Sign the image after we finish patching the boot image
if (boot.flags[AVB1_SIGNED_FLAG]) {
byte_view payload(out.buf() + off.header, off.total - off.header);
auto sig = rust::sign_boot_image(payload, "/boot", nullptr, nullptr);
byte_view payload(out.data() + off.header, aosp_img_size);
auto sig = sign_payload(payload);
if (!sig.empty()) {
lseek(fd, off.total, SEEK_SET);
lseek(fd, off.tail, SEEK_SET);
xwrite(fd, sig.data(), sig.size());
}
}
@@ -997,33 +1037,15 @@ void repack(const char *src_img, const char *out_img, bool skip_comp) {
close(fd);
}
int verify(const char *image, const char *cert) {
const boot_img boot(image);
if (cert == nullptr) {
// Boot image parsing already checks if the image is signed
return boot.flags[AVB1_SIGNED_FLAG] ? 0 : 1;
} else {
// Provide a custom certificate and re-verify
return boot.verify(cert) ? 0 : 1;
}
}
int sign(const char *image, const char *name, const char *cert, const char *key) {
const boot_img boot(image);
auto sig = rust::sign_boot_image(boot.payload, name, cert, key);
if (sig.empty())
return 1;
auto eof = boot.tail.buf() - boot.map.buf();
int fd = xopen(image, O_WRONLY | O_CLOEXEC);
if (lseek(fd, eof, SEEK_SET) != eof || xwrite(fd, sig.data(), sig.size()) != sig.size()) {
close(fd);
return 1;
}
if (auto off = lseek(fd, 0, SEEK_CUR); off < boot.map.sz()) {
// Wipe out rest of tail
write_zero(fd, boot.map.sz() - off);
}
close(fd);
return 0;
void cleanup() {
unlink(HEADER_FILE);
unlink(KERNEL_FILE);
unlink(RAMDISK_FILE);
unlink(SECOND_FILE);
unlink(KER_DTB_FILE);
unlink(EXTRA_FILE);
unlink(RECV_DTBO_FILE);
unlink(DTB_FILE);
unlink(BOOTCONFIG_FILE);
rm_rf(VND_RAMDISK_DIR);
}

View File

@@ -3,9 +3,7 @@
#include <cstdint>
#include <utility>
#include <bitset>
#include <cxx.h>
#include "format.hpp"
#include <rust/cxx.h>
/******************
* Special Headers
@@ -347,6 +345,17 @@ struct vendor_ramdisk_table_entry_v4 {
* Polymorphic Universal Header
*******************************/
template <typename T>
static T align_to(T v, int a) {
static_assert(std::is_integral_v<T>);
return (v + a - 1) / a * a;
}
template <typename T>
static T align_padding(T v, int a) {
return align_to(v, a) - v;
}
#define decl_val(name, len) \
virtual uint##len##_t name() const { return 0; }
@@ -428,9 +437,11 @@ private:
#define __impl_cls(name, hdr) \
protected: name() = default; \
public: \
name(const void *ptr) { \
raw = malloc(sizeof(hdr)); \
memcpy(raw, ptr, sizeof(hdr)); \
explicit \
name(const void *p, ssize_t sz = -1) { \
if (sz < 0) sz = sizeof(hdr); \
raw = calloc(sizeof(hdr), 1); \
memcpy(raw, p, sz); \
} \
size_t hdr_size() const override { \
return sizeof(hdr); \
@@ -604,7 +615,7 @@ struct boot_img {
const mmap_data map;
// Android image header
const dyn_img_hdr *hdr = nullptr;
dyn_img_hdr *hdr = nullptr;
// Flags to indicate the state of current boot image
std::bitset<BOOT_FLAGS_MAX> flags;
@@ -636,16 +647,16 @@ struct boot_img {
// The pointers/values after parse_image
// +---------------+
// | z_hdr | z_info.hdr_sz
// | z_info.hdr | z_info.hdr_sz
// +---------------+
// | kernel | hdr->kernel_size()
// +---------------+
// | z_info.tail | z_info.tail.sz()
// | z_info.tail |
// +---------------+
const zimage_hdr *z_hdr = nullptr;
struct {
uint32_t hdr_sz;
byte_view tail;
const zimage_hdr *hdr = nullptr;
uint32_t hdr_sz = 0;
byte_view tail{};
} z_info;
// AVB structs
@@ -666,17 +677,21 @@ struct boot_img {
// dtb embedded in kernel
byte_view kernel_dtb;
// Blocks defined in header but we do not care
byte_view ignore;
boot_img(const char *);
explicit boot_img(const char *);
~boot_img();
bool parse_image(const uint8_t *addr, FileFormat type);
std::pair<const uint8_t *, dyn_img_hdr *> create_hdr(const uint8_t *addr, FileFormat type);
void parse_zimage();
const uint8_t *parse_hdr(const uint8_t *addr, FileFormat type);
std::span<const vendor_ramdisk_table_entry_v4> vendor_ramdisk_tbl() const;
// Rust FFI
static std::unique_ptr<boot_img> create(Utf8CStr name) { return std::make_unique<boot_img>(name.c_str()); }
rust::Slice<const uint8_t> get_payload() const { return payload; }
rust::Slice<const uint8_t> get_tail() const { return tail; }
bool verify(const char *cert = nullptr) const;
bool is_signed() const { return flags[AVB1_SIGNED_FLAG]; }
uint64_t tail_off() const { return tail.data() - map.data(); }
// Implemented in Rust
bool verify() const noexcept;
};

View File

@@ -1,4 +1,5 @@
use pb_rs::{ConfigBuilder, types::FileDescriptor};
use pb_rs::ConfigBuilder;
use pb_rs::types::FileDescriptor;
use crate::codegen::gen_cxx_binding;

446
native/src/boot/cli.rs Normal file
View File

@@ -0,0 +1,446 @@
use crate::compress::{compress_cmd, decompress_cmd};
use crate::cpio::{cpio_commands, print_cpio_usage};
use crate::dtb::{DtbAction, dtb_commands, print_dtb_usage};
use crate::ffi::{BootImage, FileFormat, cleanup, repack, split_image_dtb, unpack};
use crate::patch::hexpatch;
use crate::payload::extract_boot_from_payload;
use crate::sign::{sha1_hash, sign_boot_image};
use argh::{CommandInfo, EarlyExit, FromArgs, SubCommand};
use base::libc::umask;
use base::nix::fcntl::OFlag;
use base::{
CmdArgs, EarlyExitExt, LoggedResult, MappedFile, PositionalArgParser, ResultExt, Utf8CStr,
Utf8CString, WriteExt, argh, cmdline_logging, cstr, log_err,
};
use std::ffi::c_char;
use std::io::{Seek, SeekFrom, Write};
use std::str::FromStr;
#[derive(FromArgs)]
struct Cli {
#[argh(subcommand)]
action: Action,
}
#[derive(FromArgs)]
#[argh(subcommand)]
enum Action {
Unpack(Unpack),
Repack(Repack),
Verify(Verify),
Sign(Sign),
Extract(Extract),
HexPatch(HexPatch),
Cpio(Cpio),
Dtb(Dtb),
Split(Split),
Sha1(Sha1),
Cleanup(Cleanup),
Compress(Compress),
Decompress(Decompress),
}
#[derive(FromArgs)]
#[argh(subcommand, name = "unpack")]
struct Unpack {
#[argh(switch, short = 'n', long = none)]
no_decompress: bool,
#[argh(switch, short = 'h', long = none)]
dump_header: bool,
#[argh(positional)]
img: Utf8CString,
}
#[derive(FromArgs)]
#[argh(subcommand, name = "repack")]
struct Repack {
#[argh(switch, short = 'n', long = none)]
no_compress: bool,
#[argh(positional)]
img: Utf8CString,
#[argh(positional)]
out: Option<Utf8CString>,
}
#[derive(FromArgs)]
#[argh(subcommand, name = "verify")]
struct Verify {
#[argh(positional)]
img: Utf8CString,
#[argh(positional)]
cert: Option<Utf8CString>,
}
#[derive(FromArgs)]
#[argh(subcommand, name = "sign")]
struct Sign {
#[argh(positional)]
img: Utf8CString,
#[argh(positional)]
name: Option<Utf8CString>,
#[argh(positional)]
cert: Option<Utf8CString>,
#[argh(positional)]
key: Option<Utf8CString>,
}
#[derive(FromArgs)]
#[argh(subcommand, name = "extract")]
struct Extract {
#[argh(positional)]
payload: Utf8CString,
#[argh(positional)]
partition: Option<Utf8CString>,
#[argh(positional)]
outfile: Option<Utf8CString>,
}
#[derive(FromArgs)]
#[argh(subcommand, name = "hexpatch")]
struct HexPatch {
#[argh(positional)]
file: Utf8CString,
#[argh(positional)]
src: Utf8CString,
#[argh(positional)]
dest: Utf8CString,
}
#[derive(FromArgs)]
#[argh(subcommand, name = "cpio")]
struct Cpio {
#[argh(positional)]
file: Utf8CString,
#[argh(positional)]
cmds: Vec<String>,
}
#[derive(FromArgs)]
#[argh(subcommand, name = "dtb")]
struct Dtb {
#[argh(positional)]
file: Utf8CString,
#[argh(subcommand)]
action: DtbAction,
}
#[derive(FromArgs)]
#[argh(subcommand, name = "split")]
struct Split {
#[argh(switch, short = 'n', long = none)]
no_decompress: bool,
#[argh(positional)]
file: Utf8CString,
}
#[derive(FromArgs)]
#[argh(subcommand, name = "sha1")]
struct Sha1 {
#[argh(positional)]
file: Utf8CString,
}
#[derive(FromArgs)]
#[argh(subcommand, name = "cleanup")]
struct Cleanup {}
struct Compress {
format: FileFormat,
file: Utf8CString,
out: Option<Utf8CString>,
}
impl FromArgs for Compress {
fn from_args(command_name: &[&str], args: &[&str]) -> Result<Self, EarlyExit> {
let cmd = command_name.last().copied().unwrap_or_default();
let fmt = cmd.strip_prefix("compress=").unwrap_or("gzip");
let Ok(fmt) = FileFormat::from_str(fmt) else {
return Err(EarlyExit::from(format!(
"Unsupported or unknown compression format: {fmt}\n"
)));
};
let mut iter = PositionalArgParser(args.iter());
Ok(Compress {
format: fmt,
file: iter.required("infile")?,
out: iter.last_optional()?,
})
}
}
impl SubCommand for Compress {
const COMMAND: &'static CommandInfo = &CommandInfo {
name: "compress",
description: "",
};
}
#[derive(FromArgs)]
#[argh(subcommand, name = "decompress")]
struct Decompress {
#[argh(positional)]
file: Utf8CString,
#[argh(positional)]
out: Option<Utf8CString>,
}
fn print_usage(cmd: &str) {
eprintln!(
r#"MagiskBoot - Boot Image Modification Tool
Usage: {0} <action> [args...]
Supported actions:
unpack [-n] [-h] <bootimg>
Unpack <bootimg> to its individual components, each component to
a file with its corresponding file name in the current directory.
Supported components: kernel, kernel_dtb, ramdisk.cpio, second,
dtb, extra, and recovery_dtbo.
By default, each component will be decompressed on-the-fly.
If '-n' is provided, all decompression operations will be skipped;
each component will remain untouched, dumped in its original format.
If '-h' is provided, the boot image header information will be
dumped to the file 'header', which can be used to modify header
configurations during repacking.
Return values:
0:valid 1:error 2:chromeos
repack [-n] <origbootimg> [outbootimg]
Repack boot image components using files from the current directory
to [outbootimg], or 'new-boot.img' if not specified. Current directory
should only contain required files for [outbootimg], or incorrect
[outbootimg] may be produced.
<origbootimg> is the original boot image used to unpack the components.
By default, each component will be automatically compressed using its
corresponding format detected in <origbootimg>. If a component file
in the current directory is already compressed, then no addition
compression will be performed for that specific component.
If '-n' is provided, all compression operations will be skipped.
If env variable PATCHVBMETAFLAG is set to true, all disable flags in
the boot image's vbmeta header will be set.
verify <bootimg> [x509.pem]
Check whether the boot image is signed with AVB 1.0 signature.
Optionally provide a certificate to verify whether the image is
signed by the public key certificate.
Return value:
0:valid 1:error
sign <bootimg> [name] [x509.pem pk8]
Sign <bootimg> with AVB 1.0 signature.
Optionally provide the name of the image (default: '/boot').
Optionally provide the certificate/private key pair for signing.
If the certificate/private key pair is not provided, the AOSP
verity key bundled in the executable will be used.
extract <payload.bin> [partition] [outfile]
Extract [partition] from <payload.bin> to [outfile].
If [outfile] is not specified, then output to '[partition].img'.
If [partition] is not specified, then attempt to extract either
'init_boot' or 'boot'. Which partition was chosen can be determined
by whichever 'init_boot.img' or 'boot.img' exists.
<payload.bin> can be '-' to be STDIN.
hexpatch <file> <hexpattern1> <hexpattern2>
Search <hexpattern1> in <file>, and replace it with <hexpattern2>
cpio <incpio> [commands...]
Do cpio commands to <incpio> (modifications are done in-place).
Each command is a single argument; add quotes for each command.
See "cpio --help" for supported commands.
dtb <file> <action> [args...]
Do dtb related actions to <file>.
See "dtb --help" for supported actions.
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>
cleanup
Cleanup the current working directory
compress[=format] <infile> [outfile]
Compress <infile> with [format] to [outfile].
<infile>/[outfile] can be '-' to be STDIN/STDOUT.
If [format] is not specified, then gzip will be used.
If [outfile] is not specified, then <infile> will be replaced
with another file suffixed with a matching file extension.
Supported formats:
{1}
decompress <infile> [outfile]
Detect format and decompress <infile> to [outfile].
<infile>/[outfile] can be '-' to be STDIN/STDOUT.
If [outfile] is not specified, then <infile> will be replaced
with another file removing its archive format file extension.
Supported formats:
{1}
"#,
cmd,
FileFormat::formats()
);
}
fn verify_cmd(image: &Utf8CStr, cert: Option<&Utf8CStr>) -> bool {
let image = BootImage::new(image);
match cert {
None => {
// Boot image parsing already checks if the image is signed
image.is_signed()
}
Some(_) => {
// Provide a custom certificate and re-verify
image.verify(cert).is_ok()
}
}
}
fn sign_cmd(
image: &Utf8CStr,
name: Option<&Utf8CStr>,
cert: Option<&Utf8CStr>,
key: Option<&Utf8CStr>,
) -> LoggedResult<()> {
let img = BootImage::new(image);
let name = name.unwrap_or(cstr!("/boot"));
let sig = sign_boot_image(img.payload(), name, cert, key)?;
let tail_off = img.tail_off();
drop(img);
let mut fd = image.open(OFlag::O_WRONLY | OFlag::O_CLOEXEC)?;
fd.seek(SeekFrom::Start(tail_off))?;
fd.write_all(&sig)?;
let current = fd.stream_position()?;
let eof = fd.seek(SeekFrom::End(0))?;
if eof > current {
// Zero out rest of the file
fd.seek(SeekFrom::Start(current))?;
fd.write_zeros((eof - current) as usize)?;
}
Ok(())
}
fn boot_main(cmds: CmdArgs) -> LoggedResult<i32> {
let mut cmds = cmds.0;
if cmds.len() < 2 {
print_usage(cmds.first().unwrap_or(&"magiskboot"));
return log_err!();
}
if cmds[1].starts_with("--") {
cmds[1] = &cmds[1][2..];
}
let cli = if cmds[1].starts_with("compress=") {
// Skip the main parser, directly parse the subcommand
Compress::from_args(&cmds[..2], &cmds[2..]).map(|compress| Cli {
action: Action::Compress(compress),
})
} else {
Cli::from_args(&[cmds[0]], &cmds[1..])
}
.on_early_exit(|| match cmds[1] {
"dtb" => print_dtb_usage(),
"cpio" => print_cpio_usage(),
_ => print_usage(cmds[0]),
});
match cli.action {
Action::Unpack(Unpack {
no_decompress,
dump_header,
img,
}) => {
return Ok(unpack(&img, no_decompress, dump_header));
}
Action::Repack(Repack {
no_compress,
img,
out,
}) => {
repack(
&img,
out.as_deref().unwrap_or(cstr!("new-boot.img")),
no_compress,
);
}
Action::Verify(Verify { img, cert }) => {
if !verify_cmd(&img, cert.as_deref()) {
return log_err!();
}
}
Action::Sign(Sign {
img,
name,
cert,
key,
}) => {
sign_cmd(&img, name.as_deref(), cert.as_deref(), key.as_deref())?;
}
Action::Extract(Extract {
payload,
partition,
outfile,
}) => {
extract_boot_from_payload(
&payload,
partition.as_ref().map(AsRef::as_ref),
outfile.as_ref().map(AsRef::as_ref),
)
.log_with_msg(|w| w.write_str("Failed to extract from payload"))?;
}
Action::HexPatch(HexPatch { file, src, dest }) => {
if !hexpatch(&file, &src, &dest) {
log_err!("Failed to patch")?;
}
}
Action::Cpio(Cpio { file, cmds }) => {
cpio_commands(&file, &cmds).log_with_msg(|w| w.write_str("Failed to process cpio"))?;
}
Action::Dtb(Dtb { file, action }) => {
return dtb_commands(&file, &action)
.map(|b| if b { 0 } else { 1 })
.log_with_msg(|w| w.write_str("Failed to process dtb"));
}
Action::Split(Split {
no_decompress,
file,
}) => {
return Ok(split_image_dtb(&file, no_decompress));
}
Action::Sha1(Sha1 { file }) => {
let file = MappedFile::open(&file)?;
let mut sha1 = [0u8; 20];
sha1_hash(file.as_ref(), &mut sha1);
for byte in &sha1 {
print!("{byte:02x}");
}
println!();
}
Action::Cleanup(_) => {
eprintln!("Cleaning up...");
cleanup();
}
Action::Decompress(Decompress { file, out }) => {
decompress_cmd(&file, out.as_deref())?;
}
Action::Compress(Compress { format, file, out }) => {
compress_cmd(format, &file, out.as_deref())?;
}
}
Ok(0)
}
#[unsafe(no_mangle)]
pub extern "C" fn main(argc: i32, argv: *const *const c_char, _envp: *const *const c_char) -> i32 {
cmdline_logging();
unsafe { umask(0) };
let cmds = CmdArgs::new(argc, argv);
boot_main(cmds).unwrap_or(1)
}

View File

@@ -1,23 +1,29 @@
use crate::ffi::FileFormat;
use base::{Chunker, LoggedResult, WriteExt};
use bytemuck::bytes_of_mut;
use bzip2::{Compression as BzCompression, write::BzDecoder, write::BzEncoder};
use flate2::{Compression as GzCompression, write::GzEncoder, write::MultiGzDecoder};
use lz4::{
BlockMode, BlockSize, ContentChecksum, Encoder as LZ4FrameEncoder,
EncoderBuilder as LZ4FrameEncoderBuilder, block::CompressionMode, liblz4::BlockChecksum,
use crate::ffi::{FileFormat, check_fmt};
use base::nix::fcntl::OFlag;
use base::{
Chunker, FileOrStd, LoggedResult, ReadExt, ResultExt, Utf8CStr, Utf8CString, WriteExt, log_err,
};
use std::cell::Cell;
use bzip2::Compression as BzCompression;
use bzip2::read::BzDecoder;
use bzip2::write::BzEncoder;
use flate2::Compression as GzCompression;
use flate2::read::MultiGzDecoder;
use flate2::write::GzEncoder;
use lz4::block::CompressionMode;
use lz4::liblz4::BlockChecksum;
use lz4::{
BlockMode, BlockSize, ContentChecksum, Decoder as LZ4FrameDecoder, Encoder as LZ4FrameEncoder,
EncoderBuilder as LZ4FrameEncoderBuilder,
};
use lzma_rust2::{CheckType, LzmaOptions, LzmaReader, LzmaWriter, XzOptions, XzReader, XzWriter};
use std::cmp::min;
use std::fmt::Write as FmtWrite;
use std::fs::File;
use std::io::{BufWriter, Read, Write};
use std::io::{BufWriter, Cursor, Read, Write};
use std::mem::ManuallyDrop;
use std::num::NonZeroU64;
use std::ops::DerefMut;
use std::os::fd::{FromRawFd, RawFd};
use xz2::{
stream::{Check as LzmaCheck, Filters as LzmaFilters, LzmaOptions, Stream as LzmaStream},
write::{XzDecoder, XzEncoder},
};
use zopfli::{BlockType, GzipEncoder as ZopFliEncoder, Options as ZopfliOptions};
pub trait WriteFinish<W: Write>: Write {
@@ -36,19 +42,7 @@ macro_rules! finish_impl {
)*}
}
finish_impl!(GzEncoder<W>, MultiGzDecoder<W>, BzEncoder<W>, XzEncoder<W>);
macro_rules! finish_impl_ref {
($($t:ty),*) => {$(
impl<W: Write> WriteFinish<W> for $t {
fn finish(mut self: Box<Self>) -> std::io::Result<W> {
Self::finish(self.as_mut())
}
}
)*}
}
finish_impl_ref!(BzDecoder<W>, XzDecoder<W>);
finish_impl!(GzEncoder<W>, BzEncoder<W>, XzWriter<W>, LzmaWriter<W>);
impl<W: Write> WriteFinish<W> for BufWriter<ZopFliEncoder<W>> {
fn finish(self: Box<Self>) -> std::io::Result<W> {
@@ -65,89 +59,6 @@ impl<W: Write> WriteFinish<W> for LZ4FrameEncoder<W> {
}
}
// Adapt Reader to Writer
// In case some decoders don't support the Write trait, instead of pushing data into the
// decoder, we have no choice but to pull data out of it. So first, we create a "fake" reader
// that does not own any data as a placeholder. In the Writer adapter struct, when data
// is fed in, we call FakeReader::set_data to forward this data as the "source" of the
// decoder. Next, we pull data out of the decoder, and finally, forward the decoded data to output.
struct FakeReader(Cell<&'static [u8]>);
impl FakeReader {
fn new() -> FakeReader {
FakeReader(Cell::new(&[]))
}
// SAFETY: the lifetime of the buffer is between the invocation of
// this method and the invocation of FakeReader::clear. There is currently
// no way to represent this with Rust's lifetime marker, so we transmute all
// lifetimes away and make the users of this struct manually manage the lifetime.
// It is the responsibility of the caller to ensure the underlying reference does not
// live longer than it should.
unsafe fn set_data(&self, data: &[u8]) {
let buf: &'static [u8] = unsafe { std::mem::transmute(data) };
self.0.set(buf)
}
fn clear(&self) {
self.0.set(&[])
}
}
impl Read for FakeReader {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
let data = self.0.get();
let len = std::cmp::min(buf.len(), data.len());
buf[..len].copy_from_slice(&data[..len]);
self.0.set(&data[len..]);
Ok(len)
}
}
// LZ4FrameDecoder
struct LZ4FrameDecoder<W: Write> {
write: W,
decoder: lz4::Decoder<FakeReader>,
}
impl<W: Write> LZ4FrameDecoder<W> {
fn new(write: W) -> Self {
let fake = FakeReader::new();
let decoder = lz4::Decoder::new(fake).unwrap();
LZ4FrameDecoder { write, decoder }
}
}
impl<W: Write> Write for LZ4FrameDecoder<W> {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.write_all(buf)?;
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()> {
// SAFETY: buf is removed from the reader immediately after usage
unsafe { self.decoder.reader().set_data(buf) };
std::io::copy(&mut self.decoder, &mut self.write)?;
self.decoder.reader().clear();
Ok(())
}
}
impl<W: Write> WriteFinish<W> for LZ4FrameDecoder<W> {
fn finish(self: Box<Self>) -> std::io::Result<W> {
let (_, r) = self.decoder.finish();
r?;
Ok(self.write)
}
}
// LZ4BlockArchive format
//
// len: | 4 | 4 | n | ... | 4 |
@@ -157,7 +68,7 @@ impl<W: Write> WriteFinish<W> for LZ4FrameDecoder<W> {
const LZ4_BLOCK_SIZE: usize = 0x800000;
const LZ4HC_CLEVEL_MAX: i32 = 12;
const LZ4_MAGIC: &[u8] = b"\x02\x21\x4c\x18";
const LZ4_MAGIC: u32 = 0x184c2102;
struct LZ4BlockEncoder<W: Write> {
write: W,
@@ -206,7 +117,7 @@ impl<W: Write> Write for LZ4BlockEncoder<W> {
fn write_all(&mut self, mut buf: &[u8]) -> std::io::Result<()> {
if self.total == 0 {
// Write header
self.write.write_all(LZ4_MAGIC)?;
self.write.write_pod(&LZ4_MAGIC)?;
}
self.total += buf.len() as u32;
@@ -236,88 +147,72 @@ impl<W: Write> WriteFinish<W> for LZ4BlockEncoder<W> {
// LZ4BlockDecoder
struct LZ4BlockDecoder<W: Write> {
write: W,
chunker: Chunker,
struct LZ4BlockDecoder<R: Read> {
read: R,
in_buf: Box<[u8]>,
out_buf: Box<[u8]>,
curr_block_size: usize,
out_len: usize,
out_pos: usize,
}
impl<W: Write> LZ4BlockDecoder<W> {
fn new(write: W) -> Self {
LZ4BlockDecoder {
write,
chunker: Chunker::new(size_of::<u32>()),
// SAFETY: all bytes will be initialized before it is used
impl<R: Read> LZ4BlockDecoder<R> {
fn new(read: R) -> Self {
let compressed_sz = lz4::block::compress_bound(LZ4_BLOCK_SIZE).unwrap_or(LZ4_BLOCK_SIZE);
Self {
read,
in_buf: unsafe { Box::new_uninit_slice(compressed_sz).assume_init() },
out_buf: unsafe { Box::new_uninit_slice(LZ4_BLOCK_SIZE).assume_init() },
curr_block_size: 0,
out_len: 0,
out_pos: 0,
}
}
fn decode_block(write: &mut W, out_buf: &mut [u8], chunk: &[u8]) -> std::io::Result<()> {
let decompressed_size =
lz4::block::decompress_to_buffer(chunk, Some(LZ4_BLOCK_SIZE as i32), out_buf)?;
write.write_all(&out_buf[..decompressed_size])
}
}
impl<W: Write> Write for LZ4BlockDecoder<W> {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.write_all(buf)?;
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
fn write_all(&mut self, mut buf: &[u8]) -> std::io::Result<()> {
while !buf.is_empty() {
let (b, chunk) = self.chunker.add_data(buf);
buf = b;
if let Some(chunk) = chunk {
if chunk == LZ4_MAGIC {
// Skip magic, read next u32
continue;
}
if self.curr_block_size == 0 {
// We haven't got the current block size yet, try read it
let mut next_u32: u32 = 0;
bytes_of_mut(&mut next_u32).copy_from_slice(chunk);
if next_u32 > lz4::block::compress_bound(LZ4_BLOCK_SIZE)? as u32 {
// This is the LG format trailer, EOF
continue;
}
// Update chunker to read next block
self.curr_block_size = next_u32 as usize;
self.chunker.set_chunk_size(self.curr_block_size);
continue;
}
// Actually decode the block
Self::decode_block(&mut self.write, &mut self.out_buf, chunk)?;
// Reset for the next block
self.curr_block_size = 0;
self.chunker.set_chunk_size(size_of::<u32>());
impl<R: Read> Read for LZ4BlockDecoder<R> {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
if self.out_pos == self.out_len {
let mut block_size: u32 = 0;
if let Err(e) = self.read.read_pod(&mut block_size) {
return if e.kind() == std::io::ErrorKind::UnexpectedEof {
Ok(0)
} else {
Err(e)
};
}
if block_size == LZ4_MAGIC {
self.read.read_pod(&mut block_size)?;
}
}
Ok(())
}
}
impl<W: Write> WriteFinish<W> for LZ4BlockDecoder<W> {
fn finish(mut self: Box<Self>) -> std::io::Result<W> {
let chunk = self.chunker.get_available();
if !chunk.is_empty() {
return Err(std::io::Error::new(
std::io::ErrorKind::Interrupted,
"Finish ran before end of compressed stream",
));
let block_size = block_size as usize;
if block_size > self.in_buf.len() {
// This may be the LG format trailer, EOF
return Ok(0);
}
// Read the entire compressed block
let compressed_block = &mut self.in_buf[..block_size];
if let Ok(len) = self.read.read(compressed_block) {
if len == 0 {
// We hit EOF, that's fine
return Ok(0);
} else if len != block_size {
let remain = &mut compressed_block[len..];
self.read.read_exact(remain)?;
}
}
self.out_len = lz4::block::decompress_to_buffer(
compressed_block,
Some(LZ4_BLOCK_SIZE as i32),
&mut self.out_buf,
)?;
self.out_pos = 0;
}
Ok(self.write)
let copy_len = min(buf.len(), self.out_len - self.out_pos);
buf[..copy_len].copy_from_slice(&self.out_buf[self.out_pos..self.out_pos + copy_len]);
self.out_pos += copy_len;
Ok(copy_len)
}
}
@@ -326,16 +221,12 @@ impl<W: Write> WriteFinish<W> for LZ4BlockDecoder<W> {
pub fn get_encoder<'a, W: Write + 'a>(format: FileFormat, w: W) -> Box<dyn WriteFinish<W> + 'a> {
match format {
FileFormat::XZ => {
let opt = LzmaOptions::new_preset(9).unwrap();
let stream =
LzmaStream::new_stream_encoder(LzmaFilters::new().lzma2(&opt), LzmaCheck::Crc32)
.unwrap();
Box::new(XzEncoder::new_stream(w, stream))
let mut opt = XzOptions::with_preset(9);
opt.set_check_sum_type(CheckType::Crc32);
Box::new(XzWriter::new(w, opt).unwrap())
}
FileFormat::LZMA => {
let opt = LzmaOptions::new_preset(9).unwrap();
let stream = LzmaStream::new_lzma_encoder(&opt).unwrap();
Box::new(XzEncoder::new_stream(w, stream))
Box::new(LzmaWriter::new_use_header(w, &LzmaOptions::with_preset(9), None).unwrap())
}
FileFormat::BZIP2 => Box::new(BzEncoder::new(w, BzCompression::best())),
FileFormat::LZ4 => {
@@ -366,45 +257,20 @@ pub fn get_encoder<'a, W: Write + 'a>(format: FileFormat, w: W) -> Box<dyn Write
}
}
pub fn get_decoder<'a, W: Write + 'a>(format: FileFormat, w: W) -> Box<dyn WriteFinish<W> + 'a> {
pub fn get_decoder<'a, R: Read + 'a>(format: FileFormat, r: R) -> Box<dyn Read + 'a> {
match format {
FileFormat::XZ | FileFormat::LZMA => {
let stream = LzmaStream::new_auto_decoder(u64::MAX, 0).unwrap();
Box::new(XzDecoder::new_stream(w, stream))
}
FileFormat::BZIP2 => Box::new(BzDecoder::new(w)),
FileFormat::LZ4 => Box::new(LZ4FrameDecoder::new(w)),
FileFormat::LZ4_LG | FileFormat::LZ4_LEGACY => Box::new(LZ4BlockDecoder::new(w)),
FileFormat::ZOPFLI | FileFormat::GZIP => Box::new(MultiGzDecoder::new(w)),
FileFormat::XZ => Box::new(XzReader::new(r, true)),
FileFormat::LZMA => Box::new(LzmaReader::new_mem_limit(r, u32::MAX, None).unwrap()),
FileFormat::BZIP2 => Box::new(BzDecoder::new(r)),
FileFormat::LZ4 => Box::new(LZ4FrameDecoder::new(r).unwrap()),
FileFormat::LZ4_LG | FileFormat::LZ4_LEGACY => Box::new(LZ4BlockDecoder::new(r)),
FileFormat::ZOPFLI | FileFormat::GZIP => Box::new(MultiGzDecoder::new(r)),
_ => unreachable!(),
}
}
// C++ FFI
pub fn compress_fd(format: FileFormat, in_fd: RawFd, out_fd: RawFd) {
let mut in_file = unsafe { ManuallyDrop::new(File::from_raw_fd(in_fd)) };
let mut out_file = unsafe { ManuallyDrop::new(File::from_raw_fd(out_fd)) };
let mut encoder = get_encoder(format, out_file.deref_mut());
let _: LoggedResult<()> = try {
std::io::copy(in_file.deref_mut(), encoder.as_mut())?;
encoder.finish()?;
};
}
pub fn decompress_bytes_fd(format: FileFormat, in_bytes: &[u8], in_fd: RawFd, out_fd: RawFd) {
let mut in_file = unsafe { ManuallyDrop::new(File::from_raw_fd(in_fd)) };
let mut out_file = unsafe { ManuallyDrop::new(File::from_raw_fd(out_fd)) };
let mut decoder = get_decoder(format, out_file.deref_mut());
let _: LoggedResult<()> = try {
decoder.write_all(in_bytes)?;
std::io::copy(in_file.deref_mut(), decoder.as_mut())?;
decoder.finish()?;
};
}
pub fn compress_bytes(format: FileFormat, in_bytes: &[u8], out_fd: RawFd) {
let mut out_file = unsafe { ManuallyDrop::new(File::from_raw_fd(out_fd)) };
@@ -418,9 +284,112 @@ pub fn compress_bytes(format: FileFormat, in_bytes: &[u8], out_fd: RawFd) {
pub fn decompress_bytes(format: FileFormat, in_bytes: &[u8], out_fd: RawFd) {
let mut out_file = unsafe { ManuallyDrop::new(File::from_raw_fd(out_fd)) };
let mut decoder = get_decoder(format, out_file.deref_mut());
let _: LoggedResult<()> = try {
decoder.write_all(in_bytes)?;
decoder.finish()?;
};
let mut decoder = get_decoder(format, in_bytes);
std::io::copy(decoder.as_mut(), out_file.deref_mut()).log_ok();
}
// Command-line entry points
pub(crate) fn decompress_cmd(infile: &Utf8CStr, outfile: Option<&Utf8CStr>) -> LoggedResult<()> {
let in_std = infile == "-";
let mut rm_in = false;
let mut buf = [0u8; 4096];
let input = if in_std {
FileOrStd::StdIn
} else {
FileOrStd::File(infile.open(OFlag::O_RDONLY)?)
};
// First read some bytes for format detection
let len = input.as_file().read(&mut buf)?;
let buf = &buf[..len];
let format = check_fmt(buf);
eprintln!("Detected format: {format}");
if !format.is_compressed() {
return log_err!("Input file is not a supported type!");
}
// If user did not provide outfile, infile has to be either
// <path>.[ext], or "-". Outfile will be either <path> or "-".
// If the input does not have proper format, abort.
let output = if let Some(outfile) = outfile {
if outfile == "-" {
FileOrStd::StdOut
} else {
FileOrStd::File(outfile.create(OFlag::O_WRONLY | OFlag::O_TRUNC, 0o644)?)
}
} else if in_std {
FileOrStd::StdOut
} else {
// Strip out extension and remove input
let outfile = if let Some((outfile, ext)) = infile.rsplit_once('.')
&& ext == format.ext()
{
Utf8CString::from(outfile)
} else {
return log_err!("Input file is not a supported type!");
};
rm_in = true;
eprintln!("Decompressing to [{outfile}]");
FileOrStd::File(outfile.create(OFlag::O_WRONLY | OFlag::O_TRUNC, 0o644)?)
};
let mut decoder = get_decoder(format, Cursor::new(buf).chain(input.as_file()));
std::io::copy(decoder.as_mut(), &mut output.as_file())?;
if rm_in {
infile.remove()?;
}
Ok(())
}
pub(crate) fn compress_cmd(
method: FileFormat,
infile: &Utf8CStr,
outfile: Option<&Utf8CStr>,
) -> LoggedResult<()> {
let in_std = infile == "-";
let mut rm_in = false;
let input = if in_std {
FileOrStd::StdIn
} else {
FileOrStd::File(infile.open(OFlag::O_RDONLY)?)
};
let output = if let Some(outfile) = outfile {
if outfile == "-" {
FileOrStd::StdOut
} else {
FileOrStd::File(outfile.create(OFlag::O_WRONLY | OFlag::O_TRUNC, 0o644)?)
}
} else if in_std {
FileOrStd::StdOut
} else {
let mut outfile = Utf8CString::default();
outfile.write_str(infile).ok();
outfile.write_char('.').ok();
outfile.write_str(method.ext()).ok();
eprintln!("Compressing to [{outfile}]");
rm_in = true;
let outfile = outfile.create(OFlag::O_WRONLY | OFlag::O_TRUNC, 0o644)?;
FileOrStd::File(outfile)
};
let mut encoder = get_encoder(method, output.as_file());
std::io::copy(&mut input.as_file(), encoder.as_mut())?;
encoder.finish()?;
if rm_in {
infile.remove()?;
}
Ok(())
}

View File

@@ -1,41 +1,33 @@
#![allow(clippy::useless_conversion)]
use argh::FromArgs;
use base::argh;
use bytemuck::{Pod, Zeroable, from_bytes};
use num_traits::cast::AsPrimitive;
use size::{Base, Size, Style};
use std::cmp::Ordering;
use std::collections::{BTreeMap, HashMap};
use std::fmt::{Display, Formatter};
use std::fs::File;
use std::io::{Read, Write};
use std::io::{Cursor, Read, Write};
use std::mem::size_of;
use std::process::exit;
use std::str;
use argh::FromArgs;
use bytemuck::{Pod, Zeroable, from_bytes};
use num_traits::cast::AsPrimitive;
use size::{Base, Size, Style};
use base::libc::{
O_CLOEXEC, O_CREAT, O_RDONLY, O_TRUNC, O_WRONLY, S_IFBLK, S_IFCHR, S_IFDIR, S_IFLNK, S_IFMT,
S_IFREG, S_IRGRP, S_IROTH, S_IRUSR, S_IWGRP, S_IWOTH, S_IWUSR, S_IXGRP, S_IXOTH, S_IXUSR,
c_char, dev_t, gid_t, major, makedev, minor, mknod, mode_t, uid_t,
};
use base::{
BytesExt, EarlyExitExt, LoggedResult, MappedFile, ResultExt, Utf8CStr, Utf8CStrBuf, WriteExt,
cstr, log_err, map_args,
};
use crate::check_env;
use crate::compress::{get_decoder, get_encoder};
use crate::ffi::FileFormat;
use crate::patch::{patch_encryption, patch_verity};
#[derive(FromArgs)]
struct CpioCli {
#[argh(positional)]
file: String,
#[argh(positional)]
commands: Vec<String>,
}
use base::libc::{
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, dev_t, gid_t, major, makedev, minor, mknod,
mode_t, uid_t,
};
use base::nix::fcntl::OFlag;
use base::{
BytesExt, EarlyExitExt, LoggedResult, MappedFile, OptionExt, ResultExt, Utf8CStr, Utf8CStrBuf,
WriteExt, cstr, log_err,
};
#[derive(FromArgs)]
struct CpioCommand {
@@ -151,7 +143,7 @@ struct List {
recursive: bool,
}
fn print_cpio_usage() {
pub(crate) fn print_cpio_usage() {
eprintln!(
r#"Usage: magiskboot cpio <incpio> [commands...]
@@ -235,7 +227,7 @@ impl Cpio {
let hdr_sz = size_of::<CpioHeader>();
let hdr = from_bytes::<CpioHeader>(&data[pos..(pos + hdr_sz)]);
if &hdr.magic != b"070701" {
return Err(log_err!("invalid cpio magic"));
return log_err!("invalid cpio magic");
}
pos += hdr_sz;
let name_sz = x8u(&hdr.namesize)? as usize;
@@ -339,7 +331,7 @@ impl Cpio {
let entry = self
.entries
.get(path)
.ok_or_else(|| log_err!("No such file"))?;
.ok_or_log_msg(|w| w.write_str("No such file"))?;
eprintln!("Extracting entry [{path}] to [{out}]");
let out = Utf8CStr::from_string(out);
@@ -357,7 +349,10 @@ impl Cpio {
match entry.mode & S_IFMT {
S_IFDIR => out.mkdir(mode)?,
S_IFREG => {
let mut file = out.create(O_CREAT | O_TRUNC | O_WRONLY | O_CLOEXEC, mode)?;
let mut file = out.create(
OFlag::O_CREAT | OFlag::O_TRUNC | OFlag::O_WRONLY | OFlag::O_CLOEXEC,
mode,
)?;
file.write_all(&entry.data)?;
}
S_IFLNK => {
@@ -370,7 +365,7 @@ impl Cpio {
unsafe { mknod(out.as_ptr().cast(), entry.mode, dev) };
}
_ => {
return Err(log_err!("unknown entry type"));
return log_err!("unknown entry type");
}
}
Ok(())
@@ -397,7 +392,7 @@ impl Cpio {
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"));
return log_err!("path cannot end with / for add");
}
let file = Utf8CStr::from_string(file);
let attr = file.get_attr()?;
@@ -410,7 +405,8 @@ impl Cpio {
let mode = if attr.is_file() || attr.is_symlink() {
rdevmajor = 0;
rdevminor = 0;
file.open(O_RDONLY | O_CLOEXEC)?.read_to_end(&mut content)?;
file.open(OFlag::O_RDONLY | OFlag::O_CLOEXEC)?
.read_to_end(&mut content)?;
mode | S_IFREG
} else {
rdevmajor = major(attr.st.st_rdev.as_()).as_();
@@ -420,7 +416,7 @@ impl Cpio {
} else if attr.is_char_device() {
mode | S_IFCHR
} else {
return Err(log_err!("unsupported file type"));
return log_err!("unsupported file type");
}
};
@@ -473,7 +469,7 @@ impl Cpio {
let entry = self
.entries
.remove(&norm_path(from))
.ok_or_else(|| log_err!("no such entry {}", from))?;
.ok_or_log_msg(|w| w.write_fmt(format_args!("No such entry {from}")))?;
self.entries.insert(norm_path(to), entry);
eprintln!("Move [{from}] -> [{to}]");
Ok(())
@@ -568,7 +564,7 @@ impl Cpio {
let mut backups = HashMap::<String, Box<CpioEntry>>::new();
let mut rm_list = String::new();
self.entries
.extract_if(|name, _| name.starts_with(".backup/"))
.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) {
@@ -713,10 +709,11 @@ impl CpioEntry {
return false;
}
let mut decoder = get_decoder(FileFormat::XZ, Vec::new());
let Ok(data): std::io::Result<Vec<u8>> = (try {
decoder.write_all(&self.data)?;
decoder.finish()?
let mut decoder = get_decoder(FileFormat::XZ, Cursor::new(&self.data));
let mut data = Vec::new();
std::io::copy(decoder.as_mut(), &mut data)?;
data
}) else {
eprintln!("xz compression failed");
return false;
@@ -761,74 +758,61 @@ impl Display for CpioEntry {
}
}
pub fn cpio_commands(argc: i32, argv: *const *const c_char) -> bool {
let res: LoggedResult<()> = try {
if argc < 1 {
Err(log_err!("No arguments"))?;
}
let cmds = map_args(argc, argv)?;
let mut cli =
CpioCli::from_args(&["magiskboot", "cpio"], &cmds).on_early_exit(print_cpio_usage);
let file = Utf8CStr::from_string(&mut cli.file);
let mut cpio = if file.exists() {
Cpio::load_from_file(file)?
} else {
Cpio::new()
};
for cmd in cli.commands {
if cmd.starts_with('#') {
continue;
}
let mut cli = CpioCommand::from_args(
&["magiskboot", "cpio", file],
cmd.split(' ')
.filter(|x| !x.is_empty())
.collect::<Vec<_>>()
.as_slice(),
)
.on_early_exit(print_cpio_usage);
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);
}
}
CpioAction::Backup(Backup {
origin,
skip_compress,
}) => 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 {
Err(log_err!("invalid arguments"))?;
}
let mut it = paths.iter_mut();
cpio.extract(it.next(), it.next())?;
}
CpioAction::List(List { path, recursive }) => {
cpio.ls(path.as_str(), *recursive);
exit(0);
}
};
}
cpio.dump(file)?;
pub(crate) fn cpio_commands(file: &Utf8CStr, cmds: &Vec<String>) -> LoggedResult<()> {
let mut cpio = if file.exists() {
Cpio::load_from_file(file)?
} else {
Cpio::new()
};
res.log_with_msg(|w| w.write_str("Failed to process cpio"))
.is_ok()
for cmd in cmds {
if cmd.starts_with('#') {
continue;
}
let mut cmd = CpioCommand::from_args(
&["magiskboot", "cpio", file],
cmd.split(' ')
.filter(|x| !x.is_empty())
.collect::<Vec<_>>()
.as_slice(),
)
.on_early_exit(print_cpio_usage);
match &mut cmd.action {
CpioAction::Test(_) => exit(cpio.test()),
CpioAction::Restore(_) => cpio.restore()?,
CpioAction::Patch(_) => cpio.patch(),
CpioAction::Exists(Exists { path }) => {
return if cpio.exists(path) {
Ok(())
} else {
log_err!()
};
}
CpioAction::Backup(Backup {
origin,
skip_compress,
}) => 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 {
log_err!("invalid arguments")?;
}
let mut it = paths.iter_mut();
cpio.extract(it.next(), it.next())?;
}
CpioAction::List(List { path, recursive }) => {
cpio.ls(path.as_str(), *recursive);
return Ok(());
}
};
}
cpio.dump(file)?;
Ok(())
}
fn x8u(x: &[u8; 8]) -> LoggedResult<u32> {
@@ -836,7 +820,9 @@ fn x8u(x: &[u8; 8]) -> LoggedResult<u32> {
let mut ret = 0u32;
let s = str::from_utf8(x).log_with_msg(|w| w.write_str("bad cpio header"))?;
for c in s.chars() {
ret = ret * 16 + c.to_digit(16).ok_or_else(|| log_err!("bad cpio header"))?;
ret = ret * 16
+ c.to_digit(16)
.ok_or_log_msg(|w| w.write_str("bad cpio header"))?;
}
Ok(ret)
}

View File

@@ -1,28 +1,15 @@
use std::{cell::UnsafeCell, process::exit};
use argh::FromArgs;
use fdt::{
Fdt, FdtError,
node::{FdtNode, NodeProperty},
};
use base::{LoggedResult, MappedFile, Utf8CStr, argh};
use fdt::node::{FdtNode, NodeProperty};
use fdt::{Fdt, FdtError};
use std::cell::UnsafeCell;
use base::{
EarlyExitExt, LoggedResult, MappedFile, ResultExt, Utf8CStr, libc::c_char, log_err, map_args,
};
use crate::{check_env, patch::patch_verity};
#[derive(FromArgs)]
struct DtbCli {
#[argh(positional)]
file: String,
#[argh(subcommand)]
action: DtbAction,
}
use crate::check_env;
use crate::patch::patch_verity;
#[derive(FromArgs)]
#[argh(subcommand)]
enum DtbAction {
pub(crate) enum DtbAction {
Print(Print),
Patch(Patch),
Test(Test),
@@ -30,20 +17,20 @@ enum DtbAction {
#[derive(FromArgs)]
#[argh(subcommand, name = "print")]
struct Print {
#[argh(switch, short = 'f')]
pub(crate) struct Print {
#[argh(switch, short = 'f', long = none)]
fstab: bool,
}
#[derive(FromArgs)]
#[argh(subcommand, name = "patch")]
struct Patch {}
pub(crate) struct Patch {}
#[derive(FromArgs)]
#[argh(subcommand, name = "test")]
struct Test {}
pub(crate) struct Test {}
fn print_dtb_usage() {
pub(crate) fn print_dtb_usage() {
eprintln!(
r#"Usage: magiskboot dtb <file> <action> [args...]
Do dtb related actions to <file>.
@@ -220,11 +207,11 @@ fn dtb_test(file: &Utf8CStr) -> LoggedResult<bool> {
if child.name != "system" {
continue;
}
if let Some(mount_point) = child.property("mnt_point") {
if mount_point.value == b"/system_root\0" {
ret = false;
break;
}
if let Some(mount_point) = child.property("mnt_point")
&& mount_point.value == b"/system_root\0"
{
ret = false;
break;
}
}
}
@@ -274,34 +261,13 @@ fn dtb_patch(file: &Utf8CStr) -> LoggedResult<bool> {
Ok(patched)
}
pub fn dtb_commands(argc: i32, argv: *const *const c_char) -> bool {
let res: LoggedResult<()> = try {
if argc < 1 {
Err(log_err!("No arguments"))?;
pub(crate) fn dtb_commands(file: &Utf8CStr, action: &DtbAction) -> LoggedResult<bool> {
match action {
DtbAction::Print(Print { fstab }) => {
dtb_print(file, *fstab)?;
Ok(true)
}
let cmds = map_args(argc, argv)?;
let mut cli =
DtbCli::from_args(&["magiskboot", "dtb"], &cmds).on_early_exit(print_dtb_usage);
let file = Utf8CStr::from_string(&mut cli.file);
match cli.action {
DtbAction::Print(Print { fstab }) => {
dtb_print(file, fstab)?;
}
DtbAction::Test(_) => {
if !dtb_test(file)? {
exit(1);
}
}
DtbAction::Patch(_) => {
if !dtb_patch(file)? {
exit(1);
}
}
}
};
res.log_with_msg(|w| w.write_str("Failed to process dtb"))
.is_ok()
DtbAction::Test(_) => Ok(dtb_test(file)?),
DtbAction::Patch(_) => Ok(dtb_patch(file)?),
}
}

View File

@@ -1,111 +0,0 @@
#include "boot-rs.hpp"
#include "format.hpp"
Name2Fmt name2fmt;
Fmt2Name fmt2name;
Fmt2Ext fmt2ext;
#define CHECKED_MATCH(s) (len >= (sizeof(s) - 1) && BUFFER_MATCH(buf, s))
FileFormat check_fmt(const void *buf, size_t len) {
if (CHECKED_MATCH(CHROMEOS_MAGIC)) {
return FileFormat::CHROMEOS;
} else if (CHECKED_MATCH(BOOT_MAGIC)) {
return FileFormat::AOSP;
} else if (CHECKED_MATCH(VENDOR_BOOT_MAGIC)) {
return FileFormat::AOSP_VENDOR;
} else if (CHECKED_MATCH(GZIP1_MAGIC) || CHECKED_MATCH(GZIP2_MAGIC)) {
return FileFormat::GZIP;
} else if (CHECKED_MATCH(LZOP_MAGIC)) {
return FileFormat::LZOP;
} else if (CHECKED_MATCH(XZ_MAGIC)) {
return FileFormat::XZ;
} else if (len >= 13 && memcmp(buf, "\x5d\x00\x00", 3) == 0
&& (((char *)buf)[12] == '\xff' || ((char *)buf)[12] == '\x00')) {
return FileFormat::LZMA;
} else if (CHECKED_MATCH(BZIP_MAGIC)) {
return FileFormat::BZIP2;
} else if (CHECKED_MATCH(LZ41_MAGIC) || CHECKED_MATCH(LZ42_MAGIC)) {
return FileFormat::LZ4;
} else if (CHECKED_MATCH(LZ4_LEG_MAGIC)) {
return FileFormat::LZ4_LEGACY;
} else if (CHECKED_MATCH(MTK_MAGIC)) {
return FileFormat::MTK;
} else if (CHECKED_MATCH(DTB_MAGIC)) {
return FileFormat::DTB;
} else if (CHECKED_MATCH(DHTB_MAGIC)) {
return FileFormat::DHTB;
} else if (CHECKED_MATCH(TEGRABLOB_MAGIC)) {
return FileFormat::BLOB;
} else if (len >= 0x28 && memcmp(&((char *)buf)[0x24], ZIMAGE_MAGIC, 4) == 0) {
return FileFormat::ZIMAGE;
} else {
return FileFormat::UNKNOWN;
}
}
const char *Fmt2Name::operator[](FileFormat fmt) {
switch (fmt) {
case FileFormat::GZIP:
return "gzip";
case FileFormat::ZOPFLI:
return "zopfli";
case FileFormat::LZOP:
return "lzop";
case FileFormat::XZ:
return "xz";
case FileFormat::LZMA:
return "lzma";
case FileFormat::BZIP2:
return "bzip2";
case FileFormat::LZ4:
return "lz4";
case FileFormat::LZ4_LEGACY:
return "lz4_legacy";
case FileFormat::LZ4_LG:
return "lz4_lg";
case FileFormat::DTB:
return "dtb";
case FileFormat::ZIMAGE:
return "zimage";
default:
return "raw";
}
}
const char *Fmt2Ext::operator[](FileFormat fmt) {
switch (fmt) {
case FileFormat::GZIP:
case FileFormat::ZOPFLI:
return ".gz";
case FileFormat::LZOP:
return ".lzo";
case FileFormat::XZ:
return ".xz";
case FileFormat::LZMA:
return ".lzma";
case FileFormat::BZIP2:
return ".bz2";
case FileFormat::LZ4:
case FileFormat::LZ4_LEGACY:
case FileFormat::LZ4_LG:
return ".lz4";
default:
return "";
}
}
#define CHECK(s, f) else if (name == s) return f;
FileFormat Name2Fmt::operator[](std::string_view name) {
if (0) {}
CHECK("gzip", FileFormat::GZIP)
CHECK("zopfli", FileFormat::ZOPFLI)
CHECK("xz", FileFormat::XZ)
CHECK("lzma", FileFormat::LZMA)
CHECK("bzip2", FileFormat::BZIP2)
CHECK("lz4", FileFormat::LZ4)
CHECK("lz4_legacy", FileFormat::LZ4_LEGACY)
CHECK("lz4_lg", FileFormat::LZ4_LG)
else return FileFormat::UNKNOWN;
}

View File

@@ -1,67 +0,0 @@
#pragma once
#include <string_view>
enum class FileFormat : ::std::uint8_t;
#define COMPRESSED(fmt) ((+fmt) >= +FileFormat::GZIP && (+fmt) < +FileFormat::LZOP)
#define COMPRESSED_ANY(fmt) ((+fmt) >= +FileFormat::GZIP && (+fmt) <= +FileFormat::LZOP)
#define BUFFER_MATCH(buf, s) (memcmp(buf, s, sizeof(s) - 1) == 0)
#define BUFFER_CONTAIN(buf, sz, s) (memmem(buf, sz, s, sizeof(s) - 1) != nullptr)
#define BOOT_MAGIC "ANDROID!"
#define VENDOR_BOOT_MAGIC "VNDRBOOT"
#define CHROMEOS_MAGIC "CHROMEOS"
#define GZIP1_MAGIC "\x1f\x8b"
#define GZIP2_MAGIC "\x1f\x9e"
#define LZOP_MAGIC "\x89""LZO"
#define XZ_MAGIC "\xfd""7zXZ"
#define BZIP_MAGIC "BZh"
#define LZ4_LEG_MAGIC "\x02\x21\x4c\x18"
#define LZ41_MAGIC "\x03\x21\x4c\x18"
#define LZ42_MAGIC "\x04\x22\x4d\x18"
#define MTK_MAGIC "\x88\x16\x88\x58"
#define DTB_MAGIC "\xd0\x0d\xfe\xed"
#define LG_BUMP_MAGIC "\x41\xa9\xe4\x67\x74\x4d\x1d\x1b\xa4\x29\xf2\xec\xea\x65\x52\x79"
#define DHTB_MAGIC "\x44\x48\x54\x42\x01\x00\x00\x00"
#define SEANDROID_MAGIC "SEANDROIDENFORCE"
#define TEGRABLOB_MAGIC "-SIGNED-BY-SIGNBLOB-"
#define NOOKHD_RL_MAGIC "Red Loader"
#define NOOKHD_GL_MAGIC "Green Loader"
#define NOOKHD_GR_MAGIC "Green Recovery"
#define NOOKHD_EB_MAGIC "eMMC boot.img+secondloader"
#define NOOKHD_ER_MAGIC "eMMC recovery.img+secondloader"
#define NOOKHD_PRE_HEADER_SZ 1048576
#define ACCLAIM_MAGIC "BauwksBoot"
#define ACCLAIM_PRE_HEADER_SZ 262144
#define AMONET_MICROLOADER_MAGIC "microloader"
#define AMONET_MICROLOADER_SZ 1024
#define AVB_FOOTER_MAGIC "AVBf"
#define AVB_MAGIC "AVB0"
#define ZIMAGE_MAGIC "\x18\x28\x6f\x01"
class Fmt2Name {
public:
const char *operator[](FileFormat fmt);
};
class Fmt2Ext {
public:
const char *operator[](FileFormat fmt);
};
class Name2Fmt {
public:
FileFormat operator[](std::string_view name);
};
FileFormat check_fmt(const void *buf, size_t len);
static inline FileFormat check_fmt(rust::Slice<const uint8_t> bytes) {
return check_fmt(bytes.data(), bytes.size());
}
extern Name2Fmt name2fmt;
extern Fmt2Name fmt2name;
extern Fmt2Ext fmt2ext;

104
native/src/boot/format.rs Normal file
View File

@@ -0,0 +1,104 @@
use crate::ffi::FileFormat;
use base::{Utf8CStr, cstr, libc};
use std::fmt::{Display, Formatter};
use std::str::FromStr;
impl FromStr for FileFormat {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"gzip" => Ok(Self::GZIP),
"zopfli" => Ok(Self::ZOPFLI),
"xz" => Ok(Self::XZ),
"lzma" => Ok(Self::LZMA),
"bzip2" => Ok(Self::BZIP2),
"lz4" => Ok(Self::LZ4),
"lz4_legacy" => Ok(Self::LZ4_LEGACY),
"lz4_lg" => Ok(Self::LZ4_LG),
_ => Err(()),
}
}
}
impl Display for FileFormat {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_cstr())
}
}
impl FileFormat {
fn as_cstr(&self) -> &'static Utf8CStr {
match *self {
Self::GZIP => cstr!("gzip"),
Self::ZOPFLI => cstr!("zopfli"),
Self::LZOP => cstr!("lzop"),
Self::XZ => cstr!("xz"),
Self::LZMA => cstr!("lzma"),
Self::BZIP2 => cstr!("bzip2"),
Self::LZ4 => cstr!("lz4"),
Self::LZ4_LEGACY => cstr!("lz4_legacy"),
Self::LZ4_LG => cstr!("lz4_lg"),
Self::DTB => cstr!("dtb"),
Self::ZIMAGE => cstr!("zimage"),
_ => cstr!("raw"),
}
}
}
impl FileFormat {
pub fn ext(&self) -> &'static str {
match *self {
Self::GZIP | Self::ZOPFLI => "gz",
Self::LZOP => "lzo",
Self::XZ => "xz",
Self::LZMA => "lzma",
Self::BZIP2 => "bz2",
Self::LZ4 | Self::LZ4_LEGACY | Self::LZ4_LG => "lz4",
_ => "",
}
}
pub fn is_compressed(&self) -> bool {
matches!(
*self,
Self::GZIP
| Self::ZOPFLI
| Self::XZ
| Self::LZMA
| Self::BZIP2
| Self::LZ4
| Self::LZ4_LEGACY
| Self::LZ4_LG
)
}
pub fn formats() -> String {
[
Self::GZIP,
Self::ZOPFLI,
Self::XZ,
Self::LZMA,
Self::BZIP2,
Self::LZ4,
Self::LZ4_LEGACY,
Self::LZ4_LG,
]
.map(|f| f.to_string())
.join(" ")
}
}
// C++ FFI
pub fn fmt2name(fmt: FileFormat) -> *const libc::c_char {
fmt.as_cstr().as_ptr()
}
pub fn fmt_compressed(fmt: FileFormat) -> bool {
fmt.is_compressed()
}
pub fn fmt_compressed_any(fmt: FileFormat) -> bool {
fmt.is_compressed() || matches!(fmt, FileFormat::LZOP)
}

View File

@@ -1,20 +1,18 @@
#![feature(format_args_nl)]
#![feature(btree_extract_if)]
#![feature(iter_intersperse)]
#![feature(try_blocks)]
pub use base;
use compress::{compress_bytes, compress_fd, decompress_bytes, decompress_bytes_fd};
use cpio::cpio_commands;
use dtb::dtb_commands;
use patch::hexpatch;
use payload::extract_boot_from_payload;
use sign::{SHA, get_sha, sha1_hash, sha256_hash, sign_boot_image, verify_boot_image};
use compress::{compress_bytes, decompress_bytes};
use format::{fmt_compressed, fmt_compressed_any, fmt2name};
use sign::{SHA, get_sha, sha256_hash, sign_payload_for_cxx};
use std::env;
mod cli;
mod compress;
mod cpio;
mod dtb;
mod format;
mod patch;
mod payload;
// Suppress warnings in generated code
@@ -50,24 +48,16 @@ pub mod ffi {
}
unsafe extern "C++" {
include!("../base/include/base.hpp");
include!("magiskboot.hpp");
#[namespace = "rust"]
#[cxx_name = "Utf8CStr"]
type Utf8CStrRef<'a> = base::ffi::Utf8CStrRef<'a>;
}
type Utf8CStrRef<'a> = base::Utf8CStrRef<'a>;
unsafe extern "C++" {
include!("format.hpp");
fn cleanup();
fn unpack(image: Utf8CStrRef, skip_decomp: bool, hdr: bool) -> i32;
fn repack(src_img: Utf8CStrRef, out_img: Utf8CStrRef, skip_comp: bool);
fn split_image_dtb(filename: Utf8CStrRef, skip_decomp: bool) -> i32;
fn check_fmt(buf: &[u8]) -> FileFormat;
include!("bootimg.hpp");
#[cxx_name = "boot_img"]
type BootImage;
#[cxx_name = "get_payload"]
fn payload(self: &BootImage) -> &[u8];
#[cxx_name = "get_tail"]
fn tail(self: &BootImage) -> &[u8];
}
extern "Rust" {
@@ -76,33 +66,38 @@ pub mod ffi {
fn update(self: &mut SHA, data: &[u8]);
fn finalize_into(self: &mut SHA, out: &mut [u8]);
fn output_size(self: &SHA) -> usize;
fn sha1_hash(data: &[u8], out: &mut [u8]);
fn sha256_hash(data: &[u8], out: &mut [u8]);
fn hexpatch(file: &[u8], from: &[u8], to: &[u8]) -> bool;
fn compress_fd(format: FileFormat, in_fd: i32, out_fd: i32);
fn compress_bytes(format: FileFormat, in_bytes: &[u8], out_fd: i32);
fn decompress_bytes(format: FileFormat, in_bytes: &[u8], out_fd: i32);
fn decompress_bytes_fd(format: FileFormat, in_bytes: &[u8], in_fd: i32, out_fd: i32);
fn fmt2name(fmt: FileFormat) -> *const c_char;
fn fmt_compressed(fmt: FileFormat) -> bool;
fn fmt_compressed_any(fmt: FileFormat) -> bool;
#[cxx_name = "sign_payload"]
fn sign_payload_for_cxx(payload: &[u8]) -> Vec<u8>;
}
#[namespace = "rust"]
#[allow(unused_unsafe)]
// BootImage FFI
unsafe extern "C++" {
include!("bootimg.hpp");
#[cxx_name = "boot_img"]
type BootImage;
#[cxx_name = "get_payload"]
fn payload(self: &BootImage) -> &[u8];
#[cxx_name = "get_tail"]
fn tail(self: &BootImage) -> &[u8];
fn is_signed(self: &BootImage) -> bool;
fn tail_off(self: &BootImage) -> u64;
#[Self = BootImage]
#[cxx_name = "create"]
fn new(img: Utf8CStrRef) -> UniquePtr<BootImage>;
}
extern "Rust" {
fn extract_boot_from_payload(
partition: Utf8CStrRef,
in_path: Utf8CStrRef,
out_path: Utf8CStrRef,
) -> bool;
unsafe fn cpio_commands(argc: i32, argv: *const *const c_char) -> bool;
unsafe fn verify_boot_image(img: &BootImage, cert: *const c_char) -> bool;
unsafe fn sign_boot_image(
payload: &[u8],
name: *const c_char,
cert: *const c_char,
key: *const c_char,
) -> Vec<u8>;
unsafe fn dtb_commands(argc: i32, argv: *const *const c_char) -> bool;
#[cxx_name = "verify"]
fn verify_for_cxx(self: &BootImage) -> bool;
}
}

View File

@@ -1,5 +1,7 @@
#pragma once
#include <base.hpp>
#define HEADER_FILE "header"
#define KERNEL_FILE "kernel"
#define RAMDISK_FILE "ramdisk.cpio"
@@ -12,8 +14,49 @@
#define BOOTCONFIG_FILE "bootconfig"
#define NEW_BOOT "new-boot.img"
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, bool skip_decomp = false);
#define BUFFER_MATCH(buf, s) (memcmp(buf, s, sizeof(s) - 1) == 0)
#define BUFFER_CONTAIN(buf, sz, s) (memmem(buf, sz, s, sizeof(s) - 1) != nullptr)
#define CHECKED_MATCH(s) (len >= (sizeof(s) - 1) && BUFFER_MATCH(buf, s))
#define BOOT_MAGIC "ANDROID!"
#define VENDOR_BOOT_MAGIC "VNDRBOOT"
#define CHROMEOS_MAGIC "CHROMEOS"
#define GZIP1_MAGIC "\x1f\x8b"
#define GZIP2_MAGIC "\x1f\x9e"
#define LZOP_MAGIC "\x89""LZO"
#define XZ_MAGIC "\xfd""7zXZ"
#define BZIP_MAGIC "BZh"
#define LZ4_LEG_MAGIC "\x02\x21\x4c\x18"
#define LZ41_MAGIC "\x03\x21\x4c\x18"
#define LZ42_MAGIC "\x04\x22\x4d\x18"
#define MTK_MAGIC "\x88\x16\x88\x58"
#define DTB_MAGIC "\xd0\x0d\xfe\xed"
#define LG_BUMP_MAGIC "\x41\xa9\xe4\x67\x74\x4d\x1d\x1b\xa4\x29\xf2\xec\xea\x65\x52\x79"
#define DHTB_MAGIC "\x44\x48\x54\x42\x01\x00\x00\x00"
#define SEANDROID_MAGIC "SEANDROIDENFORCE"
#define TEGRABLOB_MAGIC "-SIGNED-BY-SIGNBLOB-"
#define NOOKHD_RL_MAGIC "Red Loader"
#define NOOKHD_GL_MAGIC "Green Loader"
#define NOOKHD_GR_MAGIC "Green Recovery"
#define NOOKHD_EB_MAGIC "eMMC boot.img+secondloader"
#define NOOKHD_ER_MAGIC "eMMC recovery.img+secondloader"
#define NOOKHD_PRE_HEADER_SZ 1048576
#define ACCLAIM_MAGIC "BauwksBoot"
#define ACCLAIM_PRE_HEADER_SZ 262144
#define AMONET_MICROLOADER_MAGIC "microloader"
#define AMONET_MICROLOADER_SZ 1024
#define AVB_FOOTER_MAGIC "AVBf"
#define AVB_MAGIC "AVB0"
#define ZIMAGE_MAGIC "\x18\x28\x6f\x01"
enum class FileFormat : uint8_t;
int unpack(Utf8CStr image, bool skip_decomp = false, bool hdr = false);
void repack(Utf8CStr src_img, Utf8CStr out_img, bool skip_comp = false);
int split_image_dtb(Utf8CStr filename, bool skip_decomp = false);
void cleanup();
FileFormat check_fmt(const void *buf, size_t len);
static inline FileFormat check_fmt(rust::Slice<const uint8_t> bytes) {
return check_fmt(bytes.data(), bytes.size());
}

View File

@@ -1,305 +0,0 @@
#include <base.hpp>
#include "boot-rs.hpp"
#include "magiskboot.hpp"
using namespace std;
static void print_formats() {
for (int fmt = +FileFormat::GZIP; fmt < +FileFormat::LZOP; ++fmt) {
fprintf(stderr, "%s ", fmt2name[(FileFormat) fmt]);
}
}
static void usage(char *arg0) {
fprintf(stderr,
R"EOF(MagiskBoot - Boot Image Modification Tool
Usage: %s <action> [args...]
Supported actions:
unpack [-n] [-h] <bootimg>
Unpack <bootimg> to its individual components, each component to
a file with its corresponding file name in the current directory.
Supported components: kernel, kernel_dtb, ramdisk.cpio, second,
dtb, extra, and recovery_dtbo.
By default, each component will be decompressed on-the-fly.
If '-n' is provided, all decompression operations will be skipped;
each component will remain untouched, dumped in its original format.
If '-h' is provided, the boot image header information will be
dumped to the file 'header', which can be used to modify header
configurations during repacking.
Return values:
0:valid 1:error 2:chromeos
repack [-n] <origbootimg> [outbootimg]
Repack boot image components using files from the current directory
to [outbootimg], or 'new-boot.img' if not specified. Current directory
should only contain required files for [outbootimg], or incorrect
[outbootimg] may be produced.
<origbootimg> is the original boot image used to unpack the components.
By default, each component will be automatically compressed using its
corresponding format detected in <origbootimg>. If a component file
in the current directory is already compressed, then no addition
compression will be performed for that specific component.
If '-n' is provided, all compression operations will be skipped.
If env variable PATCHVBMETAFLAG is set to true, all disable flags in
the boot image's vbmeta header will be set.
verify <bootimg> [x509.pem]
Check whether the boot image is signed with AVB 1.0 signature.
Optionally provide a certificate to verify whether the image is
signed by the public key certificate.
Return value:
0:valid 1:error
sign <bootimg> [name] [x509.pem pk8]
Sign <bootimg> with AVB 1.0 signature.
Optionally provide the name of the image (default: '/boot').
Optionally provide the certificate/private key pair for signing.
If the certificate/private key pair is not provided, the AOSP
verity key bundled in the executable will be used.
extract <payload.bin> [partition] [outfile]
Extract [partition] from <payload.bin> to [outfile].
If [outfile] is not specified, then output to '[partition].img'.
If [partition] is not specified, then attempt to extract either
'init_boot' or 'boot'. Which partition was chosen can be determined
by whichever 'init_boot.img' or 'boot.img' exists.
<payload.bin> can be '-' to be STDIN.
hexpatch <file> <hexpattern1> <hexpattern2>
Search <hexpattern1> in <file>, and replace it with <hexpattern2>
cpio <incpio> [commands...]
Do cpio commands to <incpio> (modifications are done in-place).
Each command is a single argument; add quotes for each command.
See "cpio --help" for supported commands.
dtb <file> <action> [args...]
Do dtb related actions to <file>.
See "dtb --help" for supported actions.
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>
cleanup
Cleanup the current working directory
compress[=format] <infile> [outfile]
Compress <infile> with [format] to [outfile].
<infile>/[outfile] can be '-' to be STDIN/STDOUT.
If [format] is not specified, then gzip will be used.
If [outfile] is not specified, then <infile> will be replaced
with another file suffixed with a matching file extension.
Supported formats: )EOF", arg0);
print_formats();
fprintf(stderr, R"EOF(
decompress <infile> [outfile]
Detect format and decompress <infile> to [outfile].
<infile>/[outfile] can be '-' to be STDIN/STDOUT.
If [outfile] is not specified, then <infile> will be replaced
with another file removing its archive format file extension.
Supported formats: )EOF");
print_formats();
fprintf(stderr, "\n\n");
exit(1);
}
static void decompress(char *infile, const char *outfile) {
bool in_std = infile == "-"sv;
bool rm_in = false;
int in_fd = in_std ? STDIN_FILENO : xopen(infile, O_RDONLY);
int out_fd = -1;
uint8_t buf[4096];
size_t len = read(in_fd, buf, sizeof(buf));
FileFormat type = check_fmt(buf, len);
fprintf(stderr, "Detected format: [%s]\n", fmt2name[type]);
if (!COMPRESSED(type))
LOGE("Input file is not a supported compressed type!\n");
// If user does not provide outfile, infile has to be either
// <path>.[ext], or '-'. Outfile will be either <path> or '-'.
// If the input does not have proper format, abort.
char *ext = nullptr;
if (outfile == nullptr) {
outfile = infile;
if (!in_std) {
ext = strrchr(infile, '.');
if (ext == nullptr || strcmp(ext, fmt2ext[type]) != 0)
LOGE("Input file is not a supported type!\n");
// Strip out extension and remove input
*ext = '\0';
rm_in = true;
fprintf(stderr, "Decompressing to [%s]\n", outfile);
}
}
out_fd = outfile == "-"sv ?
STDOUT_FILENO :
xopen(outfile, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (ext) *ext = '.';
decompress_bytes_fd(type, byte_view{ buf, len }, in_fd, out_fd);
if (in_fd != STDIN_FILENO) close(in_fd);
if (out_fd != STDOUT_FILENO) close(out_fd);
if (rm_in)
unlink(infile);
}
static void compress(const char *method, const char *infile, const char *outfile) {
FileFormat fmt = name2fmt[method];
if (fmt == FileFormat::UNKNOWN)
LOGE("Unknown compression method: [%s]\n", method);
bool in_std = infile == "-"sv;
bool rm_in = false;
int in_fd = in_std ? STDIN_FILENO : xopen(infile, O_RDONLY);
int out_fd = -1;
if (outfile == nullptr) {
if (in_std) {
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_fd = xopen(tmp.data(), O_WRONLY | O_CREAT | O_TRUNC, 0644);
fprintf(stderr, "Compressing to [%s]\n", tmp.data());
rm_in = true;
}
} else {
out_fd = outfile == "-"sv ?
STDOUT_FILENO :
xopen(outfile, O_WRONLY | O_CREAT | O_TRUNC, 0644);
}
compress_fd(fmt, in_fd, out_fd);
if (in_fd != STDIN_FILENO) close(in_fd);
if (out_fd != STDOUT_FILENO) close(out_fd);
if (rm_in)
unlink(infile);
}
int main(int argc, char *argv[]) {
cmdline_logging();
umask(0);
if (argc < 2)
usage(argv[0]);
// Skip '--' for backwards compatibility
string_view action(argv[1]);
if (str_starts(action, "--"))
action = argv[1] + 2;
if (action == "cleanup") {
fprintf(stderr, "Cleaning up...\n");
unlink(HEADER_FILE);
unlink(KERNEL_FILE);
unlink(RAMDISK_FILE);
unlink(SECOND_FILE);
unlink(KER_DTB_FILE);
unlink(EXTRA_FILE);
unlink(RECV_DTBO_FILE);
unlink(DTB_FILE);
unlink(BOOTCONFIG_FILE);
rm_rf(VND_RAMDISK_DIR);
} else if (argc > 2 && action == "sha1") {
uint8_t sha1[20];
{
mmap_data m(argv[2]);
sha1_hash(m, byte_data(sha1, sizeof(sha1)));
}
for (uint8_t i : sha1)
printf("%02x", i);
printf("\n");
} else if (argc > 2 && action == "split") {
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;
bool hdr = false;
for (;;) {
if (idx >= argc)
usage(argv[0]);
if (argv[idx][0] != '-')
break;
for (char *flag = &argv[idx][1]; *flag; ++flag) {
if (*flag == 'n')
nodecomp = true;
else if (*flag == 'h')
hdr = true;
else
usage(argv[0]);
}
++idx;
}
return unpack(argv[idx], nodecomp, hdr);
} else if (argc > 2 && action == "repack") {
if (argv[2] == "-n"sv) {
if (argc == 3)
usage(argv[0]);
repack(argv[3], argv[4] ? argv[4] : NEW_BOOT, true);
} else {
repack(argv[2], argv[3] ? argv[3] : NEW_BOOT);
}
} else if (argc > 2 && action == "verify") {
return verify(argv[2], argv[3]);
} else if (argc > 2 && action == "sign") {
if (argc == 5) usage(argv[0]);
return sign(
argv[2],
argc > 3 ? argv[3] : "/boot",
argc > 5 ? argv[4] : nullptr,
argc > 5 ? argv[5] : nullptr);
} else if (argc > 2 && action == "decompress") {
decompress(argv[2], argv[3]);
} else if (argc > 2 && str_starts(action, "compress")) {
compress(action[8] == '=' ? &action[9] : "gzip", argv[2], argv[3]);
} else if (argc > 4 && action == "hexpatch") {
return hexpatch(byte_view(argv[2]), byte_view(argv[3]), byte_view(argv[4])) ? 0 : 1;
} else if (argc > 2 && action == "cpio") {
return rust::cpio_commands(argc - 2, argv + 2) ? 0 : 1;
} else if (argc > 2 && action == "dtb") {
return rust::dtb_commands(argc - 2, argv + 2) ? 0 : 1;
} else if (argc > 2 && action == "extract") {
return rust::extract_boot_from_payload(
argv[2],
argc > 3 ? argv[3] : "",
argc > 4 ? argv[4] : ""
) ? 0 : 1;
} else {
usage(argv[0]);
}
return 0;
}

View File

@@ -102,12 +102,8 @@ fn hex2byte(hex: &[u8]) -> Vec<u8> {
v
}
pub fn hexpatch(file: &[u8], from: &[u8], to: &[u8]) -> bool {
pub fn hexpatch(file: &Utf8CStr, from: &Utf8CStr, to: &Utf8CStr) -> bool {
let res: LoggedResult<bool> = try {
let file = Utf8CStr::from_bytes(file)?;
let from = Utf8CStr::from_bytes(from)?;
let to = Utf8CStr::from_bytes(to)?;
let mut map = MappedFile::open_rw(file)?;
let pattern = hex2byte(from.as_bytes());
let patch = hex2byte(to.as_bytes());

View File

@@ -1,17 +1,13 @@
use byteorder::{BigEndian, ReadBytesExt};
use quick_protobuf::{BytesReader, MessageRead};
use std::{
fs::File,
io::{BufReader, Read, Seek, SeekFrom, Write},
os::fd::FromRawFd,
};
use crate::compress::get_decoder;
use crate::ffi::check_fmt;
use crate::proto::update_metadata::{DeltaArchiveManifest, mod_InstallOperation::Type};
use base::{
LoggedError, LoggedResult, ReadSeekExt, ResultExt, Utf8CStr, WriteExt, error, ffi::Utf8CStrRef,
};
use crate::proto::update_metadata::DeltaArchiveManifest;
use crate::proto::update_metadata::mod_InstallOperation::Type;
use base::{LoggedError, LoggedResult, ReadSeekExt, ResultExt, WriteExt, error};
use byteorder::{BigEndian, ReadBytesExt};
use quick_protobuf::{BytesReader, MessageRead};
use std::fs::File;
use std::io::{BufReader, Cursor, Read, Seek, SeekFrom, Write};
use std::os::fd::FromRawFd;
macro_rules! bad_payload {
($msg:literal) => {{
@@ -26,10 +22,10 @@ macro_rules! bad_payload {
const PAYLOAD_MAGIC: &str = "CrAU";
fn do_extract_boot_from_payload(
in_path: &Utf8CStr,
partition_name: Option<&Utf8CStr>,
out_path: Option<&Utf8CStr>,
pub fn extract_boot_from_payload(
in_path: &str,
partition_name: Option<&str>,
out_path: Option<&str>,
) -> LoggedResult<()> {
let mut reader = BufReader::new(if in_path == "-" {
unsafe { File::from_raw_fd(0) }
@@ -166,9 +162,11 @@ fn do_extract_boot_from_payload(
}
Type::REPLACE_BZ | Type::REPLACE_XZ => {
out_file.seek(SeekFrom::Start(out_offset))?;
let mut decoder = get_decoder(check_fmt(data), &mut out_file);
let fmt = check_fmt(data);
let mut decoder = get_decoder(fmt, Cursor::new(data));
let Ok(_): std::io::Result<()> = (try {
decoder.write_all(data)?;
std::io::copy(decoder.as_mut(), &mut out_file)?;
}) else {
return Err(bad_payload!("decompression failed"));
};
@@ -179,25 +177,3 @@ fn do_extract_boot_from_payload(
Ok(())
}
pub fn extract_boot_from_payload(
in_path: Utf8CStrRef,
partition: Utf8CStrRef,
out_path: Utf8CStrRef,
) -> bool {
let res: LoggedResult<()> = try {
let partition = if partition.is_empty() {
None
} else {
Some(partition)
};
let out_path = if out_path.is_empty() {
None
} else {
Some(out_path)
};
do_extract_boot_from_payload(in_path, partition, out_path)?
};
res.log_with_msg(|w| w.write_str("Failed to extract from payload"))
.is_ok()
}

View File

@@ -25,8 +25,7 @@ use x509_cert::der::Any;
use x509_cert::der::asn1::{OctetString, PrintableString};
use x509_cert::spki::AlgorithmIdentifier;
use base::libc::c_char;
use base::{LoggedResult, MappedFile, ResultExt, StrErr, Utf8CStr, log_err};
use base::{LoggedResult, MappedFile, ResultExt, SilentLogExt, Utf8CStr, cstr, log_err};
use crate::ffi::BootImage;
@@ -117,7 +116,7 @@ impl Verifier {
digest = Box::<Sha512>::default();
VerifyingKey::SHA521withECDSA(ec)
} else {
return Err(log_err!("Unsupported private key"));
return log_err!("Unsupported private key");
};
Ok(Verifier { digest, key })
}
@@ -178,7 +177,7 @@ impl Signer {
SigningKey::SHA521withECDSA(ec)
}
_ => {
return Err(log_err!("Unsupported private key"));
return log_err!("Unsupported private key");
}
},
},
@@ -249,7 +248,7 @@ struct BootSignature {
impl BootSignature {
fn verify(self, payload: &[u8]) -> LoggedResult<()> {
if self.authenticated_attributes.length as usize != payload.len() {
return Err(log_err!("Invalid image size"));
return log_err!("Invalid image size");
}
let mut verifier = Verifier::from_public_key(
self.certificate
@@ -265,23 +264,27 @@ impl BootSignature {
}
}
pub fn verify_boot_image(img: &BootImage, cert: *const c_char) -> bool {
let res: LoggedResult<()> = try {
let tail = img.tail();
impl BootImage {
pub fn verify(&self, cert: Option<&Utf8CStr>) -> LoggedResult<()> {
let tail = self.tail();
if tail.starts_with(b"AVB0") {
return log_err!();
}
// Don't use BootSignature::from_der because tail might have trailing zeros
let mut reader = SliceReader::new(tail)?;
let mut sig = BootSignature::decode(&mut reader)?;
match unsafe { Utf8CStr::from_ptr(cert) } {
Ok(s) => {
let pem = MappedFile::open(s)?;
sig.certificate = Certificate::from_pem(pem)?;
}
Err(StrErr::NullPointerError) => {}
Err(e) => Err(e)?,
let mut sig = BootSignature::decode(&mut reader).silent()?;
if let Some(s) = cert {
let pem = MappedFile::open(s)?;
sig.certificate = Certificate::from_pem(pem)?;
};
sig.verify(img.payload())?;
};
res.is_ok()
sig.verify(self.payload()).log()
}
pub fn verify_for_cxx(&self) -> bool {
self.verify(None).is_ok()
}
}
enum Bytes {
@@ -303,47 +306,44 @@ const VERITY_PK8: &[u8] = include_bytes!("../../../tools/keys/verity.pk8");
pub fn sign_boot_image(
payload: &[u8],
name: *const c_char,
cert: *const c_char,
key: *const c_char,
) -> Vec<u8> {
let res: LoggedResult<Vec<u8>> = try {
// Process arguments
let name = unsafe { Utf8CStr::from_ptr(name) }?;
let cert = match unsafe { Utf8CStr::from_ptr(cert) } {
Ok(s) => Bytes::Mapped(MappedFile::open(s)?),
Err(StrErr::NullPointerError) => Bytes::Slice(VERITY_PEM),
Err(e) => Err(e)?,
};
let key = match unsafe { Utf8CStr::from_ptr(key) } {
Ok(s) => Bytes::Mapped(MappedFile::open(s)?),
Err(StrErr::NullPointerError) => Bytes::Slice(VERITY_PK8),
Err(e) => Err(e)?,
};
// Parse cert and private key
let cert = Certificate::from_pem(cert)?;
let mut signer = Signer::from_private_key(key.as_ref())?;
// Sign image
let attr = AuthenticatedAttributes {
target: PrintableString::new(name.as_bytes())?,
length: payload.len() as u64,
};
signer.update(payload);
signer.update(attr.to_der()?.as_slice());
let sig = signer.sign()?;
// Create BootSignature DER
let alg_id = cert.signature_algorithm().clone();
let sig = BootSignature {
format_version: 1,
certificate: cert,
algorithm_identifier: alg_id,
authenticated_attributes: attr,
signature: OctetString::new(sig)?,
};
sig.to_der()?
name: &Utf8CStr,
cert: Option<&Utf8CStr>,
key: Option<&Utf8CStr>,
) -> LoggedResult<Vec<u8>> {
let cert = match cert {
Some(s) => Bytes::Mapped(MappedFile::open(s)?),
None => Bytes::Slice(VERITY_PEM),
};
res.unwrap_or_default()
let key = match key {
Some(s) => Bytes::Mapped(MappedFile::open(s)?),
None => Bytes::Slice(VERITY_PK8),
};
// Parse cert and private key
let cert = Certificate::from_pem(cert)?;
let mut signer = Signer::from_private_key(key.as_ref())?;
// Sign image
let attr = AuthenticatedAttributes {
target: PrintableString::new(name.as_bytes())?,
length: payload.len() as u64,
};
signer.update(payload);
signer.update(attr.to_der()?.as_slice());
let sig = signer.sign()?;
// Create BootSignature DER
let alg_id = cert.signature_algorithm().clone();
let sig = BootSignature {
format_version: 1,
certificate: cert,
algorithm_identifier: alg_id,
authenticated_attributes: attr,
signature: OctetString::new(sig)?,
};
sig.to_der().log()
}
pub fn sign_payload_for_cxx(payload: &[u8]) -> Vec<u8> {
sign_boot_image(payload, cstr!("/boot"), None, None).unwrap_or_default()
}

View File

@@ -8,16 +8,19 @@ crate-type = ["staticlib"]
path = "lib.rs"
[features]
default = ["check-signature"]
default = ["check-signature", "check-client", "su-check-db"]
# Disable these features for easier debugging during development
check-signature = []
check-client = []
su-check-db = []
[build-dependencies]
cxx-gen = { workspace = true }
pb-rs = { workspace = true }
[dependencies]
base = { path = "../base", features = ["selinux"] }
derive = { path = "derive" }
base = { workspace = true, features = ["selinux"] }
cxx = { workspace = true }
num-traits = { workspace = true }
num-derive = { workspace = true }
@@ -25,3 +28,5 @@ quick-protobuf = { workspace = true }
bytemuck = { workspace = true, features = ["derive"] }
thiserror = { workspace = true }
bit-set = { workspace = true }
nix = { workspace = true, features = ["fs", "mount", "poll", "signal", "term", "user", "zerocopy"] }
bitflags = { workspace = true }

View File

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

View File

@@ -1,9 +1,7 @@
#include <libgen.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <consts.hpp>
#include <base.hpp>
#include <core.hpp>
using namespace std;
@@ -28,7 +26,7 @@ int main(int argc, char *argv[]) {
cmdline_logging();
init_argv0(argc, argv);
string_view argv0 = basename(argv[0]);
Utf8CStr argv0 = basename(argv[0]);
umask(0);
@@ -63,6 +61,6 @@ int main(int argc, char *argv[]) {
return app.fn(argc, argv);
}
}
fprintf(stderr, "%s: applet not found\n", argv0.data());
fprintf(stderr, "%s: applet not found\n", argv0.c_str());
return 1;
}

View File

@@ -0,0 +1,254 @@
use crate::consts::{APP_PACKAGE_NAME, BBPATH, DATABIN, MODULEROOT, SECURE_DIR};
use crate::daemon::MagiskD;
use crate::ffi::{
DbEntryKey, RequestCode, check_key_combo, exec_common_scripts, exec_module_scripts,
get_magisk_tmp, initialize_denylist,
};
use crate::logging::setup_logfile;
use crate::module::disable_modules;
use crate::mount::{clean_mounts, setup_preinit_dir};
use crate::resetprop::get_prop;
use crate::selinux::restorecon;
use base::const_format::concatcp;
use base::{BufReadExt, FsPathBuilder, ResultExt, cstr, error, info};
use bitflags::bitflags;
use nix::fcntl::OFlag;
use std::io::BufReader;
use std::os::unix::net::UnixStream;
use std::process::{Command, Stdio};
use std::sync::atomic::Ordering;
bitflags! {
#[derive(Default)]
pub struct BootState : u32 {
const PostFsDataDone = 1 << 0;
const LateStartDone = 1 << 1;
const BootComplete = 1 << 2;
const SafeMode = 1 << 3;
}
}
impl MagiskD {
fn setup_magisk_env(&self) -> bool {
info!("* Initializing Magisk environment");
let mut buf = cstr::buf::default();
let app_bin_dir = buf
.append_path(self.app_data_dir())
.append_path("0")
.append_path(APP_PACKAGE_NAME)
.append_path("install");
// Alternative binaries paths
let alt_bin_dirs = &[
cstr!("/cache/data_adb/magisk"),
cstr!("/data/magisk"),
app_bin_dir,
];
for dir in alt_bin_dirs {
if dir.exists() {
cstr!(DATABIN).remove_all().ok();
dir.copy_to(cstr!(DATABIN)).ok();
dir.remove_all().ok();
}
}
cstr!("/cache/data_adb").remove_all().ok();
// Directories in /data/adb
cstr!(SECURE_DIR).follow_link().chmod(0o700).log_ok();
cstr!(DATABIN).mkdir(0o755).log_ok();
cstr!(MODULEROOT).mkdir(0o755).log_ok();
cstr!(concatcp!(SECURE_DIR, "/post-fs-data.d"))
.mkdir(0o755)
.log_ok();
cstr!(concatcp!(SECURE_DIR, "/service.d"))
.mkdir(0o755)
.log_ok();
restorecon();
let busybox = cstr!(concatcp!(DATABIN, "/busybox"));
if !busybox.exists() {
return false;
}
let tmp_bb = buf.append_path(get_magisk_tmp()).append_path(BBPATH);
tmp_bb.mkdirs(0o755).ok();
tmp_bb.append_path("busybox");
busybox.copy_to(tmp_bb).ok();
tmp_bb.follow_link().chmod(0o755).log_ok();
// Install busybox applets
Command::new(&tmp_bb)
.arg("--install")
.arg("-s")
.arg(tmp_bb.parent_dir().unwrap())
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()
.log_ok();
// magisk32 and magiskpolicy are not installed into ramdisk and has to be copied
// from data to magisk tmp
let magisk32 = cstr!(concatcp!(DATABIN, "/magisk32"));
if magisk32.exists() {
let tmp = buf.append_path(get_magisk_tmp()).append_path("magisk32");
magisk32.copy_to(tmp).log_ok();
}
let magiskpolicy = cstr!(concatcp!(DATABIN, "/magiskpolicy"));
if magiskpolicy.exists() {
let tmp = buf
.append_path(get_magisk_tmp())
.append_path("magiskpolicy");
magiskpolicy.copy_to(tmp).log_ok();
}
true
}
fn post_fs_data(&self) -> bool {
setup_logfile();
info!("** post-fs-data mode running");
self.preserve_stub_apk();
// Check secure dir
let secure_dir = cstr!(SECURE_DIR);
if !secure_dir.exists() {
if self.sdk_int < 24 {
secure_dir.mkdir(0o700).log_ok();
} else {
error!("* {} is not present, abort", SECURE_DIR);
return true;
}
}
self.prune_su_access();
if !self.setup_magisk_env() {
error!("* Magisk environment incomplete, abort");
return true;
}
// Check safe mode
let boot_cnt = self.get_db_setting(DbEntryKey::BootloopCount);
self.set_db_setting(DbEntryKey::BootloopCount, boot_cnt + 1)
.log()
.ok();
let safe_mode = boot_cnt >= 2
|| get_prop(cstr!("persist.sys.safemode")) == "1"
|| get_prop(cstr!("ro.sys.safemode")) == "1"
|| check_key_combo();
if safe_mode {
info!("* Safe mode triggered");
// Disable all modules and zygisk so next boot will be clean
disable_modules();
self.set_db_setting(DbEntryKey::ZygiskConfig, 0).log_ok();
return true;
}
exec_common_scripts(cstr!("post-fs-data"));
self.zygisk_enabled.store(
self.get_db_setting(DbEntryKey::ZygiskConfig) != 0,
Ordering::Release,
);
initialize_denylist();
self.handle_modules();
clean_mounts();
false
}
fn late_start(&self) {
setup_logfile();
info!("** late_start service mode running");
exec_common_scripts(cstr!("service"));
if let Some(module_list) = self.module_list.get() {
exec_module_scripts(cstr!("service"), module_list);
}
}
fn boot_complete(&self) {
setup_logfile();
info!("** boot-complete triggered");
// Reset the bootloop counter once we have boot-complete
self.set_db_setting(DbEntryKey::BootloopCount, 0).log_ok();
// At this point it's safe to create the folder
let secure_dir = cstr!(SECURE_DIR);
if !secure_dir.exists() {
secure_dir.mkdir(0o700).log_ok();
}
setup_preinit_dir();
self.ensure_manager();
if self.zygisk_enabled.load(Ordering::Relaxed) {
self.zygisk.lock().unwrap().reset(true);
}
}
pub fn boot_stage_handler(&self, client: UnixStream, code: RequestCode) {
// Make sure boot stage execution is always serialized
let mut state = self.boot_stage_lock.lock().unwrap();
match code {
RequestCode::POST_FS_DATA => {
if check_data() && !state.contains(BootState::PostFsDataDone) {
if self.post_fs_data() {
state.insert(BootState::SafeMode);
}
state.insert(BootState::PostFsDataDone);
}
}
RequestCode::LATE_START => {
drop(client);
if state.contains(BootState::PostFsDataDone) && !state.contains(BootState::SafeMode)
{
self.late_start();
state.insert(BootState::LateStartDone);
}
}
RequestCode::BOOT_COMPLETE => {
drop(client);
if state.contains(BootState::PostFsDataDone) {
state.insert(BootState::BootComplete);
self.boot_complete()
}
}
_ => {}
}
}
}
fn check_data() -> bool {
if let Ok(file) = cstr!("/proc/mounts").open(OFlag::O_RDONLY | OFlag::O_CLOEXEC) {
let mut mnt = false;
BufReader::new(file).for_each_line(|line| {
if line.contains(" /data ") && !line.contains("tmpfs") {
mnt = true;
return false;
}
true
});
if !mnt {
return false;
}
let crypto = get_prop(cstr!("ro.crypto.state"));
return if !crypto.is_empty() {
if crypto != "encrypted" {
// Unencrypted, we can directly access data
true
} else {
// Encrypted, check whether vold is started
!get_prop(cstr!("init.svc.vold")).is_empty()
}
} else {
// ro.crypto.state is not set, assume it's unencrypted
true
};
}
false
}

View File

@@ -1,4 +1,5 @@
use pb_rs::{ConfigBuilder, types::FileDescriptor};
use pb_rs::ConfigBuilder;
use pb_rs::types::FileDescriptor;
use crate::codegen::gen_cxx_binding;

View File

@@ -1,542 +0,0 @@
#include <csignal>
#include <libgen.h>
#include <sys/un.h>
#include <sys/mount.h>
#include <sys/sysmacros.h>
#include <linux/input.h>
#include <consts.hpp>
#include <base.hpp>
#include <core.hpp>
#include <flags.h>
using namespace std;
int SDK_INT = -1;
static struct stat self_st;
static map<int, poll_callback> *poll_map;
static vector<pollfd> *poll_fds;
static int poll_ctrl;
enum {
POLL_CTRL_NEW,
POLL_CTRL_RM,
};
void register_poll(const pollfd *pfd, poll_callback callback) {
if (gettid() == getpid()) {
// On main thread, directly modify
poll_map->try_emplace(pfd->fd, callback);
poll_fds->emplace_back(*pfd);
} else {
// Send it to poll_ctrl
write_int(poll_ctrl, POLL_CTRL_NEW);
xwrite(poll_ctrl, pfd, sizeof(*pfd));
xwrite(poll_ctrl, &callback, sizeof(callback));
}
}
void unregister_poll(int fd, bool auto_close) {
if (fd < 0)
return;
if (gettid() == getpid()) {
// On main thread, directly modify
poll_map->erase(fd);
for (auto &poll_fd : *poll_fds) {
if (poll_fd.fd == fd) {
if (auto_close) {
close(poll_fd.fd);
}
// Cannot modify while iterating, invalidate it instead
// It will be removed in the next poll loop
poll_fd.fd = -1;
break;
}
}
} else {
// Send it to poll_ctrl
write_int(poll_ctrl, POLL_CTRL_RM);
write_int(poll_ctrl, fd);
write_int(poll_ctrl, auto_close);
}
}
void clear_poll() {
if (poll_fds) {
for (auto &poll_fd : *poll_fds) {
close(poll_fd.fd);
}
}
delete poll_fds;
delete poll_map;
poll_fds = nullptr;
poll_map = nullptr;
}
static void poll_ctrl_handler(pollfd *pfd) {
int code = read_int(pfd->fd);
switch (code) {
case POLL_CTRL_NEW: {
pollfd new_fd{};
poll_callback cb;
xxread(pfd->fd, &new_fd, sizeof(new_fd));
xxread(pfd->fd, &cb, sizeof(cb));
register_poll(&new_fd, cb);
break;
}
case POLL_CTRL_RM: {
int fd = read_int(pfd->fd);
bool auto_close = read_int(pfd->fd);
unregister_poll(fd, auto_close);
break;
}
default:
__builtin_unreachable();
}
}
[[noreturn]] static void poll_loop() {
// Register poll_ctrl
auto pipefd = array<int, 2>{-1, -1};
xpipe2(pipefd, O_CLOEXEC);
poll_ctrl = pipefd[1];
pollfd poll_ctrl_pfd = { pipefd[0], POLLIN, 0 };
register_poll(&poll_ctrl_pfd, poll_ctrl_handler);
for (;;) {
if (poll(poll_fds->data(), poll_fds->size(), -1) <= 0)
continue;
// MUST iterate with index because any poll_callback could add new elements to poll_fds
for (int i = 0; i < poll_fds->size();) {
auto &pfd = (*poll_fds)[i];
if (pfd.revents) {
if (pfd.revents & POLLERR || pfd.revents & POLLNVAL) {
poll_map->erase(pfd.fd);
poll_fds->erase(poll_fds->begin() + i);
continue;
}
if (auto it = poll_map->find(pfd.fd); it != poll_map->end()) {
it->second(&pfd);
}
}
++i;
}
}
}
bool get_client_cred(int fd, sock_cred *cred) {
socklen_t len = sizeof(ucred);
if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, cred, &len) != 0)
return false;
char buf[4096];
len = sizeof(buf);
if (getsockopt(fd, SOL_SOCKET, SO_PEERSEC, buf, &len) != 0)
len = 0;
buf[len] = '\0';
cred->context = buf;
return true;
}
bool read_string(int fd, std::string &str) {
str.clear();
int len = read_int(fd);
str.resize(len);
return xxread(fd, str.data(), len) == len;
}
string read_string(int fd) {
string str;
read_string(fd, str);
return str;
}
void write_string(int fd, string_view str) {
if (fd < 0) return;
write_int(fd, str.size());
xwrite(fd, str.data(), str.size());
}
static void handle_request_async(int client, int code, const sock_cred &cred) {
auto &daemon = MagiskD::Get();
switch (code) {
case +RequestCode::DENYLIST:
denylist_handler(client, &cred);
break;
case +RequestCode::SUPERUSER:
daemon.su_daemon_handler(client, cred);
break;
case +RequestCode::ZYGOTE_RESTART: {
LOGI("** zygote restarted\n");
daemon.prune_su_access();
scan_deny_apps();
daemon.zygisk_reset(false);
close(client);
break;
}
case +RequestCode::SQLITE_CMD:
daemon.db_exec(client);
break;
case +RequestCode::REMOVE_MODULES: {
int do_reboot = read_int(client);
remove_modules();
write_int(client, 0);
close(client);
if (do_reboot) {
daemon.reboot();
}
break;
}
case +RequestCode::ZYGISK:
daemon.zygisk_handler(client);
break;
default:
__builtin_unreachable();
}
}
static void handle_request_sync(int client, int code) {
switch (code) {
case +RequestCode::CHECK_VERSION:
#if MAGISK_DEBUG
write_string(client, MAGISK_VERSION ":MAGISK:D");
#else
write_string(client, MAGISK_VERSION ":MAGISK:R");
#endif
break;
case +RequestCode::CHECK_VERSION_CODE:
write_int(client, MAGISK_VER_CODE);
break;
case +RequestCode::START_DAEMON:
setup_logfile();
break;
case +RequestCode::STOP_DAEMON: {
// Unmount all overlays
denylist_handler(-1, nullptr);
// Restore native bridge property
restore_zygisk_prop();
write_int(client, 0);
// Terminate the daemon!
exit(0);
}
default:
__builtin_unreachable();
}
}
static bool is_client(pid_t pid) {
// Verify caller is the same as server
char path[32];
sprintf(path, "/proc/%d/exe", pid);
struct stat st{};
return !(stat(path, &st) || st.st_dev != self_st.st_dev || st.st_ino != self_st.st_ino);
}
static void handle_request(pollfd *pfd) {
owned_fd client = xaccept4(pfd->fd, nullptr, nullptr, SOCK_CLOEXEC);
// Verify client credentials
sock_cred cred;
bool is_root;
bool is_zygote;
int code;
if (!get_client_cred(client, &cred)) {
// Client died
return;
}
is_root = cred.uid == AID_ROOT;
is_zygote = cred.context == "u:r:zygote:s0";
if (!is_root && !is_zygote && !is_client(cred.pid)) {
// Unsupported client state
write_int(client, +RespondCode::ACCESS_DENIED);
return;
}
code = read_int(client);
if (code < 0 || code >= +RequestCode::END ||
code == +RequestCode::_SYNC_BARRIER_ ||
code == +RequestCode::_STAGE_BARRIER_) {
// Unknown request code
return;
}
// Check client permissions
switch (code) {
case +RequestCode::POST_FS_DATA:
case +RequestCode::LATE_START:
case +RequestCode::BOOT_COMPLETE:
case +RequestCode::ZYGOTE_RESTART:
case +RequestCode::SQLITE_CMD:
case +RequestCode::DENYLIST:
case +RequestCode::STOP_DAEMON:
if (!is_root) {
write_int(client, +RespondCode::ROOT_REQUIRED);
return;
}
break;
case +RequestCode::REMOVE_MODULES:
if (!is_root && cred.uid != AID_SHELL) {
write_int(client, +RespondCode::ACCESS_DENIED);
return;
}
break;
case +RequestCode::ZYGISK:
if (!is_zygote) {
// Invalid client context
write_int(client, +RespondCode::ACCESS_DENIED);
return;
}
break;
default:
break;
}
write_int(client, +RespondCode::OK);
if (code < +RequestCode::_SYNC_BARRIER_) {
handle_request_sync(client, code);
} else if (code < +RequestCode::_STAGE_BARRIER_) {
exec_task([=, fd = client.release()] { handle_request_async(fd, code, cred); });
} else {
exec_task([=, fd = client.release()] {
MagiskD::Get().boot_stage_handler(fd, code);
});
}
}
static void daemon_entry() {
android_logging();
// Block all signals
sigset_t block_set;
sigfillset(&block_set);
pthread_sigmask(SIG_SETMASK, &block_set, nullptr);
// Change process name
set_nice_name("magiskd");
int fd = xopen("/dev/null", O_WRONLY);
xdup2(fd, STDOUT_FILENO);
xdup2(fd, STDERR_FILENO);
if (fd > STDERR_FILENO)
close(fd);
fd = xopen("/dev/zero", O_RDONLY);
xdup2(fd, STDIN_FILENO);
if (fd > STDERR_FILENO)
close(fd);
rust::daemon_entry();
SDK_INT = MagiskD::Get().sdk_int();
// Get self stat
xstat("/proc/self/exe", &self_st);
fd = xsocket(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0);
sockaddr_un addr = {.sun_family = AF_LOCAL};
ssprintf(addr.sun_path, sizeof(addr.sun_path), "%s/" MAIN_SOCKET, get_magisk_tmp());
unlink(addr.sun_path);
if (xbind(fd, (sockaddr *) &addr, sizeof(addr)))
exit(1);
chmod(addr.sun_path, 0666);
setfilecon(addr.sun_path, MAGISK_FILE_CON);
xlisten(fd, 10);
default_new(poll_map);
default_new(poll_fds);
// Register handler for main socket
pollfd main_socket_pfd = { fd, POLLIN, 0 };
register_poll(&main_socket_pfd, handle_request);
// Loop forever to listen for requests
init_thread_pool();
poll_loop();
}
const char *get_magisk_tmp() {
static const char *path = nullptr;
if (path == nullptr) {
if (access("/debug_ramdisk/" INTLROOT, F_OK) == 0) {
path = "/debug_ramdisk";
} else if (access("/sbin/" INTLROOT, F_OK) == 0) {
path = "/sbin";
} else {
path = "";
}
}
return path;
}
int connect_daemon(int req, bool create) {
int fd = xsocket(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0);
sockaddr_un addr = {.sun_family = AF_LOCAL};
const char *tmp = get_magisk_tmp();
ssprintf(addr.sun_path, sizeof(addr.sun_path), "%s/" MAIN_SOCKET, tmp);
if (connect(fd, (sockaddr *) &addr, sizeof(addr))) {
if (!create || getuid() != AID_ROOT) {
LOGE("No daemon is currently running!\n");
close(fd);
return -1;
}
char buf[64];
xreadlink("/proc/self/exe", buf, sizeof(buf));
if (tmp[0] == '\0' || !str_starts(buf, tmp)) {
LOGE("Start daemon on magisk tmpfs\n");
close(fd);
return -1;
}
if (fork_dont_care() == 0) {
close(fd);
daemon_entry();
}
while (connect(fd, (sockaddr *) &addr, sizeof(addr)))
usleep(10000);
}
write_int(fd, req);
int res = read_int(fd);
if (res < +RespondCode::ERROR || res >= +RespondCode::END)
res = +RespondCode::ERROR;
switch (res) {
case +RespondCode::OK:
break;
case +RespondCode::ERROR:
LOGE("Daemon error\n");
close(fd);
return -1;
case +RespondCode::ROOT_REQUIRED:
LOGE("Root is required for this operation\n");
close(fd);
return -1;
case +RespondCode::ACCESS_DENIED:
LOGE("Access denied\n");
close(fd);
return -1;
default:
__builtin_unreachable();
}
return fd;
}
bool setup_magisk_env() {
char buf[4096];
LOGI("* Initializing Magisk environment\n");
ssprintf(buf, sizeof(buf), "%s/0/%s/install", APP_DATA_DIR, JAVA_PACKAGE_NAME);
// Alternative binaries paths
const char *alt_bin[] = { "/cache/data_adb/magisk", "/data/magisk", buf };
for (auto alt : alt_bin) {
if (access(alt, F_OK) == 0) {
rm_rf(DATABIN);
cp_afc(alt, DATABIN);
rm_rf(alt);
}
}
rm_rf("/cache/data_adb");
// Directories in /data/adb
chmod(SECURE_DIR, 0700);
xmkdir(DATABIN, 0755);
xmkdir(MODULEROOT, 0755);
xmkdir(SECURE_DIR "/post-fs-data.d", 0755);
xmkdir(SECURE_DIR "/service.d", 0755);
restorecon();
if (access(DATABIN "/busybox", X_OK))
return false;
ssprintf(buf, sizeof(buf), "%s/" BBPATH "/busybox", get_magisk_tmp());
mkdir(dirname(buf), 0755);
cp_afc(DATABIN "/busybox", buf);
exec_command_async(buf, "--install", "-s", dirname(buf));
// magisk32 and magiskpolicy are not installed into ramdisk and has to be copied
// from data to magisk tmp
if (access(DATABIN "/magisk32", X_OK) == 0) {
ssprintf(buf, sizeof(buf), "%s/magisk32", get_magisk_tmp());
cp_afc(DATABIN "/magisk32", buf);
}
if (access(DATABIN "/magiskpolicy", X_OK) == 0) {
ssprintf(buf, sizeof(buf), "%s/magiskpolicy", get_magisk_tmp());
cp_afc(DATABIN "/magiskpolicy", buf);
}
return true;
}
void unlock_blocks() {
int fd, dev, OFF = 0;
auto dir = xopen_dir("/dev/block");
if (!dir)
return;
dev = dirfd(dir.get());
for (dirent *entry; (entry = readdir(dir.get()));) {
if (entry->d_type == DT_BLK) {
if ((fd = openat(dev, entry->d_name, O_RDONLY | O_CLOEXEC)) < 0)
continue;
if (ioctl(fd, BLKROSET, &OFF) < 0)
PLOGE("unlock %s", entry->d_name);
close(fd);
}
}
}
#define test_bit(bit, array) (array[bit / 8] & (1 << (bit % 8)))
bool check_key_combo() {
uint8_t bitmask[(KEY_MAX + 1) / 8];
vector<int> events;
constexpr char name[] = "/dev/.ev";
// First collect candidate events that accepts volume down
for (int minor = 64; minor < 96; ++minor) {
if (xmknod(name, S_IFCHR | 0444, makedev(13, minor)))
continue;
int fd = open(name, O_RDONLY | O_CLOEXEC);
unlink(name);
if (fd < 0)
continue;
memset(bitmask, 0, sizeof(bitmask));
ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(bitmask)), bitmask);
if (test_bit(KEY_VOLUMEDOWN, bitmask))
events.push_back(fd);
else
close(fd);
}
if (events.empty())
return false;
run_finally fin([&]{ std::for_each(events.begin(), events.end(), close); });
// Check if volume down key is held continuously for more than 3 seconds
for (int i = 0; i < 300; ++i) {
bool pressed = false;
for (const int &fd : events) {
memset(bitmask, 0, sizeof(bitmask));
ioctl(fd, EVIOCGKEY(sizeof(bitmask)), bitmask);
if (test_bit(KEY_VOLUMEDOWN, bitmask)) {
pressed = true;
break;
}
}
if (!pressed)
return false;
// Check every 10ms
usleep(10000);
}
LOGD("KEY_VOLUMEDOWN detected: enter safe mode\n");
return true;
}

View File

@@ -1,51 +1,43 @@
use crate::consts::{MAGISK_FULL_VER, MAGISK_PROC_CON, MAIN_CONFIG, ROOTMNT, ROOTOVL, SECURE_DIR};
use crate::bootstages::BootState;
use crate::consts::{
MAGISK_FILE_CON, MAGISK_FULL_VER, MAGISK_PROC_CON, MAGISK_VER_CODE, MAGISK_VERSION,
MAIN_CONFIG, MAIN_SOCKET, ROOTMNT, ROOTOVL,
};
use crate::db::Sqlite3;
use crate::ffi::{
DbEntryKey, ModuleInfo, RequestCode, check_key_combo, exec_common_scripts, exec_module_scripts,
get_magisk_tmp, get_prop, initialize_denylist, set_prop, setup_magisk_env,
ModuleInfo, RequestCode, RespondCode, denylist_handler, get_magisk_tmp, scan_deny_apps,
};
use crate::logging::{magisk_logging, setup_logfile, start_log_daemon};
use crate::module::disable_modules;
use crate::mount::{clean_mounts, setup_preinit_dir};
use crate::logging::{android_logging, magisk_logging, setup_logfile, start_log_daemon};
use crate::module::remove_modules;
use crate::package::ManagerInfo;
use crate::resetprop::{get_prop, set_prop};
use crate::selinux::restore_tmpcon;
use crate::socket::{IpcRead, IpcWrite};
use crate::su::SuInfo;
use base::libc::{O_APPEND, O_CLOEXEC, O_RDONLY, O_WRONLY};
use crate::thread::ThreadPool;
use crate::zygisk::ZygiskState;
use base::const_format::concatcp;
use base::{
AtomicArc, BufReadExt, FsPathBuilder, ResultExt, Utf8CStr, Utf8CStrBuf, cstr, error, info, libc,
AtomicArc, BufReadExt, FileAttr, FsPathBuilder, LoggedResult, ReadExt, ResultExt, Utf8CStr,
Utf8CStrBuf, WriteExt, cstr, fork_dont_care, info, libc, log_err, set_nice_name,
};
use std::fmt::Write as FmtWrite;
use nix::fcntl::OFlag;
use nix::mount::MsFlags;
use nix::sys::signal::SigSet;
use nix::unistd::{dup2_stderr, dup2_stdin, dup2_stdout, getpid, getuid, setsid};
use num_traits::AsPrimitive;
use std::fmt::Write as _;
use std::io::{BufReader, Write};
use std::os::unix::net::UnixStream;
use std::process::Command;
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
use std::os::fd::{AsFd, AsRawFd, IntoRawFd, RawFd};
use std::os::unix::net::{UCred, UnixListener, UnixStream};
use std::process::{Command, exit};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Mutex, OnceLock};
use std::time::Duration;
// Global magiskd singleton
pub static MAGISKD: OnceLock<MagiskD> = OnceLock::new();
#[repr(u32)]
enum BootState {
PostFsDataDone = (1 << 0),
LateStartDone = (1 << 1),
BootComplete = (1 << 2),
SafeMode = (1 << 3),
}
#[derive(Default)]
#[repr(transparent)]
struct BootStateFlags(u32);
impl BootStateFlags {
fn contains(&self, stage: BootState) -> bool {
(self.0 & stage as u32) != 0
}
fn set(&mut self, stage: BootState) {
self.0 |= stage as u32;
}
}
pub const AID_ROOT: i32 = 0;
pub const AID_SHELL: i32 = 2000;
pub const AID_APP_START: i32 = 10000;
@@ -64,15 +56,15 @@ pub const fn to_user_id(uid: i32) -> i32 {
pub struct MagiskD {
pub sql_connection: Mutex<Option<Sqlite3>>,
pub manager_info: Mutex<ManagerInfo>,
boot_stage_lock: Mutex<BootStateFlags>,
pub boot_stage_lock: Mutex<BootState>,
pub module_list: OnceLock<Vec<ModuleInfo>>,
pub zygiskd_sockets: Mutex<(Option<UnixStream>, Option<UnixStream>)>,
pub zygisk_enabled: AtomicBool,
pub zygote_start_count: AtomicU32,
pub zygisk: Mutex<ZygiskState>,
pub cached_su_info: AtomicArc<SuInfo>,
sdk_int: i32,
pub sdk_int: i32,
pub is_emulator: bool,
is_recovery: bool,
exe_attr: FileAttr,
}
impl MagiskD {
@@ -80,10 +72,6 @@ impl MagiskD {
unsafe { MAGISKD.get().unwrap_unchecked() }
}
pub fn zygisk_enabled(&self) -> bool {
self.zygisk_enabled.load(Ordering::Acquire)
}
pub fn sdk_int(&self) -> i32 {
self.sdk_int
}
@@ -96,125 +84,73 @@ impl MagiskD {
}
}
fn post_fs_data(&self) -> bool {
setup_logfile();
info!("** post-fs-data mode running");
self.preserve_stub_apk();
// Check secure dir
let secure_dir = cstr!(SECURE_DIR);
if !secure_dir.exists() {
if self.sdk_int < 24 {
secure_dir.mkdir(0o700).log_ok();
} else {
error!("* {} is not present, abort", SECURE_DIR);
return true;
}
}
self.prune_su_access();
if !setup_magisk_env() {
error!("* Magisk environment incomplete, abort");
return true;
}
// Check safe mode
let boot_cnt = self.get_db_setting(DbEntryKey::BootloopCount);
self.set_db_setting(DbEntryKey::BootloopCount, boot_cnt + 1)
.log()
.ok();
let safe_mode = boot_cnt >= 2
|| get_prop(cstr!("persist.sys.safemode"), true) == "1"
|| get_prop(cstr!("ro.sys.safemode"), false) == "1"
|| check_key_combo();
if safe_mode {
info!("* Safe mode triggered");
// Disable all modules and zygisk so next boot will be clean
disable_modules();
self.set_db_setting(DbEntryKey::ZygiskConfig, 0).log_ok();
return true;
}
exec_common_scripts(cstr!("post-fs-data"));
self.zygisk_enabled.store(
self.get_db_setting(DbEntryKey::ZygiskConfig) != 0,
Ordering::Release,
);
initialize_denylist();
self.handle_modules();
clean_mounts();
false
}
fn late_start(&self) {
setup_logfile();
info!("** late_start service mode running");
exec_common_scripts(cstr!("service"));
if let Some(module_list) = self.module_list.get() {
exec_module_scripts(cstr!("service"), module_list);
}
}
fn boot_complete(&self) {
setup_logfile();
info!("** boot-complete triggered");
// Reset the bootloop counter once we have boot-complete
self.set_db_setting(DbEntryKey::BootloopCount, 0).log_ok();
// At this point it's safe to create the folder
let secure_dir = cstr!(SECURE_DIR);
if !secure_dir.exists() {
secure_dir.mkdir(0o700).log_ok();
}
setup_preinit_dir();
self.ensure_manager();
self.zygisk_reset(true)
}
pub fn boot_stage_handler(&self, client: i32, code: i32) {
// Make sure boot stage execution is always serialized
let mut state = self.boot_stage_lock.lock().unwrap();
let code = RequestCode { repr: code };
fn handle_request_sync(&self, mut client: UnixStream, code: RequestCode) {
match code {
RequestCode::POST_FS_DATA => {
if check_data() && !state.contains(BootState::PostFsDataDone) {
if self.post_fs_data() {
state.set(BootState::SafeMode);
}
state.set(BootState::PostFsDataDone);
}
unsafe { libc::close(client) };
RequestCode::CHECK_VERSION => {
#[cfg(debug_assertions)]
let s = concatcp!(MAGISK_VERSION, ":MAGISK:D");
#[cfg(not(debug_assertions))]
let s = concatcp!(MAGISK_VERSION, ":MAGISK:R");
client.write_encodable(s).log_ok();
}
RequestCode::LATE_START => {
unsafe { libc::close(client) };
if state.contains(BootState::PostFsDataDone) && !state.contains(BootState::SafeMode)
{
self.late_start();
state.set(BootState::LateStartDone);
}
RequestCode::CHECK_VERSION_CODE => {
client.write_pod(&MAGISK_VER_CODE).log_ok();
}
RequestCode::BOOT_COMPLETE => {
unsafe { libc::close(client) };
if state.contains(BootState::PostFsDataDone) {
state.set(BootState::BootComplete);
self.boot_complete()
}
RequestCode::START_DAEMON => {
setup_logfile();
}
_ => {
unsafe { libc::close(client) };
RequestCode::STOP_DAEMON => {
// Unmount all overlays
denylist_handler(-1);
// Restore native bridge property
self.zygisk.lock().unwrap().restore_prop();
client.write_pod(&0).log_ok();
// Terminate the daemon!
exit(0);
}
_ => {}
}
}
pub fn reboot(&self) {
fn handle_request_async(&self, mut client: UnixStream, code: RequestCode, cred: UCred) {
match code {
RequestCode::DENYLIST => {
denylist_handler(client.into_raw_fd());
}
RequestCode::SUPERUSER => {
self.su_daemon_handler(client, cred);
}
RequestCode::ZYGOTE_RESTART => {
info!("** zygote restarted");
self.prune_su_access();
scan_deny_apps();
if self.zygisk_enabled.load(Ordering::Relaxed) {
self.zygisk.lock().unwrap().reset(false);
}
}
RequestCode::SQLITE_CMD => {
self.db_exec_for_cli(client).ok();
}
RequestCode::REMOVE_MODULES => {
let do_reboot: bool = client.read_decodable().log().unwrap_or_default();
remove_modules();
client.write_pod(&0).log_ok();
if do_reboot {
self.reboot();
}
}
RequestCode::ZYGISK => {
self.zygisk_handler(client);
}
_ => {}
}
}
fn reboot(&self) {
if self.is_recovery {
Command::new("/system/bin/reboot").arg("recovery").status()
} else {
@@ -222,24 +158,161 @@ impl MagiskD {
}
.ok();
}
#[cfg(feature = "check-client")]
fn is_client(&self, pid: i32) -> bool {
let mut buf = cstr::buf::new::<32>();
write!(buf, "/proc/{pid}/exe").ok();
if let Ok(attr) = buf.follow_link().get_attr() {
attr.st.st_dev == self.exe_attr.st.st_dev && attr.st.st_ino == self.exe_attr.st.st_ino
} else {
false
}
}
#[cfg(not(feature = "check-client"))]
fn is_client(&self, pid: i32) -> bool {
true
}
fn handle_requests(&'static self, mut client: UnixStream) {
let Ok(cred) = client.peer_cred() else {
// Client died
return;
};
// There are no abstractions for SO_PEERSEC yet, call the raw C API.
let mut context = cstr::buf::new::<256>();
unsafe {
let mut len: libc::socklen_t = context.capacity().as_();
libc::getsockopt(
client.as_raw_fd(),
libc::SOL_SOCKET,
libc::SO_PEERSEC,
context.as_mut_ptr().cast(),
&mut len,
);
}
context.rebuild().ok();
let is_root = cred.uid == 0;
let is_shell = cred.uid == 2000;
let is_zygote = &context == "u:r:zygote:s0";
if !is_root && !is_zygote && !self.is_client(cred.pid.unwrap_or(-1)) {
// Unsupported client state
client.write_pod(&RespondCode::ACCESS_DENIED.repr).log_ok();
return;
}
let mut code = -1;
client.read_pod(&mut code).ok();
if !(0..RequestCode::END.repr).contains(&code)
|| code == RequestCode::_SYNC_BARRIER_.repr
|| code == RequestCode::_STAGE_BARRIER_.repr
{
// Unknown request code
return;
}
let code = RequestCode { repr: code };
// Permission checks
match code {
RequestCode::POST_FS_DATA
| RequestCode::LATE_START
| RequestCode::BOOT_COMPLETE
| RequestCode::ZYGOTE_RESTART
| RequestCode::SQLITE_CMD
| RequestCode::DENYLIST
| RequestCode::STOP_DAEMON => {
if !is_root {
client.write_pod(&RespondCode::ROOT_REQUIRED.repr).log_ok();
return;
}
}
RequestCode::REMOVE_MODULES => {
if !is_root && !is_shell {
// Only allow root and ADB shell to remove modules
client.write_pod(&RespondCode::ACCESS_DENIED.repr).log_ok();
return;
}
}
RequestCode::ZYGISK => {
if !is_zygote {
// Invalid client context
client.write_pod(&RespondCode::ACCESS_DENIED.repr).log_ok();
return;
}
}
_ => {}
}
if client.write_pod(&RespondCode::OK.repr).is_err() {
return;
}
if code.repr < RequestCode::_SYNC_BARRIER_.repr {
self.handle_request_sync(client, code)
} else if code.repr < RequestCode::_STAGE_BARRIER_.repr {
ThreadPool::exec_task(move || {
self.handle_request_async(client, code, cred);
})
} else {
ThreadPool::exec_task(move || {
self.boot_stage_handler(client, code);
})
}
}
}
pub fn daemon_entry() {
unsafe { libc::setsid() };
fn switch_cgroup(cgroup: &str, pid: i32) {
let mut buf = cstr::buf::new::<64>()
.join_path(cgroup)
.join_path("cgroup.procs");
if !buf.exists() {
return;
}
if let Ok(mut file) = buf.open(OFlag::O_WRONLY | OFlag::O_APPEND | OFlag::O_CLOEXEC) {
buf.clear();
write!(buf, "{pid}").ok();
file.write_all(buf.as_bytes()).log_ok();
}
}
fn daemon_entry() {
set_nice_name(cstr!("magiskd"));
android_logging();
// Block all signals
SigSet::all().thread_set_mask().log_ok();
// Swap out the original stdio
if let Ok(null) = cstr!("/dev/null").open(OFlag::O_WRONLY).log() {
dup2_stdout(null.as_fd()).log_ok();
dup2_stderr(null.as_fd()).log_ok();
}
if let Ok(zero) = cstr!("/dev/zero").open(OFlag::O_RDONLY).log() {
dup2_stdin(zero).log_ok();
}
setsid().log_ok();
// Make sure the current context is magisk
if let Ok(mut current) = cstr!("/proc/self/attr/current").open(O_WRONLY | O_CLOEXEC) {
if let Ok(mut current) =
cstr!("/proc/self/attr/current").open(OFlag::O_WRONLY | OFlag::O_CLOEXEC)
{
let con = cstr!(MAGISK_PROC_CON);
current.write_all(con.as_bytes_with_nul()).log_ok();
}
start_log_daemon();
magisk_logging();
info!("Magisk {} daemon started", MAGISK_FULL_VER);
info!("Magisk {MAGISK_FULL_VER} daemon started");
let is_emulator = get_prop(cstr!("ro.kernel.qemu"), false) == "1"
|| get_prop(cstr!("ro.boot.qemu"), false) == "1"
|| get_prop(cstr!("ro.product.device"), false).contains("vsoc");
let is_emulator = get_prop(cstr!("ro.kernel.qemu")) == "1"
|| get_prop(cstr!("ro.boot.qemu")) == "1"
|| get_prop(cstr!("ro.product.device")).contains("vsoc");
// Load config status
let magisk_tmp = get_magisk_tmp();
@@ -247,8 +320,8 @@ pub fn daemon_entry() {
.join_path(magisk_tmp)
.join_path(MAIN_CONFIG);
let mut is_recovery = false;
if let Ok(main_config) = tmp_path.open(O_RDONLY | O_CLOEXEC) {
BufReader::new(main_config).foreach_props(|key, val| {
if let Ok(main_config) = tmp_path.open(OFlag::O_RDONLY | OFlag::O_CLOEXEC) {
BufReader::new(main_config).for_each_prop(|key, val| {
if key == "RECOVERYMODE" {
is_recovery = val == "true";
return false;
@@ -259,8 +332,8 @@ pub fn daemon_entry() {
tmp_path.truncate(magisk_tmp.len());
let mut sdk_int = -1;
if let Ok(build_prop) = cstr!("/system/build.prop").open(O_RDONLY | O_CLOEXEC) {
BufReader::new(build_prop).foreach_props(|key, val| {
if let Ok(build_prop) = cstr!("/system/build.prop").open(OFlag::O_RDONLY | OFlag::O_CLOEXEC) {
BufReader::new(build_prop).for_each_prop(|key, val| {
if key == "ro.build.version.sdk" {
sdk_int = val.parse::<i32>().unwrap_or(-1);
return false;
@@ -270,32 +343,32 @@ pub fn daemon_entry() {
}
if sdk_int < 0 {
// In case some devices do not store this info in build.prop, fallback to getprop
sdk_int = get_prop(cstr!("ro.build.version.sdk"), false)
sdk_int = get_prop(cstr!("ro.build.version.sdk"))
.parse::<i32>()
.unwrap_or(-1);
}
info!("* Device API level: {}", sdk_int);
info!("* Device API level: {sdk_int}");
restore_tmpcon().log_ok();
// Escape from cgroup
let pid = unsafe { libc::getpid() };
let pid = getpid().as_raw();
switch_cgroup("/acct", pid);
switch_cgroup("/dev/cg2_bpf", pid);
switch_cgroup("/sys/fs/cgroup", pid);
if get_prop(cstr!("ro.config.per_app_memcg"), false) != "false" {
if get_prop(cstr!("ro.config.per_app_memcg")) != "false" {
switch_cgroup("/dev/memcg/apps", pid);
}
// Samsung workaround #7887
if cstr!("/system_ext/app/mediatek-res/mediatek-res.apk").exists() {
set_prop(cstr!("ro.vendor.mtk_model"), cstr!("0"), false);
set_prop(cstr!("ro.vendor.mtk_model"), cstr!("0"));
}
// Cleanup pre-init mounts
tmp_path.append_path(ROOTMNT);
if let Ok(mount_list) = tmp_path.open(O_RDONLY | O_CLOEXEC) {
BufReader::new(mount_list).foreach_lines(|line| {
if let Ok(mount_list) = tmp_path.open(OFlag::O_RDONLY | OFlag::O_CLOEXEC) {
BufReader::new(mount_list).for_each_line(|line| {
line.truncate(line.trim_end().len());
let item = Utf8CStr::from_string(line);
item.unmount().log_ok();
@@ -306,65 +379,111 @@ pub fn daemon_entry() {
// Remount rootfs as read-only if requested
if std::env::var_os("REMOUNT_ROOT").is_some() {
cstr!("/").remount_mount_flags(libc::MS_RDONLY).log_ok();
cstr!("/").remount_mount_flags(MsFlags::MS_RDONLY).log_ok();
unsafe { std::env::remove_var("REMOUNT_ROOT") };
}
// Remove all pre-init overlay files to free-up memory
tmp_path.append_path(ROOTOVL);
tmp_path.remove_all().log_ok();
tmp_path.remove_all().ok();
tmp_path.truncate(magisk_tmp.len());
let magiskd = MagiskD {
let exe_attr = cstr!("/proc/self/exe")
.follow_link()
.get_attr()
.log()
.unwrap_or_default();
let daemon = MagiskD {
sdk_int,
is_emulator,
is_recovery,
zygote_start_count: AtomicU32::new(1),
exe_attr,
..Default::default()
};
MAGISKD.set(magiskd).ok();
}
MAGISKD.set(daemon).ok();
fn switch_cgroup(cgroup: &str, pid: i32) {
let mut buf = cstr::buf::new::<64>()
.join_path(cgroup)
.join_path("cgroup.procs");
if !buf.exists() {
return;
}
if let Ok(mut file) = buf.open(O_WRONLY | O_APPEND | O_CLOEXEC) {
buf.clear();
buf.write_fmt(format_args!("{pid}")).ok();
file.write_all(buf.as_bytes()).log_ok();
}
}
let sock_path = cstr::buf::new::<64>()
.join_path(get_magisk_tmp())
.join_path(MAIN_SOCKET);
sock_path.remove().ok();
fn check_data() -> bool {
if let Ok(file) = cstr!("/proc/mounts").open(O_RDONLY | O_CLOEXEC) {
let mut mnt = false;
BufReader::new(file).foreach_lines(|line| {
if line.contains(" /data ") && !line.contains("tmpfs") {
mnt = true;
return false;
}
true
});
if !mnt {
return false;
}
let crypto = get_prop(cstr!("ro.crypto.state"), false);
return if !crypto.is_empty() {
if crypto != "encrypted" {
// Unencrypted, we can directly access data
true
} else {
// Encrypted, check whether vold is started
!get_prop(cstr!("init.svc.vold"), false).is_empty()
}
let Ok(sock) = UnixListener::bind(&sock_path).log() else {
exit(1);
};
sock_path.follow_link().chmod(0o666).log_ok();
sock_path.set_secontext(cstr!(MAGISK_FILE_CON)).log_ok();
// Loop forever to listen for requests
let daemon = MagiskD::get();
for client in sock.incoming() {
if let Ok(client) = client.log() {
daemon.handle_requests(client);
} else {
// ro.crypto.state is not set, assume it's unencrypted
true
};
exit(1);
}
}
false
}
pub fn connect_daemon(code: RequestCode, create: bool) -> LoggedResult<UnixStream> {
let sock_path = cstr::buf::new::<64>()
.join_path(get_magisk_tmp())
.join_path(MAIN_SOCKET);
fn send_request(code: RequestCode, mut socket: UnixStream) -> LoggedResult<UnixStream> {
socket.write_pod(&code.repr).log_ok();
let mut res = -1;
socket.read_pod(&mut res).log_ok();
let res = RespondCode { repr: res };
match res {
RespondCode::OK => Ok(socket),
RespondCode::ROOT_REQUIRED => {
log_err!("Root is required for this operation")
}
RespondCode::ACCESS_DENIED => {
log_err!("Accessed denied")
}
_ => {
log_err!("Daemon error")
}
}
}
match UnixStream::connect(&sock_path) {
Ok(socket) => send_request(code, socket),
Err(e) => {
if !create || !getuid().is_root() {
return log_err!("Cannot connect to daemon: {e}");
}
let mut buf = cstr::buf::new::<64>();
if cstr!("/proc/self/exe").read_link(&mut buf).is_err()
|| !buf.starts_with(get_magisk_tmp().as_str())
{
return log_err!("Start daemon on magisk tmpfs");
}
// Fork a process and run the daemon
if fork_dont_care() == 0 {
daemon_entry();
exit(0);
}
// In the client, we keep retry and connect to the socket
loop {
if let Ok(socket) = UnixStream::connect(&sock_path) {
return send_request(code, socket);
} else {
std::thread::sleep(Duration::from_millis(100));
}
}
}
}
}
pub fn connect_daemon_for_cxx(code: RequestCode, create: bool) -> RawFd {
connect_daemon(code, create)
.map(IntoRawFd::into_raw_fd)
.unwrap_or(-1)
}

View File

@@ -9,9 +9,8 @@ use base::{LoggedResult, ResultExt, Utf8CStr};
use num_derive::FromPrimitive;
use num_traits::FromPrimitive;
use std::ffi::c_void;
use std::fs::File;
use std::io::{BufReader, BufWriter};
use std::os::fd::{FromRawFd, OwnedFd, RawFd};
use std::os::unix::net::UnixStream;
use std::pin::Pin;
use std::ptr;
use std::ptr::NonNull;
@@ -302,8 +301,7 @@ impl MagiskD {
.sql_result()
}
fn db_exec_for_client(&self, fd: OwnedFd) -> LoggedResult<()> {
let mut file = File::from(fd);
pub fn db_exec_for_cli(&self, mut file: UnixStream) -> LoggedResult<()> {
let mut reader = BufReader::new(&mut file);
let sql: String = reader.read_decodable()?;
let mut writer = BufWriter::new(&mut file);
@@ -328,12 +326,6 @@ impl MagiskD {
pub fn set_db_setting_for_cxx(&self, key: DbEntryKey, value: i32) -> bool {
self.set_db_setting(key, value).log().is_ok()
}
pub fn db_exec_for_cxx(&self, client_fd: RawFd) {
// Take ownership
let fd = unsafe { OwnedFd::from_raw_fd(client_fd) };
self.db_exec_for_client(fd).ok();
}
}
#[unsafe(export_name = "sql_exec_rs")]

View File

@@ -26,7 +26,7 @@ Actions:
exit(1);
}
void denylist_handler(int client, const sock_cred *cred) {
void denylist_handler(int client) {
if (client < 0) {
revert_unmount();
return;
@@ -62,39 +62,46 @@ void denylist_handler(int client, const sock_cred *cred) {
close(client);
}
int denylist_cli(int argc, char **argv) {
if (argc < 2)
int denylist_cli(rust::Vec<rust::String> &args) {
if (args.empty())
usage();
// Convert rust strings into c strings
size_t argc = args.size();
std::vector<const char *> argv;
ranges::transform(args, std::back_inserter(argv), [](rust::String &arg) { return arg.c_str(); });
// End with nullptr
argv.push_back(nullptr);
int req;
if (argv[1] == "enable"sv)
if (argv[0] == "enable"sv)
req = DenyRequest::ENFORCE;
else if (argv[1] == "disable"sv)
else if (argv[0] == "disable"sv)
req = DenyRequest::DISABLE;
else if (argv[1] == "add"sv)
else if (argv[0] == "add"sv)
req = DenyRequest::ADD;
else if (argv[1] == "rm"sv)
else if (argv[0] == "rm"sv)
req = DenyRequest::REMOVE;
else if (argv[1] == "ls"sv)
else if (argv[0] == "ls"sv)
req = DenyRequest::LIST;
else if (argv[1] == "status"sv)
else if (argv[0] == "status"sv)
req = DenyRequest::STATUS;
else if (argv[1] == "exec"sv && argc > 2) {
else if (argv[0] == "exec"sv && argc > 1) {
xunshare(CLONE_NEWNS);
xmount(nullptr, "/", nullptr, MS_PRIVATE | MS_REC, nullptr);
revert_unmount();
execvp(argv[2], argv + 2);
execvp(argv[1], (char **) argv.data() + 1);
exit(1);
} else {
usage();
}
// Send request
int fd = connect_daemon(+RequestCode::DENYLIST);
int fd = connect_daemon(RequestCode::DENYLIST);
write_int(fd, req);
if (req == DenyRequest::ADD || req == DenyRequest::REMOVE) {
write_string(fd, argv[2]);
write_string(fd, argv[3] ? argv[3] : "");
write_string(fd, argv[1]);
write_string(fd, argv[2] ? argv[2] : "");
}
// Get response

View File

@@ -2,6 +2,7 @@
#include <android/log.h>
#include <sys/syscall.h>
#include <string>
#include <map>
#include <core.hpp>

View File

@@ -5,6 +5,7 @@
#include <fcntl.h>
#include <dirent.h>
#include <set>
#include <map>
#include <consts.hpp>
#include <sqlite.hpp>
@@ -90,9 +91,10 @@ static void crawl_procfs(const F &fn) {
}
}
static inline bool str_eql(string_view a, string_view b) { return a == b; }
static bool str_eql(string_view a, string_view b) { return a == b; }
static bool str_starts_with(string_view a, string_view b) { return a.starts_with(b); }
template<bool str_op(string_view, string_view) = &str_eql>
template<bool str_op(string_view, string_view) = str_eql>
static bool proc_name_match(int pid, string_view name) {
char buf[4019];
sprintf(buf, "/proc/%d/cmdline", pid);
@@ -111,7 +113,7 @@ bool proc_context_match(int pid, string_view context) {
sprintf(buf, "/proc/%d", pid);
if (lgetfilecon(buf, byte_data{ con, sizeof(con) })) {
return str_starts(con, context);
return string_view(con).starts_with(context);
}
return false;
}
@@ -173,7 +175,7 @@ static bool add_hide_set(const char *pkg, const char *proc) {
return true;
if (str_eql(pkg, ISOLATED_MAGIC)) {
// Kill all matching isolated processes
kill_process<&proc_name_match<str_starts>>(proc, true);
kill_process<&proc_name_match<str_starts_with>>(proc, true);
} else {
kill_process(proc);
}
@@ -411,7 +413,7 @@ bool is_deny_target(int uid, string_view process) {
if (app_id >= 90000) {
if (auto it = pkg_to_procs.find(ISOLATED_MAGIC); it != pkg_to_procs.end()) {
for (const auto &s : it->second) {
if (str_starts(process, s))
if (process.starts_with(s))
return true;
}
}

View File

@@ -1,8 +0,0 @@
use proc_macro::TokenStream;
mod decodable;
#[proc_macro_derive(Decodable)]
pub fn derive_decodable(input: TokenStream) -> TokenStream {
decodable::derive_decodable(input)
}

View File

@@ -1,7 +1,6 @@
#pragma once
#include <sys/socket.h>
#include <poll.h>
#include <string>
#include <atomic>
#include <functional>
@@ -17,34 +16,23 @@
#define to_app_id(uid) (uid % AID_USER_OFFSET)
#define to_user_id(uid) (uid / AID_USER_OFFSET)
// Return codes for daemon
enum class RespondCode : int {
ERROR = -1,
OK = 0,
ROOT_REQUIRED,
ACCESS_DENIED,
END
};
#define SDK_INT (MagiskD::Get().sdk_int())
#define APP_DATA_DIR (SDK_INT >= 24 ? "/data/user_de" : "/data/user")
inline int connect_daemon(RequestCode req) {
return connect_daemon(req, false);
}
// Multi-call entrypoints
int su_client_main(int argc, char *argv[]);
int zygisk_main(int argc, char *argv[]);
struct ModuleInfo;
// Daemon
int connect_daemon(int req, bool create = false);
// Utils
const char *get_magisk_tmp();
void unlock_blocks();
bool setup_magisk_env();
bool check_key_combo();
// Zygisk daemon
rust::Str get_zygisk_lib_name();
void set_zygisk_prop();
void restore_zygisk_prop();
// Sockets
struct sock_cred : public ucred {
std::string context;
};
template<typename T> requires(std::is_trivially_copyable_v<T>)
T read_any(int fd) {
T val;
@@ -52,26 +40,21 @@ T read_any(int fd) {
return -1;
return val;
}
template<typename T> requires(std::is_trivially_copyable_v<T>)
void write_any(int fd, T val) {
if (fd < 0) return;
xwrite(fd, &val, sizeof(val));
}
bool get_client_cred(int fd, sock_cred *cred);
static inline int read_int(int fd) { return read_any<int>(fd); }
static inline void write_int(int fd, int val) { write_any(fd, val); }
inline int read_int(int fd) { return read_any<int>(fd); }
inline void write_int(int fd, int val) { write_any(fd, val); }
std::string read_string(int fd);
bool read_string(int fd, std::string &str);
void write_string(int fd, std::string_view str);
template<typename T> requires(std::is_trivially_copyable_v<T>)
void write_vector(int fd, const std::vector<T> &vec) {
write_int(fd, vec.size());
xwrite(fd, vec.data(), vec.size() * sizeof(T));
}
template<typename T> requires(std::is_trivially_copyable_v<T>)
bool read_vector(int fd, std::vector<T> &vec) {
int size = read_int(fd);
@@ -79,31 +62,19 @@ bool read_vector(int fd, std::vector<T> &vec) {
return xread(fd, vec.data(), size * sizeof(T)) == size * sizeof(T);
}
// Poll control
using poll_callback = void(*)(pollfd*);
void register_poll(const pollfd *pfd, poll_callback callback);
void unregister_poll(int fd, bool auto_close);
void clear_poll();
// Thread pool
void init_thread_pool();
void exec_task(std::function<void()> &&task);
// Daemon handlers
void denylist_handler(int client, const sock_cred *cred);
// Scripting
void install_apk(rust::Utf8CStr apk);
void uninstall_pkg(rust::Utf8CStr pkg);
void exec_common_scripts(rust::Utf8CStr stage);
void exec_module_scripts(rust::Utf8CStr stage, const rust::Vec<ModuleInfo> &module_list);
void exec_script(const char *script);
void install_apk(Utf8CStr apk);
void uninstall_pkg(Utf8CStr pkg);
void exec_common_scripts(Utf8CStr stage);
void exec_module_scripts(Utf8CStr stage, const rust::Vec<ModuleInfo> &module_list);
void exec_script(Utf8CStr script);
void clear_pkg(const char *pkg, int user_id);
[[noreturn]] void install_module(const char *file);
[[noreturn]] void install_module(Utf8CStr file);
// Denylist
extern std::atomic<bool> denylist_enforced;
int denylist_cli(int argc, char **argv);
int denylist_cli(rust::Vec<rust::String> &args);
void denylist_handler(int client);
void initialize_denylist();
void scan_deny_apps();
bool is_deny_target(int uid, std::string_view process);
@@ -112,13 +83,9 @@ void update_deny_flags(int uid, rust::Str process, uint32_t &flags);
// MagiskSU
void exec_root_shell(int client, int pid, SuRequest &req, MntNsMode mode);
void app_log(const SuAppRequest &info, SuPolicy policy, bool notify);
void app_notify(const SuAppRequest &info, SuPolicy policy);
int app_request(const SuAppRequest &info);
// Rust bindings
static inline rust::Utf8CStr get_magisk_tmp_rs() { return get_magisk_tmp(); }
static inline rust::String resolve_preinit_dir_rs(rust::Utf8CStr base_dir) {
inline Utf8CStr get_magisk_tmp_rs() { return get_magisk_tmp(); }
inline rust::String resolve_preinit_dir_rs(Utf8CStr base_dir) {
return resolve_preinit_dir(base_dir.c_str());
}
static inline void exec_script_rs(rust::Utf8CStr script) { exec_script(script.data()); }

View File

@@ -1,38 +0,0 @@
#pragma once
#include <string>
#include <map>
#include <cxx.h>
struct prop_cb {
virtual void exec(const char *name, const char *value, uint32_t serial) = 0;
};
using prop_list = std::map<std::string, std::string>;
struct prop_collector : prop_cb {
explicit prop_collector(prop_list &list) : list(list) {}
void exec(const char *name, const char *value, uint32_t) override {
list.insert({name, value});
}
private:
prop_list &list;
};
// System properties
std::string get_prop(const char *name, bool persist = false);
int delete_prop(const char *name, bool persist = false);
int set_prop(const char *name, const char *value, bool skip_svc = false);
void load_prop_file(const char *filename, bool skip_svc = false);
// Rust bindings
rust::String get_prop_rs(rust::Utf8CStr name, bool persist);
static inline int set_prop_rs(rust::Utf8CStr name, rust::Utf8CStr value, bool skip_svc) {
return set_prop(name.data(), value.data(), skip_svc);
}
static inline void load_prop_file_rs(rust::Utf8CStr filename, bool skip_svc) {
load_prop_file(filename.data(), skip_svc);
}
static inline void prop_cb_exec(prop_cb &cb, rust::Utf8CStr name, rust::Utf8CStr value, uint32_t serial) {
cb.exec(name.data(), value.data(), serial);
}

View File

@@ -2,7 +2,7 @@
#include <functional>
#include <cxx.h>
#include <rust/cxx.h>
#define SQLITE_OPEN_READWRITE 0x00000002 /* Ok for sqlite3_open_v2() */
#define SQLITE_OPEN_CREATE 0x00000004 /* Ok for sqlite3_open_v2() */

View File

@@ -1,34 +1,35 @@
#![feature(try_blocks)]
#![feature(let_chains)]
#![feature(fn_traits)]
#![feature(unix_socket_ancillary_data)]
#![feature(unix_socket_peek)]
#![feature(default_field_values)]
#![feature(peer_credentials_unix_socket)]
#![allow(clippy::missing_safety_doc)]
use crate::ffi::SuRequest;
use crate::socket::Encodable;
use base::libc;
use cxx::{ExternType, type_id};
use daemon::{MagiskD, daemon_entry};
use derive::Decodable;
use logging::{android_logging, setup_logfile, zygisk_close_logd, zygisk_get_logd, zygisk_logging};
use module::remove_modules;
use mount::{find_preinit_device, revert_unmount};
use resetprop::{persist_delete_prop, persist_get_prop, persist_get_props, persist_set_prop};
use selinux::{lgetfilecon, lsetfilecon, restorecon, setfilecon};
use socket::{recv_fd, recv_fds, send_fd, send_fds};
use base::derive::Decodable;
use daemon::{MagiskD, connect_daemon_for_cxx};
use logging::{android_logging, zygisk_close_logd, zygisk_get_logd, zygisk_logging};
use magisk::magisk_main;
use mount::revert_unmount;
use resetprop::{get_prop, resetprop_main};
use selinux::{lgetfilecon, setfilecon};
use socket::{recv_fd, recv_fds, send_fd};
use std::fs::File;
use std::mem::ManuallyDrop;
use std::ops::DerefMut;
use std::os::fd::FromRawFd;
use su::{get_pty_num, pump_tty, restore_stdin};
use su::{get_pty_num, pump_tty};
use zygisk::zygisk_should_load_module;
mod bootstages;
#[path = "../include/consts.rs"]
mod consts;
mod daemon;
mod db;
mod logging;
mod magisk;
mod module;
mod mount;
mod package;
@@ -36,6 +37,7 @@ mod resetprop;
mod selinux;
mod socket;
mod su;
mod thread;
mod zygisk;
#[allow(clippy::needless_lifetimes)]
@@ -66,6 +68,15 @@ pub mod ffi {
END,
}
#[repr(i32)]
enum RespondCode {
ERROR = -1,
OK = 0,
ROOT_REQUIRED,
ACCESS_DENIED,
END,
}
enum DbEntryKey {
RootAccess,
SuMultiuserMode,
@@ -125,21 +136,9 @@ pub mod ffi {
gids: Vec<u32>,
}
struct SuAppRequest<'a> {
uid: i32,
pid: i32,
eval_uid: i32,
mgr_pkg: &'a str,
mgr_uid: i32,
request: &'a SuRequest,
}
unsafe extern "C++" {
#[namespace = "rust"]
#[cxx_name = "Utf8CStr"]
type Utf8CStrRef<'a> = base::ffi::Utf8CStrRef<'a>;
#[cxx_name = "ucred"]
type UCred = crate::UCred;
type Utf8CStrRef<'a> = base::Utf8CStrRef<'a>;
include!("include/core.hpp");
@@ -147,24 +146,25 @@ pub mod ffi {
fn get_magisk_tmp() -> Utf8CStrRef<'static>;
#[cxx_name = "resolve_preinit_dir_rs"]
fn resolve_preinit_dir(base_dir: Utf8CStrRef) -> String;
fn setup_magisk_env() -> bool;
fn check_key_combo() -> bool;
#[cxx_name = "exec_script_rs"]
fn unlock_blocks();
fn update_deny_flags(uid: i32, process: &str, flags: &mut u32);
fn initialize_denylist();
fn switch_mnt_ns(pid: i32) -> i32;
fn exec_root_shell(client: i32, pid: i32, req: &mut SuRequest, mode: MntNsMode);
// Scripting
fn exec_script(script: Utf8CStrRef);
fn exec_common_scripts(stage: Utf8CStrRef);
fn exec_module_scripts(state: Utf8CStrRef, modules: &Vec<ModuleInfo>);
fn install_apk(apk: Utf8CStrRef);
fn uninstall_pkg(apk: Utf8CStrRef);
fn update_deny_flags(uid: i32, process: &str, flags: &mut u32);
fn initialize_denylist();
fn get_zygisk_lib_name() -> &'static str;
fn set_zygisk_prop();
fn restore_zygisk_prop();
fn switch_mnt_ns(pid: i32) -> i32;
fn app_request(req: &SuAppRequest) -> i32;
fn app_notify(req: &SuAppRequest, policy: SuPolicy);
fn app_log(req: &SuAppRequest, policy: SuPolicy, notify: bool);
fn exec_root_shell(client: i32, pid: i32, req: &mut SuRequest, mode: MntNsMode);
fn install_module(zip: Utf8CStrRef);
// Denylist
fn denylist_cli(args: &mut Vec<String>) -> i32;
fn denylist_handler(client: i32);
fn scan_deny_apps();
include!("include/sqlite.hpp");
@@ -179,18 +179,6 @@ pub mod ffi {
fn get_text(self: &DbValues, index: i32) -> &str;
fn bind_text(self: Pin<&mut DbStatement>, index: i32, val: &str) -> i32;
fn bind_int64(self: Pin<&mut DbStatement>, index: i32, val: i64) -> i32;
include!("include/resetprop.hpp");
#[cxx_name = "prop_cb"]
type PropCb;
#[cxx_name = "get_prop_rs"]
fn get_prop(name: Utf8CStrRef, persist: bool) -> String;
#[cxx_name = "set_prop_rs"]
fn set_prop(name: Utf8CStrRef, value: Utf8CStrRef, skip_svc: bool) -> i32;
#[cxx_name = "load_prop_file_rs"]
fn load_prop_file(filename: Utf8CStrRef, skip_svc: bool);
fn prop_cb_exec(cb: Pin<&mut PropCb>, name: Utf8CStrRef, value: Utf8CStrRef, serial: u32);
}
extern "Rust" {
@@ -198,30 +186,23 @@ pub mod ffi {
fn zygisk_logging();
fn zygisk_close_logd();
fn zygisk_get_logd() -> i32;
fn setup_logfile();
fn find_preinit_device() -> String;
fn revert_unmount(pid: i32);
fn remove_modules();
fn zygisk_should_load_module(flags: u32) -> bool;
unsafe fn persist_get_prop(name: Utf8CStrRef, prop_cb: Pin<&mut PropCb>);
unsafe fn persist_get_props(prop_cb: Pin<&mut PropCb>);
unsafe fn persist_delete_prop(name: Utf8CStrRef) -> bool;
unsafe fn persist_set_prop(name: Utf8CStrRef, value: Utf8CStrRef) -> bool;
fn send_fd(socket: i32, fd: i32) -> bool;
fn send_fds(socket: i32, fds: &[i32]) -> bool;
fn recv_fd(socket: i32) -> i32;
fn recv_fds(socket: i32) -> Vec<i32>;
unsafe fn write_to_fd(self: &SuRequest, fd: i32);
fn pump_tty(infd: i32, outfd: i32);
fn write_to_fd(self: &SuRequest, fd: i32);
fn pump_tty(ptmx: i32, pump_stdin: bool);
fn get_pty_num(fd: i32) -> i32;
fn restore_stdin() -> bool;
fn restorecon();
fn lgetfilecon(path: Utf8CStrRef, con: &mut [u8]) -> bool;
fn setfilecon(path: Utf8CStrRef, con: Utf8CStrRef) -> bool;
fn lsetfilecon(path: Utf8CStrRef, con: Utf8CStrRef) -> bool;
#[namespace = "rust"]
fn daemon_entry();
fn get_prop(name: Utf8CStrRef) -> String;
unsafe fn resetprop_main(argc: i32, argv: *mut *mut c_char) -> i32;
#[cxx_name = "connect_daemon"]
fn connect_daemon_for_cxx(code: RequestCode, create: bool) -> i32;
unsafe fn magisk_main(argc: i32, argv: *mut *mut c_char) -> i32;
}
// Default constructors
@@ -234,22 +215,11 @@ pub mod ffi {
// FFI for MagiskD
extern "Rust" {
type MagiskD;
fn reboot(&self);
fn sdk_int(&self) -> i32;
fn zygisk_enabled(&self) -> bool;
fn boot_stage_handler(&self, client: i32, code: i32);
fn zygisk_handler(&self, client: i32);
fn zygisk_reset(&self, restore: bool);
fn prune_su_access(&self);
fn su_daemon_handler(&self, client: i32, cred: &UCred);
#[cxx_name = "get_manager"]
unsafe fn get_manager_for_cxx(&self, user: i32, ptr: *mut CxxString, install: bool) -> i32;
fn get_db_setting(&self, key: DbEntryKey) -> i32;
#[cxx_name = "set_db_setting"]
fn set_db_setting_for_cxx(&self, key: DbEntryKey, value: i32) -> bool;
#[cxx_name = "db_exec"]
fn db_exec_for_cxx(&self, client_fd: i32);
#[Self = MagiskD]
#[cxx_name = "Get"]
@@ -257,16 +227,8 @@ pub mod ffi {
}
}
#[repr(transparent)]
pub struct UCred(pub libc::ucred);
unsafe impl ExternType for UCred {
type Id = type_id!("ucred");
type Kind = cxx::kind::Trivial;
}
impl SuRequest {
unsafe fn write_to_fd(&self, fd: i32) {
fn write_to_fd(&self, fd: i32) {
unsafe {
let mut w = ManuallyDrop::new(File::from_raw_fd(fd));
self.encode(w.deref_mut()).ok();

View File

@@ -1,24 +1,24 @@
use crate::consts::{LOG_PIPE, LOGFILE};
use crate::ffi::get_magisk_tmp;
use crate::logging::LogFile::{Actual, Buffer};
use base::libc::{
O_CLOEXEC, O_RDWR, O_WRONLY, PIPE_BUF, SIG_BLOCK, SIG_SETMASK, SIGPIPE, getpid, gettid,
localtime_r, pthread_sigmask, sigaddset, sigset_t, sigtimedwait, time_t, timespec, tm,
};
use base::const_format::concatcp;
use base::{
FsPathBuilder, LOGGER, LogLevel, Logger, ReadExt, Utf8CStr, Utf8CStrBuf, WriteExt,
const_format::concatcp, cstr, libc, raw_cstr,
FsPathBuilder, LogLevel, LoggedResult, ReadExt, ResultExt, Utf8CStr, Utf8CStrBuf, WriteExt,
cstr, libc, new_daemon_thread, raw_cstr, update_logger,
};
use bytemuck::{Pod, Zeroable, bytes_of, write_zeroes};
use libc::{PIPE_BUF, c_char, localtime_r, sigtimedwait, time_t, timespec, tm};
use nix::fcntl::OFlag;
use nix::sys::signal::{SigSet, SigmaskHow, Signal};
use nix::unistd::{Gid, Uid, chown, getpid, gettid};
use num_derive::{FromPrimitive, ToPrimitive};
use num_traits::FromPrimitive;
use std::cmp::min;
use std::ffi::{c_char, c_void};
use std::fmt::Write as FmtWrite;
use std::fmt::Write as _;
use std::fs::File;
use std::io::{IoSlice, Read, Write};
use std::mem::ManuallyDrop;
use std::os::fd::{FromRawFd, RawFd};
use std::os::fd::{FromRawFd, IntoRawFd, RawFd};
use std::ptr::null_mut;
use std::sync::atomic::{AtomicI32, Ordering};
use std::sync::{Arc, Mutex};
@@ -40,17 +40,14 @@ enum ALogPriority {
ANDROID_LOG_SILENT,
}
type ThreadEntry = extern "C" fn(*mut c_void) -> *mut c_void;
unsafe extern "C" {
fn __android_log_write(prio: i32, tag: *const c_char, msg: *const c_char);
fn strftime(buf: *mut c_char, len: usize, fmt: *const c_char, tm: *const tm) -> usize;
fn new_daemon_thread(entry: ThreadEntry, arg: *mut c_void);
}
fn level_to_prio(level: LogLevel) -> i32 {
match level {
LogLevel::Error | LogLevel::ErrorCxx => ALogPriority::ANDROID_LOG_ERROR as i32,
LogLevel::Error => ALogPriority::ANDROID_LOG_ERROR as i32,
LogLevel::Warn => ALogPriority::ANDROID_LOG_WARN as i32,
LogLevel::Info => ALogPriority::ANDROID_LOG_INFO as i32,
LogLevel::Debug => ALogPriority::ANDROID_LOG_DEBUG as i32,
@@ -64,13 +61,7 @@ fn android_log_write(level: LogLevel, msg: &Utf8CStr) {
}
pub fn android_logging() {
let logger = Logger {
write: android_log_write,
flags: 0,
};
unsafe {
LOGGER = logger;
}
update_logger(|logger| logger.write = android_log_write);
}
pub fn magisk_logging() {
@@ -78,14 +69,7 @@ pub fn magisk_logging() {
android_log_write(level, msg);
magisk_log_to_pipe(level_to_prio(level), msg);
}
let logger = Logger {
write: magisk_log_write,
flags: 0,
};
unsafe {
LOGGER = logger;
}
update_logger(|logger| logger.write = magisk_log_write);
}
pub fn zygisk_logging() {
@@ -93,14 +77,7 @@ pub fn zygisk_logging() {
android_log_write(level, msg);
zygisk_log_to_pipe(level_to_prio(level), msg);
}
let logger = Logger {
write: zygisk_log_write,
flags: 0,
};
unsafe {
LOGGER = logger;
}
update_logger(|logger| logger.write = zygisk_log_write);
}
#[derive(Copy, Clone, Pod, Zeroable)]
@@ -122,17 +99,16 @@ fn write_log_to_pipe(mut logd: &File, prio: i32, msg: &Utf8CStr) -> io::Result<u
let meta = LogMeta {
prio,
len: len as i32,
pid: unsafe { getpid() },
tid: unsafe { gettid() },
pid: getpid().as_raw(),
tid: gettid().as_raw(),
};
let io1 = IoSlice::new(bytes_of(&meta));
let io2 = IoSlice::new(msg);
let result = logd.write_vectored(&[io1, io2]);
if let Err(ref e) = result {
let mut buf = cstr::buf::default();
buf.write_fmt(format_args!("Cannot write_log_to_pipe: {e}"))
.ok();
let mut buf = cstr::buf::new::<256>();
write!(buf, "Cannot write_log_to_pipe: {e}").ok();
android_log_write(LogLevel::Error, &buf);
}
result
@@ -177,25 +153,25 @@ pub fn zygisk_get_logd() -> i32 {
// to make zygote NOT crash if necessary. We accomplish this by hooking __android_log_close
// and closing it at the same time as the rest of logging FDs.
let mut fd = ZYGISK_LOGD.load(Ordering::Relaxed);
if fd < 0 {
let mut raw_fd = ZYGISK_LOGD.load(Ordering::Relaxed);
if raw_fd < 0 {
android_logging();
let path = cstr::buf::default()
.join_path(get_magisk_tmp())
.join_path(LOG_PIPE);
// Open as RW as sometimes it may block
fd = unsafe { libc::open(path.as_ptr(), O_RDWR | O_CLOEXEC) };
if fd >= 0 {
if let Ok(fd) = path.open(OFlag::O_RDWR | OFlag::O_CLOEXEC) {
// Only re-enable zygisk logging if success
zygisk_logging();
raw_fd = fd.into_raw_fd();
unsafe {
libc::close(ZYGISK_LOGD.swap(fd, Ordering::Relaxed));
libc::close(ZYGISK_LOGD.swap(raw_fd, Ordering::Relaxed));
}
} else {
return -1;
}
}
fd
raw_fd
}
fn zygisk_log_to_pipe(prio: i32, msg: &Utf8CStr) {
@@ -206,23 +182,22 @@ fn zygisk_log_to_pipe(prio: i32, msg: &Utf8CStr) {
}
// Block SIGPIPE
let mut mask: sigset_t;
let mut orig_mask: sigset_t;
unsafe {
mask = std::mem::zeroed();
orig_mask = std::mem::zeroed();
sigaddset(&mut mask, SIGPIPE);
pthread_sigmask(SIG_BLOCK, &mask, &mut orig_mask);
}
let mut mask = SigSet::empty();
mask.add(Signal::SIGPIPE);
let orig_mask = mask.thread_swap_mask(SigmaskHow::SIG_SETMASK);
let logd = ManuallyDrop::new(unsafe { File::from_raw_fd(fd) });
let result = write_log_to_pipe(&logd, prio, msg);
// Consume SIGPIPE if exists, then restore mask
unsafe {
let ts: timespec = std::mem::zeroed();
sigtimedwait(&mask, null_mut(), &ts);
pthread_sigmask(SIG_SETMASK, &orig_mask, null_mut());
if let Ok(orig_mask) = orig_mask {
unsafe {
// Unfortunately nix does not have an abstraction over sigtimedwait.
// Fallback to use raw libc function calls.
let ts: timespec = std::mem::zeroed();
sigtimedwait(mask.as_ref(), null_mut(), &ts);
}
orig_mask.thread_set_mask().ok();
}
// If any error occurs, shut down the logd pipe
@@ -238,110 +213,92 @@ enum LogFile {
Actual(File),
}
impl Write for LogFile {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
impl LogFile {
fn as_write(&mut self) -> &mut dyn Write {
match self {
Buffer(e) => e.write(buf),
Actual(e) => e.write(buf),
}
}
fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
match self {
Buffer(e) => e.write_vectored(bufs),
Actual(e) => e.write_vectored(bufs),
}
}
fn flush(&mut self) -> io::Result<()> {
match self {
Buffer(e) => e.flush(),
Actual(e) => e.flush(),
Buffer(e) => e,
Actual(e) => e,
}
}
}
extern "C" fn logfile_writer(arg: *mut c_void) -> *mut c_void {
fn writer_loop(pipefd: RawFd) -> io::Result<()> {
let mut pipe = unsafe { File::from_raw_fd(pipefd) };
let mut logfile: LogFile = Buffer(Vec::new());
fn logfile_write_loop(mut pipe: File) -> io::Result<()> {
let mut logfile: LogFile = Buffer(Vec::new());
let mut meta = LogMeta::zeroed();
let mut msg_buf = [0u8; MAX_MSG_LEN];
let mut aux = cstr::buf::new::<64>();
let mut meta = LogMeta::zeroed();
let mut msg_buf = [0u8; MAX_MSG_LEN];
let mut aux = cstr::buf::new::<64>();
loop {
// Read request
write_zeroes(&mut meta);
pipe.read_pod(&mut meta)?;
loop {
// Read request
write_zeroes(&mut meta);
pipe.read_pod(&mut meta)?;
if meta.prio < 0 {
if let Buffer(ref mut buf) = logfile {
fs::rename(LOGFILE, concatcp!(LOGFILE, ".bak")).ok();
let mut out = File::create(LOGFILE)?;
out.write_all(buf.as_slice())?;
logfile = Actual(out);
}
continue;
if meta.prio < 0 {
if let Buffer(ref mut buf) = logfile {
fs::rename(LOGFILE, concatcp!(LOGFILE, ".bak")).ok();
let mut out = File::create(LOGFILE)?;
out.write_all(buf.as_slice())?;
logfile = Actual(out);
}
if meta.len < 0 || meta.len > MAX_MSG_LEN as i32 {
continue;
}
// Read the rest of the message
let msg = &mut msg_buf[..(meta.len as usize)];
pipe.read_exact(msg)?;
// Start building the log string
aux.clear();
let prio =
ALogPriority::from_i32(meta.prio).unwrap_or(ALogPriority::ANDROID_LOG_UNKNOWN);
let prio = match prio {
ALogPriority::ANDROID_LOG_VERBOSE => 'V',
ALogPriority::ANDROID_LOG_DEBUG => 'D',
ALogPriority::ANDROID_LOG_INFO => 'I',
ALogPriority::ANDROID_LOG_WARN => 'W',
ALogPriority::ANDROID_LOG_ERROR => 'E',
// Unsupported values, skip
_ => continue,
};
let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
// Note: the obvious better implementation is to use the rust chrono crate, however
// the crate cannot fetch the proper local timezone without pulling in a bunch of
// timezone handling code. To reduce binary size, fallback to use localtime_r in libc.
unsafe {
let secs: time_t = now.as_secs() as time_t;
let mut tm: tm = std::mem::zeroed();
if localtime_r(&secs, &mut tm).is_null() {
continue;
}
let len = strftime(aux.as_mut_ptr(), aux.capacity(), raw_cstr!("%m-%d %T"), &tm);
aux.set_len(len);
aux.write_fmt(format_args!(
".{:03} {:5} {:5} {} : ",
now.subsec_millis(),
meta.pid,
meta.tid,
prio
))
.ok();
}
let io1 = IoSlice::new(aux.as_bytes());
let io2 = IoSlice::new(msg);
// We don't need to care the written len because we are writing less than PIPE_BUF
// It's guaranteed to always write the whole thing atomically
let _ = logfile.write_vectored(&[io1, io2])?;
continue;
}
}
writer_loop(arg as RawFd).ok();
// If any error occurs, shut down the logd pipe
*MAGISK_LOGD_FD.lock().unwrap() = None;
null_mut()
if meta.len < 0 || meta.len > MAX_MSG_LEN as i32 {
continue;
}
// Read the rest of the message
let msg = &mut msg_buf[..(meta.len as usize)];
pipe.read_exact(msg)?;
// Start building the log string
aux.clear();
let prio = ALogPriority::from_i32(meta.prio).unwrap_or(ALogPriority::ANDROID_LOG_UNKNOWN);
let prio = match prio {
ALogPriority::ANDROID_LOG_VERBOSE => 'V',
ALogPriority::ANDROID_LOG_DEBUG => 'D',
ALogPriority::ANDROID_LOG_INFO => 'I',
ALogPriority::ANDROID_LOG_WARN => 'W',
ALogPriority::ANDROID_LOG_ERROR => 'E',
// Unsupported values, skip
_ => continue,
};
let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
// Note: the obvious better implementation is to use the rust chrono crate, however
// the crate cannot fetch the proper local timezone without pulling in a bunch of
// timezone handling code. To reduce binary size, fallback to use localtime_r in libc.
unsafe {
let secs = now.as_secs() as time_t;
let mut tm: tm = std::mem::zeroed();
if localtime_r(&secs, &mut tm).is_null() {
continue;
}
strftime(aux.as_mut_ptr(), aux.capacity(), raw_cstr!("%m-%d %T"), &tm);
}
if aux.rebuild().is_ok() {
write!(
aux,
".{:03} {:5} {:5} {} : ",
now.subsec_millis(),
meta.pid,
meta.tid,
prio
)
.ok();
} else {
continue;
}
let io1 = IoSlice::new(aux.as_bytes());
let io2 = IoSlice::new(msg);
// We don't need to care the written len because we are writing less than PIPE_BUF
// It's guaranteed to always write the whole thing atomically
let _ = logfile.as_write().write_vectored(&[io1, io2])?;
}
}
pub fn setup_logfile() {
@@ -361,12 +318,22 @@ pub fn start_log_daemon() {
.join_path(get_magisk_tmp())
.join_path(LOG_PIPE);
unsafe {
libc::mkfifo(path.as_ptr(), 0o666);
libc::chown(path.as_ptr(), 0, 0);
let read = libc::open(path.as_ptr(), O_RDWR | O_CLOEXEC);
let write = libc::open(path.as_ptr(), O_WRONLY | O_CLOEXEC);
*MAGISK_LOGD_FD.lock().unwrap() = Some(Arc::new(File::from_raw_fd(write)));
new_daemon_thread(logfile_writer, read as *mut c_void);
extern "C" fn logfile_writer_thread(arg: usize) -> usize {
let file = unsafe { File::from_raw_fd(arg as RawFd) };
logfile_write_loop(file).ok();
// If any error occurs, shut down the logd pipe
*MAGISK_LOGD_FD.lock().unwrap() = None;
0
}
let _: LoggedResult<()> = try {
path.mkfifo(0o666).log_ok();
chown(path.as_utf8_cstr(), Some(Uid::from(0)), Some(Gid::from(0)))?;
let read = path.open(OFlag::O_RDWR | OFlag::O_CLOEXEC)?;
let write = path.open(OFlag::O_WRONLY | OFlag::O_CLOEXEC)?;
*MAGISK_LOGD_FD.lock().unwrap() = Some(Arc::new(write));
unsafe {
new_daemon_thread(logfile_writer_thread, read.into_raw_fd() as usize);
}
};
}

View File

@@ -1,156 +0,0 @@
#include <sys/mount.h>
#include <libgen.h>
#include <base.hpp>
#include <consts.hpp>
#include <core.hpp>
#include <flags.h>
using namespace std;
[[noreturn]] static void usage() {
fprintf(stderr,
R"EOF(Magisk - Multi-purpose Utility
Usage: magisk [applet [arguments]...]
or: magisk [options]...
Options:
-c print current binary version
-v print running daemon version
-V print running daemon version code
--list list all available applets
--remove-modules [-n] remove all modules, reboot if -n is not provided
--install-module ZIP install a module zip file
Advanced Options (Internal APIs):
--daemon manually start magisk daemon
--stop remove all magisk changes and stop daemon
--[init trigger] callback on init triggers. Valid triggers:
post-fs-data, service, boot-complete, zygote-restart
--unlock-blocks set BLKROSET flag to OFF for all block devices
--restorecon restore selinux context on Magisk files
--clone-attr SRC DEST clone permission, owner, and selinux context
--clone SRC DEST clone SRC to DEST
--sqlite SQL exec SQL commands to Magisk database
--path print Magisk tmpfs mount path
--denylist ARGS denylist config CLI
--preinit-device resolve a device to store preinit files
Available applets:
)EOF");
for (int i = 0; applet_names[i]; ++i)
fprintf(stderr, i ? ", %s" : " %s", applet_names[i]);
fprintf(stderr, "\n\n");
exit(1);
}
#define quote(s) #s
#define str(s) quote(s)
int magisk_main(int argc, char *argv[]) {
if (argc < 2)
usage();
if (argv[1] == "-c"sv) {
#if MAGISK_DEBUG
printf(MAGISK_VERSION ":MAGISK:D (" str(MAGISK_VER_CODE) ")\n");
#else
printf(MAGISK_VERSION ":MAGISK:R (" str(MAGISK_VER_CODE) ")\n");
#endif
return 0;
} else if (argv[1] == "-v"sv) {
int fd = connect_daemon(+RequestCode::CHECK_VERSION);
string v = read_string(fd);
printf("%s\n", v.data());
return 0;
} else if (argv[1] == "-V"sv) {
int fd = connect_daemon(+RequestCode::CHECK_VERSION_CODE);
printf("%d\n", read_int(fd));
return 0;
} else if (argv[1] == "--list"sv) {
for (int i = 0; applet_names[i]; ++i)
printf("%s\n", applet_names[i]);
return 0;
} else if (argv[1] == "--unlock-blocks"sv) {
unlock_blocks();
return 0;
} else if (argv[1] == "--restorecon"sv) {
restorecon();
return 0;
} else if (argc >= 4 && argv[1] == "--clone-attr"sv) {
clone_attr(argv[2], argv[3]);
return 0;
} else if (argc >= 4 && argv[1] == "--clone"sv) {
cp_afc(argv[2], argv[3]);
return 0;
} else if (argv[1] == "--daemon"sv) {
close(connect_daemon(+RequestCode::START_DAEMON, true));
return 0;
} else if (argv[1] == "--stop"sv) {
int fd = connect_daemon(+RequestCode::STOP_DAEMON);
return read_int(fd);
} else if (argv[1] == "--post-fs-data"sv) {
int fd = connect_daemon(+RequestCode::POST_FS_DATA, true);
struct pollfd pfd = { fd, POLLIN, 0 };
poll(&pfd, 1, 1000 * POST_FS_DATA_WAIT_TIME);
return 0;
} else if (argv[1] == "--service"sv) {
close(connect_daemon(+RequestCode::LATE_START, true));
return 0;
} else if (argv[1] == "--boot-complete"sv) {
close(connect_daemon(+RequestCode::BOOT_COMPLETE));
return 0;
} else if (argv[1] == "--zygote-restart"sv) {
close(connect_daemon(+RequestCode::ZYGOTE_RESTART));
return 0;
} else if (argv[1] == "--denylist"sv) {
return denylist_cli(argc - 1, argv + 1);
} else if (argc >= 3 && argv[1] == "--sqlite"sv) {
int fd = connect_daemon(+RequestCode::SQLITE_CMD);
write_string(fd, argv[2]);
string res;
for (;;) {
read_string(fd, res);
if (res.empty())
return 0;
printf("%s\n", res.data());
}
} else if (argv[1] == "--remove-modules"sv) {
int do_reboot;
if (argc == 3 && argv[2] == "-n"sv) {
do_reboot = 0;
} else if (argc == 2) {
do_reboot = 1;
} else {
usage();
}
int fd = connect_daemon(+RequestCode::REMOVE_MODULES);
write_int(fd, do_reboot);
return read_int(fd);
} else if (argv[1] == "--path"sv) {
const char *path = get_magisk_tmp();
if (path[0] != '\0') {
printf("%s\n", path);
return 0;
}
return 1;
} else if (argc >= 3 && argv[1] == "--install-module"sv) {
install_module(argv[2]);
} else if (argv[1] == "--preinit-device"sv) {
auto name = find_preinit_device();
if (!name.empty()) {
printf("%s\n", name.c_str());
return 0;
}
return 1;
}
#if 0
/* Entry point for testing stuffs */
else if (argv[1] == "--test"sv) {
rust_test_entry();
return 0;
}
#endif
usage();
}

298
native/src/core/magisk.rs Normal file
View File

@@ -0,0 +1,298 @@
use crate::consts::{APPLET_NAMES, MAGISK_VER_CODE, MAGISK_VERSION, POST_FS_DATA_WAIT_TIME};
use crate::daemon::connect_daemon;
use crate::ffi::{RequestCode, denylist_cli, get_magisk_tmp, install_module, unlock_blocks};
use crate::mount::find_preinit_device;
use crate::selinux::restorecon;
use crate::socket::{Decodable, Encodable};
use argh::FromArgs;
use base::{CmdArgs, EarlyExitExt, LoggedResult, Utf8CString, argh, clone_attr};
use nix::poll::{PollFd, PollFlags, PollTimeout};
use std::ffi::c_char;
use std::os::fd::AsFd;
use std::process::exit;
fn print_usage() {
eprintln!(
r#"Magisk - Multi-purpose Utility
Usage: magisk [applet [arguments]...]
or: magisk [options]...
Options:
-c print current binary version
-v print running daemon version
-V print running daemon version code
--list list all available applets
--remove-modules [-n] remove all modules, reboot if -n is not provided
--install-module ZIP install a module zip file
Advanced Options (Internal APIs):
--daemon manually start magisk daemon
--stop remove all magisk changes and stop daemon
--[init trigger] callback on init triggers. Valid triggers:
post-fs-data, service, boot-complete, zygote-restart
--unlock-blocks set BLKROSET flag to OFF for all block devices
--restorecon restore selinux context on Magisk files
--clone-attr SRC DEST clone permission, owner, and selinux context
--clone SRC DEST clone SRC to DEST
--sqlite SQL exec SQL commands to Magisk database
--path print Magisk tmpfs mount path
--denylist ARGS denylist config CLI
--preinit-device resolve a device to store preinit files
Available applets:
{}
"#,
APPLET_NAMES.join(", ")
);
}
#[derive(FromArgs)]
struct Cli {
#[argh(subcommand)]
action: MagiskAction,
}
#[derive(FromArgs)]
#[argh(subcommand)]
enum MagiskAction {
LocalVersion(LocalVersion),
Version(Version),
VersionCode(VersionCode),
List(ListApplets),
RemoveModules(RemoveModules),
InstallModule(InstallModule),
Daemon(StartDaemon),
Stop(StopDaemon),
PostFsData(PostFsData),
Service(ServiceCmd),
BootComplete(BootComplete),
ZygoteRestart(ZygoteRestart),
UnlockBlocks(UnlockBlocks),
RestoreCon(RestoreCon),
CloneAttr(CloneAttr),
CloneFile(CloneFile),
Sqlite(Sqlite),
Path(PathCmd),
DenyList(DenyList),
PreInitDevice(PreInitDevice),
}
#[derive(FromArgs)]
#[argh(subcommand, name = "-c")]
struct LocalVersion {}
#[derive(FromArgs)]
#[argh(subcommand, name = "-v")]
struct Version {}
#[derive(FromArgs)]
#[argh(subcommand, name = "-V")]
struct VersionCode {}
#[derive(FromArgs)]
#[argh(subcommand, name = "--list")]
struct ListApplets {}
#[derive(FromArgs)]
#[argh(subcommand, name = "--remove-modules")]
struct RemoveModules {
#[argh(switch, short = 'n')]
no_reboot: bool,
}
#[derive(FromArgs)]
#[argh(subcommand, name = "--install-module")]
struct InstallModule {
#[argh(positional)]
zip: Utf8CString,
}
#[derive(FromArgs)]
#[argh(subcommand, name = "--daemon")]
struct StartDaemon {}
#[derive(FromArgs)]
#[argh(subcommand, name = "--stop")]
struct StopDaemon {}
#[derive(FromArgs)]
#[argh(subcommand, name = "--post-fs-data")]
struct PostFsData {}
#[derive(FromArgs)]
#[argh(subcommand, name = "--service")]
struct ServiceCmd {}
#[derive(FromArgs)]
#[argh(subcommand, name = "--boot-complete")]
struct BootComplete {}
#[derive(FromArgs)]
#[argh(subcommand, name = "--zygote-restart")]
struct ZygoteRestart {}
#[derive(FromArgs)]
#[argh(subcommand, name = "--unlock-blocks")]
struct UnlockBlocks {}
#[derive(FromArgs)]
#[argh(subcommand, name = "--restorecon")]
struct RestoreCon {}
#[derive(FromArgs)]
#[argh(subcommand, name = "--clone-attr")]
struct CloneAttr {
#[argh(positional)]
from: Utf8CString,
#[argh(positional)]
to: Utf8CString,
}
#[derive(FromArgs)]
#[argh(subcommand, name = "--clone")]
struct CloneFile {
#[argh(positional)]
from: Utf8CString,
#[argh(positional)]
to: Utf8CString,
}
#[derive(FromArgs)]
#[argh(subcommand, name = "--sqlite")]
struct Sqlite {
#[argh(positional)]
sql: String,
}
#[derive(FromArgs)]
#[argh(subcommand, name = "--path")]
struct PathCmd {}
#[derive(FromArgs)]
#[argh(subcommand, name = "--denylist")]
struct DenyList {
#[argh(positional, greedy)]
args: Vec<String>,
}
#[derive(FromArgs)]
#[argh(subcommand, name = "--preinit-device")]
struct PreInitDevice {}
impl MagiskAction {
fn exec(self) -> LoggedResult<i32> {
use MagiskAction::*;
match self {
LocalVersion(_) => {
#[cfg(debug_assertions)]
println!("{MAGISK_VERSION}:MAGISK:D ({MAGISK_VER_CODE})");
#[cfg(not(debug_assertions))]
println!("{MAGISK_VERSION}:MAGISK:R ({MAGISK_VER_CODE})");
}
Version(_) => {
let mut fd = connect_daemon(RequestCode::CHECK_VERSION, false)?;
let ver = String::decode(&mut fd)?;
println!("{ver}");
}
VersionCode(_) => {
let mut fd = connect_daemon(RequestCode::CHECK_VERSION_CODE, false)?;
let ver = i32::decode(&mut fd)?;
println!("{ver}");
}
List(_) => {
for name in APPLET_NAMES {
println!("{name}");
}
}
RemoveModules(self::RemoveModules { no_reboot }) => {
let mut fd = connect_daemon(RequestCode::REMOVE_MODULES, false)?;
let do_reboot = !no_reboot;
do_reboot.encode(&mut fd)?;
return Ok(i32::decode(&mut fd)?);
}
InstallModule(self::InstallModule { zip }) => {
install_module(&zip);
}
Daemon(_) => {
let _ = connect_daemon(RequestCode::START_DAEMON, true)?;
}
Stop(_) => {
let mut fd = connect_daemon(RequestCode::STOP_DAEMON, false)?;
return Ok(i32::decode(&mut fd)?);
}
PostFsData(_) => {
let fd = connect_daemon(RequestCode::POST_FS_DATA, true)?;
let mut pfd = [PollFd::new(fd.as_fd(), PollFlags::POLLIN)];
nix::poll::poll(
&mut pfd,
PollTimeout::try_from(POST_FS_DATA_WAIT_TIME * 1000)?,
)?;
}
Service(_) => {
let _ = connect_daemon(RequestCode::LATE_START, true)?;
}
BootComplete(_) => {
let _ = connect_daemon(RequestCode::BOOT_COMPLETE, false)?;
}
ZygoteRestart(_) => {
let _ = connect_daemon(RequestCode::ZYGOTE_RESTART, false)?;
}
UnlockBlocks(_) => {
unlock_blocks();
}
RestoreCon(_) => {
restorecon();
}
CloneAttr(self::CloneAttr { from, to }) => {
clone_attr(&from, &to)?;
}
CloneFile(self::CloneFile { from, to }) => {
from.copy_to(&to)?;
}
Sqlite(self::Sqlite { sql }) => {
let mut fd = connect_daemon(RequestCode::SQLITE_CMD, false)?;
sql.encode(&mut fd)?;
loop {
let line = String::decode(&mut fd)?;
if line.is_empty() {
return Ok(0);
}
println!("{line}");
}
}
Path(_) => {
let tmp = get_magisk_tmp();
if tmp.is_empty() {
return Ok(1);
} else {
println!("{tmp}");
}
}
DenyList(self::DenyList { mut args }) => {
return Ok(denylist_cli(&mut args));
}
PreInitDevice(_) => {
let name = find_preinit_device();
if name.is_empty() {
return Ok(1);
} else {
println!("{name}");
}
}
};
Ok(0)
}
}
pub fn magisk_main(argc: i32, argv: *mut *mut c_char) -> i32 {
if argc < 2 {
print_usage();
exit(1);
}
let mut cmds = CmdArgs::new(argc, argv.cast()).0;
// We need to manually inject "--" so that all actions can be treated as subcommands
cmds.insert(1, "--");
let cli = Cli::from_args(&cmds[..1], &cmds[1..]).on_early_exit(print_usage);
cli.action.exec().unwrap_or(1)
}

View File

@@ -1,18 +1,18 @@
use crate::consts::{MODULEMNT, MODULEROOT, MODULEUPGRADE, WORKERDIR};
use crate::daemon::MagiskD;
use crate::ffi::{
ModuleInfo, exec_module_scripts, exec_script, get_magisk_tmp, get_zygisk_lib_name,
load_prop_file, set_zygisk_prop,
};
use crate::ffi::{ModuleInfo, exec_module_scripts, exec_script, get_magisk_tmp};
use crate::mount::setup_module_mount;
use crate::resetprop::load_prop_file;
use base::{
DirEntry, Directory, FsPathBuilder, LibcReturn, LoggedResult, OsResultStatic, ResultExt,
Utf8CStr, Utf8CStrBuf, Utf8CString, WalkResult, clone_attr, cstr, debug, error, info, libc,
raw_cstr, warn,
DirEntry, Directory, FsPathBuilder, LoggedResult, OsResult, ResultExt, SilentLogExt, Utf8CStr,
Utf8CStrBuf, Utf8CString, WalkResult, clone_attr, cstr, debug, error, info, libc, raw_cstr,
warn,
};
use libc::{AT_REMOVEDIR, MS_RDONLY, O_CLOEXEC, O_CREAT, O_RDONLY};
use nix::fcntl::OFlag;
use nix::mount::MsFlags;
use nix::unistd::UnlinkatFlags;
use std::collections::BTreeMap;
use std::os::fd::{AsRawFd, IntoRawFd};
use std::os::fd::IntoRawFd;
use std::path::{Component, Path};
use std::ptr;
use std::sync::atomic::Ordering;
@@ -41,14 +41,19 @@ fn bind_mount(reason: &str, src: &Utf8CStr, dest: &Utf8CStr, rec: bool) {
// Ignore any kind of error here. If a single bind mount fails due to selinux permissions or
// kernel limitations, don't let it break module mount entirely.
src.bind_mount_to(dest, rec).log_ok();
dest.remount_mount_point_flags(MS_RDONLY).log_ok();
dest.remount_mount_point_flags(MsFlags::MS_RDONLY).log_ok();
}
fn mount_dummy(reason: &str, src: &Utf8CStr, dest: &Utf8CStr, is_dir: bool) -> OsResultStatic<()> {
fn mount_dummy<'a>(
reason: &str,
src: &Utf8CStr,
dest: &'a Utf8CStr,
is_dir: bool,
) -> OsResult<'a, ()> {
if is_dir {
dest.mkdir(0o000)?;
} else {
dest.create(O_CREAT | O_RDONLY | O_CLOEXEC, 0o000)?;
dest.create(OFlag::O_CREAT | OFlag::O_RDONLY | OFlag::O_CLOEXEC, 0o000)?;
}
bind_mount(reason, src, dest, false);
Ok(())
@@ -72,7 +77,7 @@ impl PathTracker<'_> {
PathTracker { path, len }
}
fn append(&mut self, name: &str) -> PathTracker {
fn append(&mut self, name: &str) -> PathTracker<'_> {
let len = self.path.len();
self.path.append_path(name);
PathTracker {
@@ -81,7 +86,7 @@ impl PathTracker<'_> {
}
}
fn reborrow(&mut self) -> PathTracker {
fn reborrow(&mut self) -> PathTracker<'_> {
Self::from(self.path)
}
}
@@ -93,46 +98,101 @@ impl Drop for PathTracker<'_> {
}
}
struct FilePaths<'a> {
// The comments for this struct assume real = "/system/bin"
struct ModulePaths<'a> {
real: PathTracker<'a>,
worker: PathTracker<'a>,
module: PathTracker<'a>,
module_mnt: PathTracker<'a>,
module_root: PathTracker<'a>,
}
impl FilePaths<'_> {
fn append(&mut self, name: &str) -> FilePaths {
FilePaths {
real: self.real.append(name),
worker: self.worker.append(name),
module_mnt: self.module_mnt.append(name),
module_root: self.module_root.append(name),
impl ModulePaths<'_> {
fn new<'a>(
real: &'a mut dyn Utf8CStrBuf,
module: &'a mut dyn Utf8CStrBuf,
module_mnt: &'a mut dyn Utf8CStrBuf,
) -> ModulePaths<'a> {
real.append_path("/");
module.append_path(MODULEROOT);
module_mnt
.append_path(get_magisk_tmp())
.append_path(MODULEMNT);
ModulePaths {
real: PathTracker::from(real),
module: PathTracker::from(module),
module_mnt: PathTracker::from(module_mnt),
}
}
fn reborrow(&mut self) -> FilePaths {
FilePaths {
fn set_module(&mut self, module: &str) -> ModulePaths<'_> {
ModulePaths {
real: self.real.reborrow(),
worker: self.worker.reborrow(),
module_mnt: self.module_mnt.reborrow(),
module_root: self.module_root.reborrow(),
module: self.module.append(module),
module_mnt: self.module_mnt.append(module),
}
}
fn append(&mut self, name: &str) -> ModulePaths<'_> {
ModulePaths {
real: self.real.append(name),
module: self.module.append(name),
module_mnt: self.module_mnt.append(name),
}
}
// Returns "/system/bin"
fn real(&self) -> &Utf8CStr {
self.real.path
}
fn worker(&self) -> &Utf8CStr {
self.worker.path
// Returns "/data/adb/modules/{module}/system/bin"
fn module(&self) -> &Utf8CStr {
self.module.path
}
// Returns "$MAGISK_TMP/.magisk/modules/{module}/system/bin"
fn module_mnt(&self) -> &Utf8CStr {
self.module_mnt.path
}
}
fn module(&self) -> &Utf8CStr {
self.module_root.path
// The comments for this struct assume real = "/system/bin"
struct MountPaths<'a> {
real: PathTracker<'a>,
worker: PathTracker<'a>,
}
impl MountPaths<'_> {
fn new<'a>(real: &'a mut dyn Utf8CStrBuf, worker: &'a mut dyn Utf8CStrBuf) -> MountPaths<'a> {
real.append_path("/");
worker.append_path(get_magisk_tmp()).append_path(WORKERDIR);
MountPaths {
real: PathTracker::from(real),
worker: PathTracker::from(worker),
}
}
fn append(&mut self, name: &str) -> MountPaths<'_> {
MountPaths {
real: self.real.append(name),
worker: self.worker.append(name),
}
}
fn reborrow(&mut self) -> MountPaths<'_> {
MountPaths {
real: self.real.reborrow(),
worker: self.worker.reborrow(),
}
}
// Returns "/system/bin"
fn real(&self) -> &Utf8CStr {
self.real.path
}
// Returns "$MAGISK_TMP/.magisk/worker/system/bin"
fn worker(&self) -> &Utf8CStr {
self.worker.path
}
}
@@ -151,7 +211,7 @@ impl FsNode {
}
}
fn collect(&mut self, mut paths: FilePaths) -> LoggedResult<()> {
fn collect(&mut self, mut paths: ModulePaths) -> LoggedResult<()> {
let FsNode::Directory { children } = self else {
return Ok(());
};
@@ -166,6 +226,7 @@ impl FsNode {
.or_insert_with(FsNode::new_dir);
node.collect(entry_paths)?;
} else if entry.is_symlink() {
// Read the link and store its target
let mut link = cstr::buf::default();
path.read_link(&mut link)?;
children
@@ -222,7 +283,7 @@ impl FsNode {
}
}
fn commit(&mut self, mut path: FilePaths, is_root_dir: bool) -> LoggedResult<()> {
fn commit(&mut self, mut path: MountPaths, is_root_dir: bool) -> LoggedResult<()> {
match self {
FsNode::Directory { children } => {
let mut is_tmpfs = false;
@@ -274,7 +335,7 @@ impl FsNode {
Ok(())
}
fn commit_tmpfs(&mut self, mut path: FilePaths) -> LoggedResult<()> {
fn commit_tmpfs(&mut self, mut path: MountPaths) -> LoggedResult<()> {
match self {
FsNode::Directory { children } => {
path.worker().mkdirs(0o000)?;
@@ -285,21 +346,18 @@ impl FsNode {
clone_attr(&parent, path.worker())?;
}
// Check whether a file name '.replace' exist
// Check whether a file named '.replace' exists
if let Some(FsNode::File { src }) = children.remove(".replace")
&& let Some(base_dir) = src.parent_dir()
&& let Some(replace_dir) = src.parent_dir()
{
for (name, node) in children {
let path = path.append(name);
match node {
FsNode::Directory { .. } | FsNode::File { .. } => {
let src = Utf8CString::from(base_dir).join_path(name);
mount_dummy(
"mount",
&src,
path.worker(),
matches!(node, FsNode::Directory { .. }),
)?;
FsNode::Directory { .. } => {
// For replace, we don't need to traverse any deeper for mirroring.
// We can simply just bind mount the module dir to worker dir.
let src = Utf8CString::from(replace_dir).join_path(name);
mount_dummy("mount", &src, path.worker(), true)?;
}
_ => node.commit_tmpfs(path)?,
}
@@ -309,7 +367,7 @@ impl FsNode {
return Ok(());
}
// Traverse the real directory and mount mirrors
// Traverse the real directory and mount mirror files
if let Ok(mut dir) = Directory::open(path.real()) {
while let Ok(Some(entry)) = dir.read() {
if children.contains_key(entry.name().as_str()) {
@@ -319,18 +377,22 @@ impl FsNode {
let path = path.append(entry.name());
if entry.is_symlink() {
// Add the symlink into children and handle it later
if entry.is_dir() {
// At the first glance, it looks like we can directly mount the
// real dir to worker dir as mirror. However, this should NOT be done,
// because init will track these mounts with dev.mnt, causing issues.
// We unfortunately have to traverse recursively for mirroring.
FsNode::new_dir().commit_tmpfs(path)?;
} else if entry.is_symlink() {
let mut link = cstr::buf::default();
entry.read_link(&mut link).log_ok();
children.insert(
entry.name().to_string(),
FsNode::Symlink {
target: link.to_owned(),
},
);
FsNode::Symlink {
target: link.to_owned(),
}
.commit_tmpfs(path)?;
} else {
mount_dummy("mirror", path.real(), path.worker(), entry.is_dir())?;
// Mount the mirror file
mount_dummy("mirror", path.real(), path.worker(), false)?;
}
}
}
@@ -376,7 +438,7 @@ fn get_path_env() -> String {
.unwrap_or_default()
}
fn inject_magisk_bins(system: &mut FsNode) {
fn inject_magisk_bins(system: &mut FsNode, is_emulator: bool) {
fn inject(children: &mut FsNodeMap) {
let mut path = cstr::buf::default().join_path(get_magisk_tmp());
@@ -418,7 +480,7 @@ fn inject_magisk_bins(system: &mut FsNode) {
let mut candidates = vec![];
for orig_item in path_env.split(':') {
// Filter not suitbale paths
// Filter non-suitable paths
if !MAGISK_BIN_INJECT_PARTITIONS
.iter()
.any(|p| orig_item.starts_with(p.as_str()))
@@ -430,6 +492,11 @@ fn inject_magisk_bins(system: &mut FsNode) {
continue;
}
// We want to keep /system/xbin/su on emulators (for debugging)
if is_emulator && orig_item.starts_with("/system/xbin") {
continue;
}
// Override existing su first
let su_path = Utf8CString::from(format!("{orig_item}/su"));
if su_path.exists() {
@@ -497,9 +564,7 @@ fn inject_magisk_bins(system: &mut FsNode) {
}
}
fn inject_zygisk_bins(system: &mut FsNode) {
let name = get_zygisk_lib_name();
fn inject_zygisk_bins(name: &str, system: &mut FsNode) {
#[cfg(target_pointer_width = "64")]
let has_32_bit = cstr!("/system/bin/linker").exists();
@@ -555,117 +620,8 @@ fn inject_zygisk_bins(system: &mut FsNode) {
}
}
fn apply_modules(zygisk: bool, module_list: &[ModuleInfo]) {
let mut system = FsNode::new_dir();
// Build all the base "prefix" paths
let mut root = cstr::buf::default().join_path("/");
let mut module_dir = cstr::buf::default().join_path(MODULEROOT);
let mut module_mnt = cstr::buf::default()
.join_path(get_magisk_tmp())
.join_path(MODULEMNT);
let mut worker = cstr::buf::default()
.join_path(get_magisk_tmp())
.join_path(WORKERDIR);
// Create a collection of all relevant paths
let mut root_paths = FilePaths {
real: PathTracker::from(&mut root),
worker: PathTracker::from(&mut worker),
module_mnt: PathTracker::from(&mut module_mnt),
module_root: PathTracker::from(&mut module_dir),
};
// Step 1: Create virtual filesystem tree
//
// In this step, there is zero logic applied during tree construction; we simply collect and
// record the union of all module filesystem trees under each of their /system directory.
for info in module_list {
let mut module_paths = root_paths.append(&info.name);
{
// Read props
let prop = module_paths.append("system.prop");
if prop.module().exists() {
// Do NOT go through property service as it could cause boot lock
load_prop_file(prop.module(), true);
}
}
{
// Check whether skip mounting
let skip = module_paths.append("skip_mount");
if skip.module().exists() {
continue;
}
}
{
// Double check whether the system folder exists
let sys = module_paths.append("system");
if sys.module().exists() {
info!("{}: loading module files", &info.name);
system.collect(sys).log_ok();
}
}
}
// Step 2: Inject custom files
//
// Magisk provides some built-in functionality that requires augmenting the filesystem.
// We expose several cmdline tools (e.g. su) into PATH, and the zygisk shared library
// has to also be added into the default LD_LIBRARY_PATH for code injection.
// We directly inject file nodes into the virtual filesystem tree we built in the previous
// step, treating Magisk just like a special "module".
if get_magisk_tmp() != "/sbin" || get_path_env().split(":").all(|s| s != "/sbin") {
inject_magisk_bins(&mut system);
}
if zygisk {
inject_zygisk_bins(&mut system);
}
// Step 3: Extract all supported read-only partition roots
//
// For simplicity and backwards compatibility on older Android versions, when constructing
// Magisk modules, we always assume that there is only a single read-only partition mounted
// at /system. However, on modern Android there are actually multiple read-only partitions
// mounted at their respective paths. We need to extract these subtrees out of the main
// tree and treat them as individual trees.
let mut roots = BTreeMap::new(); /* mapOf(partition_name -> FsNode) */
if let FsNode::Directory { children } = &mut system {
for dir in SECONDARY_READ_ONLY_PARTITIONS {
// Only treat these nodes as root iff it is actually a directory in rootdir
if let Ok(attr) = dir.get_attr()
&& attr.is_dir()
{
let name = dir.trim_start_matches('/');
if let Some(root) = children.remove(name) {
roots.insert(name, root);
}
}
}
}
roots.insert("system", system);
for (dir, mut root) in roots {
// Step 4: Convert virtual filesystem tree into concrete operations
//
// Compare the virtual filesystem tree we constructed against the real filesystem
// structure on-device to generate a series of "operations".
// The "core" of the logic is to decide which directories need to be rebuilt in the
// tmpfs worker directory, and real sub-nodes need to be mirrored inside it.
let path = root_paths.append(dir);
root.commit(path, true).log_ok();
}
}
fn upgrade_modules() -> LoggedResult<()> {
let mut upgrade = Directory::open(cstr!(MODULEUPGRADE))?;
let ufd = upgrade.as_raw_fd();
let mut upgrade = Directory::open(cstr!(MODULEUPGRADE)).silent()?;
let root = Directory::open(cstr!(MODULEROOT))?;
while let Some(e) = upgrade.read()? {
if !e.is_dir() {
@@ -679,23 +635,19 @@ fn upgrade_modules() -> LoggedResult<()> {
// If the old module is disabled, we need to also disable the new one
disable = module.contains_path(cstr!("disable"));
module.remove_all()?;
root.unlink_at(module_name, AT_REMOVEDIR)?;
root.unlink_at(module_name, UnlinkatFlags::RemoveDir)?;
}
info!("Upgrade / New module: {module_name}");
unsafe {
libc::renameat(
ufd,
module_name.as_ptr(),
root.as_raw_fd(),
module_name.as_ptr(),
)
.check_os_err("renameat", Some(module_name), None)?;
}
e.rename_to(&root, module_name)?;
if disable {
let path = cstr::buf::default()
.join_path(module_name)
.join_path("disable");
let _ = root.open_as_file_at(&path, O_RDONLY | O_CREAT | O_CLOEXEC, 0)?;
let _ = root.open_as_file_at(
&path,
OFlag::O_RDONLY | OFlag::O_CREAT | OFlag::O_CLOEXEC,
0,
)?;
}
}
upgrade.remove_all()?;
@@ -716,7 +668,11 @@ fn for_each_module(mut func: impl FnMut(&DirEntry) -> LoggedResult<()>) -> Logge
pub fn disable_modules() {
for_each_module(|e| {
let dir = e.open_as_dir()?;
dir.open_as_file_at(cstr!("disable"), O_RDONLY | O_CREAT | O_CLOEXEC, 0)?;
dir.open_as_file_at(
cstr!("disable"),
OFlag::O_RDONLY | OFlag::O_CREAT | OFlag::O_CLOEXEC,
0,
)?;
Ok(())
})
.log_ok();
@@ -758,7 +714,8 @@ fn collect_modules(zygisk_enabled: bool, open_zygisk: bool) -> Vec<ModuleInfo> {
e.unlink()?;
return Ok(());
}
dir.unlink_at(cstr!("update"), 0).ok();
dir.unlink_at(cstr!("update"), UnlinkatFlags::NoRemoveDir)
.ok();
if dir.contains_path(cstr!("disable")) {
return Ok(());
}
@@ -775,7 +732,7 @@ fn collect_modules(zygisk_enabled: bool, open_zygisk: bool) -> Vec<ModuleInfo> {
}
fn open_fd_safe(dir: &Directory, name: &Utf8CStr) -> i32 {
dir.open_as_file_at(name, O_RDONLY | O_CLOEXEC, 0)
dir.open_as_file_at(name, OFlag::O_RDONLY | OFlag::O_CLOEXEC, 0)
.log()
.map(IntoRawFd::into_raw_fd)
.unwrap_or(-1)
@@ -804,7 +761,8 @@ fn collect_modules(zygisk_enabled: bool, open_zygisk: bool) -> Vec<ModuleInfo> {
{
z64 = open_fd_safe(&dir, cstr!("zygisk/riscv64.so"));
}
dir.unlink_at(cstr!("zygisk/unloaded"), 0).ok();
dir.unlink_at(cstr!("zygisk/unloaded"), UnlinkatFlags::NoRemoveDir)
.ok();
}
} else {
// Ignore zygisk modules when zygisk is not enabled
@@ -872,11 +830,107 @@ impl MagiskD {
// Recollect modules (module scripts could remove itself)
let modules = collect_modules(zygisk, true);
if zygisk {
set_zygisk_prop();
}
apply_modules(zygisk, &modules);
self.apply_modules(&modules);
self.module_list.set(modules).ok();
}
fn apply_modules(&self, module_list: &[ModuleInfo]) {
let mut system = FsNode::new_dir();
// Create buffers for paths
let mut buf1 = cstr::buf::dynamic(256);
let mut buf2 = cstr::buf::dynamic(256);
let mut buf3 = cstr::buf::dynamic(256);
let mut paths = ModulePaths::new(&mut buf1, &mut buf2, &mut buf3);
// Step 1: Create virtual filesystem tree
//
// In this step, there is zero logic applied during tree construction; we simply collect and
// record the union of all module filesystem trees under each of their /system directory.
for info in module_list {
let mut paths = paths.set_module(&info.name);
// Read props
let prop = paths.append("system.prop");
if prop.module().exists() {
load_prop_file(prop.module());
}
drop(prop);
// Check whether skip mounting
let skip = paths.append("skip_mount");
if skip.module().exists() {
continue;
}
drop(skip);
// Double check whether the system folder exists
let sys = paths.append("system");
if sys.module().exists() {
info!("{}: loading module files", &info.name);
system.collect(sys).log_ok();
}
}
// Step 2: Inject custom files
//
// Magisk provides some built-in functionality that requires augmenting the filesystem.
// We expose several cmdline tools (e.g. su) into PATH, and the zygisk shared library
// has to also be added into the default LD_LIBRARY_PATH for code injection.
// We directly inject file nodes into the virtual filesystem tree we built in the previous
// step, treating Magisk just like a special "module".
if get_magisk_tmp() != "/sbin" || get_path_env().split(":").all(|s| s != "/sbin") {
inject_magisk_bins(&mut system, self.is_emulator);
}
// Handle zygisk
if self.zygisk_enabled.load(Ordering::Acquire) {
let mut zygisk = self.zygisk.lock().unwrap();
zygisk.set_prop();
inject_zygisk_bins(&zygisk.lib_name, &mut system);
}
// Step 3: Extract all supported read-only partition roots
//
// For simplicity and backwards compatibility on older Android versions, when constructing
// Magisk modules, we always assume that there is only a single read-only partition mounted
// at /system. However, on modern Android there are actually multiple read-only partitions
// mounted at their respective paths. We need to extract these subtrees out of the main
// tree and treat them as individual trees.
let mut roots = BTreeMap::new(); /* mapOf(partition_name -> FsNode) */
if let FsNode::Directory { children } = &mut system {
for dir in SECONDARY_READ_ONLY_PARTITIONS {
// Only treat these nodes as root iff it is actually a directory in rootdir
if let Ok(attr) = dir.get_attr()
&& attr.is_dir()
{
let name = dir.trim_start_matches('/');
if let Some(root) = children.remove(name) {
roots.insert(name, root);
}
}
}
}
roots.insert("system", system);
drop(paths);
let mut paths = MountPaths::new(&mut buf1, &mut buf2);
for (dir, mut root) in roots {
// Step 4: Convert virtual filesystem tree into concrete operations
//
// Compare the virtual filesystem tree we constructed against the real filesystem
// structure on-device to generate a series of "operations".
// The "core" of the logic is to decide which directories need to be rebuilt in the
// tmpfs worker directory, and real sub-nodes need to be mirrored inside it.
let paths = paths.append(dir);
root.commit(paths, true).log_ok();
}
}
}

View File

@@ -1,18 +1,16 @@
use std::{
cmp::Ordering::{Greater, Less},
path::{Path, PathBuf},
};
use num_traits::AsPrimitive;
use base::libc::{c_uint, dev_t};
use crate::consts::{MODULEMNT, MODULEROOT, PREINITDEV, PREINITMIRR, WORKERDIR};
use crate::ffi::{get_magisk_tmp, resolve_preinit_dir, switch_mnt_ns};
use crate::resetprop::get_prop;
use base::{
FsPathBuilder, LibcReturn, LoggedResult, MountInfo, ResultExt, Utf8CStr, Utf8CStrBuf, cstr,
debug, info, libc, parse_mount_info, warn,
};
use crate::consts::{MODULEMNT, MODULEROOT, PREINITDEV, PREINITMIRR, WORKERDIR};
use crate::ffi::{get_magisk_tmp, get_prop, resolve_preinit_dir, switch_mnt_ns};
use libc::{c_uint, dev_t};
use nix::mount::MsFlags;
use nix::sys::stat::{Mode, SFlag, mknod};
use num_traits::AsPrimitive;
use std::cmp::Ordering::{Greater, Less};
use std::path::{Path, PathBuf};
pub fn setup_preinit_dir() {
let magisk_tmp = get_magisk_tmp();
@@ -68,7 +66,7 @@ pub fn setup_module_mount() {
let _: LoggedResult<()> = try {
module_mnt.mkdir(0o755)?;
cstr!(MODULEROOT).bind_mount_to(&module_mnt, false)?;
module_mnt.remount_mount_point_flags(libc::MS_RDONLY)?;
module_mnt.remount_mount_point_flags(MsFlags::MS_RDONLY)?;
};
}
@@ -109,11 +107,11 @@ enum EncryptType {
}
pub fn find_preinit_device() -> String {
let encrypt_type = if get_prop(cstr!("ro.crypto.state"), false) != "encrypted" {
let encrypt_type = if get_prop(cstr!("ro.crypto.state")) != "encrypted" {
EncryptType::None
} else if get_prop(cstr!("ro.crypto.type"), false) == "block" {
} else if get_prop(cstr!("ro.crypto.type")) == "block" {
EncryptType::Block
} else if get_prop(cstr!("ro.crypto.metadata.enabled"), false) == "true" {
} else if get_prop(cstr!("ro.crypto.metadata.enabled")) == "true" {
EncryptType::Metadata
} else {
EncryptType::File
@@ -193,16 +191,14 @@ pub fn find_preinit_device() -> String {
if std::env::var_os("MAKEDEV").is_some() {
buf.clear();
let dev_path = buf.append_path(&tmp).append_path(PREINITDEV);
unsafe {
libc::mknod(
dev_path.as_ptr(),
libc::S_IFBLK | 0o600,
info.device as dev_t,
)
.check_io_err()
.log()
.ok();
}
mknod(
dev_path.as_utf8_cstr(),
SFlag::S_IFBLK,
Mode::from_bits_truncate(0o600),
info.device as dev_t,
)
.check_os_err("mknod", Some(dev_path), None)
.log_ok();
}
}
Path::new(&info.source)
@@ -248,10 +244,8 @@ pub fn revert_unmount(pid: i32) {
for mut target in targets {
let target = Utf8CStr::from_string(&mut target);
unsafe {
if libc::umount2(target.as_ptr(), libc::MNT_DETACH) == 0 {
debug!("denylist: Unmounted ({})", target);
}
if target.unmount().is_ok() {
debug!("denylist: Unmounted ({})", target);
}
}
}

View File

@@ -2,19 +2,17 @@ use crate::consts::{APP_PACKAGE_NAME, MAGISK_VER_CODE};
use crate::daemon::{AID_APP_END, AID_APP_START, AID_USER_OFFSET, MagiskD, to_app_id};
use crate::ffi::{DbEntryKey, get_magisk_tmp, install_apk, uninstall_pkg};
use base::WalkResult::{Abort, Continue, Skip};
use base::libc::{O_CLOEXEC, O_CREAT, O_RDONLY, O_TRUNC, O_WRONLY};
use base::{
BufReadExt, Directory, FsPathBuilder, LoggedResult, ReadExt, ResultExt, Utf8CStrBuf,
Utf8CString, cstr, error, fd_get_attr, warn,
};
use bit_set::BitSet;
use cxx::CxxString;
use nix::fcntl::OFlag;
use std::collections::BTreeMap;
use std::fs::File;
use std::io;
use std::io::{Cursor, Read, Seek, SeekFrom};
use std::os::fd::AsRawFd;
use std::pin::Pin;
use std::time::Duration;
const EOCD_MAGIC: u32 = 0x06054B50;
@@ -88,7 +86,7 @@ fn read_certificate(apk: &mut File, version: i32) -> Vec<u8> {
apk.read_exact(&mut comment)?;
let mut comment = Cursor::new(&comment);
let mut apk_ver = 0;
comment.foreach_props(|k, v| {
comment.for_each_prop(|k, v| {
if k == "versionCode" {
apk_ver = v.parse::<i32>().unwrap_or(0);
false
@@ -240,7 +238,7 @@ impl ManagerInfo {
.join_path("dyn")
.join_path("current.apk");
let uid: i32;
let cert = match apk.open(O_RDONLY | O_CLOEXEC) {
let cert = match apk.open(OFlag::O_RDONLY | OFlag::O_CLOEXEC) {
Ok(mut fd) => {
uid = fd_get_attr(fd.as_raw_fd())
.map(|attr| attr.st.st_uid as i32)
@@ -272,7 +270,7 @@ impl ManagerInfo {
return Status::NotInstalled;
};
let cert = match apk.open(O_RDONLY | O_CLOEXEC) {
let cert = match apk.open(OFlag::O_RDONLY | OFlag::O_CLOEXEC) {
Ok(mut fd) => read_certificate(&mut fd, -1),
Err(_) => return Status::NotInstalled,
};
@@ -295,7 +293,7 @@ impl ManagerInfo {
return Status::NotInstalled;
};
let cert = match apk.open(O_RDONLY | O_CLOEXEC) {
let cert = match apk.open(OFlag::O_RDONLY | OFlag::O_CLOEXEC) {
Ok(mut fd) => read_certificate(&mut fd, MAGISK_VER_CODE),
Err(_) => return Status::NotInstalled,
};
@@ -319,8 +317,10 @@ impl ManagerInfo {
let tmp_apk = cstr!("/data/stub.apk");
let result: LoggedResult<()> = try {
{
let mut tmp_apk_file =
tmp_apk.create(O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0o600)?;
let mut tmp_apk_file = tmp_apk.create(
OFlag::O_WRONLY | OFlag::O_CREAT | OFlag::O_TRUNC | OFlag::O_CLOEXEC,
0o600,
)?;
io::copy(stub_fd, &mut tmp_apk_file)?;
}
// Seek the fd back to start
@@ -447,7 +447,7 @@ impl MagiskD {
.join_path(get_magisk_tmp())
.join_path("stub.apk");
if let Ok(mut fd) = apk.open(O_RDONLY | O_CLOEXEC) {
if let Ok(mut fd) = apk.open(OFlag::O_RDONLY | OFlag::O_CLOEXEC) {
info.trusted_cert = read_certificate(&mut fd, MAGISK_VER_CODE);
// Seek the fd back to start
fd.seek(SeekFrom::Start(0)).log_ok();
@@ -474,19 +474,6 @@ impl MagiskD {
let _ = info.get_manager(self, 0, true);
}
pub unsafe fn get_manager_for_cxx(&self, user: i32, ptr: *mut CxxString, install: bool) -> i32 {
unsafe {
let mut info = self.manager_info.lock().unwrap();
let (uid, pkg) = info.get_manager(self, user, install);
if let Some(str) = ptr.as_mut() {
let mut str = Pin::new_unchecked(str);
str.as_mut().clear();
str.push_str(pkg);
}
uid
}
}
// app_id = app_no + AID_APP_START
// app_no range: [0, 9999]
pub fn get_app_no_list(&self) -> BitSet {

View File

@@ -1,4 +0,0 @@
pub use persist::{persist_delete_prop, persist_get_prop, persist_get_props, persist_set_prop};
mod persist;
mod proto;

View File

@@ -0,0 +1,326 @@
use super::persist::{
persist_delete_prop, persist_get_all_props, persist_get_prop, persist_set_prop,
};
use super::{PropInfo, PropReader, SYS_PROP};
use argh::{EarlyExit, FromArgs, MissingRequirements};
use base::libc::PROP_VALUE_MAX;
use base::{
BufReadExt, CmdArgs, EarlyExitExt, LogLevel, LoggedResult, ResultExt, Utf8CStr, Utf8CStrBuf,
Utf8CString, argh, cstr, debug, log_err, set_log_level_state,
};
use nix::fcntl::OFlag;
use std::collections::BTreeMap;
use std::ffi::c_char;
use std::io::BufReader;
#[derive(FromArgs, Default)]
struct ResetProp {
#[argh(switch, short = 'v')]
verbose: bool,
#[argh(switch, short = 'w', long = none)]
wait_mode: bool,
#[argh(switch, short = 'p', long = none)]
persist: bool,
#[argh(switch, short = 'P', long = none)]
persist_only: bool,
#[argh(switch, short = 'Z', long = none)]
context: bool,
#[argh(switch, short = 'n', long = none)]
skip_svc: bool,
#[argh(option, short = 'f')]
file: Option<Utf8CString>,
#[argh(option, short = 'd', long = "delete")]
delete_key: Option<Utf8CString>,
#[argh(positional, greedy = true)]
args: Vec<Utf8CString>,
}
fn print_usage(cmd: &str) {
eprintln!(
r#"resetprop - System Property Manipulation Tool
Usage: {cmd} [flags] [arguments...]
Read mode arguments:
(no arguments) print all properties
NAME get property of NAME
Write mode arguments:
NAME VALUE set property NAME as VALUE
-f,--file FILE load and set properties from FILE
-d,--delete NAME delete property
Wait mode arguments (toggled with -w):
NAME wait until property NAME changes
NAME OLD_VALUE if value of property NAME is not OLD_VALUE, get value
or else wait until property NAME changes
General flags:
-h,--help show this message
-v,--verbose print verbose output to stderr
-w switch to wait mode
Read mode flags:
-p also read persistent properties from storage
-P only read persistent properties from storage
-Z get property context instead of value
Write mode flags:
-n set properties bypassing property_service
-p always write persistent prop changes to storage
"#
);
}
impl ResetProp {
fn get(&self, key: &Utf8CStr) -> Option<String> {
if self.context {
return Some(SYS_PROP.get_context(key).to_string());
}
let mut val = if !self.persist_only {
SYS_PROP.find(key).map(|info| {
let mut v = String::new();
info.read(&mut PropReader::Value(&mut v));
debug!("resetprop: get prop [{key}]=[{v}]");
v
})
} else {
None
};
if val.is_none() && (self.persist || self.persist_only) && key.starts_with("persist.") {
val = persist_get_prop(key).ok();
}
if val.is_none() {
debug!("resetprop: prop [{key}] does not exist");
}
val
}
fn print_all(&self) {
let mut map: BTreeMap<String, String> = BTreeMap::new();
if !self.persist_only {
SYS_PROP.for_each(&mut PropReader::List(&mut map));
}
if self.persist || self.persist_only {
persist_get_all_props(&mut PropReader::List(&mut map)).log_ok();
}
for (mut k, v) in map.into_iter() {
if self.context {
println!(
"[{k}]: [{}]",
SYS_PROP.get_context(Utf8CStr::from_string(&mut k))
);
} else {
println!("[{k}]: [{v}]");
}
}
}
fn set(&self, key: &Utf8CStr, val: &Utf8CStr) {
let mut skip_svc = self.skip_svc;
let mut info = SYS_PROP.find_mut(key);
// Delete existing read-only properties if they are or will be long properties,
// which cannot directly go through __system_property_update
if key.starts_with("ro.") {
skip_svc = true;
if let Some(pi) = &info
&& (pi.is_long() || val.len() >= PROP_VALUE_MAX as usize)
{
// Skip pruning nodes as we will add it back ASAP
SYS_PROP.delete(key, false);
info = None;
}
}
#[allow(unused_variables)]
let msg = if skip_svc {
"direct modification"
} else {
"property_service"
};
if let Some(pi) = info {
if skip_svc {
pi.update(val);
} else {
SYS_PROP.set(key, val);
}
debug!("resetprop: update prop [{key}]=[{val}] by {msg}");
} else {
if skip_svc {
SYS_PROP.add(key, val);
} else {
SYS_PROP.set(key, val);
}
debug!("resetprop: create prop [{key}]=[{val}] by {msg}");
}
// When bypassing property_service, persistent props won't be stored in storage.
// Explicitly handle this situation.
if skip_svc && self.persist && key.starts_with("persist.") {
persist_set_prop(key, val).log_ok();
}
}
fn delete(&self, key: &Utf8CStr) -> bool {
debug!("resetprop: delete prop [{key}]");
let mut ret = false;
ret |= SYS_PROP.delete(key, true);
if self.persist && key.starts_with("persist.") {
ret |= persist_delete_prop(key).is_ok()
}
ret
}
fn wait(&self) {
let key = &self.args[0];
let val = self.args.get(1).map(|s| &**s);
// Find PropInfo
let info: &PropInfo;
loop {
let i = SYS_PROP.find(key);
if let Some(i) = i {
info = i;
break;
} else {
debug!("resetprop: waiting for prop [{key}] to exist");
let mut serial = SYS_PROP.area_serial();
SYS_PROP.wait(None, serial, &mut serial);
}
}
if let Some(val) = val {
let mut curr_val = String::new();
let mut serial = 0;
loop {
let mut r = PropReader::ValueSerial(&mut curr_val, &mut serial);
SYS_PROP.read(info, &mut r);
if *val != *curr_val {
debug!("resetprop: get prop [{key}]=[{curr_val}]");
break;
}
debug!("resetprop: waiting for prop [{key}]!=[{val}]");
SYS_PROP.wait(Some(info), serial, &mut serial);
}
}
}
fn load_file(&self, file: &Utf8CStr) -> LoggedResult<()> {
let fd = file.open(OFlag::O_RDONLY | OFlag::O_CLOEXEC)?;
let mut key = cstr::buf::dynamic(128);
let mut val = cstr::buf::dynamic(128);
BufReader::new(fd).for_each_prop(|k, v| {
key.clear();
val.clear();
key.push_str(k);
val.push_str(v);
self.set(&key, &val);
true
});
Ok(())
}
fn run(self) -> LoggedResult<()> {
if self.wait_mode {
self.wait();
} else if let Some(file) = &self.file {
self.load_file(file)?;
} else if let Some(key) = &self.delete_key {
if !self.delete(key) {
return log_err!();
}
} else {
match self.args.len() {
0 => self.print_all(),
1 => {
if let Some(val) = self.get(&self.args[0]) {
println!("{val}");
} else {
return log_err!();
}
}
2 => self.set(&self.args[0], &self.args[1]),
_ => unreachable!(),
}
}
Ok(())
}
}
pub fn resetprop_main(argc: i32, argv: *mut *mut c_char) -> i32 {
set_log_level_state(LogLevel::Debug, false);
let cmds = CmdArgs::new(argc, argv.cast());
let cmds = cmds.as_slice();
let cli = ResetProp::from_args(&[cmds[0]], &cmds[1..])
.and_then(|cli| {
let mut special_mode = 0;
if cli.wait_mode {
if cli.args.is_empty() {
let mut missing = MissingRequirements::default();
missing.missing_positional_arg("NAME");
missing.err_on_any()?;
}
special_mode += 1;
}
if cli.file.is_some() {
special_mode += 1;
}
if cli.delete_key.is_some() {
special_mode += 1;
}
if special_mode > 1 {
return Err(EarlyExit::from(
"Multiple operation mode detected!\n".to_string(),
));
}
if cli.args.len() > 2 {
return Err(EarlyExit::from(format!(
"Unrecognized argument: {}\n",
cli.args[2]
)));
}
Ok(cli)
})
.on_early_exit(|| print_usage(cmds[0]));
if cli.verbose {
set_log_level_state(LogLevel::Debug, true);
}
if cli.run().is_ok() { 0 } else { 1 }
}
// Magisk's own helper functions
pub fn set_prop(key: &Utf8CStr, val: &Utf8CStr) {
let prop = ResetProp {
// All Magisk's internal usage should skip property_service
skip_svc: true,
..Default::default()
};
prop.set(key, val);
}
pub fn load_prop_file(file: &Utf8CStr) {
let prop = ResetProp {
// All Magisk's internal usage should skip property_service
skip_svc: true,
..Default::default()
};
prop.load_file(file).ok();
}
pub fn get_prop(key: &Utf8CStr) -> String {
let prop = ResetProp {
persist: key.starts_with("persist."),
..Default::default()
};
prop.get(key).unwrap_or_default()
}

View File

@@ -0,0 +1,181 @@
use base::libc::c_char;
use base::{Utf8CStr, libc};
pub use cli::{get_prop, load_prop_file, resetprop_main, set_prop};
use libc::timespec;
use std::collections::BTreeMap;
use std::ffi::CStr;
use std::ptr;
use std::sync::LazyLock;
mod cli;
mod persist;
mod proto;
static SYS_PROP: LazyLock<SysProp> = LazyLock::new(|| unsafe { get_sys_prop() });
#[repr(C)]
struct PropInfo {
_private: cxx::private::Opaque,
}
type CharPtr = *const c_char;
type ReadCallback = unsafe extern "C" fn(&mut PropReader, CharPtr, CharPtr, u32);
type ForEachCallback = unsafe extern "C" fn(&PropInfo, &mut PropReader);
enum PropReader<'a> {
Value(&'a mut String),
ValueSerial(&'a mut String, &'a mut u32),
List(&'a mut BTreeMap<String, String>),
}
impl PropReader<'_> {
fn put_cstr(&mut self, key: CharPtr, val: CharPtr, serial: u32) {
let key = unsafe { CStr::from_ptr(key) };
let val = unsafe { CStr::from_ptr(val) };
match self {
PropReader::Value(v) => {
**v = String::from_utf8_lossy(val.to_bytes()).into_owned();
}
PropReader::ValueSerial(v, s) => {
**v = String::from_utf8_lossy(val.to_bytes()).into_owned();
**s = serial;
}
PropReader::List(map) => {
map.insert(
String::from_utf8_lossy(key.to_bytes()).into_owned(),
String::from_utf8_lossy(val.to_bytes()).into_owned(),
);
}
}
}
fn put_str(&mut self, key: String, val: String, serial: u32) {
match self {
PropReader::Value(v) => {
**v = val;
}
PropReader::ValueSerial(v, s) => {
**v = val;
**s = serial;
}
PropReader::List(map) => {
map.insert(key, val);
}
}
}
}
unsafe extern "C" {
// SAFETY: the improper_ctypes warning is about PropReader. We only pass PropReader
// to C functions as raw pointers, and all actual usage happens on the Rust side.
#[allow(improper_ctypes)]
fn get_sys_prop() -> SysProp;
fn prop_info_is_long(info: &PropInfo) -> bool;
#[link_name = "__system_property_find2"]
fn sys_prop_find(key: CharPtr) -> Option<&'static mut PropInfo>;
#[link_name = "__system_property_update2"]
fn sys_prop_update(info: &mut PropInfo, val: CharPtr, val_len: u32) -> i32;
#[link_name = "__system_property_add2"]
fn sys_prop_add(key: CharPtr, key_len: u32, val: CharPtr, val_len: u32) -> i32;
#[link_name = "__system_property_delete"]
fn sys_prop_delete(key: CharPtr, prune: bool) -> i32;
#[link_name = "__system_property_get_context"]
fn sys_prop_get_context(key: CharPtr) -> CharPtr;
#[link_name = "__system_property_area_serial2"]
fn sys_prop_area_serial() -> u32;
}
#[repr(C)]
struct SysProp {
set: unsafe extern "C" fn(CharPtr, CharPtr) -> i32,
find: unsafe extern "C" fn(CharPtr) -> Option<&'static PropInfo>,
read_callback: unsafe extern "C" fn(&PropInfo, ReadCallback, &mut PropReader) -> i32,
foreach: unsafe extern "C" fn(ForEachCallback, &mut PropReader) -> i32,
wait: unsafe extern "C" fn(Option<&PropInfo>, u32, &mut u32, *const timespec) -> i32,
}
// Safe abstractions over raw C APIs
impl PropInfo {
fn read(&self, reader: &mut PropReader) {
SYS_PROP.read(self, reader);
}
fn update(&mut self, val: &Utf8CStr) {
SYS_PROP.update(self, val);
}
fn is_long(&self) -> bool {
unsafe { prop_info_is_long(self) }
}
}
impl SysProp {
fn read(&self, info: &PropInfo, reader: &mut PropReader) {
unsafe extern "C" fn read_fn(r: &mut PropReader, key: CharPtr, val: CharPtr, serial: u32) {
r.put_cstr(key, val, serial);
}
unsafe {
(self.read_callback)(info, read_fn, reader);
}
}
fn find(&self, key: &Utf8CStr) -> Option<&'static PropInfo> {
unsafe { (self.find)(key.as_ptr()) }
}
fn find_mut(&self, key: &Utf8CStr) -> Option<&'static mut PropInfo> {
unsafe { sys_prop_find(key.as_ptr()) }
}
fn set(&self, key: &Utf8CStr, val: &Utf8CStr) {
unsafe {
(self.set)(key.as_ptr(), val.as_ptr());
}
}
fn add(&self, key: &Utf8CStr, val: &Utf8CStr) {
unsafe {
sys_prop_add(
key.as_ptr(),
key.len() as u32,
val.as_ptr(),
val.len() as u32,
);
}
}
fn update(&self, info: &mut PropInfo, val: &Utf8CStr) {
unsafe {
sys_prop_update(info, val.as_ptr(), val.len() as u32);
}
}
fn delete(&self, key: &Utf8CStr, prune: bool) -> bool {
unsafe { sys_prop_delete(key.as_ptr(), prune) == 0 }
}
fn for_each(&self, reader: &mut PropReader) {
unsafe extern "C" fn for_each_fn(info: &PropInfo, vals: &mut PropReader) {
SYS_PROP.read(info, vals);
}
unsafe {
(self.foreach)(for_each_fn, reader);
}
}
fn wait(&self, info: Option<&PropInfo>, old_serial: u32, new_serial: &mut u32) {
unsafe {
(self.wait)(info, old_serial, new_serial, ptr::null());
}
}
fn get_context(&self, key: &Utf8CStr) -> &'static Utf8CStr {
unsafe { Utf8CStr::from_ptr_unchecked(sys_prop_get_context(key.as_ptr())) }
}
fn area_serial(&self) -> u32 {
unsafe { sys_prop_area_serial() }
}
}

View File

@@ -1,65 +1,36 @@
use std::io::Read;
use std::{
fs::File,
io::{BufWriter, Write},
ops::{Deref, DerefMut},
os::fd::FromRawFd,
pin::Pin,
};
use nix::fcntl::OFlag;
use quick_protobuf::{BytesReader, MessageRead, MessageWrite, Writer};
use std::fs::File;
use std::io::{BufWriter, Read, Write};
use std::os::fd::FromRawFd;
use crate::ffi::{PropCb, prop_cb_exec};
use crate::resetprop::proto::persistent_properties::{
PersistentProperties, mod_PersistentProperties::PersistentPropertyRecord,
};
use crate::resetprop::PropReader;
use crate::resetprop::proto::persistent_properties::PersistentProperties;
use crate::resetprop::proto::persistent_properties::mod_PersistentProperties::PersistentPropertyRecord;
use base::const_format::concatcp;
use base::libc::{O_CLOEXEC, O_RDONLY};
use base::libc::mkstemp;
use base::{
Directory, FsPathBuilder, LibcReturn, LoggedResult, MappedFile, SilentResultExt, Utf8CStr,
Utf8CStrBuf, WalkResult, clone_attr, cstr, debug, libc::mkstemp,
Directory, FsPathBuilder, LibcReturn, LoggedResult, MappedFile, SilentLogExt, Utf8CStr,
Utf8CStrBuf, WalkResult, clone_attr, cstr, debug, log_err,
};
const PERSIST_PROP_DIR: &str = "/data/property";
const PERSIST_PROP: &str = concatcp!(PERSIST_PROP_DIR, "/persistent_properties");
trait PropCbExec {
fn exec(&mut self, name: &Utf8CStr, value: &Utf8CStr);
}
impl PropCbExec for Pin<&mut PropCb> {
fn exec(&mut self, name: &Utf8CStr, value: &Utf8CStr) {
prop_cb_exec(self.as_mut(), name, value, u32::MAX)
}
}
impl Deref for PersistentProperties {
type Target = Vec<PersistentPropertyRecord>;
fn deref(&self) -> &Self::Target {
&self.properties
}
}
impl DerefMut for PersistentProperties {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.properties
}
}
trait PropExt {
fn find_index(&self, name: &Utf8CStr) -> Result<usize, usize>;
fn find(&mut self, name: &Utf8CStr) -> LoggedResult<&mut PersistentPropertyRecord>;
fn find(self, name: &Utf8CStr) -> Option<PersistentPropertyRecord>;
}
impl PropExt for PersistentProperties {
fn find_index(&self, name: &Utf8CStr) -> Result<usize, usize> {
self.binary_search_by(|p| p.name.as_deref().cmp(&Some(name.deref())))
self.properties
.binary_search_by(|p| p.name.as_deref().cmp(&Some(name.as_str())))
}
fn find(&mut self, name: &Utf8CStr) -> LoggedResult<&mut PersistentPropertyRecord> {
let idx = self.find_index(name).silent()?;
Ok(&mut self[idx])
fn find(self, name: &Utf8CStr) -> Option<PersistentPropertyRecord> {
let idx = self.find_index(name).ok()?;
self.properties.into_iter().nth(idx)
}
}
@@ -71,7 +42,7 @@ fn file_get_prop(name: &Utf8CStr) -> LoggedResult<String> {
let path = cstr::buf::default()
.join_path(PERSIST_PROP_DIR)
.join_path(name);
let mut file = path.open(O_RDONLY | O_CLOEXEC).silent()?;
let mut file = path.open(OFlag::O_RDONLY | OFlag::O_CLOEXEC).silent()?;
debug!("resetprop: read prop from [{}]", path);
let mut s = String::new();
file.read_to_string(&mut s)?;
@@ -89,7 +60,7 @@ fn file_set_prop(name: &Utf8CStr, value: Option<&Utf8CStr>) -> LoggedResult<()>
{
let mut f = unsafe {
mkstemp(tmp.as_mut_ptr())
.as_os_result("mkstemp", None, None)
.into_os_result("mkstemp", None, None)
.map(|fd| File::from_raw_fd(fd))?
};
f.write_all(value.as_bytes())?;
@@ -110,7 +81,9 @@ fn proto_read_props() -> LoggedResult<PersistentProperties> {
let mut r = BytesReader::from_bytes(m);
let mut props = PersistentProperties::from_reader(&mut r, m)?;
// Keep the list sorted for binary search
props.sort_unstable_by(|a, b| a.name.cmp(&b.name));
props
.properties
.sort_unstable_by(|a, b| a.name.cmp(&b.name));
Ok(props)
}
@@ -119,7 +92,7 @@ fn proto_write_props(props: &PersistentProperties) -> LoggedResult<()> {
{
let f = unsafe {
mkstemp(tmp.as_mut_ptr())
.as_os_result("mkstemp", None, None)
.into_os_result("mkstemp", None, None)
.map(|fd| File::from_raw_fd(fd))?
};
debug!("resetprop: encode with protobuf [{}]", tmp);
@@ -130,88 +103,80 @@ fn proto_write_props(props: &PersistentProperties) -> LoggedResult<()> {
Ok(())
}
pub fn persist_get_prop(name: &Utf8CStr, mut prop_cb: Pin<&mut PropCb>) {
let res: LoggedResult<()> = try {
if check_proto() {
let mut props = proto_read_props()?;
let prop = props.find(name)?;
pub(super) fn persist_get_prop(key: &Utf8CStr) -> LoggedResult<String> {
if check_proto() {
let props = proto_read_props()?;
let prop = props.find(key).silent()?;
if let PersistentPropertyRecord {
name: Some(_),
value: Some(v),
} = prop
{
return Ok(v);
}
} else {
let value = file_get_prop(key)?;
debug!("resetprop: get persist prop [{}]=[{}]", key, value);
return Ok(value);
}
log_err!()
}
pub(super) fn persist_get_all_props(reader: &mut PropReader) -> LoggedResult<()> {
if check_proto() {
let props = proto_read_props()?;
props.properties.into_iter().for_each(|prop| {
if let PersistentPropertyRecord {
name: Some(n),
value: Some(v),
} = prop
{
prop_cb.exec(Utf8CStr::from_string(n), Utf8CStr::from_string(v));
reader.put_str(n, v, 0);
}
} else {
let mut value = file_get_prop(name)?;
prop_cb.exec(name, Utf8CStr::from_string(&mut value));
debug!("resetprop: found prop [{}] = [{}]", name, value);
}
};
res.ok();
}
pub fn persist_get_props(mut prop_cb: Pin<&mut PropCb>) {
let res: LoggedResult<()> = try {
if check_proto() {
let mut props = proto_read_props()?;
props.iter_mut().for_each(|prop| {
if let PersistentPropertyRecord {
name: Some(n),
value: Some(v),
} = prop
{
prop_cb.exec(Utf8CStr::from_string(n), Utf8CStr::from_string(v));
}
});
} else {
let mut dir = Directory::open(cstr!(PERSIST_PROP_DIR))?;
dir.pre_order_walk(|e| {
if e.is_file()
&& let Ok(mut value) = file_get_prop(e.name())
{
prop_cb.exec(e.name(), Utf8CStr::from_string(&mut value));
}
// Do not traverse recursively
Ok(WalkResult::Skip)
})?;
}
};
res.ok();
}
pub fn persist_delete_prop(name: &Utf8CStr) -> bool {
let res: LoggedResult<()> = try {
if check_proto() {
let mut props = proto_read_props()?;
let idx = props.find_index(name).silent()?;
props.remove(idx);
proto_write_props(&props)?;
} else {
file_set_prop(name, None)?;
}
};
res.is_ok()
}
pub fn persist_set_prop(name: &Utf8CStr, value: &Utf8CStr) -> bool {
let res: LoggedResult<()> = try {
if check_proto() {
let mut props = proto_read_props()?;
match props.find_index(name) {
Ok(idx) => props[idx].value = Some(value.to_string()),
Err(idx) => props.insert(
idx,
PersistentPropertyRecord {
name: Some(name.to_string()),
value: Some(value.to_string()),
},
),
});
} else {
let mut dir = Directory::open(cstr!(PERSIST_PROP_DIR))?;
dir.pre_order_walk(|e| {
if e.is_file()
&& let Ok(value) = file_get_prop(e.name())
{
reader.put_str(e.name().to_string(), value, 0);
}
proto_write_props(&props)?;
} else {
file_set_prop(name, Some(value))?;
}
};
res.is_ok()
// Do not traverse recursively
Ok(WalkResult::Skip)
})?;
}
Ok(())
}
pub(super) fn persist_delete_prop(key: &Utf8CStr) -> LoggedResult<()> {
if check_proto() {
let mut props = proto_read_props()?;
let idx = props.find_index(key).silent()?;
props.properties.remove(idx);
proto_write_props(&props)?;
} else {
file_set_prop(key, None)?;
}
Ok(())
}
pub(super) fn persist_set_prop(key: &Utf8CStr, val: &Utf8CStr) -> LoggedResult<()> {
if check_proto() {
let mut props = proto_read_props()?;
match props.find_index(key) {
Ok(idx) => props.properties[idx].value = Some(val.to_string()),
Err(idx) => props.properties.insert(
idx,
PersistentPropertyRecord {
name: Some(key.to_string()),
value: Some(val.to_string()),
},
),
}
proto_write_props(&props)?;
} else {
file_set_prop(key, Some(val))?;
}
Ok(())
}

View File

@@ -1,459 +0,0 @@
#include <dlfcn.h>
#include <sys/types.h>
#include <vector>
#include <map>
#include <base.hpp>
#include <core.hpp>
#include <resetprop.hpp>
#include <api/system_properties.h>
#include <system_properties/prop_info.h>
using namespace std;
#ifdef APPLET_STUB_MAIN
#define system_property_set __system_property_set
#define system_property_read(...)
#define system_property_find __system_property_find
#define system_property_read_callback __system_property_read_callback
#define system_property_foreach __system_property_foreach
#define system_property_wait __system_property_wait
#else
static int (*system_property_set)(const char*, const char*);
static int (*system_property_read)(const prop_info*, char*, char*);
static const prop_info *(*system_property_find)(const char*);
static void (*system_property_read_callback)(
const prop_info*, void (*)(void*, const char*, const char*, uint32_t), void*);
static int (*system_property_foreach)(void (*)(const prop_info*, void*), void*);
static bool (*system_property_wait)(const prop_info*, uint32_t, uint32_t*, const struct timespec*);
#endif
struct PropFlags {
void setSkipSvc() { flags |= 1; }
void setPersist() { flags |= (1 << 1); }
void setContext() { flags |= (1 << 2); }
void setPersistOnly() { flags |= (1 << 3); setPersist(); }
void setWait() { flags |= (1 << 4); }
bool isSkipSvc() const { return flags & 1; }
bool isPersist() const { return flags & (1 << 1); }
bool isContext() const { return flags & (1 << 2); }
bool isPersistOnly() const { return flags & (1 << 3); }
bool isWait() const { return flags & (1 << 4); }
private:
uint32_t flags = 0;
};
[[noreturn]] static void usage(char* arg0) {
fprintf(stderr,
R"EOF(resetprop - System Property Manipulation Tool
Usage: %s [flags] [arguments...]
Read mode arguments:
(no arguments) print all properties
NAME get property of NAME
Write mode arguments:
NAME VALUE set property NAME as VALUE
-f,--file FILE load and set properties from FILE
-d,--delete NAME delete property
Wait mode arguments (toggled with -w):
NAME wait until property NAME changes
NAME OLD_VALUE if value of property NAME is not OLD_VALUE, get value
or else wait until property NAME changes
General flags:
-h,--help show this message
-v print verbose output to stderr
-w switch to wait mode
Read mode flags:
-p also read persistent props from storage
-P only read persistent props from storage
-Z get property context instead of value
Write mode flags:
-n set properties bypassing property_service
-p always write persistent prop changes to storage
)EOF", arg0);
exit(1);
}
static bool check_legal_property_name(const char *name) {
int namelen = strlen(name);
if (namelen < 1) goto illegal;
if (name[0] == '.') goto illegal;
if (name[namelen - 1] == '.') goto illegal;
/* Only allow alphanumeric, plus '.', '-', '@', ':', or '_' */
/* Don't allow ".." to appear in a property name */
for (size_t i = 0; i < namelen; i++) {
if (name[i] == '.') {
// i=0 is guaranteed to never have a dot. See above.
if (name[i-1] == '.') goto illegal;
continue;
}
if (name[i] == '_' || name[i] == '-' || name[i] == '@' || name[i] == ':') continue;
if (name[i] >= 'a' && name[i] <= 'z') continue;
if (name[i] >= 'A' && name[i] <= 'Z') continue;
if (name[i] >= '0' && name[i] <= '9') continue;
goto illegal;
}
return true;
illegal:
LOGE("Illegal property name: [%s]\n", name);
return false;
}
static void read_prop_with_cb(const prop_info *pi, void *cb) {
if (system_property_read_callback) {
auto callback = [](void *cb, const char *name, const char *value, uint32_t serial) {
static_cast<prop_cb*>(cb)->exec(name, value, serial);
};
system_property_read_callback(pi, callback, cb);
} else {
char name[PROP_NAME_MAX];
char value[PROP_VALUE_MAX];
name[0] = '\0';
value[0] = '\0';
system_property_read(pi, name, value);
static_cast<prop_cb*>(cb)->exec(name, value, pi->serial);
}
}
template<class StringType>
struct prop_to_string : prop_cb {
void exec(const char *, const char *value, uint32_t s) override {
val = value;
serial = s;
}
StringType val;
uint32_t serial;
};
template<> void prop_to_string<rust::String>::exec(const char *, const char *value, uint32_t s) {
// We do not want to crash when values are not UTF-8
val = rust::String::lossy(value);
serial = s;
}
static int set_prop(const char *name, const char *value, PropFlags flags) {
if (!check_legal_property_name(name))
return 1;
auto pi = const_cast<prop_info *>(__system_property_find(name));
// Delete existing read-only properties if they are or will be long properties,
// which cannot directly go through __system_property_update
if (str_starts(name, "ro.")) {
if (pi != nullptr && (pi->is_long() || strlen(value) >= PROP_VALUE_MAX)) {
// Skip pruning nodes as we will add it back ASAP
__system_property_delete(name, false);
pi = nullptr;
}
flags.setSkipSvc();
}
const char *msg = flags.isSkipSvc() ? "direct modification" : "property_service";
int ret;
if (pi != nullptr) {
if (flags.isSkipSvc()) {
ret = __system_property_update(pi, value, strlen(value));
} else {
ret = system_property_set(name, value);
}
LOGD("resetprop: update prop [%s]: [%s] by %s\n", name, value, msg);
} else {
if (flags.isSkipSvc()) {
ret = __system_property_add(name, strlen(name), value, strlen(value));
} else {
ret = system_property_set(name, value);
}
LOGD("resetprop: create prop [%s]: [%s] by %s\n", name, value, msg);
}
// When bypassing property_service, persistent props won't be stored in storage.
// Explicitly handle this situation.
if (ret == 0 && flags.isSkipSvc() && flags.isPersist() && str_starts(name, "persist.")) {
ret = persist_set_prop(name, value) ? 0 : 1;
}
if (ret) {
LOGW("resetprop: set prop error\n");
}
return ret;
}
template<class StringType>
static StringType get_prop(const char *name, PropFlags flags) {
if (!check_legal_property_name(name))
return {};
prop_to_string<StringType> cb;
if (flags.isContext()) {
auto context = __system_property_get_context(name) ?: "";
LOGD("resetprop: prop context [%s]: [%s]\n", name, context);
cb.exec(name, context, -1);
return cb.val;
}
if (!flags.isPersistOnly()) {
if (auto pi = system_property_find(name)) {
read_prop_with_cb(pi, &cb);
LOGD("resetprop: get prop [%s]: [%s]\n", name, cb.val.c_str());
}
}
if (cb.val.empty() && flags.isPersist() && str_starts(name, "persist."))
persist_get_prop(name, cb);
if (cb.val.empty())
LOGD("resetprop: prop [%s] does not exist\n", name);
return cb.val;
}
template<class StringType>
static StringType wait_prop(const char *name, const char *old_value) {
if (!check_legal_property_name(name))
return {};
const prop_info *pi;
auto serial = __system_property_area_serial();
while (!(pi = system_property_find(name))) {
LOGD("resetprop: waiting for prop [%s] to exist\n", name);
system_property_wait(nullptr, serial, &serial, nullptr);
}
prop_to_string<StringType> cb;
read_prop_with_cb(pi, &cb);
while (old_value == nullptr || cb.val == old_value) {
LOGD("resetprop: waiting for prop [%s]\n", name);
uint32_t new_serial;
system_property_wait(pi, cb.serial, &new_serial, nullptr);
read_prop_with_cb(pi, &cb);
if (old_value == nullptr) break;
}
LOGD("resetprop: get prop [%s]: [%s]\n", name, cb.val.c_str());
return cb.val;
}
static void print_props(PropFlags flags) {
prop_list list;
prop_collector collector(list);
if (!flags.isPersistOnly())
system_property_foreach(read_prop_with_cb, &collector);
if (flags.isPersist())
persist_get_props(collector);
for (auto &[key, val] : list) {
const char *v = flags.isContext() ?
(__system_property_get_context(key.data()) ?: "") :
val.data();
printf("[%s]: [%s]\n", key.data(), v);
}
}
static int delete_prop(const char *name, PropFlags flags) {
if (!check_legal_property_name(name))
return 1;
LOGD("resetprop: delete prop [%s]\n", name);
int ret = __system_property_delete(name, true);
if (flags.isPersist() && str_starts(name, "persist.")) {
if (persist_delete_prop(name))
ret = 0;
}
return ret;
}
static void load_file(const char *filename, PropFlags flags) {
LOGD("resetprop: Parse prop file [%s]\n", filename);
parse_prop_file(filename, [=](auto key, auto val) -> bool {
set_prop(key.data(), val.data(), flags);
return true;
});
}
struct Initialize {
Initialize() {
#ifndef APPLET_STUB_MAIN
#define DLOAD(name) (*(void **) &name = dlsym(RTLD_DEFAULT, "__" #name))
// Load platform implementations
DLOAD(system_property_set);
DLOAD(system_property_read);
DLOAD(system_property_find);
DLOAD(system_property_read_callback);
DLOAD(system_property_foreach);
DLOAD(system_property_wait);
#undef DLOAD
if (system_property_wait == nullptr) {
// The platform API only exist on API 26+
system_property_wait = __system_property_wait;
}
#endif
if (__system_properties_init()) {
LOGE("resetprop: __system_properties_init error\n");
}
}
};
static void InitOnce() {
static struct Initialize init;
}
#define consume_next(val) \
if (argc != 2) usage(argv0); \
val = argv[1]; \
stop_parse = true; \
int resetprop_main(int argc, char *argv[]) {
PropFlags flags;
char *argv0 = argv[0];
set_log_level_state(LogLevel::Debug, false);
const char *prop_file = nullptr;
const char *prop_to_rm = nullptr;
--argc;
++argv;
// Parse flags and -- options
while (argc && argv[0][0] == '-') {
bool stop_parse = false;
for (int idx = 1; true; ++idx) {
switch (argv[0][idx]) {
case '-':
if (argv[0] == "--file"sv) {
consume_next(prop_file);
} else if (argv[0] == "--delete"sv) {
consume_next(prop_to_rm);
} else {
usage(argv0);
}
break;
case 'd':
consume_next(prop_to_rm);
continue;
case 'f':
consume_next(prop_file);
continue;
case 'n':
flags.setSkipSvc();
continue;
case 'p':
flags.setPersist();
continue;
case 'P':
flags.setPersistOnly();
continue;
case 'v':
set_log_level_state(LogLevel::Debug, true);
continue;
case 'Z':
flags.setContext();
continue;
case 'w':
flags.setWait();
continue;
case '\0':
break;
default:
usage(argv0);
}
break;
}
--argc;
++argv;
if (stop_parse)
break;
}
InitOnce();
if (prop_to_rm) {
return delete_prop(prop_to_rm, flags);
}
if (prop_file) {
load_file(prop_file, flags);
return 0;
}
if (flags.isWait()) {
if (argc == 0) usage(argv0);
auto val = wait_prop<string>(argv[0], argv[1]);
if (val.empty())
return 1;
printf("%s\n", val.data());
return 0;
}
switch (argc) {
case 0:
print_props(flags);
return 0;
case 1: {
auto val = get_prop<string>(argv[0], flags);
if (val.empty())
return 1;
printf("%s\n", val.data());
return 0;
}
case 2:
return set_prop(argv[0], argv[1], flags);
default:
usage(argv0);
}
}
/***************
* Public APIs
****************/
template<class StringType>
static StringType get_prop_impl(const char *name, bool persist) {
InitOnce();
PropFlags flags;
if (persist) flags.setPersist();
return get_prop<StringType>(name, flags);
}
rust::String get_prop_rs(rust::Utf8CStr name, bool persist) {
return get_prop_impl<rust::String>(name.data(), persist);
}
string get_prop(const char *name, bool persist) {
return get_prop_impl<string>(name, persist);
}
int delete_prop(const char *name, bool persist) {
InitOnce();
PropFlags flags;
if (persist) flags.setPersist();
return delete_prop(name, flags);
}
int set_prop(const char *name, const char *value, bool skip_svc) {
InitOnce();
PropFlags flags;
if (skip_svc) flags.setSkipSvc();
return set_prop(name, value, flags);
}
void load_prop_file(const char *filename, bool skip_svc) {
InitOnce();
PropFlags flags;
if (skip_svc) flags.setSkipSvc();
load_file(filename, flags);
}

View File

@@ -0,0 +1,55 @@
#include <dlfcn.h>
#include <base.hpp>
#include <core.hpp>
#include <api/system_properties.h>
#include <system_properties/prop_info.h>
using namespace std;
// This has to keep in sync with SysProp in mod.rs
struct SysProp {
int (*set)(const char*, const char*);
const prop_info *(*find)(const char*);
void (*read_callback)(const prop_info*, void (*)(void*, const char*, const char*, uint32_t), void*);
int (*foreach)(void (*)(const prop_info*, void*), void*);
bool (*wait)(const prop_info*, uint32_t, uint32_t*, const timespec*);
};
extern "C" bool prop_info_is_long(const prop_info &info) {
return info.is_long();
}
extern "C" SysProp get_sys_prop() {
SysProp prop{};
#ifdef APPLET_STUB_MAIN
// Use internal implementation
prop.set = __system_property_set;
prop.find = __system_property_find;
prop.read_callback = __system_property_read_callback;
prop.foreach = __system_property_foreach;
prop.wait = __system_property_wait;
#else
#define DLOAD(name) (*(void **) &prop.name = dlsym(RTLD_DEFAULT, "__system_property_" #name))
// Dynamic load platform implementation
DLOAD(set);
DLOAD(find);
DLOAD(read_callback);
DLOAD(foreach);
DLOAD(wait);
#undef DLOAD
if (prop.wait == nullptr) {
// This platform API only exist on API 26+
prop.wait = __system_property_wait;
}
if (prop.read_callback == nullptr) {
// This platform API only exist on API 26+
prop.read_callback = __system_property_read_callback;
}
#endif
if (__system_properties_init()) {
LOGE("resetprop: __system_properties_init error\n");
}
return prop;
}

View File

@@ -29,12 +29,12 @@ static void set_script_env() {
setenv("ZYGISK_ENABLED", "1", 1);
};
void exec_script(const char *script) {
void exec_script(Utf8CStr script) {
exec_t exec {
.pre_exec = set_script_env,
.fork = fork_no_orphan
};
exec_command_sync(exec, BBEXEC_CMD, script);
exec_command_sync(exec, BBEXEC_CMD, script.c_str());
}
static timespec pfs_timeout;
@@ -74,7 +74,7 @@ if (pfs) { \
exit(0); \
}
void exec_common_scripts(rust::Utf8CStr stage) {
void exec_common_scripts(Utf8CStr stage) {
LOGI("* Running %s.d scripts\n", stage.c_str());
char path[4096];
char *name = path + sprintf(path, SECURE_DIR "/%s.d", stage.c_str());
@@ -116,12 +116,12 @@ static bool operator>(const timespec &a, const timespec &b) {
return a.tv_nsec > b.tv_nsec;
}
void exec_module_scripts(rust::Utf8CStr stage, const rust::Vec<ModuleInfo> &module_list) {
void exec_module_scripts(Utf8CStr stage, const rust::Vec<ModuleInfo> &module_list) {
LOGI("* Running module %s scripts\n", stage.c_str());
if (module_list.empty())
return;
bool pfs = (string_view) stage == "post-fs-data";
bool pfs = stage == "post-fs-data";
if (pfs) {
timespec now{};
clock_gettime(CLOCK_MONOTONIC, &now);
@@ -157,7 +157,7 @@ appops set %s REQUEST_INSTALL_PACKAGES allow
rm -f $APK
)EOF";
void install_apk(rust::Utf8CStr apk) {
void install_apk(Utf8CStr apk) {
setfilecon(apk.c_str(), MAGISK_FILE_CON);
char cmds[sizeof(install_script) + 4096];
ssprintf(cmds, sizeof(cmds), install_script, apk.c_str(), JAVA_PACKAGE_NAME);
@@ -170,7 +170,7 @@ log -t Magisk "pm_uninstall: $PKG"
log -t Magisk "pm_uninstall: $(pm uninstall $PKG 2>&1)"
)EOF";
void uninstall_pkg(rust::Utf8CStr pkg) {
void uninstall_pkg(Utf8CStr pkg) {
char cmds[sizeof(uninstall_script) + 256];
ssprintf(cmds, sizeof(cmds), uninstall_script, pkg.c_str());
exec_command_async("/system/bin/sh", "-c", cmds);
@@ -205,17 +205,17 @@ install_module
exit 0
)EOF";
void install_module(const char *file) {
void install_module(Utf8CStr file) {
if (getuid() != 0)
abort(stderr, "Run this command with root");
if (access(DATABIN, F_OK) ||
access(bbpath(), X_OK) ||
access(DATABIN "/util_functions.sh", F_OK))
abort(stderr, "Incomplete Magisk install");
if (access(file, F_OK))
abort(stderr, "'%s' does not exist", file);
if (access(file.c_str(), F_OK))
abort(stderr, "'%s' does not exist", file.c_str());
char *zip = realpath(file, nullptr);
char *zip = realpath(file.c_str(), nullptr);
setenv("OUTFD", "1", 1);
setenv("ZIPFILE", zip, 1);
setenv("ASH_STANDALONE", "1", 1);

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