Skip to content

Instantly share code, notes, and snippets.

@doshiraki
Last active September 27, 2025 06:15
Show Gist options
  • Save doshiraki/3d3a89993d25b3a9122e1eca72841a53 to your computer and use it in GitHub Desktop.
Save doshiraki/3d3a89993d25b3a9122e1eca72841a53 to your computer and use it in GitHub Desktop.
bash for remount when adb remount fails because permission or read-only.
#!/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
@doshiraki
Copy link
Author

doshiraki commented Apr 19, 2025

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:

$ ../platform-tools/adb remount
AVB verification is disabled, disabling verity state may have no effect
failed to remount partition dev:/dev/block/dm-0 mnt:/: Permission denied
Remounted /system_dlkm as RW
failed to remount partition dev:/dev/block/dm-2 mnt:/system_ext: Permission denied
failed to remount partition dev:/dev/block/dm-3 mnt:/product: Permission denied
Remounted /vendor as RW
Remounted /vendor_dlkm as RW
Remount failed

Case 2. Error Message:

$ adb root
$ adb shell
# mount -o rw,remount /
'/dev/block/dm-0' is read-only

This Shell output:

$ adb root
$ adb shell
# bash toggle_mount.sh --all
shell-init: error retrieving current directory: getcwd: cannot access parent directories: Math result not representable
chdir: error retrieving current directory: getcwd: cannot access parent directories: Math result not representable
Converting all system, product, vendor devices to read-write
>>>Processing product_a (mode: rw)
  Check: Table: Targets in the device-mapper table for product_a: 0-4120600: linear, 259:14 3425776 
  Running: dmctl replace product_a linear 0 4120600 259:14 3425776
/dev/block/dm-3 on /product type ext4 (ro,seclabel,noatime) -> dmctl replace product_a linear 0 4120600 259:14 3425776
  Success: product_a is now rw
>>>Processing system_a (mode: rw)
  Check: Table: Targets in the device-mapper table for system_a: 0-2410336: linear, 259:14 2048 
  Running: dmctl replace system_a linear 0 2410336 259:14 2048
/dev/block/dm-0 on / type ext4 (ro,seclabel,noatime) -> dmctl replace system_a linear 0 2410336 259:14 2048
  Success: system_a is now rw
>>>Processing system_dlkm_a (mode: rw)
  Check: Table: Targets in the device-mapper table for system_dlkm_a: 0-23032: linear, 259:14 2412384 
  Running: dmctl replace system_dlkm_a linear 0 23032 259:14 2412384
/dev/block/dm-1 on /system_dlkm type ext4 (ro,seclabel,noatime) -> dmctl replace system_dlkm_a linear 0 23032 259:14 2412384
  Success: system_dlkm_a is now rw
>>>Processing system_ext_a (mode: rw)
  Check: Table: Targets in the device-mapper table for system_ext_a: 0-990360: linear, 259:14 2435416 
  Running: dmctl replace system_ext_a linear 0 990360 259:14 2435416
/dev/block/dm-2 on /system_ext type ext4 (ro,seclabel,noatime) -> dmctl replace system_ext_a linear 0 990360 259:14 2435416
  Success: system_ext_a is now rw
>>>Processing vendor_a (mode: rw)
  Check: Table: Targets in the device-mapper table for vendor_a: 0-1513152: linear, 259:14 7546376 
  Running: dmctl replace vendor_a linear 0 1513152 259:14 7546376
/dev/block/dm-4 on /vendor type ext4 (ro,seclabel,noatime) -> dmctl replace vendor_a linear 0 1513152 259:14 7546376
  Success: vendor_a is now rw
>>>Processing vendor_dlkm_a (mode: rw)
  Check: Table: Targets in the device-mapper table for vendor_dlkm_a: 0-121984: linear, 259:14 9059528 
  Running: dmctl replace vendor_dlkm_a linear 0 121984 259:14 9059528
/dev/block/dm-5 on /vendor_dlkm type ext4 (ro,seclabel,noatime) -> dmctl replace vendor_dlkm_a linear 0 121984 259:14 9059528
  Success: vendor_dlkm_a is now rw
Summary: 6 devices succeeded, 0 devices failed

@doshiraki
Copy link
Author

doshiraki commented Apr 22, 2025

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment