2
0
mirror of https://github.com/xcat2/xcat-core.git synced 2026-07-31 10:09:40 +00:00
Files
Vinícius Ferrão 036bb24c87 fix(packaging): preserve xcatd init state on Debian (#7615)
* refactor(packaging): share precise systemd state detection

* test(packaging): cover precise systemd state detection

* test(packaging): allow explicit Debian init mode

* fix(packaging): honor explicit Debian init targets

* test(packaging): cover Debian init target detection

* fix(packaging): add Debian xcatd init state helper

* test(packaging): cover Debian xcatd init state helper

* test(packaging): allow delegated SysV registration

* fix(packaging): preserve Debian xcatd conffile lifecycle

* test(packaging): mirror explicit Debian init mode

* test(packaging): cover Debian xcatd conffile lifecycle

* fix(packaging): contain init state file umask

* test(packaging): cover init state permissions

* fix(packaging): contain preinstall context umask

* test(packaging): cover preinstall umask containment

* fix(packaging): detect all systemd enablement links

* test(packaging): cover all systemd enablement links

* test(packaging): allow shared purge state path

* refactor(packaging): reuse Debian init state path

* fix(packaging): detect runtime systemd masks

* test(packaging): cover runtime systemd masks

* test(packaging): model Debian SysV registration

* refactor(packaging): reuse shared init state detection

* test(packaging): enforce shared Debian state probes

* test(packaging): allow explicit unregistered masks

* fix(packaging): preserve Debian SysV registration state

* test(packaging): cover Debian SysV registration states

* fix(packaging): clean failed Debian state writes

* test(packaging): cover failed Debian state writes

* fix(packaging): retain unregistered systemd provenance

* test(packaging): cover unregistered systemd upgrades

* fix(packaging): fail closed on shared state errors

* test(packaging): cover shared state detector failures

* test(packaging): mirror native xcatd runlevels

* fix(packaging): recover rejected SysV layouts

* test(packaging): cover rejected SysV layouts

* test(packaging): cover SysV rebuild retries

* refactor(packaging): reuse systemctl readiness guard

* test(packaging): enforce shared systemctl guard
2026-07-28 19:31:36 +00:00

702 lines
21 KiB
Bash
Executable File

#!/bin/sh
# Preserve the Debian xcatd SysV configuration while the target uses systemd.
# The state file is parsed as data; it is never sourced.
set -e
compat_root=${XCAT_COMPAT_ROOT:-}
compat_root=${compat_root%/}
xcat_root=${XCATROOT:-/opt/xcat}
case "$xcat_root" in
/*) ;;
*)
echo "XCATROOT must be an absolute path" >&2
exit 2
;;
esac
legacy_init="$compat_root/etc/init.d/xcatd"
legacy_template="$compat_root$xcat_root/share/xcat/scripts/xcatd"
init_compat="$compat_root$xcat_root/share/xcat/scripts/xcatd-init-compat"
state_dir="$compat_root/var/lib/xcat/xcatd-init-state"
state_file="$state_dir/state"
state_stash="$state_dir/xcatd"
pending_stash="$state_dir/pending-xcatd"
pending_deleted="$state_dir/pending-deleted"
pending_enabled="$state_dir/pending-enabled"
old_systemd_marker="$compat_root/var/lib/xcat/xcatd-systemd-mode"
state_mode=unknown
state_content=unknown
state_enabled=unknown
state_origin=unknown
state_format=
read_state()
{
[ -r "$state_file" ] || return 1
state_mode=unknown
state_content=unknown
state_enabled=unknown
state_origin=unknown
state_format=
seen_format=0
seen_mode=0
seen_content=0
seen_enabled=0
seen_origin=0
while IFS='=' read -r state_key state_value; do
case "$state_key" in
format)
[ "$seen_format" -eq 0 ] || return 1
state_format=$state_value
seen_format=1
;;
mode)
[ "$seen_mode" -eq 0 ] || return 1
state_mode=$state_value
seen_mode=1
;;
content)
[ "$seen_content" -eq 0 ] || return 1
state_content=$state_value
seen_content=1
;;
enabled)
[ "$seen_enabled" -eq 0 ] || return 1
state_enabled=$state_value
seen_enabled=1
;;
origin)
[ "$seen_origin" -eq 0 ] || return 1
state_origin=$state_value
seen_origin=1
;;
'') ;;
*)
echo "Invalid xcatd init state key: $state_key" >&2
return 1
;;
esac
done < "$state_file"
if [ "$seen_format" -ne 1 ] || [ "$seen_mode" -ne 1 ] ||
[ "$seen_content" -ne 1 ] || [ "$seen_enabled" -ne 1 ] ||
[ "$seen_origin" -ne 1 ]; then
echo "Invalid xcatd init state format: ${state_format:-missing}" >&2
return 1
fi
case "$state_format" in
1|2) ;;
*)
echo "Invalid xcatd init state format: ${state_format:-missing}" >&2
return 1
;;
esac
case "$state_mode" in
legacy|systemd|transition-systemd|transition-legacy|unknown) ;;
*)
echo "Invalid xcatd init mode state: $state_mode" >&2
return 1
;;
esac
case "$state_content" in
active|stashed|deleted|package-default|unknown) ;;
*)
echo "Invalid xcatd init content state: $state_content" >&2
return 1
;;
esac
case "$state_enabled" in
yes|no|unknown) ;;
unregistered)
if [ "$state_format" = 2 ]; then
:
else
echo "Invalid xcatd init enablement state: $state_enabled" >&2
return 1
fi
;;
*)
echo "Invalid xcatd init enablement state: $state_enabled" >&2
return 1
;;
esac
case "$state_origin" in
fresh|legacy|systemd|unknown) ;;
*)
echo "Invalid xcatd init transition origin: $state_origin" >&2
return 1
;;
esac
# Format 1 collapsed registered-disabled and unregistered into "no",
# then restored that value by removing every SysV link. Preserve that
# behavior when recovering an interrupted pre-format-2 transaction.
if [ "$state_format" = 1 ] && [ "$state_enabled" = no ]; then
state_enabled=unregistered
fi
}
ensure_state_dir()
{
if [ -L "$state_dir" ] || { [ -e "$state_dir" ] && [ ! -d "$state_dir" ]; }; then
echo "Invalid xcatd init state directory: $state_dir" >&2
exit 1
fi
mkdir -p "$state_dir"
chmod 700 "$state_dir"
}
write_state()
{
new_mode=$1
new_content=$2
new_enabled=$3
new_origin=$4
ensure_state_dir
state_tmp="$state_file.tmp.$$"
if ! (
umask 077
printf 'format=2\nmode=%s\ncontent=%s\nenabled=%s\norigin=%s\n' \
"$new_mode" "$new_content" "$new_enabled" "$new_origin" > "$state_tmp" &&
mv -f "$state_tmp" "$state_file"
); then
rm -f "$state_tmp"
return 1
fi
state_mode=$new_mode
state_content=$new_content
state_enabled=$new_enabled
state_origin=$new_origin
}
legacy_init_exists()
{
[ -e "$legacy_init" ] || [ -L "$legacy_init" ]
}
find_recovery_candidate()
{
recovery_candidate=
for candidate in \
"$legacy_init" \
"$legacy_init.dpkg-bak" \
"$legacy_init.dpkg-backup" \
"$legacy_init.dpkg-remove"; do
if [ -e "$candidate" ] || [ -L "$candidate" ]; then
recovery_candidate=$candidate
return 0
fi
done
return 1
}
stash_candidate()
{
stash_source=$1
ensure_state_dir
stash_tmp="$state_stash.tmp.$$"
rm -f "$stash_tmp"
if ! cp -a "$stash_source" "$stash_tmp" ||
! mv -f "$stash_tmp" "$state_stash"; then
rm -f "$stash_tmp"
return 1
fi
}
shared_init_state()
{
if [ ! -x "$init_compat" ]; then
echo "Missing xcatd init compatibility helper: $init_compat" >&2
return 1
fi
"$init_compat" "$@"
}
legacy_enable_state()
{
if ! detected_legacy_state=$(shared_init_state debian-legacy-state); then
return 1
fi
case "$detected_legacy_state" in
enabled) printf '%s\n' yes ;;
disabled) printf '%s\n' no ;;
unregistered) printf '%s\n' unregistered ;;
*)
echo "Invalid legacy state from $init_compat: $detected_legacy_state" >&2
return 1
;;
esac
}
systemd_enable_state()
{
if ! detected_systemd_state=$(shared_init_state systemd-state --allow-unknown); then
return 1
fi
case "$detected_systemd_state" in
enabled) printf '%s\n' yes ;;
disabled) printf '%s\n' no ;;
masked) printf '%s\n' unregistered ;;
unknown) printf '%s\n' unknown ;;
*)
echo "Invalid systemd state from $init_compat: $detected_systemd_state" >&2
return 1
;;
esac
}
validate_context()
{
case "$1" in
fresh|upgrade) ;;
*)
echo "Invalid xcatd init transition context: $1" >&2
exit 2
;;
esac
}
prepare_systemd()
{
transition_context=$1
validate_context "$transition_context"
if [ -e "$state_file" ]; then
read_state
had_state=yes
else
had_state=no
state_mode=unknown
state_content=unknown
state_enabled=unknown
state_origin=unknown
if [ -f "$old_systemd_marker" ]; then
state_mode=systemd
state_content=package-default
fi
fi
previous_mode=$state_mode
previous_origin=$state_origin
known_legacy=no
case "$previous_mode" in
systemd|transition-systemd|transition-legacy) from_systemd=yes ;;
*) from_systemd=no ;;
esac
case "$previous_mode" in
transition-systemd|transition-legacy) transition_origin=$previous_origin ;;
systemd) transition_origin=systemd ;;
legacy) transition_origin=legacy ;;
*)
if [ "$transition_context" = fresh ]; then
transition_origin=fresh
else
transition_origin=systemd
fi
;;
esac
detected_systemd_enabled=$(systemd_enable_state)
if [ "$had_state" = no ] && \
{ [ -e "$pending_stash" ] || [ -L "$pending_stash" ]; }; then
stash_candidate "$pending_stash"
state_content=stashed
known_legacy=yes
transition_origin=legacy
elif [ "$had_state" = no ] && [ -f "$pending_deleted" ]; then
rm -f "$state_stash"
state_content=deleted
known_legacy=yes
transition_origin=legacy
elif [ "$previous_mode" = transition-legacy ]; then
case "$state_content" in
stashed)
if [ ! -e "$state_stash" ] && [ ! -L "$state_stash" ]; then
echo "Stashed xcatd init script is missing: $state_stash" >&2
exit 1
fi
;;
active)
if legacy_init_exists; then
stash_candidate "$legacy_init"
state_content=stashed
else
echo "Active xcatd init transition has no recoverable script" >&2
exit 1
fi
;;
esac
elif legacy_init_exists; then
stash_candidate "$legacy_init"
state_content=stashed
if [ "$from_systemd" = no ]; then
known_legacy=yes
transition_origin=legacy
fi
elif [ "$from_systemd" = no ] && find_recovery_candidate; then
stash_candidate "$recovery_candidate"
state_content=stashed
known_legacy=yes
transition_origin=legacy
elif [ "$previous_mode" = legacy ]; then
rm -f "$state_stash"
state_content=deleted
known_legacy=yes
transition_origin=legacy
elif [ "$from_systemd" = no ] && [ "$transition_context" = fresh ]; then
rm -f "$state_stash"
state_content=package-default
transition_origin=fresh
elif [ "$from_systemd" = no ]; then
rm -f "$state_stash"
if [ "$detected_systemd_enabled" = yes ]; then
state_content=package-default
else
state_content=unknown
fi
fi
if [ "$detected_systemd_enabled" = unregistered ]; then
state_enabled=unregistered
transition_origin=systemd
elif [ "$transition_context" = fresh ]; then
state_enabled=yes
transition_origin=fresh
elif [ "$known_legacy" = yes ]; then
if [ -r "$pending_enabled" ]; then
pending_enabled_value=$(sed -n '1p' "$pending_enabled")
else
pending_enabled_value=unknown
fi
case "$pending_enabled_value" in
yes|no|unregistered) legacy_enabled=$pending_enabled_value ;;
*) legacy_enabled=$(legacy_enable_state) ;;
esac
if [ "$legacy_enabled" = yes ]; then
state_enabled=yes
elif [ "$had_state" = no ]; then
if [ "$detected_systemd_enabled" = yes ]; then
state_enabled=yes
else
state_enabled=$legacy_enabled
fi
else
state_enabled=$legacy_enabled
fi
elif { [ "$previous_mode" = transition-systemd ] ||
[ "$previous_mode" = transition-legacy ]; } && \
[ "$state_enabled" != unknown ]; then
:
else
case "$detected_systemd_enabled" in
yes) state_enabled=yes ;;
no)
if [ "$state_enabled" != unregistered ]; then
state_enabled=no
fi
;;
unregistered) state_enabled=unregistered ;;
esac
fi
write_state transition-systemd "$state_content" "$state_enabled" "$transition_origin"
rm -f "$pending_stash" "$pending_deleted" "$pending_enabled"
}
commit_systemd()
{
read_state
write_state systemd "$state_content" "$state_enabled" unknown
rm -f "$old_systemd_marker"
}
apply_legacy_registration()
{
command -v update-rc.d >/dev/null 2>&1 || return 0
case "$state_content" in
deleted|unknown)
update-rc.d -f xcatd remove
return
;;
esac
if [ -x "$legacy_init" ]; then
case "$state_enabled" in
yes)
update-rc.d xcatd defaults
;;
no)
update-rc.d xcatd defaults
if ! update-rc.d xcatd disable; then
# defaults preserves any existing registration, including
# links outside xcatd's declared start runlevels that the
# native disable operation cannot toggle. Canonicalize
# only after that unusable layout is rejected.
update-rc.d -f xcatd remove
update-rc.d xcatd defaults
update-rc.d xcatd disable
fi
;;
unregistered|unknown)
update-rc.d -f xcatd remove
;;
esac
else
update-rc.d -f xcatd remove
fi
}
configure_legacy()
{
transition_context=$1
validate_context "$transition_context"
returning_from_systemd=no
refresh_systemd_enabled=no
resuming_legacy_transition=no
resuming_stashed_transition=no
stable_legacy_upgrade=no
detected_systemd_enabled=unknown
transition_origin=legacy
if [ -e "$state_file" ]; then
read_state
had_state=yes
if [ "$state_mode" = legacy ] && \
[ "$transition_context" = upgrade ]; then
stable_legacy_upgrade=yes
else
detected_systemd_enabled=$(systemd_enable_state)
fi
if [ "$state_mode" = transition-legacy ]; then
resuming_legacy_transition=yes
if [ "$state_content" = stashed ] &&
{ [ -e "$state_stash" ] || [ -L "$state_stash" ]; }; then
resuming_stashed_transition=yes
fi
fi
case "$state_mode" in
systemd)
returning_from_systemd=yes
refresh_systemd_enabled=yes
transition_origin=systemd
;;
transition-systemd)
returning_from_systemd=yes
transition_origin=$state_origin
;;
transition-legacy)
transition_origin=$state_origin
returning_from_systemd=yes
;;
legacy)
transition_origin=legacy
if legacy_init_exists; then
state_content=active
else
state_content=deleted
fi
if [ "$transition_context" = upgrade ]; then
state_enabled=$(legacy_enable_state)
fi
;;
esac
else
had_state=no
detected_systemd_enabled=$(systemd_enable_state)
state_mode=unknown
state_content=unknown
state_enabled=unknown
state_origin=unknown
if [ -e "$pending_stash" ] || [ -L "$pending_stash" ]; then
stash_candidate "$pending_stash"
state_content=stashed
transition_origin=legacy
elif [ -f "$pending_deleted" ]; then
rm -f "$state_stash"
state_content=deleted
transition_origin=legacy
elif [ -f "$old_systemd_marker" ]; then
returning_from_systemd=yes
refresh_systemd_enabled=yes
transition_origin=systemd
state_content=package-default
elif find_recovery_candidate; then
if [ "$recovery_candidate" = "$legacy_init" ]; then
state_content=active
transition_origin=legacy
else
stash_candidate "$recovery_candidate"
state_content=stashed
returning_from_systemd=yes
refresh_systemd_enabled=yes
transition_origin=systemd
fi
elif [ "$transition_context" = fresh ]; then
state_content=package-default
state_enabled=yes
transition_origin=fresh
else
returning_from_systemd=yes
refresh_systemd_enabled=yes
transition_origin=systemd
state_enabled=$detected_systemd_enabled
if [ "$detected_systemd_enabled" = yes ]; then
state_content=package-default
fi
fi
fi
if legacy_init_exists && [ "$resuming_legacy_transition" = no ]; then
case "$returning_from_systemd:$state_content" in
yes:*|*:active)
stash_candidate "$legacy_init"
state_content=stashed
;;
esac
fi
if [ "$stable_legacy_upgrade" = yes ]; then
:
elif [ "$detected_systemd_enabled" = unregistered ]; then
state_enabled=unregistered
transition_origin=systemd
elif [ "$transition_context" = fresh ]; then
state_enabled=yes
transition_origin=fresh
elif [ "$had_state" = no ]; then
if [ -r "$pending_enabled" ]; then
pending_enabled_value=$(sed -n '1p' "$pending_enabled")
else
pending_enabled_value=unknown
fi
case "$pending_enabled_value" in
yes|no|unregistered) legacy_enabled=$pending_enabled_value ;;
*) legacy_enabled=$(legacy_enable_state) ;;
esac
if [ "$legacy_enabled" = yes ] || \
[ "$detected_systemd_enabled" = yes ]; then
state_enabled=yes
elif [ "$transition_origin" = legacy ] && \
{ [ "$legacy_enabled" = no ] || \
[ "$legacy_enabled" = unregistered ]; }; then
state_enabled=$legacy_enabled
elif [ "$detected_systemd_enabled" = no ]; then
state_enabled=no
elif [ "$detected_systemd_enabled" = unregistered ]; then
state_enabled=unregistered
else
state_enabled=$legacy_enabled
fi
elif [ "$refresh_systemd_enabled" = yes ]; then
case "$detected_systemd_enabled" in
yes) state_enabled=yes ;;
no)
if [ "$state_enabled" != unregistered ]; then
state_enabled=no
fi
;;
unregistered) state_enabled=unregistered ;;
esac
fi
write_state transition-legacy "$state_content" "$state_enabled" "$transition_origin"
case "$state_content" in
stashed)
if [ ! -e "$state_stash" ] && [ ! -L "$state_stash" ]; then
echo "Stashed xcatd init script is missing: $state_stash" >&2
exit 1
fi
if [ "$resuming_stashed_transition" = yes ]; then
rm -f "$legacy_init"
mkdir -p "$compat_root/etc/init.d"
cp -a "$state_stash" "$legacy_init"
elif ! legacy_init_exists; then
mkdir -p "$compat_root/etc/init.d"
cp -a "$state_stash" "$legacy_init"
fi
ucf "$legacy_template" "$legacy_init"
;;
active)
if legacy_init_exists; then
ucf "$legacy_template" "$legacy_init"
fi
;;
package-default)
mkdir -p "$compat_root/etc/init.d"
if [ "$resuming_legacy_transition" = yes ]; then
rm -f "$legacy_init"
fi
UCF_FORCE_CONFFMISS=1 ucf "$legacy_template" "$legacy_init"
;;
deleted)
rm -f "$legacy_init"
;;
unknown)
echo "xcat-server: prior xcatd init state is ambiguous; preserving its absence" >&2
;;
esac
ucfr xcat-server "$legacy_init"
if [ "$transition_context" = fresh ] || \
[ "$returning_from_systemd" = yes ] || [ "$had_state" = no ]; then
apply_legacy_registration
fi
if legacy_init_exists; then
final_content=active
else
final_content=deleted
fi
final_enabled=$(legacy_enable_state)
write_state legacy "$final_content" "$final_enabled" unknown
rm -f "$state_stash" "$old_systemd_marker" \
"$pending_stash" "$pending_deleted" "$pending_enabled" \
"$legacy_init.dpkg-bak" "$legacy_init.dpkg-backup" \
"$legacy_init.dpkg-remove"
}
get_state()
{
requested_field=$1
read_state
case "$requested_field" in
mode) printf '%s\n' "$state_mode" ;;
content) printf '%s\n' "$state_content" ;;
enabled) printf '%s\n' "$state_enabled" ;;
origin) printf '%s\n' "$state_origin" ;;
*)
echo "Usage: $0 get {mode|content|enabled|origin}" >&2
exit 2
;;
esac
}
case "${1:-}" in
prepare-systemd)
prepare_systemd "${2:-}"
;;
commit-systemd)
[ "$#" -eq 1 ] || exit 2
commit_systemd
;;
configure-legacy)
configure_legacy "${2:-}"
;;
get)
get_state "${2:-}"
;;
*)
echo "Usage: $0 {prepare-systemd {fresh|upgrade}|commit-systemd|configure-legacy {fresh|upgrade}|get {mode|content|enabled|origin}}" >&2
exit 2
;;
esac