mirror of
https://github.com/topjohnwu/Magisk.git
synced 2025-12-10 06:40:52 -08:00
Compare commits
150 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
363566d0d5 | ||
|
|
d9dc459bf4 | ||
|
|
5d6b703622 | ||
|
|
f7ce9c38e1 | ||
|
|
bdbfb40383 | ||
|
|
283fc0f46f | ||
|
|
2c24a41bf2 | ||
|
|
97c93a1f4d | ||
|
|
8d534e6de8 | ||
|
|
3a60ef2039 | ||
|
|
52d7eff03f | ||
|
|
020e23ea13 | ||
|
|
1599bfc2c5 | ||
|
|
c8d51b38ba | ||
|
|
f741a4aeb8 | ||
|
|
4ee2235961 | ||
|
|
536e50c6e0 | ||
|
|
57d9fc6099 | ||
|
|
52d8910bdd | ||
|
|
c94bd49a89 | ||
|
|
b72ba6759e | ||
|
|
5bcb55b7fc | ||
|
|
0dc8231585 | ||
|
|
470acc93c9 | ||
|
|
0edb80b10f | ||
|
|
bcc6296d94 | ||
|
|
c3db2e368d | ||
|
|
d37da5ca66 | ||
|
|
aac52176ed | ||
|
|
78e2fc37e5 | ||
|
|
ca2e40593f | ||
|
|
c07fdc87e3 | ||
|
|
7270f5e413 | ||
|
|
07cc85ccb1 | ||
|
|
d6f17c42d5 | ||
|
|
d60806f429 | ||
|
|
8836a09c8c | ||
|
|
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
|
||||
|
||||
@@ -73,7 +73,8 @@ class InstallViewModel(svc: NetworkService, markwon: Markwon) : BaseViewModel()
|
||||
val noteText = when {
|
||||
noteFile.exists() -> noteFile.readText()
|
||||
else -> {
|
||||
val note = svc.fetchUpdate(APP_VERSION_CODE).note
|
||||
val note = svc.fetchUpdate(APP_VERSION_CODE)?.note.orEmpty()
|
||||
if (note.isEmpty()) return@launch
|
||||
noteFile.writeText(note)
|
||||
note
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -27,10 +27,8 @@
|
||||
isEnabled="@{!item.removed && item.enabled && !item.showNotice}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clickable="@{!item.removed && item.enabled && !item.showNotice}"
|
||||
android:focusable="@{!item.removed && item.enabled && !item.showNotice}"
|
||||
android:nextFocusRight="@id/module_indicator"
|
||||
android:onClick="@{() -> item.setEnabled(!item.enabled)}"
|
||||
app:cardBackgroundColor="@color/color_card_background_color_selector"
|
||||
tools:isEnabled="false"
|
||||
tools:layout_gravity="center"
|
||||
|
||||
@@ -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.14206865"
|
||||
|
||||
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")
|
||||
|
||||
@@ -19,7 +19,7 @@ abstract class SuLogDatabase : RoomDatabase() {
|
||||
|
||||
companion object {
|
||||
val MIGRATION_1_2 = object : Migration(1, 2) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) = with(database) {
|
||||
override fun migrate(db: SupportSQLiteDatabase) = with(db) {
|
||||
execSQL("ALTER TABLE logs ADD COLUMN target INTEGER NOT NULL DEFAULT -1")
|
||||
execSQL("ALTER TABLE logs ADD COLUMN context TEXT NOT NULL DEFAULT ''")
|
||||
execSQL("ALTER TABLE logs ADD COLUMN gids TEXT NOT NULL DEFAULT ''")
|
||||
|
||||
@@ -44,7 +44,7 @@ object ServiceLocator {
|
||||
private fun createSuLogDatabase(context: Context) =
|
||||
Room.databaseBuilder(context, SuLogDatabase::class.java, "sulogs.db")
|
||||
.addMigrations(SuLogDatabase.MIGRATION_1_2)
|
||||
.fallbackToDestructiveMigration()
|
||||
.fallbackToDestructiveMigration(true)
|
||||
.build()
|
||||
|
||||
private fun createMarkwon(context: Context) =
|
||||
|
||||
@@ -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(
|
||||
@@ -35,29 +34,29 @@ data class ModuleJson(
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ReleaseAssets(
|
||||
val name: String,
|
||||
@Json(name = "browser_download_url") val url: String,
|
||||
@param:Json(name = "browser_download_url") val url: String,
|
||||
)
|
||||
|
||||
class DateTimeAdapter {
|
||||
@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)
|
||||
}
|
||||
}
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Release(
|
||||
@Json(name = "tag_name") val tag: String,
|
||||
@param:Json(name = "tag_name") val tag: String,
|
||||
val name: String,
|
||||
val prerelease: Boolean,
|
||||
val assets: List<ReleaseAssets>,
|
||||
val body: String,
|
||||
@Json(name = "created_at") val createdTime: LocalDateTime,
|
||||
@param: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,
|
||||
@@ -41,7 +41,9 @@ class NetworkService(
|
||||
info
|
||||
}
|
||||
|
||||
suspend fun fetchUpdate(version: Int) = findRelease { it.versionCode == version }.asInfo()
|
||||
suspend fun fetchUpdate(version: Int) = safe {
|
||||
findRelease { it.versionCode == version }.asInfo()
|
||||
}
|
||||
|
||||
// Keep going through all release pages until we find a match
|
||||
private suspend inline fun findRelease(predicate: (Release) -> Boolean): Release? {
|
||||
@@ -74,7 +76,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>
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
<string name="install">نصب</string>
|
||||
<string name="section_home">خانه</string>
|
||||
<string name="section_theme">تم ها</string>
|
||||
<string name="denylist">لیست منع</string>
|
||||
|
||||
<!--Home-->
|
||||
<string name="no_connection">هیچ اتصالی وجود ندارد</string>
|
||||
@@ -17,7 +18,9 @@
|
||||
<string name="not_available">غیر/قابل دسترسی</string>
|
||||
<string name="hide">پنهان کردن</string>
|
||||
<string name="home_package">پکیج</string>
|
||||
|
||||
<string name="home_app_title">برنامه</string>
|
||||
<string name="home_notice_content">Magisk را فقط از صفحه رسمی GitHub دانلود کنید. فایلها از منابع ناشناس میتوانند مخرب باشند!</string>
|
||||
<string name="home_follow_title">ما را دنبال کنید</string>
|
||||
<string name="home_support_title">حمایت ما</string>
|
||||
<string name="home_item_source">منبع</string>
|
||||
<string name="home_support_content">این برنامه (Magisk) رایگان و متن باز است و همیشه خواهد ماند. اگرچه شما میتواند با دونیت خود نشان دهد که به ما اهمیت می دهید.</string>
|
||||
@@ -47,8 +50,10 @@
|
||||
|
||||
<!--Superuser-->
|
||||
<string name="su_request_title">درخواست کاربر روت</string>
|
||||
<string name="touch_filtered_warning">به دلیل اینکه یک برنامه در حال پوشاندن درخواست Superuser است، Magisk نمیتواند پاسخ شما را تأیید کند.</string>
|
||||
<string name="deny">رد کردن</string>
|
||||
<string name="prompt">درخواست کردن</string>
|
||||
<string name="restrict">محدود کردن</string>
|
||||
<string name="grant">اجازه دادن</string>
|
||||
<string name="su_warning">دسترسی کامل به دستگاه شما را اعطا می کند. \nاگر مطمئن نیستید رد کنید!</string>
|
||||
<string name="forever">همیشه</string>
|
||||
@@ -67,37 +72,50 @@
|
||||
<string name="su_snack_log_off">ورود به سیستم از %1$s غییر فعال است</string>
|
||||
<string name="su_revoke_title">باطل بشه؟</string>
|
||||
<string name="su_revoke_msg">تایید کنید که %1$s باطل بشه؟</string>
|
||||
<string name="toast">پیام کوتاه</string>
|
||||
<string name="none">هیچ کدام</string>
|
||||
|
||||
<string name="superuser_toggle_notification">اعلان ها</string>
|
||||
<string name="superuser_toggle_revoke">ابطال</string>
|
||||
<string name="superuser_policy_none">هنوز هیچ برنامه ای مجوز روت درخواست نکرده است.</string>
|
||||
|
||||
<!--Logs-->
|
||||
<string name="log_data_none">شما هیچ لاگی ندارید, سعی کنید برنامه های که به روت دسترسی میگیرند را استفاده کنید.</string>
|
||||
<string name="log_data_magisk_none">لاگ های مربوط به Magisk خالی است. پنا بر خدا.</string>
|
||||
<string name="log_data_magisk_none">لاگ های مربوط به Magisk خالی است.</string>
|
||||
<string name="menuSaveLog">ذخیره کردن لاگ</string>
|
||||
<string name="menuClearLog">پاک کردن لاگ</string>
|
||||
<string name="logs_cleared">لاگ با موفقیت پاک شد.</string>
|
||||
|
||||
<!--SafetyNet-->
|
||||
<string name="pid">شناسه پردازش: %1$d</string>
|
||||
<string name="target_uid">شناسه کاربر هدف: %1$d</string>
|
||||
<string name="target_pid">شناسه پردازش هدف: %s</string>
|
||||
<string name="selinux_context">متن SELinux: %s</string>
|
||||
<string name="supp_group">گروه تکمیلی: %s</string>
|
||||
|
||||
<!-- MagiskHide -->
|
||||
<string name="show_system_app">نشان دادن برنامه های سیستمی</string>
|
||||
<string name="show_os_app">نمایش برنامههای سیستم عامل</string>
|
||||
<string name="hide_filter_hint">فیلتر کردن با نام</string>
|
||||
<string name="hide_search">سرچ کردن</string>
|
||||
|
||||
<!--Module -->
|
||||
<string name="no_info_provided">(هیچ اطلاعاتی ارائه نشده است)</string>
|
||||
<string name="reboot_userspace">راه اندازی مججد</string>
|
||||
<string name="reboot_recovery">راه اندازی مججد برای رفتن به ریکاوری</string>
|
||||
<string name="reboot_bootloader">راه اندازی مججد برای رفتن به بوت لودر</string>
|
||||
<string name="reboot_download">راه اندازی مججد برای دانلود کردن</string>
|
||||
<string name="reboot_edl">راه اندازی مججد برای رفتن به EDL</string>
|
||||
<string name="reboot_safe_mode">راه اندازی مججد برای رفتن به حالت امن</string>
|
||||
<string name="module_version_author">%1$s با %2$s</string>
|
||||
<string name="module_state_remove">حذف کردن</string>
|
||||
<string name="module_state_restore">بازگرداندن</string>
|
||||
<string name="module_action_install_external">نصب از حافظه</string>
|
||||
<string name="update_available">بروزرسانی در دسترس است</string>
|
||||
<string name="suspend_text_riru">ماژول به دلیل فعال بودن %1$s متوقف شد</string>
|
||||
<string name="suspend_text_zygisk">ماژول به دلیل غیرفعال بودن %1$s متوقف شد</string>
|
||||
<string name="zygisk_module_unloaded">ماژول Zygisk به دلیل ناسازگاری بارگذاری نشد</string>
|
||||
<string name="module_empty">هیچ ماژولی نصب نشده است</string>
|
||||
<string name="confirm_install">نصب ماژول %1$s؟</string>
|
||||
<string name="confirm_install_title">تأیید نصب</string>
|
||||
|
||||
|
||||
<!--Settings -->
|
||||
<string name="settings_dark_mode_title">حالت تم</string>
|
||||
@@ -107,6 +125,10 @@
|
||||
<string name="settings_dark_mode_dark">همیشه تاریک</string>
|
||||
<string name="settings_download_path_title">مسیر دانلود</string>
|
||||
<string name="settings_download_path_message">فایل ها در %1$s ذخیره خواهند شد.</string>
|
||||
<string name="settings_hide_app_title">مخفی کردن برنامه Magisk</string>
|
||||
<string name="settings_hide_app_summary">نصب یک برنامه پروکسی با شناسه بسته تصادفی و برچسب سفارشی</string>
|
||||
<string name="settings_restore_app_title">بازگردانی برنامه Magisk</string>
|
||||
<string name="settings_restore_app_summary">آشکار کردن برنامه و بازگرداندن APK اصلی</string>
|
||||
<string name="language">زبان</string>
|
||||
<string name="system_default">(پیش فرض سیستم)</string>
|
||||
<string name="settings_check_update_title">چک کردن بروز رسانی ها</string>
|
||||
@@ -114,8 +136,14 @@
|
||||
<string name="settings_update_channel_title">کانال بروزرسانی</string>
|
||||
<string name="settings_update_stable">پایدار</string>
|
||||
<string name="settings_update_beta">آزمایشی</string>
|
||||
<string name="settings_update_debug">اشکالزدایی</string>
|
||||
<string name="settings_update_custom">شخصی سازی شده</string>
|
||||
<string name="settings_update_custom_msg">اضافه کردن یک URL سفارشی</string>
|
||||
<string name="settings_zygisk_summary">اجرای بخشهایی از Magisk در سرویس Zygote</string>
|
||||
<string name="settings_denylist_title">اعمال لیست منع</string>
|
||||
<string name="settings_denylist_summary">فرآیندهای موجود در لیست منع تمام تغییرات Magisk را از دست خواهند داد</string>
|
||||
<string name="settings_denylist_config_title">پیکربندی لیست منع</string>
|
||||
<string name="settings_denylist_config_summary">انتخاب فرآیندهایی که باید در لیست منع قرار گیرند</string>
|
||||
<string name="settings_hosts_title">نصب بدون حذف یا تغییر در فایل ها</string>
|
||||
<string name="settings_hosts_summary">نصب بدون حذف یا تغییر در فایل ها رای ساپورت از برنامه های Adblock</string>
|
||||
<string name="settings_hosts_toast">ماژول نصب بدون حذف یا تغییر در فایل ها اضافه شد</string>
|
||||
@@ -138,9 +166,19 @@
|
||||
<string name="superuser_notification">اعلان روت</string>
|
||||
<string name="settings_su_reauth_title">احراز هویت دوباره پس از بروز رسانی</string>
|
||||
<string name="settings_su_reauth_summary">تأیید کردندوباره مجوزهای روت پس از ارتقاء برنامه</string>
|
||||
<string name="settings_su_tapjack_title">محافظت در برابر Tapjacking</string>
|
||||
<string name="settings_su_tapjack_summary">پنجره درخواست Superuser زمانی که توسط پنجره یا لایه دیگری پوشانده شود، به ورودی پاسخ نخواهد داد</string>
|
||||
<string name="settings_su_auth_title">احراز هویت کاربر</string>
|
||||
<string name="settings_su_auth_summary">درخواست احراز هویت کاربر هنگام درخواست Superuser</string>
|
||||
<string name="settings_su_auth_insecure">هیچ روش احراز هویتی روی دستگاه پیکربندی نشده است</string>
|
||||
<string name="settings_su_restrict_title">محدود کردن دسترسی روت</string>
|
||||
<string name="settings_su_restrict_summary">به طور پیشفرض برنامههای Superuser جدید را محدود میکند. هشدار: این کار بیشتر برنامهها را از کار میاندازد. فقط اگر دقیقاً میدانید چه میکنید آن را فعال کنید.</string>
|
||||
<string name="settings_customization">سفارشی سازی</string>
|
||||
<string name="setting_add_shortcut_summary">اضافه کردن یک میانبر زیبا را در صفحه اصلی در صورت شناسایی نام و نماد پس از پنهان کردن برنامه</string>
|
||||
|
||||
<string name="settings_doh_title">DNS روی HTTPS</string>
|
||||
<string name="settings_doh_description">دور زدن مسمومیت DNS در برخی کشورها</string>
|
||||
<string name="settings_random_name_title">تغییر تصادفی نام خروجی</string>
|
||||
<string name="settings_random_name_description">تغییر تصادفی نام فایل خروجی تصاویر و فایلهای tar پچشده برای جلوگیری از شناسایی</string>
|
||||
<string name="multiuser_mode">حالت چند کاربره</string>
|
||||
<string name="settings_owner_only">فقط صاحب دستگاه</string>
|
||||
<string name="settings_owner_manage">صاحب دستگاه مدیریت شود</string>
|
||||
@@ -148,7 +186,6 @@
|
||||
<string name="owner_only_summary">فقط مالک دسترسی روت دارد</string>
|
||||
<string name="owner_manage_summary">فقط مالک می تواند دسترسی روت را مدیریت کرده و درخواست های پرامپت را دریافت کند</string>
|
||||
<string name="user_independent_summary">هر کاربر قوانین روت جداگانه خود را دارد</string>
|
||||
|
||||
<string name="mount_namespace_mode">نصب کردن Namespace Mode</string>
|
||||
<string name="settings_ns_global">سراسری Namespace</string>
|
||||
<string name="settings_ns_requester">وراثتی Namespace</string>
|
||||
@@ -160,19 +197,27 @@
|
||||
<!--Notifications-->
|
||||
<string name="update_channel">Magisk بروزرسانی های</string>
|
||||
<string name="progress_channel">اعلان پیشرفت</string>
|
||||
<string name="updated_channel">بهروزرسانی کامل شد</string>
|
||||
<string name="download_complete">دانلود کامل شد</string>
|
||||
<string name="download_file_error">خطا در دانلود فایل</string>
|
||||
<string name="magisk_update_title">بروزرسانی Magisk در دسترس است!</string>
|
||||
<string name="updated_title">Magisk بهروزرسانی شد</string>
|
||||
<string name="updated_text">برای باز کردن برنامه لمس کنید</string>
|
||||
|
||||
<!--Toasts, Dialogs-->
|
||||
<string name="yes">بله</string>
|
||||
<string name="no">نه</string>
|
||||
<string name="download">انلود کردن</string>
|
||||
<string name="repo_install_title">نصب %1$s %2$s(%3$d)</string>
|
||||
<string name="download">دانلود کردن</string>
|
||||
<string name="reboot">راه اندازی مجدد</string>
|
||||
<string name="close">بستن</string>
|
||||
<string name="release_notes">نکته های نسخه</string>
|
||||
<string name="flashing">ر حال فلش کردن…</string>
|
||||
<string name="running">در حال اجرا…</string>
|
||||
<string name="done">تمام!</string>
|
||||
<string name="done_action">انجام عملیات %1$s به پایان رسید</string>
|
||||
<string name="failure">ناموفق</string>
|
||||
<string name="hide_app_title">در حال مخفی کردن برنامه Magisk…</string>
|
||||
<string name="open_link_failed_toast">هیچ برنامه ای برای باز کردن لینک یافت نشد</string>
|
||||
<string name="complete_uninstall">کامل کردن حذف</string>
|
||||
<string name="restore_img">بازیابی تصاویر</string>
|
||||
@@ -181,9 +226,24 @@
|
||||
<string name="restore_fail">نسخه پشتیبان استک موجود نیست!</string>
|
||||
<string name="setup_fail">نصب انجام نشد</string>
|
||||
<string name="env_fix_title">به تنظیمات اضافی نیاز دارد</string>
|
||||
<string name="env_fix_msg">دستگاه شما به پیکربندی اضافی نیاز دارد تا Magisk به درستی کار کند. آیا میخواهید ادامه دهید و راهاندازی مجدد انجام شود؟</string>
|
||||
<string name="env_full_fix_msg">دستگاه شما نیاز به نصب دوباره Magisk دارد تا به درستی کار کند. لطفاً Magisk را از داخل برنامه دوباره نصب کنید، حالت Recovery نمیتواند اطلاعات دستگاه را به درستی بگیرد.</string>
|
||||
<string name="setup_msg">راه اندازی محیط نصب…</string>
|
||||
<string name="unsupport_magisk_title">نسخه پشتیبانی نشده Magisk</string>
|
||||
<string name="unsupport_magisk_msg">این نسخه از برنامه از نسخههای Magisk پایینتر از %1$s پشتیبانی نمیکند.\n\nبرنامه طوری رفتار میکند که انگار Magisk نصب نشده است. لطفاً هرچه سریعتر Magisk را بهروزرسانی کنید.</string>
|
||||
<string name="unsupport_general_title">وضعیت غیرعادی</string>
|
||||
<string name="unsupport_system_app_msg">اجرای این برنامه به عنوان برنامه سیستمی پشتیبانی نمیشود. لطفاً آن را به برنامه کاربری بازگردانید.</string>
|
||||
<string name="unsupport_other_su_msg">یک باینری "su" غیر از Magisk شناسایی شد. لطفاً هر راهکار روت دیگری را حذف کنید و/یا Magisk را دوباره نصب کنید.</string>
|
||||
<string name="unsupport_external_storage_msg">Magisk روی حافظه خارجی نصب شده است. لطفاً برنامه را به حافظه داخلی منتقل کنید.</string>
|
||||
<string name="unsupport_nonroot_stub_msg">برنامه مخفی Magisk نمیتواند ادامه دهد زیرا دسترسی روت از بین رفته است. لطفاً APK اصلی را بازگردانید.</string>
|
||||
<string name="unsupport_nonroot_stub_title">@string/settings_restore_app_title</string>
|
||||
<string name="external_rw_permission_denied">برای فعال کردن این قابلیت ، اجازه دسترسی به حافظه بدهید</string>
|
||||
<string name="post_notifications_denied">برای فعالسازی این قابلیت، مجوز اعلانها را بدهید</string>
|
||||
<string name="install_unknown_denied">برای فعالسازی این قابلیت، «نصب برنامههای ناشناخته» را مجاز کنید</string>
|
||||
<string name="add_shortcut_title">اضافه کردن میانبر را به صفحه</string>
|
||||
<string name="add_shortcut_msg">بعد از مخفی کردن این برنامه، ممکن است نام و آیکون آن سخت قابل شناسایی شود. آیا میخواهید یک میانبر زیبا به صفحه اصلی اضافه کنید؟</string>
|
||||
<string name="app_not_found">هیچ برنامهای برای انجام این عملیات یافت نشد</string>
|
||||
<string name="reboot_apply_change">برای اعمال تغییرات، دستگاه را دوباره راهاندازی کنید</string>
|
||||
<string name="restore_app_confirmation">این کار برنامه مخفی شده را به نسخه اصلی بازمیگرداند. آیا واقعاً میخواهید این کار را انجام دهید؟</string>
|
||||
|
||||
</resources>
|
||||
|
||||
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=30500
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
[versions]
|
||||
kotlin = "2.1.21"
|
||||
android = "8.11.0"
|
||||
ksp = "2.1.21-2.0.1"
|
||||
kotlin = "2.2.21"
|
||||
android = "8.13.1"
|
||||
ksp = "2.3.3"
|
||||
rikka = "1.3.0"
|
||||
navigation = "2.9.0"
|
||||
navigation = "2.9.6"
|
||||
libsu = "6.0.0"
|
||||
okhttp = "4.12.0"
|
||||
okhttp = "5.3.2"
|
||||
retrofit = "3.0.0"
|
||||
room = "2.7.2"
|
||||
room = "2.8.4"
|
||||
|
||||
[libraries]
|
||||
bcpkix = { module = "org.bouncycastle:bcpkix-jdk18on", version = "1.81" }
|
||||
commons-compress = { module = "org.apache.commons:commons-compress", version = "1.27.1" }
|
||||
bcpkix = { module = "org.bouncycastle:bcpkix-jdk18on", version = "1.83" }
|
||||
commons-compress = { module = "org.apache.commons:commons-compress", version = "1.28.0" }
|
||||
retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
|
||||
retrofit-moshi = { module = "com.squareup.retrofit2:converter-moshi", version.ref = "retrofit" }
|
||||
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.12.0" }
|
||||
appcompat = { module = "androidx.appcompat:appcompat", version = "1.7.1" }
|
||||
core-ktx = { module = "androidx.core:core-ktx", version = "1.16.0" }
|
||||
core-splashscreen = { module = "androidx.core:core-splashscreen", version = "1.0.1" }
|
||||
core-ktx = { module = "androidx.core:core-ktx", version = "1.17.0" }
|
||||
core-splashscreen = { module = "androidx.core:core-splashscreen", version = "1.2.0" }
|
||||
constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version = "2.2.1" }
|
||||
fragment-ktx = { module = "androidx.fragment:fragment-ktx", version = "1.8.8" }
|
||||
fragment-ktx = { module = "androidx.fragment:fragment-ktx", version = "1.8.9" }
|
||||
navigation-fragment-ktx = { module = "androidx.navigation:navigation-fragment-ktx", version.ref = "navigation" }
|
||||
navigation-ui-ktx = { module = "androidx.navigation:navigation-ui-ktx", version.ref = "navigation" }
|
||||
profileinstaller = { module = "androidx.profileinstaller:profileinstaller", version = "1.4.1" }
|
||||
@@ -39,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.34.1" }
|
||||
|
||||
[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.3"
|
||||
|
||||
# Global vars
|
||||
config = {}
|
||||
args = {}
|
||||
build_abis = {}
|
||||
args: argparse.Namespace
|
||||
build_abis: dict[str, str]
|
||||
force_out = False
|
||||
|
||||
###################
|
||||
# Helper functions
|
||||
@@ -126,7 +136,7 @@ def rm_rf(path: Path):
|
||||
|
||||
|
||||
def execv(cmds: list, env=None):
|
||||
out = None if args.force_out or args.verbose > 0 else subprocess.DEVNULL
|
||||
out = None if force_out or args.verbose > 0 else subprocess.DEVNULL
|
||||
# Use shell on Windows to support PATHEXT
|
||||
return subprocess.run(cmds, stdout=out, env=env, shell=is_windows)
|
||||
|
||||
@@ -171,7 +181,7 @@ def collect_ndk_build():
|
||||
mv(source, target)
|
||||
|
||||
|
||||
def run_ndk_build(cmds: list):
|
||||
def run_ndk_build(cmds: list[str]):
|
||||
os.chdir("native")
|
||||
cmds.append("NDK_PROJECT_PATH=.")
|
||||
cmds.append("NDK_APPLICATION_MK=src/Application.mk")
|
||||
@@ -187,7 +197,7 @@ def run_ndk_build(cmds: list):
|
||||
os.chdir("..")
|
||||
|
||||
|
||||
def build_cpp_src(targets: set):
|
||||
def build_cpp_src(targets: set[str]):
|
||||
cmds = []
|
||||
clean = False
|
||||
|
||||
@@ -226,15 +236,22 @@ def build_cpp_src(targets: set):
|
||||
clean_elf()
|
||||
|
||||
|
||||
def run_cargo(cmds):
|
||||
def run_cargo(cmds: list[str]):
|
||||
ensure_paths()
|
||||
env = os.environ.copy()
|
||||
env["RUSTUP_TOOLCHAIN"] = str(rust_sysroot)
|
||||
env["PATH"] = f"{rust_sysroot / "bin"}{os.pathsep}{env["PATH"]}"
|
||||
env["CARGO_BUILD_RUSTFLAGS"] = f"-Z threads={min(8, cpu_count)}"
|
||||
# Cargo calls executables in $RUSTROOT/lib/rustlib/$TRIPLE/bin, we need
|
||||
# to make sure the runtime linker also search $RUSTROOT/lib for libraries.
|
||||
# This is only required on Unix, as Windows search dlls from PATH.
|
||||
if os_name == "darwin":
|
||||
env["DYLD_FALLBACK_LIBRARY_PATH"] = str(rust_sysroot / "lib")
|
||||
elif os_name == "linux":
|
||||
env["LD_LIBRARY_PATH"] = str(rust_sysroot / "lib")
|
||||
return execv(["cargo", *cmds], env)
|
||||
|
||||
|
||||
def build_rust_src(targets: set):
|
||||
def build_rust_src(targets: set[str]):
|
||||
targets = targets.copy()
|
||||
if "resetprop" in targets:
|
||||
targets.add("magisk")
|
||||
@@ -433,8 +450,7 @@ def build_stub():
|
||||
|
||||
|
||||
def build_test():
|
||||
global args
|
||||
args_bak = copy.copy(args)
|
||||
old_release = args.release
|
||||
# Test APK has to be built as release to prevent classname clash
|
||||
args.release = True
|
||||
try:
|
||||
@@ -444,7 +460,7 @@ def build_test():
|
||||
mv(source, target)
|
||||
header(f"Output: {target}")
|
||||
finally:
|
||||
args = args_bak
|
||||
args.release = old_release
|
||||
|
||||
|
||||
################
|
||||
@@ -454,14 +470,13 @@ def build_test():
|
||||
|
||||
def cleanup():
|
||||
ensure_paths()
|
||||
support_targets = {"native", "cpp", "rust", "app"}
|
||||
if args.targets:
|
||||
targets = set(args.targets) & support_targets
|
||||
targets: set[str] = set(args.targets) & clean_targets
|
||||
if "native" in targets:
|
||||
targets.add("cpp")
|
||||
targets.add("rust")
|
||||
else:
|
||||
targets = support_targets
|
||||
targets = clean_targets
|
||||
|
||||
if "cpp" in targets:
|
||||
header("* Cleaning C++")
|
||||
@@ -470,11 +485,11 @@ def cleanup():
|
||||
|
||||
if "rust" in targets:
|
||||
header("* Cleaning Rust")
|
||||
rm_rf(Path("native", "src", "target"))
|
||||
rm_rf(Path("native", "out", "rust"))
|
||||
rm(Path("native", "src", "boot", "proto", "mod.rs"))
|
||||
rm(Path("native", "src", "boot", "proto", "update_metadata.rs"))
|
||||
for rs_gen in glob.glob("native/**/*-rs.*pp", recursive=True):
|
||||
rm(rs_gen)
|
||||
rm(Path(rs_gen))
|
||||
|
||||
if "native" in targets:
|
||||
header("* Cleaning native")
|
||||
@@ -501,7 +516,7 @@ def build_all():
|
||||
|
||||
def gen_ide():
|
||||
ensure_paths()
|
||||
set_archs({args.abi})
|
||||
set_build_abis({args.abi})
|
||||
|
||||
# Dump flags for both C++ and Rust code
|
||||
dump_flag_header()
|
||||
@@ -529,19 +544,31 @@ def gen_ide():
|
||||
|
||||
def clippy_cli():
|
||||
ensure_toolchain()
|
||||
args.force_out = True
|
||||
set_archs(default_archs)
|
||||
global force_out
|
||||
force_out = True
|
||||
if args.abi:
|
||||
set_build_abis(set(args.abi))
|
||||
else:
|
||||
set_build_abis(default_abis)
|
||||
|
||||
if not args.release and not args.debug:
|
||||
# If none is specified, run both
|
||||
args.release = True
|
||||
args.debug = True
|
||||
|
||||
os.chdir(Path("native", "src"))
|
||||
cmds = ["clippy", "--no-deps", "--target"]
|
||||
for triple in build_abis.values():
|
||||
run_cargo(cmds + [triple])
|
||||
run_cargo(cmds + [triple, "--release"])
|
||||
if args.debug:
|
||||
run_cargo(cmds + [triple])
|
||||
if args.release:
|
||||
run_cargo(cmds + [triple, "--release"])
|
||||
os.chdir(Path("..", ".."))
|
||||
|
||||
|
||||
def cargo_cli():
|
||||
args.force_out = True
|
||||
global force_out
|
||||
force_out = True
|
||||
if len(args.commands) >= 1 and args.commands[0] == "--":
|
||||
args.commands = args.commands[1:]
|
||||
os.chdir(Path("native", "src"))
|
||||
@@ -601,7 +628,7 @@ def setup_rustup():
|
||||
##################
|
||||
|
||||
|
||||
def push_files(script):
|
||||
def push_files(script: Path):
|
||||
if args.build:
|
||||
build_all()
|
||||
ensure_adb()
|
||||
@@ -678,8 +705,8 @@ def patch_avd_file():
|
||||
|
||||
|
||||
def ensure_paths():
|
||||
global sdk_path, ndk_root, ndk_path, ndk_build, rust_sysroot
|
||||
global llvm_bin, gradlew, adb_path, native_gen_path
|
||||
global sdk_path, ndk_root, ndk_path, rust_sysroot
|
||||
global ndk_build, gradlew, adb_path
|
||||
|
||||
# Skip if already initialized
|
||||
if "sdk_path" in globals():
|
||||
@@ -697,9 +724,6 @@ def ensure_paths():
|
||||
ndk_path = ndk_root / "magisk"
|
||||
ndk_build = ndk_path / "ndk-build"
|
||||
rust_sysroot = ndk_path / "toolchains" / "rust"
|
||||
llvm_bin = (
|
||||
ndk_path / "toolchains" / "llvm" / "prebuilt" / f"{os_name}-x86_64" / "bin"
|
||||
)
|
||||
adb_path = sdk_path / "platform-tools" / "adb"
|
||||
gradlew = Path.cwd() / "app" / "gradlew"
|
||||
|
||||
@@ -708,14 +732,13 @@ def ensure_paths():
|
||||
def ensure_adb():
|
||||
global adb_path
|
||||
if "adb_path" not in globals():
|
||||
adb_path = shutil.which("adb")
|
||||
if not adb_path:
|
||||
error("Command 'adb' cannot be found in PATH")
|
||||
if adb := shutil.which("adb"):
|
||||
adb_path = Path(adb)
|
||||
else:
|
||||
adb_path = Path(adb_path)
|
||||
error("Command 'adb' cannot be found in PATH")
|
||||
|
||||
|
||||
def parse_props(file):
|
||||
def parse_props(file: Path) -> dict[str, str]:
|
||||
props = {}
|
||||
with open(file, "r") as f:
|
||||
for line in [l.strip(" \t\r\n") for l in f]:
|
||||
@@ -732,10 +755,14 @@ def parse_props(file):
|
||||
return props
|
||||
|
||||
|
||||
def set_archs(archs: set):
|
||||
triples = map(support_abis.get, archs)
|
||||
def set_build_abis(abis: set[str]):
|
||||
global build_abis
|
||||
build_abis = dict(zip(archs, triples))
|
||||
# Try to convert several aliases to real ABI
|
||||
abis = {abi_alias.get(k, k) for k in abis}
|
||||
# Check any unknown ABIs
|
||||
for k in abis - support_abis.keys():
|
||||
error(f"Unknown ABI: {k}")
|
||||
build_abis = {k: support_abis[k] for k in abis if k in support_abis}
|
||||
|
||||
|
||||
def load_config():
|
||||
@@ -746,8 +773,6 @@ def load_config():
|
||||
config["versionCode"] = 1000000
|
||||
config["outdir"] = "out"
|
||||
|
||||
args.config = Path(args.config)
|
||||
|
||||
# Load prop files
|
||||
if args.config.exists():
|
||||
config.update(parse_props(args.config))
|
||||
@@ -767,12 +792,11 @@ def load_config():
|
||||
config["outdir"].mkdir(mode=0o755, parents=True, exist_ok=True)
|
||||
|
||||
if "abiList" in config:
|
||||
abiList = re.split("\\s*,\\s*", config["abiList"])
|
||||
archs = set(abiList) & support_abis.keys()
|
||||
abis = set(re.split("\\s*,\\s*", config["abiList"]))
|
||||
else:
|
||||
archs = default_archs
|
||||
abis = default_abis
|
||||
|
||||
set_archs(archs)
|
||||
set_build_abis(abis)
|
||||
|
||||
|
||||
def parse_args():
|
||||
@@ -837,6 +861,15 @@ def parse_args():
|
||||
cargo_parser.add_argument("commands", nargs=argparse.REMAINDER)
|
||||
|
||||
clippy_parser = subparsers.add_parser("clippy", help="run clippy on Rust sources")
|
||||
clippy_parser.add_argument(
|
||||
"--abi", action="append", help="target ABI(s) to run clippy"
|
||||
)
|
||||
clippy_parser.add_argument(
|
||||
"-r", "--release", action="store_true", help="run clippy as release"
|
||||
)
|
||||
clippy_parser.add_argument(
|
||||
"-d", "--debug", action="store_true", help="run clippy as debug"
|
||||
)
|
||||
|
||||
rustup_parser = subparsers.add_parser("rustup", help="setup rustup wrapper")
|
||||
rustup_parser.add_argument(
|
||||
@@ -871,8 +904,8 @@ def parse_args():
|
||||
def main():
|
||||
global args
|
||||
args = parse_args()
|
||||
args.config = Path(args.config)
|
||||
load_config()
|
||||
vars(args)["force_out"] = False
|
||||
args.func()
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
181
docs/changes.md
181
docs/changes.md
@@ -1,13 +1,40 @@
|
||||
# Magisk Changelog
|
||||
|
||||
### v30.0
|
||||
### v30.5 (2025.12.1)
|
||||
|
||||
- [General] Improve commandline argument parsing logic
|
||||
- [resetprop] Properly support Android versions with property overrides
|
||||
|
||||
### v30.4 (2025.10.2)
|
||||
|
||||
- [MagiskSU] Fix several implementation bugs
|
||||
|
||||
### v30.3 (2025.9.29)
|
||||
|
||||
- [General] Support installing Magisk into vendor_boot partition
|
||||
- [MagiskPolicy] Support new sepolicy binary format introduced in Android 16 QPR2
|
||||
- [Core] Migrate much more code into Rust
|
||||
- [MagiskSU] Fallback to older implementation when the kernel doesn't support zero userspace copy APIs
|
||||
|
||||
### v30.2 (2025.8.6)
|
||||
|
||||
- [Core] Fix an edge case breaking modules when overlayfs is involved
|
||||
- [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 +43,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 +51,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 +75,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 +83,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 +94,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 +119,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 +142,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 +150,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 +159,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 +177,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 +201,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 +234,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 +248,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 +263,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 +275,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 +295,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 +310,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 +330,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 +348,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 +360,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 +369,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 +379,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 +395,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 +403,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 +412,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 +422,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 +440,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 +469,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 +480,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 +498,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 +506,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 +528,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 +542,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 +562,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 +580,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 +594,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 +617,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 +637,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 +647,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 +666,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 +685,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 +729,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 +746,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 +758,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 +791,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 +811,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 +823,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 +841,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 +860,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 +870,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 +880,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)
|
||||
@@ -10,3 +10,6 @@ target-dir = "../out/rust"
|
||||
build-std = ["std", "panic_abort"]
|
||||
build-std-features = ["panic_immediate_abort", "optimize_for_size"]
|
||||
profile-rustflags = true
|
||||
|
||||
[profile.release]
|
||||
rustflags = ["-Z", "location-detail=none", "-Z", "fmt-debug=none"]
|
||||
|
||||
@@ -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
|
||||
|
||||
705
native/src/Cargo.lock
generated
705
native/src/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
[workspace]
|
||||
exclude = ["external"]
|
||||
members = ["base", "boot", "core", "core/derive", "init", "sepolicy"]
|
||||
members = ["base", "base/derive", "boot", "core", "init", "sepolicy"]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.package]
|
||||
@@ -8,52 +8,56 @@ version = "0.0.0"
|
||||
edition = "2024"
|
||||
|
||||
[workspace.dependencies]
|
||||
base = { path = "base" }
|
||||
derive = { path = "base/derive" }
|
||||
magiskpolicy = { path = "sepolicy" }
|
||||
cxx = { path = "external/cxx-rs" }
|
||||
cxx-gen = { path = "external/cxx-rs/gen/lib" }
|
||||
libc = "0.2.172"
|
||||
cfg-if = "1.0.1"
|
||||
libc = "0.2.177"
|
||||
cfg-if = "1.0.4"
|
||||
num-traits = "0.2.19"
|
||||
num-derive = "0.4.2"
|
||||
thiserror = "2.0.12"
|
||||
thiserror = "2.0.17"
|
||||
byteorder = "1.5.0"
|
||||
size = "0.5.0"
|
||||
bytemuck = "1.23.1"
|
||||
bytemuck = "1.24.0"
|
||||
fdt = "0.1.5"
|
||||
const_format = "0.2.34"
|
||||
const_format = "0.2.35"
|
||||
bit-set = "0.8.0"
|
||||
syn = "2.0.102"
|
||||
quote = "1.0.40"
|
||||
proc-macro2 = "1.0.95"
|
||||
argh = { version = "0.1.13", default-features = false }
|
||||
syn = "2.0.111"
|
||||
quote = "1.0.42"
|
||||
proc-macro2 = "1.0.103"
|
||||
pb-rs = { version = "0.10.0", default-features = false }
|
||||
quick-protobuf = "0.8.1"
|
||||
flate2 = { version = "1.1.2", default-features = false }
|
||||
bzip2 = { version = "0.5.2", default-features = false }
|
||||
zopfli = "0.8.2"
|
||||
flate2 = { version = "1.1.5", default-features = false }
|
||||
bzip2 = "0.6.1"
|
||||
zopfli = "0.8.3"
|
||||
lz4 = "1.28.1"
|
||||
xz2 = "0.1.7"
|
||||
lzma-rust2 = { version = "0.15.2", default-features = false }
|
||||
nix = "0.30.1"
|
||||
bitflags = "2.10.0"
|
||||
|
||||
# Rust crypto crates are tied together
|
||||
sha1 = "0.11.0-rc.0"
|
||||
sha2 = "0.11.0-rc.0"
|
||||
digest = "0.11.0-rc.0"
|
||||
p256 = "0.14.0-pre.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.3"
|
||||
sha2 = "0.11.0-rc.3"
|
||||
digest = "0.11.0-rc.4"
|
||||
p256 = "0.14.0-rc.1"
|
||||
p384 = "0.14.0-rc.1"
|
||||
p521 = "0.14.0-rc.1"
|
||||
rsa = "0.10.0-rc.10"
|
||||
x509-cert = "0.3.0-rc.2"
|
||||
der = "0.8.0-rc.10"
|
||||
|
||||
[patch.crates-io]
|
||||
pb-rs = { git = "https://github.com/tafia/quick-protobuf.git" }
|
||||
quick-protobuf = { git = "https://github.com/tafia/quick-protobuf.git" }
|
||||
pb-rs = { git = "https://github.com/topjohnwu/quick-protobuf.git" }
|
||||
quick-protobuf = { git = "https://github.com/topjohnwu/quick-protobuf.git" }
|
||||
lz4-sys = { path = "external/lz4-sys" }
|
||||
lzma-sys = { path = "external/lzma-sys" }
|
||||
|
||||
[profile.dev]
|
||||
opt-level = "z"
|
||||
lto = "thin"
|
||||
panic = "abort"
|
||||
debug = "none"
|
||||
|
||||
[profile.release]
|
||||
opt-level = "z"
|
||||
|
||||
@@ -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,18 +8,19 @@ path = "lib.rs"
|
||||
|
||||
[features]
|
||||
selinux = []
|
||||
dyn_selinux = []
|
||||
|
||||
[build-dependencies]
|
||||
cxx-gen = { workspace = true }
|
||||
|
||||
[dependencies]
|
||||
derive = { workspace = true }
|
||||
cxx = { workspace = true }
|
||||
libc = { workspace = true }
|
||||
cfg-if = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
argh = { workspace = true }
|
||||
bytemuck = { workspace = true }
|
||||
num-traits = { workspace = true }
|
||||
num-derive = { workspace = true }
|
||||
const_format = { workspace = true }
|
||||
nix = { workspace = true, features = ["fs", "mount", "user"] }
|
||||
bitflags = { workspace = true }
|
||||
|
||||
1226
native/src/base/argh.rs
Normal file
1226
native/src/base/argh.rs
Normal file
File diff suppressed because it is too large
Load Diff
433
native/src/base/base.cpp
Normal file
433
native/src/base/base.cpp
Normal file
@@ -0,0 +1,433 @@
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/prctl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <android/log.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <syscall.h>
|
||||
#include <random>
|
||||
#include <string>
|
||||
|
||||
#include <base.hpp>
|
||||
#include <flags.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
#ifndef __call_bypassing_fortify
|
||||
#define __call_bypassing_fortify(fn) (&fn)
|
||||
#endif
|
||||
|
||||
// Override libc++ new implementation to optimize final build size
|
||||
|
||||
void* operator new(std::size_t s) { return std::malloc(s); }
|
||||
void* operator new[](std::size_t s) { return std::malloc(s); }
|
||||
void operator delete(void *p) { std::free(p); }
|
||||
void operator delete[](void *p) { std::free(p); }
|
||||
void* operator new(std::size_t s, const std::nothrow_t&) noexcept { return std::malloc(s); }
|
||||
void* operator new[](std::size_t s, const std::nothrow_t&) noexcept { return std::malloc(s); }
|
||||
void operator delete(void *p, const std::nothrow_t&) noexcept { std::free(p); }
|
||||
void operator delete[](void *p, const std::nothrow_t&) noexcept { std::free(p); }
|
||||
|
||||
bool byte_view::contains(byte_view pattern) const {
|
||||
return _buf != nullptr && memmem(_buf, _sz, pattern._buf, pattern._sz) != nullptr;
|
||||
}
|
||||
|
||||
bool byte_view::operator==(byte_view rhs) const {
|
||||
return _sz == rhs._sz && memcmp(_buf, rhs._buf, _sz) == 0;
|
||||
}
|
||||
|
||||
void byte_data::swap(byte_data &o) {
|
||||
std::swap(_buf, o._buf);
|
||||
std::swap(_sz, o._sz);
|
||||
}
|
||||
|
||||
rust::Vec<size_t> byte_data::patch(byte_view from, byte_view to) const {
|
||||
rust::Vec<size_t> v;
|
||||
if (_buf == nullptr)
|
||||
return v;
|
||||
auto p = _buf;
|
||||
auto eof = _buf + _sz;
|
||||
while (p < eof) {
|
||||
p = static_cast<uint8_t *>(memmem(p, eof - p, from.data(), from.size()));
|
||||
if (p == nullptr)
|
||||
return v;
|
||||
memset(p, 0, from.size());
|
||||
memcpy(p, to.data(), to.size());
|
||||
v.push_back(p - _buf);
|
||||
p += from.size();
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
rust::Vec<size_t> mut_u8_patch(MutByteSlice buf, ByteSlice from, ByteSlice to) {
|
||||
byte_data data(buf);
|
||||
return data.patch(from, to);
|
||||
}
|
||||
|
||||
int fork_dont_care() {
|
||||
if (int pid = xfork()) {
|
||||
waitpid(pid, nullptr, 0);
|
||||
return pid;
|
||||
} else if (xfork()) {
|
||||
exit(0);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int fork_no_orphan() {
|
||||
int pid = xfork();
|
||||
if (pid)
|
||||
return pid;
|
||||
prctl(PR_SET_PDEATHSIG, SIGKILL);
|
||||
if (getppid() == 1)
|
||||
exit(1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_command(exec_t &exec) {
|
||||
auto pipefd = array<int, 2>{-1, -1};
|
||||
int outfd = -1;
|
||||
|
||||
if (exec.fd == -1) {
|
||||
if (xpipe2(pipefd, O_CLOEXEC) == -1)
|
||||
return -1;
|
||||
outfd = pipefd[1];
|
||||
} else if (exec.fd >= 0) {
|
||||
outfd = exec.fd;
|
||||
}
|
||||
|
||||
int pid = exec.fork();
|
||||
if (pid < 0) {
|
||||
close(pipefd[0]);
|
||||
close(pipefd[1]);
|
||||
return -1;
|
||||
} else if (pid) {
|
||||
if (exec.fd == -1) {
|
||||
exec.fd = pipefd[0];
|
||||
close(pipefd[1]);
|
||||
}
|
||||
return pid;
|
||||
}
|
||||
|
||||
// Unblock all signals
|
||||
sigset_t set;
|
||||
sigfillset(&set);
|
||||
pthread_sigmask(SIG_UNBLOCK, &set, nullptr);
|
||||
|
||||
if (outfd >= 0) {
|
||||
xdup2(outfd, STDOUT_FILENO);
|
||||
if (exec.err)
|
||||
xdup2(outfd, STDERR_FILENO);
|
||||
close(outfd);
|
||||
}
|
||||
|
||||
// Call the pre-exec callback
|
||||
if (exec.pre_exec)
|
||||
exec.pre_exec();
|
||||
|
||||
execve(exec.argv[0], (char **) exec.argv, environ);
|
||||
PLOGE("execve %s", exec.argv[0]);
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
int exec_command_sync(exec_t &exec) {
|
||||
int pid = exec_command(exec);
|
||||
if (pid < 0)
|
||||
return -1;
|
||||
int status;
|
||||
waitpid(pid, &status, 0);
|
||||
return WEXITSTATUS(status);
|
||||
}
|
||||
|
||||
int new_daemon_thread(thread_entry entry, void *arg) {
|
||||
pthread_t thread;
|
||||
pthread_attr_t attr;
|
||||
pthread_attr_init(&attr);
|
||||
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
|
||||
errno = pthread_create(&thread, &attr, entry, arg);
|
||||
if (errno) {
|
||||
PLOGE("pthread_create");
|
||||
}
|
||||
return errno;
|
||||
}
|
||||
|
||||
static char *argv0;
|
||||
static size_t name_len;
|
||||
void init_argv0(int argc, char **argv) {
|
||||
argv0 = argv[0];
|
||||
name_len = (argv[argc - 1] - argv[0]) + strlen(argv[argc - 1]) + 1;
|
||||
}
|
||||
|
||||
void set_nice_name(Utf8CStr name) {
|
||||
memset(argv0, 0, name_len);
|
||||
strscpy(argv0, name.c_str(), name_len);
|
||||
prctl(PR_SET_NAME, name.c_str());
|
||||
}
|
||||
|
||||
template<typename T, int base>
|
||||
static T parse_num(string_view s) {
|
||||
T val = 0;
|
||||
for (char c : s) {
|
||||
if (isdigit(c)) {
|
||||
c -= '0';
|
||||
} else if (base > 10 && isalpha(c)) {
|
||||
c -= isupper(c) ? 'A' - 10 : 'a' - 10;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
if (c >= base) {
|
||||
return -1;
|
||||
}
|
||||
val *= base;
|
||||
val += c;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
/*
|
||||
* Bionic's atoi runs through strtol().
|
||||
* Use our own implementation for faster conversion.
|
||||
*/
|
||||
int parse_int(string_view s) {
|
||||
return parse_num<int, 10>(s);
|
||||
}
|
||||
|
||||
uint32_t parse_uint32_hex(string_view s) {
|
||||
return parse_num<uint32_t, 16>(s);
|
||||
}
|
||||
|
||||
int switch_mnt_ns(int pid) {
|
||||
int ret = -1;
|
||||
int fd = syscall(__NR_pidfd_open, pid, 0);
|
||||
if (fd > 0) {
|
||||
ret = setns(fd, CLONE_NEWNS);
|
||||
close(fd);
|
||||
}
|
||||
if (ret < 0) {
|
||||
char mnt[32];
|
||||
ssprintf(mnt, sizeof(mnt), "/proc/%d/ns/mnt", pid);
|
||||
fd = open(mnt, O_RDONLY);
|
||||
if (fd < 0) return 1; // Maybe process died..
|
||||
|
||||
// Switch to its namespace
|
||||
ret = xsetns(fd, 0);
|
||||
close(fd);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
string &replace_all(string &str, string_view from, string_view to) {
|
||||
size_t pos = 0;
|
||||
while((pos = str.find(from, pos)) != string::npos) {
|
||||
str.replace(pos, from.length(), to);
|
||||
pos += to.length();
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static auto split_impl(string_view s, string_view delims) {
|
||||
vector<T> result;
|
||||
size_t base = 0;
|
||||
size_t found;
|
||||
while (true) {
|
||||
found = s.find_first_of(delims, base);
|
||||
result.emplace_back(s.substr(base, found - base));
|
||||
if (found == string::npos)
|
||||
break;
|
||||
base = found + 1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
vector<string> split(string_view s, string_view delims) {
|
||||
return split_impl<string>(s, delims);
|
||||
}
|
||||
|
||||
#undef vsnprintf
|
||||
int vssprintf(char *dest, size_t size, const char *fmt, va_list ap) {
|
||||
if (size > 0) {
|
||||
*dest = 0;
|
||||
return std::min(vsnprintf(dest, size, fmt, ap), (int) size - 1);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int ssprintf(char *dest, size_t size, const char *fmt, ...) {
|
||||
va_list va;
|
||||
va_start(va, fmt);
|
||||
int r = vssprintf(dest, size, fmt, va);
|
||||
va_end(va);
|
||||
return r;
|
||||
}
|
||||
|
||||
#undef strlcpy
|
||||
size_t strscpy(char *dest, const char *src, size_t size) {
|
||||
return std::min(strlcpy(dest, src, size), size - 1);
|
||||
}
|
||||
|
||||
#undef vsnprintf
|
||||
static int fmt_and_log_with_rs(LogLevel level, const char *fmt, va_list ap) {
|
||||
constexpr int sz = 4096;
|
||||
char buf[sz];
|
||||
buf[0] = '\0';
|
||||
// Fortify logs when a fatal error occurs. Do not run through fortify again
|
||||
int len = std::min(__call_bypassing_fortify(vsnprintf)(buf, sz, fmt, ap), sz - 1);
|
||||
log_with_rs(level, Utf8CStr(buf, len + 1));
|
||||
return len;
|
||||
}
|
||||
|
||||
// Used to override external C library logging
|
||||
extern "C" int magisk_log_print(int prio, const char *tag, const char *fmt, ...) {
|
||||
LogLevel level;
|
||||
switch (prio) {
|
||||
case ANDROID_LOG_DEBUG:
|
||||
level = LogLevel::Debug;
|
||||
break;
|
||||
case ANDROID_LOG_INFO:
|
||||
level = LogLevel::Info;
|
||||
break;
|
||||
case ANDROID_LOG_WARN:
|
||||
level = LogLevel::Warn;
|
||||
break;
|
||||
case ANDROID_LOG_ERROR:
|
||||
level = LogLevel::Error;
|
||||
break;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
||||
char fmt_buf[4096];
|
||||
auto len = strscpy(fmt_buf, tag, sizeof(fmt_buf) - 1);
|
||||
// Prevent format specifications in the tag
|
||||
std::replace(fmt_buf, fmt_buf + len, '%', '_');
|
||||
len = ssprintf(fmt_buf + len, sizeof(fmt_buf) - len - 1, ": %s", fmt) + len;
|
||||
// Ensure the fmt string always ends with newline
|
||||
if (fmt_buf[len - 1] != '\n') {
|
||||
fmt_buf[len] = '\n';
|
||||
fmt_buf[len + 1] = '\0';
|
||||
}
|
||||
va_list argv;
|
||||
va_start(argv, fmt);
|
||||
int ret = fmt_and_log_with_rs(level, fmt_buf, argv);
|
||||
va_end(argv);
|
||||
return ret;
|
||||
}
|
||||
|
||||
#define LOG_BODY(level) \
|
||||
va_list argv; \
|
||||
va_start(argv, fmt); \
|
||||
fmt_and_log_with_rs(LogLevel::level, fmt, argv); \
|
||||
va_end(argv); \
|
||||
|
||||
// LTO will optimize out the NOP function
|
||||
#if MAGISK_DEBUG
|
||||
void LOGD(const char *fmt, ...) { LOG_BODY(Debug) }
|
||||
#else
|
||||
void LOGD(const char *fmt, ...) {}
|
||||
#endif
|
||||
void LOGI(const char *fmt, ...) { LOG_BODY(Info) }
|
||||
void LOGW(const char *fmt, ...) { LOG_BODY(Warn) }
|
||||
void LOGE(const char *fmt, ...) { LOG_BODY(Error) }
|
||||
|
||||
// Export raw symbol to fortify compat
|
||||
extern "C" void __vloge(const char* fmt, va_list ap) {
|
||||
fmt_and_log_with_rs(LogLevel::Error, fmt, ap);
|
||||
}
|
||||
|
||||
string full_read(int fd) {
|
||||
string str;
|
||||
char buf[4096];
|
||||
for (ssize_t len; (len = xread(fd, buf, sizeof(buf))) > 0;)
|
||||
str.insert(str.end(), buf, buf + len);
|
||||
return str;
|
||||
}
|
||||
|
||||
string full_read(const char *filename) {
|
||||
string str;
|
||||
if (int fd = xopen(filename, O_RDONLY | O_CLOEXEC); fd >= 0) {
|
||||
str = full_read(fd);
|
||||
close(fd);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
void write_zero(int fd, size_t size) {
|
||||
char buf[4096] = {0};
|
||||
size_t len;
|
||||
while (size > 0) {
|
||||
len = sizeof(buf) > size ? size : sizeof(buf);
|
||||
write(fd, buf, len);
|
||||
size -= len;
|
||||
}
|
||||
}
|
||||
|
||||
sDIR make_dir(DIR *dp) {
|
||||
return sDIR(dp, [](DIR *dp){ return dp ? closedir(dp) : 1; });
|
||||
}
|
||||
|
||||
sFILE make_file(FILE *fp) {
|
||||
return sFILE(fp, [](FILE *fp){ return fp ? fclose(fp) : 1; });
|
||||
}
|
||||
|
||||
mmap_data::mmap_data(const char *name, bool rw) {
|
||||
auto slice = rust::map_file(name, rw);
|
||||
if (!slice.empty()) {
|
||||
_buf = slice.data();
|
||||
_sz = slice.size();
|
||||
}
|
||||
}
|
||||
|
||||
mmap_data::mmap_data(int dirfd, const char *name, bool rw) {
|
||||
auto slice = rust::map_file_at(dirfd, name, rw);
|
||||
if (!slice.empty()) {
|
||||
_buf = slice.data();
|
||||
_sz = slice.size();
|
||||
}
|
||||
}
|
||||
|
||||
mmap_data::mmap_data(int fd, size_t sz, bool rw) {
|
||||
auto slice = rust::map_fd(fd, sz, rw);
|
||||
if (!slice.empty()) {
|
||||
_buf = slice.data();
|
||||
_sz = slice.size();
|
||||
}
|
||||
}
|
||||
|
||||
mmap_data::~mmap_data() {
|
||||
if (_buf)
|
||||
munmap(_buf, _sz);
|
||||
}
|
||||
|
||||
string resolve_preinit_dir(const char *base_dir) {
|
||||
string dir = base_dir;
|
||||
if (access((dir + "/unencrypted").data(), F_OK) == 0) {
|
||||
dir += "/unencrypted/magisk";
|
||||
} else if (access((dir + "/adb").data(), F_OK) == 0) {
|
||||
dir += "/adb";
|
||||
} else if (access((dir + "/watchdog").data(), F_OK) == 0) {
|
||||
dir += "/watchdog/magisk";
|
||||
} else {
|
||||
dir += "/magisk";
|
||||
}
|
||||
return dir;
|
||||
}
|
||||
|
||||
// FFI for Utf8CStr
|
||||
|
||||
extern "C" void cxx$utf8str$new(Utf8CStr *self, const void *s, size_t len);
|
||||
extern "C" const char *cxx$utf8str$ptr(const Utf8CStr *self);
|
||||
extern "C" size_t cxx$utf8str$len(const Utf8CStr *self);
|
||||
|
||||
Utf8CStr::Utf8CStr(const char *s, size_t len) {
|
||||
cxx$utf8str$new(this, s, len);
|
||||
}
|
||||
|
||||
const char *Utf8CStr::data() const {
|
||||
return cxx$utf8str$ptr(this);
|
||||
}
|
||||
|
||||
size_t Utf8CStr::length() const {
|
||||
return cxx$utf8str$len(this);
|
||||
}
|
||||
@@ -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)));
|
||||
}
|
||||
|
||||
185
native/src/base/derive/argh/errors.rs
Normal file
185
native/src/base/derive/argh/errors.rs
Normal file
@@ -0,0 +1,185 @@
|
||||
// Copyright (c) 2020 Google LLC All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use quote::ToTokens;
|
||||
use std::cell::RefCell;
|
||||
|
||||
/// A type for collecting procedural macro errors.
|
||||
#[derive(Default)]
|
||||
pub struct Errors {
|
||||
errors: RefCell<Vec<syn::Error>>,
|
||||
}
|
||||
|
||||
/// Produce functions to expect particular literals in `syn::Expr`
|
||||
macro_rules! expect_lit_fn {
|
||||
($(($fn_name:ident, $syn_type:ident, $variant:ident, $lit_name:literal),)*) => {
|
||||
$(
|
||||
pub fn $fn_name<'a>(&self, e: &'a syn::Expr) -> Option<&'a syn::$syn_type> {
|
||||
if let syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::$variant(inner), .. }) = e {
|
||||
Some(inner)
|
||||
} else {
|
||||
self.unexpected_lit($lit_name, e);
|
||||
None
|
||||
}
|
||||
}
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
/// Produce functions to expect particular variants of `syn::Meta`
|
||||
macro_rules! expect_meta_fn {
|
||||
($(($fn_name:ident, $syn_type:ident, $variant:ident, $meta_name:literal),)*) => {
|
||||
$(
|
||||
pub fn $fn_name<'a>(&self, meta: &'a syn::Meta) -> Option<&'a syn::$syn_type> {
|
||||
if let syn::Meta::$variant(inner) = meta {
|
||||
Some(inner)
|
||||
} else {
|
||||
self.unexpected_meta($meta_name, meta);
|
||||
None
|
||||
}
|
||||
}
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
impl Errors {
|
||||
/// Issue an error like:
|
||||
///
|
||||
/// Duplicate foo attribute
|
||||
/// First foo attribute here
|
||||
pub fn duplicate_attrs(
|
||||
&self,
|
||||
attr_kind: &str,
|
||||
first: &impl syn::spanned::Spanned,
|
||||
second: &impl syn::spanned::Spanned,
|
||||
) {
|
||||
self.duplicate_attrs_inner(attr_kind, first.span(), second.span())
|
||||
}
|
||||
|
||||
fn duplicate_attrs_inner(&self, attr_kind: &str, first: Span, second: Span) {
|
||||
self.err_span(second, &["Duplicate ", attr_kind, " attribute"].concat());
|
||||
self.err_span(first, &["First ", attr_kind, " attribute here"].concat());
|
||||
}
|
||||
|
||||
expect_lit_fn![
|
||||
(expect_lit_str, LitStr, Str, "string"),
|
||||
(expect_lit_char, LitChar, Char, "character"),
|
||||
(expect_lit_int, LitInt, Int, "integer"),
|
||||
];
|
||||
|
||||
expect_meta_fn![
|
||||
(expect_meta_word, Path, Path, "path"),
|
||||
(expect_meta_list, MetaList, List, "list"),
|
||||
(
|
||||
expect_meta_name_value,
|
||||
MetaNameValue,
|
||||
NameValue,
|
||||
"name-value pair"
|
||||
),
|
||||
];
|
||||
|
||||
fn unexpected_lit(&self, expected: &str, found: &syn::Expr) {
|
||||
fn lit_kind(lit: &syn::Lit) -> &'static str {
|
||||
use syn::Lit::{Bool, Byte, ByteStr, Char, Float, Int, Str, Verbatim};
|
||||
match lit {
|
||||
Str(_) => "string",
|
||||
ByteStr(_) => "bytestring",
|
||||
Byte(_) => "byte",
|
||||
Char(_) => "character",
|
||||
Int(_) => "integer",
|
||||
Float(_) => "float",
|
||||
Bool(_) => "boolean",
|
||||
Verbatim(_) => "unknown (possibly extra-large integer)",
|
||||
_ => "unknown literal kind",
|
||||
}
|
||||
}
|
||||
|
||||
if let syn::Expr::Lit(syn::ExprLit { lit, .. }) = found {
|
||||
self.err(
|
||||
found,
|
||||
&[
|
||||
"Expected ",
|
||||
expected,
|
||||
" literal, found ",
|
||||
lit_kind(lit),
|
||||
" literal",
|
||||
]
|
||||
.concat(),
|
||||
)
|
||||
} else {
|
||||
self.err(
|
||||
found,
|
||||
&[
|
||||
"Expected ",
|
||||
expected,
|
||||
" literal, found non-literal expression.",
|
||||
]
|
||||
.concat(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn unexpected_meta(&self, expected: &str, found: &syn::Meta) {
|
||||
fn meta_kind(meta: &syn::Meta) -> &'static str {
|
||||
use syn::Meta::{List, NameValue, Path};
|
||||
match meta {
|
||||
Path(_) => "path",
|
||||
List(_) => "list",
|
||||
NameValue(_) => "name-value pair",
|
||||
}
|
||||
}
|
||||
|
||||
self.err(
|
||||
found,
|
||||
&[
|
||||
"Expected ",
|
||||
expected,
|
||||
" attribute, found ",
|
||||
meta_kind(found),
|
||||
" attribute",
|
||||
]
|
||||
.concat(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Issue an error relating to a particular `Spanned` structure.
|
||||
pub fn err(&self, spanned: &impl syn::spanned::Spanned, msg: &str) {
|
||||
self.err_span(spanned.span(), msg);
|
||||
}
|
||||
|
||||
/// Issue an error relating to a particular `Span`.
|
||||
pub fn err_span(&self, span: Span, msg: &str) {
|
||||
self.push(syn::Error::new(span, msg));
|
||||
}
|
||||
|
||||
/// Issue an error spanning over the given syntax tree node.
|
||||
pub fn err_span_tokens<T: ToTokens>(&self, tokens: T, msg: &str) {
|
||||
self.push(syn::Error::new_spanned(tokens, msg));
|
||||
}
|
||||
|
||||
/// Push a `syn::Error` onto the list of errors to issue.
|
||||
pub fn push(&self, err: syn::Error) {
|
||||
self.errors.borrow_mut().push(err);
|
||||
}
|
||||
|
||||
/// Convert a `syn::Result` to an `Option`, logging the error if present.
|
||||
pub fn ok<T>(&self, r: syn::Result<T>) -> Option<T> {
|
||||
match r {
|
||||
Ok(v) => Some(v),
|
||||
Err(e) => {
|
||||
self.push(e);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for Errors {
|
||||
/// Convert the errors into tokens that, when emit, will cause
|
||||
/// the user of the macro to receive compiler errors.
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
tokens.extend(self.errors.borrow().iter().map(|e| e.to_compile_error()));
|
||||
}
|
||||
}
|
||||
912
native/src/base/derive/argh/mod.rs
Normal file
912
native/src/base/derive/argh/mod.rs
Normal file
@@ -0,0 +1,912 @@
|
||||
// Copyright (c) 2020 Google LLC All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
use syn::ext::IdentExt as _;
|
||||
|
||||
/// Implementation of the `FromArgs` and `argh(...)` derive attributes.
|
||||
///
|
||||
/// For more thorough documentation, see the `argh` crate itself.
|
||||
extern crate proc_macro;
|
||||
|
||||
use errors::Errors;
|
||||
use parse_attrs::{FieldAttrs, FieldKind, TypeAttrs, check_long_name};
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use quote::{ToTokens, quote, quote_spanned};
|
||||
use std::collections::HashMap;
|
||||
use std::str::FromStr;
|
||||
use syn::spanned::Spanned;
|
||||
use syn::{GenericArgument, LitStr, PathArguments, Type};
|
||||
|
||||
mod errors;
|
||||
mod parse_attrs;
|
||||
|
||||
/// Transform the input into a token stream containing any generated implementations,
|
||||
/// as well as all errors that occurred.
|
||||
pub(crate) fn impl_from_args(input: &syn::DeriveInput) -> TokenStream {
|
||||
let errors = &Errors::default();
|
||||
let type_attrs = &TypeAttrs::parse(errors, input);
|
||||
let mut output_tokens = match &input.data {
|
||||
syn::Data::Struct(ds) => {
|
||||
impl_from_args_struct(errors, &input.ident, type_attrs, &input.generics, ds)
|
||||
}
|
||||
syn::Data::Enum(de) => {
|
||||
impl_from_args_enum(errors, &input.ident, type_attrs, &input.generics, de)
|
||||
}
|
||||
syn::Data::Union(_) => {
|
||||
errors.err(input, "`#[derive(FromArgs)]` cannot be applied to unions");
|
||||
TokenStream::new()
|
||||
}
|
||||
};
|
||||
errors.to_tokens(&mut output_tokens);
|
||||
output_tokens
|
||||
}
|
||||
|
||||
/// The kind of optionality a parameter has.
|
||||
enum Optionality {
|
||||
None,
|
||||
Defaulted(TokenStream),
|
||||
Optional,
|
||||
Repeating,
|
||||
DefaultedRepeating(TokenStream),
|
||||
}
|
||||
|
||||
impl PartialEq<Optionality> for Optionality {
|
||||
fn eq(&self, other: &Optionality) -> bool {
|
||||
use Optionality::*;
|
||||
// NB: (Defaulted, Defaulted) can't contain the same token streams
|
||||
matches!((self, other), (Optional, Optional) | (Repeating, Repeating))
|
||||
}
|
||||
}
|
||||
|
||||
impl Optionality {
|
||||
/// Whether or not this is `Optionality::None`
|
||||
fn is_required(&self) -> bool {
|
||||
matches!(self, Optionality::None)
|
||||
}
|
||||
}
|
||||
|
||||
/// A field of a `#![derive(FromArgs)]` struct with attributes and some other
|
||||
/// notable metadata appended.
|
||||
struct StructField<'a> {
|
||||
/// The original parsed field
|
||||
field: &'a syn::Field,
|
||||
/// The parsed attributes of the field
|
||||
attrs: FieldAttrs,
|
||||
/// The field name. This is contained optionally inside `field`,
|
||||
/// but is duplicated non-optionally here to indicate that all field that
|
||||
/// have reached this point must have a field name, and it no longer
|
||||
/// needs to be unwrapped.
|
||||
name: &'a syn::Ident,
|
||||
/// Similar to `name` above, this is contained optionally inside `FieldAttrs`,
|
||||
/// but here is fully present to indicate that we only have to consider fields
|
||||
/// with a valid `kind` at this point.
|
||||
kind: FieldKind,
|
||||
// If `field.ty` is `Vec<T>` or `Option<T>`, this is `T`, otherwise it's `&field.ty`.
|
||||
// This is used to enable consistent parsing code between optional and non-optional
|
||||
// keyed and subcommand fields.
|
||||
ty_without_wrapper: &'a syn::Type,
|
||||
// Whether the field represents an optional value, such as an `Option` subcommand field
|
||||
// or an `Option` or `Vec` keyed argument, or if it has a `default`.
|
||||
optionality: Optionality,
|
||||
// The `--`-prefixed name of the option, if one exists.
|
||||
long_name: Option<String>,
|
||||
}
|
||||
|
||||
impl<'a> StructField<'a> {
|
||||
/// Attempts to parse a field of a `#[derive(FromArgs)]` struct, pulling out the
|
||||
/// fields required for code generation.
|
||||
fn new(errors: &Errors, field: &'a syn::Field, attrs: FieldAttrs) -> Option<Self> {
|
||||
let name = field.ident.as_ref().expect("missing ident for named field");
|
||||
|
||||
// Ensure that one "kind" is present (switch, option, subcommand, positional)
|
||||
let kind = if let Some(field_type) = &attrs.field_type {
|
||||
field_type.kind
|
||||
} else {
|
||||
errors.err(
|
||||
field,
|
||||
concat!(
|
||||
"Missing `argh` field kind attribute.\n",
|
||||
"Expected one of: `switch`, `option`, `remaining`, `subcommand`, `positional`",
|
||||
),
|
||||
);
|
||||
return None;
|
||||
};
|
||||
|
||||
// Parse out whether a field is optional (`Option` or `Vec`).
|
||||
let optionality;
|
||||
let ty_without_wrapper;
|
||||
match kind {
|
||||
FieldKind::Switch => {
|
||||
if !ty_expect_switch(errors, &field.ty) {
|
||||
return None;
|
||||
}
|
||||
optionality = Optionality::Optional;
|
||||
ty_without_wrapper = &field.ty;
|
||||
}
|
||||
FieldKind::Option | FieldKind::Positional => {
|
||||
if let Some(default) = &attrs.default {
|
||||
let tokens = match TokenStream::from_str(&default.value()) {
|
||||
Ok(tokens) => tokens,
|
||||
Err(_) => {
|
||||
errors.err(&default, "Invalid tokens: unable to lex `default` value");
|
||||
return None;
|
||||
}
|
||||
};
|
||||
// Set the span of the generated tokens to the string literal
|
||||
let tokens: TokenStream = tokens
|
||||
.into_iter()
|
||||
.map(|mut tree| {
|
||||
tree.set_span(default.span());
|
||||
tree
|
||||
})
|
||||
.collect();
|
||||
let inner = if let Some(x) = ty_inner(&["Vec"], &field.ty) {
|
||||
optionality = Optionality::DefaultedRepeating(tokens);
|
||||
x
|
||||
} else {
|
||||
optionality = Optionality::Defaulted(tokens);
|
||||
&field.ty
|
||||
};
|
||||
ty_without_wrapper = inner;
|
||||
} else {
|
||||
let mut inner = None;
|
||||
optionality = if let Some(x) = ty_inner(&["Option"], &field.ty) {
|
||||
inner = Some(x);
|
||||
Optionality::Optional
|
||||
} else if let Some(x) = ty_inner(&["Vec"], &field.ty) {
|
||||
inner = Some(x);
|
||||
Optionality::Repeating
|
||||
} else {
|
||||
Optionality::None
|
||||
};
|
||||
ty_without_wrapper = inner.unwrap_or(&field.ty);
|
||||
}
|
||||
}
|
||||
FieldKind::SubCommand => {
|
||||
let inner = ty_inner(&["Option"], &field.ty);
|
||||
optionality = if inner.is_some() {
|
||||
Optionality::Optional
|
||||
} else {
|
||||
Optionality::None
|
||||
};
|
||||
ty_without_wrapper = inner.unwrap_or(&field.ty);
|
||||
}
|
||||
}
|
||||
|
||||
// Determine the "long" name of options and switches.
|
||||
// Defaults to the kebab-cased field name if `#[argh(long = "...")]` is omitted.
|
||||
// If `#[argh(long = none)]` is explicitly set, no long name will be set.
|
||||
let long_name = match kind {
|
||||
FieldKind::Switch | FieldKind::Option => {
|
||||
let long_name = match &attrs.long {
|
||||
None => {
|
||||
let kebab_name = to_kebab_case(&name.unraw().to_string());
|
||||
check_long_name(errors, name, &kebab_name);
|
||||
Some(kebab_name)
|
||||
}
|
||||
Some(None) => None,
|
||||
Some(Some(long)) => Some(long.value()),
|
||||
}
|
||||
.map(|long_name| {
|
||||
if long_name == "help" {
|
||||
errors.err(field, "Custom `--help` flags are not supported.");
|
||||
}
|
||||
format!("--{}", long_name)
|
||||
});
|
||||
if let (None, None) = (&attrs.short, &long_name) {
|
||||
errors.err(field, "At least one of `short` or `long` has to be set.")
|
||||
};
|
||||
long_name
|
||||
}
|
||||
FieldKind::SubCommand | FieldKind::Positional => None,
|
||||
};
|
||||
|
||||
Some(StructField {
|
||||
field,
|
||||
attrs,
|
||||
kind,
|
||||
optionality,
|
||||
ty_without_wrapper,
|
||||
name,
|
||||
long_name,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn positional_arg_name(&self) -> String {
|
||||
self.attrs
|
||||
.arg_name
|
||||
.as_ref()
|
||||
.map(LitStr::value)
|
||||
.unwrap_or_else(|| self.name.to_string().trim_matches('_').to_owned())
|
||||
}
|
||||
|
||||
fn option_arg_name(&self) -> String {
|
||||
match (&self.attrs.short, &self.long_name) {
|
||||
(None, None) => unreachable!("short and long cannot both be None"),
|
||||
(Some(short), None) => format!("-{}", short.value()),
|
||||
(None, Some(long)) => long.clone(),
|
||||
(Some(short), Some(long)) => format!("-{},{long}", short.value()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn to_kebab_case(s: &str) -> String {
|
||||
let words = s.split('_').filter(|word| !word.is_empty());
|
||||
let mut res = String::with_capacity(s.len());
|
||||
for word in words {
|
||||
if !res.is_empty() {
|
||||
res.push('-')
|
||||
}
|
||||
res.push_str(word)
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
/// Implements `FromArgs` and `TopLevelCommand` or `SubCommand` for a `#[derive(FromArgs)]` struct.
|
||||
fn impl_from_args_struct(
|
||||
errors: &Errors,
|
||||
name: &syn::Ident,
|
||||
type_attrs: &TypeAttrs,
|
||||
generic_args: &syn::Generics,
|
||||
ds: &syn::DataStruct,
|
||||
) -> TokenStream {
|
||||
let fields = match &ds.fields {
|
||||
syn::Fields::Named(fields) => fields,
|
||||
syn::Fields::Unnamed(_) => {
|
||||
errors.err(
|
||||
&ds.struct_token,
|
||||
"`#![derive(FromArgs)]` is not currently supported on tuple structs",
|
||||
);
|
||||
return TokenStream::new();
|
||||
}
|
||||
syn::Fields::Unit => {
|
||||
errors.err(
|
||||
&ds.struct_token,
|
||||
"#![derive(FromArgs)]` cannot be applied to unit structs",
|
||||
);
|
||||
return TokenStream::new();
|
||||
}
|
||||
};
|
||||
|
||||
let fields: Vec<_> = fields
|
||||
.named
|
||||
.iter()
|
||||
.filter_map(|field| {
|
||||
let attrs = FieldAttrs::parse(errors, field);
|
||||
StructField::new(errors, field, attrs)
|
||||
})
|
||||
.collect();
|
||||
|
||||
ensure_unique_names(errors, &fields);
|
||||
ensure_only_trailing_positionals_are_optional(errors, &fields);
|
||||
|
||||
let impl_span = Span::call_site();
|
||||
|
||||
let from_args_method = impl_from_args_struct_from_args(errors, type_attrs, &fields);
|
||||
|
||||
let top_or_sub_cmd_impl = top_or_sub_cmd_impl(errors, name, type_attrs, generic_args);
|
||||
|
||||
let (impl_generics, ty_generics, where_clause) = generic_args.split_for_impl();
|
||||
let trait_impl = quote_spanned! { impl_span =>
|
||||
#[automatically_derived]
|
||||
impl #impl_generics argh::FromArgs for #name #ty_generics #where_clause {
|
||||
#from_args_method
|
||||
}
|
||||
|
||||
#top_or_sub_cmd_impl
|
||||
};
|
||||
|
||||
trait_impl
|
||||
}
|
||||
|
||||
fn impl_from_args_struct_from_args<'a>(
|
||||
errors: &Errors,
|
||||
type_attrs: &TypeAttrs,
|
||||
fields: &'a [StructField<'a>],
|
||||
) -> TokenStream {
|
||||
let init_fields = declare_local_storage_for_from_args_fields(fields);
|
||||
let unwrap_fields = unwrap_from_args_fields(fields);
|
||||
let positional_fields: Vec<&StructField<'_>> = fields
|
||||
.iter()
|
||||
.filter(|field| field.kind == FieldKind::Positional)
|
||||
.collect();
|
||||
let positional_field_idents = positional_fields.iter().map(|field| &field.field.ident);
|
||||
let positional_field_names = positional_fields.iter().map(|field| field.name.to_string());
|
||||
let last_positional_is_repeating = positional_fields
|
||||
.last()
|
||||
.map(|field| field.optionality == Optionality::Repeating)
|
||||
.unwrap_or(false);
|
||||
let last_positional_is_greedy = positional_fields
|
||||
.last()
|
||||
.map(|field| field.kind == FieldKind::Positional && field.attrs.greedy.is_some())
|
||||
.unwrap_or(false);
|
||||
|
||||
let flag_output_table = fields.iter().filter_map(|field| {
|
||||
let field_name = &field.field.ident;
|
||||
match field.kind {
|
||||
FieldKind::Option => Some(quote! { argh::ParseStructOption::Value(&mut #field_name) }),
|
||||
FieldKind::Switch => Some(quote! { argh::ParseStructOption::Flag(&mut #field_name) }),
|
||||
FieldKind::SubCommand | FieldKind::Positional => None,
|
||||
}
|
||||
});
|
||||
|
||||
let flag_str_to_output_table_map = flag_str_to_output_table_map_entries(fields);
|
||||
|
||||
let mut subcommands_iter = fields
|
||||
.iter()
|
||||
.filter(|field| field.kind == FieldKind::SubCommand)
|
||||
.fuse();
|
||||
|
||||
let subcommand: Option<&StructField<'_>> = subcommands_iter.next();
|
||||
for dup_subcommand in subcommands_iter {
|
||||
errors.duplicate_attrs(
|
||||
"subcommand",
|
||||
subcommand.unwrap().field,
|
||||
dup_subcommand.field,
|
||||
);
|
||||
}
|
||||
|
||||
let impl_span = Span::call_site();
|
||||
|
||||
let missing_requirements_ident = syn::Ident::new("__missing_requirements", impl_span);
|
||||
|
||||
let append_missing_requirements =
|
||||
append_missing_requirements(&missing_requirements_ident, fields);
|
||||
|
||||
let parse_subcommands = if let Some(subcommand) = subcommand {
|
||||
let name = subcommand.name;
|
||||
let ty = subcommand.ty_without_wrapper;
|
||||
quote_spanned! { impl_span =>
|
||||
Some(argh::ParseStructSubCommand {
|
||||
subcommands: <#ty as argh::SubCommands>::COMMANDS,
|
||||
dynamic_subcommands: &<#ty as argh::SubCommands>::dynamic_commands(),
|
||||
parse_func: &mut |__command, __remaining_args| {
|
||||
#name = Some(<#ty as argh::FromArgs>::from_args(__command, __remaining_args)?);
|
||||
Ok(())
|
||||
},
|
||||
})
|
||||
}
|
||||
} else {
|
||||
quote_spanned! { impl_span => None }
|
||||
};
|
||||
|
||||
let help_triggers = get_help_triggers(type_attrs);
|
||||
|
||||
let method_impl = quote_spanned! { impl_span =>
|
||||
fn from_args(__cmd_name: &[&str], __args: &[&str])
|
||||
-> std::result::Result<Self, argh::EarlyExit>
|
||||
{
|
||||
#![allow(clippy::unwrap_in_result)]
|
||||
|
||||
#( #init_fields )*
|
||||
|
||||
argh::parse_struct_args(
|
||||
__cmd_name,
|
||||
__args,
|
||||
argh::ParseStructOptions {
|
||||
arg_to_slot: &[ #( #flag_str_to_output_table_map ,)* ],
|
||||
slots: &mut [ #( #flag_output_table, )* ],
|
||||
help_triggers: &[ #( #help_triggers ),* ],
|
||||
},
|
||||
argh::ParseStructPositionals {
|
||||
positionals: &mut [
|
||||
#(
|
||||
argh::ParseStructPositional {
|
||||
name: #positional_field_names,
|
||||
slot: &mut #positional_field_idents as &mut dyn argh::ParseValueSlot,
|
||||
},
|
||||
)*
|
||||
],
|
||||
last_is_repeating: #last_positional_is_repeating,
|
||||
last_is_greedy: #last_positional_is_greedy,
|
||||
},
|
||||
#parse_subcommands,
|
||||
)?;
|
||||
|
||||
let mut #missing_requirements_ident = argh::MissingRequirements::default();
|
||||
#(
|
||||
#append_missing_requirements
|
||||
)*
|
||||
#missing_requirements_ident.err_on_any()?;
|
||||
|
||||
Ok(Self {
|
||||
#( #unwrap_fields, )*
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
method_impl
|
||||
}
|
||||
|
||||
/// get help triggers vector from type_attrs.help_triggers as a [`Vec<String>`]
|
||||
///
|
||||
/// Defaults to vec!["-h", "--help"] if type_attrs.help_triggers is None
|
||||
fn get_help_triggers(type_attrs: &TypeAttrs) -> Vec<String> {
|
||||
if type_attrs.is_subcommand.is_some() {
|
||||
// Subcommands should never have any help triggers
|
||||
Vec::new()
|
||||
} else {
|
||||
type_attrs.help_triggers.as_ref().map_or_else(
|
||||
|| vec!["-h".to_string(), "--help".to_string()],
|
||||
|s| {
|
||||
s.iter()
|
||||
.filter_map(|s| {
|
||||
let trigger = s.value();
|
||||
let trigger_trimmed = trigger.trim().to_owned();
|
||||
if trigger_trimmed.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(trigger_trimmed)
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Ensures that only trailing positional args are non-required.
|
||||
fn ensure_only_trailing_positionals_are_optional(errors: &Errors, fields: &[StructField<'_>]) {
|
||||
let mut first_non_required_span = None;
|
||||
for field in fields {
|
||||
if field.kind == FieldKind::Positional {
|
||||
if let Some(first) = first_non_required_span
|
||||
&& field.optionality.is_required()
|
||||
{
|
||||
errors.err_span(
|
||||
first,
|
||||
"Only trailing positional arguments may be `Option`, `Vec`, or defaulted.",
|
||||
);
|
||||
errors.err(
|
||||
&field.field,
|
||||
"Later non-optional positional argument declared here.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
if !field.optionality.is_required() {
|
||||
first_non_required_span = Some(field.field.span());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Ensures that only one short or long name is used.
|
||||
fn ensure_unique_names(errors: &Errors, fields: &[StructField<'_>]) {
|
||||
let mut seen_short_names = HashMap::new();
|
||||
let mut seen_long_names = HashMap::new();
|
||||
|
||||
for field in fields {
|
||||
if let Some(short_name) = &field.attrs.short {
|
||||
let short_name = short_name.value();
|
||||
if let Some(first_use_field) = seen_short_names.get(&short_name) {
|
||||
errors.err_span_tokens(
|
||||
first_use_field,
|
||||
&format!(
|
||||
"The short name of \"-{}\" was already used here.",
|
||||
short_name
|
||||
),
|
||||
);
|
||||
errors.err_span_tokens(field.field, "Later usage here.");
|
||||
}
|
||||
|
||||
seen_short_names.insert(short_name, &field.field);
|
||||
}
|
||||
|
||||
if let Some(long_name) = &field.long_name {
|
||||
if let Some(first_use_field) = seen_long_names.get(&long_name) {
|
||||
errors.err_span_tokens(
|
||||
*first_use_field,
|
||||
&format!("The long name of \"{}\" was already used here.", long_name),
|
||||
);
|
||||
errors.err_span_tokens(field.field, "Later usage here.");
|
||||
}
|
||||
|
||||
seen_long_names.insert(long_name, field.field);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Implement `argh::TopLevelCommand` or `argh::SubCommand` as appropriate.
|
||||
fn top_or_sub_cmd_impl(
|
||||
errors: &Errors,
|
||||
name: &syn::Ident,
|
||||
type_attrs: &TypeAttrs,
|
||||
generic_args: &syn::Generics,
|
||||
) -> TokenStream {
|
||||
let description = String::new();
|
||||
let (impl_generics, ty_generics, where_clause) = generic_args.split_for_impl();
|
||||
if type_attrs.is_subcommand.is_none() {
|
||||
// Not a subcommand
|
||||
quote! {
|
||||
#[automatically_derived]
|
||||
impl #impl_generics argh::TopLevelCommand for #name #ty_generics #where_clause {}
|
||||
}
|
||||
} else {
|
||||
let empty_str = syn::LitStr::new("", Span::call_site());
|
||||
let subcommand_name = type_attrs.name.as_ref().unwrap_or_else(|| {
|
||||
errors.err(
|
||||
name,
|
||||
"`#[argh(name = \"...\")]` attribute is required for subcommands",
|
||||
);
|
||||
&empty_str
|
||||
});
|
||||
quote! {
|
||||
#[automatically_derived]
|
||||
impl #impl_generics argh::SubCommand for #name #ty_generics #where_clause {
|
||||
const COMMAND: &'static argh::CommandInfo = &argh::CommandInfo {
|
||||
name: #subcommand_name,
|
||||
description: #description,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Declare a local slots to store each field in during parsing.
|
||||
///
|
||||
/// Most fields are stored in `Option<FieldType>` locals.
|
||||
/// `argh(option)` fields are stored in a `ParseValueSlotTy` along with a
|
||||
/// function that knows how to decode the appropriate value.
|
||||
fn declare_local_storage_for_from_args_fields<'a>(
|
||||
fields: &'a [StructField<'a>],
|
||||
) -> impl Iterator<Item = TokenStream> + 'a {
|
||||
fields.iter().map(|field| {
|
||||
let field_name = &field.field.ident;
|
||||
let field_type = &field.ty_without_wrapper;
|
||||
|
||||
// Wrap field types in `Option` if they aren't already `Option` or `Vec`-wrapped.
|
||||
let field_slot_type = match field.optionality {
|
||||
Optionality::Optional | Optionality::Repeating => (&field.field.ty).into_token_stream(),
|
||||
Optionality::None | Optionality::Defaulted(_) => {
|
||||
quote! { std::option::Option<#field_type> }
|
||||
}
|
||||
Optionality::DefaultedRepeating(_) => {
|
||||
quote! { std::option::Option<std::vec::Vec<#field_type>> }
|
||||
}
|
||||
};
|
||||
|
||||
match field.kind {
|
||||
FieldKind::Option | FieldKind::Positional => {
|
||||
let from_str_fn = match &field.attrs.from_str_fn {
|
||||
Some(from_str_fn) => from_str_fn.into_token_stream(),
|
||||
None => {
|
||||
quote! {
|
||||
<#field_type as argh::FromArgValue>::from_arg_value
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
quote! {
|
||||
let mut #field_name: argh::ParseValueSlotTy<#field_slot_type, #field_type>
|
||||
= argh::ParseValueSlotTy {
|
||||
slot: std::default::Default::default(),
|
||||
parse_func: |_, value| { #from_str_fn(value) },
|
||||
};
|
||||
}
|
||||
}
|
||||
FieldKind::SubCommand => {
|
||||
quote! { let mut #field_name: #field_slot_type = None; }
|
||||
}
|
||||
FieldKind::Switch => {
|
||||
quote! { let mut #field_name: #field_slot_type = argh::Flag::default(); }
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Unwrap non-optional fields and take options out of their tuple slots.
|
||||
fn unwrap_from_args_fields<'a>(
|
||||
fields: &'a [StructField<'a>],
|
||||
) -> impl Iterator<Item = TokenStream> + 'a {
|
||||
fields.iter().map(|field| {
|
||||
let field_name = field.name;
|
||||
match field.kind {
|
||||
FieldKind::Option | FieldKind::Positional => match &field.optionality {
|
||||
Optionality::None => quote! {
|
||||
#field_name: #field_name.slot.unwrap()
|
||||
},
|
||||
Optionality::Optional | Optionality::Repeating => {
|
||||
quote! { #field_name: #field_name.slot }
|
||||
}
|
||||
Optionality::Defaulted(tokens) | Optionality::DefaultedRepeating(tokens) => {
|
||||
quote! {
|
||||
#field_name: #field_name.slot.unwrap_or_else(|| #tokens)
|
||||
}
|
||||
}
|
||||
},
|
||||
FieldKind::Switch => field_name.into_token_stream(),
|
||||
FieldKind::SubCommand => match field.optionality {
|
||||
Optionality::None => quote! { #field_name: #field_name.unwrap() },
|
||||
Optionality::Optional | Optionality::Repeating => field_name.into_token_stream(),
|
||||
Optionality::Defaulted(_) | Optionality::DefaultedRepeating(_) => unreachable!(),
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Entries of tokens like `("--some-flag-key", 5)` that map from a flag key string
|
||||
/// to an index in the output table.
|
||||
fn flag_str_to_output_table_map_entries<'a>(fields: &'a [StructField<'a>]) -> Vec<TokenStream> {
|
||||
let mut flag_str_to_output_table_map = vec![];
|
||||
|
||||
for (i, field) in fields.iter().enumerate() {
|
||||
if let Some(short) = &field.attrs.short {
|
||||
let short = format!("-{}", short.value());
|
||||
flag_str_to_output_table_map.push(quote! { (#short, #i) });
|
||||
}
|
||||
if let Some(long) = &field.long_name {
|
||||
flag_str_to_output_table_map.push(quote! { (#long, #i) });
|
||||
}
|
||||
}
|
||||
flag_str_to_output_table_map
|
||||
}
|
||||
|
||||
/// For each non-optional field, add an entry to the `argh::MissingRequirements`.
|
||||
fn append_missing_requirements<'a>(
|
||||
// missing_requirements_ident
|
||||
mri: &syn::Ident,
|
||||
fields: &'a [StructField<'a>],
|
||||
) -> impl Iterator<Item = TokenStream> + 'a {
|
||||
let mri = mri.clone();
|
||||
fields
|
||||
.iter()
|
||||
.filter(|f| f.optionality.is_required())
|
||||
.map(move |field| {
|
||||
let field_name = field.name;
|
||||
match field.kind {
|
||||
FieldKind::Switch => unreachable!("switches are always optional"),
|
||||
FieldKind::Positional => {
|
||||
let name = field.positional_arg_name();
|
||||
quote! {
|
||||
if #field_name.slot.is_none() {
|
||||
#mri.missing_positional_arg(#name)
|
||||
}
|
||||
}
|
||||
}
|
||||
FieldKind::Option => {
|
||||
let name = field.option_arg_name();
|
||||
quote! {
|
||||
if #field_name.slot.is_none() {
|
||||
#mri.missing_option(#name)
|
||||
}
|
||||
}
|
||||
}
|
||||
FieldKind::SubCommand => {
|
||||
let ty = field.ty_without_wrapper;
|
||||
quote! {
|
||||
if #field_name.is_none() {
|
||||
#mri.missing_subcommands(
|
||||
<#ty as argh::SubCommands>::COMMANDS
|
||||
.iter()
|
||||
.cloned()
|
||||
.chain(
|
||||
<#ty as argh::SubCommands>::dynamic_commands()
|
||||
.iter()
|
||||
.copied()
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Require that a type can be a `switch`.
|
||||
/// Throws an error for all types except booleans and integers
|
||||
fn ty_expect_switch(errors: &Errors, ty: &syn::Type) -> bool {
|
||||
fn ty_can_be_switch(ty: &syn::Type) -> bool {
|
||||
if let syn::Type::Path(path) = ty {
|
||||
if path.qself.is_some() {
|
||||
return false;
|
||||
}
|
||||
if path.path.segments.len() != 1 {
|
||||
return false;
|
||||
}
|
||||
let ident = &path.path.segments[0].ident;
|
||||
// `Option<bool>` can be used as a `switch`.
|
||||
if ident == "Option"
|
||||
&& let PathArguments::AngleBracketed(args) = &path.path.segments[0].arguments
|
||||
&& let GenericArgument::Type(Type::Path(p)) = &args.args[0]
|
||||
&& p.path.segments[0].ident == "bool"
|
||||
{
|
||||
return true;
|
||||
}
|
||||
[
|
||||
"bool", "u8", "u16", "u32", "u64", "u128", "i8", "i16", "i32", "i64", "i128",
|
||||
]
|
||||
.iter()
|
||||
.any(|path| ident == path)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
let res = ty_can_be_switch(ty);
|
||||
if !res {
|
||||
errors.err(
|
||||
ty,
|
||||
"switches must be of type `bool`, `Option<bool>`, or integer type",
|
||||
);
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
/// Returns `Some(T)` if a type is `wrapper_name<T>` for any `wrapper_name` in `wrapper_names`.
|
||||
fn ty_inner<'a>(wrapper_names: &[&str], ty: &'a syn::Type) -> Option<&'a syn::Type> {
|
||||
if let syn::Type::Path(path) = ty {
|
||||
if path.qself.is_some() {
|
||||
return None;
|
||||
}
|
||||
// Since we only check the last path segment, it isn't necessarily the case that
|
||||
// we're referring to `std::vec::Vec` or `std::option::Option`, but there isn't
|
||||
// a fool proof way to check these since name resolution happens after macro expansion,
|
||||
// so this is likely "good enough" (so long as people don't have their own types called
|
||||
// `Option` or `Vec` that take one generic parameter they're looking to parse).
|
||||
let last_segment = path.path.segments.last()?;
|
||||
if !wrapper_names.iter().any(|name| last_segment.ident == *name) {
|
||||
return None;
|
||||
}
|
||||
if let syn::PathArguments::AngleBracketed(gen_args) = &last_segment.arguments {
|
||||
let generic_arg = gen_args.args.first()?;
|
||||
if let syn::GenericArgument::Type(ty) = &generic_arg {
|
||||
return Some(ty);
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Implements `FromArgs` and `SubCommands` for a `#![derive(FromArgs)]` enum.
|
||||
fn impl_from_args_enum(
|
||||
errors: &Errors,
|
||||
name: &syn::Ident,
|
||||
type_attrs: &TypeAttrs,
|
||||
generic_args: &syn::Generics,
|
||||
de: &syn::DataEnum,
|
||||
) -> TokenStream {
|
||||
parse_attrs::check_enum_type_attrs(errors, type_attrs, &de.enum_token.span);
|
||||
|
||||
// An enum variant like `<name>(<ty>)`
|
||||
struct SubCommandVariant<'a> {
|
||||
name: &'a syn::Ident,
|
||||
ty: &'a syn::Type,
|
||||
}
|
||||
|
||||
let mut dynamic_type_and_variant = None;
|
||||
|
||||
let variants: Vec<SubCommandVariant<'_>> = de
|
||||
.variants
|
||||
.iter()
|
||||
.filter_map(|variant| {
|
||||
let name = &variant.ident;
|
||||
let ty = enum_only_single_field_unnamed_variants(errors, &variant.fields)?;
|
||||
if parse_attrs::VariantAttrs::parse(errors, variant)
|
||||
.is_dynamic
|
||||
.is_some()
|
||||
{
|
||||
if dynamic_type_and_variant.is_some() {
|
||||
errors.err(variant, "Only one variant can have the `dynamic` attribute");
|
||||
}
|
||||
dynamic_type_and_variant = Some((ty, name));
|
||||
None
|
||||
} else {
|
||||
Some(SubCommandVariant { name, ty })
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let name_repeating = std::iter::repeat(name.clone());
|
||||
let variant_ty = variants.iter().map(|x| x.ty).collect::<Vec<_>>();
|
||||
let variant_names = variants.iter().map(|x| x.name).collect::<Vec<_>>();
|
||||
let dynamic_from_args =
|
||||
dynamic_type_and_variant
|
||||
.as_ref()
|
||||
.map(|(dynamic_type, dynamic_variant)| {
|
||||
quote! {
|
||||
if let Some(result) = <#dynamic_type as argh::DynamicSubCommand>::try_from_args(
|
||||
command_name, args) {
|
||||
return result.map(#name::#dynamic_variant);
|
||||
}
|
||||
}
|
||||
});
|
||||
let dynamic_commands = dynamic_type_and_variant.as_ref().map(|(dynamic_type, _)| {
|
||||
quote! {
|
||||
fn dynamic_commands() -> &'static [&'static argh::CommandInfo] {
|
||||
<#dynamic_type as argh::DynamicSubCommand>::commands()
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let (impl_generics, ty_generics, where_clause) = generic_args.split_for_impl();
|
||||
quote! {
|
||||
impl #impl_generics argh::FromArgs for #name #ty_generics #where_clause {
|
||||
fn from_args(command_name: &[&str], args: &[&str])
|
||||
-> std::result::Result<Self, argh::EarlyExit>
|
||||
{
|
||||
let subcommand_name = if let Some(subcommand_name) = command_name.last() {
|
||||
*subcommand_name
|
||||
} else {
|
||||
return Err(argh::EarlyExit::from("no subcommand name".to_owned()));
|
||||
};
|
||||
|
||||
#(
|
||||
if subcommand_name == <#variant_ty as argh::SubCommand>::COMMAND.name {
|
||||
return Ok(#name_repeating::#variant_names(
|
||||
<#variant_ty as argh::FromArgs>::from_args(command_name, args)?
|
||||
));
|
||||
}
|
||||
)*
|
||||
|
||||
#dynamic_from_args
|
||||
|
||||
Err(argh::EarlyExit::from("no subcommand matched".to_owned()))
|
||||
}
|
||||
}
|
||||
|
||||
impl #impl_generics argh::SubCommands for #name #ty_generics #where_clause {
|
||||
const COMMANDS: &'static [&'static argh::CommandInfo] = &[#(
|
||||
<#variant_ty as argh::SubCommand>::COMMAND,
|
||||
)*];
|
||||
|
||||
#dynamic_commands
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `Some(Bar)` if the field is a single-field unnamed variant like `Foo(Bar)`.
|
||||
/// Otherwise, generates an error.
|
||||
fn enum_only_single_field_unnamed_variants<'a>(
|
||||
errors: &Errors,
|
||||
variant_fields: &'a syn::Fields,
|
||||
) -> Option<&'a syn::Type> {
|
||||
macro_rules! with_enum_suggestion {
|
||||
($help_text:literal) => {
|
||||
concat!(
|
||||
$help_text,
|
||||
"\nInstead, use a variant with a single unnamed field for each subcommand:\n",
|
||||
" enum MyCommandEnum {\n",
|
||||
" SubCommandOne(SubCommandOne),\n",
|
||||
" SubCommandTwo(SubCommandTwo),\n",
|
||||
" }",
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
match variant_fields {
|
||||
syn::Fields::Named(fields) => {
|
||||
errors.err(
|
||||
fields,
|
||||
with_enum_suggestion!(
|
||||
"`#![derive(FromArgs)]` `enum`s do not support variants with named fields."
|
||||
),
|
||||
);
|
||||
None
|
||||
}
|
||||
syn::Fields::Unit => {
|
||||
errors.err(
|
||||
variant_fields,
|
||||
with_enum_suggestion!(
|
||||
"`#![derive(FromArgs)]` does not support `enum`s with no variants."
|
||||
),
|
||||
);
|
||||
None
|
||||
}
|
||||
syn::Fields::Unnamed(fields) => {
|
||||
if fields.unnamed.len() != 1 {
|
||||
errors.err(
|
||||
fields,
|
||||
with_enum_suggestion!(
|
||||
"`#![derive(FromArgs)]` `enum` variants must only contain one field."
|
||||
),
|
||||
);
|
||||
None
|
||||
} else {
|
||||
// `unwrap` is okay because of the length check above.
|
||||
let first_field = fields.unnamed.first().unwrap();
|
||||
Some(&first_field.ty)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
688
native/src/base/derive/argh/parse_attrs.rs
Normal file
688
native/src/base/derive/argh/parse_attrs.rs
Normal file
@@ -0,0 +1,688 @@
|
||||
// Copyright (c) 2020 Google LLC All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
use syn::parse::Parser;
|
||||
use syn::punctuated::Punctuated;
|
||||
|
||||
use super::errors::Errors;
|
||||
use proc_macro2::Span;
|
||||
use std::collections::hash_map::{Entry, HashMap};
|
||||
|
||||
/// Attributes applied to a field of a `#![derive(FromArgs)]` struct.
|
||||
#[derive(Default)]
|
||||
pub struct FieldAttrs {
|
||||
pub default: Option<syn::LitStr>,
|
||||
pub description: Option<Description>,
|
||||
pub from_str_fn: Option<syn::ExprPath>,
|
||||
pub field_type: Option<FieldType>,
|
||||
pub long: Option<Option<syn::LitStr>>,
|
||||
pub short: Option<syn::LitChar>,
|
||||
pub arg_name: Option<syn::LitStr>,
|
||||
pub greedy: Option<syn::Path>,
|
||||
pub hidden_help: bool,
|
||||
}
|
||||
|
||||
/// The purpose of a particular field on a `#![derive(FromArgs)]` struct.
|
||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||
pub enum FieldKind {
|
||||
/// Switches are booleans that are set to "true" by passing the flag.
|
||||
Switch,
|
||||
/// Options are `--key value`. They may be optional (using `Option`),
|
||||
/// or repeating (using `Vec`), or required (neither `Option` nor `Vec`)
|
||||
Option,
|
||||
/// Subcommand fields (of which there can be at most one) refer to enums
|
||||
/// containing one of several potential subcommands. They may be optional
|
||||
/// (using `Option`) or required (no `Option`).
|
||||
SubCommand,
|
||||
/// Positional arguments are parsed literally if the input
|
||||
/// does not begin with `-` or `--` and is not a subcommand.
|
||||
/// They are parsed in declaration order, and only the last positional
|
||||
/// argument in a type may be an `Option`, `Vec`, or have a default value.
|
||||
Positional,
|
||||
}
|
||||
|
||||
/// The type of a field on a `#![derive(FromArgs)]` struct.
|
||||
///
|
||||
/// This is a simple wrapper around `FieldKind` which includes the `syn::Ident`
|
||||
/// of the attribute containing the field kind.
|
||||
pub struct FieldType {
|
||||
pub kind: FieldKind,
|
||||
pub ident: syn::Ident,
|
||||
}
|
||||
|
||||
/// A description of a `#![derive(FromArgs)]` struct.
|
||||
///
|
||||
/// Defaults to the docstring if one is present, or `#[argh(description = "...")]`
|
||||
/// if one is provided.
|
||||
pub struct Description {
|
||||
/// Whether the description was an explicit annotation or whether it was a doc string.
|
||||
pub explicit: bool,
|
||||
pub content: syn::LitStr,
|
||||
}
|
||||
|
||||
impl FieldAttrs {
|
||||
pub fn parse(errors: &Errors, field: &syn::Field) -> Self {
|
||||
let mut this = Self::default();
|
||||
|
||||
for attr in &field.attrs {
|
||||
if is_doc_attr(attr) {
|
||||
parse_attr_doc(errors, attr, &mut this.description);
|
||||
continue;
|
||||
}
|
||||
|
||||
let ml = if let Some(ml) = argh_attr_to_meta_list(errors, attr) {
|
||||
ml
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
|
||||
for meta in ml {
|
||||
let name = meta.path();
|
||||
if name.is_ident("arg_name") {
|
||||
if let Some(m) = errors.expect_meta_name_value(&meta) {
|
||||
this.parse_attr_arg_name(errors, m);
|
||||
}
|
||||
} else if name.is_ident("default") {
|
||||
if let Some(m) = errors.expect_meta_name_value(&meta) {
|
||||
this.parse_attr_default(errors, m);
|
||||
}
|
||||
} else if name.is_ident("description") {
|
||||
if let Some(m) = errors.expect_meta_name_value(&meta) {
|
||||
parse_attr_description(errors, m, &mut this.description);
|
||||
}
|
||||
} else if name.is_ident("from_str_fn") {
|
||||
if let Some(m) = errors.expect_meta_list(&meta) {
|
||||
this.parse_attr_from_str_fn(errors, m);
|
||||
}
|
||||
} else if name.is_ident("long") {
|
||||
if let Some(m) = errors.expect_meta_name_value(&meta) {
|
||||
this.parse_attr_long(errors, m);
|
||||
}
|
||||
} else if name.is_ident("option") {
|
||||
parse_attr_field_type(errors, &meta, FieldKind::Option, &mut this.field_type);
|
||||
} else if name.is_ident("short") {
|
||||
if let Some(m) = errors.expect_meta_name_value(&meta) {
|
||||
this.parse_attr_short(errors, m);
|
||||
}
|
||||
} else if name.is_ident("subcommand") {
|
||||
parse_attr_field_type(
|
||||
errors,
|
||||
&meta,
|
||||
FieldKind::SubCommand,
|
||||
&mut this.field_type,
|
||||
);
|
||||
} else if name.is_ident("switch") {
|
||||
parse_attr_field_type(errors, &meta, FieldKind::Switch, &mut this.field_type);
|
||||
} else if name.is_ident("positional") {
|
||||
parse_attr_field_type(
|
||||
errors,
|
||||
&meta,
|
||||
FieldKind::Positional,
|
||||
&mut this.field_type,
|
||||
);
|
||||
} else if name.is_ident("greedy") {
|
||||
this.greedy = Some(name.clone());
|
||||
} else if name.is_ident("hidden_help") {
|
||||
this.hidden_help = true;
|
||||
} else {
|
||||
errors.err(
|
||||
&meta,
|
||||
concat!(
|
||||
"Invalid field-level `argh` attribute\n",
|
||||
"Expected one of: `arg_name`, `default`, `description`, `from_str_fn`, `greedy`, ",
|
||||
"`long`, `option`, `short`, `subcommand`, `switch`, `hidden_help`",
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let (Some(default), Some(field_type)) = (&this.default, &this.field_type) {
|
||||
match field_type.kind {
|
||||
FieldKind::Option | FieldKind::Positional => {}
|
||||
FieldKind::SubCommand | FieldKind::Switch => errors.err(
|
||||
default,
|
||||
"`default` may only be specified on `#[argh(option)]` \
|
||||
or `#[argh(positional)]` fields",
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
match (&this.greedy, this.field_type.as_ref().map(|f| f.kind)) {
|
||||
(Some(_), Some(FieldKind::Positional)) => {}
|
||||
(Some(greedy), Some(_)) => errors.err(
|
||||
&greedy,
|
||||
"`greedy` may only be specified on `#[argh(positional)]` \
|
||||
fields",
|
||||
),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if let Some(d) = &this.description {
|
||||
check_option_description(errors, d.content.value().trim(), d.content.span());
|
||||
}
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
fn parse_attr_from_str_fn(&mut self, errors: &Errors, m: &syn::MetaList) {
|
||||
parse_attr_fn_name(errors, m, "from_str_fn", &mut self.from_str_fn)
|
||||
}
|
||||
|
||||
fn parse_attr_default(&mut self, errors: &Errors, m: &syn::MetaNameValue) {
|
||||
parse_attr_single_string(errors, m, "default", &mut self.default);
|
||||
}
|
||||
|
||||
fn parse_attr_arg_name(&mut self, errors: &Errors, m: &syn::MetaNameValue) {
|
||||
parse_attr_single_string(errors, m, "arg_name", &mut self.arg_name);
|
||||
}
|
||||
|
||||
fn parse_attr_long(&mut self, errors: &Errors, m: &syn::MetaNameValue) {
|
||||
if let Some(first) = &self.long {
|
||||
errors.duplicate_attrs("long", first, m);
|
||||
} else if let syn::Expr::Path(syn::ExprPath { path, .. }) = &m.value
|
||||
&& let Some(ident) = path.get_ident()
|
||||
&& ident.to_string().eq_ignore_ascii_case("none")
|
||||
{
|
||||
self.long = Some(None);
|
||||
} else if let Some(lit_str) = errors.expect_lit_str(&m.value) {
|
||||
self.long = Some(Some(lit_str.clone()));
|
||||
}
|
||||
if let Some(Some(long)) = &self.long {
|
||||
let value = long.value();
|
||||
check_long_name(errors, long, &value);
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_attr_short(&mut self, errors: &Errors, m: &syn::MetaNameValue) {
|
||||
if let Some(first) = &self.short {
|
||||
errors.duplicate_attrs("short", first, m);
|
||||
} else if let Some(lit_char) = errors.expect_lit_char(&m.value) {
|
||||
self.short = Some(lit_char.clone());
|
||||
if !lit_char.value().is_ascii() {
|
||||
errors.err(lit_char, "Short names must be ASCII");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn check_long_name(errors: &Errors, spanned: &impl syn::spanned::Spanned, value: &str) {
|
||||
if !value.is_ascii() {
|
||||
errors.err(spanned, "Long names must be ASCII");
|
||||
}
|
||||
if !value
|
||||
.chars()
|
||||
.all(|c| c.is_lowercase() || c == '-' || c.is_ascii_digit())
|
||||
{
|
||||
errors.err(
|
||||
spanned,
|
||||
"Long names may only contain lowercase letters, digits, and dashes",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_attr_fn_name(
|
||||
errors: &Errors,
|
||||
m: &syn::MetaList,
|
||||
attr_name: &str,
|
||||
slot: &mut Option<syn::ExprPath>,
|
||||
) {
|
||||
if let Some(first) = slot {
|
||||
errors.duplicate_attrs(attr_name, first, m);
|
||||
}
|
||||
|
||||
*slot = errors.ok(m.parse_args());
|
||||
}
|
||||
|
||||
fn parse_attr_field_type(
|
||||
errors: &Errors,
|
||||
meta: &syn::Meta,
|
||||
kind: FieldKind,
|
||||
slot: &mut Option<FieldType>,
|
||||
) {
|
||||
if let Some(path) = errors.expect_meta_word(meta) {
|
||||
if let Some(first) = slot {
|
||||
errors.duplicate_attrs("field kind", &first.ident, path);
|
||||
} else if let Some(word) = path.get_ident() {
|
||||
*slot = Some(FieldType {
|
||||
kind,
|
||||
ident: word.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Whether the attribute is one like `#[<name> ...]`
|
||||
fn is_matching_attr(name: &str, attr: &syn::Attribute) -> bool {
|
||||
attr.path().segments.len() == 1 && attr.path().segments[0].ident == name
|
||||
}
|
||||
|
||||
/// Checks for `#[doc ...]`, which is generated by doc comments.
|
||||
fn is_doc_attr(attr: &syn::Attribute) -> bool {
|
||||
is_matching_attr("doc", attr)
|
||||
}
|
||||
|
||||
/// Checks for `#[argh ...]`
|
||||
fn is_argh_attr(attr: &syn::Attribute) -> bool {
|
||||
is_matching_attr("argh", attr)
|
||||
}
|
||||
|
||||
/// Filters out non-`#[argh(...)]` attributes and converts to a sequence of `syn::Meta`.
|
||||
fn argh_attr_to_meta_list(
|
||||
errors: &Errors,
|
||||
attr: &syn::Attribute,
|
||||
) -> Option<impl IntoIterator<Item = syn::Meta>> {
|
||||
if !is_argh_attr(attr) {
|
||||
return None;
|
||||
}
|
||||
let ml = errors.expect_meta_list(&attr.meta)?;
|
||||
errors.ok(ml.parse_args_with(
|
||||
syn::punctuated::Punctuated::<syn::Meta, syn::Token![,]>::parse_terminated,
|
||||
))
|
||||
}
|
||||
|
||||
/// Represents a `#[derive(FromArgs)]` type's top-level attributes.
|
||||
#[derive(Default)]
|
||||
pub struct TypeAttrs {
|
||||
pub is_subcommand: Option<syn::Ident>,
|
||||
pub name: Option<syn::LitStr>,
|
||||
pub description: Option<Description>,
|
||||
pub examples: Vec<syn::LitStr>,
|
||||
pub notes: Vec<syn::LitStr>,
|
||||
pub error_codes: Vec<(syn::LitInt, syn::LitStr)>,
|
||||
/// Arguments that trigger printing of the help message
|
||||
pub help_triggers: Option<Vec<syn::LitStr>>,
|
||||
}
|
||||
|
||||
impl TypeAttrs {
|
||||
/// Parse top-level `#[argh(...)]` attributes
|
||||
pub fn parse(errors: &Errors, derive_input: &syn::DeriveInput) -> Self {
|
||||
let mut this = TypeAttrs::default();
|
||||
|
||||
for attr in &derive_input.attrs {
|
||||
if is_doc_attr(attr) {
|
||||
parse_attr_doc(errors, attr, &mut this.description);
|
||||
continue;
|
||||
}
|
||||
|
||||
let ml: Vec<syn::Meta> = if let Some(ml) = argh_attr_to_meta_list(errors, attr) {
|
||||
ml.into_iter().collect()
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
|
||||
for meta in ml.iter() {
|
||||
let name = meta.path();
|
||||
if name.is_ident("description") {
|
||||
if let Some(m) = errors.expect_meta_name_value(meta) {
|
||||
parse_attr_description(errors, m, &mut this.description);
|
||||
}
|
||||
} else if name.is_ident("error_code") {
|
||||
if let Some(m) = errors.expect_meta_list(meta) {
|
||||
this.parse_attr_error_code(errors, m);
|
||||
}
|
||||
} else if name.is_ident("example") {
|
||||
if let Some(m) = errors.expect_meta_name_value(meta) {
|
||||
this.parse_attr_example(errors, m);
|
||||
}
|
||||
} else if name.is_ident("name") {
|
||||
if let Some(m) = errors.expect_meta_name_value(meta) {
|
||||
this.parse_attr_name(errors, m);
|
||||
}
|
||||
} else if name.is_ident("note") {
|
||||
if let Some(m) = errors.expect_meta_name_value(meta) {
|
||||
this.parse_attr_note(errors, m);
|
||||
}
|
||||
} else if name.is_ident("subcommand") {
|
||||
if let Some(ident) = errors.expect_meta_word(meta).and_then(|p| p.get_ident()) {
|
||||
this.parse_attr_subcommand(errors, ident);
|
||||
}
|
||||
} else if name.is_ident("help_triggers") {
|
||||
if let Some(m) = errors.expect_meta_list(meta) {
|
||||
Self::parse_help_triggers(m, errors, &mut this);
|
||||
}
|
||||
} else {
|
||||
errors.err(
|
||||
meta,
|
||||
concat!(
|
||||
"Invalid type-level `argh` attribute\n",
|
||||
"Expected one of: `description`, `error_code`, `example`, `name`, ",
|
||||
"`note`, `subcommand`, `help_triggers`",
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if this.is_subcommand.is_some() && this.help_triggers.is_some() {
|
||||
let help_meta = ml
|
||||
.iter()
|
||||
.find(|meta| meta.path().is_ident("help_triggers"))
|
||||
.unwrap();
|
||||
errors.err(help_meta, "Cannot use `help_triggers` on a subcommand");
|
||||
}
|
||||
}
|
||||
|
||||
this.check_error_codes(errors);
|
||||
this
|
||||
}
|
||||
|
||||
/// Checks that error codes are within range for `i32` and that they are
|
||||
/// never duplicated.
|
||||
fn check_error_codes(&self, errors: &Errors) {
|
||||
// map from error code to index
|
||||
let mut map: HashMap<u64, usize> = HashMap::new();
|
||||
for (index, (lit_int, _lit_str)) in self.error_codes.iter().enumerate() {
|
||||
let value = match lit_int.base10_parse::<u64>() {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
errors.push(e);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
if value > (i32::MAX as u64) {
|
||||
errors.err(lit_int, "Error code out of range for `i32`");
|
||||
}
|
||||
match map.entry(value) {
|
||||
Entry::Occupied(previous) => {
|
||||
let previous_index = *previous.get();
|
||||
let (previous_lit_int, _previous_lit_str) = &self.error_codes[previous_index];
|
||||
errors.err(lit_int, &format!("Duplicate error code {}", value));
|
||||
errors.err(
|
||||
previous_lit_int,
|
||||
&format!("Error code {} previously defined here", value),
|
||||
);
|
||||
}
|
||||
Entry::Vacant(slot) => {
|
||||
slot.insert(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_attr_error_code(&mut self, errors: &Errors, ml: &syn::MetaList) {
|
||||
errors.ok(ml.parse_args_with(|input: syn::parse::ParseStream| {
|
||||
let err_code = input.parse()?;
|
||||
input.parse::<syn::Token![,]>()?;
|
||||
let err_msg = input.parse()?;
|
||||
if let (Some(err_code), Some(err_msg)) = (
|
||||
errors.expect_lit_int(&err_code),
|
||||
errors.expect_lit_str(&err_msg),
|
||||
) {
|
||||
self.error_codes.push((err_code.clone(), err_msg.clone()));
|
||||
}
|
||||
Ok(())
|
||||
}));
|
||||
}
|
||||
|
||||
fn parse_attr_example(&mut self, errors: &Errors, m: &syn::MetaNameValue) {
|
||||
parse_attr_multi_string(errors, m, &mut self.examples)
|
||||
}
|
||||
|
||||
fn parse_attr_name(&mut self, errors: &Errors, m: &syn::MetaNameValue) {
|
||||
parse_attr_single_string(errors, m, "name", &mut self.name);
|
||||
if let Some(name) = &self.name
|
||||
&& name.value() == "help"
|
||||
{
|
||||
errors.err(name, "Custom `help` commands are not supported.");
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_attr_note(&mut self, errors: &Errors, m: &syn::MetaNameValue) {
|
||||
parse_attr_multi_string(errors, m, &mut self.notes)
|
||||
}
|
||||
|
||||
fn parse_attr_subcommand(&mut self, errors: &Errors, ident: &syn::Ident) {
|
||||
if let Some(first) = &self.is_subcommand {
|
||||
errors.duplicate_attrs("subcommand", first, ident);
|
||||
} else {
|
||||
self.is_subcommand = Some(ident.clone());
|
||||
}
|
||||
}
|
||||
|
||||
// get the list of arguments that trigger printing of the help message as a vector of strings (help_arguments("-h", "--help", "help"))
|
||||
fn parse_help_triggers(m: &syn::MetaList, errors: &Errors, this: &mut TypeAttrs) {
|
||||
let parser = Punctuated::<syn::Expr, syn::Token![,]>::parse_terminated;
|
||||
match parser.parse(m.tokens.clone().into()) {
|
||||
Ok(args) => {
|
||||
let mut triggers = Vec::new();
|
||||
for arg in args {
|
||||
if let syn::Expr::Lit(syn::ExprLit {
|
||||
lit: syn::Lit::Str(lit_str),
|
||||
..
|
||||
}) = arg
|
||||
{
|
||||
triggers.push(lit_str);
|
||||
}
|
||||
}
|
||||
|
||||
this.help_triggers = Some(triggers);
|
||||
}
|
||||
Err(err) => errors.push(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents an enum variant's attributes.
|
||||
#[derive(Default)]
|
||||
pub struct VariantAttrs {
|
||||
pub is_dynamic: Option<syn::Path>,
|
||||
}
|
||||
|
||||
impl VariantAttrs {
|
||||
/// Parse enum variant `#[argh(...)]` attributes
|
||||
pub fn parse(errors: &Errors, variant: &syn::Variant) -> Self {
|
||||
let mut this = VariantAttrs::default();
|
||||
|
||||
let fields = match &variant.fields {
|
||||
syn::Fields::Named(fields) => Some(&fields.named),
|
||||
syn::Fields::Unnamed(fields) => Some(&fields.unnamed),
|
||||
syn::Fields::Unit => None,
|
||||
};
|
||||
|
||||
for field in fields.into_iter().flatten() {
|
||||
for attr in &field.attrs {
|
||||
if is_argh_attr(attr) {
|
||||
err_unused_enum_attr(errors, attr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for attr in &variant.attrs {
|
||||
let ml = if let Some(ml) = argh_attr_to_meta_list(errors, attr) {
|
||||
ml
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
|
||||
for meta in ml {
|
||||
let name = meta.path();
|
||||
if name.is_ident("dynamic") {
|
||||
if let Some(prev) = this.is_dynamic.as_ref() {
|
||||
errors.duplicate_attrs("dynamic", prev, &meta);
|
||||
} else {
|
||||
this.is_dynamic = errors.expect_meta_word(&meta).cloned();
|
||||
}
|
||||
} else {
|
||||
errors.err(
|
||||
&meta,
|
||||
"Invalid variant-level `argh` attribute\n\
|
||||
Variants can only have the #[argh(dynamic)] attribute.",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
fn check_option_description(errors: &Errors, desc: &str, span: Span) {
|
||||
let chars = &mut desc.trim().chars();
|
||||
match (chars.next(), chars.next()) {
|
||||
(Some(x), _) if x.is_lowercase() => {}
|
||||
// If both the first and second letter are not lowercase,
|
||||
// this is likely an initialism which should be allowed.
|
||||
(Some(x), Some(y)) if !x.is_lowercase() && (y.is_alphanumeric() && !y.is_lowercase()) => {}
|
||||
_ => {
|
||||
errors.err_span(span, "Descriptions must begin with a lowercase letter");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_attr_single_string(
|
||||
errors: &Errors,
|
||||
m: &syn::MetaNameValue,
|
||||
name: &str,
|
||||
slot: &mut Option<syn::LitStr>,
|
||||
) {
|
||||
if let Some(first) = slot {
|
||||
errors.duplicate_attrs(name, first, m);
|
||||
} else if let Some(lit_str) = errors.expect_lit_str(&m.value) {
|
||||
*slot = Some(lit_str.clone());
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_attr_multi_string(errors: &Errors, m: &syn::MetaNameValue, list: &mut Vec<syn::LitStr>) {
|
||||
if let Some(lit_str) = errors.expect_lit_str(&m.value) {
|
||||
list.push(lit_str.clone());
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_attr_doc(errors: &Errors, attr: &syn::Attribute, slot: &mut Option<Description>) {
|
||||
let nv = if let Some(nv) = errors.expect_meta_name_value(&attr.meta) {
|
||||
nv
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
// Don't replace an existing explicit description.
|
||||
if slot.as_ref().map(|d| d.explicit).unwrap_or(false) {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(lit_str) = errors.expect_lit_str(&nv.value) {
|
||||
let lit_str = if let Some(previous) = slot {
|
||||
let previous = &previous.content;
|
||||
let previous_span = previous.span();
|
||||
syn::LitStr::new(
|
||||
&(previous.value() + &unescape_doc(lit_str.value())),
|
||||
previous_span,
|
||||
)
|
||||
} else {
|
||||
syn::LitStr::new(&unescape_doc(lit_str.value()), lit_str.span())
|
||||
};
|
||||
*slot = Some(Description {
|
||||
explicit: false,
|
||||
content: lit_str,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Replaces escape sequences in doc-comments with the characters they represent.
|
||||
///
|
||||
/// Rustdoc understands CommonMark escape sequences consisting of a backslash followed by an ASCII
|
||||
/// punctuation character. Any other backslash is treated as a literal backslash.
|
||||
fn unescape_doc(s: String) -> String {
|
||||
let mut result = String::with_capacity(s.len());
|
||||
|
||||
let mut characters = s.chars().peekable();
|
||||
while let Some(mut character) = characters.next() {
|
||||
if character == '\\'
|
||||
&& let Some(next_character) = characters.peek()
|
||||
&& next_character.is_ascii_punctuation()
|
||||
{
|
||||
character = *next_character;
|
||||
characters.next();
|
||||
}
|
||||
|
||||
// Braces must be escaped as this string will be used as a format string
|
||||
if character == '{' || character == '}' {
|
||||
result.push(character);
|
||||
}
|
||||
|
||||
result.push(character);
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn parse_attr_description(errors: &Errors, m: &syn::MetaNameValue, slot: &mut Option<Description>) {
|
||||
let lit_str = if let Some(lit_str) = errors.expect_lit_str(&m.value) {
|
||||
lit_str
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
// Don't allow multiple explicit (non doc-comment) descriptions
|
||||
if let Some(description) = slot
|
||||
&& description.explicit
|
||||
{
|
||||
errors.duplicate_attrs("description", &description.content, lit_str);
|
||||
}
|
||||
|
||||
*slot = Some(Description {
|
||||
explicit: true,
|
||||
content: lit_str.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
/// Checks that a `#![derive(FromArgs)]` enum has an `#[argh(subcommand)]`
|
||||
/// attribute and that it does not have any other type-level `#[argh(...)]` attributes.
|
||||
pub fn check_enum_type_attrs(errors: &Errors, type_attrs: &TypeAttrs, type_span: &Span) {
|
||||
let TypeAttrs {
|
||||
is_subcommand,
|
||||
name,
|
||||
description,
|
||||
examples,
|
||||
notes,
|
||||
error_codes,
|
||||
help_triggers,
|
||||
} = type_attrs;
|
||||
|
||||
// Ensure that `#[argh(subcommand)]` is present.
|
||||
if is_subcommand.is_none() {
|
||||
errors.err_span(
|
||||
*type_span,
|
||||
concat!(
|
||||
"`#![derive(FromArgs)]` on `enum`s can only be used to enumerate subcommands.\n",
|
||||
"Consider adding `#[argh(subcommand)]` to the `enum` declaration.",
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Error on all other type-level attributes.
|
||||
if let Some(name) = name {
|
||||
err_unused_enum_attr(errors, name);
|
||||
}
|
||||
if let Some(description) = description
|
||||
&& description.explicit
|
||||
{
|
||||
err_unused_enum_attr(errors, &description.content);
|
||||
}
|
||||
if let Some(example) = examples.first() {
|
||||
err_unused_enum_attr(errors, example);
|
||||
}
|
||||
if let Some(note) = notes.first() {
|
||||
err_unused_enum_attr(errors, note);
|
||||
}
|
||||
if let Some(err_code) = error_codes.first() {
|
||||
err_unused_enum_attr(errors, &err_code.0);
|
||||
}
|
||||
if let Some(triggers) = help_triggers
|
||||
&& let Some(trigger) = triggers.first()
|
||||
{
|
||||
err_unused_enum_attr(errors, trigger);
|
||||
}
|
||||
}
|
||||
|
||||
fn err_unused_enum_attr(errors: &Errors, location: &impl syn::spanned::Spanned) {
|
||||
errors.err(
|
||||
location,
|
||||
concat!(
|
||||
"Unused `argh` attribute on `#![derive(FromArgs)]` enum. ",
|
||||
"Such `enum`s can only be used to dispatch to subcommands, ",
|
||||
"and should only contain the #[argh(subcommand)] attribute.",
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -20,16 +20,12 @@ pub(crate) fn derive_decodable(input: proc_macro::TokenStream) -> proc_macro::To
|
||||
|
||||
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
|
||||
|
||||
let sum = gen_size_sum(&input.data);
|
||||
let encode = gen_encode(&input.data);
|
||||
let decode = gen_decode(&input.data);
|
||||
|
||||
let expanded = quote! {
|
||||
// The generated impl.
|
||||
impl #impl_generics crate::socket::Encodable for #name #ty_generics #where_clause {
|
||||
fn encoded_len(&self) -> usize {
|
||||
#sum
|
||||
}
|
||||
fn encode(&self, w: &mut impl std::io::Write) -> std::io::Result<()> {
|
||||
#encode
|
||||
Ok(())
|
||||
@@ -45,32 +41,6 @@ pub(crate) fn derive_decodable(input: proc_macro::TokenStream) -> proc_macro::To
|
||||
proc_macro::TokenStream::from(expanded)
|
||||
}
|
||||
|
||||
// Generate an expression to sum up the size of each field.
|
||||
fn gen_size_sum(data: &Data) -> TokenStream {
|
||||
match *data {
|
||||
Data::Struct(ref data) => {
|
||||
match data.fields {
|
||||
Fields::Named(ref fields) => {
|
||||
// Expands to an expression like
|
||||
//
|
||||
// 0 + self.x.encoded_len() + self.y.encoded_len() + self.z.encoded_len()
|
||||
let recurse = fields.named.iter().map(|f| {
|
||||
let name = &f.ident;
|
||||
quote_spanned! { f.span() =>
|
||||
crate::socket::Encodable::encoded_len(&self.#name)
|
||||
}
|
||||
});
|
||||
quote! {
|
||||
0 #(+ #recurse)*
|
||||
}
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
Data::Enum(_) | Data::Union(_) => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
// Generate an expression to encode each field.
|
||||
fn gen_encode(data: &Data) -> TokenStream {
|
||||
match *data {
|
||||
19
native/src/base/derive/lib.rs
Normal file
19
native/src/base/derive/lib.rs
Normal file
@@ -0,0 +1,19 @@
|
||||
#![recursion_limit = "256"]
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
|
||||
mod argh;
|
||||
mod decodable;
|
||||
|
||||
#[proc_macro_derive(Decodable)]
|
||||
pub fn derive_decodable(input: TokenStream) -> TokenStream {
|
||||
decodable::derive_decodable(input)
|
||||
}
|
||||
|
||||
/// Entrypoint for `#[derive(FromArgs)]`.
|
||||
#[proc_macro_derive(FromArgs, attributes(argh))]
|
||||
pub fn argh_derive(input: TokenStream) -> TokenStream {
|
||||
let ast = syn::parse_macro_input!(input as syn::DeriveInput);
|
||||
let token = argh::impl_from_args(&ast);
|
||||
token.into()
|
||||
}
|
||||
@@ -1,18 +1,21 @@
|
||||
use crate::cxx_extern::readlinkat;
|
||||
use crate::{
|
||||
FsPathBuilder, LibcReturn, OsError, OsResult, OsResultStatic, Utf8CStr, Utf8CStrBuf, cstr,
|
||||
errno, fd_path, fd_set_attr,
|
||||
FsPathBuilder, LibcReturn, LoggedResult, OsError, OsResult, Utf8CStr, Utf8CStrBuf, cstr, errno,
|
||||
fd_path, fd_set_attr,
|
||||
};
|
||||
use libc::{EEXIST, O_CLOEXEC, O_CREAT, O_RDONLY, O_TRUNC, O_WRONLY, dirent, mode_t};
|
||||
use libc::{dirent, mode_t};
|
||||
use nix::errno::Errno;
|
||||
use nix::fcntl::{AtFlags, OFlag};
|
||||
use nix::sys::stat::Mode;
|
||||
use nix::unistd::UnlinkatFlags;
|
||||
use std::fs::File;
|
||||
use std::marker::PhantomData;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd};
|
||||
use std::ops::Deref;
|
||||
use std::os::fd::{AsFd, AsRawFd, BorrowedFd, IntoRawFd, OwnedFd, RawFd};
|
||||
use std::ptr::NonNull;
|
||||
use std::{mem, slice};
|
||||
use std::slice;
|
||||
|
||||
pub struct DirEntry<'a> {
|
||||
dir: BorrowedDirectory<'a>,
|
||||
dir: &'a Directory,
|
||||
entry: NonNull<dirent>,
|
||||
d_name_len: usize,
|
||||
}
|
||||
@@ -23,6 +26,7 @@ impl DirEntry<'_> {
|
||||
}
|
||||
|
||||
pub fn name(&self) -> &Utf8CStr {
|
||||
// SAFETY: Utf8CStr is already validated in Directory::read
|
||||
unsafe {
|
||||
Utf8CStr::from_bytes_unchecked(slice::from_raw_parts(
|
||||
self.d_name.as_ptr().cast(),
|
||||
@@ -63,19 +67,23 @@ impl DirEntry<'_> {
|
||||
self.d_type == libc::DT_SOCK
|
||||
}
|
||||
|
||||
pub fn unlink(&self) -> OsResult<()> {
|
||||
let flag = if self.is_dir() { libc::AT_REMOVEDIR } else { 0 };
|
||||
pub fn unlink(&self) -> OsResult<'_, ()> {
|
||||
let flag = if self.is_dir() {
|
||||
UnlinkatFlags::RemoveDir
|
||||
} else {
|
||||
UnlinkatFlags::NoRemoveDir
|
||||
};
|
||||
self.dir.unlink_at(self.name(), flag)
|
||||
}
|
||||
|
||||
pub fn read_link(&self, buf: &mut dyn Utf8CStrBuf) -> OsResult<()> {
|
||||
pub fn read_link(&self, buf: &mut dyn Utf8CStrBuf) -> OsResult<'_, ()> {
|
||||
self.dir.read_link_at(self.name(), buf)
|
||||
}
|
||||
|
||||
pub fn open_as_dir(&self) -> OsResult<Directory> {
|
||||
pub fn open_as_dir(&self) -> OsResult<'_, Directory> {
|
||||
if !self.is_dir() {
|
||||
return Err(OsError::with_os_error(
|
||||
libc::ENOTDIR,
|
||||
return Err(OsError::new(
|
||||
Errno::ENOTDIR,
|
||||
"fdopendir",
|
||||
Some(self.name()),
|
||||
None,
|
||||
@@ -84,10 +92,10 @@ impl DirEntry<'_> {
|
||||
self.dir.open_as_dir_at(self.name())
|
||||
}
|
||||
|
||||
pub fn open_as_file(&self, flags: i32) -> OsResult<File> {
|
||||
pub fn open_as_file(&self, flags: OFlag) -> OsResult<'_, File> {
|
||||
if self.is_dir() {
|
||||
return Err(OsError::with_os_error(
|
||||
libc::EISDIR,
|
||||
return Err(OsError::new(
|
||||
Errno::EISDIR,
|
||||
"open_as_file",
|
||||
Some(self.name()),
|
||||
None,
|
||||
@@ -95,6 +103,14 @@ impl DirEntry<'_> {
|
||||
}
|
||||
self.dir.open_as_file_at(self.name(), flags, 0)
|
||||
}
|
||||
|
||||
pub fn rename_to<'a, 'entry: 'a>(
|
||||
&'entry self,
|
||||
new_dir: impl AsFd,
|
||||
path: &'a Utf8CStr,
|
||||
) -> OsResult<'a, ()> {
|
||||
self.dir.rename_at(self.name(), new_dir, path)
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for DirEntry<'_> {
|
||||
@@ -110,30 +126,6 @@ pub struct Directory {
|
||||
inner: NonNull<libc::DIR>,
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
pub struct BorrowedDirectory<'a> {
|
||||
inner: NonNull<libc::DIR>,
|
||||
phantom: PhantomData<&'a Directory>,
|
||||
}
|
||||
|
||||
impl Deref for BorrowedDirectory<'_> {
|
||||
type Target = Directory;
|
||||
|
||||
fn deref(&self) -> &Directory {
|
||||
// SAFETY: layout of NonNull<libc::DIR> is the same as Directory
|
||||
// SAFETY: the lifetime of the raw pointer is tracked in the PhantomData
|
||||
unsafe { mem::transmute(&self.inner) }
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for BorrowedDirectory<'_> {
|
||||
fn deref_mut(&mut self) -> &mut Directory {
|
||||
// SAFETY: layout of NonNull<libc::DIR> is the same as Directory
|
||||
// SAFETY: the lifetime of the raw pointer is tracked in the PhantomData
|
||||
unsafe { mem::transmute(&mut self.inner) }
|
||||
}
|
||||
}
|
||||
|
||||
pub enum WalkResult {
|
||||
Continue,
|
||||
Abort,
|
||||
@@ -141,19 +133,14 @@ pub enum WalkResult {
|
||||
}
|
||||
|
||||
impl Directory {
|
||||
fn borrow(&self) -> BorrowedDirectory {
|
||||
BorrowedDirectory {
|
||||
inner: self.inner,
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
fn openat<'a>(&self, name: &'a Utf8CStr, flags: i32, mode: u32) -> OsResult<'a, OwnedFd> {
|
||||
unsafe {
|
||||
libc::openat(self.as_raw_fd(), name.as_ptr(), flags | O_CLOEXEC, mode)
|
||||
.as_os_result("openat", Some(name), None)
|
||||
.map(|fd| OwnedFd::from_raw_fd(fd))
|
||||
}
|
||||
fn open_at<'a>(&self, name: &'a Utf8CStr, flags: OFlag, mode: mode_t) -> OsResult<'a, OwnedFd> {
|
||||
nix::fcntl::openat(
|
||||
self,
|
||||
name,
|
||||
flags | OFlag::O_CLOEXEC,
|
||||
Mode::from_bits_truncate(mode),
|
||||
)
|
||||
.into_os_result("openat", Some(name), None)
|
||||
}
|
||||
|
||||
fn path_at(&self, name: &Utf8CStr, buf: &mut dyn Utf8CStrBuf) -> OsResult<'static, ()> {
|
||||
@@ -163,14 +150,15 @@ impl Directory {
|
||||
}
|
||||
}
|
||||
|
||||
// Low-level methods, we should track the caller when error occurs, so return OsResult.
|
||||
impl Directory {
|
||||
pub fn open(path: &Utf8CStr) -> OsResult<Directory> {
|
||||
pub fn open(path: &Utf8CStr) -> OsResult<'_, Directory> {
|
||||
let dirp = unsafe { libc::opendir(path.as_ptr()) };
|
||||
let dirp = dirp.as_os_result("opendir", Some(path), None)?;
|
||||
let dirp = dirp.into_os_result("opendir", Some(path), None)?;
|
||||
Ok(Directory { inner: dirp })
|
||||
}
|
||||
|
||||
pub fn read(&mut self) -> OsResult<'static, Option<DirEntry>> {
|
||||
pub fn read(&mut self) -> OsResult<'static, Option<DirEntry<'_>>> {
|
||||
*errno() = 0;
|
||||
let e = unsafe { libc::readdir(self.inner.as_ptr()) };
|
||||
if e.is_null() {
|
||||
@@ -192,7 +180,7 @@ impl Directory {
|
||||
self.read()
|
||||
} else {
|
||||
let e = DirEntry {
|
||||
dir: self.borrow(),
|
||||
dir: self,
|
||||
entry: NonNull::from(entry),
|
||||
d_name_len: name.as_bytes_with_nul().len(),
|
||||
};
|
||||
@@ -206,17 +194,17 @@ impl Directory {
|
||||
}
|
||||
|
||||
pub fn open_as_dir_at<'a>(&self, name: &'a Utf8CStr) -> OsResult<'a, Directory> {
|
||||
let fd = self.openat(name, O_RDONLY, 0)?;
|
||||
let fd = self.open_at(name, OFlag::O_RDONLY, 0)?;
|
||||
Directory::try_from(fd).map_err(|e| e.set_args(Some(name), None))
|
||||
}
|
||||
|
||||
pub fn open_as_file_at<'a>(
|
||||
&self,
|
||||
name: &'a Utf8CStr,
|
||||
flags: i32,
|
||||
mode: u32,
|
||||
flags: OFlag,
|
||||
mode: mode_t,
|
||||
) -> OsResult<'a, File> {
|
||||
let fd = self.openat(name, flags, mode)?;
|
||||
let fd = self.open_at(name, flags, mode)?;
|
||||
Ok(File::from(fd))
|
||||
}
|
||||
|
||||
@@ -227,27 +215,23 @@ impl Directory {
|
||||
) -> OsResult<'a, ()> {
|
||||
buf.clear();
|
||||
unsafe {
|
||||
let r = readlinkat(
|
||||
readlinkat(
|
||||
self.as_raw_fd(),
|
||||
name.as_ptr(),
|
||||
buf.as_mut_ptr().cast(),
|
||||
buf.capacity(),
|
||||
)
|
||||
.as_os_result("readlinkat", Some(name), None)? as usize;
|
||||
buf.set_len(r);
|
||||
.check_os_err("readlinkat", Some(name), None)?;
|
||||
}
|
||||
buf.rebuild().ok();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn mkdir_at<'a>(&self, name: &'a Utf8CStr, mode: mode_t) -> OsResult<'a, ()> {
|
||||
unsafe {
|
||||
if libc::mkdirat(self.as_raw_fd(), name.as_ptr(), mode as mode_t) < 0
|
||||
&& *errno() != EEXIST
|
||||
{
|
||||
return Err(OsError::last_os_error("mkdirat", Some(name), None));
|
||||
}
|
||||
match nix::sys::stat::mkdirat(self, name, Mode::from_bits_truncate(mode)) {
|
||||
Ok(_) | Err(Errno::EEXIST) => Ok(()),
|
||||
Err(e) => Err(OsError::new(e, "mkdirat", Some(name), None)),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ln -s target self/name
|
||||
@@ -256,60 +240,56 @@ impl Directory {
|
||||
name: &'a Utf8CStr,
|
||||
target: &'a Utf8CStr,
|
||||
) -> OsResult<'a, ()> {
|
||||
unsafe {
|
||||
libc::symlinkat(target.as_ptr(), self.as_raw_fd(), name.as_ptr()).check_os_err(
|
||||
"symlinkat",
|
||||
Some(target),
|
||||
Some(name),
|
||||
)
|
||||
}
|
||||
nix::unistd::symlinkat(target, self, name).check_os_err(
|
||||
"symlinkat",
|
||||
Some(target),
|
||||
Some(name),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn unlink_at<'a>(&self, name: &'a Utf8CStr, flag: i32) -> OsResult<'a, ()> {
|
||||
unsafe {
|
||||
libc::unlinkat(self.as_raw_fd(), name.as_ptr(), flag).check_os_err(
|
||||
"unlinkat",
|
||||
Some(name),
|
||||
None,
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
pub fn unlink_at<'a>(&self, name: &'a Utf8CStr, flag: UnlinkatFlags) -> OsResult<'a, ()> {
|
||||
nix::unistd::unlinkat(self, name, flag).check_os_err("unlinkat", Some(name), None)
|
||||
}
|
||||
|
||||
pub fn contains_path(&self, path: &Utf8CStr) -> bool {
|
||||
// WARNING: Using faccessat is incorrect, because the raw linux kernel syscall
|
||||
// does not support the flag AT_SYMLINK_NOFOLLOW until 5.8 with faccessat2.
|
||||
// Use fstatat to check the existence of a file instead.
|
||||
unsafe {
|
||||
let mut st: libc::stat = mem::zeroed();
|
||||
libc::fstatat(
|
||||
self.as_raw_fd(),
|
||||
path.as_ptr(),
|
||||
&mut st,
|
||||
libc::AT_SYMLINK_NOFOLLOW,
|
||||
) == 0
|
||||
}
|
||||
nix::sys::stat::fstatat(self, path, AtFlags::AT_SYMLINK_NOFOLLOW).is_ok()
|
||||
}
|
||||
|
||||
pub fn resolve_path(&self, buf: &mut dyn Utf8CStrBuf) -> OsResult<'static, ()> {
|
||||
fd_path(self.as_raw_fd(), buf)
|
||||
}
|
||||
|
||||
pub fn post_order_walk<F: FnMut(&DirEntry) -> OsResultStatic<WalkResult>>(
|
||||
pub fn rename_at<'a>(
|
||||
&self,
|
||||
old: &'a Utf8CStr,
|
||||
new_dir: impl AsFd,
|
||||
new: &'a Utf8CStr,
|
||||
) -> OsResult<'a, ()> {
|
||||
nix::fcntl::renameat(self, old, new_dir, new).check_os_err("renameat", Some(old), Some(new))
|
||||
}
|
||||
}
|
||||
|
||||
// High-level helper methods, composed of multiple operations.
|
||||
// We should treat these as application logic and log ASAP, so return LoggedResult.
|
||||
impl Directory {
|
||||
pub fn post_order_walk<F: FnMut(&DirEntry) -> LoggedResult<WalkResult>>(
|
||||
&mut self,
|
||||
mut f: F,
|
||||
) -> OsResultStatic<WalkResult> {
|
||||
) -> LoggedResult<WalkResult> {
|
||||
self.post_order_walk_impl(&mut f)
|
||||
}
|
||||
|
||||
pub fn pre_order_walk<F: FnMut(&DirEntry) -> OsResultStatic<WalkResult>>(
|
||||
pub fn pre_order_walk<F: FnMut(&DirEntry) -> LoggedResult<WalkResult>>(
|
||||
&mut self,
|
||||
mut f: F,
|
||||
) -> OsResultStatic<WalkResult> {
|
||||
) -> LoggedResult<WalkResult> {
|
||||
self.pre_order_walk_impl(&mut f)
|
||||
}
|
||||
|
||||
pub fn remove_all(&mut self) -> OsResultStatic<()> {
|
||||
pub fn remove_all(mut self) -> LoggedResult<()> {
|
||||
self.post_order_walk(|e| {
|
||||
e.unlink()?;
|
||||
Ok(WalkResult::Continue)
|
||||
@@ -317,13 +297,12 @@ impl Directory {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn copy_into(&mut self, dir: &Directory) -> OsResultStatic<()> {
|
||||
pub fn copy_into(&mut self, dir: &Directory) -> LoggedResult<()> {
|
||||
let mut buf = cstr::buf::default();
|
||||
self.copy_into_impl(dir, &mut buf)
|
||||
}
|
||||
|
||||
pub fn move_into(&mut self, dir: &Directory) -> OsResultStatic<()> {
|
||||
let dir_fd = self.as_raw_fd();
|
||||
pub fn move_into(&mut self, dir: &Directory) -> LoggedResult<()> {
|
||||
while let Some(ref e) = self.read()? {
|
||||
if e.is_dir() && dir.contains_path(e.name()) {
|
||||
// Destination folder exists, needs recursive move
|
||||
@@ -332,31 +311,22 @@ impl Directory {
|
||||
src.move_into(&dest)?;
|
||||
return Ok(e.unlink()?);
|
||||
}
|
||||
|
||||
unsafe {
|
||||
libc::renameat(
|
||||
dir_fd,
|
||||
e.d_name.as_ptr(),
|
||||
dir.as_raw_fd(),
|
||||
e.d_name.as_ptr(),
|
||||
)
|
||||
.check_os_err("renameat", Some(e.name()), None)?;
|
||||
}
|
||||
e.rename_to(dir, e.name())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn link_into(&mut self, dir: &Directory) -> OsResultStatic<()> {
|
||||
pub fn link_into(&mut self, dir: &Directory) -> LoggedResult<()> {
|
||||
let mut buf = cstr::buf::default();
|
||||
self.link_into_impl(dir, &mut buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl Directory {
|
||||
fn post_order_walk_impl<F: FnMut(&DirEntry) -> OsResultStatic<WalkResult>>(
|
||||
fn post_order_walk_impl<F: FnMut(&DirEntry) -> LoggedResult<WalkResult>>(
|
||||
&mut self,
|
||||
f: &mut F,
|
||||
) -> OsResultStatic<WalkResult> {
|
||||
) -> LoggedResult<WalkResult> {
|
||||
use WalkResult::*;
|
||||
loop {
|
||||
match self.read()? {
|
||||
@@ -378,10 +348,10 @@ impl Directory {
|
||||
}
|
||||
}
|
||||
|
||||
fn pre_order_walk_impl<F: FnMut(&DirEntry) -> OsResultStatic<WalkResult>>(
|
||||
fn pre_order_walk_impl<F: FnMut(&DirEntry) -> LoggedResult<WalkResult>>(
|
||||
&mut self,
|
||||
f: &mut F,
|
||||
) -> OsResultStatic<WalkResult> {
|
||||
) -> LoggedResult<WalkResult> {
|
||||
use WalkResult::*;
|
||||
loop {
|
||||
match self.read()? {
|
||||
@@ -406,7 +376,7 @@ impl Directory {
|
||||
&mut self,
|
||||
dest_dir: &Directory,
|
||||
buf: &mut dyn Utf8CStrBuf,
|
||||
) -> OsResultStatic<()> {
|
||||
) -> LoggedResult<()> {
|
||||
while let Some(ref e) = self.read()? {
|
||||
e.resolve_path(buf)?;
|
||||
let attr = buf.get_attr()?;
|
||||
@@ -417,9 +387,12 @@ impl Directory {
|
||||
src.copy_into_impl(&dest, buf)?;
|
||||
fd_set_attr(dest.as_raw_fd(), &attr)?;
|
||||
} else if e.is_file() {
|
||||
let mut src = e.open_as_file(O_RDONLY)?;
|
||||
let mut dest =
|
||||
dest_dir.open_as_file_at(e.name(), O_WRONLY | O_CREAT | O_TRUNC, 0o777)?;
|
||||
let mut src = e.open_as_file(OFlag::O_RDONLY)?;
|
||||
let mut dest = dest_dir.open_as_file_at(
|
||||
e.name(),
|
||||
OFlag::O_WRONLY | OFlag::O_CREAT | OFlag::O_TRUNC,
|
||||
0o777,
|
||||
)?;
|
||||
std::io::copy(&mut src, &mut dest)?;
|
||||
fd_set_attr(dest.as_raw_fd(), &attr)?;
|
||||
} else if e.is_symlink() {
|
||||
@@ -436,8 +409,7 @@ impl Directory {
|
||||
&mut self,
|
||||
dest_dir: &Directory,
|
||||
buf: &mut dyn Utf8CStrBuf,
|
||||
) -> OsResultStatic<()> {
|
||||
let dir_fd = self.as_raw_fd();
|
||||
) -> LoggedResult<()> {
|
||||
while let Some(ref e) = self.read()? {
|
||||
if e.is_dir() {
|
||||
dest_dir.mkdir_at(e.name(), 0o777)?;
|
||||
@@ -448,16 +420,8 @@ impl Directory {
|
||||
src.link_into_impl(&dest, buf)?;
|
||||
fd_set_attr(dest.as_raw_fd(), &attr)?;
|
||||
} else {
|
||||
unsafe {
|
||||
libc::linkat(
|
||||
dir_fd,
|
||||
e.d_name.as_ptr(),
|
||||
dest_dir.as_raw_fd(),
|
||||
e.d_name.as_ptr(),
|
||||
0,
|
||||
)
|
||||
nix::unistd::linkat(e.dir, e.name(), dest_dir, e.name(), AtFlags::empty())
|
||||
.check_os_err("linkat", Some(e.name()), None)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
@@ -469,7 +433,7 @@ impl TryFrom<OwnedFd> for Directory {
|
||||
|
||||
fn try_from(fd: OwnedFd) -> OsResult<'static, Self> {
|
||||
let dirp = unsafe { libc::fdopendir(fd.into_raw_fd()) };
|
||||
let dirp = dirp.as_os_result("fdopendir", None, None)?;
|
||||
let dirp = dirp.into_os_result("fdopendir", None, None)?;
|
||||
Ok(Directory { inner: dirp })
|
||||
}
|
||||
}
|
||||
@@ -481,7 +445,7 @@ impl AsRawFd for Directory {
|
||||
}
|
||||
|
||||
impl AsFd for Directory {
|
||||
fn as_fd(&self) -> BorrowedFd {
|
||||
fn as_fd(&self) -> BorrowedFd<'_> {
|
||||
unsafe { BorrowedFd::borrow_raw(self.as_raw_fd()) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,30 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <linux/fs.h>
|
||||
#include <functional>
|
||||
#include <string_view>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <linux/fs.h>
|
||||
#include "misc.hpp"
|
||||
|
||||
template <typename T>
|
||||
static inline T align_to(T v, int a) {
|
||||
static_assert(std::is_integral<T>::value);
|
||||
return (v + a - 1) / a * a;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static inline T align_padding(T v, int a) {
|
||||
return align_to(v, a) - v;
|
||||
}
|
||||
#include "base-rs.hpp"
|
||||
|
||||
struct mmap_data : public byte_data {
|
||||
static_assert((sizeof(void *) == 8 && BLKGETSIZE64 == 0x80081272) ||
|
||||
(sizeof(void *) == 4 && BLKGETSIZE64 == 0x80041272));
|
||||
ALLOW_MOVE_ONLY(mmap_data)
|
||||
|
||||
mmap_data() = default;
|
||||
explicit mmap_data(const char *name, bool rw = false);
|
||||
mmap_data(int dirfd, const char *name, bool rw = false);
|
||||
mmap_data(int fd, size_t sz, bool rw = false);
|
||||
@@ -45,24 +34,26 @@ bool fclone_attr(int src, int dest);
|
||||
|
||||
} // extern "C"
|
||||
|
||||
int fd_pathat(int dirfd, const char *name, char *path, size_t size);
|
||||
static inline ssize_t realpath(
|
||||
const char * __restrict__ path, char * __restrict__ buf, size_t bufsiz) {
|
||||
return canonical_path(path, buf, bufsiz);
|
||||
}
|
||||
void full_read(int fd, std::string &str);
|
||||
void full_read(const char *filename, std::string &str);
|
||||
std::string full_read(int fd);
|
||||
std::string full_read(const char *filename);
|
||||
void write_zero(int fd, size_t size);
|
||||
void file_readline(bool trim, FILE *fp, const std::function<bool(std::string_view)> &fn);
|
||||
void file_readline(bool trim, const char *file, const std::function<bool(std::string_view)> &fn);
|
||||
void file_readline(const char *file, const std::function<bool(std::string_view)> &fn);
|
||||
void parse_prop_file(FILE *fp, const std::function<bool(std::string_view, std::string_view)> &fn);
|
||||
void parse_prop_file(const char *file,
|
||||
const std::function<bool(std::string_view, std::string_view)> &fn);
|
||||
std::string resolve_preinit_dir(const char *base_dir);
|
||||
|
||||
// Functor = function<bool(Utf8CStr, Utf8CStr)>
|
||||
template <typename Functor>
|
||||
void parse_prop_file(const char *file, Functor &&fn) {
|
||||
parse_prop_file_rs(file, [&](rust::Str key, rust::Str val) -> bool {
|
||||
// We perform the null termination here in C++ because it's very difficult to do it
|
||||
// right in Rust due to pointer provenance. Trying to dereference a pointer without
|
||||
// the correct provenance in Rust, even in unsafe code, is undefined behavior.
|
||||
// However on the C++ side, there are fewer restrictions on pointers, so the const_cast here
|
||||
// will not trigger UB in the compiler.
|
||||
*(const_cast<char *>(key.data()) + key.size()) = '\0';
|
||||
*(const_cast<char *>(val.data()) + val.size()) = '\0';
|
||||
return fn(Utf8CStr(key.data(), key.size() + 1), Utf8CStr(val.data(), val.size() + 1));
|
||||
});
|
||||
}
|
||||
|
||||
using sFILE = std::unique_ptr<FILE, decltype(&fclose)>;
|
||||
using sDIR = std::unique_ptr<DIR, decltype(&closedir)>;
|
||||
sDIR make_dir(DIR *dp);
|
||||
|
||||
@@ -1,22 +1,23 @@
|
||||
use crate::{
|
||||
Directory, FsPathFollow, LibcReturn, OsError, OsResult, OsResultStatic, Utf8CStr, Utf8CStrBuf,
|
||||
Directory, FsPathFollow, LibcReturn, LoggedResult, OsError, OsResult, Utf8CStr, Utf8CStrBuf,
|
||||
cstr, errno, error,
|
||||
};
|
||||
use bytemuck::{Pod, bytes_of, bytes_of_mut};
|
||||
use libc::{
|
||||
EEXIST, ENOENT, F_OK, O_CLOEXEC, O_CREAT, O_PATH, O_RDONLY, O_RDWR, O_TRUNC, O_WRONLY, c_uint,
|
||||
makedev, mode_t, stat,
|
||||
};
|
||||
use mem::MaybeUninit;
|
||||
use libc::{c_uint, makedev, mode_t};
|
||||
use nix::errno::Errno;
|
||||
use nix::fcntl::{AT_FDCWD, OFlag};
|
||||
use nix::sys::stat::{FchmodatFlags, Mode};
|
||||
use nix::unistd::{AccessFlags, Gid, Uid};
|
||||
use num_traits::AsPrimitive;
|
||||
use std::cmp::min;
|
||||
use std::ffi::CStr;
|
||||
use std::fmt::Display;
|
||||
use std::fs::File;
|
||||
use std::io::{BufRead, BufReader, Read, Seek, SeekFrom, Write};
|
||||
use std::mem::MaybeUninit;
|
||||
use std::os::fd::{AsFd, BorrowedFd};
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
use std::os::unix::io::{AsRawFd, FromRawFd, OwnedFd, RawFd};
|
||||
use std::os::unix::io::{AsRawFd, OwnedFd, RawFd};
|
||||
use std::path::Path;
|
||||
use std::{io, mem, ptr, slice};
|
||||
|
||||
@@ -57,12 +58,12 @@ impl<T: Read + Seek> ReadSeekExt for T {
|
||||
}
|
||||
|
||||
pub trait BufReadExt {
|
||||
fn foreach_lines<F: FnMut(&mut String) -> bool>(&mut self, f: F);
|
||||
fn foreach_props<F: FnMut(&str, &str) -> bool>(&mut self, f: F);
|
||||
fn for_each_line<F: FnMut(&mut String) -> bool>(&mut self, f: F);
|
||||
fn for_each_prop<F: FnMut(&str, &str) -> bool>(&mut self, f: F);
|
||||
}
|
||||
|
||||
impl<T: BufRead> BufReadExt for T {
|
||||
fn foreach_lines<F: FnMut(&mut String) -> bool>(&mut self, mut f: F) {
|
||||
fn for_each_line<F: FnMut(&mut String) -> bool>(&mut self, mut f: F) {
|
||||
let mut buf = String::new();
|
||||
loop {
|
||||
match self.read_line(&mut buf) {
|
||||
@@ -81,8 +82,11 @@ impl<T: BufRead> BufReadExt for T {
|
||||
}
|
||||
}
|
||||
|
||||
fn foreach_props<F: FnMut(&str, &str) -> bool>(&mut self, mut f: F) {
|
||||
self.foreach_lines(|line| {
|
||||
fn for_each_prop<F: FnMut(&str, &str) -> bool>(&mut self, mut f: F) {
|
||||
self.for_each_line(|line| {
|
||||
// Reserve an additional byte, because this string will be manually
|
||||
// null terminated on the C++ side, and it may need more space.
|
||||
line.reserve(1);
|
||||
let line = line.trim();
|
||||
if line.starts_with('#') {
|
||||
return true;
|
||||
@@ -116,17 +120,34 @@ impl<T: Write> WriteExt for T {
|
||||
}
|
||||
}
|
||||
|
||||
fn open_fd(path: &Utf8CStr, flags: i32, mode: mode_t) -> OsResult<OwnedFd> {
|
||||
unsafe {
|
||||
let fd = libc::open(path.as_ptr(), flags, mode as c_uint).as_os_result(
|
||||
"open",
|
||||
Some(path),
|
||||
None,
|
||||
)?;
|
||||
Ok(OwnedFd::from_raw_fd(fd))
|
||||
pub enum FileOrStd {
|
||||
StdIn,
|
||||
StdOut,
|
||||
StdErr,
|
||||
File(File),
|
||||
}
|
||||
|
||||
impl FileOrStd {
|
||||
pub fn as_file(&self) -> &File {
|
||||
let raw_fd_ref: &'static RawFd = match self {
|
||||
FileOrStd::StdIn => &0,
|
||||
FileOrStd::StdOut => &1,
|
||||
FileOrStd::StdErr => &2,
|
||||
FileOrStd::File(file) => return file,
|
||||
};
|
||||
// SAFETY: File is guaranteed to have the same ABI as RawFd
|
||||
unsafe { mem::transmute(raw_fd_ref) }
|
||||
}
|
||||
}
|
||||
|
||||
fn open_fd(path: &Utf8CStr, flags: OFlag, mode: mode_t) -> OsResult<'_, OwnedFd> {
|
||||
nix::fcntl::open(path, flags, Mode::from_bits_truncate(mode)).into_os_result(
|
||||
"open",
|
||||
Some(path),
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn fd_path(fd: RawFd, buf: &mut dyn Utf8CStrBuf) -> OsResult<'static, ()> {
|
||||
let path = cstr::buf::new::<64>()
|
||||
.join_path("/proc/self/fd")
|
||||
@@ -140,8 +161,14 @@ pub struct FileAttr {
|
||||
pub con: crate::Utf8CStrBufArr<128>,
|
||||
}
|
||||
|
||||
impl Default for FileAttr {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl FileAttr {
|
||||
fn new() -> Self {
|
||||
pub fn new() -> Self {
|
||||
FileAttr {
|
||||
st: unsafe { mem::zeroed() },
|
||||
#[cfg(feature = "selinux")]
|
||||
@@ -190,180 +217,136 @@ impl FileAttr {
|
||||
|
||||
const XATTR_NAME_SELINUX: &CStr = c"security.selinux";
|
||||
|
||||
// Low-level methods, we should track the caller when error occurs, so return OsResult.
|
||||
impl Utf8CStr {
|
||||
pub fn follow_link(&self) -> &FsPathFollow {
|
||||
unsafe { mem::transmute(self) }
|
||||
}
|
||||
|
||||
pub fn open(&self, flags: i32) -> OsResult<File> {
|
||||
pub fn open(&self, flags: OFlag) -> OsResult<'_, File> {
|
||||
Ok(File::from(open_fd(self, flags, 0)?))
|
||||
}
|
||||
|
||||
pub fn create(&self, flags: i32, mode: mode_t) -> OsResult<File> {
|
||||
Ok(File::from(open_fd(self, O_CREAT | flags, mode)?))
|
||||
pub fn create(&self, flags: OFlag, mode: mode_t) -> OsResult<'_, File> {
|
||||
Ok(File::from(open_fd(self, OFlag::O_CREAT | flags, mode)?))
|
||||
}
|
||||
|
||||
pub fn exists(&self) -> bool {
|
||||
unsafe {
|
||||
let mut st: stat = mem::zeroed();
|
||||
libc::lstat(self.as_ptr(), &mut st) == 0
|
||||
}
|
||||
nix::sys::stat::lstat(self).is_ok()
|
||||
}
|
||||
|
||||
pub fn rename_to<'a>(&'a self, name: &'a Utf8CStr) -> OsResult<'a, ()> {
|
||||
unsafe {
|
||||
libc::rename(self.as_ptr(), name.as_ptr()).check_os_err(
|
||||
"rename",
|
||||
Some(self),
|
||||
Some(name),
|
||||
)
|
||||
}
|
||||
nix::fcntl::renameat(AT_FDCWD, self, AT_FDCWD, name).check_os_err(
|
||||
"rename",
|
||||
Some(self),
|
||||
Some(name),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn remove(&self) -> OsResult<()> {
|
||||
pub fn remove(&self) -> OsResult<'_, ()> {
|
||||
unsafe { libc::remove(self.as_ptr()).check_os_err("remove", Some(self), None) }
|
||||
}
|
||||
|
||||
pub fn remove_all(&self) -> OsResultStatic<()> {
|
||||
let attr = self.get_attr()?;
|
||||
if attr.is_dir() {
|
||||
let 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 +362,6 @@ impl Utf8CStr {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn copy_to(&self, path: &Utf8CStr) -> OsResultStatic<()> {
|
||||
let attr = self.get_attr()?;
|
||||
if attr.is_dir() {
|
||||
path.mkdir(0o777)?;
|
||||
let mut src = Directory::open(self)?;
|
||||
let dest = Directory::open(path)?;
|
||||
src.copy_into(&dest)?;
|
||||
} else {
|
||||
// It's OK if remove failed
|
||||
path.remove().ok();
|
||||
if attr.is_file() {
|
||||
let mut src = self.open(O_RDONLY | O_CLOEXEC)?;
|
||||
let mut dest = path.create(O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0o777)?;
|
||||
std::io::copy(&mut src, &mut dest)?;
|
||||
} else if attr.is_symlink() {
|
||||
let mut buf = cstr::buf::default();
|
||||
self.read_link(&mut buf)?;
|
||||
unsafe {
|
||||
libc::symlink(buf.as_ptr(), path.as_ptr()).check_os_err(
|
||||
"symlink",
|
||||
Some(&buf),
|
||||
Some(path),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
path.set_attr(&attr)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn move_to(&self, path: &Utf8CStr) -> OsResultStatic<()> {
|
||||
if path.exists() {
|
||||
let attr = path.get_attr()?;
|
||||
if attr.is_dir() {
|
||||
let mut src = Directory::open(self)?;
|
||||
let dest = Directory::open(path)?;
|
||||
return src.move_into(&dest);
|
||||
} else {
|
||||
path.remove()?;
|
||||
}
|
||||
}
|
||||
self.rename_to(path)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn parent_dir(&self) -> Option<&str> {
|
||||
Path::new(self.as_str())
|
||||
.parent()
|
||||
@@ -439,8 +377,119 @@ impl Utf8CStr {
|
||||
.map(|s| unsafe { std::str::from_utf8_unchecked(s.as_bytes()) })
|
||||
}
|
||||
|
||||
// ln -s target self
|
||||
pub fn create_symlink_to<'a>(&'a self, target: &'a Utf8CStr) -> OsResult<'a, ()> {
|
||||
nix::unistd::symlinkat(target, AT_FDCWD, self).check_os_err(
|
||||
"symlink",
|
||||
Some(target),
|
||||
Some(self),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn mkfifo(&self, mode: mode_t) -> OsResult<'_, ()> {
|
||||
nix::unistd::mkfifo(self, Mode::from_bits_truncate(mode)).check_os_err(
|
||||
"mkfifo",
|
||||
Some(self),
|
||||
None,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// High-level helper methods, composed of multiple operations.
|
||||
// We should treat these as application logic and log ASAP, so return LoggedResult.
|
||||
impl Utf8CStr {
|
||||
pub fn remove_all(&self) -> LoggedResult<()> {
|
||||
let attr = match self.get_attr() {
|
||||
Ok(attr) => attr,
|
||||
Err(e) => {
|
||||
return match e.errno {
|
||||
// Allow calling remove_all on non-existence file
|
||||
Errno::ENOENT => Ok(()),
|
||||
_ => Err(e)?,
|
||||
};
|
||||
}
|
||||
};
|
||||
if attr.is_dir() {
|
||||
let dir = Directory::open(self)?;
|
||||
dir.remove_all()?;
|
||||
}
|
||||
Ok(self.remove()?)
|
||||
}
|
||||
|
||||
pub fn mkdirs(&self, mode: mode_t) -> LoggedResult<()> {
|
||||
if self.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut path = cstr::buf::default();
|
||||
let mut components = self.split('/').filter(|s| !s.is_empty());
|
||||
|
||||
if self.starts_with('/') {
|
||||
path.append_path("/");
|
||||
}
|
||||
|
||||
loop {
|
||||
let Some(s) = components.next() else {
|
||||
break;
|
||||
};
|
||||
path.append_path(s);
|
||||
path.mkdir(mode)?;
|
||||
}
|
||||
|
||||
*errno() = 0;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn copy_to(&self, path: &Utf8CStr) -> LoggedResult<()> {
|
||||
let attr = self.get_attr()?;
|
||||
if attr.is_dir() {
|
||||
path.mkdir(0o777)?;
|
||||
let mut src = Directory::open(self)?;
|
||||
let dest = Directory::open(path)?;
|
||||
src.copy_into(&dest)?;
|
||||
} else {
|
||||
// It's OK if remove failed
|
||||
path.remove().ok();
|
||||
if attr.is_file() {
|
||||
let mut src = self.open(OFlag::O_RDONLY | OFlag::O_CLOEXEC)?;
|
||||
let mut dest = path.create(
|
||||
OFlag::O_WRONLY | OFlag::O_CREAT | OFlag::O_TRUNC | OFlag::O_CLOEXEC,
|
||||
0o777,
|
||||
)?;
|
||||
std::io::copy(&mut src, &mut dest)?;
|
||||
} else if attr.is_symlink() {
|
||||
let mut buf = cstr::buf::default();
|
||||
self.read_link(&mut buf)?;
|
||||
unsafe {
|
||||
libc::symlink(buf.as_ptr(), path.as_ptr()).check_os_err(
|
||||
"symlink",
|
||||
Some(&buf),
|
||||
Some(path),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
path.set_attr(&attr)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn move_to(&self, path: &Utf8CStr) -> LoggedResult<()> {
|
||||
if path.exists() {
|
||||
let attr = path.get_attr()?;
|
||||
if attr.is_dir() {
|
||||
let mut src = Directory::open(self)?;
|
||||
let dest = Directory::open(path)?;
|
||||
return src.move_into(&dest);
|
||||
} else {
|
||||
path.remove()?;
|
||||
}
|
||||
}
|
||||
self.rename_to(path)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ln self path
|
||||
pub fn link_to(&self, path: &Utf8CStr) -> OsResultStatic<()> {
|
||||
pub fn link_to(&self, path: &Utf8CStr) -> LoggedResult<()> {
|
||||
let attr = self.get_attr()?;
|
||||
if attr.is_dir() {
|
||||
path.mkdir(0o777)?;
|
||||
@@ -459,78 +508,76 @@ impl Utf8CStr {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// ln -s target self
|
||||
pub fn create_symlink_to<'a>(&'a self, target: &'a Utf8CStr) -> OsResult<'a, ()> {
|
||||
unsafe {
|
||||
libc::symlink(target.as_ptr(), self.as_ptr()).check_os_err(
|
||||
"symlink",
|
||||
Some(target),
|
||||
Some(self),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mkfifo(&self, mode: mode_t) -> OsResult<()> {
|
||||
unsafe { libc::mkfifo(self.as_ptr(), mode).check_os_err("mkfifo", Some(self), None) }
|
||||
}
|
||||
}
|
||||
|
||||
impl FsPathFollow {
|
||||
pub fn exists(&self) -> bool {
|
||||
unsafe { libc::access(self.as_ptr(), F_OK) == 0 }
|
||||
nix::unistd::access(self.as_utf8_cstr(), AccessFlags::F_OK).is_ok()
|
||||
}
|
||||
|
||||
pub fn get_attr(&self) -> OsResult<FileAttr> {
|
||||
let mut attr = FileAttr::new();
|
||||
unsafe {
|
||||
libc::stat(self.as_ptr(), &mut attr.st).check_os_err("stat", Some(self), None)?;
|
||||
pub fn chmod(&self, mode: mode_t) -> OsResult<'_, ()> {
|
||||
nix::sys::stat::fchmodat(
|
||||
AT_FDCWD,
|
||||
self.as_utf8_cstr(),
|
||||
Mode::from_bits_truncate(mode),
|
||||
FchmodatFlags::FollowSymlink,
|
||||
)
|
||||
.check_os_err("chmod", Some(self), None)
|
||||
}
|
||||
|
||||
pub fn get_attr(&self) -> OsResult<'_, FileAttr> {
|
||||
#[allow(unused_mut)]
|
||||
let mut attr = FileAttr {
|
||||
st: nix::sys::stat::stat(self.as_utf8_cstr()).into_os_result(
|
||||
"lstat",
|
||||
Some(self),
|
||||
None,
|
||||
)?,
|
||||
#[cfg(feature = "selinux")]
|
||||
self.get_secontext(&mut attr.con)?;
|
||||
}
|
||||
con: cstr::buf::new(),
|
||||
};
|
||||
#[cfg(feature = "selinux")]
|
||||
self.get_secontext(&mut attr.con)?;
|
||||
Ok(attr)
|
||||
}
|
||||
|
||||
pub fn set_attr<'a>(&'a self, attr: &'a FileAttr) -> OsResult<'a, ()> {
|
||||
unsafe {
|
||||
libc::chmod(self.as_ptr(), (attr.st.st_mode & 0o777).as_()).check_os_err(
|
||||
"chmod",
|
||||
Some(self),
|
||||
None,
|
||||
)?;
|
||||
libc::chown(self.as_ptr(), attr.st.st_uid, attr.st.st_gid).check_os_err(
|
||||
"chown",
|
||||
Some(self),
|
||||
None,
|
||||
)?;
|
||||
self.chmod((attr.st.st_mode & 0o777).as_())?;
|
||||
|
||||
#[cfg(feature = "selinux")]
|
||||
if !attr.con.is_empty() {
|
||||
self.set_secontext(&attr.con)?;
|
||||
}
|
||||
nix::unistd::chown(
|
||||
self.as_utf8_cstr(),
|
||||
Some(Uid::from(attr.st.st_uid)),
|
||||
Some(Gid::from(attr.st.st_gid)),
|
||||
)
|
||||
.check_os_err("chown", Some(self), None)?;
|
||||
|
||||
#[cfg(feature = "selinux")]
|
||||
if !attr.con.is_empty() {
|
||||
self.set_secontext(&attr.con)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_secontext(&self, con: &mut dyn Utf8CStrBuf) -> OsResult<()> {
|
||||
unsafe {
|
||||
let sz = libc::getxattr(
|
||||
pub fn get_secontext(&self, con: &mut dyn Utf8CStrBuf) -> OsResult<'_, ()> {
|
||||
con.clear();
|
||||
let result = unsafe {
|
||||
libc::getxattr(
|
||||
self.as_ptr(),
|
||||
XATTR_NAME_SELINUX.as_ptr(),
|
||||
con.as_mut_ptr().cast(),
|
||||
con.capacity(),
|
||||
);
|
||||
if sz < 1 {
|
||||
con.clear();
|
||||
if *errno() != libc::ENODATA {
|
||||
return Err(OsError::last_os_error("getxattr", Some(self), None));
|
||||
}
|
||||
} else {
|
||||
con.set_len((sz - 1) as usize);
|
||||
)
|
||||
.check_err()
|
||||
};
|
||||
|
||||
match result {
|
||||
Ok(_) => {
|
||||
con.rebuild().ok();
|
||||
Ok(())
|
||||
}
|
||||
Err(Errno::ENODATA) => Ok(()),
|
||||
Err(e) => Err(OsError::new(e, "getxattr", Some(self), None)),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_secontext<'a>(&'a self, con: &'a Utf8CStr) -> OsResult<'a, ()> {
|
||||
@@ -611,7 +658,7 @@ pub fn fd_get_attr(fd: RawFd) -> OsResult<'static, FileAttr> {
|
||||
Ok(attr)
|
||||
}
|
||||
|
||||
pub fn fd_set_attr(fd: RawFd, attr: &FileAttr) -> OsResult<()> {
|
||||
pub fn fd_set_attr(fd: RawFd, attr: &FileAttr) -> OsResult<'_, ()> {
|
||||
unsafe {
|
||||
libc::fchmod(fd, (attr.st.st_mode & 0o777).as_()).check_os_err("fchmod", None, None)?;
|
||||
libc::fchown(fd, attr.st.st_uid, attr.st.st_gid).check_os_err("fchown", None, None)?;
|
||||
@@ -625,25 +672,28 @@ pub fn fd_set_attr(fd: RawFd, attr: &FileAttr) -> OsResult<()> {
|
||||
}
|
||||
|
||||
pub fn fd_get_secontext(fd: RawFd, con: &mut dyn Utf8CStrBuf) -> OsResult<'static, ()> {
|
||||
unsafe {
|
||||
let sz = libc::fgetxattr(
|
||||
con.clear();
|
||||
let result = unsafe {
|
||||
libc::fgetxattr(
|
||||
fd,
|
||||
XATTR_NAME_SELINUX.as_ptr(),
|
||||
con.as_mut_ptr().cast(),
|
||||
con.capacity(),
|
||||
);
|
||||
if sz < 1 {
|
||||
if *errno() != libc::ENODATA {
|
||||
return Err(OsError::last_os_error("fgetxattr", None, None));
|
||||
}
|
||||
} else {
|
||||
con.set_len((sz - 1) as usize);
|
||||
)
|
||||
.check_err()
|
||||
};
|
||||
|
||||
match result {
|
||||
Ok(_) => {
|
||||
con.rebuild().ok();
|
||||
Ok(())
|
||||
}
|
||||
Err(Errno::ENODATA) => Ok(()),
|
||||
Err(e) => Err(OsError::new(e, "fgetxattr", None, None)),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn fd_set_secontext(fd: RawFd, con: &Utf8CStr) -> OsResult<()> {
|
||||
pub fn fd_set_secontext(fd: RawFd, con: &Utf8CStr) -> OsResult<'_, ()> {
|
||||
unsafe {
|
||||
libc::fsetxattr(
|
||||
fd,
|
||||
@@ -669,11 +719,11 @@ pub fn fclone_attr(a: RawFd, b: RawFd) -> OsResult<'static, ()> {
|
||||
pub struct MappedFile(&'static mut [u8]);
|
||||
|
||||
impl MappedFile {
|
||||
pub fn open(path: &Utf8CStr) -> OsResult<MappedFile> {
|
||||
pub fn open(path: &Utf8CStr) -> OsResult<'_, MappedFile> {
|
||||
Ok(MappedFile(map_file(path, false)?))
|
||||
}
|
||||
|
||||
pub fn open_rw(path: &Utf8CStr) -> OsResult<MappedFile> {
|
||||
pub fn open_rw(path: &Utf8CStr) -> OsResult<'_, MappedFile> {
|
||||
Ok(MappedFile(map_file(path, true)?))
|
||||
}
|
||||
|
||||
@@ -716,8 +766,8 @@ unsafe extern "C" {
|
||||
}
|
||||
|
||||
// We mark the returned slice static because it is valid until explicitly unmapped
|
||||
pub(crate) fn map_file(path: &Utf8CStr, rw: bool) -> OsResult<&'static mut [u8]> {
|
||||
unsafe { map_file_at(BorrowedFd::borrow_raw(libc::AT_FDCWD), path, rw) }
|
||||
pub(crate) fn map_file(path: &Utf8CStr, rw: bool) -> OsResult<'_, &'static mut [u8]> {
|
||||
map_file_at(AT_FDCWD, path, rw)
|
||||
}
|
||||
|
||||
pub(crate) fn map_file_at<'a>(
|
||||
@@ -731,17 +781,9 @@ pub(crate) fn map_file_at<'a>(
|
||||
#[cfg(target_pointer_width = "32")]
|
||||
const BLKGETSIZE64: u32 = 0x80041272;
|
||||
|
||||
let flag = if rw { O_RDWR } else { O_RDONLY };
|
||||
let fd = unsafe {
|
||||
OwnedFd::from_raw_fd(
|
||||
libc::openat(dirfd.as_raw_fd(), path.as_ptr(), flag | O_CLOEXEC).as_os_result(
|
||||
"openat",
|
||||
Some(path),
|
||||
None,
|
||||
)?,
|
||||
)
|
||||
};
|
||||
|
||||
let flag = if rw { OFlag::O_RDWR } else { OFlag::O_RDONLY };
|
||||
let fd = nix::fcntl::openat(dirfd, path, flag | OFlag::O_CLOEXEC, Mode::empty())
|
||||
.into_os_result("openat", Some(path), None)?;
|
||||
let attr = fd_get_attr(fd.as_raw_fd())?;
|
||||
let sz = if attr.is_block_device() {
|
||||
let mut sz = 0_u64;
|
||||
@@ -847,8 +889,8 @@ fn parse_mount_info_line(line: &str) -> Option<MountInfo> {
|
||||
pub fn parse_mount_info(pid: &str) -> Vec<MountInfo> {
|
||||
let mut res = vec![];
|
||||
let mut path = format!("/proc/{pid}/mountinfo");
|
||||
if let Ok(file) = Utf8CStr::from_string(&mut path).open(O_RDONLY | O_CLOEXEC) {
|
||||
BufReader::new(file).foreach_lines(|line| {
|
||||
if let Ok(file) = Utf8CStr::from_string(&mut path).open(OFlag::O_RDONLY | OFlag::O_CLOEXEC) {
|
||||
BufReader::new(file).for_each_line(|line| {
|
||||
parse_mount_info_line(line)
|
||||
.map(|info| res.push(info))
|
||||
.is_some()
|
||||
|
||||
@@ -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,20 +1,21 @@
|
||||
#![feature(vec_into_raw_parts)]
|
||||
#![allow(clippy::missing_safety_doc)]
|
||||
|
||||
pub use const_format;
|
||||
pub use libc;
|
||||
use num_traits::FromPrimitive;
|
||||
pub use {const_format, libc, nix};
|
||||
|
||||
pub use cstr::{
|
||||
FsPathFollow, StrErr, Utf8CStr, Utf8CStrBuf, Utf8CStrBufArr, Utf8CStrBufRef, Utf8CString,
|
||||
};
|
||||
use cxx_extern::*;
|
||||
pub use derive;
|
||||
pub use dir::*;
|
||||
pub use ffi::fork_dont_care;
|
||||
pub use ffi::{Utf8CStrRef, fork_dont_care, set_nice_name};
|
||||
pub use files::*;
|
||||
pub use logging::*;
|
||||
pub use misc::*;
|
||||
pub use result::*;
|
||||
|
||||
pub mod argh;
|
||||
pub mod cstr;
|
||||
mod cxx_extern;
|
||||
mod dir;
|
||||
@@ -26,12 +27,11 @@ mod result;
|
||||
mod xwrap;
|
||||
|
||||
#[cxx::bridge]
|
||||
pub mod ffi {
|
||||
mod ffi {
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(i32)]
|
||||
#[cxx_name = "LogLevel"]
|
||||
pub(crate) enum LogLevelCxx {
|
||||
ErrorCxx,
|
||||
Error,
|
||||
Warn,
|
||||
Info,
|
||||
@@ -41,28 +41,32 @@ pub mod ffi {
|
||||
unsafe extern "C++" {
|
||||
include!("misc.hpp");
|
||||
|
||||
#[namespace = "rust"]
|
||||
#[cxx_name = "Utf8CStr"]
|
||||
type Utf8CStrRef<'a> = &'a crate::cstr::Utf8CStr;
|
||||
|
||||
fn mut_u8_patch(buf: &mut [u8], from: &[u8], to: &[u8]) -> Vec<usize>;
|
||||
fn fork_dont_care() -> i32;
|
||||
fn set_nice_name(name: Utf8CStrRef);
|
||||
|
||||
type FnBoolStrStr;
|
||||
fn call(self: &FnBoolStrStr, key: &str, value: &str) -> bool;
|
||||
|
||||
type FnBoolStr;
|
||||
fn call(self: &FnBoolStr, key: Utf8CStrRef) -> bool;
|
||||
}
|
||||
|
||||
extern "Rust" {
|
||||
#[cxx_name = "log_with_rs"]
|
||||
fn log_from_cxx(level: LogLevelCxx, msg: Utf8CStrRef);
|
||||
#[cxx_name = "set_log_level_state"]
|
||||
fn set_log_level_state_cxx(level: LogLevelCxx, enabled: bool);
|
||||
fn exit_on_error(b: bool);
|
||||
fn cmdline_logging();
|
||||
fn parse_prop_file_rs(name: Utf8CStrRef, f: &FnBoolStrStr);
|
||||
#[cxx_name = "file_readline"]
|
||||
fn file_readline_for_cxx(fd: i32, f: &FnBoolStr);
|
||||
}
|
||||
|
||||
#[namespace = "rust"]
|
||||
extern "Rust" {
|
||||
fn xpipe2(fds: &mut [i32; 2], flags: i32) -> i32;
|
||||
#[cxx_name = "fd_path"]
|
||||
fn fd_path_for_cxx(fd: i32, buf: &mut [u8]) -> isize;
|
||||
#[cxx_name = "map_file"]
|
||||
fn map_file_for_cxx(path: Utf8CStrRef, rw: bool) -> &'static mut [u8];
|
||||
#[cxx_name = "map_file_at"]
|
||||
@@ -72,8 +76,9 @@ pub mod ffi {
|
||||
}
|
||||
}
|
||||
|
||||
fn set_log_level_state_cxx(level: ffi::LogLevelCxx, enabled: bool) {
|
||||
if let Some(level) = LogLevel::from_i32(level.repr) {
|
||||
set_log_level_state(level, enabled)
|
||||
}
|
||||
// In Rust, we do not want to deal with raw pointers, so we change the
|
||||
// signature of all *mut c_void to usize for new_daemon_thread.
|
||||
pub type ThreadEntry = extern "C" fn(usize) -> usize;
|
||||
unsafe extern "C" {
|
||||
pub fn new_daemon_thread(entry: ThreadEntry, arg: usize);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user