#! /bin/bash
#
# Copyright (c) 2013-2025 University of Utah and the Flux Group.
# 
# {{{EMULAB-LICENSE
# 
# This file is part of the Emulab network testbed software.
# 
# This file is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or (at
# your option) any later version.
# 
# This file is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public
# License for more details.
# 
# You should have received a copy of the GNU Affero General Public License
# along with this file.  If not, see <http://www.gnu.org/licenses/>.
# 
# }}}
#
echo -n ' Starting diskcheck..'

# find the number of disks
# find the size of each disk
# first arg is the log output file, if not set then /tmp/...

declare error=""
declare err=""

source checkutils.sh
source hbis.sh

x=$(caller)
[[ "${x/NULL}" = "$x" ]] && declare -ri diskcheck_standalone=0 || declare -ri diskcheck_standalone=1

declare failed=""
## declare -p todo_exit

main_diskcheck() {
    initialize $@
    cp /dev/null $tmplog

    SMARTCTL=$(findSmartctl)
    drivenames=$(getdrivenames)

    # run through the drivelist filtering out non-local drives or other devices 
    for i in $drivenames ; do
        model=$(getmodel $i)
    	if [ "$model" == "iSCSIDisk" ] ; then
	    echo "Filter out $i because $model"
	    drivenames=${drivenames/${i}}
	fi
    done
    
    if [ -z "$drivenames" ] ; then
	echo "No drives found. exit"
	(( $diskcheck_standalone )) && exit 1 || return 1
    fi

    # array to put drive inventory
    unset -v driveinv ; declare -a driveinv=()
    # array of drives to hold each drive inventory
    unset -v drive ; declare -a drive=($drivenames)

    numberofdrives=${#drive[*]}

   # the index into dirveinv array
    # DISKUNIT SN=<serial> TYPE=<PATA|SATA|SCSI|RAID|NVMe> SECSIZE=<#> SECTORS=<#> RSPE ED=<MBs> WSPEED=<MBs> 
    declare -i -r header=0
    declare -i -r header_val=1
    declare -i -r name=2
    declare -i -r name_val=3
    declare -i -r driver=4
    declare -i -r driver_val=5
    declare -i -r type=6
    declare -i -r type_val=7
    declare -i -r size=8
    declare -i -r size_val=9
    declare -i -r temp=10
    declare -i -r temp_val=11
    declare -i -r model=12
    declare -i -r model_val=13
    declare -i -r serial=14 
    declare -i -r serial_val=15
    declare -i -r bpers=16
    declare -i -r bpers_val=17
    declare -i -r sectors=18
    declare -i -r sectors_val=19
    declare -i -r wspeed=20    
    declare -i -r wspeed_val=21
    declare -i -r rspeed=22
    declare -i -r rspeed_val=23
    declare -i -r wcache=24
    declare -i -r wcache_val=25
    declare -i -r lastslot=25 # used for iteration thru values

    # init a default string
    unset -v d; declare -a d=()
    for ((i=0;i<=$lastslot;i++)) ; do
	case $i in
	    $header ) d[$header]="DISKUNIT" ;;
	    $header_val ) d[$header_val]="$header_val" ;;
	    $name ) d[$name]="Device=" ;;
	    $name_val ) d[$name_val]="unset " ;;
	    $driver ) d[$driver]="Driver=" ;;
	    $driver_val ) d[$driver_val]="\"UNKNOWN\" " ;;
	    $type ) d[$type]="TYPE=" ;;
	    $type_val ) d[$type_val]="\"UNKNOWN\"" ;;
	    $size ) d[$size]="Size=" ;;
	    $size_val ) d[$size_val]="unk" ;;
	    $temp ) d[$temp]="Temp=" ;;
	    $temp_val ) d[$temp_val]="unk" ;;
	    $model ) d[$model]="Model=" ;;
	    $model_val ) d[$model_val]="unk" ;;
	    $serial  ) d[$serial]="SN=" ;;
	    $serial_val ) d[$serial_val]="\"UNKNOWN\"" ;;
	    $wcache  ) d[$wcache]="wcache=" ;;
	    $wcache_val ) d[$wcache_val]="\"UNKNOWN\"" ;;
	    $bpers ) d[$bpers]="SECSIZE=" ;;
	    $bpers_val ) d[$bpers_val]="unk" ;;
	    $sectors ) d[$sectors]="SECTORS=" ;;
	    $sectors_val ) d[$sectors_val]="\"UNKNOWN\"" ;;
	    $wspeed   ) d[$wspeed]="WSPEED=" ;;
	    $wspeed_val ) d[$wspeed_val]="\"UNKNOWN\"" ;;
	    $rspeed ) d[$rspeed]="RSPEED=" ;;
	    $rspeed_val ) d[$rspeed_val]="\"UNKNOWN\"" ;;
	esac
    done

    #default string
    driveinvinit="${d[@]}"

    # initalize the driveinv array
    for ((idx=0; idx<$numberofdrives; idx++)) ; do
	driveinv[$idx]="$driveinvinit" 
    done

    #now fill in the array values
    for ((idx=0; idx<$numberofdrives; idx++)) ; do
	unset -v d ; declare -a d=(${driveinv[$idx]})
	for ((i=0;i<=$lastslot;i++)) ; do
	    case $i in
		$header_val ) d[$header_val]="$idx" ;;
		$name_val ) d[$name_val]="${drive[$idx]}" ;;
		$sectors_val ) d[$sectors_val]=$(getsectors ${drive[$idx]}) ;;
		$bpers_val ) d[$bpers_val]=$(getbpers ${drive[$idx]}) ;;
		$driver_val ) d[$driver_val]=$(getdriver ${drive[$idx]}) ;;
		$type_val ) d[$type_val]=$(gettype ${drive[$idx]}) ;;
		$temp_val ) d[$temp_val]=$(gettemp ${drive[$idx]}) ;;
		$model_val ) d[$model_val]=$(getmodel ${drive[$idx]}) ;;
		$serial_val ) d[$serial_val]=$(getserial ${drive[$idx]}) ;;
	    esac
	done
	# Must have sectors_val an bpers_val before calculating size
	for ((i=0;i<=$lastslot;i++)) ; do
	    case $i in
		$size_val ) 
		x=$((${d[$sectors_val]}*${d[$bpers_val]}))
		d[$size_val]=$(($x / 1000000000))		
		;;
	    esac
	done
	# Must have size val before attempting disk speed tests
	if (( $mfsmode == 1 )) ; then
	    for ((i=0;i<=$lastslot;i++)) ; do
		case $i in
		    $rspeed_val ) 
		# check the size, if small then might be a USB drive
		    if [ ${d[$size_val]} -gt 8 ] ; then
			d[$rspeed_val]=$(getrspeed ${drive[$idx]})
		    else
			d[$type_val]="SMALL"
		    fi
		    ;;
		    $wspeed_val ) 
		    # check the WCE before the speed test
		    d[$wcache_val]=$(getwcache ${drive[$idx]})
		    if [ ${d[$size_val]} -gt 8 ] ; then
			d[$wspeed_val]=$(getwspeed ${drive[$idx]})
		    else
			d[$type_val]="SMALL"
		    fi
		    ;;
		esac
	    done
	fi
	
	# update driveinv with modified data
	driveinv[$idx]=${d[@]}
    done
    
    if (( $collect_flag )) ; then
	printf "%s%d\n"  "DISKINFO UNITS=" ${numberofdrives} >> ${logfile4tb}
	for (( idx=0; idx<$numberofdrives; idx++)) ; do
	    unset -v d ; declare -a d=(${driveinv[$idx]})
	    printf "%s %s\""%s\"" %s\""%s\"" %s%s %s%s %s%s %s%s %s%s" \
		${d[$header]} \
		${d[$serial]} ${d[$serial_val]} \
		${d[$wcache]} ${d[$wcache_val]} \
		${d[$type]} ${d[$type_val]} \
		${d[$bpers]} ${d[$bpers_val]} \
		${d[$sectors]} ${d[$sectors_val]} \
		${d[$wspeed]} ${d[$wspeed_val]} \
		${d[$rspeed]} ${d[$rspeed_val]} >> ${logfile4tb}
	    printf " %s%s %s%s %s%s %s%s %s%s\n" \
		${d[$name]} ${d[$name_val]} \
		${d[$size]} ${d[$size_val]} \
		${d[$temp]} ${d[$temp_val]} \
		${d[$model]} ${d[$model_val]} \
		${d[$driver]} ${d[$driver_val]} >> ${logfile4tb}
	done
    fi

# we are done if in MFS mode
if (( $mfsmode )) ; then
    (( $diskcheck_standalone )) && exit 0 || return 0
fi

#output to log file
{
echo "Diskcheck $(date): "
echo -e "name\t\tdriver\ttype\tsize\ttemp\tmodel\t\t\tserial\t\twcache"
for ((idx=0; idx<${numberofdrives}; idx++)) ; do
    unset -v d ; declare -a d=(${driveinv[$idx]})
    echo -e "${d[$name_val]}\t${d[$driver_val]}\t${d[$type_val]}\t${d[$size_val]}\t${d[$temp_val]}\t${d[$model_val]}\t\t${d[$serial_val]}\t${d[$wcache_val]}"
done
} > ${tmplog} 2>&1
cat ${tmplog} >> ${logfile} 

driveinfo=""
# echo name,size and serial to stdout, addr serialnumber
for ((idx=0; idx<${#driveinv[*]}; idx++)) ; do
    unset -v d ; declare -a d=(${driveinv[$idx]})
    echo -n "${d[$name_val]} ${d[$size_val]} ${d[$serial_val]} ${d[$wcache_val]}  "
    driveinfo+="${d[$serial_val]} "
done
echo ""

driveinfo=${driveinfo% } # get rid of trailing space

if (( $check_flag )) ; then
# Now check against the testbed DB
# readtmcinfo /proj/emulab-ops/nodecheck/pc507/pc507  tmccinfo
    tbinfo=$(getfromtb DISKUNIT)
    tbinfo=${tbinfo% }  # get rid of trailing space
    
# echo driveinfo:$driveinfo: tbinfo:$tbinfo: 
{
#save for both output to stdout and log file 
    if [ -z "$tbinfo" ] ; then
	failed="TBmiss no info"
	echo "TBmiss empty info returned. "
    fi
    
#upper case
    tbinfo=${tbinfo^^}
    driveinfo=${driveinfo^^}

# turn space seperated string into array
    unset -v tb; declare -a tb=(${tbinfo// / })
    unset -v have; declare -a have=(${driveinfo// / })

#   lines commented with #-# disable the checking of HD order
    
    havecnt=${#have[*]}
    tbcnt=${#tb[*]}
#-#     numserial=${tbcnt}
#-#     maxcnt=${tbcnt}
#-#     
    if [[ ${tbcnt} -ne ${havecnt} ]] ; then
	failed="TBmiss "
#-#     [[ ${tbcnt} -gt ${havecnt} ]] && maxcnt=${tbcnt} || maxcnt=${havecnt}
    fi
#-#     
#-#     for ((idx=0; idx<$maxcnt; idx++)) ; do
#-# 	for ((ifi=0; ifi<$havecnt; ifi++)) ; do
#-# 	    for ((tbi=0; tbi<$tbcnt; tbi++)) ; do
#-# 		if [ "${have[$ifi]}" = "${tb[$tbi]}" ] ; then
#-# 		    have[$ifi]=''
#-# 		    tb[$tbi]=''
#-# 	    fi
#-# 	    done
#-# 	done
#-#     done
#-# #turn array into string
     haveresult=${have[@]-""}
     tbresult=${tb[@]-""}
# comment out the next if statment and uncommet the #-# lines for drive
# ordering not to matter when comparing drives found with tbdb
     if [[ "$haveresult" != "$tbresult" ]] ; then
	 failed=" found $haveresult but tbdb says $tbresult "
     fi
#-#     
#-#     if [[ "$haveresult" != "$tbresult" ]] ; then
#-# 	if [[ $haveresult ]] ; then
#-# 	    failed="${failed}: NotIn TB $haveresult"
#-# 	fi
#-# 	if [[ $tbresult ]] ; then
#-# 	    failed="${failed}: TB Claims $tbresult"
#-# 	fi
#-#     fi
    
    echo -n "Have $havecnt drive"
    [[ ${havecnt} -gt 1 ]] && echo -n "s" 
    [[ -z ${failed} ]] && echo " OK" || echo " $failed" FAILED

} > ${tmplog} 2>&1
    cat ${tmplog} >> $logfile
    cat ${tmplog}
fi # matching endif of (( $check_flag ))
}

getdriveinfo () {
#need to make sure smartcrl is installed
{
#    echo -n "${FUNCNAME[0]}:${LINENO} " ; echo "args::$@::"
    declare buildscan=""
    logout="$1"
    tmpout="$2"

    if [ "${SMARTCTL}" != "$NOSM" ] ; then
	rtn=$($SMARTCTL --scan)
	# unrecongnized
	if [ -n "$(echo $rtn | grep 'UNRECOGNIZED OPTION')" ] ; then
	    error="(smartctl option '--scan' not supported. Attempt alternet method) "
	    err=scan
	elif [ -n "$(echo $rtn | grep -v 'device')" ] ; then
            # output in unexpected format - missing deliminator "device"
	    error="(smartctl option '--scan' strange ouput. Attempt alternet method) "
	    err=scan
	# empty
	elif [ -z "$rtn" ] ; then
	    dt=$(df / | grep /dev)
	    dt=${dt:5}
	    dt=${dt%% *}
	    error="(smartctl device_type '$dt' not supported"
	    err=device
	fi
	[[ $error ]] && echo "$error"
    else
	error="smartmontools missing."
	err="missing"
	echo "$error. FAIL "
    fi
} > ${logout} 2>&1

# put smartctl --scan into driveinv array
# a better control flow control could be used 

placeholder=" . . . . . device"
case $err in
    scan | missing | device )
	case $os in
	    Linux )
		# XXX there appear to be static entries in the MFS's /dev
		# including /dev/sda, so for sda, we ensure there is really
		# something there. This only comes up when you have an all
		# NVMe machine.
		list=""
		if grep -q 'sda$' /proc/partitions; then
		    list+="a "
		fi
		list+="b c d e f g h i j k l m n o p"
		for i in $list
		do
		    if [ -b /dev/sd${i} ] ; then
			buildscan+="/dev/sd${i} $placeholder"
		    fi
		done
		;;
	    FreeBSD )
		list="0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15"
		for i in $list
		do
		    [[ -b /dev/da${i} ]] && buildscan+="/dev/da${i} $placeholder " 
		    [[ -c /dev/ad${i} ]] && buildscan+="/dev/ad${i} $placeholder " 
		    [[ -c /dev/amrd${i} ]] && buildscan+="/dev/amrd${i} $placeholder " 
		done
		;;
	    * )
		echo "${FUNCNAME[0]}:${LINENO} Internal error"
		(( $diskcheck_standalone )) && exit 1 || return 1
		;;
	esac
	unset -v scan
	[[ $buildscan ]] && declare -a scan=($buildscan) || declare -a scan=("")
#	echo -n "${FUNCNAME[0]}:${LINENO} " ; echo "buildscan::${buildscan}::"
	;;
    root )
	echo -n "$error. FAIL " >> ${tmpout}
	echo "Last attempt return roots mount point" >> ${tmpout}
	x=$(df / | grep /dev)
	lastattempt="${x%% *} $placeholder "
	unset -v scan ; declare -a scan=($lastattempt)
	;;
    * )
        # get the output of --scan into array scan
	 unset -v scan ; declare -a scan=($rtn)
#	 echo -n "${FUNCNAME[0]}:${LINENO} " ; echo "rtn::${rtn}::"
	;;
esac

# the result
echo -n "${scan[@]}"

}

getdriver() { 
    # don't support this right now
#    smtcl=$(findSmartctl)
#    if [ "${smtcl/smartcl}" == "$smtcl" ] ; then
#	echo "UNKNOWN"
#	return 0
#    fi
    echo "NoInfo"
    return 0
}
gettype() { 
    smtcl=$(findSmartctl)
    if [ "${smtcl/smartctl}" == "$smtcl" ] ; then
	echo "NoInfo"
	return 0
    fi
    smtinfo=$($smtcl -i $1)
    res=$(echo $smtinfo | grep "ATA Version is")
    if [ -n "$res" ] ; then
	res=${res##*is: }
	res=${res// /} #get rid of spaces
	[[ $res > 6 ]] && res="SATA" || res="PATA"
	echo $res
	return 0
    fi
    res=$(echo $smtinfo | grep "SAS")
    if [ -n "$res" ] ; then
	echo "SAS"
	return 0
    fi
    res=$(echo $smtinfo | grep "SCSI")
    if [ -n "$res" ] ; then
	echo "SCSI"
	return 0
    fi
    res=$(echo $smtinfo | grep "NVM Capacity")
    if [ -n "$res" ] ; then
	echo "NVMe"
	return 0
    fi
    echo "UNKNOWN"
    return 0
}
gettemp() { 
    smtcl=$(findSmartctl)
    if [ "${smtcl/smartctl}" == "$smtcl" ] ; then
	echo "NoInfo"
	return 0
    fi
    res="$($smtcl  -l scttempsts $1 | grep -i "Current Temperature:" | awk '{print $3}')"
    if [ -z $res ] ; then
        #type 2
	res="$($smtcl -a $1 | grep -i "Current Drive Temperature:" | awk '{print $4}')"
    fi
    [[ $res ]] && echo $res || echo "UNKNOWN"
    return 0
}
getmodel() { 
    smtcl=$(findSmartctl)

    if [ "${smtcl/smartctl}" == "$smtcl" ] ; then
	#don't have smartctl, maybe we can use hdparm
	hdp=$(which hdparm)
	if [ "${hdp/hdparm}" != "${hdp}" ] ; then
	    x=$(hdparm -I $1 | grep -i 'Model Number:')
	    x=${x/Model Number: }
	    res=${x/# */}
	    res=${res%# */}
	    # note: can get the model from '/proc/scsi/scsi'
	else
	    # XXX 	# note: 'camcontrol devlist'
	                # will give model on FreeBSD
	    echo "NoInfo"
	    return 0
	fi
    else
	res=$($smtcl -a $1 | grep 'Device Model:')
	res=${res/Device Model: }
	if [ -z "$res" ] ; then
	    res=$($smtcl -a $1 | grep 'Product:')
	    res=${res/Product: }
	fi
        # remove leading spaces
#	res=${res##* }
    fi
    # no internal spaces
    res=${res// /}
    res=${res// /-}


    [[ $res ]] && echo "$res" || echo "UNKNOWN"
    return 0
}
getserial() {
    res=""
    if [ "$os" == "Linux" ] ; then
	smtcl=$(findSmartctl)
	if [ "${smtcl/smartctl}" == "$smtcl" ] ; then
	#don't have smartctl, maybe we can use hdparm
	    hdp=$(which hdparm)
	    if [ "${hdp/hdparm}" != "${hdp}" ] ; then
		res=$(hdparm -I $1 2>/dev/null | grep -i 'Serial number:')
	    fi
	else
	    res=$($smtcl -a $1 | grep -i 'Serial number:')
	fi
	res=${res,,} # lower case
	res=${res/serial number: }
#	res=$(echo $res | tr 'a-z' 'A-Z') # upper case
	res=${res^^} # upper case
    fi
    if [ "$os" == "FreeBSD" ] ; then
	smtcl=$(findSmartctl)
	if [ "${smtcl/smartctl}" == "$smtcl" ] ; then
	    # XXX don't have smartctl, try camcontrol
	    cmc=$(which camcontrol)
	    if [ "${cmc/camcontrol}" != "${cmc}" ] ; then
		sd=$1
		sd=${sd#/dev/}
		res=$(camcontrol inquiry $sd -S 2>/dev/null)
	    fi
	else
	    res=$($smtcl -a $1 | grep -i 'Serial number:')

	    # XXX smartctl failed and old mfi driver, we can try mfiutil
	    if [ -z "${res}" -a "${1#/dev/mfisyspd}" != "$1" ]; then
		mfiutil=$(findMfiutil)
		if [ -n "${mfiutil}" ]; then
		    dn=${1#/dev/mfisyspd}
		    res=$($mfiutil show drives | grep "^ *${dn} ")
		    res=${res##*serial=}
		    res=${res%%> *}
		fi
	    fi

	    res=${res,,} # lower case
	    res=${res/serial number: }
#	    res=$(echo $res | tr 'a-z' 'A-Z') # upper case
	    res=${res^^} # upper case
	fi
    fi
    [[ $res ]] && echo "$res" || echo "UNKNOWN"
    return 0
}
getwcache() {
    res="NA"
    if [ "$os" == "Linux" ] ; then
	smtcl=$(findSmartctl_getopt)
	if [ "${smtcl/smartctl}" == "$smtcl" ] ; then
	    # don't have smartctl, maybe we can use hdparm
	    hdp=$(which hdparm)
	    if [ "${hdp/hdparm}" != "${hdp}" ] ; then
		x=$(hdparm -W $1 2>/dev/null | grep -i 'write-caching')
		if [ "${x/on}" != "$x" ] ; then
		    res="enabled"
		elif [ "${x/off}" != "$x" ] ; then
		    res="disabled"
		    hdparm -W1 $1  > /dev/null 2>&1
		fi
	    fi
	else
	    x=$($smtcl --get=wcache $1 | grep -i Write | grep -i cache)
	    if [ "${x/Enabled}" != "$x" ] ; then
		res="enabled"
	    elif [ "${x/Disabled}" != "$x" ] ; then
		res="disabled"
		$smtcl --set=wcache,on $1 > /dev/null 2>&1
	    else
		res="WCE_not_supported"
	    fi
	fi
    elif [ "$os" == "FreeBSD" ] ; then
	# try camcontrol first
	if [ "$osrel" == "10" ] ; then
	    cmc=$(which camcontrol10)
	else
	    cmc=$(which camcontrol)
	fi
#set -x
	if [ "${cmc/camcontrol}" != "${cmc}" ] ; then
	    sd=${1#/dev/}		
	    x=$($cmc modepage $sd -m8 2>/dev/null | grep WCE)
	    if [ "${x/1}" != "$x" ] ; then
		res="enabled"
	    elif [ "${x/0}" != "$x" ] ; then
		res="disabled"
		printf " %s:%s\n"  "Setting WCE" "$sd" >> ${logfile4tb}
		echo "WCE: 1" | $cmc modepage $sd -m8 -P3 -e > /dev/null 2>&1
	    fi
	fi
#set +x
	# don't have don't have camcontrol or it did not work, try smartctl
	if [ "$res" == "NA" ] ; then
	    smtcl=$(findSmartctl_getopt)
	    if [ "${smtcl/smartctl}" != "$smtcl" ] ; then
		x=$($smtcl --get=wcache $1 | grep -i Write | grep -i cache)
		if [ "${x/Enabled}" != "$x" ] ; then
		    res="enabled"
		elif [ "${x/Disabled}" != "$x" ] ; then
		    res="disabled"
		    $smtcl --set=wcache,on $1 > /dev/null 2>&1
		fi
	    fi
	fi
    else
	echo "try to get WCE unknown OS $os"
	return 0
    fi
    [[ $res ]] && echo "$res" || echo "UNKNOWN"
    return 0
}
getbpers() { 
    hdname=$1
    case $os in
	Linux )
	    hdname=${hdname##*/}
	    res=$(dmesg | grep "logical blocks" | grep $hdname)
	    if [ -z "$res" ] ; then
		res=$(dmesg | grep "hardware sectors" | grep $hdname)
	    fi
	    res=${res%%-byte *}
	    res=${res##*] }
	    res=${res#* }
	    [[ $res ]] || res=512
	    ;;
	FreeBSD )
	    hdname=${hdname##*/}
	    res=$(grep sectors /var/run/dmesg.boot | grep $hdname)
	    if [ -z "$res" ] ; then
		hdnamex=${hdname/ad/ada}
		res=$(grep sectors /var/run/dmesg.boot | grep $hdnamex)
		if [ -z "$res" ] ; then
		    echo 512
		    return 0
		fi
	    fi
	    if [ "$res" == "${res/sectors)}" ] ; then
		# this format: "da0: 140014MB (286749480 512 byte sectors: 255H 63S/T 17849C)"
		res=${res%%byte sectors*}
		res=${res##*(}
		res=${res#* }
	    else
		# this format: "mfid0: 5869927MB (12021612416 sectors) RAID volume '' is optimal"
		# just assume 512
		res=512
	    fi
	    [[ $res ]] || res=512
	    ;;
	* ) echo "$FUNCNAME internal error"
	(( $diskcheck_standalone )) && exit 1 || return 1
	    ;;
    esac
    echo $res
    return 0
}

getsectors() { 
    hdname=$1
    case $os in
	Linux )
	    hdname=${hdname##*/}
	    res=$(dmesg | grep "logical blocks" | grep $hdname)
	    if [ -z "$res" ] ; then
		res=$(dmesg | grep "hardware sectors" | grep $hdname)
	    else
		res=${res%%-byte *}
	    fi
	    res=${res##*] }
            res=${res%% *}
	    [[ $res ]] || res=0
	    ;;
	FreeBSD )
	    hdname=${hdname##*/}
	    res=$(grep sectors /var/run/dmesg.boot | grep $hdname)
	    if [ -z "$res" ] ; then
		# XXX fixme right
		case $hdname in
		    ad4 ) hdnamex="ada0" ;;
		    ad6 ) hdnamex="ada1" ;;
		    ad8 ) hdnamex="ada2" ;;
		    ad10 ) hdnamex="ada3" ;;
		    ad12 ) hdnamex="ada4" ;;
		    ad14 ) hdnamex="ada5" ;;
		    ad16 ) hdnamex="ada6" ;;
		    ad18 ) hdnamex="ada7" ;;
		    nvme* ) hdnamex="nvd${hdname##nvme}" ;;
		    * )   hdnamex=$hdname ;;
		esac
		res=$(grep sectors /var/run/dmesg.boot | grep $hdnamex)
		if [ -z "$res" ] ; then
		    res=$(grep ${hdname} /var/run/dmesg.boot)
		    if [ -z "$res" ] ; then
			echo 0
			return 0
		    fi
		    # fake and go by the size 
		    res=${res%%MB*}
		    res=${res##* }
		    echo $(( $res * 2048 )) # assume 512 byte sectors
		    return 0
		fi
	    fi
	    if [ "$res" == "${res/sectors)}" ] ; then
	        # this format: "da0: 140014MB (286749480 512 byte sectors: 255H 63S/T 17849C)"
		res=${res%%byte sectors*} # truncate 'byte sectors' to end
		res=${res##*(} # chop off begining to sector number
		res=${res%% *} # get rid of everthing after the space
	    else
		# this format: "mfid0: 5869927MB (12021612416 sectors) RAID volume '' is optimal"
		res=${res%%sectors)*}
		res=${res##*(}
		res=${res%% *}
	    fi
	    [[ $res ]] || res=0
	    ;;
	* ) echo "$FUNCNAME internal error"
	(( $diskcheck_standalone )) && exit 1 || return 1
	    ;;
    esac
    echo $res
    return 0
}


getwspeed() {
    hdname=$1

    # XXX NVME hack
    [[ "$os" == "FreeBSD" ]] && hdname=${hdname/nvme/nvd}

    # disk is mounted somewhere then don't do the raw write speed
    canwe=$(df | grep $hdname)
    [[ $canwe ]] && { echo mounted; return 0; }

    dd=$USE_DD
    [[ -x $dd ]] || { echo "$dd"; return 0; }
    args=$(ddargs)

    add_on_exit 'rm -f /tmp/ddresultw'
    [[ -n "$TDD_DD" ]] && export TDD_DD
    $dd if=/dev/zero of=$hdname $args >/tmp/ddresultw 2>&1
    res=$(grep bytes /tmp/ddresultw)

    echo $(parsedd $res)
    return 0
}

getrspeed() { 
    hdname=$1

    # XXX NVME hack
    [[ "$os" == "FreeBSD" ]] && hdname=${hdname/nvme/nvd}

    # do we have a working dd
    dd=$USE_DD
    [[ -x $dd ]] || { echo "$dd"; return 0; }
    args=$(ddargs)

    add_on_exit 'rm -f /tmp/ddresultr'
    [[ -n "$TDD_DD" ]] && export TDD_DD
    $dd of=/dev/null if=$hdname $args >/tmp/ddresultr 2>&1
    res=$(grep bytes /tmp/ddresultr)

    echo $(parsedd $res)
    return 0
}

parsedd() {
    if [ "$os" == "Linux" ] ; then 
	# linux dd returns 9 positional elements
	if [ $# -lt 9 ] ; then
	    x="DDparseERR"
	else
	    y=$9
	    if [ $y != ${y/GB} ] ; then
		# bash only does int arithmetic
		x=$(echo $8 | awk '{print int($1 * 1024)}')
	    elif [ $y != ${y/MB} ] ; then
		x=$8
	    else
		x="ParseddError"
	    fi
	fi
    elif [ "$os" == "FreeBSD" ] ; then 
	if [ $# -lt 8 ] ; then
	    x="DDtimeinERR"
	else
            x=$7
	    x=${x#(}
	    y=$8
	    # FreeBSD seems to return val in bytes/sec, check and convirt to Mb/sec
	    if [ "$y" == "bytes/sec)" ] ; then
		x=$(( $x / 1048576 ))
	    fi
	fi
    fi
    echo $x
    return 0
}

main_diskcheck $@

(( $diskcheck_standalone )) && exit 0 || return 0
