--- /dev/null
+#!/bin/sh
+
+###########################################################################
+
+# Utilities
+
+deviceid()
+{
+ ls -lL "$1" 2>/dev/null | awk -F '[ ,]+' '{print "(" $5 ", " $6 ")"}'
+}
+
+dmcryptmap()
+{
+ devnums="`deviceid $1`"
+
+ dmsetup ls --target crypt | awk '{print $1}' | while read dmdev; do
+ if dmsetup deps $dmdev | grep -qF "$devnums"; then
+ echo "/dev/mapper/$dmdev"
+ fi
+ done
+}
+
+###########################################################################
+
+# Map device/sector through blockdevice mappings (e.g. lvm)
+
+map()
+{
+ while true; do
+ if detect_raid; then
+ echo "# $device: Raid detected" 1>&2
+ map_raid
+ elif detect_partition; then
+ echo "# $device: partition table detected" 1>&2
+ map_partition
+ elif detect_crypt; then
+ echo "# $device: LUKS/cryptsetup detected" 1>&2
+ map_crypt
+ elif detect_lvm; then
+ echo "# $device: LVM detected" 1>&2
+ map_lvm
+ else
+ break
+ fi
+ done
+}
+
+########################################
+#### Partitions
+
+detect_partition()
+{
+ [ -z "`fdisk -l $device 2>&1 >/dev/null`" ]
+}
+
+map_partition()
+{
+ # Step 1: Find partition to which this sector belongs
+
+ local partdev
+ local partstart
+ local partsector
+
+ partdev="`fdisk -ul $device | awk -v sector="$sector" -F '[ *]+' '/^\// && !/Extended$/ && $2<=sector && $3>=sector {print $1,$2}'`" #`"
+
+ if [ -z "$partdev" ]; then
+ echo "# sector $sector is not part of any partition on $device" 1>&2
+ exit 0
+ fi
+
+ partstart="${partdev#* }"
+ partdev="${partdev% *}"
+ partsector="`dc -e "$sector $partstart - p"`" # `"
+
+ echo "offset $partstart"
+ echo "device $partdev partition"
+ echo "sector $partsector"
+
+ device="$partdev"
+ sector="$partsector"
+}
+
+########################################
+#### LUKS / cryptsetup
+
+detect_crypt()
+{
+ which cryptsetup >/dev/null 2>&1 && [ -n "`dmcryptmap $device`" ]
+}
+
+map_crypt()
+{
+ # Step 2: Find the crypted volume defined for this partition
+
+ local offset
+ local devnums
+ local cryptdev
+ local cryptsector
+ local type
+
+ offset="`cryptsetup luksDump $device 2>/dev/null | awk '/^Payload offset/{print $3}'`"
+
+ if [ -z "$offset" ]; then
+ # Plain dmcrypt
+ offset=0
+ type=dmcrypt
+ else
+ type=luks
+ fi
+
+ cryptdev="`dmcryptmap $device`"
+
+ if [ -z "$cryptdev" ]; then
+ echo "! Failed to find decrypted mapper device for $device"
+ exit 1;
+ fi
+
+ cryptsector="`dc -e "$sector $offset - p"`" #`"
+
+ echo "offset $offset"
+ echo "device $cryptdev crypt"
+ echo "type $type"
+ echo "sector $cryptsector"
+
+ device="$cryptdev"
+ sector="$cryptsector"
+}
+
+########################################
+#### LVM
+
+detect_lvm()
+{
+ which pvdisplay >/dev/null 2>&1 && pvdisplay $device >/dev/null 2>&1
+}
+
+map_lvm()
+{
+ local pvname
+ local pesize
+ local vgname
+ local pestart
+ local penum
+ local subsector
+ local lestart
+ local firstpe
+ local lenum
+ local fsdev
+ local fssector
+
+ # Step 3: Get pysical extent number
+
+ pvname="`pvdisplay -c $device 2>/dev/null | awk -F: '{print $1,$2,$8}' | sed -e 's/^ *//'`"
+
+ if [ -z "$pvname" ]; then
+ echo "! $device is not a physical volume" 1>&2
+ exit 1
+ fi
+
+ pesize="${pvname##* }"
+ vgname="${pvname% *}"
+ pvname="${vgname% *}"
+ vgname="${vgname#* }"
+ pesize="`dc -e "$pesize 2 * p"`" #`"
+ pestart="`pvs --unit s -ope_start $device 2>/dev/null | sed -n -e 's/ *//g' -e 's/S.*$//' -e '$p'`"
+ penum="`dc -e "$sector $pestart - $pesize ~ n [ ] n p"`" #`"
+ subsector="${penum% *}"
+ penum="${penum#* }"
+
+ echo "device $pvname pv"
+ echo "offset $pestart"
+ echo "pesize $pesize"
+ echo "extent $penum"
+ echo "subsector $subsector"
+ echo "group $vgname"
+
+ # Step 4: Find associated logical volume
+
+ lestart="$(vgdisplay -v $vgname 2>/dev/null | awk '/LV Name/{print $3}' | while read lvname; do \
+ lvdisplay -m $lvname 2>/dev/null \
+ | awk -v RS="\n *\n( --- Segments ---\n)?" \
+ -F"[ \t\n:]+" \
+ -v pvname="$pvname" \
+ -v penum="$penum" \
+ -v lvname="$lvname" \
+ '$0 ~ "Physical volume[ \t]+" pvname && $14<=penum && $16>=penum{print lvname,$4,$14}'; \
+ done)"
+
+ if [ -z "$lestart" ]; then
+ echo "# pysical extent $penum of $pvname is not mapped in any logical volume" 1>&2
+ exit 0
+ fi
+
+ lvname="${lestart%% *}"
+ lestart="${lestart#* }"
+ firstpe="${lestart#* }"
+ lestart="${lestart% *}"
+ lenum="`dc -e "$penum $firstpe - $lestart + p"`" #`"
+
+ echo "device $lvname lv"
+ echo "extent $lenum"
+
+ fsdev="$lvname"
+ fssector="`dc -e "$lenum $pesize * $subsector + p"`" #`"
+
+ echo "sector $fssector"
+
+ device="$fsdev"
+ sector="$fssector"
+}
+
+########################################
+#### Raid-1
+
+detect_raid()
+{
+ which mdadm >/dev/null 2>&1 && mdadm -Q $device | grep -qF -- "--examine"
+}
+
+map_raid()
+{
+ local mddevice
+ local mdlevel
+
+ mddevice="`mdadm -Q $device | sed -ne 's/.*\(raid[0-9] \/dev\/[^.]*\).*/\1/' -eT -ep`"
+
+ if [ -z "$mddevice" ]; then
+ echo "! raid master device for raid componentn device $device not found" 1>&2
+ exit 1
+ fi
+
+ mdlevel="${mddevice% *}"
+ mddevice="${mddevice#* }"
+
+ echo "device $mddevice md"
+ echo "raidlevel $mdlevel"
+ echo "sector $sector"
+
+ device="$mddevice"
+}
+
+###########################################################################
+
+# Scan the final blockdevice for additional information.
+# Information includes mountpoint, filesystem type and filesystem type
+# specific information: filesysetm block, inode number and filename
+
+scan()
+{
+ scan_mountpoint
+ if detect_ext2fs; then
+ echo "# $device: ext2/3/4 filesystem detected" 1>&2
+ scan_ext2fs
+ elif detect_reiserfs; then
+ echo "# $device: reiserfs filesystem detected" 1>&2
+ scan_reiserfs
+ fi
+}
+
+########################################
+#### Mountpoint
+
+scan_mountpoint()
+{
+ local devnums
+ devnums="`deviceid $device`"
+
+ # Step 5: Find filesystem mount point
+
+ while read dev dir opts; do
+ case "$dev" in
+ *:*) ;;
+ *)
+ if [ "$devnums" == "`deviceid $dev`" ]; then
+ echo "mountpoint $dir"
+ break
+ fi
+ ;;
+ esac
+ done < /proc/mounts
+}
+
+########################################
+#### ext2/ext3
+
+detect_ext2fs()
+{
+ which tune2fs >/dev/null 2>&1 && tune2fs -l $device >/dev/null 2>&1
+}
+
+scan_ext2fs()
+{
+ local fsblocksize
+ local fsblock
+ local fssubsector
+ local fstype
+ local inode
+
+ fstype="`file -s $device | sed -e 's/.*\(ext[0-9]\).*/\1/'`"
+ echo "fstype $fstype"
+
+ # Step 6: Get filesystem blocksize and convert sector number to filesystem block number
+
+ fsblocksize="`tune2fs -l $device | awk '/Block size/{print $3/512}'`"
+
+ if [ -z "$fsblocksize" ]; then
+ echo "! $device is not ext2/ext3" 1>&2
+ exit 1
+ fi
+
+ fsblock="`dc -e "$sector $fsblocksize ~ n [ ] n p"`" #`"
+ fssubsector="${fsblock% *}"
+ fsblock="${fsblock#* }"
+
+ echo "blocksize $fsblocksize"
+ echo "block $fsblock"
+ echo "subsector $fssubsector"
+
+ # Step 7: Check, whether block is in use
+
+ if echo "testb $fsblock" | debugfs $device 2>/dev/null | grep -qF "not in use"; then
+ echo "blockstate free"
+ exit 0
+ fi
+ echo "blockstate used"
+
+ # Step 8: Find inode, to which the block belongs
+
+ inode="`echo "icheck $fsblock" | debugfs $device 2>/dev/null | awk 'FNR>1{print $2}'`" #`"
+
+ if [ -z "$inode" ]; then
+ echo "blocktype meta?"
+ exit 0
+ fi
+
+ echo "inode $inode"
+
+ # Step 9: Find file name(s) referencing the inode
+
+ (
+ namefound="$(\
+ echo "ncheck $inode" \
+ | debugfs $device 2>/dev/null \
+ | sed -e '1d' -e 's/^[0-9]*[ ]*//' -e 's/^\/\//\//' \
+ | while read name; do \
+ if [ -z "$firstname" ]; then \
+ echo "blocktype data" 1>&3; \
+ echo "1"; \
+ firstname=1; \
+ fi; \
+ echo "name $name" 1>&3; \
+ done \
+ )"
+ if [ -z "$namefound" ]; then
+ echo "blocktype journal?"
+ fi
+ ) 3>&1
+}
+
+########################################
+#### Reiser-FS
+
+detect_reiserfs()
+{
+ which debugreiserfs >/dev/null 2>&1 && debugreiserfs $device >/dev/null 2>&1
+}
+
+scan_reiserfs()
+{
+ local blocksize
+ local block
+ local subsector
+
+ echo "fstype reiserfs"
+
+ # Step 6: Get filesystem blocksize and convert sector number to filesystem block number
+
+ blocksize="`debugreiserfs $device 2>/dev/null | awk '/^Blocksize:/{print $2/512}'`"
+
+ if [ -z "$blocksize" ]; then
+ echo "! $device is not reiserfs" 1>&2
+ exit 1
+ fi
+
+ block="`dc -e "$sector $blocksize ~ n [ ] n p"`" #`"
+ subsector="${block% *}"
+ block="${block#* }"
+
+ echo "blocksize $blocksize"
+ echo "block $block"
+ echo "subsector $subsector"
+
+ # Step 7: Check, whether block is in use
+
+ if debugreiserfs -1 $block $device 2>&1 >/dev/null | grep -qF "free in ondisk bitmap"; then
+ echo "blockstate free"
+ exit 0
+ fi
+ echo "blockstate used"
+
+ # Use debugreiserfs -1 to check the block type. This however only works if the block is readable.
+ type="`debugreiserfs -1 $block $device 2>/dev/null | sed -e '/^=*$/d' | head -1`"
+
+ case "$type" in
+ "Looks like unformatted") type="data" ;;
+ "Reiserfs super block"*) type="superblock" ;;
+ "LEAF NODE"*) type="meta" ;;
+ "INTERNAL NODE"*) type="meta" ;;
+ "Desc block"*) type="journal" ;;
+ *) type="" ;;
+ esac
+
+ if [ -n "$type" ]; then
+ echo "blocktype $type"
+ fi
+
+ # Step 8: Find object id to which this block belongs
+ # Step 9: Find file name(s) referencing this object id
+
+ # Currently we only look for $block in indirect blocks.
+
+ python - $device $block <<EOF
+import sys
+import os
+import os.path
+
+device = sys.argv[1]
+blocknr = int(sys.argv[2])
+
+dirtree = {}
+blockid = None
+
+fp = os.popen("debugreiserfs -d %s 2>/dev/null" % device)
+
+def parse_leafnode():
+ global fp
+ l = fp.readline()
+ #sys.stderr.write("> leafnode(1): %s\n" % repr(l))
+ if not l.startswith("LEAF NODE") : return
+ for i in range(4) : fp.readline()
+ while True:
+ l = fp.readline()
+ #sys.stderr.write("> leafnode(2): %s\n" % repr(l))
+ parts = l.split("|")
+ if len(parts)<2 : return
+ parts = parts[2].split()
+ if len(parts)<4 : return
+ obid=(int(parts[0]),int(parts[1]))
+ if parts[3] == "DIR" : parse_dir(obid)
+ elif parts[3] == "IND" : parse_indirect(obid)
+ else:
+ while True:
+ l = fp.readline().strip()
+ #sys.stderr.write("> leafnode(3): %s\n" % repr(l))
+ if l == 79*"-" or l == 67*"=" : break
+
+def parse_dir(obid):
+ global fp
+ global dirtree
+ fp.readline()
+ while True:
+ try:
+ l = fp.readline().rstrip()
+ #sys.stderr.write("> dir: %s\n" % repr(l))
+ if l == 79*"-" or l == 67*"=": return
+ if l.endswith("not set"): continue
+ i = l.index("\"(")
+ name = l[6:6+int(l[i+2:i+5])]
+ if name == "." or name == ".." : continue
+ i = l.index("[",len(name)+12)
+ entryid = tuple(map(int,l[i+1:l.index("]",i+1)].split()))
+ entry = dirtree.get(entryid)
+ if entry is None : entry = dirtree[entryid] = set()
+ entry.add((name,obid))
+ except ValueError:
+ pass
+
+def parse_indirect(obid):
+ global fp
+ global blocknr
+ global blockid
+ fp.readline()
+ for pointer in fp.readline().strip()[1:-1].split():
+ i = pointer.find("(")
+ if i == -1:
+ blkmin = blkmax = int(pointer)
+ else:
+ blkmin = int(pointer[:i])
+ blkmax = blkmin+int(pointer[i+1:-1])-1
+ if blocknr >= blkmin and blocknr <= blkmax:
+ blockid = obid
+ fp.readline()
+
+def scan_path(id):
+ global dirtree
+ entry = dirtree.get(id)
+ #sys.stderr.write("> scan_path: %s->%s\n" % (id, entry))
+ if entry is None:
+ return [ '/' ]
+ else:
+ rv = []
+ for name, parent in entry:
+ for path in scan_path(parent):
+ rv.append(os.path.join(path,name))
+ return rv
+
+def parse():
+ global fp
+ while True:
+ while True:
+ l = fp.readline()
+ #sys.stderr.write("> parse: %s\n" % repr(l))
+ if not(l) : return
+ if l.strip() == 67*"=" : break
+ parse_leafnode()
+
+parse()
+
+if blockid is None:
+ sys.exit(0)
+
+sys.stdout.write("objectid %d %d\n" % blockid)
+
+for path in scan_path(blockid):
+ if path != '/':
+ sys.stdout.write("name %s\n" % path)
+EOF
+
+}
+
+###########################################################################
+
+unset LANG
+
+noscan=""
+if [ "$1" == "--noscan" ]; then
+ noscan=1
+ shift
+fi
+
+if [ -z "$2" ]; then
+ cat <<EOF
+Usage: $0 [--noscan] <device> <sector>"
+
+mapsector -- Map sector numbers to file name(s)
+
+Given a device and a sector number, mapsector will try to find the
+file name(s) mapping to this sector. It will try to gather as much
+information about the given sector as possible.
+
+mapsector currently has support for the following mapping schemes:
+
+ partition table
+ RAID-1
+ cryptsetup (luks and plain dmcrypt)
+ LVM in linear allocation mode
+
+mapsector currently supports the following filesystems
+
+ ext2/3/4
+ reiserfs
+
+mapsector will try it's best to find an associated file name but
+depending on the filesystem state and the type of sector (e.g. if
+the sector is part of a filesystem metadata block) this may not be
+possible.
+
+For mapsector to work, the filesystem must be currently active
+(e.g. LVM must be running, crypted devices must have been set up). The
+filesystem must not necessarily be mounted (though if mounted,
+mapsector will give you the mountpoint).
+
+if '--noscan' is given, the possibly lengthy (!!) filesystem scan for
+filenames is skipped.
+EOF
+ exit 1
+fi
+
+device="$1"
+sector="$2"
+
+echo "device $device"
+echo "sector $sector"
+
+map
+if [ -z "$noscan" ]; then
+ scan
+fi