diff --git a/userspace/ksud/src/banner b/userspace/ksud/src/banner new file mode 100644 index 00000000..b1f3070e --- /dev/null +++ b/userspace/ksud/src/banner @@ -0,0 +1,5 @@ +_ __ +/\ /\___ _ __ _ __ ___| / _\/\ /\ +/ //_/ _ \ '__| '_ \ / _ \ \ \/ / \ \ +/ __ \ __/ | | | | | __/ |\ \ \_/ / +\/ \/\___|_| |_| |_|\___|_\__/\___/ diff --git a/userspace/ksud/src/event.rs b/userspace/ksud/src/event.rs new file mode 100644 index 00000000..a48be8a0 --- /dev/null +++ b/userspace/ksud/src/event.rs @@ -0,0 +1,113 @@ +use std::path::{Path, PathBuf}; + +use crate::{defs, utils::mount_image}; +use anyhow::{bail, Result}; +use subprocess::Exec; + +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; + + if Path::new(module_update_img).exists() && 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)?; + } + + if !Path::new(target_update_img).exists() { + // no image exist, do nothing for module! + return Ok(()); + } + + let module_path = Path::new(module_dir); + if !module_path.exists() { + std::fs::create_dir_all(module_path)?; + } + + println!("mount {} to {}", target_update_img, module_dir); + mount_image(target_update_img, module_dir)?; + + // 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 lowerdir: Vec = 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 { + println!("module: {} is disabled, ignore!", module.display()); + continue; + } + + let mut module_system = PathBuf::new(); + module_system.push(&module); + module_system.push("system"); + + if !module_system.as_path().exists() { + println!( + "module: {} has no system overlay, ignore!", + module.display() + ); + continue; + } + lowerdir.push(format!("{}", module_system.display())); + } + + if lowerdir.is_empty() { + println!("lowerdir is empty, ignore!"); + return Ok(()); + } + + // add /system as the last lowerdir + lowerdir.push(String::from("/system")); + + let lowerdir = lowerdir.join(":"); + println!("lowerdir: {}", lowerdir); + + let mount_args = format!( + "mount -t overlay overlay -o ro,lowerdir={} /system", + lowerdir + ); + let result = Exec::shell(mount_args).join()?; + if !result.success() { + println!("mount overlay failed"); + } + + Ok(()) +} + +pub fn on_boot_completed() -> Result<()> { + 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 + std::fs::rename(module_update_img, module_img)?; + } + Ok(()) +} + +pub fn daemon() -> Result<()> { + Ok(()) +} + +pub fn install() -> Result<()> { + let src = "/proc/self/exe"; + let dst = defs::DAEMON_PATH; + + std::fs::copy(src, dst)?; + Ok(()) +} diff --git a/userspace/ksud/src/installer.sh b/userspace/ksud/src/installer.sh new file mode 100644 index 00000000..846aded0 --- /dev/null +++ b/userspace/ksud/src/installer.sh @@ -0,0 +1,401 @@ +############################################ +# KernelSU installer script +# Credit to Magisk!!! +############################################ + +ui_print() { + if $BOOTMODE; then + echo "$1" + else + echo -e "ui_print $1\nui_print" >> /proc/self/fd/$OUTFD + fi +} + +toupper() { + echo "$@" | tr '[:lower:]' '[:upper:]' +} + +grep_cmdline() { + local REGEX="s/^$1=//p" + { echo $(cat /proc/cmdline)$(sed -e 's/[^"]//g' -e 's/""//g' /proc/cmdline) | xargs -n 1; \ + sed -e 's/ = /=/g' -e 's/, /,/g' -e 's/"//g' /proc/bootconfig; \ + } 2>/dev/null | sed -n "$REGEX" +} + +grep_prop() { + local REGEX="s/^$1=//p" + shift + local FILES=$@ + [ -z "$FILES" ] && FILES='/system/build.prop' + cat $FILES 2>/dev/null | dos2unix | sed -n "$REGEX" | head -n 1 +} + +grep_get_prop() { + local result=$(grep_prop $@) + if [ -z "$result" ]; then + # Fallback to getprop + getprop "$1" + else + echo $result + fi +} + +is_mounted() { + grep -q " $(readlink -f $1) " /proc/mounts 2>/dev/null + return $? +} + +abort() { + ui_print "$1" + $BOOTMODE || recovery_cleanup + [ ! -z $MODPATH ] && rm -rf $MODPATH + rm -rf $TMPDIR + exit 1 +} + +print_title() { + local len line1len line2len bar + line1len=$(echo -n $1 | wc -c) + line2len=$(echo -n $2 | wc -c) + len=$line2len + [ $line1len -gt $line2len ] && len=$line1len + len=$((len + 2)) + bar=$(printf "%${len}s" | tr ' ' '*') + ui_print "$bar" + ui_print " $1 " + [ "$2" ] && ui_print " $2 " + ui_print "$bar" +} + +###################### +# Environment Related +###################### + +setup_flashable() { + ensure_bb + $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 + if readlink /proc/$$/fd/$FD | grep -q pipe; then + if ps | grep -v grep | grep -qE " 3 $FD |status_fd=$FD"; then + OUTFD=$FD + break + fi + fi + done + fi + recovery_actions +} + +ensure_bb() { +} + +recovery_actions() { + +} + +recovery_cleanup() { + +} + +####################### +# Installation Related +####################### + +# find_block [partname...] +find_block() { + local BLOCK DEV DEVICE DEVNAME PARTNAME UEVENT + for BLOCK in "$@"; do + DEVICE=`find /dev/block \( -type b -o -type c -o -type l \) -iname $BLOCK | head -n 1` 2>/dev/null + if [ ! -z $DEVICE ]; then + readlink -f $DEVICE + return 0 + fi + done + # Fallback by parsing sysfs uevents + for UEVENT in /sys/dev/block/*/uevent; do + DEVNAME=`grep_prop DEVNAME $UEVENT` + PARTNAME=`grep_prop PARTNAME $UEVENT` + for BLOCK in "$@"; do + if [ "$(toupper $BLOCK)" = "$(toupper $PARTNAME)" ]; then + echo /dev/block/$DEVNAME + return 0 + fi + done + done + # Look just in /dev in case we're dealing with MTD/NAND without /dev/block devices/links + for DEV in "$@"; do + DEVICE=`find /dev \( -type b -o -type c -o -type l \) -maxdepth 1 -iname $DEV | head -n 1` 2>/dev/null + if [ ! -z $DEVICE ]; then + readlink -f $DEVICE + return 0 + fi + done + return 1 +} + +# setup_mntpoint +setup_mntpoint() { + local POINT=$1 + [ -L $POINT ] && mv -f $POINT ${POINT}_link + if [ ! -d $POINT ]; then + rm -f $POINT + mkdir -p $POINT + fi +} + +# mount_name +mount_name() { + local PART=$1 + local POINT=$2 + local FLAG=$3 + setup_mntpoint $POINT + is_mounted $POINT && return + # First try mounting with fstab + mount $FLAG $POINT 2>/dev/null + if ! is_mounted $POINT; then + local BLOCK=$(find_block $PART) + mount $FLAG $BLOCK $POINT || return + fi + ui_print "- Mounting $POINT" +} + +# mount_ro_ensure +mount_ro_ensure() { + # We handle ro partitions only in recovery + $BOOTMODE && return + local PART=$1 + local POINT=$2 + mount_name "$PART" $POINT '-o ro' + is_mounted $POINT || abort "! Cannot mount $POINT" +} + +mount_partitions() { + # Check A/B slot + SLOT=`grep_cmdline androidboot.slot_suffix` + if [ -z $SLOT ]; then + SLOT=`grep_cmdline androidboot.slot` + [ -z $SLOT ] || SLOT=_${SLOT} + fi + [ -z $SLOT ] || ui_print "- Current boot slot: $SLOT" + + # Mount ro partitions + if is_mounted /system_root; then + umount /system 2&>/dev/null + umount /system_root 2&>/dev/null + fi + mount_ro_ensure "system$SLOT app$SLOT" /system + if [ -f /system/init -o -L /system/init ]; then + SYSTEM_ROOT=true + setup_mntpoint /system_root + if ! mount --move /system /system_root; then + umount /system + umount -l /system 2>/dev/null + mount_ro_ensure "system$SLOT app$SLOT" /system_root + fi + mount -o bind /system_root/system /system + else + SYSTEM_ROOT=false + grep ' / ' /proc/mounts | grep -qv 'rootfs' || grep -q ' /system_root ' /proc/mounts && SYSTEM_ROOT=true + fi + # /vendor is used only on some older devices for recovery AVBv1 signing so is not critical if fails + [ -L /system/vendor ] && mount_name vendor$SLOT /vendor '-o ro' + $SYSTEM_ROOT && ui_print "- Device is system-as-root" + + # Mount sepolicy rules dir locations in recovery (best effort) + if ! $BOOTMODE; then + mount_name "cache cac" /cache + mount_name metadata /metadata + mount_name persist /persist + fi +} + +api_level_arch_detect() { + API=$(grep_get_prop ro.build.version.sdk) + ABI=$(grep_get_prop ro.product.cpu.abi) + if [ "$ABI" = "x86" ]; then + ARCH=x86 + ABI32=x86 + IS64BIT=false + elif [ "$ABI" = "arm64-v8a" ]; then + ARCH=arm64 + ABI32=armeabi-v7a + IS64BIT=true + elif [ "$ABI" = "x86_64" ]; then + ARCH=x64 + ABI32=x86 + IS64BIT=true + else + ARCH=arm + ABI=armeabi-v7a + ABI32=armeabi-v7a + IS64BIT=false + fi +} + +################# +# Module Related +################# + +set_perm() { + chown $2:$3 $1 || return 1 + chmod $4 $1 || return 1 + local CON=$5 + [ -z $CON ] && CON=u:object_r:system_file:s0 + chcon $CON $1 || return 1 +} + +set_perm_recursive() { + find $1 -type d 2>/dev/null | while read dir; do + set_perm $dir $2 $3 $4 $6 + done + find $1 -type f -o -type l 2>/dev/null | while read file; do + set_perm $file $2 $3 $5 $6 + done +} + +mktouch() { + mkdir -p ${1%/*} 2>/dev/null + [ -z $2 ] && touch $1 || echo $2 > $1 + chmod 644 $1 +} + +request_size_check() { + reqSizeM=`du -ms "$1" | cut -f1` +} + +unzip() { + /system/bin/unzip -q "$@" +} + +request_zip_size_check() { + reqSizeM=`unzip -l "$1" | tail -n 1 | awk '{ print int(($1 - 1) / 1048576 + 1) }'` +} + +boot_actions() { return; } + +# Require ZIPFILE to be set +is_legacy_script() { + unzip -l "$ZIPFILE" install.sh | grep -q install.sh + return $? +} + +# Require OUTFD, ZIPFILE to be set +install_module() { + rm -rf $TMPDIR + mkdir -p $TMPDIR + chcon u:object_r:system_file:s0 $TMPDIR + cd $TMPDIR + + mount_partitions + api_level_arch_detect + + # Setup busybox and binaries + if $BOOTMODE; then + boot_actions + else + recovery_actions + fi + + # Extract prop file + unzip -o "$ZIPFILE" module.prop -d $TMPDIR >&2 + [ ! -f $TMPDIR/module.prop ] && abort "! Unable to extract zip file!" + + local MODDIRNAME=modules + $BOOTMODE && MODDIRNAME=modules_update + local MODULEROOT=$NVBASE/$MODDIRNAME + MODID=`grep_prop id $TMPDIR/module.prop` + MODNAME=`grep_prop name $TMPDIR/module.prop` + MODAUTH=`grep_prop author $TMPDIR/module.prop` + MODPATH=$MODULEROOT/$MODID + + # Create mod paths + rm -rf $MODPATH + mkdir -p $MODPATH + + if is_legacy_script; then + unzip -oj "$ZIPFILE" module.prop install.sh uninstall.sh 'common/*' -d $TMPDIR >&2 + + # Load install script + . $TMPDIR/install.sh + + # Callbacks + print_modname + on_install + + [ -f $TMPDIR/uninstall.sh ] && cp -af $TMPDIR/uninstall.sh $MODPATH/uninstall.sh + $SKIPMOUNT && touch $MODPATH/skip_mount + $PROPFILE && cp -af $TMPDIR/system.prop $MODPATH/system.prop + cp -af $TMPDIR/module.prop $MODPATH/module.prop + $POSTFSDATA && cp -af $TMPDIR/post-fs-data.sh $MODPATH/post-fs-data.sh + $LATESTARTSERVICE && cp -af $TMPDIR/service.sh $MODPATH/service.sh + + ui_print "- Setting permissions" + set_permissions + else + print_title "$MODNAME" "by $MODAUTH" + print_title "Powered by KernelSU" + + unzip -o "$ZIPFILE" customize.sh -d $MODPATH >&2 + + if ! grep -q '^SKIPUNZIP=1$' $MODPATH/customize.sh 2>/dev/null; then + ui_print "- Extracting module files" + unzip -o "$ZIPFILE" -x 'META-INF/*' -d $MODPATH >&2 + + # Default permissions + set_perm_recursive $MODPATH 0 0 0755 0644 + set_perm_recursive $MODPATH/system/bin 0 2000 0755 0755 + set_perm_recursive $MODPATH/system/xbin 0 2000 0755 0755 + set_perm_recursive $MODPATH/system/system_ext/bin 0 2000 0755 0755 + set_perm_recursive $MODPATH/system/vendor/bin 0 2000 0755 0755 u:object_r:vendor_file:s0 + fi + + # Load customization script + [ -f $MODPATH/customize.sh ] && . $MODPATH/customize.sh + fi + + # Handle replace folders + for TARGET in $REPLACE; do + ui_print "- Replace target: $TARGET" + mktouch $MODPATH$TARGET/.replace + done + + if $BOOTMODE; then + mktouch $NVBASE/modules/$MODID/update + rm -rf $NVBASE/modules/$MODID/remove 2>/dev/null + rm -rf $NVBASE/modules/$MODID/disable 2>/dev/null + cp -af $MODPATH/module.prop $NVBASE/modules/$MODID/module.prop + fi + + # Remove stuff that doesn't belong to modules and clean up any empty directories + rm -rf \ + $MODPATH/system/placeholder $MODPATH/customize.sh \ + $MODPATH/README.md $MODPATH/.git* + rmdir -p $MODPATH 2>/dev/null + + cd / + $BOOTMODE || recovery_cleanup + rm -rf $TMPDIR + + ui_print "- Done" +} + +########## +# Presets +########## + +# Detect whether in boot mode +[ -z $BOOTMODE ] && ps | grep zygote | grep -qv grep && BOOTMODE=true +[ -z $BOOTMODE ] && ps -A 2>/dev/null | grep zygote | grep -qv grep && BOOTMODE=true +[ -z $BOOTMODE ] && BOOTMODE=false + +NVBASE=/data/adb/ksu +TMPDIR=/dev/tmp + +# Some modules dependents on this +MAGISK_VER=25.2 +MAGISK_VER_CODE=25200 + +# KSU to recognize +KSU=true \ No newline at end of file