# -*- shell-script -*- vim:ft=sh:
#---------------------------------------------------------------------
# Description: Mount appropriate Ubuntu Core root filesystem read-only
#   and writable partition writable.
# Entry point: mountroot().
#---------------------------------------------------------------------

pre_mountroot()
{
	local script_dir="/scripts/local-top"
	[ "$quiet" != "y" ] && log_begin_msg "Running $script_dir"
	run_scripts "$script_dir"
	[ "$quiet" != "y" ] && log_end_msg
}

sync_dirs()
{
	base="$1"
	source="$2"
	target="$3"

	OLD_PWD="$PWD"
	cd "$base"

	for file in "$source"/*
	do
		# Skip empty directories
		[ ! -e "$base/$file" ] && continue

		# If the target already exists as a file or link, there's nothing we can do
		[ -e "$target/$file" -o -L "$target/$file" ] && [ ! -d "$target/$file" ] && continue

		# If the target doesn't exist, just copy it over
		if [ ! -e "$target/$file" -a ! -L "$target/$file" ]; then
			cp -Ra "$base/$file" "$target/$file"
			continue
		fi

		# That leaves us with directories and a recursive call
		[ -d "$file" ] && sync_dirs "$base" "$file" "$target"
	done

	cd "$OLD_PWD"
}

# Determine full path to disk partition given a filesystem label.
get_partition_from_label()
{
	local label="$1"

	[ -n "$label" ] || panic "need FS label"

	# Make sure the device has been created by udev before looking for it
	# Don't need to panic, since the output will be validated outside this function
	wait-for-root "LABEL=$label" "${ROOTDELAY:-180}" >/dev/null || true

	local part=$(find /dev -name "$label"|tail -1)
	[ -z "$part" ] && return
	local path=$(readlink -f "$part")
	[ -n "$path" ] && echo "$path"
}

# Process the list of bind-mounts (but don't mount them - systemd will handle that)
# File format is documented in writable-paths(5).
handle_writable_paths()
{
	writable_paths="$1"
	fstab="$2"

	[ -n "$writable_paths" ] || panic "need writeable paths"
	[ -e "$writable_paths" ] || panic "writeable paths does not exist"
	[ -n "fstab" ] || panic "need fstab"

	cat "$writable_paths" | while read line; do
		# tokenise
		set -- $line

		# skip invalid/commented entries
		([ -z "$1" ] || \
		 [ -z "$2" ] || \
		 [ -z "$3" ] || \
		 [ -z "$4" ] || \
		 [ -z "$5" ]) && continue

		# ignore anything that isn't an absolute path (including comments)
		case "$1" in
			/*) ;;
			*) continue ;;
		esac

		# skip invalid mount points
		dstpath="${rootmnt}$1"
		[ ! -e "$dstpath" ] && continue

		if [ "$3" = "temporary" ]; then
			# Temporary entries are simple, just mount a tmpfs
			echo "tmpfs $1 tmpfs $5 0 0" >> "$fstab"
		elif [ "$3" = "persistent" ] || \
		     [ "$3" = "synced" ]; then
			# Figure out the source path
			if [ "$2" = "auto" ]; then
				srcpath="${rootmnt}/writable/system-data${1}"
				path="/writable/system-data${1}"
			else
				srcpath="${rootmnt}/writable/$2"
				path="/writable/$2"
			fi

			if [ ! -e "$srcpath" ]; then
				# Process new persistent or synced paths
				dstown=$(stat -c "%u:%g" "$dstpath")
				dstmode=$(stat -c "%a" "$dstpath")
				mkdir -p ${srcpath%/*}
				if [ ! -d "$dstpath" ]; then
					# Deal with redirected files
					if [ "$4" = "transition" ]; then
						cp -a "$dstpath" "$srcpath"
					else
						touch "$srcpath"
						chown "$dstown" "$srcpath"
						chmod "$dstmode" "$srcpath"
					fi
				else
					# Deal with redirected directories
					if [ "$4" = "transition" ] || [ "$3" = "synced" ]; then
						cp -aR "$dstpath" "$srcpath"
					else
						mkdir "$srcpath"
						chown "$dstown" "$srcpath"
						chmod "$dstmode" "$srcpath"
					fi
				fi
			elif [ "$3" = "synced" ]; then
				# Process existing synced paths
				sync_dirs "$dstpath" . "$srcpath"
			fi

                        # mount all /etc dirs right now, not later when fstab is
                        # processed, as it will cause races.
                        case $1 in
                            /etc*)
                                [ -d "${rootmnt}/writable/system-data/$1" ] || mkdir -p "${rootmnt}/writable/system-data/$1"
                                mount -o bind "${rootmnt}/writable/system-data/$1" "${rootmnt}/$1"
                                ;;
                            *)
                                # Write the fstab entry
                                if [ "$5" = "none" ]; then
                                        echo "$path $1 none bind 0 0" >> "$fstab"
                                else
                                        echo "$path $1 none bind,$5 0 0" >> "$fstab"
                                fi
                                ;;
                        esac
		else
			continue
		fi
	done
}

fsck_writable()
{
	local writable_label="$1"
        local writable_mnt="$2"

	path=$(get_partition_from_label "$writable_label")

	[ -n "$path" ] || panic "cannot find '$writable_label' partition"

	# Mount the writable partition to a temporary mount point
	# (to allow it to be move-mounted on top of the read-only root).
	logfile="/run/initramfs/fsck-${writable_label}"

	echo "initrd: checking filesystem for ${writable_label} partition" >/dev/kmsg || true

	echo "$(date '+%s'): start" >> "$logfile" || true

	# XXX: The following commands must not fail (to ensure the system boots!)

	# Mount and umount first to let the kernel handle
	# the journal and orphaned inodes (much faster than e2fsck).
	mount -o errors=remount-ro "$path" "$writable_mnt" || true
	umount "$writable_mnt" || true

	# Automatically fix errors
	/sbin/e2fsck -va "$path" >> "$logfile" 2>&1 || true

	echo "$(date '+%s'): end" >> "$logfile" || true

}

# setup $rootmnt based on os/kernel snaps
do_new_style_root_mounting()
{
        root="LABEL=$writable_label"

	# Make sure the device has been created by udev before we try to mount
	wait-for-root "$root" "${ROOTDELAY:-180}" || panic "unable to find root partition '$root'"

	[ -n "$root" ] || panic "no root partition specified"

	if echo "$root" | grep -q ^/; then
		path="$root"
	else
		# convert UUID/LABEL to a device name
		path=$(findfs "$root" 2>/dev/null || :)
	fi

	[ -e "$path" ] || panic "root device $path does not exist"

        # try loading squashfs, but don't fail if its e.g. in the kernel
        # already
        modprobe squashfs || true

        if ! grep -q squashfs /proc/filesystems; then
        	 panic "no squashfs support found in your system, aborting"
        fi

        # mount writable rw
        path=$(get_partition_from_label "$writable_label")
        mount "$path" "$writable_mnt"

        # mount OS snap
        mount "${writable_mnt}/system-data/var/lib/snapd/snaps/${snappy_os}" "$rootmnt"

        # now add a kernel bind mounts to it
        local kernel_mnt="/tmpmnt_kernel"
        mkdir -p "$kernel_mnt"
        mount "${writable_mnt}/system-data/var/lib/snapd/snaps/${snappy_kernel}" "$kernel_mnt"
        for d in modules firmware; do
            mount -o bind "${kernel_mnt}/lib/$d" "$rootmnt/lib/$d"            
        done
}    

# setup $rootmnt from system_ab style
do_old_style_root_mounting()
{
	# Make sure the device has been created by udev before we try to mount
	wait-for-root "$root" "${ROOTDELAY:-180}" || panic "unable to find root partition '$root'"

	[ -n "$root" ] || panic "no root partition specified"

	if echo "$root" | grep -q ^/; then
		path="$root"
	else
		# convert UUID/LABEL to a device name
		path=$(findfs "$root" 2>/dev/null || :)
	fi

	[ -e "$path" ] || panic "root device $path does not exist"

	# Mount the root filesystem read-only.
	echo "initrd: mounting $path" >/dev/kmsg || true
	mount -o defaults,ro "$path" "$rootmnt"
	rootpath="$path"

        # mount writable rw
        path=$(get_partition_from_label "$writable_label")
        mount "$path" "$writable_mnt"
        mkdir -p "${rootmnt}/writable"
        
	# Create a read-only bind mount on top of the already read-only
	# FS. This is to stop the system from noticing when the root
	# filesystem is made writable (when system updates are applied).
	mount -o bind,ro "$rootmnt" "$rootmnt"
}
    
#---------------------------------------------------------------------
# XXX: Entry point - called by the initramfs "/init" script.
#---------------------------------------------------------------------
mountroot()
{
        pre_mountroot

	[ "$quiet" != "y" ] && log_begin_msg "Running /scripts/local-premount"
	run_scripts /scripts/local-premount
	[ "$quiet" != "y" ] && log_end_msg
        
        # find what snappy-os version to use
        for x in $(cat /proc/cmdline); do
	    case "${x}" in
                # new kernel/os snap vars
		snappy_os=*)
			snappy_os="${x#*=}"
			;;
		snappy_kernel=*)
			snappy_kernel="${x#*=}"
			;;
                # old system-ab style
		root=*)
			root="${x#*=}"
			;;
		esac
	done

        # always ensure writable is in a good state
        writable_label="writable"
        writable_mnt="/tmpmnt_${writable_label}"
	mkdir -p "$writable_mnt"
        fsck_writable "$writable_label" "$writable_mnt"

        # check if we are on a new os/kernel snap system or a old snappy_ab one
        if [ -n "$snappy_os" ] && [ -n "$snappy_kernel" ]; then
            do_new_style_root_mounting
        else
            do_old_style_root_mounting
        fi
        
        # mount /run
	echo "initrd: mounting /run" >/dev/kmsg || true
	mount -o rw,nosuid,noexec,relatime,mode=755 -t tmpfs tmpfs "${rootmnt}/run"

        # move /writable to its final destination
	mount --move "$writable_mnt" "${rootmnt}/writable"

	# Prepare the fstab
	fstab="${rootmnt}/etc/fstab"
	writable_paths="${rootmnt}/etc/system-image/writable-paths"

	# Add writable overlays
	if [ -e "$writable_paths" ]; then
		touch "${rootmnt}/run/image.fstab"
		mount -o bind "${rootmnt}/run/image.fstab" "$fstab" || panic "Cannot bind mount fstab"
		echo "# Auto-generated by $0" >> "$fstab"
		echo "# DO NOT EDIT THIS FILE BY HAND - YOUR CHANGES WILL BE OVERWRITTEN" >> "$fstab"
		echo "# (See writable-paths(5) for details)" >> "$fstab"
		echo "/dev/root / rootfs defaults,ro 0 0" >> "$fstab"
                # FIXME: ideally we would mount /writable RO here and
                #        let systemd do a "remount,rw" for us. unfortunately
                #        this is not supported by systemd so we need to do
                #        the RW mount and fsck dance etc here :/
                echo "LABEL=writable /writable auto defaults 0 0" >> "$fstab"
		handle_writable_paths "$writable_paths" "$fstab"
	fi

	# Request bootloader partition be mounted
	boot_partition=$(findfs LABEL="system-boot" 2>/dev/null || :)

	if [ -n "$boot_partition" ]; then
		# determine bootloader type
		grubdir="/boot/grub"
		ubootdir="/boot/uboot"

		# Since the boot partition is not required to actually boot
		# the image, request the init system handle it (fsck+mount).
		if [ -d "${rootmnt}/${grubdir}" ] && [ -e "${rootmnt}/usr/sbin/grub-install" ]; then
			bootdir="$grubdir"

			efidir="/boot/efi"
			efibinddir="${efidir}/EFI/ubuntu/grub"
			if [ -d "${rootmnt}/${efidir}" ]; then
				echo "$boot_partition $efidir auto defaults 0 2" >> "$fstab"
				echo "$efibinddir $bootdir none bind 0 0" >> "$fstab"
			fi
		else
			bootdir="$ubootdir"

			echo "$boot_partition $bootdir auto defaults 0 2" >> "$fstab"
		fi
	fi

        # FIXME: the below can go away once we no longer support snappy_ab
	# add an fstab entry to mount the "other" rootfs partition read-only
	partition=$(findfs LABEL="system-b" 2>/dev/null || :)
	abs_rootpath=$(readlink -f "$rootpath" || :)
	if [ "$partition" = "$abs_rootpath" ]; then
		other=$(findfs LABEL="system-a" 2>/dev/null || :)
	else
		other="$partition"
	fi
	if [ -n "$other" ]; then
		echo "$other /writable/cache/system auto defaults,ro 0 0" >> "$fstab"
        fi
        
	# Mount the systemd overlay so that we have a complete root partition during boot
	mkdir -p "${rootmnt}/writable/system-data/etc/systemd/system"
	mount -o bind "${rootmnt}/writable/system-data/etc/systemd/system" "${rootmnt}/etc/systemd/system"

	# Apply customized content
	for user in "${rootmnt}"/writable/user-data/*
	do
		if [ -d "${rootmnt}/custom/home" ] && [ ! -e "$user/.customized" ]; then
			echo "initrd: copying custom content tp " >/dev/kmsg || true
			cp -Rap "${rootmnt}"/custom/home/* "$user/"
			cp -Rap "${rootmnt}"/custom/home/.[a-zA-Z0-9]* "$user/"
			touch "$user/.customized"
			dstown=$(stat -c "%u:%g" "$user")
			chown -R "$dstown" "$user/"
		fi
	done

	[ "$quiet" != "y" ] && log_begin_msg "Running /scripts/local-bottom"
	run_scripts /scripts/local-bottom
	[ "$quiet" != "y" ] && log_end_msg
}
