f1112fdf37
The way how logical partition, or "Logical Resizable Android Partitions" as they say in AOSP source code, is setup makes it impossible to early mount the partitions from the shared super partition with just a few lines of code; in fact, AOSP has a whole "fs_mgr" folder which consist of multiple complex libraries, with 15K lines of code just to deal with the device mapper shenanigans. In order to keep the already overly complicated MagiskInit more managable, I chose NOT to go the route of including fs_mgr directly into MagiskInit. Luckily, starting from Android Q, Google decided to split init startup into 3 stages, with the first stage doing _only_ early mount. This is great news, because we can simply let the stock init do its own thing for us, and we intercept the bootup sequence. So the workflow can be visualized roughly below: Magisk First Stage --> First Stage Mount --> Magisk Second Stage --+ (MagiskInit) (Original Init) (MagiskInit) + + + ...Rest of the boot... <-- Second Stage <-- Selinux Setup <--+ (__________________ Original Init ____________________) The catch here is that after doing all the first stage mounting, /init will pivot /system as root directory (/), leaving us impossible to regain control after we hand it over. So the solution here is to patch fstab in /first_stage_ramdisk on-the-fly to redirect /system to /system_root, making the original init do all the hard work for us and mount required early mount partitions, but skips the step of switching root directory. It will also conveniently hand over execution back to MagiskInit, which we will reuse the routine for patching root directory in normal system-as-root situations.
429 lines
11 KiB
Bash
429 lines
11 KiB
Bash
#########################################
|
|
#
|
|
# Magisk General Utility Functions
|
|
# by topjohnwu
|
|
#
|
|
#########################################
|
|
|
|
##########
|
|
# Presets
|
|
##########
|
|
|
|
#MAGISK_VERSION_STUB
|
|
|
|
# Detect whether in boot mode
|
|
[ -z $BOOTMODE ] && BOOTMODE=false
|
|
$BOOTMODE || ps | grep zygote | grep -qv grep && BOOTMODE=true
|
|
$BOOTMODE || ps -A 2>/dev/null | grep zygote | grep -qv grep && BOOTMODE=true
|
|
|
|
# Presets
|
|
MAGISKTMP=/sbin/.magisk
|
|
NVBASE=/data/adb
|
|
[ -z $TMPDIR ] && TMPDIR=/dev/tmp
|
|
|
|
# Bootsigner related stuff
|
|
BOOTSIGNERCLASS=a.a
|
|
BOOTSIGNER="/system/bin/dalvikvm -Xnodex2oat -Xnoimage-dex2oat -cp \$APK \$BOOTSIGNERCLASS"
|
|
BOOTSIGNED=false
|
|
|
|
###################
|
|
# Helper Functions
|
|
###################
|
|
|
|
ui_print() {
|
|
$BOOTMODE && echo "$1" || echo -e "ui_print $1\nui_print" >> /proc/self/fd/$OUTFD
|
|
}
|
|
|
|
toupper() {
|
|
echo "$@" | tr '[:lower:]' '[:upper:]'
|
|
}
|
|
|
|
grep_cmdline() {
|
|
local REGEX="s/^$1=//p"
|
|
cat /proc/cmdline | tr '[:space:]' '\n' | sed -n "$REGEX" 2>/dev/null
|
|
}
|
|
|
|
grep_prop() {
|
|
local REGEX="s/^$1=//p"
|
|
shift
|
|
local FILES=$@
|
|
[ -z "$FILES" ] && FILES='/system/build.prop'
|
|
sed -n "$REGEX" $FILES 2>/dev/null | head -n 1
|
|
}
|
|
|
|
getvar() {
|
|
local VARNAME=$1
|
|
local VALUE=
|
|
VALUE=`grep_prop $VARNAME /sbin/.magisk/config /data/.magisk /cache/.magisk`
|
|
[ ! -z $VALUE ] && eval $VARNAME=\$VALUE
|
|
}
|
|
|
|
is_mounted() {
|
|
grep -q " `readlink -f $1` " /proc/mounts 2>/dev/null
|
|
return $?
|
|
}
|
|
|
|
abort() {
|
|
ui_print "$1"
|
|
$BOOTMODE || recovery_cleanup
|
|
exit 1
|
|
}
|
|
|
|
resolve_vars() {
|
|
MAGISKBIN=$NVBASE/magisk
|
|
POSTFSDATAD=$NVBASE/post-fs-data.d
|
|
SERVICED=$NVBASE/service.d
|
|
}
|
|
|
|
######################
|
|
# Environment Related
|
|
######################
|
|
|
|
setup_flashable() {
|
|
# Preserve environment varibles
|
|
OLD_PATH=$PATH
|
|
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 -q " 3 $FD "; then
|
|
OUTFD=$FD
|
|
break
|
|
fi
|
|
fi
|
|
done
|
|
fi
|
|
}
|
|
|
|
ensure_bb() {
|
|
if [ -x $MAGISKTMP/busybox/busybox ]; then
|
|
[ -z $BBDIR ] && BBDIR=$MAGISKTMP/busybox
|
|
elif [ -x $TMPDIR/bin/busybox ]; then
|
|
[ -z $BBDIR ] && BBDIR=$TMPDIR/bin
|
|
else
|
|
# Construct the PATH
|
|
[ -z $BBDIR ] && BBDIR=$TMPDIR/bin
|
|
mkdir -p $BBDIR
|
|
ln -s $MAGISKBIN/busybox $BBDIR/busybox
|
|
$MAGISKBIN/busybox --install -s $BBDIR
|
|
fi
|
|
echo $PATH | grep -q "^$BBDIR" || export PATH=$BBDIR:$PATH
|
|
}
|
|
|
|
recovery_actions() {
|
|
# Make sure random don't get blocked
|
|
mount -o bind /dev/urandom /dev/random
|
|
# Unset library paths
|
|
OLD_LD_LIB=$LD_LIBRARY_PATH
|
|
OLD_LD_PRE=$LD_PRELOAD
|
|
OLD_LD_CFG=$LD_CONFIG_FILE
|
|
unset LD_LIBRARY_PATH
|
|
unset LD_PRELOAD
|
|
unset LD_CONFIG_FILE
|
|
# Force our own busybox path to be in the front
|
|
# and do not use anything in recovery's sbin
|
|
export PATH=$BBDIR:/system/bin:/vendor/bin
|
|
}
|
|
|
|
recovery_cleanup() {
|
|
export PATH=$OLD_PATH
|
|
[ -z $OLD_LD_LIB ] || export LD_LIBRARY_PATH=$OLD_LD_LIB
|
|
[ -z $OLD_LD_PRE ] || export LD_PRELOAD=$OLD_LD_PRE
|
|
[ -z $OLD_LD_CFG ] || export LD_CONFIG_FILE=$OLD_LD_CFG
|
|
ui_print "- Unmounting partitions"
|
|
umount -l /system_root 2>/dev/null
|
|
umount -l /system 2>/dev/null
|
|
umount -l /vendor 2>/dev/null
|
|
umount -l /dev/random 2>/dev/null
|
|
}
|
|
|
|
#######################
|
|
# Installation Related
|
|
#######################
|
|
|
|
find_block() {
|
|
for BLOCK in "$@"; do
|
|
DEVICE=`find /dev/block -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
|
|
local DEVNAME=`grep_prop DEVNAME $uevent`
|
|
local PARTNAME=`grep_prop PARTNAME $uevent`
|
|
for BLOCK in "$@"; do
|
|
if [ "`toupper $BLOCK`" = "`toupper $PARTNAME`" ]; then
|
|
echo /dev/block/$DEVNAME
|
|
return 0
|
|
fi
|
|
done
|
|
done
|
|
return 1
|
|
}
|
|
|
|
mount_part() {
|
|
$BOOTMODE && return
|
|
local PART=$1
|
|
local POINT=/${PART}
|
|
[ -L $POINT ] && rm -f $POINT
|
|
mkdir $POINT 2>/dev/null
|
|
is_mounted $POINT && return
|
|
ui_print "- Mounting $PART"
|
|
mount -o ro $POINT 2>/dev/null
|
|
if ! is_mounted $POINT; then
|
|
local BLOCK=`find_block $PART$SLOT`
|
|
mount -o ro $BLOCK $POINT
|
|
fi
|
|
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_part system
|
|
if [ -f /system/init.rc ]; then
|
|
SYSTEM_ROOT=true
|
|
[ -L /system_root ] && rm -f /system_root
|
|
mkdir /system_root 2>/dev/null
|
|
mount --move /system /system_root
|
|
mount -o bind /system_root/system /system
|
|
else
|
|
grep ' / ' /proc/mounts | grep -qv 'rootfs' || grep -q ' /system_root ' /proc/mounts \
|
|
&& SYSTEM_ROOT=true || SYSTEM_ROOT=false
|
|
fi
|
|
[ -L /system/vendor ] && mount_part vendor
|
|
$SYSTEM_ROOT && ui_print "- Device is system-as-root"
|
|
}
|
|
|
|
get_flags() {
|
|
# override variables
|
|
getvar KEEPVERITY
|
|
getvar KEEPFORCEENCRYPT
|
|
getvar RECOVERYMODE
|
|
if [ -z $KEEPVERITY ]; then
|
|
if $SYSTEM_ROOT; then
|
|
KEEPVERITY=true
|
|
ui_print "- System-as-root, keep dm/avb-verity"
|
|
else
|
|
KEEPVERITY=false
|
|
fi
|
|
fi
|
|
if [ -z $KEEPFORCEENCRYPT ]; then
|
|
grep ' /data ' /proc/mounts | grep -q 'dm-' && FDE=true || FDE=false
|
|
[ -d /data/unencrypted ] && FBE=true || FBE=false
|
|
# No data access means unable to decrypt in recovery
|
|
if $FDE || $FBE || ! $DATA; then
|
|
KEEPFORCEENCRYPT=true
|
|
ui_print "- Encrypted data, keep forceencrypt"
|
|
else
|
|
KEEPFORCEENCRYPT=false
|
|
fi
|
|
fi
|
|
[ -z $RECOVERYMODE ] && RECOVERYMODE=false
|
|
}
|
|
|
|
find_boot_image() {
|
|
BOOTIMAGE=
|
|
if $RECOVERYMODE; then
|
|
BOOTIMAGE=`find_block recovery_ramdisk$SLOT recovery`
|
|
elif [ ! -z $SLOT ]; then
|
|
BOOTIMAGE=`find_block ramdisk$SLOT recovery_ramdisk$SLOT boot$SLOT`
|
|
else
|
|
BOOTIMAGE=`find_block ramdisk recovery_ramdisk kern-a android_boot kernel boot lnx bootimg boot_a`
|
|
fi
|
|
if [ -z $BOOTIMAGE ]; then
|
|
# Lets see what fstabs tells me
|
|
BOOTIMAGE=`grep -v '#' /etc/*fstab* | grep -E '/boot[^a-zA-Z]' | grep -oE '/dev/[a-zA-Z0-9_./-]*' | head -n 1`
|
|
fi
|
|
}
|
|
|
|
flash_image() {
|
|
# Make sure all blocks are writable
|
|
$MAGISKBIN/magisk --unlock-blocks 2>/dev/null
|
|
case "$1" in
|
|
*.gz) CMD1="$MAGISKBIN/magiskboot decompress '$1' - 2>/dev/null";;
|
|
*) CMD1="cat '$1'";;
|
|
esac
|
|
if $BOOTSIGNED; then
|
|
CMD2="$BOOTSIGNER -sign"
|
|
ui_print "- Sign image with test keys"
|
|
else
|
|
CMD2="cat -"
|
|
fi
|
|
if [ -b "$2" ]; then
|
|
local img_sz=`stat -c '%s' "$1"`
|
|
local blk_sz=`blockdev --getsize64 "$2"`
|
|
[ $img_sz -gt $blk_sz ] && return 1
|
|
eval $CMD1 | eval $CMD2 | cat - /dev/zero > "$2" 2>/dev/null
|
|
else
|
|
ui_print "- Not block device, storing image"
|
|
eval $CMD1 | eval $CMD2 > "$2" 2>/dev/null
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
find_dtbo_image() {
|
|
DTBOIMAGE=`find_block dtbo$SLOT`
|
|
}
|
|
|
|
patch_dtbo_image() {
|
|
find_dtbo_image
|
|
if [ ! -z $DTBOIMAGE ]; then
|
|
ui_print "- DTBO image: $DTBOIMAGE"
|
|
if $MAGISKBIN/magiskboot --dtb-test $DTBOIMAGE; then
|
|
ui_print "- Backing up stock DTBO image"
|
|
$MAGISKBIN/magiskboot --compress $DTBOIMAGE $MAGISKBIN/stock_dtbo.img.gz
|
|
ui_print "- Patching DTBO to remove avb-verity"
|
|
$MAGISKBIN/magiskboot --dtb-patch $DTBOIMAGE
|
|
return 0
|
|
fi
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
sign_chromeos() {
|
|
ui_print "- Signing ChromeOS boot image"
|
|
|
|
echo > empty
|
|
./chromeos/futility vbutil_kernel --pack new-boot.img.signed \
|
|
--keyblock ./chromeos/kernel.keyblock --signprivate ./chromeos/kernel_data_key.vbprivk \
|
|
--version 1 --vmlinuz new-boot.img --config empty --arch arm --bootloader empty --flags 0x1
|
|
|
|
rm -f empty new-boot.img
|
|
mv new-boot.img.signed new-boot.img
|
|
}
|
|
|
|
remove_system_su() {
|
|
if [ -f /system/bin/su -o -f /system/xbin/su ] && [ ! -f /su/bin/su ]; then
|
|
ui_print "- Removing system installed root"
|
|
mount -o rw,remount /system
|
|
# SuperSU
|
|
if [ -e /system/bin/.ext/.su ]; then
|
|
mv -f /system/bin/app_process32_original /system/bin/app_process32 2>/dev/null
|
|
mv -f /system/bin/app_process64_original /system/bin/app_process64 2>/dev/null
|
|
mv -f /system/bin/install-recovery_original.sh /system/bin/install-recovery.sh 2>/dev/null
|
|
cd /system/bin
|
|
if [ -e app_process64 ]; then
|
|
ln -sf app_process64 app_process
|
|
elif [ -e app_process32 ]; then
|
|
ln -sf app_process32 app_process
|
|
fi
|
|
fi
|
|
rm -rf /system/.pin /system/bin/.ext /system/etc/.installed_su_daemon /system/etc/.has_su_daemon \
|
|
/system/xbin/daemonsu /system/xbin/su /system/xbin/sugote /system/xbin/sugote-mksh /system/xbin/supolicy \
|
|
/system/bin/app_process_init /system/bin/su /cache/su /system/lib/libsupol.so /system/lib64/libsupol.so \
|
|
/system/su.d /system/etc/install-recovery.sh /system/etc/init.d/99SuperSUDaemon /cache/install-recovery.sh \
|
|
/system/.supersu /cache/.supersu /data/.supersu \
|
|
/system/app/Superuser.apk /system/app/SuperSU /cache/Superuser.apk 2>/dev/null
|
|
fi
|
|
}
|
|
|
|
api_level_arch_detect() {
|
|
API=`grep_prop ro.build.version.sdk`
|
|
ABI=`grep_prop ro.product.cpu.abi | cut -c-3`
|
|
ABI2=`grep_prop ro.product.cpu.abi2 | cut -c-3`
|
|
ABILONG=`grep_prop ro.product.cpu.abi`
|
|
|
|
ARCH=arm
|
|
ARCH32=arm
|
|
IS64BIT=false
|
|
if [ "$ABI" = "x86" ]; then ARCH=x86; ARCH32=x86; fi;
|
|
if [ "$ABI2" = "x86" ]; then ARCH=x86; ARCH32=x86; fi;
|
|
if [ "$ABILONG" = "arm64-v8a" ]; then ARCH=arm64; ARCH32=arm; IS64BIT=true; fi;
|
|
if [ "$ABILONG" = "x86_64" ]; then ARCH=x64; ARCH32=x86; IS64BIT=true; fi;
|
|
}
|
|
|
|
check_data() {
|
|
DATA=false
|
|
DATA_DE=false
|
|
if grep ' /data ' /proc/mounts | grep -vq 'tmpfs'; then
|
|
# Test if data is writable
|
|
touch /data/.rw && rm /data/.rw && DATA=true
|
|
# Test if DE storage is writable
|
|
$DATA && [ -d /data/adb ] && touch /data/adb/.rw && rm /data/adb/.rw && DATA_DE=true
|
|
fi
|
|
$DATA && NVBASE=/data || NVBASE=/cache/data_adb
|
|
$DATA_DE && NVBASE=/data/adb
|
|
resolve_vars
|
|
}
|
|
|
|
find_manager_apk() {
|
|
APK=/data/adb/magisk.apk
|
|
[ -f $APK ] || APK=/data/magisk/magisk.apk
|
|
[ -f $APK ] || APK=/data/app/com.topjohnwu.magisk*/*.apk
|
|
if [ ! -f $APK ]; then
|
|
DBAPK=`magisk --sqlite "SELECT value FROM strings WHERE key='requester'" | cut -d= -f2`
|
|
[ -z "$DBAPK" ] || APK=/data/app/$DBAPK*/*.apk
|
|
fi
|
|
}
|
|
|
|
#################
|
|
# Module Related
|
|
#################
|
|
|
|
set_perm() {
|
|
chown $2:$3 $1 || return 1
|
|
chmod $4 $1 || return 1
|
|
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`
|
|
}
|
|
|
|
request_zip_size_check() {
|
|
reqSizeM=`unzip -l "$1" | tail -n 1 | awk '{ print int(($1 - 1) / 1048576 + 1) }'`
|
|
}
|
|
|
|
##################################
|
|
# Backwards Compatibile Functions
|
|
##################################
|
|
|
|
get_outfd() { setup_flashable; }
|
|
|
|
mount_magisk_img() {
|
|
$BOOTMODE && MODULE_BASE=modules_update || MODULE_BASE=modules
|
|
MODULEPATH=$NVBASE/$MODULE_BASE
|
|
mkdir -p $MODULEPATH 2>/dev/null
|
|
ln -s $MODULEPATH $MOUNTPATH
|
|
}
|
|
|
|
unmount_magisk_img() {
|
|
rm -f $MOUNTPATH 2>/dev/null
|
|
}
|
|
|
|
boot_actions() { return; }
|
|
|
|
########
|
|
# Setup
|
|
########
|
|
|
|
resolve_vars
|