#!/bin/sh

# Uncomment below to see more logs
# set -x

BUSYBOX_MOUNT_OPTS="loop (a|)sync (no|)atime (no|)diratime (no|)relatime (no|)dev (no|)exec (no|)suid (r|)shared (r|)slave (r|)private (un|)bindable (r|)bind move remount ro"
NTFS_3G_MOUNT_OPTS="ro uid=[0-9]* gid=[0-9]* umask=[0-9]* fmask=[0-9]* dmask=[0-9]*"

message()
{
	echo "${ID:+"[$ID]: "}$@"
}

check_tool()
{
	TOOL=${1%% *}
	BR2_CONFIG=$2

	type $TOOL >/dev/null && return 0

	if grep -wq "ID=buildroot" /etc/os-release 2>/dev/null; then
		[ -n "$BR2_CONFIG" ] && \
			message "You may need to enable $BR2_CONFIG"
	else
		message "Missing tool: $TOOL"
	fi
	return 1
}

is_rootfs()
{
	[ $MOUNT_POINT = "/" ]
}

is_ro()
{
	mountpoint -q $MOUNT_POINT || return 1
	touch $MOUNT_POINT 2>/dev/null || return 0
	return 1
}

normalize_device()
{
	if is_rootfs; then
		DEV_NO="$(mountpoint -d /)"
		ROOT_DEV="$(find /sys/dev/ -name "$DEV_NO")"
		echo "/dev/$(basename "$(realpath "$ROOT_DEV")")"
		return
	fi

	case "$DEV" in
		""|tmpfs|proc|devtmpfs|devpts|sysfs|configfs|debugfs|pstore)
			return
			;;
		/dev/*)
			echo $DEV
			return
			;;
	esac

	# Try udev rules symlink
	if find /dev/block/by-name/$DEV 2>/dev/null; then
		return
	fi
	if find /dev/disk/*/$DEV 2>/dev/null; then
		return
	fi

	# Check partition name
	UEVENT=$(grep -l -m 1 "^PARTNAME=$DEV$" \
		/sys/class/block/*/uevent 2>/dev/null || true)
	if [ "$UEVENT" ]; then
		echo "/dev/$(basename $(dirname $UEVENT))"
		return
	fi

	# Check mtdblock name
	for d in $(ls /dev/ | grep mtdblock || true); do
		if grep -q "^$DEV$" /sys/block/$d/device/name; then
			echo "/dev/$d"
			return
		fi
	done

	# Check mtd
	if grep -q "\"$DEV\"$" /proc/mtd 2>/dev/null; then
		echo "/dev/$(grep "\"$DEV\"$" /proc/mtd | cut -d':' -f1)"
		return
	fi

	# Parse from blkid
	if blkid 2>/dev/null | grep -w "$DEV" | tail -n 1 | \
		grep -o "^[^:]*"; then
		return
	fi

	# Give up
	echo $DEV
}

prepare_ubi()
{
	# Only support ubi for mtd device
	if echo $DEV | grep -vq /dev/mtd; then
		message "$DEV is not a mtd device!"
		return 1
	fi

	[ "$PART_NO" ] || { message "No valid part number!" && return 1; }

	MTD_DEV=/dev/mtd${PART_NO}
	UBI_DEV=/dev/ubi${PART_NO}
	UBIVOL_DEV=/dev/ubi${PART_NO}_0
	UBIBLOCK_DEV=/dev/ubiblock${PART_NO}_0

	if [ "$FSGROUP" = ubifs ]; then
		DEV=$UBIVOL_DEV
	else
		DEV=$UBIBLOCK_DEV
	fi

	message "Preparing $DEV from $MTD_DEV"

	message "Remove ubi block device"
	if echo $DEV | grep -q ubiblock; then
		check_tool ubiblock BR2_PACKAGE_MTD_UBIBLOCK || return 1
		ubiblock -r $UBIVOL_DEV >/dev/null 2>/dev/null
	fi

	message "Detach ubi device"
	check_tool ubidetach BR2_PACKAGE_MTD_UBIDETACH || return 1
	ubidetach -p $MTD_DEV >/dev/null 2>/dev/null

	message "Attach ubi device"
	check_tool ubiattach BR2_PACKAGE_MTD_UBIATTACH || return 1
	ubiattach /dev/ubi_ctrl -m $PART_NO -d $PART_NO || return 1

	message "Check for valid volume"
	if [ ! -e $UBIVOL_DEV ]; then
		message "No valid ubi volume"
		return 1
	fi

	message "Create ubi block device"
	if echo $DEV | grep -q ubiblock; then
		check_tool ubiblock BR2_PACKAGE_MTD_UBIBLOCK || return 1
		ubiblock -c $UBIVOL_DEV || return 1
	fi

	return 0
}

format_ubifs()
{
	message "Formatting $MTD_DEV for $DEV"

	message "Remove ubi block device"
	if echo $DEV | grep -q ubiblock; then
		check_tool ubiblock BR2_PACKAGE_MTD_UBIBLOCK || return 1
		ubiblock -r $UBIVOL_DEV >/dev/null 2>/dev/null
	fi

	message "Detach ubi device"
	check_tool ubidetach BR2_PACKAGE_MTD_UBIDETACH || return 1
	ubidetach -p $MTD_DEV >/dev/null 2>/dev/null

	message "Format device"
	check_tool ubiformat BR2_PACKAGE_MTD_UBIFORMAT || return 1
	ubiformat -yq $MTD_DEV || return 1

	message "Attach ubi device"
	ubiattach /dev/ubi_ctrl -m $PART_NO -d $PART_NO || return 1

	message "Create ubi volume"
	check_tool ubimkvol BR2_PACKAGE_MTD_UBIMKVOL || return 1
	ubimkvol $UBI_DEV -N $PART_NAME -m || return 1

	message "Create ubi block device"
	if echo $DEV | grep -q ubiblock; then
		check_tool ubiblock BR2_PACKAGE_MTD_UBIBLOCK || return 1
		ubiblock -c $UBIVOL_DEV || return 1
	fi
}

remount_part()
{
	mountpoint -q $MOUNT_POINT || return

	if is_ro; then
		[ "$1" != ro ] || return
	else
		[ "$1" != rw ] || return
	fi

	mount -o remount,$1 $MOUNT_POINT
}

format_part()
{
	message "Formatting $DEV($FSTYPE)"

	case $FSGROUP in
		ext2)
			# Set max-mount-counts to 0, and disable the time-dependent checking.
			check_tool mke2fs BR2_PACKAGE_E2FSPROGS && \
			mke2fs -F -L $PART_NAME $DEV && \
			tune2fs -c 0 -i 0 $DEV
			;;
		vfat)
			# Use fat32 by default
			check_tool mkfs.vfat BR2_PACKAGE_DOSFSTOOLS_MKFS_FAT && \
			mkfs.vfat -I -F 32 -n $PART_NAME $DEV
			;;
		exfat)
			check_tool mkexfatfs BR2_PACKAGE_EXFAT_UTILS && \
			mkexfatfs -n $PART_NAME $DEV
			;;
		ntfs)
			# Enable compression
			check_tool mkntfs BR2_PACKAGE_NTFS_3G_NTFSPROGS && \
			mkntfs -FCQ -L $PART_NAME $DEV
			;;
		btrfs)
			check_tool mkfs.btrfs BR2_PACKAGE_BTRFS_PROGS && \
			mkfs.btrfs -f -L $PART_NAME $DEV
			;;
		f2fs)
			check_tool mkfs.f2fs BR2_PACKAGE_F2FS_TOOLS && \
			mkfs.f2fs -f -l $PART_NAME $DEV
			;;
		ubifs)
			format_ubifs
			;;
		jffs2)
			check_tool mkfs.jffs2 BR2_PACKAGE_MTD_MKFSJFFS2 && \
			mkfs.jffs2 -o $DEV 0x10000 --pad=0x400000 -s 0x1000 -n
			;;
		erofs|squashfs)
			message "It's pointness to format a read-only partition..."
			false
			;;
		auto)
			message "Unable to format a auto partition..."
			false
			;;
		*)
			message "Unsupported file system $FSTYPE for $DEV"
			false
			;;
	esac
}

format_resize()
{
	BACKUP=$1
	SRC=$(realpath $MOUNT_POINT)

	message "Format-resizing $DEV($FSTYPE)"

	message "Backup original data"
	cp -a "$SRC" "$BACKUP/" || return 1
	umount "$SRC" || return 1

	message "Format and mount rw"
	format_part || return 1
	mount_part || return 1
	remount_part rw

	message "Restore backup data"
	cp -a "$BACKUP/$SRC" $(dirname "$SRC") || return 1
}

resize_ext2()
{
	check_tool resize2fs BR2_PACKAGE_E2FSPROGS_RESIZE2FS || return 1

	resize2fs $DEV
}

resize_vfat()
{
	check_tool fatresize BR2_PACKAGE_FATRESIZE || return 1

	SIZE=$(fatresize -i $DEV | grep "Size:" | grep -o "[0-9]*$")

	# Somehow fatresize only works for 256M+ fat
	[ "$SIZE" -gt $((256 * 1024 * 1024)) ] && return 1

	MAX_SIZE=$(( $(cat $SYS_PATH/size) * 512))
	MIN_SIZE=$(($MAX_SIZE - 16 * 1024 * 1024))
	[ $MIN_SIZE -lt $SIZE ] && return 0 # Large enough!
	while [ $MAX_SIZE -gt $MIN_SIZE ];do
		# Somehow fatresize cannot resize to max size
		MAX_SIZE=$(($MAX_SIZE - 512 * 1024))

		# Try to resize with fatresize, not always work
		fatresize -s $MAX_SIZE $DEV && return
	done
	return 1
}

resize_ntfs()
{
	check_tool ntfsresize BR2_PACKAGE_NTFS_3G_NTFSPROGS || return 1

	echo y | ntfsresize -f $DEV
}

resize_btrfs()
{
	check_tool btrfs BR2_PACKAGE_BTRFS_PROGS || return 1

	btrfs filesystem resize max $DEV
}

resize_f2fs()
{
	check_tool resize.f2fs BR2_PACKAGE_F2FS_TOOLS || return 1

	resize.f2fs $DEV
}

resize_part()
{
	# Already resized
	if [ -f $MOUNT_POINT/.resized ]; then
		message "Already resized $DEV($FSTYPE)"
		return
	fi

	if [ -z "$FSRESIZE" ]; then
		message "No resize for $FSTYPE"
		return
	fi

	message "Resizing $DEV($FSTYPE)"

	# Online resize needs read-write
	remount_part rw
	if eval $FSRESIZE; then
		touch $MOUNT_POINT/.resized
		sync
		return
	fi

	message "Done with rootfs"
	is_rootfs && return

	message "Fallback to format resize"
	TEMP_BACKUP=$(mktemp -d)
	if format_resize $TEMP_BACKUP; then
		touch $MOUNT_POINT/.resized
		sync
	fi
	rm -rf $TEMP_BACKUP
}

convert_mount_opts()
{
	# Accept all opts by default for standard mount tool
	if [ -z "$@" ] && ! mount --help 2>&1 | grep -q BusyBox; then
		echo $OPTS
		return
	fi

	# Filter out unsupported opts
	for opt in ${@:-$BUSYBOX_MOUNT_OPTS}; do
		echo ${OPTS//,/ } | xargs -n 1 | grep -oE "^$opt$"
	done | tr "\n" ","
}

prepare_part()
{
	# Ignore external storages
	case "$MOUNT_POINT" in
		/mnt/* | /media/*) return 1 ;;
	esac

	DEV="$(realpath "$(normalize_device | head -n 1)" 2>/dev/null)"
	[ "$DEV" ] || return 1

	# Only accept block or char device
	[ -b "$DEV" -o -c "$DEV" ] || return 1

	message "Handling $DEV $MOUNT_POINT $FSTYPE $OPTS $PASS"

	SYS_PATH=$(echo /sys/class/*/${DEV##*/})
	if [ -f "$SYS_PATH/name" ]; then
		PART_NAME=$(cat $SYS_PATH/name)
	else
		PART_NAME=$(grep PARTNAME ${SYS_PATH}/uevent | cut -d '=' -f 2)
	fi
	PART_NAME=${PART_NAME:-${DEV##*/}}
	PART_NO=$(echo $DEV | grep -oE "[0-9]*$")

	case $FSTYPE in
		ext[234])
			FSGROUP=ext2
			FSCK="fsck.$FSTYPE -y"
			FSCK_CONFIG=BR2_PACKAGE_E2FSPROGS_FSCK
			FSRESIZE=resize_ext2
			;;
		msdos|fat|vfat)
			FSGROUP=vfat
			FSCK="fsck.vfat -y"
			FSCK_CONFIG=BR2_PACKAGE_DOSFSTOOLS_FSCK_FAT
			FSRESIZE=resize_vfat
			;;
		exfat)
			FSGROUP=exfat
			FSCK="exfatfsck -y"
			FSCK_CONFIG=BR2_PACKAGE_EXFAT_UTILS
			unset FSRESIZE
			;;
		ntfs)
			FSGROUP=ntfs
			FSCK=ntfsfix
			FSCK_CONFIG=BR2_PACKAGE_NTFS_3G_NTFSPROGS
			FSRESIZE=resize_ntfs
			;;
		btrfs)
			FSGROUP=btrfs
			FSCK="btrfs check --repair"
			FSCK_CONFIG=BR2_PACKAGE_BTRFS_PROGS
			FSRESIZE=resize_btrfs
			;;
		f2fs)
			FSGROUP=f2fs
			FSCK="fsck.f2fs -y"
			FSCK_CONFIG=BR2_PACKAGE_F2FS_TOOLS
			FSRESIZE=resize_f2fs
			;;
		ubi|ubifs)
			FSTYPE=ubifs
			FSGROUP=ubifs
			unset FSCK
			unset FSRESIZE
			;;
		squashfs)
			FSGROUP=squashfs
			unset FSCK
			unset FSRESIZE
			;;
		erofs)
			FSGROUP=erofs
			unset FSCK
			unset FSRESIZE
			;;
		jffs2)
			FSGROUP=jffs2
			unset FSCK
			unset FSRESIZE
			;;
		auto)
			FSGROUP=auto
			message "Running fsck on a random fs is dangerous"
			unset FSCK
			unset FSRESIZE
			;;
		*)
			message "Unsupported file system $FSTYPE for $DEV"
			return
	esac

	# Setup mount tool and opts
	case $FSGROUP in
		ntfs)
			MOUNT=ntfs-3g
			check_tool ntfs-3g BR2_PACKAGE_NTFS_3G || return 1
			OPTS=$(convert_mount_opts "$NTFS_3G_MOUNT_OPTS")
			;;
		auto)
			MOUNT=mount
			OPTS=$(convert_mount_opts)
			;;
		*)
			MOUNT="mount -t $FSTYPE"
			OPTS=$(convert_mount_opts)
			;;
	esac
	MOUNT_OPTS=${OPTS:+" -o ${OPTS%,}"}

	# Detect ro/rw
	MOUNT_RO_RW=$(echo "$OPTS" | grep -wo "[^-]\<r[ow]\>" || true)
	if [ -z "$MOUNT_RO_RW" ]; then
		if is_rootfs && is_ro; then
			MOUNT_RO_RW=ro
		else
			MOUNT_RO_RW=rw
		fi
	fi

	# Prepare for ubi (consider /dev/mtdX as ubiblock)
	if [ "$FSGROUP" = ubifs ] || echo $DEV | grep -q "/dev/mtd[0-9]";then
		if ! prepare_ubi; then
			message "Failed to prepare ubi for $DEV"
			[ "$AUTO_MKFS" ] || return

			message "Auto formatting"
			format_ubifs || return
		fi
	fi
}

check_part()
{
	[ "$SKIP_FSCK" -o "$PASS" -eq 0 ] && return

	if [ -z "$FSCK" ]; then
		message "No fsck for $FSTYPE"
		return
	fi

	message "Checking $DEV($FSTYPE)"

	if is_rootfs; then
		# Remount ro for checking
		remount_part ro
	elif mountpoint -q "$MOUNT_POINT"; then
		# Unmount for checking
		umount "$MOUNT_POINT" || remount_part ro
	fi

	check_tool "$FSCK" $FSCK_CONFIG || return

	$FSCK $DEV
}

mount_part()
{
	mountpoint -q "$MOUNT_POINT" && return

	message "Mounting $DEV($FSTYPE) on $MOUNT_POINT ${MOUNT_OPTS:+with$MOUNT_OPTS}"
	$MOUNT $DEV $MOUNT_POINT $MOUNT_OPTS && return
	[ "$AUTO_MKFS" ] || return

	message "Failed to mount $DEV, try to format it"
	format_part && $MOUNT $DEV $MOUNT_POINT $MOUNT_OPTS
}
