ksud: dropped magic_mount for stability so welcome back OverlayFS

manager: brought back shrink images in settings
This commit is contained in:
Rifat Azad
2024-12-24 04:21:38 +06:00
parent f0d235c266
commit a0540aa8ec
16 changed files with 1246 additions and 742 deletions

View File

@@ -14,7 +14,7 @@ A Kernel-based root solution for Android devices.
## Features ## Features
1. Kernel-based `su` and root access management. 1. Kernel-based `su` and root access management.
2. Module system based on [Magic Mount](https://github.com/topjohnwu/Magisk/blob/c512496847d182526f2043295ecfd275398eccac/docs/releases/26100.md#new-magic-mount-implementation). 2. Module system based on [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS).
3. [App Profile](https://kernelsu.org/guide/app-profile.html): Lock up the root power in a cage. 3. [App Profile](https://kernelsu.org/guide/app-profile.html): Lock up the root power in a cage.
## Compatibility State ## Compatibility State
@@ -42,4 +42,3 @@ For information on reporting security vulnerabilities in KernelSU, see [SECURITY
- [genuine](https://github.com/brevent/genuine/): apk v2 signature validation. - [genuine](https://github.com/brevent/genuine/): apk v2 signature validation.
- [Diamorphine](https://github.com/m0nad/Diamorphine): some rootkit skills. - [Diamorphine](https://github.com/m0nad/Diamorphine): some rootkit skills.
- [KernelSU](https://github.com/tiann/KernelSU): thanks to tiann or else KernelSU Next wouldn't even exist. - [KernelSU](https://github.com/tiann/KernelSU): thanks to tiann or else KernelSU Next wouldn't even exist.
- [Magic Mount Port](https://github.com/5ec1cff/KernelSU/blob/main/userspace/ksud/src/magic_mount.rs): 💜 5ec1cff for saving KernelSU!

View File

@@ -106,6 +106,7 @@ fun SettingScreen(navigator: DestinationsNavigator) {
AboutDialog(it) AboutDialog(it)
} }
val loadingDialog = rememberLoadingDialog() val loadingDialog = rememberLoadingDialog()
val shrinkDialog = rememberConfirmDialog()
Column( Column(
modifier = Modifier modifier = Modifier
@@ -324,6 +325,28 @@ fun SettingScreen(navigator: DestinationsNavigator) {
) )
} }
val shrink = stringResource(id = R.string.shrink_sparse_image)
val shrinkMessage = stringResource(id = R.string.shrink_sparse_image_message)
ListItem(
leadingContent = {
Icon(
Icons.Filled.Compress,
shrink
)
},
headlineContent = { Text(shrink) },
modifier = Modifier.clickable {
scope.launch {
val result = shrinkDialog.awaitConfirm(title = shrink, content = shrinkMessage)
if (result == ConfirmResult.Confirmed) {
loadingDialog.withLoading {
shrinkModules()
}
}
}
}
)
val lkmMode = Natives.version >= Natives.MINIMAL_SUPPORTED_KERNEL_LKM && Natives.isLkmMode val lkmMode = Natives.version >= Natives.MINIMAL_SUPPORTED_KERNEL_LKM && Natives.isLkmMode
if (lkmMode) { if (lkmMode) {
UninstallItem(navigator) { UninstallItem(navigator) {

View File

@@ -282,6 +282,10 @@ fun uninstallPermanently(
return result.isSuccess return result.isSuccess
} }
suspend fun shrinkModules(): Boolean = withContext(Dispatchers.IO) {
execKsud("module shrink", true)
}
@Parcelize @Parcelize
sealed class LkmSelection : Parcelable { sealed class LkmSelection : Parcelable {
data class LkmUri(val uri: Uri) : LkmSelection() data class LkmUri(val uri: Uri) : LkmSelection()

View File

@@ -1 +1,2 @@
libksud.so libksud.so
libsusfs.so

View File

@@ -29,7 +29,7 @@ version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
dependencies = [ dependencies = [
"cfg-if", "cfg-if 1.0.0",
"once_cell", "once_cell",
"version_check", "version_check",
"zerocopy", "zerocopy",
@@ -37,9 +37,9 @@ dependencies = [
[[package]] [[package]]
name = "allocator-api2" name = "allocator-api2"
version = "0.2.21" version = "0.2.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" checksum = "45862d1c77f2228b9e10bc609d5bc203d86ebc9b87ad8d5d5167a6c9abf739d9"
[[package]] [[package]]
name = "android-properties" name = "android-properties"
@@ -130,9 +130,9 @@ dependencies = [
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.94" version = "1.0.93"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7" checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775"
[[package]] [[package]]
name = "arbitrary" name = "arbitrary"
@@ -167,7 +167,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a"
dependencies = [ dependencies = [
"addr2line", "addr2line",
"cfg-if", "cfg-if 1.0.0",
"libc", "libc",
"miniz_oxide", "miniz_oxide",
"object", "object",
@@ -210,19 +210,25 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]] [[package]]
name = "bytes" name = "bytes"
version = "1.9.0" version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da"
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.2.5" version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c31a0499c1dc64f458ad13872de75c0eb7e3fdb0e67964610c914b034fc5956e" checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47"
dependencies = [ dependencies = [
"shlex", "shlex",
] ]
[[package]]
name = "cfg-if"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
version = "1.0.0" version = "1.0.0"
@@ -231,9 +237,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]] [[package]]
name = "chrono" name = "chrono"
version = "0.4.39" version = "0.4.38"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401"
dependencies = [ dependencies = [
"android-tzdata", "android-tzdata",
"iana-time-zone", "iana-time-zone",
@@ -245,9 +251,9 @@ dependencies = [
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.5.23" version = "4.5.21"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84" checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f"
dependencies = [ dependencies = [
"clap_builder", "clap_builder",
"clap_derive", "clap_derive",
@@ -255,9 +261,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_builder" name = "clap_builder"
version = "4.5.23" version = "4.5.21"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838" checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec"
dependencies = [ dependencies = [
"anstream", "anstream",
"anstyle", "anstyle",
@@ -279,9 +285,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_lex" name = "clap_lex"
version = "0.7.4" version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7"
[[package]] [[package]]
name = "colorchoice" name = "colorchoice"
@@ -291,18 +297,18 @@ checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
[[package]] [[package]]
name = "const_format" name = "const_format"
version = "0.2.34" version = "0.2.33"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "126f97965c8ad46d6d9163268ff28432e8f6a1196a55578867832e3049df63dd" checksum = "50c655d81ff1114fb0dcdea9225ea9f0cc712a6f8d189378e82bdf62a473a64b"
dependencies = [ dependencies = [
"const_format_proc_macros", "const_format_proc_macros",
] ]
[[package]] [[package]]
name = "const_format_proc_macros" name = "const_format_proc_macros"
version = "0.2.34" version = "0.2.33"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" checksum = "eff1a44b93f47b1bac19a27932f5c591e43d1ba357ee4f61526c8a25603f0eb1"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -326,9 +332,9 @@ dependencies = [
[[package]] [[package]]
name = "cpufeatures" name = "cpufeatures"
version = "0.2.16" version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" checksum = "0ca741a962e1b0bff6d724a1a0958b686406e853bb14061f218562e1896f95e6"
dependencies = [ dependencies = [
"libc", "libc",
] ]
@@ -354,7 +360,7 @@ version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
dependencies = [ dependencies = [
"cfg-if", "cfg-if 1.0.0",
] ]
[[package]] [[package]]
@@ -372,18 +378,18 @@ dependencies = [
[[package]] [[package]]
name = "crossbeam-channel" name = "crossbeam-channel"
version = "0.5.14" version = "0.5.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471" checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2"
dependencies = [ dependencies = [
"crossbeam-utils", "crossbeam-utils",
] ]
[[package]] [[package]]
name = "crossbeam-deque" name = "crossbeam-deque"
version = "0.8.6" version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d"
dependencies = [ dependencies = [
"crossbeam-epoch", "crossbeam-epoch",
"crossbeam-utils", "crossbeam-utils",
@@ -400,18 +406,18 @@ dependencies = [
[[package]] [[package]]
name = "crossbeam-queue" name = "crossbeam-queue"
version = "0.3.12" version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35"
dependencies = [ dependencies = [
"crossbeam-utils", "crossbeam-utils",
] ]
[[package]] [[package]]
name = "crossbeam-utils" name = "crossbeam-utils"
version = "0.8.21" version = "0.8.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
[[package]] [[package]]
name = "crypto-common" name = "crypto-common"
@@ -499,29 +505,23 @@ version = "0.8.35"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
dependencies = [ dependencies = [
"cfg-if", "cfg-if 1.0.0",
] ]
[[package]] [[package]]
name = "env_filter" name = "env_filter"
version = "0.1.3" version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab"
dependencies = [ dependencies = [
"log", "log",
] ]
[[package]]
name = "env_home"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe"
[[package]] [[package]]
name = "env_logger" name = "env_logger"
version = "0.11.6" version = "0.11.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0" checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d"
dependencies = [ dependencies = [
"env_filter", "env_filter",
"log", "log",
@@ -577,9 +577,9 @@ dependencies = [
[[package]] [[package]]
name = "fastrand" name = "fastrand"
version = "2.3.0" version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4"
[[package]] [[package]]
name = "flate2" name = "flate2"
@@ -616,7 +616,7 @@ version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
dependencies = [ dependencies = [
"cfg-if", "cfg-if 1.0.0",
"libc", "libc",
"wasi", "wasi",
] ]
@@ -655,6 +655,28 @@ version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "hole-punch"
version = "0.0.4-alpha.0"
source = "git+https://github.com/tiann/hole-punch#11ab7a61bfb98682b72fd7f58a47d8e5d997328e"
dependencies = [
"cfg-if 0.1.10",
"errno 0.2.8",
"libc",
"memmap",
"thiserror",
"winapi",
]
[[package]]
name = "home"
version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5"
dependencies = [
"windows-sys 0.52.0",
]
[[package]] [[package]]
name = "humansize" name = "humansize"
version = "2.1.3" version = "2.1.3"
@@ -712,12 +734,12 @@ dependencies = [
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "2.7.0" version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da"
dependencies = [ dependencies = [
"equivalent", "equivalent",
"hashbrown 0.15.1", "hashbrown 0.15.0",
] ]
[[package]] [[package]]
@@ -737,9 +759,9 @@ checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]] [[package]]
name = "itoa" name = "itoa"
version = "1.0.14" version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" checksum = "540654e97a3f4470a492cd30ff187bc95d89557a903a2bbf112e2fae98104ef2"
[[package]] [[package]]
name = "java-properties" name = "java-properties"
@@ -785,6 +807,7 @@ dependencies = [
"env_logger", "env_logger",
"extattr", "extattr",
"getopts", "getopts",
"hole-punch",
"humansize", "humansize",
"is_executable", "is_executable",
"java-properties", "java-properties",
@@ -816,9 +839,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.169" version = "0.2.164"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f"
[[package]] [[package]]
name = "libflate" name = "libflate"
@@ -893,6 +916,16 @@ version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "memmap"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b"
dependencies = [
"libc",
"winapi",
]
[[package]] [[package]]
name = "minimal-lexical" name = "minimal-lexical"
version = "0.2.1" version = "0.2.1"
@@ -901,9 +934,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]] [[package]]
name = "miniz_oxide" name = "miniz_oxide"
version = "0.8.2" version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394" checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1"
dependencies = [ dependencies = [
"adler2", "adler2",
] ]
@@ -971,9 +1004,9 @@ dependencies = [
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.92" version = "1.0.91"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" checksum = "307e3004becf10f5a6e0d59d20f3cd28231b0e0827a96cd3e0ce6d14bc1e4bb3"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
@@ -1168,18 +1201,18 @@ dependencies = [
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.216" version = "1.0.215"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.216" version = "1.0.215"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -1204,7 +1237,7 @@ version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
dependencies = [ dependencies = [
"cfg-if", "cfg-if 1.0.0",
"cpufeatures", "cpufeatures",
"digest", "digest",
] ]
@@ -1215,7 +1248,7 @@ version = "0.10.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
dependencies = [ dependencies = [
"cfg-if", "cfg-if 1.0.0",
"cpufeatures", "cpufeatures",
"digest", "digest",
] ]
@@ -1253,9 +1286,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.90" version = "2.0.89"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -1268,27 +1301,27 @@ version = "3.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c"
dependencies = [ dependencies = [
"cfg-if", "cfg-if 1.0.0",
"fastrand", "fastrand",
"once_cell", "once_cell",
"rustix 0.38.41", "rustix 0.38.41",
"windows-sys 0.59.0", "windows-sys 0.52.0",
] ]
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "2.0.8" version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08f5383f3e0071702bf93ab5ee99b52d26936be9dedd9413067cbdcddcb6141a" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
dependencies = [ dependencies = [
"thiserror-impl", "thiserror-impl",
] ]
[[package]] [[package]]
name = "thiserror-impl" name = "thiserror-impl"
version = "2.0.8" version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2f357fcec90b3caef6623a099691be676d033b40a058ac95d2a6ade6fa0c943" checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -1297,9 +1330,9 @@ dependencies = [
[[package]] [[package]]
name = "time" name = "time"
version = "0.3.37" version = "0.3.36"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885"
dependencies = [ dependencies = [
"deranged", "deranged",
"num-conv", "num-conv",
@@ -1316,9 +1349,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.42.0" version = "1.41.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33"
dependencies = [ dependencies = [
"backtrace", "backtrace",
"bytes", "bytes",
@@ -1379,23 +1412,24 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]] [[package]]
name = "wasm-bindgen" name = "wasm-bindgen"
version = "0.2.99" version = "0.2.95"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e"
dependencies = [ dependencies = [
"cfg-if", "cfg-if 1.0.0",
"once_cell", "once_cell",
"wasm-bindgen-macro", "wasm-bindgen-macro",
] ]
[[package]] [[package]]
name = "wasm-bindgen-backend" name = "wasm-bindgen-backend"
version = "0.2.99" version = "0.2.95"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"log", "log",
"once_cell",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn",
@@ -1404,9 +1438,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-macro" name = "wasm-bindgen-macro"
version = "0.2.99" version = "0.2.95"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56"
dependencies = [ dependencies = [
"quote", "quote",
"wasm-bindgen-macro-support", "wasm-bindgen-macro-support",
@@ -1414,9 +1448,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-macro-support" name = "wasm-bindgen-macro-support"
version = "0.2.99" version = "0.2.95"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -1427,18 +1461,18 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-shared" name = "wasm-bindgen-shared"
version = "0.2.99" version = "0.2.95"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d"
[[package]] [[package]]
name = "which" name = "which"
version = "7.0.1" version = "7.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb4a9e33648339dc1642b0e36e21b3385e6148e289226f657c809dee59df5028" checksum = "c9cad3279ade7346b96e38731a641d7343dd6a53d55083dd54eadfa5a1b38c6b"
dependencies = [ dependencies = [
"either", "either",
"env_home", "home",
"rustix 0.38.41", "rustix 0.38.41",
"winsafe", "winsafe",
] ]
@@ -1465,7 +1499,7 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [ dependencies = [
"windows-sys 0.59.0", "windows-sys 0.52.0",
] ]
[[package]] [[package]]
@@ -1594,9 +1628,9 @@ dependencies = [
[[package]] [[package]]
name = "zip" name = "zip"
version = "2.2.2" version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae9c1ea7b3a5e1f4b922ff856a129881167511563dc219869afe3787fc0c1a45" checksum = "dc5e4288ea4057ae23afc69a4472434a87a2495cafce6632fd1c4ec9f5cf3494"
dependencies = [ dependencies = [
"arbitrary", "arbitrary",
"crc32fast", "crc32fast",

View File

@@ -41,6 +41,7 @@ sha256 = "1"
sha1 = "0.10" sha1 = "0.10"
tempfile = "3.14" tempfile = "3.14"
chrono = "0.4" chrono = "0.4"
hole-punch = { git = "https://github.com/tiann/hole-punch" }
regex-lite = "0.1" regex-lite = "0.1"
[target.'cfg(any(target_os = "android", target_os = "linux"))'.dependencies] [target.'cfg(any(target_os = "android", target_os = "linux"))'.dependencies]

View File

@@ -1,13 +1,12 @@
use anyhow::{Ok, Result}; use anyhow::{Ok, Result};
use clap::Parser; use clap::Parser;
use std::path::{Path, PathBuf}; use std::path::PathBuf;
#[cfg(target_os = "android")] #[cfg(target_os = "android")]
use android_logger::Config; use android_logger::Config;
#[cfg(target_os = "android")] #[cfg(target_os = "android")]
use log::LevelFilter; use log::LevelFilter;
use crate::defs::KSUD_VERBOSE_LOG_FILE;
use crate::{apk_sign, assets, debug, defs, init_event, ksucalls, module, utils}; use crate::{apk_sign, assets, debug, defs, init_event, ksucalls, module, utils};
/// KernelSU-Next userspace cli /// KernelSU-Next userspace cli
@@ -16,9 +15,6 @@ use crate::{apk_sign, assets, debug, defs, init_event, ksucalls, module, utils};
struct Args { struct Args {
#[command(subcommand)] #[command(subcommand)]
command: Commands, command: Commands,
#[arg(short, long, default_value_t = cfg!(debug_assertions))]
verbose: bool,
} }
#[derive(clap::Subcommand, Debug)] #[derive(clap::Subcommand, Debug)]
@@ -165,6 +161,17 @@ enum Debug {
Mount, Mount,
/// Copy sparse file
Xcp {
/// source file
src: String,
/// destination file
dst: String,
/// punch hole
#[arg(short, long, default_value = "false")]
punch_hole: bool,
},
/// For testing /// For testing
Test, Test,
} }
@@ -230,6 +237,9 @@ enum Module {
/// list all modules /// list all modules
List, List,
/// Shrink module image size
Shrink,
} }
#[derive(clap::Subcommand, Debug)] #[derive(clap::Subcommand, Debug)]
@@ -291,10 +301,6 @@ pub fn run() -> Result<()> {
let cli = Args::parse(); let cli = Args::parse();
if !cli.verbose && !Path::new(KSUD_VERBOSE_LOG_FILE).exists() {
log::set_max_level(LevelFilter::Info);
}
log::info!("command: {:?}", cli.command); log::info!("command: {:?}", cli.command);
let result = match cli.command { let result = match cli.command {
@@ -315,6 +321,7 @@ pub fn run() -> Result<()> {
Module::Disable { id } => module::disable_module(&id), Module::Disable { id } => module::disable_module(&id),
Module::Action { id } => module::run_action(&id), Module::Action { id } => module::run_action(&id),
Module::List => module::list_modules(), Module::List => module::list_modules(),
Module::Shrink => module::shrink_ksu_images(),
} }
} }
Commands::Install { magiskboot } => utils::install(magiskboot), Commands::Install { magiskboot } => utils::install(magiskboot),
@@ -348,7 +355,15 @@ pub fn run() -> Result<()> {
Ok(()) Ok(())
} }
Debug::Su { global_mnt } => crate::su::grant_root(global_mnt), Debug::Su { global_mnt } => crate::su::grant_root(global_mnt),
Debug::Mount => init_event::mount_modules_systemlessly(), Debug::Mount => init_event::mount_modules_systemlessly(defs::MODULE_DIR),
Debug::Xcp {
src,
dst,
punch_hole,
} => {
utils::copy_sparse_file(src, dst, punch_hole)?;
Ok(())
}
Debug::Test => assets::ensure_binaries(false), Debug::Test => assets::ensure_binaries(false),
}, },

View File

@@ -10,7 +10,7 @@ pub const PROFILE_SELINUX_DIR: &str = concatcp!(PROFILE_DIR, "selinux/");
pub const PROFILE_TEMPLATE_DIR: &str = concatcp!(PROFILE_DIR, "templates/"); pub const PROFILE_TEMPLATE_DIR: &str = concatcp!(PROFILE_DIR, "templates/");
pub const KSURC_PATH: &str = concatcp!(WORKING_DIR, ".ksurc"); pub const KSURC_PATH: &str = concatcp!(WORKING_DIR, ".ksurc");
pub const KSU_MOUNT_SOURCE: &str = "KSU"; pub const KSU_OVERLAY_SOURCE: &str = "KSU";
pub const DAEMON_PATH: &str = concatcp!(ADB_DIR, "ksud"); pub const DAEMON_PATH: &str = concatcp!(ADB_DIR, "ksud");
pub const MAGISKBOOT_PATH: &str = concatcp!(BINARY_DIR, "magiskboot"); pub const MAGISKBOOT_PATH: &str = concatcp!(BINARY_DIR, "magiskboot");
@@ -18,11 +18,15 @@ pub const MAGISKBOOT_PATH: &str = concatcp!(BINARY_DIR, "magiskboot");
pub const DAEMON_LINK_PATH: &str = concatcp!(BINARY_DIR, "ksud"); pub const DAEMON_LINK_PATH: &str = concatcp!(BINARY_DIR, "ksud");
pub const MODULE_DIR: &str = concatcp!(ADB_DIR, "modules/"); pub const MODULE_DIR: &str = concatcp!(ADB_DIR, "modules/");
pub const MODULE_IMG: &str = concatcp!(WORKING_DIR, "modules.img");
pub const MODULE_UPDATE_IMG: &str = concatcp!(WORKING_DIR, "modules_update.img");
pub const MODULE_UPDATE_TMP_IMG: &str = concatcp!(WORKING_DIR, "update_tmp.img");
// warning: this directory should not change, or you need to change the code in module_installer.sh!!! // warning: this directory should not change, or you need to change the code in module_installer.sh!!!
pub const MODULE_UPDATE_DIR: &str = concatcp!(ADB_DIR, "modules_update/"); pub const MODULE_UPDATE_TMP_DIR: &str = concatcp!(ADB_DIR, "modules_update/");
pub const KSUD_VERBOSE_LOG_FILE: &str = concatcp!(ADB_DIR, "verbose"); pub const SYSTEM_RW_DIR: &str = concatcp!(MODULE_DIR, ".rw/");
pub const TEMP_DIR: &str = "/debug_ramdisk"; pub const TEMP_DIR: &str = "/debug_ramdisk";
pub const MODULE_WEB_DIR: &str = "webroot"; pub const MODULE_WEB_DIR: &str = "webroot";
@@ -31,7 +35,6 @@ pub const DISABLE_FILE_NAME: &str = "disable";
pub const UPDATE_FILE_NAME: &str = "update"; pub const UPDATE_FILE_NAME: &str = "update";
pub const REMOVE_FILE_NAME: &str = "remove"; pub const REMOVE_FILE_NAME: &str = "remove";
pub const SKIP_MOUNT_FILE_NAME: &str = "skip_mount"; pub const SKIP_MOUNT_FILE_NAME: &str = "skip_mount";
pub const MAGIC_MOUNT_WORK_DIR: &str = concatcp!(TEMP_DIR, "/workdir");
pub const VERSION_CODE: &str = include_str!(concat!(env!("OUT_DIR"), "/VERSION_CODE")); pub const VERSION_CODE: &str = include_str!(concat!(env!("OUT_DIR"), "/VERSION_CODE"));
pub const VERSION_NAME: &str = include_str!(concat!(env!("OUT_DIR"), "/VERSION_NAME")); pub const VERSION_NAME: &str = include_str!(concat!(env!("OUT_DIR"), "/VERSION_NAME"));

View File

@@ -1,10 +1,100 @@
use crate::defs::{KSU_MOUNT_SOURCE, TEMP_DIR}; use anyhow::{bail, Context, Result};
use crate::module::{handle_updated_modules, prune_modules};
use crate::{assets, defs, ksucalls, restorecon, utils};
use anyhow::{Context, Result};
use log::{info, warn}; use log::{info, warn};
use rustix::fs::{mount, MountFlags}; use std::{collections::HashMap, path::Path};
use std::path::Path;
use crate::module::prune_modules;
use crate::{
assets, defs, ksucalls, mount, restorecon,
utils::{self, ensure_clean_dir},
};
fn mount_partition(partition_name: &str, lowerdir: &Vec<String>) -> Result<()> {
if lowerdir.is_empty() {
warn!("partition: {partition_name} lowerdir is empty");
return Ok(());
}
let partition = format!("/{partition_name}");
// if /partition is a symlink and linked to /system/partition, then we don't need to overlay it separately
if Path::new(&partition).read_link().is_ok() {
warn!("partition: {partition} is a symlink");
return Ok(());
}
let mut workdir = None;
let mut upperdir = None;
let system_rw_dir = Path::new(defs::SYSTEM_RW_DIR);
if system_rw_dir.exists() {
workdir = Some(system_rw_dir.join(partition_name).join("workdir"));
upperdir = Some(system_rw_dir.join(partition_name).join("upperdir"));
}
mount::mount_overlay(&partition, lowerdir, workdir, upperdir)
}
pub fn mount_modules_systemlessly(module_dir: &str) -> Result<()> {
// construct overlay mount params
let dir = std::fs::read_dir(module_dir);
let Ok(dir) = dir else {
bail!("open {} failed", defs::MODULE_DIR);
};
let mut system_lowerdir: Vec<String> = Vec::new();
let partition = vec!["vendor", "product", "system_ext", "odm", "oem"];
let mut partition_lowerdir: HashMap<String, Vec<String>> = HashMap::new();
for ele in &partition {
partition_lowerdir.insert((*ele).to_string(), Vec::new());
}
for entry in dir.flatten() {
let module = entry.path();
if !module.is_dir() {
continue;
}
let disabled = module.join(defs::DISABLE_FILE_NAME).exists();
if disabled {
info!("module: {} is disabled, ignore!", module.display());
continue;
}
let skip_mount = module.join(defs::SKIP_MOUNT_FILE_NAME).exists();
if skip_mount {
info!("module: {} skip_mount exist, skip!", module.display());
continue;
}
let module_system = Path::new(&module).join("system");
if module_system.is_dir() {
system_lowerdir.push(format!("{}", module_system.display()));
}
for part in &partition {
// if /partition is a mountpoint, we would move it to $MODPATH/$partition when install
// otherwise it must be a symlink and we don't need to overlay!
let part_path = Path::new(&module).join(part);
if part_path.is_dir() {
if let Some(v) = partition_lowerdir.get_mut(*part) {
v.push(format!("{}", part_path.display()));
}
}
}
}
// mount /system first
if let Err(e) = mount_partition("system", &system_lowerdir) {
warn!("mount system failed: {:#}", e);
}
// mount other partitions
for (k, v) in partition_lowerdir {
if let Err(e) = mount_partition(&k, &v) {
warn!("mount {k} failed: {:#}", e);
}
}
Ok(())
}
pub fn on_post_data_fs() -> Result<()> { pub fn on_post_data_fs() -> Result<()> {
ksucalls::report_post_fs_data(); ksucalls::report_post_fs_data();
@@ -21,9 +111,11 @@ pub fn on_post_data_fs() -> Result<()> {
return Ok(()); return Ok(());
} }
let safe_mode = utils::is_safe_mode(); let safe_mode = crate::utils::is_safe_mode();
if safe_mode { if safe_mode {
// we should still mount modules.img to `/data/adb/modules` in safe mode
// becuase we may need to operate the module dir in safe mode
warn!("safe mode, skip common post-fs-data.d scripts"); warn!("safe mode, skip common post-fs-data.d scripts");
} else { } else {
// Then exec common post-fs-data scripts // Then exec common post-fs-data scripts
@@ -32,8 +124,43 @@ pub fn on_post_data_fs() -> Result<()> {
} }
} }
let module_update_img = defs::MODULE_UPDATE_IMG;
let module_img = defs::MODULE_IMG;
let module_dir = defs::MODULE_DIR;
let module_update_flag = Path::new(defs::WORKING_DIR).join(defs::UPDATE_FILE_NAME);
// modules.img is the default image
let mut target_update_img = &module_img;
// we should clean the module mount point if it exists
ensure_clean_dir(module_dir)?;
assets::ensure_binaries(true).with_context(|| "Failed to extract bin assets")?; assets::ensure_binaries(true).with_context(|| "Failed to extract bin assets")?;
if Path::new(module_update_img).exists() {
if module_update_flag.exists() {
// if modules_update.img exists, and the the flag indicate this is an update
// this make sure that if the update failed, we will fallback to the old image
// if we boot succeed, we will rename the modules_update.img to modules.img #on_boot_complete
target_update_img = &module_update_img;
// And we should delete the flag immediately
std::fs::remove_file(module_update_flag)?;
} else {
// if modules_update.img exists, but the flag not exist, we should delete it
std::fs::remove_file(module_update_img)?;
}
}
if !Path::new(target_update_img).exists() {
return Ok(());
}
// we should always mount the module.img to module dir
// becuase we may need to operate the module dir in safe mode
info!("mount module image: {target_update_img} to {module_dir}");
mount::AutoMountExt4::try_new(target_update_img, module_dir, false)
.with_context(|| "mount module image failed".to_string())?;
// tell kernel that we've mount the module, so that it can do some optimization // tell kernel that we've mount the module, so that it can do some optimization
ksucalls::report_module_mounted(); ksucalls::report_module_mounted();
@@ -50,10 +177,6 @@ pub fn on_post_data_fs() -> Result<()> {
warn!("prune modules failed: {}", e); warn!("prune modules failed: {}", e);
} }
if let Err(e) = handle_updated_modules() {
warn!("handle updated modules failed: {}", e);
}
if let Err(e) = restorecon::restorecon() { if let Err(e) = restorecon::restorecon() {
warn!("restorecon failed: {}", e); warn!("restorecon failed: {}", e);
} }
@@ -68,7 +191,7 @@ pub fn on_post_data_fs() -> Result<()> {
} }
// mount temp dir // mount temp dir
if let Err(e) = mount(KSU_MOUNT_SOURCE, TEMP_DIR, "tmpfs", MountFlags::empty(), "") { if let Err(e) = mount::mount_tmpfs(defs::TEMP_DIR) {
warn!("do temp dir mount failed: {}", e); warn!("do temp dir mount failed: {}", e);
} }
@@ -83,23 +206,15 @@ pub fn on_post_data_fs() -> Result<()> {
warn!("load system.prop failed: {}", e); warn!("load system.prop failed: {}", e);
} }
// mount module systemlessly by magic mount // mount module systemlessly by overlay
if let Err(e) = mount_modules_systemlessly() { if let Err(e) = mount_modules_systemlessly(module_dir) {
warn!("do systemless mount failed: {}", e); warn!("do systemless mount failed: {}", e);
} }
run_stage("post-mount", true); run_stage("post-mount", true);
Ok(()) std::env::set_current_dir("/").with_context(|| "failed to chdir to /")?;
}
#[cfg(target_os = "android")]
pub fn mount_modules_systemlessly() -> Result<()> {
crate::magic_mount::magic_mount()
}
#[cfg(not(target_os = "android"))]
pub fn mount_modules_systemlessly() -> Result<()> {
Ok(()) Ok(())
} }
@@ -134,6 +249,17 @@ pub fn on_services() -> Result<()> {
pub fn on_boot_completed() -> Result<()> { pub fn on_boot_completed() -> Result<()> {
ksucalls::report_boot_complete(); ksucalls::report_boot_complete();
info!("on_boot_completed triggered!"); info!("on_boot_completed triggered!");
let module_update_img = Path::new(defs::MODULE_UPDATE_IMG);
let module_img = Path::new(defs::MODULE_IMG);
if module_update_img.exists() {
// this is a update and we successfully booted
if std::fs::rename(module_update_img, module_img).is_err() {
warn!("Failed to rename images, copy it now.",);
utils::copy_sparse_file(module_update_img, module_img, false)
.with_context(|| "Failed to copy images")?;
std::fs::remove_file(module_update_img).with_context(|| "Failed to remove image!")?;
}
}
run_stage("boot-completed", false); run_stage("boot-completed", false);

View File

@@ -85,7 +85,7 @@ setup_flashable() {
$BOOTMODE && return $BOOTMODE && return
if [ -z $OUTFD ] || readlink /proc/$$/fd/$OUTFD | grep -q /tmp; then if [ -z $OUTFD ] || readlink /proc/$$/fd/$OUTFD | grep -q /tmp; then
# We will have to manually find out OUTFD # We will have to manually find out OUTFD
for FD in /proc/$$/fd/*; do for FD in `ls /proc/$$/fd`; do
if readlink /proc/$$/fd/$FD | grep -q pipe; then if readlink /proc/$$/fd/$FD | grep -q pipe; then
if ps | grep -v grep | grep -qE " 3 $FD |status_fd=$FD"; then if ps | grep -v grep | grep -qE " 3 $FD |status_fd=$FD"; then
OUTFD=$FD OUTFD=$FD
@@ -302,16 +302,18 @@ is_legacy_script() {
} }
handle_partition() { handle_partition() {
PARTITION="$1" # if /system/vendor is a symlink, we need to move it out of $MODPATH/system, otherwise it will be overlayed
REQUIRE_SYMLINK="$2" # if /system/vendor is a normal directory, it is ok to overlay it and we don't need to overlay it separately.
if [ ! -e "$MODPATH/system/$PARTITION" ]; then if [ ! -e $MODPATH/system/$1 ]; then
# no partition found # no partition found
return; return;
fi fi
if [ "$REQUIRE_SYMLINK" = "false" ] || [ -L "/system/$PARTITION" ] && [ "$(readlink -f "/system/$PARTITION")" = "/$PARTITION" ]; then if [ -L "/system/$1" ] && [ "$(readlink -f /system/$1)" = "/$1" ]; then
ui_print "- Handle partition /$PARTITION" ui_print "- Handle partition /$1"
ln -sf "./system/$PARTITION" "$MODPATH/$PARTITION" # we create a symlink if module want to access $MODPATH/system/$1
# but it doesn't always work(ie. write it in post-fs-data.sh would fail because it is readonly)
mv -f $MODPATH/system/$1 $MODPATH/$1 && ln -sf ../$1 $MODPATH/system/$1
fi fi
} }
@@ -389,23 +391,22 @@ install_module() {
[ -f $MODPATH/customize.sh ] && . $MODPATH/customize.sh [ -f $MODPATH/customize.sh ] && . $MODPATH/customize.sh
fi fi
handle_partition vendor true
handle_partition system_ext true
handle_partition product true
handle_partition odm false
# Handle replace folders # Handle replace folders
for TARGET in $REPLACE; do for TARGET in $REPLACE; do
ui_print "- Replace target: $TARGET" ui_print "- Replace target: $TARGET"
mark_replace "$MODPATH$TARGET" mark_replace $MODPATH$TARGET
done done
# Handle remove files # Handle remove files
for TARGET in $REMOVE; do for TARGET in $REMOVE; do
ui_print "- Remove target: $TARGET" ui_print "- Remove target: $TARGET"
mark_remove "$MODPATH$TARGET" mark_remove $MODPATH$TARGET
done done
handle_partition vendor
handle_partition system_ext
handle_partition product
if $BOOTMODE; then if $BOOTMODE; then
mktouch $NVBASE/modules/$MODID/update mktouch $NVBASE/modules/$MODID/update
rm -rf $NVBASE/modules/$MODID/remove 2>/dev/null rm -rf $NVBASE/modules/$MODID/remove 2>/dev/null

View File

@@ -1,434 +0,0 @@
use crate::defs::{
DISABLE_FILE_NAME, KSU_MOUNT_SOURCE, MAGIC_MOUNT_WORK_DIR, MODULE_DIR, SKIP_MOUNT_FILE_NAME,
};
use crate::magic_mount::NodeFileType::{Directory, RegularFile, Symlink, Whiteout};
use crate::restorecon::{lgetfilecon, lsetfilecon};
use crate::utils::ensure_dir_exists;
use anyhow::{bail, Context, Result};
use extattr::lgetxattr;
use rustix::fs::{
bind_mount, chmod, chown, mount, move_mount, unmount, Gid, MetadataExt, Mode, MountFlags,
MountPropagationFlags, Uid, UnmountFlags,
};
use rustix::mount::mount_change;
use rustix::path::Arg;
use std::cmp::PartialEq;
use std::collections::hash_map::Entry;
use std::collections::HashMap;
use std::fs;
use std::fs::{create_dir, create_dir_all, read_dir, read_link, DirEntry, FileType};
use std::os::unix::fs::{symlink, FileTypeExt};
use std::path::{Path, PathBuf};
const REPLACE_DIR_XATTR: &str = "trusted.overlay.opaque";
#[derive(PartialEq, Eq, Hash, Clone, Debug)]
enum NodeFileType {
RegularFile,
Directory,
Symlink,
Whiteout,
}
impl NodeFileType {
fn from_file_type(file_type: FileType) -> Option<Self> {
if file_type.is_file() {
Some(RegularFile)
} else if file_type.is_dir() {
Some(Directory)
} else if file_type.is_symlink() {
Some(Symlink)
} else {
None
}
}
}
#[derive(Debug)]
struct Node {
name: String,
file_type: NodeFileType,
children: HashMap<String, Node>,
// the module that owned this node
module_path: Option<PathBuf>,
replace: bool,
skip: bool,
}
impl Node {
fn collect_module_files<T: AsRef<Path>>(&mut self, module_dir: T) -> Result<bool> {
let dir = module_dir.as_ref();
let mut has_file = false;
for entry in dir.read_dir()?.flatten() {
let name = entry.file_name().to_string_lossy().to_string();
let node = match self.children.entry(name.clone()) {
Entry::Occupied(o) => Some(o.into_mut()),
Entry::Vacant(v) => Self::new_module(&name, &entry).map(|it| v.insert(it)),
};
if let Some(node) = node {
has_file |= if node.file_type == Directory {
node.collect_module_files(dir.join(&node.name))? || node.replace
} else {
true
}
}
}
Ok(has_file)
}
fn new_root<T: ToString>(name: T) -> Self {
Node {
name: name.to_string(),
file_type: Directory,
children: Default::default(),
module_path: None,
replace: false,
skip: false,
}
}
fn new_module<T: ToString>(name: T, entry: &DirEntry) -> Option<Self> {
if let Ok(metadata) = entry.metadata() {
let path = entry.path();
let file_type = if metadata.file_type().is_char_device() && metadata.rdev() == 0 {
Some(Whiteout)
} else {
NodeFileType::from_file_type(metadata.file_type())
};
if let Some(file_type) = file_type {
let mut replace = false;
if file_type == Directory {
if let Ok(v) = lgetxattr(&path, REPLACE_DIR_XATTR) {
if String::from_utf8_lossy(&v) == "y" {
replace = true;
}
}
}
return Some(Node {
name: name.to_string(),
file_type,
children: Default::default(),
module_path: Some(path),
replace,
skip: false,
});
}
}
None
}
}
fn collect_module_files() -> Result<Option<Node>> {
let mut root = Node::new_root("");
let mut system = Node::new_root("system");
let module_root = Path::new(MODULE_DIR);
let mut has_file = false;
for entry in module_root.read_dir()?.flatten() {
if !entry.file_type()?.is_dir() {
continue;
}
if entry.path().join(DISABLE_FILE_NAME).exists()
|| entry.path().join(SKIP_MOUNT_FILE_NAME).exists()
{
continue;
}
let mod_system = entry.path().join("system");
if !mod_system.is_dir() {
continue;
}
log::debug!("collecting {}", entry.path().display());
has_file |= system.collect_module_files(&mod_system)?;
}
if has_file {
for (partition, require_symlink) in [
("vendor", true),
("system_ext", true),
("product", true),
("odm", false),
] {
let path_of_root = Path::new("/").join(partition);
let path_of_system = Path::new("/system").join(partition);
if path_of_root.is_dir() && (!require_symlink || path_of_system.is_symlink()) {
let name = partition.to_string();
if let Some(node) = system.children.remove(&name) {
root.children.insert(name, node);
}
}
}
root.children.insert("system".to_string(), system);
Ok(Some(root))
} else {
Ok(None)
}
}
fn clone_symlink<Src: AsRef<Path>, Dst: AsRef<Path>>(src: Src, dst: Dst) -> Result<()> {
let src_symlink = read_link(src.as_ref())?;
symlink(&src_symlink, dst.as_ref())?;
lsetfilecon(dst.as_ref(), lgetfilecon(src.as_ref())?.as_str())?;
log::debug!(
"clone symlink {} -> {}({})",
dst.as_ref().display(),
dst.as_ref().display(),
src_symlink.display()
);
Ok(())
}
fn mount_mirror<P: AsRef<Path>, WP: AsRef<Path>>(
path: P,
work_dir_path: WP,
entry: &DirEntry,
) -> Result<()> {
let path = path.as_ref().join(entry.file_name());
let work_dir_path = work_dir_path.as_ref().join(entry.file_name());
let file_type = entry.file_type()?;
if file_type.is_file() {
log::debug!(
"mount mirror file {} -> {}",
path.display(),
work_dir_path.display()
);
fs::File::create(&work_dir_path)?;
bind_mount(&path, &work_dir_path)?;
} else if file_type.is_dir() {
log::debug!(
"mount mirror dir {} -> {}",
path.display(),
work_dir_path.display()
);
create_dir(&work_dir_path)?;
let metadata = entry.metadata()?;
chmod(&work_dir_path, Mode::from_raw_mode(metadata.mode()))?;
unsafe {
chown(
&work_dir_path,
Some(Uid::from_raw(metadata.uid())),
Some(Gid::from_raw(metadata.gid())),
)?;
}
lsetfilecon(&work_dir_path, lgetfilecon(&path)?.as_str())?;
for entry in read_dir(&path)?.flatten() {
mount_mirror(&path, &work_dir_path, &entry)?;
}
} else if file_type.is_symlink() {
log::debug!(
"create mirror symlink {} -> {}",
path.display(),
work_dir_path.display()
);
clone_symlink(&path, &work_dir_path)?;
}
Ok(())
}
fn do_magic_mount<P: AsRef<Path>, WP: AsRef<Path>>(
path: P,
work_dir_path: WP,
current: Node,
has_tmpfs: bool,
) -> Result<()> {
let mut current = current;
let path = path.as_ref().join(&current.name);
let work_dir_path = work_dir_path.as_ref().join(&current.name);
match current.file_type {
RegularFile => {
let target_path = if has_tmpfs {
fs::File::create(&work_dir_path)?;
&work_dir_path
} else {
&path
};
if let Some(module_path) = &current.module_path {
log::debug!(
"mount module file {} -> {}",
module_path.display(),
work_dir_path.display()
);
bind_mount(module_path, target_path)?;
} else {
bail!("cannot mount root file {}!", path.display());
}
}
Symlink => {
if let Some(module_path) = &current.module_path {
log::debug!(
"create module symlink {} -> {}",
module_path.display(),
work_dir_path.display()
);
clone_symlink(module_path, &work_dir_path)?;
} else {
bail!("cannot mount root symlink {}!", path.display());
}
}
Directory => {
let mut create_tmpfs = !has_tmpfs && current.replace && current.module_path.is_some();
if !has_tmpfs && !create_tmpfs {
for it in &mut current.children {
let (name, node) = it;
let real_path = path.join(name);
let need = match node.file_type {
Symlink => true,
Whiteout => real_path.exists(),
_ => {
if let Ok(metadata) = real_path.metadata() {
let file_type = NodeFileType::from_file_type(metadata.file_type())
.unwrap_or(Whiteout);
file_type != node.file_type || file_type == Symlink
} else {
// real path not exists
true
}
}
};
if need {
if current.module_path.is_none() {
log::error!(
"cannot create tmpfs on {}, ignore: {name}",
path.display()
);
node.skip = true;
continue;
}
create_tmpfs = true;
break;
}
}
}
let has_tmpfs = has_tmpfs || create_tmpfs;
if has_tmpfs {
log::debug!(
"creating tmpfs skeleton for {} at {}",
path.display(),
work_dir_path.display()
);
create_dir_all(&work_dir_path)?;
let (metadata, path) = if path.exists() {
(path.metadata()?, &path)
} else if let Some(module_path) = &current.module_path {
(module_path.metadata()?, module_path)
} else {
bail!("cannot mount root dir {}!", path.display());
};
chmod(&work_dir_path, Mode::from_raw_mode(metadata.mode()))?;
unsafe {
chown(
&work_dir_path,
Some(Uid::from_raw(metadata.uid())),
Some(Gid::from_raw(metadata.gid())),
)?;
}
lsetfilecon(&work_dir_path, lgetfilecon(path)?.as_str())?;
}
if create_tmpfs {
log::debug!(
"creating tmpfs for {} at {}",
path.display(),
work_dir_path.display()
);
bind_mount(&work_dir_path, &work_dir_path).context("bind self")?;
}
if path.exists() && !current.replace {
for entry in path.read_dir()?.flatten() {
let name = entry.file_name().to_string_lossy().to_string();
let result = if let Some(node) = current.children.remove(&name) {
if node.skip {
continue;
}
do_magic_mount(&path, &work_dir_path, node, has_tmpfs)
.with_context(|| format!("magic mount {}/{name}", path.display()))
} else if has_tmpfs {
mount_mirror(&path, &work_dir_path, &entry)
.with_context(|| format!("mount mirror {}/{name}", path.display()))
} else {
Ok(())
};
if let Err(e) = result {
if has_tmpfs {
return Err(e);
} else {
log::error!("mount child {}/{name} failed: {}", path.display(), e);
}
}
}
}
if current.replace {
if current.module_path.is_none() {
bail!(
"dir {} is declared as replaced but it is root!",
path.display()
);
} else {
log::debug!("dir {} is replaced", path.display());
}
}
for (name, node) in current.children.into_iter() {
if node.skip {
continue;
}
if let Err(e) = do_magic_mount(&path, &work_dir_path, node, has_tmpfs)
.with_context(|| format!("magic mount {}/{name}", path.display()))
{
if has_tmpfs {
return Err(e);
} else {
log::error!("mount child {}/{name} failed: {}", path.display(), e);
}
}
}
if create_tmpfs {
log::debug!(
"moving tmpfs {} -> {}",
work_dir_path.display(),
path.display()
);
move_mount(&work_dir_path, &path).context("move self")?;
mount_change(&path, MountPropagationFlags::PRIVATE).context("make self private")?;
}
}
Whiteout => {
log::debug!("file {} is removed", path.display());
}
}
Ok(())
}
pub fn magic_mount() -> Result<()> {
if let Some(root) = collect_module_files()? {
log::debug!("collected: {:#?}", root);
let tmp_dir = PathBuf::from(MAGIC_MOUNT_WORK_DIR);
ensure_dir_exists(&tmp_dir)?;
mount(KSU_MOUNT_SOURCE, &tmp_dir, "tmpfs", MountFlags::empty(), "").context("mount tmp")?;
mount_change(&tmp_dir, MountPropagationFlags::PRIVATE).context("make tmp private")?;
let result = do_magic_mount("/", &tmp_dir, root, false);
if let Err(e) = unmount(&tmp_dir, UnmountFlags::DETACH) {
log::error!("failed to unmount tmp {}", e);
}
fs::remove_dir(tmp_dir).ok();
result
} else {
log::info!("no modules to mount, skipping!");
Ok(())
}
}

View File

@@ -6,9 +6,8 @@ mod debug;
mod defs; mod defs;
mod init_event; mod init_event;
mod ksucalls; mod ksucalls;
#[cfg(target_os = "android")]
mod magic_mount;
mod module; mod module;
mod mount;
mod profile; mod profile;
mod restorecon; mod restorecon;
mod sepolicy; mod sepolicy;

View File

@@ -1,9 +1,9 @@
#[allow(clippy::wildcard_imports)] #[allow(clippy::wildcard_imports)]
use crate::utils::*; use crate::utils::*;
use crate::{ use crate::{
assets, defs, ksucalls, assets, defs, ksucalls, mount,
restorecon::{restore_syscon, setsyscon}, restorecon::{restore_syscon, setsyscon},
sepolicy, sepolicy, utils,
}; };
use anyhow::{anyhow, bail, ensure, Context, Result}; use anyhow::{anyhow, bail, ensure, Context, Result};
@@ -12,21 +12,20 @@ use is_executable::is_executable;
use java_properties::PropertiesIter; use java_properties::PropertiesIter;
use log::{info, warn}; use log::{info, warn};
use std::fs::{copy, rename}; use std::fs::OpenOptions;
use std::{ use std::{
collections::HashMap, collections::HashMap,
env::var as env_var, env::var as env_var,
fs::{remove_dir_all, remove_file, set_permissions, File, Permissions}, fs::{remove_dir_all, remove_file, set_permissions, File, Permissions},
io::Cursor, io::Cursor,
path::{Path, PathBuf}, path::{Path, PathBuf},
process::Command, process::{Command, Stdio},
str::FromStr, str::FromStr,
}; };
use zip_extensions::zip_extract_file_to_memory; use zip_extensions::zip_extract_file_to_memory;
use crate::defs::{MODULE_DIR, MODULE_UPDATE_DIR, UPDATE_FILE_NAME};
#[cfg(unix)] #[cfg(unix)]
use std::os::unix::{prelude::PermissionsExt, process::CommandExt}; use std::os::unix::{fs::MetadataExt, prelude::PermissionsExt, process::CommandExt};
const INSTALLER_CONTENT: &str = include_str!("./installer.sh"); const INSTALLER_CONTENT: &str = include_str!("./installer.sh");
const INSTALL_MODULE_SCRIPT: &str = concatcp!( const INSTALL_MODULE_SCRIPT: &str = concatcp!(
@@ -76,34 +75,24 @@ fn ensure_boot_completed() -> Result<()> {
Ok(()) Ok(())
} }
fn mark_module_state(module: &str, flag_file: &str, create: bool) -> Result<()> { fn mark_update() -> Result<()> {
let module_state_file = Path::new(MODULE_DIR).join(module).join(flag_file); ensure_file_exists(concatcp!(defs::WORKING_DIR, defs::UPDATE_FILE_NAME))
if create { }
fn mark_module_state(module: &str, flag_file: &str, create_or_delete: bool) -> Result<()> {
let module_state_file = Path::new(defs::MODULE_DIR).join(module).join(flag_file);
if create_or_delete {
ensure_file_exists(module_state_file) ensure_file_exists(module_state_file)
} else { } else {
if module_state_file.exists() { if module_state_file.exists() {
remove_file(module_state_file)?; std::fs::remove_file(module_state_file)?;
} }
Ok(()) Ok(())
} }
} }
#[derive(PartialEq, Eq)] fn foreach_module(active_only: bool, mut f: impl FnMut(&Path) -> Result<()>) -> Result<()> {
enum ModuleType { let modules_dir = Path::new(defs::MODULE_DIR);
All,
Active,
Updated,
}
fn foreach_module(module_type: ModuleType, mut f: impl FnMut(&Path) -> Result<()>) -> Result<()> {
let modules_dir = Path::new(match module_type {
ModuleType::Updated => MODULE_UPDATE_DIR,
_ => defs::MODULE_DIR,
});
if !modules_dir.is_dir() {
warn!("{} is not a directory, skip", modules_dir.display());
return Ok(());
}
let dir = std::fs::read_dir(modules_dir)?; let dir = std::fs::read_dir(modules_dir)?;
for entry in dir.flatten() { for entry in dir.flatten() {
let path = entry.path(); let path = entry.path();
@@ -112,11 +101,11 @@ fn foreach_module(module_type: ModuleType, mut f: impl FnMut(&Path) -> Result<()
continue; continue;
} }
if module_type == ModuleType::Active && path.join(defs::DISABLE_FILE_NAME).exists() { if active_only && path.join(defs::DISABLE_FILE_NAME).exists() {
info!("{} is disabled, skip", path.display()); info!("{} is disabled, skip", path.display());
continue; continue;
} }
if module_type == ModuleType::Active && path.join(defs::REMOVE_FILE_NAME).exists() { if active_only && path.join(defs::REMOVE_FILE_NAME).exists() {
warn!("{} is removed, skip", path.display()); warn!("{} is removed, skip", path.display());
continue; continue;
} }
@@ -128,7 +117,27 @@ fn foreach_module(module_type: ModuleType, mut f: impl FnMut(&Path) -> Result<()
} }
fn foreach_active_module(f: impl FnMut(&Path) -> Result<()>) -> Result<()> { fn foreach_active_module(f: impl FnMut(&Path) -> Result<()>) -> Result<()> {
foreach_module(ModuleType::Active, f) foreach_module(true, f)
}
fn check_image(img: &str) -> Result<()> {
let result = Command::new("e2fsck")
.args(["-yf", img])
.stdout(Stdio::piped())
.status()
.with_context(|| format!("Failed to exec e2fsck {img}"))?;
let code = result.code();
// 0 or 1 is ok
// 0: no error
// 1: file system errors corrected
// https://man7.org/linux/man-pages/man8/e2fsck.8.html
// ensure!(
// code == Some(0) || code == Some(1),
// "Failed to check image, e2fsck exit code: {}",
// code.unwrap_or(-1)
// );
info!("e2fsck exit code: {}", code.unwrap_or(-1));
Ok(())
} }
pub fn load_sepolicy_rule() -> Result<()> { pub fn load_sepolicy_rule() -> Result<()> {
@@ -247,153 +256,370 @@ pub fn load_system_prop() -> Result<()> {
} }
pub fn prune_modules() -> Result<()> { pub fn prune_modules() -> Result<()> {
foreach_module(ModuleType::All, |module| { foreach_module(false, |module| {
if module.join(defs::REMOVE_FILE_NAME).exists() { remove_file(module.join(defs::UPDATE_FILE_NAME)).ok();
info!("remove module: {}", module.display());
let uninstaller = module.join("uninstall.sh"); if !module.join(defs::REMOVE_FILE_NAME).exists() {
if uninstaller.exists() { return Ok(());
if let Err(e) = exec_script(uninstaller, true) {
warn!("Failed to exec uninstaller: {}", e);
}
}
if let Err(e) = remove_dir_all(module) {
warn!("Failed to remove {}: {}", module.display(), e);
}
} else {
remove_file(module.join(defs::UPDATE_FILE_NAME)).ok();
} }
info!("remove module: {}", module.display());
let uninstaller = module.join("uninstall.sh");
if uninstaller.exists() {
if let Err(e) = exec_script(uninstaller, true) {
warn!("Failed to exec uninstaller: {}", e);
}
}
if let Err(e) = remove_dir_all(module) {
warn!("Failed to remove {}: {}", module.display(), e);
}
Ok(()) Ok(())
})?; })?;
Ok(()) Ok(())
} }
pub fn handle_updated_modules() -> Result<()> { fn create_module_image(image: &str, image_size: u64, journal_size: u64) -> Result<()> {
let modules_root = Path::new(MODULE_DIR); File::create(image)
foreach_module(ModuleType::Updated, |module| { .context("Failed to create ext4 image file")?
if !module.is_dir() { .set_len(image_size)
return Ok(()); .context("Failed to truncate ext4 image")?;
}
if let Some(name) = module.file_name() { // format the img to ext4 filesystem
let old_dir = modules_root.join(name); let result = Command::new("mkfs.ext4")
if old_dir.exists() { .arg("-J")
if let Err(e) = remove_dir_all(&old_dir) { .arg(format!("size={journal_size}"))
log::error!("Failed to remove old {}: {}", old_dir.display(), e); .arg(image)
} .stdout(Stdio::piped())
} .output()?;
if let Err(e) = rename(module, &old_dir) { ensure!(
log::error!("Failed to move new module {}: {}", module.display(), e); result.status.success(),
"Failed to format ext4 image: {}",
String::from_utf8(result.stderr).unwrap()
);
check_image(image)?;
Ok(())
}
fn _install_module(zip: &str) -> Result<()> {
ensure_boot_completed()?;
// print banner
println!(include_str!("banner"));
assets::ensure_binaries(false).with_context(|| "Failed to extract assets")?;
// first check if workding dir is usable
ensure_dir_exists(defs::WORKING_DIR).with_context(|| "Failed to create working dir")?;
ensure_dir_exists(defs::BINARY_DIR).with_context(|| "Failed to create bin dir")?;
// read the module_id from zip, if faild if will return early.
let mut buffer: Vec<u8> = Vec::new();
let entry_path = PathBuf::from_str("module.prop")?;
let zip_path = PathBuf::from_str(zip)?;
let zip_path = zip_path.canonicalize()?;
zip_extract_file_to_memory(&zip_path, &entry_path, &mut buffer)?;
let mut module_prop = HashMap::new();
PropertiesIter::new_with_encoding(Cursor::new(buffer), encoding_rs::UTF_8).read_into(
|k, v| {
module_prop.insert(k, v);
},
)?;
info!("module prop: {:?}", module_prop);
let Some(module_id) = module_prop.get("id") else {
bail!("module id not found in module.prop!");
};
let module_id = module_id.trim();
let modules_img = Path::new(defs::MODULE_IMG);
let modules_update_img = Path::new(defs::MODULE_UPDATE_IMG);
let module_update_tmp_dir = defs::MODULE_UPDATE_TMP_DIR;
let modules_img_exist = modules_img.exists();
let modules_update_img_exist = modules_update_img.exists();
// prepare the tmp module img
let tmp_module_img = defs::MODULE_UPDATE_TMP_IMG;
let tmp_module_path = Path::new(tmp_module_img);
if tmp_module_path.exists() {
std::fs::remove_file(tmp_module_path)?;
}
let zip_uncompressed_size = get_zip_uncompressed_size(zip)?;
info!(
"zip uncompressed size: {}",
humansize::format_size(zip_uncompressed_size, humansize::DECIMAL)
);
println!("- Preparing image");
println!(
"- Module size: {}",
humansize::format_size(zip_uncompressed_size, humansize::DECIMAL)
);
let sparse_image_size = 1 << 34; // 16GB
let journal_size = 8; // 8M
if !modules_img_exist && !modules_update_img_exist {
// if no modules and modules_update, it is brand new installation, we should create a new img
// create a tmp module img and mount it to modules_update
info!("Creating brand new module image");
create_module_image(tmp_module_img, sparse_image_size, journal_size)?;
} else if modules_update_img_exist {
// modules_update.img exists, we should use it as tmp img
info!("Using existing modules_update.img as tmp image");
utils::copy_sparse_file(modules_update_img, tmp_module_img, true).with_context(|| {
format!(
"Failed to copy {} to {}",
modules_update_img.display(),
tmp_module_img
)
})?;
} else {
// modules.img exists, we should use it as tmp img
info!("Using existing modules.img as tmp image");
#[cfg(unix)]
let blksize = std::fs::metadata(defs::MODULE_DIR)?.blksize();
#[cfg(not(unix))]
let blksize = 0;
// legacy image, it's block size is 1024 with unlimited journal size
if blksize == 1024 {
println!("- Legacy image, migrating to new format, please be patient...");
create_module_image(tmp_module_img, sparse_image_size, journal_size)?;
let _dontdrop =
mount::AutoMountExt4::try_new(tmp_module_img, module_update_tmp_dir, true)
.with_context(|| format!("Failed to mount {tmp_module_img}"))?;
utils::copy_module_files(defs::MODULE_DIR, module_update_tmp_dir)
.with_context(|| "Failed to migrate module files".to_string())?;
} else {
utils::copy_sparse_file(modules_img, tmp_module_img, true)
.with_context(|| "Failed to copy module image".to_string())?;
if std::fs::metadata(tmp_module_img)?.len() < sparse_image_size {
// truncate the file to new size
OpenOptions::new()
.write(true)
.open(tmp_module_img)
.context("Failed to open ext4 image")?
.set_len(sparse_image_size)
.context("Failed to truncate ext4 image")?;
// resize the image to new size
check_image(tmp_module_img)?;
Command::new("resize2fs")
.arg(tmp_module_img)
.stdout(Stdio::piped())
.status()?;
} }
} }
Ok(()) }
})?;
// ensure modules_update exists
ensure_dir_exists(module_update_tmp_dir)?;
// mount the modules_update.img to mountpoint
println!("- Mounting image");
let _dontdrop = mount::AutoMountExt4::try_new(tmp_module_img, module_update_tmp_dir, true)?;
info!("mounted {} to {}", tmp_module_img, module_update_tmp_dir);
setsyscon(module_update_tmp_dir)?;
let module_dir = format!("{module_update_tmp_dir}/{module_id}");
ensure_clean_dir(&module_dir)?;
info!("module dir: {}", module_dir);
// unzip the image and move it to modules_update/<id> dir
let file = File::open(zip)?;
let mut archive = zip::ZipArchive::new(file)?;
archive.extract(&module_dir)?;
// set permission and selinux context for $MOD/system
let module_system_dir = PathBuf::from(module_dir).join("system");
if module_system_dir.exists() {
#[cfg(unix)]
set_permissions(&module_system_dir, Permissions::from_mode(0o755))?;
restore_syscon(&module_system_dir)?;
}
exec_install_script(zip)?;
info!("rename {tmp_module_img} to {}", defs::MODULE_UPDATE_IMG);
// all done, rename the tmp image to modules_update.img
if std::fs::rename(tmp_module_img, defs::MODULE_UPDATE_IMG).is_err() {
warn!("Rename image failed, try copy it.");
utils::copy_sparse_file(tmp_module_img, defs::MODULE_UPDATE_IMG, true)
.with_context(|| "Failed to copy image.".to_string())?;
let _ = std::fs::remove_file(tmp_module_img);
}
mark_update()?;
info!("Module install successfully!");
Ok(()) Ok(())
} }
pub fn install_module(zip: &str) -> Result<()> { pub fn install_module(zip: &str) -> Result<()> {
fn inner(zip: &str) -> Result<()> { let result = _install_module(zip);
ensure_boot_completed()?;
// print banner
println!(include_str!("banner"));
assets::ensure_binaries(false).with_context(|| "Failed to extract assets")?;
// first check if working dir is usable
ensure_dir_exists(defs::WORKING_DIR).with_context(|| "Failed to create working dir")?;
ensure_dir_exists(defs::BINARY_DIR).with_context(|| "Failed to create bin dir")?;
// read the module_id from zip, if failed it will return early.
let mut buffer: Vec<u8> = Vec::new();
let entry_path = PathBuf::from_str("module.prop")?;
let zip_path = PathBuf::from_str(zip)?;
let zip_path = zip_path.canonicalize()?;
zip_extract_file_to_memory(&zip_path, &entry_path, &mut buffer)?;
let mut module_prop = HashMap::new();
PropertiesIter::new_with_encoding(Cursor::new(buffer), encoding_rs::UTF_8).read_into(
|k, v| {
module_prop.insert(k, v);
},
)?;
info!("module prop: {:?}", module_prop);
let Some(module_id) = module_prop.get("id") else {
bail!("module id not found in module.prop!");
};
let module_id = module_id.trim();
let zip_uncompressed_size = get_zip_uncompressed_size(zip)?;
info!(
"zip uncompressed size: {}",
humansize::format_size(zip_uncompressed_size, humansize::DECIMAL)
);
println!("- Preparing Zip");
println!(
"- Module size: {}",
humansize::format_size(zip_uncompressed_size, humansize::DECIMAL)
);
// ensure modules_update exists
ensure_dir_exists(MODULE_UPDATE_DIR)?;
setsyscon(MODULE_UPDATE_DIR)?;
let update_module_dir = Path::new(MODULE_UPDATE_DIR).join(module_id);
ensure_clean_dir(&update_module_dir)?;
info!("module dir: {}", update_module_dir.display());
let do_install = || -> Result<()> {
// unzip the image and move it to modules_update/<id> dir
let file = File::open(zip)?;
let mut archive = zip::ZipArchive::new(file)?;
archive.extract(&update_module_dir)?;
// set permission and selinux context for $MOD/system
let module_system_dir = update_module_dir.join("system");
if module_system_dir.exists() {
#[cfg(unix)]
set_permissions(&module_system_dir, Permissions::from_mode(0o755))?;
restore_syscon(&module_system_dir)?;
}
exec_install_script(zip)?;
let module_dir = Path::new(MODULE_DIR).join(module_id);
ensure_dir_exists(&module_dir)?;
copy(
update_module_dir.join("module.prop"),
module_dir.join("module.prop"),
)?;
ensure_file_exists(module_dir.join(UPDATE_FILE_NAME))?;
info!("Module install successfully!");
Ok(())
};
let result = do_install();
if result.is_err() {
remove_dir_all(&update_module_dir).ok();
}
result
}
let result = inner(zip);
if let Err(ref e) = result { if let Err(ref e) = result {
// error happened, do some cleanup!
let _ = std::fs::remove_file(defs::MODULE_UPDATE_TMP_IMG);
let _ = mount::umount_dir(defs::MODULE_UPDATE_TMP_DIR);
println!("- Error: {e}"); println!("- Error: {e}");
} }
result result
} }
fn update_module<F>(update_dir: &str, id: &str, func: F) -> Result<()>
where
F: Fn(&str, &str) -> Result<()>,
{
ensure_boot_completed()?;
let modules_img = Path::new(defs::MODULE_IMG);
let modules_update_img = Path::new(defs::MODULE_UPDATE_IMG);
let modules_update_tmp_img = Path::new(defs::MODULE_UPDATE_TMP_IMG);
if !modules_update_img.exists() && !modules_img.exists() {
bail!("Please install module first!");
} else if modules_update_img.exists() {
info!(
"copy {} to {}",
modules_update_img.display(),
modules_update_tmp_img.display()
);
utils::copy_sparse_file(modules_update_img, modules_update_tmp_img, true)?;
} else {
info!(
"copy {} to {}",
modules_img.display(),
modules_update_tmp_img.display()
);
utils::copy_sparse_file(modules_img, modules_update_tmp_img, true)?;
}
// ensure modules_update dir exist
ensure_clean_dir(update_dir)?;
// mount the modules_update img
let _dontdrop = mount::AutoMountExt4::try_new(defs::MODULE_UPDATE_TMP_IMG, update_dir, true)?;
// call the operation func
let result = func(id, update_dir);
if let Err(e) = std::fs::rename(modules_update_tmp_img, defs::MODULE_UPDATE_IMG) {
warn!("Rename image failed: {e}, try copy it.");
utils::copy_sparse_file(modules_update_tmp_img, defs::MODULE_UPDATE_IMG, true)
.with_context(|| "Failed to copy image.".to_string())?;
let _ = std::fs::remove_file(modules_update_tmp_img);
}
mark_update()?;
result
}
pub fn uninstall_module(id: &str) -> Result<()> { pub fn uninstall_module(id: &str) -> Result<()> {
mark_module_state(id, defs::REMOVE_FILE_NAME, true) update_module(defs::MODULE_UPDATE_TMP_DIR, id, |mid, update_dir| {
let dir = Path::new(update_dir);
ensure!(dir.exists(), "No module installed");
// iterate the modules_update dir, find the module to be removed
let dir = std::fs::read_dir(dir)?;
for entry in dir.flatten() {
let path = entry.path();
let module_prop = path.join("module.prop");
if !module_prop.exists() {
continue;
}
let content = std::fs::read(module_prop)?;
let mut module_id: String = String::new();
PropertiesIter::new_with_encoding(Cursor::new(content), encoding_rs::UTF_8).read_into(
|k, v| {
if k.eq("id") {
module_id = v;
}
},
)?;
if module_id.eq(mid) {
let remove_file = path.join(defs::REMOVE_FILE_NAME);
File::create(remove_file).with_context(|| "Failed to create remove file.")?;
break;
}
}
// santity check
let target_module_path = format!("{update_dir}/{mid}");
let target_module = Path::new(&target_module_path);
if target_module.exists() {
let remove_file = target_module.join(defs::REMOVE_FILE_NAME);
if !remove_file.exists() {
File::create(remove_file).with_context(|| "Failed to create remove file.")?;
}
}
let _ = mark_module_state(id, defs::REMOVE_FILE_NAME, true);
Ok(())
})
} }
pub fn restore_module(id: &str) -> Result<()> { pub fn restore_module(id: &str) -> Result<()> {
mark_module_state(id, defs::REMOVE_FILE_NAME, false) update_module(defs::MODULE_UPDATE_TMP_DIR, id, |mid, update_dir| {
let dir = Path::new(update_dir);
ensure!(dir.exists(), "No module directory found");
// Iterate over the modules_update directory to find the target module
let dir_entries = std::fs::read_dir(dir)?;
for entry in dir_entries.flatten() {
let path = entry.path();
let module_prop = path.join("module.prop");
if !module_prop.exists() {
continue;
}
let content = std::fs::read(module_prop)?;
let mut module_id: String = String::new();
PropertiesIter::new_with_encoding(Cursor::new(content), encoding_rs::UTF_8).read_into(
|k, v| {
if k.eq("id") {
module_id = v;
}
},
)?;
if module_id.eq(mid) {
let remove_file = path.join(defs::REMOVE_FILE_NAME);
if remove_file.exists() {
std::fs::remove_file(&remove_file)
.with_context(|| "Failed to remove remove file.")?;
}
break;
}
}
// Sanity check to ensure the module is restored
let target_module_path = format!("{update_dir}/{mid}");
let target_module = Path::new(&target_module_path);
if target_module.exists() {
let remove_file = target_module.join(defs::REMOVE_FILE_NAME);
if remove_file.exists() {
std::fs::remove_file(&remove_file)
.with_context(|| "Failed to remove remove file.")?;
}
}
// Reverse the state marking
let _ = mark_module_state(id, defs::REMOVE_FILE_NAME, false);
Ok(())
})
} }
pub fn run_action(id: &str) -> Result<()> { pub fn run_action(id: &str) -> Result<()> {
@@ -401,12 +627,37 @@ pub fn run_action(id: &str) -> Result<()> {
exec_script(&action_script_path, true) exec_script(&action_script_path, true)
} }
fn _enable_module(module_dir: &str, mid: &str, enable: bool) -> Result<()> {
let src_module_path = format!("{module_dir}/{mid}");
let src_module = Path::new(&src_module_path);
ensure!(src_module.exists(), "module: {} not found!", mid);
let disable_path = src_module.join(defs::DISABLE_FILE_NAME);
if enable {
if disable_path.exists() {
std::fs::remove_file(&disable_path).with_context(|| {
format!("Failed to remove disable file: {}", &disable_path.display())
})?;
}
} else {
ensure_file_exists(disable_path)?;
}
let _ = mark_module_state(mid, defs::DISABLE_FILE_NAME, !enable);
Ok(())
}
pub fn enable_module(id: &str) -> Result<()> { pub fn enable_module(id: &str) -> Result<()> {
mark_module_state(id, defs::DISABLE_FILE_NAME, false) update_module(defs::MODULE_UPDATE_TMP_DIR, id, |mid, update_dir| {
_enable_module(update_dir, mid, true)
})
} }
pub fn disable_module(id: &str) -> Result<()> { pub fn disable_module(id: &str) -> Result<()> {
mark_module_state(id, defs::DISABLE_FILE_NAME, true) update_module(defs::MODULE_UPDATE_TMP_DIR, id, |mid, update_dir| {
_enable_module(update_dir, mid, false)
})
} }
pub fn disable_all_modules() -> Result<()> { pub fn disable_all_modules() -> Result<()> {
@@ -418,7 +669,8 @@ pub fn uninstall_all_modules() -> Result<()> {
} }
fn mark_all_modules(flag_file: &str) -> Result<()> { fn mark_all_modules(flag_file: &str) -> Result<()> {
let dir = std::fs::read_dir(MODULE_DIR)?; // we assume the module dir is already mounted
let dir = std::fs::read_dir(defs::MODULE_DIR)?;
for entry in dir.flatten() { for entry in dir.flatten() {
let path = entry.path(); let path = entry.path();
let flag = path.join(flag_file); let flag = path.join(flag_file);
@@ -496,3 +748,21 @@ pub fn list_modules() -> Result<()> {
println!("{}", serde_json::to_string_pretty(&modules)?); println!("{}", serde_json::to_string_pretty(&modules)?);
Ok(()) Ok(())
} }
pub fn shrink_image(img: &str) -> Result<()> {
check_image(img)?;
Command::new("resize2fs")
.arg("-M")
.arg(img)
.stdout(Stdio::piped())
.status()?;
Ok(())
}
pub fn shrink_ksu_images() -> Result<()> {
shrink_image(defs::MODULE_IMG)?;
if Path::new(defs::MODULE_UPDATE_IMG).exists() {
shrink_image(defs::MODULE_UPDATE_IMG)?;
}
Ok(())
}

306
userspace/ksud/src/mount.rs Normal file
View File

@@ -0,0 +1,306 @@
use anyhow::{anyhow, bail, Ok, Result};
#[cfg(any(target_os = "linux", target_os = "android"))]
use anyhow::Context;
#[cfg(any(target_os = "linux", target_os = "android"))]
use rustix::{fd::AsFd, fs::CWD, mount::*};
use crate::defs::KSU_OVERLAY_SOURCE;
use log::{info, warn};
#[cfg(any(target_os = "linux", target_os = "android"))]
use procfs::process::Process;
use std::path::Path;
use std::path::PathBuf;
pub struct AutoMountExt4 {
target: String,
auto_umount: bool,
}
impl AutoMountExt4 {
#[cfg(any(target_os = "linux", target_os = "android"))]
pub fn try_new(source: &str, target: &str, auto_umount: bool) -> Result<Self> {
mount_ext4(source, target)?;
Ok(Self {
target: target.to_string(),
auto_umount,
})
}
#[cfg(not(any(target_os = "linux", target_os = "android")))]
pub fn try_new(_src: &str, _mnt: &str, _auto_umount: bool) -> Result<Self> {
unimplemented!()
}
#[cfg(any(target_os = "linux", target_os = "android"))]
pub fn umount(&self) -> Result<()> {
unmount(self.target.as_str(), UnmountFlags::DETACH)?;
Ok(())
}
}
#[cfg(any(target_os = "linux", target_os = "android"))]
impl Drop for AutoMountExt4 {
fn drop(&mut self) {
log::info!(
"AutoMountExt4 drop: {}, auto_umount: {}",
self.target,
self.auto_umount
);
if self.auto_umount {
let _ = self.umount();
}
}
}
#[cfg(any(target_os = "linux", target_os = "android"))]
pub fn mount_ext4(source: impl AsRef<Path>, target: impl AsRef<Path>) -> Result<()> {
let new_loopback = loopdev::LoopControl::open()?
.next_free()
.with_context(|| "Failed to alloc loop")?;
new_loopback
.with()
.attach(source)
.with_context(|| "Failed to attach loop")?;
let lo = new_loopback.path().ok_or(anyhow!("no loop"))?;
let fs = fsopen("ext4", FsOpenFlags::FSOPEN_CLOEXEC)?;
let fs = fs.as_fd();
fsconfig_set_string(fs, "source", lo)?;
fsconfig_create(fs)?;
let mount = fsmount(fs, FsMountFlags::FSMOUNT_CLOEXEC, MountAttrFlags::empty())?;
move_mount(
mount.as_fd(),
"",
CWD,
target.as_ref(),
MoveMountFlags::MOVE_MOUNT_F_EMPTY_PATH,
)?;
Ok(())
}
#[cfg(any(target_os = "linux", target_os = "android"))]
pub fn umount_dir(src: impl AsRef<Path>) -> Result<()> {
unmount(src.as_ref(), UnmountFlags::empty())
.with_context(|| format!("Failed to umount {}", src.as_ref().display()))?;
Ok(())
}
#[cfg(any(target_os = "linux", target_os = "android"))]
pub fn mount_overlayfs(
lower_dirs: &[String],
lowest: &str,
upperdir: Option<PathBuf>,
workdir: Option<PathBuf>,
dest: impl AsRef<Path>,
) -> Result<()> {
let lowerdir_config = lower_dirs
.iter()
.map(|s| s.as_ref())
.chain(std::iter::once(lowest))
.collect::<Vec<_>>()
.join(":");
info!(
"mount overlayfs on {:?}, lowerdir={}, upperdir={:?}, workdir={:?}",
dest.as_ref(),
lowerdir_config,
upperdir,
workdir
);
let upperdir = upperdir
.filter(|up| up.exists())
.map(|e| e.display().to_string());
let workdir = workdir
.filter(|wd| wd.exists())
.map(|e| e.display().to_string());
let result = (|| {
let fs = fsopen("overlay", FsOpenFlags::FSOPEN_CLOEXEC)?;
let fs = fs.as_fd();
fsconfig_set_string(fs, "lowerdir", &lowerdir_config)?;
if let (Some(upperdir), Some(workdir)) = (&upperdir, &workdir) {
fsconfig_set_string(fs, "upperdir", upperdir)?;
fsconfig_set_string(fs, "workdir", workdir)?;
}
fsconfig_set_string(fs, "source", KSU_OVERLAY_SOURCE)?;
fsconfig_create(fs)?;
let mount = fsmount(fs, FsMountFlags::FSMOUNT_CLOEXEC, MountAttrFlags::empty())?;
move_mount(
mount.as_fd(),
"",
CWD,
dest.as_ref(),
MoveMountFlags::MOVE_MOUNT_F_EMPTY_PATH,
)
})();
if let Err(e) = result {
warn!("fsopen mount failed: {:#}, fallback to mount", e);
let mut data = format!("lowerdir={lowerdir_config}");
if let (Some(upperdir), Some(workdir)) = (upperdir, workdir) {
data = format!("{data},upperdir={upperdir},workdir={workdir}");
}
mount(
KSU_OVERLAY_SOURCE,
dest.as_ref(),
"overlay",
MountFlags::empty(),
data,
)?;
}
Ok(())
}
#[cfg(any(target_os = "linux", target_os = "android"))]
pub fn mount_tmpfs(dest: impl AsRef<Path>) -> Result<()> {
info!("mount tmpfs on {}", dest.as_ref().display());
let fs = fsopen("tmpfs", FsOpenFlags::FSOPEN_CLOEXEC)?;
let fs = fs.as_fd();
fsconfig_set_string(fs, "source", KSU_OVERLAY_SOURCE)?;
fsconfig_create(fs)?;
let mount = fsmount(fs, FsMountFlags::FSMOUNT_CLOEXEC, MountAttrFlags::empty())?;
move_mount(
mount.as_fd(),
"",
CWD,
dest.as_ref(),
MoveMountFlags::MOVE_MOUNT_F_EMPTY_PATH,
)?;
Ok(())
}
#[cfg(any(target_os = "linux", target_os = "android"))]
pub fn bind_mount(from: impl AsRef<Path>, to: impl AsRef<Path>) -> Result<()> {
info!(
"bind mount {} -> {}",
from.as_ref().display(),
to.as_ref().display()
);
let tree = open_tree(
CWD,
from.as_ref(),
OpenTreeFlags::OPEN_TREE_CLOEXEC
| OpenTreeFlags::OPEN_TREE_CLONE
| OpenTreeFlags::AT_RECURSIVE,
)?;
move_mount(
tree.as_fd(),
"",
CWD,
to.as_ref(),
MoveMountFlags::MOVE_MOUNT_F_EMPTY_PATH,
)?;
Ok(())
}
#[cfg(any(target_os = "linux", target_os = "android"))]
fn mount_overlay_child(
mount_point: &str,
relative: &String,
module_roots: &Vec<String>,
stock_root: &String,
) -> Result<()> {
if !module_roots
.iter()
.any(|lower| Path::new(&format!("{lower}{relative}")).exists())
{
return bind_mount(stock_root, mount_point);
}
if !Path::new(&stock_root).is_dir() {
return Ok(());
}
let mut lower_dirs: Vec<String> = vec![];
for lower in module_roots {
let lower_dir = format!("{lower}{relative}");
let path = Path::new(&lower_dir);
if path.is_dir() {
lower_dirs.push(lower_dir);
} else if path.exists() {
// stock root has been blocked by this file
return Ok(());
}
}
if lower_dirs.is_empty() {
return Ok(());
}
// merge modules and stock
if let Err(e) = mount_overlayfs(&lower_dirs, stock_root, None, None, mount_point) {
warn!("failed: {:#}, fallback to bind mount", e);
bind_mount(stock_root, mount_point)?;
}
Ok(())
}
#[cfg(any(target_os = "linux", target_os = "android"))]
pub fn mount_overlay(
root: &String,
module_roots: &Vec<String>,
workdir: Option<PathBuf>,
upperdir: Option<PathBuf>,
) -> Result<()> {
info!("mount overlay for {}", root);
std::env::set_current_dir(root).with_context(|| format!("failed to chdir to {root}"))?;
let stock_root = ".";
// collect child mounts before mounting the root
let mounts = Process::myself()?
.mountinfo()
.with_context(|| "get mountinfo")?;
let mut mount_seq = mounts
.0
.iter()
.filter(|m| {
m.mount_point.starts_with(root) && !Path::new(&root).starts_with(&m.mount_point)
})
.map(|m| m.mount_point.to_str())
.collect::<Vec<_>>();
mount_seq.sort();
mount_seq.dedup();
mount_overlayfs(module_roots, root, upperdir, workdir, root)
.with_context(|| "mount overlayfs for root failed")?;
for mount_point in mount_seq.iter() {
let Some(mount_point) = mount_point else {
continue;
};
let relative = mount_point.replacen(root, "", 1);
let stock_root: String = format!("{stock_root}{relative}");
if !Path::new(&stock_root).exists() {
continue;
}
if let Err(e) = mount_overlay_child(mount_point, &relative, module_roots, &stock_root) {
warn!(
"failed to mount overlay for child {}: {:#}, revert",
mount_point, e
);
umount_dir(root).with_context(|| format!("failed to revert {root}"))?;
bail!(e);
}
}
Ok(())
}
#[cfg(not(any(target_os = "linux", target_os = "android")))]
pub fn mount_ext4(_src: &str, _target: &str, _autodrop: bool) -> Result<()> {
unimplemented!()
}
#[cfg(not(any(target_os = "linux", target_os = "android")))]
pub fn umount_dir(_src: &str) -> Result<()> {
unimplemented!()
}
#[cfg(not(any(target_os = "linux", target_os = "android")))]
pub fn mount_overlay(
_root: &String,
_module_roots: &Vec<String>,
_workdir: Option<PathBuf>,
_upperdir: Option<PathBuf>,
) -> Result<()> {
unimplemented!()
}
#[cfg(not(any(target_os = "linux", target_os = "android")))]
pub fn mount_tmpfs(_dest: impl AsRef<Path>) -> Result<()> {
unimplemented!()
}

View File

@@ -61,11 +61,11 @@ pub fn restore_syscon<P: AsRef<Path>>(dir: P) -> Result<()> {
Ok(()) Ok(())
} }
fn restore_modules_con<P: AsRef<Path>>(dir: P) -> Result<()> { fn restore_syscon_if_unlabeled<P: AsRef<Path>>(dir: P) -> Result<()> {
for dir_entry in WalkDir::new(dir).parallelism(Serial) { for dir_entry in WalkDir::new(dir).parallelism(Serial) {
if let Some(path) = dir_entry.ok().map(|dir_entry| dir_entry.path()) { if let Some(path) = dir_entry.ok().map(|dir_entry| dir_entry.path()) {
if let Result::Ok(con) = lgetfilecon(&path) { if let anyhow::Result::Ok(con) = lgetfilecon(&path) {
if con == ADB_CON || con == UNLABEL_CON || con.is_empty() { if con == UNLABEL_CON || con.is_empty() {
lsetfilecon(&path, SYSTEM_CON)?; lsetfilecon(&path, SYSTEM_CON)?;
} }
} }
@@ -76,6 +76,6 @@ fn restore_modules_con<P: AsRef<Path>>(dir: P) -> Result<()> {
pub fn restorecon() -> Result<()> { pub fn restorecon() -> Result<()> {
lsetfilecon(defs::DAEMON_PATH, ADB_CON)?; lsetfilecon(defs::DAEMON_PATH, ADB_CON)?;
restore_modules_con(defs::MODULE_DIR)?; restore_syscon_if_unlabeled(defs::MODULE_DIR)?;
Ok(()) Ok(())
} }

View File

@@ -15,6 +15,10 @@ use std::fs::{set_permissions, Permissions};
#[cfg(unix)] #[cfg(unix)]
use std::os::unix::prelude::PermissionsExt; use std::os::unix::prelude::PermissionsExt;
use hole_punch::*;
use std::io::{Read, Seek, SeekFrom};
use jwalk::WalkDir;
use std::path::PathBuf; use std::path::PathBuf;
#[cfg(any(target_os = "linux", target_os = "android"))] #[cfg(any(target_os = "linux", target_os = "android"))]
@@ -35,7 +39,7 @@ pub fn ensure_clean_dir(dir: impl AsRef<Path>) -> Result<()> {
pub fn ensure_file_exists<T: AsRef<Path>>(file: T) -> Result<()> { pub fn ensure_file_exists<T: AsRef<Path>>(file: T) -> Result<()> {
match File::options().write(true).create_new(true).open(&file) { match File::options().write(true).create_new(true).open(&file) {
Result::Ok(_) => Ok(()), std::result::Result::Ok(_) => Ok(()),
Err(err) => { Err(err) => {
if err.kind() == AlreadyExists && file.as_ref().is_file() { if err.kind() == AlreadyExists && file.as_ref().is_file() {
Ok(()) Ok(())
@@ -220,7 +224,9 @@ pub fn uninstall(magiskboot_path: Option<PathBuf>) -> Result<()> {
println!("- Removing directories.."); println!("- Removing directories..");
std::fs::remove_dir_all(defs::WORKING_DIR).ok(); std::fs::remove_dir_all(defs::WORKING_DIR).ok();
std::fs::remove_file(defs::DAEMON_PATH).ok(); std::fs::remove_file(defs::DAEMON_PATH).ok();
crate::mount::umount_dir(defs::MODULE_DIR).ok();
std::fs::remove_dir_all(defs::MODULE_DIR).ok(); std::fs::remove_dir_all(defs::MODULE_DIR).ok();
std::fs::remove_dir_all(defs::MODULE_UPDATE_TMP_DIR).ok();
println!("- Restore boot image.."); println!("- Restore boot image..");
boot_patch::restore(None, magiskboot_path, true)?; boot_patch::restore(None, magiskboot_path, true)?;
println!("- Uninstall KernelSU-Next manager.."); println!("- Uninstall KernelSU-Next manager..");
@@ -232,3 +238,153 @@ pub fn uninstall(magiskboot_path: Option<PathBuf>) -> Result<()> {
Command::new("reboot").spawn()?; Command::new("reboot").spawn()?;
Ok(()) Ok(())
} }
// TODO: use libxcp to improve the speed if cross's MSRV is 1.70
pub fn copy_sparse_file<P: AsRef<Path>, Q: AsRef<Path>>(
src: P,
dst: Q,
punch_hole: bool,
) -> Result<()> {
let mut src_file = File::open(src.as_ref())?;
let mut dst_file = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(dst.as_ref())?;
dst_file.set_len(src_file.metadata()?.len())?;
let segments = src_file.scan_chunks()?;
for segment in segments {
if let SegmentType::Data = segment.segment_type {
let start = segment.start;
let end = segment.end;
src_file.seek(SeekFrom::Start(start))?;
dst_file.seek(SeekFrom::Start(start))?;
let mut buffer = [0; 4096];
let mut total_bytes_copied = 0;
while total_bytes_copied < end - start {
let bytes_to_read =
std::cmp::min(buffer.len() as u64, end - start - total_bytes_copied);
let bytes_read = src_file.read(&mut buffer[..bytes_to_read as usize])?;
if bytes_read == 0 {
break;
}
if punch_hole && buffer[..bytes_read].iter().all(|&x| x == 0) {
// all zero, don't copy it at all!
dst_file.seek(SeekFrom::Current(bytes_read as i64))?;
total_bytes_copied += bytes_read as u64;
continue;
}
dst_file.write_all(&buffer[..bytes_read])?;
total_bytes_copied += bytes_read as u64;
}
}
}
Ok(())
}
#[cfg(any(target_os = "linux", target_os = "android"))]
fn copy_xattrs(src_path: impl AsRef<Path>, dest_path: impl AsRef<Path>) -> Result<()> {
use rustix::path::Arg;
let std::result::Result::Ok(xattrs) = extattr::llistxattr(src_path.as_ref()) else {
return Ok(());
};
for xattr in xattrs {
let std::result::Result::Ok(value) = extattr::lgetxattr(src_path.as_ref(), &xattr) else {
continue;
};
log::info!(
"Set {:?} xattr {} = {}",
dest_path.as_ref(),
xattr.to_string_lossy(),
value.to_string_lossy(),
);
if let Err(e) =
extattr::lsetxattr(dest_path.as_ref(), &xattr, &value, extattr::Flags::empty())
{
log::warn!("Failed to set xattr: {}", e);
}
}
Ok(())
}
#[cfg(any(target_os = "linux", target_os = "android"))]
pub fn copy_module_files(source: impl AsRef<Path>, destination: impl AsRef<Path>) -> Result<()> {
use rustix::fs::FileTypeExt;
use rustix::fs::MetadataExt;
for entry in WalkDir::new(source.as_ref()).into_iter() {
let entry = entry.context("Failed to access entry")?;
let source_path = entry.path();
let relative_path = source_path
.strip_prefix(source.as_ref())
.context("Failed to generate relative path")?;
let dest_path = destination.as_ref().join(relative_path);
if let Some(parent) = dest_path.parent() {
std::fs::create_dir_all(parent).context("Failed to create directory")?;
}
if entry.file_type().is_file() {
std::fs::copy(&source_path, &dest_path).with_context(|| {
format!("Failed to copy file from {source_path:?} to {dest_path:?}",)
})?;
copy_xattrs(&source_path, &dest_path)?;
} else if entry.file_type().is_symlink() {
if dest_path.exists() {
std::fs::remove_file(&dest_path).context("Failed to remove file")?;
}
let target = std::fs::read_link(entry.path()).context("Failed to read symlink")?;
log::info!("Symlink: {:?} -> {:?}", dest_path, target);
std::os::unix::fs::symlink(target, &dest_path).context("Failed to create symlink")?;
copy_xattrs(&source_path, &dest_path)?;
} else if entry.file_type().is_dir() {
create_dir_all(&dest_path)?;
let metadata = std::fs::metadata(&source_path).context("Failed to read metadata")?;
std::fs::set_permissions(&dest_path, metadata.permissions())
.with_context(|| format!("Failed to set permissions for {dest_path:?}"))?;
copy_xattrs(&source_path, &dest_path)?;
} else if entry.file_type().is_char_device() {
if dest_path.exists() {
std::fs::remove_file(&dest_path).context("Failed to remove file")?;
}
let metadata = std::fs::metadata(&source_path).context("Failed to read metadata")?;
let mode = metadata.permissions().mode();
let dev = metadata.rdev();
if dev == 0 {
log::info!(
"Found a char device with major 0: {}",
entry.path().display()
);
rustix::fs::mknodat(
rustix::fs::CWD,
&dest_path,
rustix::fs::FileType::CharacterDevice,
mode.into(),
dev,
)
.with_context(|| format!("Failed to create device file at {dest_path:?}"))?;
copy_xattrs(&source_path, &dest_path)?;
}
} else {
log::info!(
"Unknown file type: {:?}, {:?},",
entry.file_type(),
entry.path(),
);
}
}
Ok(())
}
#[cfg(not(any(target_os = "linux", target_os = "android")))]
pub fn copy_module_files(_source: impl AsRef<Path>, _destination: impl AsRef<Path>) -> Result<()> {
unimplemented!()
}