#!/system/bin/sh
#
#h#
#h# remount_magisk_mounts.sh <VERSION> - remount magisk mounts read-write or read-only
#h#
#h# Usage:  remount_magisk_mounts.sh [-h|--help] [-n|--dry-run] [--version] [--verbose|-v] [--test] [var=value]  [rw|ro|test|help]
#h#
#H#
#H# Without an action parameter the script prints the short usage help.
#H#
#H#
#H# The known parameter are:
#H# 
#H# --dry-run    run the script in dry-run mode 
#H# --version    print the script version and exit
#H# --verbose    print more messages
#H# --test       test write access also
#H# 
#H# rw           remount the Magisk mounts read/write
#H# ro           remount the Magisk mounts read-only
#H# test         only test write access
#H# help         print the detailed usage help
#H#
#H# Other supported parameter:
#H#
#H# var=value sets the variable "var" to the value "value"
#H#
#H# Notes
#H#
#H# 
#H# To use the script without a parameter, create a symbolic link with _rw or _ro in the name, e.g.:
#H#
#H#     ln -s ./remount_magisk_mounts.sh ./remount_magisk_mounts_rw
#H#
#H#     ln -s ./remount_magisk_mounts.sh ./remount_magisk_mounts_ro  
#H#
#H# Set the variable TRACE to any value before starting script execute it with "set -x"
#H#
#H# When running in dry-run mode, the script only prints the commands to execute to STDOUT using the command "echo". 
#H# To use another command use this syntax:
#H#
#H#     PREFIX="echo \"DRY_RUN\"": ./remount_magisk_mounts.sh
#H#
#
# Prerequisites
#
#    Magisk 28.0 or newer
#    root access
#
# Author
#   Bernd Schemmer (bernd dot schemmer at gmx dot de)
#
# History
#   28.06.2025 /bs v1.0.0
#     initial release
#

# ----------------------------------------------------------------------

__TRUE=0
__FALSE=1

# ----------------------------------------------------------------------
# enable tracing if requested
#
if [ "${TRACE}"x != ""x ] ; then
  set -x
elif [[ $- == *x* ]] ; then
#
# tracing is already enabled 
#
  TRACE=${__TRUE}
fi

# ----------------------------------------------------------------------
# define constants
#

__TRUE=0
__FALSE=1


# read the script version from the source code
#
SCRIPT_VERSION="$( grep  "^#" $0 | grep "/bs v"  | tail -1 | sed "s#.*v#v#g" )"


# ---------------------------------------------------------------------
# aliase
#
alias LogInfoVar='f() { [[ ${__FUNCTION} = "" ]] && __FUNCTION=main ; [[ ${VERBOSE} != 0 ]] && return; varname="$1"; eval "echo \"INFO: in $__FUNCTION:  $varname ist \${$varname}\" >&2"; unset -f f; } ;  f'

# ---------------------------------------------------------------------
# functions

# ---------------------------------------------------------------------
# LogMsg - write a message to STDOUT
#
# Usage: LogMsg [message]
#
function LogMsg {
  typeset __FUNCTION="LogMsg"
  
  typeset THISMSG="$@"

  echo "${THISMSG}"

  return ${__TRUE}
}

# ---------------------------------------------------------------------
# LogInfo - write a message to STDERR if VERBOSE is ${__TRUE}
#
# Usage: LogInfo [message]
#
# The function  returns ${__TRUE} if the message was written and
# ${__FALSE} if the message was not written
#
function LogInfo { 
  typeset __FUNCTION="LogInfo"

  [[ ${VERBOSE} == ${__TRUE} ]] && LogMsg "INFO: $@" >&2 || return ${__FALSE}
}

# ---------------------------------------------------------------------
# LogWarning - write a warning message to STDERR
#
# Usage: LogWarning [message]
#
function LogWarning {
  typeset __FUNCTION="LogWarning"

  LogMsg "WARNING: $@" >&2
}


# ---------------------------------------------------------------------
# LogError - write an error message to STDERR
#
# Usage: LogError [message]
#
function LogError {
  typeset __FUNCTION="LogError"

  LogMsg "ERROR: $@" >&2
}


# ---------------------------------------------------------------------
# die - end the program
#
# Usage:
#  die [returncode] [message]
# 
# returns:
#   n/a
#
function die {
  typeset __FUNCTION="die"

  typeset THISRC="$1"

  [ "${THISRC}"x = ""x ] && THISRC=0

  [ $# -gt 0 ] && shift

  typeset THISMSG="$*"

  if [ "${THISRC}"x = "0"x -o "${THISRC}"x = "1"x ] ; then
    [ "${THISMSG}"x != ""x ] && LogMsg "${THISMSG} (RC=${THISRC})"
  else
    LogError "${THISMSG} (RC=${THISRC})"
  fi


  exit ${THISRC}
}

# ---------------------------------------------------------------------

# ---------------------------------------------------------------------
# magisk_mounts - print the list of Magisk mounts
#
# Usage: magisk_mounts list_of_mounts type_of_mounts
#
#
function print_magisk_mounts {
  typeset __FUNCTION="print_magisk_mounts"

  typeset LIST_OF_MOUNTS="$1"
  typeset TYPE_OF_MOUNTS="$2"
    
  typeset CUR_MSG=""
  typeset CUR_MSG1=""
    
  if [ "${TYPE_OF_MOUNTS}"x != ""x ] ; then
    CUR_MSG="The currently \"${TYPE_OF_MOUNTS}\" mounted Magisk directories are: "
    CUR_MSG1="There are currently no \"${TYPE_OF_MOUNTS}\" mounted Magisk directories"

  else
    CUR_MSG="The currently mounted Magisk directories are: "  
    CUR_MSG1="There are currently no mounted Magisk directories"
  fi
  
  if [ "${LIST_OF_MOUNTS}"x = ""x ] ; then
    LogMsg "${CUR_MSG1}"
  else
    LogMsg "${CUR_MSG}

${LIST_OF_MOUNTS}
"
  fi
}

# ---------------------------------------------------------------------
# main function
#

# ----------------------------------------------------------------------
# enable verbose mode if requested
#

if [[ " $* " == *\ -v\ * || " $* " == *\ --verbose\ *  ]] ; then
  VERBOSE=${__TRUE}
fi

# ----------------------------------------------------------------------
# return code of the script

MAIN_RC=0

# ----------------------------------------------------------------------
# set the default action depending on the name of the script

if [[ $0 == *_ro ]] ; then
  NEW_MOUNT_OPTION="ro"
  ACTION="mount_rw"
elif [[ $0 == *_rw ]] ; then
  NEW_MOUNT_OPTION="rw"
  ACTION="mount_ro"
else
  NEW_MOUNT_OPTION=""
fi


# ----------------------------------------------------------------------
#
# process the parameter
#
TEST_RW_MODE=${__FALSE}
TEST_ONLY=${__FALSE}

PRINT_DETAILED_USAGE_HELP=${__FALSE}

if [ $# -ne 0 ] ; then

  LogInfo "Processing the parameter ..."

  LogInfo "The parameter for the script are "  && \
    LogMsg "$*"

  while [ $# -ne 0 ] ; do
    CUR_PARAMETER="$1"
    shift

    case ${CUR_PARAMETER}  in

      -h | --help )
        ACTION="help"
        ;;

      help )
        [ "${ACTION}"x != ""x ] && die 101 "Duplicate action parameter found (\"${ACTION}\" and \"${CUR_PARAMETER}\") "      
        PRINT_DETAILED_USAGE_HELP=${__TRUE}
        ACTION="help"
        ;;

      *=* )
        LogInfo "Executing now \"${CUR_PARAMETER}\" ..."
        eval ${CUR_PARAMETER}
        if [ $? -ne 0 ] ; then
          die 70 "Error executing \"${CUR_PARAMETER}\" "
        fi
        ;;

      -V | --version )
        ACTION="version"
         ;;
 
      rw )
        [ "${ACTION}"x != ""x ] && die 101 "Duplicate action parameter found (\"${ACTION}\" and \"${CUR_PARAMETER}\") "
        ACTION="mount_rw"
        OLD_MOUNT_OPTION="ro" 
        NEW_MOUNT_OPTION="rw"
        ;;

      ro )
        [ "${ACTION}"x != ""x ] && die 101 "Duplicate action parameter found (\"${ACTION}\" and \"${CUR_PARAMETER}\") "
        ACTION="mount_ro"
        OLD_MOUNT_OPTION="rw" 
        NEW_MOUNT_OPTION="ro"
        ;;

      test )
        [ "${ACTION}"x != ""x ] && die 101 "Duplicate action parameter found (\"${ACTION}\" and \"${CUR_PARAMETER}\") "
        ACTION="test_only"
        TEST_RW_MODE=${__TRUE}
        TEST_ONLY=${__TRUE} 
        ;;

      -t | --test )
        TEST_RW_MODE=${__TRUE}
        ;;  
      
      --verbose | -v )
        VERBOSE=${__TRUE}
        ;; 

      ++verbose | +v )
        VERBOSE=""
        ;; 

      -n | --dry-run )
        PREFIX="echo"
        ;;

      --* | -* )
        die 80 "Unknown parameter found: ${CUR_PARAMETER}"
        ;;

      * )
        die 90 "Unknown parameter found: \"${CUR_PARAMETER}\" " 
        ;;

    esac
  done
fi

# ---------------------------------------------------------------------

if [ "${NEW_MOUNT_OPTION}"x = ""x  -a ${TEST_RW_MODE} != ${__TRUE} ] ; then
  ACTION="help"
fi

[ "${PREFIX}"x != ""x ] && LogMsg "
INFO: The script is executed in dry-run mode
"

# ---------------------------------------------------------------------
# first check for actions that can be executed by any user
#
case ${ACTION} in

  help )
    grep "^#h#" $0 | cut -c4- | sed -e "s#<VERSION>#${SCRIPT_VERSION}#g"

    if [ ${VERBOSE}x = ${__TRUE}x -o ${PRINT_DETAILED_USAGE_HELP}x = ${__TRUE}x ] ; then
      grep "^#H#" $0 | cut -c4- 
    fi
    die 0
    ;;

  version )
    echo "${SCRIPT_VERSION}"
    die 0
    ;;

esac

# ---------------------------------------------------------------------
# all other actions need root access
#

CUR_USER="$( id -un )"
if [ "${CUR_USER}"x != "root"x ] ; then
  die 9 "This script must be executed by the user root"
fi

# ---------------------------------------------------------------------
# check the other prerequisites
#  


MAGISK="$( which magisk )"
if [ "${MAGISK}"x = ""x ] ; then
  die 11 "magiks binary not found"
fi

CUR_MAGISK_VERSION="$( ${MAGISK} -V )"
if [ ${CUR_MAGISK_VERSION} -lt 28000 ] ; then
  die 12 "The script needs Magisk v28.0 or newer"
fi

# ---------------------------------------------------------------------
# retrieve the list of magisk moounts
#
MAGISK_MOUNTS="$( mount | grep "^magisk" )"

LogInfoVar MAGISK_MOUNTS

if [ "${MAGISK_MOUNTS}"x = ""x ] ; then
  die 10 "No magisk mounts found"
fi

LogMsg "========================================================================"
LogMsg ""

MAGISK_RW_MOUNTS="$( echo "${MAGISK_MOUNTS}" | grep "(rw,"  | cut -f3 -d " " )"
MAGISK_RO_MOUNTS="$( echo "${MAGISK_MOUNTS}" | grep "(ro,"  | cut -f3 -d " " )"


LogInfoVar MAGISK_RW_MOUNTS
LogInfoVar MAGISK_RO_MOUNTS

print_magisk_mounts "${MAGISK_RO_MOUNTS}" "ro"
print_magisk_mounts "${MAGISK_RW_MOUNTS}" "rw"

LogMsg 
LogMsg "========================================================================"
LogMsg 

case ${ACTION} in

  "test_only" )
     :
    ;;
    
  mount_rw | mount_ro )
    echo "${MAGISK_MOUNTS}" | while read LINE ; do
    
      echo ${LINE} | while read DUMMY1 DUMMY2 MOUNT_POINT DUMMY3 FS_TYPE FS_OPTIONS ; do 

        LogInfoVar LINE

        LogInfoVar DUMMY1 
        LogInfoVar DUMMY2 
        LogInfoVar MOUNT_POINT 
        LogInfoVar DUMMY3 
        LogInfoVar FS_TYPE
        LogInfoVar FS_OPTIONS 
    
        if [[ ${FS_OPTIONS} == *${NEW_MOUNT_OPTION},* ]] ; then
          LogMsg "\"${MOUNT_POINT}\" is already mounted \"${NEW_MOUNT_OPTION}\" "
          continue
        fi
        
        if [ -f "${MOUNT_POINT}" ] ; then
          LogMsg "\"${MOUNT_POINT}\" is a file - skipping this entry"
          continue
        elif [  ! -d "${MOUNT_POINT}" ] ; then
          LogMsg "\"${MOUNT_POINT}\" is not a directory - skipping this entry"
          continue
        fi
                
        LogMsg "Remounting \"${MOUNT_POINT}\" with the mode \"${NEW_MOUNT_OPTION}\"  ..."
        MOUNT_OPTIONS="$( echo "${FS_OPTIONS}"  |  sed  -e "s/(${OLD_MOUNT_OPTION},/${NEW_MOUNT_OPTION},/g" -e "s/)//g" )"
        ${PREFIX} mount -t tmpfs magisk -o "remount,${MOUNT_OPTIONS}" "${MOUNT_POINT}"
        if [ $? -ne 0 ] ; then
          LogError "Error remounting \"${MOUNT_POINT}\"  \"${NEW_MOUNT_OPTION}\"  "
        fi
      done
    done

    MAGISK_MOUNTS="$( mount | grep "^magisk" )"

    MAGISK_RW_MOUNTS="$( echo "${MAGISK_MOUNTS}" | grep "(rw,"  | cut -f3 -d " " )"
    MAGISK_RO_MOUNTS="$( echo "${MAGISK_MOUNTS}" | grep "(ro,"  | cut -f3 -d " " )"

    LogMsg ""
  
    LogMsg "========================================================================"
    LogMsg "Summary"
    LogMsg ""

    print_magisk_mounts "${MAGISK_RO_MOUNTS}" "ro"
    print_magisk_mounts "${MAGISK_RW_MOUNTS}" "rw"  
    ;;
esac
  
if [ ${TEST_RW_MODE} = ${__TRUE} ] ; then
  LogMsg
  LogMsg "Testing the write access ..."

  for CUR_DIR in ${MAGISK_RW_MOUNTS} ; do
    if [ ! -d ${CUR_DIR} ] ; then
      LogMsg "${CUR_DIR} is NOT a directory -- skipping this entry"
      continue
    fi
    
    TEST_FILE="${CUR_DIR}/test.$$"
    
    ${PREFIX} touch "${TEST_FILE}"
    if [ $? -eq 0 ] ; then  
      ${PREFIX} \rm "${TEST_FILE}" 2>/dev/null
      LogMsg "Write access to the directory \"${CUR_DIR}\" works"
    else
      LogMsg "ERROR: Write access to the directory \"${CUR_DIR}\" does not work"
    fi
  done
  LogMsg
fi

[ "${PREFIX}"x != ""x ] && LogMsg "
INFO: The script was executed in dry-run mode
"

# ---------------------------------------------------------------------

return ${MAIN_RC}

# ---------------------------------------------------------------------

