#!/sbin/openrc-run
# SPDX-License-Identifier: MIT

description="Check and backup GP1 filesystem for at-log"

depend() {
	after dev-mount cryptsetup
	before localmount
}

recreate_mount_atlog() {
	# vars: dev, copy_dev
	ewarn "Filesystem in $dev was corrupt, recreating it"

	if [ -n "$copy_dev" ]; then
		dd if="$dev" of="$copy_dev" bs=1M conv=fsync status=none
		eerror "Error backuping $dev to $copy_dev" || return
	fi

	mkfs.vfat -n ATLOG "$dev" >/dev/null \
		|| eerror "Error in re-formating $dev" \
		|| return

	mount /var/at-log \
		|| eerror "Error mounting re-created at-log" \
		|| return

	abos-ctrl atlog-write fsck_atlog \
			"$dev was broken, recreated${copy_dev:+ after backup to $copy_dev}" \
		|| eerror "Error initializing at-log"
}

check_fat_fstype() {
	local expect="$1" offset="$2"
	[ "$(dd if="$dev" bs=5 count=1 skip="$offset" iflag=skip_bytes status=none 2>/dev/null)" = "$expect" ]
}

check_fat_dirtybit() {
	local offset="$1"
	if [ "$(dd if="$dev" bs=1 count=1 skip="$offset" status=none | xxd -p)" = "01" ]; then
		ewarn "Clearing dirty bit of $dev. This is sign of unclean shutdown"
		unclean=1
		dd if=/dev/zero of="$dev" bs=1 count=1 seek="$offset" status=none
	fi
}

check_mount_atlog() {
	local dev copy_dev=""

	if mountpoint -q /var/at-log; then
		ewarn "at-log is already mounted, skipping check"
		return
	fi

	dev="$(awk '$2 == "/var/at-log" { print $1; exit; }' /etc/fstab)"
	# skip if not set
	[ -n "$dev" ] && [ -e "$dev" ] || return 0
	if [ "${dev%gp1}" != "$dev" ]; then
		copy_dev="${dev%1}2"
	fi

	# check dirty bit first.
	# busybox's mkfs.fat creates a FAT32 filesystem...
	if check_fat_fstype FAT32 82; then
		check_fat_dirtybit 65
	# And dosfstools' mkfs.vfat creates a FAT12 filesystem...
	elif check_fat_fstype FAT12 54; then
		check_fat_dirtybit 37
	fi

	# if dosfstools' fsck is available run it first
	if command -v fsck.vfat >/dev/null && ! fsck.vfat -n "$dev" >/dev/null; then
		recreate_mount_atlog
		return
	fi

	# Recreate fs if mount fails
	if ! mount /var/at-log; then
		recreate_mount_atlog
		return
	fi

	# ... and also if file is not readable
	if [ -e /var/at-log/atlog ] && ! cat /var/at-log/atlog > /dev/null; then
		umount /var/at-log
		recreate_mount_atlog
	fi
}

ext4_journal_needs_recover() {
	# vars: dev

	# pretend it needs recovery if not ext? below check index comes from file's magic db
	# (final mount /var/log will fail anyway and recreate fs)
	# [ "$(dd if="$dev" bs=2 count=1 skip=$((0x438)) iflag=skip_bytes status=none 2>/dev/null \
	#      | xxd -p)" = 53ef ]

	features=0x$(dd if="$dev" bs=1 count=1 skip=$((0x460)) iflag=skip_bytes status=none | xxd -p)
	[ "$((features & 4))" = 4 ]
}

recreate_mount_varlog() {
	# vars: dev
	ewarn "Filesystem in $dev was corrupt, recreating it."

	# keep one backup in appfs?

	dd if=/dev/zero of="$dev" bs=32k count=1 status=none
	mkfs.ext4 -q -L logs "$dev" \
		|| eerror "Failed to recreate /var/log fs" \
		|| return

	mount /var/log \
		|| eerror "Error mounting re-created /var/log" \
		|| return

	abos-ctrl atlog-write fsck_atlog "$dev was broken, recreated" \
		|| eerror "Error logging /var/log recreation to at-log"
}

check_mount_varlog() {
	local dev

	if mountpoint -q /var/log; then
		ewarn "/var/log is already mounted, skipping check"
		return
	fi

	dev="$(awk '$2 == "/var/log" && $3 == "ext4" { print $1; exit; }' /etc/fstab)"
	# skip if not in fstab or not ext4
	[ -n "$dev" ] && [ -e "$dev" ] || return 0

	# 'needs_recovery' flag is cleared by fsck, but remember for unclean shutdown log
	if ext4_journal_needs_recover; then
		ewarn "$dev was not unmounted properly, running fsck"
		unclean=1
		output=$(fsck -y "$dev" 2>&1)
		case "$?" in
		0) ;; # no error
		1|2) # errors corrected, just output log?
			echo "$output"
			;;
		*) # fsck failed
			echo "$output"
			recreate_mount_varlog
			return
			;;
		esac
	fi

	if ! mount /var/log; then
		recreate_mount_varlog
	fi
}

start() {
	local unclean="" rc=0

	ebegin "Checking log filesystems"

	check_mount_atlog || rc=$?
	check_mount_varlog || rc=$?

	if [ -n "$unclean" ]; then
		abos-ctrl atlog-write fsck_atlog "Booting after unclean shutdown"
	fi

	eend "$rc"
}
