From 8489e159691a7f145db9f00b4ea3c2cbd116ab3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Ferr=C3=A3o?= <2031761+viniciusferrao@users.noreply.github.com> Date: Sun, 19 Jul 2026 11:33:13 -0300 Subject: [PATCH 01/20] fix(packaging): expose explicit init target detection --- .../share/xcat/scripts/xcatd-init-compat | 81 ++++++++++++++----- 1 file changed, 60 insertions(+), 21 deletions(-) diff --git a/xCAT-server/share/xcat/scripts/xcatd-init-compat b/xCAT-server/share/xcat/scripts/xcatd-init-compat index 01e0dbf2c..bb4b5de75 100755 --- a/xCAT-server/share/xcat/scripts/xcatd-init-compat +++ b/xCAT-server/share/xcat/scripts/xcatd-init-compat @@ -18,10 +18,10 @@ esac legacy_init="$compat_root/etc/init.d/xcatd" legacy_template="$compat_root$xcat_root/share/xcat/scripts/xcatd" -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 +43,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 @@ -126,13 +156,22 @@ configure_legacy_init() case "${1:-}" in uses-systemd) - uses_systemd + detection_mode=compat + case "${2:-}" in + '') ;; + --explicit-target) detection_mode=explicit ;; + *) + echo "Usage: $0 uses-systemd [--explicit-target]" >&2 + exit 2 + ;; + esac + uses_systemd "$detection_mode" ;; configure) case "${2:-}" in ''|--replace) ;; *) - echo "Usage: $0 {uses-systemd|configure [--replace]|remove}" >&2 + echo "Usage: $0 {uses-systemd [--explicit-target]|configure [--replace]|remove}" >&2 exit 2 ;; esac @@ -142,7 +181,7 @@ case "${1:-}" in rm -f "$legacy_init" ;; *) - echo "Usage: $0 {uses-systemd|configure [--replace]|remove}" >&2 + echo "Usage: $0 {uses-systemd [--explicit-target]|configure [--replace]|remove}" >&2 exit 2 ;; esac From 002634ef6bd288e4340925206f1a45ec1ec5ea1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Ferr=C3=A3o?= <2031761+viniciusferrao@users.noreply.github.com> Date: Sun, 19 Jul 2026 11:34:51 -0300 Subject: [PATCH 02/20] test(packaging): cover hybrid init targets --- xCAT-test/unit/xcatd_rpm_service_packaging.t | 106 ++++++++++++++----- 1 file changed, 81 insertions(+), 25 deletions(-) diff --git a/xCAT-test/unit/xcatd_rpm_service_packaging.t b/xCAT-test/unit/xcatd_rpm_service_packaging.t index 0dc3785cc..a2b282435 100644 --- a/xCAT-test/unit/xcatd_rpm_service_packaging.t +++ b/xCAT-test/unit/xcatd_rpm_service_packaging.t @@ -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; @@ -63,6 +76,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 +97,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, @@ -109,8 +124,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 +156,33 @@ 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' ); -is( run_helper( $legacy_root, 'configure' ), 0, - 'legacy configuration succeeds' ); -ok( -x legacy_init($legacy_root), - 'legacy 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' ); +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' ); + +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' ); my $dangling_legacy_root = stage_root(); make_path( File::Spec->catdir( $dangling_legacy_root, 'etc', 'init.d' ) ); @@ -145,21 +195,27 @@ 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 $rpm_spec = read_file( From 6d7de3a927eed28e36a68aee9b7fa77a2107106d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Ferr=C3=A3o?= <2031761+viniciusferrao@users.noreply.github.com> Date: Sun, 19 Jul 2026 12:01:25 -0300 Subject: [PATCH 03/20] test(packaging): allow shared init state detection --- xCAT-test/unit/xcatd_rpm_service_packaging.t | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/xCAT-test/unit/xcatd_rpm_service_packaging.t b/xCAT-test/unit/xcatd_rpm_service_packaging.t index a2b282435..3a51396b2 100644 --- a/xCAT-test/unit/xcatd_rpm_service_packaging.t +++ b/xCAT-test/unit/xcatd_rpm_service_packaging.t @@ -246,8 +246,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, read_file($helper) ), + qr{if \[ -e "\$legacy(?:_xcatd)?_link" \] \|\| \[ -L "\$legacy(?:_xcatd)?_link" \]}, + 'xcatd init management recognizes enabled state through dangling legacy links' ); done_testing(); From 5495f133e8428ea8ac45e9c10645cf43aefacbe7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Ferr=C3=A3o?= <2031761+viniciusferrao@users.noreply.github.com> Date: Sun, 19 Jul 2026 12:12:54 -0300 Subject: [PATCH 04/20] refactor(packaging): centralize xcatd init management --- .../share/xcat/scripts/xcatd-init-compat | 196 ++++++++++++++++-- 1 file changed, 182 insertions(+), 14 deletions(-) diff --git a/xCAT-server/share/xcat/scripts/xcatd-init-compat b/xCAT-server/share/xcat/scripts/xcatd-init-compat index bb4b5de75..b9568c393 100755 --- a/xCAT-server/share/xcat/scripts/xcatd-init-compat +++ b/xCAT-server/share/xcat/scripts/xcatd-init-compat @@ -122,26 +122,175 @@ uses_systemd() return 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 +} + +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 + if [ "$desired_state" = enabled ]; then + /sbin/chkconfig --level 345 xcatd on + else + /sbin/chkconfig xcatd off + fi + elif [ "$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() +{ + if [ -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" +} + configure_legacy_init() { - replace_existing=${1:-} + replace_existing=0 + detection_mode=compat + for configure_option in "$@"; do + case "$configure_option" in + --replace) replace_existing=1 ;; + --explicit-target) detection_mode=explicit ;; + *) + echo "Usage: $0 configure [--replace] [--explicit-target]" >&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 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 return fi - rm -f "$legacy_init" + rm -f "$legacy_init" || return 1 fi if [ ! -r "$legacy_template" ]; then @@ -149,9 +298,9 @@ 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 } case "${1:-}" in @@ -167,21 +316,40 @@ case "${1:-}" in esac uses_systemd "$detection_mode" ;; - configure) + legacy-state) + legacy_state + ;; + systemd-state) + systemd_state + ;; + register-legacy) case "${2:-}" in - ''|--replace) ;; + enabled|disabled) ;; *) - echo "Usage: $0 {uses-systemd [--explicit-target]|configure [--replace]|remove}" >&2 + echo "Usage: $0 register-legacy {enabled|disabled}" >&2 exit 2 ;; esac - configure_legacy_init "${2:-}" + register_legacy_init "$2" + ;; + unregister-legacy) + unregister_legacy_init + ;; + disable-systemd) + disable_systemd_init + ;; + unregister-all) + unregister_all_init + ;; + configure) + shift + configure_legacy_init "$@" ;; remove) rm -f "$legacy_init" ;; *) - echo "Usage: $0 {uses-systemd [--explicit-target]|configure [--replace]|remove}" >&2 + echo "Usage: $0 {uses-systemd [--explicit-target]|legacy-state|systemd-state|register-legacy {enabled|disabled}|unregister-legacy|disable-systemd|unregister-all|configure [--replace] [--explicit-target]|remove}" >&2 exit 2 ;; esac From 5a9c32e885105a6cc03f0206e84e9a1e6e863554 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Ferr=C3=A3o?= <2031761+viniciusferrao@users.noreply.github.com> Date: Sun, 19 Jul 2026 12:14:40 -0300 Subject: [PATCH 05/20] test(packaging): cover shared init management --- xCAT-test/unit/xcatd_rpm_service_packaging.t | 130 ++++++++++++++++++- 1 file changed, 129 insertions(+), 1 deletion(-) diff --git a/xCAT-test/unit/xcatd_rpm_service_packaging.t b/xCAT-test/unit/xcatd_rpm_service_packaging.t index 3a51396b2..358b6a840 100644 --- a/xCAT-test/unit/xcatd_rpm_service_packaging.t +++ b/xCAT-test/unit/xcatd_rpm_service_packaging.t @@ -57,6 +57,28 @@ 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' ); @@ -161,6 +183,16 @@ 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, + '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), + 'explicit-target configuration materializes an executable init script' ); +is( read_file( legacy_init($legacy_root) ), read_file($template), + 'the explicit-target init script matches the packaged template' ); my $real_sysvinit_root = stage_root(); make_path( @@ -183,6 +215,96 @@ 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' ); +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' ); +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' ); + +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' ); + +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' ); + +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' ); + +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' ); my $dangling_legacy_root = stage_root(); make_path( File::Spec->catdir( $dangling_legacy_root, 'etc', 'init.d' ) ); @@ -221,6 +343,12 @@ ok( !-e legacy_init($custom_legacy_root), 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{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' ); 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' ); @@ -246,7 +374,7 @@ 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( join( "\n", $rpm_spec, read_file($helper) ), +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' ); From a47e73dc465d2d766ad61af6c14952a7ad0dab19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Ferr=C3=A3o?= <2031761+viniciusferrao@users.noreply.github.com> Date: Sun, 19 Jul 2026 12:15:07 -0300 Subject: [PATCH 06/20] fix(packaging): converge xcatd init transitions --- xCAT-server/xCAT-server.spec | 99 +++++++++++++++++++----------------- 1 file changed, 51 insertions(+), 48 deletions(-) diff --git a/xCAT-server/xCAT-server.spec b/xCAT-server/xCAT-server.spec index bdeeacfdc..f3082e376 100644 --- a/xCAT-server/xCAT-server.spec +++ b/xCAT-server/xCAT-server.spec @@ -523,8 +523,8 @@ 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 "$xcatd_init_compat" uses-systemd --explicit-target; then + "$xcatd_init_compat" configure --explicit-target || exit 1 if xcat_can_use_systemctl; then systemctl daemon-reload fi @@ -532,43 +532,27 @@ if [ "$1" = "1" ]; then #Only if installing for the first time.. 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 || exit 1 + legacy_xcatd_state=$("$xcatd_init_compat" legacy-state) + case "$legacy_xcatd_state" in + enabled|disabled) + "$xcatd_init_compat" register-legacy "$legacy_xcatd_state" + ;; + *) + if [ "$("$xcatd_init_compat" systemd-state)" != masked ]; then + "$xcatd_init_compat" register-legacy enabled + 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 @@ -578,14 +562,28 @@ if [ "$1" -gt "1" ]; then #only on upgrade... if xcat_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_state=$("$xcatd_init_compat" legacy-state) + if [ "$legacy_xcatd_state" = unregistered ]; then + legacy_xcatd_state=$("$xcatd_init_compat" systemd-state) + fi + "$xcatd_init_compat" disable-systemd + # 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 || exit 1 + + case "$legacy_xcatd_state" in + enabled|disabled) + "$xcatd_init_compat" register-legacy "$legacy_xcatd_state" + ;; + masked) + ;; + esac fi #migration issue for monitoring XCATROOT=$RPM_INSTALL_PREFIX0 $RPM_INSTALL_PREFIX0/sbin/chtab filename=monitorctrl.pm notification -d @@ -620,6 +618,21 @@ 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 || exit 1 +fi +%endif + %preun %ifos linux xcat_can_use_systemctl() @@ -634,22 +647,12 @@ 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 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" unregister-all "$xcatd_init_compat" remove rm -f /usr/sbin/xcatd #remove the symbolic From 11f9cada054a55e26dfab43dd12f228a2272b981 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Ferr=C3=A3o?= <2031761+viniciusferrao@users.noreply.github.com> Date: Sun, 19 Jul 2026 12:18:57 -0300 Subject: [PATCH 07/20] test(packaging): cover RPM init transitions --- xCAT-test/unit/xcatd_rpm_service_packaging.t | 43 ++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/xCAT-test/unit/xcatd_rpm_service_packaging.t b/xCAT-test/unit/xcatd_rpm_service_packaging.t index 358b6a840..6c4e32117 100644 --- a/xCAT-test/unit/xcatd_rpm_service_packaging.t +++ b/xCAT-test/unit/xcatd_rpm_service_packaging.t @@ -349,6 +349,17 @@ like( $helper_source, qr{/sbin/chkconfig --level 345 xcatd on}, 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' ); @@ -360,9 +371,41 @@ like( $rpm_spec, 'RPM tracks the runtime-created init path only on legacy SUSE' ); 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' ); 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_state=\$\("\$xcatd_init_compat" legacy-state\).*?if \[ "\$legacy_xcatd_state" = unregistered \].*?systemd-state.*?"\$xcatd_init_compat" disable-systemd.*?configure --replace.*?register-legacy "\$legacy_xcatd_state"}s, + 'RPM legacy transitions preserve prior state and clear systemd enablement' ); +like( $rpm_fresh, + qr{if \[ "\$\("\$xcatd_init_compat" systemd-state\)" != masked \]; then\s+"\$xcatd_init_compat" register-legacy enabled}s, + 'RPM fresh legacy installs do not override a persistent systemd mask' ); +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 \|\| 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_preun, + qr{"\$xcatd_init_compat" unregister-all.*?"\$xcatd_init_compat" remove}s, + 'RPM erase cleans both managers before removing the generated script' ); +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, From 11709a22218a52f21b33aa0bf09d9fa64aecddbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Ferr=C3=A3o?= <2031761+viniciusferrao@users.noreply.github.com> Date: Sun, 19 Jul 2026 12:21:41 -0300 Subject: [PATCH 08/20] test(packaging): prepare managed init ownership --- xCAT-test/unit/xcatd_rpm_service_packaging.t | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/xCAT-test/unit/xcatd_rpm_service_packaging.t b/xCAT-test/unit/xcatd_rpm_service_packaging.t index 6c4e32117..6d78241f1 100644 --- a/xCAT-test/unit/xcatd_rpm_service_packaging.t +++ b/xCAT-test/unit/xcatd_rpm_service_packaging.t @@ -363,12 +363,6 @@ ok( defined($rpm_post) && defined($rpm_posttrans) && defined($rpm_preun) 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' ); like( $rpm_spec, qr{xcatd-init-compat.*uses-systemd}s, 'RPM scriptlets select the init implementation at install time' ); unlike( $rpm_spec, @@ -396,7 +390,7 @@ 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 \|\| exit 1}s, + 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' ); From 115c9cda7e88d52b8fdbaeed6bf4ec5225491e40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Ferr=C3=A3o?= <2031761+viniciusferrao@users.noreply.github.com> Date: Sun, 19 Jul 2026 12:25:01 -0300 Subject: [PATCH 09/20] fix(packaging): preserve unmanaged xcatd init scripts --- .../share/xcat/scripts/xcatd-init-compat | 55 ++++++++++++++++++- xCAT-server/xCAT-server.spec | 31 +++-------- 2 files changed, 61 insertions(+), 25 deletions(-) diff --git a/xCAT-server/share/xcat/scripts/xcatd-init-compat b/xCAT-server/share/xcat/scripts/xcatd-init-compat index b9568c393..d1ec398f4 100755 --- a/xCAT-server/share/xcat/scripts/xcatd-init-compat +++ b/xCAT-server/share/xcat/scripts/xcatd-init-compat @@ -17,6 +17,7 @@ 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" systemd_os_release_status() { @@ -260,16 +261,52 @@ unregister_all_init() 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=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]" >&2 + echo "Usage: $0 configure [--replace] [--explicit-target] [--track-managed]" >&2 exit 2 ;; esac @@ -277,6 +314,7 @@ configure_legacy_init() if uses_systemd "$detection_mode"; then rm -f "$legacy_init" || return 1 + rm -f "$managed_marker" || return 1 return fi @@ -288,6 +326,9 @@ configure_legacy_init() if [ -e "$legacy_init" ] || [ -L "$legacy_init" ]; 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" || return 1 @@ -301,6 +342,11 @@ configure_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 @@ -346,10 +392,13 @@ case "${1:-}" in configure_legacy_init "$@" ;; remove) - rm -f "$legacy_init" + remove_legacy_init + ;; + remove-managed) + remove_managed_legacy_init ;; *) - echo "Usage: $0 {uses-systemd [--explicit-target]|legacy-state|systemd-state|register-legacy {enabled|disabled}|unregister-legacy|disable-systemd|unregister-all|configure [--replace] [--explicit-target]|remove}" >&2 + echo "Usage: $0 {uses-systemd [--explicit-target]|legacy-state|systemd-state|register-legacy {enabled|disabled}|unregister-legacy|disable-systemd|unregister-all|configure [--replace] [--explicit-target] [--track-managed]|remove|remove-managed}" >&2 exit 2 ;; esac diff --git a/xCAT-server/xCAT-server.spec b/xCAT-server/xCAT-server.spec index f3082e376..1ff870f15 100644 --- a/xCAT-server/xCAT-server.spec +++ b/xCAT-server/xCAT-server.spec @@ -364,17 +364,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 @@ -448,12 +437,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 @@ -532,7 +517,7 @@ if [ "$1" = "1" ]; then #Only if installing for the first time.. systemctl enable xcatd.service fi else - "$xcatd_init_compat" configure --explicit-target || exit 1 + "$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) @@ -575,7 +560,7 @@ if [ "$1" -gt "1" ]; then #only on upgrade... # 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 --explicit-target || exit 1 + "$xcatd_init_compat" configure --replace --explicit-target --track-managed || exit 1 case "$legacy_xcatd_state" in enabled|disabled) @@ -629,7 +614,7 @@ 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 || exit 1 + "$xcatd_init_compat" configure --explicit-target --track-managed || exit 1 fi %endif @@ -652,8 +637,10 @@ if [ $1 == 0 ]; then #This means only on -e fi fi - "$xcatd_init_compat" unregister-all - "$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 From 4efcd5132bee7a613da3f3d5c4a2e718462d4be1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Ferr=C3=A3o?= <2031761+viniciusferrao@users.noreply.github.com> Date: Sun, 19 Jul 2026 12:27:23 -0300 Subject: [PATCH 10/20] test(packaging): cover managed xcatd init scripts --- xCAT-test/unit/xcatd_rpm_service_packaging.t | 135 +++++++++++++++++++ 1 file changed, 135 insertions(+) diff --git a/xCAT-test/unit/xcatd_rpm_service_packaging.t b/xCAT-test/unit/xcatd_rpm_service_packaging.t index 6d78241f1..7217107e1 100644 --- a/xCAT-test/unit/xcatd_rpm_service_packaging.t +++ b/xCAT-test/unit/xcatd_rpm_service_packaging.t @@ -84,6 +84,13 @@ sub legacy_init { 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' ) ); @@ -340,6 +347,119 @@ is( run_helper( $custom_legacy_root, 'remove' ), 0, 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, '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 $rpm_spec = read_file( File::Spec->catfile( $repo_root, 'xCAT-server', 'xCAT-server.spec' ) ); @@ -363,6 +483,9 @@ ok( defined($rpm_post) && defined($rpm_posttrans) && defined($rpm_preun) 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' ); +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, @@ -386,6 +509,9 @@ like( $rpm_upgrade_legacy, like( $rpm_fresh, qr{if \[ "\$\("\$xcatd_init_compat" systemd-state\)" != masked \]; then\s+"\$xcatd_init_compat" register-legacy enabled}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' ); @@ -394,9 +520,18 @@ like( $rpm_posttrans, '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' ); From 9bff441795a9aa85c503c8aed2af9e716467c26f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Ferr=C3=A3o?= <2031761+viniciusferrao@users.noreply.github.com> Date: Sun, 19 Jul 2026 13:55:08 -0300 Subject: [PATCH 11/20] test(packaging): prepare legacy state resolution --- xCAT-test/unit/xcatd_rpm_service_packaging.t | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/xCAT-test/unit/xcatd_rpm_service_packaging.t b/xCAT-test/unit/xcatd_rpm_service_packaging.t index 7217107e1..2add99523 100644 --- a/xCAT-test/unit/xcatd_rpm_service_packaging.t +++ b/xCAT-test/unit/xcatd_rpm_service_packaging.t @@ -504,8 +504,11 @@ 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_state=\$\("\$xcatd_init_compat" legacy-state\).*?if \[ "\$legacy_xcatd_state" = unregistered \].*?systemd-state.*?"\$xcatd_init_compat" disable-systemd.*?configure --replace.*?register-legacy "\$legacy_xcatd_state"}s, + qr{legacy_xcatd_state=\$\("\$xcatd_init_compat" legacy-(?:state|transition-state)\).*?"\$xcatd_init_compat" disable-systemd.*?configure --replace.*?register-legacy "\$legacy_xcatd_state"}s, 'RPM legacy transitions preserve prior state and clear systemd enablement' ); +like( $rpm_upgrade_legacy, + qr{if \[ "\$legacy_xcatd_state" = unregistered \].*?systemd-state|legacy-transition-state}s, + 'RPM legacy transitions resolve unregistered state through the shared helper' ); like( $rpm_fresh, qr{if \[ "\$\("\$xcatd_init_compat" systemd-state\)" != masked \]; then\s+"\$xcatd_init_compat" register-legacy enabled}s, 'RPM fresh legacy installs do not override a persistent systemd mask' ); From 7df7da59b35228aea78c0c1d7241574c05e33d8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Ferr=C3=A3o?= <2031761+viniciusferrao@users.noreply.github.com> Date: Sun, 19 Jul 2026 13:35:01 -0300 Subject: [PATCH 12/20] fix(packaging): preserve unregistered legacy state --- .../share/xcat/scripts/xcatd-init-compat | 34 ++++++++++++++++++- xCAT-server/xCAT-server.spec | 16 ++++++--- 2 files changed, 44 insertions(+), 6 deletions(-) diff --git a/xCAT-server/share/xcat/scripts/xcatd-init-compat b/xCAT-server/share/xcat/scripts/xcatd-init-compat index d1ec398f4..1a42c8b18 100755 --- a/xCAT-server/share/xcat/scripts/xcatd-init-compat +++ b/xCAT-server/share/xcat/scripts/xcatd-init-compat @@ -150,6 +150,35 @@ legacy_state() 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 \ @@ -365,6 +394,9 @@ case "${1:-}" in legacy-state) legacy_state ;; + legacy-transition-state) + legacy_transition_state + ;; systemd-state) systemd_state ;; @@ -398,7 +430,7 @@ case "${1:-}" in remove_managed_legacy_init ;; *) - echo "Usage: $0 {uses-systemd [--explicit-target]|legacy-state|systemd-state|register-legacy {enabled|disabled}|unregister-legacy|disable-systemd|unregister-all|configure [--replace] [--explicit-target] [--track-managed]|remove|remove-managed}" >&2 + echo "Usage: $0 {uses-systemd [--explicit-target]|legacy-state|legacy-transition-state|systemd-state|register-legacy {enabled|disabled}|unregister-legacy|disable-systemd|unregister-all|configure [--replace] [--explicit-target] [--track-managed]|remove|remove-managed}" >&2 exit 2 ;; esac diff --git a/xCAT-server/xCAT-server.spec b/xCAT-server/xCAT-server.spec index 1ff870f15..7cdf422e5 100644 --- a/xCAT-server/xCAT-server.spec +++ b/xCAT-server/xCAT-server.spec @@ -552,12 +552,18 @@ if [ "$1" -gt "1" ]; then #only on upgrade... systemctl enable xcatd.service fi else - legacy_xcatd_state=$("$xcatd_init_compat" legacy-state) - if [ "$legacy_xcatd_state" = unregistered ]; then - legacy_xcatd_state=$("$xcatd_init_compat" systemd-state) - fi + legacy_xcatd_state=$("$xcatd_init_compat" legacy-transition-state) "$xcatd_init_compat" disable-systemd + # 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 --explicit-target --track-managed || exit 1 @@ -566,7 +572,7 @@ if [ "$1" -gt "1" ]; then #only on upgrade... enabled|disabled) "$xcatd_init_compat" register-legacy "$legacy_xcatd_state" ;; - masked) + masked|unregistered) ;; esac fi From 7f44bd51bf73c785eda1569fe1926dfd043235f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Ferr=C3=A3o?= <2031761+viniciusferrao@users.noreply.github.com> Date: Sun, 19 Jul 2026 13:35:33 -0300 Subject: [PATCH 13/20] test(packaging): cover legacy upgrade edge cases --- xCAT-test/unit/xcatd_rpm_service_packaging.t | 42 ++++++++++++++++++-- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/xCAT-test/unit/xcatd_rpm_service_packaging.t b/xCAT-test/unit/xcatd_rpm_service_packaging.t index 2add99523..74fa0b6b9 100644 --- a/xCAT-test/unit/xcatd_rpm_service_packaging.t +++ b/xCAT-test/unit/xcatd_rpm_service_packaging.t @@ -142,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), @@ -234,18 +236,41 @@ ok( -x legacy_init($real_sysvinit_root), 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', @@ -256,6 +281,8 @@ stage_symlink( ); 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( @@ -272,6 +299,8 @@ stage_symlink( ); 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( @@ -280,6 +309,8 @@ stage_symlink( ); 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 = ( @@ -312,6 +343,8 @@ 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 $dangling_legacy_root = stage_root(); make_path( File::Spec->catdir( $dangling_legacy_root, 'etc', 'init.d' ) ); @@ -420,6 +453,10 @@ 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) ), @@ -504,11 +541,8 @@ 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_state=\$\("\$xcatd_init_compat" legacy-(?:state|transition-state)\).*?"\$xcatd_init_compat" disable-systemd.*?configure --replace.*?register-legacy "\$legacy_xcatd_state"}s, + qr{legacy_xcatd_state=\$\("\$xcatd_init_compat" legacy-transition-state\).*?"\$xcatd_init_compat" disable-systemd.*?masked\|unregistered\).*?"\$xcatd_init_compat" unregister-legacy.*?configure --replace.*?enabled\|disabled\).*?register-legacy "\$legacy_xcatd_state".*?masked\|unregistered\)}s, 'RPM legacy transitions preserve prior state and clear systemd enablement' ); -like( $rpm_upgrade_legacy, - qr{if \[ "\$legacy_xcatd_state" = unregistered \].*?systemd-state|legacy-transition-state}s, - 'RPM legacy transitions resolve unregistered state through the shared helper' ); like( $rpm_fresh, qr{if \[ "\$\("\$xcatd_init_compat" systemd-state\)" != masked \]; then\s+"\$xcatd_init_compat" register-legacy enabled}s, 'RPM fresh legacy installs do not override a persistent systemd mask' ); From bdbf7b894a09e67466c632dfd3bed2606d5136c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Ferr=C3=A3o?= <2031761+viniciusferrao@users.noreply.github.com> Date: Sun, 19 Jul 2026 14:24:22 -0300 Subject: [PATCH 14/20] fix(packaging): preserve custom SysV runlevels --- xCAT-server/share/xcat/scripts/xcatd-init-compat | 16 +++++++++++++--- xCAT-server/xCAT-server.spec | 10 +++++++--- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/xCAT-server/share/xcat/scripts/xcatd-init-compat b/xCAT-server/share/xcat/scripts/xcatd-init-compat index 1a42c8b18..a1001068b 100755 --- a/xCAT-server/share/xcat/scripts/xcatd-init-compat +++ b/xCAT-server/share/xcat/scripts/xcatd-init-compat @@ -270,7 +270,10 @@ unregister_legacy_init() disable_systemd_init() { - if [ -z "$compat_root" ] && command -v systemctl >/dev/null 2>&1; then + 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 @@ -414,7 +417,14 @@ case "${1:-}" in unregister_legacy_init ;; disable-systemd) - disable_systemd_init + 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 @@ -430,7 +440,7 @@ case "${1:-}" in remove_managed_legacy_init ;; *) - echo "Usage: $0 {uses-systemd [--explicit-target]|legacy-state|legacy-transition-state|systemd-state|register-legacy {enabled|disabled}|unregister-legacy|disable-systemd|unregister-all|configure [--replace] [--explicit-target] [--track-managed]|remove|remove-managed}" >&2 + echo "Usage: $0 {uses-systemd [--explicit-target]|legacy-state|legacy-transition-state|systemd-state|register-legacy {enabled|disabled}|unregister-legacy|disable-systemd [--links-only]|unregister-all|configure [--replace] [--explicit-target] [--track-managed]|remove|remove-managed}" >&2 exit 2 ;; esac diff --git a/xCAT-server/xCAT-server.spec b/xCAT-server/xCAT-server.spec index 7cdf422e5..11e688a25 100644 --- a/xCAT-server/xCAT-server.spec +++ b/xCAT-server/xCAT-server.spec @@ -521,7 +521,8 @@ if [ "$1" = "1" ]; then #Only if installing for the first time.. legacy_xcatd_state=$("$xcatd_init_compat" legacy-state) case "$legacy_xcatd_state" in enabled|disabled) - "$xcatd_init_compat" register-legacy "$legacy_xcatd_state" + # Preserve any pre-existing administrator runlevel layout. + : ;; *) if [ "$("$xcatd_init_compat" systemd-state)" != masked ]; then @@ -552,8 +553,9 @@ if [ "$1" -gt "1" ]; then #only on upgrade... 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 + "$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 @@ -570,7 +572,9 @@ if [ "$1" -gt "1" ]; then #only on upgrade... case "$legacy_xcatd_state" in enabled|disabled) - "$xcatd_init_compat" register-legacy "$legacy_xcatd_state" + if [ "$legacy_xcatd_registration" = unregistered ]; then + "$xcatd_init_compat" register-legacy "$legacy_xcatd_state" + fi ;; masked|unregistered) ;; From 9091b3f598174827674ae6bfcb9d972ff1c69d8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Ferr=C3=A3o?= <2031761+viniciusferrao@users.noreply.github.com> Date: Sun, 19 Jul 2026 14:25:10 -0300 Subject: [PATCH 15/20] test(packaging): cover custom SysV runlevels --- xCAT-test/unit/xcatd_rpm_service_packaging.t | 32 +++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/xCAT-test/unit/xcatd_rpm_service_packaging.t b/xCAT-test/unit/xcatd_rpm_service_packaging.t index 74fa0b6b9..942c44680 100644 --- a/xCAT-test/unit/xcatd_rpm_service_packaging.t +++ b/xCAT-test/unit/xcatd_rpm_service_packaging.t @@ -346,6 +346,30 @@ is( run_helper( $cleanup_root, 'register-legacy', 'enabled' ), 2, 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' ) ); symlink( '/missing/xcatd', legacy_init($dangling_legacy_root) ) @@ -541,8 +565,14 @@ 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_state=\$\("\$xcatd_init_compat" legacy-transition-state\).*?"\$xcatd_init_compat" disable-systemd.*?masked\|unregistered\).*?"\$xcatd_init_compat" unregister-legacy.*?configure --replace.*?enabled\|disabled\).*?register-legacy "\$legacy_xcatd_state".*?masked\|unregistered\)}s, + 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 enabled}s, 'RPM fresh legacy installs do not override a persistent systemd mask' ); From 0826a6b1ad9024c36d507791bb7edd32e5a4845c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Ferr=C3=A3o?= <2031761+viniciusferrao@users.noreply.github.com> Date: Sun, 19 Jul 2026 16:34:46 -0300 Subject: [PATCH 16/20] test(packaging): prepare fresh legacy defaults --- xCAT-test/unit/xcatd_rpm_service_packaging.t | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xCAT-test/unit/xcatd_rpm_service_packaging.t b/xCAT-test/unit/xcatd_rpm_service_packaging.t index 942c44680..8b2594ff2 100644 --- a/xCAT-test/unit/xcatd_rpm_service_packaging.t +++ b/xCAT-test/unit/xcatd_rpm_service_packaging.t @@ -574,7 +574,7 @@ 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 enabled}s, + qr{if \[ "\$\("\$xcatd_init_compat" systemd-state\)" != masked \]; then\s+"\$xcatd_init_compat" register-legacy (?:enabled|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, From 09d1befb625a65bc773f2a3be6129afcc31e5044 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Ferr=C3=A3o?= <2031761+viniciusferrao@users.noreply.github.com> Date: Sun, 19 Jul 2026 16:36:54 -0300 Subject: [PATCH 17/20] fix(packaging): preserve fresh legacy defaults --- .../share/xcat/scripts/xcatd-init-compat | 19 ++++++++++--------- xCAT-server/xCAT-server.spec | 3 ++- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/xCAT-server/share/xcat/scripts/xcatd-init-compat b/xCAT-server/share/xcat/scripts/xcatd-init-compat index a1001068b..82b6bb6b3 100755 --- a/xCAT-server/share/xcat/scripts/xcatd-init-compat +++ b/xCAT-server/share/xcat/scripts/xcatd-init-compat @@ -236,12 +236,13 @@ register_legacy_init() if [ -x /sbin/chkconfig ]; then /sbin/chkconfig --add xcatd || return - if [ "$desired_state" = enabled ]; then - /sbin/chkconfig --level 345 xcatd on - else - /sbin/chkconfig xcatd off - fi - elif [ "$desired_state" = enabled ] && [ -x /usr/lib/lsb/install_initd ]; then + 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. @@ -405,9 +406,9 @@ case "${1:-}" in ;; register-legacy) case "${2:-}" in - enabled|disabled) ;; + default|enabled|disabled) ;; *) - echo "Usage: $0 register-legacy {enabled|disabled}" >&2 + echo "Usage: $0 register-legacy {default|enabled|disabled}" >&2 exit 2 ;; esac @@ -440,7 +441,7 @@ case "${1:-}" in remove_managed_legacy_init ;; *) - echo "Usage: $0 {uses-systemd [--explicit-target]|legacy-state|legacy-transition-state|systemd-state|register-legacy {enabled|disabled}|unregister-legacy|disable-systemd [--links-only]|unregister-all|configure [--replace] [--explicit-target] [--track-managed]|remove|remove-managed}" >&2 + echo "Usage: $0 {uses-systemd [--explicit-target]|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 diff --git a/xCAT-server/xCAT-server.spec b/xCAT-server/xCAT-server.spec index 11e688a25..93c2e0e01 100644 --- a/xCAT-server/xCAT-server.spec +++ b/xCAT-server/xCAT-server.spec @@ -525,8 +525,9 @@ if [ "$1" = "1" ]; then #Only if installing for the first time.. : ;; *) + # Preserve the script's declared defaults on first install. if [ "$("$xcatd_init_compat" systemd-state)" != masked ]; then - "$xcatd_init_compat" register-legacy enabled + "$xcatd_init_compat" register-legacy default fi ;; esac From 3505c24040c3cca66ebf4e891ff367398207d43d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Ferr=C3=A3o?= <2031761+viniciusferrao@users.noreply.github.com> Date: Sun, 19 Jul 2026 16:37:25 -0300 Subject: [PATCH 18/20] test(packaging): cover fresh legacy defaults --- xCAT-test/unit/xcatd_rpm_service_packaging.t | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/xCAT-test/unit/xcatd_rpm_service_packaging.t b/xCAT-test/unit/xcatd_rpm_service_packaging.t index 8b2594ff2..b5420f153 100644 --- a/xCAT-test/unit/xcatd_rpm_service_packaging.t +++ b/xCAT-test/unit/xcatd_rpm_service_packaging.t @@ -527,6 +527,9 @@ my $rpm_spec = read_file( 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' ); @@ -574,7 +577,7 @@ 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 (?:enabled|default)}s, + 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, From ef6e0373661f2fa1720630533688833ec7a2bda4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Ferr=C3=A3o?= <2031761+viniciusferrao@users.noreply.github.com> Date: Sun, 19 Jul 2026 17:38:57 -0300 Subject: [PATCH 19/20] refactor(packaging): share systemctl readiness guard --- xCAT-server/share/xcat/scripts/xcatd-init-compat | 16 +++++++++++++++- xCAT-server/xCAT-server.spec | 16 +++------------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/xCAT-server/share/xcat/scripts/xcatd-init-compat b/xCAT-server/share/xcat/scripts/xcatd-init-compat index 82b6bb6b3..f9beb42d2 100755 --- a/xCAT-server/share/xcat/scripts/xcatd-init-compat +++ b/xCAT-server/share/xcat/scripts/xcatd-init-compat @@ -123,6 +123,11 @@ uses_systemd() return 1 } +can_use_systemctl() +{ + [ -d /run/systemd/system ] && command -v systemctl >/dev/null 2>&1 +} + legacy_state() { for legacy_link in \ @@ -395,6 +400,15 @@ case "${1:-}" in esac 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 ;; @@ -441,7 +455,7 @@ case "${1:-}" in remove_managed_legacy_init ;; *) - echo "Usage: $0 {uses-systemd [--explicit-target]|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 + 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 diff --git a/xCAT-server/xCAT-server.spec b/xCAT-server/xCAT-server.spec index 93c2e0e01..be11b486c 100644 --- a/xCAT-server/xCAT-server.spec +++ b/xCAT-server/xCAT-server.spec @@ -499,18 +499,13 @@ 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 --explicit-target; then "$xcatd_init_compat" configure --explicit-target || exit 1 - if xcat_can_use_systemctl; then + if "$xcatd_init_compat" can-use-systemctl; then systemctl daemon-reload fi if command -v systemctl >/dev/null 2>&1; then @@ -546,7 +541,7 @@ if [ "$1" -gt "1" ]; then #only on upgrade... # 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_state" = enabled ] && command -v systemctl >/dev/null 2>&1; then @@ -631,17 +626,12 @@ fi %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 --explicit-target && [ -x /etc/init.d/xcatd ]; then /etc/init.d/xcatd stop From 6c26a10863c5056938a4454a53d089de8bb82394 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Ferr=C3=A3o?= <2031761+viniciusferrao@users.noreply.github.com> Date: Sun, 19 Jul 2026 17:40:00 -0300 Subject: [PATCH 20/20] test(packaging): cover shared systemctl guard --- xCAT-test/unit/xcatd_rpm_service_packaging.t | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/xCAT-test/unit/xcatd_rpm_service_packaging.t b/xCAT-test/unit/xcatd_rpm_service_packaging.t index b5420f153..2af06e242 100644 --- a/xCAT-test/unit/xcatd_rpm_service_packaging.t +++ b/xCAT-test/unit/xcatd_rpm_service_packaging.t @@ -521,6 +521,15 @@ 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' ) ); @@ -558,6 +567,12 @@ unlike( $rpm_spec, 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' );