Newer
Older
bootslot-mounts / bootslot-mounts
@Xogium Xogium on 23 Jul 2021 5 KB Initial commit.
#!/bin/sh

# bootslot-mounts - generates systemd mounts based on the booted RAUC slot
# Copyright (C) 2019  Kopis Mobile

# This program is free software: you can redistribute it and/or modify it under
# the terms of version 3 of the GNU General Public License as published by the
# Free Software Foundation.

# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
# details.

# You should have received a copy of the GNU General Public License along with
# this program.  If not, see <https://www.gnu.org/licenses/>.

CONFFILE=/etc/bootslot-mounts.conf
SELFNAME=$0
UNIT_DIR=$1

main() {
	BOOTSLOT=$(get_cmdline_value bootchooser.active) || {
		>&2 echo $SELFNAME: bootchooser.active not specified on kernel command line\; aborting!
		exit 1
	}

	mkdir -p "$UNIT_DIR/local-fs.target.requires"

	shini_parse_section "$CONFFILE" "mountpoints" mountpoints
	shini_parse_section "$CONFFILE" "slot_$BOOTSLOT" slot

	[ -z "$SLOTS_WERE_EMITTED" ] && {
		>&2 echo $SELFNAME: Detected boot slot $BOOTSLOT, but no mountpoints were emitted
		exit 2
	}

	return 0
}

__shini_parsed_mountpoints() {
	eval "MOUNTPOINT_$2=$3"
}

__shini_parsed_slot() {
	eval "local the_mountpoint=\$MOUNTPOINT_$2"
	[ -z "$the_mountpoint" ] && {
		>&2 echo "$SELFNAME: bad mountpoint key $2"
		return
	}
	local systemd_name=$(echo "${the_mountpoint##/}" | tr / -)
	emit_mountpoint "$systemd_name.mount" "$the_mountpoint" "$3"

	SLOTS_WERE_EMITTED=yes
}

get_cmdline_value () {
	</proc/cmdline awk -F '=' -v 'RS= ' 'BEGIN { ec=1 } { if ($1 == "'"$1"'") { print $2; ec=0; exit } } END { exit ec }'
}

emit_mountpoint () {
	cat >"$UNIT_DIR/$1" <<-EOF
		# Automatically generated by bootslot-mounts
		[Unit]
		SourcePath=$CONFFILE
		Before=local-fs.target

		[Mount]
		Where=$2
		What=$3
	EOF
	ln -s "$UNIT_DIR/$1" "$UNIT_DIR/local-fs.target.requires/$1"
}

# shini - compatible INI library for sh
#
# This code is released freely under the MIT license.
# For the latest version etc, please see https://github.com/wallyhall/shini

shini_function_exists()
{
	type "$1" > /dev/null 2>&1
	return $?
}

shini_regex_match()
{
	printf '%s' "$1" | grep -qe "$2"
	return $?
}

shini_regex_replace()
{
	shini_retval="$(printf '%s' "$1" | sed -E "s/$2/\1/")"
}

# @param inifile Filename of INI file to parse
# @param section Section to parse (or empty string for entire file)
# @param postfix Function postfix for callbacks (optional)
# @param extra Extra argument for callbacks (optional)
shini_parse_section()
{
	RX_KEY='[a-zA-Z0-9_\-\.]'
	RX_VALUE="[^;\"]"
	RX_SECTION='[a-zA-Z0-9_\-]'
	RX_WS='[ 	]'
	RX_QUOTE='"'
	RX_HEX='[0-9A-F]'
	POSTFIX=''
	SKIP_TO_SECTION=''
	EXTRA1=''
	EXTRA2=''
	EXTRA3=''
	SECTION_FOUND=-1

	if [ $# -ge 2 ] && [ ! -z "$2" ]; then
		SKIP_TO_SECTION="$2"
	fi

	if [ $# -ge 3 ] && [ ! -z "$3" ]; then
		POSTFIX="_$3"
	fi

	if [ $# -ge 4 ] && ! [ -z "$4" ]; then
		EXTRA1="$4"
	fi

	if [ $# -ge 5 ] && [ ! -z "$5" ]; then
		EXTRA2="$5"
	fi

	if [ $# -ge 6 ] && [ ! -z "$6" ]; then
		EXTRA3="$6"
	fi

	if ! shini_function_exists "__shini_parsed${POSTFIX}"; then
		printf 'shini: __shini_parsed%s function not declared.\n' "${POSTFIX}" 1>&2
		exit 255
	fi

	if [ $# -lt 1 ]; then
		printf 'shini: Argument 1 needs to specify the INI file to parse.\n' 1>&2
		exit 254
	fi
	INI_FILE="$1"

	if [ ! -r "$INI_FILE" ]; then
		printf 'shini: Unable to read INI file:\n  `%s`\n' "$INI_FILE" 1>&2
		exit 253
	fi

	# Iterate INI file line by line
	LINE_NUM=0
	SECTION=''
	while read LINE || [ -n "$LINE" ]; do  # -n $LINE catches final line if not empty
		# Check for new sections
		if shini_regex_match "$LINE" "^${RX_WS}*\[${RX_SECTION}${RX_SECTION}*\]${RX_WS}*$"; then
			shini_regex_replace "$LINE" "^${RX_WS}*\[(${RX_SECTION}${RX_SECTION}*)\]${RX_WS}*$" "\1"
			SECTION=$shini_retval

			if [ "$SKIP_TO_SECTION" != '' ]; then
				# stop once specific section is finished
				[ $SECTION_FOUND -eq 0 ] && break;

				# mark the specified section as found
				[ "$SKIP_TO_SECTION" = "$SECTION" ] && SECTION_FOUND=0;
			fi

			LINE_NUM=$((LINE_NUM+1))
			continue
		fi

		# Skip over sections we don't care about, if a specific section was specified
		[ "$SKIP_TO_SECTION" != '' ] && [ $SECTION_FOUND -ne 0 ] && LINE_NUM=$((LINE_NUM+1)) && continue;

		# Check for new values
		if shini_regex_match "$LINE" "^${RX_WS}*${RX_KEY}${RX_KEY}*${RX_WS}*="; then
			shini_regex_replace "$LINE" "^${RX_WS}*(${RX_KEY}${RX_KEY}*)${RX_WS}*=.*$"
			KEY=$shini_retval

			shini_regex_replace "$LINE" "^${RX_WS}*${RX_KEY}${RX_KEY}*${RX_WS}*=${RX_WS}*${RX_QUOTE}{0,1}(${RX_VALUE}*)${RX_QUOTE}{0,1}(${RX_WS}*\;.*)*$"
			VALUE=$shini_retval

			if shini_regex_match "$LINE" "^0x${RX_HEX}${RX_HEX}*$"; then
				VALUE=$(printf '%d' "$VALUE")
			fi

			"__shini_parsed${POSTFIX}" "$SECTION" "$KEY" "$VALUE" "$EXTRA1" "$EXTRA2" "$EXTRA3"

			LINE_NUM=$((LINE_NUM+1))
			continue
		fi

		# Announce parse errors
		if [ "$LINE" != '' ] &&
		  ! shini_regex_match "$LINE" "^${RX_WS}*;.*$" &&
		  ! shini_regex_match "$LINE" "^${RX_WS}*$"; then
			printf 'shini: Unable to parse line %d:\n  `%s`\n' $LINE_NUM "$LINE"
		fi

		LINE_NUM=$((LINE_NUM+1))
	done < "$INI_FILE"
}

main