#!/bin/sh

#hdd0.sh: hard_disk_refresh.sh
#hdd1.sh: hard_disk_read_raw_report_sector.sh
#hdd2.sh: hard_disk_check_filesystem.sh
#hdd3.sh: hard_disk_check_raw.sh
#hdd4.sh: hard_disk_wipe_with_sector_rellocation.sh
#==> hdd5.sh: hard_disk_locate_bad_sector_from_vfs_smartctl.sh
#in.sh  : initialize disk/gpart



# Minas Dasygenis, mdasyg@ieee.org
#
# hard_disk_locate_bad_sector_from_vfs_smartctl.sh
# ** ONLY FOR FREEBSD **
#
# Tool to locate the file that holds the bad block
# v0.99 2020/01/07 First Release. Works on my system.
#
# License: Free to copy/use it. Just do not change my name
# I am not responsible for any bad effect of running this script, 
# so do use it with extreme caution. 
# If you like this script you can just send me a thank you email or
# whatever you wish.
#
#
# Two operation modes:
# (a)
# Searching the log file for these lines:
# Jan  7 01:29:35 vlsi kernel: g_vfs_done():ufs/vlsi2zstorage[READ(offset=423260749824, length=131072)]error = 5
# __AND__
# parsing the output of the smartctl command
# (b) give a vfs block number as an argument
#
# Defaults to /dev/ada0 and first slie but you can edit this bellow to change disk
#
# Tested on FreeBSD 12
# Requires: smartmontools


#
#Variables to be changed:
#
disk=ada0
mypart=/dev/${disk}s1
prefix=.dasygenis_disk
smartctlargs=""

for param in atacam usbsunplus
do
	smartctl -d $param -i /dev/$disk > /dev/null 2>&1
	if [ $? -eq 0 ] ; then
		smartctlargs="-d $param"
		break
	fi

done

#Indicates whether we will speed up the proess with skipping sectors that we have seen
skip_same=1


#
# === Nothing to be changed bellow ===
#
hdd_sector_size=`smartctl $smartctlargs -i /dev/$disk | grep "Sector Size" | awk '{print $3}'`

if [ $# -eq 1 ] ; then
	sectors=$@
else
	echo "Locating g_vfs_done messages at /var/log/messages"
	
	sectorsT=`grep g_vfs_done /var/log/messages | sed 's@.*offset=\([0-9]*\).*$@\1@' | sort | uniq`
	filesystems=`grep g_vfs_done /var/log/messages | sed 's@.*g_vfs_done():\([a-zA-Z0-9/]*\).*@\1@g' | sort| uniq`

	for i in $sectorsT
	do
		thefilesystem=`grep g_vfs_done /var/log/messages | grep $i | sed 's@.*g_vfs_done():\([a-zA-Z0-9/]*\).*@\1@g' | sort| uniq | head -1`
		sector=`expr $i / $hdd_sector_size | bc`
		echo $thefilesystem | grep -q $disk
		if [ $? -eq 0 ] ; then
			echo "Adding Sector from g_vfs_done: [$sector] of filesystem [$thefilesystem]"
			sectors="$sectors $sector"
		else
			echo "The Sector from g_vfs_done: [$sector] of [$thefilesystem] does not belong to the filesystem under consideration [$disk]. Skipping it."
		fi
	done

	if [ ! -z "$sectors" ] ; then
		echo "g_vfs_done reported bad relative sectors: $sectors"
	fi

	sleep 1; echo -n 3 ; sleep 1 ; echo -n 2 ; sleep 1 ; echo 1 ; sleep 1; echo " "

	#Lets locate bad sectors from smart also
	sectorsSmart=`smartctl $smartctlargs -a /dev/$disk | grep 'read failure' | grep '^# [1-5]' | awk '{print $10}' | sort | uniq`
	echo "Adding sectors from smartctl: [$sectorsSmart]"

	gpart show /dev/$disk > /dev/null 2>&1
	if [ $? -ne 0 ] ; then
		#this is not a standard label, lets locate zfs?
		zpool list  | grep ONLINE | cut -f 1 -d' '
		if [ $? -eq 0 ] ; then
			tmp=`zpool list  | grep ONLINE | cut -f 1 -d' '`
			zpool status $tmp
		fi
	 echo "INCOMPLETE PATH--EXITING"
	 glabel list $disk
	 exit
	fi

	offset_slice=`gpart show /dev/$disk | grep freebsd | head | awk '{print $1}'`
	gpart show $mypart  | grep -v '=' > /tmp/$prefix.gpart

	for i in $sectorsSmart
	do
	while read line
	do
	offset=$i
	labeloffset=`echo $line | awk '{print $1}'`
	size=`echo $line | awk '{print $2}'`
	if [ -z $size ] ; then continue ; fi
	offset=`expr -- $offset - $labeloffset`
	offset=`expr -- $offset - $offset_slice`
	if [ -z "$offset" ] ; then continue ; fi
	if [ $offset -ge 0 -a $offset -le $size ] ; then
		#we found our solution
		label=`echo $line | awk '{print $3,$4,$5}'`
		sector=`expr $hdd_sector_size \* $offset`
		#echo "SmartCTL reports bad setors located at $label with offset $offset (at sector $sector)"
		sectors2="$sectors2 $offset"
	fi
	done < /tmp/$prefix.gpart
	done

	if [ ! -z $sectors2 ] ; then
		echo "smartctl reported bad relative LBA sectors: $sectors2"
	fi
fi


sectors="$sectors $sectors2"

if [ -z $sectors ] ; then
	echo "No bad sectors have been recorded to smartctl or /var/log/messages OR you did not edit correctl the first lines of this file"
	exit 
fi


if [ -e "$mypart" ] ; then
	disklabel $mypart 2>/dev/null | grep -v 'c:' | grep '[a-l]:' | awk '{print $1,$2}' > /tmp/$prefix.label
	echo "Total Bad Sectors for examination: $sectors"
	allexamined=""
	for sector in $sectors 
	do
	echo "=======> Locating sector $sector at $mypart"
	acc=0
	while read line
	do
	part=`echo $line | cut -f 1 -d" "`
	size=`echo $line | cut -f 2 -d" "`
	acc=`expr $acc + $size`
	if [ $acc -ge $sector ] ; then
		offset=`expr $acc - $sector`
		disklab=`echo ${mypart}${part} | tr -d ":"`
		echo "Bad sector $sector located at ${disklab} at offset $offset"
		if [ ! -f /tmp/$prefix.dump.${part} ] ; then
			dumpfs ${disklab} > /tmp/$prefix.dump.${part}
		fi
		mounted=`cat /tmp/$prefix.dump.${part}  | grep fsmnt | awk '{print $2}'`
		volname=`cat /tmp/$prefix.dump.${part}  | grep volname | awk '{print $2}'`
		echo "Mounted at $mounted | Volume name $volname"
		bsize=`cat /tmp/$prefix.dump.${part} | grep "^bsize" | awk '{print $2}'`
		fsize=`cat /tmp/$prefix.dump.${part} | grep "^fsize" | awk '{print $2}'`
		note="Could not get a value at fsize...continuing..."
		if [ -z "$fsize" ] ; then
			continue
		fi
		badblock=`echo "scale=1; $sector / $bsize " | bc`
		seen=`echo "$sector / $bsize " | bc`
		echo $allexamined | grep -q $seen 
		if [ $? -eq 0 -a $skip_same -eq 1 ] ; then
			echo "__File System Block $seen has been investigated before. Skipping it."
			continue
		fi
		allexamined="$allexamined $seen"
		echo "The bad sector is located at file system block $badblock"
		echo "findblk $sector" > /tmp/$prefix.BADblock
		echo "Trying to locate inode of file system badblock (size $bsize) $badblock, relative disk block $sector (size=$hdd_sector_size) (if none found, then it is not occupied)"
		fsdb -r ${disklab} < /tmp/$prefix.BADblock  > /tmp/$prefix.FSDB 2>&1
		line=`grep 'data block of inode' /tmp/$prefix.FSDB`
		if [ $? -eq 0 ] ; then
			inum=`echo $line | awk '{print $6}'`
			echo "***Finding file that holds the inum $inum on $mounted***"
			find $mounted -inum $inum
		else
			echo "*** Nothing Found ***"
		fi
		break #We located the partition, no need to keep examining
	fi #endif $acc -ge $sector
	done < /tmp/$prefix.label
	done
	rm /tmp/$prefix*
	exit
else
	echo $mypart does not exist
	exit
fi
