initial commit v0.0.0
Stefan Bund [Fri, 6 Nov 2009 11:15:06 +0000 (12:15 +0100)]
mapsector.sh [new file with mode: 0755]

diff --git a/mapsector.sh b/mapsector.sh
new file mode 100755 (executable)
index 0000000..514ad38
--- /dev/null
@@ -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 <<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