From: Stefan Bund Date: Fri, 6 Nov 2009 11:15:06 +0000 (+0100) Subject: initial commit X-Git-Tag: v0.0.0^0 X-Git-Url: http://g0dil.de/git?p=mapsector.git;a=commitdiff_plain;h=06809543df5847ffeca7b63bbc2a568c8cc34cb9 initial commit --- 06809543df5847ffeca7b63bbc2a568c8cc34cb9 diff --git a/mapsector.sh b/mapsector.sh new file mode 100755 index 0000000..514ad38 --- /dev/null +++ b/mapsector.sh @@ -0,0 +1,588 @@ +#!/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 </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 < " + +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