Files
pmaports-rtwo/extra-repos/systemd/postmarketos-base-systemd/rootfs-usr-libexec-systemd-apk-trigger
T
Clayton Craft 2a4d4f8b61 systemd/postmarketos-base-systemd: fix handling of added and removed units in trigger
Apparently the eval trick doesn't work for passing vars if the var to
set is the same as the var for the value, in this case the caller of
this function was using `added_paths` and `removed_paths`. Shell scripts
suck.

This was missed in the tests because the unit tests call
trigger_unit_presets directly.

Part-of: <https://gitlab.postmarketos.org/postmarketOS/pmaports/-/merge_requests/7025>
2025-09-09 17:19:47 +00:00

195 lines
4.9 KiB
Bash

#!/bin/sh
# shellcheck disable=SC3043
#
# This is meant to be called by an apk trigger. By splitting the logic out
# into a new script, one can easily modify/test/debug this logic without having
# to rebuild the package that installs it.
# NOTE: this variable, when set, is also used to indicate test mode
SYSTEMD_APK_ROOT="${SYSTEMD_APK_ROOT:-}"
UNIT_LIST_PATH="$SYSTEMD_APK_ROOT/var/lib/systemd-apk/installed.units"
remove_unit_symlinks() {
local unit_path="$1"
local search_dir
local found_broken
# Determine which config directory to search
case "$unit_path" in
*/systemd/system/*)
search_dir="$SYSTEMD_APK_ROOT/etc/systemd/system"
;;
*/systemd/user/*)
search_dir="$SYSTEMD_APK_ROOT/etc/systemd/user"
;;
*)
return
;;
esac
[ -d "$search_dir" ] || return 0
# Keep removing broken symlinks until none remain
while true; do
found_broken=false
symlinks=$(busybox find "$search_dir" -type l)
while IFS= read -r symlink; do
[ -z "$symlink" ] && continue
target=$(readlink "$symlink")
# Resolve relative paths to absolute paths
if [ "${target#/}" = "$target" ]; then
# Relative path - resolve from symlink directory
abs_target=$(cd "$(dirname "$symlink")" && realpath "$target" 2>/dev/null)
else
# Already absolute
abs_target="$target"
fi
if [ "$abs_target" = "$unit_path" ]; then
echo "Removing dangling symlink: $symlink -> $target"
rm -vf "$symlink"
found_broken=true
fi
done <<-EOF
$symlinks
EOF
[ "$found_broken" = "false" ] && break
done
}
parse_diff_output() {
local diff_output="$1"
local added_var="$2"
local removed_var="$3"
# Note: use an underscore here to reduce the chances that $added_var and
# $removed_car are set to the variable names here:
local _added_paths=""
local _removed_paths=""
# Using heredoc to process lines and avoid a subshell, see SC2031
while IFS= read -r line; do
line_without_prefix=${line#?}
path=${line_without_prefix%% *}
case "$line" in
*'type=dir'*)
# Skip directories
continue;;
-[!-]*)
_removed_paths="$_removed_paths $path"
;;
+[!+]*)
_added_paths="$_added_paths $path"
;;
*)
;;
esac
done <<-EOF
$diff_output
EOF
# Set results in caller's variables
if [ -n "$added_var" ]; then eval "$added_var=\"$_added_paths\""; fi
if [ -n "$removed_var" ]; then eval "$removed_var=\"$_removed_paths\""; fi
}
run_systemctl() {
local args="$*"
if [ -n "$SYSTEMD_APK_ROOT" ]; then
# Print what we'd run, when in 'test' mode
echo "systemctl $args"
else
# shellcheck disable=SC2086
systemctl $args
fi
}
trigger_systemd_presets() {
local added_paths="$1"
local removed_paths="$2"
local system_units_to_preset=""
local user_units_to_preset=""
# Process added paths for presets, skip if also removed (modified files)
for path in $added_paths; do
if echo " $removed_paths " | grep -qF " $path "; then
continue
fi
case "$path" in
*/systemd/system/*)
if [ -f "$path" ] && grep -qE "^\[Install\]" "$path" 2>/dev/null; then
system_units_to_preset="$system_units_to_preset $(basename "$path")"
fi
;;
*/systemd/user/*)
if [ -f "$path" ] && grep -qE "^\[Install\]" "$path" 2>/dev/null; then
user_units_to_preset="$user_units_to_preset $(basename "$path")"
fi
;;
esac
done
# Apply presets to newly added units
if [ -n "$system_units_to_preset" ]; then
echo "Applying presets to new system units..."
for unit in $system_units_to_preset; do
run_systemctl --no-reload preset "$unit"
done
else
echo "No new system units to preset"
fi
if [ -n "$user_units_to_preset" ]; then
echo "Applying presets to new user units..."
for unit in $user_units_to_preset; do
run_systemctl --no-reload preset --global "$unit"
done
else
echo "No new user units to preset"
fi
}
handle_unit_changes() {
mkdir -p "$(dirname "$UNIT_LIST_PATH")"
local current_units="${UNIT_LIST_PATH}.new"
local removed_paths=""
local added_paths=""
# Create current state
treescan /usr/lib/systemd/system > "$current_units"
treescan /usr/lib/systemd/user >> "$current_units"
# Ensure previous state file exists (create empty if missing)
[ -f "$UNIT_LIST_PATH" ] || touch "$UNIT_LIST_PATH"
diff_output=$(busybox diff "$UNIT_LIST_PATH" "$current_units")
parse_diff_output "$diff_output" "added_paths" "removed_paths"
# Clean up symlinks for truly removed units (removed but not re-added)
for path in $removed_paths; do
echo " $added_paths " | grep -qF " $path " && continue
case "$path" in
*/systemd/system/*|*/systemd/user/*)
remove_unit_symlinks "$path"
;;
esac
done
trigger_systemd_presets "$added_paths" "$removed_paths"
# Update stored state
mv "$current_units" "$UNIT_LIST_PATH"
}
# If is set, then we're unit testing the trigger and don't actually want to run the rest of the stuff below
if [ -z "$SYSTEMD_APK_ROOT" ]; then
handle_unit_changes
exit 0
fi