Skip to content

icheck.sh

#!/bin/ksh -p
#
# CDDL HEADER START
#
# The contents of this file are subject to the terms of the
# Common Development and Distribution License (the "License").
# You may not use this file except in compliance with the License.
#
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
# or http://www.opensolaris.org/os/licensing.
# See the License for the specific language governing permissions
# and limitations under the License.
#
# When distributing Covered Code, include this CDDL HEADER in each
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
# If applicable, add the following below this CDDL HEADER, with the
# fields enclosed by brackets "[]" replaced with your own identifying
# information: Portions Copyright [yyyy] [name of copyright owner]
#
# CDDL HEADER END
#
# This script is provided as is. No liability is accepted for it's
# use.
#
# It makes no attempt to handle any log when enabled.
#
# Chris.Gerhard@sun.com
#
#
# Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
# Use is subject to license terms.
#
# Download the latest version of this script from:
#
# http://blogs.sun.com/roller/resources/chrisg/icheck.sh
#

#pragma ident    "%Z%%M%    %I%    %E% SMI"


#
# list_inodes can use fsdb or find to do the job.
#
# find obviously requires that the file system is mounted but has the
# advantage that it is more reliable on some systems as fsdb can be less
# robust than you would want.
#
function list_inodes
{
    if [[ ${USE_FIND:-0} == 0 ]]
    then
        print ":ls -l -R /" | fsdb -F ufs $1 |\
            nawk '/^i#:/ && (inodes[$2] == 0) {
            printf("0x%s\n", $2);
            inodes[$2] = 1;
            next;
        }'
    else
        find $FS -xdev -ls |\
            nawk '(inodes[$1] == 0) {
            printf("0x%x\n", $1);
            inodes[$1] = 1;
            next;
        }'
    fi
}

function isdebug
{
    [[ ${debug} -ge $1  ]]
    return  $?
}

function debug
{
    if isdebug $1
    then
        shift
        echo DEBUG $@
    fi
}

#
# fsdb does not expect to be driven by a script and buffers lots of output.
# To workaround this push down plenty of commands so that the data that
# is actually interesting gets flushed back to the script.
#
# Expect would be another workaround for this.
#
function flush
{
    typeset -i i=0
    while ((i < $flush_count ))
    do
        print -p ':base=0x10' || exit
        let i=i+1
    done
}

#
# Output a marker of some sort so you can tell when all the flush data
# has ended.
#
function end_flush
{
    print -p '0xfeedbede=X' || exit
}

function eat_flush
{
    typeset  one two three four

    while read -p one two three four
    do
        if [[ "$one" == "fffffffffeedbede" || $three == "fffffffffeedbede" ]]
        then
            break
        fi
    done
}

#
#    so that it can tell when the output of a command has completed
#    it writes down this fsdb command and then continues processing
#    until it finds the result in the output stream.
#
function end_command
{
    print -p '0xfefeadbc=X' || exit
}

#
# The number we pushed down gets sign extended. Hence all the extra "f"s.
#
function is_end
{
    if [[ ${1:-1} = "fffffffffefeadbc" ]]
    then
        return 0
    else
        : echo $1
        return 1
    fi
}
#
#    do_indirect0
#        args:
#        $1 = inode
#        $2 = offset in file for this block of indrects
#        $3 = The block of indirects
#
#    If the block offset is more then 7FFFFF then fsdb is going to
#    incorrectly display the offsets as they will wrap.
#
#    This is bug 4658830.
#
#    Since this script is also living in a 32 bit world this makes life
#    a little bit harder.
#
#    Hence we do the calulation ((offset * $Fsize) & $Wmask) ) which
#    gets the low 20 bits of the real calculation. If the number
#    of words per block will not fit into Wmask there will be trouble.
#
#
function do_indirect0
{
    debug 1 ib0 enter
    typeset buf
    typeset inode=$1
    typeset file_off=$2
    typeset -i16 offset=16#${3#16#}
    typeset -i16 start_offset=$(( ($offset * $Fsize) & $Wmask ))

    debug 2 "ib0 inode $inode $offset"

    flush
    end_flush
    print -p "0x${offset#16#}:block,${WordsPerBlock#16#}/X"|| exit
    end_command
    flush
    eat_flush
    debug 2 ib0 read -p buf
    while read -p buf
    do
        set - $buf
        [[ $# == 0 ]] && continue
        debug 1 ib0 $@
        if [[ $1 == $RAW_DEVICE ]]
        then
            shift 2
        fi

        [[ $# == 0 ]] && continue

        if is_end $1 || is_end $3
        then
            break;
        fi
        debug 1 ib0 loop $buf
        if [[ ${1%:} != ${1} ]]
        then
            typeset -i16 tmp
            tmp=$(( 16#${1%:} & $Wmask ))
            tmp=$(($file_off + (( $tmp - ${start_offset} ) / $Wsize) ))
            debug 1 XX $file_off $offset $tmp ${start_offset} $(( $tmp - ${start_offset} ))
            shift
            $do_block $inode ${tmp#16#} ${1}
            let tmp=$tmp+1
            shift
            $do_block $inode ${tmp#16#} ${1}
            let tmp=$tmp+1
            shift
            $do_block $inode ${tmp#16#} ${1}
            let tmp=$tmp+1
            shift
            $do_block $inode ${tmp#16#} ${1}
        else
            : debug ${1%:} ${offset}
        fi
    done
}

function do_indirect1
{
    debug 1 ib1 enter
    typeset buf
    typeset inode=$1
    typeset file_off=$2
    typeset -i16 offset=16#${3#16#}
    typeset -i16 start_offset=$(( ($offset * $Fsize) & $Wmask ))
    debug 3 spam3 $3 $offset
    typeset -i i=0;

    debug 2 "ib1 inode $inode $offset"
    flush
    end_flush
    print -p "0x${offset#16#}:block,${WordsPerBlock#16#}/X" || exit
    end_command
    flush

    while read -p buf
    do
        set - ${buf}

        if [[ $1 == $RAW_DEVICE ]]
        then
            shift 2
        fi

        if is_end $1 || is_end $3
        then
            break;
        fi
        debug 1 ib1 loop $buf
        if [[ ${1%:} != ${1} ]]
        then
            typeset -i16 tmp
            tmp=$(( 16#${1%:} & $Wmask ))
            tmp=$(( ($tmp - ${start_offset} ) / $Wsize))
            debug 1 ib1 $offset $tmp
            shift
            [[ $# == 0 ]] && continue
            eval "typeset blocks$i"
            eval "blocks$i=\"$tmp $1\""
            let i=i+1
            let tmp=tmp+1
            shift
            [[ $# == 0 ]] && continue
            eval "typeset blocks$i"
            eval "blocks$i=\"$tmp $1\""
            let i=i+1
            let tmp=tmp+1
            shift
            [[ $# == 0 ]] && continue
            eval "typeset blocks$i"
            eval "blocks$i=\"$tmp $1\""
            let i=i+1
            let tmp=tmp+1
            debug 3 ib1 X $i
            shift
            [[ $# == 0 ]] && continue
            eval "typeset blocks$i"
            eval "blocks$i=\"$tmp $1\""
            let i=i+1
        else
            : debug ${1%:} ${offset}
        fi
    done
    debug 2 ib1 loop done

    typeset -i j=0

    debug 1 "in ib1 $i"
    while (( j < i ))
    do

        typeset -i16 tmp
        typeset -i16 tmp2
        eval "set - \${blocks${j}}"
        tmp2=16#$2
        tmp=$(( $file_off + ( $WordsPerBlock * $1 )  ))
        debug 3 ib1 spam $j $tmp $tmp2
        if (( $tmp2 != 0 ))
        then
            debug 1 do_indirect0 $inode ${tmp} ${tmp2} $1
            do_indirect0 $inode ${tmp} ${tmp2}
        fi
        let j=j+1
    done
}

function do_indirect2
{
    debug 1 ib2 enter
    typeset buf
    typeset inode=$1
    typeset file_off=$2
    typeset -i16 offset=16#${3#16#}
    typeset -i16 start_offset=$(( ($offset * $Fsize) & $Wmask ))
    typeset -i i=0;

    set -f
    debug 2 "ib2 inode $inode $offset"
    flush
    end_flush
    print -p "0x${offset#16#}:block,${WordsPerBlock#16#}/X" || exit
    end_command
    flush
    debug  3 ib2 read -p buf
    while read -p buf
    do
        set - $buf
        debug 3 ib2 $@
        if [[ $1 = $RAW_DEVICE ]]
        then
            shift 2
        fi
        if is_end $1 || is_end $3
        then
            break;
        fi
        debug 1 ib2 loop $buf
        if [[ ${1%:} != ${1} ]]
        then
            typeset -i16 tmp
            tmp=$(( 16#${1%:} & $Wmask ))
            tmp=$(( ($tmp - ${start_offset} ) / $Wsize))
            shift
            [[ $# == 0 ]] && continue
            eval "typeset blocks$i"
            eval "blocks$i=\"$tmp $1\""
            let i=i+1
            let tmp=tmp+1
            shift
            [[ $# == 0 ]] && continue
            eval "typeset blocks$i"
            eval "blocks$i=\"$tmp $1\""
            let i=i+1
            let tmp=tmp+1
            shift
            [[ $# == 0 ]] && continue
            eval "typeset blocks$i"
            eval "blocks$i=\"$tmp $1\""
            let i=i+1
            shift
            [[ $# == 0 ]] && continue
            eval "typeset blocks$i"
            eval "blocks$i=\"$tmp $1\""
            let i=i+1
        else
            : debug ${1%:} ${offset}
        fi
    done

    typeset -i j=0

    while (( j < i ))
    do
        typeset -i16 tmp
        typeset -i16 tmp2
        eval "set - \${blocks${j}}"
        tmp2=16#$2
        tmp=$(( $file_off + ( $WordsPerBlock * $WordsPerBlock * $1 )  ))
        debug 1 spam $j $tmp $tmp2
        if (( $tmp2 != 0 ))
        then
            debug 1 do_indirect1 $inode ${tmp} ${tmp2} $1
            do_indirect1 $inode ${tmp} $tmp2
        fi
        let j=j+1
    done
}
#
# This is the original do_block which is easier to read than the
# second version. However the second version runs in about 2/3s the
# time and since this is the most called routine in this script, being
# called for every block that is checked speed is everything.
#
# Both routines should produce the same results.
#
function do_block
{
    typeset inode=$1
    typeset fileblk=$2
    typeset blk=$3

    if (( 16#$blk != 0 ))
    then
        typeset -i b=$(( 0x${blk} & $Bmask ))
        typeset x

        eval "x=\$BLOCKS_$b"
        if [[ "${x}" != "" ]]
        then
            print inode $inode: file block: 16#${fileblk} device block: 16#${blk}
        fi
    fi
}

function do_block
{
    if (( 16#$3 != 0 ))
    then
        eval "[[ \${#BLOCKS_$(( 0x$3 & $Bmask ))} != 0 ]]" && \
            print inode $1: file block: 16#${2} device block: 16#${3}
    fi
}

#
# Routines that will copy a file.  I just used this for testing purposes
# figuring that if I can copy a file like this and the source and target
# compare correctly they I have probably got this right!
#
# There is no attempt to cope with fragments.  So the last block of the
# target file is padded to the block size.
#
function do_dd
{
    if (( ${DD_iseek} / $FragsPerBlock == 0 ))
    then
        dd conv=notrunc bs=${Bsize} \
            iseek=$(( ${DD_iseek} / $FragsPerBlock )) \
            oseek=${DD_oseek} \
            of=$DD_ofile if=${RAW_DEVICE} \
            count=$DD_count 2> /dev/null || exit
    else
        dd conv=notrunc bs=${Fsize} \
            iseek=$(( ${DD_iseek})) \
            oseek=${DD_oseek} \
            of=$DD_ofile if=${RAW_DEVICE} \
            count=$(( $DD_count * $FragsPerBlock  )) 2> /dev/null || exit

    fi
    DD_count=0
}

function do_block_cp
{
    debug 1 dd_start $@
    if (( 16#$3 != 0 ))
    then
        if (( 16#$2 != 0 )) && (( 16#$2 <= ${DD_oseek:-0} ))
        then
            echo ARg $@ 
        fi

        if (( ${DD_count:-0} != 0 )) && \
            (( 16#$2 == (${DD_oseek:-0} + ${DD_count:-0} ) )) &&  \
            (( 16#$3 == (${DD_iseek:-0} + \
                (${DD_count:-0} * $FragsPerBlock)) )) && \
                ((  16#$2 != ${LastBlock:-0} ))
        then
            let DD_count=DD_count+1
        elif (( ${DD_count:-0} == 0 ))
        then
            DD_count=1
            DD_oseek=$(( 16#$2 ))
            DD_iseek=$(( 16#$3 ))
        else
            $do_dd
            DD_count=1
            DD_oseek=$(( 16#$2 ))
            DD_iseek=$(( 16#$3 ))
        fi
    elif (( ${DD_count:-0} != 0))
    then
        $do_dd
    fi
    debug 1 dd_end $@
}

#
#    It would be silly not to allow the reporting of the file layout.
#
function do_extent
{
    echo file off ${DD_oseek} dev off $(( ${DD_iseek} / $FragsPerBlock )) len $DD_count
    DD_count=0
}

#
# Note we don't trust the inode number returned by the :inode command.
# this is because of bug:
#
# 6433317 fsdb_ufs's :inode command can display the incorrect inode number.
#
#
function do_inode
{
    typeset one two three four five six seven eight nine
    typeset buf
    typeset inode
    typeset inode_in=${1#0x}
    typeset ib0 ib1 ib2
    typeset si

    end_flush
    print -p "0x${inode_in}:inode?i" || exit
    end_command
    flush
    eat_flush
    debug 1 do_inode  read -p buf
    while read -p buf
    do
        set - $buf
        if is_end $1 || is_end $3
        then
            return
        fi
        if [[ "$1" == "inode" && $2 == "not" && "$3" == "allocated" ]]
        then
            print "WARNING: $inode_in not allocated" >&2
            break
        fi
        if [[ "$1" == "$RAW_DEVICE" ]]
        then
            shift 2
        fi
        if [[ "$1" == 'i#:' ]]
        then
            inode=$2
            break;
        fi
        if [[ "$1" == $RAW_DEVICE && $three == 'i#:' ]]
        then
            inode=$4
            break
        fi
    done
    if [[ "$inode_in" != $inode ]]
    then
        print "WARNING: wrong inode. Asked for $inode_in, got $inode" >&2
        print $@ >&2
    else
        debug 1 "Right inode. Asked for $inode_in, got $inode"
    fi

    while read -p one two three four five six seven eight nine
    do
        debug 1 in inode $one $two $three $four $five $six $seven $eight
        if is_end $one || is_end $three
        then
            break
        fi

        if [[ ${eight#si:} != ${eight} ]]
        then
            si=${nine:-0}
        fi

        if [[ ${one#db#} != ${one} ]]
        then
            typeset tmp=${one#db#}
            $do_block $inode_in ${tmp%:} ${two}
        fi
        if [[ ${three#db#} != ${three} ]]
        then
            typeset tmp=${three#db#}
            $do_block $inode_in ${tmp%:} ${four}
        fi
        if [[ ${five#db#} != ${five} ]]
        then
            typeset tmp=${five#db#}
            $do_block $inode_in ${tmp%:} ${six}
        fi
        if [[ ${seven#db#} != ${seven} ]]
        then
            typeset tmp=${seven#db#}
            $do_block $inode_in ${tmp%:} ${eight}
        fi
        if [[ ${one#ib#} != ${one} ]]
        then
            typeset tmp=${one#ib#}
            eval "ib${tmp%:}=$two"
            if [[ ${tmp%:} != 0 ]]
            then
                print expect indirect 0 got ${tmp%:} >&2
            else
                debug 2 ib0 = $two
            fi
            if [[ ${three#ib#} != ${three} ]]
            then
                tmp=${three#ib#}
                eval "ib${tmp%:}=$four"
                if [[ ${tmp%:} != 1 ]]
                then
                    print expect indirect 1 got ${tmp%:} >&2
                else
                    debug 2 ib1 = $four
                fi
                if [[ ${five#ib#} != ${five} ]]
                then
                    tmp=${five#ib#}
                    eval "ib${tmp%:}=$six"
                    if [[ ${tmp%:} != 2 ]]
                    then
                        print expect indirect 2 got ${tmp%:} >&2
                    fi
                fi
            fi
        fi
        unset one two three four five six seven eight
    done

    if [[ ${SHADOW:-0} != 0 ]]
    then
        if [[ ${si:-0} == ${SHADOW:-0} ]]
        then
            echo Shadow inode ${SHADOW} associated with inode $inode_in
        fi
        return
    fi
    if [[ ${ib0:-0} != 0 ]]
    then
        debug 2 in inode doing ib0 $inode_in 16#c  $ib0
        do_indirect0 $inode_in 16#c $ib0
    fi
    if [[ ${ib1:-0} != 0 ]]
    then
        debug 2 in inode doing ib1 $inode_in 16#$(( 16#c + $WordsPerBlock )) $ib1
        typeset -i16 x=$(( 16#c + $WordsPerBlock ))
        do_indirect1 $inode_in $x $ib1
    fi
    if [[ ${ib2:-0} != 0 ]]
    then
        typeset -i16 x=$(( 16#c + $WordsPerBlock + ($WordsPerBlock * $WordsPerBlock) ))
        do_indirect2 $inode_in $x $ib2
    fi
}

function do_sz
{
    typeset buf

    end_flush
    print -p "0x${1#0x}:inode;:sz" || exit
    end_command
    flush
    eat_flush
    while read -p buf
    do
        set - $buf
        if is_end $1 || is_end $3
        then
            break
        fi
        if [[ $1 == $RAW_DEVICE ]]
        then
            shift 2
        fi
        if [[ $1 != '?' ]]
        then
            DD_filesize=$1
        fi
    done
}

function do_superblock
{
    typeset buf

    end_flush
    print -p ":sb" || exit
    end_command
    flush
    eat_flush
    while read -p buf
    do
        set - $buf
        if is_end $1 || is_end $3
        then
            break
        fi
        if [[ $1 == "bsize" ]]
        then
            debug 1 bsize $2
            Bsize=$2
        fi
        if [[ $1 == "fsize" ]]
        then
            debug 1 fsize $2
            Fsize=$2
        fi
        if [[ $3 == "shift" ]]
        then
            debug 1 shift $4
            Fshift=$4
        fi
    done
}

function usage
{
    printf "USAGE: %s: [-D level ][ -i inode ][ -f directory][[ -s shadow inode ]|[ -c target_file ]|[ -x ]] -d device  blocks...\n"  $0
}
#
#    End of functions
#
#    main()
#
# RAW_DEVICE=/dev/zvol/rdsk/tank/rootfs
set -f
typeset -i Fsize
typeset -i Bsize

do_block=do_block
while getopts f:d:D:i:c:xs: name
do
    case $name in
    c)    do_block=do_block_cp
        do_dd=do_dd
        DD_ofile=$OPTARG
        [[ -f $DD_ofile ]] && : > $DD_ofile ;; 
    x)    do_block=do_block_cp
        do_dd=do_extent ;;
    f)    USE_FIND=1
        FS=$OPTARG;;
    d)     RAW_DEVICE=$OPTARG ;;
    i)     INODE=$OPTARG ;;
    s)     SHADOW=$OPTARG ;;
    D)     fsdb_debug=$OPTARG ;;
    ?)     usage
     exit 2;;
    esac
done
shift $(($OPTIND - 1))

if [[ ! -c ${RAW_DEVICE:-""} || ( ${#INODE} == 0 && $# == 0 && ${SHADOW:-0} == 0) ]]
then
    usage
    exit 2
fi

debug=${fsdb_debug:-0}

typeset -i Wsize=4
#
# The number of commands that are sure to flush the output buffer of
# fsdb.
#
flush_count=200
#
# The mask to cope with wrapping. See the comment above do_indirect0
#
typeset -i16 Wmask=$((16#fffff))
#
# Now start the fsdb co-process
#
fsdb -F ufs $RAW_DEVICE |&

print -p ':base=0x10' || exit
flush

do_superblock

#
# remove any fragments from this.
#
typeset -i Bmask=$(( (0xffffffff >> $Fshift) << $Fshift ))

for i in $@
do
    typeset -i16 y=$((0x${i#0x} ))
    typeset -i x=$((y & Bmask ))
    if (( x != y ))
    then
        y=x
        echo $i is a fragment address. Rounding to block $y
    fi
    debug 1 BLOCKS_$x=$i
    eval "BLOCKS_$x=$i"
    unset x
    unset y
done

typeset -i16 WordsPerBlock=$(( $Bsize / $Wsize ))
typeset -i16 FragsPerBlock=$(( $Bsize / $Fsize ))
debug 1 We are off
if [[ ${#INODE} != 0 ]]
then
    do_inode $INODE

    print -p ':quit'
    if (( ${DD_count:-0} != 0 ))
    then
        $do_dd
    fi
    exit
fi

isdebug 2 && typeset -ft do_indirect0
isdebug 2 && typeset -ft do_indirect1
isdebug 2 && typeset -ft do_indirect2
isdebug 2 && typeset -ft do_inode
debug 1 "go"
if (( ${debug:-0} == 0 ))
#
# Get rid of the debug functions for extra speed
#
then
function debug
{
:
}
fi
list_inodes $RAW_DEVICE | while read inode
do
    debug 1 Doing $inode
    do_inode $inode
done
print -p ':quit' || exit

 

Leave a comment