#!/bin/sh
# SPDX-License-Identifier: MIT

error() {
	echo "Error: $*" >&2
	logger -t "swupdate-auto-update[$$]" "error: $*"
	exit 1
}

info() {
	echo "$*"
	logger -t swupdate-auto-update "$*"
}

# robustly lock a file while allowing cleanup
LOCKFILE=/var/lock/swupdate-usb-lock
lock() {
	local FD_INO FILE_INO

	# fd cannot be put in variable without eval, keep this simple.
	if [ -e "/proc/$$/fd/8" ]; then
		info "fd 8 already taken, overwriting it: $(ls -l /proc/$$/fd/8)"
	fi

	while :; do
		exec 8<>"$LOCKFILE" || error "Could not open $LOCKFILE"
		if ! flock -n 8; then
			# We didn't get lock, stop if another process waits
			# for the same device.
			# Note this is not 100% reliable (we might miss a lock file),
			# but this is fine
			if grep -qx "$DEV" /proc/self/fd/8 2>/dev/null; then
				exit 0
			fi
			# append ourselves. If we come back here it
			# is a new file and content was removed.
			echo "$DEV" >> /proc/self/fd/8
			flock 8 || error "Could not lock $LOCKFILE"
		fi

		# Check if file has changed since opening
		# if it has, drop lock and try again
		FD_INO=$(stat -c "%i" -L "/proc/self/fd/8")
		FILE_INO=$(stat -c "%i" "$LOCKFILE" 2>/dev/null)

		[ "$FD_INO" = "$FILE_INO" ] && break

		exec 8<&-
	done

	# we have lock, note device name
	echo "$DEV" > "$LOCKFILE"
}
unlock() {
	rm -f "$LOCKFILE"
	exec 8<&-
}

probe_disk_fstype() {
        local src="$1" fstype

        # store in a variable to return failure if empty
        fstype="$(blkid "$src" | LANG=C sed -ne 's/.*\bTYPE="\([^"]*\)\".*/\1/p')"

        if [ -z "$fstype" ]; then
                return 1
        fi
        echo "$fstype"
}

update_unshared() {
	local DEV="$1" TMPDIR

	[ "$(findmnt -nr -o ID /)" != "$(findmnt -nr --task 1 -o ID /)" ] \
		|| error "Running in pid mount namespace, aborting"

	fstype="$(probe_disk_fstype "/dev/$DEV" 2>/dev/null)"
	case "$fstype" in
	ext2|ext3|ext4) fstype=ext4;;
	btrfs|vfat|exfat) ;;
	"") error "Could not probe fstype for $DEV";;
	*) error "Unsupported filesystem $fstype for $DEV";;
	esac
	info "Mounting $DEV on /mnt in private namespace"
	mount -o ro -t "$fstype" "/dev/$DEV" "/mnt" || error "mount failed"

	for f in /mnt/*.swu; do
		[ -e "$f" ] || continue
		info "Trying update $f"
		TMPDIR=$(mktemp -d "/var/tmp/swupdate-usb.XXXXXX") \
			|| error "Could not create swupdate-usb tmp dir"

		TMPDIR="$TMPDIR" SWUPDATE_USB_SWU="$f" swupdate -i "$f"

		find "$TMPDIR" -xdev -delete 2>/dev/null
	done
}

update() {
	local DEV="$1" rootdev

	# running from udev somehow unsets PATH, export it back
	export PATH="${PATH:-/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin}"

	# Do not run on first boot
	[ -e /etc/init.d/firstboot-atmark ] && return
	# or partitions on system disk...
	rootdev="$(findmnt -nr --evaluate -o SOURCE /live/rootfs \
		|| findmnt -nr --evaluate -o SOURCE /)"
	rootdev="${rootdev#/dev/}"
	rootdev="${rootdev%[0-9]}"
	case "$rootdev" in
	mmcblk*) rootdev="${rootdev%p}";;
	esac
	[ "${DEV#"$rootdev"}" != "$DEV" ] && return
	# or if it was added to fstab (-f = --fake)...
	mount -f "/dev/$DEV" 2>/dev/null && return

	# avoid running in parallel
	lock
	trap unlock EXIT

	unshare -m "$0" unshared "$DEV"
}


if [ "$1" = "unshared" ]; then
	shift
	update_unshared "$@"
	exit
fi

[ -e "/dev/$1" ] || error "Usage: $0 device-name-in-/dev"


# run update in background to not block udev or get update killed
# eudev checks for stdout/stderr pipes being still open as well as
# process return status
if ! tty >/dev/null; then
	exec >/dev/null 2>/dev/null
fi
update "$@" &
