This fixes a bug where template presets with a specified instance were not being handled. The install script previously parsed the entire line, including the instance(s) and ran 'systemctl preset' on it all, e.g. 'systemctl preset unudhcpd@.service usb0'. The script will now: - detect lines that contain a template + instance(s) - diff the instances - run preset on template@instance for all added or removed instances Template presets that don't have any instances specified in the preset file (because they have a DefaultInstance= set) are treated just like any other unit since we can run preset on those and it enables the template with the default instance. Signed-off-by: Clayton Craft <craftyguy@postmarketos.org> Part-of: <https://gitlab.postmarketos.org/postmarketOS/pmaports/-/merge_requests/9026>
177 lines
4.9 KiB
Bash
177 lines
4.9 KiB
Bash
#!/bin/sh
|
|
|
|
system_preset_path="/usr/lib/systemd/system-preset/90-base.preset"
|
|
user_preset_path="/usr/lib/systemd/user-preset/90-base.preset"
|
|
|
|
# $1: path to file (without .old)
|
|
remove_old() {
|
|
rm "$1.old" 2>/dev/null || true
|
|
}
|
|
|
|
# https://www.freedesktop.org/software/systemd/man/latest/systemd.preset.html
|
|
# tldr: preset file format
|
|
# - discard line when:
|
|
# - empty line
|
|
# - first character is # or :
|
|
# - expect line to be
|
|
# - keyword + whitespace + unit name + [template unit directive]
|
|
# - keywords are: 'enable', 'disable', 'ignore'
|
|
# - unit name can contain shell-style wild-cards
|
|
# - unit name must match to a real file
|
|
# - when template unit directives are defined:
|
|
# - doesn't match to DefaultInstance
|
|
# - template unit gets expanded by directives
|
|
# - example: `enable dirsrv@.service foo bar baz` equivalent to:
|
|
# `systemctl preset dirsrv@foo.service`
|
|
# `systemctl preset dirsrv@bar.service`
|
|
# `systemctl preset dirsrv@baz.service`
|
|
|
|
# $1: path to preset
|
|
units_affected_by_preset() {
|
|
# https://stackoverflow.com/questions/4198138/printing-everything-except-the-first-field-with-awk
|
|
if [ ! -f "$1" ]; then
|
|
# If the file doesn't exist, then there's no policy
|
|
echo ""
|
|
return
|
|
fi
|
|
# "enable unit.service abc def" becomes "unit.service abc def"
|
|
cat "$1" |
|
|
busybox awk '$1 == "enable" || $1 == "disable" || $1 == "ignore"' |
|
|
busybox sed 's/(\|enable \|disable \|ignore )//' |
|
|
uniq |
|
|
sort
|
|
}
|
|
|
|
# $1: path to file (without .old)
|
|
units_affected_by_old_and_new_preset() {
|
|
# step one: get list of services in general
|
|
local new="$(units_affected_by_preset "$1")"
|
|
local old="$(units_affected_by_preset "$1.old")"
|
|
|
|
printf "$new\n$old" | sort | uniq
|
|
}
|
|
|
|
|
|
# $1: path to file
|
|
# $2: entry name
|
|
# returns: string oder exitcode
|
|
get_policy_for_unit() {
|
|
# printf '%q' isn't available in busybox
|
|
# so we hope only * as an wildcard is used for now
|
|
# FIXME: better shell escaping
|
|
local escaped="$(echo "$2" | sed 's/\*/\\\*/')"
|
|
|
|
if [ ! -f "$1" ]; then
|
|
# If the file doesn't exist, then there's no policy
|
|
echo ""
|
|
return
|
|
fi
|
|
|
|
cat $1 |
|
|
grep "$escaped"'$' |
|
|
awk '$1 == "enable" || $1 == "disable" || $1 == "ignore" {print $1}'
|
|
}
|
|
|
|
# $1: path to file
|
|
# $2: entry name
|
|
# returns: exitcode
|
|
policy_changed() {
|
|
# we don't have to care about the exitcode altough it's faster
|
|
# "" != "enable"
|
|
local new="$(get_policy_for_unit $1 "$2")"
|
|
local old="$(get_policy_for_unit $1.old "$2")"
|
|
[ "$new" != "$old" ]
|
|
}
|
|
|
|
# $1: takes system or user
|
|
check_and_apply_presets() {
|
|
local type="$1"
|
|
local preset_path=""
|
|
local extra_args=""
|
|
if [ "$type" = user ]; then
|
|
preset_path="$user_preset_path"
|
|
extra_args="--global"
|
|
else
|
|
preset_path="$system_preset_path"
|
|
type="system"
|
|
fi
|
|
|
|
local entries="$(units_affected_by_old_and_new_preset $preset_path)"
|
|
local old_ifs=$IFS
|
|
IFS=$'\n'
|
|
for entry in $entries; do
|
|
if ! policy_changed "$preset_path" "$entry"; then
|
|
continue
|
|
fi
|
|
|
|
echo "unit policy has changed for $entry"
|
|
|
|
if echo "$entry" | grep -qF '*'; then
|
|
echo " wildcards are not supported yet, skipping"
|
|
continue
|
|
fi
|
|
# Handle template units with instance arguments, e.g. "unudhcpd@.service usb0"
|
|
# See systemd.preset(5) "Enable multiple template instances"
|
|
case "$entry" in
|
|
*@.*)
|
|
# Only act if there are instances listed after the template name
|
|
instances="${entry#* }"
|
|
if [ "$instances" = "$entry" ]; then
|
|
# handle templates listed without instances in the preset just like any other
|
|
# unit, this assumes they have a DefaultInstance= set
|
|
break
|
|
fi
|
|
|
|
template="${entry%% *}"
|
|
if [ ! -f "/usr/lib/systemd/$type/$template" ]; then
|
|
echo " no unit file found, skipping"
|
|
continue
|
|
fi
|
|
# e.g. "unudhcpd@" and "service"
|
|
base="${template%.*}"
|
|
suffix="${template##*.}"
|
|
|
|
# extract instance lists from new and old preset files for diffing
|
|
new_instances=""
|
|
if [ -f "$preset_path" ]; then
|
|
new_instances="$(grep " $template " "$preset_path" | sed "s/.* $template //")"
|
|
fi
|
|
old_instances=""
|
|
if [ -f "$preset_path.old" ]; then
|
|
old_instances="$(grep " $template " "$preset_path.old" | sed "s/.* $template //")"
|
|
fi
|
|
|
|
# only preset instances that were added or removed should be preset
|
|
# note: IFS is changed to split on words, so we can pick up multiple instances
|
|
# that might be listed after the template
|
|
local saved_ifs="$IFS"
|
|
IFS=' '
|
|
for inst in $new_instances $old_instances; do
|
|
if echo " $new_instances " | grep -qF " $inst " && \
|
|
echo " $old_instances " | grep -qF " $inst "; then
|
|
continue
|
|
fi
|
|
systemctl --no-reload preset $extra_args "${base}${inst}.${suffix}"
|
|
done
|
|
IFS="$saved_ifs"
|
|
|
|
# done processing this line
|
|
continue
|
|
;;
|
|
esac
|
|
|
|
if [ -f "/usr/lib/systemd/$type/$entry" ]; then
|
|
systemctl --no-reload preset $extra_args "$entry"
|
|
else
|
|
echo " no unit file found, skipping"
|
|
fi
|
|
done
|
|
IFS="$old_ifs"
|
|
}
|
|
|
|
check_and_apply_presets system
|
|
check_and_apply_presets user
|
|
|
|
remove_old "$system_preset_path"
|
|
remove_old "$user_preset_path"
|