#!/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