mirror of
https://github.com/topjohnwu/Magisk.git
synced 2025-12-11 10:42:19 +00:00
Compare commits
113 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f16e93c7db | ||
|
|
1b0ddec66e | ||
|
|
cd8820f563 | ||
|
|
b70192ca3e | ||
|
|
d42ec5da9a | ||
|
|
742913ebcb | ||
|
|
ed206c6480 | ||
|
|
f9a8052583 | ||
|
|
f4fdd516f9 | ||
|
|
5925a71f94 | ||
|
|
3cda9beb93 | ||
|
|
8b7d1ffcdd | ||
|
|
8d02d0632e | ||
|
|
dd743f6f7e | ||
|
|
cf483ad4d2 | ||
|
|
4aed644e08 | ||
|
|
0acc39cec0 | ||
|
|
8b3a44344f | ||
|
|
8b49eda85a | ||
|
|
7057d4c7f1 | ||
|
|
aab8344058 | ||
|
|
7cccf83b37 | ||
|
|
f10ad93c4e | ||
|
|
f143b5df15 | ||
|
|
71213cc6f4 | ||
|
|
e2a1774e5b | ||
|
|
0222527a1e | ||
|
|
312bfe1bab | ||
|
|
48c62a1dae | ||
|
|
cfc2bcb665 | ||
|
|
94b1ff674f | ||
|
|
111136733a | ||
|
|
c8caaa98f5 | ||
|
|
8d28f10a3f | ||
|
|
177a456d8b | ||
|
|
ef4e230258 | ||
|
|
17082af438 | ||
|
|
1df5b34175 | ||
|
|
ea5fe7525d | ||
|
|
a75c335261 | ||
|
|
3903f42cf6 | ||
|
|
fb0c4ea838 | ||
|
|
bc89c60977 | ||
|
|
bd657c354c | ||
|
|
675b5f9565 | ||
|
|
1b2c43268e | ||
|
|
653730d75e | ||
|
|
d472e9c36e | ||
|
|
484d53ef7e | ||
|
|
c4e2985677 | ||
|
|
42d9f87bc9 | ||
|
|
2e4fa6864c | ||
|
|
e2abb648ac | ||
|
|
3599dcedfb | ||
|
|
ea72666df8 | ||
|
|
bd2a47ba18 | ||
|
|
b861671391 | ||
|
|
e91fc75d86 | ||
|
|
78f5cd55c7 | ||
|
|
9787a69528 | ||
|
|
87b8fe374d | ||
|
|
7b706bb0cb | ||
|
|
c1491b8d2b | ||
|
|
5cbaf2ae11 | ||
|
|
8ebc6207b4 | ||
|
|
7848ee616b | ||
|
|
fd193c3cae | ||
|
|
36d33c7a85 | ||
|
|
5caf28d27c | ||
|
|
2c39d0234d | ||
|
|
c313812129 | ||
|
|
af51880a81 | ||
|
|
db8d832707 | ||
|
|
8dc23d0ead | ||
|
|
b4287700d5 | ||
|
|
8d10ab89f2 | ||
|
|
49fdc1addb | ||
|
|
1333d3b986 | ||
|
|
335146a6a2 | ||
|
|
eaf9527971 | ||
|
|
da937a88c8 | ||
|
|
9476e7282d | ||
|
|
251c3c3e0e | ||
|
|
cd0eca20b0 | ||
|
|
6839cb9ab2 | ||
|
|
d11a3397d8 | ||
|
|
975120d6a6 | ||
|
|
e489b3b6dd | ||
|
|
589a270b8d | ||
|
|
7961be5cfa | ||
|
|
959430e030 | ||
|
|
2923c8ccd1 | ||
|
|
7df4a9d74f | ||
|
|
bf4ed295da | ||
|
|
a5fca960dc | ||
|
|
f99912b9db | ||
|
|
a54bdb54e4 | ||
|
|
cd9851a1fe | ||
|
|
9ca469898c | ||
|
|
0665549473 | ||
|
|
9d7a14b335 | ||
|
|
62e29fee74 | ||
|
|
e472db552b | ||
|
|
466e4bd4e1 | ||
|
|
4cf525c588 | ||
|
|
c8aec2510d | ||
|
|
ccbfe0e66e | ||
|
|
23ea28de6f | ||
|
|
55c3ee3a6f | ||
|
|
2a42ca2b8f | ||
|
|
a897e82fa4 | ||
|
|
ffa15831d3 | ||
|
|
a344ebf28c |
2
.github/actions/setup/action.yml
vendored
2
.github/actions/setup/action.yml
vendored
@@ -45,7 +45,7 @@ runs:
|
||||
env:
|
||||
SCCACHE_DIRECT: false
|
||||
SCCACHE_DIR: ${{ github.workspace }}/.sccache
|
||||
SCCACHE_CACHE_SIZE: 2G
|
||||
SCCACHE_CACHE_SIZE: ${{ inputs.is-asset-build == 'true' && '2G' || '300M' }}
|
||||
SCCACHE_IDLE_TIMEOUT: 0
|
||||
run: |
|
||||
bash $GITHUB_ACTION_PATH/sccache.sh
|
||||
|
||||
12
.github/workflows/build.yml
vendored
12
.github/workflows/build.yml
vendored
@@ -82,12 +82,10 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
version: [23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35]
|
||||
version: [23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, "CANARY"]
|
||||
type: [""]
|
||||
include:
|
||||
- version: 36
|
||||
type: "google_apis"
|
||||
- version: 36
|
||||
- version: "CANARY"
|
||||
type: "google_apis_ps16k"
|
||||
|
||||
steps:
|
||||
@@ -107,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 }}
|
||||
@@ -148,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
|
||||
@@ -193,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
3
.gitmodules
vendored
@@ -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
|
||||
|
||||
@@ -22,11 +22,11 @@ import com.topjohnwu.magisk.core.ktx.activity
|
||||
import com.topjohnwu.magisk.core.ktx.toast
|
||||
import com.topjohnwu.magisk.core.tasks.AppMigration
|
||||
import com.topjohnwu.magisk.core.utils.LocaleSetting
|
||||
import com.topjohnwu.magisk.core.utils.RootUtils
|
||||
import com.topjohnwu.magisk.databinding.bindExtra
|
||||
import com.topjohnwu.magisk.events.AddHomeIconEvent
|
||||
import com.topjohnwu.magisk.events.AuthEvent
|
||||
import com.topjohnwu.magisk.events.SnackbarEvent
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class SettingsViewModel : BaseViewModel(), BaseSettingsItem.Handler {
|
||||
@@ -130,7 +130,8 @@ class SettingsViewModel : BaseViewModel(), BaseSettingsItem.Handler {
|
||||
}
|
||||
|
||||
private fun createHosts() {
|
||||
Shell.cmd("add_hosts_module").submit {
|
||||
viewModelScope.launch {
|
||||
RootUtils.addSystemlessHosts()
|
||||
AppContext.toast(R.string.settings_hosts_toast, Toast.LENGTH_SHORT)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -18,12 +18,6 @@ gradlePlugin {
|
||||
}
|
||||
}
|
||||
|
||||
kotlin {
|
||||
compilerOptions {
|
||||
languageVersion = KotlinVersion.KOTLIN_2_0
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(kotlin("gradle-plugin", libs.versions.kotlin.get()))
|
||||
implementation(libs.android.gradle.plugin)
|
||||
|
||||
@@ -55,7 +55,7 @@ fun Project.setupCommon() {
|
||||
compileSdkVersion(36)
|
||||
buildToolsVersion = "36.0.0"
|
||||
ndkPath = "$sdkDirectory/ndk/magisk"
|
||||
ndkVersion = "28.1.13356709"
|
||||
ndkVersion = "29.0.13846066"
|
||||
|
||||
defaultConfig {
|
||||
minSdk = 23
|
||||
|
||||
@@ -309,9 +309,9 @@ fun Project.setupStubApk() {
|
||||
outputs.dir(outResDir)
|
||||
doLast {
|
||||
val apkTmp = File("${apk}.tmp")
|
||||
exec {
|
||||
providers.exec {
|
||||
commandLine(aapt, "optimize", "-o", apkTmp, "--collapse-resource-names", apk)
|
||||
}
|
||||
}.result.get()
|
||||
|
||||
val bos = ByteArrayOutputStream()
|
||||
ZipFile(apkTmp).use { src ->
|
||||
|
||||
@@ -27,10 +27,15 @@ android {
|
||||
aidl = true
|
||||
buildConfig = true
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
isCoreLibraryDesugaringEnabled = true
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api(project(":shared"))
|
||||
coreLibraryDesugaring(libs.jdk.libs)
|
||||
|
||||
api(libs.timber)
|
||||
api(libs.markwon.core)
|
||||
|
||||
1
app/core/proguard-rules.pro
vendored
1
app/core/proguard-rules.pro
vendored
@@ -38,3 +38,4 @@
|
||||
-allowaccessmodification
|
||||
|
||||
-dontwarn org.junit.**
|
||||
-dontwarn org.apache.**
|
||||
|
||||
@@ -6,4 +6,5 @@ package com.topjohnwu.magisk.core.utils;
|
||||
interface IRootUtils {
|
||||
android.app.ActivityManager.RunningAppProcessInfo getAppProcess(int pid);
|
||||
IBinder getFileSystem();
|
||||
boolean addSystemlessHosts();
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -109,7 +109,7 @@ fun PackageManager.getPackageInfo(uid: Int, pid: Int): PackageInfo? {
|
||||
return null
|
||||
}
|
||||
// Try to find package name from PID
|
||||
val proc = RootUtils.obj?.getAppProcess(pid)
|
||||
val proc = RootUtils.getAppProcess(pid)
|
||||
if (proc == null) {
|
||||
if (uid == Process.SHELL_UID) {
|
||||
// It is possible that some apps installed are sharing UID with shell.
|
||||
|
||||
@@ -14,10 +14,11 @@ import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.lang.reflect.Field
|
||||
import java.text.DateFormat
|
||||
import java.text.SimpleDateFormat
|
||||
import java.time.Instant
|
||||
import java.time.ZoneId
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.time.format.FormatStyle
|
||||
import java.util.Collections
|
||||
import java.util.Locale
|
||||
|
||||
inline fun <In : Closeable, Out : Closeable> withInOut(
|
||||
input: In,
|
||||
@@ -83,19 +84,15 @@ inline fun <T, R> Flow<T>.concurrentMap(crossinline transform: suspend (T) -> R)
|
||||
}
|
||||
}
|
||||
|
||||
fun Long.toTime(format: DateFormat) = format.format(this).orEmpty()
|
||||
fun Long.toTime(format: DateTimeFormatter): String = format.format(Instant.ofEpochMilli(this))
|
||||
|
||||
// Some devices don't allow filenames containing ":"
|
||||
val timeFormatStandard by lazy {
|
||||
SimpleDateFormat(
|
||||
"yyyy-MM-dd'T'HH.mm.ss",
|
||||
Locale.ROOT
|
||||
)
|
||||
val timeFormatStandard: DateTimeFormatter by lazy {
|
||||
DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH.mm.ss").withZone(ZoneId.systemDefault())
|
||||
}
|
||||
val timeDateFormat: DateFormat by lazy {
|
||||
DateFormat.getDateTimeInstance(
|
||||
DateFormat.DEFAULT,
|
||||
DateFormat.DEFAULT,
|
||||
Locale.ROOT
|
||||
)
|
||||
val timeDateFormat: DateTimeFormatter by lazy {
|
||||
DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM).withZone(ZoneId.systemDefault())
|
||||
}
|
||||
val dateFormat: DateTimeFormatter by lazy {
|
||||
DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT).withZone(ZoneId.systemDefault())
|
||||
}
|
||||
|
||||
@@ -7,8 +7,7 @@ import com.squareup.moshi.JsonClass
|
||||
import com.squareup.moshi.JsonQualifier
|
||||
import com.squareup.moshi.ToJson
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import java.time.LocalDateTime
|
||||
import java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME
|
||||
import java.time.Instant
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
class UpdateJson(
|
||||
@@ -40,13 +39,13 @@ data class ReleaseAssets(
|
||||
|
||||
class DateTimeAdapter {
|
||||
@ToJson
|
||||
fun toJson(date: LocalDateTime): String {
|
||||
fun toJson(date: Instant): String {
|
||||
return date.toString()
|
||||
}
|
||||
|
||||
@FromJson
|
||||
fun fromJson(date: String): LocalDateTime {
|
||||
return LocalDateTime.parse(date, ISO_OFFSET_DATE_TIME)
|
||||
fun fromJson(date: String): Instant {
|
||||
return Instant.parse(date)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,7 +56,7 @@ data class Release(
|
||||
val prerelease: Boolean,
|
||||
val assets: List<ReleaseAssets>,
|
||||
val body: String,
|
||||
@Json(name = "created_at") val createdTime: LocalDateTime,
|
||||
@Json(name = "created_at") val createdTime: Instant,
|
||||
) {
|
||||
val versionCode: Int get() {
|
||||
return if (tag[0] == 'v') {
|
||||
|
||||
@@ -13,7 +13,7 @@ import java.io.IOException
|
||||
import java.util.Locale
|
||||
|
||||
data class LocalModule(
|
||||
private val base: ExtendedFile,
|
||||
val base: ExtendedFile,
|
||||
) : Module() {
|
||||
private val svc get() = ServiceLocator.networkService
|
||||
|
||||
|
||||
@@ -10,13 +10,13 @@ import com.topjohnwu.magisk.core.Config.Value.STABLE_CHANNEL
|
||||
import com.topjohnwu.magisk.core.Info
|
||||
import com.topjohnwu.magisk.core.data.GithubApiServices
|
||||
import com.topjohnwu.magisk.core.data.RawUrl
|
||||
import com.topjohnwu.magisk.core.ktx.dateFormat
|
||||
import com.topjohnwu.magisk.core.model.Release
|
||||
import com.topjohnwu.magisk.core.model.ReleaseAssets
|
||||
import com.topjohnwu.magisk.core.model.UpdateInfo
|
||||
import retrofit2.HttpException
|
||||
import timber.log.Timber
|
||||
import java.io.IOException
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
class NetworkService(
|
||||
private val raw: RawUrl,
|
||||
@@ -74,7 +74,7 @@ class NetworkService(
|
||||
|
||||
private inline fun Release.asPublicInfo(selector: (ReleaseAssets) -> Boolean): UpdateInfo {
|
||||
val version = tag.drop(1)
|
||||
val date = createdTime.format(DateTimeFormatter.ofPattern("yyyy.M.d"))
|
||||
val date = dateFormat.format(createdTime)
|
||||
return UpdateInfo(
|
||||
version = version,
|
||||
versionCode = versionCode,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -7,11 +7,14 @@ import android.content.ServiceConnection
|
||||
import android.os.IBinder
|
||||
import android.system.Os
|
||||
import androidx.core.content.getSystemService
|
||||
import com.topjohnwu.magisk.core.Const
|
||||
import com.topjohnwu.magisk.core.Info
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import com.topjohnwu.superuser.ShellUtils
|
||||
import com.topjohnwu.superuser.ipc.RootService
|
||||
import com.topjohnwu.superuser.nio.FileSystemManager
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
import java.util.concurrent.locks.AbstractQueuedSynchronizer
|
||||
@@ -43,16 +46,7 @@ class RootUtils(stub: Any?) : RootService() {
|
||||
return object : IRootUtils.Stub() {
|
||||
override fun getAppProcess(pid: Int) = safe(null) { getAppProcessImpl(pid) }
|
||||
override fun getFileSystem(): IBinder = FileSystemManager.getService()
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun <T> safe(default: T, block: () -> T): T {
|
||||
return try {
|
||||
block()
|
||||
} catch (e: Throwable) {
|
||||
// The process died unexpectedly
|
||||
Timber.e(e)
|
||||
default
|
||||
override fun addSystemlessHosts() = safe(false) { addSystemlessHostsImpl() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,6 +72,26 @@ class RootUtils(stub: Any?) : RootService() {
|
||||
return null
|
||||
}
|
||||
|
||||
private fun addSystemlessHostsImpl(): Boolean {
|
||||
val module = File(Const.MODULE_PATH, "hosts")
|
||||
if (module.exists()) return true
|
||||
val hosts = File(module, "system/etc/hosts")
|
||||
if (!hosts.parentFile.mkdirs()) return false
|
||||
File(module, "module.prop").outputStream().writer().use {
|
||||
it.write("""
|
||||
id=hosts
|
||||
name=Systemless Hosts
|
||||
version=1.0
|
||||
versionCode=1
|
||||
author=Magisk
|
||||
description=Magisk app built-in systemless hosts module
|
||||
""".trimIndent())
|
||||
}
|
||||
File("/system/etc/hosts").copyTo(hosts)
|
||||
File(module, "update").createNewFile()
|
||||
return true
|
||||
}
|
||||
|
||||
object Connection : AbstractQueuedSynchronizer(), ServiceConnection {
|
||||
init {
|
||||
state = 1
|
||||
@@ -131,11 +145,25 @@ class RootUtils(stub: Any?) : RootService() {
|
||||
return field
|
||||
}
|
||||
private set
|
||||
var obj: IRootUtils? = null
|
||||
private var obj: IRootUtils? = null
|
||||
get() {
|
||||
Connection.await()
|
||||
return field
|
||||
}
|
||||
private set
|
||||
|
||||
fun getAppProcess(pid: Int) = safe(null) { obj?.getAppProcess(pid) }
|
||||
|
||||
suspend fun addSystemlessHosts() =
|
||||
withContext(Dispatchers.IO) { safe(false) { obj?.addSystemlessHosts() ?: false } }
|
||||
|
||||
private inline fun <T> safe(default: T, block: () -> T): T {
|
||||
return try {
|
||||
block()
|
||||
} catch (e: Throwable) {
|
||||
// The process died unexpectedly
|
||||
Timber.e(e)
|
||||
default
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,9 +12,11 @@ import com.topjohnwu.magisk.test.Environment.Companion.INVALID_ZYGISK
|
||||
import com.topjohnwu.magisk.test.Environment.Companion.MOUNT_TEST
|
||||
import com.topjohnwu.magisk.test.Environment.Companion.REMOVE_TEST
|
||||
import com.topjohnwu.magisk.test.Environment.Companion.SEPOLICY_RULE
|
||||
import com.topjohnwu.magisk.test.Environment.Companion.UPGRADE_TEST
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertArrayEquals
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertNotNull
|
||||
@@ -55,7 +57,7 @@ class AdditionalTest : BaseTest {
|
||||
|
||||
@Test
|
||||
fun testModuleCount() {
|
||||
var expected = 3
|
||||
var expected = 4
|
||||
if (Environment.mount()) expected++
|
||||
if (Environment.preinit()) expected++
|
||||
if (Environment.lsposed()) expected++
|
||||
@@ -98,9 +100,10 @@ class AdditionalTest : BaseTest {
|
||||
RootUtils.fs.getFile("/system/bin/screenrecord").exists()
|
||||
)
|
||||
val egg = RootUtils.fs.getFile("/system/app/EasterEgg").list() ?: arrayOf()
|
||||
assertTrue(
|
||||
"/system/app/EasterEgg should be empty",
|
||||
egg.isEmpty()
|
||||
assertArrayEquals(
|
||||
"/system/app/EasterEgg should be replaced",
|
||||
egg,
|
||||
arrayOf("newfile")
|
||||
)
|
||||
}
|
||||
|
||||
@@ -134,5 +137,25 @@ class AdditionalTest : BaseTest {
|
||||
@Test
|
||||
fun testRemoveModule() {
|
||||
assertNull("$REMOVE_TEST should be removed", modules.find { it.id == REMOVE_TEST })
|
||||
assertTrue(
|
||||
"Uninstaller of $REMOVE_TEST should be run",
|
||||
RootUtils.fs.getFile(Environment.REMOVE_TEST_MARKER).exists()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testModuleUpgrade() {
|
||||
val module = modules.find { it.id == UPGRADE_TEST }
|
||||
assertNotNull("$UPGRADE_TEST is not installed", module)
|
||||
module!!
|
||||
assertFalse("$UPGRADE_TEST should be disabled", module.enable)
|
||||
assertTrue(
|
||||
"$UPGRADE_TEST should be updated",
|
||||
module.base.getChildFile("post-fs-data.sh").exists()
|
||||
)
|
||||
assertFalse(
|
||||
"$UPGRADE_TEST should be updated",
|
||||
module.base.getChildFile("service.sh").exists()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,12 +58,15 @@ class Environment : BaseTest {
|
||||
return Build.VERSION.SDK_INT >= 27
|
||||
}
|
||||
|
||||
private const val MODULE_UPDATE_PATH = "/data/adb/modules_update"
|
||||
private const val MODULE_ERROR = "Module zip processing incorrect"
|
||||
const val MOUNT_TEST = "mount_test"
|
||||
const val SEPOLICY_RULE = "sepolicy_rule"
|
||||
const val INVALID_ZYGISK = "invalid_zygisk"
|
||||
const val REMOVE_TEST = "remove_test"
|
||||
const val REMOVE_TEST_MARKER = "/dev/.remove_test_removed"
|
||||
const val EMPTY_ZYGISK = "empty_zygisk"
|
||||
const val UPGRADE_TEST = "upgrade_test"
|
||||
}
|
||||
|
||||
object TimberLog : CallbackList<String>(Runnable::run) {
|
||||
@@ -108,6 +111,9 @@ class Environment : BaseTest {
|
||||
assertTrue(error, egg.mkdirs())
|
||||
assertTrue(error, egg.getChildFile(".replace").createNewFile())
|
||||
|
||||
// Create /system/app/EasterEgg/newfile
|
||||
assertTrue(error, egg.getChildFile("newfile").createNewFile())
|
||||
|
||||
// Delete /system/bin/screenrecord
|
||||
val bin = path.getChildFile("system").getChildFile("bin")
|
||||
assertTrue(error, bin.mkdirs())
|
||||
@@ -117,7 +123,9 @@ class Environment : BaseTest {
|
||||
}
|
||||
|
||||
private fun setupSystemlessHost() {
|
||||
assertTrue("hosts setup failed", Shell.cmd("add_hosts_module").exec().isSuccess)
|
||||
val error = "hosts setup failed"
|
||||
assertTrue(error, runBlocking { RootUtils.addSystemlessHosts() })
|
||||
assertTrue(error, RootUtils.fs.getFile(Const.MODULE_PATH).getChildFile("hosts").exists())
|
||||
}
|
||||
|
||||
private fun setupSepolicyRuleModule(root: ExtendedFile) {
|
||||
@@ -167,12 +175,39 @@ class Environment : BaseTest {
|
||||
// Create a new module but mark is as "remove"
|
||||
val module = LocalModule(path)
|
||||
assertTrue(error, path.mkdirs())
|
||||
// Create uninstaller script
|
||||
path.getChildFile("uninstall.sh").newOutputStream().writer().use {
|
||||
it.write("touch $REMOVE_TEST_MARKER")
|
||||
}
|
||||
assertTrue(error, path.getChildFile("service.sh").createNewFile())
|
||||
module.remove = true
|
||||
|
||||
assertTrue(error, Shell.cmd("set_default_perm $path").exec().isSuccess)
|
||||
}
|
||||
|
||||
private fun setupUpgradeModule(root: ExtendedFile, update: ExtendedFile) {
|
||||
val error = "$UPGRADE_TEST setup failed"
|
||||
val oldPath = root.getChildFile(UPGRADE_TEST)
|
||||
val newPath = update.getChildFile(UPGRADE_TEST)
|
||||
|
||||
// Create an existing module but mark as "disable
|
||||
val module = LocalModule(oldPath)
|
||||
assertTrue(error, oldPath.mkdirs())
|
||||
module.enable = false
|
||||
// Install service.sh into the old module
|
||||
assertTrue(error, oldPath.getChildFile("service.sh").createNewFile())
|
||||
|
||||
// Create an upgrade module
|
||||
assertTrue(error, newPath.mkdirs())
|
||||
// Install post-fs-data.sh into the new module
|
||||
assertTrue(error, newPath.getChildFile("post-fs-data.sh").createNewFile())
|
||||
|
||||
assertTrue(error, Shell.cmd(
|
||||
"set_default_perm $oldPath",
|
||||
"set_default_perm $newPath",
|
||||
).exec().isSuccess)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun setupEnvironment() {
|
||||
runBlocking {
|
||||
@@ -217,12 +252,14 @@ class Environment : BaseTest {
|
||||
}
|
||||
|
||||
val root = RootUtils.fs.getFile(Const.MODULE_PATH)
|
||||
if (mount()) { setupMountTest(root) }
|
||||
if (preinit()) { setupSepolicyRuleModule(root) }
|
||||
val update = RootUtils.fs.getFile(MODULE_UPDATE_PATH)
|
||||
if (mount()) { setupMountTest(update) }
|
||||
if (preinit()) { setupSepolicyRuleModule(update) }
|
||||
setupSystemlessHost()
|
||||
setupEmptyZygiskModule(root)
|
||||
setupInvalidZygiskModule(root)
|
||||
setupEmptyZygiskModule(update)
|
||||
setupInvalidZygiskModule(update)
|
||||
setupRemoveModule(root)
|
||||
setupUpgradeModule(root, update)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -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>
|
||||
|
||||
249
app/core/src/main/res/values-hn/strings.xml
Normal file
249
app/core/src/main/res/values-hn/strings.xml
Normal 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>
|
||||
@@ -53,6 +53,7 @@
|
||||
<string name="touch_filtered_warning">Como um app está ocultando uma solicitação de SuperUsuário, o Magisk não pode verificar sua resposta.</string>
|
||||
<string name="deny">Negar</string>
|
||||
<string name="prompt">Perguntar</string>
|
||||
<string name="restrict">Restringir</string>
|
||||
<string name="grant">Permitir</string>
|
||||
<string name="su_warning">Permite acesso total ao seu dispositivo.\nNão permita se você não tiver certeza do que está fazendo!</string>
|
||||
<string name="forever">Sempre</string>
|
||||
@@ -170,6 +171,8 @@
|
||||
<string name="settings_su_auth_title">Autenticação de usuário</string>
|
||||
<string name="settings_su_auth_summary">Solicite autenticação de usuário durante solicitações de SuperUsuário</string>
|
||||
<string name="settings_su_auth_insecure">Nenhum método de autenticação está configurado no dispositivo</string>
|
||||
<string name="settings_su_restrict_title">Restringir recursos root</string>
|
||||
<string name="settings_su_restrict_summary">Restringirá novos apps de SuperUsuário por padrão. Aviso: isso quebrará a maioria dos apps. Não ative se você não souber o que está fazendo.</string>
|
||||
<string name="settings_customization">Personalizações</string>
|
||||
<string name="setting_add_shortcut_summary">Adicione um atalho na tela inicial, caso o nome e o ícone sejam difíceis de reconhecer logo após ocultar o app.</string>
|
||||
<string name="settings_doh_title">DNS sobre HTTPS</string>
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
<string name="touch_filtered_warning">Como um app está a ocultar um pedido de SuperUsuário, o Magisk não consegue verificar a sua resposta.</string>
|
||||
<string name="deny">Negar</string>
|
||||
<string name="prompt">Perguntar</string>
|
||||
<string name="restrict">Restringir</string>
|
||||
<string name="grant">Permitir</string>
|
||||
<string name="su_warning">Permite o acesso total ao seu dispositivo.\nNão o permita se não tiver a certeza do que está a fazer!</string>
|
||||
<string name="forever">Sempre</string>
|
||||
@@ -129,7 +130,7 @@
|
||||
<string name="settings_restore_app_title">Restaurar app do Magisk</string>
|
||||
<string name="settings_restore_app_summary">Desoculta o app do Magisk e restaura o APK original</string>
|
||||
<string name="language">Idioma</string>
|
||||
<string name="system_default">(Padrão do sistema)</string>
|
||||
<string name="system_default">(Predefinição do sistema)</string>
|
||||
<string name="settings_check_update_title">Verificar por atualizações</string>
|
||||
<string name="settings_check_update_summary">Verifique automaticamente se há atualizações ao abrir o app</string>
|
||||
<string name="settings_update_channel_title">Canal de atualização</string>
|
||||
@@ -170,6 +171,8 @@
|
||||
<string name="settings_su_auth_title">Autenticação de usuário</string>
|
||||
<string name="settings_su_auth_summary">Solicite autenticação de usuário durante pedidos de SuperUsuário</string>
|
||||
<string name="settings_su_auth_insecure">Nenhum método de autenticação está configurado no dispositivo</string>
|
||||
<string name="settings_su_restrict_title">Restringir recursos root</string>
|
||||
<string name="settings_su_restrict_summary">Restringirá novos apps de SuperUsuário por predefinição. Aviso: isto quebrará a maioria dos apps. Não ative se você não souber o que está a fazer.</string>
|
||||
<string name="settings_customization">Personalizações</string>
|
||||
<string name="setting_add_shortcut_summary">Adicione um atalho no ecrã inicial, caso o nome e o ícone sejam difíceis de reconhecer logo após ocultar o app.</string>
|
||||
<string name="settings_doh_title">DNS sobre HTTPS</string>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -5,116 +5,114 @@
|
||||
<string name="superuser">Süper Kullanıcı</string>
|
||||
<string name="logs">Günlükler</string>
|
||||
<string name="settings">Ayarlar</string>
|
||||
<string name="install">Yükle</string>
|
||||
<string name="install">Kur</string>
|
||||
<string name="section_home">Ana Sayfa</string>
|
||||
<string name="section_theme">Temalar</string>
|
||||
<string name="denylist">Reddetme Listesi</string>
|
||||
<string name="denylist">Red Listesi</string>
|
||||
|
||||
<!--Home-->
|
||||
<string name="no_connection">Bağlantı yok</string>
|
||||
<string name="no_connection">Bağlantı Yok</string>
|
||||
<string name="app_changelog">Değişiklik Günlüğü</string>
|
||||
<string name="loading">Yükleniyor…</string>
|
||||
<string name="update">Güncelle</string>
|
||||
<string name="not_available">Yok</string>
|
||||
<string name="not_available">Mevcut Değil</string>
|
||||
<string name="hide">Gizle</string>
|
||||
<string name="home_package">Paket</string>
|
||||
<string name="home_app_title">Uygulama</string>
|
||||
|
||||
<string name="home_notice_content">Magisk\'i YALNIZCA resmi GitHub sayfasından indirin. Bilinmeyen kaynaklardan gelen dosyalar zararlı olabilir!</string>
|
||||
<string name="home_support_title">Bizi Destekleyin</string>
|
||||
<string name="home_notice_content">Magisk\'i YALNIZCA resmi GitHub sayfasından indirin. Bilinmeyen kaynaklardan gelen dosyalar kötü amaçlı olabilir!</string>
|
||||
<string name="home_support_title">Bize Destek Olun</string>
|
||||
<string name="home_follow_title">Bizi Takip Edin</string>
|
||||
<string name="home_item_source">Kaynak</string>
|
||||
<string name="home_support_content">Magisk her zaman ücretsiz ve açık kaynak olacaktır. Ancak, bir bağış yaparak bize destek olabilirsiniz.</string>
|
||||
<string name="home_installed_version">Yüklü Sürüm</string>
|
||||
<string name="home_latest_version">En Son Sürüm</string>
|
||||
<string name="invalid_update_channel">Geçersiz Güncelleme Kanalı</string>
|
||||
<string name="home_support_content">Magisk ücretsizdir, açık kaynaklıdır ve her zaman öyle kalacaktır. Ancak, bağış yaparak bize değer verdiğinizi gösterebilirsiniz.</string>
|
||||
<string name="home_installed_version">Yüklü</string>
|
||||
<string name="home_latest_version">En Son</string>
|
||||
<string name="invalid_update_channel">Geçersiz güncelleme kanalı</string>
|
||||
<string name="uninstall_magisk_title">Magisk\'i Kaldır</string>
|
||||
<string name="uninstall_magisk_msg">Tüm modüller devre dışı bırakılacak/kaldırılacak!\nKök kaldırılacak!\nMagisk kullanılarak şifrelenmemiş herhangi bir dahili depolama yeniden şifrelenecek!</string>
|
||||
<string name="uninstall_magisk_msg">Tüm modüller devre dışı bırakılacak/kaldırılacak!\nRoot kaldırılacak!\nMagisk kullanılarak şifresi çözülen dahili depolama birimleri yeniden şifrelenecek!</string>
|
||||
|
||||
<!--Install-->
|
||||
<string name="keep_force_encryption">Zorla şifrelemeyi koru</string>
|
||||
<string name="keep_dm_verity">AVB 2.0/dm-verity\'yi koru</string>
|
||||
<string name="keep_force_encryption">Zorunlu Şifrelemeyi Koru</string>
|
||||
<string name="keep_dm_verity">AVB 2.0/dm-verity Koru</string>
|
||||
<string name="recovery_mode">Kurtarma Modu</string>
|
||||
<string name="install_options_title">Seçenekler</string>
|
||||
<string name="install_method_title">Yöntem</string>
|
||||
<string name="install_next">Sonraki</string>
|
||||
<string name="install_start">Hadi başlayalım</string>
|
||||
<string name="manager_download_install">İndirmek ve yüklemek için basın</string>
|
||||
<string name="direct_install">Doğrudan Yükleme (Önerilir)</string>
|
||||
<string name="install_inactive_slot">Etkin Olmayan Slot\'a Yükle (OTA Sonrası)</string>
|
||||
<string name="install_inactive_slot_msg">Cihazınız yeniden başlatıldıktan sonra zorunlu olarak mevcut etkin olmayan slota önyükleme yapılacaktır!\nBu seçeneği yalnızca OTA tamamlandıktan sonra kullanın.\nDevam etmek istiyor musunuz?</string>
|
||||
<string name="install_start">Hadi Başlayalım</string>
|
||||
<string name="manager_download_install">İndirmek ve kurmak için basın</string>
|
||||
<string name="direct_install">Doğrudan kurulum (Önerilen)</string>
|
||||
<string name="install_inactive_slot">Etkin olmayan slota kur (OTA sonrası)</string>
|
||||
<string name="install_inactive_slot_msg">Cihazınız, yeniden başlattıktan sonra mevcut etkin olmayan slota önyükleme yapmaya ZORLANACAKTIR!\nBu seçeneği yalnızca OTA güncellemesi yapıldıktan sonra kullanın.\nDevam edilsin mi?</string>
|
||||
<string name="setup_title">Ek Kurulum</string>
|
||||
<string name="select_patch_file">Bir Dosya Seç ve Yama Yap</string>
|
||||
<string name="patch_file_msg">Ham bir görüntü (*.img) veya bir ODIN tar dosyası (*.tar) veya bir payload.bin (*.bin) seçin</string>
|
||||
<string name="select_patch_file">Bir dosya seçin ve yamalayın</string>
|
||||
<string name="patch_file_msg">Ham bir imaj (*.img), bir ODIN tar dosyası (*.tar) veya bir payload.bin (*.bin) seçin</string>
|
||||
<string name="reboot_delay_toast">5 saniye içinde yeniden başlatılıyor…</string>
|
||||
<string name="flash_screen_title">Yükleniyor</string>
|
||||
<string name="flash_screen_title">Kuruluyor</string>
|
||||
|
||||
<!--Superuser-->
|
||||
<string name="su_request_title">Süper Kullanıcı İsteği</string>
|
||||
<string name="touch_filtered_warning">Bir uygulama bir Süper Kullanıcı isteğini engellediği için Magisk yanıtınızı doğrulayamıyor</string>
|
||||
<string name="touch_filtered_warning">Bir uygulama Süper Kullanıcı isteğini engellediği için Magisk yanıtınızı doğrulayamıyor.</string>
|
||||
<string name="deny">Reddet</string>
|
||||
<string name="prompt">İstem</string>
|
||||
<string name="prompt">Sor</string>
|
||||
<string name="restrict">Kısıtla</string>
|
||||
<string name="grant">İzin Ver</string>
|
||||
<string name="su_warning">Cihazınıza tam erişim sağlar.\nEmin değilseniz reddedin!</string>
|
||||
<string name="forever">Daima</string>
|
||||
<string name="once">Bir kez</string>
|
||||
<string name="tenmin">10 dakika</string>
|
||||
<string name="twentymin">20 dakika</string>
|
||||
<string name="thirtymin">30 dakika</string>
|
||||
<string name="sixtymin">60 dakika</string>
|
||||
<string name="su_allow_toast">%1$s uygulamasının süper kullanıcı hakları verildi</string>
|
||||
<string name="su_deny_toast">%1$s uygulamasının süper kullanıcı hakları reddedildi</string>
|
||||
<string name="su_snack_grant">%1$s uygulamasının süper kullanıcı hakları verildi</string>
|
||||
<string name="su_snack_deny">%1$s uygulamasının süper kullanıcı hakları reddedildi</string>
|
||||
<string name="su_snack_notif_on">%1$s uygulamasının bildirimleri etkinleştirildi</string>
|
||||
<string name="su_snack_notif_off">%1$s uygulamasının bildirimleri devre dışı bırakıldı</string>
|
||||
<string name="su_snack_log_on">%1$s uygulamasının günlüğü etkinleştirildi</string>
|
||||
<string name="su_snack_log_off">%1$s uygulamasının günlüğü devre dışı bırakıldı</string>
|
||||
<string name="su_revoke_title">İptal Et?</string>
|
||||
<string name="su_revoke_msg">%1$s uygulamasının süper kullanıcı haklarını iptal etmek istediğinize emin misiniz?</string>
|
||||
<string name="toast">Bildirim</string>
|
||||
<string name="forever">Her Zaman</string>
|
||||
<string name="once">Bir Kez</string>
|
||||
<string name="tenmin">10 Dakika</string>
|
||||
<string name="twentymin">20 Dakika</string>
|
||||
<string name="thirtymin">30 Dakika</string>
|
||||
<string name="sixtymin">60 Dakika</string>
|
||||
<string name="su_allow_toast">%1$s uygulamasına Süper Kullanıcı hakları verildi</string>
|
||||
<string name="su_deny_toast">%1$s uygulamasının Süper Kullanıcı hakları reddedildi</string>
|
||||
<string name="su_snack_grant">%1$s uygulamasının Süper Kullanıcı hakları verildi</string>
|
||||
<string name="su_snack_deny">%1$s uygulamasının Süper Kullanıcı hakları reddedildi</string>
|
||||
<string name="su_snack_notif_on">%1$s bildirimleri etkinleştirildi</string>
|
||||
<string name="su_snack_notif_off">%1$s bildirimleri devre dışı bırakıldı</string>
|
||||
<string name="su_snack_log_on">%1$s için günlük kaydı etkinleştirildi</string>
|
||||
<string name="su_snack_log_off">%1$s için günlük kaydı devre dışı bırakıldı</string>
|
||||
<string name="su_revoke_title">İptal Edilsin mi?</string>
|
||||
<string name="su_revoke_msg">%1$s uygulamasının Süper Kullanıcı haklarını iptal etmeyi onaylayın</string>
|
||||
<string name="toast">Bildirim Penceresi</string>
|
||||
<string name="none">Yok</string>
|
||||
|
||||
<string name="superuser_toggle_notification">Bildirimler</string>
|
||||
<string name="superuser_toggle_revoke">İptal Et</string>
|
||||
<string name="superuser_policy_none">Henüz hiçbir uygulama Süper Kullanıcı izni istemedi.</string>
|
||||
|
||||
<!--Logs-->
|
||||
<string name="log_data_none">Günlük kullanmıyorsunuz, root (kök) uygulamalarınızı daha çok kullanmayı deneyin</string>
|
||||
<string name="log_data_magisk_none">Magisk günlükleri boş, bu tuhaf</string>
|
||||
<string name="menuSaveLog">Günlüğü kaydet</string>
|
||||
<string name="menuClearLog">Günlüğü şimdi temizle</string>
|
||||
<string name="logs_cleared">Günlük kaydı başarıyla temizlendi</string>
|
||||
<string name="log_data_none">Günlüğünüz temiz. Root uygulamalarınızı daha fazla kullanmayı deneyin.</string>
|
||||
<string name="log_data_magisk_none">Magisk günlükleri boş, bu garip.</string>
|
||||
<string name="menuSaveLog">Günlüğü Kaydet</string>
|
||||
<string name="menuClearLog">Günlüğü Şimdi Temizle</string>
|
||||
<string name="logs_cleared">Günlük başarıyla temizlendi</string>
|
||||
<string name="pid">PID: %1$d</string>
|
||||
<string name="target_uid">Hedef UID: %1$d</string>
|
||||
<string name="target_pid">Mount ns hedef PID: %s</string>
|
||||
<string name="target_pid">Hedef PID: %s</string>
|
||||
<string name="selinux_context">SELinux bağlamı: %s</string>
|
||||
<string name="supp_group">Ek grup: %s</string>
|
||||
|
||||
<!--SafetyNet-->
|
||||
|
||||
<!--MagiskHide-->
|
||||
<string name="show_system_app">Sistem uygulamalarını göster</string>
|
||||
<string name="show_os_app">İşletim sistemi uygulamalarını göster</string>
|
||||
<string name="hide_filter_hint">İsme göre filtrele</string>
|
||||
<string name="show_system_app">Sistem Uygulamalarını Göster</string>
|
||||
<string name="show_os_app">İS Uygulamalarını Göster</string>
|
||||
<string name="hide_filter_hint">Ada göre filtrele</string>
|
||||
<string name="hide_search">Ara</string>
|
||||
|
||||
<!--Module-->
|
||||
<string name="no_info_provided">(Bilgi verilmedi)</string>
|
||||
<string name="reboot_userspace">Hızlı yeniden başlat</string>
|
||||
<string name="reboot_recovery">Kurtarma modunda yeniden başlat</string>
|
||||
<string name="reboot_bootloader">Önyükleyici modunda yeniden başlat</string>
|
||||
<string name="reboot_download">İndirme modunda yeniden başlat</string>
|
||||
<string name="reboot_edl">EDL modunda yeniden başlat</string>
|
||||
<string name="reboot_safe_mode">Güvenli mod</string>
|
||||
<string name="module_version_author">%1$s / %2$s</string>
|
||||
<string name="no_info_provided">(Bilgi sağlanmadı)</string>
|
||||
<string name="reboot_userspace">Hızlı Yeniden Başlat</string>
|
||||
<string name="reboot_recovery">Kurtarma Modunda Yeniden Başlat</string>
|
||||
<string name="reboot_bootloader">Önyükleyici Modunda Yeniden Başlat</string>
|
||||
<string name="reboot_download">Download Modunda Yeniden Başlat</string>
|
||||
<string name="reboot_edl">EDL Modunda Yeniden Başlat</string>
|
||||
<string name="reboot_safe_mode">Güvenli Mod</string>
|
||||
<string name="module_version_author">%1$s, Geliştirici: %2$s</string>
|
||||
<string name="module_state_remove">Kaldır</string>
|
||||
<string name="module_action">Eylem</string>
|
||||
<string name="module_state_restore">Geri Yükle</string>
|
||||
<string name="module_action_install_external">Depolamadan yükle</string>
|
||||
<string name="update_available">Güncelleme Mevcut</string>
|
||||
<string name="update_available">Güncelleme mevcut</string>
|
||||
<string name="suspend_text_riru">Modül, %1$s etkin olduğu için askıya alındı</string>
|
||||
<string name="suspend_text_zygisk">Modül, %1$s etkin olmadığı için askıya alındı</string>
|
||||
<string name="zygisk_module_unloaded">Uyumsuzluk nedeniyle Zygisk modülü yüklenmedi</string>
|
||||
<string name="zygisk_module_unloaded">Zygisk modülü uyumsuzluk nedeniyle yüklenmedi</string>
|
||||
<string name="module_empty">Yüklü modül yok</string>
|
||||
<string name="confirm_install">%1$s modülü yüklensin mi?</string>
|
||||
<string name="confirm_install_title">Yükleme Onayı</string>
|
||||
@@ -127,121 +125,125 @@
|
||||
<string name="settings_dark_mode_dark">Her Zaman Karanlık</string>
|
||||
<string name="settings_download_path_title">İndirme Yolu</string>
|
||||
<string name="settings_download_path_message">Dosyalar %1$s konumuna kaydedilecek</string>
|
||||
<string name="settings_hide_app_title">Magisk uygulamasını gizle</string>
|
||||
<string name="settings_hide_app_summary">Rastgele bir paket kimliği ve özel uygulama etiketi olan bir vekil (proxy) uygulaması yükleyin</string>
|
||||
<string name="settings_restore_app_title">Magisk uygulamasını geri yükle</string>
|
||||
<string name="settings_restore_app_summary">Uygulamayı göster ve orijinal APK\'yı geri yükle</string>
|
||||
<string name="settings_hide_app_title">Magisk Uygulamasını Gizle</string>
|
||||
<string name="settings_hide_app_summary">Rastgele bir paket kimliği ve özel uygulama etiketi ile bir proxy uygulaması yükleyin</string>
|
||||
<string name="settings_restore_app_title">Magisk Uygulamasını Geri Yükle</string>
|
||||
<string name="settings_restore_app_summary">Uygulamanın gizliliğini kaldırın ve orijinal APK\'yi geri yükleyin</string>
|
||||
<string name="language">Dil</string>
|
||||
<string name="system_default">(Sistem Varsayılanı)</string>
|
||||
<string name="settings_check_update_title">Güncellemeleri Kontrol Et</string>
|
||||
<string name="settings_check_update_summary">Arka planda düzenli olarak güncellemeleri kontrol et</string>
|
||||
<string name="settings_check_update_summary">Arka planda periyodik olarak güncellemeleri kontrol et</string>
|
||||
<string name="settings_update_channel_title">Güncelleme Kanalı</string>
|
||||
<string name="settings_update_stable">Kararlı</string>
|
||||
<string name="settings_update_beta">Beta</string>
|
||||
<string name="settings_update_debug">Hata Ayıklama</string>
|
||||
<string name="settings_update_custom">Özel</string>
|
||||
<string name="settings_update_custom_msg">Özel kanal URL\'si girin</string>
|
||||
<string name="settings_zygisk_summary">Magisk\'in bazı bölümlerini zygote daemon\'unda çalıştır</string>
|
||||
<string name="settings_denylist_title">Reddetme Listesini Zorla</string>
|
||||
<string name="settings_denylist_summary">Reddetme Listesindeki işlemler tüm Magisk değişikliklerini geri alacak</string>
|
||||
<string name="settings_denylist_config_title">Reddetme Listesini Yapılandır</string>
|
||||
<string name="settings_denylist_config_summary">Reddetme Listesine dahil edilecek işlemleri seçin</string>
|
||||
<string name="settings_hosts_title">Sistemsiz ana makineler (systemless hosts)</string>
|
||||
<string name="settings_hosts_summary">Reklam engelleme uygulamaları için sistemsiz ana makineler (systemless hosts) desteği</string>
|
||||
<string name="settings_hosts_toast">Sistemsiz ana makineler (systemless hosts) modülü eklendi</string>
|
||||
<string name="settings_update_custom_msg">Özel bir kanal URL\'si girin</string>
|
||||
<string name="settings_zygisk_summary">Magisk\'in bazı kısımlarını Zygote daemon içinde çalıştırın</string>
|
||||
<string name="settings_denylist_title">Red Listesini Uygula</string>
|
||||
<string name="settings_denylist_summary">Red listesindeki işlemlerin tüm Magisk değişiklikleri geri alınacak</string>
|
||||
<string name="settings_denylist_config_title">Red Listesini Yapılandır</string>
|
||||
<string name="settings_denylist_config_summary">Red listesine dahil edilecek işlemleri seçin</string>
|
||||
<string name="settings_hosts_title">Sistemsiz Hosts</string>
|
||||
<string name="settings_hosts_summary">Reklam engelleme uygulamaları için sistemsiz hosts desteği</string>
|
||||
<string name="settings_hosts_toast">Sistemsiz hosts modülü eklendi</string>
|
||||
<string name="settings_app_name_hint">Yeni ad</string>
|
||||
<string name="settings_app_name_helper">Uygulama bu isimle yeniden paketlenecek</string>
|
||||
<string name="settings_app_name_helper">Uygulama bu adla yeniden paketlenecek</string>
|
||||
<string name="settings_app_name_error">Geçersiz format</string>
|
||||
<string name="settings_su_app_adb">Uygulamalar ve ADB</string>
|
||||
<string name="settings_su_app">Sadece Uygulamalar</string>
|
||||
<string name="settings_su_adb">Sadece ADB</string>
|
||||
<string name="settings_su_disable">Devre Dışı</string>
|
||||
<string name="settings_su_request_10">10 saniye</string>
|
||||
<string name="settings_su_request_15">15 saniye</string>
|
||||
<string name="settings_su_request_20">20 saniye</string>
|
||||
<string name="settings_su_request_30">30 saniye</string>
|
||||
<string name="settings_su_request_45">45 saniye</string>
|
||||
<string name="settings_su_request_60">60 saniye</string>
|
||||
<string name="settings_su_request_10">10 Saniye</string>
|
||||
<string name="settings_su_request_15">15 Saniye</string>
|
||||
<string name="settings_su_request_20">20 Saniye</string>
|
||||
<string name="settings_su_request_30">30 Saniye</string>
|
||||
<string name="settings_su_request_45">45 Saniye</string>
|
||||
<string name="settings_su_request_60">60 Saniye</string>
|
||||
<string name="superuser_access">Süper Kullanıcı Erişimi</string>
|
||||
<string name="auto_response">Otomatik Yanıt</string>
|
||||
<string name="request_timeout">İstek Zaman Aşımı</string>
|
||||
<string name="superuser_notification">Süper Kullanıcı Bildirimi</string>
|
||||
<string name="settings_su_reauth_title">Yükseltme sonrası yeniden doğrulama yap</string>
|
||||
<string name="settings_su_reauth_summary">Uygulama güncellemelerinden sonra Süper Kullanıcı izinlerini tekrar iste</string>
|
||||
<string name="settings_su_tapjack_title">Tapjacking Koruması</string>
|
||||
<string name="settings_su_tapjack_summary">Süper Kullanıcı istemi diyalogu, başka bir pencere veya katman tarafından gizlendiğinde girdilere yanıt vermeyecektir</string>
|
||||
<string name="settings_su_auth_title">Kullanıcı Kimlik Doğrulama</string>
|
||||
<string name="settings_su_auth_summary">Süper Kullanıcı isteklerinde kullanıcı kimlik doğrulaması iste</string>
|
||||
<string name="settings_su_reauth_title">Yükseltmeden Sonra Yeniden Kimlik Doğrula</string>
|
||||
<string name="settings_su_reauth_summary">Uygulamaları yükselttikten sonra tekrar Süper Kullanıcı izinlerini iste</string>
|
||||
<string name="settings_su_tapjack_title">Dokunma Saldırısı Koruması</string>
|
||||
<string name="settings_su_tapjack_summary">Süper Kullanıcı istek penceresi, başka bir pencere veya katman tarafından engellendiğinde girdilere yanıt vermeyecektir</string>
|
||||
<string name="settings_su_auth_title">Kullanıcı Kimlik Doğrulaması</string>
|
||||
<string name="settings_su_auth_summary">Süper Kullanıcı istekleri sırasında kullanıcı kimlik doğrulaması iste</string>
|
||||
<string name="settings_su_auth_insecure">Cihazda yapılandırılmış bir kimlik doğrulama yöntemi yok</string>
|
||||
<string name="settings_customization">Özelleştir</string>
|
||||
<string name="setting_add_shortcut_summary">Uygulamayı gizledikten sonra adı ve simgeyi tanımakta zorlanıyorsanız ana ekrana güzel bir kısayol ekle</string>
|
||||
<string name="settings_doh_title">DNS üzerinden HTTPS</string>
|
||||
<string name="settings_doh_description">Bazı ülkelerde DNS zehirlemesine karşı geçici çözüm</string>
|
||||
<string name="settings_random_name_title">Çıkış adını rastgele seç</string>
|
||||
<string name="settings_random_name_description">Algılamayı önlemek için yamalı resimlerin ve tar dosyalarının çıkış dosya adını rastgele seç</string>
|
||||
|
||||
<string name="multiuser_mode">Çok Kullanıcılı Mod</string>
|
||||
<string name="settings_owner_only">Yalnızca Cihaz Sahibi</string>
|
||||
<string name="settings_owner_manage">Cihaz Sahibi Yönetiminde</string>
|
||||
<string name="settings_su_restrict_title">Root Yeteneklerini Kısıtla</string>
|
||||
<string name="settings_su_restrict_summary">Yeni Süper Kullanıcı uygulamalarını varsayılan olarak kısıtlayacaktır. Uyarı: Bu, çoğu uygulamayı bozacaktır. Ne yaptığınızı bilmiyorsanız etkinleştirmeyin.</string>
|
||||
<string name="settings_customization">Özelleştirme</string>
|
||||
<string name="setting_add_shortcut_summary">Uygulamayı gizledikten sonra adı ve simgesi tanınması zor olursa ana ekrana güzel bir kısayol ekleyin</string>
|
||||
<string name="settings_doh_title">HTTPS üzerinden DNS</string>
|
||||
<string name="settings_doh_description">Bazı ülkelerdeki DNS zehirlenmesini aşmak için geçici çözüm</string>
|
||||
<string name="settings_random_name_title">Çıktı Adını Rastgele Yap</string>
|
||||
<string name="settings_random_name_description">Tespit edilmesini önlemek için yamalanmış imajların ve tar dosyalarının çıktı dosya adını rastgele yapın</string>
|
||||
<string name="multiuser_mode">Çoklu Kullanıcı Modu</string>
|
||||
<string name="settings_owner_only">Sadece Cihaz Sahibi</string>
|
||||
<string name="settings_owner_manage">Cihaz Sahibi Tarafından Yönetilen</string>
|
||||
<string name="settings_user_independent">Kullanıcıdan Bağımsız</string>
|
||||
<string name="owner_only_summary">Yalnızca sahip kök (root) erişimine sahiptir</string>
|
||||
<string name="owner_manage_summary">Yalnızca sahip kök (root) erişimini yönetebilir ve istek uyarılarını alabilir</string>
|
||||
<string name="user_independent_summary">Her kullanıcının kendi ayrı kök (root) kuralları vardır</string>
|
||||
|
||||
<string name="owner_only_summary">Sadece cihaz sahibi root erişimine sahiptir</string>
|
||||
<string name="owner_manage_summary">Sadece cihaz sahibi root erişimini yönetebilir ve istekleri alabilir</string>
|
||||
<string name="user_independent_summary">Her kullanıcının kendi ayrı root kuralları vardır</string>
|
||||
<string name="mount_namespace_mode">Bağlama Ad Alanı Modu</string>
|
||||
<string name="settings_ns_global">Küresel Ad Alanı</string>
|
||||
<string name="settings_ns_global">Genel Ad Alanı</string>
|
||||
<string name="settings_ns_requester">Ad Alanını Devral</string>
|
||||
<string name="settings_ns_isolate">İzolasyon Ad Alanı</string>
|
||||
<string name="global_summary">Tüm kök (root) oturumları küresel bağlama ad alanını kullanır</string>
|
||||
<string name="requester_summary">Kök (root) oturumları isteyicisinin ad alanını devralacak</string>
|
||||
<string name="isolate_summary">Her kök (root) oturumu kendi izole ad alanına sahip olacak</string>
|
||||
<string name="settings_ns_isolate">Yalıtılmış Ad Alanı</string>
|
||||
<string name="global_summary">Tüm root oturumları genel bağlama ad alanını kullanır</string>
|
||||
<string name="requester_summary">Root oturumları, istekçilerinin ad alanını devralır</string>
|
||||
<string name="isolate_summary">Her root oturumunun kendi yalıtılmış ad alanı olacaktır</string>
|
||||
|
||||
<!--Notifications-->
|
||||
<string name="update_channel">Magisk Güncellemeleri</string>
|
||||
<string name="progress_channel">İlerleme Bildirimleri</string>
|
||||
<string name="updated_channel">Güncelleme Tamamlandı</string>
|
||||
<string name="update_channel">Magisk güncellemeleri</string>
|
||||
<string name="progress_channel">İlerleme bildirimleri</string>
|
||||
<string name="updated_channel">Güncelleme tamamlandı</string>
|
||||
<string name="download_complete">İndirme tamamlandı</string>
|
||||
<string name="download_file_error">Dosya indirilirken hata oluştu</string>
|
||||
<string name="magisk_update_title">Magisk Güncellemesi Mevcut!</string>
|
||||
<string name="updated_title">Magisk Güncellendi</string>
|
||||
<string name="magisk_update_title">Magisk güncellemesi mevcut!</string>
|
||||
<string name="updated_title">Magisk güncellendi</string>
|
||||
<string name="updated_text">Uygulamayı açmak için dokunun</string>
|
||||
|
||||
<!--Toasts, Dialogs-->
|
||||
<string name="yes">Mevcut</string>
|
||||
<string name="no">Mevcut Değil</string>
|
||||
<string name="repo_install_title">%1$s %2$s(%3$d) Kur</string>
|
||||
<string name="yes">Evet</string>
|
||||
<string name="no">Hayır</string>
|
||||
<string name="repo_install_title">%1$s %2$s(%3$d) Yüklensin mi?</string>
|
||||
<string name="download">İndir</string>
|
||||
<string name="reboot">Yeniden Başlat</string>
|
||||
<string name="close">Kapat</string>
|
||||
<string name="release_notes">Sürüm Notları</string>
|
||||
<string name="flashing">Yükleniyor...</string>
|
||||
<string name="done">Tamamlandı!</string>
|
||||
<string name="flashing">Flaşlanıyor…</string>
|
||||
<string name="running">Çalışıyor…</string>
|
||||
<string name="done">Bitti!</string>
|
||||
<string name="done_action">%1$s eylemi tamamlandı</string>
|
||||
<string name="failure">Başarısız!</string>
|
||||
<string name="hide_app_title">Magisk uygulaması gizleniyor…</string>
|
||||
<string name="open_link_failed_toast">Bağlantıyı açmak için uygulama bulunamadı</string>
|
||||
<string name="open_link_failed_toast">Bağlantıyı açacak bir uygulama bulunamadı</string>
|
||||
<string name="complete_uninstall">Tamamen Kaldır</string>
|
||||
<string name="restore_img">Görüntüleri Geri Yükle</string>
|
||||
<string name="restore_img_msg">Geri Yükleniyor...</string>
|
||||
<string name="restore_img">İmajları Geri Yükle</string>
|
||||
<string name="restore_img_msg">Geri yükleniyor…</string>
|
||||
<string name="restore_done">Geri yükleme tamamlandı!</string>
|
||||
<string name="restore_fail">Stok yedeği mevcut değil!</string>
|
||||
<string name="setup_fail">Kurulum başarısız oldu</string>
|
||||
<string name="env_fix_title">Ek Ayar Gerekiyor</string>
|
||||
<string name="env_fix_msg">Magisk\'in düzgün çalışabilmesi için cihazınızın ek ayarlar yapması gerekiyor. Devam etmek ve yeniden başlatmak istiyor musunuz?</string>
|
||||
<string name="env_full_fix_msg">Cihazınızın düzgün çalışabilmesi için Magisk\'in yeniden yüklenmesi gerekiyor. Lütfen Magisk\'i uygulama içinde yeniden yükleyin, kurtarma modu doğru cihaz bilgilerini alamaz.</string>
|
||||
<string name="setup_msg">Ortam kurulumu yapılıyor...</string>
|
||||
<string name="env_fix_title">Ek Kurulum Gerektiriyor</string>
|
||||
<string name="env_fix_msg">Cihazınızın Magisk\'in düzgün çalışması için ek kuruluma ihtiyacı var. Devam edip yeniden başlatmak ister misiniz?</string>
|
||||
<string name="env_full_fix_msg">Cihazınızın düzgün çalışması için Magisk\'i yeniden flaşlamanız gerekiyor. Lütfen Magisk\'i uygulama içinden yeniden kurun, Kurtarma modu doğru cihaz bilgisini alamaz.</string>
|
||||
<string name="setup_msg">Çalışma ortamı kuruluyor…</string>
|
||||
<string name="unsupport_magisk_title">Desteklenmeyen Magisk Sürümü</string>
|
||||
<string name="unsupport_magisk_msg">Bu uygulama sürümü, %1$s altındaki Magisk sürümlerini desteklemiyor.\n\nUygulama, Magisk yüklü değilmiş gibi davranacaktır, lütfen en kısa sürede Magisk\'i güncelleyin.</string>
|
||||
<string name="unsupport_magisk_msg">Uygulamanın bu sürümü, %1$s sürümünden daha düşük Magisk sürümlerini desteklemiyor.\n\nUygulama, Magisk yüklü değilmiş gibi davranacaktır. Lütfen Magisk\'i en kısa sürede güncelleyin.</string>
|
||||
<string name="unsupport_general_title">Anormal Durum</string>
|
||||
<string name="unsupport_system_app_msg">Bu uygulamanın sistem uygulaması olarak çalıştırılması desteklenmiyor. Lütfen uygulamayı kullanıcı uygulamasına geri döndürün.</string>
|
||||
<string name="unsupport_other_su_msg">Magisk\'ten gelmeyen bir "su" ikili dosyası tespit edildi. Lütfen herhangi bir rakip kök (root) çözümünü kaldırın ve/veya Magisk\'i yeniden yükleyin.</string>
|
||||
<string name="unsupport_external_storage_msg">Magisk harici depolamaya yüklendi. Lütfen uygulamayı dahili depolamaya taşıyın.</string>
|
||||
<string name="unsupport_nonroot_stub_msg">Gizli Magisk uygulaması kök (root) erişimi kaybolduğu için çalışmaya devam edemez. Lütfen orijinal APK\'yı geri yükleyin.</string>
|
||||
<string name="unsupport_system_app_msg">Bu uygulamanın bir sistem uygulaması olarak çalıştırılması desteklenmiyor. Lütfen uygulamayı bir kullanıcı uygulamasına geri döndürün.</string>
|
||||
<string name="unsupport_other_su_msg">Magisk\'e ait olmayan bir "su" ikili dosyası tespit edildi. Lütfen rakip root çözümlerini kaldırın ve/veya Magisk\'i yeniden yükleyin.</string>
|
||||
<string name="unsupport_external_storage_msg">Magisk harici depolamaya kurulmuş. Lütfen uygulamayı dahili depolamaya taşıyın.</string>
|
||||
<string name="unsupport_nonroot_stub_msg">Gizlenmiş Magisk uygulaması, root kaybolduğu için çalışmaya devam edemiyor. Lütfen orijinal APK\'yi geri yükleyin.</string>
|
||||
<string name="unsupport_nonroot_stub_title">@string/settings_restore_app_title</string>
|
||||
<string name="external_rw_permission_denied">Bu işlevselliği etkinleştirmek için depolama izni verin</string>
|
||||
<string name="post_notifications_denied">Bu işlevselliği etkinleştirmek için bildirim izni verin</string>
|
||||
<string name="install_unknown_denied">Bu işlevselliği etkinleştirmek için "bilinmeyen uygulamaları yükle" iznini verin</string>
|
||||
<string name="add_shortcut_title">Ana ekrana kısayol ekle</string>
|
||||
<string name="add_shortcut_msg">Bu uygulamayı gizledikten sonra adı ve simgesi tanınmayabilir. Ana ekrana güzel bir kısayol eklemek ister misiniz?</string>
|
||||
<string name="app_not_found">Bu işlemi gerçekleştirecek uygulama bulunamadı</string>
|
||||
<string name="external_rw_permission_denied">Bu işlevi etkinleştirmek için depolama izni verin</string>
|
||||
<string name="post_notifications_denied">Bu işlevi etkinleştirmek için bildirim izni verin</string>
|
||||
<string name="install_unknown_denied">Bu işlevi etkinleştirmek için "Bilinmeyen uygulamaları yükle" iznini verin</string>
|
||||
<string name="add_shortcut_title">Ana Ekrana Kısayol Ekle</string>
|
||||
<string name="add_shortcut_msg">Bu uygulamayı gizledikten sonra adı ve simgesi tanınması zor olabilir. Ana ekrana güzel bir kısayol eklemek ister misiniz?</string>
|
||||
<string name="app_not_found">Bu eylemi gerçekleştirecek bir uygulama bulunamadı</string>
|
||||
<string name="reboot_apply_change">Değişiklikleri uygulamak için yeniden başlatın</string>
|
||||
<string name="restore_app_confirmation">Bu, gizli uygulamayı orijinal uygulamaya geri yükleyecektir. Gerçekten bunu yapmak istiyor musunuz?</string>
|
||||
<string name="restore_app_confirmation">Bu, gizlenmiş uygulamayı orijinal uygulamaya geri yükleyecektir. Bunu gerçekten yapmak istiyor musunuz?</string>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -172,7 +172,7 @@
|
||||
<string name="settings_su_auth_summary">Ask for user authentication during Superuser requests</string>
|
||||
<string name="settings_su_auth_insecure">No authentication method is configured on the device</string>
|
||||
<string name="settings_su_restrict_title">Restrict root capabilities</string>
|
||||
<string name="settings_su_restrict_summary">Will restrict new superuser apps by default. Warning, this will break most apps, do not enable it.</string>
|
||||
<string name="settings_su_restrict_summary">Will restrict new Superuser apps by default. Warning: this will break most apps. Don\'t enable it unless you know what you\'re doing.</string>
|
||||
<string name="settings_customization">Customization</string>
|
||||
<string name="setting_add_shortcut_summary">Add a pretty shortcut to the home screen in case the name and icon are difficult to recognize after hiding the app</string>
|
||||
<string name="settings_doh_title">DNS over HTTPS</string>
|
||||
|
||||
@@ -30,4 +30,4 @@ android.nonFinalResIds=false
|
||||
|
||||
# Magisk
|
||||
magisk.stubVersion=40
|
||||
magisk.versionCode=30100
|
||||
magisk.versionCode=30300
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
[versions]
|
||||
kotlin = "2.1.21"
|
||||
android = "8.11.0"
|
||||
ksp = "2.1.21-2.0.1"
|
||||
kotlin = "2.2.20"
|
||||
android = "8.13.0"
|
||||
ksp = "2.2.20-2.0.2"
|
||||
rikka = "1.3.0"
|
||||
navigation = "2.9.0"
|
||||
navigation = "2.9.4"
|
||||
libsu = "6.0.0"
|
||||
okhttp = "4.12.0"
|
||||
okhttp = "5.1.0"
|
||||
retrofit = "3.0.0"
|
||||
room = "2.7.2"
|
||||
room = "2.8.0"
|
||||
|
||||
[libraries]
|
||||
bcpkix = { module = "org.bouncycastle:bcpkix-jdk18on", version = "1.81" }
|
||||
commons-compress = { module = "org.apache.commons:commons-compress", version = "1.27.1" }
|
||||
bcpkix = { module = "org.bouncycastle:bcpkix-jdk18on", version = "1.82" }
|
||||
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" }
|
||||
retrofit-scalars = { module = "com.squareup.retrofit2:converter-scalars", 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.11.0" }
|
||||
appcompat = { module = "androidx.appcompat:appcompat", version = "1.7.1" }
|
||||
core-ktx = { module = "androidx.core:core-ktx", version = "1.16.0" }
|
||||
core-ktx = { module = "androidx.core:core-ktx", version = "1.17.0" }
|
||||
core-splashscreen = { module = "androidx.core:core-splashscreen", version = "1.0.1" }
|
||||
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,11 +39,11 @@ 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.6.2" }
|
||||
test-rules = { module = "androidx.test:rules", version = "1.6.1" }
|
||||
test-junit = { module = "androidx.test.ext:junit", version = "1.2.1" }
|
||||
test-runner = { module = "androidx.test:runner", version = "1.7.0" }
|
||||
test-rules = { module = "androidx.test:rules", version = "1.7.0" }
|
||||
test-junit = { module = "androidx.test.ext:junit", version = "1.3.0" }
|
||||
test-uiautomator = { module = "androidx.test.uiautomator:uiautomator", version = "2.3.0" }
|
||||
|
||||
# topjohnwu
|
||||
@@ -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.30.0" }
|
||||
moshi-plugin = { module = "dev.zacsweers.moshix:dev.zacsweers.moshix.gradle.plugin", version = "0.32.0" }
|
||||
|
||||
[plugins]
|
||||
|
||||
2
app/gradle/wrapper/gradle-wrapper.properties
vendored
2
app/gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
7
app/stub/src/main/res/values-hn/strings.xml
Normal file
7
app/stub/src/main/res/values-hn/strings.xml
Normal 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
139
build.py
@@ -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.2"
|
||||
|
||||
# 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()
|
||||
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
- [Installation Instructions](install.md)
|
||||
- [Frequently Asked Questions](faq.md)
|
||||
- [Release Notes](releases/index.md)
|
||||
- [Magisk Changelog](changes.md)
|
||||
|
||||
The following sections are for developers
|
||||
|
||||
172
docs/changes.md
172
docs/changes.md
@@ -1,13 +1,31 @@
|
||||
# Magisk Changelog
|
||||
|
||||
### v30.0
|
||||
### 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
|
||||
- [Core] Fix module `.replace` functionality in certain situations
|
||||
- [resetprop] Reduce property modification traces
|
||||
|
||||
### v30.1 (2025.7.3)
|
||||
|
||||
- [Core] Fix bug in module mounting implementation
|
||||
- [MagiskSU] Add ability to restrict Linux capabilities even if running as root (uid=0)
|
||||
|
||||
### v30.0 (2025.7.1)
|
||||
|
||||
- [General] Various minor bug fixes
|
||||
- [Core] Migrate module implementation to Rust
|
||||
- [Core] Improve Magisk specific files injection logic
|
||||
- [MagiskBoot] Migrate compression code to Rust
|
||||
|
||||
### v29.0
|
||||
### v29.0 (2025.5.14)
|
||||
|
||||
- [General] Massive internal refactoring and code migration
|
||||
- [App] Support downloading module zip files with XZ compression
|
||||
@@ -16,7 +34,7 @@
|
||||
- [MagiskInit] Redesign sepolicy patching and injection logic
|
||||
- [MagiskSU] Better TTY/PTY support
|
||||
|
||||
### v28.1
|
||||
### v28.1 (2024.12.6)
|
||||
|
||||
- [App] Fix stub APK download link
|
||||
- [App] Fix support for Android lower than 8.0
|
||||
@@ -24,7 +42,7 @@
|
||||
- [MagiskInit] Fix a regression for 2SI devices
|
||||
- [MagiskPolicy] Fix a regression causing `overlay.d` replaced files to be not accessible
|
||||
|
||||
### v28.0
|
||||
### v28.0 (2024.10.10)
|
||||
|
||||
- [General] Support 16k page size
|
||||
- [General] Add basic support for RISC-V (not built in releases)
|
||||
@@ -48,7 +66,7 @@
|
||||
- [MagiskBoot] Properly support vendor boot images
|
||||
- [MagiskBoot] Disable Samsung PROCA from kernel image
|
||||
|
||||
### v27.0
|
||||
### v27.0 (2024.2.3)
|
||||
|
||||
- [Zygisk] Introduce new code injection mechanism
|
||||
- [Zygisk] Support new signature introduced in U QPR2
|
||||
@@ -56,7 +74,7 @@
|
||||
- [MagiskBoot] Support compressing `init` so Magisk is installable on devices with small boot partitions
|
||||
- [ResetProp] Add new wait for property feature `resetprop -w`
|
||||
|
||||
### v26.4
|
||||
### v26.4 (2023.11.5)
|
||||
|
||||
- [MagiskBoot] Don't pad zeros if signed boot image is larger
|
||||
- [MagiskPolicy] Fix `genfscon` and `filename_trans`
|
||||
@@ -67,14 +85,14 @@
|
||||
- [Daemon] Fix certificate parsing of APKs
|
||||
- [General] Fix logging errors from C++ code being ignored
|
||||
|
||||
### v26.3
|
||||
### v26.3 (2023.9.4)
|
||||
|
||||
- [General] Fix device information detection script
|
||||
- [General] Update BusyBox to 1.36.1
|
||||
- [General] Update toolchain that produces broken arm32 executables
|
||||
- [App] Fix root service unable to bind on OnePlus devices
|
||||
|
||||
### v26.2
|
||||
### v26.2 (2023.8.27)
|
||||
|
||||
- [MagiskBoot] Support extracting boot image from `payload.bin`
|
||||
- [MagiskBoot] Support cpio files containing character files
|
||||
@@ -92,13 +110,13 @@
|
||||
- [App] Support patching boot image from ROM zips
|
||||
- [App] Properly preserve `boot.img` when patching Samsung firmware with `init_boot.img`
|
||||
|
||||
### v26.1
|
||||
### v26.1 (2023.4.11)
|
||||
|
||||
- [App] Fix crashing when revoking root permissions
|
||||
- [MagiskInit] Always prefer `ext4` partitions over `f2fs` when selecting the pre-init partition
|
||||
- [General] Restore module files' context/owner/group from mirror. This is a regression introduced in v26.0
|
||||
|
||||
### v26.0
|
||||
### v26.0 (2023.4.5)
|
||||
|
||||
- [General] Bump minimum supported Android version to Android 6.0
|
||||
- [General] New magic mount backend. It supports loading modules into system with `overlayfs` files injected
|
||||
@@ -115,7 +133,7 @@
|
||||
- [MagiskPolicy] Fix minor bug in command line argument parsing
|
||||
- [MagiskPolicy] Update rules to support Android U
|
||||
|
||||
### v25.2
|
||||
### v25.2 (2022.7.20)
|
||||
|
||||
- [MagiskInit] Fix a potential issue when stub cpio is used
|
||||
- [MagiskInit] Fix reboot to recovery when stub cpio is used
|
||||
@@ -123,7 +141,7 @@
|
||||
- [General] Better data encryption detection
|
||||
- [General] Move the whole logging infrastructure into Rust
|
||||
|
||||
### v25.1
|
||||
### v25.1 (2022.6.19)
|
||||
|
||||
- [MagiskBoot] Fix ramdisk backup being incorrectly skipped
|
||||
- [MagiskBoot] Add new feature to detect unsupported dtb and abort during installation
|
||||
@@ -132,7 +150,7 @@
|
||||
- [MagiskInit] Fix config not properly exported in legacy SAR devices
|
||||
- [General] Enforce the Magisk app to always match or be newer than `magiskd`
|
||||
|
||||
### v25.0
|
||||
### v25.0 (2022.6.7)
|
||||
|
||||
- [MagiskInit] Update 2SI implementation, significantly increase device compatibility (e.g. Sony Xperia devices)
|
||||
- [MagiskInit] Introduce new `sepolicy` injection mechanism
|
||||
@@ -150,13 +168,13 @@
|
||||
- [DenyList] Fix DenyList on shared UID apps
|
||||
- [BusyBox] Add workaround for devices running old kernels
|
||||
|
||||
### v24.3
|
||||
### v24.3 (2022.3.10)
|
||||
|
||||
- [General] Stop using `getrandom` syscall
|
||||
- [Zygisk] Update API to v3, adding new fields to `AppSpecializeArgs`
|
||||
- [App] Improve app repackaging installation workflow
|
||||
|
||||
### v24.2
|
||||
### v24.2 (2022.3.1)
|
||||
|
||||
- [MagiskSU] Fix buffer overflow
|
||||
- [MagiskSU] Fix owner managed multiuser superuser settings
|
||||
@@ -174,11 +192,11 @@
|
||||
- [App] Major app upgrade flow improvements
|
||||
- [General] Improve commandline error handling and messaging
|
||||
|
||||
### v24.1
|
||||
### v24.1 (2022.1.28)
|
||||
|
||||
- [App] Stability improvements
|
||||
|
||||
### v24.0
|
||||
### v24.0 (2022.1.26)
|
||||
|
||||
- [General] MagiskHide is removed from Magisk
|
||||
- [General] Support Android 12
|
||||
@@ -207,7 +225,7 @@
|
||||
- [App] Restore the ability to install Magisk on the other slot on some A/B devices
|
||||
- [App] Allow modules to specify an update URL for in-app update + install
|
||||
|
||||
### v23.0
|
||||
### v23.0 (2021.5.12)
|
||||
|
||||
- [App] Update snet extension. This fixes SafetyNet API errors.
|
||||
- [App] Fix a bug in the stub app that causes APK installation to fail
|
||||
@@ -221,7 +239,7 @@
|
||||
- [MagiskHide] Update package and process name validation logic
|
||||
- [MagiskHide] Some changes that prevents zygote deadlock
|
||||
|
||||
### v22.1
|
||||
### v22.1 (2021.4.9)
|
||||
|
||||
- [App] Prevent multiple installation sessions running in parallel
|
||||
- [App] Prevent OutOfMemory crashes when checking boot signature on PXA boot images
|
||||
@@ -236,7 +254,7 @@
|
||||
- [MagiskInit] Fix `sepolicy.rule` mounting strategy
|
||||
- [resetprop] Always delete existing `ro.` props before updating. This will fix bootloops that could be caused by modifying device fingerprint properties.
|
||||
|
||||
### v22.0
|
||||
### v22.0 (2021.2.23)
|
||||
|
||||
- [General] Magisk and Magisk Manager is now merged into the same package!
|
||||
- [App] The term "Magisk Manager" is no longer used elsewhere. We refer it as the Magisk app.
|
||||
@@ -248,18 +266,18 @@
|
||||
- [MagiskInit] Support Galaxy S21 series
|
||||
- [MagiskSU] Fix incorrect APEX paths that caused `libsqlite.so` fail to load
|
||||
|
||||
### v21.4
|
||||
### v21.4 (2021.1.17)
|
||||
|
||||
- [MagiskSU] Fix `su -c` behavior that broke many root apps
|
||||
- [General] Properly handle read/write over sockets (the `broken pipe` issue)
|
||||
|
||||
### v21.3
|
||||
### v21.3 (2021.1.16)
|
||||
|
||||
- [MagiskInit] Avoid mounting `f2fs` userdata as it may result in kernel crashes. This shall fix a lot of bootloops
|
||||
- [MagiskBoot] Fix a minor header checksum bug for `DHTB` header and ASUS `blob` image formats
|
||||
- [MagiskHide] Allowing hiding isolated processes if the mount namespace is separated
|
||||
|
||||
### v21.2
|
||||
### v21.2 (2020.12.28)
|
||||
|
||||
- [MagiskInit] Detect 2SI after mounting `system_root` on legacy SAR devices
|
||||
- [General] Make sure `post-fs-data` scripts cannot block more than 35 seconds
|
||||
@@ -268,7 +286,7 @@
|
||||
- [General] Directly log to file to prevent `logcat` weirdness
|
||||
- [MagiskBoot] Fix header dump/load for header v3 images
|
||||
|
||||
### v21.1
|
||||
### v21.1 (2020.11.13)
|
||||
|
||||
- [MagiskBoot] Support boot header v3 (Pixel 5 and 4a 5G)
|
||||
- [MagiskBoot] Distinguish `lz4_lg` and `lz4_legacy` (Pixel 5 and 4a 5G)
|
||||
@@ -283,7 +301,7 @@
|
||||
- [MagiskHide] Support hiding apps installed in secondary users (e.g. work profile)
|
||||
- [MagiskHide] Make zygote detection more robust
|
||||
|
||||
### v21.0
|
||||
### v21.0 (2020.10.3)
|
||||
|
||||
- [General] Support Android 11 🎉
|
||||
- [General] Add Safe Mode detection. Disable all modules when the device is booting into Safe Mode.
|
||||
@@ -303,7 +321,7 @@
|
||||
- [MagiskBoot] Pad boot images to original size with zeros
|
||||
- [MagiskHide] Manipulate additional vendor properties
|
||||
|
||||
### v20.4
|
||||
### v20.4 (2020.3.23)
|
||||
|
||||
- [MagiskInit] Fix potential bootloop in A-only 2SI devices
|
||||
- [MagiskInit] Properly support Tegra partition naming
|
||||
@@ -321,11 +339,11 @@
|
||||
- [Scripts] Better addon.d (both v1 and v2) support
|
||||
- [Scripts] Support Lineage Recovery for Android 10+
|
||||
|
||||
### v20.3
|
||||
### v20.3 (2020.1.10)
|
||||
|
||||
- [MagiskBoot] Fix `lz4_legacy` decompression
|
||||
|
||||
### v20.2
|
||||
### v20.2 (2020.1.2)
|
||||
|
||||
- [MagiskSU] Properly handle communication between daemon and application (root request prompt)
|
||||
- [MagiskInit] Fix logging in kmsg
|
||||
@@ -333,7 +351,7 @@
|
||||
- [General] Support pre-init sepolicy patch in modules
|
||||
- [Scripts] Update magisk stock image backup format
|
||||
|
||||
### v20.1
|
||||
### v20.1 (2019.11.2)
|
||||
|
||||
- [MagiskSU] Support component name agnostic communication (for stub APK)
|
||||
- [MagiskBoot] Set proper `header_size` in boot image headers (fix vbmeta error on Samsung devices)
|
||||
@@ -342,7 +360,7 @@
|
||||
- [General] Move acct to prevent daemon being killed
|
||||
- [General] Make sure "--remove-modules" will execute uninstall.sh after removal
|
||||
|
||||
### v20.0
|
||||
### v20.0 (2019.10.11)
|
||||
|
||||
- [MagiskBoot] Support inject/modify `mnt_point` value in DTB fstab
|
||||
- [MagiskBoot] Support patching QCDT
|
||||
@@ -352,7 +370,7 @@
|
||||
- [MagiskHide] Fix bug that reject process names with ":"
|
||||
- [MagicMount] Fix a bug that cause /product mirror not created
|
||||
|
||||
### v19.4
|
||||
### v19.4 (2019.9.19)
|
||||
|
||||
- [MagiskInit] [SAR] Boot system-as-root devices with system mounted as /
|
||||
- [MagiskInit] [2SI] Support 2-stage-init for A/B devices (Pixel 3 Android 10)
|
||||
@@ -368,7 +386,7 @@
|
||||
- [General] Add new `--remove-modules` command to remove modules without root in ADB shell
|
||||
- [General] Support Android 10 new APEX libraries (Project Mainline)
|
||||
|
||||
### v19.3
|
||||
### v19.3 (2019.6.5)
|
||||
|
||||
- [MagiskHide] Hugely improve process monitor implementation, hopefully should no longer cause 100% CPU and daemon crashes
|
||||
- [MagiskInit] Wait for partitions to be ready for early mount, should fix bootloops on a handful of devices
|
||||
@@ -376,7 +394,7 @@
|
||||
- [MagiskSU] Properly implement mount namespace isolation
|
||||
- [MagiskBoot] Proper checksum calculation for header v2
|
||||
|
||||
### v19.2
|
||||
### v19.2 (2019.5.20)
|
||||
|
||||
- [General] Fix uninstaller
|
||||
- [General] Fix bootloops on some devices with tmpfs mounting to /data
|
||||
@@ -385,7 +403,7 @@
|
||||
This fix issues with users locking Magisk Manager with app lock, and prevent
|
||||
video apps get messed up when an app is requesting root in the background.
|
||||
|
||||
### v19.1
|
||||
### v19.1 (2019.5.1)
|
||||
|
||||
- [General] Support recovery based Magisk
|
||||
- [General] Support Android Q Beta 2
|
||||
@@ -395,7 +413,7 @@
|
||||
- [MagicMount] Use self created device nodes for mirrors
|
||||
- [MagicMount] Do not allow adding new files/folders in partition root folder (e.g. /system or /vendor)
|
||||
|
||||
### v19.0
|
||||
### v19.0 (2019.3.28)
|
||||
|
||||
- [General] Remove usage of magisk.img
|
||||
- [General] Add 64 bit magisk binary for native 64 bit support
|
||||
@@ -413,14 +431,14 @@
|
||||
- [MagiskSU] Use `ACTION_REBOOT` intent to workaround some OEM broadcast restrictions
|
||||
- [General] Use `skip_mount` instead of `auto_mount`: from opt-in to opt-out
|
||||
|
||||
### v18.1
|
||||
### v18.1 (2019.2.4)
|
||||
|
||||
- [General] Support EMUI 9.0
|
||||
- [General] Support Kirin 960 devices
|
||||
- [General] Support down to Android 4.2
|
||||
- [General] Major code base modernization under-the-hood
|
||||
|
||||
### v18.0
|
||||
### v18.0 (2018.12.8)
|
||||
|
||||
- [General] Migrate all code base to C++
|
||||
- [General] Modify database natively instead of going through Magisk Manager
|
||||
@@ -442,7 +460,7 @@
|
||||
- [MagiskBoot] Try to repair broken v1 boot image headers
|
||||
- [MagiskBoot] Add new CPIO command: "exists"
|
||||
|
||||
### v17.3
|
||||
### v17.3 (2018.10.20)
|
||||
|
||||
- [MagiskBoot] Support boot image header v1 (Pixel 3)
|
||||
- [MagiskSU] No more linked lists for caching `su_info`
|
||||
@@ -453,13 +471,13 @@
|
||||
- [Scripts] Switch hexpatch to remove Samsung Defex to a more general pattern
|
||||
- [Scripts] Update data encryption detection for better custom recovery support
|
||||
|
||||
### v17.2
|
||||
### v17.2 (2018.9.21)
|
||||
|
||||
- [ResetProp] Update to AOSP upstream to support serialized system properties
|
||||
- [MagiskInit] Randomize Magisk service names to prevent detection (e.g. FGO)
|
||||
- [MagiskSU] New communication scheme to communicate with Magisk Manager
|
||||
|
||||
### v17.0/17.1
|
||||
### v17.0/17.1 (2018.9.1)
|
||||
|
||||
- [General] Bring back install to inactive slot for OTAs on A/B devices
|
||||
- [Script] Remove system based root in addon.d
|
||||
@@ -471,7 +489,7 @@
|
||||
- [MagiskHide] Kill all processes with same UID of the target to workaround OOS embryo optimization
|
||||
- [MagiskInit] Move all sepolicy patches pre-init to prevent Pixel 2 (XL) boot service breakdown
|
||||
|
||||
### v16.7
|
||||
### v16.7 (2018.7.19)
|
||||
|
||||
- [Scripts] Fix boot image patching errors on Android P (workaround the strengthened seccomp)
|
||||
- [MagiskHide] Support hardlink based ns proc mnt (old kernel support)
|
||||
@@ -479,7 +497,7 @@
|
||||
- [Daemon] Log fatal errors only on debug builds
|
||||
- [MagiskInit] Detect early mount partname from fstab in device tree
|
||||
|
||||
### v16.6
|
||||
### v16.6 (2018.7.8)
|
||||
|
||||
- [General] Add wrapper script to overcome weird `LD_XXX` flags set in apps
|
||||
- [General] Prevent bootloop when flashing Magisk after full wipe on FBE devices
|
||||
@@ -501,7 +519,7 @@
|
||||
- [ImgTool] Use precise free space calculation methods
|
||||
- [ImgTool] Use our own set of loop devices hidden along side with sbin tmpfs overlay. This not only eliminates another possible detection method, but also fixes apps that mount OBB files as loop devices (huge thanks to dev of Pzizz for reporting this issue)
|
||||
|
||||
### v16.4
|
||||
### v16.4 (2018.4.29)
|
||||
|
||||
- [Daemon] Directly check logcat command instead of detecting logd, should fix logging and MagiskHide on several Samsung devices
|
||||
- [Daemon] Fix startup Magisk Manager APK installation on Android P
|
||||
@@ -515,17 +533,17 @@
|
||||
- [resetprop] Add Protobuf encode/decode to support manipulating persist properties on Android P
|
||||
- [MagiskHide] Include app sub-services as hiding targets. This might significantly increase the amount of apps that could be properly hidden
|
||||
|
||||
### v16.3
|
||||
### v16.3 (2018.3.28)
|
||||
|
||||
- [General] Remove symlinks used for backwards compatibility
|
||||
- [MagiskBoot] Fix a small size calculation bug
|
||||
|
||||
### v16.2
|
||||
### v16.2 (2018.3.18)
|
||||
|
||||
- [General] Force use system binaries in handling ext4 images (fix module installation on Android P)
|
||||
- [MagiskHide] Change property state to disable if logd is disabled
|
||||
|
||||
### v16.1
|
||||
### v16.1 (2018.3.11)
|
||||
|
||||
- [MagiskBoot] Fix MTK boot image packaging
|
||||
- [MagiskBoot] Add more Nook/Acclaim headers support
|
||||
@@ -535,13 +553,13 @@
|
||||
- [resetprop] Support Android P new property context files
|
||||
- [MagiskPolicy] Add new rules for Android P
|
||||
|
||||
### v16.0
|
||||
### v16.0 (2018.2.22)
|
||||
|
||||
- [MagiskInit] Support non `skip_initramfs` devices with slot suffix (Huawei Treble)
|
||||
- [MagiskPolicy] Add rules for Magisk Manager
|
||||
- [Compiler] Workaround an NDK compiler bug that causes bootloops
|
||||
|
||||
### v15.4
|
||||
### v15.4 (2018.2.13)
|
||||
|
||||
- [MagiskBoot] Support Samsung PXA, DHTB header images
|
||||
- [MagiskBoot] Support ASUS blob images
|
||||
@@ -553,13 +571,13 @@
|
||||
- [Daemon] Obfuscate binary names to prevent naive detections
|
||||
- [Daemon] Check logd before force trying to start logcat in a loop
|
||||
|
||||
### v15.3
|
||||
### v15.3 (2018.1.12)
|
||||
|
||||
- [Daemon] Fix the bug that only one script would be executed in post-fs-data.d/service.d
|
||||
- [Daemon] Add `MS_SILENT` flag when mounting, should fix some devices that cannot mount magisk.img
|
||||
- [MagiskBoot] Fix potential segmentation fault when patching ramdisk, should fix some installation failures
|
||||
|
||||
### v15.2
|
||||
### v15.2 (2018.1.1)
|
||||
|
||||
- [MagiskBoot] Fix dtb verity patches, should fix dm-verity bootloops on newer devices placing fstabs in dtb
|
||||
- [MagiskPolicy] Add new rules for proper Samsung support, should fix MagiskHide
|
||||
@@ -567,17 +585,17 @@
|
||||
- [Daemon] Use specific logcat buffers, some devices does not support all log buffers
|
||||
- [scripts] Update scripts to double check whether boot slot is available, some devices set a boot slot without A/B partitions
|
||||
|
||||
### v15.1
|
||||
### v15.1 (2017.12.29)
|
||||
|
||||
- [MagiskBoot] Fix faulty code in ramdisk patches which causes bootloops in some config and fstab format combos
|
||||
|
||||
### v15.0
|
||||
### v15.0 (2017.12.26)
|
||||
|
||||
- [Daemon] Fix the bug that Magisk cannot properly detect /data encryption state
|
||||
- [Daemon] Add merging `/cache/magisk.img` and `/data/adb/magisk_merge.img` support
|
||||
- [Daemon] Update to upstream libsepol to support cutting edge split policy custom ROM cil compilations
|
||||
|
||||
### v14.6 (1468)
|
||||
### v14.6 (2017.12.22)
|
||||
|
||||
- [General] Move all files into a safe location: /data/adb
|
||||
- [Daemon] New invincible implementation: use `magiskinit_daemon` to monitor sockets
|
||||
@@ -590,12 +608,12 @@
|
||||
- [MagiskBoot] Massive refactor, rewrite all cpio operations and CLI
|
||||
- [MagiskInit][magiskboot] Support ramdisk high compression mode
|
||||
|
||||
### v14.5 (1456)
|
||||
### v14.5 (1456) (2017.11.23)
|
||||
|
||||
- [Magiskinit] Fix bootloop issues on several devices
|
||||
- [misc] Build binaries with NDK r10e, should get rid of the nasty linker warning when executing magisk
|
||||
|
||||
### v14.5 (1455)
|
||||
### v14.5 (1455) (2017.11.23)
|
||||
|
||||
- [Daemon] Moved internal path to /sbin/.core, new image mountpoint is /sbin/.core/img
|
||||
- [MagiskSU] Support switching package name, used when Magisk Manager is hidden
|
||||
@@ -610,7 +628,7 @@
|
||||
- [script] Add dtbo.img backup and restore support
|
||||
- [misc] Many small adjustments to properly support old platforms like Android 5.0
|
||||
|
||||
### v14.3 (1437)
|
||||
### v14.3 (2017.10.15)
|
||||
|
||||
- [MagiskBoot] Fix Pixel C installation
|
||||
- [MagiskBoot] Handle special `lz4_legacy` format properly, should fix all LG devices
|
||||
@@ -620,11 +638,11 @@
|
||||
- [Daemon] Add brute-force image resizing mode, should prevent the notorious Samsung crappy resize2fs from affecting the result
|
||||
- [resetprop] Add new "-p" flag, used to toggle whether alter/access the actual persist storage for persist props
|
||||
|
||||
### v14.2
|
||||
### v14.2 (2017.9.28)
|
||||
|
||||
- [MagicMount] Clone attributes to tmpfs mountpoint, should fix massive module breakage
|
||||
|
||||
### v14.1
|
||||
### v14.1 (2017.9.28)
|
||||
|
||||
- [MagiskInit] Introduce a new init binary to support `skip_initramfs` devices (Pixel family)
|
||||
- [script] Fix typo in update-binary for x86 devices
|
||||
@@ -639,7 +657,7 @@
|
||||
- [resetprop] Fix a bug which delete props won't remove persist props not in memory
|
||||
- [MagicMount] Remove usage of dummy folder, directly mount tmpfs and construct file structure skeleton in place
|
||||
|
||||
### v14.0
|
||||
### v14.0 (2017.9.6)
|
||||
|
||||
- [script] Simplify installation scripts
|
||||
- [script] Fix a bug causing backing up and restoring stock boot images failure
|
||||
@@ -658,20 +676,20 @@
|
||||
- [Daemon] Adjustments to prevent stock Samsung kernel restrictions on exec system calls for binaries started from /data
|
||||
- [Daemon] Workaround on Samsung device with weird fork behaviors
|
||||
|
||||
### v13.3
|
||||
### v13.3 (2017.7.18)
|
||||
|
||||
- [MagiskHide] Update to bypass Google CTS (2017.7.17)
|
||||
- [resetprop] Properly support removing persist props
|
||||
- [uninstaller] Remove Magisk Manager and persist props
|
||||
|
||||
### v13.2
|
||||
### v13.2 (2017.7.14)
|
||||
|
||||
- [magiskpolicy] Fix magiskpolicy segfault on old Android versions, should fix tons of older devices that couldn't use v13.1
|
||||
- [MagiskHide] Set proper selinux context while re-linking /sbin to hide Magisk, should potentially fix many issues
|
||||
- [MagiskBoot] Change lzma compression encoder flag from `LZMA_CHECK_CRC64` to `LZMA_CHECK_CRC32`, kernel only supports latter
|
||||
- [General] Core-only mode now properly mounts systemless hosts and magiskhide
|
||||
|
||||
### v13.1
|
||||
### v13.1 (2017.7.11)
|
||||
|
||||
- [General] Merge MagiskSU, magiskhide, resetprop, magiskpolicy into one binary
|
||||
- [General] Add Android O support (tested on DP3)
|
||||
@@ -702,7 +720,7 @@
|
||||
- [MagiskHide] Remove background magiskhide daemon, spawn short life process for unmounting purpose
|
||||
- [Magic Mount] Ditched shell script based mounting, use proper C program to parse and mount files. Speed is SIGNIFICANTLY improved
|
||||
|
||||
### v12.0
|
||||
### v12.0 (2017.3.31)
|
||||
|
||||
- [General] Move most binaries into magisk.img (Samsung cannot run su daemon in /data)
|
||||
- [General] Move sepolicy live patch to `late_start` service
|
||||
@@ -719,7 +737,7 @@
|
||||
- [MagiskBoot] Add lz4 legacy format support (most linux kernel using lz4 for compression is using this)
|
||||
- [MagiskBoot] Fix MTK kernels with MTK headers
|
||||
|
||||
### v11.5/11.6
|
||||
### v11.5/11.6 (2017.3.21)
|
||||
|
||||
- [Magic Mount] Fix mounting issues with devices that have separate /vendor partitions
|
||||
- [MagiskBoot] Whole new boot image patching tool, please check release note for more info
|
||||
@@ -731,12 +749,12 @@
|
||||
- [MagiskSU] Fix read-only partition mounting issues
|
||||
- [MagiskSU] Disable -cn option, the option will do nothing, preserved for compatibility
|
||||
|
||||
### v11.1
|
||||
### v11.1 (2017.2.6)
|
||||
|
||||
- [sepolicy-inject] Add missing messages
|
||||
- [magiskhide] Start MagiskHide with scripts
|
||||
|
||||
### v11.0
|
||||
### v11.0 (2017.2.6)
|
||||
|
||||
- [Magic Mount] Support replacing symlinks.
|
||||
Symlinks cannot be a target of a bind mounted, so they are treated the same as new files
|
||||
@@ -764,13 +782,13 @@
|
||||
- [Addition] Add post-fs-data.d and service.d
|
||||
- [Addition] Add option to disable Magisk (MagiskSU will still be started)
|
||||
|
||||
### v10.2
|
||||
### v10.2 (2017.1.2)
|
||||
|
||||
- [Magic Mount] Remove apps/priv-app from whitelist, should fix all crashes
|
||||
- [phh] Fix binary out-of-date issue
|
||||
- [scripts] Fix root disappear issue when upgrading within Magisk Manager
|
||||
|
||||
### v10
|
||||
### v10 (2017.1.2)
|
||||
|
||||
- [Magic Mount] Use a new way to mount system (vendor) mirrors
|
||||
- [Magic Mount] Use universal way to deal with /vendor, handle both separate partition or not
|
||||
@@ -784,7 +802,7 @@
|
||||
- [scripts] Improve SuperSU integration, now uses sukernel to patch ramdisk, support SuperSU built in ramdisk restore
|
||||
- [template] Add PROPFILE option to load system.prop
|
||||
|
||||
### v9
|
||||
### v9 (2016.11.14)
|
||||
|
||||
- **[API Change] Remove the interface for post-fs modules**
|
||||
- [resetprop] New tool "resetprop" is added to Magisk to replace most post-fs modules' functionality
|
||||
@@ -796,13 +814,13 @@
|
||||
- [Boot Image] Add support for Motorola boot image dtb, it shall now unpack correctly
|
||||
- [Uninstaller] Add removal of SuperSU custom patch script
|
||||
|
||||
### v8
|
||||
### v8 (2016.10.19)
|
||||
|
||||
- Add Magisk Hide to bypass SafetyNet
|
||||
- Improve SuperSU integration: no longer changes the SuperSU PATH
|
||||
- Support rc script entry points not located in init.rc
|
||||
|
||||
### v7
|
||||
### v7 (2016.10.04)
|
||||
|
||||
- Fully open source
|
||||
- Remove supolicy dependency, use my own sepolicy-injection
|
||||
@@ -814,17 +832,17 @@
|
||||
- New paths to toggle busybox, and support all root solutions
|
||||
- Remove root management API; both SuperSU and phh has their own superior solutions
|
||||
|
||||
### v6
|
||||
### [v6 (2016.8.21)](https://xdaforums.com/t/magisk-general-support-discussion.3432382/post-68298121)
|
||||
|
||||
- Fixed the algorithm for adding new files and dummy system
|
||||
- Updated the module template with a default permission, since people tend to forget them :)
|
||||
|
||||
### v5
|
||||
### [v5 (2016.8.20)](https://xdaforums.com/t/magisk-general-support-discussion.3432382/post-68274534)
|
||||
|
||||
- Hotfix for older Android versions (detect policy before patching)
|
||||
- Update uninstaller to NOT uninstall Magisk Manager, since it cause problems
|
||||
|
||||
### v4
|
||||
### [v4 (2016.8.19)](https://xdaforums.com/t/magisk-general-support-discussion.3432382/post-68269300)
|
||||
|
||||
- Important: Uninstall v1 - v3 Magisk before upgrading with the uninstaller in the OP!!
|
||||
- Massive Rewrite Magisk Interface API! All previous mods are NOT compatible! Please download the latest version of the mods you use (root/xposed)
|
||||
@@ -833,7 +851,7 @@
|
||||
- Use minimal sepolicy patch in boot image for smaller ramdisk size. Live patch policies after bootup
|
||||
- Include updated open source sepolicy injection tool (source code available), support nearly all SuperSU supolicy tool's functionality
|
||||
|
||||
### v3
|
||||
### [v3 (2016.8.11)](https://xdaforums.com/t/magisk-general-support-discussion.3432382/post-68146978)
|
||||
|
||||
- Fix bootimg-extract for Exynos Samsung devices (thanks to @phhusson), should fix all Samsung device issues
|
||||
- Add supolicy back to patch sepolicy (stock Samsung do not accept permissive domain)
|
||||
@@ -843,7 +861,7 @@
|
||||
- Use the highest possible compression rate for ramdisk, hope to fix some devices with no boot partition space
|
||||
- Detect boot partition space insufficient, will abort installer instead of breaking your device
|
||||
|
||||
### v2
|
||||
### [v2 (2016.8.9)](https://xdaforums.com/t/magisk-general-support-discussion.3432382/post-68108058)
|
||||
|
||||
- Fix verity patch. It should now work on all devices (might fix some of the unable-to-boot issues)
|
||||
- All scripts will now run in selinux permissive mode for maximum compatibility (this will **NOT** turn your device to permissive)
|
||||
@@ -853,6 +871,6 @@
|
||||
- Remove sepolicy patches that uses SuperSU's supolicy tool; it is now using a minimal set of modifications
|
||||
- Removed Magisk Manager in Magisk patch, it is now included in Magisk phh's superuser only
|
||||
|
||||
### v1
|
||||
### [v1 (2016.8.3)](https://xdaforums.com/t/magisk-general-support-discussion.3432382/post-68034103)
|
||||
|
||||
- Initial release
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
## 2018.12.7 Magisk v18.0
|
||||
|
||||
Here comes a stable release, this time with quite a few major updates!
|
||||
|
||||
### MagiskHide Improvements
|
||||
Starting from v18, the process monitor matches component names instead of process names. Android allow app services to name their process arbitrarily, and many apps starting to use dedicated services to detect root; it used to require adding all of these service process names to the list to hide Magisk effectively. Component names have the format: `<package name>/<java class name>`, which means we can always know which application spawned a given process.
|
||||
|
||||
**TL;DR, ALL processes spawned from the applications on the hide list will be targeted.**
|
||||
|
||||
Recently I discovered a *very widespread Linux kernel bug* affecting tons of Android devices (full write-up: [Medium Article](https://medium.com/@topjohnwu/from-anime-game-to-android-system-security-vulnerability-9b955a182f20)). This bug exposes the supposedly protected `procfs`, which is abused in some apps to detect Magisk with information leaked from other processes. Magisk will patch this bug on all Android 7.0+ devices. Yes, a fully effective MagiskHide requires the enhanced Android Sandbox in modern Android versions.
|
||||
|
||||
### Path Changes
|
||||
The name of the folder `/sbin/.core` is confusing and will no longer be used; it is replaced with `/sbin/.magisk`. Another major change is the location to store general boot scripts. As these boot scripts should still run even if `magisk.img` is not mounted, they are moved out of `magisk.img`, from `<img>/.core/<stage>.d` to `/data/adb/<stage>.d` (stage is either `post-fs-data` or `service`). Say goodbye to stupid paths like `/sbin/.core/img/.core/post-fs-data.d`!
|
||||
|
||||
Quick recap:
|
||||
|
||||
- New `magisk.img` mountpoint: `/sbin/.magisk/img`
|
||||
- New internal busybox PATH: `/sbin/.magisk/busybox`
|
||||
- The folder `<img>/.core` is no longer used in any places. `magisk.img` is solely used for storing modules, no other functionality depends on it.
|
||||
- **Symlinks are created so all old paths will still work. None of the existing apps/scripts depending on these internal paths should break, but please migrate to the new paths ASAP.**
|
||||
|
||||
### Dropping Legacy Support
|
||||
**The NEXT Magisk Manager upgrade (not this one) will only support v18+, please upgrade ASAP.** Magisk Manager is always designed to be fully functional across a wide range of Magisk versions. However, to enforce full obfuscation, I will have to drop legacy support eventually.
|
||||
|
||||
This is also a good opportunity to push the whole community forward, all module developers should forget about backward compatibility (e.g. stop supporting the old Magisk paths, please don't torture yourself...). I expect very few structural changes in the near future, so again, please upgrade ASAP :)
|
||||
|
||||
### Modern C++ Code Base
|
||||
Although this has nothing to do with the end user, tons of effort was done to migrate Magisk to a more modern C++ code base instead of the previous good plain old C. This makes the code easier to maintain and allows me to utilized many C++ language features.
|
||||
|
||||
### Full Changelog: [here](https://topjohnwu.github.io/Magisk/changes.html)
|
||||
@@ -1,17 +0,0 @@
|
||||
# 2019.2.4 Magisk v18.1
|
||||
|
||||
What is a better way to celebrate Chinese New Year than a new Magisk update!
|
||||
|
||||
### EMUI 9 Support
|
||||
Welcome on board "again", Huawei! Even though Huawei had officially blocked bootloader unlocks, people still love to buy them (duh), and there are paid services that unlock Huawei bootloaders. So hey, get Magisk installed on that bad boy! One caveat is that since Huawei have changed the partitions, special workarounds has to be done. Details and instructions are in the newly created [instruction page](https://topjohnwu.github.io/Magisk/install.html)
|
||||
|
||||
### Support Down to Android 4.2
|
||||
Because why not, it was quite a lot of fun LOL. All devices running KitKat and higher will have all features enabled. MagiskHide and resetprop aren't possible on Jellybean, and Magic Mount (modules) is temporarily disabled; basically it only works as a root solution for now. Android 4.1 isn't 100% usable yet, so installation is also temporarily blocked. Eventually, all Jellybean devices will have full Magic Mount and MagiskSU support.
|
||||
|
||||
### Major Magisk Manager Update
|
||||
Aside from the obvious major UI overhaul, tons of little user experience and performance improvements are also added. The app is finally less crappy now :)
|
||||
|
||||
### Final Words
|
||||
I'm aware that there are apps updated to detect Magisk, however no MagiskHide improvements efforts are done in this release; v18.1 is aimed to be as stable as possible. Stay tuned for future public betas, or if you are more adventurous, jump on the Canary Channel bandwagon for more aggressive hiding techniques :)
|
||||
|
||||
### Full Changelog: [here](https://topjohnwu.github.io/Magisk/changes.html)
|
||||
@@ -1,29 +0,0 @@
|
||||
# 2019.3.28 Magisk v19.0
|
||||
|
||||
I would say this is one of my most ambitious release of all time! Due to the extremely massive changes, this release will be a public beta. Calling it v18.2 doesn't do it justice, so v19.0 we go.
|
||||
|
||||
## Magisk Module installer
|
||||
**Magisk module developers: pay extra attention!** A completely new [Magisk Module Installer](https://github.com/topjohnwu/magisk-module-installer) replaces the old Magisk module template. This new format decouples **ALL** installation logic from modules, and encourages developers to use the provided API for installation. This new format is **ENFORCED**, meaning all existing modules should upgrade ASAP, and new modules are **REQUIRED** to follow the rules.
|
||||
|
||||
Carefully read through the [updated docs](https://topjohnwu.github.io/Magisk/guides.html)!
|
||||
|
||||
**Warning: All existing modules that does not use the new module format will be automatically removed on May 1st, 2019. Module devs: upgrade your existing modules ASAP!**
|
||||
|
||||
## Imageless Magisk
|
||||
Since the existence of Magisk, all modules are stored within an EXT4 image which will be loop mounted at boot. This approach has a few problems: resizing the image is a huge headache (no live resizing, `resize2fs` on some devices refuse to work properly), and also MANY devices using F2FS ships a broken driver with the kernel, causing EXT4 loop devices unable to be mounted at all. All these problems come to an end now: modules are now directly stored in `/data`! Backwards compatibility is provided, for modules that uses the official module template, installation should work just fine.
|
||||
|
||||
**Warning: Although module migration was tested, there are still chances that your modules will get lost in the process. Be prepared to reinstall your existing modules in that case.**
|
||||
|
||||
## Native 64 Bit is Back
|
||||
At one point in history, Magisk uses native 64 binaries. However due to binary size considerations, all binaries was switched to 32 bit. Starting from v19, all static binaries are still 32 bit only, but the most important part: the main `magisk` binary now runs in native 64 bit on supported devices.
|
||||
|
||||
## Zygote Ptrace Based MagiskHide
|
||||
MagiskHide used to use `logcat` to monitor activity manager events for new process creation. That method is extremely unreliable: even with constant improvements since introduction, it is still not working 100% of the time. Here comes a fundamentally new approach: ptrace the zygote process and step through all fork events. In layman's term, this new method is able to target a process before it even starts to run! The code for it is extremely tricky, but it was tested for quite a while in the canary channel, so I'm confident enough to release this to the public :)
|
||||
|
||||
## Android Q
|
||||
Full support for Android Q Beta 1 is also introduced in this release. However, you cannot use it on the Pixel 3 (XL) due to the fact that Google decided to use logical partitions on the 3rd gen Pixels starting with Q. A solution is still WIP, please stay tuned!
|
||||
|
||||
## Final Words
|
||||
What you can expect in upcoming releases: Samsung S10 support, and full logical partition support. Also, I *AM* aware of Google Pay issues, but these are not my main focus now since there are still tons of other issues for me to focus on. Several discussion threads on XDA provide seemingly working solutions, please do some research on your own.
|
||||
|
||||
### Full Changelog: [here](https://topjohnwu.github.io/Magisk/changes.html)
|
||||
@@ -1,12 +0,0 @@
|
||||
# 2019.5.1 Magisk v19.1
|
||||
Finally, a lovely stable release!
|
||||
|
||||
For those that were using v18.1, here are some quick highlights of v19.0
|
||||
|
||||
- Imageless Magisk: Although module migration was tested, there are still chances that your modules will get lost in the process. Be prepared to reinstall your existing modules in that case.
|
||||
- Native 64-bit support
|
||||
- Zygote Ptrace Based MagiskHide
|
||||
|
||||
Other than adding support for Samsung system-as-root devices, this release is mostly bug fixes from v19.0. Enjoy :)
|
||||
|
||||
### Full Changelog: [here](https://topjohnwu.github.io/Magisk/changes.html)
|
||||
@@ -1 +0,0 @@
|
||||
# TODO
|
||||
@@ -1 +0,0 @@
|
||||
# TODO
|
||||
@@ -1,23 +0,0 @@
|
||||
# 2019.9.19 Magisk v19.4
|
||||
This version is heavily tested and tons of bugs were squashed before release. However due to the massive changes, it is decided to release a public beta for people/root app developers to adjust/update before things hit public stable.
|
||||
|
||||
### New System-as-root Implementation
|
||||
Magisk has supported system-as-root devices for a long time since the first Pixel came out. The goal is always to revert things back to the good old initramfs based root dir. However, this not only creates tons of issues on many devices, not easily hide-able with MagiskHide, but most importantly not even possible on Android 10. Starting with v19.4, Magisk will follow how Google has designed system-as-root: mounting system actually to `/` (root).
|
||||
|
||||
This implies several **MASSIVE** consequences for system-as-root devices:
|
||||
- `/system` is no longer a valid mount point. For existing root apps that remounts `/system` to `rw`, you will have to remount `/` instead of `/system`
|
||||
- The root directory (`/`) is no longer `rootfs`, but actually system. Remounting `/` to `rw` and modify files means you are writing to the actual system partition, NOT volatile storage as it used to be in `rootfs`. This is not recommended as user is not necessary aware that you are tampering an actual partition, sometimes dangerous if dm-verity/AVB-verity is enforced, or sometimes outright impossible since many devices now ship with read-only system partitions (e.g. EROFS, EXT4 dedup)
|
||||
- Several custom kernel rely on Magisk's root directory overlay system (`overlay`) for modifying `/`. This is no longer compatible with the new implementation. A new overlay system (`overlay.d`) will replace the existing one as an alternative (details in [documentations](https://topjohnwu.github.io/Magisk/guides.html#root-directory-overlay-system)). To provide backwards compatibility, Magisk will switch to "Compat Mode" when `/overlay` is detected, which simply reverts to the old system-as-root setup. **Compat Mode will not work on Android 10 and will cause bootloop**. Although things will still work as it used to, **please upgrade to `overlay.d` ASAP**.
|
||||
|
||||
### Android 10 Support
|
||||
Other than A-only devices running Android 10, Android 10 is fully supported with MagiskHide fully functioning. Android 10's biggest challenge is the new "2-Stage-Init" system-as-root implementation, which is the sole reason why A-only is not support yet. Stay tuned for further updates as that is the next thing on the list.
|
||||
|
||||
(For those interested in "2-Stage-Init" and other details of system-as-root, check [this Twitter thread I tweeted](https://twitter.com/topjohnwu/status/1174392824625676288))
|
||||
|
||||
### Product Partition Support
|
||||
Magisk Module developers can now finally properly modify files in `/product`! This partition is now an essential part in Android 10, and many files are moved from system to product. Please check [documentations](https://topjohnwu.github.io/Magisk/details.html#magic-mount) for more details.
|
||||
|
||||
### A-Only System-as-root
|
||||
A huge number of new devices have A-only system-as-root setups (Android 9.0). These unfortunate devices will have to install Magisk into the recovery partition. Please check the fully updated [Installation Guide](https://topjohnwu.github.io/Magisk/install.html) for more details.
|
||||
|
||||
### Full Changelog: [here](https://topjohnwu.github.io/Magisk/changes.html)
|
||||
@@ -1,23 +0,0 @@
|
||||
# 2019.10.11 Magisk v20.0
|
||||
The following release notes are mostly the same as v19.4. Compared to v19.4 beta, the most notable change is adding tons of support for more devices on Android 10, along with several bug fixes.
|
||||
|
||||
### New System-as-root Implementation
|
||||
Magisk has supported system-as-root devices for a long time since the first Pixel came out. The goal is always to revert things back to the good old initramfs based root dir. However, this not only creates tons of issues on many devices, not easily hide-able with MagiskHide, but most importantly not even possible on Android 10. Magisk will now start to follow how Google has designed system-as-root: mounting system actually to `/` (root).
|
||||
|
||||
This implies several **MASSIVE** consequences for system-as-root devices:
|
||||
- `/system` is no longer a valid mount point. For existing root apps that remounts `/system` to `rw`, you will have to remount `/` instead of `/system`
|
||||
- The root directory (`/`) is no longer `rootfs`, but actually system. Remounting `/` to `rw` and modify files means you are writing to the actual system partition, NOT volatile storage as it used to be in `rootfs`. This is not recommended as user is not necessary aware that you are tampering an actual partition, sometimes dangerous if dm-verity/AVB-verity is enforced, or sometimes outright impossible since many devices now ship with read-only system partitions (e.g. EROFS, EXT4 dedup)
|
||||
- Several custom kernel rely on Magisk's root directory overlay system (`overlay`) for modifying `/`. This is no longer compatible with the new implementation. A new overlay system (`overlay.d`) will replace the existing one as an alternative (details in [documentations](https://topjohnwu.github.io/Magisk/guides.html#root-directory-overlay-system)). To provide backwards compatibility, Magisk will switch to "Compat Mode" when `/overlay` is detected, which simply reverts to the old system-as-root setup. **Compat Mode will not work on Android 10 and will cause bootloop**. Although things will still work as it used to, **please upgrade to `overlay.d` ASAP**.
|
||||
|
||||
### Android 10 Support
|
||||
Android 10 is now fully supported with MagiskHide working as expected. Android 10's biggest challenge is the new "2-Stage-Init" system-as-root implementation, which requires modding early mount fstab in a specific way, and in many devices' cases involves patching DTBs in the boot image.
|
||||
|
||||
(For those interested in "2-Stage-Init" and other details of system-as-root, check [this Twitter thread I tweeted](https://twitter.com/topjohnwu/status/1174392824625676288))
|
||||
|
||||
### Product Partition Support
|
||||
Magisk Module developers can now finally properly modify files in `/product`! This partition is now an essential part in Android 10, and many files are moved from system to product. Please check [documentations](https://topjohnwu.github.io/Magisk/details.html#magic-mount) for more details.
|
||||
|
||||
### A-Only System-as-root
|
||||
A huge number of new devices have A-only system-as-root setups (Android 9.0). These unfortunate devices will have to install Magisk into the recovery partition. Please check the fully updated [Installation Guide](https://topjohnwu.github.io/Magisk/install.html) for more details.
|
||||
|
||||
### Full Changelog: [here](https://topjohnwu.github.io/Magisk/changes.html)
|
||||
@@ -1,16 +0,0 @@
|
||||
# 2019.11.2 Magisk v20.1
|
||||
Lots of bug fixes from v20.0, and some cool new features!
|
||||
|
||||
### Updated Magisk Manager Hiding
|
||||
Starting with Magisk v20.1 paired with Magisk Manager v7.4.0, a new hiding mode is introduced for Android 9.0+. On supported devices, Magisk Manager will download and customize a heavily obfuscated stub APK and use it as a replacement. The stub app will then download the full app into its private internal data, then dynamically load and run the actual full Magisk Manager.
|
||||
|
||||
Note, not all Android 9.0+ devices will be able to use this feature. To use an obfuscated stub as Magisk Manager, the Magisk daemon will have to rely on a special way to communicate with the app, and some OEMs (most likely Chinese manufacturers) block certain broadcasts, breaking the communication channel.
|
||||
|
||||
Magisk Manager will verify compatibility before it uses stubs to hide itself on Android 9.0+. **The verification relies on Magisk v20.1+, which means you have to fully upgrade and reboot in order to opt in this feature.** If you are already running a hidden Magisk Manager, **restore and upgrade Magisk Manager, upgrade Magisk and reboot, then re-hide the app**.
|
||||
|
||||
For those incompatible with the hiding-with-stub feature, there are also a few updates that everyone, regardless whether using stubs or not, can enjoy:
|
||||
|
||||
- You can now customize the app name of the repackaged Magisk Manager
|
||||
- Magisk Manager will generate new keys to sign the repackaged APK to prevent signature detection
|
||||
|
||||
### Full Changelog: [here](https://topjohnwu.github.io/Magisk/changes.html)
|
||||
@@ -1,15 +0,0 @@
|
||||
# 2020.1.2 Magisk v20.2
|
||||
|
||||
Happy New Year! Let's start 2020 with a new Magisk release :)
|
||||
|
||||
### Pre-Init sepolicy Patches for Modules
|
||||
Magisk v20.2 add support for modules to include its own custom sepolicy patches. Developers used to use boot scripts along with the `magiskpolicy` tool to do live sepolicy patches; however, this method leads to numerous issues as Android is no longer designed to allow live sepolicy patches, and on some devices (e.g. Huawei) this method is outright inapplicable.
|
||||
|
||||
To address this issue, Magisk allow module devs to create a new file called `sepolicy.rule` in their modules. The module installer script and Magisk daemon will make sure this file is stored in somewhere accessible pre-init to allow `magiskinit` to do its job every time your device boots up.
|
||||
|
||||
### New Module Installer Format
|
||||
The old template is actually pretty convoluted: developers are expected to implement specific callback functions in their `install.sh`, and the zip file structure does not directly represent how modules are actually stored on your device. The new module installer format makes creating new modules very easy, but still give experienced developers tons of freedom to do anything they want in the installation process.
|
||||
|
||||
For details regarding `sepolicy.rule` and the new module installer format, please read the updated [Developer Guides](https://topjohnwu.github.io/Magisk/guides.html). Note that the old "Module Installer Template" is obsolete; creating a Magisk module no longer requires a "template" as it is now a straightforward process.
|
||||
|
||||
### Full Changelog: [here](https://topjohnwu.github.io/Magisk/changes.html)
|
||||
@@ -1,9 +0,0 @@
|
||||
# 2020.1.10 Magisk v20.3
|
||||
|
||||
### Magisk
|
||||
- Fix `magiskboot` crashing when dealing with `lz4_legacy` format
|
||||
|
||||
### Magisk Manager
|
||||
- Fix MagiskHide app component toggles
|
||||
|
||||
### Full Changelog: [here](https://topjohnwu.github.io/Magisk/changes.html)
|
||||
@@ -1,28 +0,0 @@
|
||||
## 2020.3.23 Magisk v20.4
|
||||
|
||||
### Miscellaneous
|
||||
This release is mainly focused on stability and bug squashing. Please be aware that MagiskHide is no longer enabled by default. Since Google has enabled [hardware-based key attestation](https://twitter.com/topjohnwu/status/1237656703929180160?s=20) in SafetyNet ([FAQ](https://twitter.com/topjohnwu/status/1237830555523149824?s=20)), there is no effective way to pass full CTS SafetyNet anymore (although Google seems to have temporarily [reverted the change](https://twitter.com/topjohnwu/status/1238514375150850048?s=20)).
|
||||
|
||||
I decided that the fully redesigned Magisk Manager isn't fully ready for prime time yet, so this time around no Magisk Manager update is released. The WIP manager will continue to be improved and is available for testing on the canary channel.
|
||||
|
||||
### BusyBox Standalone Mode
|
||||
Starting with Magisk v20.4, all Magisk related scripts, including boot scripts and module installation scripts, will run on BusyBox's shell (ash) in **standalone mode**. In BusyBox ash standalone mode, **every single command** will be **forced** to use the one that is in Magisk's BusyBox (if available). For instance, no matter how you change the environment variable `PATH`, the `rm` command will always use the one in BusyBox, not the one in system, external BusyBox, vendor, or included in custom recovery.
|
||||
|
||||
The reason behind this change is that all scripts will be guaranteed to have 100% consistent results no matter how the environment is setup. The internal BusyBox is significantly beefed up with patches from @osm0sis, and also with SELinux features enabled. It shall offer a very complete, reliable, and consistent scripting environment. If in any case you **require** to use a command outside of BusyBox, please call it with the full path (e.g. `/system/bin/nslookup`)
|
||||
|
||||
### Magisk Changelog
|
||||
- [MagiskInit] Fix potential bootloop in A-only 2SI devices
|
||||
- [MagiskInit] Properly support Tegra partition naming
|
||||
- [General] Load libsqlite.so dynamically, which removes the need to use wrapper scripts on Android 10+
|
||||
- [General] Detect API level with a fallback method on some devices
|
||||
- [General] Workaround possible bug in x86 kernel readlinkat system call
|
||||
- [BusyBox] Enable SELinux features. Add chcon/runcon etc., and '-Z' option to many applets
|
||||
- [BusyBox] Introduce standalone mode. More details in release notes
|
||||
- [MagiskHide] Disable MagiskHide by default
|
||||
- [MagiskHide] Add more potential detectable system properties
|
||||
- [MagiskHide] Add workaround for Xiaomi devices bootloop when MagiskHide is enabled on cross region ROMs
|
||||
- [MagiskBoot] Support patching special Motorolla DTB format
|
||||
- [MagiskPolicy] Support 'genfscon' sepolicy rules
|
||||
- [Scripts] Support NAND based boot images (character nodes in /dev/block)
|
||||
- [Scripts] Better addon.d (both v1 and v2) support
|
||||
- [Scripts] Support Lineage Recovery for Android 10+
|
||||
@@ -1,19 +0,0 @@
|
||||
## 2020.10.3 Magisk v21.0
|
||||
|
||||
Long time no see! v21.0 is the largest release in Magisk's history. It comes with full Android 11 support (tons of stuff had to be rewritten from scratch!), and a completely redesigned Magisk Manager. These are the reasons why this particular public release took me over half a year to wrap up.
|
||||
|
||||
To the end user, not much has changed other than the fact that Magisk Manager has completely changed its appearance. However developers should pay attention to some changes due to adjustments for Android 11. Full changelogs are too massive to fit, so here I'll point out the main changes and links to updated documentations.
|
||||
|
||||
### Highlights
|
||||
|
||||
- Android 11 support 🎉
|
||||
- Completely redesigned Magisk Manager
|
||||
- Safe Mode detection: if you installed a module that bootloops your device, reboot into Safe Mode and all modules will be disabled. More instructions on how to deal with broken modules is linked [here](https://topjohnwu.github.io/Magisk/faq.html).
|
||||
|
||||
The following are for advanced users/developer:
|
||||
|
||||
- On Android 8.0+, Magisk now uses a new SELinux setup that keeps Android sandbox less compromised. This provides better security to rooted users, and also separates Magisk rules from original rules. Details [here](https://topjohnwu.github.io/Magisk/details.html#selinux-policies).
|
||||
- On Android 11, `/sbin` may no longer exist. For developers, this means the Magisk's internal `tmpfs` directory is no longer always `/sbin`, and instead randomly created every boot. To get the `tmpfs` path, use the command `magisk --path` (more details [here](https://topjohnwu.github.io/Magisk/details.html)). For custom kernel developers that uses `overlay.d`, updated docs are [here](https://topjohnwu.github.io/Magisk/guides.html#root-directory-overlay-system).
|
||||
- `magiskpolicy` gained more features and some minor syntax changes, details [here](https://topjohnwu.github.io/Magisk/tools.html#magiskpolicy).
|
||||
|
||||
### Full Changelog: [here](https://topjohnwu.github.io/Magisk/changes.html)
|
||||
@@ -1,5 +0,0 @@
|
||||
## 2020.11.13 Magisk v21.1
|
||||
|
||||
v21.1 is a maintenance update from v21.0, mostly addressing bugs, refining some details, and adding new boot image format support (for Pixel 5 and 4a 5G). Checkout the full [v21.0 release notes](https://topjohnwu.github.io/Magisk/releases/21000.html) if coming from older releases.
|
||||
|
||||
### Full Changelog: [here](https://topjohnwu.github.io/Magisk/changes.html)
|
||||
@@ -1,14 +0,0 @@
|
||||
## 2020.12.28 Magisk v21.2
|
||||
|
||||
v21.2 is a maintenance update, mostly addressing bugs, and expanding device compatibility. Checkout the full [v21.0 release notes](https://topjohnwu.github.io/Magisk/releases/21000.html) if coming from older releases.
|
||||
|
||||
### v21.2
|
||||
|
||||
- [MagiskInit] Detect 2SI after mounting `system_root` on legacy SAR devices
|
||||
- [General] Make sure `post-fs-data` scripts cannot block more than 35 seconds
|
||||
- [General] Fix the `magisk --install-module` command
|
||||
- [General] Trim Windows newline when reading files
|
||||
- [General] Directly log to file to prevent `logcat` weirdness
|
||||
- [MagiskBoot] Fix header dump/load for header v3 images
|
||||
|
||||
### Full Changelog: [here](https://topjohnwu.github.io/Magisk/changes.html)
|
||||
@@ -1,18 +0,0 @@
|
||||
## 2021.1.17 Magisk v21.4
|
||||
|
||||
**Update**: v21.4 adds more regression hot fixes.
|
||||
|
||||
Happy 2021! v21.3 adds a workaround for devices with buggy F2FS Linux kernel drivers. This F2FS bug may cause bootloops on many devices. Checkout the full [v21.0 release notes](https://topjohnwu.github.io/Magisk/releases/21000.html) if coming from older releases.
|
||||
|
||||
### v21.4
|
||||
|
||||
- [MagiskSU] Fix `su -c` behavior that broke many root apps
|
||||
- [General] Properly handle read/write over sockets (the `broken pipe` issue)
|
||||
|
||||
### v21.3
|
||||
|
||||
- [MagiskInit] Avoid mounting `f2fs` userdata as it may result in kernel crashes. This shall fix a lot of bootloops
|
||||
- [MagiskBoot] Fix a minor header checksum bug for `DHTB` header and ASUS `blob` image formats
|
||||
- [MagiskHide] Allowing hiding isolated processes if the mount namespace is separated
|
||||
|
||||
### Full Changelog: [here](https://topjohnwu.github.io/Magisk/changes.html)
|
||||
@@ -1,24 +0,0 @@
|
||||
## 2021.2.23 Magisk v22.0
|
||||
|
||||
### RESTORE THE EXISTING MAGISK MANAGER BACK TO NORMAL BEFORE UPGRADING IF HIDDEN!
|
||||
|
||||
Another major Magisk release! This time our focus is not the core Magisk implementation, but rather on improving the whole Magisk user experience.
|
||||
|
||||
### Magisk Manager is dead.<br>Long live the Magisk app!
|
||||
|
||||
Ever since the first Magisk release, Magisk (the core components) and Magisk Manager (the companion app) are released separately and isn't necessarily always in sync. This leads to some confusion and a lot of complexity when downloading/installing Magisk through the app. Starting from v22.0, the Magisk app (renamed from Magisk Manager) includes everything it needs within the APK itself, making installation a 100% offline process.
|
||||
|
||||
Custom recovery lovers, no worries! The Magisk app APK *itself* is a custom recovery flashable zip, just like MAGIC™🌈. Check out the updated [installation guide](https://topjohnwu.github.io/Magisk/install.html) for more info.
|
||||
|
||||
### App Hiding
|
||||
|
||||
Another major breakthrough in this release is that devices lower than Android 9.0 can now also use the advanced app hiding technique to hide the Magisk app. Due to this incompatible change, **RESTORE THE EXISTING MAGISK MANAGER BACK TO NORMAL BEFORE UPGRADING IF HIDDEN!**
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- [MagiskHide] Fix a bug when stopping MagiskHide does not take effect
|
||||
- [MagiskBoot] Fix bug when unpacking `lz4_lg` compressed boot images
|
||||
- [MagiskInit] Support Galaxy S21 series
|
||||
- [MagiskSU] Fix incorrect APEX paths that caused `libsqlite.so` fail to load
|
||||
|
||||
### Full Changelog: [here](https://topjohnwu.github.io/Magisk/changes.html)
|
||||
@@ -1,22 +0,0 @@
|
||||
## 2021.4.9 Magisk v22.1
|
||||
|
||||
This release is focused on fixing regressions and bugs. Check the [v22.0 release notes](https://topjohnwu.github.io/Magisk/releases/22000.html) if coming from older releases.
|
||||
|
||||
Note: Magisk v22 is the last major version to support Jellybean and Kitkat. Magisk v23 will only support Android 5.0 and higher.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- [App] Prevent multiple installation sessions running in parallel
|
||||
- [App] Prevent OutOfMemory crashes when checking boot signature on PXA boot images
|
||||
- [General] Proper cgroup migration implementation
|
||||
- [General] Rewrite log writer from scratch, should resolve any crashes and deadlocks
|
||||
- [General] Many scripts updates fixing regressions
|
||||
- [MagiskHide] Prevent possible deadlock when signal arrives
|
||||
- [MagiskHide] Partial match process names if necessary
|
||||
- [MagiskBoot] Preserve and patch AVB 2.0 structures/headers in boot images
|
||||
- [MagiskBoot] Properly strip out data encryption flags
|
||||
- [MagiskBoot] Prevent possible integer overflow
|
||||
- [MagiskInit] Fix `sepolicy.rule` mounting strategy
|
||||
- [resetprop] Always delete existing `ro.` props before updating. This will fix bootloops that could be caused by modifying device fingerprint properties.
|
||||
|
||||
### Full Changelog: [here](https://topjohnwu.github.io/Magisk/changes.html)
|
||||
@@ -1,21 +0,0 @@
|
||||
## 2021.5.12 Magisk v23.0
|
||||
|
||||
This release is focused on fixing regressions and bugs.
|
||||
|
||||
Note: Magisk v22 is the last major version to support Jellybean and Kitkat. Magisk v23 only supports Android 5.0 and higher.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- [App] Update snet extension. This fixes SafetyNet API errors.
|
||||
- [App] Fix a bug in the stub app that causes APK installation to fail
|
||||
- [App] Hide annoying errors in logs when hidden as stub
|
||||
- [App] Fix issues when patching ODIN tar files when the app is hidden
|
||||
- [General] Remove all pre Android 5.0 support
|
||||
- [General] Update BusyBox to use proper libc
|
||||
- [General] Fix C++ undefined behaviors
|
||||
- [General] Several `sepolicy.rule` copy/installation fixes
|
||||
- [MagiskPolicy] Remove unnecessary sepolicy rules
|
||||
- [MagiskHide] Update package and process name validation logic
|
||||
- [MagiskHide] Some changes that prevents zygote deadlock
|
||||
|
||||
### Full Changelog: [here](https://topjohnwu.github.io/Magisk/changes.html)
|
||||
@@ -1,21 +0,0 @@
|
||||
## 2022.1.26 Magisk v24.0
|
||||
|
||||
It has been a while since the last public release, long time no see! A personal update for those unaware: I am now working at Google on the Android Platform Security team. Without further ado, let's jump right into it!
|
||||
|
||||
### MagiskHide Removal
|
||||
|
||||
I have lost interest in fighting this battle for quite a while; plus, the existing MagiskHide implementation is flawed in so many ways. Decoupling Magisk from root hiding is, in my opinion, beneficial to the community. Ever since my announcement on Twitter months ago, highly effective "root hiding" modules (much **MUCH** better than MagiskHide) has been flourishing, which again shows that people are way more capable than I am on this subject. So why not give those determined their time to shine, and let me focus on improving Magisk instead of drowning in the everlasting cat-and-mouse game 😉.
|
||||
|
||||
### Sunsetting Magisk-Modules-Repo
|
||||
|
||||
Due to lack of time and maintenance, the centralized Magisk-Modules-Repo was frozen, and the functionality to download modules from the repo is removed in v24.0. As a supplement, module developers can now specify an `updateJson` URL in their modules. The Magisk app will use that to check, download, and install module updates.
|
||||
|
||||
### Introducing Zygisk
|
||||
|
||||
Zygisk is **Magisk in Zygote**, the next big thing for Magisk! When this feature is enabled, a part of Magisk will run in the `Zygote` daemon process, allowing module developers to run code directly in every Android apps' processes. If you've heard of [Riru](https://github.com/RikkaApps/Riru), then Zygisk is inspired by that project and is functionally similar, though the implementation is quite different internally. I cannot wait to see what module developers can achieve using Zygisk!
|
||||
|
||||
### Documentation
|
||||
|
||||
For developers, details about `updateJson` and building Zygisk modules can all be found in the updated [documentation](https://topjohnwu.github.io/Magisk/guides.html#magisk-modules).
|
||||
|
||||
### Full Changelog: [here](https://topjohnwu.github.io/Magisk/changes.html)
|
||||
@@ -1,23 +0,0 @@
|
||||
## 2022.1.28 Magisk v24.1
|
||||
|
||||
> For those coming from v24.0, v24.1 only has some minor app improvements. The following are copied from v24.0 release notes.
|
||||
|
||||
It has been a while since the last public release, long time no see! A personal update for those unaware: I am now working at Google on the Android Platform Security team. Without further ado, let's jump right into it!
|
||||
|
||||
### MagiskHide Removal
|
||||
|
||||
I have lost interest in fighting this battle for quite a while; plus, the existing MagiskHide implementation is flawed in so many ways. Decoupling Magisk from root hiding is, in my opinion, beneficial to the community. Ever since my announcement on Twitter months ago, highly effective "root hiding" modules (much **MUCH** better than MagiskHide) has been flourishing, which again shows that people are way more capable than I am on this subject. So why not give those determined their time to shine, and let me focus on improving Magisk instead of drowning in the everlasting cat-and-mouse game 😉.
|
||||
|
||||
### Sunsetting Magisk-Modules-Repo
|
||||
|
||||
Due to lack of time and maintenance, the centralized Magisk-Modules-Repo was frozen, and the functionality to download modules from the repo is removed in v24.0. As a supplement, module developers can now specify an `updateJson` URL in their modules. The Magisk app will use that to check, download, and install module updates.
|
||||
|
||||
### Introducing Zygisk
|
||||
|
||||
Zygisk is **Magisk in Zygote**, the next big thing for Magisk! When this feature is enabled, a part of Magisk will run in the `Zygote` daemon process, allowing module developers to run code directly in every Android apps' processes. If you've heard of [Riru](https://github.com/RikkaApps/Riru), then Zygisk is inspired by that project and is functionally similar, though the implementation is quite different internally. I cannot wait to see what module developers can achieve using Zygisk!
|
||||
|
||||
### Documentation
|
||||
|
||||
For developers, details about `updateJson` and building Zygisk modules can all be found in the updated [documentation](https://topjohnwu.github.io/Magisk/guides.html#magisk-modules).
|
||||
|
||||
### Full Changelog: [here](https://topjohnwu.github.io/Magisk/changes.html)
|
||||
@@ -1,21 +0,0 @@
|
||||
## 2022.3.1 Magisk v24.2
|
||||
|
||||
Maintenance release fixing various issues.
|
||||
|
||||
- [MagiskSU] Fix buffer overflow
|
||||
- [MagiskSU] Fix owner managed multiuser superuser settings
|
||||
- [MagiskSU] Fix command logging when using `su -c <cmd>`
|
||||
- [MagiskSU] Prevent su request indefinite blocking
|
||||
- [MagiskBoot] Support `lz4_legacy` archive with multiple magic
|
||||
- [MagiskBoot] Fix `lz4_lg` compression
|
||||
- [DenyList] Allow targeting processes running as system UID
|
||||
- [Zygisk] Workaround Samsung's "early zygote"
|
||||
- [Zygisk] Improved Zygisk loading mechanism
|
||||
- [Zygisk] Fix application UID tracking
|
||||
- [Zygisk] Fix improper `umask` being set in zygote
|
||||
- [App] Fix BusyBox execution test
|
||||
- [App] Improve stub loading mechanism
|
||||
- [App] Major app upgrade flow improvements
|
||||
- [General] Improve commandline error handling and messaging
|
||||
|
||||
### Full Changelog: [here](https://topjohnwu.github.io/Magisk/changes.html)
|
||||
@@ -1,9 +0,0 @@
|
||||
## 2022.3.10 Magisk v24.3
|
||||
|
||||
For those coming from v24.1, check the full changelog for changes introduced in v24.2.
|
||||
|
||||
- [General] Stop using `getrandom` syscall
|
||||
- [Zygisk] Update API to v3, adding new fields to `AppSpecializeArgs`
|
||||
- [App] Improve app repackaging installation workflow
|
||||
|
||||
### Full Changelog: [here](https://topjohnwu.github.io/Magisk/changes.html)
|
||||
@@ -1,23 +0,0 @@
|
||||
## 2022.6.7 Magisk v25.0
|
||||
|
||||
Another major release! A lot of the changes aren't visible at the surface, but v25 is actually a really substantial upgrade!
|
||||
|
||||
### MagiskInit Rewrite
|
||||
|
||||
A significant portion of `magiskinit` (the critical software that runs before your device boots up) is completely rewritten from scratch. Ever since Android introduced [Project Treble](https://android-developers.googleblog.com/2017/05/here-comes-treble-modular-base-for.html) in Android 8.0, Magisk has been constantly fighting against the increasingly complex partitioning and early mount setups of all kinds of devices, sometimes with weird OEM specific implementations. It got to a point that `magiskinit` had become so complicated that few people (including myself!) were aware of every detail, and maintaining this piece of software like this was clearly not sustainable. After many months of planning (yes, this whole re-architecture has been in my head for a long time) and some help from external contributors, a whole new `sepolicy` injection mechanism is introduced into Magisk, solving the "SELinux Problem" once and for all.
|
||||
|
||||
Since this is a full paradigm shift on how Magisk hot-patch the device at boot, several behaviors that many developers implicitly relied on might not exist. For example, Magisk no longer patches fstabs in most scenarios, which means AVB will remain intact; some custom kernels rely on AVB being stripped out for them by Magisk.
|
||||
|
||||
### MagiskSU Security Enhancements
|
||||
|
||||
The superuser functionality of Magisk has not seen much changes ever since its introduction. v25 focuses on making root permission management more accurate and secure:
|
||||
|
||||
- Add a whole new package tracking system to ensure malicious UID reuse attack cannot be performed
|
||||
- Properly support and implement the UX in the Magisk app for packages using `sharedUserId`
|
||||
- Enforce root manager APK signature verification to combat the rampant unofficial Magisk app "mods"
|
||||
|
||||
Many might not realize, but using a trusted, unmodified Magisk app is really important. Magisk's root daemon treats the Magisk app differently and gives it blanket root access without any restrictions. A modded Magisk app can potentially backdoor your device.
|
||||
|
||||
And in case some of you are about to put on your tin foil hats, this is not designed to "vendor lock-in"; the goal is to make sure your root management app comes from the same developer of the underlying root implementation. Magisk's build system allows custom distributors to use its own signing keys, and in addition, I am also providing official debug builds which skips any signature verification for development.
|
||||
|
||||
### Full Changelog: [here](https://topjohnwu.github.io/Magisk/changes.html)
|
||||
@@ -1,25 +0,0 @@
|
||||
## 2022.6.19 Magisk v25.1
|
||||
|
||||
> v25.1 fixes some minor bugs over v25.0. The following are the same as v25.0 release notes.
|
||||
|
||||
Another major release! A lot of the changes aren't visible at the surface, but v25 is actually a really substantial upgrade!
|
||||
|
||||
### MagiskInit Rewrite
|
||||
|
||||
A significant portion of `magiskinit` (the critical software that runs before your device boots up) is completely rewritten from scratch. Ever since Android introduced [Project Treble](https://android-developers.googleblog.com/2017/05/here-comes-treble-modular-base-for.html) in Android 8.0, Magisk has been constantly fighting against the increasingly complex partitioning and early mount setups of all kinds of devices, sometimes with weird OEM specific implementations. It got to a point that `magiskinit` had become so complicated that few people (including myself!) were aware of every detail, and maintaining this piece of software like this was clearly not sustainable. After many months of planning (yes, this whole re-architecture has been in my head for a long time) and some help from external contributors, a whole new `sepolicy` injection mechanism is introduced into Magisk, solving the "SELinux Problem" once and for all.
|
||||
|
||||
Since this is a full paradigm shift on how Magisk hot-patch the device at boot, several behaviors that many developers implicitly relied on might not exist. For example, Magisk no longer patches fstabs in most scenarios, which means AVB will remain intact; some custom kernels rely on AVB being stripped out for them by Magisk.
|
||||
|
||||
### MagiskSU Security Enhancements
|
||||
|
||||
The superuser functionality of Magisk has not seen much changes ever since its introduction. v25 focuses on making root permission management more accurate and secure:
|
||||
|
||||
- Add a whole new package tracking system to ensure malicious UID reuse attack cannot be performed
|
||||
- Properly support and implement the UX in the Magisk app for packages using `sharedUserId`
|
||||
- Enforce root manager APK signature verification to combat the rampant unofficial Magisk app "mods"
|
||||
|
||||
Many might not realize, but using a trusted, unmodified Magisk app is really important. Magisk's root daemon treats the Magisk app differently and gives it blanket root access without any restrictions. A modded Magisk app can potentially backdoor your device.
|
||||
|
||||
And in case some of you are about to put on your tin foil hats, this is not designed to "vendor lock-in"; the goal is to make sure your root management app comes from the same developer of the underlying root implementation. Magisk's build system allows custom distributors to use its own signing keys, and in addition, I am also providing official debug builds which skips any signature verification for development.
|
||||
|
||||
### Full Changelog: [here](https://topjohnwu.github.io/Magisk/changes.html)
|
||||
@@ -1,11 +0,0 @@
|
||||
## 2022.7.20 Magisk v25.2
|
||||
|
||||
Maintenance release fixing various issues.
|
||||
|
||||
- [MagiskInit] Fix a potential issue when stub cpio is used
|
||||
- [MagiskInit] Fix reboot to recovery when stub cpio is used
|
||||
- [MagiskInit] Fix sepolicy.rules symlink for rootfs devices
|
||||
- [General] Better data encryption detection
|
||||
- [General] Move the whole logging infrastructure into Rust
|
||||
|
||||
### Full Changelog: [here](https://topjohnwu.github.io/Magisk/changes.html)
|
||||
@@ -1,25 +0,0 @@
|
||||
## 2023.4.5 Magisk v26.0
|
||||
|
||||
Hey! Long time no see!
|
||||
|
||||
### Bumping Minimum Android Version to 6.0
|
||||
|
||||
Magisk's support for Android Lollipop has been pretty broken for a while without it being noticed. Also, none of the active developers of Magisk have actual hardware to run Android Lollipop. We rely on using the official Android emulator for regression testing on older platforms, however Google never shipped a Lollipop emulator image with SELinux support, leaving us with no option but to drop Lollipop support since we don't feel comfortable supporting Android Lollipop without adequate testing.
|
||||
|
||||
### New Magic Mount Implementation
|
||||
|
||||
Magic Mount, the feature that make modules modify partitions, has gone through a major rewrite. The existing implementation doesn't work well with OEMs injecting overlays into their system using `overlayfs`. The new implementation fundamentally changes how filesystem mirrors are created, giving us a more accurate clone of the unmodified filesystem.
|
||||
|
||||
### New `sepolicy.rule` Implementation
|
||||
|
||||
Magisk allows modules to provide custom SELinux patches by including the file `sepolicy.rule`. Due to the complicated nature of SELinux patching, the compatibility of this functionality has been pretty spotty; many devices are not supported. In this release, a brand new pre-init partition detection mechanism has been designed to support even more devices. Due to complicated reasons, this detection mechanism cannot be performed in a custom recovery environment.
|
||||
|
||||
**This means that any installation of Magisk v26+ using custom recovery will be incomplete; a subsequent re-installation through the Magisk app after booting up is required.**
|
||||
|
||||
### Zygisk Updates
|
||||
|
||||
**The new Zygisk API v4 is now live!** It comes with new features and a refined PLT function hook API. The implementaton of Zygisk has also gone through some major refactoring, including new code loading/unloading mechanisms and a new PLT function hook implementation.
|
||||
|
||||
Head over to the [Zygisk Module Sample](https://github.com/topjohnwu/zygisk-module-sample) repository to check out the new API and documentation!
|
||||
|
||||
### Full Changelog: [here](https://topjohnwu.github.io/Magisk/changes.html)
|
||||
@@ -1,33 +0,0 @@
|
||||
## 2023.4.11 Magisk v26.1
|
||||
|
||||
### Changes from v26.0
|
||||
|
||||
- [App] Fix crashing when revoking root permissions
|
||||
- [MagiskInit] Always prefer `ext4` partitions over `f2fs` when selecting the pre-init partition
|
||||
- [General] Restore module files' context/owner/group from mirror. This is a regression introduced in v26.0
|
||||
|
||||
(The following is the same as v26.0 release notes)
|
||||
|
||||
Hey! Long time no see!
|
||||
|
||||
### Bumping Minimum Android Version to 6.0
|
||||
|
||||
Magisk's support for Android Lollipop has been pretty broken for a while without it being noticed. Also, none of the active developers of Magisk have actual hardware to run Android Lollipop. We rely on using the official Android emulator for regression testing on older platforms, however Google never shipped a Lollipop emulator image with SELinux support, leaving us with no option but to drop Lollipop support since we don't feel comfortable supporting Android Lollipop without adequate testing.
|
||||
|
||||
### New Magic Mount Implementation
|
||||
|
||||
Magic Mount, the feature that make modules modify partitions, has gone through a major rewrite. The existing implementation doesn't work well with OEMs injecting overlays into their system using `overlayfs`. The new implementation fundamentally changes how filesystem mirrors are created, giving us a more accurate clone of the unmodified filesystem.
|
||||
|
||||
### New `sepolicy.rule` Implementation
|
||||
|
||||
Magisk allows modules to provide custom SELinux patches by including the file `sepolicy.rule`. Due to the complicated nature of SELinux patching, the compatibility of this functionality has been pretty spotty; many devices are not supported. In this release, a brand new pre-init partition detection mechanism has been designed to support even more devices. Due to complicated reasons, this detection mechanism cannot be performed in a custom recovery environment.
|
||||
|
||||
**This means that any installation of Magisk v26+ using custom recovery will be incomplete; a subsequent re-installation through the Magisk app after booting up is required.**
|
||||
|
||||
### Zygisk Updates
|
||||
|
||||
**The new Zygisk API v4 is now live!** It comes with new features and a refined PLT function hook API. The implementaton of Zygisk has also gone through some major refactoring, including new code loading/unloading mechanisms and a new PLT function hook implementation.
|
||||
|
||||
Head over to the [Zygisk Module Sample](https://github.com/topjohnwu/zygisk-module-sample) repository to check out the new API and documentation!
|
||||
|
||||
### Full Changelog: [here](https://topjohnwu.github.io/Magisk/changes.html)
|
||||
@@ -1,19 +0,0 @@
|
||||
## 2023.8.27 Magisk v26.2
|
||||
|
||||
- [MagiskBoot] Support extracting boot image from `payload.bin`
|
||||
- [MagiskBoot] Support cpio files containing character files
|
||||
- [MagiskBoot] Support listing cpio content
|
||||
- [MagiskBoot] Directly handle AVB 1.0 signing and verification without going through Java implementation
|
||||
- [Daemon] Make daemon socket a fixed path in MAGISKTMP
|
||||
- [resetprop] Support printing property context
|
||||
- [resetprop] Support only printing persistent properties from storage
|
||||
- [resetprop] Properly support setting persistent properties bypassing property_service
|
||||
- [MagiskSU] Support `-g` and `-G` options
|
||||
- [MagiskSU] Support switching mount namespace to PID with `-t`
|
||||
- [MagiskPolicy] Fix patching extended permissions
|
||||
- [MagiskPolicy] Support more syntax for extended permissions
|
||||
- [MagiskPolicy] Support printing out the loaded sepolicy rules
|
||||
- [App] Support patching boot image from ROM zips
|
||||
- [App] Properly preserve `boot.img` when patching Samsung firmware with `init_boot.img`
|
||||
|
||||
### Full Changelog: [here](https://topjohnwu.github.io/Magisk/changes.html)
|
||||
@@ -1,28 +0,0 @@
|
||||
## 2023.9.4 Magisk v26.3
|
||||
|
||||
### v26.3
|
||||
|
||||
- [General] Fix device information detection script
|
||||
- [General] Update BusyBox to 1.36.1
|
||||
- [General] Update toolchain that produces broken arm32 executables
|
||||
- [App] Fix root service unable to bind on OnePlus devices
|
||||
|
||||
### v26.2
|
||||
|
||||
- [MagiskBoot] Support extracting boot image from `payload.bin`
|
||||
- [MagiskBoot] Support cpio files containing character files
|
||||
- [MagiskBoot] Support listing cpio content
|
||||
- [MagiskBoot] Directly handle AVB 1.0 signing and verification without going through Java implementation
|
||||
- [Daemon] Make daemon socket a fixed path in MAGISKTMP
|
||||
- [resetprop] Support printing property context
|
||||
- [resetprop] Support only printing persistent properties from storage
|
||||
- [resetprop] Properly support setting persistent properties bypassing property_service
|
||||
- [MagiskSU] Support `-g` and `-G` options
|
||||
- [MagiskSU] Support switching mount namespace to PID with `-t`
|
||||
- [MagiskPolicy] Fix patching extended permissions
|
||||
- [MagiskPolicy] Support more syntax for extended permissions
|
||||
- [MagiskPolicy] Support printing out the loaded sepolicy rules
|
||||
- [App] Support patching boot image from ROM zips
|
||||
- [App] Properly preserve `boot.img` when patching Samsung firmware with `init_boot.img`
|
||||
|
||||
### Full Changelog: [here](https://topjohnwu.github.io/Magisk/changes.html)
|
||||
@@ -1,12 +0,0 @@
|
||||
## 2023.11.5 Magisk v26.4
|
||||
|
||||
- [MagiskBoot] Don't pad zeros if signed boot image is larger
|
||||
- [MagiskPolicy] Fix `genfscon` and `filename_trans`
|
||||
- [MagiskPolicy] Fix bug in `libsepol`
|
||||
- [Zygisk] Fix and simplify file descriptor sanitization logic
|
||||
- [App] Prevent OOM when patching AP tarfiles
|
||||
- [App] Fix bug in device configuration detection
|
||||
- [Daemon] Fix certificate parsing of APKs
|
||||
- [General] Fix logging errors from C++ code being ignored
|
||||
|
||||
### Full Changelog: [here](https://topjohnwu.github.io/Magisk/changes.html)
|
||||
@@ -1,9 +0,0 @@
|
||||
## 2024.2.3 Magisk v27.0
|
||||
|
||||
- [Zygisk] Introduce new code injection mechanism
|
||||
- [Zygisk] Support new signature introduced in U QPR2
|
||||
- [SEPolicy] Update libsepol to properly set some policy config bits
|
||||
- [MagiskBoot] Support compressing `init` so Magisk is installable on devices with small boot partitions
|
||||
- [ResetProp] Add new wait for property feature `resetprop -w`
|
||||
|
||||
### Full Changelog: [here](https://topjohnwu.github.io/Magisk/changes.html)
|
||||
@@ -1,25 +0,0 @@
|
||||
## 2024.10.10 Magisk v28.0
|
||||
|
||||
- [General] Support 16k page size
|
||||
- [General] Add basic support for RISC-V (not built in releases)
|
||||
- [General] Use a minimal libc to build static executables (`magiskinit` and `magiskboot`) for smaller sizes
|
||||
- [Core] Remove unnecessary mirror for magic mount
|
||||
- [Core] Update boot image detection logic to support more devices
|
||||
- [MagiskInit] Rewrite 2SI logic for injecting `magiskinit` as `init`
|
||||
- [MagiskInit] Update preinit partition detection
|
||||
- [Zygisk] Update internal JNI hooking implementation
|
||||
- [MagiskPolicy] Preserve sepolicy config flag after patching
|
||||
- [MagiskPolicy] Optimize patching rules to reduce the amount of new rules being injected
|
||||
- [DenyList] Support enforcing denylist when Zygisk is disabled
|
||||
- [Resetprop] Improve implementation to workaround several property modification detections
|
||||
- [Resetprop] Update to properly work with property overlays
|
||||
- [App] Major internal code refactoring
|
||||
- [App] Support patching Samsung firmware with images larger than 8GiB
|
||||
- [App] Use user-initiated job instead of foreground services on Android 14
|
||||
- [App] Support Android 13+ built-in per-app language preferences
|
||||
- [App] Add `action.sh` support to allow modules to define an action triggered from UI
|
||||
- [MagiskBoot] Support spliting kernel images without decompression
|
||||
- [MagiskBoot] Properly support vendor boot images
|
||||
- [MagiskBoot] Disable Samsung PROCA from kernel image
|
||||
|
||||
### Full Changelog: [here](https://topjohnwu.github.io/Magisk/changes.html)
|
||||
@@ -1,33 +0,0 @@
|
||||
## 2024.12.6 Magisk v28.1
|
||||
|
||||
- [App] Fix stub APK download link
|
||||
- [App] Fix support for Android lower than 8.0
|
||||
- [General] Fix support for MTK Samsung devices
|
||||
- [MagiskInit] Fix a regression for 2SI devices
|
||||
- [MagiskPolicy] Fix a regression causing `overlay.d` replaced files to be not accessible
|
||||
|
||||
## Magisk v28.0 Changes
|
||||
|
||||
- [General] Support 16k page size
|
||||
- [General] Add basic support for RISC-V (not built in releases)
|
||||
- [General] Use a minimal libc to build static executables (`magiskinit` and `magiskboot`) for smaller sizes
|
||||
- [Core] Remove unnecessary mirror for magic mount
|
||||
- [Core] Update boot image detection logic to support more devices
|
||||
- [MagiskInit] Rewrite 2SI logic for injecting `magiskinit` as `init`
|
||||
- [MagiskInit] Update preinit partition detection
|
||||
- [Zygisk] Update internal JNI hooking implementation
|
||||
- [MagiskPolicy] Preserve sepolicy config flag after patching
|
||||
- [MagiskPolicy] Optimize patching rules to reduce the amount of new rules being injected
|
||||
- [DenyList] Support enforcing denylist when Zygisk is disabled
|
||||
- [Resetprop] Improve implementation to workaround several property modification detections
|
||||
- [Resetprop] Update to properly work with property overlays
|
||||
- [App] Major internal code refactoring
|
||||
- [App] Support patching Samsung firmware with images larger than 8GiB
|
||||
- [App] Use user-initiated job instead of foreground services on Android 14
|
||||
- [App] Support Android 13+ built-in per-app language preferences
|
||||
- [App] Add `action.sh` support to allow modules to define an action triggered from UI
|
||||
- [MagiskBoot] Support spliting kernel images without decompression
|
||||
- [MagiskBoot] Properly support vendor boot images
|
||||
- [MagiskBoot] Disable Samsung PROCA from kernel image
|
||||
|
||||
### Full Changelog: [here](https://topjohnwu.github.io/Magisk/changes.html)
|
||||
@@ -1,16 +0,0 @@
|
||||
## 2025.5.14 Magisk v29.0
|
||||
|
||||
This release looks minor at the surface, however, the entire codebase has gone through significant refactoring and migration. The native code in Magisk used to be mainly C++, but several contributors and I have been steadily rewriting parts of the code in Rust since April 2022. After years of effort, the Rust-ification of the project slowly began picking up steam, and at the moment of this release, over 40% of the native code has been rewritten in Rust, with several major subsystem rewrites in the PR queue, planned to be merged for the next release.
|
||||
|
||||
Many might wonder, why introduce a new language to the project? My reason is actually not to reduce memory safety issues (although it is a nice side benefit), but to be able to develop Magisk using a more modern programming language. After using Rust for a while, it's clear to me that using Rust allows me to write more correct code and makes me happier compared to dealing with C++. People share the [same sentiment as I do](https://threadreaderapp.com/thread/1577667445719912450.html).
|
||||
|
||||
## Changelog
|
||||
|
||||
- [General] Massive internal refactoring and code migration
|
||||
- [App] Support downloading module zip files with XZ compression
|
||||
- [App] Disable app animations when system animations are disabled
|
||||
- [MagiskMount] Support systemlessly deleting files with modules using blank file nodes
|
||||
- [MagiskInit] Redesign sepolicy patching and injection logic
|
||||
- [MagiskSU] Better TTY/PTY support
|
||||
|
||||
### Full Changelog: [here](https://topjohnwu.github.io/Magisk/changes.html)
|
||||
@@ -1,37 +0,0 @@
|
||||
# Release Notes
|
||||
|
||||
- [v29.0](29000.md)
|
||||
- [v28.1](28100.md)
|
||||
- [v28.0](28000.md)
|
||||
- [v27.0](27000.md)
|
||||
- [v26.4](26400.md)
|
||||
- [v26.3](26300.md)
|
||||
- [v26.2](26200.md)
|
||||
- [v26.1](26100.md)
|
||||
- [v26.0](26000.md)
|
||||
- [v25.2](25200.md)
|
||||
- [v25.1](25100.md)
|
||||
- [v25.0](25000.md)
|
||||
- [v24.3](24300.md)
|
||||
- [v24.2](24200.md)
|
||||
- [v24.1](24100.md)
|
||||
- [v24.0](24000.md)
|
||||
- [v23.0](23000.md)
|
||||
- [v22.1](22100.md)
|
||||
- [v22.0](22000.md)
|
||||
- [v21.4](21400.md)
|
||||
- [v21.2](21200.md)
|
||||
- [v21.1](21100.md)
|
||||
- [v21.0](21000.md)
|
||||
- [v20.4](20400.md)
|
||||
- [v20.3](20300.md)
|
||||
- [v20.2](20200.md)
|
||||
- [v20.1](20100.md)
|
||||
- [v20.0](20000.md)
|
||||
- [v19.4](19400.md)
|
||||
- [v19.3](19300.md)
|
||||
- [v19.2](19200.md)
|
||||
- [v19.1](19100.md)
|
||||
- [v19.0](19000.md)
|
||||
- [v18.1](18100.md)
|
||||
- [v18.0](18000.md)
|
||||
@@ -16,16 +16,12 @@ LOCAL_STATIC_LIBRARIES := \
|
||||
|
||||
LOCAL_SRC_FILES := \
|
||||
core/applets.cpp \
|
||||
core/magisk.cpp \
|
||||
core/daemon.cpp \
|
||||
core/scripting.cpp \
|
||||
core/sqlite.cpp \
|
||||
core/module.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 \
|
||||
@@ -83,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
|
||||
@@ -128,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
|
||||
|
||||
564
native/src/Cargo.lock
generated
564
native/src/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -10,45 +10,46 @@ edition = "2024"
|
||||
[workspace.dependencies]
|
||||
cxx = { path = "external/cxx-rs" }
|
||||
cxx-gen = { path = "external/cxx-rs/gen/lib" }
|
||||
libc = "0.2.172"
|
||||
cfg-if = "1.0.1"
|
||||
libc = "0.2.176"
|
||||
cfg-if = "1.0.3"
|
||||
num-traits = "0.2.19"
|
||||
num-derive = "0.4.2"
|
||||
thiserror = "2.0.12"
|
||||
thiserror = "2.0.16"
|
||||
byteorder = "1.5.0"
|
||||
size = "0.5.0"
|
||||
bytemuck = "1.23.1"
|
||||
bytemuck = "1.23.2"
|
||||
fdt = "0.1.5"
|
||||
const_format = "0.2.34"
|
||||
bit-set = "0.8.0"
|
||||
syn = "2.0.102"
|
||||
syn = "2.0.106"
|
||||
quote = "1.0.40"
|
||||
proc-macro2 = "1.0.95"
|
||||
proc-macro2 = "1.0.101"
|
||||
argh = { version = "0.1.13", default-features = false }
|
||||
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.5.2", default-features = false }
|
||||
bzip2 = "0.6.0"
|
||||
zopfli = "0.8.2"
|
||||
lz4 = "1.28.1"
|
||||
xz2 = "0.1.7"
|
||||
lzma-rust2 = { version = "0.14.2", default-features = false }
|
||||
nix = "0.30.1"
|
||||
bitflags = "2.9.4"
|
||||
|
||||
# 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.5"
|
||||
p384 = "0.14.0-pre.5"
|
||||
p521 = "0.14.0-pre.5"
|
||||
rsa = "0.10.0-rc.0"
|
||||
x509-cert = "0.3.0-rc.0"
|
||||
der = "0.8.0-rc.4"
|
||||
sha1 = "0.11.0-rc.2"
|
||||
sha2 = "0.11.0-rc.2"
|
||||
digest = "0.11.0-rc.2"
|
||||
p256 = "0.14.0-pre.11"
|
||||
p384 = "0.14.0-pre.11"
|
||||
p521 = "0.14.0-pre.11"
|
||||
rsa = "0.10.0-rc.8"
|
||||
x509-cert = "0.3.0-rc.2"
|
||||
der = "0.8.0-rc.9"
|
||||
|
||||
[patch.crates-io]
|
||||
pb-rs = { git = "https://github.com/tafia/quick-protobuf.git" }
|
||||
quick-protobuf = { git = "https://github.com/tafia/quick-protobuf.git" }
|
||||
lz4-sys = { path = "external/lz4-sys" }
|
||||
lzma-sys = { path = "external/lzma-sys" }
|
||||
|
||||
[profile.dev]
|
||||
opt-level = "z"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -8,7 +8,6 @@ path = "lib.rs"
|
||||
|
||||
[features]
|
||||
selinux = []
|
||||
dyn_selinux = []
|
||||
|
||||
[build-dependencies]
|
||||
cxx-gen = { workspace = true }
|
||||
@@ -23,3 +22,5 @@ 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 }
|
||||
|
||||
432
native/src/base/base.cpp
Normal file
432
native/src/base/base.cpp
Normal file
@@ -0,0 +1,432 @@
|
||||
#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>
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
)*}
|
||||
}
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
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, fcntl::AtFlags, fcntl::OFlag, sys::stat::Mode, 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 +23,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 +64,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 +89,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 +100,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 +123,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 +130,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 +147,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 +177,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 +191,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 +212,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 +237,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 +294,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 +308,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 +345,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 +373,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 +384,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 +406,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 +417,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 +430,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 +442,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()) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -1,24 +1,12 @@
|
||||
#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) ||
|
||||
@@ -45,24 +33,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);
|
||||
|
||||
@@ -1,22 +1,26 @@
|
||||
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 libc::{c_uint, makedev, mode_t};
|
||||
use nix::{
|
||||
errno::Errno,
|
||||
fcntl::{AT_FDCWD, OFlag},
|
||||
sys::stat::{FchmodatFlags, Mode},
|
||||
unistd::AccessFlags,
|
||||
unistd::{Gid, Uid},
|
||||
};
|
||||
use mem::MaybeUninit;
|
||||
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 +61,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 +85,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 +123,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 +164,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 +220,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 mut 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 +365,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 +380,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 +511,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 +661,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 +675,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 +722,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 +769,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 +784,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 +892,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()
|
||||
|
||||
@@ -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>>;
|
||||
|
||||
1
native/src/base/include/rust/cxx.h
Symbolic link
1
native/src/base/include/rust/cxx.h
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../external/cxx-rs/include/cxx.h
|
||||
@@ -1,15 +1,16 @@
|
||||
#![feature(vec_into_raw_parts)]
|
||||
#![allow(clippy::missing_safety_doc)]
|
||||
|
||||
pub use const_format;
|
||||
pub use libc;
|
||||
use num_traits::FromPrimitive;
|
||||
pub use nix;
|
||||
|
||||
pub use cstr::{
|
||||
FsPathFollow, StrErr, Utf8CStr, Utf8CStrBuf, Utf8CStrBufArr, Utf8CStrBufRef, Utf8CString,
|
||||
};
|
||||
use cxx_extern::*;
|
||||
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::*;
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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 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]
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -5,8 +5,7 @@
|
||||
#include <functional>
|
||||
#include <string_view>
|
||||
#include <bitset>
|
||||
#include <random>
|
||||
#include <cxx.h>
|
||||
#include <rust/cxx.h>
|
||||
|
||||
#include "xwrap.hpp"
|
||||
|
||||
@@ -19,6 +18,8 @@ clazz(const clazz&) = delete; \
|
||||
clazz(clazz &&o) { 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);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
use crate::{StrErr, Utf8CStr, ffi};
|
||||
use argh::EarlyExit;
|
||||
use crate::{Utf8CStr, Utf8CString, cstr, ffi};
|
||||
use argh::{EarlyExit, MissingRequirements};
|
||||
use libc::c_char;
|
||||
use std::fmt::Arguments;
|
||||
use std::io::Write;
|
||||
use std::mem::ManuallyDrop;
|
||||
use std::process::exit;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicPtr, Ordering};
|
||||
use std::{fmt, slice, str};
|
||||
use std::{
|
||||
fmt,
|
||||
fmt::Arguments,
|
||||
io::Write,
|
||||
mem::ManuallyDrop,
|
||||
process::exit,
|
||||
slice, str,
|
||||
sync::Arc,
|
||||
sync::atomic::{AtomicPtr, Ordering},
|
||||
};
|
||||
|
||||
pub fn errno() -> &'static mut i32 {
|
||||
unsafe { &mut *libc::__errno() }
|
||||
@@ -76,15 +79,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;
|
||||
}
|
||||
@@ -108,6 +102,51 @@ impl<T> EarlyExitExt<T> for Result<T, EarlyExit> {
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FmtAdaptor<'a, T>(pub &'a mut T)
|
||||
where
|
||||
T: Write;
|
||||
@@ -227,3 +266,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) })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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); }
|
||||
@@ -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>;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ fdt = { workspace = true }
|
||||
bytemuck = { workspace = true, features = ["derive", "min_const_generics"] }
|
||||
num-traits = { workspace = true }
|
||||
flate2 = { workspace = true, features = ["zlib-rs"] }
|
||||
bzip2 = { workspace = true, features = ["libbz2-rs-sys"] }
|
||||
bzip2 = { workspace = true }
|
||||
lz4 = { workspace = true }
|
||||
xz2 = { workspace = true }
|
||||
lzma-rust2 = { workspace = true, features = ["xz", "std", "encoder", "optimization"] }
|
||||
zopfli = { workspace = true, features = ["gzip"] }
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -277,62 +318,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 +389,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 +450,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 +497,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 +509,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 +532,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,80 +557,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;
|
||||
}
|
||||
|
||||
// Check if the image is signed
|
||||
if (verify()) {
|
||||
} 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());
|
||||
@@ -591,18 +633,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));
|
||||
@@ -611,13 +648,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());
|
||||
@@ -631,7 +668,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());
|
||||
@@ -650,7 +687,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) \
|
||||
@@ -658,9 +697,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;
|
||||
@@ -669,7 +708,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{};
|
||||
|
||||
@@ -689,22 +728,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);
|
||||
@@ -714,16 +753,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]) {
|
||||
@@ -734,7 +773,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));
|
||||
}
|
||||
@@ -749,7 +788,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
|
||||
@@ -764,15 +803,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;
|
||||
@@ -786,10 +821,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;
|
||||
}
|
||||
@@ -803,13 +838,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();
|
||||
}
|
||||
@@ -825,10 +860,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();
|
||||
}
|
||||
@@ -855,7 +890,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();
|
||||
}
|
||||
|
||||
@@ -865,6 +900,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);
|
||||
@@ -875,7 +912,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
|
||||
@@ -891,8 +927,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -900,17 +936,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);
|
||||
}
|
||||
@@ -922,28 +960,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);
|
||||
@@ -956,42 +994,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());
|
||||
}
|
||||
}
|
||||
@@ -999,33 +1036,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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
463
native/src/boot/cli.rs
Normal file
463
native/src/boot/cli.rs
Normal file
@@ -0,0 +1,463 @@
|
||||
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::{
|
||||
CmdArgs, EarlyExitExt, LoggedResult, MappedFile, PositionalArgParser, ResultExt, Utf8CStr,
|
||||
Utf8CString, WriteExt, cmdline_logging, cstr, libc::umask, log_err, nix::fcntl::OFlag,
|
||||
};
|
||||
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')]
|
||||
no_decompress: bool,
|
||||
#[argh(switch, short = 'h')]
|
||||
dump_header: bool,
|
||||
#[argh(positional)]
|
||||
img: Utf8CString,
|
||||
}
|
||||
|
||||
#[derive(FromArgs)]
|
||||
#[argh(subcommand, name = "repack")]
|
||||
struct Repack {
|
||||
#[argh(switch, short = 'n')]
|
||||
no_compress: bool,
|
||||
#[argh(positional)]
|
||||
img: Utf8CString,
|
||||
#[argh(positional, default = r#"Utf8CString::from("new-boot.img")"#)]
|
||||
out: 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)]
|
||||
args: Vec<Utf8CString>,
|
||||
}
|
||||
|
||||
struct Extract {
|
||||
payload: Utf8CString,
|
||||
partition: Option<Utf8CString>,
|
||||
outfile: Option<Utf8CString>,
|
||||
}
|
||||
|
||||
impl FromArgs for Extract {
|
||||
fn from_args(_command_name: &[&str], args: &[&str]) -> Result<Self, EarlyExit> {
|
||||
let mut parse = PositionalArgParser(args.iter());
|
||||
Ok(Extract {
|
||||
payload: parse.required("payload.bin")?,
|
||||
partition: parse.optional(),
|
||||
outfile: parse.last_optional()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl SubCommand for Extract {
|
||||
const COMMAND: &'static CommandInfo = &CommandInfo {
|
||||
name: "extract",
|
||||
description: "",
|
||||
};
|
||||
}
|
||||
|
||||
#[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')]
|
||||
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: "",
|
||||
};
|
||||
}
|
||||
|
||||
struct Decompress {
|
||||
file: Utf8CString,
|
||||
out: Option<Utf8CString>,
|
||||
}
|
||||
|
||||
impl FromArgs for Decompress {
|
||||
fn from_args(_command_name: &[&str], args: &[&str]) -> Result<Self, EarlyExit> {
|
||||
let mut iter = PositionalArgParser(args.iter());
|
||||
Ok(Decompress {
|
||||
file: iter.required("infile")?,
|
||||
out: iter.last_optional()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl SubCommand for Decompress {
|
||||
const COMMAND: &'static CommandInfo = &CommandInfo {
|
||||
name: "decompress",
|
||||
description: "",
|
||||
};
|
||||
}
|
||||
|
||||
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, no_compress);
|
||||
}
|
||||
Action::Verify(Verify { img, cert }) => {
|
||||
if !verify_cmd(&img, cert.as_deref()) {
|
||||
return log_err!();
|
||||
}
|
||||
}
|
||||
Action::Sign(Sign { img, args }) => {
|
||||
let mut iter = args.iter();
|
||||
sign_cmd(
|
||||
&img,
|
||||
iter.next().map(AsRef::as_ref),
|
||||
iter.next().map(AsRef::as_ref),
|
||||
iter.next().map(AsRef::as_ref),
|
||||
)?;
|
||||
}
|
||||
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)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user