-
-
Save doshiraki/3d3a89993d25b3a9122e1eca72841a53 to your computer and use it in GitHub Desktop.
| #!/system_ext/bin/bash | |
| pwd=$(dirname $0) | |
| cd ${pwd} 2> /dev/null | |
| # toggle_mount.sh: Toggles specified Device Mapper device(s) between read-only (ro) and read-write (rw) on Pixel 7a (LineageOS) | |
| # Usage: | |
| # Single device: toggle_mount <device> <ro|rw> (e.g., toggle_mount system_a rw) | |
| # All system/product/vendor: toggle_mount --all rw | |
| # Features: Compares dmctl table before/after; avoids lsblk; converts multiple devices to rw | |
| # Disclaimer: Feel free to use it at your own discretion and responsibility. | |
| # Move to a safe directory | |
| cd /storage/self/primary || { echo "Error: Cannot access /storage/self/primary"; exit 1; } | |
| # Function to toggle mount mode for a single device | |
| toggle_mount() { | |
| local device="$1" | |
| local mode="$2" | |
| # Validate inputs | |
| [[ -z "$device" ]] && { echo "Error: Device name required"; return 1; } | |
| [[ "$mode" != "ro" && "$mode" != "rw" ]] && { echo "Error: Mode must be 'ro' or 'rw'"; return 1; } | |
| echo "Processing $device (mode: $mode)" | |
| # Get initial table and access info | |
| table_before=$(dmctl table "$device" | tr '\n' ' ') | |
| [[ -z "$table_before" ]] && { echo "Error: No table for $device"; return 1; } | |
| echo " Check: Table: $table_before" | |
| # Check for multiple targets | |
| if [[ $(echo "$table_before" | grep -c "linear") -gt 1 ]]; then | |
| echo " Error: Multiple targets detected for $device" | |
| echo " Table: $table_before" | |
| return 1 | |
| fi | |
| # Parse table | |
| pattern="^.* for ${device}: ([0-9]+)-([0-9]+): (linear), ([0-9]+:[0-9]+) ([0-9]+)" | |
| if echo "$table_before" | grep -qE "${pattern}"; then | |
| table_info=$(echo "$table_before" | sed -E "s/${pattern}/\1 \2 \3 \4 \5/") | |
| read start end target maj_min offset <<< "$table_info" | |
| sectors=$((end - start)) | |
| # Verify sector count | |
| if [[ $sectors -le 0 ]]; then | |
| echo " Error: Invalid sector count ($sectors) for $device" | |
| return 1 | |
| fi | |
| # Use maj:min directly | |
| block_device="$maj_min" | |
| # Build dmctl replace command | |
| cmd="dmctl replace $device" | |
| [[ "$mode" == "ro" ]] && cmd="$cmd -ro" | |
| cmd="$cmd $target $start $sectors $block_device $offset" | |
| echo " Running: $cmd" | |
| # Get mount path | |
| mount_path=$(dmctl getpath "$device") | |
| [[ -z "$mount_path" ]] && { echo "Error: No mount path for $device"; return 1; } | |
| # Show mount status | |
| mount | grep -e "$mount_path " | sed -e "s/$/ -> $cmd/" | |
| # Replace table | |
| $cmd || { echo " Error: Failed to replace table for $device"; return 1; } | |
| # Remount | |
| mount -o "$mode,remount" "$mount_path" || { echo "Error: Failed to remount $mount_path"; return 1; } | |
| # Get table and access info after | |
| table_after=$(dmctl table "$device" | tr '\n' ' ') | |
| # echo " After: Table: $table_after" | |
| # Compare table and access | |
| if [[ "$table_before" == "$table_after" ]]; then | |
| echo " Success: $device is now $mode" | |
| else | |
| echo " Warning: Table changed unexpectedly" | |
| echo " Before: $table_before" | |
| echo " After: $table_after" | |
| return 1 | |
| fi | |
| else | |
| echo "Error: Invalid table format for $device: $table_before" | |
| return 1 | |
| fi | |
| } | |
| # Function to convert all system/product/vendor devices to rw | |
| convert_all_to_rw() { | |
| echo "Converting all system, product, vendor devices to read-write" | |
| local devices | |
| devices=$(dmctl list devices | grep -E 'system|product|vendor' | awk '{print $1}' | grep -v "<empty>") | |
| if [[ -z "$devices" ]]; then | |
| echo "Error: No system, product, or vendor devices found" | |
| return 1 | |
| fi | |
| local success_count=0 | |
| local fail_count=0 | |
| for device in $devices; do | |
| echo -n ">>>" | |
| toggle_mount "$device" "rw" | |
| if [[ $? -eq 0 ]]; then | |
| ((success_count++)) | |
| else | |
| ((fail_count++)) | |
| fi | |
| done | |
| echo "Summary: $success_count devices succeeded, $fail_count devices failed" | |
| [[ $fail_count -eq 0 ]] | |
| } | |
| # Check arguments | |
| if [[ $# -eq 1 && "$1" == "--all" ]]; then | |
| convert_all_to_rw | |
| elif [[ $# -eq 2 ]]; then | |
| toggle_mount "$1" "$2" | |
| else | |
| echo "Usage:" | |
| echo " Single device: $0 <device> <ro|rw> (e.g., $0 system_a rw)" | |
| echo " All devices: $0 --all" | |
| exit 1 | |
| fi |
https://android.stackexchange.com/a/260267/588577
The error "/dev/block/dm-0 is read-only" when attempting to remount /system as read-write (RW) on Android O (e.g., with mount -o rw,remount /system) occurs because the partition is managed by Device Mapper (dmctl) and locked as read-only due to dm-verity and A/B partitioning. This is common on devices like the Pixel 7a running LineageOS.
Cause
Android uses Device Mapper to create virtual block devices (e.g., /dev/block/dm-0 for /system).
dm-verity enforces read-only mounts to ensure system integrity.
Commands like adb remount or mount -o rw,remount fail because the Device Mapper table is set to read-only.
Solution
Use dmctl replace to Enable Read-Write
The dmctl tool can modify the Device Mapper table to allow RW access. Below are steps to remount /system (or other partitions like product, vendor) as RW.
Prerequisites
- Root access: Device must be rooted (adb root must work).
- Device: Tested on Pixel 7a with LineageOS; may work on other Android O+ devices.
Caution: Modifying system partitions is risky. Use at your own risk. Changes revert on reboot.
Steps for a Single Partition
Enter a root shell:
adb root
adb shell
Check the Device Mapper table for the partition (e.g., system_a for /):
dmctl table system_a
Example output:
Targets in the device-mapper table for system_a:
0-2410336: linear, 259:14 2048
This shows the start sector (0), size (2410336), target type (linear), block device (259:14), and offset (2048).
Run dmctl replace to redefine the partition without the read-only flag:
dmctl replace system_a linear 0 2410336 259:14 2048
Remount the partition as RW:
mount -o rw,remount /
Verify the mount is RW:
mount | grep "/ "
Expected output includes rw:
/dev/block/dm-0 on / type ext4 (rw,seclabel,noatime)
Automating for Multiple Partitions
To remount all system-related partitions (system, product, vendor, etc.), use this Bash script:
#!/system_ext/bin/bash
# toggle_mount.sh: Remounts Device Mapper partitions as read-write
# Usage: bash toggle_mount.sh --all
....
convert_all_to_rw() {
echo "Converting system, product, vendor devices to read-write"
devices=$(dmctl list devices | grep -E 'system|product|vendor' | awk '{print $1}')
[[ -z "$devices" ]] && { echo "Error: No devices found"; exit 1; }
....
for device in $devices; do
echo "Processing $device"
table=$(dmctl table "$device" | tr '\n' ' ')
if echo "$table" | grep -qE "([0-9]+)-([0-9]+): (linear), ([0-9]+:[0-9]+) ([0-9]+)"; then
table_info=$(echo "$table" | sed -E "s/.*: ([0-9]+)-([0-9]+): (linear), ([0-9]+:[0-9]+) ([0-9]+)/\1 \2 \3 \4 \5/")
read start end target maj_min offset <<< "$table_info"
sectors=$((end - start))
cmd="dmctl replace $device $target $start $sectors $maj_min $offset"
echo "Running: $cmd"
$cmd && mount -o rw,remount "$(dmctl getpath "$device")" && echo "Success: $device is now rw"
else
echo "Error: Invalid table for $device"
fi
done
}
....
[[ "$1" == "--all" ]] && convert_all_to_rw || echo "Usage: $0 --all"
Usage
Push the script to the device:
adb push toggle_mount.sh /data/local/tmp/
Run it:
adb shell
cd /data/local/tmp
chmod +x toggle_mount.sh
bash toggle_mount.sh --all
This processes all system, product, and vendor partitions, making them RW.
Notes
- Reboot resets changes: Partitions revert to read-only after a reboot.
- Error handling: If dmctl replace fails, verify the table format or root privileges.
On a Pixel 7a with LineageOS, adb remount fails with "failed to remount partition dev:/dev/block/dm-0 mnt:/: Permission denied" or mount fails with "/dev/block/dm-0 is read-only" because Device Mapper (dmctl) locks partitions as read-only due to dm-verity and A/B partitioning. The toggle_mount.sh script fixes this by using dmctl to toggle partitions (e.g., system, product, vendor) to read-write, verifying changes with dmctl table comparisons. Use at your own discretion with root (e.g., Magisk) and a backup (TWRP).
Case 1. Error Message:
Case 2. Error Message:
This Shell output: