From a0540aa8ec6ef0eefa31adcd66f00f5fc86d0bc4 Mon Sep 17 00:00:00 2001 From: Rifat Azad Date: Tue, 24 Dec 2024 04:21:38 +0600 Subject: [PATCH] ksud: dropped magic_mount for stability so welcome back OverlayFS manager: brought back shrink images in settings --- docs/README.md | 3 +- .../com/rifsxd/ksunext/ui/screen/Settings.kt | 23 + .../java/com/rifsxd/ksunext/ui/util/KsuCli.kt | 4 + manager/app/src/main/jniLibs/.gitignore | 3 +- userspace/ksud/Cargo.lock | 220 ++++--- userspace/ksud/Cargo.toml | 1 + userspace/ksud/src/cli.rs | 35 +- userspace/ksud/src/defs.rs | 11 +- userspace/ksud/src/init_event.rs | 172 +++++- userspace/ksud/src/installer.sh | 29 +- userspace/ksud/src/magic_mount.rs | 434 ------------- userspace/ksud/src/main.rs | 3 +- userspace/ksud/src/module.rs | 578 +++++++++++++----- userspace/ksud/src/mount.rs | 306 ++++++++++ userspace/ksud/src/restorecon.rs | 8 +- userspace/ksud/src/utils.rs | 158 ++++- 16 files changed, 1246 insertions(+), 742 deletions(-) delete mode 100644 userspace/ksud/src/magic_mount.rs create mode 100644 userspace/ksud/src/mount.rs diff --git a/docs/README.md b/docs/README.md index 0578196d..7e3fc731 100644 --- a/docs/README.md +++ b/docs/README.md @@ -14,7 +14,7 @@ A Kernel-based root solution for Android devices. ## Features 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. ## 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. - [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. -- [Magic Mount Port](https://github.com/5ec1cff/KernelSU/blob/main/userspace/ksud/src/magic_mount.rs): 💜 5ec1cff for saving KernelSU! diff --git a/manager/app/src/main/java/com/rifsxd/ksunext/ui/screen/Settings.kt b/manager/app/src/main/java/com/rifsxd/ksunext/ui/screen/Settings.kt index db29c293..c7af9ec4 100644 --- a/manager/app/src/main/java/com/rifsxd/ksunext/ui/screen/Settings.kt +++ b/manager/app/src/main/java/com/rifsxd/ksunext/ui/screen/Settings.kt @@ -106,6 +106,7 @@ fun SettingScreen(navigator: DestinationsNavigator) { AboutDialog(it) } val loadingDialog = rememberLoadingDialog() + val shrinkDialog = rememberConfirmDialog() Column( 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 if (lkmMode) { UninstallItem(navigator) { diff --git a/manager/app/src/main/java/com/rifsxd/ksunext/ui/util/KsuCli.kt b/manager/app/src/main/java/com/rifsxd/ksunext/ui/util/KsuCli.kt index 24ea7118..28291a22 100644 --- a/manager/app/src/main/java/com/rifsxd/ksunext/ui/util/KsuCli.kt +++ b/manager/app/src/main/java/com/rifsxd/ksunext/ui/util/KsuCli.kt @@ -282,6 +282,10 @@ fun uninstallPermanently( return result.isSuccess } +suspend fun shrinkModules(): Boolean = withContext(Dispatchers.IO) { + execKsud("module shrink", true) +} + @Parcelize sealed class LkmSelection : Parcelable { data class LkmUri(val uri: Uri) : LkmSelection() diff --git a/manager/app/src/main/jniLibs/.gitignore b/manager/app/src/main/jniLibs/.gitignore index 311f7299..311c7396 100644 --- a/manager/app/src/main/jniLibs/.gitignore +++ b/manager/app/src/main/jniLibs/.gitignore @@ -1 +1,2 @@ -libksud.so \ No newline at end of file +libksud.so +libsusfs.so \ No newline at end of file diff --git a/userspace/ksud/Cargo.lock b/userspace/ksud/Cargo.lock index 0c5692f2..906b44f2 100644 --- a/userspace/ksud/Cargo.lock +++ b/userspace/ksud/Cargo.lock @@ -29,7 +29,7 @@ version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "once_cell", "version_check", "zerocopy", @@ -37,9 +37,9 @@ dependencies = [ [[package]] name = "allocator-api2" -version = "0.2.21" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" +checksum = "45862d1c77f2228b9e10bc609d5bc203d86ebc9b87ad8d5d5167a6c9abf739d9" [[package]] name = "android-properties" @@ -130,9 +130,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.94" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7" +checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" [[package]] name = "arbitrary" @@ -167,7 +167,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" dependencies = [ "addr2line", - "cfg-if", + "cfg-if 1.0.0", "libc", "miniz_oxide", "object", @@ -210,19 +210,25 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.9.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" +checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" [[package]] name = "cc" -version = "1.2.5" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31a0499c1dc64f458ad13872de75c0eb7e3fdb0e67964610c914b034fc5956e" +checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47" dependencies = [ "shlex", ] +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + [[package]] name = "cfg-if" version = "1.0.0" @@ -231,9 +237,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.39" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ "android-tzdata", "iana-time-zone", @@ -245,9 +251,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.23" +version = "4.5.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84" +checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f" dependencies = [ "clap_builder", "clap_derive", @@ -255,9 +261,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.23" +version = "4.5.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838" +checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec" dependencies = [ "anstream", "anstyle", @@ -279,9 +285,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.4" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" +checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" [[package]] name = "colorchoice" @@ -291,18 +297,18 @@ checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] name = "const_format" -version = "0.2.34" +version = "0.2.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "126f97965c8ad46d6d9163268ff28432e8f6a1196a55578867832e3049df63dd" +checksum = "50c655d81ff1114fb0dcdea9225ea9f0cc712a6f8d189378e82bdf62a473a64b" dependencies = [ "const_format_proc_macros", ] [[package]] name = "const_format_proc_macros" -version = "0.2.34" +version = "0.2.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" +checksum = "eff1a44b93f47b1bac19a27932f5c591e43d1ba357ee4f61526c8a25603f0eb1" dependencies = [ "proc-macro2", "quote", @@ -326,9 +332,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.16" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" +checksum = "0ca741a962e1b0bff6d724a1a0958b686406e853bb14061f218562e1896f95e6" dependencies = [ "libc", ] @@ -354,7 +360,7 @@ version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", ] [[package]] @@ -372,18 +378,18 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.14" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471" +checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-deque" -version = "0.8.6" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" dependencies = [ "crossbeam-epoch", "crossbeam-utils", @@ -400,18 +406,18 @@ dependencies = [ [[package]] name = "crossbeam-queue" -version = "0.3.12" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.21" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] name = "crypto-common" @@ -499,29 +505,23 @@ version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", ] [[package]] name = "env_filter" -version = "0.1.3" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" +checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab" dependencies = [ "log", ] -[[package]] -name = "env_home" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe" - [[package]] name = "env_logger" -version = "0.11.6" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0" +checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d" dependencies = [ "env_filter", "log", @@ -577,9 +577,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.3.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" [[package]] name = "flate2" @@ -616,7 +616,7 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "libc", "wasi", ] @@ -655,6 +655,28 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" 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]] name = "humansize" version = "2.1.3" @@ -712,12 +734,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.7.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", - "hashbrown 0.15.1", + "hashbrown 0.15.0", ] [[package]] @@ -737,9 +759,9 @@ checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itoa" -version = "1.0.14" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" +checksum = "540654e97a3f4470a492cd30ff187bc95d89557a903a2bbf112e2fae98104ef2" [[package]] name = "java-properties" @@ -785,6 +807,7 @@ dependencies = [ "env_logger", "extattr", "getopts", + "hole-punch", "humansize", "is_executable", "java-properties", @@ -816,9 +839,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.169" +version = "0.2.164" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" +checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f" [[package]] name = "libflate" @@ -893,6 +916,16 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" 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]] name = "minimal-lexical" version = "0.2.1" @@ -901,9 +934,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.8.2" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" dependencies = [ "adler2", ] @@ -971,9 +1004,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.92" +version = "1.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +checksum = "307e3004becf10f5a6e0d59d20f3cd28231b0e0827a96cd3e0ce6d14bc1e4bb3" dependencies = [ "unicode-ident", ] @@ -1168,18 +1201,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.216" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" +checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.216" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" +checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", @@ -1204,7 +1237,7 @@ version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "cpufeatures", "digest", ] @@ -1215,7 +1248,7 @@ version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "cpufeatures", "digest", ] @@ -1253,9 +1286,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.90" +version = "2.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" +checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" dependencies = [ "proc-macro2", "quote", @@ -1268,27 +1301,27 @@ version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "fastrand", "once_cell", "rustix 0.38.41", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] name = "thiserror" -version = "2.0.8" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f5383f3e0071702bf93ab5ee99b52d26936be9dedd9413067cbdcddcb6141a" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.8" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f357fcec90b3caef6623a099691be676d033b40a058ac95d2a6ade6fa0c943" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", @@ -1297,9 +1330,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.37" +version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", "num-conv", @@ -1316,9 +1349,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "tokio" -version = "1.42.0" +version = "1.41.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" +checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" dependencies = [ "backtrace", "bytes", @@ -1379,23 +1412,24 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.99" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" +checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.99" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" +checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" dependencies = [ "bumpalo", "log", + "once_cell", "proc-macro2", "quote", "syn", @@ -1404,9 +1438,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.99" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" +checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1414,9 +1448,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.99" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" +checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" dependencies = [ "proc-macro2", "quote", @@ -1427,18 +1461,18 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.99" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" +checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" [[package]] name = "which" -version = "7.0.1" +version = "7.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb4a9e33648339dc1642b0e36e21b3385e6148e289226f657c809dee59df5028" +checksum = "c9cad3279ade7346b96e38731a641d7343dd6a53d55083dd54eadfa5a1b38c6b" dependencies = [ "either", - "env_home", + "home", "rustix 0.38.41", "winsafe", ] @@ -1465,7 +1499,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -1594,9 +1628,9 @@ dependencies = [ [[package]] name = "zip" -version = "2.2.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae9c1ea7b3a5e1f4b922ff856a129881167511563dc219869afe3787fc0c1a45" +checksum = "dc5e4288ea4057ae23afc69a4472434a87a2495cafce6632fd1c4ec9f5cf3494" dependencies = [ "arbitrary", "crc32fast", diff --git a/userspace/ksud/Cargo.toml b/userspace/ksud/Cargo.toml index b2700f56..33786366 100644 --- a/userspace/ksud/Cargo.toml +++ b/userspace/ksud/Cargo.toml @@ -41,6 +41,7 @@ sha256 = "1" sha1 = "0.10" tempfile = "3.14" chrono = "0.4" +hole-punch = { git = "https://github.com/tiann/hole-punch" } regex-lite = "0.1" [target.'cfg(any(target_os = "android", target_os = "linux"))'.dependencies] diff --git a/userspace/ksud/src/cli.rs b/userspace/ksud/src/cli.rs index 3edb8abc..d21f2c0d 100644 --- a/userspace/ksud/src/cli.rs +++ b/userspace/ksud/src/cli.rs @@ -1,13 +1,12 @@ use anyhow::{Ok, Result}; use clap::Parser; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; #[cfg(target_os = "android")] use android_logger::Config; #[cfg(target_os = "android")] use log::LevelFilter; -use crate::defs::KSUD_VERBOSE_LOG_FILE; use crate::{apk_sign, assets, debug, defs, init_event, ksucalls, module, utils}; /// KernelSU-Next userspace cli @@ -16,9 +15,6 @@ use crate::{apk_sign, assets, debug, defs, init_event, ksucalls, module, utils}; struct Args { #[command(subcommand)] command: Commands, - - #[arg(short, long, default_value_t = cfg!(debug_assertions))] - verbose: bool, } #[derive(clap::Subcommand, Debug)] @@ -165,6 +161,17 @@ enum Debug { 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 Test, } @@ -230,6 +237,9 @@ enum Module { /// list all modules List, + + /// Shrink module image size + Shrink, } #[derive(clap::Subcommand, Debug)] @@ -291,10 +301,6 @@ pub fn run() -> Result<()> { 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); let result = match cli.command { @@ -315,6 +321,7 @@ pub fn run() -> Result<()> { Module::Disable { id } => module::disable_module(&id), Module::Action { id } => module::run_action(&id), Module::List => module::list_modules(), + Module::Shrink => module::shrink_ksu_images(), } } Commands::Install { magiskboot } => utils::install(magiskboot), @@ -348,7 +355,15 @@ pub fn run() -> Result<()> { Ok(()) } 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), }, diff --git a/userspace/ksud/src/defs.rs b/userspace/ksud/src/defs.rs index 5454def1..d5514501 100644 --- a/userspace/ksud/src/defs.rs +++ b/userspace/ksud/src/defs.rs @@ -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 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 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 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!!! -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 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 REMOVE_FILE_NAME: &str = "remove"; 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_NAME: &str = include_str!(concat!(env!("OUT_DIR"), "/VERSION_NAME")); diff --git a/userspace/ksud/src/init_event.rs b/userspace/ksud/src/init_event.rs index f5bb8b87..5839c195 100644 --- a/userspace/ksud/src/init_event.rs +++ b/userspace/ksud/src/init_event.rs @@ -1,10 +1,100 @@ -use crate::defs::{KSU_MOUNT_SOURCE, TEMP_DIR}; -use crate::module::{handle_updated_modules, prune_modules}; -use crate::{assets, defs, ksucalls, restorecon, utils}; -use anyhow::{Context, Result}; +use anyhow::{bail, Context, Result}; use log::{info, warn}; -use rustix::fs::{mount, MountFlags}; -use std::path::Path; +use std::{collections::HashMap, 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) -> 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 = Vec::new(); + + let partition = vec!["vendor", "product", "system_ext", "odm", "oem"]; + let mut partition_lowerdir: HashMap> = 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<()> { ksucalls::report_post_fs_data(); @@ -21,9 +111,11 @@ pub fn on_post_data_fs() -> Result<()> { return Ok(()); } - let safe_mode = utils::is_safe_mode(); + let safe_mode = crate::utils::is_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"); } else { // 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")?; + 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 ksucalls::report_module_mounted(); @@ -50,10 +177,6 @@ pub fn on_post_data_fs() -> Result<()> { warn!("prune modules failed: {}", e); } - if let Err(e) = handle_updated_modules() { - warn!("handle updated modules failed: {}", e); - } - if let Err(e) = restorecon::restorecon() { warn!("restorecon failed: {}", e); } @@ -68,7 +191,7 @@ pub fn on_post_data_fs() -> Result<()> { } // 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); } @@ -83,23 +206,15 @@ pub fn on_post_data_fs() -> Result<()> { warn!("load system.prop failed: {}", e); } - // mount module systemlessly by magic mount - if let Err(e) = mount_modules_systemlessly() { + // mount module systemlessly by overlay + if let Err(e) = mount_modules_systemlessly(module_dir) { warn!("do systemless mount failed: {}", e); } 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(()) } @@ -134,6 +249,17 @@ pub fn on_services() -> Result<()> { pub fn on_boot_completed() -> Result<()> { ksucalls::report_boot_complete(); 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); diff --git a/userspace/ksud/src/installer.sh b/userspace/ksud/src/installer.sh index 4d20ec0e..957572af 100644 --- a/userspace/ksud/src/installer.sh +++ b/userspace/ksud/src/installer.sh @@ -85,7 +85,7 @@ setup_flashable() { $BOOTMODE && return if [ -z $OUTFD ] || readlink /proc/$$/fd/$OUTFD | grep -q /tmp; then # 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 ps | grep -v grep | grep -qE " 3 $FD |status_fd=$FD"; then OUTFD=$FD @@ -302,16 +302,18 @@ is_legacy_script() { } handle_partition() { - PARTITION="$1" - REQUIRE_SYMLINK="$2" - if [ ! -e "$MODPATH/system/$PARTITION" ]; then + # if /system/vendor is a symlink, we need to move it out of $MODPATH/system, otherwise it will be overlayed + # 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/$1 ]; then # no partition found return; fi - if [ "$REQUIRE_SYMLINK" = "false" ] || [ -L "/system/$PARTITION" ] && [ "$(readlink -f "/system/$PARTITION")" = "/$PARTITION" ]; then - ui_print "- Handle partition /$PARTITION" - ln -sf "./system/$PARTITION" "$MODPATH/$PARTITION" + if [ -L "/system/$1" ] && [ "$(readlink -f /system/$1)" = "/$1" ]; then + ui_print "- Handle partition /$1" + # 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 } @@ -389,23 +391,22 @@ install_module() { [ -f $MODPATH/customize.sh ] && . $MODPATH/customize.sh fi - handle_partition vendor true - handle_partition system_ext true - handle_partition product true - handle_partition odm false - # Handle replace folders for TARGET in $REPLACE; do ui_print "- Replace target: $TARGET" - mark_replace "$MODPATH$TARGET" + mark_replace $MODPATH$TARGET done # Handle remove files for TARGET in $REMOVE; do ui_print "- Remove target: $TARGET" - mark_remove "$MODPATH$TARGET" + mark_remove $MODPATH$TARGET done + handle_partition vendor + handle_partition system_ext + handle_partition product + if $BOOTMODE; then mktouch $NVBASE/modules/$MODID/update rm -rf $NVBASE/modules/$MODID/remove 2>/dev/null diff --git a/userspace/ksud/src/magic_mount.rs b/userspace/ksud/src/magic_mount.rs deleted file mode 100644 index c6bd9f18..00000000 --- a/userspace/ksud/src/magic_mount.rs +++ /dev/null @@ -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 { - 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, - // the module that owned this node - module_path: Option, - replace: bool, - skip: bool, -} - -impl Node { - fn collect_module_files>(&mut self, module_dir: T) -> Result { - 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(name: T) -> Self { - Node { - name: name.to_string(), - file_type: Directory, - children: Default::default(), - module_path: None, - replace: false, - skip: false, - } - } - - fn new_module(name: T, entry: &DirEntry) -> Option { - 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> { - 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, Dst: AsRef>(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, WP: AsRef>( - 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, WP: AsRef>( - path: P, - work_dir_path: WP, - current: Node, - has_tmpfs: bool, -) -> Result<()> { - let mut current = current; - let path = path.as_ref().join(¤t.name); - let work_dir_path = work_dir_path.as_ref().join(¤t.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) = ¤t.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) = ¤t.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) = ¤t.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(()) - } -} diff --git a/userspace/ksud/src/main.rs b/userspace/ksud/src/main.rs index a91fc0cb..3b517205 100644 --- a/userspace/ksud/src/main.rs +++ b/userspace/ksud/src/main.rs @@ -6,9 +6,8 @@ mod debug; mod defs; mod init_event; mod ksucalls; -#[cfg(target_os = "android")] -mod magic_mount; mod module; +mod mount; mod profile; mod restorecon; mod sepolicy; diff --git a/userspace/ksud/src/module.rs b/userspace/ksud/src/module.rs index 8c5c8c87..92e2cb59 100644 --- a/userspace/ksud/src/module.rs +++ b/userspace/ksud/src/module.rs @@ -1,9 +1,9 @@ #[allow(clippy::wildcard_imports)] use crate::utils::*; use crate::{ - assets, defs, ksucalls, + assets, defs, ksucalls, mount, restorecon::{restore_syscon, setsyscon}, - sepolicy, + sepolicy, utils, }; use anyhow::{anyhow, bail, ensure, Context, Result}; @@ -12,21 +12,20 @@ use is_executable::is_executable; use java_properties::PropertiesIter; use log::{info, warn}; -use std::fs::{copy, rename}; +use std::fs::OpenOptions; use std::{ collections::HashMap, env::var as env_var, fs::{remove_dir_all, remove_file, set_permissions, File, Permissions}, io::Cursor, path::{Path, PathBuf}, - process::Command, + process::{Command, Stdio}, str::FromStr, }; use zip_extensions::zip_extract_file_to_memory; -use crate::defs::{MODULE_DIR, MODULE_UPDATE_DIR, UPDATE_FILE_NAME}; #[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 INSTALL_MODULE_SCRIPT: &str = concatcp!( @@ -76,34 +75,24 @@ fn ensure_boot_completed() -> Result<()> { Ok(()) } -fn mark_module_state(module: &str, flag_file: &str, create: bool) -> Result<()> { - let module_state_file = Path::new(MODULE_DIR).join(module).join(flag_file); - if create { +fn mark_update() -> Result<()> { + ensure_file_exists(concatcp!(defs::WORKING_DIR, defs::UPDATE_FILE_NAME)) +} + +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) } else { if module_state_file.exists() { - remove_file(module_state_file)?; + std::fs::remove_file(module_state_file)?; } Ok(()) } } -#[derive(PartialEq, Eq)] -enum ModuleType { - 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(()); - } +fn foreach_module(active_only: bool, mut f: impl FnMut(&Path) -> Result<()>) -> Result<()> { + let modules_dir = Path::new(defs::MODULE_DIR); let dir = std::fs::read_dir(modules_dir)?; for entry in dir.flatten() { let path = entry.path(); @@ -112,11 +101,11 @@ fn foreach_module(module_type: ModuleType, mut f: impl FnMut(&Path) -> Result<() 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()); 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()); 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<()> { - 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<()> { @@ -247,153 +256,370 @@ pub fn load_system_prop() -> Result<()> { } pub fn prune_modules() -> Result<()> { - foreach_module(ModuleType::All, |module| { - if module.join(defs::REMOVE_FILE_NAME).exists() { - info!("remove module: {}", module.display()); + foreach_module(false, |module| { + remove_file(module.join(defs::UPDATE_FILE_NAME)).ok(); - 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); - } - } else { - remove_file(module.join(defs::UPDATE_FILE_NAME)).ok(); + if !module.join(defs::REMOVE_FILE_NAME).exists() { + return 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(()) } -pub fn handle_updated_modules() -> Result<()> { - let modules_root = Path::new(MODULE_DIR); - foreach_module(ModuleType::Updated, |module| { - if !module.is_dir() { - return Ok(()); - } +fn create_module_image(image: &str, image_size: u64, journal_size: u64) -> Result<()> { + File::create(image) + .context("Failed to create ext4 image file")? + .set_len(image_size) + .context("Failed to truncate ext4 image")?; - if let Some(name) = module.file_name() { - let old_dir = modules_root.join(name); - if old_dir.exists() { - if let Err(e) = remove_dir_all(&old_dir) { - log::error!("Failed to remove old {}: {}", old_dir.display(), e); - } - } - if let Err(e) = rename(module, &old_dir) { - log::error!("Failed to move new module {}: {}", module.display(), e); + // format the img to ext4 filesystem + let result = Command::new("mkfs.ext4") + .arg("-J") + .arg(format!("size={journal_size}")) + .arg(image) + .stdout(Stdio::piped()) + .output()?; + ensure!( + 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 = 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/ 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(()) } pub fn install_module(zip: &str) -> Result<()> { - fn inner(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 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 = 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/ 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); + let result = _install_module(zip); 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}"); } result } +fn update_module(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<()> { - 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<()> { - 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<()> { @@ -401,12 +627,37 @@ pub fn run_action(id: &str) -> Result<()> { 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<()> { - 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<()> { - 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<()> { @@ -418,7 +669,8 @@ pub fn uninstall_all_modules() -> 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() { let path = entry.path(); let flag = path.join(flag_file); @@ -496,3 +748,21 @@ pub fn list_modules() -> Result<()> { println!("{}", serde_json::to_string_pretty(&modules)?); 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(()) +} diff --git a/userspace/ksud/src/mount.rs b/userspace/ksud/src/mount.rs new file mode 100644 index 00000000..a15deb97 --- /dev/null +++ b/userspace/ksud/src/mount.rs @@ -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 { + 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 { + 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, target: impl AsRef) -> 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) -> 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, + workdir: Option, + dest: impl AsRef, +) -> Result<()> { + let lowerdir_config = lower_dirs + .iter() + .map(|s| s.as_ref()) + .chain(std::iter::once(lowest)) + .collect::>() + .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) -> 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, to: impl AsRef) -> 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, + 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 = 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, + workdir: Option, + upperdir: Option, +) -> 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::>(); + 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, + _workdir: Option, + _upperdir: Option, +) -> Result<()> { + unimplemented!() +} + +#[cfg(not(any(target_os = "linux", target_os = "android")))] +pub fn mount_tmpfs(_dest: impl AsRef) -> Result<()> { + unimplemented!() +} diff --git a/userspace/ksud/src/restorecon.rs b/userspace/ksud/src/restorecon.rs index c396481d..152a7c5e 100644 --- a/userspace/ksud/src/restorecon.rs +++ b/userspace/ksud/src/restorecon.rs @@ -61,11 +61,11 @@ pub fn restore_syscon>(dir: P) -> Result<()> { Ok(()) } -fn restore_modules_con>(dir: P) -> Result<()> { +fn restore_syscon_if_unlabeled>(dir: P) -> Result<()> { for dir_entry in WalkDir::new(dir).parallelism(Serial) { if let Some(path) = dir_entry.ok().map(|dir_entry| dir_entry.path()) { - if let Result::Ok(con) = lgetfilecon(&path) { - if con == ADB_CON || con == UNLABEL_CON || con.is_empty() { + if let anyhow::Result::Ok(con) = lgetfilecon(&path) { + if con == UNLABEL_CON || con.is_empty() { lsetfilecon(&path, SYSTEM_CON)?; } } @@ -76,6 +76,6 @@ fn restore_modules_con>(dir: P) -> Result<()> { pub fn restorecon() -> Result<()> { lsetfilecon(defs::DAEMON_PATH, ADB_CON)?; - restore_modules_con(defs::MODULE_DIR)?; + restore_syscon_if_unlabeled(defs::MODULE_DIR)?; Ok(()) } diff --git a/userspace/ksud/src/utils.rs b/userspace/ksud/src/utils.rs index 068911ed..d947546f 100644 --- a/userspace/ksud/src/utils.rs +++ b/userspace/ksud/src/utils.rs @@ -15,6 +15,10 @@ use std::fs::{set_permissions, Permissions}; #[cfg(unix)] use std::os::unix::prelude::PermissionsExt; +use hole_punch::*; +use std::io::{Read, Seek, SeekFrom}; + +use jwalk::WalkDir; use std::path::PathBuf; #[cfg(any(target_os = "linux", target_os = "android"))] @@ -35,7 +39,7 @@ pub fn ensure_clean_dir(dir: impl AsRef) -> Result<()> { pub fn ensure_file_exists>(file: T) -> Result<()> { match File::options().write(true).create_new(true).open(&file) { - Result::Ok(_) => Ok(()), + std::result::Result::Ok(_) => Ok(()), Err(err) => { if err.kind() == AlreadyExists && file.as_ref().is_file() { Ok(()) @@ -220,7 +224,9 @@ pub fn uninstall(magiskboot_path: Option) -> Result<()> { println!("- Removing directories.."); std::fs::remove_dir_all(defs::WORKING_DIR).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_UPDATE_TMP_DIR).ok(); println!("- Restore boot image.."); boot_patch::restore(None, magiskboot_path, true)?; println!("- Uninstall KernelSU-Next manager.."); @@ -232,3 +238,153 @@ pub fn uninstall(magiskboot_path: Option) -> Result<()> { Command::new("reboot").spawn()?; Ok(()) } + +// TODO: use libxcp to improve the speed if cross's MSRV is 1.70 +pub fn copy_sparse_file, Q: AsRef>( + 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, dest_path: impl AsRef) -> 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, destination: impl AsRef) -> 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, _destination: impl AsRef) -> Result<()> { + unimplemented!() +}