#!/bin/sh
#
# $NetBSD: fixsb,v 1.12 2004/12/30 09:32:13 dsainty Exp $
#

# PROVIDE: fixsb
# REQUIRE: localswap
# BEFORE: fsck

$_rc_subr_loaded . /etc/rc.subr

name="fixsb"
rcvar=$name
start_cmd="fsck_start"
stop_cmd=":"

# This rc.d script attempts to correct problems with ffs1 filesystems
# which may have been introduced by booting a netbsd-current kernel
# from between April of 2003 and January 2004. For more information
# see <http://mail-index.NetBSD.org/current-users/2004/01/11/0022.html>
# This script was developed as a response to NetBSD pr install/25138
# Additional prs regarding the original issue include:
#  bin/17910 kern/21283 kern/21404 port-macppc/23925 port-macppc/23926
#

fstab=/etc/fstab
#verbose=1

verbose()
{
	if [ -n "${verbose}" ]; then
		echo "$@" 1>&2
	fi
}

# This reads a field from the ffs superblock
# at the specified offset and length in bytes
# from the start of the superblock
readsbfield()
{
	# The first dd command reads in the superblock using block aligned i/o
	# so that it works on a raw character device.  The second dd extracts
	# the exact field from the superblock that we wish to read.
	(dd if="$1" bs=8192 count=1 skip=1 | 
	 dd bs=1 skip="$2" count="$3" | cat -v) 2> /dev/null
}

# This shell function extracts the `ffs superblock' of the file
# provided as its argument and tests for the following condition:
# ((fs_magic == FS_UFS1_MAGIC) || fs_magic == FS_UFS1_MAGIC_SWAPPED) &&
# (fs_sbsize == fs_maxbsize) && !(fs_old_flags & FS_FLAGS_UPDATED)
#
# return status is based on status of last filesystem checked:
#   0 for botched superblock
#   1 for filesystem does not appear to be ffs1 filesystem
#   3 for ok fslevel 3 filesystem
#   4 for ok fslevel 4 filesystem
#
# dbj@NetBSD.org 2004-04-12T18:15:06-0400
check_part()
{
	verbose -n "Checking $1 ... "

	# The following are 'cat -v' representations of the ffs1 magic number:
	fsmagicn="^@^A^YT"  # 0x00011954 FS_UFS1_MAGIC
	fsmagics="T^Y^A^@"  # 0x54190100 FS_UFS1_MAGIC_SWAPPED

	# First we extract the superblock magic number field.
	# We use cat -v to avoid having binary data in shell strings.
	magic="$(readsbfield "$1" 1372 4)"

	# Then we check if the magic number is valid (swapped or unswapped):
	if [ "${magic}" != "${fsmagicn}" -a "${magic}" != "${fsmagics}" ]; then
		verbose "does not appear to be an ffs1 filesystem."
		return 1
	fi

	# Then we read fs_old_flags fields from disk
	# And check the value of its high bit.
	oldflags="$(readsbfield "$1" 211 1)"

	case "${oldflags}" in
	# Since the shell variable is the cat -v output, the
	# high bit is indicated in the variable with the prefix M-
	M-*)
		verbose "file system looks ok at fslevel 4."
		return 4
		;;
	esac

	# Then we read fs_bsize, fs_maxbsize fields from the disk:
	bsize="$(readsbfield "$1" 48 4)"
	maxbsize="$(readsbfield "$1" 860 4)"

	# Compare the fs_bsize with fs_maxbsize to see if they are the same
	if [ "${bsize}" != "${maxbsize}" ]; then
		verbose "file system looks ok at fslevel 3."
		return 3
	fi

	verbose "file system has botched superblock upgrade."
	return 0
}

# This extracts raw ufs partitions to be fsck'ed from the file ${fstab}
parse_fstab()
{
	for l in 1 2; do
		cat "${fstab}" 2> /dev/null | 
		while read d m t o b f err; do
			case "$d" in
			\#*)
				continue
				;;
			/dev/*)
				d="/dev/r${d#/dev/}"
				;;
			esac
			case "$t" in
			ffs|ufs)
				if [ "$f" = "$l" ]; then
					echo "$d"
				fi
				;;
			esac
		done
	done
}

stop_boot()
{
	# Terminate the process (which may include the parent /etc/rc)
	# if booting directly to multiuser mode.
	if [ "$autoboot" = "yes" ]; then
		kill -TERM $$
	fi
	exit 1
}

do_fsck()
{
	# During fsck ignore SIGQUIT

	fsck_ffs "$@"
	case $? in
	0)
		;;
	2)
		stop_boot
		;;
	4)
		echo "Rebooting..."
		reboot
		echo "Reboot failed; help!"
		stop_boot
		;;
	8)
		echo "Automatic file system check failed; help!"
		stop_boot
		;;
	12)
		echo "Boot interrupted."
		stop_boot
		;;
	130)
		stop_boot
		;;
	*)
		echo "Unknown error; help!"
		stop_boot
		;;
	esac
}

fsck_start()
{
	if [ -e /fastboot ]; then
		echo "Fast boot: skipping disk checks."
	else
		# During fsck ignore SIGQUIT
		trap : 3
		okfs=true

		echo -n "Checking for botched superblock upgrades:"
		for p in $(parse_fstab); do
			if check_part "$p"; then
				if $okfs; then
					echo
					okfs=false
				fi
				echo "Repairing partition $p"
				do_fsck -p -b 16 -c 4 "$p"
				do_fsck -p -c 3 "$p"
			fi
		done
		if $okfs; then
			echo " done."
		else
			echo "Superblock(s) updated successfully."
		fi
	fi
}

load_rc_config $name
run_rc_command "$1"
