#!/bin/sh
#
# $Id$
#

# PROVIDE: mountsafe
# REQUIRE: mountcritlocal zfs
# BEFORE: FILESYSTEMS
# KEYWORD: nojail

# Define these mountsafe_* variables in one of these files:
#	/etc/rc.conf
#	/etc/rc.conf.local
#	/etc/rc.conf.d/mountsafe
#
# mountsafe_fs=""		# Whitespace-separated list of safe file systems.
# mountsafe_fsck_y="YES"	# Set to YES to do fsck -y on all safe file
#				# systems if the initial preen fails.
# mountsafe_fsck_y<fs>="YES"	# Set to YES to do fsck -y on selected file
#				# system if the initial preen fails.
# mountsafe_mailto="<e-mail>"	# Reported to <e-mail>
# mountsafe_stat_enable="YES"	# Set to YES to gather statistics of reboots.
# mountsafe_stat_num_wtmp=<num>	# Number of recent wtmp logs to get reboot
#				# statistics.
# mountsafe_stat_num_log_lines=<num>	# Number of context lines from log-files
#				# before reboot which will be printed.
#
# Example:
# mountsafe_fs="/place /opt/www /test"
# mountsafe_fsck_y_place="YES"
# mountsafe_fsck_y_opt_www="YES"
# mountsafe_mailto="sa@somedomain.tdl"


. /etc/rc.subr

name="mountsafe"
start_cmd="${name}_start"
stop_cmd=":"

# Set some defaults. Do not change these default values here.
: ${mountsafe_fs:=""}
: ${mountsafe_fsck_y:="NO"}
: ${mountsafe_mailto:="search-maintenance@yandex-team.ru"}
: ${mountsafe_unknown_hostname:="UNKNOWN"}
: ${mountsafe_stat_enable:="YES"}
: ${mountsafe_stat_num_wtmp:=2}
: ${mountsafe_stat_num_log_lines:=3}
: ${mountsafe_journal:="AUTO"}

mountsafe_logs=""
mountsafe_delim=""

add_to_ms_logs()
{
	mountsafe_logs="${mountsafe_logs}${mountsafe_logs:+ }$*"
}

get_statistics()
{
	local _wtmp_log_mask _wtmp_logs _wtmp_log _log _since

	if ! checkyesno mountsafe_stat_enable; then
		return 0
	fi

	echo -n "Get statistics of reboots"

	case $(uname -r) in
		# Log format depends on FreeBSD version
		[5-8].*) _wtmp_log_mask="/var/log/wtmp*" ;;
		9.*|10.*) _wtmp_log_mask="/var/log/utx.log*" ;;
	esac

	_wtmp_logs=$(ls -1t ${_wtmp_log_mask} 2>/dev/null | \
		head -n ${mountsafe_stat_num_wtmp})

	_log=$(mktemp -t ${name})

	_since=$(stat -f %SB -t %F \
		$(echo $_wtmp_logs | awk '{print $NF}'))

	for _wtmp_log in $_wtmp_logs; do
		last -f $_wtmp_log | \
			grep -E "^(reboot|shutdown|(boot|shutdown) time)"
	done | awk -v lines="${mountsafe_stat_num_log_lines}" \
		-v since="${_since}" '
			/(reboot|boot time)/ {
				reboot[++n] = $0
				# We need time columns from last(1)
				# output to find appropriate logs in
				# messages. But last(1) prints only
				# approximate time so we do not use
				# minutes column from last(1) output.
				h = $NF
				sub(/:.*/, "", h)
				reboot_time[n] = sprintf("%s %2d %s", \
					$(NF-2), $(NF-1), h)
			}
			/shutdown( time)?/ { delete reboot[n] }
			END{
				for (i in reboot) {
					num_reboots++
					reboot_list = sprintf("%s%s\n", \
						reboot_list, reboot[i])
					if (time_re) time_re = \
						sprintf("%s|%s", \
						time_re, reboot_time[i])
					else time_re = reboot_time[i]
				}
				if (num_reboots > 0) {
					printf "\n"
					printf "===> List of incorrect " \
						"reboots since %s "\
						"[%d one(s)]\n%s\n", \
						since, num_reboots, reboot_list
					grep_cmd = sprintf("bzgrep -hEB %d " \
						"\"^(%s).*boot file\" " \
						"/var/log/messages*", \
						lines, time_re)
					printf "===> Logs before reboots\n"
					system(grep_cmd)
				}
			}
		' >> $_log 2>&1

	add_to_ms_logs $_log

	num_reboots=$(sed -nEe "s/^===>.*\[([0-9]+).*\]/\1/p" $_log)

	echo "."
}

mountsafe_fs()
{
	local _fs _fs_name _fsck_y _log _ret_code

	if [ -z "$1" ]; then
		err 1 "Not determined file system. Exiting .."
	fi

	_fs="$1"
	_log=$(mktemp -t ${name})

	_fs_name=$(echo ${_fs} | sed -e 's#/#_#g')
	eval _fsck_y=\"\$${name}_fsck_y${_fs_name}\"
	: ${_fsck_y:="NO"}

	script -q $_log fsck -p $_fs
	_ret_code=$?

	echo -n "${mountsafe_delim} ${_fs} .. "

	case $_ret_code in
		0)
			if mount $_fs >> $_log 2>&1; then
				echo -n "mount ok"
			else
				echo -n "mount failed"
			fi
			;;
		*)
			if checkyesno mountsafe_fsck_y || checkyesno _fsck_y
			then
				echo -n "preen failed, "
				script -a -q $_log fsck -f -y $_fs
				case $? in
					0)
						echo -n "automatic check ok, "
						if mount $_fs >> $_log 2>&1
						then
							echo -n "mount ok"
						else
							echo -n "mount failed"
						fi
						;;
					*)
						echo -n "automatic check failed"
						;;
				esac
			else
				echo -n "automatic check failed"
			fi
			;;
	esac

	add_to_ms_logs $_log
}

try_journaling_fs()
{
	local _fs _mounted

	if [ $# -ne 1 ]; then
		return 1
	fi

	_fs="$1"
	if tunefs -p "${_fs}" 2>&1 | \
	    grep -q "soft update journaling: .* disabled"; then
		_mounted="NO"
		if mount | grep -qw "${_fs}"; then
			if umount "${_fs}"; then
				_mounted="YES"
			else
				return 1
			fi
		fi
		if tunefs -j enable $_fs; then
			echo "Soft update journaling turned on: ${_fs}."
		else
			warn "Soft update journaling failed on: ${_fs}."
		fi
		if checkyesno _mounted; then
			mount "${_fs}"
		fi
	fi
}

journaling()
{
	local _dev _mp _type _junk _fs

	case $mountsafe_journal in
	[Aa][Uu][Tt][Oo])
		while read _dev _mp _type _junk; do
			case ":${_dev}" in
			:#*|:)
				continue
				;;
			esac
			if [ "${_type}" = "ufs" -a "${_mp}" != "/" ]; then
				try_journaling_fs "${_mp}"
			fi
		done < /etc/fstab
		;;
	[Nn][Oo])
		;;
	*)
		for _fs in $mountsafe_journal; do
			try_journaling_fs "${_fs}"
		done
		;;
	esac
}

mountsafe_start()
{
	local _safe_fs _subj

	echo -n "Mounting safe local file systems:"
	for _safe_fs in $mountsafe_fs; do
		mountsafe_fs $_safe_fs
		mountsafe_delim=";"
	done
	echo "."

	if [ `sysctl -n kern.osreldate` -gt 900500 ]; then
		journaling
	fi

	get_statistics

	_subj="FSCK on ${hostname:-${mountsafe_unknown_hostname}}"
	_subj="${_subj}${num_reboots:+ [$num_reboots reboot(s)]}"

	if [ -n "${mountsafe_logs}" ]; then
		cat $mountsafe_logs | mail -v -s "${_subj}" $mountsafe_mailto
		rm -f $mountsafe_logs 2>/dev/null
	fi
}

load_rc_config $name
run_rc_command "$1"
