Skip to content

Instantly share code, notes, and snippets.

@coderjo
Last active March 1, 2025 16:56
Show Gist options
  • Save coderjo/c8de3ecc31f1d6450254b5e97ea2c595 to your computer and use it in GitHub Desktop.
Save coderjo/c8de3ecc31f1d6450254b5e97ea2c595 to your computer and use it in GitHub Desktop.
access a disk image read-only with a copy-on-write overlay to allow fsck or the like to write changes
#!/bin/bash
# usage: attach_cow_image.sh [imagefile] [cowfile] [devname]
# imagefile: path to the image file you want to load
# cowfile: path to the file to store writes into. If it doesn't exist, a sparse 1GB file will be created.
# devname: the name you want the drive to show up as in /dev/mapper
imgfile="$1"
cowfile="$2"
dmname="$3"
# create COW file if it doesn't exist (otherwise, assume we are using a file from a previous use)
# the if you don't think 1000MiB will be enough, you can create a larger one.
[ -f "$cowfile" ] || dd if=/dev/zero of="$cowfile" bs=1 count=1 seek=1048576000
# attach the files to loop devices (with the image file read-only)
imgdev=`losetup -f -r --show "$imgfile"`
cowdev=`losetup -f --show "$cowfile"`
# get the size of the image device
imgsz=`blockdev --getsz $imgdev`
# create the devmapper table for a copy-on-write device using the two loop devices
# p means persist the snapshot data
# The 4 is the number of sectors to use per COW image chunk
echo 0 $imgsz snapshot $imgdev $cowdev p 4| dmsetup create $dmname
# and now probe the partition table of the new device
partprobe -s /dev/mapper/$dmname
# to detatch everything:
# dmsetup remove $dmname
# losetup -d $cowdev
# losetup -d $imgdev
@jaharkes
Copy link

There are two typos, but this worked perfectly to give me early access while I was recovering a failing drive with ddrescue. I was able to pass the block device of the drive I'm recovering to as 'imgfile', and this made sure I didn't accidentally clobber anything. Thank you!

cowdev=`losetup ...
imgsz=`blockdev --getsz $imgdev`

@coderjo
Copy link
Author

coderjo commented Oct 16, 2020

Oops. I don't know how I missed that. I might have been typing this up from memory and didn't actually run this version until later. I'm glad it was helpful.

@coderjo
Copy link
Author

coderjo commented Oct 16, 2020

Since you are recovering a drive, I have another script that can take the log file output by GNU's ddrescue and output a dm table which forces errors where the original drive had read errors, if you have need for that behavior.

@coderjo
Copy link
Author

coderjo commented Nov 14, 2020

If you're working on recovering a drive, you should probably also make the block device read only so you can't accidentally make changes with any tools.

For example:
# blockdev --setro /dev/sda

@JuniorJPDJ
Copy link

Wow, thanks!
Is there a way to resize new device to larger without changing backing RO one?

@coderjo
Copy link
Author

coderjo commented Jul 3, 2021

You could make a devmapper device with the linear target. You could probably actually append the linear target to this table after the snapshot target. Check out the manpage for dmsetup.

@JuniorJPDJ
Copy link

I did it like that:

# echo "0 $imgsz linear $imgdev 0"$'\n'"$imgsz 204800 zero" | dmsetup create big_xp
# echo "0 $((imgsz+204800)) snapshot /dev/mapper/big_xp $cowdev p 8" | dmsetup create big_xp_rw

man didn't help even a bit, but this gentoo wiki was helpful as hell: https://wiki.gentoo.org/wiki/Device-mapper#Create

@redwil
Copy link

redwil commented Oct 29, 2024

To make sure the loop device is RO can add although not necessary since dm should never write on the $imgdev when 'snapshot target:
blockdev --setro $imgdev

@MichZia
Copy link

MichZia commented Dec 29, 2024

This script is excellent for recovery purposes, but I believe we can enhance it by integrating dmsetup cache. This approach could further protect the disk from additional harm during the recovery process.

When dealing with a failing disk, every read operation risks worsening the damage. While tools like ddrescue are commonly used to recover data from failing disks, they often operate indiscriminately, reading the entire disk. This can be wasteful if the underlying filesystem contains much unused space.

For example, consider a failing disk /dev/sdx with a total size of 1000GB. It has a single partition spanning the entire disk, but only 200GB of actual data, scattered across the partition due to fragmentation from long-term use. Using ddrescue would require reading the full 1000GB, even though 800GB of it is unnecessary. My proposal aims to avoid this inefficiency and reduce stress on the failing disk.

Proposed Steps:

  • Set the Disk to Read-Only Mode:

Prevent any accidental writes to the disk:

blockdev --setro /dev/sdx

  • Set Up a Cache with dmsetup:

Create a sparse cache file of 1000GB to store read data. Using dmsetup cache allows us to cache all read blocks. This avoids invalidating previously read blocks and eliminate redundant reads.

  • Use dmsetup snapshot for Further Operations:

On top of the cache, create a dmsetup snapshot device. This enables safe file system checks (fsck) and mounting or extraction of files from the filesystem.

  • Handle I/O Errors Gracefully:

Ideally, dmsetup could be configured to mask I/O errors by returning null blocks or other placeholders, avoiding unnecessary retries that could stress the disk further.

Advantages:

  • Efficiency: Avoid reading unused parts of the disk.
  • Reduced Stress: Minimize the number of reads on a failing disk.
  • Flexibility: Allows filesystem-level recovery operations (e.g., fsck or file extraction tools) without risking further damage.

This approach would combine the strengths of ddrescue with the precision of filesystem-aware tools, offering a safer and more efficient way to recover data from damaged disks.

@coderjo
Copy link
Author

coderjo commented Dec 29, 2024

BTW, I haven't checked in quite a while, but last I checked, the block device read-only flag ("blockdev --setro") is not actually completely enforced in a sound way. (ref: https://github.com/msuhanov/Linux-write-blocker/blob/master/README.md )

@JuniorJPDJ
Copy link

JuniorJPDJ commented Dec 30, 2024

You can use ddrescue with partclone domain map to only read used data.
Never run any recovery on failed disk, always work on bit-perfect copy.

If you don't want to recover any deleted files and just failing medium is your concern - you can use partclone Create ddrescue domain log from source device option to output the map containing only used space and then use ddrescue with map to copy the disk's used space. Then use this recovered copy with snapshot and play with it. Snapshot allows you to not lose source file which may be problematic to copy again due to medium failing.

@thomas-riccardi
Copy link

I had disastrous performances on a 4TB hdd hosting origin img and cow files: ~3MiB/s:

# sync ; time sh -c "dd if=/dev/zero of=tmp-$(date +%s) bs=1M count=100 ; sync"
100+0 records in
100+0 records out
104857600 bytes (105 MB, 100 MiB) copied, 0,114658 s, 915 MB/s

real    0m28,193s
user    0m0,003s
sys     0m0,119s

A quick research on chunk size showed:

I tried with 128KiB chunk size (256 sectors) and it mostly fixed my performance issue:

# sync ; time sh -c "dd if=/dev/zero of=tmp-$(date +%s) bs=1M count=1000 ; sync"
1000+0 records in
1000+0 records out
1048576000 bytes (1,0 GB, 1000 MiB) copied, 1,28779 s, 814 MB/s

real    0m9,339s
user    0m0,003s
sys     0m1,268s

=> ~107MiB/s

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