#!/system/bin/sh

LOGFILE=/cache/magisk.log
IMG=/data/magisk.img

MOUNTPOINT=/magisk

COREDIR=$MOUNTPOINT/.core

TMPDIR=/dev/magisk
DUMMDIR=$TMPDIR/dummy
MIRRDIR=$TMPDIR/mirror
MOUNTINFO=$TMPDIR/mnt

# Use the included busybox for maximum compatibility and reliable results
# e.g. we rely on the option "-c" for cp (reserve contexts), and -exec for find
TOOLPATH=/data/busybox
BINPATH=/data/magisk

# Legacy support for old phh, we don't change PATH now
# Will remove in the next release
export OLDPATH=$PATH


log_print() {
  echo "$1"
  echo "$1" >> $LOGFILE
  log -p i -t Magisk "$1"
}

mktouch() {
  mkdir -p ${1%/*} 2>/dev/null
  if [ -z "$2" ]; then
    touch $1 2>/dev/null
  else
    echo $2 > $1 2>/dev/null
  fi
}

unblock() {
  touch /dev/.magisk.unblock
  exit
}

run_scripts() {
  BASE=$MOUNTPOINT
  for MOD in $BASE/* ; do
    if [ ! -f "$MOD/disable" ]; then
      if [ -f "$MOD/$1.sh" ]; then
        chmod 755 $MOD/$1.sh
        chcon "u:object_r:system_file:s0" "$MOD/$1.sh"
        log_print "$1: $MOD/$1.sh"
        sh $MOD/$1.sh
      fi
    fi
  done
}

loopsetup() {
  LOOPDEVICE=
  for DEV in $(ls /dev/block/loop*); do
    if [ `$TOOLPATH/losetup $DEV $1 >/dev/null 2>&1; echo $?` -eq 0 ]; then
      LOOPDEVICE=$DEV
      break
    fi
  done
}

target_size_check() {
  e2fsck -p -f $1
  curBlocks=`e2fsck -n $1 2>/dev/null | cut -d, -f3 | cut -d\  -f2`;
  curUsedM=$((`echo "$curBlocks" | cut -d/ -f1` * 4 / 1024));
  curSizeM=$((`echo "$curBlocks" | cut -d/ -f2` * 4 / 1024));
  curFreeM=$((curSizeM - curUsedM));
}

travel() {
  cd "$1/$2"
  if [ -f ".replace" ]; then
    rm -rf "$MOUNTINFO/$2"
    mktouch "$MOUNTINFO/$2" "$1"
  else
    for ITEM in * ; do
      if [ ! -e "/$2/$ITEM" ]; then
        # New item found
        if [ "$2" = "system" ]; then
          # We cannot add new items to /system root, delete it
          rm -rf "$ITEM"
        else
          # If we are in a higher level, delete the lower levels
          rm -rf "$MOUNTINFO/dummy/$2"
          # Mount the dummy parent
          mktouch "$MOUNTINFO/dummy/$2"

          if [ -d "$ITEM" ]; then
            # Create new dummy directory and mount it
            mkdir -p "$DUMMDIR/$2/$ITEM"
            mktouch "$MOUNTINFO/$2/$ITEM" "$1"
          elif [ -L "$ITEM" ]; then
            # Symlinks are small, copy them
            mkdir -p "$DUMMDIR/$2" 2>/dev/null
            $TOOLPATH/cp -afc "$ITEM" "$DUMMDIR/$2/$ITEM"
          else
            # Create new dummy file and mount it
            mktouch "$DUMMDIR/$2/$ITEM"
            mktouch "$MOUNTINFO/$2/$ITEM" "$1"
          fi
        fi
      else
        if [ -d "$ITEM" ]; then
          # It's an directory, travel deeper
          (travel "$1" "$2/$ITEM")
        elif [ ! -L "$ITEM" ]; then
          # Mount this file
          mktouch "$MOUNTINFO/$2/$ITEM" "$1"
        fi
      fi
    done
  fi
}

clone_dummy() {
  for ITEM in "$1/"* ; do
    if [ -d "$DUMMDIR$ITEM" ]; then
      (clone_dummy "$ITEM")
    else
      if [ -d "$ITEM" ]; then
        # Create dummy directory
        mkdir -p "$DUMMDIR$ITEM"
      elif [ -L "$ITEM" ]; then
        # Symlinks are small, copy them
        $TOOLPATH/cp -afc "$ITEM" "$DUMMDIR$ITEM"
      else
        # Create dummy file
        mktouch "$DUMMDIR$ITEM"
      fi
    fi
  done
}

bind_mount() {
  if [ -e "$1" -a -e "$2" ]; then
    mount -o bind $1 $2
    if [ "$?" -eq "0" ]; then 
      log_print "Mount: $1"
    else 
      log_print "Mount Fail: $1"
    fi 
  fi
}

merge_image() {
  if [ -f "$1" ]; then
    log_print "$1 found"
    if [ -f "$IMG" ]; then
      log_print "$IMG found, attempt to merge"

      # Handle large images
      target_size_check $1
      MERGEUSED=$curUsedM
      target_size_check $IMG
      if [ "$MERGEUSED" -gt "$curFreeM" ]; then
        NEWDATASIZE=$((((MERGEUSED + curUsedM) / 32 + 2) * 32))
        log_print "Expanding $IMG to ${NEWDATASIZE}M..."
        resize2fs $IMG ${NEWDATASIZE}M
      fi

      # Start merging
      mkdir /cache/data_img
      mkdir /cache/merge_img

      # setup loop devices
      loopsetup $IMG
      LOOPDATA=$LOOPDEVICE
      log_print "$LOOPDATA $IMG"

      loopsetup $1
      LOOPMERGE=$LOOPDEVICE
      log_print "$LOOPMERGE $1"

      if [ ! -z "$LOOPDATA" ]; then
        if [ ! -z "$LOOPMERGE" ]; then
          # if loop devices have been setup, mount images
          OK=true

          if [ `mount -t ext4 -o rw,noatime $LOOPDATA /cache/data_img >/dev/null 2>&1; echo $?` -ne 0 ]; then
            OK=false
          fi

          if [ `mount -t ext4 -o rw,noatime $LOOPMERGE /cache/merge_img >/dev/null 2>&1; echo $?` -ne 0 ]; then
            OK=false
          fi

          if ($OK); then
            # Merge (will reserve selinux contexts)
            cd /cache/merge_img
            for MOD in *; do
              if [ "$MOD" != "lost+found" ]; then
                log_print "Merging: $MOD"
                rm -rf /cache/data_img/$MOD
                $TOOLPATH/cp -afc $MOD /cache/data_img/
              fi
            done
            $TOOLPATH/cp -afc .core/. /cache/data_img/.core 2>/dev/null
            log_print "Merge complete"
          fi

          umount /cache/data_img
          umount /cache/merge_img
        fi
      fi

      $TOOLPATH/losetup -d $LOOPDATA
      $TOOLPATH/losetup -d $LOOPMERGE

      rmdir /cache/data_img
      rmdir /cache/merge_img
    else 
      log_print "Moving $1 to $IMG "
      mv $1 $IMG
    fi
    rm -f $1
  fi
}

case $1 in
  post-fs )
    mv $LOGFILE /cache/last_magisk.log
    touch $LOGFILE
    chmod 644 $LOGFILE

    # No more cache mods!
    # Only for multirom!

    # log_print "** Magisk post-fs mode running..."
    
    # Cleanup previous stuffs...
    rm -rf /cache/magisk /cache/magisk_merge /cache/magiskhide.log

    unblock
    ;;

  post-fs-data )
    if [ `mount | grep " /data " >/dev/null 2>&1; echo $?` -ne 0 ]; then
      # /data not mounted yet, we will be called again later
      unblock
    fi

    if [ `mount | grep " /data " | grep "tmpfs" >/dev/null 2>&1; echo $?` -eq 0 ]; then
      # /data not mounted yet, we will be called again later
      unblock
    fi

    # Don't run twice
    if [ "`getprop magisk.restart_pfsd`" != "1" ]; then

      log_print "** Magisk post-fs-data mode running..."

      # Live patch sepolicy
      $BINPATH/sepolicy-inject --live -s su

      # Multirom functions should go here, not available right now
      MULTIROM=false

      # Cache support
      if [ -d "/cache/data_bin" ]; then
        rm -rf $BINPATH $TOOLPATH
        mkdir -p $TOOLPATH
        mv /cache/data_bin $BINPATH
        chmod -R 755 $BINPATH $TOOLPATH
        $BINPATH/busybox --install -s $TOOLPATH
        ln -s $BINPATH/busybox $TOOLPATH/busybox
        # Prevent issues
        rm -f $TOOLPATH/su $TOOLPATH/sh
      fi

      mv /cache/stock_boot.img /data 2>/dev/null

      chcon -R "u:object_r:system_file:s0" $BINPATH $TOOLPATH

      # Image merging
      chmod 644 $IMG /cache/magisk.img /data/magisk_merge.img 2>/dev/null
      merge_image /cache/magisk.img
      merge_image /data/magisk_merge.img

      # Mount magisk.img
      [ ! -d "$MOUNTPOINT" ] && mkdir -p $MOUNTPOINT
      if [ `cat /proc/mounts | grep $MOUNTPOINT >/dev/null 2>&1; echo $?` -ne 0 ]; then
        loopsetup $IMG
        if [ ! -z "$LOOPDEVICE" ]; then
          mount -t ext4 -o rw,noatime $LOOPDEVICE $MOUNTPOINT
        fi
      fi

      if [ `cat /proc/mounts | grep $MOUNTPOINT >/dev/null 2>&1; echo $?` -ne 0 ]; then
        log_print "magisk.img mount failed, nothing to do :("
        unblock
      fi

      # Remove empty directories, legacy paths and symlinks
      rm -rf $COREDIR/bin $COREDIR/dummy $COREDIR/mirror
      $TOOLPATH/find $MOUNTPOINT -type d -depth ! -path "*core*" -exec rmdir {} \; 2>/dev/null

      # Remove modules
      for MOD in $MOUNTPOINT/* ; do
        if [ -f "$MOD/remove" ]; then
          log_print "Remove module: $MOD"
          rm -rf $MOD
        fi
      done

      # Unmount, shrink, remount
      if [ `umount $MOUNTPOINT >/dev/null 2>&1; echo $?` -eq 0 ]; then
        $TOOLPATH/losetup -d $LOOPDEVICE
        target_size_check $IMG
        NEWDATASIZE=$(((curUsedM / 32 + 2) * 32))
        if [ "$curSizeM" -gt "$NEWDATASIZE" ]; then
          log_print "Shrinking $IMG to ${NEWDATASIZE}M..."
          resize2fs $IMG ${NEWDATASIZE}M
        fi
        loopsetup $IMG
        if [ ! -z "$LOOPDEVICE" ]; then
          mount -t ext4 -o rw,noatime $LOOPDEVICE $MOUNTPOINT
        fi
        if [ `cat /proc/mounts | grep $MOUNTPOINT >/dev/null 2>&1; echo $?` -ne 0 ]; then
          log_print "magisk.img mount failed, nothing to do :("
          unblock
        fi
      fi

      log_print "Preparing modules"

      mkdir -p $DUMMDIR
      mkdir -p $MIRRDIR/system

      # Travel through all mods, all magic happens here
      for MOD in $MOUNTPOINT/* ; do
        if [ -f "$MOD/auto_mount" -a -d "$MOD/system" -a ! -f "$MOD/disable" ]; then
          (travel $MOD system)
        fi
      done

      # linker(64), t*box, and app_process* are required if we need to dummy mount bin folder
      if [ -f "$MOUNTINFO/dummy/system/bin" ]; then
        rm -f $DUMMDIR/system/bin/linker* $DUMMDIR/system/bin/t*box $DUMMDIR/system/bin/app_process*
        cd /system/bin
        $TOOLPATH/cp -afc linker* t*box app_process* $DUMMDIR/system/bin/
      fi

      # Remove crap folder
      rm -rf $MOUNTPOINT/lost+found
      
      # Start doing tasks
      
      # Stage 1
      log_print "Bind mount dummy system"
      $TOOLPATH/find $MOUNTINFO/dummy -type f 2>/dev/null | while read ITEM ; do
        TARGET=${ITEM#$MOUNTINFO/dummy}
        ORIG="$DUMMDIR$TARGET"
        clone_dummy "$TARGET"
        bind_mount "$ORIG" "$TARGET"
      done

      # Proper permissions for generated items
      $TOOLPATH/find $TMPDIR -type d -exec chmod 755 "{}" \;
      $TOOLPATH/find $TMPDIR -type f -exec chmod 644 "{}" \;
      $TOOLPATH/find $TMPDIR -exec chcon "u:object_r:system_file:s0" "{}" \;

      # Stage 2
      log_print "Bind mount module items"
      $TOOLPATH/find $MOUNTINFO/system -type f 2>/dev/null | while read ITEM ; do
        TARGET=${ITEM#$MOUNTINFO}
        ORIG=`cat $ITEM`$TARGET
        bind_mount $ORIG $TARGET
        rm -f $DUMMDIR${TARGET%/*}/.dummy 2>/dev/null
      done

      # Run scripts
      run_scripts post-fs-data

      # Bind hosts for Adblock apps
      [ ! -f "$COREDIR/hosts" ] && $TOOLPATH/cp -afc /system/etc/hosts $COREDIR/hosts
      log_print "Enabling systemless hosts file support"
      bind_mount $COREDIR/hosts /system/etc/hosts

      # Stage 3
      log_print "Bind mount system mirror"
      bind_mount /system $MIRRDIR/system

      # Stage 4
      log_print "Bind mount mirror items"
      # Find all empty directores and dummy files, they should be mounted by original files in /system
      TOOLPATH=/data/busybox $TOOLPATH/find $DUMMDIR -type d \
      -exec sh -c '[ -z "`$TOOLPATH/ls -A $1`" ] && echo $1' -- {} \; \
      -o \( -type f -size 0 -print \) | \
      while read ITEM ; do
        ORIG=${ITEM/dummy/mirror}
        TARGET=${ITEM#$DUMMDIR}
        bind_mount $ORIG $TARGET
      done

      # Restart post-fs-data if necessary (multirom)
      ($MULTIROM) && setprop magisk.restart_pfsd 1

    fi
    unblock
    ;;

  service )
    # Version info
    MAGISK_VERSION_STUB
    log_print "** Magisk late_start service mode running..."
    run_scripts service

    # Hide Safety Net
    if [ -f "$COREDIR/magiskhide/enable" ]; then
      log_print "** Removing tampered read-only system props"

      VERIFYBOOT=`getprop ro.boot.verifiedbootstate`
      FLASHLOCKED=`getprop ro.boot.flash.locked`
      VERITYMODE=`getprop ro.boot.veritymode`

      [ ! -z "$VERIFYBOOT" -a "$VERIFYBOOT" != "green" ] && log_print "`$BINPATH/resetprop -v -n ro.boot.verifiedbootstate green`"
      [ ! -z "$FLASHLOCKED" -a "$FLASHLOCKED" != "1" ] && log_print "`$BINPATH/resetprop -v -n ro.boot.flash.locked 1`"
      [ ! -z "$VERITYMODE" -a "$VERITYMODE" != "enforcing" ] && log_print "`$BINPATH/resetprop -v -n ro.boot.veritymode enforcing`"

      mktouch $COREDIR/magiskhide/hidelist
      chmod -R 755 $COREDIR/magiskhide
      # Add Safety Net preset
      $COREDIR/magiskhide/add com.google.android.gms.unstable
      log_print "** Starting Magisk Hide"
      /data/magisk/magiskhide
    fi
    ;;

esac
