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 193f76b2..09d1f607 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,20 +362,6 @@ 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 1a2416b2..6fbc8773 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,12 +351,6 @@ 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 4533e6e5..e9611a10 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,7 +11,6 @@ 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 @@ -49,9 +48,6 @@ 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 { @@ -75,8 +71,6 @@ 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 36d1b26f..35e6983a 100644 --- a/manager/app/src/main/res/values-ar/strings.xml +++ b/manager/app/src/main/res/values-ar/strings.xml @@ -38,7 +38,6 @@ فشل إلغاء التثبيت: %s الإصدار المطور - الوحدات غير متوفرة حيث يتم تعطيل نظام الملفات المتراكب بواسطة النواة. إنعاش إظهار تطبيقات النظام إخفاء تطبيقات النظام diff --git a/manager/app/src/main/res/values-az/strings.xml b/manager/app/src/main/res/values-az/strings.xml index 21ec189f..2d78feed 100644 --- a/manager/app/src/main/res/values-az/strings.xml +++ b/manager/app/src/main/res/values-az/strings.xml @@ -40,7 +40,6 @@ 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 312672c8..588424aa 100644 --- a/manager/app/src/main/res/values-bn/strings.xml +++ b/manager/app/src/main/res/values-bn/strings.xml @@ -38,7 +38,6 @@ আনইন্সটল ব্যর্থ: %s ভার্সন লেখক - ওভারলেএফএস উপলব্ধ নয়, মডিউল কাজ করতে পারে না! রিফ্রেশ শো সিস্টেম অ্যাপস হাইড সিস্টেম অ্যাপস diff --git a/manager/app/src/main/res/values-bs/strings.xml b/manager/app/src/main/res/values-bs/strings.xml index da078631..8ce06eb2 100644 --- a/manager/app/src/main/res/values-bs/strings.xml +++ b/manager/app/src/main/res/values-bs/strings.xml @@ -73,7 +73,6 @@ 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 6ab02256..a487382f 100644 --- a/manager/app/src/main/res/values-da/strings.xml +++ b/manager/app/src/main/res/values-da/strings.xml @@ -25,7 +25,6 @@ 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 721dba59..9411f95c 100644 --- a/manager/app/src/main/res/values-de/strings.xml +++ b/manager/app/src/main/res/values-de/strings.xml @@ -14,7 +14,6 @@ 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 a630db99..f65ba748 100644 --- a/manager/app/src/main/res/values-es/strings.xml +++ b/manager/app/src/main/res/values-es/strings.xml @@ -39,7 +39,6 @@ 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 4f177ff8..9f83e6cf 100644 --- a/manager/app/src/main/res/values-et/strings.xml +++ b/manager/app/src/main/res/values-et/strings.xml @@ -59,7 +59,6 @@ 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 17469cc8..17bc3d4d 100644 --- a/manager/app/src/main/res/values-fa/strings.xml +++ b/manager/app/src/main/res/values-fa/strings.xml @@ -38,7 +38,6 @@ پاک کردن ناموفق بود: %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 fc527bf3..259590a6 100644 --- a/manager/app/src/main/res/values-fil/strings.xml +++ b/manager/app/src/main/res/values-fil/strings.xml @@ -28,7 +28,6 @@ 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 a5de8723..aa437c4a 100644 --- a/manager/app/src/main/res/values-fr/strings.xml +++ b/manager/app/src/main/res/values-fr/strings.xml @@ -39,7 +39,6 @@ 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 f9304469..390d73d9 100644 --- a/manager/app/src/main/res/values-hi/strings.xml +++ b/manager/app/src/main/res/values-hi/strings.xml @@ -44,7 +44,6 @@ इंस्टाल करने के लिए क्लिक करें नियम समूह - Overlayfs उपलब्ध नहीं है, मॉड्यूल काम नहीं कर सकता ! मॉड्यूल निर्माता हमारे बारे में diff --git a/manager/app/src/main/res/values-hr/strings.xml b/manager/app/src/main/res/values-hr/strings.xml index 9fb1e4c6..8b5d72ab 100644 --- a/manager/app/src/main/res/values-hr/strings.xml +++ b/manager/app/src/main/res/values-hr/strings.xml @@ -45,7 +45,6 @@ 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 2b52317c..9a34be44 100644 --- a/manager/app/src/main/res/values-hu/strings.xml +++ b/manager/app/src/main/res/values-hu/strings.xml @@ -14,7 +14,6 @@ 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 f2223e06..a9756699 100644 --- a/manager/app/src/main/res/values-in/strings.xml +++ b/manager/app/src/main/res/values-in/strings.xml @@ -38,7 +38,6 @@ 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 642640b3..54e34512 100644 --- a/manager/app/src/main/res/values-it/strings.xml +++ b/manager/app/src/main/res/values-it/strings.xml @@ -38,7 +38,6 @@ 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 18cfc638..46364706 100644 --- a/manager/app/src/main/res/values-iw/strings.xml +++ b/manager/app/src/main/res/values-iw/strings.xml @@ -44,7 +44,6 @@ לחץ להתקנה כללים קבוצה - שכבות-על לא זמינות, המודול לא יכול לעבוד! מודולים יוצר אודות diff --git a/manager/app/src/main/res/values-ja/strings.xml b/manager/app/src/main/res/values-ja/strings.xml index bf2108a8..f0611907 100644 --- a/manager/app/src/main/res/values-ja/strings.xml +++ b/manager/app/src/main/res/values-ja/strings.xml @@ -38,7 +38,6 @@ アンインストールに失敗: %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 3c4e79fe..df9a46c5 100644 --- a/manager/app/src/main/res/values-kn/strings.xml +++ b/manager/app/src/main/res/values-kn/strings.xml @@ -36,7 +36,6 @@ ಮೌಂಟ್ ನೇಮ್‌ಸ್ಪೇಸ್ ನಿಯಮಗಳು ಗುಂಪುಗಳು - ಓವರ್‌ಲೇಫ್‌ಗಳು ಲಭ್ಯವಿಲ್ಲ, ಮಾಡ್ಯೂಲ್ ಕಾರ್ಯನಿರ್ವಹಿಸುವುದಿಲ್ಲ! ಮಾಡ್ಯೂಲ್ ಲೇಖಕ ಬಗ್ಗೆ diff --git a/manager/app/src/main/res/values-ko/strings.xml b/manager/app/src/main/res/values-ko/strings.xml index 7c29cc02..1f4e12b6 100644 --- a/manager/app/src/main/res/values-ko/strings.xml +++ b/manager/app/src/main/res/values-ko/strings.xml @@ -38,7 +38,6 @@ 모듈 삭제 실패: %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 cba5f5b1..bae21077 100644 --- a/manager/app/src/main/res/values-lt/strings.xml +++ b/manager/app/src/main/res/values-lt/strings.xml @@ -19,7 +19,6 @@ %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 c264b76a..117c7f52 100644 --- a/manager/app/src/main/res/values-lv/strings.xml +++ b/manager/app/src/main/res/values-lv/strings.xml @@ -94,7 +94,6 @@ 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 eaaf2a25..1be3706a 100644 --- a/manager/app/src/main/res/values-mr/strings.xml +++ b/manager/app/src/main/res/values-mr/strings.xml @@ -32,7 +32,6 @@ 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 19031425..bf1560ee 100644 --- a/manager/app/src/main/res/values-nl/strings.xml +++ b/manager/app/src/main/res/values-nl/strings.xml @@ -38,7 +38,6 @@ 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 61250420..9194d5e7 100644 --- a/manager/app/src/main/res/values-pl/strings.xml +++ b/manager/app/src/main/res/values-pl/strings.xml @@ -39,7 +39,6 @@ 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 4d69f8a7..b6255eca 100644 --- a/manager/app/src/main/res/values-pt-rBR/strings.xml +++ b/manager/app/src/main/res/values-pt-rBR/strings.xml @@ -38,7 +38,6 @@ 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 ea1547db..970cfe83 100644 --- a/manager/app/src/main/res/values-pt/strings.xml +++ b/manager/app/src/main/res/values-pt/strings.xml @@ -39,7 +39,6 @@ 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 fea3ffb6..743f3922 100644 --- a/manager/app/src/main/res/values-ro/strings.xml +++ b/manager/app/src/main/res/values-ro/strings.xml @@ -38,7 +38,6 @@ 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 885d6bcc..eae51fe6 100644 --- a/manager/app/src/main/res/values-ru/strings.xml +++ b/manager/app/src/main/res/values-ru/strings.xml @@ -40,7 +40,6 @@ Не удалось удалить: %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 ae02e596..8a82f550 100644 --- a/manager/app/src/main/res/values-sl/strings.xml +++ b/manager/app/src/main/res/values-sl/strings.xml @@ -24,7 +24,6 @@ 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 d903b62b..c2b93b32 100644 --- a/manager/app/src/main/res/values-th/strings.xml +++ b/manager/app/src/main/res/values-th/strings.xml @@ -35,7 +35,6 @@ รีบูตเข้าสู่โหมด 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 7d87c4fe..5048fcb3 100644 --- a/manager/app/src/main/res/values-tr/strings.xml +++ b/manager/app/src/main/res/values-tr/strings.xml @@ -39,7 +39,6 @@ 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 919ac133..9b0dd7b7 100644 --- a/manager/app/src/main/res/values-uk/strings.xml +++ b/manager/app/src/main/res/values-uk/strings.xml @@ -38,7 +38,6 @@ Не вдалося видалити: %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 04d87838..5313f13f 100644 --- a/manager/app/src/main/res/values-vi/strings.xml +++ b/manager/app/src/main/res/values-vi/strings.xml @@ -57,7 +57,6 @@ 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 d56b625a..cf6003d8 100644 --- a/manager/app/src/main/res/values-zh-rCN/strings.xml +++ b/manager/app/src/main/res/values-zh-rCN/strings.xml @@ -37,7 +37,6 @@ 卸载失败: %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 154ddac9..94775ec9 100644 --- a/manager/app/src/main/res/values-zh-rHK/strings.xml +++ b/manager/app/src/main/res/values-zh-rHK/strings.xml @@ -38,7 +38,6 @@ 無法解除安裝:%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 7917ddef..e595250d 100644 --- a/manager/app/src/main/res/values-zh-rTW/strings.xml +++ b/manager/app/src/main/res/values-zh-rTW/strings.xml @@ -37,7 +37,6 @@ 無法解除安裝:%s 版本 作者 - OverlayFS 無法使用,模組無法正常運作! 重新整理 顯示系統應用程式 隱藏系統應用程式 diff --git a/manager/app/src/main/res/values/strings.xml b/manager/app/src/main/res/values/strings.xml index e653ac57..3b94e6df 100644 --- a/manager/app/src/main/res/values/strings.xml +++ b/manager/app/src/main/res/values/strings.xml @@ -41,7 +41,6 @@ Failed to uninstall: %s Version Author - Modules are unavailable as OverlayFS is disabled by the kernel. Refresh Show system apps Hide system apps @@ -51,7 +50,7 @@ Modules are unavailable due to a conflict with Magisk! 🔥 Next Build https://github.com/rifsxd/KernelSU - Next modifications branch, Check it out! + Next modifications branch. Check it out on GitHub! 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 906b44f2..a373e3d6 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 1.0.0", + "cfg-if", "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 1.0.0", + "cfg-if", "libc", "miniz_oxide", "object", @@ -223,12 +223,6 @@ 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" @@ -360,7 +354,7 @@ version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", ] [[package]] @@ -505,7 +499,7 @@ version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", ] [[package]] @@ -616,7 +610,7 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "libc", "wasi", ] @@ -655,19 +649,6 @@ 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" @@ -807,7 +788,6 @@ dependencies = [ "env_logger", "extattr", "getopts", - "hole-punch", "humansize", "is_executable", "java-properties", @@ -916,16 +896,6 @@ 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" @@ -1237,7 +1207,7 @@ version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "cpufeatures", "digest", ] @@ -1248,7 +1218,7 @@ version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "cpufeatures", "digest", ] @@ -1301,7 +1271,7 @@ version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "fastrand", "once_cell", "rustix 0.38.41", @@ -1416,7 +1386,7 @@ version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "once_cell", "wasm-bindgen-macro", ] diff --git a/userspace/ksud/Cargo.toml b/userspace/ksud/Cargo.toml index 33786366..b2700f56 100644 --- a/userspace/ksud/Cargo.toml +++ b/userspace/ksud/Cargo.toml @@ -41,7 +41,6 @@ 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 14717e62..7d8f35ce 100644 --- a/userspace/ksud/src/cli.rs +++ b/userspace/ksud/src/cli.rs @@ -1,12 +1,13 @@ use anyhow::{Ok, Result}; use clap::Parser; -use std::path::PathBuf; +use std::path::{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 @@ -15,6 +16,9 @@ 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)] @@ -161,17 +165,6 @@ 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, } @@ -231,9 +224,6 @@ enum Module { /// list all modules List, - - /// Shrink module image size - Shrink, } #[derive(clap::Subcommand, Debug)] @@ -295,6 +285,10 @@ 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 { @@ -314,7 +308,6 @@ 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,15 +341,7 @@ pub fn run() -> Result<()> { Ok(()) } Debug::Su { global_mnt } => crate::su::grant_root(global_mnt), - 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::Mount => init_event::mount_modules_systemlessly(), Debug::Test => assets::ensure_binaries(false), }, diff --git a/userspace/ksud/src/defs.rs b/userspace/ksud/src/defs.rs index d5514501..5454def1 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_OVERLAY_SOURCE: &str = "KSU"; +pub const KSU_MOUNT_SOURCE: &str = "KSU"; pub const DAEMON_PATH: &str = concatcp!(ADB_DIR, "ksud"); pub const MAGISKBOOT_PATH: &str = concatcp!(BINARY_DIR, "magiskboot"); @@ -18,15 +18,11 @@ 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_TMP_DIR: &str = concatcp!(ADB_DIR, "modules_update/"); +pub const MODULE_UPDATE_DIR: &str = concatcp!(ADB_DIR, "modules_update/"); -pub const SYSTEM_RW_DIR: &str = concatcp!(MODULE_DIR, ".rw/"); +pub const KSUD_VERBOSE_LOG_FILE: &str = concatcp!(ADB_DIR, "verbose"); pub const TEMP_DIR: &str = "/debug_ramdisk"; pub const MODULE_WEB_DIR: &str = "webroot"; @@ -35,6 +31,7 @@ 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 5839c195..f5bb8b87 100644 --- a/userspace/ksud/src/init_event.rs +++ b/userspace/ksud/src/init_event.rs @@ -1,100 +1,10 @@ -use anyhow::{bail, Context, Result}; +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 log::{info, warn}; -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(()) -} +use rustix::fs::{mount, MountFlags}; +use std::path::Path; pub fn on_post_data_fs() -> Result<()> { ksucalls::report_post_fs_data(); @@ -111,11 +21,9 @@ pub fn on_post_data_fs() -> Result<()> { return Ok(()); } - let safe_mode = crate::utils::is_safe_mode(); + let safe_mode = 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 @@ -124,43 +32,8 @@ 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(); @@ -177,6 +50,10 @@ 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); } @@ -191,7 +68,7 @@ pub fn on_post_data_fs() -> Result<()> { } // mount temp dir - if let Err(e) = mount::mount_tmpfs(defs::TEMP_DIR) { + if let Err(e) = mount(KSU_MOUNT_SOURCE, TEMP_DIR, "tmpfs", MountFlags::empty(), "") { warn!("do temp dir mount failed: {}", e); } @@ -206,15 +83,23 @@ pub fn on_post_data_fs() -> Result<()> { warn!("load system.prop failed: {}", e); } - // mount module systemlessly by overlay - if let Err(e) = mount_modules_systemlessly(module_dir) { + // mount module systemlessly by magic mount + if let Err(e) = mount_modules_systemlessly() { warn!("do systemless mount failed: {}", e); } run_stage("post-mount", true); - std::env::set_current_dir("/").with_context(|| "failed to chdir to /")?; + Ok(()) +} +#[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(()) } @@ -249,17 +134,6 @@ 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 40138ae3..2ebdddc7 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 `ls /proc/$$/fd`; do + for FD in /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,18 +302,16 @@ is_legacy_script() { } handle_partition() { - # 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 + PARTITION="$1" + REQUIRE_SYMLINK="$2" + if [ ! -e "$MODPATH/system/$PARTITION" ]; then # no partition found return; fi - 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 + if [ "$REQUIRE_SYMLINK" = "false" ] || [ -L "/system/$PARTITION" ] && [ "$(readlink -f "/system/$PARTITION")" = "/$PARTITION" ]; then + ui_print "- Handle partition /$PARTITION" + ln -sf "./system/$PARTITION" "$MODPATH/$PARTITION" fi } @@ -391,22 +389,23 @@ 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 new file mode 100644 index 00000000..c6bd9f18 --- /dev/null +++ b/userspace/ksud/src/magic_mount.rs @@ -0,0 +1,434 @@ +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 3b517205..a91fc0cb 100644 --- a/userspace/ksud/src/main.rs +++ b/userspace/ksud/src/main.rs @@ -6,8 +6,9 @@ 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 f927e78a..4971c2f0 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, mount, + assets, defs, ksucalls, restorecon::{restore_syscon, setsyscon}, - sepolicy, utils, + sepolicy, }; use anyhow::{anyhow, bail, ensure, Context, Result}; @@ -12,20 +12,21 @@ use is_executable::is_executable; use java_properties::PropertiesIter; use log::{info, warn}; -use std::fs::OpenOptions; +use std::fs::{copy, rename}; 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, Stdio}, + process::Command, 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::{fs::MetadataExt, prelude::PermissionsExt, process::CommandExt}; +use std::os::unix::{prelude::PermissionsExt, process::CommandExt}; const INSTALLER_CONTENT: &str = include_str!("./installer.sh"); const INSTALL_MODULE_SCRIPT: &str = concatcp!( @@ -75,24 +76,34 @@ fn ensure_boot_completed() -> Result<()> { Ok(()) } -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 { +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 { ensure_file_exists(module_state_file) } else { if module_state_file.exists() { - std::fs::remove_file(module_state_file)?; + remove_file(module_state_file)?; } Ok(()) } } -fn foreach_module(active_only: bool, mut f: impl FnMut(&Path) -> Result<()>) -> Result<()> { - let modules_dir = Path::new(defs::MODULE_DIR); +#[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(()); + } let dir = std::fs::read_dir(modules_dir)?; for entry in dir.flatten() { let path = entry.path(); @@ -101,11 +112,11 @@ fn foreach_module(active_only: bool, mut f: impl FnMut(&Path) -> Result<()>) -> continue; } - if active_only && path.join(defs::DISABLE_FILE_NAME).exists() { + if module_type == ModuleType::Active && path.join(defs::DISABLE_FILE_NAME).exists() { info!("{} is disabled, skip", path.display()); continue; } - if active_only && path.join(defs::REMOVE_FILE_NAME).exists() { + if module_type == ModuleType::Active && path.join(defs::REMOVE_FILE_NAME).exists() { warn!("{} is removed, skip", path.display()); continue; } @@ -117,27 +128,7 @@ fn foreach_module(active_only: bool, mut f: impl FnMut(&Path) -> Result<()>) -> } fn foreach_active_module(f: impl FnMut(&Path) -> Result<()>) -> Result<()> { - 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(()) + foreach_module(ModuleType::Active, f) } pub fn load_sepolicy_rule() -> Result<()> { @@ -256,318 +247,149 @@ pub fn load_system_prop() -> Result<()> { } pub fn prune_modules() -> Result<()> { - foreach_module(false, |module| { - remove_file(module.join(defs::UPDATE_FILE_NAME)).ok(); + foreach_module(ModuleType::All, |module| { + if module.join(defs::REMOVE_FILE_NAME).exists() { + info!("remove module: {}", module.display()); - 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); + 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); + 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(); } - Ok(()) })?; 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")?; +pub fn handle_updated_modules() -> Result<()> { + let modules_root = Path::new(MODULE_DIR); + foreach_module(ModuleType::Updated, |module| { + if !module.is_dir() { + return Ok(()); + } - // 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()?; + 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); } } - } - - // 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(()) + })?; Ok(()) } pub fn install_module(zip: &str) -> Result<()> { - let result = _install_module(zip); + 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); 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<()> { - 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(()) - }) + mark_module_state(id, defs::REMOVE_FILE_NAME, true) } pub fn run_action(id: &str) -> Result<()> { @@ -575,37 +397,12 @@ 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<()> { - update_module(defs::MODULE_UPDATE_TMP_DIR, id, |mid, update_dir| { - _enable_module(update_dir, mid, true) - }) + mark_module_state(id, defs::DISABLE_FILE_NAME, false) } pub fn disable_module(id: &str) -> Result<()> { - update_module(defs::MODULE_UPDATE_TMP_DIR, id, |mid, update_dir| { - _enable_module(update_dir, mid, false) - }) + mark_module_state(id, defs::DISABLE_FILE_NAME, true) } pub fn disable_all_modules() -> Result<()> { @@ -617,8 +414,7 @@ pub fn uninstall_all_modules() -> Result<()> { } fn mark_all_modules(flag_file: &str) -> Result<()> { - // we assume the module dir is already mounted - let dir = std::fs::read_dir(defs::MODULE_DIR)?; + let dir = std::fs::read_dir(MODULE_DIR)?; for entry in dir.flatten() { let path = entry.path(); let flag = path.join(flag_file); @@ -696,21 +492,3 @@ 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 deleted file mode 100644 index a15deb97..00000000 --- a/userspace/ksud/src/mount.rs +++ /dev/null @@ -1,306 +0,0 @@ -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 152a7c5e..c396481d 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_syscon_if_unlabeled>(dir: P) -> Result<()> { +fn restore_modules_con>(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 anyhow::Result::Ok(con) = lgetfilecon(&path) { - if con == UNLABEL_CON || con.is_empty() { + if let Result::Ok(con) = lgetfilecon(&path) { + if con == ADB_CON || con == UNLABEL_CON || con.is_empty() { lsetfilecon(&path, SYSTEM_CON)?; } } @@ -76,6 +76,6 @@ fn restore_syscon_if_unlabeled>(dir: P) -> Result<()> { pub fn restorecon() -> Result<()> { lsetfilecon(defs::DAEMON_PATH, ADB_CON)?; - restore_syscon_if_unlabeled(defs::MODULE_DIR)?; + restore_modules_con(defs::MODULE_DIR)?; Ok(()) } diff --git a/userspace/ksud/src/utils.rs b/userspace/ksud/src/utils.rs index c08fdeee..0bc13245 100644 --- a/userspace/ksud/src/utils.rs +++ b/userspace/ksud/src/utils.rs @@ -15,10 +15,6 @@ 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"))] @@ -39,7 +35,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) { - std::result::Result::Ok(_) => Ok(()), + Result::Ok(_) => Ok(()), Err(err) => { if err.kind() == AlreadyExists && file.as_ref().is_file() { Ok(()) @@ -224,9 +220,7 @@ 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.."); @@ -238,153 +232,3 @@ 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!() -}