Files
Wiktor a87e4b33c1 feat(settings): add comprehensive settings application script
- Introduce apply-settings.sh to apply and validate module settings
- Manage WebUI, backup, safety, system, and notification configurations
- Support automatic backup scheduling and safe mode configuration
- Include settings validation and default settings creation
- Enhance module configuration management and logging

Co-authored-by: terragon-labs[bot] <terragon-labs[bot]@users.noreply.github.com>
2025-07-21 16:37:20 +00:00

493 lines
14 KiB
Bash
Executable File

#!/system/bin/sh
# KernelSU Anti-Bootloop Backup Partition Management System
# Handles partition detection, block-level access, and snapshot management
MODDIR=${0%/*}
MODDIR=${MODDIR%/*}
CONFIG_DIR="$MODDIR/config"
PART_INFO_DIR="$CONFIG_DIR/partition_info"
SNAPSHOT_DIR="$CONFIG_DIR/snapshots"
# Ensure directories exist
mkdir -p "$PART_INFO_DIR"
mkdir -p "$SNAPSHOT_DIR"
# Log function
log_message() {
echo "$(date +"%Y-%m-%d %H:%M:%S") - $1" >> "$CONFIG_DIR/partition_manager.log"
}
log_message "Partition manager started"
# -----------------------------------------------
# PARTITION DETECTION FOR A/B SCHEMES
# -----------------------------------------------
# Detect if the device uses A/B partition scheme
detect_ab_partitioning() {
log_message "Detecting A/B partition scheme"
# Check if device has slot_suffix or slot properties
SLOT=$(getprop ro.boot.slot_suffix 2>/dev/null)
if [ -z "$SLOT" ]; then
SLOT=$(getprop ro.boot.slot 2>/dev/null)
fi
if [ -n "$SLOT" ]; then
log_message "A/B partition scheme detected, current slot: $SLOT"
echo "$SLOT" > "$PART_INFO_DIR/current_slot"
echo "true" > "$PART_INFO_DIR/is_ab_device"
return 0
else
log_message "Device does not use A/B partition scheme"
echo "false" > "$PART_INFO_DIR/is_ab_device"
return 1
fi
}
# Get the inactive slot (for A/B devices)
get_inactive_slot() {
if [ -f "$PART_INFO_DIR/is_ab_device" ] && [ "$(cat "$PART_INFO_DIR/is_ab_device")" = "true" ]; then
CURRENT_SLOT=$(cat "$PART_INFO_DIR/current_slot")
if [ "$CURRENT_SLOT" = "_a" ]; then
echo "_b"
else
echo "_a"
fi
else
echo ""
fi
}
# List all available partitions
list_partitions() {
log_message "Listing available partitions"
# Clear previous partition info
rm -f "$PART_INFO_DIR/partitions.list" 2>/dev/null
# Find all block devices
for BLOCK in /dev/block/by-name/*; do
if [ -L "$BLOCK" ]; then
PART_NAME=$(basename "$BLOCK")
PART_PATH=$(readlink -f "$BLOCK")
echo "$PART_NAME:$PART_PATH" >> "$PART_INFO_DIR/partitions.list"
fi
done
# Also check /dev/block/bootdevice/by-name if available
if [ -d "/dev/block/bootdevice/by-name" ]; then
for BLOCK in /dev/block/bootdevice/by-name/*; do
if [ -L "$BLOCK" ]; then
PART_NAME=$(basename "$BLOCK")
PART_PATH=$(readlink -f "$BLOCK")
# Check if already in the list
if ! grep -q "^$PART_NAME:" "$PART_INFO_DIR/partitions.list" 2>/dev/null; then
echo "$PART_NAME:$PART_PATH" >> "$PART_INFO_DIR/partitions.list"
fi
fi
done
fi
# Log total partitions found
PART_COUNT=$(wc -l < "$PART_INFO_DIR/partitions.list" 2>/dev/null || echo "0")
log_message "Found $PART_COUNT partitions"
return 0
}
# Get critical partitions that should be backed up
get_critical_partitions() {
log_message "Identifying critical partitions"
# Clear previous critical partitions list
rm -f "$PART_INFO_DIR/critical_partitions.list" 2>/dev/null
# Define list of critical partition patterns
CRITICAL_PATTERNS="boot system vendor product dtbo vbmeta super"
# Extract partition names from the list
if [ -f "$PART_INFO_DIR/partitions.list" ]; then
while IFS=: read -r PART_NAME PART_PATH; do
for PATTERN in $CRITICAL_PATTERNS; do
if echo "$PART_NAME" | grep -q "^$PATTERN"; then
echo "$PART_NAME:$PART_PATH" >> "$PART_INFO_DIR/critical_partitions.list"
break
fi
done
done < "$PART_INFO_DIR/partitions.list"
fi
# Log critical partitions count
CRITICAL_COUNT=$(wc -l < "$PART_INFO_DIR/critical_partitions.list" 2>/dev/null || echo "0")
log_message "Identified $CRITICAL_COUNT critical partitions"
return 0
}
# -----------------------------------------------
# BLOCK-LEVEL ACCESS ENGINE
# -----------------------------------------------
# Check if a partition is mounted
is_partition_mounted() {
PART_PATH="$1"
if mount | grep -q "$PART_PATH"; then
return 0
else
return 1
fi
}
# Read a block from a partition
read_partition_block() {
PART_PATH="$1"
OFFSET="$2"
SIZE="$3"
OUTPUT="$4"
log_message "Reading block from $PART_PATH at offset $OFFSET, size $SIZE"
if [ ! -e "$PART_PATH" ]; then
log_message "Error: Partition $PART_PATH does not exist"
return 1
fi
# Use dd to read a block from the partition
if ! dd if="$PART_PATH" of="$OUTPUT" bs=1 skip="$OFFSET" count="$SIZE" 2>/dev/null; then
log_message "Error: Failed to read block from $PART_PATH"
return 1
fi
return 0
}
# Write a block to a partition (with safety checks)
write_partition_block() {
PART_PATH="$1"
OFFSET="$2"
INPUT="$3"
log_message "Writing block to $PART_PATH at offset $OFFSET"
if [ ! -e "$PART_PATH" ]; then
log_message "Error: Partition $PART_PATH does not exist"
return 1
fi
# Check if partition is mounted
if is_partition_mounted "$PART_PATH"; then
log_message "Warning: Attempting to write to mounted partition $PART_PATH"
return 1
fi
# Get size of input file
SIZE=$(stat -c %s "$INPUT" 2>/dev/null)
if [ -z "$SIZE" ]; then
log_message "Error: Failed to get size of input file $INPUT"
return 1
fi
# Use dd to write the block to the partition
if ! dd if="$INPUT" of="$PART_PATH" bs=1 seek="$OFFSET" count="$SIZE" 2>/dev/null; then
log_message "Error: Failed to write block to $PART_PATH"
return 1
fi
return 0
}
# Calculate partition checksum
calculate_partition_checksum() {
PART_PATH="$1"
log_message "Calculating checksum for $PART_PATH"
if [ ! -e "$PART_PATH" ]; then
log_message "Error: Partition $PART_PATH does not exist"
return 1
fi
# Use sha256sum if available, fall back to md5sum
if command -v sha256sum >/dev/null 2>&1; then
CHECKSUM=$(dd if="$PART_PATH" bs=1M count=100 2>/dev/null | sha256sum | awk '{print $1}')
else
CHECKSUM=$(dd if="$PART_PATH" bs=1M count=100 2>/dev/null | md5sum | awk '{print $1}')
fi
if [ -n "$CHECKSUM" ]; then
echo "$CHECKSUM"
return 0
else
log_message "Error: Failed to calculate checksum for $PART_PATH"
return 1
fi
}
# -----------------------------------------------
# SNAPSHOT MANAGEMENT
# -----------------------------------------------
# Create a snapshot of a partition
create_partition_snapshot() {
PART_NAME="$1"
SNAPSHOT_NAME="$2"
if [ -z "$SNAPSHOT_NAME" ]; then
SNAPSHOT_NAME="${PART_NAME}_$(date +"%Y%m%d_%H%M%S")"
fi
log_message "Creating snapshot $SNAPSHOT_NAME for partition $PART_NAME"
# Get partition path
PART_PATH=""
if [ -f "$PART_INFO_DIR/partitions.list" ]; then
PART_PATH=$(grep "^$PART_NAME:" "$PART_INFO_DIR/partitions.list" | cut -d: -f2)
fi
if [ -z "$PART_PATH" ]; then
log_message "Error: Partition $PART_NAME not found"
return 1
fi
# Create snapshot directory
SNAPSHOT_PATH="$SNAPSHOT_DIR/$SNAPSHOT_NAME"
mkdir -p "$SNAPSHOT_PATH"
# Get partition size
PART_SIZE=$(blockdev --getsize64 "$PART_PATH" 2>/dev/null)
if [ -z "$PART_SIZE" ]; then
# Fallback method to get size
PART_SIZE=$(wc -c < "$PART_PATH" 2>/dev/null)
fi
if [ -z "$PART_SIZE" ]; then
log_message "Error: Failed to get size of partition $PART_NAME"
return 1
fi
# Create snapshot metadata
cat > "$SNAPSHOT_PATH/metadata.txt" << EOF
Partition: $PART_NAME
Path: $PART_PATH
Size: $PART_SIZE
Created: $(date)
Checksum: $(calculate_partition_checksum "$PART_PATH")
EOF
# For small partitions, create a full backup
if [ "$PART_SIZE" -lt 104857600 ]; then # 100MB
log_message "Creating full backup of small partition $PART_NAME ($PART_SIZE bytes)"
if ! dd if="$PART_PATH" of="$SNAPSHOT_PATH/full_backup.img" bs=1M 2>/dev/null; then
log_message "Error: Failed to create full backup of $PART_NAME"
return 1
fi
else
# For larger partitions, save only the first 10MB and important metadata
log_message "Creating partial backup of large partition $PART_NAME ($PART_SIZE bytes)"
if ! dd if="$PART_PATH" of="$SNAPSHOT_PATH/header.img" bs=1M count=10 2>/dev/null; then
log_message "Error: Failed to create header backup of $PART_NAME"
return 1
fi
# Save partition table if this is a super partition
if echo "$PART_NAME" | grep -q "^super"; then
log_message "Saving partition table for super partition"
if command -v lpdump >/dev/null 2>&1; then
lpdump "$PART_PATH" > "$SNAPSHOT_PATH/lpdump.txt" 2>/dev/null
fi
fi
fi
log_message "Successfully created snapshot $SNAPSHOT_NAME for partition $PART_NAME"
return 0
}
# List all available snapshots
list_snapshots() {
log_message "Listing available snapshots"
if [ ! -d "$SNAPSHOT_DIR" ]; then
log_message "No snapshots directory found"
return 1
fi
for SNAPSHOT in "$SNAPSHOT_DIR"/*; do
if [ -d "$SNAPSHOT" ]; then
SNAPSHOT_NAME=$(basename "$SNAPSHOT")
echo "$SNAPSHOT_NAME"
fi
done
return 0
}
# Restore from a snapshot
restore_from_snapshot() {
SNAPSHOT_NAME="$1"
SNAPSHOT_PATH="$SNAPSHOT_DIR/$SNAPSHOT_NAME"
log_message "Restoring from snapshot $SNAPSHOT_NAME"
if [ ! -d "$SNAPSHOT_PATH" ]; then
log_message "Error: Snapshot $SNAPSHOT_NAME not found"
return 1
fi
# Check if metadata exists
if [ ! -f "$SNAPSHOT_PATH/metadata.txt" ]; then
log_message "Error: Snapshot metadata not found"
return 1
fi
# Extract partition info from metadata
PART_NAME=$(grep "^Partition:" "$SNAPSHOT_PATH/metadata.txt" | cut -d: -f2- | tr -d ' ')
PART_PATH=$(grep "^Path:" "$SNAPSHOT_PATH/metadata.txt" | cut -d: -f2- | tr -d ' ')
if [ -z "$PART_NAME" ] || [ -z "$PART_PATH" ]; then
log_message "Error: Invalid snapshot metadata"
return 1
fi
# Check if partition exists
if [ ! -e "$PART_PATH" ]; then
log_message "Error: Partition $PART_PATH does not exist"
return 1
fi
# Check if full backup exists
if [ -f "$SNAPSHOT_PATH/full_backup.img" ]; then
log_message "Restoring full backup to $PART_NAME"
# Check if partition is mounted
if is_partition_mounted "$PART_PATH"; then
log_message "Error: Cannot restore to mounted partition $PART_NAME"
return 1
fi
# Restore the full backup
if ! dd if="$SNAPSHOT_PATH/full_backup.img" of="$PART_PATH" bs=1M 2>/dev/null; then
log_message "Error: Failed to restore full backup to $PART_NAME"
return 1
fi
else
log_message "Error: Full backup not available for restoration"
return 1
fi
log_message "Successfully restored snapshot $SNAPSHOT_NAME to partition $PART_NAME"
return 0
}
# Initialize partition manager
init_partition_manager() {
log_message "Initializing partition manager"
# Create directories if they don't exist
mkdir -p "$PART_INFO_DIR"
mkdir -p "$SNAPSHOT_DIR"
# Detect A/B partitioning
detect_ab_partitioning
# List all partitions
list_partitions
# Identify critical partitions
get_critical_partitions
log_message "Partition manager initialized"
return 0
}
# Create snapshots of all critical partitions
backup_critical_partitions() {
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
BACKUP_ID="critical_$TIMESTAMP"
log_message "Backing up all critical partitions with ID: $BACKUP_ID"
# Create backup directory
BACKUP_PATH="$SNAPSHOT_DIR/$BACKUP_ID"
mkdir -p "$BACKUP_PATH"
# Check if critical partitions list exists
if [ ! -f "$PART_INFO_DIR/critical_partitions.list" ]; then
log_message "Critical partitions list not found, generating..."
get_critical_partitions
fi
# Count of successful backups
SUCCESS_COUNT=0
# Backup each critical partition
while IFS=: read -r PART_NAME PART_PATH; do
log_message "Backing up critical partition: $PART_NAME"
SNAPSHOT_NAME="${BACKUP_ID}_${PART_NAME}"
if create_partition_snapshot "$PART_NAME" "$SNAPSHOT_NAME"; then
SUCCESS_COUNT=$((SUCCESS_COUNT + 1))
fi
done < "$PART_INFO_DIR/critical_partitions.list"
# Create backup summary
cat > "$BACKUP_PATH/summary.txt" << EOF
Backup ID: $BACKUP_ID
Created: $(date)
Successful Backups: $SUCCESS_COUNT
A/B Device: $(cat "$PART_INFO_DIR/is_ab_device")
Current Slot: $(cat "$PART_INFO_DIR/current_slot" 2>/dev/null)
EOF
log_message "Critical partitions backup completed with $SUCCESS_COUNT successful backups"
if [ "$SUCCESS_COUNT" -gt 0 ]; then
echo "$BACKUP_ID"
return 0
else
return 1
fi
}
# Main function - Command processor
main() {
COMMAND="$1"
PARAM1="$2"
PARAM2="$3"
case "$COMMAND" in
"init")
init_partition_manager
;;
"detect_ab")
detect_ab_partitioning
;;
"list_partitions")
list_partitions
;;
"get_critical")
get_critical_partitions
;;
"snapshot")
create_partition_snapshot "$PARAM1" "$PARAM2"
;;
"list_snapshots")
list_snapshots
;;
"restore")
restore_from_snapshot "$PARAM1"
;;
"backup_critical")
backup_critical_partitions
;;
*)
log_message "Unknown command: $COMMAND"
echo "Usage: $0 init|detect_ab|list_partitions|get_critical|snapshot|list_snapshots|restore|backup_critical [parameters]"
return 1
;;
esac
}
# Execute main with all arguments
main "$@"