Compare commits

...

12 Commits

Author SHA1 Message Date
Weblate (bot)
3f341a4e3a Translations update from Hosted Weblate (#1376)
Translations update from [Hosted Weblate](https://hosted.weblate.org)
for
[KernelSU/Manager](https://hosted.weblate.org/projects/kernelsu/manager/).



Current translation status:

![Weblate translation
status](https://hosted.weblate.org/widget/kernelsu/manager/horizontal-auto.svg)

---------

Co-authored-by: yuztass <inkognito0901@gmail.com>
Co-authored-by: Federico Lombardo <mlomb.federalberto@gmail.com>
Co-authored-by: I g o r <igormczampola1@gmail.com>
Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Co-authored-by: Kazuki Nakashima <flukfik41@gmail.com>
Co-authored-by: Rex_sa <rex.sa@pm.me>
Co-authored-by: dabao1955 <dabao1955@163.com>
Co-authored-by: / <sahmatov267@gmail.com>
2024-02-25 23:11:08 +08:00
weishu
40d7bc6256 ksud: Fix incorrect dir copy 2024-02-25 22:55:24 +08:00
igor
56dbc980f4 website: update translation (#1377) 2024-02-25 22:48:22 +08:00
weishu
bf71ff133c manager: Add adb file details to log 2024-02-25 22:36:58 +08:00
weishu
a9b156df43 manager: Add ksu file real size to logs 2024-02-25 22:33:04 +08:00
weishu
1690e5db02 manager: Use global mount namespace to gather logs 2024-02-25 22:30:53 +08:00
weishu
300d9d4cca ksud: Force creating new module format to avoid many wired issues. close #1384, close #1381 2024-02-25 22:29:13 +08:00
dabao1955
3f12080dfe website: update Japanese translation (#1387)
Signed-off-by: dabao1955 <dabao1955@163.com>
2024-02-25 20:24:49 +08:00
weishu
b670db2d22 ksud: fix punch hole 2024-02-25 20:21:17 +08:00
heinu
62a31b3dc2 manager: Allow http for more localhost (#1385) 2024-02-25 18:21:52 +08:00
weishu
a395b1011e manager: Allow http for localhost 2024-02-24 11:20:14 +08:00
weishu
406070914a website: Add docs for module WebUI 2024-02-23 22:43:31 +08:00
28 changed files with 416 additions and 179 deletions

View File

@@ -13,6 +13,7 @@
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:supportsRtl="true" android:supportsRtl="true"
android:networkSecurityConfig="@xml/network_security_config"
android:theme="@style/Theme.KernelSU" android:theme="@style/Theme.KernelSU"
tools:targetApi="33"> tools:targetApi="33">
<activity <activity

View File

@@ -26,7 +26,9 @@ fun getBugreportFile(context: Context): File {
val bootlogFile = File(bugreportDir, "bootlog.tar.gz") val bootlogFile = File(bugreportDir, "bootlog.tar.gz")
val mountsFile = File(bugreportDir, "mounts.txt") val mountsFile = File(bugreportDir, "mounts.txt")
val fileSystemsFile = File(bugreportDir, "filesystems.txt") val fileSystemsFile = File(bugreportDir, "filesystems.txt")
val ksuFileTree = File(bugreportDir, "ksu_tree.txt") val adbFileTree = File(bugreportDir, "adb_tree.txt")
val adbFileDetails = File(bugreportDir, "adb_details.txt")
val ksuFileSize = File(bugreportDir, "ksu_size.txt")
val appListFile = File(bugreportDir, "packages.txt") val appListFile = File(bugreportDir, "packages.txt")
val propFile = File(bugreportDir, "props.txt") val propFile = File(bugreportDir, "props.txt")
val allowListFile = File(bugreportDir, "allowlist.bin") val allowListFile = File(bugreportDir, "allowlist.bin")
@@ -34,7 +36,7 @@ fun getBugreportFile(context: Context): File {
val bootConfig = File(bugreportDir, "boot_config.txt") val bootConfig = File(bugreportDir, "boot_config.txt")
val kernelConfig = File(bugreportDir, "defconfig.gz") val kernelConfig = File(bugreportDir, "defconfig.gz")
val shell = getRootShell() val shell = getRootShell(true)
shell.newJob().add("dmesg > ${dmesgFile.absolutePath}").exec() shell.newJob().add("dmesg > ${dmesgFile.absolutePath}").exec()
shell.newJob().add("logcat -d > ${logcatFile.absolutePath}").exec() shell.newJob().add("logcat -d > ${logcatFile.absolutePath}").exec()
@@ -46,7 +48,9 @@ fun getBugreportFile(context: Context): File {
shell.newJob().add("cat /proc/1/mountinfo > ${mountsFile.absolutePath}").exec() shell.newJob().add("cat /proc/1/mountinfo > ${mountsFile.absolutePath}").exec()
shell.newJob().add("cat /proc/filesystems > ${fileSystemsFile.absolutePath}").exec() shell.newJob().add("cat /proc/filesystems > ${fileSystemsFile.absolutePath}").exec()
shell.newJob().add("ls -alRZ /data/adb > ${ksuFileTree.absolutePath}").exec() shell.newJob().add("busybox tree /data/adb > ${adbFileTree.absolutePath}").exec()
shell.newJob().add("ls -alRZ /data/adb > ${adbFileDetails.absolutePath}").exec()
shell.newJob().add("du -sh /data/adb/ksu/* > ${ksuFileSize.absolutePath}").exec()
shell.newJob().add("cp /data/system/packages.list ${appListFile.absolutePath}").exec() shell.newJob().add("cp /data/system/packages.list ${appListFile.absolutePath}").exec()
shell.newJob().add("getprop > ${propFile.absolutePath}").exec() shell.newJob().add("getprop > ${propFile.absolutePath}").exec()
shell.newJob().add("cp /data/adb/ksu/.allowlist ${allowListFile.absolutePath}").exec() shell.newJob().add("cp /data/adb/ksu/.allowlist ${allowListFile.absolutePath}").exec()

View File

@@ -102,4 +102,8 @@
<string name="app_profile_template_delete">حذف</string> <string name="app_profile_template_delete">حذف</string>
<string name="app_profile_template_import_empty">الحافظة فارغة!</string> <string name="app_profile_template_import_empty">الحافظة فارغة!</string>
<string name="app_profile_template_view">عرض القالب</string> <string name="app_profile_template_view">عرض القالب</string>
<string name="grant_root_failed">فشل في منح صلاحية الجذر!</string>
<string name="open">فتح</string>
<string name="settings_check_update_summary">التحقق تلقائيًا من وجود تحديثات عند فتح التطبيق</string>
<string name="settings_check_update">التحقق من التحديث</string>
</resources> </resources>

View File

@@ -80,4 +80,30 @@
<string name="settings_umount_modules_default_summary">Il valore predefinito per \"Scollega moduli\" in Profili App. Se attivato, rimuoverà tutte le modifiche al sistema da parte dei moduli per le applicazioni che non hanno un profilo impostato.</string> <string name="settings_umount_modules_default_summary">Il valore predefinito per \"Scollega moduli\" in Profili App. Se attivato, rimuoverà tutte le modifiche al sistema da parte dei moduli per le applicazioni che non hanno un profilo impostato.</string>
<string name="require_kernel_version">La versione attualmente installata di KernelSU (%d) è troppo vecchia ed il gestore non può funzionare correttamente. Si prega di aggiornare alla versione %d o successiva!</string> <string name="require_kernel_version">La versione attualmente installata di KernelSU (%d) è troppo vecchia ed il gestore non può funzionare correttamente. Si prega di aggiornare alla versione %d o successiva!</string>
<string name="module_changelog">Registro aggiornamenti</string> <string name="module_changelog">Registro aggiornamenti</string>
<string name="app_profile_template_create">Crea modello</string>
<string name="app_profile_template_edit">Modifica Modello</string>
<string name="app_profile_template_id">identificativo</string>
<string name="app_profile_template_id_invalid">Identificativo modello non valido</string>
<string name="app_profile_template_name">Nome</string>
<string name="app_profile_template_view">Visualizza modello</string>
<string name="app_profile_template_readonly">Sola lettura</string>
<string name="app_profile_template_id_exist">Esiste già l\'identificativo del modello!</string>
<string name="app_profile_import_export">Importa/Esporta</string>
<string name="app_profile_import_from_clipboard">Importa dagli appunti</string>
<string name="app_profile_export_to_clipboard">Esporta negli appunti</string>
<string name="app_profile_template_export_empty">Impossibile trovare profilo locale da esportare!</string>
<string name="app_profile_template_import_success">Importato con successo</string>
<string name="app_profile_template_sync">Sincronizza i modelli remoti</string>
<string name="app_profile_template_import_empty">Gli appunti sono vuoti!</string>
<string name="grant_root_failed">Impossibile ottenere l\'accesso root!</string>
<string name="settings_profile_template">Modelli Profili App</string>
<string name="settings_profile_template_summary">Gestisci i modelli locali e remoti dei Profili App</string>
<string name="app_profile_template_delete">Elimina</string>
<string name="app_profile_template_description">Descrizione</string>
<string name="app_profile_template_save">Salva</string>
<string name="app_profile_template_save_failed">Impossibile salvare profilo</string>
<string name="open">Apri</string>
<string name="module_changelog_failed">Impossibile reperire il changelog: %s</string>
<string name="settings_check_update">Controlla aggiornamenti</string>
<string name="settings_check_update_summary">Controlla automaticamente la disponibilità di aggiornamenti all\'apertura dell\'applicazione</string>
</resources> </resources>

View File

@@ -105,4 +105,6 @@
<string name="app_profile_template_view">テンプレートを表示</string> <string name="app_profile_template_view">テンプレートを表示</string>
<string name="settings_check_update">アップデートを確認</string> <string name="settings_check_update">アップデートを確認</string>
<string name="settings_check_update_summary">アプリを開いたときにアップデートを自動的に確認する</string> <string name="settings_check_update_summary">アプリを開いたときにアップデートを自動的に確認する</string>
<string name="grant_root_failed">root の付与に失敗しました!</string>
<string name="open">開ける</string>
</resources> </resources>

View File

@@ -100,4 +100,6 @@
<string name="app_profile_import_export">Importeren/Exporteren</string> <string name="app_profile_import_export">Importeren/Exporteren</string>
<string name="app_profile_import_from_clipboard">Importeren vanaf klembord</string> <string name="app_profile_import_from_clipboard">Importeren vanaf klembord</string>
<string name="module_changelog_failed">Ophalen van wijzigingslogboek mislukt: %s</string> <string name="module_changelog_failed">Ophalen van wijzigingslogboek mislukt: %s</string>
<string name="app_profile_export_to_clipboard">Exporteren naar klembord</string>
<string name="settings_check_update">Controleer update</string>
</resources> </resources>

View File

@@ -105,4 +105,6 @@
<string name="app_profile_template_view">Ver modelo</string> <string name="app_profile_template_view">Ver modelo</string>
<string name="settings_check_update">Verificar atualização</string> <string name="settings_check_update">Verificar atualização</string>
<string name="settings_check_update_summary">Verifique automaticamente se há atualizações ao abrir o app</string> <string name="settings_check_update_summary">Verifique automaticamente se há atualizações ao abrir o app</string>
<string name="grant_root_failed">Falha ao conceder acesso root!</string>
<string name="open">Abrir</string>
</resources> </resources>

View File

@@ -106,4 +106,8 @@
<string name="app_profile_template_delete">Удалить</string> <string name="app_profile_template_delete">Удалить</string>
<string name="app_profile_template_import_empty">Буфер обмена пуст!</string> <string name="app_profile_template_import_empty">Буфер обмена пуст!</string>
<string name="app_profile_template_view">Просмотр шаблона</string> <string name="app_profile_template_view">Просмотр шаблона</string>
<string name="settings_check_update">Проверка обновления</string>
<string name="settings_check_update_summary">Автоматическая проверка обновлений при открытии приложения</string>
<string name="grant_root_failed">Не удалось выдать root!</string>
<string name="open">Открыть</string>
</resources> </resources>

View File

@@ -102,4 +102,8 @@
<string name="app_profile_template_import_empty">คลิปบอร์ดว่างเปล่า!</string> <string name="app_profile_template_import_empty">คลิปบอร์ดว่างเปล่า!</string>
<string name="app_profile_template_view">ดูเทมเพลต</string> <string name="app_profile_template_view">ดูเทมเพลต</string>
<string name="module_changelog_failed">ดึงข้อมูลบันทึกการเปลี่ยนแปลงล้มเหลว: %s</string> <string name="module_changelog_failed">ดึงข้อมูลบันทึกการเปลี่ยนแปลงล้มเหลว: %s</string>
<string name="open">เปิด</string>
<string name="grant_root_failed">ไม่สามารถให้สิทธิ์รูทได้!</string>
<string name="settings_check_update">ตรวจสอบการอัปเดต</string>
<string name="settings_check_update_summary">ตรวจสอบการอัปเดตโดยอัตโนมัติเมื่อเปิดแอป</string>
</resources> </resources>

View File

@@ -106,4 +106,6 @@
<string name="module_changelog_failed">Değişiklik geçmişi alınamadı: %s</string> <string name="module_changelog_failed">Değişiklik geçmişi alınamadı: %s</string>
<string name="settings_check_update">Güncellemeleri denetle</string> <string name="settings_check_update">Güncellemeleri denetle</string>
<string name="settings_check_update_summary">Uygulamayı açarken güncellemeleri otomatik denetle</string> <string name="settings_check_update_summary">Uygulamayı açarken güncellemeleri otomatik denetle</string>
<string name="grant_root_failed">Root izni verilemedi!</string>
<string name="open"></string>
</resources> </resources>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">127.0.0.1</domain>
<domain includeSubdomains="true">0.0.0.0</domain>
<domain includeSubdomains="true">::1</domain>
</domain-config>
</network-security-config>

View File

@@ -537,6 +537,12 @@ dependencies = [
"miniz_oxide", "miniz_oxide",
] ]
[[package]]
name = "fs_extra"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
[[package]] [[package]]
name = "fuchsia-cprng" name = "fuchsia-cprng"
version = "0.1.1" version = "0.1.1"
@@ -800,6 +806,7 @@ dependencies = [
"encoding_rs", "encoding_rs",
"env_logger", "env_logger",
"extattr", "extattr",
"fs_extra",
"getopts", "getopts",
"hole-punch", "hole-punch",
"humansize", "humansize",

View File

@@ -37,6 +37,7 @@ sha256 = "1"
tempdir = "0.3" tempdir = "0.3"
chrono = "0.4" chrono = "0.4"
hole-punch = { git = "https://github.com/tiann/hole-punch" } hole-punch = { git = "https://github.com/tiann/hole-punch" }
fs_extra = "1.3"
[target.'cfg(any(target_os = "android", target_os = "linux"))'.dependencies] [target.'cfg(any(target_os = "android", target_os = "linux"))'.dependencies]
rustix = { git = "https://github.com/Kernel-SU/rustix.git", branch = "main", features = ["all-apis"] } rustix = { git = "https://github.com/Kernel-SU/rustix.git", branch = "main", features = ["all-apis"] }

View File

@@ -122,12 +122,9 @@ enum Debug {
src: String, src: String,
/// destination file /// destination file
dst: String, dst: String,
}, /// punch hole
#[arg(short, long, default_value = "false")]
/// Punch hole file punch_hole: bool,
PunchHole {
/// file path
file: String,
}, },
/// For testing /// For testing
@@ -299,11 +296,14 @@ pub fn run() -> Result<()> {
} }
Debug::Su { global_mnt } => crate::ksu::grant_root(global_mnt), Debug::Su { global_mnt } => crate::ksu::grant_root(global_mnt),
Debug::Mount => event::mount_systemlessly(defs::MODULE_DIR), Debug::Mount => event::mount_systemlessly(defs::MODULE_DIR),
Debug::Xcp { src, dst } => { Debug::Xcp {
utils::copy_sparse_file(src, dst)?; src,
dst,
punch_hole,
} => {
utils::copy_sparse_file(src, dst, punch_hole)?;
Ok(()) Ok(())
} }
Debug::PunchHole { file } => utils::punch_hole(file),
Debug::Test => todo!(), Debug::Test => todo!(),
}, },

View File

@@ -255,7 +255,7 @@ pub fn on_boot_completed() -> Result<()> {
// this is a update and we successfully booted // this is a update and we successfully booted
if std::fs::rename(module_update_img, module_img).is_err() { if std::fs::rename(module_update_img, module_img).is_err() {
warn!("Failed to rename images, copy it now.",); warn!("Failed to rename images, copy it now.",);
utils::copy_sparse_file(module_update_img, module_img) utils::copy_sparse_file(module_update_img, module_img, false)
.with_context(|| "Failed to copy images")?; .with_context(|| "Failed to copy images")?;
std::fs::remove_file(module_update_img).with_context(|| "Failed to remove image!")?; std::fs::remove_file(module_update_img).with_context(|| "Failed to remove image!")?;
} }

View File

@@ -15,7 +15,7 @@ use log::{info, warn};
use std::{ use std::{
collections::HashMap, collections::HashMap,
env::var as env_var, env::var as env_var,
fs::{remove_dir_all, remove_file, set_permissions, File, OpenOptions, Permissions}, fs::{remove_dir_all, remove_file, set_permissions, File, Permissions},
io::Cursor, io::Cursor,
path::{Path, PathBuf}, path::{Path, PathBuf},
process::{Command, Stdio}, process::{Command, Stdio},
@@ -24,7 +24,7 @@ use std::{
use zip_extensions::zip_extract_file_to_memory; use zip_extensions::zip_extract_file_to_memory;
#[cfg(unix)] #[cfg(unix)]
use std::os::unix::{prelude::PermissionsExt, process::CommandExt}; use std::os::unix::{fs::MetadataExt, prelude::PermissionsExt, process::CommandExt};
const INSTALLER_CONTENT: &str = include_str!("./installer.sh"); const INSTALLER_CONTENT: &str = include_str!("./installer.sh");
const INSTALL_MODULE_SCRIPT: &str = concatcp!( const INSTALL_MODULE_SCRIPT: &str = concatcp!(
@@ -281,6 +281,27 @@ pub fn prune_modules() -> Result<()> {
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")?;
// 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<()> { fn _install_module(zip: &str) -> Result<()> {
ensure_boot_completed()?; ensure_boot_completed()?;
@@ -341,33 +362,16 @@ fn _install_module(zip: &str) -> Result<()> {
); );
let sparse_image_size = 1 << 40; // 1T let sparse_image_size = 1 << 40; // 1T
let jounnel_size = 8; // 8M let journal_size = 8; // 8M
if !modules_img_exist && !modules_update_img_exist { 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 // 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 // create a tmp module img and mount it to modules_update
info!("Creating brand new module image"); info!("Creating brand new module image");
File::create(tmp_module_img) create_module_image(tmp_module_img, sparse_image_size, journal_size)?;
.context("Failed to create ext4 image file")?
.set_len(sparse_image_size)
.context("Failed to truncate ext4 image")?;
// format the img to ext4 filesystem
let result = Command::new("mkfs.ext4")
.arg("-J")
.arg(format!("size={jounnel_size}"))
.arg(tmp_module_img)
.stdout(Stdio::piped())
.output()?;
ensure!(
result.status.success(),
"Failed to format ext4 image: {}",
String::from_utf8(result.stderr).unwrap()
);
check_image(tmp_module_img)?;
} else if modules_update_img_exist { } else if modules_update_img_exist {
// modules_update.img exists, we should use it as tmp img // modules_update.img exists, we should use it as tmp img
info!("Using existing modules_update.img as tmp image"); info!("Using existing modules_update.img as tmp image");
utils::copy_sparse_file(modules_update_img, tmp_module_img).with_context(|| { utils::copy_sparse_file(modules_update_img, tmp_module_img, true).with_context(|| {
format!( format!(
"Failed to copy {} to {}", "Failed to copy {} to {}",
modules_update_img.display(), modules_update_img.display(),
@@ -377,40 +381,32 @@ fn _install_module(zip: &str) -> Result<()> {
} else { } else {
// modules.img exists, we should use it as tmp img // modules.img exists, we should use it as tmp img
info!("Using existing modules.img as tmp image"); info!("Using existing modules.img as tmp image");
utils::copy_sparse_file(modules_img, tmp_module_img).with_context(|| {
format!(
"Failed to copy {} to {}",
modules_img.display(),
tmp_module_img
)
})?;
// legacy image, truncate it to new size. #[cfg(unix)]
if std::fs::metadata(modules_img)?.len() < sparse_image_size { let blksize = std::fs::metadata(defs::MODULE_DIR)?.blksize();
println!("- Truncate legacy image to new size"); #[cfg(not(unix))]
let blksize = 0;
// shrink it to minimum size // legacy image, it's block size is 1024 with unlimited journal size
check_image(tmp_module_img)?; if blksize == 1024 {
Command::new("resize2fs") println!("- Legacy image, migrating to new format, please be patient...");
.arg("-M") create_module_image(tmp_module_img, sparse_image_size, journal_size)?;
.arg(tmp_module_img) let _dontdrop =
.stdout(Stdio::piped()) mount::AutoMountExt4::try_new(tmp_module_img, module_update_tmp_dir, true)?;
.status()?; fs_extra::dir::copy(
defs::MODULE_DIR,
// truncate the file to new size module_update_tmp_dir,
OpenOptions::new() &fs_extra::dir::CopyOptions::new()
.write(true) .overwrite(true)
.open(tmp_module_img) .content_only(true),
.context("Failed to open ext4 image")? )?;
.set_len(sparse_image_size) } else {
.context("Failed to truncate ext4 image")?; utils::copy_sparse_file(modules_img, tmp_module_img, true).with_context(|| {
format!(
// resize the image to new size "Failed to copy {} to {}",
check_image(tmp_module_img)?; modules_img.display(),
Command::new("resize2fs") tmp_module_img
.arg(tmp_module_img) )
.stdout(Stdio::piped()) })?;
.status()?;
} }
} }
@@ -445,15 +441,11 @@ fn _install_module(zip: &str) -> Result<()> {
exec_install_script(zip)?; exec_install_script(zip)?;
if let Err(e) = utils::punch_hole(tmp_module_img) {
warn!("Failed to punch hole: {}", e);
}
info!("rename {tmp_module_img} to {}", defs::MODULE_UPDATE_IMG); info!("rename {tmp_module_img} to {}", defs::MODULE_UPDATE_IMG);
// all done, rename the tmp image to modules_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() { if std::fs::rename(tmp_module_img, defs::MODULE_UPDATE_IMG).is_err() {
warn!("Rename image failed, try copy it."); warn!("Rename image failed, try copy it.");
utils::copy_sparse_file(tmp_module_img, defs::MODULE_UPDATE_IMG) utils::copy_sparse_file(tmp_module_img, defs::MODULE_UPDATE_IMG, true)
.with_context(|| "Failed to copy image.".to_string())?; .with_context(|| "Failed to copy image.".to_string())?;
let _ = std::fs::remove_file(tmp_module_img); let _ = std::fs::remove_file(tmp_module_img);
} }
@@ -476,7 +468,7 @@ pub fn install_module(zip: &str) -> Result<()> {
result result
} }
fn update_module<F>(update_dir: &str, id: &str, punch_hole: bool, func: F) -> Result<()> fn update_module<F>(update_dir: &str, id: &str, func: F) -> Result<()>
where where
F: Fn(&str, &str) -> Result<()>, F: Fn(&str, &str) -> Result<()>,
{ {
@@ -493,14 +485,14 @@ where
modules_update_img.display(), modules_update_img.display(),
modules_update_tmp_img.display() modules_update_tmp_img.display()
); );
utils::copy_sparse_file(modules_update_img, modules_update_tmp_img)?; utils::copy_sparse_file(modules_update_img, modules_update_tmp_img, true)?;
} else { } else {
info!( info!(
"copy {} to {}", "copy {} to {}",
modules_img.display(), modules_img.display(),
modules_update_tmp_img.display() modules_update_tmp_img.display()
); );
utils::copy_sparse_file(modules_img, modules_update_tmp_img)?; utils::copy_sparse_file(modules_img, modules_update_tmp_img, true)?;
} }
// ensure modules_update dir exist // ensure modules_update dir exist
@@ -512,15 +504,9 @@ where
// call the operation func // call the operation func
let result = func(id, update_dir); let result = func(id, update_dir);
if punch_hole {
if let Err(e) = utils::punch_hole(modules_update_tmp_img) {
warn!("Failed to punch hole: {}", e);
}
}
if let Err(e) = std::fs::rename(modules_update_tmp_img, defs::MODULE_UPDATE_IMG) { if let Err(e) = std::fs::rename(modules_update_tmp_img, defs::MODULE_UPDATE_IMG) {
warn!("Rename image failed: {e}, try copy it."); warn!("Rename image failed: {e}, try copy it.");
utils::copy_sparse_file(modules_update_tmp_img, defs::MODULE_UPDATE_IMG) utils::copy_sparse_file(modules_update_tmp_img, defs::MODULE_UPDATE_IMG, true)
.with_context(|| "Failed to copy image.".to_string())?; .with_context(|| "Failed to copy image.".to_string())?;
let _ = std::fs::remove_file(modules_update_tmp_img); let _ = std::fs::remove_file(modules_update_tmp_img);
} }
@@ -531,7 +517,7 @@ where
} }
pub fn uninstall_module(id: &str) -> Result<()> { pub fn uninstall_module(id: &str) -> Result<()> {
update_module(defs::MODULE_UPDATE_TMP_DIR, id, true, |mid, update_dir| { update_module(defs::MODULE_UPDATE_TMP_DIR, id, |mid, update_dir| {
let dir = Path::new(update_dir); let dir = Path::new(update_dir);
ensure!(dir.exists(), "No module installed"); ensure!(dir.exists(), "No module installed");
@@ -597,13 +583,13 @@ fn _enable_module(module_dir: &str, mid: &str, enable: bool) -> Result<()> {
} }
pub fn enable_module(id: &str) -> Result<()> { pub fn enable_module(id: &str) -> Result<()> {
update_module(defs::MODULE_UPDATE_TMP_DIR, id, false, |mid, update_dir| { update_module(defs::MODULE_UPDATE_TMP_DIR, id, |mid, update_dir| {
_enable_module(update_dir, mid, true) _enable_module(update_dir, mid, true)
}) })
} }
pub fn disable_module(id: &str) -> Result<()> { pub fn disable_module(id: &str) -> Result<()> {
update_module(defs::MODULE_UPDATE_TMP_DIR, id, false, |mid, update_dir| { update_module(defs::MODULE_UPDATE_TMP_DIR, id, |mid, update_dir| {
_enable_module(update_dir, mid, false) _enable_module(update_dir, mid, false)
}) })
} }

View File

@@ -192,7 +192,11 @@ pub fn get_tmp_path() -> &'static str {
} }
// TODO: use libxcp to improve the speed if cross's MSRV is 1.70 // TODO: use libxcp to improve the speed if cross's MSRV is 1.70
pub fn copy_sparse_file<P: AsRef<Path>, Q: AsRef<Path>>(src: P, dst: Q) -> Result<()> { pub fn copy_sparse_file<P: AsRef<Path>, Q: AsRef<Path>>(
src: P,
dst: Q,
punch_hole: bool,
) -> Result<()> {
let mut src_file = File::open(src.as_ref())?; let mut src_file = File::open(src.as_ref())?;
let mut dst_file = OpenOptions::new() let mut dst_file = OpenOptions::new()
.write(true) .write(true)
@@ -223,6 +227,12 @@ pub fn copy_sparse_file<P: AsRef<Path>, Q: AsRef<Path>>(src: P, dst: Q) -> Resul
break; 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])?; dst_file.write_all(&buffer[..bytes_read])?;
total_bytes_copied += bytes_read as u64; total_bytes_copied += bytes_read as u64;
} }
@@ -231,94 +241,3 @@ pub fn copy_sparse_file<P: AsRef<Path>, Q: AsRef<Path>>(src: P, dst: Q) -> Resul
Ok(()) Ok(())
} }
#[cfg(any(target_os = "linux", target_os = "android"))]
pub fn punch_hole(src: impl AsRef<Path>) -> Result<()> {
let mut src_file = OpenOptions::new().write(true).read(true).open(src)?;
let st = rustix::fs::fstat(&src_file)?;
let bufsz = st.st_blksize;
let mut buf = vec![0u8; bufsz as usize];
let mut ct = 0;
let mut hole_sz = 0;
let mut hole_start = 0;
let segments = src_file.scan_chunks()?;
for segment in segments {
if segment.segment_type != SegmentType::Data {
continue;
}
let mut off = segment.start;
let end = segment.end + 1;
while off < end {
let mut rsz = rustix::io::pread(&src_file, &mut buf, off)? as u64;
if rsz > 0 && rsz > end - off {
// exceed the end of the boundary
rsz = end - off;
}
if rsz == 0 {
break;
}
if buf.iter().all(|&x| x == 0) {
// the whole buf is zero, mark it as a hole
if hole_sz == 0 {
hole_start = off;
}
// for continuous zero, we can merge them into a bigger hole
hole_sz += rsz;
} else if hole_sz > 0 {
if let Err(e) = rustix::fs::fallocate(
&src_file,
rustix::fs::FallocateFlags::PUNCH_HOLE | rustix::fs::FallocateFlags::KEEP_SIZE,
hole_start,
hole_sz,
) {
log::warn!("Failed to punch hole: {:?}", e);
}
ct += hole_sz;
hole_sz = 0;
hole_start = 0;
}
off += rsz;
}
// if the last segment is a hole, we need to punch it
if hole_sz > 0 {
let mut alloc_sz = hole_sz;
if off >= end {
alloc_sz += st.st_blksize as u64;
}
if let Err(e) = rustix::fs::fallocate(
&src_file,
rustix::fs::FallocateFlags::PUNCH_HOLE | rustix::fs::FallocateFlags::KEEP_SIZE,
hole_start,
alloc_sz,
) {
log::warn!("Failed to punch hole: {:?}", e);
}
ct += hole_sz;
}
}
log::info!(
"Punched {} of hole",
humansize::format_size(ct, humansize::DECIMAL)
);
Ok(())
}
#[cfg(not(any(target_os = "linux", target_os = "android")))]
pub fn punch_hole(src: impl AsRef<Path>) -> Result<()> {
unimplemented!()
}

View File

@@ -51,6 +51,7 @@ function sidebarGuide() {
{ text: 'Intergrate for non-GKI devices', link: '/guide/how-to-integrate-for-non-gki'}, { text: 'Intergrate for non-GKI devices', link: '/guide/how-to-integrate-for-non-gki'},
{ text: 'Unofficially supported devices', link: '/guide/unofficially-support-devices.md' }, { text: 'Unofficially supported devices', link: '/guide/unofficially-support-devices.md' },
{ text: 'Module Guide', link: '/guide/module.md' }, { text: 'Module Guide', link: '/guide/module.md' },
{ text: 'Module WebUI', link: '/guide/module-webui.md' },
{ text: 'App Profile', link: '/guide/app-profile.md' }, { text: 'App Profile', link: '/guide/app-profile.md' },
{ text: 'Rescue from bootloop', link: '/guide/rescue-from-bootloop.md' }, { text: 'Rescue from bootloop', link: '/guide/rescue-from-bootloop.md' },
{ text: 'FAQ', link: '/guide/faq' }, { text: 'FAQ', link: '/guide/faq' },

View File

@@ -23,7 +23,7 @@ export default defineConfig({
footer: { footer: {
message: 'Lançado sob a Licença GPL3.', message: 'Lançado sob a Licença GPL3.',
copyright: 'Copyright © Desenvolvedores KernelSU atuais de 2022' copyright: 'Copyright © Desenvolvedores do KernelSU atuais de 2022'
}, },
editLink: { editLink: {
@@ -51,6 +51,7 @@ function sidebarGuide() {
{ text: 'Integração para dispositivos não GKI', link: '/pt_BR/guide/how-to-integrate-for-non-gki'}, { text: 'Integração para dispositivos não GKI', link: '/pt_BR/guide/how-to-integrate-for-non-gki'},
{ text: 'Dispositivos com suporte não oficial', link: '/pt_BR/guide/unofficially-support-devices.md' }, { text: 'Dispositivos com suporte não oficial', link: '/pt_BR/guide/unofficially-support-devices.md' },
{ text: 'Guias de módulo', link: '/pt_BR/guide/module.md' }, { text: 'Guias de módulo', link: '/pt_BR/guide/module.md' },
{ text: 'Módulo WebUI', link: '/pt_BR/guide/module-webui.md' },
{ text: 'Perfil do Aplicativo', link: '/pt_BR/guide/app-profile.md' }, { text: 'Perfil do Aplicativo', link: '/pt_BR/guide/app-profile.md' },
{ text: 'Resgate do bootloop', link: '/pt_BR/guide/rescue-from-bootloop.md' }, { text: 'Resgate do bootloop', link: '/pt_BR/guide/rescue-from-bootloop.md' },
{ text: 'Perguntas frequentes', link: '/pt_BR/guide/faq' }, { text: 'Perguntas frequentes', link: '/pt_BR/guide/faq' },

View File

@@ -51,6 +51,7 @@ function sidebarGuide() {
{ text: '如何为非GKI设备集成 KernelSU', link: '/zh_CN/guide/how-to-integrate-for-non-gki'}, { text: '如何为非GKI设备集成 KernelSU', link: '/zh_CN/guide/how-to-integrate-for-non-gki'},
{ text: '非官方支持设备', link: '/zh_CN/guide/unofficially-support-devices.md' }, { text: '非官方支持设备', link: '/zh_CN/guide/unofficially-support-devices.md' },
{ text: '模块开发指南', link: '/zh_CN/guide/module.md' }, { text: '模块开发指南', link: '/zh_CN/guide/module.md' },
{ text: '模块 Web 界面', link: '/guide/module-webui.md' },
{ text: 'App Profile', link: '/zh_CN/guide/app-profile.md' }, { text: 'App Profile', link: '/zh_CN/guide/app-profile.md' },
{ text: '救砖', link: '/zh_CN/guide/rescue-from-bootloop.md' }, { text: '救砖', link: '/zh_CN/guide/rescue-from-bootloop.md' },
{ text: '常见问题', link: '/zh_CN/guide/faq' }, { text: '常见问题', link: '/zh_CN/guide/faq' },

View File

@@ -0,0 +1,48 @@
# Module WebUI
In addition to executing boot scripts and modifying system files, KernelSU's modules also support displaying UI interfaces and interacting with users.
The module can write HTML + CSS + JavaScript pages through any web technology. KernelSU's manager will display these pages through WebView. It also provides some APIs for interacting with the system, such as executing shell commands.
## webroot directory
Web resource files should be placed in the `webroot` subdirectory of the module root directory, and there **MUST** be a file named `index.html`, which is the module page entry. The simplest module structure containing a web interface is as follows:
```txt
tree .
.
|-- module.prop
`-- webroot
`-- index.html
```
:::warning
When installing the module, KernelSU will automatically set the permissions and SELinux context of this directory. If you dont know what you are doing, please do not set the permissions of this directory yourself!
:::
If your page contains CSS and JavaScript, you need to place it in this directory as well.
## JavaScript API
If it is just a display page, it is no different from a normal web page. More importantly, KernelSU provides a series of system API that allow you to implement the unique functions of the module.
KernelSU provides a JavaScript library and [publishes it on npm](https://www.npmjs.com/package/kernelsu), which you can use in the JavaScript code of your web pages.
For example, you can execute a shell command to obtain a specific configuration or modify a property:
```javascript
import { exec } from 'kernelsu';
const { errno, stdout } = exec("getprop ro.product.model");
```
For another example, you can make the web page display full screen, or display a toast.
[API documentation](https://www.npmjs.com/package/kernelsu)
If you find that the existing API does not meet your needs or is inconvenient to use, you are welcome to give us suggestions [here](https://github.com/tiann/KernelSU/issues)!
## Some tips
1. You can use `localStorage` normally to store some data, but it will be lost after the Manager App is uninstalled. If you need to save persistently, you can write data to some directory yourself.
2. For simple pages, I recommend you use [parceljs](https://parceljs.org/) for packaging. It requires zero configuration and is very convenient to use. However, if you are a front-end master or have your own preferences, then just choose one you like!

View File

@@ -4,6 +4,10 @@ KernelSU provides a module mechanism that achieves the effect of modifying the s
The module mechanism of KernelSU is almost the same as that of Magisk. If you are familiar with Magisk module development, developing KernelSU modules is very similar. You can skip the introduction of modules below and only need to read [difference-with-magisk](difference-with-magisk.md). The module mechanism of KernelSU is almost the same as that of Magisk. If you are familiar with Magisk module development, developing KernelSU modules is very similar. You can skip the introduction of modules below and only need to read [difference-with-magisk](difference-with-magisk.md).
## WebUI
KernelSU's modules support displaying interfaces and interacting with users, please refer to the [WebUI documentation](module-webui.md).
## Busybox ## Busybox
KernelSU ships with a feature complete BusyBox binary (including full SELinux support). The executable is located at `/data/adb/ksu/bin/busybox`. KernelSU's BusyBox supports runtime toggle-able "ASH Standalone Shell Mode". What this standalone mode means is that when running in the `ash` shell of BusyBox, every single command will directly use the applet within BusyBox, regardless of what is set as `PATH`. For example, commands like `ls`, `rm`, `chmod` will **NOT** use what is in `PATH` (in the case of Android by default it will be `/system/bin/ls`, `/system/bin/rm`, and `/system/bin/chmod` respectively), but will instead directly call internal BusyBox applets. This makes sure that scripts always run in a predictable environment and always have the full suite of commands no matter which Android version it is running on. To force a command _not_ to use BusyBox, you have to call the executable with full paths. KernelSU ships with a feature complete BusyBox binary (including full SELinux support). The executable is located at `/data/adb/ksu/bin/busybox`. KernelSU's BusyBox supports runtime toggle-able "ASH Standalone Shell Mode". What this standalone mode means is that when running in the `ash` shell of BusyBox, every single command will directly use the applet within BusyBox, regardless of what is set as `PATH`. For example, commands like `ls`, `rm`, `chmod` will **NOT** use what is in `PATH` (in the case of Android by default it will be `/system/bin/ls`, `/system/bin/rm`, and `/system/bin/chmod` respectively), but will instead directly call internal BusyBox applets. This makes sure that scripts always run in a predictable environment and always have the full suite of commands no matter which Android version it is running on. To force a command _not_ to use BusyBox, you have to call the executable with full paths.

View File

@@ -0,0 +1,58 @@
# KernelSU のビルド方法は?
まず、Android の公式ドキュメントを読むべきです:
1. [カーネルをビルドする](https://source.android.com/docs/setup/build/building-kernels)
2. [GKI リリースビルド](https://source.android.com/docs/core/architecture/kernel/gki-release-builds)
::: 警告
このページは GKI デバイス用です。もし古いカーネルを使用している場合は、[古いカーネルへの KernelSU の統合方法](how-to-integrate-for-non-gki)を参照してください。
:::
## カーネルビルド
### カーネルソースコードの同期
```sh
repo init -u https://android.googlesource.com/kernel/manifest
mv <kernel_manifest.xml> .repo/manifests
repo init -m manifest.xml
repo sync
```
`<kernel_manifest.xml>` は、ビルドを一意に決定するマニフェストファイルです。マニフェストを使用して再現可能なビルドを行えます。マニフェストファイルは [Google GKI リリースビルド](https://source.android.com/docs/core/architecture/kernel/gki-release-builds) からダウンロードしてください。
### ビルド
まずは [公式ドキュメント](https://source.android.com/docs/setup/build/building-kernels)を確認してください。
たとえば、aarch64 カーネルイメージをビルドする必要があります:
```sh
LTO=thin BUILD_CONFIG=common/build.config.gki.aarch64 build/build.sh
```
`LTO=thin` フラグを追加するのを忘れないでください。それをしないと、コンピュータのメモリが 24Gb 未満の場合にビルドに失敗する可能性があります。
Android 13 からは、カーネルは `bazel` によってビルドされます:
```sh
tools/bazel build --config=fast //common:kernel_aarch64_dist
```
## KernelSU を使ったカーネルビルド
もしカーネルを正常にビルドできた場合、KernelSU をビルドするのは簡単です。カーネルソースのルートディレクトリで任意のものを選択して実行します:
::: code-group
```sh[最新タグ(安定版)]
curl -LSs "https://raw.githubusercontent.com/tiann/KernelSU/main/kernel/setup.sh" | bash -
```
```sh[ main ブランチ (開発用)]
curl -LSs "https://raw.githubusercontent.com/tiann/KernelSU/main/kernel/setup.sh" | bash -s main
```
```sh[タグを選択 (例v0.5.2)]
curl -LSs "https://raw.githubusercontent.com/tiann/KernelSU/main/kernel/setup.sh" | bash -s v0.5.2
```
:::
その後でカーネルを再ビルドすると、KernelSU が組み込まれたカーネルイメージが得られます!

View File

@@ -0,0 +1,48 @@
# Module WebUI
KernelSU のモジュールは、ブートスクリプトの実行やシステムファイルの修正に加えて、UI インターフェースの表示やユーザーとの対話もサポートしています。
モジュールは、任意の Web 技術を通じて HTML + CSS + JavaScript のページを作成することができます。KernelSU のマネージャーは WebView を通じてこれらのページを表示します。また、シェルコマンドの実行など、システムと対話するためのいくつかのAPIを提供しています。
## webroot ディレクトリ
Web リソースファイルは、モジュールのルートディレクトリの webroot サブディレクトリに置かれるべきであり、index.html という名前のファイルが必ず存在しなければなりません。これがモジュールページのエントリです。Web インターフェイスを含む最もシンプルなモジュール構造は以下の通りです:
```txt
tree .
.
|-- module.prop
`-- webroot
`-- index.html
```
:::警告
モジュールをインストールするとき、KernelSU はこのディレクトリのパーミッションと SELinux コンテキストを自動的に設定します。何をしているかわからないのであれば、自分でこのディレクトリのパーミッションを設定しないでください!
:::
ページに css や JavaScript が含まれている場合は、このディレクトリに配置する必要があります。
## Javascript API
単なる表示ページであれば、通常の Web ページとの違いはありません。より重要なのは、KernelSU がモジュールの固有機能を実装させるための一連のシステム API を提供することです。
KernelSU は Javascript ライブラリを提供し、[npm で公開しています](https://www.npmjs.com/package/kernelsu)。これを Web ページの JavaScript コードで使用することができます。
たとえば、特定の設定を取得したり、プロパティを変更するために、シェルコマンドを実行することができます:
```javascript
import { exec } from 'kernelsu';
const { errno, stdout } = exec("getprop ro.product.model");
```
別の例として、Webページをフルスクリーンで表示したり、トーストを表示することができます。
[API ドキュメント](https://www.npmjs.com/package/kernelsu)
既存のAPIがご自身のニーズを満たしていない、または使い勝手が不便である場合、[こちら](https://github.com/tiann/KernelSU/issues)でご提案いただければ幸いです!
## いくつかのヒント
1. localStorage を通常通りに使用してデータを保存することができますが、Manager アプリをアンインストールした後には失われます。永続的に保存する必要がある場合は、自分でいくつかのディレクトリにデータを書き込むことができます。
2. シンプルなページには、[parceljs](https://parceljs.org/)を使用することをお勧めします。設定が不要で非常に便利です。しかし、フロントエンドの達人である場合や、自分の好みがある場合は、気に入ったものを選んでください!

View File

@@ -0,0 +1,48 @@
# Módulo WebUI
Além de executar scripts de inicialização e modificar arquivos do sistema, os módulos do KernelSU também suportam a exibição de interfaces da UI e a interação com os usuários.
O módulo pode escrever páginas HTML + CSS + JavaScript através de qualquer tecnologia web. O gerenciador do KernelSU exibirá essas páginas através do WebView. Ele também fornece algumas APIs para interagir com o sistema, como executar comandos shell.
## Diretório webroot
Os arquivos de recursos da web devem ser colocados no subdiretório `webroot` do diretório raiz do módulo, e **DEVE** haver um arquivo chamado `index.html`, que é a entrada da página do módulo. A estrutura do módulo mais simples contendo uma interface web é a seguinte:
```txt
tree .
.
|-- module.prop
`-- webroot
`-- index.html
```
:::warning AVISO
Ao instalar o módulo, KernelSU definirá automaticamente as permissões e o contexto SELinux deste diretório. Se você não sabe o que está fazendo, não defina você mesmo as permissões deste diretório!
:::
Se sua página contém CSS e JavaScript, você também precisa colocá-la neste diretório.
## API JavaScript
Se for apenas uma página de exibição, não será diferente de uma página da web normal. Mais importante ainda, KernelSU fornece uma série de APIs de sistema que permitem implementar as funções exclusivas do módulo.
KernelSU fornece uma biblioteca JavaScript e [publica-a no npm](https://www.npmjs.com/package/kernelsu), que você pode usar no código JavaScript de suas páginas da web.
Por exemplo, você pode executar um comando shell para obter uma configuração específica ou modificar uma propriedade:
```javascript
import { exec } from 'kernelsu';
const { errno, stdout } = exec("getprop ro.product.model");
```
Para outro exemplo, você pode fazer com que a página da web seja exibida em tela inteira ou exibir um dica.
[Documentação da API](https://www.npmjs.com/package/kernelsu)
Se você achar que a API existente não atende às suas necessidades ou é inconveniente de usar, fique à vontade para nos dar sugestões [aqui](https://github.com/tiann/KernelSU/issues)!
## Algumas dicas
1. Você pode usar `localStorage` normalmente para armazenar alguns dados, mas eles serão perdidos após a desinstalação do app gerenciador. Se precisar salvar persistentemente, você mesmo pode gravar os dados em algum diretório.
2. Para páginas simples, recomendo que você use [parceljs](https://parceljs.org/) para empacotamento. Não requer configuração do zero e é muito conveniente de usar. Porém, se você é um mestre front-end ou tem suas próprias preferências, basta escolher o que você gosta!

View File

@@ -4,6 +4,10 @@ O KernelSU fornece um mecanismo de módulo que consegue modificar o diretório d
O mecanismo de módulo do KernelSU é quase o mesmo do Magisk. Se você está familiarizado com o desenvolvimento de módulos Magisk, o desenvolvimento de módulos KernelSU é muito semelhante. Você pode pular a introdução dos módulos abaixo e só precisa ler [Diferença com Magisk](difference-with-magisk.md). O mecanismo de módulo do KernelSU é quase o mesmo do Magisk. Se você está familiarizado com o desenvolvimento de módulos Magisk, o desenvolvimento de módulos KernelSU é muito semelhante. Você pode pular a introdução dos módulos abaixo e só precisa ler [Diferença com Magisk](difference-with-magisk.md).
## WebUI
Os módulos do KernelSU suportam a exibição de interfaces e a interação com os usuários, consulte a [documentação WebUI](module-webui.md).
## BusyBox ## BusyBox
O KernelSU vem com um recurso binário BusyBox completo (incluindo suporte completo ao SELinux). O executável está localizado em `/data/adb/ksu/bin/busybox`. O BusyBox do KernelSU suporta o "ASH Standalone Shell Mode" alternável em tempo de execução. O que este Modo Autônomo significa é que ao executar no shell `ash` do BusyBox, cada comando usará diretamente o miniaplicativo dentro do BusyBox, independentemente do que estiver definido como `PATH`. Por exemplo, comandos como `ls`, `rm`, `chmod` **NÃO** usarão o que está em `PATH` (no caso do Android por padrão será `/system/bin/ls`, `/system/bin/rm` e `/system/bin/chmod` respectivamente), mas em vez disso chamará diretamente os miniaplicativos internos do BusyBox. Isso garante que os scripts sempre sejam executados em um ambiente previsível e sempre tenham o conjunto completo de comandos, independentemente da versão do Android em que estão sendo executados. Para forçar um comando a **NÃO** usar o BusyBox, você deve chamar o executável com caminhos completos. O KernelSU vem com um recurso binário BusyBox completo (incluindo suporte completo ao SELinux). O executável está localizado em `/data/adb/ksu/bin/busybox`. O BusyBox do KernelSU suporta o "ASH Standalone Shell Mode" alternável em tempo de execução. O que este Modo Autônomo significa é que ao executar no shell `ash` do BusyBox, cada comando usará diretamente o miniaplicativo dentro do BusyBox, independentemente do que estiver definido como `PATH`. Por exemplo, comandos como `ls`, `rm`, `chmod` **NÃO** usarão o que está em `PATH` (no caso do Android por padrão será `/system/bin/ls`, `/system/bin/rm` e `/system/bin/chmod` respectivamente), mas em vez disso chamará diretamente os miniaplicativos internos do BusyBox. Isso garante que os scripts sempre sejam executados em um ambiente previsível e sempre tenham o conjunto completo de comandos, independentemente da versão do Android em que estão sendo executados. Para forçar um comando a **NÃO** usar o BusyBox, você deve chamar o executável com caminhos completos.

View File

@@ -0,0 +1,48 @@
# 模块 WebUI
KernelSU 的模块除了执行启动脚本和修改系统文件之外,还支持显示 UI 界面和与用户交互。
你可以通过任何 Web 技术编写 HTML + CSS + Javascript 页面KernelSU 的管理器将通过WebView 显示这些页面。此外KernelSU 还提供了一些用于与系统交互的 Javascript API例如执行shell命令。
## WebUI 根目录
Web 资源文件应放置在模块根目录的 `webroot` 子目录中,并且其中**必须**有一个名为`index.html`的文件,该文件是模块页面入口。包含 Web 界面的最简单的模块结构如下:
````txt
tree .
.
|-- module.prop
`-- webroot
`--index.html
````
:::warning
安装模块时KernelSU 会自动设置 `webroot` 目录的权限和 SELinux context如果您不知道自己在做什么请不要自行设置该目录的权限
:::
如果您的页面包含 css 和 javascript您也需要将其放入此目录中。
## JavaScript API
如果只是一个显示页面那它和普通网页没有什么区别。更重要的是KernelSU 提供了一系列的系统API可以让您实现模块特有的功能。
KernelSU 提供了一个 Javascript 库并[在 npm 上发布](https://www.npmjs.com/package/kernelsu),您可以在网页的 javascript 代码中使用它。
例如,您可以执行 shell 命令来获取特定配置或修改属性:
```javascript
import { exec } from 'kernelsu';
const { errno, stdout } = await exec("getprop ro.product.model");
````
再比如你可以让网页全屏显示或者显示一个Toast。
[API文档](https://www.npmjs.com/package/kernelsu)
如果您发现现有的API不能满足您的需求或者使用不方便欢迎[在这里](https://github.com/tiann/KernelSU/issues)给我们提出建议!
## 一些技巧
1. 您可以正常使用`localStorage`存储一些数据,但卸载管理器后,这些数据将会丢失。 如果需要持久保存,可以自己将数据写入某个目录。
2. 对于简单的页面,我建议您使用[parceljs](https://parceljs.org/)进行打包。它零配置,使用非常方便。不过,如果你是前端高手或者有自己的喜好,那就选择你喜欢的吧!

View File

@@ -4,6 +4,10 @@ KernelSU 提供了一个模块机制,它可以在保持系统分区完整性
KernelSU 的模块运作机制与 Magisk 几乎是一样的,如果你熟悉 Magisk 模块的开发,那么开发 KernelSU 的模块大同小异,你可以跳过下面有关模块的介绍,只需要了解 [KernelSU 模块与 Magisk 模块的异同](difference-with-magisk.md)。 KernelSU 的模块运作机制与 Magisk 几乎是一样的,如果你熟悉 Magisk 模块的开发,那么开发 KernelSU 的模块大同小异,你可以跳过下面有关模块的介绍,只需要了解 [KernelSU 模块与 Magisk 模块的异同](difference-with-magisk.md)。
## 模块界面
KernelSU 的模块支持显示界面并与用户交互,请参阅 [WebUI 文档](module-webui.md)。
## Busybox ## Busybox
KernelSU 提供了一个功能完备的 BusyBox 二进制文件包括完整的SELinux支持。可执行文件位于 `/data/adb/ksu/bin/busybox` KernelSU 提供了一个功能完备的 BusyBox 二进制文件包括完整的SELinux支持。可执行文件位于 `/data/adb/ksu/bin/busybox`