2
0
mirror of https://github.com/xcat2/xcat-core.git synced 2026-07-31 10:09:40 +00:00

Merge pull request #7614 from VersatusHPC/fix/xcatd-rpm-init-state

fix(packaging): preserve xcatd init state across RPM transactions
This commit is contained in:
Daniel Hilst
2026-07-24 12:25:28 -03:00
committed by GitHub
3 changed files with 890 additions and 145 deletions
+348 -35
View File
@@ -17,11 +17,12 @@ esac
legacy_init="$compat_root/etc/init.d/xcatd"
legacy_template="$compat_root$xcat_root/share/xcat/scripts/xcatd"
managed_marker="$compat_root/var/lib/xcat/xcatd-init-compat-managed"
uses_systemd_os_release()
systemd_os_release_status()
{
os_release="$compat_root/etc/os-release"
[ -r "$os_release" ] || return 1
[ -r "$os_release" ] || return 2
os_id=
os_version=
@@ -43,42 +44,72 @@ uses_systemd_os_release()
os_major=${os_version%%.*}
case "$os_major" in
''|*[!0-9]*) return 1 ;;
''|*[!0-9]*) return 2 ;;
esac
case "$os_id" in
rhel|centos|rocky|almalinux|ol|scientific)
[ "$os_major" -ge 7 ]
;;
fedora|ubuntu)
[ "$os_major" -ge 15 ]
;;
debian)
[ "$os_major" -ge 8 ]
;;
sles|sled|opensuse|opensuse-leap|opensuse-tumbleweed)
[ "$os_major" -ge 12 ]
;;
*)
[ "$os_major" -ge 7 ] && return 0
return 1
;;
fedora|ubuntu)
[ "$os_major" -ge 15 ] && return 0
return 1
;;
debian)
[ "$os_major" -ge 8 ] && return 0
return 1
;;
sles|sled|opensuse|opensuse-leap|opensuse-tumbleweed)
[ "$os_major" -ge 12 ] && return 0
return 1
;;
*)
return 2
;;
esac
}
uses_systemd()
{
detection_mode=${1:-compat}
if [ -d "$compat_root/run/systemd/system" ]; then
return 0
fi
# Chroots and minimal images may have systemd installed without a running
# manager or an /sbin/init link.
if [ "$detection_mode" = explicit ]; then
if [ -L "$compat_root/sbin/init" ]; then
init_target=$(readlink "$compat_root/sbin/init" 2>/dev/null || true)
case "$init_target" in
*systemd*) return 0 ;;
*) return 1 ;;
esac
fi
if [ -e "$compat_root/sbin/init" ]; then
return 1
fi
if systemd_os_release_status; then
return 0
else
os_release_status=$?
if [ "$os_release_status" -eq 1 ]; then
return 1
fi
fi
fi
# Preserve the historical fallback order unless every package lifecycle
# phase has explicitly opted in to authoritative /sbin/init detection.
if [ -x "$compat_root/usr/lib/systemd/systemd" ] ||
[ -x "$compat_root/lib/systemd/systemd" ]; then
return 0
fi
if uses_systemd_os_release; then
if systemd_os_release_status; then
return 0
fi
@@ -92,26 +123,253 @@ uses_systemd()
return 1
}
can_use_systemctl()
{
[ -d /run/systemd/system ] && command -v systemctl >/dev/null 2>&1
}
legacy_state()
{
for legacy_link in \
"$compat_root"/etc/rc.d/rc?.d/S??xcatd \
"$compat_root"/etc/rc?.d/S??xcatd \
"$compat_root"/etc/init.d/rc?.d/S??xcatd
do
if [ -e "$legacy_link" ] || [ -L "$legacy_link" ]; then
echo enabled
return
fi
done
for legacy_link in \
"$compat_root"/etc/rc.d/rc?.d/K??xcatd \
"$compat_root"/etc/rc?.d/K??xcatd \
"$compat_root"/etc/init.d/rc?.d/K??xcatd
do
if [ -e "$legacy_link" ] || [ -L "$legacy_link" ]; then
echo disabled
return
fi
done
echo unregistered
}
has_legacy_provenance()
{
[ -e "$legacy_init" ] || [ -L "$legacy_init" ] ||
[ -f "$managed_marker" ]
}
legacy_transition_state()
{
transition_state=$(legacy_state)
if [ "$transition_state" != unregistered ]; then
echo "$transition_state"
return
fi
transition_state=$(systemd_state)
case "$transition_state" in
enabled|masked)
echo "$transition_state"
;;
disabled)
if has_legacy_provenance; then
echo unregistered
else
echo disabled
fi
;;
esac
}
systemd_state()
{
for systemd_mask in \
"$compat_root/etc/systemd/system/xcatd.service" \
"$compat_root/run/systemd/system/xcatd.service"
do
if [ -L "$systemd_mask" ]; then
systemd_mask_target=$(readlink "$systemd_mask" 2>/dev/null || true)
case "$systemd_mask_target" in
/dev/null|*/dev/null)
echo masked
return
;;
esac
fi
done
if [ -z "$compat_root" ] && command -v systemctl >/dev/null 2>&1; then
detected_systemd_state=$(systemctl is-enabled xcatd.service 2>/dev/null || true)
case "$detected_systemd_state" in
enabled*)
echo enabled
return
;;
masked*)
echo masked
return
;;
esac
fi
for systemd_link in \
"$compat_root"/etc/systemd/system/*.wants/xcatd.service \
"$compat_root"/etc/systemd/system/*.requires/xcatd.service \
"$compat_root"/run/systemd/system/*.wants/xcatd.service \
"$compat_root"/run/systemd/system/*.requires/xcatd.service
do
if [ -e "$systemd_link" ] || [ -L "$systemd_link" ]; then
echo enabled
return
fi
done
echo disabled
}
register_legacy_init()
{
desired_state=$1
if [ -n "$compat_root" ]; then
echo "Registering legacy xcatd is not supported with XCAT_COMPAT_ROOT" >&2
return 2
fi
if [ -x /sbin/chkconfig ]; then
/sbin/chkconfig --add xcatd || return
case "$desired_state" in
default) : ;;
enabled) /sbin/chkconfig --level 345 xcatd on ;;
disabled) /sbin/chkconfig xcatd off ;;
esac
elif { [ "$desired_state" = default ] || [ "$desired_state" = enabled ]; } &&
[ -x /usr/lib/lsb/install_initd ]; then
/usr/lib/lsb/install_initd /etc/init.d/xcatd
elif [ "$desired_state" = disabled ] && [ -x /usr/lib/lsb/remove_initd ]; then
# The LSB fallback has no portable register-but-disabled operation.
/usr/lib/lsb/remove_initd /etc/init.d/xcatd
else
echo "Unable to register init scripts on this system"
fi
}
unregister_legacy_init()
{
if [ -z "$compat_root" ] &&
{ [ -e "$legacy_init" ] || [ -L "$legacy_init" ]; }; then
if [ -x /sbin/chkconfig ]; then
/sbin/chkconfig --del xcatd || true
elif [ -x /usr/lib/lsb/remove_initd ]; then
/usr/lib/lsb/remove_initd /etc/init.d/xcatd || true
fi
fi
rm -f \
"$compat_root"/etc/rc.d/rc?.d/[SK]??xcatd \
"$compat_root"/etc/rc?.d/[SK]??xcatd \
"$compat_root"/etc/init.d/rc?.d/[SK]??xcatd
}
disable_systemd_init()
{
disable_mode=${1:-host-tools}
if [ "$disable_mode" != links-only ] &&
[ -z "$compat_root" ] && command -v systemctl >/dev/null 2>&1; then
systemctl disable xcatd.service >/dev/null 2>&1 || true
fi
# Preserve administrator masks while removing only boot enablement links.
rm -f \
"$compat_root"/etc/systemd/system/*.wants/xcatd.service \
"$compat_root"/etc/systemd/system/*.requires/xcatd.service \
"$compat_root"/run/systemd/system/*.wants/xcatd.service \
"$compat_root"/run/systemd/system/*.requires/xcatd.service
}
unregister_all_init()
{
unregister_status=0
disable_systemd_init || unregister_status=$?
unregister_legacy_init || unregister_status=$?
return "$unregister_status"
}
legacy_init_is_managed()
{
[ -f "$managed_marker" ] &&
[ -f "$legacy_init" ] && [ ! -L "$legacy_init" ] &&
[ -r "$legacy_template" ] && cmp -s "$legacy_template" "$legacy_init"
}
mark_legacy_init_managed()
{
marker_dir=${managed_marker%/*}
marker_tmp="$managed_marker.tmp.$$"
mkdir -p -m 0755 "$marker_dir" || return 1
umask 077
: > "$marker_tmp" || return 1
if ! mv -f "$marker_tmp" "$managed_marker"; then
rm -f "$marker_tmp"
return 1
fi
}
remove_legacy_init()
{
rm -f "$legacy_init" || return 1
rm -f "$managed_marker"
}
remove_managed_legacy_init()
{
if legacy_init_is_managed; then
rm -f "$legacy_init" || return 1
fi
rm -f "$managed_marker"
}
configure_legacy_init()
{
replace_existing=${1:-}
replace_existing=0
detection_mode=compat
track_managed=0
for configure_option in "$@"; do
case "$configure_option" in
--replace) replace_existing=1 ;;
--explicit-target) detection_mode=explicit ;;
--track-managed) track_managed=1 ;;
*)
echo "Usage: $0 configure [--replace] [--explicit-target] [--track-managed]" >&2
exit 2
;;
esac
done
if uses_systemd; then
rm -f "$legacy_init"
if uses_systemd "$detection_mode"; then
rm -f "$legacy_init" || return 1
rm -f "$managed_marker" || return 1
return
fi
# A broken link cannot provide a usable init script. Replace it while
# preserving links whose administrator-managed target still exists.
if [ -L "$legacy_init" ] && [ ! -e "$legacy_init" ]; then
rm -f "$legacy_init"
rm -f "$legacy_init" || return 1
fi
if [ -e "$legacy_init" ] || [ -L "$legacy_init" ]; then
if [ "$replace_existing" != "--replace" ]; then
if [ "$replace_existing" -ne 1 ]; then
if [ -f "$managed_marker" ] && ! legacy_init_is_managed; then
rm -f "$managed_marker" || return 1
fi
return
fi
rm -f "$legacy_init"
rm -f "$legacy_init" || return 1
fi
if [ ! -r "$legacy_template" ]; then
@@ -119,30 +377,85 @@ configure_legacy_init()
exit 1
fi
mkdir -p "$compat_root/etc/init.d"
cp "$legacy_template" "$legacy_init"
chmod 755 "$legacy_init"
mkdir -p "$compat_root/etc/init.d" || return 1
cp "$legacy_template" "$legacy_init" || return 1
chmod 755 "$legacy_init" || return 1
if [ "$track_managed" -eq 1 ]; then
mark_legacy_init_managed || return 1
else
rm -f "$managed_marker" || return 1
fi
}
case "${1:-}" in
uses-systemd)
uses_systemd
;;
configure)
detection_mode=compat
case "${2:-}" in
''|--replace) ;;
'') ;;
--explicit-target) detection_mode=explicit ;;
*)
echo "Usage: $0 {uses-systemd|configure [--replace]|remove}" >&2
echo "Usage: $0 uses-systemd [--explicit-target]" >&2
exit 2
;;
esac
configure_legacy_init "${2:-}"
uses_systemd "$detection_mode"
;;
can-use-systemctl)
case "${2:-}" in
'') can_use_systemctl ;;
*)
echo "Usage: $0 can-use-systemctl" >&2
exit 2
;;
esac
;;
legacy-state)
legacy_state
;;
legacy-transition-state)
legacy_transition_state
;;
systemd-state)
systemd_state
;;
register-legacy)
case "${2:-}" in
default|enabled|disabled) ;;
*)
echo "Usage: $0 register-legacy {default|enabled|disabled}" >&2
exit 2
;;
esac
register_legacy_init "$2"
;;
unregister-legacy)
unregister_legacy_init
;;
disable-systemd)
case "${2:-}" in
'') disable_systemd_init ;;
--links-only) disable_systemd_init links-only ;;
*)
echo "Usage: $0 disable-systemd [--links-only]" >&2
exit 2
;;
esac
;;
unregister-all)
unregister_all_init
;;
configure)
shift
configure_legacy_init "$@"
;;
remove)
rm -f "$legacy_init"
remove_legacy_init
;;
remove-managed)
remove_managed_legacy_init
;;
*)
echo "Usage: $0 {uses-systemd|configure [--replace]|remove}" >&2
echo "Usage: $0 {uses-systemd [--explicit-target]|can-use-systemctl|legacy-state|legacy-transition-state|systemd-state|register-legacy {default|enabled|disabled}|unregister-legacy|disable-systemd [--links-only]|unregister-all|configure [--replace] [--explicit-target] [--track-managed]|remove|remove-managed}" >&2
exit 2
;;
esac
+70 -79
View File
@@ -369,17 +369,6 @@ chmod 644 $RPM_BUILD_ROOT/%{prefix}/lib/shfunctions
%ifos linux
cp etc/init.d/xcatd $RPM_BUILD_ROOT/%{prefix}/share/xcat/scripts/xcatd
chmod 755 $RPM_BUILD_ROOT/%{prefix}/share/xcat/scripts/xcatd
# Keep ownership continuity only on legacy RPM targets where the script is
# materialized at runtime. Modern RPM versions can create /etc/init.d from a
# ghost entry alone, which breaks later chkconfig/initscripts transactions.
%if 0%{?rhel} && 0%{?rhel} < 7
mkdir -p $RPM_BUILD_ROOT/etc/init.d
touch $RPM_BUILD_ROOT/etc/init.d/xcatd
%endif
%if 0%{?suse_version} && 0%{?suse_version} < 1200
mkdir -p $RPM_BUILD_ROOT/etc/init.d
touch $RPM_BUILD_ROOT/etc/init.d/xcatd
%endif
mkdir -p $RPM_BUILD_ROOT/usr/lib/systemd/system
cp etc/init.d/xcatd.service $RPM_BUILD_ROOT/usr/lib/systemd/system
%else
@@ -453,12 +442,8 @@ rm -rf $RPM_BUILD_ROOT
%if %fsm
%else
%ifos linux
%if 0%{?rhel} && 0%{?rhel} < 7
%ghost %attr(0755,root,root) /etc/init.d/xcatd
%endif
%if 0%{?suse_version} && 0%{?suse_version} < 1200
%ghost %attr(0755,root,root) /etc/init.d/xcatd
%endif
# The compatibility helper owns runtime cleanup. Listing this path as %ghost
# would let RPM unlink preserved administrator content after %preun.
/usr/lib/systemd/system/xcatd.service
%else
/etc/init.d/xcatd
@@ -519,78 +504,82 @@ ln -sf $RPM_INSTALL_PREFIX0/share/xcat/netboot/sles $RPM_INSTALL_PREFIX0/share/x
ln -sf $RPM_INSTALL_PREFIX0/share/xcat/install/centos $RPM_INSTALL_PREFIX0/share/xcat/install/centos-stream
ln -sf $RPM_INSTALL_PREFIX0/share/xcat/netboot/centos $RPM_INSTALL_PREFIX0/share/xcat/netboot/centos-stream
xcat_can_use_systemctl()
{
[ -d /run/systemd/system ] && command -v systemctl >/dev/null 2>&1
}
xcatd_init_compat=$RPM_INSTALL_PREFIX0/share/xcat/scripts/xcatd-init-compat
export XCATROOT="$RPM_INSTALL_PREFIX0"
if [ "$1" = "1" ]; then #Only if installing for the first time..
if "$xcatd_init_compat" uses-systemd; then
"$xcatd_init_compat" configure || exit 1
if xcat_can_use_systemctl; then
if "$xcatd_init_compat" uses-systemd --explicit-target; then
"$xcatd_init_compat" configure --explicit-target || exit 1
if "$xcatd_init_compat" can-use-systemctl; then
systemctl daemon-reload
fi
if command -v systemctl >/dev/null 2>&1; then
systemctl enable xcatd.service
fi
else
"$xcatd_init_compat" configure || exit 1
if [ -x /sbin/chkconfig ]; then
/sbin/chkconfig --add xcatd
elif [ -x /usr/lib/lsb/install_initd ]; then
/usr/lib/lsb/install_initd /etc/init.d/xcatd
else
echo "Unable to register init scripts on this system"
fi
"$xcatd_init_compat" configure --explicit-target --track-managed || exit 1
legacy_xcatd_state=$("$xcatd_init_compat" legacy-state)
case "$legacy_xcatd_state" in
enabled|disabled)
# Preserve any pre-existing administrator runlevel layout.
:
;;
*)
# Preserve the script's declared defaults on first install.
if [ "$("$xcatd_init_compat" systemd-state)" != masked ]; then
"$xcatd_init_compat" register-legacy default
fi
;;
esac
fi
fi
if [ "$1" -gt "1" ]; then #only on upgrade...
if "$xcatd_init_compat" uses-systemd; then
legacy_xcatd_enabled=0
for legacy_xcatd_link in \
/etc/rc.d/rc?.d/S??xcatd \
/etc/rc?.d/S??xcatd \
/etc/init.d/rc?.d/S??xcatd
do
if [ -e "$legacy_xcatd_link" ] || [ -L "$legacy_xcatd_link" ]; then
legacy_xcatd_enabled=1
break
fi
done
if [ -e /etc/init.d/xcatd ] || [ -L /etc/init.d/xcatd ]; then
if [ -x /sbin/chkconfig ]; then
/sbin/chkconfig --del xcatd
elif [ -x /usr/lib/lsb/remove_initd ]; then
/usr/lib/lsb/remove_initd /etc/init.d/xcatd
fi
fi
rm -f \
/etc/rc.d/rc?.d/[SK]??xcatd \
/etc/rc?.d/[SK]??xcatd \
/etc/init.d/rc?.d/[SK]??xcatd
if "$xcatd_init_compat" uses-systemd --explicit-target; then
legacy_xcatd_state=$("$xcatd_init_compat" legacy-state)
"$xcatd_init_compat" unregister-legacy
"$xcatd_init_compat" configure || exit 1
"$xcatd_init_compat" configure --explicit-target || exit 1
%if 0%{?suse_version}
%else
# Remove only an empty directory left by an older xCAT RPM. SUSE base
# packages own /etc/init.d, so leave that directory intact there.
rmdir /etc/init.d 2>/dev/null || true
%endif
if xcat_can_use_systemctl; then
if "$xcatd_init_compat" can-use-systemctl; then
systemctl daemon-reload
fi
if [ "$legacy_xcatd_enabled" = "1" ] && command -v systemctl >/dev/null 2>&1; then
if [ "$legacy_xcatd_state" = enabled ] && command -v systemctl >/dev/null 2>&1; then
# Preserve the previous enabled state without enabling HA nodes by default.
systemctl enable xcatd.service
fi
else
legacy_xcatd_registration=$("$xcatd_init_compat" legacy-state)
legacy_xcatd_state=$("$xcatd_init_compat" legacy-transition-state)
"$xcatd_init_compat" disable-systemd --links-only
# systemctl may delegate to systemd-sysv-install and recreate K links for
# a pre-existing SysV script. Restore the captured unregistered or masked
# state before materializing the package's replacement script.
case "$legacy_xcatd_state" in
masked|unregistered)
"$xcatd_init_compat" unregister-legacy
;;
esac
# This path was previously a regular RPM payload file, so upgrades replaced
# it with the current package version rather than retaining an older copy.
"$xcatd_init_compat" configure --replace || exit 1
"$xcatd_init_compat" configure --replace --explicit-target --track-managed || exit 1
case "$legacy_xcatd_state" in
enabled|disabled)
if [ "$legacy_xcatd_registration" = unregistered ]; then
"$xcatd_init_compat" register-legacy "$legacy_xcatd_state"
fi
;;
masked|unregistered)
;;
esac
fi
#migration issue for monitoring
XCATROOT=$RPM_INSTALL_PREFIX0 $RPM_INSTALL_PREFIX0/sbin/chtab filename=monitorctrl.pm notification -d
@@ -625,37 +614,39 @@ fi
exit 0
%posttrans
%ifos linux
xcatd_init_compat=$RPM_INSTALL_PREFIX0/share/xcat/scripts/xcatd-init-compat
# During upgrades from pre-compatibility RPMs, old payload cleanup can remove
# the generated init script after the new post-install scriptlet. Restore only
# a missing runtime script so fresh installs retain administrator-managed files.
if [ -x "$xcatd_init_compat" ] &&
! XCATROOT="$RPM_INSTALL_PREFIX0" "$xcatd_init_compat" uses-systemd --explicit-target &&
[ ! -e /etc/init.d/xcatd ] && [ ! -L /etc/init.d/xcatd ]; then
XCATROOT="$RPM_INSTALL_PREFIX0" \
"$xcatd_init_compat" configure --explicit-target --track-managed || exit 1
fi
%endif
%preun
%ifos linux
xcat_can_use_systemctl()
{
[ -d /run/systemd/system ] && command -v systemctl >/dev/null 2>&1
}
xcatd_init_compat=$RPM_INSTALL_PREFIX0/share/xcat/scripts/xcatd-init-compat
export XCATROOT="$RPM_INSTALL_PREFIX0"
if [ $1 == 0 ]; then #This means only on -e
if [ -f "/proc/cmdline" ]; then # prevent running it during install into chroot image
if xcat_can_use_systemctl; then
if "$xcatd_init_compat" can-use-systemctl; then
systemctl stop xcatd.service
elif ! "$xcatd_init_compat" uses-systemd && [ -x /etc/init.d/xcatd ]; then
elif ! "$xcatd_init_compat" uses-systemd --explicit-target && [ -x /etc/init.d/xcatd ]; then
/etc/init.d/xcatd stop
fi
fi
if "$xcatd_init_compat" uses-systemd; then
if command -v systemctl >/dev/null 2>&1; then
systemctl disable xcatd.service
fi
else
if [ -x /sbin/chkconfig ]; then
/sbin/chkconfig --del xcatd
elif [ -x /usr/lib/lsb/remove_initd ]; then
/usr/lib/lsb/remove_initd /etc/init.d/xcatd
fi
fi
"$xcatd_init_compat" remove
# Preserve RPM's existing recoverable erase policy if local service state
# cannot be cleaned completely; no package payload remains afterward.
"$xcatd_init_compat" unregister-all || true
"$xcatd_init_compat" remove-managed || true
rm -f /usr/sbin/xcatd #remove the symbolic
fi
+472 -31
View File
@@ -36,6 +36,19 @@ sub stage_root {
return $root;
}
sub stage_systemd_binary {
my ($root) = @_;
my $binary = File::Spec->catfile(
$root, 'usr', 'lib', 'systemd', 'systemd'
);
make_path( File::Spec->catdir( $root, 'usr', 'lib', 'systemd' ) );
open( my $fh, '>', $binary )
or die "Unable to stage systemd binary: $!";
close($fh);
chmod 0755, $binary;
return $binary;
}
sub run_helper {
my ( $root, @arguments ) = @_;
local $ENV{XCAT_COMPAT_ROOT} = $root;
@@ -44,11 +57,40 @@ sub run_helper {
return $? >> 8;
}
sub helper_output {
my ( $root, @arguments ) = @_;
local $ENV{XCAT_COMPAT_ROOT} = $root;
local $ENV{XCATROOT} = '/opt/xcat';
open( my $pipe, '-|', '/bin/sh', $helper, @arguments )
or die "Unable to run compatibility helper: $!";
my $output = do { local $/; <$pipe> };
close($pipe) or die "Compatibility helper failed: $?";
$output =~ s/\s+\z//;
return $output;
}
sub stage_symlink {
my ( $root, $target, @path ) = @_;
my $name = pop @path;
my $dir = File::Spec->catdir( $root, @path );
make_path($dir);
my $link = File::Spec->catfile( $dir, $name );
symlink( $target, $link ) or die "Unable to stage $link: $!";
return $link;
}
sub legacy_init {
my ($root) = @_;
return File::Spec->catfile( $root, 'etc', 'init.d', 'xcatd' );
}
sub managed_marker {
my ($root) = @_;
return File::Spec->catfile(
$root, 'var', 'lib', 'xcat', 'xcatd-init-compat-managed'
);
}
my $active_systemd_root = stage_root();
make_path( File::Spec->catdir( $active_systemd_root, 'run', 'systemd', 'system' ) );
make_path( File::Spec->catdir( $active_systemd_root, 'etc', 'init.d' ) );
@@ -63,6 +105,15 @@ is( run_helper( $active_systemd_root, 'configure' ), 0,
ok( !-e legacy_init($active_systemd_root),
'systemd configuration removes an upgraded legacy init script' );
my $active_hybrid_root = stage_root();
make_path( File::Spec->catdir( $active_hybrid_root, 'run', 'systemd', 'system' ) );
make_path( File::Spec->catdir( $active_hybrid_root, 'sbin' ) );
symlink( 'upstart', File::Spec->catfile( $active_hybrid_root, 'sbin', 'init' ) )
or die "Unable to stage stale upstart init symlink: $!";
stage_systemd_binary($active_hybrid_root);
is( run_helper( $active_hybrid_root, 'uses-systemd' ), 0,
'an active systemd manager overrides stale legacy target evidence' );
my $systemd_image_root = stage_root();
make_path( File::Spec->catdir( $systemd_image_root, 'sbin' ) );
symlink( '../lib/systemd/systemd', File::Spec->catfile( $systemd_image_root, 'sbin', 'init' ) )
@@ -75,14 +126,7 @@ ok( !-e legacy_init($systemd_image_root),
'systemd images do not receive the legacy init script' );
my $minimal_systemd_root = stage_root();
my $systemd_binary = File::Spec->catfile(
$minimal_systemd_root, 'usr', 'lib', 'systemd', 'systemd'
);
make_path( File::Spec->catdir( $minimal_systemd_root, 'usr', 'lib', 'systemd' ) );
open( my $minimal_systemd, '>', $systemd_binary )
or die "Unable to stage minimal systemd binary: $!";
close($minimal_systemd);
chmod 0755, $systemd_binary;
stage_systemd_binary($minimal_systemd_root);
is( run_helper( $minimal_systemd_root, 'uses-systemd' ), 0,
'an installed systemd binary identifies a stopped or minimal systemd target' );
is( run_helper( $minimal_systemd_root, 'configure' ), 0,
@@ -98,6 +142,8 @@ print {$modern_os_release} qq{ID="rocky"\nVERSION_ID="9.6"\n};
close($modern_os_release);
is( run_helper( $modern_os_root, 'uses-systemd' ), 0,
'a modern supported distro is systemd even in a stripped-down environment' );
is( run_helper( $modern_os_root, 'uses-systemd', '--explicit-target' ), 0,
'explicit target detection accepts a modern distro without an init path' );
is( run_helper( $modern_os_root, 'configure' ), 0,
'stripped-down modern distro configuration succeeds' );
ok( !-e legacy_init($modern_os_root),
@@ -109,8 +155,24 @@ open( my $legacy_os_release, '>', File::Spec->catfile( $legacy_os_root, 'etc', '
or die "Unable to stage legacy os-release: $!";
print {$legacy_os_release} qq{ID="sles"\nVERSION_ID="11.4"\n};
close($legacy_os_release);
isnt( run_helper( $legacy_os_root, 'uses-systemd' ), 0,
'SLES 11 remains classified as a legacy init target' );
stage_systemd_binary($legacy_os_root);
is( run_helper( $legacy_os_root, 'uses-systemd' ), 0,
'the compatibility classifier preserves installed systemd precedence on SLES 11' );
isnt( run_helper( $legacy_os_root, 'uses-systemd', '--explicit-target' ), 0,
'SLES 11 overrides an installed but inactive systemd binary' );
my $legacy_ubuntu_root = stage_root();
make_path( File::Spec->catdir( $legacy_ubuntu_root, 'etc' ) );
open( my $legacy_ubuntu_release, '>',
File::Spec->catfile( $legacy_ubuntu_root, 'etc', 'os-release' ) )
or die "Unable to stage legacy Ubuntu os-release: $!";
print {$legacy_ubuntu_release} qq{ID=ubuntu\nVERSION_ID=14.04\n};
close($legacy_ubuntu_release);
stage_systemd_binary($legacy_ubuntu_root);
is( run_helper( $legacy_ubuntu_root, 'uses-systemd' ), 0,
'the compatibility classifier preserves installed systemd precedence on Ubuntu 14.04' );
isnt( run_helper( $legacy_ubuntu_root, 'uses-systemd', '--explicit-target' ), 0,
'Ubuntu 14.04 overrides an installed but inactive systemd binary' );
my $debian_sid_root = stage_root();
make_path( File::Spec->catdir( $debian_sid_root, 'etc' ) );
@@ -125,14 +187,188 @@ my $legacy_root = stage_root();
make_path( File::Spec->catdir( $legacy_root, 'sbin' ) );
symlink( 'upstart', File::Spec->catfile( $legacy_root, 'sbin', 'init' ) )
or die "Unable to stage upstart init symlink: $!";
isnt( run_helper( $legacy_root, 'uses-systemd' ), 0,
'an upstart target is not classified as systemd' );
stage_systemd_binary($legacy_root);
is( run_helper( $legacy_root, 'uses-systemd' ), 0,
'the compatibility classifier preserves installed systemd precedence for upstart' );
isnt( run_helper( $legacy_root, 'uses-systemd', '--explicit-target' ), 0,
'an explicit upstart target overrides an installed systemd binary' );
is( run_helper( $legacy_root, 'configure' ), 0,
'legacy configuration succeeds' );
'compatibility-mode configuration succeeds for a hybrid target' );
ok( !-e legacy_init($legacy_root),
'compatibility-mode configuration preserves systemd precedence' );
is( run_helper( $legacy_root, 'configure', '--explicit-target' ), 0,
'explicit-target legacy configuration succeeds' );
ok( -x legacy_init($legacy_root),
'legacy configuration materializes an executable init script' );
'explicit-target configuration materializes an executable init script' );
is( read_file( legacy_init($legacy_root) ), read_file($template),
'the materialized legacy init script matches the packaged template' );
'the explicit-target init script matches the packaged template' );
my $real_sysvinit_root = stage_root();
make_path(
File::Spec->catdir( $real_sysvinit_root, 'sbin' ),
File::Spec->catdir( $real_sysvinit_root, 'etc' )
);
open( my $real_sysvinit, '>',
File::Spec->catfile( $real_sysvinit_root, 'sbin', 'init' ) )
or die "Unable to stage real SysV init: $!";
print {$real_sysvinit} "#!/bin/sh\n";
close($real_sysvinit);
chmod 0755, File::Spec->catfile( $real_sysvinit_root, 'sbin', 'init' );
open( my $real_sysv_os_release, '>',
File::Spec->catfile( $real_sysvinit_root, 'etc', 'os-release' ) )
or die "Unable to stage modern Debian metadata: $!";
print {$real_sysv_os_release} "ID=debian\nVERSION_ID=12\n";
close($real_sysv_os_release);
stage_systemd_binary($real_sysvinit_root);
is( run_helper( $real_sysvinit_root, 'uses-systemd' ), 0,
'the compatibility classifier preserves installed systemd precedence for real SysV init' );
isnt( run_helper( $real_sysvinit_root, 'uses-systemd', '--explicit-target' ), 0,
'a real SysV init binary overrides modern release and systemd fallback evidence' );
is( run_helper( $real_sysvinit_root, 'configure' ), 0,
'compatibility-mode configuration succeeds for a real SysV target' );
ok( !-e legacy_init($real_sysvinit_root),
'compatibility-mode configuration preserves installed systemd precedence' );
is( run_helper( $real_sysvinit_root, 'configure', '--explicit-target' ), 0,
'real SysV explicit-target configuration succeeds' );
ok( -x legacy_init($real_sysvinit_root),
'real SysV explicit-target configuration installs the legacy init script' );
my $legacy_state_root = stage_root();
is( helper_output( $legacy_state_root, 'legacy-state' ), 'unregistered',
'legacy state reports no registration when rc links are absent' );
is( helper_output( $legacy_state_root, 'legacy-transition-state' ), 'disabled',
'a disabled systemd service without legacy provenance becomes registered-off SysV' );
stage_symlink(
$legacy_state_root, '/etc/init.d/xcatd',
qw(etc rc.d rc3.d K60xcatd)
);
is( helper_output( $legacy_state_root, 'legacy-state' ), 'disabled',
'legacy state recognizes registered-off rc links' );
is( helper_output( $legacy_state_root, 'legacy-transition-state' ), 'disabled',
'registered-off SysV state takes precedence during a legacy transition' );
stage_symlink(
$legacy_state_root, '/etc/init.d/xcatd',
qw(etc rc.d rc3.d S85xcatd)
);
is( helper_output( $legacy_state_root, 'legacy-state' ), 'enabled',
'legacy enabled state takes precedence when start and kill links coexist' );
is( helper_output( $legacy_state_root, 'legacy-transition-state' ), 'enabled',
'enabled SysV state takes precedence during a legacy transition' );
my $unregistered_legacy_root = stage_root();
make_path( File::Spec->catdir( $unregistered_legacy_root, 'etc', 'init.d' ) );
copy( $template, legacy_init($unregistered_legacy_root) )
or die "Unable to stage unregistered legacy init script: $!";
is( helper_output( $unregistered_legacy_root, 'legacy-transition-state' ),
'unregistered',
'a present but administrator-unregistered SysV service stays unregistered' );
my $deleted_managed_root = stage_root();
make_path( File::Spec->catdir( $deleted_managed_root, 'var', 'lib', 'xcat' ) );
open( my $deleted_managed_marker, '>', managed_marker($deleted_managed_root) )
or die "Unable to stage managed-file provenance: $!";
close($deleted_managed_marker);
is( helper_output( $deleted_managed_root, 'legacy-transition-state' ),
'unregistered',
'managed provenance preserves unregistered state after the init script is deleted' );
my $systemd_state_root = stage_root();
is( helper_output( $systemd_state_root, 'systemd-state' ), 'disabled',
'systemd state reports disabled when enablement links are absent' );
stage_symlink(
$systemd_state_root, '/usr/lib/systemd/system/xcatd.service',
qw(etc systemd system multi-user.target.wants xcatd.service)
);
is( helper_output( $systemd_state_root, 'systemd-state' ), 'enabled',
'systemd state recognizes persistent wants links' );
is( helper_output( $systemd_state_root, 'legacy-transition-state' ), 'enabled',
'enabled systemd state transfers to an enabled SysV service' );
my $systemd_requires_root = stage_root();
stage_symlink(
$systemd_requires_root, '/usr/lib/systemd/system/xcatd.service',
qw(run systemd system multi-user.target.requires xcatd.service)
);
is( helper_output( $systemd_requires_root, 'systemd-state' ), 'enabled',
'systemd state recognizes runtime requires links' );
my $systemd_linked_root = stage_root();
stage_symlink(
$systemd_linked_root, '/usr/lib/systemd/system/xcatd.service',
qw(etc systemd system xcatd.service)
);
is( helper_output( $systemd_linked_root, 'systemd-state' ), 'disabled',
'a linked unit without target enablement remains disabled' );
is( helper_output( $systemd_linked_root, 'legacy-transition-state' ), 'disabled',
'a linked-but-disabled systemd unit becomes registered-off SysV' );
my $systemd_masked_root = stage_root();
stage_symlink(
$systemd_masked_root, '/dev/null',
qw(etc systemd system xcatd.service)
);
is( helper_output( $systemd_masked_root, 'systemd-state' ), 'masked',
'systemd state preserves an administrator mask' );
is( helper_output( $systemd_masked_root, 'legacy-transition-state' ), 'masked',
'a systemd mask prevents SysV registration during a legacy transition' );
my $cleanup_root = stage_root();
my @cleanup_links = (
stage_symlink(
$cleanup_root, '/etc/init.d/xcatd',
qw(etc rc.d rc3.d S85xcatd)
),
stage_symlink(
$cleanup_root, '/etc/init.d/xcatd',
qw(etc rc.d rc0.d K60xcatd)
),
stage_symlink(
$cleanup_root, '/usr/lib/systemd/system/xcatd.service',
qw(etc systemd system multi-user.target.wants xcatd.service)
),
stage_symlink(
$cleanup_root, '/usr/lib/systemd/system/xcatd.service',
qw(run systemd system multi-user.target.requires xcatd.service)
),
);
my $cleanup_mask = stage_symlink(
$cleanup_root, '/dev/null',
qw(etc systemd system xcatd.service)
);
is( run_helper( $cleanup_root, 'unregister-all' ), 0,
'cross-manager registration cleanup succeeds in a target root' );
ok( !( grep { -e $_ || -l $_ } @cleanup_links ),
'cross-manager cleanup removes SysV and systemd enablement links' );
ok( -l $cleanup_mask,
'cross-manager cleanup preserves an administrator systemd mask' );
is( run_helper( $cleanup_root, 'register-legacy', 'enabled' ), 2,
'legacy registration refuses to execute host tools for a target root' );
is( run_helper( $cleanup_root, 'register-legacy', 'invalid' ), 2,
'legacy registration rejects an invalid desired state' );
my $links_only_root = stage_root();
my @preserved_legacy_links = (
stage_symlink(
$links_only_root, '/etc/init.d/xcatd',
qw(etc rc.d rc2.d K42xcatd)
),
stage_symlink(
$links_only_root, '/etc/init.d/xcatd',
qw(etc rc.d rc3.d S17xcatd)
),
);
my $removed_systemd_link = stage_symlink(
$links_only_root, '/usr/lib/systemd/system/xcatd.service',
qw(etc systemd system multi-user.target.wants xcatd.service)
);
is( run_helper( $links_only_root, 'disable-systemd', '--links-only' ), 0,
'links-only systemd cleanup succeeds' );
ok( !-e $removed_systemd_link && !-l $removed_systemd_link,
'links-only cleanup removes native systemd enablement' );
ok( !( grep { !-e $_ && !-l $_ } @preserved_legacy_links ),
'links-only cleanup preserves exact custom SysV registration links' );
is( run_helper( $links_only_root, 'disable-systemd', '--invalid' ), 2,
'systemd cleanup rejects an invalid mode' );
my $dangling_legacy_root = stage_root();
make_path( File::Spec->catdir( $dangling_legacy_root, 'etc', 'init.d' ) );
@@ -145,40 +381,245 @@ ok( !-l legacy_init($dangling_legacy_root),
is( read_file( legacy_init($dangling_legacy_root) ), read_file($template),
'the dangling init symlink replacement matches the packaged template' );
open( my $custom_init, '>', legacy_init($legacy_root) )
my $custom_legacy_root = stage_root();
make_path( File::Spec->catdir( $custom_legacy_root, 'sbin' ) );
symlink( 'upstart', File::Spec->catfile( $custom_legacy_root, 'sbin', 'init' ) )
or die "Unable to stage customization target: $!";
is( run_helper( $custom_legacy_root, 'configure' ), 0,
'customizable legacy target configuration succeeds' );
open( my $custom_init, '>', legacy_init($custom_legacy_root) )
or die "Unable to customize staged init script: $!";
print {$custom_init} "administrator customization\n";
close($custom_init);
is( run_helper( $legacy_root, 'configure' ), 0,
is( run_helper( $custom_legacy_root, 'configure' ), 0,
'reconfiguring a legacy target succeeds' );
is( read_file( legacy_init($legacy_root) ), "administrator customization\n",
is( read_file( legacy_init($custom_legacy_root) ), "administrator customization\n",
'legacy reconfiguration preserves an existing init script' );
is( run_helper( $legacy_root, 'configure', '--replace' ), 0,
is( run_helper( $custom_legacy_root, 'configure', '--replace' ), 0,
'RPM-style legacy upgrade configuration succeeds' );
is( read_file( legacy_init($legacy_root) ), read_file($template),
is( read_file( legacy_init($custom_legacy_root) ), read_file($template),
'RPM-style legacy upgrades refresh the init script from the packaged template' );
is( run_helper( $legacy_root, 'remove' ), 0,
is( run_helper( $custom_legacy_root, 'remove' ), 0,
'legacy cleanup succeeds' );
ok( !-e legacy_init($legacy_root),
ok( !-e legacy_init($custom_legacy_root),
'legacy cleanup removes the generated init script' );
my $tracked_legacy_root = stage_root();
is( run_helper( $tracked_legacy_root, 'configure', '--track-managed' ), 0,
'RPM-style configuration can track a generated legacy script' );
ok( -x legacy_init($tracked_legacy_root),
'tracked configuration materializes an executable legacy script' );
ok( -f managed_marker($tracked_legacy_root),
'tracked configuration records generated-file provenance' );
is( ( stat( managed_marker($tracked_legacy_root) ) )[2] & 07777, 0600,
'managed-file provenance is private' );
my $tracked_state_dir = File::Spec->catdir(
$tracked_legacy_root, 'var', 'lib', 'xcat'
);
is( ( stat($tracked_state_dir) )[2] & 07777, 0755,
'managed-file tracking keeps the shared xCAT state directory traversable' );
is( run_helper( $tracked_legacy_root, 'remove-managed' ), 0,
'managed legacy cleanup succeeds' );
ok( !-e legacy_init($tracked_legacy_root)
&& !-e managed_marker($tracked_legacy_root),
'managed cleanup removes an unchanged generated script and its marker' );
my $admin_legacy_root = stage_root();
make_path( File::Spec->catdir( $admin_legacy_root, 'etc', 'init.d' ) );
open( my $admin_init, '>', legacy_init($admin_legacy_root) )
or die "Unable to stage administrator init script: $!";
print {$admin_init} "administrator-owned init script\n";
close($admin_init);
chmod 0755, legacy_init($admin_legacy_root);
is( run_helper( $admin_legacy_root, 'configure', '--track-managed' ), 0,
'tracked configuration accepts a pre-existing administrator script' );
ok( !-e managed_marker($admin_legacy_root),
'a pre-existing administrator script is not marked as generated' );
is( run_helper( $admin_legacy_root, 'remove-managed' ), 0,
'managed cleanup accepts an unowned administrator script' );
is( read_file( legacy_init($admin_legacy_root) ),
"administrator-owned init script\n",
'managed cleanup preserves an unowned administrator script' );
my $identical_admin_root = stage_root();
make_path( File::Spec->catdir( $identical_admin_root, 'etc', 'init.d' ) );
copy( $template, legacy_init($identical_admin_root) )
or die "Unable to stage byte-identical administrator script: $!";
chmod 0755, legacy_init($identical_admin_root);
is( run_helper( $identical_admin_root, 'remove-managed' ), 0,
'managed cleanup accepts an unmarked byte-identical script' );
ok( -f legacy_init($identical_admin_root),
'content equality alone does not grant ownership for cleanup' );
is( read_file( legacy_init($identical_admin_root) ), read_file($template),
'managed cleanup preserves an unmarked byte-identical script' );
my $admin_symlink_root = stage_root();
my $admin_init_dir = File::Spec->catdir( $admin_symlink_root, 'etc', 'init.d' );
make_path($admin_init_dir);
my $admin_symlink_target = File::Spec->catfile( $admin_init_dir, 'xcatd.admin' );
open( my $admin_target, '>', $admin_symlink_target )
or die "Unable to stage administrator symlink target: $!";
print {$admin_target} "administrator symlink target\n";
close($admin_target);
symlink( 'xcatd.admin', legacy_init($admin_symlink_root) )
or die "Unable to stage administrator init symlink: $!";
is( run_helper( $admin_symlink_root, 'configure', '--track-managed' ), 0,
'tracked configuration preserves a valid administrator symlink' );
is( run_helper( $admin_symlink_root, 'remove-managed' ), 0,
'managed cleanup accepts an unowned administrator symlink' );
ok( -l legacy_init($admin_symlink_root),
'managed cleanup preserves an unowned administrator symlink' );
my $modified_managed_root = stage_root();
is( run_helper( $modified_managed_root, 'configure', '--track-managed' ), 0,
'modified-file scenario starts with a tracked generated script' );
open( my $modified_init, '>', legacy_init($modified_managed_root) )
or die "Unable to modify tracked init script: $!";
print {$modified_init} "administrator modification\n";
close($modified_init);
is( run_helper( $modified_managed_root, 'configure' ), 0,
'legacy reconfiguration accepts an administrator-modified managed script' );
ok( !-e managed_marker($modified_managed_root),
'legacy reconfiguration clears stale managed-file provenance' );
is( run_helper( $modified_managed_root, 'remove-managed' ), 0,
'managed cleanup accepts an administrator-modified generated script' );
is( read_file( legacy_init($modified_managed_root) ),
"administrator modification\n",
'managed cleanup preserves administrator changes' );
ok( !-e managed_marker($modified_managed_root),
'managed cleanup clears provenance after preserving a modified script' );
my $replace_managed_root = stage_root();
make_path( File::Spec->catdir( $replace_managed_root, 'etc', 'init.d' ) );
open( my $replace_init, '>', legacy_init($replace_managed_root) )
or die "Unable to stage replaceable init script: $!";
print {$replace_init} "old package payload\n";
close($replace_init);
is( run_helper( $replace_managed_root, 'configure', '--replace', '--track-managed' ), 0,
'RPM upgrade replacement records generated-file provenance' );
is( read_file( legacy_init($replace_managed_root) ), read_file($template),
'tracked replacement installs the current legacy template' );
ok( -f managed_marker($replace_managed_root),
'tracked replacement creates a managed marker' );
my $systemd_tracked_root = stage_root();
is( run_helper( $systemd_tracked_root, 'configure', '--track-managed' ), 0,
'systemd-transition scenario starts with a tracked legacy script' );
make_path( File::Spec->catdir( $systemd_tracked_root, 'run', 'systemd', 'system' ) );
is( run_helper( $systemd_tracked_root, 'configure' ), 0,
'systemd configuration removes tracked legacy state' );
ok( !-e legacy_init($systemd_tracked_root)
&& !-e managed_marker($systemd_tracked_root),
'systemd configuration clears the generated script and its provenance' );
my $legacy_remove_root = stage_root();
is( run_helper( $legacy_remove_root, 'configure', '--track-managed' ), 0,
'legacy remove scenario starts with tracked state' );
is( run_helper( $legacy_remove_root, 'remove' ), 0,
'legacy remove remains compatible with tracked state' );
ok( !-e legacy_init($legacy_remove_root)
&& !-e managed_marker($legacy_remove_root),
'legacy remove clears both the script and stale provenance' );
my $systemctl_ready_status = system(
'/bin/sh', '-c',
'[ -d /run/systemd/system ] && command -v systemctl >/dev/null 2>&1'
) >> 8;
is( run_helper( stage_root(), 'can-use-systemctl' ), $systemctl_ready_status,
'shared helper preserves the host systemctl readiness predicate' );
is( run_helper( stage_root(), 'can-use-systemctl', '--invalid' ), 2,
'systemctl readiness rejects unexpected arguments' );
my $rpm_spec = read_file(
File::Spec->catfile( $repo_root, 'xCAT-server', 'xCAT-server.spec' )
);
my $helper_source = read_file($helper);
like( $helper_source, qr{/sbin/chkconfig --level 345 xcatd on},
'legacy registration enables only the init template runlevels' );
like( $helper_source,
qr{case "\$desired_state" in\s+default\) : ;;\s+enabled\) /sbin/chkconfig --level 345 xcatd on ;;\s+disabled\) /sbin/chkconfig xcatd off ;;\s+esac}s,
'legacy registration can preserve script defaults without weakening explicit state restoration' );
like( $helper_source,
qr{if \[ -z "\$compat_root" \] &&\s+\{ \[ -e "\$legacy_init" \] \|\| \[ -L "\$legacy_init" \]; \}; then\s+if \[ -x /sbin/chkconfig \]; then\s+/sbin/chkconfig --del xcatd}s,
'legacy cleanup invokes host registration tools only for an existing init script' );
my ($rpm_post) = $rpm_spec =~ /^%post\n(.*?)^%posttrans\n/ms;
my ($rpm_posttrans) = $rpm_spec =~ /^%posttrans\n(.*?)^%preun\n/ms;
my ($rpm_preun) = $rpm_spec =~ /^%preun\n(.*)\z/ms;
my ($rpm_fresh) = $rpm_post =~
/if \[ "\$1" = "1" \]; then(.*?)\nfi\n\nif \[ "\$1" -gt "1" \]; then/ms;
my ( $rpm_upgrade_systemd, $rpm_upgrade_legacy ) = $rpm_post =~
/if \[ "\$1" -gt "1" \]; then.*?if "\$xcatd_init_compat" uses-systemd --explicit-target; then(.*?)\n else\n(.*?)\n fi/ms;
ok( defined($rpm_post) && defined($rpm_posttrans) && defined($rpm_preun)
&& defined($rpm_fresh) && defined($rpm_upgrade_systemd)
&& defined($rpm_upgrade_legacy),
'RPM service lifecycle scriptlets can be inspected independently' );
like( $rpm_spec,
qr{cp etc/init\.d/xcatd \$RPM_BUILD_ROOT/%\{prefix\}/share/xcat/scripts/xcatd},
'RPM stages the legacy script as a compatibility template' );
like( $rpm_spec,
qr{%if 0%\{\?rhel\} && 0%\{\?rhel\} < 7\n%ghost %attr\(0755,root,root\) /etc/init\.d/xcatd},
'RPM tracks the runtime-created init path only on legacy RHEL' );
like( $rpm_spec,
qr{%if 0%\{\?suse_version\} && 0%\{\?suse_version\} < 1200\n%ghost %attr\(0755,root,root\) /etc/init\.d/xcatd},
'RPM tracks the runtime-created init path only on legacy SUSE' );
unlike( $rpm_spec,
qr{touch \$RPM_BUILD_ROOT/etc/init\.d/xcatd|%ghost[^\n]*/etc/init\.d/xcatd},
'RPM leaves the runtime init script unowned so helper provenance controls erase' );
like( $rpm_spec, qr{xcatd-init-compat.*uses-systemd}s,
'RPM scriptlets select the init implementation at install time' );
unlike( $rpm_spec,
qr{"\$xcatd_init_compat" uses-systemd(?! --explicit-target)},
'every RPM lifecycle classifier opts into explicit target detection' );
unlike( $rpm_spec,
qr{"\$xcatd_init_compat" configure(?![^\n]*--explicit-target)},
'every RPM lifecycle configuration uses the matching explicit target mode' );
unlike( $rpm_spec, qr{xcat_can_use_systemctl},
'RPM scriptlets do not duplicate the shared systemctl readiness guard' );
my @systemctl_readiness_calls =
( $rpm_spec =~ /"\$xcatd_init_compat" can-use-systemctl/g );
is( scalar @systemctl_readiness_calls, 3,
'RPM delegates every active-systemd operation guard to the shared helper' );
like( $rpm_spec,
qr{xcatd_init_compat=.*?"\$xcatd_init_compat" configure --replace}s,
'RPM upgrades refresh an existing SysV init script' );
like( $rpm_fresh,
qr{"\$xcatd_init_compat" legacy-state.*?"\$xcatd_init_compat" systemd-state}s,
'RPM fresh installs query service state through the shared helper' );
like( $rpm_upgrade_systemd,
qr{legacy_xcatd_state=\$\("\$xcatd_init_compat" legacy-state\).*?"\$xcatd_init_compat" unregister-legacy.*?"\$xcatd_init_compat" configure.*?if \[ "\$legacy_xcatd_state" = enabled \].*?systemctl enable xcatd\.service}s,
'RPM systemd transitions preserve enabled SysV state after unregistering it' );
like( $rpm_upgrade_legacy,
qr{legacy_xcatd_registration=\$\("\$xcatd_init_compat" legacy-state\).*?legacy_xcatd_state=\$\("\$xcatd_init_compat" legacy-transition-state\).*?"\$xcatd_init_compat" disable-systemd --links-only.*?masked\|unregistered\).*?"\$xcatd_init_compat" unregister-legacy.*?configure --replace.*?enabled\|disabled\).*?if \[ "\$legacy_xcatd_registration" = unregistered \]; then.*?register-legacy "\$legacy_xcatd_state".*?masked\|unregistered\)}s,
'RPM legacy transitions preserve prior state and clear systemd enablement' );
like( $rpm_fresh,
qr{legacy_xcatd_state=\$\("\$xcatd_init_compat" legacy-state\).*?enabled\|disabled\)\s+# Preserve any pre-existing administrator runlevel layout\.\s+:\s+;;}s,
'RPM fresh installs preserve pre-existing custom SysV runlevel links' );
like( $helper_source,
qr{if \[ "\$disable_mode" != links-only \].*?systemctl disable xcatd\.service}s,
'links-only cleanup avoids host tools that can rewrite SysV registration' );
like( $rpm_fresh,
qr{if \[ "\$\("\$xcatd_init_compat" systemd-state\)" != masked \]; then\s+"\$xcatd_init_compat" register-legacy default}s,
'RPM fresh legacy installs do not override a persistent systemd mask' );
like( $rpm_fresh,
qr{uses-systemd --explicit-target; then\s+"\$xcatd_init_compat" configure --explicit-target \|\| exit 1.*?else\s+"\$xcatd_init_compat" configure --explicit-target --track-managed \|\| exit 1}s,
'RPM tracks only fresh legacy scripts as generated files' );
unlike( $rpm_post,
qr{/sbin/chkconfig --(?:add|del) xcatd|/usr/lib/lsb/(?:install|remove)_initd},
'RPM post delegates registration mechanics to the shared helper' );
like( $rpm_posttrans,
qr{%ifos linux.*?uses-systemd --explicit-target.*?\[ ! -e /etc/init\.d/xcatd \].*?\[ ! -L /etc/init\.d/xcatd \].*?"\$xcatd_init_compat" configure --explicit-target(?: --track-managed)? \|\| exit 1}s,
'RPM post-transaction recovery restores only a missing legacy script' );
unlike( $rpm_posttrans, qr{\$1|--replace},
'RPM post-transaction recovery is idempotent and not argument-gated' );
like( $rpm_upgrade_legacy,
qr{configure --replace --explicit-target --track-managed \|\| exit 1},
'RPM legacy upgrades track the replacement script as generated' );
like( $rpm_posttrans,
qr{configure --explicit-target --track-managed \|\| exit 1},
'RPM post-transaction recovery restores managed-file provenance' );
like( $rpm_preun,
qr{"\$xcatd_init_compat" unregister-all.*?"\$xcatd_init_compat" remove}s,
'RPM erase cleans both managers before removing the generated script' );
like( $rpm_preun,
qr{"\$xcatd_init_compat" unregister-all \|\| true.*?"\$xcatd_init_compat" remove-managed \|\| true}s,
'RPM erase preserves its recoverable cleanup policy with managed files' );
unlike( $rpm_preun,
qr{/sbin/chkconfig --del xcatd|/usr/lib/lsb/remove_initd},
'RPM erase delegates cross-manager cleanup to the shared helper' );
my @xcatroot_exports =
( $rpm_spec =~ /^export XCATROOT="\$RPM_INSTALL_PREFIX0"$/mg );
is( scalar @xcatroot_exports, 2,
@@ -190,8 +631,8 @@ is( scalar @empty_init_cleanup, 1,
like( $rpm_spec,
qr{%if 0%\{\?suse_version\}\s+%else\s+# Remove only an empty directory.*?rmdir /etc/init\.d 2>/dev/null \|\| true\s+%endif}s,
'RPM upgrades preserve the SUSE-owned init directory' );
like( $rpm_spec,
qr{if \[ -e "\$legacy_xcatd_link" \] \|\| \[ -L "\$legacy_xcatd_link" \]},
'RPM upgrade recognizes enabled state through dangling legacy links' );
like( join( "\n", $rpm_spec, $helper_source ),
qr{if \[ -e "\$legacy(?:_xcatd)?_link" \] \|\| \[ -L "\$legacy(?:_xcatd)?_link" \]},
'xcatd init management recognizes enabled state through dangling legacy links' );
done_testing();