From 7a35c43fb964b3e5ece35aef1b642f9ccf4acd13 Mon Sep 17 00:00:00 2001 From: Rifat Azad Date: Mon, 9 Dec 2024 22:58:32 +0600 Subject: [PATCH] Revert "ksud: implement magic_mount" This reverts commit de12a47b2c7749aac89fc4723c5b9df47624e8e8. --- .../me/weishu/kernelsu/ui/screen/Module.kt | 14 + .../java/me/weishu/kernelsu/ui/util/KsuCli.kt | 6 + .../kernelsu/ui/viewmodel/ModuleViewModel.kt | 6 + .../app/src/main/res/values-ar/strings.xml | 1 + .../app/src/main/res/values-az/strings.xml | 1 + .../app/src/main/res/values-bn/strings.xml | 1 + .../app/src/main/res/values-bs/strings.xml | 1 + .../app/src/main/res/values-da/strings.xml | 1 + .../app/src/main/res/values-de/strings.xml | 1 + .../app/src/main/res/values-es/strings.xml | 1 + .../app/src/main/res/values-et/strings.xml | 1 + .../app/src/main/res/values-fa/strings.xml | 1 + .../app/src/main/res/values-fil/strings.xml | 1 + .../app/src/main/res/values-fr/strings.xml | 1 + .../app/src/main/res/values-hi/strings.xml | 1 + .../app/src/main/res/values-hr/strings.xml | 1 + .../app/src/main/res/values-hu/strings.xml | 1 + .../app/src/main/res/values-in/strings.xml | 1 + .../app/src/main/res/values-it/strings.xml | 1 + .../app/src/main/res/values-iw/strings.xml | 1 + .../app/src/main/res/values-ja/strings.xml | 1 + .../app/src/main/res/values-kn/strings.xml | 1 + .../app/src/main/res/values-ko/strings.xml | 1 + .../app/src/main/res/values-lt/strings.xml | 1 + .../app/src/main/res/values-lv/strings.xml | 1 + .../app/src/main/res/values-mr/strings.xml | 1 + .../app/src/main/res/values-nl/strings.xml | 1 + .../app/src/main/res/values-pl/strings.xml | 1 + .../src/main/res/values-pt-rBR/strings.xml | 1 + .../app/src/main/res/values-pt/strings.xml | 1 + .../app/src/main/res/values-ro/strings.xml | 1 + .../app/src/main/res/values-ru/strings.xml | 1 + .../app/src/main/res/values-sl/strings.xml | 1 + .../app/src/main/res/values-th/strings.xml | 1 + .../app/src/main/res/values-tr/strings.xml | 1 + .../app/src/main/res/values-uk/strings.xml | 1 + .../app/src/main/res/values-vi/strings.xml | 1 + .../src/main/res/values-zh-rCN/strings.xml | 1 + .../src/main/res/values-zh-rHK/strings.xml | 1 + .../src/main/res/values-zh-rTW/strings.xml | 1 + manager/app/src/main/res/values/strings.xml | 3 +- userspace/ksud/Cargo.lock | 48 +- 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 | 528 +++++++++++++----- userspace/ksud/src/mount.rs | 306 ++++++++++ userspace/ksud/src/restorecon.rs | 8 +- userspace/ksud/src/utils.rs | 158 +++++- 53 files changed, 1144 insertions(+), 655 deletions(-) delete mode 100644 userspace/ksud/src/magic_mount.rs create mode 100644 userspace/ksud/src/mount.rs diff --git a/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/Module.kt b/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/Module.kt index 09d1f607..193f76b2 100644 --- a/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/Module.kt +++ b/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/Module.kt @@ -362,6 +362,20 @@ private fun ModuleList( }, ) { when { + !viewModel.isOverlayAvailable -> { + item { + Box( + modifier = Modifier.fillParentMaxSize(), + contentAlignment = Alignment.Center + ) { + Text( + stringResource(R.string.module_overlay_fs_not_available), + textAlign = TextAlign.Center + ) + } + } + } + viewModel.moduleList.isEmpty() -> { item { Box( diff --git a/manager/app/src/main/java/me/weishu/kernelsu/ui/util/KsuCli.kt b/manager/app/src/main/java/me/weishu/kernelsu/ui/util/KsuCli.kt index 6fbc8773..1a2416b2 100644 --- a/manager/app/src/main/java/me/weishu/kernelsu/ui/util/KsuCli.kt +++ b/manager/app/src/main/java/me/weishu/kernelsu/ui/util/KsuCli.kt @@ -351,6 +351,12 @@ suspend fun getSupportedKmis(): List = withContext(Dispatchers.IO) { out.filter { it.isNotBlank() }.map { it.trim() } } +fun overlayFsAvailable(): Boolean { + val shell = getRootShell() + // check /proc/filesystems + return ShellUtils.fastCmdResult(shell, "cat /proc/filesystems | grep overlay") +} + fun hasMagisk(): Boolean { val shell = getRootShell(true) val result = shell.newJob().add("which magisk").exec() diff --git a/manager/app/src/main/java/me/weishu/kernelsu/ui/viewmodel/ModuleViewModel.kt b/manager/app/src/main/java/me/weishu/kernelsu/ui/viewmodel/ModuleViewModel.kt index e9611a10..4533e6e5 100644 --- a/manager/app/src/main/java/me/weishu/kernelsu/ui/viewmodel/ModuleViewModel.kt +++ b/manager/app/src/main/java/me/weishu/kernelsu/ui/viewmodel/ModuleViewModel.kt @@ -11,6 +11,7 @@ import androidx.lifecycle.viewModelScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import me.weishu.kernelsu.ui.util.listModules +import me.weishu.kernelsu.ui.util.overlayFsAvailable import org.json.JSONArray import org.json.JSONObject import java.text.Collator @@ -48,6 +49,9 @@ class ModuleViewModel : ViewModel() { var isRefreshing by mutableStateOf(false) private set + var isOverlayAvailable by mutableStateOf(overlayFsAvailable()) + private set + val moduleList by derivedStateOf { val comparator = compareBy(Collator.getInstance(Locale.getDefault()), ModuleInfo::id) modules.sortedWith(comparator).also { @@ -71,6 +75,8 @@ class ModuleViewModel : ViewModel() { val start = SystemClock.elapsedRealtime() kotlin.runCatching { + isOverlayAvailable = overlayFsAvailable() + val result = listModules() Log.i(TAG, "result: $result") diff --git a/manager/app/src/main/res/values-ar/strings.xml b/manager/app/src/main/res/values-ar/strings.xml index 35e6983a..36d1b26f 100644 --- a/manager/app/src/main/res/values-ar/strings.xml +++ b/manager/app/src/main/res/values-ar/strings.xml @@ -38,6 +38,7 @@ فشل إلغاء التثبيت: %s الإصدار المطور + الوحدات غير متوفرة حيث يتم تعطيل نظام الملفات المتراكب بواسطة النواة. إنعاش إظهار تطبيقات النظام إخفاء تطبيقات النظام diff --git a/manager/app/src/main/res/values-az/strings.xml b/manager/app/src/main/res/values-az/strings.xml index 2d78feed..21ec189f 100644 --- a/manager/app/src/main/res/values-az/strings.xml +++ b/manager/app/src/main/res/values-az/strings.xml @@ -40,6 +40,7 @@ Silmək mümkün olmadı: %s %s silindi Sistem proqramlarını gizlət + overlayfs mövcud deyil,modul işləyə bilməyəcək! Log-u göndər Yenilə Təhlükəsiz rejimi diff --git a/manager/app/src/main/res/values-bn/strings.xml b/manager/app/src/main/res/values-bn/strings.xml index 588424aa..312672c8 100644 --- a/manager/app/src/main/res/values-bn/strings.xml +++ b/manager/app/src/main/res/values-bn/strings.xml @@ -38,6 +38,7 @@ আনইন্সটল ব্যর্থ: %s ভার্সন লেখক + ওভারলেএফএস উপলব্ধ নয়, মডিউল কাজ করতে পারে না! রিফ্রেশ শো সিস্টেম অ্যাপস হাইড সিস্টেম অ্যাপস diff --git a/manager/app/src/main/res/values-bs/strings.xml b/manager/app/src/main/res/values-bs/strings.xml index 8ce06eb2..da078631 100644 --- a/manager/app/src/main/res/values-bs/strings.xml +++ b/manager/app/src/main/res/values-bs/strings.xml @@ -73,6 +73,7 @@ Isključeno O Jeste li sigurni da želite deinstalirati modulu %s\? + overlayfs nije dostupan, modula ne može raditi! KernelSU je, i uvijek če biti, besplatan, i otvorenog izvora. Možete nam međutim pokazati da vas je briga s time da napravite donaciju. Zadano Šablon diff --git a/manager/app/src/main/res/values-da/strings.xml b/manager/app/src/main/res/values-da/strings.xml index a487382f..6ab02256 100644 --- a/manager/app/src/main/res/values-da/strings.xml +++ b/manager/app/src/main/res/values-da/strings.xml @@ -25,6 +25,7 @@ Er du sikker på, at du vil afinstallere modulet %s\? %s afinstalleret Afinstallation af: %s fejlede + overlayfs er ikke tilgængeligt, modulet kan ikke fungere! Opdater Send Log Sikker tilstand diff --git a/manager/app/src/main/res/values-de/strings.xml b/manager/app/src/main/res/values-de/strings.xml index 9411f95c..721dba59 100644 --- a/manager/app/src/main/res/values-de/strings.xml +++ b/manager/app/src/main/res/values-de/strings.xml @@ -14,6 +14,7 @@ In den Download-Modus neustarten In den EDL-Modus neustarten Autor + Module sind nicht verfügbar, da OverlayFS vom Kernel deaktiviert ist. Über KernelSU Module sind aufgrund eines Konfliktes mit Magisk nicht verfügbar! https://kernelsu.org/guide/what-is-kernelsu.html diff --git a/manager/app/src/main/res/values-es/strings.xml b/manager/app/src/main/res/values-es/strings.xml index f65ba748..a630db99 100644 --- a/manager/app/src/main/res/values-es/strings.xml +++ b/manager/app/src/main/res/values-es/strings.xml @@ -39,6 +39,7 @@ Fallo al desinstalar: %s Versión Autor + Los módulos no están disponibles ya que OverlayFS está desactivado por el kernel. Refrescar Mostrar aplicaciones del sistema Ocultar aplicaciones del sistema diff --git a/manager/app/src/main/res/values-et/strings.xml b/manager/app/src/main/res/values-et/strings.xml index 9f83e6cf..4f177ff8 100644 --- a/manager/app/src/main/res/values-et/strings.xml +++ b/manager/app/src/main/res/values-et/strings.xml @@ -59,6 +59,7 @@ Autor Eemaldamine ebaõnnestus: %s Versioon + Moodulid pole saadaval, kuna OverlayFS on kernelis keelatud. Kuva süsteemirakendused Peida süsteemirakendused Moodulid pole saadaval Magiski konflikti tõttu! diff --git a/manager/app/src/main/res/values-fa/strings.xml b/manager/app/src/main/res/values-fa/strings.xml index 17bc3d4d..17469cc8 100644 --- a/manager/app/src/main/res/values-fa/strings.xml +++ b/manager/app/src/main/res/values-fa/strings.xml @@ -38,6 +38,7 @@ پاک کردن ناموفق بود: %s نسخه سازنده + overlayfs موجود نیست. مازول کار نمیکند!! تازه‌سازی نمایش برنامه های سیستمی مخفی کردن برنامه های سیستمی diff --git a/manager/app/src/main/res/values-fil/strings.xml b/manager/app/src/main/res/values-fil/strings.xml index 259590a6..fc527bf3 100644 --- a/manager/app/src/main/res/values-fil/strings.xml +++ b/manager/app/src/main/res/values-fil/strings.xml @@ -28,6 +28,7 @@ Na-uninstall ang %s Nabigong i-uninstall: %s May-akda + Ang overlayfs ay hindi magagamit, ang modyul ay hindi gagana! I-refresh Ipakita ang mga application ng system Magpadala ng Log diff --git a/manager/app/src/main/res/values-fr/strings.xml b/manager/app/src/main/res/values-fr/strings.xml index aa437c4a..a5de8723 100644 --- a/manager/app/src/main/res/values-fr/strings.xml +++ b/manager/app/src/main/res/values-fr/strings.xml @@ -39,6 +39,7 @@ Auteur Êtes-vous sûr(e) de vouloir désinstaller le module %s \? Découvrir KernelSU + Les modules sont indisponibles car OverlayFS est désactivé par le noyau. Rafraîchir Afficher les applications système Masquer les applications système diff --git a/manager/app/src/main/res/values-hi/strings.xml b/manager/app/src/main/res/values-hi/strings.xml index 390d73d9..f9304469 100644 --- a/manager/app/src/main/res/values-hi/strings.xml +++ b/manager/app/src/main/res/values-hi/strings.xml @@ -44,6 +44,7 @@ इंस्टाल करने के लिए क्लिक करें नियम समूह + Overlayfs उपलब्ध नहीं है, मॉड्यूल काम नहीं कर सकता ! मॉड्यूल निर्माता हमारे बारे में diff --git a/manager/app/src/main/res/values-hr/strings.xml b/manager/app/src/main/res/values-hr/strings.xml index 8b5d72ab..9fb1e4c6 100644 --- a/manager/app/src/main/res/values-hr/strings.xml +++ b/manager/app/src/main/res/values-hr/strings.xml @@ -45,6 +45,7 @@ Verzija Autor Osvježi + overlayfs nije dostupan, modula ne može raditi! Module su isključene jer je u sukobu sa Magisk-om! Naučite KernelSU https://kernelsu.org/guide/what-is-kernelsu.html diff --git a/manager/app/src/main/res/values-hu/strings.xml b/manager/app/src/main/res/values-hu/strings.xml index 9a34be44..2b52317c 100644 --- a/manager/app/src/main/res/values-hu/strings.xml +++ b/manager/app/src/main/res/values-hu/strings.xml @@ -14,6 +14,7 @@ Biztos vagy benne hogy eltávolítod a következő modult: %s\? Nem sikerült eltávolítani: %s Készítő + Overlayfs nem elérhető, a modul nem tud enélkül működni! Újratöltés Mutasd a rendszer alkalmazásokat Rejtsd el a rendszer alkalmazásokat diff --git a/manager/app/src/main/res/values-in/strings.xml b/manager/app/src/main/res/values-in/strings.xml index a9756699..f2223e06 100644 --- a/manager/app/src/main/res/values-in/strings.xml +++ b/manager/app/src/main/res/values-in/strings.xml @@ -38,6 +38,7 @@ Gagal menghapus: %s Versi Oleh + Kernel tidak mendukung OverlayFS, modul tidak berfungsi. Muat ulang Tampilkan aplikasi sistem Sembunyikan aplikasi sistem diff --git a/manager/app/src/main/res/values-it/strings.xml b/manager/app/src/main/res/values-it/strings.xml index 54e34512..642640b3 100644 --- a/manager/app/src/main/res/values-it/strings.xml +++ b/manager/app/src/main/res/values-it/strings.xml @@ -38,6 +38,7 @@ Impossibile disinstallare: %s Versione Autore + overlayfs non è disponibile, i moduli non possono funzionare! Ricarica Mostra app di sistema Nascondi app di sistema diff --git a/manager/app/src/main/res/values-iw/strings.xml b/manager/app/src/main/res/values-iw/strings.xml index 46364706..18cfc638 100644 --- a/manager/app/src/main/res/values-iw/strings.xml +++ b/manager/app/src/main/res/values-iw/strings.xml @@ -44,6 +44,7 @@ לחץ להתקנה כללים קבוצה + שכבות-על לא זמינות, המודול לא יכול לעבוד! מודולים יוצר אודות diff --git a/manager/app/src/main/res/values-ja/strings.xml b/manager/app/src/main/res/values-ja/strings.xml index f0611907..bf2108a8 100644 --- a/manager/app/src/main/res/values-ja/strings.xml +++ b/manager/app/src/main/res/values-ja/strings.xml @@ -38,6 +38,7 @@ アンインストールに失敗: %s バージョン 制作者 + カーネルによって OverlayFS が無効になっているため、モジュールが利用できません。 更新 システムアプリを表示 システムアプリを非表示 diff --git a/manager/app/src/main/res/values-kn/strings.xml b/manager/app/src/main/res/values-kn/strings.xml index df9a46c5..3c4e79fe 100644 --- a/manager/app/src/main/res/values-kn/strings.xml +++ b/manager/app/src/main/res/values-kn/strings.xml @@ -36,6 +36,7 @@ ಮೌಂಟ್ ನೇಮ್‌ಸ್ಪೇಸ್ ನಿಯಮಗಳು ಗುಂಪುಗಳು + ಓವರ್‌ಲೇಫ್‌ಗಳು ಲಭ್ಯವಿಲ್ಲ, ಮಾಡ್ಯೂಲ್ ಕಾರ್ಯನಿರ್ವಹಿಸುವುದಿಲ್ಲ! ಮಾಡ್ಯೂಲ್ ಲೇಖಕ ಬಗ್ಗೆ diff --git a/manager/app/src/main/res/values-ko/strings.xml b/manager/app/src/main/res/values-ko/strings.xml index 1f4e12b6..7c29cc02 100644 --- a/manager/app/src/main/res/values-ko/strings.xml +++ b/manager/app/src/main/res/values-ko/strings.xml @@ -38,6 +38,7 @@ 모듈 삭제 실패: %s 버전 제작자 + overlayfs 사용 불가, 모듈을 사용할 수 없습니다! 새로고침 시스템 앱 보이기 시스템 앱 숨기기 diff --git a/manager/app/src/main/res/values-lt/strings.xml b/manager/app/src/main/res/values-lt/strings.xml index bae21077..cba5f5b1 100644 --- a/manager/app/src/main/res/values-lt/strings.xml +++ b/manager/app/src/main/res/values-lt/strings.xml @@ -19,6 +19,7 @@ %s išdiegtas Versija Autorius + overlayfs nepasiekiamas, modulis negali veikti! Rodyti sistemos programas Slėpti sistemos programas Siųsti žurnalą diff --git a/manager/app/src/main/res/values-lv/strings.xml b/manager/app/src/main/res/values-lv/strings.xml index 117c7f52..c264b76a 100644 --- a/manager/app/src/main/res/values-lv/strings.xml +++ b/manager/app/src/main/res/values-lv/strings.xml @@ -94,6 +94,7 @@ Instalēt Vai tiešām vēlaties atinstalēt moduli %s? Versija + overlayfs nav pieejams, modulis nevar darboties! Drošais režīms Moduļi ir atspējoti, jo tie konfliktē ar Magisk! KernelSU ir un vienmēr būs bezmaksas un atvērtā koda. Tomēr jūs varat parādīt mums, ka jums rūp, veicot ziedojumu. diff --git a/manager/app/src/main/res/values-mr/strings.xml b/manager/app/src/main/res/values-mr/strings.xml index 1be3706a..eaaf2a25 100644 --- a/manager/app/src/main/res/values-mr/strings.xml +++ b/manager/app/src/main/res/values-mr/strings.xml @@ -32,6 +32,7 @@ EDL वर रीबूट करा तुमची खात्री आहे की तुम्ही मॉड्यूल %s विस्थापित करू इच्छिता\? विस्थापित करण्यात अयशस्वी: %s + overlayfs उपलब्ध नाही, मॉड्यूल काम करू शकत नाही! सिस्टम अॅप्स दाखवा बूटलोडरवर रीबूट करा %s विस्थापित diff --git a/manager/app/src/main/res/values-nl/strings.xml b/manager/app/src/main/res/values-nl/strings.xml index bf1560ee..19031425 100644 --- a/manager/app/src/main/res/values-nl/strings.xml +++ b/manager/app/src/main/res/values-nl/strings.xml @@ -38,6 +38,7 @@ Mislukt om te verwijderen: %s Versie Auteur + Modules zijn niet beschikbaar omdat OverlayFS door de kernel is uitgeschakeld. Vernieuwen Toon systeem apps Verberg systeem apps diff --git a/manager/app/src/main/res/values-pl/strings.xml b/manager/app/src/main/res/values-pl/strings.xml index 9194d5e7..61250420 100644 --- a/manager/app/src/main/res/values-pl/strings.xml +++ b/manager/app/src/main/res/values-pl/strings.xml @@ -39,6 +39,7 @@ Nie udało się odinstalować:: %s Wersja Autor + Moduły są niedostępne, ponieważ OverlayFS jest wyłączony przez jądro. Odśwież Pokaż aplikacje systemowe Ukryj aplikacje systemowe diff --git a/manager/app/src/main/res/values-pt-rBR/strings.xml b/manager/app/src/main/res/values-pt-rBR/strings.xml index b6255eca..4d69f8a7 100644 --- a/manager/app/src/main/res/values-pt-rBR/strings.xml +++ b/manager/app/src/main/res/values-pt-rBR/strings.xml @@ -38,6 +38,7 @@ Falha ao desinstalar %s Versão Autor + Os módulos estão indisponíveis porque OverlayFS está desabilitado pelo kernel! Atualizar Mostrar apps do sistema Ocultar apps do sistema diff --git a/manager/app/src/main/res/values-pt/strings.xml b/manager/app/src/main/res/values-pt/strings.xml index 970cfe83..ea1547db 100644 --- a/manager/app/src/main/res/values-pt/strings.xml +++ b/manager/app/src/main/res/values-pt/strings.xml @@ -39,6 +39,7 @@ Reiniciar em EDL Tem certeza de que deseja desinstalar o módulo %s\? Sobre + overlayfs não está disponível, o módulo não pode funcionar! Enviar log %s desinstalado Mostrar aplicativos do sistema diff --git a/manager/app/src/main/res/values-ro/strings.xml b/manager/app/src/main/res/values-ro/strings.xml index 743f3922..fea3ffb6 100644 --- a/manager/app/src/main/res/values-ro/strings.xml +++ b/manager/app/src/main/res/values-ro/strings.xml @@ -38,6 +38,7 @@ Dezinstalare eșuată: %s Versiune Autor + overlayfs nu este disponibil, modulul nu poate funcționa! Reîmprospătează Arată aplicațiile de sistem Ascunde aplicațiile de sistem diff --git a/manager/app/src/main/res/values-ru/strings.xml b/manager/app/src/main/res/values-ru/strings.xml index eae51fe6..885d6bcc 100644 --- a/manager/app/src/main/res/values-ru/strings.xml +++ b/manager/app/src/main/res/values-ru/strings.xml @@ -40,6 +40,7 @@ Не удалось удалить: %s Версия Автор + Модули недоступны, так как OverlayFS отключен ядром. Обновить страницу Показать системные приложения Скрыть системные приложения diff --git a/manager/app/src/main/res/values-sl/strings.xml b/manager/app/src/main/res/values-sl/strings.xml index 8a82f550..ae02e596 100644 --- a/manager/app/src/main/res/values-sl/strings.xml +++ b/manager/app/src/main/res/values-sl/strings.xml @@ -24,6 +24,7 @@ Ste prepričani, da želite odstraniti modul %s? %s je odmeščen Avtor + overlayfs ni na voljo, modul ne more delovati! Skrij prikaz sistemskih aplikacij Prijavite dnevnik Naučite se KernelSU diff --git a/manager/app/src/main/res/values-th/strings.xml b/manager/app/src/main/res/values-th/strings.xml index c2b93b32..d903b62b 100644 --- a/manager/app/src/main/res/values-th/strings.xml +++ b/manager/app/src/main/res/values-th/strings.xml @@ -35,6 +35,7 @@ รีบูตเข้าสู่โหมด EDL %s ถอนการติดตั้งสำเร็จ ล้มเหลวในการถอนการติดตั้ง: %s + โมดูลไม่สามารถใช้งานได้เนื่องจาก OverlayFS ถูกปิดใช้งานโดยเคอร์เนล คุณแน่ใจว่าจะถอนการติดตั้งโมดูล %s หรือไม่\? ผู้สร้าง เวอร์ชัน diff --git a/manager/app/src/main/res/values-tr/strings.xml b/manager/app/src/main/res/values-tr/strings.xml index 5048fcb3..7d87c4fe 100644 --- a/manager/app/src/main/res/values-tr/strings.xml +++ b/manager/app/src/main/res/values-tr/strings.xml @@ -39,6 +39,7 @@ Kaldırma başarısız: %s Sürüm Geliştirici + OverlayFS çekirdek tarafından devre dışı bırakıldığı için modüller kullanılamıyor. Yenile Sistem uygulamalarını göster Sistem uygulamalarını gizle diff --git a/manager/app/src/main/res/values-uk/strings.xml b/manager/app/src/main/res/values-uk/strings.xml index 9b0dd7b7..919ac133 100644 --- a/manager/app/src/main/res/values-uk/strings.xml +++ b/manager/app/src/main/res/values-uk/strings.xml @@ -38,6 +38,7 @@ Не вдалося видалити: %s Версія Автор + overlayfs не доступний, модуль не може працювати! Освіжати(Оновити) Показати системні додатки Сховати системні додатки diff --git a/manager/app/src/main/res/values-vi/strings.xml b/manager/app/src/main/res/values-vi/strings.xml index 5313f13f..04d87838 100644 --- a/manager/app/src/main/res/values-vi/strings.xml +++ b/manager/app/src/main/res/values-vi/strings.xml @@ -57,6 +57,7 @@ Lỗi khi gỡ cài đặt: %s Phiên bản Tác giả + overlayfs hiện không khả dụng, mô-đun không thể hoạt động! Làm mới Hiển thị ứng dụng hệ thống Ẩn ứng dụng hệ thống diff --git a/manager/app/src/main/res/values-zh-rCN/strings.xml b/manager/app/src/main/res/values-zh-rCN/strings.xml index cf6003d8..d56b625a 100644 --- a/manager/app/src/main/res/values-zh-rCN/strings.xml +++ b/manager/app/src/main/res/values-zh-rCN/strings.xml @@ -37,6 +37,7 @@ 卸载失败: %s 版本 作者 + OverlayFS被内核禁用,模块不可用。 刷新 显示系统应用 隐藏系统应用 diff --git a/manager/app/src/main/res/values-zh-rHK/strings.xml b/manager/app/src/main/res/values-zh-rHK/strings.xml index 94775ec9..154ddac9 100644 --- a/manager/app/src/main/res/values-zh-rHK/strings.xml +++ b/manager/app/src/main/res/values-zh-rHK/strings.xml @@ -38,6 +38,7 @@ 無法解除安裝:%s 版本 作者 + OverlayFS 無法使用,模組無法正常運作! 重新整理 顯示系統應用程式 隱藏系統應用程式 diff --git a/manager/app/src/main/res/values-zh-rTW/strings.xml b/manager/app/src/main/res/values-zh-rTW/strings.xml index e595250d..7917ddef 100644 --- a/manager/app/src/main/res/values-zh-rTW/strings.xml +++ b/manager/app/src/main/res/values-zh-rTW/strings.xml @@ -37,6 +37,7 @@ 無法解除安裝:%s 版本 作者 + OverlayFS 無法使用,模組無法正常運作! 重新整理 顯示系統應用程式 隱藏系統應用程式 diff --git a/manager/app/src/main/res/values/strings.xml b/manager/app/src/main/res/values/strings.xml index 3b94e6df..e653ac57 100644 --- a/manager/app/src/main/res/values/strings.xml +++ b/manager/app/src/main/res/values/strings.xml @@ -41,6 +41,7 @@ Failed to uninstall: %s Version Author + Modules are unavailable as OverlayFS is disabled by the kernel. Refresh Show system apps Hide system apps @@ -50,7 +51,7 @@ Modules are unavailable due to a conflict with Magisk! 🔥 Next Build https://github.com/rifsxd/KernelSU - Next modifications branch. Check it out on GitHub! + Next modifications branch, Check it out! Learn KernelSU https://kernelsu.org/guide/what-is-kernelsu.html Learn how to install KernelSU and use modules diff --git a/userspace/ksud/Cargo.lock b/userspace/ksud/Cargo.lock index a373e3d6..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", @@ -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", @@ -223,6 +223,12 @@ 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" @@ -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]] @@ -499,7 +505,7 @@ 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]] @@ -610,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", ] @@ -649,6 +655,19 @@ 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" @@ -788,6 +807,7 @@ dependencies = [ "env_logger", "extattr", "getopts", + "hole-punch", "humansize", "is_executable", "java-properties", @@ -896,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" @@ -1207,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", ] @@ -1218,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", ] @@ -1271,7 +1301,7 @@ 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", @@ -1386,7 +1416,7 @@ version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "once_cell", "wasm-bindgen-macro", ] 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 7d8f35ce..14717e62 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 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, } @@ -224,6 +231,9 @@ enum Module { /// list all modules List, + + /// Shrink module image size + Shrink, } #[derive(clap::Subcommand, Debug)] @@ -285,10 +295,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 { @@ -308,6 +314,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), @@ -341,7 +348,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 2ebdddc7..40138ae3 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 4971c2f0..f927e78a 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,149 +256,318 @@ 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; // 16G + 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 run_action(id: &str) -> Result<()> { @@ -397,12 +575,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<()> { @@ -414,7 +617,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); @@ -492,3 +696,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 0bc13245..c08fdeee 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 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!() +}