diff --git a/configure.ac b/configure.ac
index 78357bf663..1db3350fdb 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,2252 +1,2278 @@
 dnl
 dnl autoconf for Pacemaker
 dnl
 dnl Copyright 2009-2024 the Pacemaker project contributors
 dnl
 dnl The version control history for this file may have further details.
 dnl
 dnl This source code is licensed under the GNU General Public License version 2
 dnl or later (GPLv2+) WITHOUT ANY WARRANTY.
 
 
 dnl ==============================================
 dnl Bootstrap autotools
 dnl ==============================================
 
 # Require a minimum version of autoconf itself
 AC_PREREQ(2.64)
 
 dnl AC_CONFIG_MACRO_DIR is deprecated as of autoconf 2.70 (2020-12-08).
 dnl Once we can require that version, we can simplify this, and no longer
 dnl need ACLOCAL_AMFLAGS in Makefile.am.
 m4_ifdef([AC_CONFIG_MACRO_DIRS],
          [AC_CONFIG_MACRO_DIRS([m4])],
          [AC_CONFIG_MACRO_DIR([m4])])
 
 m4_include([m4/version.m4])
 AC_INIT([pacemaker], VERSION_NUMBER, [users@clusterlabs.org], [pacemaker],
         PCMK_URL)
 
 LT_CONFIG_LTDL_DIR([libltdl])
 AC_CONFIG_AUX_DIR([libltdl/config])
 
 dnl Where #defines that autoconf makes (e.g. HAVE_whatever) go
 dnl
 dnl include/config.h
 dnl   - Internal API
 dnl   - Contains all defines
 dnl   - include/config.h.in is generated automatically by autoheader
 dnl   - Not to be included in any header files except crm_internal.h
 dnl     (which is also not to be included in any other header files)
 dnl
 dnl include/crm_config.h
 dnl   - External API
 dnl   - Contains a subset of defines
 dnl   - include/crm_config.h.in is manually edited to select the subset
 dnl   - Should not include HAVE_* defines
 dnl   - Safe to include anywhere
 AC_CONFIG_HEADERS([include/config.h include/crm_config.h])
 
 dnl 1.13:           minimum automake version required
 dnl foreign:        don't require GNU-standard top-level files
 dnl tar-ustar:      use (older) POSIX variant of generated tar rather than v7
 dnl subdir-objects: keep .o's with their .c's (no-op in 2.0+)
 AM_INIT_AUTOMAKE([1.13 foreign tar-ustar subdir-objects])
 
 dnl Require minimum version of pkg-config
 PKG_PROG_PKG_CONFIG(0.27)
 AS_IF([test x"${PKG_CONFIG}" != x""], [],
       [AC_MSG_FAILURE([Could not find required build tool pkg-config (0.27 or later)])])
 PKG_INSTALLDIR
 PKG_NOARCH_INSTALLDIR
 
 
 dnl ==============================================
 dnl Compiler checks and helpers
 dnl ==============================================
 
 dnl A particular compiler can be forced by setting the CC environment variable
 AC_PROG_CC
 
 dnl C++ is needed only to run maintainer utilities, not to build
 AC_PROG_CXX
 
 dnl Use at least C99 if possible (automatic for autoconf >= 2.70)
 m4_version_prereq([2.70], [:], [AC_PROG_CC_STDC])
 
 # cc_supports_flag <compiler-flag>
 #  Return success if the C compiler supports the given flag
 cc_supports_flag() {
     local CFLAGS="-Werror $@"
     AC_MSG_CHECKING([whether $CC supports $@])
     AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ ]], [[ ]])],
                       [RC=0; AC_MSG_RESULT([yes])],
                       [RC=1; AC_MSG_RESULT([no])])
     return $RC
 }
 
 # cc_temp_flags <compiler-flags>
 #  Use the given flags for subsequent C compilation. These can be reverted to
 #  what was used previously with cc_restore_flags. This allows certain tests to
 #  use specific flags without affecting anything else.
 cc_temp_flags() {
     ac_save_CFLAGS="$CFLAGS"
     CFLAGS="$*"
 }
 
 # cc_restore_flags
 #  Restore C compiler flags to what they were before the last cc_temp_flags
 #  call.
 cc_restore_flags() {
     CFLAGS=$ac_save_CFLAGS
 }
 
 # Check for fatal warning support
 AS_IF([test $enable_fatal_warnings -ne $DISABLED dnl
        && test x"$GCC" = x"yes" && cc_supports_flag -Werror],
       [WERROR="-Werror"],
       [
           WERROR=""
           AS_CASE([$enable_fatal_warnings],
                   [$REQUIRED], [AC_MSG_ERROR([Compiler does not support fatal warnings])],
                   [$OPTIONAL], [enable_fatal_warnings=$DISABLED])
       ])
 
 dnl We use md5.c from gnulib, which has its own m4 macros. Per its docs:
 dnl "The macro gl_EARLY must be called as soon as possible after verifying that
 dnl the C compiler is working. ... The core part of the gnulib checks are done
 dnl by the macro gl_INIT." In addition, prevent gnulib from introducing OpenSSL
 dnl as a dependency.
 gl_EARLY
 gl_SET_CRYPTO_CHECK_DEFAULT([no])
 gl_INIT
 
 AC_CHECK_SIZEOF(long)
 
 
 dnl ==============================================
 dnl Linker checks
 dnl ==============================================
 
 # Check whether linker supports --enable-new-dtags to use RUNPATH instead of
 # RPATH. It is necessary to do this before libtool does linker detection.
 # See also: https://github.com/kronosnet/kronosnet/issues/107
 AX_CHECK_LINK_FLAG([-Wl,--enable-new-dtags],
                   [AM_LDFLAGS=-Wl,--enable-new-dtags],
                   [AC_MSG_ERROR(["Linker support for --enable-new-dtags is required"])])
 AC_SUBST([AM_LDFLAGS])
 
 saved_LDFLAGS="$LDFLAGS"
 LDFLAGS="$AM_LDFLAGS $LDFLAGS"
 LT_INIT([dlopen])
 LDFLAGS="$saved_LDFLAGS"
 LTDL_INIT([convenience])
 
 
 dnl ==============================================
 dnl Define configure options
 dnl ==============================================
 
 # yes_no_try <user-response> <default>
 #  Map a yes/no/try user selection to $REQUIRED for yes, $DISABLED for no, and
 #  $OPTIONAL for try.
 DISABLED=0
 REQUIRED=1
 OPTIONAL=2
 yes_no_try() {
     local value
     AS_IF([test x"$1" = x""], [value="$2"], [value="$1"])
     AS_CASE(["`echo "$value" | tr '[A-Z]' '[a-z]'`"],
             [0|no|false|disable], [return $DISABLED],
             [1|yes|true|enable], [return $REQUIRED],
             [try|check], [return $OPTIONAL]
     )
     AC_MSG_ERROR([Invalid option value "$value"])
 }
 
 #
 # Fix the defaults of certain built-in variables so they can be used in the
 # defaults for our custom arguments
 #
 
 AC_MSG_NOTICE([Sanitizing prefix: ${prefix}])
 AS_IF([test x"$prefix" = x"NONE"],
       [
           prefix=/usr
           dnl Fix default variables - "prefix" variable if not specified
           AS_IF([test x"$localstatedir" = x"\${prefix}/var"],
                 [localstatedir="/var"])
           AS_IF([test x"$sysconfdir" = x"\${prefix}/etc"],
                 [sysconfdir="/etc"])
       ])
 
 AC_MSG_NOTICE([Sanitizing exec_prefix: ${exec_prefix}])
 AS_CASE([$exec_prefix],
         [prefix|NONE], [exec_prefix=$prefix])
 
 AC_MSG_NOTICE([Sanitizing libdir: ${libdir}])
 AS_CASE([$libdir],
         [prefix|NONE], [
             AC_MSG_CHECKING([which lib directory to use])
             for aDir in lib64 lib
             do
                 trydir="${exec_prefix}/${aDir}"
                 AS_IF([test -d ${trydir}],
                       [
                           libdir=${trydir}
                           break
                       ])
             done
             AC_MSG_RESULT([$libdir])
         ])
 
 # Start a list of optional features this build supports
 PCMK_FEATURES=""
 
 dnl This section should include only the definition of configure script
 dnl options and determining their values. Processing should be done later when
 dnl possible, other than what's needed to determine values and defaults.
 
 dnl Per the autoconf docs, --enable-*/--disable-* options should control
 dnl features inherent to Pacemaker, while --with-*/--without-* options should
 dnl control the use of external software. However, --enable-*/--disable-* may
 dnl implicitly require additional external dependencies, and
 dnl --with-*/--without-* may implicitly enable or disable features, so the
 dnl line is blurry.
 dnl
 dnl We also use --with-* options for custom file, directory, and path
 dnl locations, since autoconf does not provide an option type for those.
 
 dnl --enable-* options: build process
 
 AC_ARG_ENABLE([quiet],
     [AS_HELP_STRING([--enable-quiet],
         [suppress make output unless there is an error @<:@no@:>@])]
 )
 yes_no_try "$enable_quiet" "no"
 enable_quiet=$?
 
 AC_ARG_ENABLE([fatal-warnings],
     [AS_HELP_STRING([--enable-fatal-warnings],
         [enable pedantic and fatal warnings for gcc @<:@try@:>@])],
 )
 yes_no_try "$enable_fatal_warnings" "try"
 enable_fatal_warnings=$?
 
 AC_ARG_ENABLE([hardening],
     [AS_HELP_STRING([--enable-hardening],
         [harden the resulting executables/libraries @<:@try@:>@])]
 )
 yes_no_try "$enable_hardening" "try"
 enable_hardening=$?
 
 dnl --enable-* options: features
 
 AC_ARG_ENABLE([systemd],
     [AS_HELP_STRING([--enable-systemd],
         [enable support for managing resources via systemd @<:@try@:>@])]
 )
 yes_no_try "$enable_systemd" "try"
 enable_systemd=$?
 
 AC_ARG_ENABLE([upstart],
     [AS_HELP_STRING([--enable-upstart],
         [enable support for managing resources via Upstart (deprecated) @<:@try@:>@])]
 )
 yes_no_try "$enable_upstart" "try"
 enable_upstart=$?
 
 dnl --enable-* options: features inherent to Pacemaker
 
 AC_ARG_ENABLE([compat-2.0],
     [AS_HELP_STRING([--enable-compat-2.0], m4_normalize([
         preserve certain output as it was in 2.0; this option will be
         available only for the lifetime of the 2.1 series @<:@no@:>@]))]
 )
 yes_no_try "$enable_compat_2_0" "no"
 enable_compat_2_0=$?
 
 # Add an option to create symlinks at the pre-2.0.0 daemon name locations, so
 # that users and tools can continue to invoke those names directly (e.g., for
 # meta-data). This option will be removed in a future release.
 AC_ARG_ENABLE([legacy-links],
     [AS_HELP_STRING([--enable-legacy-links],
         [add symlinks for old daemon names (deprecated) @<:@no@:>@])]
 )
 yes_no_try "$enable_legacy_links" "no"
 enable_legacy_links=$?
 
 # AM_GNU_GETTEXT calls AM_NLS which defines the nls option, but it defaults
 # to enabled. We override the definition of AM_NLS to flip the default and mark
 # it as experimental in the help text.
 AC_DEFUN([AM_NLS],
     [AC_MSG_CHECKING([whether NLS is requested])
     AC_ARG_ENABLE([nls],
         [AS_HELP_STRING([--enable-nls],
             [use Native Language Support (experimental)])],
         USE_NLS=$enableval, USE_NLS=no)
     AC_MSG_RESULT([$USE_NLS])
     AC_SUBST([USE_NLS])]
 )
 AM_GNU_GETTEXT([external])
 AM_GNU_GETTEXT_VERSION([0.18])
 
 dnl --with-* options: external software support, and custom locations
 
 dnl This argument is defined via an M4 macro so default can be a variable
 AC_DEFUN([VERSION_ARG],
     [AC_ARG_WITH([version],
         [AS_HELP_STRING([--with-version=VERSION],
             [override package version @<:@$1@:>@])],
         [ PACEMAKER_VERSION="$withval" ],
         [ PACEMAKER_VERSION="$PACKAGE_VERSION" ])]
 )
 VERSION_ARG(VERSION_NUMBER)
 
 CRM_DAEMON_USER=""
 AC_ARG_WITH([daemon-user],
     [AS_HELP_STRING([--with-daemon-user=USER],
         [user to run unprivileged Pacemaker daemons as (advanced option: changing this may break other cluster components unless similarly configured) @<:@hacluster@:>@])],
     [ CRM_DAEMON_USER="$withval" ]
 )
 AS_IF([test x"${CRM_DAEMON_USER}" = x""],
       [CRM_DAEMON_USER="hacluster"])
 
 CRM_DAEMON_GROUP=""
 AC_ARG_WITH([daemon-group],
     [AS_HELP_STRING([--with-daemon-group=GROUP],
         [group to run unprivileged Pacemaker daemons as (advanced option: changing this may break other cluster components unless similarly configured) @<:@haclient@:>@])],
     [ CRM_DAEMON_GROUP="$withval" ]
 )
 AS_IF([test x"${CRM_DAEMON_GROUP}" = x""],
       [CRM_DAEMON_GROUP="haclient"])
 
 BUG_URL=""
 AC_ARG_WITH([bug-url],
     [AS_HELP_STRING([--with-bug-url=DIR], m4_normalize([
         address where users should submit bug reports
         @<:@https://bugs.clusterlabs.org/enter_bug.cgi?product=Pacemaker@:>@]))],
     [ BUG_URL="$withval" ]
 )
 AS_IF([test x"${BUG_URL}" = x""],
       [BUG_URL="https://bugs.clusterlabs.org/enter_bug.cgi?product=Pacemaker"])
 
 dnl --with-* options: features
 
 AC_ARG_WITH([cibsecrets],
     [AS_HELP_STRING([--with-cibsecrets],
         [support separate file for CIB secrets @<:@no@:>@])]
 )
 yes_no_try "$with_cibsecrets" "no"
 with_cibsecrets=$?
 
 AC_ARG_WITH([gnutls],
     [AS_HELP_STRING([--with-gnutls],
         [support Pacemaker Remote and remote-tls-port using GnuTLS @<:@try@:>@])]
 )
 yes_no_try "$with_gnutls" "try"
 with_gnutls=$?
 
 PCMK_GNUTLS_PRIORITIES="NORMAL"
 AC_ARG_WITH([gnutls-priorities],
     [AS_HELP_STRING([--with-gnutls-priorities],
         [default GnuTLS cipher priorities @<:@NORMAL@:>@])],
     [ test x"$withval" = x"no" || PCMK_GNUTLS_PRIORITIES="$withval" ]
 )
 
 AC_ARG_WITH([concurrent-fencing-default],
     [AS_HELP_STRING([--with-concurrent-fencing-default],
         [default value for concurrent-fencing cluster option @<:@false@:>@])],
 )
 AS_CASE([$with_concurrent_fencing_default],
         [""], [with_concurrent_fencing_default="false"],
         [false], [],
         [true], [PCMK_FEATURES="$PCMK_FEATURES default-concurrent-fencing"],
         [AC_MSG_ERROR([Invalid value "$with_concurrent_fencing_default" for --with-concurrent-fencing-default])]
 )
 
 AC_ARG_WITH([sbd-sync-default],
     [AS_HELP_STRING([--with-sbd-sync-default], m4_normalize([
         default value used by sbd if SBD_SYNC_RESOURCE_STARTUP
         environment variable is not set @<:@false@:>@]))],
 )
 AS_CASE([$with_sbd_sync_default],
         [""], [with_sbd_sync_default=false],
         [false], [],
         [true], [PCMK_FEATURES="$PCMK_FEATURES default-sbd-sync"],
         [AC_MSG_ERROR([Invalid value "$with_sbd_sync_default" for --with-sbd-sync-default])]
 )
 
 AC_ARG_WITH([resource-stickiness-default],
     [AS_HELP_STRING([--with-resource-stickiness-default],
         [If positive, value to add to new CIBs as explicit resource default for resource-stickiness @<:@0@:>@])],
 )
 errmsg="Invalid value \"$with_resource_stickiness_default\" for --with-resource-stickiness-default"
 AS_CASE([$with_resource_stickiness_default],
         [0|""], [with_resource_stickiness_default="0"],
         [*[[!0-9]]*], [AC_MSG_ERROR([$errmsg])],
         [PCMK_FEATURES="$PCMK_FEATURES default-resource-stickiness"]
 )
 
 AC_ARG_WITH([corosync],
     [AS_HELP_STRING([--with-corosync],
         [support the Corosync messaging and membership layer @<:@try@:>@])]
 )
 yes_no_try "$with_corosync" "try"
 with_corosync=$?
 
 dnl Get default from Corosync if possible
 PKG_CHECK_VAR([PCMK__COROSYNC_CONF], [corosync], [corosysconfdir],
               [PCMK__COROSYNC_CONF="$PCMK__COROSYNC_CONF/corosync.conf"],
               [PCMK__COROSYNC_CONF="${sysconfdir}/corosync/corosync.conf"])
 AC_ARG_WITH([corosync-conf],
     [AS_HELP_STRING([--with-corosync-conf], m4_normalize([
         location of Corosync configuration file
         @<:@value from Corosync package if available otherwise
         SYSCONFDIR/corosync/corosync.conf@:>@]))],
     [ PCMK__COROSYNC_CONF="$withval" ]
 )
 
 AC_ARG_WITH([nagios],
     [AS_HELP_STRING([--with-nagios], [support nagios resources (deprecated)])]
 )
 yes_no_try "$with_nagios" "try"
 with_nagios=$?
 
 dnl --with-* options: directory locations
 
 AC_ARG_WITH([nagios-plugin-dir],
     [AS_HELP_STRING([--with-nagios-plugin-dir=DIR],
         [directory for nagios plugins (deprecated) @<:@LIBEXECDIR/nagios/plugins@:>@])],
     [ NAGIOS_PLUGIN_DIR="$withval" ]
 )
 
 AC_ARG_WITH([nagios-metadata-dir],
     [AS_HELP_STRING([--with-nagios-metadata-dir=DIR],
         [directory for nagios plugins metadata (deprecated) @<:@DATADIR/nagios/plugins-metadata@:>@])],
     [ NAGIOS_METADATA_DIR="$withval" ]
 )
 
 INITDIR=""
 AC_ARG_WITH([initdir],
-    [AS_HELP_STRING([--with-initdir=DIR],
-        [directory for init (rc) scripts])],
+    [AS_HELP_STRING([--with-initdir=DIR], m4_normalize([
+        directory for lsb resources (init scripts), or "try" to check for
+        common locations, or "no" to disable] @<:@try@:>@))],
     [ INITDIR="$withval" ]
 )
+AS_IF([test x"$INITDIR" = x""], [INITDIR="try"])
 
 systemdsystemunitdir="${systemdsystemunitdir-}"
 AC_ARG_WITH([systemdsystemunitdir],
     [AS_HELP_STRING([--with-systemdsystemunitdir=DIR],
         [directory for systemd unit files (advanced option: must match what systemd uses)])],
     [ systemdsystemunitdir="$withval" ]
 )
 
 CONFIGDIR=""
 AC_ARG_WITH([configdir],
     [AS_HELP_STRING([--with-configdir=DIR],
         [directory for Pacemaker configuration file @<:@SYSCONFDIR/sysconfig@:>@])],
     [ CONFIGDIR="$withval" ]
 )
 
 dnl --runstatedir is available as of autoconf 2.70 (2020-12-08). When users
 dnl have an older version, they can use our --with-runstatedir.
 pcmk_runstatedir=""
 AC_ARG_WITH([runstatedir],
     [AS_HELP_STRING([--with-runstatedir=DIR],
         [modifiable per-process data @<:@LOCALSTATEDIR/run@:>@ (ignored if --runstatedir is available)])],
     [ pcmk_runstatedir="$withval" ]
 )
 
 CRM_LOG_DIR=""
 AC_ARG_WITH([logdir],
     [AS_HELP_STRING([--with-logdir=DIR],
         [directory for Pacemaker log file @<:@LOCALSTATEDIR/log/pacemaker@:>@])],
     [ CRM_LOG_DIR="$withval" ]
 )
 
 CRM_BUNDLE_DIR=""
 AC_ARG_WITH([bundledir],
     [AS_HELP_STRING([--with-bundledir=DIR],
         [directory for Pacemaker bundle logs @<:@LOCALSTATEDIR/log/pacemaker/bundles@:>@])],
     [ CRM_BUNDLE_DIR="$withval" ]
 )
 
 dnl Get default from resource-agents if possible. Otherwise, the default uses
 dnl /usr/lib rather than libdir because it's determined by the OCF project and
 dnl not Pacemaker. Even if a user wants to install Pacemaker to /usr/local or
 dnl such, the OCF agents will be expected in their usual location. However, we
 dnl do give the user the option to override it.
 PKG_CHECK_VAR([OCF_ROOT_DIR], [resource-agents], [ocfrootdir], [],
               [OCF_ROOT_DIR="/usr/lib/ocf"])
 AC_ARG_WITH([ocfdir],
     [AS_HELP_STRING([--with-ocfdir=DIR], m4_normalize([
         OCF resource agent root directory (advanced option: changing this
         may break other cluster components unless similarly configured)
         @<:@value from resource-agents package if available otherwise
         /usr/lib/ocf@:>@]))],
     [ OCF_ROOT_DIR="$withval" ]
 )
 
 dnl Get default from resource-agents if possible
 PKG_CHECK_VAR([OCF_RA_PATH], [resource-agents], [ocfrapath], [],
               [OCF_RA_PATH="$OCF_ROOT_DIR/resource.d"])
 AC_ARG_WITH([ocfrapath],
     [AS_HELP_STRING([--with-ocfrapath=DIR], m4_normalize([
         OCF resource agent directories (colon-separated) to search
         @<:@value from resource-agents package if available otherwise
         OCFDIR/resource.d@:>@]))],
     [ OCF_RA_PATH="$withval" ]
 )
 
 OCF_RA_INSTALL_DIR="$OCF_ROOT_DIR/resource.d"
 AC_ARG_WITH([ocfrainstalldir],
     [AS_HELP_STRING([--with-ocfrainstalldir=DIR], m4_normalize([
         OCF installation directory for Pacemakers resource agents
         @<:@OCFDIR/resource.d@:>@]))],
     [ OCF_RA_INSTALL_DIR="$withval" ]
 )
 
 dnl Get default from fence-agents if available
 PKG_CHECK_VAR([FA_PREFIX], [fence-agents], [prefix],
               [PCMK__FENCE_BINDIR="${FA_PREFIX}/sbin"],
               [PCMK__FENCE_BINDIR="$sbindir"])
 AC_ARG_WITH([fence-bindir],
     [AS_HELP_STRING([--with-fence-bindir=DIR], m4_normalize([
         directory for executable fence agents @<:@value from fence-agents
         package if available otherwise SBINDIR@:>@]))],
     [ PCMK__FENCE_BINDIR="$withval" ]
 )
 
 dnl --with-* options: non-production testing
 
 AC_ARG_WITH([profiling],
     [AS_HELP_STRING([--with-profiling],
         [disable optimizations, for effective profiling @<:@no@:>@])]
 )
 yes_no_try "$with_profiling" "no"
 with_profiling=$?
 
 AC_ARG_WITH([coverage],
     [AS_HELP_STRING([--with-coverage],
         [disable optimizations, for effective profiling and coverage testing @<:@no@:>@])]
 )
 yes_no_try "$with_coverage" "no"
 with_coverage=$?
 
 AC_DEFINE_UNQUOTED([PCMK__WITH_COVERAGE], [$with_coverage], [Build with code coverage])
 AM_CONDITIONAL([BUILD_COVERAGE], [test $with_coverage -ne $DISABLED])
 
 AC_ARG_WITH([sanitizers],
   [AS_HELP_STRING([--with-sanitizers=...,...],
     [enable SANitizer build, do *NOT* use for production. Only ASAN/UBSAN/TSAN are currently supported])],
   [ SANITIZERS="$withval" ],
   [ SANITIZERS="" ])
 
 dnl Environment variable options
 
 AC_ARG_VAR([CFLAGS_HARDENED_LIB], [extra C compiler flags for hardened libraries])
 AC_ARG_VAR([LDFLAGS_HARDENED_LIB], [extra linker flags for hardened libraries])
 
 AC_ARG_VAR([CFLAGS_HARDENED_EXE], [extra C compiler flags for hardened executables])
 AC_ARG_VAR([LDFLAGS_HARDENED_EXE], [extra linker flags for hardened executables])
 
 
 dnl ==============================================
 dnl Locate essential tools
 dnl ==============================================
 
 PATH="$PATH:/sbin:/usr/sbin:/usr/local/sbin:/usr/local/bin"
 export PATH
 
 dnl Pacemaker's executable python scripts will invoke the python specified by
 dnl configure's PYTHON variable. If not specified, AM_PATH_PYTHON will check a
 dnl built-in list with (unversioned) "python" having precedence. To configure
 dnl Pacemaker to use a specific python interpreter version, define PYTHON
 dnl when calling configure, for example: ./configure PYTHON=/usr/bin/python3.6
 
 dnl If PYTHON was specified, ensure it is an absolute path
 AS_IF([test x"${PYTHON}" != x""], [AC_PATH_PROG([PYTHON], [$PYTHON])])
 
 dnl Require a minimum Python version
 AM_PATH_PYTHON([3.4])
 
 AC_PROG_LN_S
 AC_PROG_MKDIR_P
 
 AC_PATH_PROG([GIT], [git], [false])
 
 dnl Bash is needed for building man pages and running regression tests.
 dnl We set "BASH_PATH" because "BASH" is already an environment variable.
 REQUIRE_PROG([BASH_PATH], [bash])
 
 AC_PATH_PROGS(VALGRIND_BIN, valgrind, /usr/bin/valgrind)
 AC_DEFINE_UNQUOTED(VALGRIND_BIN, "$VALGRIND_BIN", Valgrind command)
 
 
 dnl ==============================================
 dnl Package and schema versioning
 dnl ==============================================
 
 # Redefine PACKAGE_VERSION and VERSION according to PACEMAKER_VERSION in case
 # the user used --with-version. Unfortunately, this can only affect the
 # substitution variables and later uses in this file, not the config.h
 # constants, so we have to be careful to use only PACEMAKER_VERSION in C code.
 PACKAGE_VERSION=$PACEMAKER_VERSION
 VERSION=$PACEMAKER_VERSION
 
 AC_DEFINE_UNQUOTED(PACEMAKER_VERSION, "$VERSION",
                    [Version number of this Pacemaker build])
 
 AC_MSG_CHECKING([build version])
 AS_IF([test "$GIT" != "false" && test -d .git],
       [
           BUILD_VERSION=`"$GIT" log --pretty="format:%h" -n 1`
           AC_MSG_RESULT([$BUILD_VERSION (git hash)])
       ],
       [
           # The current directory name make a reasonable default
           # Most generated archives will include the hash or tag
           BASE=`basename $PWD`
           BUILD_VERSION=`echo $BASE | sed s:.*[[Pp]]acemaker-::`
           AC_MSG_RESULT([$BUILD_VERSION (directory name)])
       ])
 AC_DEFINE_UNQUOTED(BUILD_VERSION, "$BUILD_VERSION", Build version)
 AC_SUBST(BUILD_VERSION)
 
 # schema_files <schema-dir>
 #  List all manually edited RNG schemas (as opposed to auto-generated via make)
 #  in the given directory. Use git if available to list managed RNGs, in case
 #  there are leftover schema files from an earlier build of a different
 #  version. Otherwise, check all RNGs.
 schema_files() {
     local files="$("$GIT" ls-files "$1"/*.rng 2>/dev/null)"
 
     AS_IF([test x"$files" = x""],
           [
               files="$(ls -1 "$1"/*.rng | grep -E -v    \
                        '/(pacemaker|api-result|crm_mon|versions)[^/]*\.rng')"
           ])
     echo "$files"
 }
 
 # latest_schema_version <schema-dir>
 #  Determine highest RNG version in the given schema directory.
 latest_schema_version() {
     schema_files "$1" | sed -n -e 's/^.*-\([[0-9]][[0-9.]]*\).rng$/\1/p' dnl
                       | sort -V | tail -1
 }
 
 # schemas_for_make <schema-dir>
 #  Like schema_files, but suitable for use in make variables.
 schemas_for_make() {
     local file
 
     for file in $(schema_files "$1"); do
         AS_ECHO_N(["\$(top_srcdir)/$file "])
     done
 }
 
 # Detect highest API schema version
 API_VERSION=$(latest_schema_version "xml/api")
 AC_DEFINE_UNQUOTED([PCMK__API_VERSION], ["$API_VERSION"],
                    [Highest API schema version])
 
 # Detect highest CIB schema version
 CIB_VERSION=$(latest_schema_version "xml")
 AC_SUBST(CIB_VERSION)
 
 # Re-run configure at next make if schema files change, to re-detect versions
 cib_schemas="$(schemas_for_make "xml")"
 api_schemas="$(schemas_for_make "xml/api")"
 CONFIG_STATUS_DEPENDENCIES="$cib_schemas $api_schemas"
 AC_SUBST(CONFIG_STATUS_DEPENDENCIES)
 
 
 dnl ==============================================
 dnl Process simple options
 dnl ==============================================
 
 AS_IF([test $enable_compat_2_0 -ne $DISABLED],
       [
           AC_DEFINE_UNQUOTED([PCMK__COMPAT_2_0], [1],
                              [Keep certain output compatible with 2.0 release series])
           PCMK_FEATURES="$PCMK_FEATURES compat-2.0"
       ]
 )
 
 AM_CONDITIONAL([BUILD_LEGACY_LINKS], [test $enable_legacy_links -ne $DISABLED])
 
 AS_IF([test x"$enable_nls" = x"yes"], [PCMK_FEATURES="$PCMK_FEATURES nls"])
 
 AC_DEFINE_UNQUOTED([PCMK__CONCURRENT_FENCING_DEFAULT],
                    ["$with_concurrent_fencing_default"],
                    [Default value for concurrent-fencing cluster option])
 
 AC_DEFINE_UNQUOTED([PCMK__SBD_SYNC_DEFAULT],
                    [$with_sbd_sync_default],
                    [Default value for SBD_SYNC_RESOURCE_STARTUP environment variable])
 
 AC_DEFINE_UNQUOTED([PCMK__RESOURCE_STICKINESS_DEFAULT],
                    [$with_resource_stickiness_default],
                    [Default value for resource-stickiness resource meta-attribute])
 
 AS_IF([test x"${PCMK_GNUTLS_PRIORITIES}" != x""], [],
       [AC_MSG_ERROR([--with-gnutls-priorities value must not be empty])])
 AC_DEFINE_UNQUOTED([PCMK_GNUTLS_PRIORITIES], ["$PCMK_GNUTLS_PRIORITIES"],
                    [GnuTLS cipher priorities])
 AC_SUBST(PCMK_GNUTLS_PRIORITIES)
 
 AC_SUBST(BUG_URL)
 AC_DEFINE_UNQUOTED([PCMK__BUG_URL], ["$BUG_URL"],
                    [Where bugs should be reported])
 
 AC_DEFINE_UNQUOTED([CRM_DAEMON_USER], ["$CRM_DAEMON_USER"],
                    [User to run Pacemaker daemons as])
 AC_SUBST(CRM_DAEMON_USER)
 
 AC_DEFINE_UNQUOTED([CRM_DAEMON_GROUP], ["$CRM_DAEMON_GROUP"],
                    [Group to run Pacemaker daemons as])
 AC_SUBST(CRM_DAEMON_GROUP)
 
 
 dnl ==============================================
 dnl Process file paths
 dnl ==============================================
 
 # expand_path_option <path-variable-name> [<default>]
 #  Given the name of a file path variable, expand any variable references
 #  inside it, use the specified default if it is not specified, and ensure it
 #  is a full path.
 expand_path_option() {
     # The first argument is the variable *name* (not value)
     ac_path_varname="$1"
 
     # Get the original value of the variable
     ac_path_value=$(eval echo "\${${ac_path_varname}}")
 
     # Expand any literal variable expressions in the value so that we don't
     # end up with something like '${prefix}' in #defines etc.
     #
     # Autoconf deliberately leaves values unexpanded to allow overriding
     # the configure script choices in make commands (for example,
     # "make exec_prefix=/foo install"). No longer being able to do this seems
     # like no great loss.
     eval ac_path_value=$(eval echo "${ac_path_value}")
 
     # Use (expanded) default if necessary
     AS_IF([test x"${ac_path_value}" = x""],
           [eval ac_path_value=$(eval echo "$2")])
 
     # Require a full path
     AS_CASE(["$ac_path_value"],
             [/*], [eval ${ac_path_varname}="$ac_path_value"],
             [*], [AC_MSG_ERROR([$ac_path_varname value "$ac_path_value" is not a full path])]
     )
 }
 
-AC_MSG_NOTICE([Sanitizing INITDIR: ${INITDIR}])
-AS_CASE([$INITDIR],
-        [prefix], [INITDIR=$prefix],
-        [""], [
-            AC_MSG_CHECKING([which init (rc) directory to use])
-            for initdir in /etc/init.d /etc/rc.d/init.d /sbin/init.d \
-                /usr/local/etc/rc.d /etc/rc.d
-            do
-                AS_IF([test -d $initdir],
-                      [
-                          INITDIR=$initdir
-                          break
-                      ])
-            done
-            AC_MSG_RESULT([$INITDIR])
-        ])
-AC_SUBST(INITDIR)
-
 dnl Expand values of autoconf-provided directory options
 expand_path_option prefix
 expand_path_option exec_prefix
 expand_path_option bindir
 expand_path_option sbindir
 expand_path_option libexecdir
 expand_path_option datarootdir
 expand_path_option datadir
 expand_path_option sysconfdir
 expand_path_option sharedstatedir
 expand_path_option localstatedir
 expand_path_option libdir
 expand_path_option includedir
 expand_path_option oldincludedir
 expand_path_option infodir
 expand_path_option mandir
 
 AC_DEFUN([AC_DATAROOTDIR_CHECKED])
 
 dnl Expand values of custom directory options
 
+AS_IF([test x"$INITDIR" = x"try"],
+      [
+          AC_MSG_CHECKING([for an init directory])
+          INITDIR=no
+          for initdir in /etc/init.d /etc/rc.d/init.d /sbin/init.d \
+                         /usr/local/etc/rc.d /etc/rc.d ${sysconfdir}/init.d
+          do
+              AS_IF([test -d $initdir],
+                    [
+                        INITDIR=$initdir
+                        break
+                    ])
+          done
+          AC_MSG_RESULT([$INITDIR])
+      ])
+support_lsb=$DISABLED
+AM_CONDITIONAL([BUILD_LSB], [test x"${INITDIR}" != x"no"])
+AM_COND_IF([BUILD_LSB],
+           [
+               support_lsb=$REQUIRED
+               expand_path_option INITDIR
+               PCMK_FEATURES="$PCMK_FEATURES lsb"
+           ],
+           [ INITDIR="" ])
+AC_SUBST(INITDIR)
+AC_DEFINE_UNQUOTED([PCMK__ENABLE_LSB], [$support_lsb],
+                   [Whether to support LSB resource agents])
+AC_DEFINE_UNQUOTED([PCMK__LSB_INIT_DIR], ["$INITDIR"],
+                   [Location for LSB init scripts])
+
 expand_path_option localedir "${datadir}/locale"
 AC_DEFINE_UNQUOTED([PCMK__LOCALE_DIR],["$localedir"],
                    [Base directory for message catalogs])
 
 AS_IF([test x"${runstatedir}" = x""], [runstatedir="${pcmk_runstatedir}"])
 expand_path_option runstatedir "${localstatedir}/run"
 AC_DEFINE_UNQUOTED([PCMK_RUN_DIR], ["$runstatedir"],
                    [Location for modifiable per-process data])
 AC_SUBST(runstatedir)
 
-expand_path_option INITDIR
-AC_DEFINE_UNQUOTED([PCMK__LSB_INIT_DIR], ["$INITDIR"],
-                   [Location for LSB init scripts])
-
 expand_path_option docdir "${datadir}/doc/${PACKAGE}-${VERSION}"
 AC_SUBST(docdir)
 
 expand_path_option CONFIGDIR "${sysconfdir}/sysconfig"
 AC_SUBST(CONFIGDIR)
 
 expand_path_option PCMK__COROSYNC_CONF "${sysconfdir}/corosync/corosync.conf"
 AC_SUBST(PCMK__COROSYNC_CONF)
 
 expand_path_option CRM_LOG_DIR "${localstatedir}/log/pacemaker"
 AC_DEFINE_UNQUOTED([CRM_LOG_DIR], ["$CRM_LOG_DIR"],
                    [Location for Pacemaker log file])
 AC_SUBST(CRM_LOG_DIR)
 
 expand_path_option CRM_BUNDLE_DIR "${localstatedir}/log/pacemaker/bundles"
 AC_DEFINE_UNQUOTED([CRM_BUNDLE_DIR], ["$CRM_BUNDLE_DIR"],
                    [Location for Pacemaker bundle logs])
 AC_SUBST(CRM_BUNDLE_DIR)
 
 expand_path_option PCMK__FENCE_BINDIR
 AC_SUBST(PCMK__FENCE_BINDIR)
 AC_DEFINE_UNQUOTED([PCMK__FENCE_BINDIR], ["$PCMK__FENCE_BINDIR"],
                    [Location for executable fence agents])
 
 expand_path_option OCF_ROOT_DIR
 AC_SUBST(OCF_ROOT_DIR)
 AC_DEFINE_UNQUOTED([OCF_ROOT_DIR], ["$OCF_ROOT_DIR"],
                    [OCF root directory for resource agents and libraries])
 
 expand_path_option OCF_RA_PATH
 AC_SUBST(OCF_RA_PATH)
 AC_DEFINE_UNQUOTED([OCF_RA_PATH], ["$OCF_RA_PATH"],
                    [OCF directories to search for resource agents ])
 
 expand_path_option OCF_RA_INSTALL_DIR
 AC_SUBST(OCF_RA_INSTALL_DIR)
 
 # Derived paths
 
 CRM_SCHEMA_DIRECTORY="${datadir}/pacemaker"
 AC_DEFINE_UNQUOTED([CRM_SCHEMA_DIRECTORY], ["$CRM_SCHEMA_DIRECTORY"],
                    [Location for the Pacemaker Relax-NG Schema])
 AC_SUBST(CRM_SCHEMA_DIRECTORY)
 
 PCMK__REMOTE_SCHEMA_DIR="${localstatedir}/lib/pacemaker/schemas"
 AC_DEFINE_UNQUOTED([PCMK__REMOTE_SCHEMA_DIR], ["$PCMK__REMOTE_SCHEMA_DIR"],
                    [Location to store Relax-NG Schema files on remote nodes])
 AC_SUBST(PCMK__REMOTE_SCHEMA_DIR)
 
 CRM_CORE_DIR="${localstatedir}/lib/pacemaker/cores"
 AC_DEFINE_UNQUOTED([CRM_CORE_DIR], ["$CRM_CORE_DIR"],
                    [Directory Pacemaker daemons should change to (without systemd, core files will go here)])
 AC_SUBST(CRM_CORE_DIR)
 
 CRM_PACEMAKER_DIR="${localstatedir}/lib/pacemaker"
 AC_DEFINE_UNQUOTED([CRM_PACEMAKER_DIR], ["$CRM_PACEMAKER_DIR"],
                    [Location to store directory produced by Pacemaker daemons])
 AC_SUBST(CRM_PACEMAKER_DIR)
 
 CRM_BLACKBOX_DIR="${localstatedir}/lib/pacemaker/blackbox"
 AC_DEFINE_UNQUOTED([CRM_BLACKBOX_DIR], ["$CRM_BLACKBOX_DIR"],
                    [Where to keep blackbox dumps])
 AC_SUBST(CRM_BLACKBOX_DIR)
 
 PE_STATE_DIR="${localstatedir}/lib/pacemaker/pengine"
 AC_DEFINE_UNQUOTED([PE_STATE_DIR], ["$PE_STATE_DIR"],
                    [Where to keep scheduler outputs])
 AC_SUBST(PE_STATE_DIR)
 
 CRM_CONFIG_DIR="${localstatedir}/lib/pacemaker/cib"
 AC_DEFINE_UNQUOTED([CRM_CONFIG_DIR], ["$CRM_CONFIG_DIR"],
                    [Where to keep configuration files])
 AC_SUBST(CRM_CONFIG_DIR)
 
 CRM_DAEMON_DIR="${libexecdir}/pacemaker"
 AC_DEFINE_UNQUOTED([CRM_DAEMON_DIR], ["$CRM_DAEMON_DIR"],
                    [Location for Pacemaker daemons])
 AC_SUBST(CRM_DAEMON_DIR)
 
 CRM_STATE_DIR="${runstatedir}/crm"
 AC_DEFINE_UNQUOTED([CRM_STATE_DIR], ["$CRM_STATE_DIR"],
                    [Where to keep state files and sockets])
 AC_SUBST(CRM_STATE_DIR)
 
 CRM_RSCTMP_DIR="${runstatedir}/resource-agents"
 AC_DEFINE_UNQUOTED([CRM_RSCTMP_DIR], ["$CRM_RSCTMP_DIR"],
                    [Where resource agents should keep state files])
 AC_SUBST(CRM_RSCTMP_DIR)
 
 PACEMAKER_CONFIG_DIR="${sysconfdir}/pacemaker"
 AC_DEFINE_UNQUOTED([PACEMAKER_CONFIG_DIR], ["$PACEMAKER_CONFIG_DIR"],
                    [Where to keep configuration files like authkey])
 AC_SUBST(PACEMAKER_CONFIG_DIR)
 
 AC_DEFINE_UNQUOTED([SBIN_DIR], ["$sbindir"], [Location for system binaries])
 
 # Warn about any directories that don't exist (which may be OK)
 for j in prefix exec_prefix bindir sbindir libexecdir datadir sysconfdir \
     sharedstatedir localstatedir libdir includedir oldincludedir infodir \
     mandir INITDIR docdir CONFIGDIR localedir
 do
     dirname=`eval echo '${'${j}'}'`
-    AS_IF([test ! -d "$dirname"],
+    AS_IF([test -n "$dirname" && test ! -d "$dirname"],
           [AC_MSG_WARN([$j directory ($dirname) does not exist (yet)])])
 done
 
 dnl ===============================================
 dnl General Processing
 dnl ===============================================
 
 us_auth=
 AC_CHECK_HEADER([sys/socket.h], [
     AC_CHECK_DECL([SO_PEERCRED], [
         # Linux
         AC_CHECK_TYPE([struct ucred], [
             us_auth=peercred_ucred;
             AC_DEFINE([HAVE_UCRED], [1],
                       [Define if Unix socket auth method is
                        getsockopt(s, SO_PEERCRED, &ucred, ...)])
         ], [
             # OpenBSD
             AC_CHECK_TYPE([struct sockpeercred], [
                 us_auth=localpeercred_sockepeercred;
                 AC_DEFINE([HAVE_SOCKPEERCRED], [1],
                           [Define if Unix socket auth method is
                            getsockopt(s, SO_PEERCRED, &sockpeercred, ...)])
             ], [], [[#include <sys/socket.h>]])
         ], [[#define _GNU_SOURCE
              #include <sys/socket.h>]])
     ], [], [[#include <sys/socket.h>]])
 ])
 
 AS_IF([test -z "${us_auth}"], [
     # FreeBSD
     AC_CHECK_DECL([getpeereid], [
         us_auth=getpeereid;
         AC_DEFINE([HAVE_GETPEEREID], [1],
                   [Define if Unix socket auth method is
                    getpeereid(s, &uid, &gid)])
     ], [
         # Solaris/OpenIndiana
         AC_CHECK_DECL([getpeerucred], [
             us_auth=getpeerucred;
             AC_DEFINE([HAVE_GETPEERUCRED], [1],
                       [Define if Unix socket auth method is
                        getpeercred(s, &ucred)])
         ], [
             AC_MSG_FAILURE([No way to authenticate a Unix socket peer])
         ], [[#include <ucred.h>]])
     ])
 ])
 
 dnl OS-based decision-making is poor autotools practice; feature-based
 dnl mechanisms are strongly preferred. Keep this section to a bare minimum;
 dnl regard as a "necessary evil".
 
 dnl Set host_os and host_cpu
 AC_CANONICAL_HOST
 
 INIT_EXT=""
 PROCFS=0
 dnl Solaris and some *BSD versions support procfs but not files we need
 AS_CASE(["$host_os"],
         [*bsd*], [INIT_EXT=".sh"],
         [*linux*], [PROCFS=1],
         [darwin*], [
             LIBS="$LIBS -L${prefix}/lib"
             CFLAGS="$CFLAGS -I${prefix}/include"
         ])
 
 AC_SUBST(INIT_EXT)
 AM_CONDITIONAL([SUPPORT_PROCFS], [test $PROCFS -eq 1])
 AC_DEFINE_UNQUOTED([HAVE_LINUX_PROCFS], [$PROCFS],
                    [Define to 1 if procfs is supported])
 
 AS_CASE(["$host_cpu"],
         [ppc64|powerpc64], [
             AS_CASE([$CFLAGS],
                     [*powerpc64*], [],
                     [*], [AS_IF([test x"$GCC" = x"yes"], [CFLAGS="$CFLAGS -m64"])
                     ])
         ])
 
 
 dnl ==============================================
 dnl Documentation build dependencies and checks
 dnl ==============================================
 
 AC_PATH_PROGS([ASCIIDOC_CONV], [asciidoc asciidoctor])
 AC_PATH_PROG([HELP2MAN], [help2man])
 AC_PATH_PROG([SPHINX], [sphinx-build])
 AC_PATH_PROG([INKSCAPE], [inkscape])
 AC_PATH_PROG([XSLTPROC], [xsltproc])
 AC_PATH_PROG([XMLCATALOG], [xmlcatalog])
 
 AM_CONDITIONAL(BUILD_HELP, test x"${HELP2MAN}" != x"")
 AS_IF([test x"${HELP2MAN}" != x""],
       [PCMK_FEATURES="$PCMK_FEATURES generated-manpages"])
 
 MANPAGE_XSLT=""
 AS_IF([test x"${XSLTPROC}" != x""],
       [
           AC_MSG_CHECKING([for DocBook-to-manpage transform])
           # first try to figure out correct template using xmlcatalog query,
           # resort to extensive (semi-deterministic) file search if that fails
           DOCBOOK_XSL_URI='http://docbook.sourceforge.net/release/xsl/current'
           DOCBOOK_XSL_PATH='manpages/docbook.xsl'
           MANPAGE_XSLT=$(${XMLCATALOG} "" ${DOCBOOK_XSL_URI}/${DOCBOOK_XSL_PATH} \
                          | sed -n 's|^file://||p;q')
           AS_IF([test x"${MANPAGE_XSLT}" = x""],
                 [
                     DIRS=$(find "${datadir}" -name $(basename $(dirname ${DOCBOOK_XSL_PATH})) \
                            -type d 2>/dev/null | LC_ALL=C sort)
                     XSLT=$(basename ${DOCBOOK_XSL_PATH})
                     for d in ${DIRS}
                     do
                         AS_IF([test -f "${d}/${XSLT}"],
                               [
                                   MANPAGE_XSLT="${d}/${XSLT}"
                                   break
                               ])
                     done
                 ])
       ])
 AC_MSG_RESULT([$MANPAGE_XSLT])
 AC_SUBST(MANPAGE_XSLT)
 
 AM_CONDITIONAL(BUILD_XML_HELP, test x"${MANPAGE_XSLT}" != x"")
 AS_IF([test x"${MANPAGE_XSLT}" != x""],
       [PCMK_FEATURES="$PCMK_FEATURES agent-manpages"])
 
 AM_CONDITIONAL([IS_ASCIIDOC], [echo "${ASCIIDOC_CONV}" | grep -Eq 'asciidoc$'])
 AM_CONDITIONAL([BUILD_ASCIIDOC], [test "x${ASCIIDOC_CONV}" != x])
 AS_IF([test x"${ASCIIDOC_CONV}" != x""],
       [PCMK_FEATURES="$PCMK_FEATURES ascii-docs"])
 
 AM_CONDITIONAL([BUILD_SPHINX_DOCS],
                [test x"${SPHINX}" != x"" && test x"${INKSCAPE}" != x""])
 AM_COND_IF([BUILD_SPHINX_DOCS], [PCMK_FEATURES="$PCMK_FEATURES books"])
 
 dnl Pacemaker's shell scripts (and thus man page builders) rely on GNU getopt
 AC_MSG_CHECKING([for GNU-compatible getopt])
 IFS_orig=$IFS
 IFS=:
 for PATH_DIR in $PATH
 do
     IFS=$IFS_orig
     GETOPT_PATH="${PATH_DIR}/getopt"
     AS_IF([test -f "$GETOPT_PATH" && test -x "$GETOPT_PATH"],
           [
               $GETOPT_PATH -T >/dev/null 2>/dev/null
               AS_IF([test $? -eq 4], [break])
           ])
     GETOPT_PATH=""
 done
 IFS=$IFS_orig
 AS_IF([test -n "$GETOPT_PATH"], [AC_MSG_RESULT([$GETOPT_PATH])],
       [
           AC_MSG_RESULT([no])
           AC_MSG_ERROR([Could not find required build tool GNU-compatible getopt])
       ])
 AC_SUBST([GETOPT_PATH])
 
 
 dnl ===============================================
 dnl Libraries
 dnl ===============================================
 AC_CHECK_LIB(socket, socket)                    dnl -lsocket
 AC_CHECK_LIB(c, dlopen)                         dnl if dlopen is in libc...
 AC_CHECK_LIB(dl, dlopen)                        dnl -ldl (for Linux)
 AC_CHECK_LIB(rt, sched_getscheduler)            dnl -lrt (for Tru64)
 AC_CHECK_LIB(gnugetopt, getopt_long)            dnl -lgnugetopt ( if available )
 AC_CHECK_LIB(pam, pam_start)                    dnl -lpam (if available)
 
 PKG_CHECK_MODULES([UUID], [uuid],
                   [CPPFLAGS="${CPPFLAGS} ${UUID_CFLAGS}"
                    LIBS="${LIBS} ${UUID_LIBS}"])
 
 AC_CHECK_FUNCS([sched_setscheduler])
 AS_IF([test x"$ac_cv_func_sched_setscheduler" != x"yes"],
       [PC_LIBS_RT=""],
       [PC_LIBS_RT="-lrt"])
 AC_SUBST(PC_LIBS_RT)
 
 # Require minimum glib version
 PKG_CHECK_MODULES([GLIB], [glib-2.0 >= 2.42.0],
                   [CPPFLAGS="${CPPFLAGS} ${GLIB_CFLAGS}"
                    LIBS="${LIBS} ${GLIB_LIBS}"])
 
 # Check whether high-resolution sleep function is available
 AC_CHECK_FUNCS([nanosleep usleep])
 
 #
 # Where is dlopen?
 #
 AS_IF([test x"$ac_cv_lib_c_dlopen" = x"yes"],
       [LIBADD_DL=""],
       [test x"$ac_cv_lib_dl_dlopen" = x"yes"],
       [LIBADD_DL=-ldl],
       [LIBADD_DL=${lt_cv_dlopen_libs}])
 
 PKG_CHECK_MODULES(LIBXML2, [libxml-2.0 >= 2.6.0],
                   [CPPFLAGS="${CPPFLAGS} ${LIBXML2_CFLAGS}"
                    LIBS="${LIBS} ${LIBXML2_LIBS}"])
 
 REQUIRE_LIB([xslt], [xsltApplyStylesheet])
 
 AC_MSG_CHECKING([whether __progname and __progname_full are available])
 AC_LINK_IFELSE([AC_LANG_PROGRAM([[extern char *__progname, *__progname_full;]],
                                 [[__progname = "foo";
                                   __progname_full = "foo bar";]])],
                [
                    have_progname="yes"
                    AC_DEFINE(HAVE_PROGNAME, 1,
                              [Define to 1 if processes can change their name])
                ],
                [have_progname="no"])
 AC_MSG_RESULT([$have_progname])
 
 dnl ========================================================================
 dnl Headers
 dnl ========================================================================
 
 # Some distributions insert #warnings into deprecated headers. If we will
 # enable fatal warnings for the build, then enable them for the header checks
 # as well, otherwise the build could fail even though the header check
 # succeeds. (We should probably be doing this in more places.)
 cc_temp_flags "$CFLAGS $WERROR"
 
 # Optional headers (inclusion of these should be conditional in C code)
 AC_CHECK_HEADERS([linux/swab.h])
 AC_CHECK_HEADERS([stddef.h])
 AC_CHECK_HEADERS([sys/signalfd.h])
 AC_CHECK_HEADERS([uuid/uuid.h])
 AC_CHECK_HEADERS([security/pam_appl.h pam/pam_appl.h])
 
 # Required headers
 REQUIRE_HEADER([arpa/inet.h])
 REQUIRE_HEADER([ctype.h])
 REQUIRE_HEADER([dirent.h])
 REQUIRE_HEADER([dlfcn.h])
 REQUIRE_HEADER([errno.h])
 REQUIRE_HEADER([fcntl.h])
 REQUIRE_HEADER([float.h])
 REQUIRE_HEADER([glib.h])
 REQUIRE_HEADER([grp.h])
 REQUIRE_HEADER([inttypes.h])
 REQUIRE_HEADER([libgen.h])
 REQUIRE_HEADER([limits.h])
 REQUIRE_HEADER([locale.h])
 REQUIRE_HEADER([netdb.h])
 REQUIRE_HEADER([netinet/in.h])
 REQUIRE_HEADER([netinet/ip.h], [
     #include <sys/types.h>
     #include <netinet/in.h>
 ])
 REQUIRE_HEADER([netinet/tcp.h])
 REQUIRE_HEADER([pwd.h])
 REQUIRE_HEADER([regex.h])
 REQUIRE_HEADER([sched.h])
 REQUIRE_HEADER([signal.h])
 REQUIRE_HEADER([stdarg.h])
 REQUIRE_HEADER([stdbool.h])
 REQUIRE_HEADER([stdint.h])
 REQUIRE_HEADER([stdio.h])
 REQUIRE_HEADER([stdlib.h])
 REQUIRE_HEADER([string.h])
 REQUIRE_HEADER([strings.h])
 REQUIRE_HEADER([sys/ioctl.h])
 REQUIRE_HEADER([sys/param.h])
 REQUIRE_HEADER([sys/reboot.h])
 REQUIRE_HEADER([sys/resource.h])
 REQUIRE_HEADER([sys/socket.h])
 REQUIRE_HEADER([sys/stat.h])
 REQUIRE_HEADER([sys/time.h])
 REQUIRE_HEADER([sys/types.h])
 REQUIRE_HEADER([sys/uio.h])
 REQUIRE_HEADER([sys/utsname.h])
 REQUIRE_HEADER([sys/wait.h])
 REQUIRE_HEADER([termios.h])
 REQUIRE_HEADER([time.h])
 REQUIRE_HEADER([unistd.h])
 REQUIRE_HEADER([libxml/xpath.h])
 REQUIRE_HEADER([libxslt/xslt.h])
 
 cc_restore_flags
 
 dnl ========================================================================
 dnl Generic declarations
 dnl ========================================================================
 
 AC_CHECK_DECLS([CLOCK_MONOTONIC], [PCMK_FEATURES="$PCMK_FEATURES monotonic"], [], [[
     #include <time.h>
 ]])
 
 dnl ========================================================================
 dnl Unit test declarations
 dnl ========================================================================
 
 AC_CHECK_DECLS([assert_float_equal], [], [], [[
     #include <stdarg.h>
     #include <stddef.h>
     #include <setjmp.h>
     #include <cmocka.h>
 ]])
 
 dnl ========================================================================
 dnl Byte size
 dnl ========================================================================
 
 # Compile-time assert hack
 # https://jonjagger.blogspot.com/2017/07/compile-time-assertions-in-c.html
 AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include <limits.h>]],
                                    [[
                                         switch (0) {
                                             case 0:
                                             case (CHAR_BIT == 8):
                                                 break;
                                         }
                                    ]])],
                   [],
                   [AC_MSG_FAILURE(m4_normalize([Pacemaker is not supported on
                                                 platforms where char is not 8
                                                 bits]))])
 
 dnl ========================================================================
 dnl Structures
 dnl ========================================================================
 
 AC_CHECK_MEMBERS([struct tm.tm_gmtoff],,,[[#include <time.h>]])
 AC_CHECK_MEMBER([struct dirent.d_type],
     AC_DEFINE(HAVE_STRUCT_DIRENT_D_TYPE,1,[Define this if struct dirent has d_type]),,
     [#include <dirent.h>])
 
 dnl ========================================================================
 dnl Functions
 dnl ========================================================================
 
 REQUIRE_FUNC([alphasort])
 REQUIRE_FUNC([getopt])
 REQUIRE_FUNC([scandir])
 REQUIRE_FUNC([setenv])
 REQUIRE_FUNC([strndup])
 REQUIRE_FUNC([strnlen])
 REQUIRE_FUNC([unsetenv])
 REQUIRE_FUNC([uuid_unparse])
 REQUIRE_FUNC([vasprintf])
 
 AC_CHECK_FUNCS([strchrnul])
 
 AC_CHECK_FUNCS([fopen64])
 AM_CONDITIONAL([WRAPPABLE_FOPEN64], [test x"$ac_cv_func_fopen64" = x"yes"])
 
 AC_MSG_CHECKING([whether strerror always returns non-NULL])
 AC_RUN_IFELSE([AC_LANG_PROGRAM([[
     #include <stdio.h>
     #include <string.h>
                                ]], [[
     return strerror(-1) == NULL;
                                ]])],
               [AC_MSG_RESULT([yes])],
               [AC_MSG_ERROR([strerror() is not C99-compliant])],
               [AC_MSG_ERROR([strerror() is not C99-compliant])])
 
 AC_RUN_IFELSE([AC_LANG_PROGRAM([[#include <stdio.h>]], [[
     const char *s = "some-command-line-arg";
     char *name = NULL;
     int n = sscanf(s, "%ms", &name);
     return n != 1;
                                ]])],
               [have_sscanf_m="yes"],
               [have_sscanf_m="no"],
               [have_sscanf_m="no"])
 AS_IF([test x"$have_sscanf_m" = x"yes"],
       [AC_DEFINE([HAVE_SSCANF_M], [1],
                  [Define to 1 if sscanf %m modifier is available])])
 
 dnl ========================================================================
 dnl   bzip2
 dnl ========================================================================
 REQUIRE_HEADER([bzlib.h])
 REQUIRE_LIB([bz2], [BZ2_bzBuffToBuffCompress])
 
 dnl ========================================================================
 dnl sighandler_t is missing from Illumos, Solaris11 systems
 dnl ========================================================================
 
 AC_MSG_CHECKING([for sighandler_t])
 AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include <signal.h>]], [[sighandler_t *f;]])],
                   [
                       AC_MSG_RESULT([yes])
                       AC_DEFINE([HAVE_SIGHANDLER_T], [1],
                                 [Define to 1 if sighandler_t is available])
                   ],
                   [AC_MSG_RESULT([no])])
 
 dnl ========================================================================
 dnl   ncurses
 dnl ========================================================================
 dnl
 dnl A few OSes (e.g. Linux) deliver a default "ncurses" alongside "curses".
 dnl Many non-Linux deliver "curses"; sites may add "ncurses".
 dnl
 dnl However, the source-code recommendation for both is to #include "curses.h"
 dnl (i.e. "ncurses" still wants the include to be simple, no-'n', "curses.h").
 dnl
 dnl ncurses takes precedence.
 dnl
 AC_CHECK_HEADERS([curses.h curses/curses.h ncurses.h ncurses/ncurses.h])
 
 dnl Although n-library is preferred, only look for it if the n-header was found.
 CURSESLIBS=''
 PC_NAME_CURSES=""
 PC_LIBS_CURSES=""
 AS_IF([test x"$ac_cv_header_ncurses_h" = x"yes"], [
     AC_CHECK_LIB(ncurses, printw,
                  [AC_DEFINE(HAVE_LIBNCURSES,1, have ncurses library)])
     CURSESLIBS=`$PKG_CONFIG --libs ncurses` || CURSESLIBS='-lncurses'
     PC_NAME_CURSES="ncurses"
 ])
 
 AS_IF([test x"$ac_cv_header_ncurses_ncurses_h" = x"yes"], [
     AC_CHECK_LIB(ncurses, printw,
                  [AC_DEFINE(HAVE_LIBNCURSES,1, have ncurses library)])
     CURSESLIBS=`$PKG_CONFIG --libs ncurses` || CURSESLIBS='-lncurses'
     PC_NAME_CURSES="ncurses"
 ])
 
 dnl Only look for non-n-library if there was no n-library.
 AS_IF([test x"$CURSESLIBS" = x"" && test x"$ac_cv_header_curses_h" = x"yes"], [
     AC_CHECK_LIB(curses, printw,
                  [CURSESLIBS='-lcurses'; AC_DEFINE(HAVE_LIBCURSES,1, have curses library)])
     PC_LIBS_CURSES="$CURSESLIBS"
 ])
 
 dnl Only look for non-n-library if there was no n-library.
 AS_IF([test x"$CURSESLIBS" = x"" && test x"$ac_cv_header_curses_curses_h" = x"yes"], [
     AC_CHECK_LIB(curses, printw,
                  [CURSESLIBS='-lcurses'; AC_DEFINE(HAVE_LIBCURSES,1, have curses library)])
     PC_LIBS_CURSES="$CURSESLIBS"
 ])
 
 AS_IF([test x"$CURSESLIBS" != x""],
       [PCMK_FEATURES="$PCMK_FEATURES ncurses"])
 
 dnl Check for printw() prototype compatibility
 AS_IF([test x"$CURSESLIBS" != x"" && cc_supports_flag -Wcast-qual], [
     ac_save_LIBS=$LIBS
     LIBS="$CURSESLIBS"
 
     # avoid broken test because of hardened build environment in Fedora 23+
     # - https://fedoraproject.org/wiki/Changes/Harden_All_Packages
     # - https://bugzilla.redhat.com/1297985
     AS_IF([cc_supports_flag -fPIC],
 	  [cc_temp_flags "-Wcast-qual $WERROR -fPIC"],
 	  [cc_temp_flags "-Wcast-qual $WERROR"])
 
     AC_MSG_CHECKING([whether curses library is compatible])
     AC_LINK_IFELSE(
         [AC_LANG_PROGRAM([
 #if defined(HAVE_NCURSES_H)
 #  include <ncurses.h>
 #elif defined(HAVE_NCURSES_NCURSES_H)
 #  include <ncurses/ncurses.h>
 #elif defined(HAVE_CURSES_H)
 #  include <curses.h>
 #endif
                          ],
                          [printw((const char *)"Test");]
         )],
         [AC_MSG_RESULT([yes])],
         [
             AC_MSG_RESULT([no])
             AC_MSG_WARN(m4_normalize([Disabling curses because the printw()
                                       function of your (n)curses library is old.
                                       If you wish to enable curses, update to a
                                       newer version (ncurses 5.4 or later is
                                       recommended, available from
                                       https://invisible-island.net/ncurses/)
                                      ]))
             AC_DEFINE([HAVE_INCOMPATIBLE_PRINTW], [1],
                       [Define to 1 if curses library has incompatible printw()])
         ]
     )
 
     LIBS=$ac_save_LIBS
     cc_restore_flags
 ])
 
 AC_SUBST(CURSESLIBS)
 AC_SUBST(PC_NAME_CURSES)
 AC_SUBST(PC_LIBS_CURSES)
 
 dnl ========================================================================
 dnl    Profiling and GProf
 dnl ========================================================================
 
 CFLAGS_ORIG="$CFLAGS"
 AS_IF([test $with_coverage -ne $DISABLED],
       [
         with_profiling=$REQUIRED
         PCMK_FEATURES="$PCMK_FEATURES coverage"
         CFLAGS="$CFLAGS -fprofile-arcs -ftest-coverage"
         dnl During linking, make sure to specify -lgcov or -coverage
       ]
 )
 
 AS_IF([test $with_profiling -ne $DISABLED],
       [
           with_profiling=$REQUIRED
           PCMK_FEATURES="$PCMK_FEATURES profile"
 
           dnl Disable various compiler optimizations
           CFLAGS="$CFLAGS -fno-omit-frame-pointer -fno-inline -fno-builtin"
           dnl CFLAGS="$CFLAGS -fno-inline-functions"
           dnl CFLAGS="$CFLAGS -fno-default-inline"
           dnl CFLAGS="$CFLAGS -fno-inline-functions-called-once"
           dnl CFLAGS="$CFLAGS -fno-optimize-sibling-calls"
 
           dnl Turn off optimization so tools can get accurate line numbers
           CFLAGS=`echo $CFLAGS | sed \
                   -e 's/-O.\ //g' \
                   -e 's/-Wp,-D_FORTIFY_SOURCE=.\ //g' \
                   -e 's/-D_FORTIFY_SOURCE=.\ //g'`
           CFLAGS="$CFLAGS -O0 -g3 -gdwarf-2"
 
           AC_MSG_NOTICE([CFLAGS before adding profiling options: $CFLAGS_ORIG])
           AC_MSG_NOTICE([CFLAGS after: $CFLAGS])
       ]
 )
 AC_DEFINE_UNQUOTED([SUPPORT_PROFILING], [$with_profiling], [Support profiling])
 AM_CONDITIONAL([BUILD_PROFILING], [test "$with_profiling" = "$REQUIRED"])
 
 dnl ========================================================================
 dnl    Cluster infrastructure - LibQB
 dnl ========================================================================
 
 PKG_CHECK_MODULES(libqb, libqb >= 0.17)
 CPPFLAGS="$libqb_CFLAGS $CPPFLAGS"
 LIBS="$libqb_LIBS $LIBS"
 
 dnl libqb 2.0.5+ (2022-03)
 AC_CHECK_FUNCS([qb_ipcc_connect_async])
 
 dnl libqb 2.0.2+ (2020-10)
 AC_CHECK_FUNCS([qb_ipcc_auth_get])
 
 dnl libqb 2.0.0+ (2020-05)
 CHECK_ENUM_VALUE([qb/qblog.h],[qb_log_conf],[QB_LOG_CONF_MAX_LINE_LEN])
 CHECK_ENUM_VALUE([qb/qblog.h],[qb_log_conf],[QB_LOG_CONF_ELLIPSIS])
 
 dnl Support Linux-HA fence agents if available
 AS_IF([test x"$cross_compiling" != x"yes"],
       [CPPFLAGS="$CPPFLAGS -I${prefix}/include/heartbeat"])
 AC_CHECK_HEADERS([stonith/stonith.h],
                  [
                      AC_CHECK_LIB([pils], [PILLoadPlugin])
                      AC_CHECK_LIB([plumb], [G_main_add_IPC_Channel])
                      PCMK_FEATURES="$PCMK_FEATURES lha"
                  ])
 AM_CONDITIONAL([BUILD_LHA_SUPPORT], [test x"$ac_cv_header_stonith_stonith_h" = x"yes"])
 
 
 dnl ===============================================
 dnl Detect DBus, systemd, and Upstart support
 dnl ===============================================
 
 HAVE_dbus=1
 PKG_CHECK_MODULES([DBUS], [dbus-1],
                   [CPPFLAGS="${CPPFLAGS} ${DBUS_CFLAGS}"],
                   [HAVE_dbus=0])
 AC_DEFINE_UNQUOTED(HAVE_DBUS, $HAVE_dbus, Support dbus)
 AM_CONDITIONAL(BUILD_DBUS, test $HAVE_dbus = 1)
 dnl libdbus 1.5.12+ (2012-03) / 1.6.0+ (2012-06)
 AC_CHECK_TYPES([DBusBasicValue],,,[[#include <dbus/dbus.h>]])
 AS_IF([test $HAVE_dbus = 0],
       [PC_NAME_DBUS=""],
       [PC_NAME_DBUS="dbus-1"])
 AC_SUBST(PC_NAME_DBUS)
 
 check_systemdsystemunitdir() {
     AC_MSG_CHECKING([which system unit file directory to use])
     PKG_CHECK_VAR([systemdsystemunitdir], [systemd], [systemdsystemunitdir])
     AC_MSG_RESULT([${systemdsystemunitdir}])
     test x"$systemdsystemunitdir" != x""
     return $?
 }
 
 AS_CASE([$enable_systemd],
         [$REQUIRED], [
             AS_IF([test $HAVE_dbus = 0],
                   [AC_MSG_FAILURE([Cannot support systemd resources without DBus])])
             AS_IF([test "$ac_cv_have_decl_CLOCK_MONOTONIC" = "no"],
                   [AC_MSG_FAILURE([Cannot support systemd resources without monotonic clock])])
             AS_IF([check_systemdsystemunitdir], [],
                   [AC_MSG_FAILURE([Cannot support systemd resources without systemdsystemunitdir])])
         ],
         [$OPTIONAL], [
             AS_IF([test $HAVE_dbus = 0 \
                    || test x"$ac_cv_have_decl_CLOCK_MONOTONIC" = x"no"],
                   [enable_systemd=$DISABLED],
                   [
                       AC_MSG_CHECKING([for systemd version (using dbus-send)])
                       ret=$({ dbus-send --system --print-reply \
                                   --dest=org.freedesktop.systemd1 \
                                   /org/freedesktop/systemd1 \
                                   org.freedesktop.DBus.Properties.Get \
                                   string:org.freedesktop.systemd1.Manager \
                                   string:Version 2>/dev/null \
                               || echo "version unavailable"; } | tail -n1)
                       # sanitize output a bit (interested just in value, not type),
                       # ret is intentionally unenquoted so as to normalize whitespace
                       ret=$(echo ${ret} | cut -d' ' -f2-)
                       AC_MSG_RESULT([${ret}])
                       AS_IF([test x"$ret" != x"unavailable" \
                              || systemctl --version 2>/dev/null | grep -q systemd],
                             [
                                 AS_IF([check_systemdsystemunitdir],
                                       [enable_systemd=$REQUIRED],
                                       [enable_systemd=$DISABLED])
                             ],
                             [enable_systemd=$DISABLED]
                       )
                   ])
         ],
 )
 AC_MSG_CHECKING([whether to enable support for managing resources via systemd])
 AS_IF([test $enable_systemd -eq $DISABLED], [AC_MSG_RESULT([no])],
       [
           AC_MSG_RESULT([yes])
           PCMK_FEATURES="$PCMK_FEATURES systemd"
       ]
 )
 AC_SUBST([systemdsystemunitdir])
 AC_DEFINE_UNQUOTED([SUPPORT_SYSTEMD], [$enable_systemd],
                    [Support systemd resources])
 AM_CONDITIONAL([BUILD_SYSTEMD], [test $enable_systemd = $REQUIRED])
 AC_SUBST(SUPPORT_SYSTEMD)
 
 AS_CASE([$enable_upstart],
         [$REQUIRED], [
             AS_IF([test $HAVE_dbus = 0],
                   [AC_MSG_FAILURE([Cannot support Upstart resources without DBus])])
         ],
         [$OPTIONAL], [
             AS_IF([test $HAVE_dbus = 0], [enable_upstart=$DISABLED],
                   [
                       AC_MSG_CHECKING([for Upstart version (using dbus-send)])
                       ret=$({ dbus-send --system --print-reply \
                                   --dest=com.ubuntu.Upstart \
                                   /com/ubuntu/Upstart org.freedesktop.DBus.Properties.Get \
                                   string:com.ubuntu.Upstart0_6 string:version 2>/dev/null \
                               || echo "version unavailable"; } | tail -n1)
                       # sanitize output a bit (interested just in value, not type),
                       # ret is intentionally unenquoted so as to normalize whitespace
                       ret=$(echo ${ret} | cut -d' ' -f2-)
                       AC_MSG_RESULT([${ret}])
                       AS_IF([test x"$ret" != x"unavailable" \
                              || initctl --version 2>/dev/null | grep -q upstart],
                             [enable_upstart=$REQUIRED],
                             [enable_upstart=$DISABLED]
                       )
                   ])
         ],
 )
 AC_MSG_CHECKING([whether to enable support for managing resources via Upstart])
 AS_IF([test $enable_upstart -eq $DISABLED], [AC_MSG_RESULT([no])],
       [
           AC_MSG_RESULT([yes])
           PCMK_FEATURES="$PCMK_FEATURES upstart"
       ]
 )
 AC_DEFINE_UNQUOTED([SUPPORT_UPSTART], [$enable_upstart],
                    [Support Upstart resources])
 AM_CONDITIONAL([BUILD_UPSTART], [test $enable_upstart -eq $REQUIRED])
 AC_SUBST(SUPPORT_UPSTART)
 
 
 dnl ========================================================================
 dnl Detect Nagios support
 dnl ========================================================================
 
 AS_CASE([$with_nagios],
         [$REQUIRED], [
             AS_IF([test x"$ac_cv_have_decl_CLOCK_MONOTONIC" = x"no"],
                   [AC_MSG_FAILURE([Cannot support nagios resources without monotonic clock])])
         ],
         [$OPTIONAL], [
             AS_IF([test x"$ac_cv_have_decl_CLOCK_MONOTONIC" = x"no"],
                   [with_nagios=$DISABLED], [with_nagios=$REQUIRED])
         ]
 )
 AS_IF([test $with_nagios -eq $REQUIRED], [PCMK_FEATURES="$PCMK_FEATURES nagios"])
 AC_DEFINE_UNQUOTED([SUPPORT_NAGIOS], [$with_nagios], [Support nagios plugins])
 AM_CONDITIONAL([BUILD_NAGIOS], [test $with_nagios -eq $REQUIRED])
 
 AS_IF([test x"$NAGIOS_PLUGIN_DIR" = x""],
       [NAGIOS_PLUGIN_DIR="${libexecdir}/nagios/plugins"])
 
 AC_DEFINE_UNQUOTED(NAGIOS_PLUGIN_DIR, "$NAGIOS_PLUGIN_DIR", Directory for nagios plugins)
 AC_SUBST(NAGIOS_PLUGIN_DIR)
 
 AS_IF([test x"$NAGIOS_METADATA_DIR" = x""],
       [NAGIOS_METADATA_DIR="${datadir}/nagios/plugins-metadata"])
 
 AC_DEFINE_UNQUOTED(NAGIOS_METADATA_DIR, "$NAGIOS_METADATA_DIR", Directory for nagios plugins metadata)
 AC_SUBST(NAGIOS_METADATA_DIR)
 
 STACKS=""
 CLUSTERLIBS=""
 PC_NAME_CLUSTER=""
 
+dnl ========================================================================
+dnl Detect support for "service" alias
+dnl ========================================================================
+
+PCMK__ENABLE_SERVICE=$DISABLED
+AM_COND_IF([BUILD_LSB], [PCMK__ENABLE_SERVICE=$REQUIRED])
+AM_COND_IF([BUILD_SYSTEMD], [PCMK__ENABLE_SERVICE=$REQUIRED])
+AM_COND_IF([BUILD_UPSTART], [PCMK__ENABLE_SERVICE=$REQUIRED])
+AS_IF([test $PCMK__ENABLE_SERVICE -ne $DISABLED],
+      [PCMK_FEATURES="$PCMK_FEATURES service"])
+AC_SUBST(PCMK__ENABLE_SERVICE)
+AC_DEFINE_UNQUOTED([PCMK__ENABLE_SERVICE], [$PCMK__ENABLE_SERVICE],
+                   [Whether "service" is supported as an agent standard])
+
 dnl ========================================================================
 dnl    Cluster stack - Corosync
 dnl ========================================================================
 
 COROSYNC_LIBS=""
 
 AS_CASE([$with_corosync],
         [$REQUIRED], [
             # These will be fatal if unavailable
             PKG_CHECK_MODULES([cpg], [libcpg])
             PKG_CHECK_MODULES([cfg], [libcfg])
             PKG_CHECK_MODULES([cmap], [libcmap])
             PKG_CHECK_MODULES([quorum], [libquorum])
             PKG_CHECK_MODULES([libcorosync_common], [libcorosync_common])
         ]
         [$OPTIONAL], [
             PKG_CHECK_MODULES([cpg], [libcpg], [], [with_corosync=$DISABLED])
             PKG_CHECK_MODULES([cfg], [libcfg], [], [with_corosync=$DISABLED])
             PKG_CHECK_MODULES([cmap], [libcmap], [], [with_corosync=$DISABLED])
             PKG_CHECK_MODULES([quorum], [libquorum], [], [with_corosync=$DISABLED])
             PKG_CHECK_MODULES([libcorosync_common], [libcorosync_common], [], [with_corosync=$DISABLED])
             AS_IF([test $with_corosync -ne $DISABLED], [with_corosync=$REQUIRED])
         ]
 )
 AS_IF([test $with_corosync -ne $DISABLED],
       [
           AC_MSG_CHECKING([for Corosync 2 or later])
           AC_MSG_RESULT([yes])
           CFLAGS="$CFLAGS $libqb_CFLAGS $cpg_CFLAGS $cfg_CFLAGS $cmap_CFLAGS $quorum_CFLAGS $libcorosync_common_CFLAGS"
           CPPFLAGS="$CPPFLAGS `$PKG_CONFIG --cflags-only-I corosync`"
           COROSYNC_LIBS="$COROSYNC_LIBS $cpg_LIBS $cfg_LIBS $cmap_LIBS $quorum_LIBS $libcorosync_common_LIBS"
           CLUSTERLIBS="$CLUSTERLIBS $COROSYNC_LIBS"
           PC_NAME_CLUSTER="$PC_CLUSTER_NAME libcfg libcmap libcorosync_common libcpg libquorum"
           STACKS="$STACKS corosync-ge-2"
 
           dnl Shutdown tracking added (back) to corosync Jan 2021
           saved_LIBS="$LIBS"
           LIBS="$LIBS $COROSYNC_LIBS"
           AC_CHECK_FUNCS([corosync_cfg_trackstart])
           LIBS="$saved_LIBS"
       ]
 )
 AC_DEFINE_UNQUOTED([SUPPORT_COROSYNC], [$with_corosync],
                    [Support the Corosync messaging and membership layer])
 AM_CONDITIONAL([BUILD_CS_SUPPORT], [test $with_corosync -eq $REQUIRED])
 AC_SUBST([SUPPORT_COROSYNC])
 
 dnl
 dnl    Cluster stack - Sanity
 dnl
 
 AS_IF([test x"$STACKS" != x""], [AC_MSG_NOTICE([Supported stacks:${STACKS}])],
       [AC_MSG_FAILURE([At least one cluster stack must be supported])])
 
 PCMK_FEATURES="${PCMK_FEATURES}${STACKS}"
 
 AC_SUBST(CLUSTERLIBS)
 AC_SUBST(PC_NAME_CLUSTER)
 
 dnl ========================================================================
 dnl    CIB secrets
 dnl ========================================================================
 
 AS_IF([test $with_cibsecrets -ne $DISABLED],
       [
           with_cibsecrets=$REQUIRED
           PCMK_FEATURES="$PCMK_FEATURES cibsecrets"
           LRM_CIBSECRETS_DIR="${localstatedir}/lib/pacemaker/lrm/secrets"
           AC_DEFINE_UNQUOTED([LRM_CIBSECRETS_DIR], ["$LRM_CIBSECRETS_DIR"],
                              [Location for CIB secrets])
           AC_SUBST([LRM_CIBSECRETS_DIR])
       ]
 )
 AC_DEFINE_UNQUOTED([SUPPORT_CIBSECRETS], [$with_cibsecrets], [Support CIB secrets])
 AM_CONDITIONAL([BUILD_CIBSECRETS], [test $with_cibsecrets -eq $REQUIRED])
 
 dnl ========================================================================
 dnl    GnuTLS
 dnl ========================================================================
 
 dnl Require GnuTLS >=2.12.0 (2011-03) for Pacemaker Remote support
 PC_NAME_GNUTLS=""
 AS_CASE([$with_gnutls],
         [$REQUIRED], [
             REQUIRE_LIB([gnutls], [gnutls_sec_param_to_pk_bits])
             REQUIRE_HEADER([gnutls/gnutls.h])
         ],
         [$OPTIONAL], [
             AC_CHECK_LIB([gnutls], [gnutls_sec_param_to_pk_bits],
                          [], [with_gnutls=$DISABLED])
             AC_CHECK_HEADERS([gnutls/gnutls.h], [], [with_gnutls=$DISABLED])
         ]
 )
 AS_IF([test $with_gnutls -ne $DISABLED],
       [
           PC_NAME_GNUTLS="gnutls"
           PCMK_FEATURES="$PCMK_FEATURES remote"
       ]
 )
 AC_SUBST([PC_NAME_GNUTLS])
 AM_CONDITIONAL([BUILD_REMOTE], [test $with_gnutls -ne $DISABLED])
 
 # --- ASAN/UBSAN/TSAN (see man gcc) ---
 # when using SANitizers, we need to pass the -fsanitize..
 # to both CFLAGS and LDFLAGS. The CFLAGS/LDFLAGS must be
 # specified as first in the list or there will be runtime
 # issues (for example user has to LD_PRELOAD asan for it to work
 # properly).
 
 AS_IF([test -n "${SANITIZERS}"], [
   SANITIZERS=$(echo $SANITIZERS | sed -e 's/,/ /g')
   for SANITIZER in $SANITIZERS
   do
     AS_CASE([$SANITIZER],
             [asan|ASAN], [
                 SANITIZERS_CFLAGS="$SANITIZERS_CFLAGS -fsanitize=address"
                 SANITIZERS_LDFLAGS="$SANITIZERS_LDFLAGS -fsanitize=address -lasan"
                 PCMK_FEATURES="$PCMK_FEATURES asan"
                 REQUIRE_LIB([asan],[main])
             ],
             [ubsan|UBSAN], [
                 SANITIZERS_CFLAGS="$SANITIZERS_CFLAGS -fsanitize=undefined"
                 SANITIZERS_LDFLAGS="$SANITIZERS_LDFLAGS -fsanitize=undefined -lubsan"
                 PCMK_FEATURES="$PCMK_FEATURES ubsan"
                 REQUIRE_LIB([ubsan],[main])
             ],
             [tsan|TSAN], [
                 SANITIZERS_CFLAGS="$SANITIZERS_CFLAGS -fsanitize=thread"
                 SANITIZERS_LDFLAGS="$SANITIZERS_LDFLAGS -fsanitize=thread -ltsan"
                 PCMK_FEATURES="$PCMK_FEATURES tsan"
                 REQUIRE_LIB([tsan],[main])
             ])
   done
 ])
 
 
 dnl ========================================================================
 dnl Compiler flags
 dnl ========================================================================
 
 dnl Make sure that CFLAGS is not exported. If the user did
 dnl not have CFLAGS in their environment then this should have
 dnl no effect. However if CFLAGS was exported from the user's
 dnl environment, then the new CFLAGS will also be exported
 dnl to sub processes.
 AS_IF([export | fgrep " CFLAGS=" > /dev/null],
       [
           SAVED_CFLAGS="$CFLAGS"
           unset CFLAGS
           CFLAGS="$SAVED_CFLAGS"
           unset SAVED_CFLAGS
       ])
 
 CC_EXTRAS=""
 
 AS_IF([test x"$GCC" != x"yes"], [CFLAGS="$CFLAGS -g"], [
     CFLAGS="$CFLAGS -ggdb"
 
 dnl When we don't have diagnostic push / pull, we can't explicitly disable
 dnl checking for nonliteral formats in the places where they occur on purpose
 dnl thus we disable nonliteral format checking globally as we are aborting
 dnl on warnings. 
 dnl what makes the things really ugly is that nonliteral format checking is 
 dnl obviously available as an extra switch in very modern gcc but for older
 dnl gcc this is part of -Wformat=2 
 dnl so if we have push/pull we can enable -Wformat=2 -Wformat-nonliteral
 dnl if we don't have push/pull but -Wformat-nonliteral we can enable -Wformat=2
 dnl otherwise none of both
 
     gcc_diagnostic_push_pull=no
     cc_temp_flags "$CFLAGS $WERROR"
     AC_MSG_CHECKING([for gcc diagnostic push / pull])
     AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
 #pragma GCC diagnostic push
 #pragma GCC diagnostic pop
                       ]])],
                       [
                           AC_MSG_RESULT([yes])
                           gcc_diagnostic_push_pull=yes
                       ], AC_MSG_RESULT([no]))
     cc_restore_flags
 
     AS_IF([cc_supports_flag "-Wformat-nonliteral"],
           [gcc_format_nonliteral=yes],
           [gcc_format_nonliteral=no])
         
     # We had to eliminate -Wnested-externs because of libtool changes
     # Make sure to order options so that the former stand for prerequisites
     # of the latter (e.g., -Wformat-nonliteral requires -Wformat).
     EXTRA_FLAGS="-fgnu89-inline"
     EXTRA_FLAGS="$EXTRA_FLAGS -Wall"
     EXTRA_FLAGS="$EXTRA_FLAGS -Waggregate-return"
     EXTRA_FLAGS="$EXTRA_FLAGS -Wbad-function-cast"
     EXTRA_FLAGS="$EXTRA_FLAGS -Wcast-align"
     EXTRA_FLAGS="$EXTRA_FLAGS -Wdeclaration-after-statement"
     EXTRA_FLAGS="$EXTRA_FLAGS -Wendif-labels"
     EXTRA_FLAGS="$EXTRA_FLAGS -Wfloat-equal"
     EXTRA_FLAGS="$EXTRA_FLAGS -Wformat-security"
     EXTRA_FLAGS="$EXTRA_FLAGS -Wimplicit-fallthrough"
     EXTRA_FLAGS="$EXTRA_FLAGS -Wmissing-prototypes"
     EXTRA_FLAGS="$EXTRA_FLAGS -Wmissing-declarations"
     EXTRA_FLAGS="$EXTRA_FLAGS -Wnested-externs"
     EXTRA_FLAGS="$EXTRA_FLAGS -Wno-long-long"
     EXTRA_FLAGS="$EXTRA_FLAGS -Wno-strict-aliasing"
     EXTRA_FLAGS="$EXTRA_FLAGS -Wpointer-arith"
     EXTRA_FLAGS="$EXTRA_FLAGS -Wstrict-prototypes"
     EXTRA_FLAGS="$EXTRA_FLAGS -Wwrite-strings"
     EXTRA_FLAGS="$EXTRA_FLAGS -Wunused-but-set-variable"
     EXTRA_FLAGS="$EXTRA_FLAGS -Wunsigned-char"
 
     AS_IF([test x"$gcc_diagnostic_push_pull" = x"yes"],
           [
               AC_DEFINE([HAVE_FORMAT_NONLITERAL], [],
                         [gcc can complain about nonliterals in format])
               EXTRA_FLAGS="$EXTRA_FLAGS -Wformat=2 -Wformat-nonliteral"
           ],
           [test x"$gcc_format_nonliteral" = x"yes"],
           [EXTRA_FLAGS="$EXTRA_FLAGS -Wformat=2"])
 
 # Additional warnings it might be nice to enable one day
 #                -Wshadow
 #                -Wunreachable-code
     for j in $EXTRA_FLAGS
     do
         AS_IF([cc_supports_flag $CC_EXTRAS $j], [CC_EXTRAS="$CC_EXTRAS $j"])
     done
 
     AC_MSG_NOTICE([Using additional gcc flags: ${CC_EXTRAS}])
 ])
 
 dnl
 dnl Hardening flags
 dnl
 dnl The prime control of whether to apply (targeted) hardening build flags and
 dnl which ones is --{enable,disable}-hardening option passed to ./configure:
 dnl
 dnl --enable-hardening=try (default):
 dnl     depending on whether any of CFLAGS_HARDENED_EXE, LDFLAGS_HARDENED_EXE,
 dnl     CFLAGS_HARDENED_LIB or LDFLAGS_HARDENED_LIB environment variables
 dnl     (see below) is set and non-null, all these custom flags (even if not
 dnl     set) are used as are, otherwise the best effort is made to offer
 dnl     reasonably strong hardening in several categories (RELRO, PIE,
 dnl     "bind now", stack protector) according to what the selected toolchain
 dnl     can offer
 dnl
 dnl --enable-hardening:
 dnl     same effect as --enable-hardening=try when the environment variables
 dnl     in question are suppressed
 dnl
 dnl --disable-hardening:
 dnl     do not apply any targeted hardening measures at all
 dnl
 dnl The user-injected environment variables that regulate the hardening in
 dnl default case are as follows:
 dnl
 dnl * CFLAGS_HARDENED_EXE, LDFLAGS_HARDENED_EXE
 dnl    compiler and linker flags (respectively) for daemon programs
 dnl    (pacemakerd, pacemaker-attrd, pacemaker-controld, pacemaker-execd,
 dnl    pacemaker-based, pacemaker-fenced, pacemaker-remoted,
 dnl    pacemaker-schedulerd)
 dnl
 dnl * CFLAGS_HARDENED_LIB, LDFLAGS_HARDENED_LIB
 dnl    compiler and linker flags (respectively) for libraries linked
 dnl    with the daemon programs
 dnl
 dnl Note that these are purposedly targeted variables (addressing particular
 dnl targets all over the scattered Makefiles) and have no effect outside of
 dnl the predestined scope (e.g., CLI utilities).  For a global reach,
 dnl use CFLAGS, LDFLAGS, etc. as usual.
 dnl
 dnl For guidance on the suitable flags consult, for instance:
 dnl https://fedoraproject.org/wiki/Changes/Harden_All_Packages#Detailed_Harden_Flags_Description
 dnl https://owasp.org/index.php/C-Based_Toolchain_Hardening#GCC.2FBinutils
 dnl
 
 AS_IF([test $enable_hardening -eq $OPTIONAL],
       [
           AS_IF([test "$(env | grep -Ec '^(C|LD)FLAGS_HARDENED_(EXE|LIB)=.')" = 0],
                 [enable_hardening=$REQUIRED],
                 [AC_MSG_NOTICE([Hardening: using custom flags from environment])]
           )
       ],
       [
           unset CFLAGS_HARDENED_EXE
           unset CFLAGS_HARDENED_LIB
           unset LDFLAGS_HARDENED_EXE
           unset LDFLAGS_HARDENED_LIB
       ]
 )
 AS_CASE([$enable_hardening],
         [$DISABLED], [AC_MSG_NOTICE([Hardening: explicitly disabled])],
         [$REQUIRED], [
             CFLAGS_HARDENED_EXE=
             CFLAGS_HARDENED_LIB=
             LDFLAGS_HARDENED_EXE=
             LDFLAGS_HARDENED_LIB=
             relro=0
             pie=0
             bindnow=0
             stackprot="none"
             # daemons incl. libs: partial RELRO
             flag="-Wl,-z,relro"
             CC_CHECK_LDFLAGS(["${flag}"],
                              [
                                  LDFLAGS_HARDENED_EXE="${LDFLAGS_HARDENED_EXE} ${flag}"
                                  LDFLAGS_HARDENED_LIB="${LDFLAGS_HARDENED_LIB} ${flag}"
                                  relro=1
                              ])
             # daemons: PIE for both CFLAGS and LDFLAGS
             AS_IF([cc_supports_flag -fPIE],
                   [
                       flag="-pie"
                       CC_CHECK_LDFLAGS(["${flag}"],
                                        [
                                            CFLAGS_HARDENED_EXE="${CFLAGS_HARDENED_EXE} -fPIE"
                                            LDFLAGS_HARDENED_EXE="${LDFLAGS_HARDENED_EXE} ${flag}"
                                            pie=1
                                        ])
                   ]
             )
             # daemons incl. libs: full RELRO if sensible + as-needed linking
             #                     so as to possibly mitigate startup performance
             #                     hit caused by excessive linking with unneeded
             #                     libraries
             AS_IF([test "${relro}" = 1 && test "${pie}" = 1],
                   [
                       flag="-Wl,-z,now"
                       CC_CHECK_LDFLAGS(["${flag}"],
                                        [
                                            LDFLAGS_HARDENED_EXE="${LDFLAGS_HARDENED_EXE} ${flag}"
                                            LDFLAGS_HARDENED_LIB="${LDFLAGS_HARDENED_LIB} ${flag}"
                                            bindnow=1
                                        ])
                   ]
             )
             AS_IF([test "${bindnow}" = 1],
                   [
                       flag="-Wl,--as-needed"
                       CC_CHECK_LDFLAGS(["${flag}"],
                                        [
                                            LDFLAGS_HARDENED_EXE="${LDFLAGS_HARDENED_EXE} ${flag}"
                                            LDFLAGS_HARDENED_LIB="${LDFLAGS_HARDENED_LIB} ${flag}"
                                        ])
                   ])
             # universal: prefer strong > all > default stack protector if possible
             flag=
             AS_IF([cc_supports_flag -fstack-protector-strong],
                   [
                       flag="-fstack-protector-strong"
                       stackprot="strong"
                   ],
                   [cc_supports_flag -fstack-protector-all],
                   [
                       flag="-fstack-protector-all"
                       stackprot="all"
                   ],
                   [cc_supports_flag -fstack-protector],
                   [
                       flag="-fstack-protector"
                       stackprot="default"
                   ]
             )
             AS_IF([test -n "${flag}"], [CC_EXTRAS="${CC_EXTRAS} ${flag}"])
             # universal: enable stack clash protection if possible
             AS_IF([cc_supports_flag -fstack-clash-protection],
                   [
                       CC_EXTRAS="${CC_EXTRAS} -fstack-clash-protection"
                       AS_IF([test "${stackprot}" = "none"],
                             [stackprot="clash-only"],
                             [stackprot="${stackprot}+clash"]
                       )
                   ]
             )
             # Log a summary
             AS_IF([test "${relro}" = 1 || test "${pie}" = 1 || test x"${stackprot}" != x"none"],
                   [AC_MSG_NOTICE(m4_normalize([Hardening:
                         relro=${relro}
                         pie=${pie}
                         bindnow=${bindnow}
                         stackprot=${stackprot}]))
                   ],
                   [AC_MSG_WARN([Hardening: no suitable features in the toolchain detected])]
             )
         ],
 )
 
 CFLAGS="$SANITIZERS_CFLAGS $CFLAGS $CC_EXTRAS"
 LDFLAGS="$SANITIZERS_LDFLAGS $LDFLAGS"
 CFLAGS_HARDENED_EXE="$SANITIZERS_CFLAGS $CFLAGS_HARDENED_EXE"
 LDFLAGS_HARDENED_EXE="$SANITIZERS_LDFLAGS $LDFLAGS_HARDENED_EXE"
 
 NON_FATAL_CFLAGS="$CFLAGS"
 AC_SUBST(NON_FATAL_CFLAGS)
 
 dnl
 dnl We reset CFLAGS to include our warnings *after* all function
 dnl checking goes on, so that our warning flags don't keep the
 dnl AC_*FUNCS() calls above from working.  In particular, -Werror will
 dnl *always* cause us troubles if we set it before here.
 dnl
 dnl
 AS_IF([test $enable_fatal_warnings -ne $DISABLED], [
     AC_MSG_NOTICE([Enabling fatal compiler warnings])
     CFLAGS="$CFLAGS $WERROR"
 ])
 AC_SUBST(CFLAGS)
 
 dnl This is useful for use in Makefiles that need to remove one specific flag
 CFLAGS_COPY="$CFLAGS"
 AC_SUBST(CFLAGS_COPY)
 
 AC_SUBST(LIBADD_DL)        dnl extra flags for dynamic linking libraries
 
 AC_SUBST(LOCALE)
 
 dnl Options for cleaning up the compiler output
 AS_IF([test $enable_quiet -ne $DISABLED],
       [
           AC_MSG_NOTICE([Suppressing make details])
           QUIET_LIBTOOL_OPTS="--silent"
           QUIET_MAKE_OPTS="-s"  # POSIX compliant
       ],
       [
           QUIET_LIBTOOL_OPTS=""
           QUIET_MAKE_OPTS=""
       ]
 )
 
 dnl Put the above variables to use
 LIBTOOL="${LIBTOOL} --tag=CC \$(QUIET_LIBTOOL_OPTS)"
 MAKEFLAGS="${MAKEFLAGS} ${QUIET_MAKE_OPTS}"
 
 # Make features list available (sorted alphabetically, without leading space)
 PCMK_FEATURES=`echo "$PCMK_FEATURES" | sed -e 's/^ //' -e 's/ /\n/g' | sort | xargs`
 AC_DEFINE_UNQUOTED(CRM_FEATURES, "$PCMK_FEATURES", Set of enabled features)
 AC_SUBST(PCMK_FEATURES)
 
 AC_SUBST(CC)
 AC_SUBST(MAKEFLAGS)
 AC_SUBST(LIBTOOL)
 AC_SUBST(QUIET_LIBTOOL_OPTS)
 
 dnl Files we output that need to be executable
 CONFIG_FILES_EXEC([agents/ocf/ClusterMon],
                   [agents/ocf/Dummy],
                   [agents/ocf/HealthCPU],
                   [agents/ocf/HealthIOWait],
                   [agents/ocf/HealthSMART],
                   [agents/ocf/Stateful],
                   [agents/ocf/SysInfo],
                   [agents/ocf/attribute],
                   [agents/ocf/controld],
                   [agents/ocf/ifspeed],
                   [agents/ocf/o2cb],
                   [agents/ocf/ping],
                   [agents/ocf/remote],
                   [agents/stonith/fence_legacy],
                   [agents/stonith/fence_watchdog],
                   [cts/cluster_test],
                   [cts/cts],
                   [cts/cts-attrd],
                   [cts/cts-cli],
                   [cts/cts-exec],
                   [cts/cts-fencing],
                   [cts/cts-lab],
                   [cts/cts-log-watcher],
                   [cts/cts-regression],
                   [cts/cts-scheduler],
                   [cts/benchmark/clubench],
                   [cts/support/LSBDummy],
                   [cts/support/cts-support],
                   [cts/support/fence_dummy],
                   [cts/support/pacemaker-cts-dummyd],
                   [doc/abi-check],
                   [maint/bumplibs],
                   [tools/cluster-clean],
                   [tools/cluster-helper],
                   [tools/crm_failcount],
                   [tools/crm_master],
                   [tools/crm_report],
                   [tools/crm_standby],
                   [tools/cibsecret],
                   [tools/pcmk_simtimes],
                   [xml/rng-helper])
 
 dnl Other files we output
 AC_CONFIG_FILES(Makefile                                            \
                 agents/Makefile                                     \
                 agents/alerts/Makefile                              \
                 agents/ocf/Makefile                                 \
                 agents/stonith/Makefile                             \
                 cts/Makefile                                        \
                 cts/benchmark/Makefile                              \
                 cts/scheduler/Makefile                              \
                 cts/scheduler/dot/Makefile                          \
                 cts/scheduler/exp/Makefile                          \
                 cts/scheduler/scores/Makefile                       \
                 cts/scheduler/stderr/Makefile                       \
                 cts/scheduler/summary/Makefile                      \
                 cts/scheduler/xml/Makefile                          \
                 cts/support/Makefile                                \
                 cts/support/pacemaker-cts-dummyd@.service           \
                 daemons/Makefile                                    \
                 daemons/attrd/Makefile                              \
                 daemons/based/Makefile                              \
                 daemons/controld/Makefile                           \
                 daemons/execd/Makefile                              \
                 daemons/execd/pacemaker_remote                      \
                 daemons/execd/pacemaker_remote.service              \
                 daemons/fenced/Makefile                             \
                 daemons/pacemakerd/Makefile                         \
                 daemons/pacemakerd/pacemaker.combined.upstart       \
                 daemons/pacemakerd/pacemaker.service                \
                 daemons/pacemakerd/pacemaker.upstart                \
                 daemons/schedulerd/Makefile                         \
                 devel/Makefile                                      \
                 doc/Doxyfile                                        \
                 doc/Makefile                                        \
                 doc/sphinx/Makefile                                 \
                 etc/Makefile                                        \
                 etc/init.d/pacemaker                                \
                 etc/logrotate.d/pacemaker                           \
                 etc/sysconfig/pacemaker                             \
                 include/Makefile                                    \
                 include/crm/Makefile                                \
                 include/crm/cib/Makefile                            \
                 include/crm/common/Makefile                         \
                 include/crm/cluster/Makefile                        \
                 include/crm/fencing/Makefile                        \
                 include/crm/pengine/Makefile                        \
                 include/pcmki/Makefile                              \
                 lib/Makefile                                        \
                 lib/cib/Makefile                                    \
                 lib/cluster/Makefile                                \
                 lib/cluster/tests/Makefile                          \
                 lib/cluster/tests/cluster/Makefile                  \
                 lib/cluster/tests/cpg/Makefile                      \
                 lib/common/Makefile                                 \
                 lib/common/tests/Makefile                           \
                 lib/common/tests/acl/Makefile                       \
                 lib/common/tests/actions/Makefile                   \
                 lib/common/tests/agents/Makefile                    \
                 lib/common/tests/cmdline/Makefile                   \
                 lib/common/tests/flags/Makefile                     \
                 lib/common/tests/health/Makefile                    \
                 lib/common/tests/io/Makefile                        \
                 lib/common/tests/iso8601/Makefile                   \
                 lib/common/tests/lists/Makefile                     \
                 lib/common/tests/nodes/Makefile                     \
                 lib/common/tests/nvpair/Makefile                    \
                 lib/common/tests/options/Makefile                   \
                 lib/common/tests/output/Makefile                    \
                 lib/common/tests/probes/Makefile                    \
                 lib/common/tests/procfs/Makefile                    \
                 lib/common/tests/resources/Makefile                 \
                 lib/common/tests/results/Makefile                   \
                 lib/common/tests/rules/Makefile                     \
                 lib/common/tests/scheduler/Makefile                 \
                 lib/common/tests/schemas/Makefile                   \
                 lib/common/tests/scores/Makefile                    \
                 lib/common/tests/strings/Makefile                   \
                 lib/common/tests/utils/Makefile                     \
                 lib/common/tests/xml/Makefile                       \
                 lib/common/tests/xpath/Makefile                     \
                 lib/fencing/Makefile                                \
                 lib/gnu/Makefile                                    \
                 lib/libpacemaker.pc                                 \
                 lib/lrmd/Makefile                                   \
                 lib/pacemaker/Makefile                              \
                 lib/pacemaker/tests/Makefile                        \
                 lib/pacemaker/tests/pcmk_resource/Makefile          \
                 lib/pacemaker/tests/pcmk_ticket/Makefile            \
                 lib/pacemaker.pc                                    \
                 lib/pacemaker-cib.pc                                \
                 lib/pacemaker-cluster.pc                            \
                 lib/pacemaker-fencing.pc                            \
                 lib/pacemaker-lrmd.pc                               \
                 lib/pacemaker-service.pc                            \
                 lib/pacemaker-pe_rules.pc                           \
                 lib/pacemaker-pe_status.pc                          \
                 lib/pengine/Makefile                                \
                 lib/pengine/tests/Makefile                          \
                 lib/pengine/tests/native/Makefile                   \
                 lib/pengine/tests/status/Makefile                   \
                 lib/pengine/tests/unpack/Makefile                   \
                 lib/pengine/tests/utils/Makefile                    \
                 lib/services/Makefile                               \
                 maint/Makefile                                      \
                 po/Makefile.in                                      \
                 python/Makefile                                     \
                 python/setup.py                                     \
                 python/pacemaker/Makefile                           \
                 python/pacemaker/_cts/Makefile                      \
                 python/pacemaker/_cts/tests/Makefile                \
                 python/pacemaker/buildoptions.py                    \
                 python/tests/Makefile                               \
                 rpm/Makefile                                        \
                 tests/Makefile                                      \
                 tools/Makefile                                      \
                 tools/crm_mon.service                               \
                 tools/crm_mon.upstart                               \
                 tools/report.collector                              \
                 tools/report.common                                 \
                 xml/Makefile                                        \
                 xml/pacemaker-schemas.pc                            \
 )
 
 dnl Now process the entire list of files added by previous
 dnl  calls to AC_CONFIG_FILES()
 AC_OUTPUT()
 
 dnl *****************
 dnl Configure summary
 dnl *****************
 
 AC_MSG_NOTICE([])
 AC_MSG_NOTICE([$PACKAGE configuration:])
 AC_MSG_NOTICE([  Version                  = ${VERSION} (Build: $BUILD_VERSION)])
 AC_MSG_NOTICE([  Features                 = ${PCMK_FEATURES}])
 AC_MSG_NOTICE([])
 AC_MSG_NOTICE([  Prefix                   = ${prefix}])
 AC_MSG_NOTICE([  Executables              = ${sbindir}])
 AC_MSG_NOTICE([  Man pages                = ${mandir}])
 AC_MSG_NOTICE([  Libraries                = ${libdir}])
 AC_MSG_NOTICE([  Header files             = ${includedir}])
 AC_MSG_NOTICE([  Arch-independent files   = ${datadir}])
 AC_MSG_NOTICE([  State information        = ${localstatedir}])
 AC_MSG_NOTICE([  System configuration     = ${sysconfdir}])
 AC_MSG_NOTICE([  OCF agents               = ${OCF_ROOT_DIR}])
+AM_COND_IF([BUILD_LSB],
+           [AC_MSG_NOTICE([  LSB agents               = ${INITDIR}])])
 AC_MSG_NOTICE([])
 AC_MSG_NOTICE([  HA group name            = ${CRM_DAEMON_GROUP}])
 AC_MSG_NOTICE([  HA user name             = ${CRM_DAEMON_USER}])
 AC_MSG_NOTICE([])
 AC_MSG_NOTICE([  CFLAGS                   = ${CFLAGS}])
 AC_MSG_NOTICE([  CFLAGS_HARDENED_EXE      = ${CFLAGS_HARDENED_EXE}])
 AC_MSG_NOTICE([  CFLAGS_HARDENED_LIB      = ${CFLAGS_HARDENED_LIB}])
 AC_MSG_NOTICE([  LDFLAGS_HARDENED_EXE     = ${LDFLAGS_HARDENED_EXE}])
 AC_MSG_NOTICE([  LDFLAGS_HARDENED_LIB     = ${LDFLAGS_HARDENED_LIB}])
 AC_MSG_NOTICE([  Libraries                = ${LIBS}])
 AC_MSG_NOTICE([  Stack Libraries          = ${CLUSTERLIBS}])
 AC_MSG_NOTICE([  Unix socket auth method  = ${us_auth}])
diff --git a/cts/cts-exec.in b/cts/cts-exec.in
index a89eb0ed53..8cc7b89225 100644
--- a/cts/cts-exec.in
+++ b/cts/cts-exec.in
@@ -1,963 +1,969 @@
 #!@PYTHON@
 """ Regression tests for Pacemaker's pacemaker-execd
 """
 
 __copyright__ = "Copyright 2012-2023 the Pacemaker project contributors"
 __license__ = "GNU General Public License version 2 or later (GPLv2+) WITHOUT ANY WARRANTY"
 
 import argparse
 import io
 import os
 import psutil
 import stat
 import sys
 import subprocess
 import shutil
 import tempfile
 import time
 
 # Where to find test binaries
 # Prefer the source tree if available
 TEST_DIR = sys.path[0]
 
 # These imports allow running from a source checkout after running `make`.
 # Note that while this doesn't necessarily mean it will successfully run tests,
 # but being able to see --help output can be useful.
 if os.path.exists("@abs_top_srcdir@/python"):
     sys.path.insert(0, "@abs_top_srcdir@/python")
 
 if os.path.exists("@abs_top_builddir@/python") and "@abs_top_builddir@" != "@abs_top_srcdir@":
     sys.path.insert(0, "@abs_top_builddir@/python")
 
 from pacemaker.buildoptions import BuildOptions
 from pacemaker.exitstatus import ExitStatus
 from pacemaker._cts.errors import ExitCodeError, OutputFoundError, OutputNotFoundError
 from pacemaker._cts.process import killall, exit_if_proc_running, pipe_communicate, stdout_from_command
 from pacemaker._cts.test import Test, Tests
 
 # File permissions for executable scripts we create
 EXECMODE = stat.S_IRUSR | stat.S_IXUSR | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH
 
 def update_path():
     """ Set the PATH environment variable appropriately for the tests """
 
     new_path = os.environ['PATH']
     if os.path.exists("%s/cts-exec.in" % TEST_DIR):
         print("Running tests from the source tree: %s (%s)" % (BuildOptions._BUILD_DIR, TEST_DIR))
         # For pacemaker-execd, cts-exec-helper, and pacemaker-remoted
         new_path = "%s/daemons/execd:%s" % (BuildOptions._BUILD_DIR, new_path)
         new_path = "%s/tools:%s" % (BuildOptions._BUILD_DIR, new_path)   # For crm_resource
         # For pacemaker-fenced
         new_path = "%s/daemons/fenced:%s" % (BuildOptions._BUILD_DIR, new_path)
         # For cts-support
         new_path = "%s/cts/support:%s" % (BuildOptions._BUILD_DIR, new_path)
 
     else:
         print("Running tests from the install tree: %s (not %s)" % (BuildOptions.DAEMON_DIR, TEST_DIR))
         # For cts-exec-helper, cts-support, pacemaker-execd, pacemaker-fenced,
         # and pacemaker-remoted
         new_path = "%s:%s" % (BuildOptions.DAEMON_DIR, new_path)
 
     print('Using PATH="%s"' % new_path)
     os.environ['PATH'] = new_path
 
 
 class ExecTest(Test):
     """ Executor for a single pacemaker-execd regression test """
 
     def __init__(self, name, description, **kwargs):
         Test.__init__(self, name, description, **kwargs)
 
         self.tls = kwargs.get("tls", False)
 
         if self.tls:
             self._daemon_location = "pacemaker-remoted"
         else:
             self._daemon_location = "pacemaker-execd"
 
         self._test_tool_location = "cts-exec-helper"
 
         # We additionally need to keep track of a stonith process.
         self._stonith_process = None
 
     def _new_cmd(self, cmd, args, exitcode, **kwargs):
         """ Add a command to be executed as part of this test """
 
         if self.verbose and cmd == self._test_tool_location:
             args += " -V "
 
         if (cmd == self._test_tool_location) and self.tls:
             args += " -S "
 
         kwargs["validate"] = False
         kwargs["check_rng"] = False
         kwargs["check_stderr"] = False
 
         Test._new_cmd(self, cmd, args, exitcode, **kwargs)
 
     def _kill_daemons(self):
         killall([
             "pacemaker-fenced",
             "lt-pacemaker-fenced",
             "pacemaker-execd",
             "lt-pacemaker-execd",
             "cts-exec-helper",
             "lt-cts-exec-helper",
             "pacemaker-remoted",
         ])
 
     def _start_daemons(self):
         if not self.tls:
             self._stonith_process = subprocess.Popen(["pacemaker-fenced", "-s"])
 
         cmd = [self._daemon_location, "-l", self.logpath]
         if self.verbose:
             cmd += ["-V"]
 
         self._daemon_process = subprocess.Popen(cmd)
 
     def clean_environment(self):
         """ Clean up the host after running a test """
 
         if self._daemon_process:
             self._daemon_process.terminate()
             self._daemon_process.wait()
 
             if self.verbose:
                 print("Daemon Output Start")
                 logfile = io.open(self.logpath, 'rt', errors='replace')
                 for line in logfile:
                     print(line.strip().encode('utf-8', 'replace'))
                 print("Daemon Output End")
 
         if self._stonith_process:
             self._stonith_process.terminate()
             self._stonith_process.wait()
 
         self._daemon_process = None
         self._stonith_process = None
 
     def add_cmd(self, args):
         """ Add a cts-exec-helper command to be executed as part of this test """
 
         self._new_cmd(self._test_tool_location, args, ExitStatus.OK)
 
     def add_cmd_and_kill(self, args, kill_proc):
         """ Add a cts-exec-helper command and system command to be executed as part of this test """
 
         self._new_cmd(self._test_tool_location, args, ExitStatus.OK, kill=kill_proc)
 
     def add_cmd_check_stdout(self, args, match, no_match=None):
         """ Add a command with expected output to be executed as part of this test """
 
         self._new_cmd(self._test_tool_location, args, ExitStatus.OK,
                       stdout_match=match, stdout_negative_match=no_match)
 
     def add_cmd_expected_fail(self, args, exitcode=ExitStatus.ERROR):
         """ Add a cts-exec-helper command to be executed as part of this test and expected to fail """
 
         self._new_cmd(self._test_tool_location, args, exitcode)
 
     def add_sys_cmd(self, cmd, args):
         """ Add a simple command to be executed as part of this test """
 
         self._new_cmd(cmd, args, ExitStatus.OK)
 
     def run(self):
         """ Execute this test. """
 
         if self.tls and self.name.count("stonith") != 0:
             self._result_txt = "SKIPPED - '%s' - disabled when testing pacemaker_remote" % (self.name)
             print(self._result_txt)
             return
 
         Test.run(self)
 
 class ExecTests(Tests):
     """ Collection of all pacemaker-execd regression tests """
 
     def __init__(self, **kwargs):
         Tests.__init__(self, **kwargs)
 
         self.tls = kwargs.get("tls", False)
 
         self._action_timeout = " -t 9000 "
         self._installed_files = []
         self._rsc_classes = self._setup_rsc_classes()
 
         print("Testing resource classes", repr(self._rsc_classes))
 
+        if "lsb" in self._rsc_classes:
+            service_agent = "LSBDummy"
+        elif "systemd" in self._rsc_classes:
+            service_agent = "pacemaker-cts-dummyd@3"
+        else:
+            service_agent = "unsupported"
+
         self._common_cmds = {
             "ocf_reg_line"      : "-c register_rsc -r ocf_test_rsc "+self._action_timeout+" -C ocf -P pacemaker -T Dummy",
             "ocf_reg_event"     : "-l \"NEW_EVENT event_type:register rsc_id:ocf_test_rsc action:none rc:ok op_status:complete\"",
             "ocf_unreg_line"    : "-c unregister_rsc -r \"ocf_test_rsc\" "+self._action_timeout,
             "ocf_unreg_event"   : "-l \"NEW_EVENT event_type:unregister rsc_id:ocf_test_rsc action:none rc:ok op_status:complete\"",
             "ocf_start_line"    : "-c exec -r \"ocf_test_rsc\" -a \"start\" "+self._action_timeout,
             "ocf_start_event"   : "-l \"NEW_EVENT event_type:exec_complete rsc_id:ocf_test_rsc action:start rc:ok op_status:complete\" ",
             "ocf_stop_line"     : "-c exec -r \"ocf_test_rsc\" -a \"stop\" "+self._action_timeout,
             "ocf_stop_event"    : "-l \"NEW_EVENT event_type:exec_complete rsc_id:ocf_test_rsc action:stop rc:ok op_status:complete\" ",
             "ocf_monitor_line"  : '-c exec -r ocf_test_rsc -a monitor -i 2s ' + self._action_timeout,
             "ocf_monitor_event" : "-l \"NEW_EVENT event_type:exec_complete rsc_id:ocf_test_rsc action:monitor rc:ok op_status:complete\" "+self._action_timeout,
             "ocf_cancel_line"   : '-c cancel -r ocf_test_rsc -a monitor -i 2s ' + self._action_timeout,
             "ocf_cancel_event"  : "-l \"NEW_EVENT event_type:exec_complete rsc_id:ocf_test_rsc action:monitor rc:ok op_status:Cancelled\" ",
 
             "systemd_reg_line"      : "-c register_rsc -r systemd_test_rsc " +
                                       self._action_timeout +
                                       " -C systemd -T pacemaker-cts-dummyd@3",
             "systemd_reg_event"     : "-l \"NEW_EVENT event_type:register rsc_id:systemd_test_rsc action:none rc:ok op_status:complete\"",
             "systemd_unreg_line"    : "-c unregister_rsc -r \"systemd_test_rsc\" "+self._action_timeout,
             "systemd_unreg_event"   : "-l \"NEW_EVENT event_type:unregister rsc_id:systemd_test_rsc action:none rc:ok op_status:complete\"",
             "systemd_start_line"    : "-c exec -r \"systemd_test_rsc\" -a \"start\" "+self._action_timeout,
             "systemd_start_event"   : "-l \"NEW_EVENT event_type:exec_complete rsc_id:systemd_test_rsc action:start rc:ok op_status:complete\" ",
             "systemd_stop_line"     : "-c exec -r \"systemd_test_rsc\" -a \"stop\" "+self._action_timeout,
             "systemd_stop_event"    : "-l \"NEW_EVENT event_type:exec_complete rsc_id:systemd_test_rsc action:stop rc:ok op_status:complete\" ",
             "systemd_monitor_line"  : '-c exec -r systemd_test_rsc -a monitor -i 2s ' + self._action_timeout,
             "systemd_monitor_event" : "-l \"NEW_EVENT event_type:exec_complete rsc_id:systemd_test_rsc action:monitor rc:ok op_status:complete\" -t 15000 ",
             "systemd_cancel_line"   : '-c cancel -r systemd_test_rsc -a monitor -i 2s ' + self._action_timeout,
             "systemd_cancel_event"  : "-l \"NEW_EVENT event_type:exec_complete rsc_id:systemd_test_rsc action:monitor rc:ok op_status:Cancelled\" ",
 
             "upstart_reg_line"      : "-c register_rsc -r upstart_test_rsc "+self._action_timeout+" -C upstart -T pacemaker-cts-dummyd",
             "upstart_reg_event"     : "-l \"NEW_EVENT event_type:register rsc_id:upstart_test_rsc action:none rc:ok op_status:complete\"",
             "upstart_unreg_line"    : "-c unregister_rsc -r \"upstart_test_rsc\" "+self._action_timeout,
             "upstart_unreg_event"   : "-l \"NEW_EVENT event_type:unregister rsc_id:upstart_test_rsc action:none rc:ok op_status:complete\"",
             "upstart_start_line"    : "-c exec -r \"upstart_test_rsc\" -a \"start\" "+self._action_timeout,
             "upstart_start_event"   : "-l \"NEW_EVENT event_type:exec_complete rsc_id:upstart_test_rsc action:start rc:ok op_status:complete\" ",
             "upstart_stop_line"     : "-c exec -r \"upstart_test_rsc\" -a \"stop\" "+self._action_timeout,
             "upstart_stop_event"    : "-l \"NEW_EVENT event_type:exec_complete rsc_id:upstart_test_rsc action:stop rc:ok op_status:complete\" ",
             "upstart_monitor_line"  : '-c exec -r upstart_test_rsc -a monitor -i 2s ' + self._action_timeout,
             "upstart_monitor_event" : '-l "NEW_EVENT event_type:exec_complete rsc_id:upstart_test_rsc action:monitor rc:ok op_status:complete" -t 15000',
             "upstart_cancel_line"   : '-c cancel -r upstart_test_rsc -a monitor -i 2s ' + self._action_timeout,
             "upstart_cancel_event"  : "-l \"NEW_EVENT event_type:exec_complete rsc_id:upstart_test_rsc action:monitor rc:ok op_status:Cancelled\" ",
 
-            "service_reg_line"      : "-c register_rsc -r service_test_rsc "+self._action_timeout+" -C service -T LSBDummy",
+            "service_reg_line": '-c register_rsc -r service_test_rsc ' + self._action_timeout
+                                + ' -C service -T %s' % service_agent,
             "service_reg_event"     : "-l \"NEW_EVENT event_type:register rsc_id:service_test_rsc action:none rc:ok op_status:complete\"",
             "service_unreg_line"    : "-c unregister_rsc -r \"service_test_rsc\" "+self._action_timeout,
             "service_unreg_event"   : "-l \"NEW_EVENT event_type:unregister rsc_id:service_test_rsc action:none rc:ok op_status:complete\"",
             "service_start_line"    : "-c exec -r \"service_test_rsc\" -a \"start\" "+self._action_timeout,
             "service_start_event"   : "-l \"NEW_EVENT event_type:exec_complete rsc_id:service_test_rsc action:start rc:ok op_status:complete\" ",
             "service_stop_line"     : "-c exec -r \"service_test_rsc\" -a \"stop\" "+self._action_timeout,
             "service_stop_event"    : "-l \"NEW_EVENT event_type:exec_complete rsc_id:service_test_rsc action:stop rc:ok op_status:complete\" ",
             "service_monitor_line"  : '-c exec -r service_test_rsc -a monitor -i 2s ' + self._action_timeout,
             "service_monitor_event" : "-l \"NEW_EVENT event_type:exec_complete rsc_id:service_test_rsc action:monitor rc:ok op_status:complete\" "+self._action_timeout,
             "service_cancel_line"   : '-c cancel -r service_test_rsc -a monitor -i 2s ' + self._action_timeout,
             "service_cancel_event"  : "-l \"NEW_EVENT event_type:exec_complete rsc_id:service_test_rsc action:monitor rc:ok op_status:Cancelled\" ",
 
             "lsb_reg_line"      : "-c register_rsc -r lsb_test_rsc "+self._action_timeout+" -C lsb -T LSBDummy",
             "lsb_reg_event"     : "-l \"NEW_EVENT event_type:register rsc_id:lsb_test_rsc action:none rc:ok op_status:complete\" ",
             "lsb_unreg_line"    : "-c unregister_rsc -r \"lsb_test_rsc\" "+self._action_timeout,
             "lsb_unreg_event"   : "-l \"NEW_EVENT event_type:unregister rsc_id:lsb_test_rsc action:none rc:ok op_status:complete\"",
             "lsb_start_line"    : "-c exec -r \"lsb_test_rsc\" -a \"start\" "+self._action_timeout,
             "lsb_start_event"   : "-l \"NEW_EVENT event_type:exec_complete rsc_id:lsb_test_rsc action:start rc:ok op_status:complete\" ",
             "lsb_stop_line"     : "-c exec -r \"lsb_test_rsc\" -a \"stop\" "+self._action_timeout,
             "lsb_stop_event"    : "-l \"NEW_EVENT event_type:exec_complete rsc_id:lsb_test_rsc action:stop rc:ok op_status:complete\" ",
             "lsb_monitor_line"  : '-c exec -r lsb_test_rsc -a status -i 2s ' + self._action_timeout,
             "lsb_monitor_event" : "-l \"NEW_EVENT event_type:exec_complete rsc_id:lsb_test_rsc action:status rc:ok op_status:complete\" "+self._action_timeout,
             "lsb_cancel_line"   : '-c cancel -r lsb_test_rsc -a status -i 2s ' + self._action_timeout,
             "lsb_cancel_event"  : "-l \"NEW_EVENT event_type:exec_complete rsc_id:lsb_test_rsc action:status rc:ok op_status:Cancelled\" ",
 
             "stonith_reg_line"      : "-c register_rsc -r stonith_test_rsc " + self._action_timeout +
 				      " -C stonith -P pacemaker -T fence_dummy",
             "stonith_reg_event"     : "-l \"NEW_EVENT event_type:register rsc_id:stonith_test_rsc action:none rc:ok op_status:complete\" ",
             "stonith_unreg_line"    : "-c unregister_rsc -r \"stonith_test_rsc\" "+self._action_timeout,
             "stonith_unreg_event"   : "-l \"NEW_EVENT event_type:unregister rsc_id:stonith_test_rsc action:none rc:ok op_status:complete\"",
             "stonith_start_line"    : '-c exec -r stonith_test_rsc -a start ' + self._action_timeout,
             "stonith_start_event"   : "-l \"NEW_EVENT event_type:exec_complete rsc_id:stonith_test_rsc action:start rc:ok op_status:complete\" ",
             "stonith_stop_line"     : "-c exec -r \"stonith_test_rsc\" -a \"stop\" "+self._action_timeout,
             "stonith_stop_event"    : "-l \"NEW_EVENT event_type:exec_complete rsc_id:stonith_test_rsc action:stop rc:ok op_status:complete\" ",
             "stonith_monitor_line"  : '-c exec -r stonith_test_rsc -a monitor -i 2s ' + self._action_timeout,
             "stonith_monitor_event" : "-l \"NEW_EVENT event_type:exec_complete rsc_id:stonith_test_rsc action:monitor rc:ok op_status:complete\" "+self._action_timeout,
             "stonith_cancel_line"   : '-c cancel -r stonith_test_rsc -a monitor -i 2s ' + self._action_timeout,
             "stonith_cancel_event"  : "-l \"NEW_EVENT event_type:exec_complete rsc_id:stonith_test_rsc action:monitor rc:ok op_status:Cancelled\" ",
         }
 
     def _setup_rsc_classes(self):
         classes = stdout_from_command(["crm_resource", "--list-standards"])
         classes = classes[:-1] # Strip trailing empty line
 
         if self.tls:
             classes.remove("stonith")
 
         if "nagios" in classes:
             classes.remove("nagios")
 
         if "systemd" in classes:
             try:
                 # This code doesn't need this import, but pacemaker-cts-dummyd
                 # does, so ensure the dependency is available rather than cause
                 # all systemd tests to fail.
                 import systemd.daemon
             except ImportError:
                 print("Python systemd bindings not found.")
                 print("The tests for systemd class are not going to be run.")
                 classes.remove("systemd")
 
         return classes
 
     def new_test(self, name, description):
         """ Create a named test """
 
         test = ExecTest(name, description, verbose=self.verbose, tls=self.tls,
                         timeout=self.timeout, force_wait=self.force_wait,
                         logdir=self.logdir)
         self._tests.append(test)
         return test
 
     def setup_environment(self):
         """ Prepare the host before executing any tests """
 
         if BuildOptions.REMOTE_ENABLED:
             os.system("service pacemaker_remote stop")
         self.cleanup_environment()
 
         if self.tls and not os.path.isfile("/etc/pacemaker/authkey"):
             print("Installing /etc/pacemaker/authkey ...")
             os.system("mkdir -p /etc/pacemaker")
             os.system("dd if=/dev/urandom of=/etc/pacemaker/authkey bs=4096 count=1")
             self._installed_files.append("/etc/pacemaker/authkey")
 
         # If we're in build directory, install agents if not already installed
         if os.path.exists("%s/cts/cts-exec.in" % BuildOptions._BUILD_DIR):
 
             if not os.path.exists("%s/pacemaker" % BuildOptions.OCF_RA_INSTALL_DIR):
                 # @TODO remember which components were created and remove them
                 os.makedirs("%s/pacemaker" % BuildOptions.OCF_RA_INSTALL_DIR, 0o755)
 
             for agent in ["Dummy", "Stateful", "ping"]:
                 agent_source = "%s/extra/resources/%s" % (BuildOptions._BUILD_DIR, agent)
                 agent_dest = "%s/pacemaker/%s" % (BuildOptions.OCF_RA_INSTALL_DIR, agent)
                 if not os.path.exists(agent_dest):
                     print("Installing %s ..." % (agent_dest))
                     shutil.copyfile(agent_source, agent_dest)
                     os.chmod(agent_dest, EXECMODE)
                     self._installed_files.append(agent_dest)
 
         subprocess.call(["cts-support", "install"])
 
     def cleanup_environment(self):
         """ Clean up the host after executing desired tests """
 
         for installed_file in self._installed_files:
             print("Removing %s ..." % (installed_file))
             os.remove(installed_file)
 
         subprocess.call(["cts-support", "uninstall"])
 
     def build_generic_tests(self):
         """ Register tests that apply to all resource classes """
 
         common_cmds = self._common_cmds
 
         ### register/unregister tests ###
         for rsc in self._rsc_classes:
             test = self.new_test("generic_registration_%s" % (rsc),
                                  "Simple resource registration test for %s standard" % (rsc))
             test.add_cmd(common_cmds["%s_reg_line" % (rsc)] + " " + common_cmds["%s_reg_event" % (rsc)])
             test.add_cmd(common_cmds["%s_unreg_line" % (rsc)] + " " + common_cmds["%s_unreg_event" % (rsc)])
 
         ### start/stop tests  ###
         for rsc in self._rsc_classes:
             test = self.new_test("generic_start_stop_%s" % (rsc), "Simple start and stop test for %s standard" % (rsc))
             test.add_cmd(common_cmds["%s_reg_line" % (rsc)]   + " " + common_cmds["%s_reg_event" % (rsc)])
             test.add_cmd(common_cmds["%s_start_line" % (rsc)] + " " + common_cmds["%s_start_event" % (rsc)])
             test.add_cmd(common_cmds["%s_stop_line" % (rsc)]  + " " + common_cmds["%s_stop_event" % (rsc)])
             test.add_cmd(common_cmds["%s_unreg_line" % (rsc)] + " " + common_cmds["%s_unreg_event" % (rsc)])
 
         ### monitor cancel test ###
         for rsc in self._rsc_classes:
             test = self.new_test("generic_monitor_cancel_%s" % (rsc),
                                  "Simple monitor cancel test for %s standard" % (rsc))
             test.add_cmd(common_cmds["%s_reg_line" % (rsc)]   + " " + common_cmds["%s_reg_event" % (rsc)])
             test.add_cmd(common_cmds["%s_start_line" % (rsc)] + " " + common_cmds["%s_start_event" % (rsc)])
             test.add_cmd(common_cmds["%s_monitor_line" % (rsc)] + " " + common_cmds["%s_monitor_event" % (rsc)])
             ### If this fails, that means the monitor may not be getting rescheduled ####
             test.add_cmd(common_cmds["%s_monitor_event" % (rsc)])
             ### If this fails, that means the monitor may not be getting rescheduled ####
             test.add_cmd(common_cmds["%s_monitor_event" % (rsc)])
             test.add_cmd(common_cmds["%s_cancel_line" % (rsc)] + " " + common_cmds["%s_cancel_event" % (rsc)])
             ### If this happens the monitor did not actually cancel correctly. ###
             test.add_cmd_expected_fail(common_cmds["%s_monitor_event" % (rsc)], ExitStatus.TIMEOUT)
             ### If this happens the monitor did not actually cancel correctly. ###
             test.add_cmd_expected_fail(common_cmds["%s_monitor_event" % (rsc)], ExitStatus.TIMEOUT)
             test.add_cmd(common_cmds["%s_stop_line" % (rsc)]  + " " + common_cmds["%s_stop_event" % (rsc)])
             test.add_cmd(common_cmds["%s_unreg_line" % (rsc)] + " " + common_cmds["%s_unreg_event" % (rsc)])
 
         ### monitor duplicate test ###
         for rsc in self._rsc_classes:
             test = self.new_test("generic_monitor_duplicate_%s" % (rsc),
                                  "Test creation and canceling of duplicate monitors for %s standard" % (rsc))
             test.add_cmd(common_cmds["%s_reg_line" % (rsc)]   + " " + common_cmds["%s_reg_event" % (rsc)])
             test.add_cmd(common_cmds["%s_start_line" % (rsc)] + " " + common_cmds["%s_start_event" % (rsc)])
             test.add_cmd(common_cmds["%s_monitor_line" % (rsc)] + " " + common_cmds["%s_monitor_event" % (rsc)])
             ### If this fails, that means the monitor may not be getting rescheduled ####
             test.add_cmd(common_cmds["%s_monitor_event" % (rsc)])
             ### If this fails, that means the monitor may not be getting rescheduled ####
             test.add_cmd(common_cmds["%s_monitor_event" % (rsc)])
 
             # Add the duplicate monitors
             test.add_cmd(common_cmds["%s_monitor_line" % (rsc)] + " " + common_cmds["%s_monitor_event" % (rsc)])
             test.add_cmd(common_cmds["%s_monitor_line" % (rsc)] + " " + common_cmds["%s_monitor_event" % (rsc)])
             test.add_cmd(common_cmds["%s_monitor_line" % (rsc)] + " " + common_cmds["%s_monitor_event" % (rsc)])
             test.add_cmd(common_cmds["%s_monitor_line" % (rsc)] + " " + common_cmds["%s_monitor_event" % (rsc)])
             # verify we still get update events
             ### If this fails, that means the monitor may not be getting rescheduled ####
             test.add_cmd(common_cmds["%s_monitor_event" % (rsc)])
 
             # cancel the monitor, if the duplicate merged with the original, we should no longer see monitor updates
             test.add_cmd(common_cmds["%s_cancel_line" % (rsc)] + " " + common_cmds["%s_cancel_event" % (rsc)])
             ### If this happens the monitor did not actually cancel correctly. ###
             test.add_cmd_expected_fail(common_cmds["%s_monitor_event" % (rsc)], ExitStatus.TIMEOUT)
             ### If this happens the monitor did not actually cancel correctly. ###
             test.add_cmd_expected_fail(common_cmds["%s_monitor_event" % (rsc)], ExitStatus.TIMEOUT)
             test.add_cmd(common_cmds["%s_stop_line" % (rsc)]  + " " + common_cmds["%s_stop_event" % (rsc)])
             test.add_cmd(common_cmds["%s_unreg_line" % (rsc)] + " " + common_cmds["%s_unreg_event" % (rsc)])
 
         ### stop implies cancel test ###
         for rsc in self._rsc_classes:
             test = self.new_test("generic_stop_implies_cancel_%s" % (rsc),
                                  "Verify stopping a resource implies cancel of recurring ops for %s standard" % (rsc))
             test.add_cmd(common_cmds["%s_reg_line" % (rsc)]   + " " + common_cmds["%s_reg_event" % (rsc)])
             test.add_cmd(common_cmds["%s_start_line" % (rsc)] + " " + common_cmds["%s_start_event" % (rsc)])
             test.add_cmd(common_cmds["%s_monitor_line" % (rsc)] + " " + common_cmds["%s_monitor_event" % (rsc)])
             ### If this fails, that means the monitor may not be getting rescheduled ####
             test.add_cmd(common_cmds["%s_monitor_event" % (rsc)])
             ### If this fails, that means the monitor may not be getting rescheduled ####
             test.add_cmd(common_cmds["%s_monitor_event" % (rsc)])
             test.add_cmd(common_cmds["%s_stop_line" % (rsc)]  + " " + common_cmds["%s_stop_event" % (rsc)])
             ### If this happens the monitor did not actually cancel correctly. ###
             test.add_cmd_expected_fail(common_cmds["%s_monitor_event" % (rsc)], ExitStatus.TIMEOUT)
             ### If this happens the monitor did not actually cancel correctly. ###
             test.add_cmd_expected_fail(common_cmds["%s_monitor_event" % (rsc)], ExitStatus.TIMEOUT)
             test.add_cmd(common_cmds["%s_unreg_line" % (rsc)] + " " + common_cmds["%s_unreg_event" % (rsc)])
 
 
     def build_multi_rsc_tests(self):
         """ Register complex tests that involve managing multiple resouces of different types """
 
         common_cmds = self._common_cmds
         # do not use service and systemd at the same time, it is the same resource.
 
         ### register start monitor stop unregister resources of each type at the same time. ###
         test = self.new_test("multi_rsc_start_stop_all",
                              "Start, monitor, and stop resources of multiple types and classes")
         for rsc in self._rsc_classes:
             test.add_cmd(common_cmds["%s_reg_line" % (rsc)]   + " " + common_cmds["%s_reg_event" % (rsc)])
         for rsc in self._rsc_classes:
             test.add_cmd(common_cmds["%s_start_line" % (rsc)] + " " + common_cmds["%s_start_event" % (rsc)])
         for rsc in self._rsc_classes:
             test.add_cmd(common_cmds["%s_monitor_line" % (rsc)] + " " + common_cmds["%s_monitor_event" % (rsc)])
         for rsc in self._rsc_classes:
             ### If this fails, that means the monitor is not being rescheduled ####
             test.add_cmd(common_cmds["%s_monitor_event" % (rsc)])
         for rsc in self._rsc_classes:
             test.add_cmd(common_cmds["%s_cancel_line" % (rsc)] + " " + common_cmds["%s_cancel_event" % (rsc)])
         for rsc in self._rsc_classes:
             test.add_cmd(common_cmds["%s_stop_line" % (rsc)]  + " " + common_cmds["%s_stop_event" % (rsc)])
         for rsc in self._rsc_classes:
             test.add_cmd(common_cmds["%s_unreg_line" % (rsc)] + " " + common_cmds["%s_unreg_event" % (rsc)])
 
     def build_negative_tests(self):
         """ Register tests related to how pacemaker-execd handles failures """
 
         ### ocf start timeout test  ###
         test = self.new_test("ocf_start_timeout", "Force start timeout to occur, verify start failure.")
         test.add_cmd("-c register_rsc -r \"test_rsc\" -C \"ocf\" -P \"pacemaker\" -T \"Dummy\" "
                      + self._action_timeout +
                      "-l \"NEW_EVENT event_type:register rsc_id:test_rsc action:none rc:ok op_status:complete\" ")
         # -t must be less than self._action_timeout
         test.add_cmd("-c exec -r \"test_rsc\" -a \"start\" -k \"op_sleep\" -v \"5\" -t 1000 -w")
         test.add_cmd('-l "NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:start rc:error op_status:Timed Out" '
                      + self._action_timeout)
         test.add_cmd("-c exec -r test_rsc -a stop " + self._action_timeout +
                      "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:stop rc:ok op_status:complete\" ")
         test.add_cmd("-c unregister_rsc -r test_rsc " + self._action_timeout +
                      "-l \"NEW_EVENT event_type:unregister rsc_id:test_rsc action:none rc:ok op_status:complete\" ")
 
         ### stonith start timeout test  ###
         test = self.new_test("stonith_start_timeout", "Force start timeout to occur, verify start failure.")
         test.add_cmd('-c register_rsc -r test_rsc ' +
                      '-C stonith -P pacemaker -T fence_dummy ' +
                      self._action_timeout +
                      '-l "NEW_EVENT event_type:register rsc_id:test_rsc action:none rc:ok op_status:complete"')
         test.add_cmd('-c exec -r test_rsc -a start -k monitor_delay -v 30 ' +
                      '-t 1000 -w') # -t must be less than self._action_timeout
         test.add_cmd('-l "NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:start rc:error op_status:Timed Out" '
                      + self._action_timeout)
         test.add_cmd("-c exec -r test_rsc -a stop " + self._action_timeout +
                      "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:stop rc:ok op_status:complete\" ")
         test.add_cmd("-c unregister_rsc -r test_rsc " + self._action_timeout +
                      "-l \"NEW_EVENT event_type:unregister rsc_id:test_rsc action:none rc:ok op_status:complete\" ")
 
         ### stonith component fail ###
         common_cmds = self._common_cmds
         test = self.new_test("stonith_component_fail", "Kill stonith component after pacemaker-execd connects")
         test.add_cmd(common_cmds["stonith_reg_line"]   + " " + common_cmds["stonith_reg_event"])
         test.add_cmd(common_cmds["stonith_start_line"] + " " + common_cmds["stonith_start_event"])
 
         test.add_cmd('-c exec -r stonith_test_rsc -a monitor -i 600s '
                      '-l "NEW_EVENT event_type:exec_complete rsc_id:stonith_test_rsc action:monitor rc:ok op_status:complete" '
                      + self._action_timeout)
 
         test.add_cmd_and_kill('-l "NEW_EVENT event_type:exec_complete rsc_id:stonith_test_rsc action:monitor rc:error op_status:error" -t 15000',
                               "killall -9 -q pacemaker-fenced lt-pacemaker-fenced")
         test.add_cmd(common_cmds["stonith_unreg_line"] + " " + common_cmds["stonith_unreg_event"])
 
 
         ### monitor fail for ocf resources ###
         test = self.new_test("monitor_fail_ocf", "Force ocf monitor to fail, verify failure is reported.")
         test.add_cmd("-c register_rsc -r \"test_rsc\" -C \"ocf\" -P \"pacemaker\" -T \"Dummy\" "
                      + self._action_timeout +
                      "-l \"NEW_EVENT event_type:register rsc_id:test_rsc action:none rc:ok op_status:complete\" ")
         test.add_cmd("-c exec -r \"test_rsc\" -a \"start\" " + self._action_timeout +
                      "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:start rc:ok op_status:complete\" ")
         test.add_cmd("-c exec -r \"test_rsc\" -a \"start\" " + self._action_timeout +
                      "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:start rc:ok op_status:complete\" ")
         test.add_cmd('-c exec -r test_rsc -a monitor -i 1s '
                      + self._action_timeout +
                      '-l "NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:ok op_status:complete"')
         test.add_cmd('-l "NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:ok op_status:complete"'
                      + self._action_timeout)
         test.add_cmd('-l "NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:ok op_status:complete"'
                      + self._action_timeout)
         test.add_cmd_and_kill('-l "NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:not running op_status:complete" ' + self._action_timeout,
                               "rm -f %s/run/Dummy-test_rsc.state" % BuildOptions.LOCAL_STATE_DIR)
         test.add_cmd('-c cancel -r test_rsc -a monitor -i 1s ' + self._action_timeout +
                      "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:not running op_status:Cancelled\" ")
         test.add_cmd_expected_fail("-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:not running op_status:complete\" "
                                    + self._action_timeout, ExitStatus.TIMEOUT)
         test.add_cmd_expected_fail("-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:ok op_status:complete\" "
                                    + self._action_timeout, ExitStatus.TIMEOUT)
         test.add_cmd("-c unregister_rsc -r \"test_rsc\" "
                      + self._action_timeout +
                      "-l \"NEW_EVENT event_type:unregister rsc_id:test_rsc action:none rc:ok op_status:complete\" ")
 
         ### verify notify changes only for monitor operation.  ###
         test = self.new_test("monitor_changes_only", "Verify when flag is set, only monitor changes are notified.")
         test.add_cmd("-c register_rsc -r \"test_rsc\" -C \"ocf\" -P \"pacemaker\" -T \"Dummy\" "+self._action_timeout+
                      "-l \"NEW_EVENT event_type:register rsc_id:test_rsc action:none rc:ok op_status:complete\" ")
         test.add_cmd("-c exec -r \"test_rsc\" -a \"start\" "+self._action_timeout+" -o "
                      "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:start rc:ok op_status:complete\" ")
         test.add_cmd('-c exec -r test_rsc -a monitor -i 1s '
                      + self._action_timeout +
                      ' -o -l "NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:ok op_status:complete" ')
         test.add_cmd_expected_fail("-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:ok op_status:complete\" "+self._action_timeout, ExitStatus.TIMEOUT)
         test.add_cmd_and_kill('-l "NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:not running op_status:complete"' + self._action_timeout,
                               'rm -f %s/run/Dummy-test_rsc.state' % BuildOptions.LOCAL_STATE_DIR)
         test.add_cmd('-c cancel -r test_rsc -a monitor -i 1s'
                      + self._action_timeout +
                      "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:not running op_status:Cancelled\" ")
         test.add_cmd_expected_fail("-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:not running op_status:complete\" "+self._action_timeout, ExitStatus.TIMEOUT)
         test.add_cmd_expected_fail("-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:ok op_status:complete\" "+self._action_timeout, ExitStatus.TIMEOUT)
         test.add_cmd('-c unregister_rsc -r "test_rsc" ' + self._action_timeout +
                      '-l "NEW_EVENT event_type:unregister rsc_id:test_rsc action:none rc:ok op_status:complete"')
 
         ### monitor fail for systemd resource ###
         if "systemd" in self._rsc_classes:
             test = self.new_test("monitor_fail_systemd", "Force systemd monitor to fail, verify failure is reported..")
             test.add_cmd("-c register_rsc -r \"test_rsc\" -C systemd -T pacemaker-cts-dummyd@3 " +
                          self._action_timeout +
                          "-l \"NEW_EVENT event_type:register rsc_id:test_rsc action:none rc:ok op_status:complete\" ")
             test.add_cmd("-c exec -r \"test_rsc\" -a \"start\" "+self._action_timeout+
                          "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:start rc:ok op_status:complete\" ")
             test.add_cmd("-c exec -r \"test_rsc\" -a \"start\" "+self._action_timeout+
                          "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:start rc:ok op_status:complete\" ")
             test.add_cmd('-c exec -r test_rsc -a monitor -i 1s '
                          + self._action_timeout +
                          "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:ok op_status:complete\" ")
             test.add_cmd("-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:ok op_status:complete\" "+self._action_timeout)
             test.add_cmd("-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:ok op_status:complete\" "+self._action_timeout)
             test.add_cmd_and_kill('-l "NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:not running op_status:complete"' + self._action_timeout,
                                   "pkill -9 -f pacemaker-cts-dummyd")
             test.add_cmd('-c cancel -r test_rsc -a monitor -i 1s' + self._action_timeout +
                          "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:not running op_status:Cancelled\" ")
             test.add_cmd_expected_fail("-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:not running op_status:complete\" "+self._action_timeout, ExitStatus.TIMEOUT)
             test.add_cmd_expected_fail("-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:ok op_status:complete\" "+self._action_timeout, ExitStatus.TIMEOUT)
             test.add_cmd("-c unregister_rsc -r \"test_rsc\" "+self._action_timeout+
                          "-l \"NEW_EVENT event_type:unregister rsc_id:test_rsc action:none rc:ok op_status:complete\" ")
 
         ### monitor fail for upstart resource ###
         if "upstart" in self._rsc_classes:
             test = self.new_test("monitor_fail_upstart", "Force upstart monitor to fail, verify failure is reported..")
             test.add_cmd("-c register_rsc -r \"test_rsc\" -C upstart -T pacemaker-cts-dummyd "+self._action_timeout+
                          "-l \"NEW_EVENT event_type:register rsc_id:test_rsc action:none rc:ok op_status:complete\" ")
             test.add_cmd("-c exec -r \"test_rsc\" -a \"start\" "+self._action_timeout+
                          "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:start rc:ok op_status:complete\" ")
             test.add_cmd("-c exec -r \"test_rsc\" -a \"start\" "+self._action_timeout+
                          "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:start rc:ok op_status:complete\" ")
             test.add_cmd('-c exec -r test_rsc -a monitor -i 1s ' + self._action_timeout +
                          "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:ok op_status:complete\" ")
             test.add_cmd("-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:ok op_status:complete\" "+self._action_timeout)
             test.add_cmd("-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:ok op_status:complete\" "+self._action_timeout)
             test.add_cmd_and_kill('-l "NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:not running op_status:complete"' + self._action_timeout,
                                   'killall -9 -q dd')
             test.add_cmd('-c cancel -r test_rsc -a monitor -i 1s'
                          + self._action_timeout +
                          "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:not running op_status:Cancelled\" ")
             test.add_cmd_expected_fail("-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:not running op_status:complete\" "+self._action_timeout, ExitStatus.TIMEOUT)
             test.add_cmd_expected_fail("-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:ok op_status:complete\" "+self._action_timeout, ExitStatus.TIMEOUT)
             test.add_cmd("-c unregister_rsc -r \"test_rsc\" "+self._action_timeout+
                          "-l \"NEW_EVENT event_type:unregister rsc_id:test_rsc action:none rc:ok op_status:complete\" ")
 
         ### Cancel non-existent operation on a resource ###
         test = self.new_test("cancel_non_existent_op", "Attempt to cancel the wrong monitor operation, verify expected failure")
         test.add_cmd("-c register_rsc -r \"test_rsc\" -C \"ocf\" -P \"pacemaker\" -T \"Dummy\" "+self._action_timeout+
                      "-l \"NEW_EVENT event_type:register rsc_id:test_rsc action:none rc:ok op_status:complete\" ")
         test.add_cmd("-c exec -r \"test_rsc\" -a \"start\" "+self._action_timeout+
                      "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:start rc:ok op_status:complete\" ")
         test.add_cmd("-c exec -r \"test_rsc\" -a \"start\" "+self._action_timeout+
                      "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:start rc:ok op_status:complete\" ")
         test.add_cmd('-c exec -r test_rsc -a monitor -i 1s '
                      + self._action_timeout +
                      "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:ok op_status:complete\" ")
         test.add_cmd("-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:ok op_status:complete\" "+self._action_timeout)
         ### interval is wrong, should fail
         test.add_cmd_expected_fail('-c cancel -r test_rsc -a monitor -i 2s' + self._action_timeout +
                                    "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:not running op_status:Cancelled\" ")
         ### action name is wrong, should fail
         test.add_cmd_expected_fail('-c cancel -r test_rsc -a stop -i 1s' + self._action_timeout +
                                    "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:not running op_status:Cancelled\" ")
         test.add_cmd("-c unregister_rsc -r \"test_rsc\" " + self._action_timeout +
                      "-l \"NEW_EVENT event_type:unregister rsc_id:test_rsc action:none rc:ok op_status:complete\" ")
 
         ### Attempt to invoke non-existent rsc id ###
         test = self.new_test("invoke_non_existent_rsc", "Attempt to perform operations on a non-existent rsc id.")
         test.add_cmd_expected_fail("-c exec -r \"test_rsc\" -a \"start\" "+self._action_timeout+
                                    "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:start rc:error op_status:complete\" ")
         test.add_cmd_expected_fail("-c exec -r test_rsc -a stop "+self._action_timeout+
                                    "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:stop rc:ok op_status:complete\" ")
         test.add_cmd_expected_fail('-c exec -r test_rsc -a monitor -i 6s '
                                    + self._action_timeout +
                                    "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:ok op_status:complete\" ")
         test.add_cmd_expected_fail("-c cancel -r test_rsc -a start "+self._action_timeout+
                                    "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:start rc:ok op_status:Cancelled\" ")
         test.add_cmd("-c unregister_rsc -r \"test_rsc\" "+self._action_timeout+
                      "-l \"NEW_EVENT event_type:unregister rsc_id:test_rsc action:none rc:ok op_status:complete\" ")
 
         ### Register and start a resource that doesn't exist, systemd  ###
         if "systemd" in self._rsc_classes:
             test = self.new_test("start_uninstalled_systemd", "Register uninstalled systemd agent, try to start, verify expected failure")
             test.add_cmd("-c register_rsc -r \"test_rsc\" -C systemd -T this_is_fake1234 "+self._action_timeout+
                          "-l \"NEW_EVENT event_type:register rsc_id:test_rsc action:none rc:ok op_status:complete\" ")
             test.add_cmd("-c exec -r \"test_rsc\" -a \"start\" "+self._action_timeout+
                          "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:start rc:not installed op_status:Not installed\" ")
             test.add_cmd("-c unregister_rsc -r \"test_rsc\" "+self._action_timeout+
                          "-l \"NEW_EVENT event_type:unregister rsc_id:test_rsc action:none rc:ok op_status:complete\" ")
 
         if "upstart" in self._rsc_classes:
             test = self.new_test("start_uninstalled_upstart", "Register uninstalled upstart agent, try to start, verify expected failure")
             test.add_cmd("-c register_rsc -r \"test_rsc\" -C upstart -T this_is_fake1234 "+self._action_timeout+
                          "-l \"NEW_EVENT event_type:register rsc_id:test_rsc action:none rc:ok op_status:complete\" ")
             test.add_cmd("-c exec -r \"test_rsc\" -a \"start\" "+self._action_timeout+
                          "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:start rc:not installed op_status:Not installed\" ")
             test.add_cmd("-c unregister_rsc -r \"test_rsc\" "+self._action_timeout+
                          "-l \"NEW_EVENT event_type:unregister rsc_id:test_rsc action:none rc:ok op_status:complete\" ")
 
         ### Register and start a resource that doesn't exist, ocf ###
         test = self.new_test("start_uninstalled_ocf", "Register uninstalled ocf agent, try to start, verify expected failure.")
         test.add_cmd("-c register_rsc -r \"test_rsc\" -C ocf -P pacemaker -T this_is_fake1234 "+self._action_timeout+
                      "-l \"NEW_EVENT event_type:register rsc_id:test_rsc action:none rc:ok op_status:complete\" ")
         test.add_cmd("-c exec -r \"test_rsc\" -a \"start\" "+self._action_timeout+
                      "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:start rc:not installed op_status:Not installed\" ")
         test.add_cmd("-c unregister_rsc -r \"test_rsc\" "+self._action_timeout+
                      "-l \"NEW_EVENT event_type:unregister rsc_id:test_rsc action:none rc:ok op_status:complete\" ")
 
         ### Register ocf with non-existent provider  ###
         test = self.new_test("start_ocf_bad_provider", "Register ocf agent with a non-existent provider, verify expected failure.")
         test.add_cmd("-c register_rsc -r \"test_rsc\" -C ocf -P pancakes -T Dummy "+self._action_timeout+
                      "-l \"NEW_EVENT event_type:register rsc_id:test_rsc action:none rc:ok op_status:complete\" ")
         test.add_cmd("-c exec -r \"test_rsc\" -a \"start\" "+self._action_timeout+
                      "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:start rc:not installed op_status:Not installed\" ")
         test.add_cmd("-c unregister_rsc -r \"test_rsc\" "+self._action_timeout+
                      "-l \"NEW_EVENT event_type:unregister rsc_id:test_rsc action:none rc:ok op_status:complete\" ")
 
         ### Register ocf with empty provider field  ###
         test = self.new_test("start_ocf_no_provider", "Register ocf agent with a no provider, verify expected failure.")
         test.add_cmd_expected_fail("-c register_rsc -r \"test_rsc\" -C ocf -T Dummy "+self._action_timeout+
                                    "-l \"NEW_EVENT event_type:register rsc_id:test_rsc action:none rc:ok op_status:complete\" ")
         test.add_cmd_expected_fail("-c exec -r \"test_rsc\" -a \"start\" "+self._action_timeout+
                                    "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:start rc:ok op_status:Error\" ")
         test.add_cmd("-c unregister_rsc -r \"test_rsc\" "+self._action_timeout+
                      "-l \"NEW_EVENT event_type:unregister rsc_id:test_rsc action:none rc:ok op_status:complete\" ")
 
     def build_stress_tests(self):
         """ Register stress tests """
 
         timeout = "-t 20000"
 
         iterations = 25
         test = self.new_test("ocf_stress", "Verify OCF agent handling works under load")
         for i in range(iterations):
             test.add_cmd("-c register_rsc -r rsc_%s %s -C ocf -P heartbeat -T Dummy -l \"NEW_EVENT event_type:register rsc_id:rsc_%s action:none rc:ok op_status:complete\"" % (i, timeout, i))
             test.add_cmd("-c exec -r rsc_%s -a start %s -l \"NEW_EVENT event_type:exec_complete rsc_id:rsc_%s action:start rc:ok op_status:complete\"" % (i, timeout, i))
             test.add_cmd('-c exec -r rsc_%s -a monitor %s -i 1s '
                          '-l "NEW_EVENT event_type:exec_complete rsc_id:rsc_%s action:monitor rc:ok op_status:complete"' % (i, timeout, i))
         for i in range(iterations):
             test.add_cmd("-c exec -r rsc_%s -a stop %s  -l \"NEW_EVENT event_type:exec_complete rsc_id:rsc_%s action:stop rc:ok op_status:complete\"" % (i, timeout, i))
             test.add_cmd("-c unregister_rsc -r rsc_%s %s -l \"NEW_EVENT event_type:unregister rsc_id:rsc_%s action:none rc:ok op_status:complete\"" % (i, timeout, i))
 
 
         if "systemd" in self._rsc_classes:
             test = self.new_test("systemd_stress", "Verify systemd dbus connection works under load")
             for i in range(iterations):
                 test.add_cmd("-c register_rsc -r rsc_%s %s -C systemd -T pacemaker-cts-dummyd@3 -l \"NEW_EVENT event_type:register rsc_id:rsc_%s action:none rc:ok op_status:complete\"" % (i, timeout, i))
                 test.add_cmd("-c exec -r rsc_%s -a start %s -l \"NEW_EVENT event_type:exec_complete rsc_id:rsc_%s action:start rc:ok op_status:complete\"" % (i, timeout, i))
                 test.add_cmd('-c exec -r rsc_%s -a monitor %s -i 1s '
                              '-l "NEW_EVENT event_type:exec_complete rsc_id:rsc_%s action:monitor rc:ok op_status:complete"' % (i, timeout, i))
 
             for i in range(iterations):
                 test.add_cmd("-c exec -r rsc_%s -a stop %s -l \"NEW_EVENT event_type:exec_complete rsc_id:rsc_%s action:stop rc:ok op_status:complete\"" % (i, timeout, i))
                 test.add_cmd("-c unregister_rsc -r rsc_%s %s -l \"NEW_EVENT event_type:unregister rsc_id:rsc_%s action:none rc:ok op_status:complete\"" % (i, timeout, i))
 
         iterations = 9
         timeout = "-t 30000"
         ### Verify recurring op in-flight collision is handled in series properly
         test = self.new_test("rsc_inflight_collision", "Verify recurring ops do not collide with other operations for the same rsc.")
         test.add_cmd("-c register_rsc -r test_rsc -P pacemaker -C ocf -T Dummy "
                      "-l \"NEW_EVENT event_type:register rsc_id:test_rsc action:none rc:ok op_status:complete\" "+self._action_timeout)
         test.add_cmd("-c exec -r test_rsc -a start %s -k op_sleep -v 1 -l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:start rc:ok op_status:complete\"" % (timeout))
         for i in range(iterations):
             test.add_cmd('-c exec -r test_rsc -a monitor %s -i 100%dms '
                          '-k op_sleep -v 2 '
                          '-l "NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:ok op_status:complete"' % (timeout, i))
 
         test.add_cmd("-c exec -r test_rsc -a stop %s -l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:stop rc:ok op_status:complete\"" % (timeout))
         test.add_cmd("-c unregister_rsc -r test_rsc %s -l \"NEW_EVENT event_type:unregister rsc_id:test_rsc action:none rc:ok op_status:complete\"" % (timeout))
 
     def build_custom_tests(self):
         """ Register tests that target specific cases """
 
         ### verify resource temporary folder is created and used by OCF agents.  ###
         test = self.new_test("rsc_tmp_dir", "Verify creation and use of rsc temporary state directory")
         test.add_sys_cmd("ls", "-al %s" % BuildOptions.RSC_TMP_DIR)
         test.add_cmd("-c register_rsc -r test_rsc -P heartbeat -C ocf -T Dummy "
                      "-l \"NEW_EVENT event_type:register rsc_id:test_rsc action:none rc:ok op_status:complete\" "+self._action_timeout)
         test.add_cmd("-c exec -r test_rsc -a start -t 4000")
         test.add_sys_cmd("ls", "-al %s" % BuildOptions.RSC_TMP_DIR)
         test.add_sys_cmd("ls", "%s/Dummy-test_rsc.state" % BuildOptions.RSC_TMP_DIR)
         test.add_cmd("-c exec -r test_rsc -a stop -t 4000")
         test.add_cmd("-c unregister_rsc -r test_rsc "+self._action_timeout+
                      "-l \"NEW_EVENT event_type:unregister rsc_id:test_rsc action:none rc:ok op_status:complete\" ")
 
         ### start delay then stop test ###
         test = self.new_test("start_delay", "Verify start delay works as expected.")
         test.add_cmd("-c register_rsc -r test_rsc -P pacemaker -C ocf -T Dummy "
                      "-l \"NEW_EVENT event_type:register rsc_id:test_rsc action:none rc:ok op_status:complete\" "+self._action_timeout)
         test.add_cmd("-c exec -r test_rsc -s 6000 -a start -w -t 6000")
         test.add_cmd_expected_fail("-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:start rc:ok op_status:complete\" -t 2000", ExitStatus.TIMEOUT)
         test.add_cmd("-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:start rc:ok op_status:complete\" -t 6000")
         test.add_cmd("-c exec -r test_rsc -a stop " + self._action_timeout +
                      "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:stop rc:ok op_status:complete\" ")
         test.add_cmd("-c unregister_rsc -r test_rsc " + self._action_timeout +
                      "-l \"NEW_EVENT event_type:unregister rsc_id:test_rsc action:none rc:ok op_status:complete\" ")
 
         ### start delay, but cancel before it gets a chance to start.  ###
         test = self.new_test("start_delay_cancel", "Using start_delay, start a rsc, but cancel the start op before execution.")
         test.add_cmd("-c register_rsc -r test_rsc -P pacemaker -C ocf -T Dummy "
                      "-l \"NEW_EVENT event_type:register rsc_id:test_rsc action:none rc:ok op_status:complete\" "+self._action_timeout)
         test.add_cmd("-c exec -r test_rsc -s 5000 -a start -w -t 4000")
         test.add_cmd("-c cancel -r test_rsc -a start " + self._action_timeout +
                      "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:start rc:ok op_status:Cancelled\" ")
         test.add_cmd_expected_fail("-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:start rc:ok op_status:complete\" -t 5000", ExitStatus.TIMEOUT)
         test.add_cmd("-c unregister_rsc -r test_rsc " + self._action_timeout +
                      "-l \"NEW_EVENT event_type:unregister rsc_id:test_rsc action:none rc:ok op_status:complete\" ")
 
         ### Register a bunch of resources, verify we can get info on them ###
         test = self.new_test("verify_get_rsc_info", "Register multiple resources, verify retrieval of rsc info.")
         if "systemd" in self._rsc_classes:
             test.add_cmd("-c register_rsc -r rsc1 -C systemd -T pacemaker-cts-dummyd@3 "+self._action_timeout)
             test.add_cmd("-c get_rsc_info -r rsc1 ")
             test.add_cmd("-c unregister_rsc -r rsc1 "+self._action_timeout)
             test.add_cmd_expected_fail("-c get_rsc_info -r rsc1 ")
 
         if "upstart" in self._rsc_classes:
             test.add_cmd("-c register_rsc -r rsc1 -C upstart -T pacemaker-cts-dummyd "+self._action_timeout)
             test.add_cmd("-c get_rsc_info -r rsc1 ")
             test.add_cmd("-c unregister_rsc -r rsc1 "+self._action_timeout)
             test.add_cmd_expected_fail("-c get_rsc_info -r rsc1 ")
 
         test.add_cmd("-c register_rsc -r rsc2 -C ocf -T Dummy -P pacemaker "+self._action_timeout)
         test.add_cmd("-c get_rsc_info -r rsc2 ")
         test.add_cmd("-c unregister_rsc -r rsc2 "+self._action_timeout)
         test.add_cmd_expected_fail("-c get_rsc_info -r rsc2 ")
 
         ### Register duplicate, verify only one entry exists and can still be removed.
         test = self.new_test("duplicate_registration", "Register resource multiple times, verify only one entry exists and can be removed.")
         test.add_cmd("-c register_rsc -r rsc2 -C ocf -T Dummy -P pacemaker "+self._action_timeout)
         test.add_cmd_check_stdout("-c get_rsc_info -r rsc2 ", "id:rsc2 class:ocf provider:pacemaker type:Dummy")
         test.add_cmd("-c register_rsc -r rsc2 -C ocf -T Dummy -P pacemaker "+self._action_timeout)
         test.add_cmd_check_stdout("-c get_rsc_info -r rsc2 ", "id:rsc2 class:ocf provider:pacemaker type:Dummy")
         test.add_cmd("-c register_rsc -r rsc2 -C ocf -T Stateful -P pacemaker "+self._action_timeout)
         test.add_cmd_check_stdout("-c get_rsc_info -r rsc2 ", "id:rsc2 class:ocf provider:pacemaker type:Stateful")
         test.add_cmd("-c unregister_rsc -r rsc2 "+self._action_timeout)
         test.add_cmd_expected_fail("-c get_rsc_info -r rsc2 ")
 
         ### verify the option to only send notification to the original client. ###
         test = self.new_test("notify_orig_client_only", "Verify option to only send notifications to the client originating the action.")
         test.add_cmd("-c register_rsc -r \"test_rsc\" -C \"ocf\" -P \"pacemaker\" -T \"Dummy\" "+self._action_timeout+
                      "-l \"NEW_EVENT event_type:register rsc_id:test_rsc action:none rc:ok op_status:complete\" ")
         test.add_cmd("-c exec -r \"test_rsc\" -a \"start\" "+self._action_timeout+
                      "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:start rc:ok op_status:complete\" ")
         test.add_cmd('-c exec -r \"test_rsc\" -a \"monitor\" -i 1s '
                      + self._action_timeout + ' -n '
                      '-l "NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:ok op_status:complete"')
         # this will fail because the monitor notifications should only go to the original caller, which no longer exists.
         test.add_cmd_expected_fail("-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:ok op_status:complete\" "+self._action_timeout, ExitStatus.TIMEOUT)
         test.add_cmd('-c cancel -r test_rsc -a monitor -i 1s -t 6000 ')
         test.add_cmd("-c unregister_rsc -r \"test_rsc\" "+self._action_timeout+
                      "-l \"NEW_EVENT event_type:unregister rsc_id:test_rsc action:none rc:ok op_status:complete\" ")
 
         ### get metadata ###
         test = self.new_test("get_ocf_metadata", "Retrieve metadata for a resource")
         test.add_cmd_check_stdout("-c metadata -C \"ocf\" -P \"pacemaker\" -T \"Dummy\"",
                                   "resource-agent name=\"Dummy\"")
         test.add_cmd("-c metadata -C \"ocf\" -P \"pacemaker\" -T \"Stateful\"")
         test.add_cmd_expected_fail("-c metadata -P \"pacemaker\" -T \"Stateful\"")
         test.add_cmd_expected_fail("-c metadata -C \"ocf\" -P \"pacemaker\" -T \"fake_agent\"")
 
-        ### get metadata ###
-        test = self.new_test("get_lsb_metadata", "Retrieve metadata for a resource")
-        test.add_cmd_check_stdout("-c metadata -C \"lsb\" -T \"LSBDummy\"",
-                                  "resource-agent name='LSBDummy'")
-
         ### get stonith metadata ###
         test = self.new_test("get_stonith_metadata", "Retrieve stonith metadata for a resource")
         test.add_cmd_check_stdout("-c metadata -C \"stonith\" -P \"pacemaker\" -T \"fence_dummy\"",
                                   "resource-agent name=\"fence_dummy\"")
 
+        # get lsb metadata
+        if "lsb" in self._rsc_classes:
+            test = self.new_test("get_lsb_metadata", "Retrieve metadata for a resource")
+            test.add_cmd_check_stdout("-c metadata -C \"lsb\" -T \"LSBDummy\"",
+                                      "resource-agent name='LSBDummy'")
+
         ### get metadata ###
         if "systemd" in self._rsc_classes:
             test = self.new_test("get_systemd_metadata", "Retrieve metadata for a resource")
             test.add_cmd_check_stdout("-c metadata -C \"systemd\" -T \"pacemaker-cts-dummyd@\"",
                                       "resource-agent name=\"pacemaker-cts-dummyd@\"")
 
         ### get metadata ###
         if "upstart" in self._rsc_classes:
             test = self.new_test("get_upstart_metadata", "Retrieve metadata for a resource")
             test.add_cmd_check_stdout("-c metadata -C \"upstart\" -T \"pacemaker-cts-dummyd\"",
                                       "resource-agent name=\"pacemaker-cts-dummyd\"")
 
         ### get ocf providers  ###
         test = self.new_test("list_ocf_providers",
                              "Retrieve list of available resource providers, verifies pacemaker is a provider.")
         test.add_cmd_check_stdout("-c list_ocf_providers ", "pacemaker")
         test.add_cmd_check_stdout("-c list_ocf_providers -T ping", "pacemaker")
 
         ### Verify agents only exist in their lists ###
         test = self.new_test("verify_agent_lists", "Verify the agent lists contain the right data.")
-        test.add_cmd_check_stdout("-c list_agents ", "Stateful")                                  ### ocf ###
-        test.add_cmd_check_stdout("-c list_agents -C ocf", "Stateful")
-        test.add_cmd_check_stdout("-c list_agents -C lsb", "", "Stateful")                        ### should not exist
-        test.add_cmd_check_stdout("-c list_agents -C service", "", "Stateful")                    ### should not exist
-        test.add_cmd_check_stdout("-c list_agents ", "LSBDummy")                                  ### init.d ###
-        test.add_cmd_check_stdout("-c list_agents -C lsb", "LSBDummy")
-        test.add_cmd_check_stdout("-c list_agents -C service", "LSBDummy")
-        test.add_cmd_check_stdout("-c list_agents -C ocf", "", "pacemaker-cts-dummyd@")           ### should not exist
-
-        test.add_cmd_check_stdout("-c list_agents -C ocf", "", "pacemaker-cts-dummyd@")           ### should not exist
-        test.add_cmd_check_stdout("-c list_agents -C lsb", "", "fence_dummy")                     ### should not exist
-        test.add_cmd_check_stdout("-c list_agents -C service", "", "fence_dummy")                 ### should not exist
-        test.add_cmd_check_stdout("-c list_agents -C ocf", "", "fence_dummy")                     ### should not exist
+
+        if "ocf" in self._rsc_classes:
+            test.add_cmd_check_stdout("-c list_agents ", "Stateful")                                  ### ocf ###
+            test.add_cmd_check_stdout("-c list_agents -C ocf", "Stateful")
+
+        if "service" in self._rsc_classes:
+            test.add_cmd_check_stdout("-c list_agents -C service", "", "Stateful")
+
+        if "lsb" in self._rsc_classes:
+            test.add_cmd_check_stdout("-c list_agents ", "LSBDummy")
+            test.add_cmd_check_stdout("-c list_agents -C lsb", "LSBDummy")
+            test.add_cmd_check_stdout("-c list_agents -C service", "LSBDummy")
 
         if "systemd" in self._rsc_classes:
             test.add_cmd_check_stdout("-c list_agents ", "pacemaker-cts-dummyd@")             ### systemd ###
-            test.add_cmd_check_stdout("-c list_agents -C service", "LSBDummy")
             test.add_cmd_check_stdout("-c list_agents -C systemd", "", "Stateful")            ### should not exist
             test.add_cmd_check_stdout("-c list_agents -C systemd", "pacemaker-cts-dummyd@")
             test.add_cmd_check_stdout("-c list_agents -C systemd", "", "fence_dummy")         ### should not exist
 
         if "upstart" in self._rsc_classes:
             test.add_cmd_check_stdout("-c list_agents ", "pacemaker-cts-dummyd")              ### upstart ###
-            test.add_cmd_check_stdout("-c list_agents -C service", "LSBDummy")
             test.add_cmd_check_stdout("-c list_agents -C upstart", "", "Stateful")            ### should not exist
             test.add_cmd_check_stdout("-c list_agents -C upstart", "pacemaker-cts-dummyd")
             test.add_cmd_check_stdout("-c list_agents -C upstart", "", "fence_dummy")         ### should not exist
 
         if "stonith" in self._rsc_classes:
             test.add_cmd_check_stdout("-c list_agents -C stonith", "fence_dummy")             ### stonith ###
             test.add_cmd_check_stdout("-c list_agents -C stonith", "", "pacemaker-cts-dummyd@") ### should not exist
             test.add_cmd_check_stdout("-c list_agents -C stonith", "", "Stateful")            ### should not exist
             test.add_cmd_check_stdout("-c list_agents ", "fence_dummy")
 
 def build_options():
     parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter,
                                      description="Run pacemaker-execd regression tests",
                                      epilog="Example: Run only the test 'start_stop'\n"
                                             "\t " + sys.argv[0] + " --run-only start_stop\n\n"
                                             "Example: Run only the tests with the string 'systemd' present in them\n"
                                             "\t " + sys.argv[0] + " --run-only-pattern systemd")
     parser.add_argument("-l", "--list-tests", action="store_true",
                         help="Print out all registered tests")
     parser.add_argument("-p", "--run-only-pattern", metavar='PATTERN',
                         help="Run only tests matching the given pattern")
     parser.add_argument("-r", "--run-only", metavar='TEST',
                         help="Run a specific test")
     parser.add_argument("-t", "--timeout", type=float, default=2,
                         help="Up to how many seconds each test case waits for the daemon to "
                              "be initialized.  Defaults to 2.  The value 0 means no limit.")
     parser.add_argument("-w", "--force-wait", action="store_true",
                         help="Each test case waits the default/specified --timeout for the "
                              "daemon without tracking the log")
     if BuildOptions.REMOTE_ENABLED:
         parser.add_argument("-R", "--pacemaker-remote", action="store_true",
                             help="Test pacemaker-remoted binary instead of pacemaker-execd")
     parser.add_argument("-V", "--verbose", action="store_true",
                         help="Verbose output")
 
     args = parser.parse_args()
     return args
 
 
 def main():
     """ Run pacemaker-execd regression tests as specified by arguments """
 
     update_path()
 
     # Ensure all command output is in portable locale for comparison
     os.environ['LC_ALL'] = "C"
 
     opts = build_options()
 
     if opts.pacemaker_remote:
         daemon_name = "pacemaker-remoted"
     else:
         daemon_name = "pacemaker-execd"
 
     exit_if_proc_running(daemon_name)
 
     # Create a temporary directory for log files (the directory will
     # automatically be erased when done)
     with tempfile.TemporaryDirectory(prefix="cts-exec-") as logdir:
         tests = ExecTests(verbose=opts.verbose, tls=opts.pacemaker_remote,
                           timeout=opts.timeout, force_wait=opts.force_wait,
                           logdir=logdir)
 
         tests.build_generic_tests()
         tests.build_multi_rsc_tests()
         tests.build_negative_tests()
         tests.build_custom_tests()
         tests.build_stress_tests()
 
         if opts.list_tests:
             tests.print_list()
             sys.exit(ExitStatus.OK)
 
         print("Starting ...")
 
         tests.setup_environment()
 
         if opts.run_only_pattern:
             tests.run_tests_matching(opts.run_only_pattern)
             tests.print_results()
         elif opts.run_only:
             tests.run_single(opts.run_only)
             tests.print_results()
         else:
             tests.run_tests()
             tests.print_results()
 
         tests.cleanup_environment()
 
     tests.exit()
 
 
 if __name__ == "__main__":
     main()
diff --git a/cts/support/cts-support.in b/cts/support/cts-support.in
index de5b7d85a5..92aef12a81 100644
--- a/cts/support/cts-support.in
+++ b/cts/support/cts-support.in
@@ -1,170 +1,172 @@
 #!/bin/sh
 #
 # Installer for support files needed by Pacemaker's Cluster Test Suite
 #
 # Copyright 2018-2022 the Pacemaker project contributors
 #
 # The version control history for this file may have further details.
 #
 # This source code is licensed under the GNU General Public License version 2
 # or later (GPLv2+) WITHOUT ANY WARRANTY.
 #
 
 USAGE_TEXT="Usage: $0 <install|uninstall|--help>"
 
 HELP_TEXT="$USAGE_TEXT
 Commands (must be run as root):
  install      Install support files needed by Pacemaker CTS
  uninstall    Remove support files needed by Pacemaker CTS"
 
 # These constants must track crm_exit_t values
 CRM_EX_OK=0
 CRM_EX_ERROR=1
 CRM_EX_USAGE=64
 
 UNIT_DIR="@systemdsystemunitdir@"
 RUNTIME_UNIT_DIR="@runstatedir@/systemd/system"
 LIBEXEC_DIR="@libexecdir@/pacemaker"
 INIT_DIR="@INITDIR@"
 PCMK__FENCE_BINDIR="@PCMK__FENCE_BINDIR@"
 DATA_DIR="@datadir@/pacemaker/tests/cts"
 UPSTART_DIR="/etc/init"
 
 DUMMY_DAEMON="pacemaker-cts-dummyd"
 DUMMY_DAEMON_UNIT="pacemaker-cts-dummyd@.service" 
 COROSYNC_RUNTIME_UNIT="corosync.service.d"
 COROSYNC_RUNTIME_CONF="cts.conf"
 
 LSB_DUMMY="LSBDummy"
 UPSTART_DUMMY="pacemaker-cts-dummyd.conf"
 FENCE_DUMMY="fence_dummy"
 FENCE_DUMMY_ALIASES="auto_unfence no_reboot no_on"
 
 # If the install directory doesn't exist, assume we're in a build directory.
 if [ ! -d "$DATA_DIR" ]; then
     # If readlink supports -e (i.e. GNU), use it.
     readlink -e / >/dev/null 2>/dev/null
     if [ $? -eq 0 ]; then
         DATA_DIR="$(dirname "$(readlink -e "$0")")"
     else
         DATA_DIR="$(dirname "$0")"
     fi
 fi
 
 usage() {
     echo "Error:" "$@"
     echo "$USAGE_TEXT"
     exit $CRM_EX_USAGE
 }
 
 must_be_root() {
     if ! [ "$(id -u)" = "0" ]; then
         usage "this command must be run as root"
         return $CRM_EX_ERROR
     fi
     return $CRM_EX_OK
 }
 
 support_uninstall() {
     must_be_root || return $CRM_EX_ERROR
 
     if [ -e "$UNIT_DIR/$DUMMY_DAEMON_UNIT" ]; then
         echo "Removing $UNIT_DIR/$DUMMY_DAEMON_UNIT ..."
         rm -f "$UNIT_DIR/$DUMMY_DAEMON_UNIT"
         systemctl daemon-reload # Ignore failure
     fi
 
     if [ -e "$RUNTIME_UNIT_DIR/$COROSYNC_RUNTIME_UNIT" ]; then
         echo "Removing $RUNTIME_UNIT_DIR/$COROSYNC_RUNTIME_UNIT ..."
         rm -rf "$RUNTIME_UNIT_DIR/$COROSYNC_RUNTIME_UNIT"
         systemctl daemon-reload # Ignore failure
     fi
 
     for FILE in \
         "$LIBEXEC_DIR/$DUMMY_DAEMON" \
         "$UPSTART_DIR/$UPSTART_DUMMY" \
         "$PCMK__FENCE_BINDIR/$FENCE_DUMMY" \
         "$INIT_DIR/$LSB_DUMMY"
     do
         if [ -e "$FILE" ]; then
             echo "Removing $FILE ..."
             rm -f "$FILE"
         fi
     done
     for ALIAS in $FENCE_DUMMY_ALIASES; do \
         FILE="$PCMK__FENCE_BINDIR/fence_dummy_$ALIAS"
         if [ -L "$FILE" ] || [ -e "$FILE" ]; then
             echo "Removing $FILE ..."
             rm -f "$FILE"
         fi
     done
 
     return $CRM_EX_OK
 }
 
 support_install() {
     support_uninstall || return $CRM_EX_ERROR
     cd "$DATA_DIR" || return $CRM_EX_ERROR
     if [ -d "$UNIT_DIR" ]; then
 
         echo "Installing $DUMMY_DAEMON ..."
         mkdir -p "$LIBEXEC_DIR"
         install -m 0755 "$DUMMY_DAEMON" "$LIBEXEC_DIR" || return $CRM_EX_ERROR
 
         echo "Installing $DUMMY_DAEMON_UNIT ..."
         install -m 0644 "$DUMMY_DAEMON_UNIT" "$UNIT_DIR" || return $CRM_EX_ERROR
         systemctl daemon-reload # Ignore failure
     fi
 
     if [ -d "$RUNTIME_UNIT_DIR" ]; then
 
         echo "Installing $COROSYNC_RUNTIME_CONF to $RUNTIME_UNIT_DIR/$COROSYNC_RUNTIME_UNIT ..."
         mkdir -p "$RUNTIME_UNIT_DIR/$COROSYNC_RUNTIME_UNIT"
         install -m 0644 "$COROSYNC_RUNTIME_CONF" "$RUNTIME_UNIT_DIR/$COROSYNC_RUNTIME_UNIT" || return $CRM_EX_ERROR
 
         systemctl daemon-reload # Ignore failure
     fi
 
     echo "Installing $FENCE_DUMMY to $PCMK__FENCE_BINDIR ..."
     mkdir -p "$PCMK__FENCE_BINDIR"
     install -m 0755 "$FENCE_DUMMY" "$PCMK__FENCE_BINDIR" || return $CRM_EX_ERROR
     for alias in $FENCE_DUMMY_ALIASES; do \
         echo "Installing fence_dummy_$alias to $PCMK__FENCE_BINDIR ..."
         ln -s "$FENCE_DUMMY" "$PCMK__FENCE_BINDIR/fence_dummy_$alias"
 	if [ $? -ne 0 ]; then
 		return $CRM_EX_ERROR
 	fi
     done
 
-    echo "Installing $LSB_DUMMY to $INIT_DIR ..."
-    mkdir -p "$INIT_DIR"
-    install -m 0755 "$LSB_DUMMY" "$INIT_DIR" || return $CRM_EX_ERROR
+    if [ -n "$INIT_DIR" ]; then
+        echo "Installing $LSB_DUMMY to $INIT_DIR ..."
+        mkdir -p "$INIT_DIR"
+        install -m 0755 "$LSB_DUMMY" "$INIT_DIR" || return $CRM_EX_ERROR
+    fi
 
     if [ -d "$UPSTART_DIR" ] && [ -f "$UPSTART_DUMMY" ]; then
         echo "Installing $UPSTART_DUMMY to $UPSTART_DIR ..."
         install -m 0644 "$UPSTART_DUMMY" "$UPSTART_DIR" || return $CRM_EX_ERROR
     fi
     return $CRM_EX_OK
 }
 
 COMMAND=""
 while [ $# -gt 0 ] ; do
     case "$1" in
         --help)
             echo "$HELP_TEXT"
             exit $CRM_EX_OK
             ;;
         install|uninstall)
             COMMAND="$1"
             shift
             ;;
         *)
             usage "unknown option '$1'"
             ;;
     esac
 done
 case "$COMMAND" in
     install)   support_install              ;;
     uninstall) support_uninstall            ;;
     *)         usage "must specify command" ;;
 esac
diff --git a/daemons/execd/Makefile.am b/daemons/execd/Makefile.am
index ce0e161268..7d1b2a9e4d 100644
--- a/daemons/execd/Makefile.am
+++ b/daemons/execd/Makefile.am
@@ -1,87 +1,89 @@
 #
 # Copyright 2012-2023 the Pacemaker project contributors
 #
 # The version control history for this file may have further details.
 #
 # This source code is licensed under the GNU Lesser General Public License
 # version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
 #
 
 include $(top_srcdir)/mk/common.mk
 include $(top_srcdir)/mk/man.mk
 
 halibdir		= $(CRM_DAEMON_DIR)
 
 halib_PROGRAMS		= pacemaker-execd \
 			  cts-exec-helper
 
 EXTRA_DIST	= pacemaker-remoted.8.inc
 
 pacemaker_execd_CFLAGS		= $(CFLAGS_HARDENED_EXE)
 pacemaker_execd_LDFLAGS		= $(LDFLAGS_HARDENED_EXE)
 
 pacemaker_execd_LDADD = $(top_builddir)/lib/fencing/libstonithd.la
 pacemaker_execd_LDADD += $(top_builddir)/lib/services/libcrmservice.la
 pacemaker_execd_LDADD += $(top_builddir)/lib/common/libcrmcommon.la
 pacemaker_execd_SOURCES	= pacemaker-execd.c 	\
 			  execd_commands.c 	\
 			  execd_alerts.c
 
 if BUILD_REMOTE
 sbin_PROGRAMS		= pacemaker-remoted
 if BUILD_SYSTEMD
 systemdsystemunit_DATA	= pacemaker_remote.service
 else
+if BUILD_LSB
 initdir			= $(INITDIR)
 init_SCRIPTS		= pacemaker_remote
 endif
+endif
 
 pacemaker_remoted_CPPFLAGS	= -DPCMK__COMPILE_REMOTE \
 				  $(AM_CPPFLAGS)
 
 pacemaker_remoted_CFLAGS	= $(CFLAGS_HARDENED_EXE)
 pacemaker_remoted_LDFLAGS	= $(LDFLAGS_HARDENED_EXE)
 
 pacemaker_remoted_LDADD = $(top_builddir)/lib/fencing/libstonithd.la
 pacemaker_remoted_LDADD	+= $(top_builddir)/lib/services/libcrmservice.la
 pacemaker_remoted_LDADD += $(top_builddir)/lib/cib/libcib.la
 pacemaker_remoted_LDADD += $(top_builddir)/lib/lrmd/liblrmd.la
 pacemaker_remoted_LDADD += $(top_builddir)/lib/common/libcrmcommon.la
 pacemaker_remoted_SOURCES	= $(pacemaker_execd_SOURCES) 	\
 				  remoted_tls.c 		\
 				  remoted_pidone.c 		\
 				  remoted_proxy.c 		\
 				  remoted_schemas.c
 endif
 
 cts_exec_helper_LDADD = $(top_builddir)/lib/pengine/libpe_status.la
 cts_exec_helper_LDADD += $(top_builddir)/lib/cib/libcib.la
 cts_exec_helper_LDADD += $(top_builddir)/lib/lrmd/liblrmd.la
 cts_exec_helper_LDADD += $(top_builddir)/lib/services/libcrmservice.la
 cts_exec_helper_LDADD += $(top_builddir)/lib/common/libcrmcommon.la
 cts_exec_helper_SOURCES	= cts-exec-helper.c
 
 noinst_HEADERS  = pacemaker-execd.h
 
 CLEANFILES = $(man8_MANS)
 
 # Always create a symlink for the old pacemaker_remoted name, so that bundle
 # container images using a current Pacemaker will run on cluster nodes running
 # Pacemaker 1 (>=1.1.17).
 .PHONY: install-exec-hook
 install-exec-hook:
 if BUILD_LEGACY_LINKS
 	cd $(DESTDIR)$(CRM_DAEMON_DIR) && rm -f lrmd && $(LN_S) pacemaker-execd lrmd
 endif
 if BUILD_REMOTE
 	cd $(DESTDIR)$(sbindir) && rm -f pacemaker_remoted && $(LN_S) pacemaker-remoted pacemaker_remoted
 endif
 
 .PHONY: uninstall-hook
 uninstall-hook:
 if BUILD_LEGACY_LINKS
 	cd $(DESTDIR)$(CRM_DAEMON_DIR) && rm -f lrmd
 endif
 if BUILD_REMOTE
 	cd $(DESTDIR)$(sbindir) && rm -f pacemaker_remoted
 endif
diff --git a/daemons/execd/execd_commands.c b/daemons/execd/execd_commands.c
index 6b1ded1b30..462654d080 100644
--- a/daemons/execd/execd_commands.c
+++ b/daemons/execd/execd_commands.c
@@ -1,1966 +1,1970 @@
 /*
  * Copyright 2012-2024 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU Lesser General Public License
  * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
 #include <crm/fencing/internal.h>
 
 #include <glib.h>
 
 // Check whether we have a high-resolution monotonic clock
 #undef PCMK__TIME_USE_CGT
 #if HAVE_DECL_CLOCK_MONOTONIC && defined(CLOCK_MONOTONIC)
 #  define PCMK__TIME_USE_CGT
 #  include <time.h>  /* clock_gettime */
 #endif
 
 #include <unistd.h>
 
 #include <crm/crm.h>
 #include <crm/fencing/internal.h>
 #include <crm/services.h>
 #include <crm/services_internal.h>
 #include <crm/common/mainloop.h>
 #include <crm/common/ipc.h>
 #include <crm/common/ipc_internal.h>
 #include <crm/common/xml.h>
 
 #include "pacemaker-execd.h"
 
 GHashTable *rsc_list = NULL;
 
 typedef struct lrmd_cmd_s {
     int timeout;
     guint interval_ms;
     int start_delay;
     int timeout_orig;
 
     int call_id;
 
     int call_opts;
     /* Timer ids, must be removed on cmd destruction. */
     int delay_id;
     int stonith_recurring_id;
 
     int rsc_deleted;
 
     int service_flags;
 
     char *client_id;
     char *origin;
     char *rsc_id;
     char *action;
     char *real_action;
     char *userdata_str;
 
     pcmk__action_result_t result;
 
     /* We can track operation queue time and run time, to be saved with the CIB
      * resource history (and displayed in cluster status). We need
      * high-resolution monotonic time for this purpose, so we use
      * clock_gettime(CLOCK_MONOTONIC, ...) (if available, otherwise this feature
      * is disabled).
      *
      * However, we also need epoch timestamps for recording the time the command
      * last ran and the time its return value last changed, for use in time
      * displays (as opposed to interval calculations). We keep time_t values for
      * this purpose.
      *
      * The last run time is used for both purposes, so we keep redundant
      * monotonic and epoch values for this. Technically the two could represent
      * different times, but since time_t has only second resolution and the
      * values are used for distinct purposes, that is not significant.
      */
 #ifdef PCMK__TIME_USE_CGT
     /* Recurring and systemd operations may involve more than one executor
      * command per operation, so they need info about the original and the most
      * recent.
      */
     struct timespec t_first_run;    // When op first ran
     struct timespec t_run;          // When op most recently ran
     struct timespec t_first_queue;  // When op was first queued
     struct timespec t_queue;        // When op was most recently queued
 #endif
     time_t epoch_last_run;          // Epoch timestamp of when op last ran
     time_t epoch_rcchange;          // Epoch timestamp of when rc last changed
 
     bool first_notify_sent;
     int last_notify_rc;
     int last_notify_op_status;
     int last_pid;
 
     GHashTable *params;
 } lrmd_cmd_t;
 
 static void cmd_finalize(lrmd_cmd_t * cmd, lrmd_rsc_t * rsc);
 static gboolean execute_resource_action(gpointer user_data);
 static void cancel_all_recurring(lrmd_rsc_t * rsc, const char *client_id);
 
 #ifdef PCMK__TIME_USE_CGT
 
 /*!
  * \internal
  * \brief Check whether a struct timespec has been set
  *
  * \param[in] timespec  Time to check
  *
  * \return true if timespec has been set (i.e. is nonzero), false otherwise
  */
 static inline bool
 time_is_set(const struct timespec *timespec)
 {
     return (timespec != NULL) &&
            ((timespec->tv_sec != 0) || (timespec->tv_nsec != 0));
 }
 
 /*
  * \internal
  * \brief Set a timespec (and its original if unset) to the current time
  *
  * \param[out] t_current  Where to store current time
  * \param[out] t_orig     Where to copy t_current if unset
  */
 static void
 get_current_time(struct timespec *t_current, struct timespec *t_orig)
 {
     clock_gettime(CLOCK_MONOTONIC, t_current);
     if ((t_orig != NULL) && !time_is_set(t_orig)) {
         *t_orig = *t_current;
     }
 }
 
 /*!
  * \internal
  * \brief Return difference between two times in milliseconds
  *
  * \param[in] now  More recent time (or NULL to use current time)
  * \param[in] old  Earlier time
  *
  * \return milliseconds difference (or 0 if old is NULL or unset)
  *
  * \note Can overflow on 32bit machines when the differences is around
  *       24 days or more.
  */
 static int
 time_diff_ms(const struct timespec *now, const struct timespec *old)
 {
     int diff_ms = 0;
 
     if (time_is_set(old)) {
         struct timespec local_now = { 0, };
 
         if (now == NULL) {
             clock_gettime(CLOCK_MONOTONIC, &local_now);
             now = &local_now;
         }
         diff_ms = (now->tv_sec - old->tv_sec) * 1000
                   + (now->tv_nsec - old->tv_nsec) / 1000000;
     }
     return diff_ms;
 }
 
 /*!
  * \internal
  * \brief Reset a command's operation times to their original values.
  *
  * Reset a command's run and queued timestamps to the timestamps of the original
  * command, so we report the entire time since then and not just the time since
  * the most recent command (for recurring and systemd operations).
  *
  * \param[in,out] cmd  Executor command object to reset
  *
  * \note It's not obvious what the queued time should be for a systemd
  *       start/stop operation, which might go like this:
  *         initial command queued 5ms, runs 3s
  *         monitor command queued 10ms, runs 10s
  *         monitor command queued 10ms, runs 10s
  *       Is the queued time for that operation 5ms, 10ms or 25ms? The current
  *       implementation will report 5ms. If it's 25ms, then we need to
  *       subtract 20ms from the total exec time so as not to count it twice.
  *       We can implement that later if it matters to anyone ...
  */
 static void
 cmd_original_times(lrmd_cmd_t * cmd)
 {
     cmd->t_run = cmd->t_first_run;
     cmd->t_queue = cmd->t_first_queue;
 }
 #endif
 
 static inline bool
 action_matches(const lrmd_cmd_t *cmd, const char *action, guint interval_ms)
 {
     return (cmd->interval_ms == interval_ms)
            && pcmk__str_eq(cmd->action, action, pcmk__str_casei);
 }
 
 /*!
  * \internal
  * \brief Log the result of an asynchronous command
  *
  * \param[in] cmd            Command to log result for
  * \param[in] exec_time_ms   Execution time in milliseconds, if known
  * \param[in] queue_time_ms  Queue time in milliseconds, if known
  */
 static void
 log_finished(const lrmd_cmd_t *cmd, int exec_time_ms, int queue_time_ms)
 {
     int log_level = LOG_INFO;
     GString *str = g_string_sized_new(100); // reasonable starting size
 
     if (pcmk__str_eq(cmd->action, PCMK_ACTION_MONITOR, pcmk__str_casei)) {
         log_level = LOG_DEBUG;
     }
 
     g_string_append_printf(str, "%s %s (call %d",
                            cmd->rsc_id, cmd->action, cmd->call_id);
     if (cmd->last_pid != 0) {
         g_string_append_printf(str, ", PID %d", cmd->last_pid);
     }
     if (cmd->result.execution_status == PCMK_EXEC_DONE) {
         g_string_append_printf(str, ") exited with status %d",
                                cmd->result.exit_status);
     } else {
         pcmk__g_strcat(str, ") could not be executed: ",
                        pcmk_exec_status_str(cmd->result.execution_status),
                        NULL);
     }
     if (cmd->result.exit_reason != NULL) {
         pcmk__g_strcat(str, " (", cmd->result.exit_reason, ")", NULL);
     }
 
 #ifdef PCMK__TIME_USE_CGT
     pcmk__g_strcat(str, " (execution time ",
                    pcmk__readable_interval(exec_time_ms), NULL);
     if (queue_time_ms > 0) {
         pcmk__g_strcat(str, " after being queued ",
                        pcmk__readable_interval(queue_time_ms), NULL);
     }
     g_string_append_c(str, ')');
 #endif
 
     do_crm_log(log_level, "%s", str->str);
     g_string_free(str, TRUE);
 }
 
 static void
 log_execute(lrmd_cmd_t * cmd)
 {
     int log_level = LOG_INFO;
 
     if (pcmk__str_eq(cmd->action, PCMK_ACTION_MONITOR, pcmk__str_casei)) {
         log_level = LOG_DEBUG;
     }
 
     do_crm_log(log_level, "executing - rsc:%s action:%s call_id:%d",
                cmd->rsc_id, cmd->action, cmd->call_id);
 }
 
 static const char *
 normalize_action_name(lrmd_rsc_t * rsc, const char *action)
 {
     if (pcmk__str_eq(action, PCMK_ACTION_MONITOR, pcmk__str_casei) &&
         pcmk_is_set(pcmk_get_ra_caps(rsc->class), pcmk_ra_cap_status)) {
         return PCMK_ACTION_STATUS;
     }
     return action;
 }
 
 static lrmd_rsc_t *
 build_rsc_from_xml(xmlNode * msg)
 {
     xmlNode *rsc_xml = get_xpath_object("//" PCMK__XE_LRMD_RSC, msg, LOG_ERR);
     lrmd_rsc_t *rsc = NULL;
 
     rsc = pcmk__assert_alloc(1, sizeof(lrmd_rsc_t));
 
     crm_element_value_int(msg, PCMK__XA_LRMD_CALLOPT, &rsc->call_opts);
 
     rsc->rsc_id = crm_element_value_copy(rsc_xml, PCMK__XA_LRMD_RSC_ID);
     rsc->class = crm_element_value_copy(rsc_xml, PCMK__XA_LRMD_CLASS);
     rsc->provider = crm_element_value_copy(rsc_xml, PCMK__XA_LRMD_PROVIDER);
     rsc->type = crm_element_value_copy(rsc_xml, PCMK__XA_LRMD_TYPE);
     rsc->work = mainloop_add_trigger(G_PRIORITY_HIGH, execute_resource_action,
                                      rsc);
 
     // Initialize fence device probes (to return "not running")
     pcmk__set_result(&rsc->fence_probe_result, CRM_EX_ERROR,
                      PCMK_EXEC_NO_FENCE_DEVICE, NULL);
     return rsc;
 }
 
 static lrmd_cmd_t *
 create_lrmd_cmd(xmlNode *msg, pcmk__client_t *client)
 {
     int call_options = 0;
     xmlNode *rsc_xml = get_xpath_object("//" PCMK__XE_LRMD_RSC, msg, LOG_ERR);
     lrmd_cmd_t *cmd = NULL;
 
     cmd = pcmk__assert_alloc(1, sizeof(lrmd_cmd_t));
 
     crm_element_value_int(msg, PCMK__XA_LRMD_CALLOPT, &call_options);
     cmd->call_opts = call_options;
     cmd->client_id = pcmk__str_copy(client->id);
 
     crm_element_value_int(msg, PCMK__XA_LRMD_CALLID, &cmd->call_id);
     crm_element_value_ms(rsc_xml, PCMK__XA_LRMD_RSC_INTERVAL,
                          &cmd->interval_ms);
     crm_element_value_int(rsc_xml, PCMK__XA_LRMD_TIMEOUT, &cmd->timeout);
     crm_element_value_int(rsc_xml, PCMK__XA_LRMD_RSC_START_DELAY,
                           &cmd->start_delay);
     cmd->timeout_orig = cmd->timeout;
 
     cmd->origin = crm_element_value_copy(rsc_xml, PCMK__XA_LRMD_ORIGIN);
     cmd->action = crm_element_value_copy(rsc_xml, PCMK__XA_LRMD_RSC_ACTION);
     cmd->userdata_str = crm_element_value_copy(rsc_xml,
                                                PCMK__XA_LRMD_RSC_USERDATA_STR);
     cmd->rsc_id = crm_element_value_copy(rsc_xml, PCMK__XA_LRMD_RSC_ID);
 
     cmd->params = xml2list(rsc_xml);
 
     if (pcmk__str_eq(g_hash_table_lookup(cmd->params, "CRM_meta_on_fail"),
                      PCMK_VALUE_BLOCK, pcmk__str_casei)) {
         crm_debug("Setting flag to leave pid group on timeout and "
                   "only kill action pid for " PCMK__OP_FMT,
                   cmd->rsc_id, cmd->action, cmd->interval_ms);
         cmd->service_flags = pcmk__set_flags_as(__func__, __LINE__,
                                                 LOG_TRACE, "Action",
                                                 cmd->action, 0,
                                                 SVC_ACTION_LEAVE_GROUP,
                                                 "SVC_ACTION_LEAVE_GROUP");
     }
     return cmd;
 }
 
 static void
 stop_recurring_timer(lrmd_cmd_t *cmd)
 {
     if (cmd) {
         if (cmd->stonith_recurring_id) {
             g_source_remove(cmd->stonith_recurring_id);
         }
         cmd->stonith_recurring_id = 0;
     }
 }
 
 static void
 free_lrmd_cmd(lrmd_cmd_t * cmd)
 {
     stop_recurring_timer(cmd);
     if (cmd->delay_id) {
         g_source_remove(cmd->delay_id);
     }
     if (cmd->params) {
         g_hash_table_destroy(cmd->params);
     }
     pcmk__reset_result(&(cmd->result));
     free(cmd->origin);
     free(cmd->action);
     free(cmd->real_action);
     free(cmd->userdata_str);
     free(cmd->rsc_id);
     free(cmd->client_id);
     free(cmd);
 }
 
 static gboolean
 stonith_recurring_op_helper(gpointer data)
 {
     lrmd_cmd_t *cmd = data;
     lrmd_rsc_t *rsc;
 
     cmd->stonith_recurring_id = 0;
 
     if (!cmd->rsc_id) {
         return FALSE;
     }
 
     rsc = g_hash_table_lookup(rsc_list, cmd->rsc_id);
 
     CRM_ASSERT(rsc != NULL);
     /* take it out of recurring_ops list, and put it in the pending ops
      * to be executed */
     rsc->recurring_ops = g_list_remove(rsc->recurring_ops, cmd);
     rsc->pending_ops = g_list_append(rsc->pending_ops, cmd);
 #ifdef PCMK__TIME_USE_CGT
     get_current_time(&(cmd->t_queue), &(cmd->t_first_queue));
 #endif
     mainloop_set_trigger(rsc->work);
 
     return FALSE;
 }
 
 static inline void
 start_recurring_timer(lrmd_cmd_t *cmd)
 {
     if (cmd && (cmd->interval_ms > 0)) {
         cmd->stonith_recurring_id = g_timeout_add(cmd->interval_ms,
                                                   stonith_recurring_op_helper,
                                                   cmd);
     }
 }
 
 static gboolean
 start_delay_helper(gpointer data)
 {
     lrmd_cmd_t *cmd = data;
     lrmd_rsc_t *rsc = NULL;
 
     cmd->delay_id = 0;
     rsc = cmd->rsc_id ? g_hash_table_lookup(rsc_list, cmd->rsc_id) : NULL;
 
     if (rsc) {
         mainloop_set_trigger(rsc->work);
     }
 
     return FALSE;
 }
 
 /*!
  * \internal
  * \brief Check whether a list already contains the equivalent of a given action
  *
  * \param[in] action_list  List to search
  * \param[in] cmd          Action to search for
  */
 static lrmd_cmd_t *
 find_duplicate_action(const GList *action_list, const lrmd_cmd_t *cmd)
 {
     for (const GList *item = action_list; item != NULL; item = item->next) {
         lrmd_cmd_t *dup = item->data;
 
         if (action_matches(cmd, dup->action, dup->interval_ms)) {
             return dup;
         }
     }
     return NULL;
 }
 
 static bool
 merge_recurring_duplicate(lrmd_rsc_t * rsc, lrmd_cmd_t * cmd)
 {
     lrmd_cmd_t * dup = NULL;
     bool dup_pending = true;
 
     if (cmd->interval_ms == 0) {
         return false;
     }
 
     // Search for a duplicate of this action (in-flight or not)
     dup = find_duplicate_action(rsc->pending_ops, cmd);
     if (dup == NULL) {
         dup_pending = false;
         dup = find_duplicate_action(rsc->recurring_ops, cmd);
         if (dup == NULL) {
             return false;
         }
     }
 
     /* Do not merge fencing monitors marked for cancellation, so we can reply to
      * the cancellation separately.
      */
     if (pcmk__str_eq(rsc->class, PCMK_RESOURCE_CLASS_STONITH,
                      pcmk__str_casei)
         && (dup->result.execution_status == PCMK_EXEC_CANCELLED)) {
         return false;
     }
 
     /* This should not occur. If it does, we need to investigate how something
      * like this is possible in the controller.
      */
     crm_warn("Duplicate recurring op entry detected (" PCMK__OP_FMT
              "), merging with previous op entry",
              rsc->rsc_id, normalize_action_name(rsc, dup->action),
              dup->interval_ms);
 
     // Merge new action's call ID and user data into existing action
     dup->first_notify_sent = false;
     free(dup->userdata_str);
     dup->userdata_str = cmd->userdata_str;
     cmd->userdata_str = NULL;
     dup->call_id = cmd->call_id;
     free_lrmd_cmd(cmd);
     cmd = NULL;
 
     /* If dup is not pending, that means it has already executed at least once
      * and is waiting in the interval. In that case, stop waiting and initiate
      * a new instance now.
      */
     if (!dup_pending) {
         if (pcmk__str_eq(rsc->class, PCMK_RESOURCE_CLASS_STONITH,
                          pcmk__str_casei)) {
             stop_recurring_timer(dup);
             stonith_recurring_op_helper(dup);
         } else {
             services_action_kick(rsc->rsc_id,
                                  normalize_action_name(rsc, dup->action),
                                  dup->interval_ms);
         }
     }
     return true;
 }
 
 static void
 schedule_lrmd_cmd(lrmd_rsc_t * rsc, lrmd_cmd_t * cmd)
 {
     CRM_CHECK(cmd != NULL, return);
     CRM_CHECK(rsc != NULL, return);
 
     crm_trace("Scheduling %s on %s", cmd->action, rsc->rsc_id);
 
     if (merge_recurring_duplicate(rsc, cmd)) {
         // Equivalent of cmd has already been scheduled
         return;
     }
 
     /* The controller expects the executor to automatically cancel
      * recurring operations before a resource stops.
      */
     if (pcmk__str_eq(cmd->action, PCMK_ACTION_STOP, pcmk__str_casei)) {
         cancel_all_recurring(rsc, NULL);
     }
 
     rsc->pending_ops = g_list_append(rsc->pending_ops, cmd);
 #ifdef PCMK__TIME_USE_CGT
     get_current_time(&(cmd->t_queue), &(cmd->t_first_queue));
 #endif
     mainloop_set_trigger(rsc->work);
 
     if (cmd->start_delay) {
         cmd->delay_id = g_timeout_add(cmd->start_delay, start_delay_helper, cmd);
     }
 }
 
 static xmlNode *
 create_lrmd_reply(const char *origin, int rc, int call_id)
 {
     xmlNode *reply = pcmk__xe_create(NULL, PCMK__XE_LRMD_REPLY);
 
     crm_xml_add(reply, PCMK__XA_LRMD_ORIGIN, origin);
     crm_xml_add_int(reply, PCMK__XA_LRMD_RC, rc);
     crm_xml_add_int(reply, PCMK__XA_LRMD_CALLID, call_id);
     return reply;
 }
 
 static void
 send_client_notify(gpointer key, gpointer value, gpointer user_data)
 {
     xmlNode *update_msg = user_data;
     pcmk__client_t *client = value;
     int rc;
     int log_level = LOG_WARNING;
     const char *msg = NULL;
 
     CRM_CHECK(client != NULL, return);
     if (client->name == NULL) {
         crm_trace("Skipping notification to client without name");
         return;
     }
     if (pcmk_is_set(client->flags, pcmk__client_to_proxy)) {
         /* We only want to notify clients of the executor IPC API. If we are
          * running as Pacemaker Remote, we may have clients proxied to other
          * IPC services in the cluster, so skip those.
          */
         crm_trace("Skipping executor API notification to client %s",
                   pcmk__client_name(client));
         return;
     }
 
     rc = lrmd_server_send_notify(client, update_msg);
     if (rc == pcmk_rc_ok) {
         return;
     }
 
     switch (rc) {
         case ENOTCONN:
         case EPIPE: // Client exited without waiting for notification
             log_level = LOG_INFO;
             msg = "Disconnected";
             break;
 
         default:
             msg = pcmk_rc_str(rc);
             break;
     }
     do_crm_log(log_level, "Could not notify client %s: %s " CRM_XS " rc=%d",
                pcmk__client_name(client), msg, rc);
 }
 
 static void
 send_cmd_complete_notify(lrmd_cmd_t * cmd)
 {
     xmlNode *notify = NULL;
     int exec_time = 0;
     int queue_time = 0;
 
 #ifdef PCMK__TIME_USE_CGT
     exec_time = time_diff_ms(NULL, &(cmd->t_run));
     queue_time = time_diff_ms(&cmd->t_run, &(cmd->t_queue));
 #endif
     log_finished(cmd, exec_time, queue_time);
 
     /* If the originator requested to be notified only for changes in recurring
      * operation results, skip the notification if the result hasn't changed.
      */
     if (cmd->first_notify_sent
         && pcmk_is_set(cmd->call_opts, lrmd_opt_notify_changes_only)
         && (cmd->last_notify_rc == cmd->result.exit_status)
         && (cmd->last_notify_op_status == cmd->result.execution_status)) {
         return;
     }
 
     cmd->first_notify_sent = true;
     cmd->last_notify_rc = cmd->result.exit_status;
     cmd->last_notify_op_status = cmd->result.execution_status;
 
     notify = pcmk__xe_create(NULL, PCMK__XE_LRMD_NOTIFY);
 
     crm_xml_add(notify, PCMK__XA_LRMD_ORIGIN, __func__);
     crm_xml_add_int(notify, PCMK__XA_LRMD_TIMEOUT, cmd->timeout);
     crm_xml_add_ms(notify, PCMK__XA_LRMD_RSC_INTERVAL, cmd->interval_ms);
     crm_xml_add_int(notify, PCMK__XA_LRMD_RSC_START_DELAY, cmd->start_delay);
     crm_xml_add_int(notify, PCMK__XA_LRMD_EXEC_RC, cmd->result.exit_status);
     crm_xml_add_int(notify, PCMK__XA_LRMD_EXEC_OP_STATUS,
                     cmd->result.execution_status);
     crm_xml_add_int(notify, PCMK__XA_LRMD_CALLID, cmd->call_id);
     crm_xml_add_int(notify, PCMK__XA_LRMD_RSC_DELETED, cmd->rsc_deleted);
 
     crm_xml_add_ll(notify, PCMK__XA_LRMD_RUN_TIME,
                    (long long) cmd->epoch_last_run);
     crm_xml_add_ll(notify, PCMK__XA_LRMD_RCCHANGE_TIME,
                    (long long) cmd->epoch_rcchange);
 #ifdef PCMK__TIME_USE_CGT
     crm_xml_add_int(notify, PCMK__XA_LRMD_EXEC_TIME, exec_time);
     crm_xml_add_int(notify, PCMK__XA_LRMD_QUEUE_TIME, queue_time);
 #endif
 
     crm_xml_add(notify, PCMK__XA_LRMD_OP, LRMD_OP_RSC_EXEC);
     crm_xml_add(notify, PCMK__XA_LRMD_RSC_ID, cmd->rsc_id);
     if(cmd->real_action) {
         crm_xml_add(notify, PCMK__XA_LRMD_RSC_ACTION, cmd->real_action);
     } else {
         crm_xml_add(notify, PCMK__XA_LRMD_RSC_ACTION, cmd->action);
     }
     crm_xml_add(notify, PCMK__XA_LRMD_RSC_USERDATA_STR, cmd->userdata_str);
     crm_xml_add(notify, PCMK__XA_LRMD_RSC_EXIT_REASON, cmd->result.exit_reason);
 
     if (cmd->result.action_stderr != NULL) {
         crm_xml_add(notify, PCMK__XA_LRMD_RSC_OUTPUT,
                     cmd->result.action_stderr);
 
     } else if (cmd->result.action_stdout != NULL) {
         crm_xml_add(notify, PCMK__XA_LRMD_RSC_OUTPUT,
                     cmd->result.action_stdout);
     }
 
     if (cmd->params) {
         char *key = NULL;
         char *value = NULL;
         GHashTableIter iter;
 
         xmlNode *args = pcmk__xe_create(notify, PCMK__XE_ATTRIBUTES);
 
         g_hash_table_iter_init(&iter, cmd->params);
         while (g_hash_table_iter_next(&iter, (gpointer *) & key, (gpointer *) & value)) {
             hash2smartfield((gpointer) key, (gpointer) value, args);
         }
     }
     if ((cmd->client_id != NULL)
         && pcmk_is_set(cmd->call_opts, lrmd_opt_notify_orig_only)) {
 
         pcmk__client_t *client = pcmk__find_client_by_id(cmd->client_id);
 
         if (client != NULL) {
             send_client_notify(client->id, client, notify);
         }
     } else {
         pcmk__foreach_ipc_client(send_client_notify, notify);
     }
 
     free_xml(notify);
 }
 
 static void
 send_generic_notify(int rc, xmlNode * request)
 {
     if (pcmk__ipc_client_count() != 0) {
         int call_id = 0;
         xmlNode *notify = NULL;
         xmlNode *rsc_xml = get_xpath_object("//" PCMK__XE_LRMD_RSC, request,
                                             LOG_ERR);
         const char *rsc_id = crm_element_value(rsc_xml, PCMK__XA_LRMD_RSC_ID);
         const char *op = crm_element_value(request, PCMK__XA_LRMD_OP);
 
         crm_element_value_int(request, PCMK__XA_LRMD_CALLID, &call_id);
 
         notify = pcmk__xe_create(NULL, PCMK__XE_LRMD_NOTIFY);
         crm_xml_add(notify, PCMK__XA_LRMD_ORIGIN, __func__);
         crm_xml_add_int(notify, PCMK__XA_LRMD_RC, rc);
         crm_xml_add_int(notify, PCMK__XA_LRMD_CALLID, call_id);
         crm_xml_add(notify, PCMK__XA_LRMD_OP, op);
         crm_xml_add(notify, PCMK__XA_LRMD_RSC_ID, rsc_id);
 
         pcmk__foreach_ipc_client(send_client_notify, notify);
 
         free_xml(notify);
     }
 }
 
 static void
 cmd_reset(lrmd_cmd_t * cmd)
 {
     cmd->last_pid = 0;
 #ifdef PCMK__TIME_USE_CGT
     memset(&cmd->t_run, 0, sizeof(cmd->t_run));
     memset(&cmd->t_queue, 0, sizeof(cmd->t_queue));
 #endif
     cmd->epoch_last_run = 0;
 
     pcmk__reset_result(&(cmd->result));
     cmd->result.execution_status = PCMK_EXEC_DONE;
 }
 
 static void
 cmd_finalize(lrmd_cmd_t * cmd, lrmd_rsc_t * rsc)
 {
     crm_trace("Resource operation rsc:%s action:%s completed (%p %p)", cmd->rsc_id, cmd->action,
               rsc ? rsc->active : NULL, cmd);
 
     if (rsc && (rsc->active == cmd)) {
         rsc->active = NULL;
         mainloop_set_trigger(rsc->work);
     }
 
     if (!rsc) {
         cmd->rsc_deleted = 1;
     }
 
     /* reset original timeout so client notification has correct information */
     cmd->timeout = cmd->timeout_orig;
 
     send_cmd_complete_notify(cmd);
 
     if ((cmd->interval_ms != 0)
         && (cmd->result.execution_status == PCMK_EXEC_CANCELLED)) {
 
         if (rsc) {
             rsc->recurring_ops = g_list_remove(rsc->recurring_ops, cmd);
             rsc->pending_ops = g_list_remove(rsc->pending_ops, cmd);
         }
         free_lrmd_cmd(cmd);
     } else if (cmd->interval_ms == 0) {
         if (rsc) {
             rsc->pending_ops = g_list_remove(rsc->pending_ops, cmd);
         }
         free_lrmd_cmd(cmd);
     } else {
         /* Clear all the values pertaining just to the last iteration of a recurring op. */
         cmd_reset(cmd);
     }
 }
 
 struct notify_new_client_data {
     xmlNode *notify;
     pcmk__client_t *new_client;
 };
 
 static void
 notify_one_client(gpointer key, gpointer value, gpointer user_data)
 {
     pcmk__client_t *client = value;
     struct notify_new_client_data *data = user_data;
 
     if (!pcmk__str_eq(client->id, data->new_client->id, pcmk__str_casei)) {
         send_client_notify(key, (gpointer) client, (gpointer) data->notify);
     }
 }
 
 void
 notify_of_new_client(pcmk__client_t *new_client)
 {
     struct notify_new_client_data data;
 
     data.new_client = new_client;
     data.notify = pcmk__xe_create(NULL, PCMK__XE_LRMD_NOTIFY);
     crm_xml_add(data.notify, PCMK__XA_LRMD_ORIGIN, __func__);
     crm_xml_add(data.notify, PCMK__XA_LRMD_OP, LRMD_OP_NEW_CLIENT);
     pcmk__foreach_ipc_client(notify_one_client, &data);
     free_xml(data.notify);
 }
 
 void
 client_disconnect_cleanup(const char *client_id)
 {
     GHashTableIter iter;
     lrmd_rsc_t *rsc = NULL;
     char *key = NULL;
 
     g_hash_table_iter_init(&iter, rsc_list);
     while (g_hash_table_iter_next(&iter, (gpointer *) & key, (gpointer *) & rsc)) {
         if (pcmk_all_flags_set(rsc->call_opts, lrmd_opt_drop_recurring)) {
             /* This client is disconnecting, drop any recurring operations
              * it may have initiated on the resource */
             cancel_all_recurring(rsc, client_id);
         }
     }
 }
 
 static void
 action_complete(svc_action_t * action)
 {
     lrmd_rsc_t *rsc;
     lrmd_cmd_t *cmd = action->cb_data;
     enum ocf_exitcode code;
 
 #ifdef PCMK__TIME_USE_CGT
     const char *rclass = NULL;
     bool goagain = false;
 #endif
 
     if (!cmd) {
         crm_err("Completed executor action (%s) does not match any known operations",
                 action->id);
         return;
     }
 
 #ifdef PCMK__TIME_USE_CGT
     if (cmd->result.exit_status != action->rc) {
         cmd->epoch_rcchange = time(NULL);
     }
 #endif
 
     cmd->last_pid = action->pid;
 
     // Cast variable instead of function return to keep compilers happy
     code = services_result2ocf(action->standard, cmd->action, action->rc);
     pcmk__set_result(&(cmd->result), (int) code,
                      action->status, services__exit_reason(action));
 
     rsc = cmd->rsc_id ? g_hash_table_lookup(rsc_list, cmd->rsc_id) : NULL;
 
 #ifdef PCMK__TIME_USE_CGT
-    if (rsc && pcmk__str_eq(rsc->class, PCMK_RESOURCE_CLASS_SERVICE, pcmk__str_casei)) {
-        rclass = resources_find_service_class(rsc->type);
-    } else if(rsc) {
+    if (rsc != NULL) {
         rclass = rsc->class;
+#if PCMK__ENABLE_SERVICE
+        if (pcmk__str_eq(rclass, PCMK_RESOURCE_CLASS_SERVICE,
+                         pcmk__str_casei)) {
+            rclass = resources_find_service_class(rsc->type);
+        }
+#endif
     }
 
     if (pcmk__str_eq(rclass, PCMK_RESOURCE_CLASS_SYSTEMD, pcmk__str_casei)) {
         if (pcmk__result_ok(&(cmd->result))
             && pcmk__strcase_any_of(cmd->action, PCMK_ACTION_START,
                                     PCMK_ACTION_STOP, NULL)) {
             /* systemd returns from start and stop actions after the action
              * begins, not after it completes. We have to jump through a few
              * hoops so that we don't report 'complete' to the rest of pacemaker
              * until it's actually done.
              */
             goagain = true;
             cmd->real_action = cmd->action;
             cmd->action = pcmk__str_copy(PCMK_ACTION_MONITOR);
 
         } else if (cmd->real_action != NULL) {
             // This is follow-up monitor to check whether start/stop completed
             if (cmd->result.execution_status == PCMK_EXEC_PENDING) {
                 goagain = true;
 
             } else if (pcmk__result_ok(&(cmd->result))
                        && pcmk__str_eq(cmd->real_action, PCMK_ACTION_STOP,
                                        pcmk__str_casei)) {
                 goagain = true;
 
             } else {
                 int time_sum = time_diff_ms(NULL, &(cmd->t_first_run));
                 int timeout_left = cmd->timeout_orig - time_sum;
 
                 crm_debug("%s systemd %s is now complete (elapsed=%dms, "
                           "remaining=%dms): %s (%d)",
                           cmd->rsc_id, cmd->real_action, time_sum, timeout_left,
                           services_ocf_exitcode_str(cmd->result.exit_status),
                           cmd->result.exit_status);
                 cmd_original_times(cmd);
 
                 // Monitors may return "not running", but start/stop shouldn't
                 if ((cmd->result.execution_status == PCMK_EXEC_DONE)
                     && (cmd->result.exit_status == PCMK_OCF_NOT_RUNNING)) {
 
                     if (pcmk__str_eq(cmd->real_action, PCMK_ACTION_START,
                                      pcmk__str_casei)) {
                         cmd->result.exit_status = PCMK_OCF_UNKNOWN_ERROR;
                     } else if (pcmk__str_eq(cmd->real_action, PCMK_ACTION_STOP,
                                             pcmk__str_casei)) {
                         cmd->result.exit_status = PCMK_OCF_OK;
                     }
                 }
             }
         }
     }
 #endif
 
 #if SUPPORT_NAGIOS
     if (rsc && pcmk__str_eq(rsc->class, PCMK_RESOURCE_CLASS_NAGIOS, pcmk__str_casei)) {
         if (action_matches(cmd, PCMK_ACTION_MONITOR, 0)
             && pcmk__result_ok(&(cmd->result))) {
             /* Successfully executed --version for the nagios plugin */
             cmd->result.exit_status = PCMK_OCF_NOT_RUNNING;
 
         } else if (pcmk__str_eq(cmd->action, PCMK_ACTION_START, pcmk__str_casei)
                    && !pcmk__result_ok(&(cmd->result))) {
 #ifdef PCMK__TIME_USE_CGT
             goagain = true;
 #endif
         }
     }
 #endif
 
 #ifdef PCMK__TIME_USE_CGT
     if (goagain) {
         int time_sum = time_diff_ms(NULL, &(cmd->t_first_run));
         int timeout_left = cmd->timeout_orig - time_sum;
         int delay = cmd->timeout_orig / 10;
 
         if(delay >= timeout_left && timeout_left > 20) {
             delay = timeout_left/2;
         }
 
         delay = QB_MIN(2000, delay);
         if (delay < timeout_left) {
             cmd->start_delay = delay;
             cmd->timeout = timeout_left;
 
             if (pcmk__result_ok(&(cmd->result))) {
                 crm_debug("%s %s may still be in progress: re-scheduling (elapsed=%dms, remaining=%dms, start_delay=%dms)",
                           cmd->rsc_id, cmd->real_action, time_sum, timeout_left, delay);
 
             } else if (cmd->result.execution_status == PCMK_EXEC_PENDING) {
                 crm_info("%s %s is still in progress: re-scheduling (elapsed=%dms, remaining=%dms, start_delay=%dms)",
                          cmd->rsc_id, cmd->action, time_sum, timeout_left, delay);
 
             } else {
                 crm_notice("%s %s failed '%s' (%d): re-scheduling (elapsed=%dms, remaining=%dms, start_delay=%dms)",
                            cmd->rsc_id, cmd->action,
                            services_ocf_exitcode_str(cmd->result.exit_status),
                            cmd->result.exit_status, time_sum, timeout_left,
                            delay);
             }
 
             cmd_reset(cmd);
             if(rsc) {
                 rsc->active = NULL;
             }
             schedule_lrmd_cmd(rsc, cmd);
 
             /* Don't finalize cmd, we're not done with it yet */
             return;
 
         } else {
             crm_notice("Giving up on %s %s (rc=%d): timeout (elapsed=%dms, remaining=%dms)",
                        cmd->rsc_id,
                        (cmd->real_action? cmd->real_action : cmd->action),
                        cmd->result.exit_status, time_sum, timeout_left);
             pcmk__set_result(&(cmd->result), PCMK_OCF_UNKNOWN_ERROR,
                              PCMK_EXEC_TIMEOUT,
                              "Investigate reason for timeout, and adjust "
                              "configured operation timeout if necessary");
             cmd_original_times(cmd);
         }
     }
 #endif
 
     pcmk__set_result_output(&(cmd->result), services__grab_stdout(action),
                             services__grab_stderr(action));
     cmd_finalize(cmd, rsc);
 }
 
 /*!
  * \internal
  * \brief Process the result of a fence device action (start, stop, or monitor)
  *
  * \param[in,out] cmd               Fence device action that completed
  * \param[in]     exit_status       Fencer API exit status for action
  * \param[in]     execution_status  Fencer API execution status for action
  * \param[in]     exit_reason       Human-friendly detail, if action failed
  */
 static void
 stonith_action_complete(lrmd_cmd_t *cmd, int exit_status,
                         enum pcmk_exec_status execution_status,
                         const char *exit_reason)
 {
     // This can be NULL if resource was removed before command completed
     lrmd_rsc_t *rsc = g_hash_table_lookup(rsc_list, cmd->rsc_id);
 
     // Simplify fencer exit status to uniform exit status
     if (exit_status != CRM_EX_OK) {
         exit_status = PCMK_OCF_UNKNOWN_ERROR;
     }
 
     if (cmd->result.execution_status == PCMK_EXEC_CANCELLED) {
         /* An in-flight fence action was cancelled. The execution status is
          * already correct, so don't overwrite it.
          */
         execution_status = PCMK_EXEC_CANCELLED;
 
     } else {
         /* Some execution status codes have specific meanings for the fencer
          * that executor clients may not expect, so map them to a simple error
          * status.
          */
         switch (execution_status) {
             case PCMK_EXEC_NOT_CONNECTED:
             case PCMK_EXEC_INVALID:
                 execution_status = PCMK_EXEC_ERROR;
                 break;
 
             case PCMK_EXEC_NO_FENCE_DEVICE:
                 /* This should be possible only for probes in practice, but
                  * interpret for all actions to be safe.
                  */
                 if (pcmk__str_eq(cmd->action, PCMK_ACTION_MONITOR,
                                  pcmk__str_none)) {
                     exit_status = PCMK_OCF_NOT_RUNNING;
 
                 } else if (pcmk__str_eq(cmd->action, PCMK_ACTION_STOP,
                                         pcmk__str_none)) {
                     exit_status = PCMK_OCF_OK;
 
                 } else {
                     exit_status = PCMK_OCF_NOT_INSTALLED;
                 }
                 execution_status = PCMK_EXEC_ERROR;
                 break;
 
             case PCMK_EXEC_NOT_SUPPORTED:
                 exit_status = PCMK_OCF_UNIMPLEMENT_FEATURE;
                 break;
 
             default:
                 break;
         }
     }
 
     pcmk__set_result(&cmd->result, exit_status, execution_status, exit_reason);
 
     // Certain successful actions change the known state of the resource
     if ((rsc != NULL) && pcmk__result_ok(&(cmd->result))) {
 
         if (pcmk__str_eq(cmd->action, PCMK_ACTION_START, pcmk__str_casei)) {
             pcmk__set_result(&rsc->fence_probe_result, CRM_EX_OK,
                              PCMK_EXEC_DONE, NULL); // "running"
 
         } else if (pcmk__str_eq(cmd->action, PCMK_ACTION_STOP,
                                 pcmk__str_casei)) {
             pcmk__set_result(&rsc->fence_probe_result, CRM_EX_ERROR,
                              PCMK_EXEC_NO_FENCE_DEVICE, NULL); // "not running"
         }
     }
 
     /* The recurring timer should not be running at this point in any case, but
      * as a failsafe, stop it if it is.
      */
     stop_recurring_timer(cmd);
 
     /* Reschedule this command if appropriate. If a recurring command is *not*
      * rescheduled, its status must be PCMK_EXEC_CANCELLED, otherwise it will
      * not be removed from recurring_ops by cmd_finalize().
      */
     if (rsc && (cmd->interval_ms > 0)
         && (cmd->result.execution_status != PCMK_EXEC_CANCELLED)) {
         start_recurring_timer(cmd);
     }
 
     cmd_finalize(cmd, rsc);
 }
 
 static void
 lrmd_stonith_callback(stonith_t * stonith, stonith_callback_data_t * data)
 {
     if ((data == NULL) || (data->userdata == NULL)) {
         crm_err("Ignoring fence action result: "
                 "Invalid callback arguments (bug?)");
     } else {
         stonith_action_complete((lrmd_cmd_t *) data->userdata,
                                 stonith__exit_status(data),
                                 stonith__execution_status(data),
                                 stonith__exit_reason(data));
     }
 }
 
 void
 stonith_connection_failed(void)
 {
     GHashTableIter iter;
     lrmd_rsc_t *rsc = NULL;
 
     crm_warn("Connection to fencer lost (any pending operations for "
              "fence devices will be considered failed)");
 
     g_hash_table_iter_init(&iter, rsc_list);
     while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &rsc)) {
         if (!pcmk__str_eq(rsc->class, PCMK_RESOURCE_CLASS_STONITH,
                           pcmk__str_none)) {
             continue;
         }
 
         /* If we registered this fence device, we don't know whether the
          * fencer still has the registration or not. Cause future probes to
          * return an error until the resource is stopped or started
          * successfully. This is especially important if the controller also
          * went away (possibly due to a cluster layer restart) and won't
          * receive our client notification of any monitors finalized below.
          */
         if (rsc->fence_probe_result.execution_status == PCMK_EXEC_DONE) {
             pcmk__set_result(&rsc->fence_probe_result, CRM_EX_ERROR,
                              PCMK_EXEC_NOT_CONNECTED,
                              "Lost connection to fencer");
         }
 
         // Consider any active, pending, or recurring operations as failed
 
         for (GList *op = rsc->recurring_ops; op != NULL; op = op->next) {
             lrmd_cmd_t *cmd = op->data;
 
             /* This won't free a recurring op but instead restart its timer.
              * If cmd is rsc->active, this will set rsc->active to NULL, so we
              * don't have to worry about finalizing it a second time below.
              */
             stonith_action_complete(cmd,
                                     CRM_EX_ERROR, PCMK_EXEC_NOT_CONNECTED,
                                     "Lost connection to fencer");
         }
 
         if (rsc->active != NULL) {
             rsc->pending_ops = g_list_prepend(rsc->pending_ops, rsc->active);
         }
         while (rsc->pending_ops != NULL) {
             // This will free the op and remove it from rsc->pending_ops
             stonith_action_complete((lrmd_cmd_t *) rsc->pending_ops->data,
                                     CRM_EX_ERROR, PCMK_EXEC_NOT_CONNECTED,
                                     "Lost connection to fencer");
         }
     }
 }
 
 /*!
  * \internal
  * \brief Execute a stonith resource "start" action
  *
  * Start a stonith resource by registering it with the fencer.
  * (Stonith agents don't have a start command.)
  *
  * \param[in,out] stonith_api  Connection to fencer
  * \param[in]     rsc          Stonith resource to start
  * \param[in]     cmd          Start command to execute
  *
  * \return pcmk_ok on success, -errno otherwise
  */
 static int
 execd_stonith_start(stonith_t *stonith_api, const lrmd_rsc_t *rsc,
                     const lrmd_cmd_t *cmd)
 {
     char *key = NULL;
     char *value = NULL;
     stonith_key_value_t *device_params = NULL;
     int rc = pcmk_ok;
 
     // Convert command parameters to stonith API key/values
     if (cmd->params) {
         GHashTableIter iter;
 
         g_hash_table_iter_init(&iter, cmd->params);
         while (g_hash_table_iter_next(&iter, (gpointer *) & key, (gpointer *) & value)) {
             device_params = stonith_key_value_add(device_params, key, value);
         }
     }
 
     /* The fencer will automatically register devices via CIB notifications
      * when the CIB changes, but to avoid a possible race condition between
      * the fencer receiving the notification and the executor requesting that
      * resource, the executor registers the device as well. The fencer knows how
      * to handle duplicate registrations.
      */
     rc = stonith_api->cmds->register_device(stonith_api, st_opt_sync_call,
                                             cmd->rsc_id, rsc->provider,
                                             rsc->type, device_params);
 
     stonith_key_value_freeall(device_params, 1, 1);
     return rc;
 }
 
 /*!
  * \internal
  * \brief Execute a stonith resource "stop" action
  *
  * Stop a stonith resource by unregistering it with the fencer.
  * (Stonith agents don't have a stop command.)
  *
  * \param[in,out] stonith_api  Connection to fencer
  * \param[in]     rsc          Stonith resource to stop
  *
  * \return pcmk_ok on success, -errno otherwise
  */
 static inline int
 execd_stonith_stop(stonith_t *stonith_api, const lrmd_rsc_t *rsc)
 {
     /* @TODO Failure would indicate a problem communicating with fencer;
      * perhaps we should try reconnecting and retrying a few times?
      */
     return stonith_api->cmds->remove_device(stonith_api, st_opt_sync_call,
                                             rsc->rsc_id);
 }
 
 /*!
  * \internal
  * \brief Initiate a stonith resource agent recurring "monitor" action
  *
  * \param[in,out] stonith_api  Connection to fencer
  * \param[in,out] rsc          Stonith resource to monitor
  * \param[in]     cmd          Monitor command being executed
  *
  * \return pcmk_ok if monitor was successfully initiated, -errno otherwise
  */
 static inline int
 execd_stonith_monitor(stonith_t *stonith_api, lrmd_rsc_t *rsc, lrmd_cmd_t *cmd)
 {
     int rc = stonith_api->cmds->monitor(stonith_api, 0, cmd->rsc_id,
                                         cmd->timeout / 1000);
 
     rc = stonith_api->cmds->register_callback(stonith_api, rc, 0, 0, cmd,
                                               "lrmd_stonith_callback",
                                               lrmd_stonith_callback);
     if (rc == TRUE) {
         rsc->active = cmd;
         rc = pcmk_ok;
     } else {
         rc = -pcmk_err_generic;
     }
     return rc;
 }
 
 static void
 execute_stonith_action(lrmd_rsc_t *rsc, lrmd_cmd_t *cmd)
 {
     int rc = 0;
     bool do_monitor = FALSE;
 
     stonith_t *stonith_api = get_stonith_connection();
 
     if (pcmk__str_eq(cmd->action, PCMK_ACTION_MONITOR, pcmk__str_casei)
         && (cmd->interval_ms == 0)) {
         // Probes don't require a fencer connection
         stonith_action_complete(cmd, rsc->fence_probe_result.exit_status,
                                 rsc->fence_probe_result.execution_status,
                                 rsc->fence_probe_result.exit_reason);
         return;
 
     } else if (stonith_api == NULL) {
         stonith_action_complete(cmd, PCMK_OCF_UNKNOWN_ERROR,
                                 PCMK_EXEC_NOT_CONNECTED,
                                 "No connection to fencer");
         return;
 
     } else if (pcmk__str_eq(cmd->action, PCMK_ACTION_START, pcmk__str_casei)) {
         rc = execd_stonith_start(stonith_api, rsc, cmd);
         if (rc == pcmk_ok) {
             do_monitor = TRUE;
         }
 
     } else if (pcmk__str_eq(cmd->action, PCMK_ACTION_STOP, pcmk__str_casei)) {
         rc = execd_stonith_stop(stonith_api, rsc);
 
     } else if (pcmk__str_eq(cmd->action, PCMK_ACTION_MONITOR,
                             pcmk__str_casei)) {
         do_monitor = TRUE;
 
     } else {
         stonith_action_complete(cmd, PCMK_OCF_UNIMPLEMENT_FEATURE,
                                 PCMK_EXEC_ERROR,
                                 "Invalid fence device action (bug?)");
         return;
     }
 
     if (do_monitor) {
         rc = execd_stonith_monitor(stonith_api, rsc, cmd);
         if (rc == pcmk_ok) {
             // Don't clean up yet, we will find out result of the monitor later
             return;
         }
     }
 
     stonith_action_complete(cmd,
                             ((rc == pcmk_ok)? CRM_EX_OK : CRM_EX_ERROR),
                             stonith__legacy2status(rc),
                             ((rc == -pcmk_err_generic)? NULL : pcmk_strerror(rc)));
 }
 
 static void
 execute_nonstonith_action(lrmd_rsc_t *rsc, lrmd_cmd_t *cmd)
 {
     svc_action_t *action = NULL;
     GHashTable *params_copy = NULL;
 
     CRM_ASSERT(rsc);
     CRM_ASSERT(cmd);
 
     crm_trace("Creating action, resource:%s action:%s class:%s provider:%s agent:%s",
               rsc->rsc_id, cmd->action, rsc->class, rsc->provider, rsc->type);
 
 #if SUPPORT_NAGIOS
     /* Recurring operations are cancelled anyway for a stop operation */
     if (pcmk__str_eq(rsc->class, PCMK_RESOURCE_CLASS_NAGIOS, pcmk__str_casei)
         && pcmk__str_eq(cmd->action, PCMK_ACTION_STOP, pcmk__str_casei)) {
 
         cmd->result.exit_status = PCMK_OCF_OK;
         cmd_finalize(cmd, rsc);
         return;
     }
 #endif
 
     params_copy = pcmk__str_table_dup(cmd->params);
 
     action = services__create_resource_action(rsc->rsc_id, rsc->class, rsc->provider,
                                      rsc->type,
                                      normalize_action_name(rsc, cmd->action),
                                      cmd->interval_ms, cmd->timeout,
                                      params_copy, cmd->service_flags);
 
     if (action == NULL) {
         pcmk__set_result(&(cmd->result), PCMK_OCF_UNKNOWN_ERROR,
                          PCMK_EXEC_ERROR, strerror(ENOMEM));
         cmd_finalize(cmd, rsc);
         return;
     }
 
     if (action->rc != PCMK_OCF_UNKNOWN) {
         pcmk__set_result(&(cmd->result), action->rc, action->status,
                          services__exit_reason(action));
         services_action_free(action);
         cmd_finalize(cmd, rsc);
         return;
     }
 
     action->cb_data = cmd;
 
     if (services_action_async(action, action_complete)) {
         /* The services library has taken responsibility for the action. It
          * could be pending, blocked, or merged into a duplicate recurring
          * action, in which case the action callback (action_complete())
          * will be called when the action completes, otherwise the callback has
          * already been called.
          *
          * action_complete() calls cmd_finalize() which can free cmd, so cmd
          * cannot be used here.
          */
     } else {
         /* This is a recurring action that is not being cancelled and could not
          * be initiated. It has been rescheduled, and the action callback
          * (action_complete()) has been called, which in this case has already
          * called cmd_finalize(), which in this case should only reset (not
          * free) cmd.
          */
 
         pcmk__set_result(&(cmd->result), action->rc, action->status,
                          services__exit_reason(action));
         services_action_free(action);
     }
 }
 
 static gboolean
 execute_resource_action(gpointer user_data)
 {
     lrmd_rsc_t *rsc = (lrmd_rsc_t *) user_data;
     lrmd_cmd_t *cmd = NULL;
 
     CRM_CHECK(rsc != NULL, return FALSE);
 
     if (rsc->active) {
         crm_trace("%s is still active", rsc->rsc_id);
         return TRUE;
     }
 
     if (rsc->pending_ops) {
         GList *first = rsc->pending_ops;
 
         cmd = first->data;
         if (cmd->delay_id) {
             crm_trace
                 ("Command %s %s was asked to run too early, waiting for start_delay timeout of %dms",
                  cmd->rsc_id, cmd->action, cmd->start_delay);
             return TRUE;
         }
         rsc->pending_ops = g_list_remove_link(rsc->pending_ops, first);
         g_list_free_1(first);
 
 #ifdef PCMK__TIME_USE_CGT
         get_current_time(&(cmd->t_run), &(cmd->t_first_run));
 #endif
         cmd->epoch_last_run = time(NULL);
     }
 
     if (!cmd) {
         crm_trace("Nothing further to do for %s", rsc->rsc_id);
         return TRUE;
     }
 
     rsc->active = cmd;          /* only one op at a time for a rsc */
     if (cmd->interval_ms) {
         rsc->recurring_ops = g_list_append(rsc->recurring_ops, cmd);
     }
 
     log_execute(cmd);
 
     if (pcmk__str_eq(rsc->class, PCMK_RESOURCE_CLASS_STONITH, pcmk__str_casei)) {
         execute_stonith_action(rsc, cmd);
     } else {
         execute_nonstonith_action(rsc, cmd);
     }
 
     return TRUE;
 }
 
 void
 free_rsc(gpointer data)
 {
     GList *gIter = NULL;
     lrmd_rsc_t *rsc = data;
     int is_stonith = pcmk__str_eq(rsc->class, PCMK_RESOURCE_CLASS_STONITH,
                                   pcmk__str_casei);
 
     gIter = rsc->pending_ops;
     while (gIter != NULL) {
         GList *next = gIter->next;
         lrmd_cmd_t *cmd = gIter->data;
 
         /* command was never executed */
         cmd->result.execution_status = PCMK_EXEC_CANCELLED;
         cmd_finalize(cmd, NULL);
 
         gIter = next;
     }
     /* frees list, but not list elements. */
     g_list_free(rsc->pending_ops);
 
     gIter = rsc->recurring_ops;
     while (gIter != NULL) {
         GList *next = gIter->next;
         lrmd_cmd_t *cmd = gIter->data;
 
         if (is_stonith) {
             cmd->result.execution_status = PCMK_EXEC_CANCELLED;
             /* If a stonith command is in-flight, just mark it as cancelled;
              * it is not safe to finalize/free the cmd until the stonith api
              * says it has either completed or timed out.
              */
             if (rsc->active != cmd) {
                 cmd_finalize(cmd, NULL);
             }
         } else {
             /* This command is already handed off to service library,
              * let service library cancel it and tell us via the callback
              * when it is cancelled. The rsc can be safely destroyed
              * even if we are waiting for the cancel result */
             services_action_cancel(rsc->rsc_id,
                                    normalize_action_name(rsc, cmd->action),
                                    cmd->interval_ms);
         }
 
         gIter = next;
     }
     /* frees list, but not list elements. */
     g_list_free(rsc->recurring_ops);
 
     free(rsc->rsc_id);
     free(rsc->class);
     free(rsc->provider);
     free(rsc->type);
     mainloop_destroy_trigger(rsc->work);
 
     free(rsc);
 }
 
 static int
 process_lrmd_signon(pcmk__client_t *client, xmlNode *request, int call_id,
                     xmlNode **reply)
 {
     int rc = pcmk_ok;
     time_t now = time(NULL);
     const char *protocol_version =
         crm_element_value(request, PCMK__XA_LRMD_PROTOCOL_VERSION);
     const char *start_state = pcmk__env_option(PCMK__ENV_NODE_START_STATE);
 
     if (compare_version(protocol_version, LRMD_COMPATIBLE_PROTOCOL) < 0) {
         crm_err("Cluster API version must be greater than or equal to %s, not %s",
                 LRMD_COMPATIBLE_PROTOCOL, protocol_version);
         rc = -EPROTO;
     }
 
     if (pcmk__xe_attr_is_true(request, PCMK__XA_LRMD_IS_IPC_PROVIDER)) {
 #ifdef PCMK__COMPILE_REMOTE
         if ((client->remote != NULL)
             && pcmk_is_set(client->flags,
                            pcmk__client_tls_handshake_complete)) {
             const char *op = crm_element_value(request, PCMK__XA_LRMD_OP);
 
             // This is a remote connection from a cluster node's controller
             ipc_proxy_add_provider(client);
 
             /* If this was a register operation, also ask for new schema files but
              * only if it's supported by the protocol version.
              */
             if (pcmk__str_eq(op, CRM_OP_REGISTER, pcmk__str_none) &&
                 LRMD_SUPPORTS_SCHEMA_XFER(protocol_version)) {
                 remoted_request_cib_schema_files();
             }
         } else {
             rc = -EACCES;
         }
 #else
         rc = -EPROTONOSUPPORT;
 #endif
     }
 
     *reply = create_lrmd_reply(__func__, rc, call_id);
     crm_xml_add(*reply, PCMK__XA_LRMD_OP, CRM_OP_REGISTER);
     crm_xml_add(*reply, PCMK__XA_LRMD_CLIENTID, client->id);
     crm_xml_add(*reply, PCMK__XA_LRMD_PROTOCOL_VERSION, LRMD_PROTOCOL_VERSION);
     crm_xml_add_ll(*reply, PCMK__XA_UPTIME, now - start_time);
 
     if (start_state) {
         crm_xml_add(*reply, PCMK__XA_NODE_START_STATE, start_state);
     }
 
     return rc;
 }
 
 static int
 process_lrmd_rsc_register(pcmk__client_t *client, uint32_t id, xmlNode *request)
 {
     int rc = pcmk_ok;
     lrmd_rsc_t *rsc = build_rsc_from_xml(request);
     lrmd_rsc_t *dup = g_hash_table_lookup(rsc_list, rsc->rsc_id);
 
     if (dup &&
         pcmk__str_eq(rsc->class, dup->class, pcmk__str_casei) &&
         pcmk__str_eq(rsc->provider, dup->provider, pcmk__str_casei) && pcmk__str_eq(rsc->type, dup->type, pcmk__str_casei)) {
 
         crm_notice("Ignoring duplicate registration of '%s'", rsc->rsc_id);
         free_rsc(rsc);
         return rc;
     }
 
     g_hash_table_replace(rsc_list, rsc->rsc_id, rsc);
     crm_info("Cached agent information for '%s'", rsc->rsc_id);
     return rc;
 }
 
 static xmlNode *
 process_lrmd_get_rsc_info(xmlNode *request, int call_id)
 {
     int rc = pcmk_ok;
     xmlNode *rsc_xml = get_xpath_object("//" PCMK__XE_LRMD_RSC, request,
                                         LOG_ERR);
     const char *rsc_id = crm_element_value(rsc_xml, PCMK__XA_LRMD_RSC_ID);
     xmlNode *reply = NULL;
     lrmd_rsc_t *rsc = NULL;
 
     if (rsc_id == NULL) {
         rc = -ENODEV;
     } else {
         rsc = g_hash_table_lookup(rsc_list, rsc_id);
         if (rsc == NULL) {
             crm_info("Agent information for '%s' not in cache", rsc_id);
             rc = -ENODEV;
         }
     }
 
     reply = create_lrmd_reply(__func__, rc, call_id);
     if (rsc) {
         crm_xml_add(reply, PCMK__XA_LRMD_RSC_ID, rsc->rsc_id);
         crm_xml_add(reply, PCMK__XA_LRMD_CLASS, rsc->class);
         crm_xml_add(reply, PCMK__XA_LRMD_PROVIDER, rsc->provider);
         crm_xml_add(reply, PCMK__XA_LRMD_TYPE, rsc->type);
     }
     return reply;
 }
 
 static int
 process_lrmd_rsc_unregister(pcmk__client_t *client, uint32_t id,
                             xmlNode *request)
 {
     int rc = pcmk_ok;
     lrmd_rsc_t *rsc = NULL;
     xmlNode *rsc_xml = get_xpath_object("//" PCMK__XE_LRMD_RSC, request,
                                         LOG_ERR);
     const char *rsc_id = crm_element_value(rsc_xml, PCMK__XA_LRMD_RSC_ID);
 
     if (!rsc_id) {
         return -ENODEV;
     }
 
     rsc = g_hash_table_lookup(rsc_list, rsc_id);
     if (rsc == NULL) {
         crm_info("Ignoring unregistration of resource '%s', which is not registered",
                  rsc_id);
         return pcmk_ok;
     }
 
     if (rsc->active) {
         /* let the caller know there are still active ops on this rsc to watch for */
         crm_trace("Operation (%p) still in progress for unregistered resource %s",
                   rsc->active, rsc_id);
         rc = -EINPROGRESS;
     }
 
     g_hash_table_remove(rsc_list, rsc_id);
 
     return rc;
 }
 
 static int
 process_lrmd_rsc_exec(pcmk__client_t *client, uint32_t id, xmlNode *request)
 {
     lrmd_rsc_t *rsc = NULL;
     lrmd_cmd_t *cmd = NULL;
     xmlNode *rsc_xml = get_xpath_object("//" PCMK__XE_LRMD_RSC, request,
                                         LOG_ERR);
     const char *rsc_id = crm_element_value(rsc_xml, PCMK__XA_LRMD_RSC_ID);
     int call_id;
 
     if (!rsc_id) {
         return -EINVAL;
     }
     if (!(rsc = g_hash_table_lookup(rsc_list, rsc_id))) {
         crm_info("Resource '%s' not found (%d active resources)",
                  rsc_id, g_hash_table_size(rsc_list));
         return -ENODEV;
     }
 
     cmd = create_lrmd_cmd(request, client);
     call_id = cmd->call_id;
 
     /* Don't reference cmd after handing it off to be scheduled.
      * The cmd could get merged and freed. */
     schedule_lrmd_cmd(rsc, cmd);
 
     return call_id;
 }
 
 static int
 cancel_op(const char *rsc_id, const char *action, guint interval_ms)
 {
     GList *gIter = NULL;
     lrmd_rsc_t *rsc = g_hash_table_lookup(rsc_list, rsc_id);
 
     /* How to cancel an action.
      * 1. Check pending ops list, if it hasn't been handed off
      *    to the service library or stonith recurring list remove
      *    it there and that will stop it.
      * 2. If it isn't in the pending ops list, then it's either a
      *    recurring op in the stonith recurring list, or the service
      *    library's recurring list.  Stop it there
      * 3. If not found in any lists, then this operation has either
      *    been executed already and is not a recurring operation, or
      *    never existed.
      */
     if (!rsc) {
         return -ENODEV;
     }
 
     for (gIter = rsc->pending_ops; gIter != NULL; gIter = gIter->next) {
         lrmd_cmd_t *cmd = gIter->data;
 
         if (action_matches(cmd, action, interval_ms)) {
             cmd->result.execution_status = PCMK_EXEC_CANCELLED;
             cmd_finalize(cmd, rsc);
             return pcmk_ok;
         }
     }
 
     if (pcmk__str_eq(rsc->class, PCMK_RESOURCE_CLASS_STONITH, pcmk__str_casei)) {
         /* The service library does not handle stonith operations.
          * We have to handle recurring stonith operations ourselves. */
         for (gIter = rsc->recurring_ops; gIter != NULL; gIter = gIter->next) {
             lrmd_cmd_t *cmd = gIter->data;
 
             if (action_matches(cmd, action, interval_ms)) {
                 cmd->result.execution_status = PCMK_EXEC_CANCELLED;
                 if (rsc->active != cmd) {
                     cmd_finalize(cmd, rsc);
                 }
                 return pcmk_ok;
             }
         }
     } else if (services_action_cancel(rsc_id,
                                       normalize_action_name(rsc, action),
                                       interval_ms) == TRUE) {
         /* The service library will tell the action_complete callback function
          * this action was cancelled, which will destroy the cmd and remove
          * it from the recurring_op list. Do not do that in this function
          * if the service library says it cancelled it. */
         return pcmk_ok;
     }
 
     return -EOPNOTSUPP;
 }
 
 static void
 cancel_all_recurring(lrmd_rsc_t * rsc, const char *client_id)
 {
     GList *cmd_list = NULL;
     GList *cmd_iter = NULL;
 
     /* Notice a copy of each list is created when concat is called.
      * This prevents odd behavior from occurring when the cmd_list
      * is iterated through later on.  It is possible the cancel_op
      * function may end up modifying the recurring_ops and pending_ops
      * lists.  If we did not copy those lists, our cmd_list iteration
      * could get messed up.*/
     if (rsc->recurring_ops) {
         cmd_list = g_list_concat(cmd_list, g_list_copy(rsc->recurring_ops));
     }
     if (rsc->pending_ops) {
         cmd_list = g_list_concat(cmd_list, g_list_copy(rsc->pending_ops));
     }
     if (!cmd_list) {
         return;
     }
 
     for (cmd_iter = cmd_list; cmd_iter; cmd_iter = cmd_iter->next) {
         lrmd_cmd_t *cmd = cmd_iter->data;
 
         if (cmd->interval_ms == 0) {
             continue;
         }
 
         if (client_id && !pcmk__str_eq(cmd->client_id, client_id, pcmk__str_casei)) {
             continue;
         }
 
         cancel_op(rsc->rsc_id, cmd->action, cmd->interval_ms);
     }
     /* frees only the copied list data, not the cmds */
     g_list_free(cmd_list);
 }
 
 static int
 process_lrmd_rsc_cancel(pcmk__client_t *client, uint32_t id, xmlNode *request)
 {
     xmlNode *rsc_xml = get_xpath_object("//" PCMK__XE_LRMD_RSC, request,
                                         LOG_ERR);
     const char *rsc_id = crm_element_value(rsc_xml, PCMK__XA_LRMD_RSC_ID);
     const char *action = crm_element_value(rsc_xml, PCMK__XA_LRMD_RSC_ACTION);
     guint interval_ms = 0;
 
     crm_element_value_ms(rsc_xml, PCMK__XA_LRMD_RSC_INTERVAL, &interval_ms);
 
     if (!rsc_id || !action) {
         return -EINVAL;
     }
 
     return cancel_op(rsc_id, action, interval_ms);
 }
 
 static void
 add_recurring_op_xml(xmlNode *reply, lrmd_rsc_t *rsc)
 {
     xmlNode *rsc_xml = pcmk__xe_create(reply, PCMK__XE_LRMD_RSC);
 
     crm_xml_add(rsc_xml, PCMK__XA_LRMD_RSC_ID, rsc->rsc_id);
     for (GList *item = rsc->recurring_ops; item != NULL; item = item->next) {
         lrmd_cmd_t *cmd = item->data;
         xmlNode *op_xml = pcmk__xe_create(rsc_xml, PCMK__XE_LRMD_RSC_OP);
 
         crm_xml_add(op_xml, PCMK__XA_LRMD_RSC_ACTION,
                     pcmk__s(cmd->real_action, cmd->action));
         crm_xml_add_ms(op_xml, PCMK__XA_LRMD_RSC_INTERVAL, cmd->interval_ms);
         crm_xml_add_int(op_xml, PCMK__XA_LRMD_TIMEOUT, cmd->timeout_orig);
     }
 }
 
 static xmlNode *
 process_lrmd_get_recurring(xmlNode *request, int call_id)
 {
     int rc = pcmk_ok;
     const char *rsc_id = NULL;
     lrmd_rsc_t *rsc = NULL;
     xmlNode *reply = NULL;
     xmlNode *rsc_xml = NULL;
 
     // Resource ID is optional
     rsc_xml = pcmk__xe_first_child(request, PCMK__XE_LRMD_CALLDATA, NULL, NULL);
     if (rsc_xml) {
         rsc_xml = pcmk__xe_first_child(rsc_xml, PCMK__XE_LRMD_RSC, NULL, NULL);
     }
     if (rsc_xml) {
         rsc_id = crm_element_value(rsc_xml, PCMK__XA_LRMD_RSC_ID);
     }
 
     // If resource ID is specified, resource must exist
     if (rsc_id != NULL) {
         rsc = g_hash_table_lookup(rsc_list, rsc_id);
         if (rsc == NULL) {
             crm_info("Resource '%s' not found (%d active resources)",
                      rsc_id, g_hash_table_size(rsc_list));
             rc = -ENODEV;
         }
     }
 
     reply = create_lrmd_reply(__func__, rc, call_id);
 
     // If resource ID is not specified, check all resources
     if (rsc_id == NULL) {
         GHashTableIter iter;
         char *key = NULL;
 
         g_hash_table_iter_init(&iter, rsc_list);
         while (g_hash_table_iter_next(&iter, (gpointer *) &key,
                                       (gpointer *) &rsc)) {
             add_recurring_op_xml(reply, rsc);
         }
     } else if (rsc) {
         add_recurring_op_xml(reply, rsc);
     }
     return reply;
 }
 
 void
 process_lrmd_message(pcmk__client_t *client, uint32_t id, xmlNode *request)
 {
     int rc = pcmk_ok;
     int call_id = 0;
     const char *op = crm_element_value(request, PCMK__XA_LRMD_OP);
     int do_reply = 0;
     int do_notify = 0;
     xmlNode *reply = NULL;
 
     /* Certain IPC commands may be done only by privileged users (i.e. root or
      * hacluster), because they would otherwise provide a means of bypassing
      * ACLs.
      */
     bool allowed = pcmk_is_set(client->flags, pcmk__client_privileged);
 
     crm_trace("Processing %s operation from %s", op, client->id);
     crm_element_value_int(request, PCMK__XA_LRMD_CALLID, &call_id);
 
     if (pcmk__str_eq(op, CRM_OP_IPC_FWD, pcmk__str_none)) {
 #ifdef PCMK__COMPILE_REMOTE
         if (allowed) {
             ipc_proxy_forward_client(client, request);
         } else {
             rc = -EACCES;
         }
 #else
         rc = -EPROTONOSUPPORT;
 #endif
         do_reply = 1;
     } else if (pcmk__str_eq(op, CRM_OP_REGISTER, pcmk__str_none)) {
         rc = process_lrmd_signon(client, request, call_id, &reply);
         do_reply = 1;
     } else if (pcmk__str_eq(op, LRMD_OP_RSC_REG, pcmk__str_none)) {
         if (allowed) {
             rc = process_lrmd_rsc_register(client, id, request);
             do_notify = 1;
         } else {
             rc = -EACCES;
         }
         do_reply = 1;
     } else if (pcmk__str_eq(op, LRMD_OP_RSC_INFO, pcmk__str_none)) {
         if (allowed) {
             reply = process_lrmd_get_rsc_info(request, call_id);
         } else {
             rc = -EACCES;
         }
         do_reply = 1;
     } else if (pcmk__str_eq(op, LRMD_OP_RSC_UNREG, pcmk__str_none)) {
         if (allowed) {
             rc = process_lrmd_rsc_unregister(client, id, request);
             /* don't notify anyone about failed un-registers */
             if (rc == pcmk_ok || rc == -EINPROGRESS) {
                 do_notify = 1;
             }
         } else {
             rc = -EACCES;
         }
         do_reply = 1;
     } else if (pcmk__str_eq(op, LRMD_OP_RSC_EXEC, pcmk__str_none)) {
         if (allowed) {
             rc = process_lrmd_rsc_exec(client, id, request);
         } else {
             rc = -EACCES;
         }
         do_reply = 1;
     } else if (pcmk__str_eq(op, LRMD_OP_RSC_CANCEL, pcmk__str_none)) {
         if (allowed) {
             rc = process_lrmd_rsc_cancel(client, id, request);
         } else {
             rc = -EACCES;
         }
         do_reply = 1;
     } else if (pcmk__str_eq(op, LRMD_OP_POKE, pcmk__str_none)) {
         do_notify = 1;
         do_reply = 1;
     } else if (pcmk__str_eq(op, LRMD_OP_CHECK, pcmk__str_none)) {
         if (allowed) {
             xmlNode *wrapper = pcmk__xe_first_child(request,
                                                     PCMK__XE_LRMD_CALLDATA,
                                                     NULL, NULL);
             xmlNode *data = pcmk__xe_first_child(wrapper, NULL, NULL, NULL);
 
             const char *timeout = NULL;
 
             CRM_LOG_ASSERT(data != NULL);
             timeout = crm_element_value(data, PCMK__XA_LRMD_WATCHDOG);
             pcmk__valid_stonith_watchdog_timeout(timeout);
         } else {
             rc = -EACCES;
         }
     } else if (pcmk__str_eq(op, LRMD_OP_ALERT_EXEC, pcmk__str_none)) {
         if (allowed) {
             rc = process_lrmd_alert_exec(client, id, request);
         } else {
             rc = -EACCES;
         }
         do_reply = 1;
     } else if (pcmk__str_eq(op, LRMD_OP_GET_RECURRING, pcmk__str_none)) {
         if (allowed) {
             reply = process_lrmd_get_recurring(request, call_id);
         } else {
             rc = -EACCES;
         }
         do_reply = 1;
     } else {
         rc = -EOPNOTSUPP;
         do_reply = 1;
         crm_err("Unknown IPC request '%s' from client %s",
                 op, pcmk__client_name(client));
     }
 
     if (rc == -EACCES) {
         crm_warn("Rejecting IPC request '%s' from unprivileged client %s",
                  op, pcmk__client_name(client));
     }
 
     crm_debug("Processed %s operation from %s: rc=%d, reply=%d, notify=%d",
               op, client->id, rc, do_reply, do_notify);
 
     if (do_reply) {
         int send_rc = pcmk_rc_ok;
 
         if (reply == NULL) {
             reply = create_lrmd_reply(__func__, rc, call_id);
         }
         send_rc = lrmd_server_send_reply(client, id, reply);
         free_xml(reply);
         if (send_rc != pcmk_rc_ok) {
             crm_warn("Reply to client %s failed: %s " CRM_XS " rc=%d",
                      pcmk__client_name(client), pcmk_rc_str(send_rc), send_rc);
         }
     }
 
     if (do_notify) {
         send_generic_notify(rc, request);
     }
 }
diff --git a/doc/sphinx/Pacemaker_Explained/resources.rst b/doc/sphinx/Pacemaker_Explained/resources.rst
index 99bd84f175..26449d3f85 100644
--- a/doc/sphinx/Pacemaker_Explained/resources.rst
+++ b/doc/sphinx/Pacemaker_Explained/resources.rst
@@ -1,794 +1,800 @@
 .. _resource:
 
 Cluster Resources
 -----------------
 
 .. _s-resource-primitive:
 
 What is a Cluster Resource?
 ###########################
 
 .. index::
    single: resource
 
 A *resource* is a service managed by Pacemaker. The simplest type of resource,
 a *primitive*, is described in this chapter. More complex forms, such as groups
 and clones, are described in later chapters.
 
 Every primitive has a *resource agent* that provides Pacemaker a standardized
 interface for managing the service. This allows Pacemaker to be agnostic about
 the services it manages. Pacemaker doesn't need to understand how the service
 works because it relies on the resource agent to do the right thing when asked.
 
-Every resource has a *class* specifying the standard that its resource agent
-follows, and a *type* identifying the specific service being managed.
+Every resource has a *standard* (also called *class*) specifying the interface
+that its resource agent follows, and a *type* identifying the specific service
+being managed.
 
 
 .. _s-resource-supported:
 
 .. index::
-   single: resource; class
+   single: resource; standard
  
-Resource Classes
-################
+Resource Standards
+##################
 
-Pacemaker supports several classes, or standards, of resource agents:
+Pacemaker can use resource agents complying with these standards, described in
+more detail below:
 
-* OCF
-* LSB
-* Systemd
-* Service
-* Fencing
-* Nagios *(deprecated since 2.1.6)*
-* Upstart *(deprecated since 2.1.0)*
+* ocf
+* lsb
+* systemd
+* service
+* stonithd
+* nagios *(deprecated since 2.1.6)*
+* upstart *(deprecated since 2.1.0)*
 
+Support for some standards is controlled by build options and so might not be
+available in any particular build of Pacemaker. The command ``crm_resource
+--list-standards`` will show which standards are supported by the local build.
 
 .. index::
    single: resource; OCF
    single: OCF; resources
    single: Open Cluster Framework; resources
 
 Open Cluster Framework
 ______________________
 
 The Open Cluster Framework (OCF) Resource Agent API is a ClusterLabs
 standard for managing services. It is the most preferred since it is
 specifically designed for use in a Pacemaker cluster.
 
 OCF agents are scripts that support a variety of actions including ``start``,
 ``stop``, and ``monitor``. They may accept parameters, making them more
-flexible than other classes. The number and purpose of parameters is left to
+flexible than other standards. The number and purpose of parameters is left to
 the agent, which advertises them via the ``meta-data`` action.
 
-Unlike other classes, OCF agents have a *provider* as well as a class and type.
+Unlike other standards, OCF agents have a *provider* as well as a standard and
+type.
 
 For more information, see the "Resource Agents" chapter of *Pacemaker
 Administration* and the `OCF standard
 <https://github.com/ClusterLabs/OCF-spec/tree/main/ra>`_.
 
 
 .. _s-resource-supported-systemd:
 
 .. index::
    single: Resource; Systemd
    single: Systemd; resources
 
 Systemd
 _______
 
 Most Linux distributions use `Systemd
 <http://www.freedesktop.org/wiki/Software/systemd>`_ for system initialization
 and service management. *Unit files* specify how to manage services and are
 usually provided by the distribution.
 
 Pacemaker can manage systemd services. Simply create a resource with
-``systemd`` as the resource class and the unit file name as the resource type.
-Do *not* run ``systemctl enable`` on the unit.
+``systemd`` as the resource standard and the unit file name as the resource
+type. Do *not* run ``systemctl enable`` on the unit.
 
 .. important::
 
    Make sure that any systemd services to be controlled by the cluster are
    *not* enabled to start at boot.
 
 
 .. index::
    single: resource; LSB
    single: LSB; resources
    single: Linux Standard Base; resources
 
 Linux Standard Base
 ___________________
 
 *LSB* resource agents, also known as `SysV-style
 <https://en.wikipedia.org/wiki/Init#SysV-style init scripts>`_, are scripts that
 provide start, stop, and status actions for a service.
 
 They are provided by some operating system distributions. If a full path is not
 given, they are assumed to be located in a directory specified when your
 Pacemaker software was built (usually ``/etc/init.d``).
 
 In order to be used with Pacemaker, they must conform to the `LSB specification
 <http://refspecs.linux-foundation.org/LSB_5.0.0/LSB-Core-generic/LSB-Core-generic/iniscrptact.html>`_
 as it relates to init scripts.
 
 .. warning::
 
    Some LSB scripts do not fully comply with the standard. For details on how
    to check whether your script is LSB-compatible, see the "Resource Agents"
    chapter of `Pacemaker Administration`. Common problems include:
 
    * Not implementing the ``status`` action
    * Not observing the correct exit status codes
    * Starting a started resource returns an error
    * Stopping a stopped resource returns an error
 
 .. important::
 
    Make sure the host is *not* configured to start any LSB services at boot
    that will be controlled by the cluster.
 
 
 .. index::
    single: Resource; System Services
    single: System Service; resources
 
 System Services
 _______________
 
 Since there are various types of system services (``systemd``,
 ``upstart``, and ``lsb``), Pacemaker supports a special ``service`` alias which
 intelligently figures out which one applies to a given cluster node.
 
 This is particularly useful when the cluster contains a mix of
 ``systemd``, ``upstart``, and ``lsb``.
 
 In order, Pacemaker will try to find the named service as:
 
 * an LSB init script
 * a Systemd unit file
 * an Upstart job
 
 
 .. index::
    single: Resource; STONITH
    single: STONITH; resources
 
 STONITH
 _______
 
-The ``stonith`` class is used for managing fencing devices, discussed later in
-:ref:`fencing`.
+The ``stonith`` standard is used for managing fencing devices, discussed later
+in :ref:`fencing`.
 
 
 .. index::
    single: Resource; Nagios Plugins
    single: Nagios Plugins; resources
 
 Nagios Plugins
 ______________
 
 Nagios Plugins are a way to monitor services. Pacemaker can use these as
 resources, to react to a change in the service's status.
 
 To use plugins as resources, Pacemaker must have been built with support, and
 OCF-style meta-data for the plugins must be installed on nodes that can run
 them. Meta-data for several common plugins is provided by the
 `nagios-agents-metadata <https://github.com/ClusterLabs/nagios-agents-metadata>`_
 project.
 
 The supported parameters for such a resource are same as the long options of
 the plugin.
 
 Start and monitor actions for plugin resources are implemented as invoking the
 plugin. A plugin result of "OK" (0) is treated as success, a result of "WARN"
 (1) is treated as a successful but degraded service, and any other result is
 considered a failure.
 
 A plugin resource is not going to change its status after recovery by
 restarting the plugin, so using them alone does not make sense with ``on-fail``
 set (or left to default) to ``restart``. Another value could make sense, for
 example, if you want to fence or standby nodes that cannot reach some external
 service.
 
 A more common use case for plugin resources is to configure them with a
 ``container`` meta-attribute set to the name of another resource that actually
 makes the service available, such as a virtual machine or container.
 
 With ``container`` set, the plugin resource will automatically be colocated
 with the containing resource and ordered after it, and the containing resource
 will be considered failed if the plugin resource fails. This allows monitoring
 of a service inside a virtual machine or container, with recovery of the
 virtual machine or container if the service fails.
 
 .. warning::
 
    Nagios support is deprecated in Pacemaker. Support will be dropped entirely
    at the next major release of Pacemaker.
 
    For monitoring a service inside a virtual machine or container, the
    recommended alternative is to configure the virtual machine as a guest node
    or the container as a :ref:`bundle <s-resource-bundle>`. For other use
    cases, or when the virtual machine or container image cannot be modified,
    the recommended alternative is to write a custom OCF agent for the service
    (which may even call the Nagios plugin as part of its status action).
 
 
 .. index::
    single: Resource; Upstart
    single: Upstart; resources
 
 Upstart
 _______
 
 Some Linux distributions previously used `Upstart
 <https://upstart.ubuntu.com/>`_ for system initialization and service
 management. Pacemaker is able to manage services using Upstart if the local
 system supports them and support was enabled when your Pacemaker software was
 built.
 
 The *jobs* that specify how services are managed are usually provided by the
 operating system distribution.
 
 .. important::
 
    Make sure the host is *not* configured to start any Upstart services at boot
    that will be controlled by the cluster.
 
 .. warning::
 
    Upstart support is deprecated in Pacemaker. Upstart is no longer actively
    maintained, and test platforms for it are no longer readily usable. Support
    will be dropped entirely at the next major release of Pacemaker.
 
 
 .. _primitive-resource:
 
 Resource Properties
 ###################
 
 These values tell the cluster which resource agent to use for the resource,
 where to find that resource agent and what standards it conforms to.
 
 .. table:: **Properties of a Primitive Resource**
    :widths: 1 4
 
    +-------------+------------------------------------------------------------------+
    | Field       | Description                                                      |
    +=============+==================================================================+
    | id          | .. index::                                                       |
    |             |    single: id; resource                                          |
    |             |    single: resource; property, id                                |
    |             |                                                                  |
    |             | Your name for the resource                                       |
    +-------------+------------------------------------------------------------------+
    | class       | .. index::                                                       |
    |             |    single: class; resource                                       |
    |             |    single: resource; property, class                             |
    |             |                                                                  |
    |             | The standard the resource agent conforms to. Allowed values:     |
    |             | ``lsb``, ``ocf``, ``service``, ``stonith``, ``systemd``,         |
    |             | ``nagios`` *(deprecated since 2.1.6)*, and ``upstart``           |
    |             | *(deprecated since 2.1.0)*                                       |
    +-------------+------------------------------------------------------------------+
    | description | .. index::                                                       |
    |             |    single: description; resource                                 |
    |             |    single: resource; property, description                       |
    |             |                                                                  |
    |             | A description of the Resource Agent, intended for local use.     |
    |             | E.g. ``IP address for website``                                  |
    +-------------+------------------------------------------------------------------+
    | type        | .. index::                                                       |
    |             |    single: type; resource                                        |
    |             |    single: resource; property, type                              |
    |             |                                                                  |
    |             | The name of the Resource Agent you wish to use. E.g.             |
    |             | ``IPaddr`` or ``Filesystem``                                     |
    +-------------+------------------------------------------------------------------+
    | provider    | .. index::                                                       |
    |             |    single: provider; resource                                    |
    |             |    single: resource; property, provider                          |
    |             |                                                                  |
    |             | The OCF spec allows multiple vendors to supply the same resource |
    |             | agent. To use the OCF resource agents supplied by the Heartbeat  |
    |             | project, you would specify ``heartbeat`` here.                   |
    +-------------+------------------------------------------------------------------+
 
 The XML definition of a resource can be queried with the **crm_resource** tool.
 For example:
 
 .. code-block:: none
 
    # crm_resource --resource Email --query-xml
 
 might produce:
 
 .. topic:: A system resource definition
 
    .. code-block:: xml
 
       <primitive id="Email" class="service" type="exim"/>
 
 .. note::
 
    One of the main drawbacks to system services (LSB, systemd or
    Upstart) resources is that they do not allow any parameters!
 
 .. topic:: An OCF resource definition
 
    .. code-block:: xml
 
       <primitive id="Public-IP" class="ocf" type="IPaddr" provider="heartbeat">
          <instance_attributes id="Public-IP-params">
             <nvpair id="Public-IP-ip" name="ip" value="192.0.2.2"/>
          </instance_attributes>
       </primitive>
 
 .. _resource_options:
 
 Resource Options
 ################
 
 Resources have two types of options: *meta-attributes* and *instance attributes*.
 Meta-attributes apply to any type of resource, while instance attributes
 are specific to each resource agent.
 
 Resource Meta-Attributes
 ________________________
 
 Meta-attributes are used by the cluster to decide how a resource should
 behave and can be easily set using the ``--meta`` option of the
 **crm_resource** command.
 
 .. list-table:: **Meta-attributes of a Primitive Resource**
    :class: longtable
    :widths: 2 2 3 5
    :header-rows: 1
 
    * - Name
      - Type
      - Default
      - Description
 
    * - .. _meta_priority:
        
        .. index::
           single: priority; resource option
           single: resource; option, priority
 
        priority
      - :ref:`score <score>`
      - 0
      - If not all resources can be active, the cluster will stop lower-priority
        resources in order to keep higher-priority ones active.
 
    * - .. _meta_critical:
        
        .. index::
           single: critical; resource option
           single: resource; option, critical
 
        critical
      - :ref:`boolean <boolean>`
      - true
      - Use this value as the default for ``influence`` in all
        :ref:`colocation constraints <s-resource-colocation>` involving this
        resource, as well as in the implicit colocation constraints created if
        this resource is in a :ref:`group <group-resources>`. For details, see
        :ref:`s-coloc-influence`. *(since 2.1.0)*
 
    * - .. _meta_target_role:
        
        .. index::
           single: target-role; resource option
           single: resource; option, target-role
 
        target-role
      - :ref:`enumeration <enumeration>`
      - Started
      - What state should the cluster attempt to keep this resource in? Allowed
        values:
 
        * ``Stopped:`` Force the resource to be stopped
        * ``Started:`` Allow the resource to be started (and in the case of
          :ref:`promotable <s-resource-promotable>` clone resources, promoted if
          appropriate)
        * ``Unpromoted:`` Allow the resource to be started, but only in the
          unpromoted role if the resource is
          :ref:`promotable <s-resource-promotable>`
        * ``Promoted:`` Equivalent to ``Started``
 
    * - .. _meta_is_managed:
        .. _is_managed:
        
        .. index::
           single: is-managed; resource option
           single: resource; option, is-managed
 
        is-managed
      - :ref:`boolean <boolean>`
      - true
      - If false, the cluster will not start, stop, promote, or demote the
        resource on any node. Recurring actions for the resource are
        unaffected. Maintenance mode overrides this setting.
 
    * - .. _meta_maintenance:
        .. _rsc_maintenance:
        
        .. index::
           single: maintenance; resource option
           single: resource; option, maintenance
 
        maintenance
      - :ref:`boolean <boolean>`
      - false
      - If true, the cluster will not start, stop, promote, or demote the
        resource on any node, and will pause any recurring monitors (except those
        specifying ``role`` as ``Stopped``). If true, the
        :ref:`maintenance-mode <maintenance_mode>` cluster option or
        :ref:`maintenance <node_maintenance>` node attribute overrides this.
 
    * - .. _meta_resource_stickiness:
        .. _resource-stickiness:
        
        .. index::
           single: resource-stickiness; resource option
           single: resource; option, resource-stickiness
 
        resource-stickiness
      - :ref:`score <score>`
      - 1 for individual clone instances, 0 for all other resources
      - A score that will be added to the current node when a resource is already
        active. This allows running resources to stay where they are, even if
        they would be placed elsewhere if they were being started from a stopped
        state.
 
    * - .. _meta_requires:
        .. _requires:
        
        .. index::
           single: requires; resource option
           single: resource; option, requires
 
        requires
      - :ref:`enumeration <enumeration>`
      - ``quorum`` for resources with a ``class`` of ``stonith``, otherwise
        ``unfencing`` if unfencing is active in the cluster, otherwise
        ``fencing`` if ``stonith-enabled`` is true, otherwise ``quorum``
      - Conditions under which the resource can be started. Allowed values:
 
        * ``nothing:`` The cluster can always start this resource.
        * ``quorum:`` The cluster can start this resource only if a majority of
          the configured nodes are active.
        * ``fencing:`` The cluster can start this resource only if a majority of
          the configured nodes are active *and* any failed or unknown nodes have
          been :ref:`fenced <fencing>`.
        * ``unfencing:`` The cluster can only start this resource if a majority
          of the configured nodes are active *and* any failed or unknown nodes
          have been fenced *and* only on nodes that have been
          :ref:`unfenced <unfencing>`.
 
    * - .. _meta_migration_threshold:
        
        .. index::
           single: migration-threshold; resource option
           single: resource; option, migration-threshold
 
        migration-threshold
      - :ref:`score <score>`
      - INFINITY
      - How many failures may occur for this resource on a node, before this node
        is marked ineligible to host this resource. A value of 0 indicates that
        this feature is disabled (the node will never be marked ineligible); by
        contrast, the cluster treats ``INFINITY`` (the default) as a very large
        but finite number. This option has an effect only if the failed operation
        specifies ``on-fail`` as ``restart`` (the default), and additionally for 
        failed ``start`` operations, if the cluster property
        ``start-failure-is-fatal`` is ``false``.
 
    * - .. _meta_failure_timeout:
        
        .. index::
           single: failure-timeout; resource option
           single: resource; option, failure-timeout
 
        failure-timeout
      - :ref:`duration <duration>`
      - 0
      - How many seconds to wait before acting as if the failure had not
        occurred, and potentially allowing the resource back to the node on which
        it failed. A value of 0 indicates that this feature is disabled.
 
    * - .. _meta_multiple_active:
        
        .. index::
           single: multiple-active; resource option
           single: resource; option, multiple-active
 
        multiple-active
      - :ref:`enumeration <enumeration>`
      - stop_start
      - What should the cluster do if it ever finds the resource active on more
        than one node? Allowed values:
 
        * ``block``: mark the resource as unmanaged
        * ``stop_only``: stop all active instances and leave them that way
        * ``stop_start``: stop all active instances and start the resource in one
          location only
        * ``stop_unexpected``: stop all active instances except where the
          resource should be active (this should be used only when extra
          instances are not expected to disrupt existing instances, and the
          resource agent's monitor of an existing instance is capable of
          detecting any problems that could be caused; note that any resources
          ordered after this will still need to be restarted) *(since 2.1.3)*
 
    * - .. _meta_allow_migrate:
        
        .. index::
           single: allow-migrate; resource option
           single: resource; option, allow-migrate
 
        allow-migrate
      - :ref:`boolean <boolean>`
      - true for ``ocf:pacemaker:remote`` resources, false otherwise
      - Whether the cluster should try to "live migrate" this resource when it
        needs to be moved (see :ref:`live-migration`)
 
    * - .. _meta_allow_unhealthy_nodes:
        
        .. index::
           single: allow-unhealthy-nodes; resource option
           single: resource; option, allow-unhealthy-nodes
 
        allow-unhealthy-nodes
      - :ref:`boolean <boolean>`
      - false
      - Whether the resource should be able to run on a node even if the node's
        health score would otherwise prevent it (see :ref:`node-health`) *(since
        2.1.3)*
 
    * - .. _meta_container_attribute_target:
        
        .. index::
           single: container-attribute-target; resource option
           single: resource; option, container-attribute-target
 
        container-attribute-target
      - :ref:`enumeration <enumeration>`
      -
      - Specific to bundle resources; see :ref:`s-bundle-attributes`
 
    * - .. _meta_remote_node:
        
        .. index::
           single: remote-node; resource option
           single: resource; option, remote-node
 
        remote-node
      - :ref:`text <text>`
      -
      - The name of the Pacemaker Remote guest node this resource is associated
        with, if any. If specified, this both enables the resource as a guest
        node and defines the unique name used to identify the guest node. The
        guest must be configured to run the Pacemaker Remote daemon when it is
        started. **WARNING:** This value cannot overlap with any resource or node
        IDs.
 
    * - .. _meta_remote_addr:
        
        .. index::
           single: remote-addr; resource option
           single: resource; option, remote-addr
 
        remote-addr
      - :ref:`text <text>`
      - value of ``remote-node``
      - If ``remote-node`` is specified, the IP address or hostname used to
        connect to the guest via Pacemaker Remote. The Pacemaker Remote daemon on
        the guest must be configured to accept connections on this address.
 
    * - .. _meta_remote_port:
        
        .. index::
           single: remote-port; resource option
           single: resource; option, remote-port
 
        remote-port
      - :ref:`port <port>`
      - 3121
      - If ``remote-node`` is specified, the port on the guest used for its
        Pacemaker Remote connection. The Pacemaker Remote daemon on the guest
        must be configured to listen on this port.
 
    * - .. _meta_remote_connect_timeout:
        
        .. index::
           single: remote-connect-timeout; resource option
           single: resource; option, remote-connect-timeout
 
        remote-connect-timeout
      - :ref:`timeout <timeout>`
      - 60s
      - If ``remote-node`` is specified, how long before a pending guest
        connection will time out.
 
    * - .. _meta_remote_allow_migrate:
 
        .. index::
           single: remote-allow-migrate; resource option
           single: resource; option, remote-allow-migrate
 
        remote-allow-migrate
      - :ref:`boolean <boolean>`
      - true
      - If ``remote-node`` is specified, this acts as the ``allow-migrate``
        meta-attribute for the implicit remote connection resource
        (``ocf:pacemaker:remote``).
 
 
 As an example of setting resource options, if you performed the following
 commands on an LSB Email resource:
 
 .. code-block:: none
 
    # crm_resource --meta --resource Email --set-parameter priority --parameter-value 100
    # crm_resource -m -r Email -p multiple-active -v block
 
 the resulting resource definition might be:
 
 .. topic:: An LSB resource with cluster options
 
    .. code-block:: xml
 
       <primitive id="Email" class="lsb" type="exim">
         <meta_attributes id="Email-meta_attributes">
           <nvpair id="Email-meta_attributes-priority" name="priority" value="100"/>
           <nvpair id="Email-meta_attributes-multiple-active" name="multiple-active" value="block"/>
         </meta_attributes>
       </primitive>
 
 In addition to the cluster-defined meta-attributes described above, you may
 also configure arbitrary meta-attributes of your own choosing. Most commonly,
 this would be done for use in :ref:`rules <rules>`. For example, an IT department
 might define a custom meta-attribute to indicate which company department each
 resource is intended for. To reduce the chance of name collisions with
 cluster-defined meta-attributes added in the future, it is recommended to use
 a unique, organization-specific prefix for such attributes.
 
 .. _s-resource-defaults:
 
 Setting Global Defaults for Resource Meta-Attributes
 ____________________________________________________
 
 To set a default value for a resource option, add it to the
 ``rsc_defaults`` section with ``crm_attribute``. For example,
 
 .. code-block:: none
 
    # crm_attribute --type rsc_defaults --name is-managed --update false
 
 would prevent the cluster from starting or stopping any of the
 resources in the configuration (unless of course the individual
 resources were specifically enabled by having their ``is-managed`` set to
 ``true``).
 
 Resource Instance Attributes
 ____________________________
 
-The resource agents of some resource classes (lsb, systemd and upstart *not* among them)
-can be given parameters which determine how they behave and which instance
-of a service they control.
+The resource agents of some resource standards (lsb, systemd and upstart *not*
+among them) can be given parameters which determine how they behave and which
+instance of a service they control.
 
 If your resource agent supports parameters, you can add them with the
 ``crm_resource`` command. For example,
 
 .. code-block:: none
 
    # crm_resource --resource Public-IP --set-parameter ip --parameter-value 192.0.2.2
 
 would create an entry in the resource like this:
 
 .. topic:: An example OCF resource with instance attributes
 
    .. code-block:: xml
 
       <primitive id="Public-IP" class="ocf" type="IPaddr" provider="heartbeat">
          <instance_attributes id="params-public-ip">
             <nvpair id="public-ip-addr" name="ip" value="192.0.2.2"/>
          </instance_attributes>
       </primitive>
 
 For an OCF resource, the result would be an environment variable
 called ``OCF_RESKEY_ip`` with a value of ``192.0.2.2``.
 
 The list of instance attributes supported by an OCF resource agent can be
 found by calling the resource agent with the ``meta-data`` command.
 The output contains an XML description of all the supported
 attributes, their purpose and default values.
 
 .. topic:: Displaying the metadata for the Dummy resource agent template
 
    .. code-block:: none
 
       # export OCF_ROOT=/usr/lib/ocf
       # $OCF_ROOT/resource.d/pacemaker/Dummy meta-data
 
    .. code-block:: xml
 
       <?xml version="1.0"?>
       <!DOCTYPE resource-agent SYSTEM "ra-api-1.dtd">
       <resource-agent name="Dummy" version="2.0">
       <version>1.1</version>
 
       <longdesc lang="en">
       This is a dummy OCF resource agent. It does absolutely nothing except keep track
       of whether it is running or not, and can be configured so that actions fail or
       take a long time. Its purpose is primarily for testing, and to serve as a
       template for resource agent writers.
       </longdesc>
       <shortdesc lang="en">Example stateless resource agent</shortdesc>
 
       <parameters>
       <parameter name="state" unique-group="state">
       <longdesc lang="en">
       Location to store the resource state in.
       </longdesc>
       <shortdesc lang="en">State file</shortdesc>
       <content type="string" default="/var/run/Dummy-RESOURCE_ID.state" />
       </parameter>
 
       <parameter name="passwd" reloadable="1">
       <longdesc lang="en">
       Fake password field
       </longdesc>
       <shortdesc lang="en">Password</shortdesc>
       <content type="string" default="" />
       </parameter>
 
       <parameter name="fake" reloadable="1">
       <longdesc lang="en">
       Fake attribute that can be changed to cause a reload
       </longdesc>
       <shortdesc lang="en">Fake attribute that can be changed to cause a reload</shortdesc>
       <content type="string" default="dummy" />
       </parameter>
 
       <parameter name="op_sleep" reloadable="1">
       <longdesc lang="en">
       Number of seconds to sleep during operations.  This can be used to test how
       the cluster reacts to operation timeouts.
       </longdesc>
       <shortdesc lang="en">Operation sleep duration in seconds.</shortdesc>
       <content type="string" default="0" />
       </parameter>
 
       <parameter name="fail_start_on" reloadable="1">
       <longdesc lang="en">
       Start, migrate_from, and reload-agent actions will return failure if running on
       the host specified here, but the resource will run successfully anyway (future
       monitor calls will find it running). This can be used to test on-fail=ignore.
       </longdesc>
       <shortdesc lang="en">Report bogus start failure on specified host</shortdesc>
       <content type="string" default="" />
       </parameter>
       <parameter name="envfile" reloadable="1">
       <longdesc lang="en">
       If this is set, the environment will be dumped to this file for every call.
       </longdesc>
       <shortdesc lang="en">Environment dump file</shortdesc>
       <content type="string" default="" />
       </parameter>
 
       </parameters>
 
       <actions>
       <action name="start"        timeout="20s" />
       <action name="stop"         timeout="20s" />
       <action name="monitor"      timeout="20s" interval="10s" depth="0"/>
       <action name="reload"       timeout="20s" />
       <action name="reload-agent" timeout="20s" />
       <action name="migrate_to"   timeout="20s" />
       <action name="migrate_from" timeout="20s" />
       <action name="validate-all" timeout="20s" />
       <action name="meta-data"    timeout="5s" />
       </actions>
       </resource-agent>
diff --git a/etc/Makefile.am b/etc/Makefile.am
index b90bb50db6..db0213bbc7 100644
--- a/etc/Makefile.am
+++ b/etc/Makefile.am
@@ -1,41 +1,43 @@
 #
-# Copyright 2021-2023 the Pacemaker project contributors
+# Copyright 2021-2024 the Pacemaker project contributors
 #
 # The version control history for this file may have further details.
 #
 # This source code is licensed under the GNU General Public License version 2
 # or later (GPLv2+) WITHOUT ANY WARRANTY.
 #
 
 MAINTAINERCLEANFILES    = Makefile.in
 
 configdir		= @CONFIGDIR@
 CONFIGS			= crm_mon 	\
 			  pacemaker
 
 if !BUILD_SYSTEMD
+if BUILD_LSB
 initdir			= $(INITDIR)
 init_SCRIPTS		= init.d/pacemaker
 endif
+endif
 
 logrotatedir		= $(sysconfdir)/logrotate.d
 logrotate_DATA		= logrotate.d/pacemaker
 
 EXTRA_DIST		= $(foreach f,$(CONFIGS),sysconfig/$(f))
 
 # Don't overwrite user's existing config files
 .PHONY: install-data-local
 install-data-local:
 	$(AM_V_at)$(MKDIR_P) $(DESTDIR)$(configdir)
 	$(AM_V_at)for f in $(CONFIGS); do				\
 		dest="$(DESTDIR)$(configdir)/$$f";			\
 		[ -e "$$dest" ] && dest="$$dest.new";			\
 		$(INSTALL_DATA) "$(srcdir)/sysconfig/$$f" "$$dest";	\
 	done
 
 .PHONY: uninstall-local
 uninstall-local:
 	$(AM_V_at)for f in $(CONFIGS); do		\
 		dest="$(DESTDIR)$(configdir)/$$f";	\
 		rm -f "$$dest" "$$dest.new";		\
 	done
diff --git a/include/crm/common/agents.h b/include/crm/common/agents.h
index 7d7733f681..56ffaee4a6 100644
--- a/include/crm/common/agents.h
+++ b/include/crm/common/agents.h
@@ -1,83 +1,84 @@
 /*
  * Copyright 2017-2024 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU Lesser General Public License
  * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
  */
 
 #ifndef PCMK__CRM_COMMON_AGENTS__H
 #define PCMK__CRM_COMMON_AGENTS__H
 
 #ifdef __cplusplus
 extern "C" {
 #endif
 
 /**
  * \file
  * \brief API related to resource agents
  * \ingroup core
  */
 
 #include <stdint.h>       // uint32_t
 #include <stdbool.h>
 
-// Known resource classes
+// Known (not necessarily supported) resource classes
 #define PCMK_RESOURCE_CLASS_OCF     "ocf"
 #define PCMK_RESOURCE_CLASS_SERVICE "service"
 #define PCMK_RESOURCE_CLASS_LSB     "lsb"
 #define PCMK_RESOURCE_CLASS_SYSTEMD "systemd"
 #define PCMK_RESOURCE_CLASS_STONITH "stonith"
 #define PCMK_RESOURCE_CLASS_ALERT   "alert"
 //! \deprecated Do not use
 #define PCMK_RESOURCE_CLASS_NAGIOS  "nagios"
 //! \deprecated Do not use
 #define PCMK_RESOURCE_CLASS_UPSTART "upstart"
 
 /* Special stonith-class agent parameters interpreted directly by Pacemaker
  * (not including the pcmk_ACTION_{action,retries,timeout} parameters)
  */
 #define PCMK_STONITH_ACTION_LIMIT       "pcmk_action_limit"
 #define PCMK_STONITH_DELAY_BASE         "pcmk_delay_base"
 #define PCMK_STONITH_DELAY_MAX          "pcmk_delay_max"
 #define PCMK_STONITH_HOST_ARGUMENT      "pcmk_host_argument"
 #define PCMK_STONITH_HOST_CHECK         "pcmk_host_check"
 #define PCMK_STONITH_HOST_LIST          "pcmk_host_list"
 #define PCMK_STONITH_HOST_MAP           "pcmk_host_map"
 #define PCMK_STONITH_PROVIDES           "provides"
 #define PCMK_STONITH_STONITH_TIMEOUT    "stonith-timeout"
 
 // OCF Resource Agent API standard version that this Pacemaker supports
 #define PCMK_OCF_MAJOR_VERSION "1"
 #define PCMK_OCF_MINOR_VERSION "1"
 #define PCMK_OCF_VERSION       PCMK_OCF_MAJOR_VERSION "." PCMK_OCF_MINOR_VERSION
 
 // Capabilities supported by a resource agent standard
 enum pcmk_ra_caps {
-    pcmk_ra_cap_none         = 0,
-    pcmk_ra_cap_provider     = (1 << 0), // Requires provider
-    pcmk_ra_cap_status       = (1 << 1), // Supports status instead of monitor
-    pcmk_ra_cap_params       = (1 << 2), // Supports parameters
-    pcmk_ra_cap_unique       = (1 << 3), // Supports unique clones
-    pcmk_ra_cap_promotable   = (1 << 4), // Supports promotable clones
-    pcmk_ra_cap_stdin        = (1 << 5), // Reads from standard input
-    pcmk_ra_cap_fence_params = (1 << 6), // Supports pcmk_monitor_timeout, etc.
+    pcmk_ra_cap_none         = 0U,
+    pcmk_ra_cap_provider     = (1U << 0), // Requires provider
+    pcmk_ra_cap_status       = (1U << 1), // Supports status instead of monitor
+    pcmk_ra_cap_params       = (1U << 2), // Supports parameters
+    pcmk_ra_cap_unique       = (1U << 3), // Supports unique clones
+    pcmk_ra_cap_promotable   = (1U << 4), // Supports promotable clones
+    pcmk_ra_cap_stdin        = (1U << 5), // Reads from standard input
+    pcmk_ra_cap_fence_params = (1U << 6), // Supports pcmk_monitor_timeout, etc.
+    pcmk_ra_cap_cli_exec     = (1U << 7), // Supports execution by crm_resource
 };
 
 uint32_t pcmk_get_ra_caps(const char *standard);
 char *crm_generate_ra_key(const char *standard, const char *provider,
                           const char *type);
 int crm_parse_agent_spec(const char *spec, char **standard, char **provider,
                          char **type);
 bool pcmk_stonith_param(const char *param);
 
 #if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1)
 #include <crm/common/agents_compat.h>
 #endif
 
 #ifdef __cplusplus
 }
 #endif
 
 #endif // PCMK__CRM_COMMON_AGENTS__H
diff --git a/lib/common/agents.c b/lib/common/agents.c
index dd4ba2e56d..12da2d47d2 100644
--- a/lib/common/agents.c
+++ b/lib/common/agents.c
@@ -1,214 +1,213 @@
 /*
  * Copyright 2004-2021 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU Lesser General Public License
  * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
 
 #ifndef _GNU_SOURCE
 #  define _GNU_SOURCE
 #endif
 
 #include <stdio.h>
 #include <string.h>
 #include <strings.h>
 
 #include <crm/crm.h>
 #include <crm/common/util.h>
 
 /*!
  * \brief Get capabilities of a resource agent standard
  *
  * \param[in] standard  Standard name
  *
  * \return Bitmask of enum pcmk_ra_caps values
  */
 uint32_t
 pcmk_get_ra_caps(const char *standard)
 {
     /* @COMPAT This should probably be case-sensitive, but isn't,
      * for backward compatibility.
      */
     if (standard == NULL) {
         return pcmk_ra_cap_none;
 
     } else if (!strcasecmp(standard, PCMK_RESOURCE_CLASS_OCF)) {
         return pcmk_ra_cap_provider | pcmk_ra_cap_params
-               | pcmk_ra_cap_unique | pcmk_ra_cap_promotable;
+               | pcmk_ra_cap_unique | pcmk_ra_cap_promotable
+               | pcmk_ra_cap_cli_exec;
 
     } else if (!strcasecmp(standard, PCMK_RESOURCE_CLASS_STONITH)) {
         /* @COMPAT Stonith resources can't really be unique clones, but we've
          * allowed it in the past and have it in some scheduler regression tests
          * (which were likely never used as real configurations).
          *
          * @TODO Remove pcmk_ra_cap_unique at the next major schema version
          * bump, with a transform to remove PCMK_META_GLOBALLY_UNIQUE from the
          * config.
          */
         return pcmk_ra_cap_params | pcmk_ra_cap_unique | pcmk_ra_cap_stdin
                | pcmk_ra_cap_fence_params;
 
+    } else if (!strcasecmp(standard, PCMK_RESOURCE_CLASS_LSB)) {
+        return pcmk_ra_cap_status | pcmk_ra_cap_cli_exec;
+
     } else if (!strcasecmp(standard, PCMK_RESOURCE_CLASS_SYSTEMD)
                || !strcasecmp(standard, PCMK_RESOURCE_CLASS_SERVICE)
-               || !strcasecmp(standard, PCMK_RESOURCE_CLASS_LSB)
                || !strcasecmp(standard, PCMK_RESOURCE_CLASS_UPSTART)) {
-
-        /* Since service can map to LSB, systemd, or upstart, these should
-         * have identical capabilities
-         */
         return pcmk_ra_cap_status;
 
     } else if (!strcasecmp(standard, PCMK_RESOURCE_CLASS_NAGIOS)) {
         return pcmk_ra_cap_params;
     }
     return pcmk_ra_cap_none;
 }
 
 int
 pcmk__effective_rc(int rc)
 {
     int remapped_rc = rc;
 
     switch (rc) {
         case PCMK_OCF_DEGRADED:
             remapped_rc = PCMK_OCF_OK;
             break;
 
         case PCMK_OCF_DEGRADED_PROMOTED:
             remapped_rc = PCMK_OCF_RUNNING_PROMOTED;
             break;
 
         default:
             break;
     }
 
     return remapped_rc;
 }
 
 char *
 crm_generate_ra_key(const char *standard, const char *provider,
                     const char *type)
 {
     bool std_empty = pcmk__str_empty(standard);
     bool prov_empty = pcmk__str_empty(provider);
     bool ty_empty = pcmk__str_empty(type);
 
     if (std_empty || ty_empty) {
         return NULL;
     }
 
     return crm_strdup_printf("%s%s%s:%s",
                              standard,
                              (prov_empty ? "" : ":"), (prov_empty ? "" : provider),
                              type);
 }
 
 /*!
  * \brief Parse a "standard[:provider]:type" agent specification
  *
  * \param[in]  spec      Agent specification
  * \param[out] standard  Newly allocated memory containing agent standard (or NULL)
  * \param[out] provider  Newly allocated memory containing agent provider (or NULL)
  * \param[put] type      Newly allocated memory containing agent type (or NULL)
  *
  * \return pcmk_ok if the string could be parsed, -EINVAL otherwise
  *
  * \note It is acceptable for the type to contain a ':' if the standard supports
  *       that. For example, systemd supports the form "systemd:UNIT@A:B".
  * \note It is the caller's responsibility to free the returned values.
  */
 int
 crm_parse_agent_spec(const char *spec, char **standard, char **provider,
                      char **type)
 {
     char *colon;
 
     CRM_CHECK(spec && standard && provider && type, return -EINVAL);
     *standard = NULL;
     *provider = NULL;
     *type = NULL;
 
     colon = strchr(spec, ':');
     if ((colon == NULL) || (colon == spec)) {
         return -EINVAL;
     }
 
     *standard = strndup(spec, colon - spec);
     spec = colon + 1;
 
     if (pcmk_is_set(pcmk_get_ra_caps(*standard), pcmk_ra_cap_provider)) {
         colon = strchr(spec, ':');
         if ((colon == NULL) || (colon == spec)) {
             free(*standard);
             return -EINVAL;
         }
         *provider = strndup(spec, colon - spec);
         spec = colon + 1;
     }
 
     if (*spec == '\0') {
         free(*standard);
         free(*provider);
         return -EINVAL;
     }
 
     *type = strdup(spec);
     return pcmk_ok;
 }
 
 /*!
  * \brief Check whether a given stonith parameter is handled by Pacemaker
  *
  * Return true if a given string is the name of one of the special resource
  * instance attributes interpreted directly by Pacemaker for stonith-class
  * resources.
  *
  * \param[in] param  Parameter name to check
  *
  * \return true if \p param is a special fencing parameter
  */
 bool
 pcmk_stonith_param(const char *param)
 {
     if (param == NULL) {
         return false;
     }
     if (pcmk__str_any_of(param, PCMK_STONITH_PROVIDES,
                          PCMK_STONITH_STONITH_TIMEOUT, NULL)) {
         return true;
     }
     if (!pcmk__starts_with(param, "pcmk_")) { // Short-circuit common case
         return false;
     }
     if (pcmk__str_any_of(param,
                          PCMK_STONITH_ACTION_LIMIT,
                          PCMK_STONITH_DELAY_BASE,
                          PCMK_STONITH_DELAY_MAX,
                          PCMK_STONITH_HOST_ARGUMENT,
                          PCMK_STONITH_HOST_CHECK,
                          PCMK_STONITH_HOST_LIST,
                          PCMK_STONITH_HOST_MAP,
                          NULL)) {
         return true;
     }
     param = strchr(param + 5, '_'); // Skip past "pcmk_ACTION"
     return pcmk__str_any_of(param, "_action", "_timeout", "_retries", NULL);
 }
 
 // Deprecated functions kept only for backward API compatibility
 // LCOV_EXCL_START
 
 #include <crm/common/agents_compat.h>
 
 bool
 crm_provider_required(const char *standard)
 {
     return pcmk_is_set(pcmk_get_ra_caps(standard), pcmk_ra_cap_provider);
 }
 
 // LCOV_EXCL_STOP
 // End deprecated API
diff --git a/lib/common/tests/agents/pcmk_get_ra_caps_test.c b/lib/common/tests/agents/pcmk_get_ra_caps_test.c
index 178dce5ffb..3756fc1b30 100644
--- a/lib/common/tests/agents/pcmk_get_ra_caps_test.c
+++ b/lib/common/tests/agents/pcmk_get_ra_caps_test.c
@@ -1,63 +1,73 @@
 /*
- * Copyright 2022 the Pacemaker project contributors
+ * Copyright 2022-2024 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU General Public License version 2
  * or later (GPLv2+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
 
 #include <crm/common/unittest_internal.h>
 #include <crm/common/agents.h>
 
 static void
-ocf_standard(void **state) {
-    uint32_t expected = pcmk_ra_cap_provider | pcmk_ra_cap_params |
-                        pcmk_ra_cap_unique | pcmk_ra_cap_promotable;
+ocf_standard(void **state)
+{
+    uint32_t expected = pcmk_ra_cap_provider | pcmk_ra_cap_params
+                        | pcmk_ra_cap_unique | pcmk_ra_cap_promotable
+                        | pcmk_ra_cap_cli_exec;
 
     assert_int_equal(pcmk_get_ra_caps("ocf"), expected);
     assert_int_equal(pcmk_get_ra_caps("OCF"), expected);
 }
 
 static void
 stonith_standard(void **state) {
     uint32_t expected = pcmk_ra_cap_params | pcmk_ra_cap_unique |
                         pcmk_ra_cap_stdin | pcmk_ra_cap_fence_params;
 
     assert_int_equal(pcmk_get_ra_caps("stonith"), expected);
     assert_int_equal(pcmk_get_ra_caps("StOnItH"), expected);
 }
 
 static void
 service_standard(void **state) {
     assert_int_equal(pcmk_get_ra_caps("systemd"), pcmk_ra_cap_status);
     assert_int_equal(pcmk_get_ra_caps("SYSTEMD"), pcmk_ra_cap_status);
     assert_int_equal(pcmk_get_ra_caps("service"), pcmk_ra_cap_status);
     assert_int_equal(pcmk_get_ra_caps("SeRvIcE"), pcmk_ra_cap_status);
-    assert_int_equal(pcmk_get_ra_caps("lsb"), pcmk_ra_cap_status);
-    assert_int_equal(pcmk_get_ra_caps("LSB"), pcmk_ra_cap_status);
     assert_int_equal(pcmk_get_ra_caps("upstart"), pcmk_ra_cap_status);
     assert_int_equal(pcmk_get_ra_caps("uPsTaRt"), pcmk_ra_cap_status);
 }
 
+static void
+lsb_standard(void **state)
+{
+    uint32_t expected = pcmk_ra_cap_status | pcmk_ra_cap_cli_exec;
+
+    assert_int_equal(pcmk_get_ra_caps("lsb"), expected);
+    assert_int_equal(pcmk_get_ra_caps("LSB"), expected);
+}
+
 static void
 nagios_standard(void **state) {
     assert_int_equal(pcmk_get_ra_caps("nagios"), pcmk_ra_cap_params);
     assert_int_equal(pcmk_get_ra_caps("NAGios"), pcmk_ra_cap_params);
 }
 
 static void
 unknown_standard(void **state) {
     assert_int_equal(pcmk_get_ra_caps("blahblah"), pcmk_ra_cap_none);
     assert_int_equal(pcmk_get_ra_caps(""), pcmk_ra_cap_none);
     assert_int_equal(pcmk_get_ra_caps(NULL), pcmk_ra_cap_none);
 }
 
 PCMK__UNIT_TEST(NULL, NULL,
                 cmocka_unit_test(ocf_standard),
                 cmocka_unit_test(stonith_standard),
                 cmocka_unit_test(service_standard),
+                cmocka_unit_test(lsb_standard),
                 cmocka_unit_test(nagios_standard),
                 cmocka_unit_test(unknown_standard))
diff --git a/lib/services/Makefile.am b/lib/services/Makefile.am
index 69c8a2cb73..aa630c7802 100644
--- a/lib/services/Makefile.am
+++ b/lib/services/Makefile.am
@@ -1,42 +1,44 @@
 #
-# Copyright 2012-2023 the Pacemaker project contributors
+# Copyright 2012-2024 the Pacemaker project contributors
 #
 # The version control history for this file may have further details.
 #
 # This source code is licensed under the GNU Lesser General Public License
 # version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
 #
 
 MAINTAINERCLEANFILES = Makefile.in
 
 AM_CPPFLAGS			= -I$(top_srcdir)/include
 
 lib_LTLIBRARIES			= libcrmservice.la
 noinst_HEADERS			= $(wildcard *.h)
 
 libcrmservice_la_LDFLAGS	= -version-info 32:1:4
 libcrmservice_la_CFLAGS		=
 
 libcrmservice_la_CFLAGS		+= $(CFLAGS_HARDENED_LIB)
 libcrmservice_la_LDFLAGS	+= $(LDFLAGS_HARDENED_LIB)
 
 libcrmservice_la_LIBADD		= $(top_builddir)/lib/common/libcrmcommon.la \
 				  $(DBUS_LIBS)
 
 ## Library sources (*must* use += format for bumplibs)
 libcrmservice_la_SOURCES	= services.c
 libcrmservice_la_SOURCES	+= services_linux.c
-libcrmservice_la_SOURCES	+= services_lsb.c
 libcrmservice_la_SOURCES	+= services_ocf.c
+if BUILD_LSB
+libcrmservice_la_SOURCES	+= services_lsb.c
+endif
 if BUILD_DBUS
 libcrmservice_la_SOURCES	+= dbus.c
 endif
 if BUILD_UPSTART
 libcrmservice_la_SOURCES	+= upstart.c
 endif
 if BUILD_SYSTEMD
 libcrmservice_la_SOURCES	+= systemd.c
 endif
 if BUILD_NAGIOS
 libcrmservice_la_SOURCES	+= services_nagios.c
 endif
diff --git a/lib/services/services.c b/lib/services/services.c
index 94a8afcd52..248c4c8495 100644
--- a/lib/services/services.c
+++ b/lib/services/services.c
@@ -1,1415 +1,1468 @@
 /*
  * Copyright 2010-2024 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU Lesser General Public License
  * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
 
 #ifndef _GNU_SOURCE
 #  define _GNU_SOURCE
 #endif
 
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <stdio.h>
 #include <errno.h>
 #include <unistd.h>
 #include <dirent.h>
 #include <fcntl.h>
 
 #include <crm/crm.h>
 #include <crm/common/mainloop.h>
 #include <crm/services.h>
 #include <crm/services_internal.h>
 #include <crm/stonith-ng.h>
 #include <crm/common/xml.h>
 #include "services_private.h"
 #include "services_ocf.h"
+
+#if PCMK__ENABLE_LSB
 #include "services_lsb.h"
+#endif
 
 #if SUPPORT_UPSTART
 #  include <upstart.h>
 #endif
 
 #if SUPPORT_SYSTEMD
 #  include <systemd.h>
 #endif
 
 #if SUPPORT_NAGIOS
 #  include <services_nagios.h>
 #endif
 
 /* TODO: Develop a rollover strategy */
 
 static int operations = 0;
 static GHashTable *recurring_actions = NULL;
 
 /* ops waiting to run async because of conflicting active
  * pending ops */
 static GList *blocked_ops = NULL;
 
 /* ops currently active (in-flight) */
 static GList *inflight_ops = NULL;
 
 static void handle_blocked_ops(void);
 
 /*!
  * \brief Find first service class that can provide a specified agent
  *
  * \param[in] agent  Name of agent to search for
  *
  * \return Service class if found, NULL otherwise
  *
  * \note The priority is LSB, then systemd, then upstart. It would be preferable
  *       to put systemd first, but LSB merely requires a file existence check,
  *       while systemd requires contacting D-Bus.
  */
 const char *
 resources_find_service_class(const char *agent)
 {
+#if PCMK__ENABLE_LSB
     if (services__lsb_agent_exists(agent)) {
         return PCMK_RESOURCE_CLASS_LSB;
     }
+#endif
 
 #if SUPPORT_SYSTEMD
     if (systemd_unit_exists(agent)) {
         return PCMK_RESOURCE_CLASS_SYSTEMD;
     }
 #endif
 
 #if SUPPORT_UPSTART
     if (upstart_job_exists(agent)) {
         return PCMK_RESOURCE_CLASS_UPSTART;
     }
 #endif
     return NULL;
 }
 
 static inline void
 init_recurring_actions(void)
 {
     if (recurring_actions == NULL) {
         recurring_actions = pcmk__strkey_table(NULL, NULL);
     }
 }
 
 /*!
  * \internal
  * \brief Check whether op is in-flight systemd or upstart op
  *
  * \param[in] op  Operation to check
  *
  * \return TRUE if op is in-flight systemd or upstart op
  */
 static inline gboolean
 inflight_systemd_or_upstart(const svc_action_t *op)
 {
     return pcmk__strcase_any_of(op->standard, PCMK_RESOURCE_CLASS_SYSTEMD,
                            PCMK_RESOURCE_CLASS_UPSTART, NULL) &&
            g_list_find(inflight_ops, op) != NULL;
 }
 
 /*!
  * \internal
  * \brief Expand "service" alias to an actual resource class
  *
  * \param[in] rsc       Resource name (for logging only)
  * \param[in] standard  Resource class as configured
  * \param[in] agent     Agent name to look for
  *
  * \return Newly allocated string with actual resource class
  *
  * \note The caller is responsible for calling free() on the result.
  */
 static char *
 expand_resource_class(const char *rsc, const char *standard, const char *agent)
 {
     char *expanded_class = NULL;
 
+#if PCMK__ENABLE_SERVICE
     if (strcasecmp(standard, PCMK_RESOURCE_CLASS_SERVICE) == 0) {
         const char *found_class = resources_find_service_class(agent);
 
-        if (found_class) {
+        if (found_class != NULL) {
             crm_debug("Found %s agent %s for %s", found_class, agent, rsc);
-            expanded_class = strdup(found_class);
+            expanded_class = pcmk__str_copy(found_class);
         } else {
-            crm_info("Assuming resource class lsb for agent %s for %s",
-                     agent, rsc);
-            expanded_class = strdup(PCMK_RESOURCE_CLASS_LSB);
+            const char *default_standard = NULL;
+
+#if PCMK__ENABLE_LSB
+            default_standard = PCMK_RESOURCE_CLASS_LSB;
+#elif SUPPORT_SYSTEMD
+            default_standard = PCMK_RESOURCE_CLASS_SYSTEMD;
+#elif SUPPORT_UPSTART
+            default_standard = PCMK_RESOURCE_CLASS_UPSTART;
+#else
+#error No standards supported for service alias (configure script bug)
+#endif
+            crm_info("Assuming resource class %s for agent %s for %s",
+                     default_standard, agent, rsc);
+            expanded_class = pcmk__str_copy(default_standard);
         }
-    } else {
-        expanded_class = strdup(standard);
     }
-    CRM_ASSERT(expanded_class);
+#endif
+
+    if (expanded_class == NULL) {
+        expanded_class = pcmk__str_copy(standard);
+    }
     return expanded_class;
 }
 
 /*!
  * \internal
  * \brief Create a simple svc_action_t instance
  *
  * \return Newly allocated instance (or NULL if not enough memory)
  */
 static svc_action_t *
 new_action(void)
 {
     svc_action_t *op = calloc(1, sizeof(svc_action_t));
 
     if (op == NULL) {
         return NULL;
     }
 
     op->opaque = calloc(1, sizeof(svc_action_private_t));
     if (op->opaque == NULL) {
         free(op);
         return NULL;
     }
 
     // Initialize result
     services__set_result(op, PCMK_OCF_UNKNOWN, PCMK_EXEC_UNKNOWN, NULL);
     return op;
 }
 
 static bool
 required_argument_missing(uint32_t ra_caps, const char *name,
                           const char *standard, const char *provider,
                           const char *agent, const char *action)
 {
     if (pcmk__str_empty(name)) {
         crm_info("Cannot create operation without resource name (bug?)");
         return true;
     }
 
     if (pcmk__str_empty(standard)) {
         crm_info("Cannot create operation for %s without resource class (bug?)",
                  name);
         return true;
     }
 
     if (pcmk_is_set(ra_caps, pcmk_ra_cap_provider)
         && pcmk__str_empty(provider)) {
         crm_info("Cannot create operation for %s resource %s "
                  "without provider (bug?)", standard, name);
         return true;
     }
 
     if (pcmk__str_empty(agent)) {
         crm_info("Cannot create operation for %s without agent name (bug?)",
                  name);
         return true;
     }
 
     if (pcmk__str_empty(action)) {
         crm_info("Cannot create operation for %s without action name (bug?)",
                  name);
         return true;
     }
     return false;
 }
 
 // \return Standard Pacemaker return code (pcmk_rc_ok or ENOMEM)
 static int
 copy_action_arguments(svc_action_t *op, uint32_t ra_caps, const char *name,
                       const char *standard, const char *provider,
                       const char *agent, const char *action)
 {
     op->rsc = strdup(name);
     if (op->rsc == NULL) {
         return ENOMEM;
     }
 
     op->agent = strdup(agent);
     if (op->agent == NULL) {
         return ENOMEM;
     }
 
     op->standard = expand_resource_class(name, standard, agent);
     if (op->standard == NULL) {
         return ENOMEM;
     }
 
     if (pcmk_is_set(ra_caps, pcmk_ra_cap_status)
         && pcmk__str_eq(action, PCMK_ACTION_MONITOR, pcmk__str_casei)) {
         action = PCMK_ACTION_STATUS;
     }
     op->action = strdup(action);
     if (op->action == NULL) {
         return ENOMEM;
     }
 
     if (pcmk_is_set(ra_caps, pcmk_ra_cap_provider)) {
         op->provider = strdup(provider);
         if (op->provider == NULL) {
             return ENOMEM;
         }
     }
     return pcmk_rc_ok;
 }
 
 svc_action_t *
 services__create_resource_action(const char *name, const char *standard,
                         const char *provider, const char *agent,
                         const char *action, guint interval_ms, int timeout,
                         GHashTable *params, enum svc_action_flags flags)
 {
     svc_action_t *op = NULL;
     uint32_t ra_caps = pcmk_get_ra_caps(standard);
     int rc = pcmk_rc_ok;
 
     op = new_action();
     if (op == NULL) {
         crm_crit("Cannot prepare action: %s", strerror(ENOMEM));
         if (params != NULL) {
             g_hash_table_destroy(params);
         }
         return NULL;
     }
 
     op->interval_ms = interval_ms;
     op->timeout = timeout;
     op->flags = flags;
     op->sequence = ++operations;
 
     // Take ownership of params
     if (pcmk_is_set(ra_caps, pcmk_ra_cap_params)) {
         op->params = params;
     } else if (params != NULL) {
         g_hash_table_destroy(params);
         params = NULL;
     }
 
     if (required_argument_missing(ra_caps, name, standard, provider, agent,
                                   action)) {
         services__set_result(op, services__generic_error(op),
                              PCMK_EXEC_ERROR_FATAL,
                              "Required agent or action information missing");
         return op;
     }
 
     op->id = pcmk__op_key(name, action, interval_ms);
 
     if (copy_action_arguments(op, ra_caps, name, standard, provider, agent,
                               action) != pcmk_rc_ok) {
         crm_crit("Cannot prepare %s action for %s: %s",
                  action, name, strerror(ENOMEM));
         services__handle_exec_error(op, ENOMEM);
         return op;
     }
 
     if (strcasecmp(op->standard, PCMK_RESOURCE_CLASS_OCF) == 0) {
         rc = services__ocf_prepare(op);
 
+#if PCMK__ENABLE_LSB
     } else if (strcasecmp(op->standard, PCMK_RESOURCE_CLASS_LSB) == 0) {
         rc = services__lsb_prepare(op);
-
+#endif
 #if SUPPORT_SYSTEMD
     } else if (strcasecmp(op->standard, PCMK_RESOURCE_CLASS_SYSTEMD) == 0) {
         rc = services__systemd_prepare(op);
 #endif
 #if SUPPORT_UPSTART
     } else if (strcasecmp(op->standard, PCMK_RESOURCE_CLASS_UPSTART) == 0) {
         rc = services__upstart_prepare(op);
 #endif
 #if SUPPORT_NAGIOS
     } else if (strcasecmp(op->standard, PCMK_RESOURCE_CLASS_NAGIOS) == 0) {
         rc = services__nagios_prepare(op);
 #endif
     } else {
         crm_info("Unknown resource standard: %s", op->standard);
         rc = ENOENT;
     }
 
     if (rc != pcmk_rc_ok) {
         crm_info("Cannot prepare %s operation for %s: %s",
                  action, name, strerror(rc));
         services__handle_exec_error(op, rc);
     }
     return op;
 }
 
 svc_action_t *
 resources_action_create(const char *name, const char *standard,
                         const char *provider, const char *agent,
                         const char *action, guint interval_ms, int timeout,
                         GHashTable *params, enum svc_action_flags flags)
 {
     svc_action_t *op = services__create_resource_action(name, standard,
                             provider, agent, action, interval_ms, timeout,
                             params, flags);
     if (op == NULL || op->rc != 0) {
         services_action_free(op);
         return NULL;
     } else {
         // Preserve public API backward compatibility
         op->rc = PCMK_OCF_OK;
         op->status = PCMK_EXEC_DONE;
 
         return op;
     }
 }
 
 svc_action_t *
 services_action_create_generic(const char *exec, const char *args[])
 {
     svc_action_t *op = new_action();
 
     pcmk__mem_assert(op);
 
     op->opaque->exec = strdup(exec);
     op->opaque->args[0] = strdup(exec);
     if ((op->opaque->exec == NULL) || (op->opaque->args[0] == NULL)) {
         crm_crit("Cannot prepare action for '%s': %s", exec, strerror(ENOMEM));
         services__set_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
                              strerror(ENOMEM));
         return op;
     }
 
     if (args == NULL) {
         return op;
     }
 
     for (int cur_arg = 1; args[cur_arg - 1] != NULL; cur_arg++) {
 
         if (cur_arg == PCMK__NELEM(op->opaque->args)) {
             crm_info("Cannot prepare action for '%s': Too many arguments",
                      exec);
             services__set_result(op, PCMK_OCF_UNKNOWN_ERROR,
                                  PCMK_EXEC_ERROR_HARD, "Too many arguments");
             break;
         }
 
         op->opaque->args[cur_arg] = strdup(args[cur_arg - 1]);
         if (op->opaque->args[cur_arg] == NULL) {
             crm_crit("Cannot prepare action for '%s': %s",
                      exec, strerror(ENOMEM));
             services__set_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
                                  strerror(ENOMEM));
             break;
         }
     }
 
     return op;
 }
 
 /*!
  * \brief Create an alert agent action
  *
  * \param[in] id        Alert ID
  * \param[in] exec      Path to alert agent executable
  * \param[in] timeout   Action timeout
  * \param[in] params    Parameters to use with action
  * \param[in] sequence  Action sequence number
  * \param[in] cb_data   Data to pass to callback function
  *
  * \return New action on success, NULL on error
  * \note It is the caller's responsibility to free cb_data.
  *       The caller should not free params explicitly.
  */
 svc_action_t *
 services_alert_create(const char *id, const char *exec, int timeout,
                       GHashTable *params, int sequence, void *cb_data)
 {
     svc_action_t *action = services_action_create_generic(exec, NULL);
 
     action->id = pcmk__str_copy(id);
     action->standard = pcmk__str_copy(PCMK_RESOURCE_CLASS_ALERT);
     action->timeout = timeout;
     action->params = params;
     action->sequence = sequence;
     action->cb_data = cb_data;
     return action;
 }
 
 /*!
  * \brief Set the user and group that an action will execute as
  *
  * \param[in,out] op      Action to modify
  * \param[in]     user    Name of user to execute action as
  * \param[in]     group   Name of group to execute action as
  *
  * \return pcmk_ok on success, -errno otherwise
  *
  * \note This will have no effect unless the process executing the action runs
  *       as root, and the action is not a systemd or upstart action.
  *       We could implement this for systemd by adding User= and Group= to
  *       [Service] in the override file, but that seems more likely to cause
  *       problems than be useful.
  */
 int
 services_action_user(svc_action_t *op, const char *user)
 {
     CRM_CHECK((op != NULL) && (user != NULL), return -EINVAL);
     return crm_user_lookup(user, &(op->opaque->uid), &(op->opaque->gid));
 }
 
 /*!
  * \brief Execute an alert agent action
  *
  * \param[in,out] action  Action to execute
  * \param[in]     cb      Function to call when action completes
  *
  * \return TRUE if the library will free action, FALSE otherwise
  *
  * \note If this function returns FALSE, it is the caller's responsibility to
  *       free the action with services_action_free(). However, unless someone
  *       intentionally creates a recurring alert action, this will never return
  *       FALSE.
  */
 gboolean
 services_alert_async(svc_action_t *action, void (*cb)(svc_action_t *op))
 {
     action->synchronous = false;
     action->opaque->callback = cb;
     return services__execute_file(action) == pcmk_rc_ok;
 }
 
 #if HAVE_DBUS
 /*!
  * \internal
  * \brief Update operation's pending DBus call, unreferencing old one if needed
  *
  * \param[in,out] op       Operation to modify
  * \param[in]     pending  Pending call to set
  */
 void
 services_set_op_pending(svc_action_t *op, DBusPendingCall *pending)
 {
     if (op->opaque->pending && (op->opaque->pending != pending)) {
         if (pending) {
             crm_info("Lost pending %s DBus call (%p)", op->id, op->opaque->pending);
         } else {
             crm_trace("Done with pending %s DBus call (%p)", op->id, op->opaque->pending);
         }
         dbus_pending_call_unref(op->opaque->pending);
     }
     op->opaque->pending = pending;
     if (pending) {
         crm_trace("Updated pending %s DBus call (%p)", op->id, pending);
     } else {
         crm_trace("Cleared pending %s DBus call", op->id);
     }
 }
 #endif
 
 void
 services_action_cleanup(svc_action_t * op)
 {
     if ((op == NULL) || (op->opaque == NULL)) {
         return;
     }
 
 #if HAVE_DBUS
     if(op->opaque->timerid != 0) {
         crm_trace("Removing timer for call %s to %s", op->action, op->rsc);
         g_source_remove(op->opaque->timerid);
         op->opaque->timerid = 0;
     }
 
     if(op->opaque->pending) {
         if (dbus_pending_call_get_completed(op->opaque->pending)) {
             // This should never be the case
             crm_warn("Result of %s op %s was unhandled",
                      op->standard, op->id);
         } else {
             crm_debug("Will ignore any result of canceled %s op %s",
                       op->standard, op->id);
         }
         dbus_pending_call_cancel(op->opaque->pending);
         services_set_op_pending(op, NULL);
     }
 #endif
 
     if (op->opaque->stderr_gsource) {
         mainloop_del_fd(op->opaque->stderr_gsource);
         op->opaque->stderr_gsource = NULL;
     }
 
     if (op->opaque->stdout_gsource) {
         mainloop_del_fd(op->opaque->stdout_gsource);
         op->opaque->stdout_gsource = NULL;
     }
 }
 
 /*!
  * \internal
  * \brief Map an actual resource action result to a standard OCF result
  *
  * \param[in] standard     Agent standard (must not be "service")
  * \param[in] action       Action that result is for
  * \param[in] exit_status  Actual agent exit status
  *
  * \return Standard OCF result
  */
 enum ocf_exitcode
 services_result2ocf(const char *standard, const char *action, int exit_status)
 {
     if (pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_OCF, pcmk__str_casei)) {
         return services__ocf2ocf(exit_status);
 
 #if SUPPORT_SYSTEMD
     } else if (pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_SYSTEMD,
                             pcmk__str_casei)) {
         return services__systemd2ocf(exit_status);
 #endif
 
 #if SUPPORT_UPSTART
     } else if (pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_UPSTART,
                             pcmk__str_casei)) {
         return services__upstart2ocf(exit_status);
 #endif
 
 #if SUPPORT_NAGIOS
     } else if (pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_NAGIOS,
                             pcmk__str_casei)) {
         return services__nagios2ocf(exit_status);
 #endif
 
+#if PCMK__ENABLE_LSB
     } else if (pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_LSB,
                             pcmk__str_casei)) {
         return services__lsb2ocf(action, exit_status);
+#endif
 
     } else {
         crm_warn("Treating result from unknown standard '%s' as OCF",
                  ((standard == NULL)? "unspecified" : standard));
         return services__ocf2ocf(exit_status);
     }
 }
 
 void
 services_action_free(svc_action_t * op)
 {
     unsigned int i;
 
     if (op == NULL) {
         return;
     }
 
     /* The operation should be removed from all tracking lists by this point.
      * If it's not, we have a bug somewhere, so bail. That may lead to a
      * memory leak, but it's better than a use-after-free segmentation fault.
      */
     CRM_CHECK(g_list_find(inflight_ops, op) == NULL, return);
     CRM_CHECK(g_list_find(blocked_ops, op) == NULL, return);
     CRM_CHECK((recurring_actions == NULL)
               || (g_hash_table_lookup(recurring_actions, op->id) == NULL),
               return);
 
     services_action_cleanup(op);
 
     if (op->opaque->repeat_timer) {
         g_source_remove(op->opaque->repeat_timer);
         op->opaque->repeat_timer = 0;
     }
 
     free(op->id);
     free(op->opaque->exec);
 
     for (i = 0; i < PCMK__NELEM(op->opaque->args); i++) {
         free(op->opaque->args[i]);
     }
 
     free(op->opaque->exit_reason);
     free(op->opaque);
     free(op->rsc);
     free(op->action);
 
     free(op->standard);
     free(op->agent);
     free(op->provider);
 
     free(op->stdout_data);
     free(op->stderr_data);
 
     if (op->params) {
         g_hash_table_destroy(op->params);
         op->params = NULL;
     }
 
     free(op);
 }
 
 gboolean
 cancel_recurring_action(svc_action_t * op)
 {
     crm_info("Cancelling %s operation %s", op->standard, op->id);
 
     if (recurring_actions) {
         g_hash_table_remove(recurring_actions, op->id);
     }
 
     if (op->opaque->repeat_timer) {
         g_source_remove(op->opaque->repeat_timer);
         op->opaque->repeat_timer = 0;
     }
 
     return TRUE;
 }
 
 /*!
  * \brief Cancel a recurring action
  *
  * \param[in] name         Name of resource that operation is for
  * \param[in] action       Name of operation to cancel
  * \param[in] interval_ms  Interval of operation to cancel
  *
  * \return TRUE if action was successfully cancelled, FALSE otherwise
  */
 gboolean
 services_action_cancel(const char *name, const char *action, guint interval_ms)
 {
     gboolean cancelled = FALSE;
     char *id = pcmk__op_key(name, action, interval_ms);
     svc_action_t *op = NULL;
 
     /* We can only cancel a recurring action */
     init_recurring_actions();
     op = g_hash_table_lookup(recurring_actions, id);
     if (op == NULL) {
         goto done;
     }
 
     // Tell services__finalize_async_op() not to reschedule the operation
     op->cancel = TRUE;
 
     /* Stop tracking it as a recurring operation, and stop its repeat timer */
     cancel_recurring_action(op);
 
     /* If the op has a PID, it's an in-flight child process, so kill it.
      *
      * Whether the kill succeeds or fails, the main loop will send the op to
      * async_action_complete() (and thus services__finalize_async_op()) when the
      * process goes away.
      */
     if (op->pid != 0) {
         crm_info("Terminating in-flight op %s[%d] early because it was cancelled",
                  id, op->pid);
         cancelled = mainloop_child_kill(op->pid);
         if (cancelled == FALSE) {
             crm_err("Termination of %s[%d] failed", id, op->pid);
         }
         goto done;
     }
 
 #if HAVE_DBUS
     // In-flight systemd and upstart ops don't have a pid
     if (inflight_systemd_or_upstart(op)) {
         inflight_ops = g_list_remove(inflight_ops, op);
 
         /* This will cause any result that comes in later to be discarded, so we
          * don't call the callback and free the operation twice.
          */
         services_action_cleanup(op);
     }
 #endif
 
     /* The rest of this is essentially equivalent to
      * services__finalize_async_op(), minus the handle_blocked_ops() call.
      */
 
     // Report operation as cancelled
     services__set_cancelled(op);
     if (op->opaque->callback) {
         op->opaque->callback(op);
     }
 
     blocked_ops = g_list_remove(blocked_ops, op);
     services_action_free(op);
     cancelled = TRUE;
     // @TODO Initiate handle_blocked_ops() asynchronously
 
 done:
     free(id);
     return cancelled;
 }
 
 gboolean
 services_action_kick(const char *name, const char *action, guint interval_ms)
 {
     svc_action_t * op = NULL;
     char *id = pcmk__op_key(name, action, interval_ms);
 
     init_recurring_actions();
     op = g_hash_table_lookup(recurring_actions, id);
     free(id);
 
     if (op == NULL) {
         return FALSE;
     }
 
 
     if (op->pid || inflight_systemd_or_upstart(op)) {
         return TRUE;
     } else {
         if (op->opaque->repeat_timer) {
             g_source_remove(op->opaque->repeat_timer);
             op->opaque->repeat_timer = 0;
         }
         recurring_action_timer(op);
         return TRUE;
     }
 
 }
 
 /*!
  * \internal
  * \brief Add a new recurring operation, checking for duplicates
  *
  * \param[in,out] op  Operation to add
  *
  * \return TRUE if duplicate found (and reschedule), FALSE otherwise
  */
 static gboolean
 handle_duplicate_recurring(svc_action_t *op)
 {
     svc_action_t * dup = NULL;
 
     /* check for duplicates */
     dup = g_hash_table_lookup(recurring_actions, op->id);
 
     if (dup && (dup != op)) {
         /* update user data */
         if (op->opaque->callback) {
             dup->opaque->callback = op->opaque->callback;
             dup->cb_data = op->cb_data;
             op->cb_data = NULL;
         }
         /* immediately execute the next interval */
         if (dup->pid != 0) {
             if (op->opaque->repeat_timer) {
                 g_source_remove(op->opaque->repeat_timer);
                 op->opaque->repeat_timer = 0;
             }
             recurring_action_timer(dup);
         }
         /* free the duplicate */
         services_action_free(op);
         return TRUE;
     }
 
     return FALSE;
 }
 
 /*!
  * \internal
  * \brief Execute an action appropriately according to its standard
  *
  * \param[in,out] op  Action to execute
  *
  * \return Standard Pacemaker return code
  * \retval EBUSY          Recurring operation could not be initiated
  * \retval pcmk_rc_error  Synchronous action failed
  * \retval pcmk_rc_ok     Synchronous action succeeded, or asynchronous action
  *                        should not be freed (because it's pending or because
  *                        it failed to execute and was already freed)
  *
  * \note If the return value for an asynchronous action is not pcmk_rc_ok, the
  *       caller is responsible for freeing the action.
  */
 static int
 execute_action(svc_action_t *op)
 {
 #if SUPPORT_UPSTART
     if (pcmk__str_eq(op->standard, PCMK_RESOURCE_CLASS_UPSTART,
                      pcmk__str_casei)) {
         return services__execute_upstart(op);
     }
 #endif
 
 #if SUPPORT_SYSTEMD
     if (pcmk__str_eq(op->standard, PCMK_RESOURCE_CLASS_SYSTEMD,
                      pcmk__str_casei)) {
         return services__execute_systemd(op);
     }
 #endif
 
     return services__execute_file(op);
 }
 
 void
 services_add_inflight_op(svc_action_t * op)
 {
     if (op == NULL) {
         return;
     }
 
     CRM_ASSERT(op->synchronous == FALSE);
 
     /* keep track of ops that are in-flight to avoid collisions in the same namespace */
     if (op->rsc) {
         inflight_ops = g_list_append(inflight_ops, op);
     }
 }
 
 /*!
  * \internal
  * \brief Stop tracking an operation that completed
  *
  * \param[in] op  Operation to stop tracking
  */
 void
 services_untrack_op(const svc_action_t *op)
 {
     /* Op is no longer in-flight or blocked */
     inflight_ops = g_list_remove(inflight_ops, op);
     blocked_ops = g_list_remove(blocked_ops, op);
 
     /* Op is no longer blocking other ops, so check if any need to run */
     handle_blocked_ops();
 }
 
 gboolean
 services_action_async_fork_notify(svc_action_t * op,
                                   void (*action_callback) (svc_action_t *),
                                   void (*action_fork_callback) (svc_action_t *))
 {
     CRM_CHECK(op != NULL, return TRUE);
 
     op->synchronous = false;
     if (action_callback != NULL) {
         op->opaque->callback = action_callback;
     }
     if (action_fork_callback != NULL) {
         op->opaque->fork_callback = action_fork_callback;
     }
 
     if (op->interval_ms > 0) {
         init_recurring_actions();
         if (handle_duplicate_recurring(op)) {
             /* entry rescheduled, dup freed */
             /* exit early */
             return TRUE;
         }
         g_hash_table_replace(recurring_actions, op->id, op);
     }
 
     if (!pcmk_is_set(op->flags, SVC_ACTION_NON_BLOCKED)
         && op->rsc && is_op_blocked(op->rsc)) {
         blocked_ops = g_list_append(blocked_ops, op);
         return TRUE;
     }
 
     return execute_action(op) == pcmk_rc_ok;
 }
 
 gboolean
 services_action_async(svc_action_t * op,
                       void (*action_callback) (svc_action_t *))
 {
     return services_action_async_fork_notify(op, action_callback, NULL);
 }
 
 static gboolean processing_blocked_ops = FALSE;
 
 gboolean
 is_op_blocked(const char *rsc)
 {
     GList *gIter = NULL;
     svc_action_t *op = NULL;
 
     for (gIter = inflight_ops; gIter != NULL; gIter = gIter->next) {
         op = gIter->data;
         if (pcmk__str_eq(op->rsc, rsc, pcmk__str_casei)) {
             return TRUE;
         }
     }
 
     return FALSE;
 }
 
 static void
 handle_blocked_ops(void)
 {
     GList *executed_ops = NULL;
     GList *gIter = NULL;
     svc_action_t *op = NULL;
 
     if (processing_blocked_ops) {
         /* avoid nested calling of this function */
         return;
     }
 
     processing_blocked_ops = TRUE;
 
     /* n^2 operation here, but blocked ops are incredibly rare. this list
      * will be empty 99% of the time. */
     for (gIter = blocked_ops; gIter != NULL; gIter = gIter->next) {
         op = gIter->data;
         if (is_op_blocked(op->rsc)) {
             continue;
         }
         executed_ops = g_list_append(executed_ops, op);
         if (execute_action(op) != pcmk_rc_ok) {
             /* this can cause this function to be called recursively
              * which is why we have processing_blocked_ops static variable */
             services__finalize_async_op(op);
         }
     }
 
     for (gIter = executed_ops; gIter != NULL; gIter = gIter->next) {
         op = gIter->data;
         blocked_ops = g_list_remove(blocked_ops, op);
     }
     g_list_free(executed_ops);
 
     processing_blocked_ops = FALSE;
 }
 
 /*!
  * \internal
  * \brief Execute a meta-data action appropriately to standard
  *
  * \param[in,out] op  Meta-data action to execute
  *
  * \return Standard Pacemaker return code
  */
 static int
 execute_metadata_action(svc_action_t *op)
 {
     const char *class = op->standard;
 
     if (op->agent == NULL) {
         crm_info("Meta-data requested without specifying agent");
         services__set_result(op, services__generic_error(op),
                              PCMK_EXEC_ERROR_FATAL, "Agent not specified");
         return EINVAL;
     }
 
     if (class == NULL) {
         crm_info("Meta-data requested for agent %s without specifying class",
                 op->agent);
         services__set_result(op, services__generic_error(op),
                              PCMK_EXEC_ERROR_FATAL,
                              "Agent standard not specified");
         return EINVAL;
     }
 
+#if PCMK__ENABLE_SERVICE
     if (!strcmp(class, PCMK_RESOURCE_CLASS_SERVICE)) {
         class = resources_find_service_class(op->agent);
     }
     if (class == NULL) {
         crm_info("Meta-data requested for %s, but could not determine class",
                  op->agent);
         services__set_result(op, services__generic_error(op),
                              PCMK_EXEC_ERROR_HARD,
                              "Agent standard could not be determined");
         return EINVAL;
     }
+#endif
 
+#if PCMK__ENABLE_LSB
     if (pcmk__str_eq(class, PCMK_RESOURCE_CLASS_LSB, pcmk__str_casei)) {
         return pcmk_legacy2rc(services__get_lsb_metadata(op->agent,
                                                          &op->stdout_data));
     }
+#endif
 
 #if SUPPORT_NAGIOS
     if (pcmk__str_eq(class, PCMK_RESOURCE_CLASS_NAGIOS, pcmk__str_casei)) {
         return pcmk_legacy2rc(services__get_nagios_metadata(op->agent,
                                                             &op->stdout_data));
     }
 #endif
 
     return execute_action(op);
 }
 
 gboolean
 services_action_sync(svc_action_t * op)
 {
     gboolean rc = TRUE;
 
     if (op == NULL) {
         crm_trace("No operation to execute");
         return FALSE;
     }
 
     op->synchronous = true;
 
     if (pcmk__str_eq(op->action, PCMK_ACTION_META_DATA, pcmk__str_casei)) {
         /* Synchronous meta-data operations are handled specially. Since most
          * resource classes don't provide any meta-data, it has to be
          * synthesized from available information about the agent.
          *
          * services_action_async() doesn't treat meta-data actions specially, so
          * it will result in an error for classes that don't support the action.
          */
         rc = (execute_metadata_action(op) == pcmk_rc_ok);
     } else {
         rc = (execute_action(op) == pcmk_rc_ok);
     }
     crm_trace(" > " PCMK__OP_FMT ": %s = %d",
               op->rsc, op->action, op->interval_ms, op->opaque->exec, op->rc);
     if (op->stdout_data) {
         crm_trace(" >  stdout: %s", op->stdout_data);
     }
     if (op->stderr_data) {
         crm_trace(" >  stderr: %s", op->stderr_data);
     }
     return rc;
 }
 
 GList *
 get_directory_list(const char *root, gboolean files, gboolean executable)
 {
     return services_os_get_directory_list(root, files, executable);
 }
 
 GList *
 resources_list_standards(void)
 {
     GList *standards = NULL;
 
     standards = g_list_append(standards, strdup(PCMK_RESOURCE_CLASS_OCF));
-    standards = g_list_append(standards, strdup(PCMK_RESOURCE_CLASS_LSB));
+
+#if PCMK__ENABLE_SERVICE
     standards = g_list_append(standards, strdup(PCMK_RESOURCE_CLASS_SERVICE));
+#endif
+
+#if PCMK__ENABLE_LSB
+    standards = g_list_append(standards, strdup(PCMK_RESOURCE_CLASS_LSB));
+#endif
 
 #if SUPPORT_SYSTEMD
     {
         GList *agents = systemd_unit_listall();
 
         if (agents != NULL) {
             standards = g_list_append(standards,
                                       strdup(PCMK_RESOURCE_CLASS_SYSTEMD));
             g_list_free_full(agents, free);
         }
     }
 #endif
 
 #if SUPPORT_UPSTART
     {
         GList *agents = upstart_job_listall();
 
         if (agents != NULL) {
             standards = g_list_append(standards,
                                       strdup(PCMK_RESOURCE_CLASS_UPSTART));
             g_list_free_full(agents, free);
         }
     }
 #endif
 
 #if SUPPORT_NAGIOS
     {
         GList *agents = services__list_nagios_agents();
 
         if (agents != NULL) {
             standards = g_list_append(standards,
                                       strdup(PCMK_RESOURCE_CLASS_NAGIOS));
             g_list_free_full(agents, free);
         }
     }
 #endif
 
     return standards;
 }
 
 GList *
 resources_list_providers(const char *standard)
 {
     if (pcmk_is_set(pcmk_get_ra_caps(standard), pcmk_ra_cap_provider)) {
         return resources_os_list_ocf_providers();
     }
 
     return NULL;
 }
 
 GList *
 resources_list_agents(const char *standard, const char *provider)
 {
     if ((standard == NULL)
-        || (strcasecmp(standard, PCMK_RESOURCE_CLASS_SERVICE) == 0)) {
+#if PCMK__ENABLE_SERVICE
+        || (strcasecmp(standard, PCMK_RESOURCE_CLASS_SERVICE) == 0)
+#endif
+        ) {
 
         GList *tmp1;
         GList *tmp2;
-        GList *result = services__list_lsb_agents();
+        GList *result = NULL;
 
         if (standard == NULL) {
             tmp1 = result;
             tmp2 = resources_os_list_ocf_agents(NULL);
             if (tmp2) {
                 result = g_list_concat(tmp1, tmp2);
             }
         }
+
+#if PCMK__ENABLE_LSB
+        result = g_list_concat(result, services__list_lsb_agents());
+#endif
+
 #if SUPPORT_SYSTEMD
         tmp1 = result;
         tmp2 = systemd_unit_listall();
         if (tmp2) {
             result = g_list_concat(tmp1, tmp2);
         }
 #endif
 
 #if SUPPORT_UPSTART
         tmp1 = result;
         tmp2 = upstart_job_listall();
         if (tmp2) {
             result = g_list_concat(tmp1, tmp2);
         }
 #endif
 
         return result;
 
     } else if (strcasecmp(standard, PCMK_RESOURCE_CLASS_OCF) == 0) {
         return resources_os_list_ocf_agents(provider);
+#if PCMK__ENABLE_LSB
     } else if (strcasecmp(standard, PCMK_RESOURCE_CLASS_LSB) == 0) {
         return services__list_lsb_agents();
+#endif
 #if SUPPORT_SYSTEMD
     } else if (strcasecmp(standard, PCMK_RESOURCE_CLASS_SYSTEMD) == 0) {
         return systemd_unit_listall();
 #endif
 #if SUPPORT_UPSTART
     } else if (strcasecmp(standard, PCMK_RESOURCE_CLASS_UPSTART) == 0) {
         return upstart_job_listall();
 #endif
 #if SUPPORT_NAGIOS
     } else if (strcasecmp(standard, PCMK_RESOURCE_CLASS_NAGIOS) == 0) {
         return services__list_nagios_agents();
 #endif
     }
 
     return NULL;
 }
 
 gboolean
 resources_agent_exists(const char *standard, const char *provider, const char *agent)
 {
     GList *standards = NULL;
     GList *providers = NULL;
     GList *iter = NULL;
     gboolean rc = FALSE;
     gboolean has_providers = FALSE;
 
     standards = resources_list_standards();
     for (iter = standards; iter != NULL; iter = iter->next) {
         if (pcmk__str_eq(iter->data, standard, pcmk__str_none)) {
             rc = TRUE;
             break;
         }
     }
 
     if (rc == FALSE) {
         goto done;
     }
 
     rc = FALSE;
 
     has_providers = pcmk_is_set(pcmk_get_ra_caps(standard), pcmk_ra_cap_provider);
     if (has_providers == TRUE && provider != NULL) {
         providers = resources_list_providers(standard);
         for (iter = providers; iter != NULL; iter = iter->next) {
             if (pcmk__str_eq(iter->data, provider, pcmk__str_none)) {
                 rc = TRUE;
                 break;
             }
         }
     } else if (has_providers == FALSE && provider == NULL) {
         rc = TRUE;
     }
 
     if (rc == FALSE) {
         goto done;
     }
 
+#if PCMK__ENABLE_SERVICE
     if (pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_SERVICE, pcmk__str_casei)) {
+#if PCMK__ENABLE_LSB
         if (services__lsb_agent_exists(agent)) {
             rc = TRUE;
+            goto done;
+        }
+#endif
 #if SUPPORT_SYSTEMD
-        } else if (systemd_unit_exists(agent)) {
+        if (systemd_unit_exists(agent)) {
             rc = TRUE;
+            goto done;
+        }
 #endif
-
 #if SUPPORT_UPSTART
-        } else if (upstart_job_exists(agent)) {
+        if (upstart_job_exists(agent)) {
             rc = TRUE;
-#endif
-        } else {
-            rc = FALSE;
+            goto done;
         }
+#endif
+        rc = FALSE;
+        goto done;
+    }
+#endif
 
-    } else if (pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_OCF, pcmk__str_casei)) {
+    if (pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_OCF, pcmk__str_casei)) {
         rc = services__ocf_agent_exists(provider, agent);
 
+#if PCMK__ENABLE_LSB
     } else if (pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_LSB, pcmk__str_casei)) {
         rc = services__lsb_agent_exists(agent);
+#endif
 
 #if SUPPORT_SYSTEMD
     } else if (pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_SYSTEMD, pcmk__str_casei)) {
         rc = systemd_unit_exists(agent);
 #endif
 
 #if SUPPORT_UPSTART
     } else if (pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_UPSTART, pcmk__str_casei)) {
         rc = upstart_job_exists(agent);
 #endif
 
 #if SUPPORT_NAGIOS
     } else if (pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_NAGIOS, pcmk__str_casei)) {
         rc = services__nagios_agent_exists(agent);
 #endif
 
     } else {
         rc = FALSE;
     }
 
 done:
     g_list_free(standards);
     g_list_free(providers);
     return rc;
 }
 
 /*!
  * \internal
  * \brief Set the result of an action
  *
  * \param[out] action        Where to set action result
  * \param[in]  agent_status  Exit status to set
  * \param[in]  exec_status   Execution status to set
  * \param[in]  reason        Human-friendly description of event to set
  */
 void
 services__set_result(svc_action_t *action, int agent_status,
                      enum pcmk_exec_status exec_status, const char *reason)
 {
     if (action == NULL) {
         return;
     }
 
     action->rc = agent_status;
     action->status = exec_status;
 
     if (!pcmk__str_eq(action->opaque->exit_reason, reason,
                       pcmk__str_none)) {
         free(action->opaque->exit_reason);
         action->opaque->exit_reason = (reason == NULL)? NULL : strdup(reason);
     }
 }
 
 /*!
  * \internal
  * \brief Set the result of an action, with a formatted exit reason
  *
  * \param[out] action        Where to set action result
  * \param[in]  agent_status  Exit status to set
  * \param[in]  exec_status   Execution status to set
  * \param[in]  format        printf-style format for a human-friendly
  *                           description of reason for result
  * \param[in]  ...           arguments for \p format
  */
 void
 services__format_result(svc_action_t *action, int agent_status,
                         enum pcmk_exec_status exec_status,
                         const char *format, ...)
 {
     va_list ap;
     int len = 0;
     char *reason = NULL;
 
     if (action == NULL) {
         return;
     }
 
     action->rc = agent_status;
     action->status = exec_status;
 
     if (format != NULL) {
         va_start(ap, format);
         len = vasprintf(&reason, format, ap);
         CRM_ASSERT(len > 0);
         va_end(ap);
     }
     free(action->opaque->exit_reason);
     action->opaque->exit_reason = reason;
 }
 
 /*!
  * \internal
  * \brief Set the result of an action to cancelled
  *
  * \param[out] action        Where to set action result
  *
  * \note This sets execution status but leaves the exit status unchanged
  */
 void
 services__set_cancelled(svc_action_t *action)
 {
     if (action != NULL) {
         action->status = PCMK_EXEC_CANCELLED;
         free(action->opaque->exit_reason);
         action->opaque->exit_reason = NULL;
     }
 }
 
 /*!
  * \internal
  * \brief Get a readable description of what an action is for
  *
  * \param[in] action  Action to check
  *
  * \return Readable name for the kind of \p action
  */
 const char *
 services__action_kind(const svc_action_t *action)
 {
     if ((action == NULL) || (action->standard == NULL)) {
         return "Process";
     } else if (pcmk__str_eq(action->standard, PCMK_RESOURCE_CLASS_STONITH,
                             pcmk__str_none)) {
         return "Fence agent";
     } else if (pcmk__str_eq(action->standard, PCMK_RESOURCE_CLASS_ALERT,
                             pcmk__str_none)) {
         return "Alert agent";
     } else {
         return "Resource agent";
     }
 }
 
 /*!
  * \internal
  * \brief Get the exit reason of an action
  *
  * \param[in] action  Action to check
  *
  * \return Action's exit reason (or NULL if none)
  */
 const char *
 services__exit_reason(const svc_action_t *action)
 {
     return action->opaque->exit_reason;
 }
 
 /*!
  * \internal
  * \brief Steal stdout from an action
  *
  * \param[in,out] action  Action whose stdout is desired
  *
  * \return Action's stdout (which may be NULL)
  * \note Upon return, \p action will no longer track the output, so it is the
  *       caller's responsibility to free the return value.
  */
 char *
 services__grab_stdout(svc_action_t *action)
 {
     char *output = action->stdout_data;
 
     action->stdout_data = NULL;
     return output;
 }
 
 /*!
  * \internal
  * \brief Steal stderr from an action
  *
  * \param[in,out] action  Action whose stderr is desired
  *
  * \return Action's stderr (which may be NULL)
  * \note Upon return, \p action will no longer track the output, so it is the
  *       caller's responsibility to free the return value.
  */
 char *
 services__grab_stderr(svc_action_t *action)
 {
     char *output = action->stderr_data;
 
     action->stderr_data = NULL;
     return output;
 }
diff --git a/lib/services/services_linux.c b/lib/services/services_linux.c
index 971d38c84c..d1be39bbc8 100644
--- a/lib/services/services_linux.c
+++ b/lib/services/services_linux.c
@@ -1,1480 +1,1488 @@
 /*
  * Copyright 2010-2023 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU Lesser General Public License
  * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
 
 #ifndef _GNU_SOURCE
 #  define _GNU_SOURCE
 #endif
 
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <sys/wait.h>
 #include <errno.h>
 #include <unistd.h>
 #include <dirent.h>
 #include <grp.h>
 #include <string.h>
 #include <sys/time.h>
 #include <sys/resource.h>
 
 #include "crm/crm.h"
 #include "crm/common/mainloop.h"
 #include "crm/services.h"
 #include "crm/services_internal.h"
 
 #include "services_private.h"
 
 static void close_pipe(int fildes[]);
 
 /* We have two alternative ways of handling SIGCHLD when synchronously waiting
  * for spawned processes to complete. Both rely on polling a file descriptor to
  * discover SIGCHLD events.
  *
  * If sys/signalfd.h is available (e.g. on Linux), we call signalfd() to
  * generate the file descriptor. Otherwise, we use the "self-pipe trick"
  * (opening a pipe and writing a byte to it when SIGCHLD is received).
  */
 #ifdef HAVE_SYS_SIGNALFD_H
 
 // signalfd() implementation
 
 #include <sys/signalfd.h>
 
 // Everything needed to manage SIGCHLD handling
 struct sigchld_data_s {
     sigset_t mask;      // Signals to block now (including SIGCHLD)
     sigset_t old_mask;  // Previous set of blocked signals
     bool ignored;       // If SIGCHLD for another child has been ignored
 };
 
 // Initialize SIGCHLD data and prepare for use
 static bool
 sigchld_setup(struct sigchld_data_s *data)
 {
     sigemptyset(&(data->mask));
     sigaddset(&(data->mask), SIGCHLD);
 
     sigemptyset(&(data->old_mask));
 
     // Block SIGCHLD (saving previous set of blocked signals to restore later)
     if (sigprocmask(SIG_BLOCK, &(data->mask), &(data->old_mask)) < 0) {
         crm_info("Wait for child process completion failed: %s "
                  CRM_XS " source=sigprocmask", pcmk_rc_str(errno));
         return false;
     }
 
     data->ignored = false;
 
     return true;
 }
 
 // Get a file descriptor suitable for polling for SIGCHLD events
 static int
 sigchld_open(struct sigchld_data_s *data)
 {
     int fd;
 
     CRM_CHECK(data != NULL, return -1);
 
     fd = signalfd(-1, &(data->mask), SFD_NONBLOCK);
     if (fd < 0) {
         crm_info("Wait for child process completion failed: %s "
                  CRM_XS " source=signalfd", pcmk_rc_str(errno));
     }
     return fd;
 }
 
 // Close a file descriptor returned by sigchld_open()
 static void
 sigchld_close(int fd)
 {
     if (fd > 0) {
         close(fd);
     }
 }
 
 // Return true if SIGCHLD was received from polled fd
 static bool
 sigchld_received(int fd, int pid, struct sigchld_data_s *data)
 {
     struct signalfd_siginfo fdsi;
     ssize_t s;
 
     if (fd < 0) {
         return false;
     }
     s = read(fd, &fdsi, sizeof(struct signalfd_siginfo));
     if (s != sizeof(struct signalfd_siginfo)) {
         crm_info("Wait for child process completion failed: %s "
                  CRM_XS " source=read", pcmk_rc_str(errno));
 
     } else if (fdsi.ssi_signo == SIGCHLD) {
         if (fdsi.ssi_pid == pid) {
             return true;
 
         } else {
             /* This SIGCHLD is for another child. We have to ignore it here but
              * will still need to resend it after this synchronous action has
              * completed and SIGCHLD has been restored to be handled by the
              * previous SIGCHLD handler, so that it will be handled.
              */
             data->ignored = true;
             return false;
         }
     }
     return false;
 }
 
 // Do anything needed after done waiting for SIGCHLD
 static void
 sigchld_cleanup(struct sigchld_data_s *data)
 {
     // Restore the original set of blocked signals
     if ((sigismember(&(data->old_mask), SIGCHLD) == 0)
         && (sigprocmask(SIG_UNBLOCK, &(data->mask), NULL) < 0)) {
         crm_warn("Could not clean up after child process completion: %s",
                  pcmk_rc_str(errno));
     }
 
     // Resend any ignored SIGCHLD for other children so that they'll be handled.
     if (data->ignored && kill(getpid(), SIGCHLD) != 0) {
         crm_warn("Could not resend ignored SIGCHLD to ourselves: %s",
                  pcmk_rc_str(errno));
     }
 }
 
 #else // HAVE_SYS_SIGNALFD_H not defined
 
 // Self-pipe implementation (see above for function descriptions)
 
 struct sigchld_data_s {
     int pipe_fd[2];             // Pipe file descriptors
     struct sigaction sa;        // Signal handling info (with SIGCHLD)
     struct sigaction old_sa;    // Previous signal handling info
     bool ignored;               // If SIGCHLD for another child has been ignored
 };
 
 // We need a global to use in the signal handler
 volatile struct sigchld_data_s *last_sigchld_data = NULL;
 
 static void
 sigchld_handler(void)
 {
     // We received a SIGCHLD, so trigger pipe polling
     if ((last_sigchld_data != NULL)
         && (last_sigchld_data->pipe_fd[1] >= 0)
         && (write(last_sigchld_data->pipe_fd[1], "", 1) == -1)) {
         crm_info("Wait for child process completion failed: %s "
                  CRM_XS " source=write", pcmk_rc_str(errno));
     }
 }
 
 static bool
 sigchld_setup(struct sigchld_data_s *data)
 {
     int rc;
 
     data->pipe_fd[0] = data->pipe_fd[1] = -1;
 
     if (pipe(data->pipe_fd) == -1) {
         crm_info("Wait for child process completion failed: %s "
                  CRM_XS " source=pipe", pcmk_rc_str(errno));
         return false;
     }
 
     rc = pcmk__set_nonblocking(data->pipe_fd[0]);
     if (rc != pcmk_rc_ok) {
         crm_info("Could not set pipe input non-blocking: %s " CRM_XS " rc=%d",
                  pcmk_rc_str(rc), rc);
     }
     rc = pcmk__set_nonblocking(data->pipe_fd[1]);
     if (rc != pcmk_rc_ok) {
         crm_info("Could not set pipe output non-blocking: %s " CRM_XS " rc=%d",
                  pcmk_rc_str(rc), rc);
     }
 
     // Set SIGCHLD handler
     data->sa.sa_handler = (sighandler_t) sigchld_handler;
     data->sa.sa_flags = 0;
     sigemptyset(&(data->sa.sa_mask));
     if (sigaction(SIGCHLD, &(data->sa), &(data->old_sa)) < 0) {
         crm_info("Wait for child process completion failed: %s "
                  CRM_XS " source=sigaction", pcmk_rc_str(errno));
     }
 
     data->ignored = false;
 
     // Remember data for use in signal handler
     last_sigchld_data = data;
     return true;
 }
 
 static int
 sigchld_open(struct sigchld_data_s *data)
 {
     CRM_CHECK(data != NULL, return -1);
     return data->pipe_fd[0];
 }
 
 static void
 sigchld_close(int fd)
 {
     // Pipe will be closed in sigchld_cleanup()
     return;
 }
 
 static bool
 sigchld_received(int fd, int pid, struct sigchld_data_s *data)
 {
     char ch;
 
     if (fd < 0) {
         return false;
     }
 
     // Clear out the self-pipe
     while (read(fd, &ch, 1) == 1) /*omit*/;
     return true;
 }
 
 static void
 sigchld_cleanup(struct sigchld_data_s *data)
 {
     // Restore the previous SIGCHLD handler
     if (sigaction(SIGCHLD, &(data->old_sa), NULL) < 0) {
         crm_warn("Could not clean up after child process completion: %s",
                  pcmk_rc_str(errno));
     }
 
     close_pipe(data->pipe_fd);
 
     // Resend any ignored SIGCHLD for other children so that they'll be handled.
     if (data->ignored && kill(getpid(), SIGCHLD) != 0) {
         crm_warn("Could not resend ignored SIGCHLD to ourselves: %s",
                  pcmk_rc_str(errno));
     }
 }
 
 #endif
 
 /*!
  * \internal
  * \brief Close the two file descriptors of a pipe
  *
  * \param[in,out] fildes  Array of file descriptors opened by pipe()
  */
 static void
 close_pipe(int fildes[])
 {
     if (fildes[0] >= 0) {
         close(fildes[0]);
         fildes[0] = -1;
     }
     if (fildes[1] >= 0) {
         close(fildes[1]);
         fildes[1] = -1;
     }
 }
 
 static gboolean
 svc_read_output(int fd, svc_action_t * op, bool is_stderr)
 {
     char *data = NULL;
     int rc = 0, len = 0;
     char buf[500];
     static const size_t buf_read_len = sizeof(buf) - 1;
 
 
     if (fd < 0) {
         crm_trace("No fd for %s", op->id);
         return FALSE;
     }
 
     if (is_stderr && op->stderr_data) {
         len = strlen(op->stderr_data);
         data = op->stderr_data;
         crm_trace("Reading %s stderr into offset %d", op->id, len);
 
     } else if (is_stderr == FALSE && op->stdout_data) {
         len = strlen(op->stdout_data);
         data = op->stdout_data;
         crm_trace("Reading %s stdout into offset %d", op->id, len);
 
     } else {
         crm_trace("Reading %s %s into offset %d", op->id, is_stderr?"stderr":"stdout", len);
     }
 
     do {
         rc = read(fd, buf, buf_read_len);
         if (rc > 0) {
             buf[rc] = 0;
             crm_trace("Got %d chars: %.80s", rc, buf);
             data = pcmk__realloc(data, len + rc + 1);
             len += sprintf(data + len, "%s", buf);
 
         } else if (errno != EINTR) {
             /* error or EOF
              * Cleanup happens in pipe_done()
              */
             rc = FALSE;
             break;
         }
 
     } while (rc == buf_read_len || rc < 0);
 
     if (is_stderr) {
         op->stderr_data = data;
     } else {
         op->stdout_data = data;
     }
 
     return rc;
 }
 
 static int
 dispatch_stdout(gpointer userdata)
 {
     svc_action_t *op = (svc_action_t *) userdata;
 
     return svc_read_output(op->opaque->stdout_fd, op, FALSE);
 }
 
 static int
 dispatch_stderr(gpointer userdata)
 {
     svc_action_t *op = (svc_action_t *) userdata;
 
     return svc_read_output(op->opaque->stderr_fd, op, TRUE);
 }
 
 static void
 pipe_out_done(gpointer user_data)
 {
     svc_action_t *op = (svc_action_t *) user_data;
 
     crm_trace("%p", op);
 
     op->opaque->stdout_gsource = NULL;
     if (op->opaque->stdout_fd > STDOUT_FILENO) {
         close(op->opaque->stdout_fd);
     }
     op->opaque->stdout_fd = -1;
 }
 
 static void
 pipe_err_done(gpointer user_data)
 {
     svc_action_t *op = (svc_action_t *) user_data;
 
     op->opaque->stderr_gsource = NULL;
     if (op->opaque->stderr_fd > STDERR_FILENO) {
         close(op->opaque->stderr_fd);
     }
     op->opaque->stderr_fd = -1;
 }
 
 static struct mainloop_fd_callbacks stdout_callbacks = {
     .dispatch = dispatch_stdout,
     .destroy = pipe_out_done,
 };
 
 static struct mainloop_fd_callbacks stderr_callbacks = {
     .dispatch = dispatch_stderr,
     .destroy = pipe_err_done,
 };
 
 static void
 set_ocf_env(const char *key, const char *value, gpointer user_data)
 {
     if (setenv(key, value, 1) != 0) {
         crm_perror(LOG_ERR, "setenv failed for key:%s and value:%s", key, value);
     }
 }
 
 static void
 set_ocf_env_with_prefix(gpointer key, gpointer value, gpointer user_data)
 {
     char buffer[500];
 
     snprintf(buffer, sizeof(buffer), strcmp(key, "OCF_CHECK_LEVEL") != 0 ? "OCF_RESKEY_%s" : "%s", (char *)key);
     set_ocf_env(buffer, value, user_data);
 }
 
 static void
 set_alert_env(gpointer key, gpointer value, gpointer user_data)
 {
     int rc;
 
     if (value != NULL) {
         rc = setenv(key, value, 1);
     } else {
         rc = unsetenv(key);
     }
 
     if (rc < 0) {
         crm_perror(LOG_ERR, "setenv %s=%s",
                   (char*)key, (value? (char*)value : ""));
     } else {
         crm_trace("setenv %s=%s", (char*)key, (value? (char*)value : ""));
     }
 }
 
 /*!
  * \internal
  * \brief Add environment variables suitable for an action
  *
  * \param[in] op  Action to use
  */
 static void
 add_action_env_vars(const svc_action_t *op)
 {
     void (*env_setter)(gpointer, gpointer, gpointer) = NULL;
     if (op->agent == NULL) {
         env_setter = set_alert_env;  /* we deal with alert handler */
 
     } else if (pcmk__str_eq(op->standard, PCMK_RESOURCE_CLASS_OCF, pcmk__str_casei)) {
         env_setter = set_ocf_env_with_prefix;
     }
 
     if (env_setter != NULL && op->params != NULL) {
         g_hash_table_foreach(op->params, env_setter, NULL);
     }
 
     if (env_setter == NULL || env_setter == set_alert_env) {
         return;
     }
 
     set_ocf_env("OCF_RA_VERSION_MAJOR", PCMK_OCF_MAJOR_VERSION, NULL);
     set_ocf_env("OCF_RA_VERSION_MINOR", PCMK_OCF_MINOR_VERSION, NULL);
     set_ocf_env("OCF_ROOT", OCF_ROOT_DIR, NULL);
     set_ocf_env("OCF_EXIT_REASON_PREFIX", PCMK_OCF_REASON_PREFIX, NULL);
 
     if (op->rsc) {
         set_ocf_env("OCF_RESOURCE_INSTANCE", op->rsc, NULL);
     }
 
     if (op->agent != NULL) {
         set_ocf_env("OCF_RESOURCE_TYPE", op->agent, NULL);
     }
 
     /* Notes: this is not added to specification yet. Sept 10,2004 */
     if (op->provider != NULL) {
         set_ocf_env("OCF_RESOURCE_PROVIDER", op->provider, NULL);
     }
 }
 
 static void
 pipe_in_single_parameter(gpointer key, gpointer value, gpointer user_data)
 {
     svc_action_t *op = user_data;
     char *buffer = crm_strdup_printf("%s=%s\n", (char *)key, (char *) value);
     int ret, total = 0, len = strlen(buffer);
 
     do {
         errno = 0;
         ret = write(op->opaque->stdin_fd, buffer + total, len - total);
         if (ret > 0) {
             total += ret;
         }
 
     } while ((errno == EINTR) && (total < len));
     free(buffer);
 }
 
 /*!
  * \internal
  * \brief Pipe parameters in via stdin for action
  *
  * \param[in] op  Action to use
  */
 static void
 pipe_in_action_stdin_parameters(const svc_action_t *op)
 {
     if (op->params) {
         g_hash_table_foreach(op->params, pipe_in_single_parameter, (gpointer) op);
     }
 }
 
 gboolean
 recurring_action_timer(gpointer data)
 {
     svc_action_t *op = data;
 
     crm_debug("Scheduling another invocation of %s", op->id);
 
     /* Clean out the old result */
     free(op->stdout_data);
     op->stdout_data = NULL;
     free(op->stderr_data);
     op->stderr_data = NULL;
     op->opaque->repeat_timer = 0;
 
     services_action_async(op, NULL);
     return FALSE;
 }
 
 /*!
  * \internal
  * \brief Finalize handling of an asynchronous operation
  *
  * Given a completed asynchronous operation, cancel or reschedule it as
  * appropriate if recurring, call its callback if registered, stop tracking it,
  * and clean it up.
  *
  * \param[in,out] op  Operation to finalize
  *
  * \return Standard Pacemaker return code
  * \retval EINVAL      Caller supplied NULL or invalid \p op
  * \retval EBUSY       Uncanceled recurring action has only been cleaned up
  * \retval pcmk_rc_ok  Action has been freed
  *
  * \note If the return value is not pcmk_rc_ok, the caller is responsible for
  *       freeing the action.
  */
 int
 services__finalize_async_op(svc_action_t *op)
 {
     CRM_CHECK((op != NULL) && !(op->synchronous), return EINVAL);
 
     if (op->interval_ms != 0) {
         // Recurring operations must be either cancelled or rescheduled
         if (op->cancel) {
             services__set_cancelled(op);
             cancel_recurring_action(op);
         } else {
             op->opaque->repeat_timer = g_timeout_add(op->interval_ms,
                                                      recurring_action_timer,
                                                      (void *) op);
         }
     }
 
     if (op->opaque->callback != NULL) {
         op->opaque->callback(op);
     }
 
     // Stop tracking the operation (as in-flight or blocked)
     op->pid = 0;
     services_untrack_op(op);
 
     if ((op->interval_ms != 0) && !(op->cancel)) {
         // Do not free recurring actions (they will get freed when cancelled)
         services_action_cleanup(op);
         return EBUSY;
     }
 
     services_action_free(op);
     return pcmk_rc_ok;
 }
 
 static void
 close_op_input(svc_action_t *op)
 {
     if (op->opaque->stdin_fd >= 0) {
         close(op->opaque->stdin_fd);
     }
 }
 
 static void
 finish_op_output(svc_action_t *op, bool is_stderr)
 {
     mainloop_io_t **source;
     int fd;
 
     if (is_stderr) {
         source = &(op->opaque->stderr_gsource);
         fd = op->opaque->stderr_fd;
     } else {
         source = &(op->opaque->stdout_gsource);
         fd = op->opaque->stdout_fd;
     }
 
     if (op->synchronous || *source) {
         crm_trace("Finish reading %s[%d] %s",
                   op->id, op->pid, (is_stderr? "stderr" : "stdout"));
         svc_read_output(fd, op, is_stderr);
         if (op->synchronous) {
             close(fd);
         } else {
             mainloop_del_fd(*source);
             *source = NULL;
         }
     }
 }
 
 // Log an operation's stdout and stderr
 static void
 log_op_output(svc_action_t *op)
 {
     char *prefix = crm_strdup_printf("%s[%d] error output", op->id, op->pid);
 
     /* The library caller has better context to know how important the output
      * is, so log it at info and debug severity here. They can log it again at
      * higher severity if appropriate.
      */
     crm_log_output(LOG_INFO, prefix, op->stderr_data);
     strcpy(prefix + strlen(prefix) - strlen("error output"), "output");
     crm_log_output(LOG_DEBUG, prefix, op->stdout_data);
     free(prefix);
 }
 
 // Truncate exit reasons at this many characters
 #define EXIT_REASON_MAX_LEN 128
 
 static void
 parse_exit_reason_from_stderr(svc_action_t *op)
 {
     const char *reason_start = NULL;
     const char *reason_end = NULL;
     const int prefix_len = strlen(PCMK_OCF_REASON_PREFIX);
 
     if ((op->stderr_data == NULL) ||
         // Only OCF agents have exit reasons in stderr
         !pcmk__str_eq(op->standard, PCMK_RESOURCE_CLASS_OCF, pcmk__str_none)) {
         return;
     }
 
     // Find the last occurrence of the magic string indicating an exit reason
     for (const char *cur = strstr(op->stderr_data, PCMK_OCF_REASON_PREFIX);
          cur != NULL; cur = strstr(cur, PCMK_OCF_REASON_PREFIX)) {
 
         cur += prefix_len; // Skip over magic string
         reason_start = cur;
     }
 
     if ((reason_start == NULL) || (reason_start[0] == '\n')
         || (reason_start[0] == '\0')) {
         return; // No or empty exit reason
     }
 
     // Exit reason goes to end of line (or end of output)
     reason_end = strchr(reason_start, '\n');
     if (reason_end == NULL) {
         reason_end = reason_start + strlen(reason_start);
     }
 
     // Limit size of exit reason to something reasonable
     if (reason_end > (reason_start + EXIT_REASON_MAX_LEN)) {
         reason_end = reason_start + EXIT_REASON_MAX_LEN;
     }
 
     free(op->opaque->exit_reason);
     op->opaque->exit_reason = strndup(reason_start, reason_end - reason_start);
 }
 
 /*!
  * \internal
  * \brief Process the completion of an asynchronous child process
  *
  * \param[in,out] p         Child process that completed
  * \param[in]     pid       Process ID of child
  * \param[in]     core      (Unused)
  * \param[in]     signo     Signal that interrupted child, if any
  * \param[in]     exitcode  Exit status of child process
  */
 static void
 async_action_complete(mainloop_child_t *p, pid_t pid, int core, int signo,
                       int exitcode)
 {
     svc_action_t *op = mainloop_child_userdata(p);
 
     mainloop_clear_child_userdata(p);
     CRM_CHECK(op->pid == pid,
               services__set_result(op, services__generic_error(op),
                                    PCMK_EXEC_ERROR, "Bug in mainloop handling");
               return);
 
     /* Depending on the priority the mainloop gives the stdout and stderr
      * file descriptors, this function could be called before everything has
      * been read from them, so force a final read now.
      */
     finish_op_output(op, true);
     finish_op_output(op, false);
 
     close_op_input(op);
 
     if (signo == 0) {
         crm_debug("%s[%d] exited with status %d", op->id, op->pid, exitcode);
         services__set_result(op, exitcode, PCMK_EXEC_DONE, NULL);
         log_op_output(op);
         parse_exit_reason_from_stderr(op);
 
     } else if (mainloop_child_timeout(p)) {
         const char *kind = services__action_kind(op);
 
         crm_info("%s %s[%d] timed out after %s",
                  kind, op->id, op->pid, pcmk__readable_interval(op->timeout));
         services__format_result(op, services__generic_error(op),
                                 PCMK_EXEC_TIMEOUT,
                                 "%s did not complete within %s",
                                 kind, pcmk__readable_interval(op->timeout));
 
     } else if (op->cancel) {
         /* If an in-flight recurring operation was killed because it was
          * cancelled, don't treat that as a failure.
          */
         crm_info("%s[%d] terminated with signal %d (%s)",
                  op->id, op->pid, signo, strsignal(signo));
         services__set_result(op, PCMK_OCF_OK, PCMK_EXEC_CANCELLED, NULL);
 
     } else {
         crm_info("%s[%d] terminated with signal %d (%s)",
                  op->id, op->pid, signo, strsignal(signo));
         services__format_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
                                 "%s interrupted by %s signal",
                                 services__action_kind(op), strsignal(signo));
     }
 
     services__finalize_async_op(op);
 }
 
 /*!
  * \internal
  * \brief Return agent standard's exit status for "generic error"
  *
  * When returning an internal error for an action, a value that is appropriate
  * to the action's agent standard must be used. This function returns a value
  * appropriate for errors in general.
  *
  * \param[in] op  Action that error is for
  *
  * \return Exit status appropriate to agent standard
  * \note Actions without a standard will get PCMK_OCF_UNKNOWN_ERROR.
  */
 int
 services__generic_error(const svc_action_t *op)
 {
     if ((op == NULL) || (op->standard == NULL)) {
         return PCMK_OCF_UNKNOWN_ERROR;
     }
 
+#if PCMK__ENABLE_LSB
     if (pcmk__str_eq(op->standard, PCMK_RESOURCE_CLASS_LSB, pcmk__str_casei)
         && pcmk__str_eq(op->action, PCMK_ACTION_STATUS, pcmk__str_casei)) {
 
         return PCMK_LSB_STATUS_UNKNOWN;
     }
+#endif
 
 #if SUPPORT_NAGIOS
     if (pcmk__str_eq(op->standard, PCMK_RESOURCE_CLASS_NAGIOS, pcmk__str_casei)) {
         return NAGIOS_STATE_UNKNOWN;
     }
 #endif
 
     return PCMK_OCF_UNKNOWN_ERROR;
 }
 
 /*!
  * \internal
  * \brief Return agent standard's exit status for "not installed"
  *
  * When returning an internal error for an action, a value that is appropriate
  * to the action's agent standard must be used. This function returns a value
  * appropriate for "not installed" errors.
  *
  * \param[in] op  Action that error is for
  *
  * \return Exit status appropriate to agent standard
  * \note Actions without a standard will get PCMK_OCF_UNKNOWN_ERROR.
  */
 int
 services__not_installed_error(const svc_action_t *op)
 {
     if ((op == NULL) || (op->standard == NULL)) {
         return PCMK_OCF_UNKNOWN_ERROR;
     }
 
+#if PCMK__ENABLE_LSB
     if (pcmk__str_eq(op->standard, PCMK_RESOURCE_CLASS_LSB, pcmk__str_casei)
         && pcmk__str_eq(op->action, PCMK_ACTION_STATUS, pcmk__str_casei)) {
 
         return PCMK_LSB_STATUS_NOT_INSTALLED;
     }
+#endif
 
 #if SUPPORT_NAGIOS
     if (pcmk__str_eq(op->standard, PCMK_RESOURCE_CLASS_NAGIOS, pcmk__str_casei)) {
         return NAGIOS_STATE_UNKNOWN;
     }
 #endif
 
     return PCMK_OCF_NOT_INSTALLED;
 }
 
 /*!
  * \internal
  * \brief Return agent standard's exit status for "insufficient privileges"
  *
  * When returning an internal error for an action, a value that is appropriate
  * to the action's agent standard must be used. This function returns a value
  * appropriate for "insufficient privileges" errors.
  *
  * \param[in] op  Action that error is for
  *
  * \return Exit status appropriate to agent standard
  * \note Actions without a standard will get PCMK_OCF_UNKNOWN_ERROR.
  */
 int
 services__authorization_error(const svc_action_t *op)
 {
     if ((op == NULL) || (op->standard == NULL)) {
         return PCMK_OCF_UNKNOWN_ERROR;
     }
 
+#if PCMK__ENABLE_LSB
     if (pcmk__str_eq(op->standard, PCMK_RESOURCE_CLASS_LSB, pcmk__str_casei)
         && pcmk__str_eq(op->action, PCMK_ACTION_STATUS, pcmk__str_casei)) {
 
         return PCMK_LSB_STATUS_INSUFFICIENT_PRIV;
     }
+#endif
 
 #if SUPPORT_NAGIOS
     if (pcmk__str_eq(op->standard, PCMK_RESOURCE_CLASS_NAGIOS, pcmk__str_casei)) {
         return NAGIOS_INSUFFICIENT_PRIV;
     }
 #endif
 
     return PCMK_OCF_INSUFFICIENT_PRIV;
 }
 
 /*!
  * \internal
  * \brief Return agent standard's exit status for "not configured"
  *
  * When returning an internal error for an action, a value that is appropriate
  * to the action's agent standard must be used. This function returns a value
  * appropriate for "not configured" errors.
  *
  * \param[in] op        Action that error is for
  * \param[in] is_fatal  Whether problem is cluster-wide instead of only local
  *
  * \return Exit status appropriate to agent standard
  * \note Actions without a standard will get PCMK_OCF_UNKNOWN_ERROR.
  */
 int
 services__configuration_error(const svc_action_t *op, bool is_fatal)
 {
     if ((op == NULL) || (op->standard == NULL)) {
         return PCMK_OCF_UNKNOWN_ERROR;
     }
 
+#if PCMK__ENABLE_LSB
     if (pcmk__str_eq(op->standard, PCMK_RESOURCE_CLASS_LSB, pcmk__str_casei)
         && pcmk__str_eq(op->action, PCMK_ACTION_STATUS, pcmk__str_casei)) {
 
         return PCMK_LSB_NOT_CONFIGURED;
     }
+#endif
 
 #if SUPPORT_NAGIOS
     if (pcmk__str_eq(op->standard, PCMK_RESOURCE_CLASS_NAGIOS, pcmk__str_casei)) {
         return NAGIOS_STATE_UNKNOWN;
     }
 #endif
 
     return is_fatal? PCMK_OCF_NOT_CONFIGURED : PCMK_OCF_INVALID_PARAM;
 }
 
 
 /*!
  * \internal
  * \brief Set operation rc and status per errno from stat(), fork() or execvp()
  *
  * \param[in,out] op     Operation to set rc and status for
  * \param[in]     error  Value of errno after system call
  *
  * \return void
  */
 void
 services__handle_exec_error(svc_action_t * op, int error)
 {
     const char *name = op->opaque->exec;
 
     if (name == NULL) {
         name = op->agent;
         if (name == NULL) {
             name = op->id;
         }
     }
 
     switch (error) {   /* see execve(2), stat(2) and fork(2) */
         case ENOENT:   /* No such file or directory */
         case EISDIR:   /* Is a directory */
         case ENOTDIR:  /* Path component is not a directory */
         case EINVAL:   /* Invalid executable format */
         case ENOEXEC:  /* Invalid executable format */
             services__format_result(op, services__not_installed_error(op),
                                     PCMK_EXEC_NOT_INSTALLED, "%s: %s",
                                     name, pcmk_rc_str(error));
             break;
         case EACCES:   /* permission denied (various errors) */
         case EPERM:    /* permission denied (various errors) */
             services__format_result(op, services__authorization_error(op),
                                     PCMK_EXEC_ERROR, "%s: %s",
                                     name, pcmk_rc_str(error));
             break;
         default:
             services__set_result(op, services__generic_error(op),
                                  PCMK_EXEC_ERROR, pcmk_rc_str(error));
     }
 }
 
 /*!
  * \internal
  * \brief Exit a child process that failed before executing agent
  *
  * \param[in] op           Action that failed
  * \param[in] exit_status  Exit status code to use
  * \param[in] exit_reason  Exit reason to output if for OCF agent
  */
 static void
 exit_child(const svc_action_t *op, int exit_status, const char *exit_reason)
 {
     if ((op != NULL) && (exit_reason != NULL)
         && pcmk__str_eq(op->standard, PCMK_RESOURCE_CLASS_OCF,
                         pcmk__str_none)) {
         fprintf(stderr, PCMK_OCF_REASON_PREFIX "%s\n", exit_reason);
     }
     _exit(exit_status);
 }
 
 static void
 action_launch_child(svc_action_t *op)
 {
     int rc;
 
     /* SIGPIPE is ignored (which is different from signal blocking) by the gnutls library.
      * Depending on the libqb version in use, libqb may set SIGPIPE to be ignored as well. 
      * We do not want this to be inherited by the child process. By resetting this the signal
      * to the default behavior, we avoid some potential odd problems that occur during OCF
      * scripts when SIGPIPE is ignored by the environment. */
     signal(SIGPIPE, SIG_DFL);
 
 #if defined(HAVE_SCHED_SETSCHEDULER)
     if (sched_getscheduler(0) != SCHED_OTHER) {
         struct sched_param sp;
 
         memset(&sp, 0, sizeof(sp));
         sp.sched_priority = 0;
 
         if (sched_setscheduler(0, SCHED_OTHER, &sp) == -1) {
             crm_info("Could not reset scheduling policy for %s", op->id);
         }
     }
 #endif
     if (setpriority(PRIO_PROCESS, 0, 0) == -1) {
         crm_info("Could not reset process priority for %s", op->id);
     }
 
     /* Man: The call setpgrp() is equivalent to setpgid(0,0)
      * _and_ compiles on BSD variants too
      * need to investigate if it works the same too.
      */
     setpgid(0, 0);
 
     pcmk__close_fds_in_child(false);
 
     /* It would be nice if errors in this function could be reported as
      * execution status (for example, PCMK_EXEC_NO_SECRETS for the secrets error
      * below) instead of exit status. However, we've already forked, so
      * exit status is all we have. At least for OCF actions, we can output an
      * exit reason for the parent to parse.
      */
 
 #if SUPPORT_CIBSECRETS
     rc = pcmk__substitute_secrets(op->rsc, op->params);
     if (rc != pcmk_rc_ok) {
         if (pcmk__str_eq(op->action, PCMK_ACTION_STOP, pcmk__str_casei)) {
             crm_info("Proceeding with stop operation for %s "
                      "despite being unable to load CIB secrets (%s)",
                      op->rsc, pcmk_rc_str(rc));
         } else {
             crm_err("Considering %s unconfigured "
                     "because unable to load CIB secrets: %s",
                     op->rsc, pcmk_rc_str(rc));
             exit_child(op, services__configuration_error(op, false),
                        "Unable to load CIB secrets");
         }
     }
 #endif
 
     add_action_env_vars(op);
 
     /* Become the desired user */
     if (op->opaque->uid && (geteuid() == 0)) {
 
         // If requested, set effective group
         if (op->opaque->gid && (setgid(op->opaque->gid) < 0)) {
             crm_err("Considering %s unauthorized because could not set "
                     "child group to %d: %s",
                     op->id, op->opaque->gid, strerror(errno));
             exit_child(op, services__authorization_error(op),
                        "Could not set group for child process");
         }
 
         // Erase supplementary group list
         // (We could do initgroups() if we kept a copy of the username)
         if (setgroups(0, NULL) < 0) {
             crm_err("Considering %s unauthorized because could not "
                     "clear supplementary groups: %s", op->id, strerror(errno));
             exit_child(op, services__authorization_error(op),
                        "Could not clear supplementary groups for child process");
         }
 
         // Set effective user
         if (setuid(op->opaque->uid) < 0) {
             crm_err("Considering %s unauthorized because could not set user "
                     "to %d: %s", op->id, op->opaque->uid, strerror(errno));
             exit_child(op, services__authorization_error(op),
                        "Could not set user for child process");
         }
     }
 
     // Execute the agent (doesn't return if successful)
     execvp(op->opaque->exec, op->opaque->args);
 
     // An earlier stat() should have avoided most possible errors
     rc = errno;
     services__handle_exec_error(op, rc);
     crm_err("Unable to execute %s: %s", op->id, strerror(rc));
     exit_child(op, op->rc, "Child process was unable to execute file");
 }
 
 /*!
  * \internal
  * \brief Wait for synchronous action to complete, and set its result
  *
  * \param[in,out] op    Action to wait for
  * \param[in,out] data  Child signal data
  */
 static void
 wait_for_sync_result(svc_action_t *op, struct sigchld_data_s *data)
 {
     int status = 0;
     int timeout = op->timeout;
     time_t start = time(NULL);
     struct pollfd fds[3];
     int wait_rc = 0;
     const char *wait_reason = NULL;
 
     fds[0].fd = op->opaque->stdout_fd;
     fds[0].events = POLLIN;
     fds[0].revents = 0;
 
     fds[1].fd = op->opaque->stderr_fd;
     fds[1].events = POLLIN;
     fds[1].revents = 0;
 
     fds[2].fd = sigchld_open(data);
     fds[2].events = POLLIN;
     fds[2].revents = 0;
 
     crm_trace("Waiting for %s[%d]", op->id, op->pid);
     do {
         int poll_rc = poll(fds, 3, timeout);
 
         wait_reason = NULL;
 
         if (poll_rc > 0) {
             if (fds[0].revents & POLLIN) {
                 svc_read_output(op->opaque->stdout_fd, op, FALSE);
             }
 
             if (fds[1].revents & POLLIN) {
                 svc_read_output(op->opaque->stderr_fd, op, TRUE);
             }
 
             if ((fds[2].revents & POLLIN)
                 && sigchld_received(fds[2].fd, op->pid, data)) {
                 wait_rc = waitpid(op->pid, &status, WNOHANG);
 
                 if ((wait_rc > 0) || ((wait_rc < 0) && (errno == ECHILD))) {
                     // Child process exited or doesn't exist
                     break;
 
                 } else if (wait_rc < 0) {
                     wait_reason = pcmk_rc_str(errno);
                     crm_info("Wait for completion of %s[%d] failed: %s "
                              CRM_XS " source=waitpid",
                              op->id, op->pid, wait_reason);
                     wait_rc = 0; // Act as if process is still running
 
 #ifndef HAVE_SYS_SIGNALFD_H
                 } else {
                    /* The child hasn't exited, so this SIGCHLD could be for
                     * another child. We have to ignore it here but will still
                     * need to resend it after this synchronous action has
                     * completed and SIGCHLD has been restored to be handled by
                     * the previous handler, so that it will be handled.
                     */
                     data->ignored = true;
 #endif
                 }
             }
 
         } else if (poll_rc == 0) {
             // Poll timed out with no descriptors ready
             timeout = 0;
             break;
 
         } else if ((poll_rc < 0) && (errno != EINTR)) {
             wait_reason = pcmk_rc_str(errno);
             crm_info("Wait for completion of %s[%d] failed: %s "
                      CRM_XS " source=poll", op->id, op->pid, wait_reason);
             break;
         }
 
         timeout = op->timeout - (time(NULL) - start) * 1000;
 
     } while ((op->timeout < 0 || timeout > 0));
 
     crm_trace("Stopped waiting for %s[%d]", op->id, op->pid);
     finish_op_output(op, true);
     finish_op_output(op, false);
     close_op_input(op);
     sigchld_close(fds[2].fd);
 
     if (wait_rc <= 0) {
 
         if ((op->timeout > 0) && (timeout <= 0)) {
             services__format_result(op, services__generic_error(op),
                                     PCMK_EXEC_TIMEOUT,
                                     "%s did not exit within specified timeout",
                                     services__action_kind(op));
             crm_info("%s[%d] timed out after %dms",
                      op->id, op->pid, op->timeout);
 
         } else {
             services__set_result(op, services__generic_error(op),
                                  PCMK_EXEC_ERROR, wait_reason);
         }
 
         /* If only child hasn't been successfully waited for, yet.
            This is to limit killing wrong target a bit more. */
         if ((wait_rc == 0) && (waitpid(op->pid, &status, WNOHANG) == 0)) {
             if (kill(op->pid, SIGKILL)) {
                 crm_warn("Could not kill rogue child %s[%d]: %s",
                          op->id, op->pid, pcmk_rc_str(errno));
             }
             /* Safe to skip WNOHANG here as we sent non-ignorable signal. */
             while ((waitpid(op->pid, &status, 0) == (pid_t) -1)
                    && (errno == EINTR)) {
                 /* keep waiting */;
             }
         }
 
     } else if (WIFEXITED(status)) {
         services__set_result(op, WEXITSTATUS(status), PCMK_EXEC_DONE, NULL);
         parse_exit_reason_from_stderr(op);
         crm_info("%s[%d] exited with status %d", op->id, op->pid, op->rc);
 
     } else if (WIFSIGNALED(status)) {
         int signo = WTERMSIG(status);
 
         services__format_result(op, services__generic_error(op),
                                 PCMK_EXEC_ERROR, "%s interrupted by %s signal",
                                 services__action_kind(op), strsignal(signo));
         crm_info("%s[%d] terminated with signal %d (%s)",
                  op->id, op->pid, signo, strsignal(signo));
 
 #ifdef WCOREDUMP
         if (WCOREDUMP(status)) {
             crm_warn("%s[%d] dumped core", op->id, op->pid);
         }
 #endif
 
     } else {
         // Shouldn't be possible to get here
         services__set_result(op, services__generic_error(op), PCMK_EXEC_ERROR,
                              "Unable to wait for child to complete");
     }
 }
 
 /*!
  * \internal
  * \brief Execute an action whose standard uses executable files
  *
  * \param[in,out] op  Action to execute
  *
  * \return Standard Pacemaker return value
  * \retval EBUSY          Recurring operation could not be initiated
  * \retval pcmk_rc_error  Synchronous action failed
  * \retval pcmk_rc_ok     Synchronous action succeeded, or asynchronous action
  *                        should not be freed (because it's pending or because
  *                        it failed to execute and was already freed)
  *
  * \note If the return value for an asynchronous action is not pcmk_rc_ok, the
  *       caller is responsible for freeing the action.
  */
 int
 services__execute_file(svc_action_t *op)
 {
     int stdout_fd[2];
     int stderr_fd[2];
     int stdin_fd[2] = {-1, -1};
     int rc;
     struct stat st;
     struct sigchld_data_s data;
 
     // Catch common failure conditions early
     if (stat(op->opaque->exec, &st) != 0) {
         rc = errno;
         crm_info("Cannot execute '%s': %s " CRM_XS " stat rc=%d",
                  op->opaque->exec, pcmk_rc_str(rc), rc);
         services__handle_exec_error(op, rc);
         goto done;
     }
 
     if (pipe(stdout_fd) < 0) {
         rc = errno;
         crm_info("Cannot execute '%s': %s " CRM_XS " pipe(stdout) rc=%d",
                  op->opaque->exec, pcmk_rc_str(rc), rc);
         services__handle_exec_error(op, rc);
         goto done;
     }
 
     if (pipe(stderr_fd) < 0) {
         rc = errno;
 
         close_pipe(stdout_fd);
 
         crm_info("Cannot execute '%s': %s " CRM_XS " pipe(stderr) rc=%d",
                  op->opaque->exec, pcmk_rc_str(rc), rc);
         services__handle_exec_error(op, rc);
         goto done;
     }
 
     if (pcmk_is_set(pcmk_get_ra_caps(op->standard), pcmk_ra_cap_stdin)) {
         if (pipe(stdin_fd) < 0) {
             rc = errno;
 
             close_pipe(stdout_fd);
             close_pipe(stderr_fd);
 
             crm_info("Cannot execute '%s': %s " CRM_XS " pipe(stdin) rc=%d",
                      op->opaque->exec, pcmk_rc_str(rc), rc);
             services__handle_exec_error(op, rc);
             goto done;
         }
     }
 
     if (op->synchronous && !sigchld_setup(&data)) {
         close_pipe(stdin_fd);
         close_pipe(stdout_fd);
         close_pipe(stderr_fd);
         sigchld_cleanup(&data);
         services__set_result(op, services__generic_error(op), PCMK_EXEC_ERROR,
                              "Could not manage signals for child process");
         goto done;
     }
 
     op->pid = fork();
     switch (op->pid) {
         case -1:
             rc = errno;
             close_pipe(stdin_fd);
             close_pipe(stdout_fd);
             close_pipe(stderr_fd);
 
             crm_info("Cannot execute '%s': %s " CRM_XS " fork rc=%d",
                      op->opaque->exec, pcmk_rc_str(rc), rc);
             services__handle_exec_error(op, rc);
             if (op->synchronous) {
                 sigchld_cleanup(&data);
             }
             goto done;
             break;
 
         case 0:                /* Child */
             close(stdout_fd[0]);
             close(stderr_fd[0]);
             if (stdin_fd[1] >= 0) {
                 close(stdin_fd[1]);
             }
             if (STDOUT_FILENO != stdout_fd[1]) {
                 if (dup2(stdout_fd[1], STDOUT_FILENO) != STDOUT_FILENO) {
                     crm_warn("Can't redirect output from '%s': %s "
                              CRM_XS " errno=%d",
                              op->opaque->exec, pcmk_rc_str(errno), errno);
                 }
                 close(stdout_fd[1]);
             }
             if (STDERR_FILENO != stderr_fd[1]) {
                 if (dup2(stderr_fd[1], STDERR_FILENO) != STDERR_FILENO) {
                     crm_warn("Can't redirect error output from '%s': %s "
                              CRM_XS " errno=%d",
                              op->opaque->exec, pcmk_rc_str(errno), errno);
                 }
                 close(stderr_fd[1]);
             }
             if ((stdin_fd[0] >= 0) &&
                 (STDIN_FILENO != stdin_fd[0])) {
                 if (dup2(stdin_fd[0], STDIN_FILENO) != STDIN_FILENO) {
                     crm_warn("Can't redirect input to '%s': %s "
                              CRM_XS " errno=%d",
                              op->opaque->exec, pcmk_rc_str(errno), errno);
                 }
                 close(stdin_fd[0]);
             }
 
             if (op->synchronous) {
                 sigchld_cleanup(&data);
             }
 
             action_launch_child(op);
             CRM_ASSERT(0);  /* action_launch_child is effectively noreturn */
     }
 
     /* Only the parent reaches here */
     close(stdout_fd[1]);
     close(stderr_fd[1]);
     if (stdin_fd[0] >= 0) {
         close(stdin_fd[0]);
     }
 
     op->opaque->stdout_fd = stdout_fd[0];
     rc = pcmk__set_nonblocking(op->opaque->stdout_fd);
     if (rc != pcmk_rc_ok) {
         crm_info("Could not set '%s' output non-blocking: %s "
                  CRM_XS " rc=%d",
                  op->opaque->exec, pcmk_rc_str(rc), rc);
     }
 
     op->opaque->stderr_fd = stderr_fd[0];
     rc = pcmk__set_nonblocking(op->opaque->stderr_fd);
     if (rc != pcmk_rc_ok) {
         crm_info("Could not set '%s' error output non-blocking: %s "
                  CRM_XS " rc=%d",
                  op->opaque->exec, pcmk_rc_str(rc), rc);
     }
 
     op->opaque->stdin_fd = stdin_fd[1];
     if (op->opaque->stdin_fd >= 0) {
         // using buffer behind non-blocking-fd here - that could be improved
         // as long as no other standard uses stdin_fd assume stonith
         rc = pcmk__set_nonblocking(op->opaque->stdin_fd);
         if (rc != pcmk_rc_ok) {
             crm_info("Could not set '%s' input non-blocking: %s "
                     CRM_XS " fd=%d,rc=%d", op->opaque->exec,
                     pcmk_rc_str(rc), op->opaque->stdin_fd, rc);
         }
         pipe_in_action_stdin_parameters(op);
         // as long as we are handling parameters directly in here just close
         close(op->opaque->stdin_fd);
         op->opaque->stdin_fd = -1;
     }
 
     // after fds are setup properly and before we plug anything into mainloop
     if (op->opaque->fork_callback) {
         op->opaque->fork_callback(op);
     }
 
     if (op->synchronous) {
         wait_for_sync_result(op, &data);
         sigchld_cleanup(&data);
         goto done;
     }
 
     crm_trace("Waiting async for '%s'[%d]", op->opaque->exec, op->pid);
     mainloop_child_add_with_flags(op->pid, op->timeout, op->id, op,
                                   pcmk_is_set(op->flags, SVC_ACTION_LEAVE_GROUP)? mainloop_leave_pid_group : 0,
                                   async_action_complete);
 
     op->opaque->stdout_gsource = mainloop_add_fd(op->id,
                                                  G_PRIORITY_LOW,
                                                  op->opaque->stdout_fd, op,
                                                  &stdout_callbacks);
     op->opaque->stderr_gsource = mainloop_add_fd(op->id,
                                                  G_PRIORITY_LOW,
                                                  op->opaque->stderr_fd, op,
                                                  &stderr_callbacks);
     services_add_inflight_op(op);
     return pcmk_rc_ok;
 
 done:
     if (op->synchronous) {
         return (op->rc == PCMK_OCF_OK)? pcmk_rc_ok : pcmk_rc_error;
     } else {
         return services__finalize_async_op(op);
     }
 }
 
 GList *
 services_os_get_single_directory_list(const char *root, gboolean files, gboolean executable)
 {
     GList *list = NULL;
     struct dirent **namelist;
     int entries = 0, lpc = 0;
     char buffer[PATH_MAX];
 
     entries = scandir(root, &namelist, NULL, alphasort);
     if (entries <= 0) {
         return list;
     }
 
     for (lpc = 0; lpc < entries; lpc++) {
         struct stat sb;
 
         if ('.' == namelist[lpc]->d_name[0]) {
             free(namelist[lpc]);
             continue;
         }
 
         snprintf(buffer, sizeof(buffer), "%s/%s", root, namelist[lpc]->d_name);
 
         if (stat(buffer, &sb)) {
             continue;
         }
 
         if (S_ISDIR(sb.st_mode)) {
             if (files) {
                 free(namelist[lpc]);
                 continue;
             }
 
         } else if (S_ISREG(sb.st_mode)) {
             if (files == FALSE) {
                 free(namelist[lpc]);
                 continue;
 
             } else if (executable
                        && (sb.st_mode & S_IXUSR) == 0
                        && (sb.st_mode & S_IXGRP) == 0 && (sb.st_mode & S_IXOTH) == 0) {
                 free(namelist[lpc]);
                 continue;
             }
         }
 
         list = g_list_append(list, strdup(namelist[lpc]->d_name));
 
         free(namelist[lpc]);
     }
 
     free(namelist);
     return list;
 }
 
 GList *
 services_os_get_directory_list(const char *root, gboolean files, gboolean executable)
 {
     GList *result = NULL;
     char *dirs = strdup(root);
     char *dir = NULL;
 
     if (pcmk__str_empty(dirs)) {
         free(dirs);
         return result;
     }
 
     for (dir = strtok(dirs, ":"); dir != NULL; dir = strtok(NULL, ":")) {
         GList *tmp = services_os_get_single_directory_list(dir, files, executable);
 
         if (tmp) {
             result = g_list_concat(result, tmp);
         }
     }
 
     free(dirs);
 
     return result;
 }
diff --git a/python/pacemaker/_cts/cib.py b/python/pacemaker/_cts/cib.py
index bb3307726a..21b6c56139 100644
--- a/python/pacemaker/_cts/cib.py
+++ b/python/pacemaker/_cts/cib.py
@@ -1,410 +1,408 @@
 """CIB generator for Pacemaker's Cluster Test Suite (CTS)."""
 
 __all__ = ["ConfigFactory"]
 __copyright__ = "Copyright 2008-2024 the Pacemaker project contributors"
 __license__ = "GNU General Public License version 2 or later (GPLv2+) WITHOUT ANY WARRANTY"
 
 import warnings
 import tempfile
 
 from pacemaker.buildoptions import BuildOptions
 from pacemaker._cts.cibxml import Alerts, Clone, Expression, FencingTopology, Group, Nodes, OpDefaults, Option, Resource, Rule
 from pacemaker._cts.network import next_ip
 
 
 class CIB:
     """A class for generating, representing, and installing a CIB file onto cluster nodes."""
 
     def __init__(self, cm, version, factory, tmpfile=None):
         """
         Create a new CIB instance.
 
         Arguments:
         cm      -- A ClusterManager instance
         version -- The schema syntax version
         factory -- A ConfigFactory instance
         tmpfile -- Where to store the CIB, or None to use a new tempfile
         """
         # pylint: disable=invalid-name
         self._cib = None
         self._cm = cm
         self._counter = 1
         self._factory = factory
         self._num_nodes = 0
 
         self.version = version
 
         if not tmpfile:
             warnings.filterwarnings("ignore")
 
             # pylint: disable=consider-using-with
             f = tempfile.NamedTemporaryFile(delete=True)
             f.close()
             tmpfile = f.name
 
             warnings.resetwarnings()
 
         self._factory.tmpfile = tmpfile
 
     def _show(self):
         """Query a cluster node for its generated CIB; log and return the result."""
         output = ""
         (_, result) = self._factory.rsh(self._factory.target, "HOME=/root CIB_file=%s cibadmin -Ql" % self._factory.tmpfile, verbose=1)
 
         for line in result:
             output += line
             self._factory.debug("Generated Config: %s" % line)
 
         return output
 
     def new_ip(self, name=None):
         """Generate an IP resource for the next available IP address, optionally specifying the resource's name."""
         if self._cm.env["IPagent"] == "IPaddr2":
             ip = next_ip(self._cm.env["IPBase"])
             if not name:
                 if ":" in ip:
                     (_, _, suffix) = ip.rpartition(":")
                     name = "r%s" % suffix
                 else:
                     name = "r%s" % ip
 
             r = Resource(self._factory, name, self._cm.env["IPagent"], "ocf")
             r["ip"] = ip
 
             if ":" in ip:
                 r["cidr_netmask"] = "64"
                 r["nic"] = "eth0"
             else:
                 r["cidr_netmask"] = "32"
 
         else:
             if not name:
                 name = "r%s%d" % (self._cm.env["IPagent"], self._counter)
                 self._counter += 1
 
             r = Resource(self._factory, name, self._cm.env["IPagent"], "ocf")
 
         r.add_op("monitor", "5s")
         return r
 
     def get_node_id(self, node_name):
         """Check the cluster configuration for the node ID for the given node_name."""
         # We can't account for every possible configuration,
         # so we only return a node ID if:
         # * The node is specified in /etc/corosync/corosync.conf
         #   with "ring0_addr:" equal to node_name and "nodeid:"
         #   explicitly specified.
         # In all other cases, we return 0.
         node_id = 0
 
         # awkward command: use } as record separator
         # so each corosync.conf "object" is one record;
         # match the "node {" record that has "ring0_addr: node_name";
         # then print the substring of that record after "nodeid:"
         awk = r"""awk -v RS="}" """ \
               r"""'/^(\s*nodelist\s*{)?\s*node\s*{.*(ring0_addr|name):\s*%s(\s+|$)/""" \
               r"""{gsub(/.*nodeid:\s*/,"");gsub(/\s+.*$/,"");print}' %s""" \
               % (node_name, BuildOptions.COROSYNC_CONFIG_FILE)
 
         (rc, output) = self._factory.rsh(self._factory.target, awk, verbose=1)
 
         if rc == 0 and len(output) == 1:
             try:
                 node_id = int(output[0])
             except ValueError:
                 node_id = 0
 
         return node_id
 
     def install(self, target):
         """Generate a CIB file and install it to the given cluster node."""
         old = self._factory.tmpfile
 
         # Force a rebuild
         self._cib = None
 
         self._factory.tmpfile = "%s/cib.xml" % BuildOptions.CIB_DIR
         self.contents(target)
         self._factory.rsh(self._factory.target, "chown %s %s" % (BuildOptions.DAEMON_USER, self._factory.tmpfile))
 
         self._factory.tmpfile = old
 
     def contents(self, target):
         """Generate a complete CIB file."""
         # fencing resource
         if self._cib:
             return self._cib
 
         if target:
             self._factory.target = target
 
         self._factory.rsh(self._factory.target, "HOME=/root cibadmin --empty %s > %s" % (self.version, self._factory.tmpfile))
         self._num_nodes = len(self._cm.env["nodes"])
 
         no_quorum = "stop"
         if self._num_nodes < 3:
             no_quorum = "ignore"
             self._factory.log("Cluster only has %d nodes, configuring: no-quorum-policy=ignore" % self._num_nodes)
 
         # We don't need a nodes section unless we add attributes
         stn = None
 
         # Fencing resource
         # Define first so that the shell doesn't reject every update
         if self._cm.env["DoFencing"]:
 
             # Define the "real" fencing device
             st = Resource(self._factory, "Fencing", self._cm.env["stonith-type"], "stonith")
 
             # Set a threshold for unreliable stonith devices such as the vmware one
             st.add_meta("migration-threshold", "5")
             st.add_op("monitor", "120s", timeout="120s")
             st.add_op("stop", "0", timeout="60s")
             st.add_op("start", "0", timeout="60s")
 
             # For remote node tests, a cluster node is stopped and brought back up
             # as a remote node with the name "remote-OLDNAME". To allow fencing
             # devices to fence these nodes, create a list of all possible node names.
             all_node_names = [prefix + n for n in self._cm.env["nodes"] for prefix in ('', 'remote-')]
 
             # Add all parameters specified by user
             entries = self._cm.env["stonith-params"].split(',')
             for entry in entries:
                 try:
                     (name, value) = entry.split('=', 1)
                 except ValueError:
                     print("Warning: skipping invalid fencing parameter: %s" % entry)
                     continue
 
                 # Allow user to specify "all" as the node list, and expand it here
                 if name in ["hostlist", "pcmk_host_list"] and value == "all":
                     value = ' '.join(all_node_names)
 
                 st[name] = value
 
             st.commit()
 
             # Test advanced fencing logic
             stf_nodes = []
             stt_nodes = []
             attr_nodes = {}
 
             # Create the levels
             stl = FencingTopology(self._factory)
             for node in self._cm.env["nodes"]:
                 # Remote node tests will rename the node
                 remote_node = "remote-%s" % node
 
                 # Randomly assign node to a fencing method
                 ftype = self._cm.env.random_gen.choice(["levels-and", "levels-or ", "broadcast "])
 
                 # For levels-and, randomly choose targeting by node name or attribute
                 by = ""
 
                 if ftype == "levels-and":
                     node_id = self.get_node_id(node)
 
                     if node_id == 0 or self._cm.env.random_gen.choice([True, False]):
                         by = " (by name)"
                     else:
                         attr_nodes[node] = node_id
                         by = " (by attribute)"
 
                 self._cm.log(" - Using %s fencing for node: %s%s" % (ftype, node, by))
 
                 if ftype == "levels-and":
                     # If targeting by name, add a topology level for this node
                     if node not in attr_nodes:
                         stl.level(1, node, "FencingPass,Fencing")
 
                     # Always target remote nodes by name, otherwise we would need to add
                     # an attribute to the remote node only during remote tests (we don't
                     # want nonexistent remote nodes showing up in the non-remote tests).
                     # That complexity is not worth the effort.
                     stl.level(1, remote_node, "FencingPass,Fencing")
 
                     # Add the node (and its remote equivalent) to the list of levels-and nodes.
                     stt_nodes.extend([node, remote_node])
 
                 elif ftype == "levels-or ":
                     for n in [node, remote_node]:
                         stl.level(1, n, "FencingFail")
                         stl.level(2, n, "Fencing")
 
                     stf_nodes.extend([node, remote_node])
 
             # If any levels-and nodes were targeted by attribute,
             # create the attributes and a level for the attribute.
             if attr_nodes:
                 stn = Nodes(self._factory)
 
                 for (node_name, node_id) in attr_nodes.items():
                     stn.add_node(node_name, node_id, {"cts-fencing": "levels-and"})
 
                 stl.level(1, None, "FencingPass,Fencing", "cts-fencing", "levels-and")
 
             # Create a Dummy agent that always passes for levels-and
             if stt_nodes:
                 stt = Resource(self._factory, "FencingPass", "fence_dummy", "stonith")
                 stt["pcmk_host_list"] = " ".join(stt_nodes)
                 # Wait this many seconds before doing anything, handy for letting disks get flushed too
                 stt["random_sleep_range"] = "30"
                 stt["mode"] = "pass"
                 stt.commit()
 
             # Create a Dummy agent that always fails for levels-or
             if stf_nodes:
                 stf = Resource(self._factory, "FencingFail", "fence_dummy", "stonith")
                 stf["pcmk_host_list"] = " ".join(stf_nodes)
                 # Wait this many seconds before doing anything, handy for letting disks get flushed too
                 stf["random_sleep_range"] = "30"
                 stf["mode"] = "fail"
                 stf.commit()
 
             # Now commit the levels themselves
             stl.commit()
 
         o = Option(self._factory)
         o["stonith-enabled"] = self._cm.env["DoFencing"]
         o["start-failure-is-fatal"] = "false"
         o["pe-input-series-max"] = "5000"
         o["shutdown-escalation"] = "5min"
         o["batch-limit"] = "10"
         o["dc-deadtime"] = "5s"
         o["no-quorum-policy"] = no_quorum
 
         o.commit()
 
         o = OpDefaults(self._factory)
         o["timeout"] = "90s"
         o.commit()
 
         # Commit the nodes section if we defined one
         if stn is not None:
             stn.commit()
 
         # Add an alerts section if possible
         if self._factory.rsh.exists_on_all(self._cm.env["notification-agent"], self._cm.env["nodes"]):
             alerts = Alerts(self._factory)
             alerts.add_alert(self._cm.env["notification-agent"],
                              self._cm.env["notification-recipient"])
             alerts.commit()
 
         # Add resources?
         if self._cm.env["CIBResource"]:
             self.add_resources()
 
         # generate cib
         self._cib = self._show()
 
         if self._factory.tmpfile != "%s/cib.xml" % BuildOptions.CIB_DIR:
             self._factory.rsh(self._factory.target, "rm -f %s" % self._factory.tmpfile)
 
         return self._cib
 
     def add_resources(self):
         """Add various resources and their constraints to the CIB."""
         # Per-node resources
         for node in self._cm.env["nodes"]:
             name = "rsc_%s" % node
             r = self.new_ip(name)
             r.prefer(node, "100")
             r.commit()
 
         # Migrator
         # Make this slightly sticky (since we have no other location constraints) to avoid relocation during Reattach
         m = Resource(self._factory, "migrator", "Dummy", "ocf", "pacemaker")
         m["passwd"] = "whatever"
         m.add_meta("resource-stickiness", "1")
         m.add_meta("allow-migrate", "1")
         m.add_op("monitor", "P10S")
         m.commit()
 
         # Ping the test exerciser
         p = Resource(self._factory, "ping-1", "ping", "ocf", "pacemaker")
         p.add_op("monitor", "60s")
         p["host_list"] = self._cm.env["cts-exerciser"]
         p["name"] = "connected"
         p["debug"] = "true"
 
         c = Clone(self._factory, "Connectivity", p)
         c["globally-unique"] = "false"
         c.commit()
 
         # promotable clone resource
         s = Resource(self._factory, "stateful-1", "Stateful", "ocf", "pacemaker")
         s.add_op("monitor", "15s", timeout="60s")
         s.add_op("monitor", "16s", timeout="60s", role="Promoted")
         ms = Clone(self._factory, "promotable-1", s)
         ms["promotable"] = "true"
         ms["clone-max"] = self._num_nodes
         ms["clone-node-max"] = 1
         ms["promoted-max"] = 1
         ms["promoted-node-max"] = 1
 
         # Require connectivity to run the promotable clone
         r = Rule(self._factory, "connected", "-INFINITY", op="or")
         r.add_child(Expression(self._factory, "m1-connected-1", "connected", "lt", "1"))
         r.add_child(Expression(self._factory, "m1-connected-2", "connected", "not_defined", None))
         ms.prefer("connected", rule=r)
 
         ms.commit()
 
         # Group Resource
         g = Group(self._factory, "group-1")
         g.add_child(self.new_ip())
 
         if self._cm.env["have_systemd"]:
             sysd = Resource(self._factory, "petulant", "pacemaker-cts-dummyd@10", "service")
             sysd.add_op("monitor", "P10S")
             g.add_child(sysd)
         else:
             g.add_child(self.new_ip())
 
         g.add_child(self.new_ip())
 
         # Make group depend on the promotable clone
         g.after("promotable-1", first="promote", then="start")
         g.colocate("promotable-1", "INFINITY", withrole="Promoted")
 
         g.commit()
 
-        # LSB resource
-        lsb = Resource(self._factory, "lsb-dummy", "LSBDummy", "lsb")
-        lsb.add_op("monitor", "5s")
-
-        # LSB with group
-        lsb.after("group-1")
-        lsb.colocate("group-1")
-
-        lsb.commit()
+        # LSB resource dependent on group-1
+        if BuildOptions.INIT_DIR is not None:
+            lsb = Resource(self._factory, "lsb-dummy", "LSBDummy", "lsb")
+            lsb.add_op("monitor", "5s")
+            lsb.after("group-1")
+            lsb.colocate("group-1")
+            lsb.commit()
 
 
 class ConfigFactory:
     """Singleton to generate a CIB file for the environment's schema version."""
 
     def __init__(self, cm):
         """
         Create a new ConfigFactory instance.
 
         Arguments:
         cm      -- A ClusterManager instance
         """
         # pylint: disable=invalid-name
         self._cm = cm
         self.rsh = self._cm.rsh
         if not self._cm.env["ListTests"]:
             self.target = self._cm.env["nodes"][0]
         self.tmpfile = None
 
     def log(self, args):
         """Log a message."""
         self._cm.log("cib: %s" % args)
 
     def debug(self, args):
         """Log a debug message."""
         self._cm.debug("cib: %s" % args)
 
     def create_config(self, name="pacemaker-%s" % BuildOptions.CIB_SCHEMA_VERSION):
         """Return a CIB object for the given schema version."""
         return CIB(self._cm, name, self)
diff --git a/python/pacemaker/buildoptions.py.in b/python/pacemaker/buildoptions.py.in
index a97640cd32..e50fbbe48f 100644
--- a/python/pacemaker/buildoptions.py.in
+++ b/python/pacemaker/buildoptions.py.in
@@ -1,65 +1,68 @@
 """A module providing information on build-time configuration of pacemaker."""
 
 __all__ = ["BuildOptions"]
 __copyright__ = "Copyright 2023-2024 the Pacemaker project contributors"
 __license__ = "GNU Lesser General Public License version 2.1 or later (LGPLv2.1+)"
 
 
 class BuildOptions:
     """
     Variables generated as part of the ./configure && make process.
 
     These affect how pacemaker was configured and where its various parts
     get installed.
     """
 
     BASH_PATH = "@BASH_PATH@"
     """Path to the bash shell."""
 
     _BUILD_DIR = "@abs_top_builddir@"
     """
     Top-level build directory.
 
     NOTE: This is not especially useful on installed systems, but is useful for
     running various programs from a source checkout
     """
 
     CIB_DIR = "@CRM_CONFIG_DIR@"
     """Where CIB files are stored."""
 
     CIB_SCHEMA_VERSION = "@CIB_VERSION@"
     """Latest supported CIB schema version number."""
 
     COROSYNC_CONFIG_FILE = "@PCMK__COROSYNC_CONF@"
     """Path to the corosync config file."""
 
     DAEMON_DIR = "@CRM_DAEMON_DIR@"
     """Where Pacemaker daemons are installed."""
 
     DAEMON_USER = "@CRM_DAEMON_USER@"
     """User to run Pacemaker daemons as."""
 
+    INIT_DIR = "@INITDIR@" if "@INITDIR@" else None
+    """Where LSB init scripts are stored."""
+
     LOCAL_STATE_DIR = "@localstatedir@"
     """Where miscellaneous temporary state files are stored."""
 
     LOG_DIR = "@CRM_LOG_DIR@"
     """Where Pacemaker log files are stored."""
 
     OCF_RA_INSTALL_DIR = "@OCF_RA_INSTALL_DIR@"
     """Where resource agents are installed."""
 
     OCF_ROOT_DIR = "@OCF_ROOT_DIR@"
     """Root directory for OCF resource agents and libraries."""
 
     RSC_TMP_DIR = "@CRM_RSCTMP_DIR@"
     """Where resource agents should keep state files."""
 
     # pylint: disable=comparison-of-constants
     REMOTE_ENABLED = "@PC_NAME_GNUTLS@" != ""
     """True if Pacemaker Remote support is enabled."""
 
     SBIN_DIR = "@sbindir@"
     """Where administrative programs are installed."""
 
     SCHEMA_DIR = "@CRM_SCHEMA_DIRECTORY@"
     """Where Relax-NG schema files are stored."""
diff --git a/tools/crm_resource_runtime.c b/tools/crm_resource_runtime.c
index 746bf726d8..8c6f04a4d2 100644
--- a/tools/crm_resource_runtime.c
+++ b/tools/crm_resource_runtime.c
@@ -1,2397 +1,2399 @@
 /*
  * Copyright 2004-2024 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU General Public License version 2
  * or later (GPLv2+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
 
 #include <stdio.h>
 #include <limits.h>
 #include <glib.h>
 #include <libxml/tree.h>
 
 #include <crm/common/ipc_attrd_internal.h>
 #include <crm/common/ipc_controld.h>
 #include <crm/common/lists_internal.h>
 #include <crm/services_internal.h>
 
 #include <crm_resource.h>
 
 static GList *
 build_node_info_list(const pcmk_resource_t *rsc)
 {
     GList *retval = NULL;
 
     for (const GList *iter = rsc->children; iter != NULL; iter = iter->next) {
         const pcmk_resource_t *child = (const pcmk_resource_t *) iter->data;
 
         for (const GList *iter2 = child->running_on;
              iter2 != NULL; iter2 = iter2->next) {
 
             const pcmk_node_t *node = (const pcmk_node_t *) iter2->data;
             node_info_t *ni = pcmk__assert_alloc(1, sizeof(node_info_t));
 
             ni->node_name = node->details->uname;
             ni->promoted = pcmk_is_set(rsc->flags, pcmk_rsc_promotable) &&
                            child->fns->state(child, TRUE) == pcmk_role_promoted;
 
             retval = g_list_prepend(retval, ni);
         }
     }
 
     return retval;
 }
 
 GList *
 cli_resource_search(pcmk_resource_t *rsc, const char *requested_name,
                     pcmk_scheduler_t *scheduler)
 {
     GList *retval = NULL;
     const pcmk_resource_t *parent = pe__const_top_resource(rsc, false);
 
     if (pcmk__is_clone(rsc)) {
         retval = build_node_info_list(rsc);
 
     /* The anonymous clone children's common ID is supplied */
     } else if (pcmk__is_clone(parent)
                && !pcmk_is_set(rsc->flags, pcmk_rsc_unique)
                && rsc->clone_name
                && pcmk__str_eq(requested_name, rsc->clone_name, pcmk__str_none)
                && !pcmk__str_eq(requested_name, rsc->id, pcmk__str_none)) {
 
         retval = build_node_info_list(parent);
 
     } else if (rsc->running_on != NULL) {
         for (GList *iter = rsc->running_on; iter != NULL; iter = iter->next) {
             pcmk_node_t *node = (pcmk_node_t *) iter->data;
             node_info_t *ni = pcmk__assert_alloc(1, sizeof(node_info_t));
 
             ni->node_name = node->details->uname;
             ni->promoted = (rsc->fns->state(rsc, TRUE) == pcmk_role_promoted);
 
             retval = g_list_prepend(retval, ni);
         }
     }
 
     return retval;
 }
 
 // \return Standard Pacemaker return code
 static int
 find_resource_attr(pcmk__output_t *out, cib_t * the_cib, const char *attr,
                    const char *rsc, const char *attr_set_type, const char *set_name,
                    const char *attr_id, const char *attr_name, xmlNode **result)
 {
     xmlNode *xml_search;
     int rc = pcmk_rc_ok;
     GString *xpath = NULL;
     const char *xpath_base = NULL;
 
     if (result) {
         *result = NULL;
     }
 
     if(the_cib == NULL) {
         return ENOTCONN;
     }
 
     xpath_base = pcmk_cib_xpath_for(PCMK_XE_RESOURCES);
     if (xpath_base == NULL) {
         crm_err(PCMK_XE_RESOURCES " CIB element not known (bug?)");
         return ENOMSG;
     }
 
     xpath = g_string_sized_new(1024);
     pcmk__g_strcat(xpath,
                    xpath_base, "//*[@" PCMK_XA_ID "=\"", rsc, "\"]", NULL);
 
     if (attr_set_type != NULL) {
         pcmk__g_strcat(xpath, "/", attr_set_type, NULL);
         if (set_name != NULL) {
             pcmk__g_strcat(xpath, "[@" PCMK_XA_ID "=\"", set_name, "\"]",
                            NULL);
         }
     }
 
     g_string_append(xpath, "//" PCMK_XE_NVPAIR);
 
     if (attr_id != NULL && attr_name!= NULL) {
         pcmk__g_strcat(xpath,
                        "[@" PCMK_XA_ID "='", attr_id, "' "
                        "and @" PCMK_XA_NAME "='", attr_name, "']", NULL);
 
     } else if (attr_id != NULL) {
         pcmk__g_strcat(xpath, "[@" PCMK_XA_ID "='", attr_id, "']", NULL);
 
     } else if (attr_name != NULL) {
         pcmk__g_strcat(xpath, "[@" PCMK_XA_NAME "='", attr_name, "']", NULL);
     }
 
     rc = the_cib->cmds->query(the_cib, (const char *) xpath->str, &xml_search,
                               cib_sync_call | cib_scope_local | cib_xpath);
     rc = pcmk_legacy2rc(rc);
 
     if (rc == pcmk_rc_ok) {
         crm_log_xml_debug(xml_search, "Match");
         if (xml_search->children != NULL) {
             rc = ENOTUNIQ;
             pcmk__warn_multiple_name_matches(out, xml_search, attr_name);
             out->spacer(out);
         }
     }
 
     if (result) {
         *result = xml_search;
     } else {
         free_xml(xml_search);
     }
 
     g_string_free(xpath, TRUE);
     return rc;
 }
 
 /* PRIVATE. Use the find_matching_attr_resources instead. */
 static void
 find_matching_attr_resources_recursive(pcmk__output_t *out,
                                        GList /* <pcmk_resource_t*> */ **result,
                                        pcmk_resource_t *rsc, const char * attr_set,
                                        const char * attr_set_type, const char * attr_id,
                                        const char * attr_name, cib_t * cib, int depth)
 {
     int rc = pcmk_rc_ok;
     char *lookup_id = clone_strip(rsc->id);
 
     /* visit the children */
     for(GList *gIter = rsc->children; gIter; gIter = gIter->next) {
         find_matching_attr_resources_recursive(out, result,
                                                (pcmk_resource_t *) gIter->data,
                                                attr_set, attr_set_type, attr_id,
                                                attr_name, cib, depth+1);
         /* do it only once for clones */
         if (pcmk__is_clone(rsc)) {
             break;
         }
     }
 
     rc = find_resource_attr(out, cib, PCMK_XA_ID, lookup_id, attr_set_type,
                             attr_set, attr_id, attr_name, NULL);
     /* Post-order traversal.
      * The root is always on the list and it is the last item. */
     if((0 == depth) || (pcmk_rc_ok == rc)) {
         /* push the head */
         *result = g_list_append(*result, rsc);
     }
 
     free(lookup_id);
 }
 
 
 /* The result is a linearized pre-ordered tree of resources. */
 static GList/*<pcmk_resource_t*>*/ *
 find_matching_attr_resources(pcmk__output_t *out, pcmk_resource_t *rsc,
                              const char * rsc_id, const char * attr_set,
                              const char * attr_set_type, const char * attr_id,
                              const char * attr_name, cib_t * cib, const char * cmd,
                              gboolean force)
 {
     int rc = pcmk_rc_ok;
     char *lookup_id = NULL;
     GList * result = NULL;
     /* If --force is used, update only the requested resource (clone or primitive).
      * Otherwise, if the primitive has the attribute, use that.
      * Otherwise use the clone. */
     if(force == TRUE) {
         return g_list_append(result, rsc);
     }
     if (pcmk__is_clone(rsc->parent)) {
         int rc = find_resource_attr(out, cib, PCMK_XA_ID, rsc_id, attr_set_type,
                                     attr_set, attr_id, attr_name, NULL);
 
         if(rc != pcmk_rc_ok) {
             rsc = rsc->parent;
             out->info(out, "Performing %s of '%s' on '%s', the parent of '%s'",
                       cmd, attr_name, rsc->id, rsc_id);
         }
         return g_list_append(result, rsc);
 
     } else if ((rsc->parent == NULL) && (rsc->children != NULL)
                && pcmk__is_clone(rsc)) {
         pcmk_resource_t *child = rsc->children->data;
 
         if (pcmk__is_primitive(child)) {
             lookup_id = clone_strip(child->id); /* Could be a cloned group! */
             rc = find_resource_attr(out, cib, PCMK_XA_ID, lookup_id,
                                     attr_set_type, attr_set, attr_id, attr_name, NULL);
 
             if(rc == pcmk_rc_ok) {
                 rsc = child;
                 out->info(out, "A value for '%s' already exists in child '%s', performing %s on that instead of '%s'",
                           attr_name, lookup_id, cmd, rsc_id);
             }
 
             free(lookup_id);
         }
         return g_list_append(result, rsc);
     }
     /* If the resource is a group ==> children inherit the attribute if defined. */
     find_matching_attr_resources_recursive(out, &result, rsc, attr_set,
                                            attr_set_type, attr_id, attr_name,
                                            cib, 0);
     return result;
 }
 
 static int
 update_element_attribute(pcmk__output_t *out, pcmk_resource_t *rsc,
                          cib_t *cib, const char *attr_name, const char *attr_value)
 {
     int rc = pcmk_rc_ok;
 
     if (cib == NULL) {
         return ENOTCONN;
     }
 
     crm_xml_add(rsc->xml, attr_name, attr_value);
 
     rc = cib->cmds->replace(cib, PCMK_XE_RESOURCES, rsc->xml, cib_sync_call);
     rc = pcmk_legacy2rc(rc);
     if (rc == pcmk_rc_ok) {
         out->info(out, "Set attribute: " PCMK_XA_NAME "=%s value=%s",
                   attr_name, attr_value);
     }
 
     return rc;
 }
 
 static int
 resources_with_attr(pcmk__output_t *out, cib_t *cib, pcmk_resource_t *rsc,
                     const char *requested_name, const char *attr_set,
                     const char *attr_set_type, const char *attr_id,
                     const char *attr_name, const char *top_id, gboolean force,
                     GList **resources)
 {
     if (pcmk__str_eq(attr_set_type, PCMK_XE_INSTANCE_ATTRIBUTES,
                      pcmk__str_casei)) {
         if (!force) {
             xmlNode *xml_search = NULL;
             int rc = pcmk_rc_ok;
 
             rc = find_resource_attr(out, cib, PCMK_XA_ID, top_id,
                                     PCMK_XE_META_ATTRIBUTES, attr_set, attr_id,
                                     attr_name, &xml_search);
 
             if (rc == pcmk_rc_ok || rc == ENOTUNIQ) {
                 char *found_attr_id = NULL;
 
                 found_attr_id = crm_element_value_copy(xml_search, PCMK_XA_ID);
 
                 if (!out->is_quiet(out)) {
                     out->err(out,
                              "WARNING: There is already a meta attribute "
                              "for '%s' called '%s' (id=%s)",
                              top_id, attr_name, found_attr_id);
                     out->err(out,
                              "         Delete '%s' first or use the force option "
                              "to override", found_attr_id);
                 }
 
                 free(found_attr_id);
                 free_xml(xml_search);
                 return ENOTUNIQ;
             }
 
             free_xml(xml_search);
         }
 
         *resources = g_list_append(*resources, rsc);
 
     } else {
         *resources = find_matching_attr_resources(out, rsc, requested_name,
                                                   attr_set, attr_set_type,
                                                   attr_id, attr_name, cib,
                                                   "update", force);
     }
 
     /* If the user specified attr_set or attr_id, the intent is to modify a
      * single resource, which will be the last item in the list.
      */
     if ((attr_set != NULL) || (attr_id != NULL)) {
         GList *last = g_list_last(*resources);
 
         *resources = g_list_remove_link(*resources, last);
         g_list_free(*resources);
         *resources = last;
     }
 
     return pcmk_rc_ok;
 }
 
 static void
 free_attr_update_data(gpointer data)
 {
     attr_update_data_t *ud = data;
 
     if (ud == NULL) {
         return;
     }
 
     free(ud->attr_set_type);
     free(ud->attr_set_id);
     free(ud->attr_name);
     free(ud->attr_value);
     free(ud->given_rsc_id);
     free(ud->found_attr_id);
     free(ud);
 }
 
 static int
 update_attribute(pcmk_resource_t *rsc, const char *requested_name,
                  const char *attr_set, const char *attr_set_type,
                  const char *attr_id, const char *attr_name,
                  const char *attr_value, gboolean recursive, cib_t *cib,
                  gboolean force, GList **results)
 {
     pcmk__output_t *out = rsc->cluster->priv;
     int rc = pcmk_rc_ok;
 
     GList/*<pcmk_resource_t*>*/ *resources = NULL;
     const char *top_id = pe__const_top_resource(rsc, false)->id;
 
     if ((attr_id == NULL) && !force) {
         find_resource_attr(out, cib, PCMK_XA_ID, top_id, NULL, NULL, NULL,
                            attr_name, NULL);
     }
 
     rc = resources_with_attr(out, cib, rsc, requested_name, attr_set, attr_set_type,
                              attr_id, attr_name, top_id, force, &resources);
 
     if (rc != pcmk_rc_ok) {
         return rc;
     }
 
     for (GList *iter = resources; iter != NULL; iter = iter->next) {
         char *lookup_id = NULL;
         char *local_attr_set = NULL;
         char *found_attr_id = NULL;
         const char *rsc_attr_id = attr_id;
         const char *rsc_attr_set = attr_set;
 
         xmlNode *xml_top = NULL;
         xmlNode *xml_obj = NULL;
         xmlNode *xml_search = NULL;
 
         rsc = (pcmk_resource_t *) iter->data;
 
         lookup_id = clone_strip(rsc->id); /* Could be a cloned group! */
         rc = find_resource_attr(out, cib, PCMK_XA_ID, lookup_id, attr_set_type,
                                 attr_set, attr_id, attr_name, &xml_search);
 
         switch (rc) {
             case pcmk_rc_ok:
                 found_attr_id = crm_element_value_copy(xml_search, PCMK_XA_ID);
                 crm_debug("Found a match for " PCMK_XA_NAME "='%s': "
                           PCMK_XA_ID "='%s'", attr_name, found_attr_id);
                 rsc_attr_id = found_attr_id;
                 break;
 
             case ENXIO:
                 if (rsc_attr_set == NULL) {
                     local_attr_set = crm_strdup_printf("%s-%s", lookup_id,
                                                        attr_set_type);
                     rsc_attr_set = local_attr_set;
                 }
                 if (rsc_attr_id == NULL) {
                     found_attr_id = crm_strdup_printf("%s-%s",
                                                       rsc_attr_set, attr_name);
                     rsc_attr_id = found_attr_id;
                 }
 
                 xml_top = pcmk__xe_create(NULL, (const char *) rsc->xml->name);
                 crm_xml_add(xml_top, PCMK_XA_ID, lookup_id);
 
                 xml_obj = pcmk__xe_create(xml_top, attr_set_type);
                 crm_xml_add(xml_obj, PCMK_XA_ID, rsc_attr_set);
                 break;
 
             default:
                 free(lookup_id);
                 free(found_attr_id);
                 free_xml(xml_search);
                 g_list_free(resources);
                 return rc;
         }
 
         xml_obj = crm_create_nvpair_xml(xml_obj, rsc_attr_id, attr_name,
                                         attr_value);
         if (xml_top == NULL) {
             xml_top = xml_obj;
         }
 
         crm_log_xml_debug(xml_top, "Update");
 
         rc = cib->cmds->modify(cib, PCMK_XE_RESOURCES, xml_top, cib_sync_call);
         rc = pcmk_legacy2rc(rc);
         if (rc == pcmk_rc_ok) {
             attr_update_data_t *ud = pcmk__assert_alloc(1, sizeof(attr_update_data_t));
 
             if (attr_set_type == NULL) {
                 attr_set_type = (const char *) xml_search->parent->name;
             }
 
             if (rsc_attr_set == NULL) {
                 rsc_attr_set = crm_element_value(xml_search->parent, PCMK_XA_ID);
             }
 
             ud->attr_set_type = pcmk__str_copy(attr_set_type);
             ud->attr_set_id = pcmk__str_copy(rsc_attr_set);
             ud->attr_name = pcmk__str_copy(attr_name);
             ud->attr_value = pcmk__str_copy(attr_value);
             ud->given_rsc_id = pcmk__str_copy(lookup_id);
             ud->found_attr_id = pcmk__str_copy(found_attr_id);
             ud->rsc = rsc;
 
             *results = g_list_append(*results, ud);
         }
 
         free_xml(xml_top);
         free_xml(xml_search);
 
         free(lookup_id);
         free(found_attr_id);
         free(local_attr_set);
 
         if (recursive
             && pcmk__str_eq(attr_set_type, PCMK_XE_META_ATTRIBUTES,
                             pcmk__str_casei)) {
             /* We want to set the attribute only on resources explicitly
              * colocated with this one, so we use rsc->rsc_cons_lhs directly
              * rather than the with_this_colocations() method.
              */
             pcmk__set_rsc_flags(rsc, pcmk_rsc_detect_loop);
             for (GList *lpc = rsc->rsc_cons_lhs; lpc != NULL; lpc = lpc->next) {
                 pcmk__colocation_t *cons = (pcmk__colocation_t *) lpc->data;
 
                 crm_debug("Checking %s %d", cons->id, cons->score);
 
                 if (pcmk_is_set(cons->dependent->flags, pcmk_rsc_detect_loop)
                     || (cons->score <= 0)) {
                     continue;
                 }
 
                 crm_debug("Setting %s=%s for dependent resource %s",
                           attr_name, attr_value, cons->dependent->id);
                 update_attribute(cons->dependent, cons->dependent->id, NULL,
                                  attr_set_type, NULL, attr_name, attr_value,
                                  recursive, cib, force, results);
             }
         }
     }
 
     g_list_free(resources);
     return rc;
 }
 
 // \return Standard Pacemaker return code
 int
 cli_resource_update_attribute(pcmk_resource_t *rsc, const char *requested_name,
                               const char *attr_set, const char *attr_set_type,
                               const char *attr_id, const char *attr_name,
                               const char *attr_value, gboolean recursive,
                               cib_t *cib, gboolean force)
 {
     static bool need_init = true;
     int rc = pcmk_rc_ok;
 
     GList *results = NULL;
     pcmk__output_t *out = rsc->cluster->priv;
 
     /* If we were asked to update the attribute in a resource element (for
      * instance, <primitive class="ocf">) there's really not much we need to do.
      */
     if (pcmk__str_eq(attr_set_type, ATTR_SET_ELEMENT, pcmk__str_none)) {
         return update_element_attribute(out, rsc, cib, attr_name, attr_value);
     }
 
     /* One time initialization - clear flags so we can detect loops */
     if (need_init) {
         need_init = false;
         pcmk__unpack_constraints(rsc->cluster);
         pe__clear_resource_flags_on_all(rsc->cluster, pcmk_rsc_detect_loop);
     }
 
     rc = update_attribute(rsc, requested_name, attr_set, attr_set_type,
                           attr_id, attr_name, attr_value, recursive, cib,
                           force, &results);
 
     if (rc == pcmk_rc_ok) {
         if (results == NULL) {
             return rc;
         }
 
         out->message(out, "attribute-changed-list", results);
         g_list_free_full(results, free_attr_update_data);
     }
 
     return rc;
 }
 
 // \return Standard Pacemaker return code
 int
 cli_resource_delete_attribute(pcmk_resource_t *rsc, const char *requested_name,
                               const char *attr_set, const char *attr_set_type,
                               const char *attr_id, const char *attr_name,
                               cib_t *cib, int cib_options, gboolean force)
 {
     pcmk__output_t *out = rsc->cluster->priv;
     int rc = pcmk_rc_ok;
     GList/*<pcmk_resource_t*>*/ *resources = NULL;
 
     if ((attr_id == NULL) && !force) {
         find_resource_attr(out, cib, PCMK_XA_ID,
                            pe__const_top_resource(rsc, false)->id, NULL,
                            NULL, NULL, attr_name, NULL);
     }
 
     if (pcmk__str_eq(attr_set_type, PCMK_XE_META_ATTRIBUTES, pcmk__str_casei)) {
         resources = find_matching_attr_resources(out, rsc, requested_name,
                                                  attr_set, attr_set_type,
                                                  attr_id, attr_name, cib,
                                                  "delete", force);
 
     } else if (pcmk__str_eq(attr_set_type, ATTR_SET_ELEMENT, pcmk__str_none)) {
         pcmk__xe_remove_attr(rsc->xml, attr_name);
         CRM_ASSERT(cib != NULL);
         rc = cib->cmds->replace(cib, PCMK_XE_RESOURCES, rsc->xml, cib_options);
         rc = pcmk_legacy2rc(rc);
         if (rc == pcmk_rc_ok) {
             out->info(out, "Deleted attribute: %s", attr_name);
         }
         return rc;
 
     } else {
         resources = g_list_append(resources, rsc);
     }
 
     for (GList *iter = resources; iter != NULL; iter = iter->next) {
         char *lookup_id = NULL;
         xmlNode *xml_obj = NULL;
         xmlNode *xml_search = NULL;
         char *found_attr_id = NULL;
         const char *rsc_attr_id = attr_id;
 
         rsc = (pcmk_resource_t *) iter->data;
 
         lookup_id = clone_strip(rsc->id);
         rc = find_resource_attr(out, cib, PCMK_XA_ID, lookup_id, attr_set_type,
                                 attr_set, attr_id, attr_name, &xml_search);
         switch (rc) {
             case pcmk_rc_ok:
                 found_attr_id = crm_element_value_copy(xml_search, PCMK_XA_ID);
                 free_xml(xml_search);
                 break;
 
             case ENXIO:
                 free(lookup_id);
                 free_xml(xml_search);
                 continue;
 
             default:
                 free(lookup_id);
                 free_xml(xml_search);
                 g_list_free(resources);
                 return rc;
         }
 
         if (rsc_attr_id == NULL) {
             rsc_attr_id = found_attr_id;
         }
 
         xml_obj = crm_create_nvpair_xml(NULL, rsc_attr_id, attr_name, NULL);
         crm_log_xml_debug(xml_obj, "Delete");
 
         CRM_ASSERT(cib);
         rc = cib->cmds->remove(cib, PCMK_XE_RESOURCES, xml_obj, cib_options);
         rc = pcmk_legacy2rc(rc);
 
         if (rc == pcmk_rc_ok) {
             out->info(out, "Deleted '%s' option: " PCMK_XA_ID "=%s%s%s%s%s",
                       lookup_id, found_attr_id,
                       ((attr_set == NULL)? "" : " set="),
                       pcmk__s(attr_set, ""),
                       ((attr_name == NULL)? "" : " " PCMK_XA_NAME "="),
                       pcmk__s(attr_name, ""));
         }
 
         free(lookup_id);
         free_xml(xml_obj);
         free(found_attr_id);
     }
     g_list_free(resources);
     return rc;
 }
 
 // \return Standard Pacemaker return code
 static int
 send_lrm_rsc_op(pcmk_ipc_api_t *controld_api, bool do_fail_resource,
                 const char *host_uname, const char *rsc_id,
                 pcmk_scheduler_t *scheduler)
 {
     pcmk__output_t *out = scheduler->priv;
     const char *router_node = host_uname;
     const char *rsc_api_id = NULL;
     const char *rsc_long_id = NULL;
     const char *rsc_class = NULL;
     const char *rsc_provider = NULL;
     const char *rsc_type = NULL;
     bool cib_only = false;
     pcmk_resource_t *rsc = pe_find_resource(scheduler->resources, rsc_id);
 
     if (rsc == NULL) {
         out->err(out, "Resource %s not found", rsc_id);
         return ENXIO;
 
     } else if (!pcmk__is_primitive(rsc)) {
         out->err(out, "We can only process primitive resources, not %s", rsc_id);
         return EINVAL;
     }
 
     rsc_class = crm_element_value(rsc->xml, PCMK_XA_CLASS);
     rsc_provider = crm_element_value(rsc->xml, PCMK_XA_PROVIDER),
     rsc_type = crm_element_value(rsc->xml, PCMK_XA_TYPE);
     if ((rsc_class == NULL) || (rsc_type == NULL)) {
         out->err(out, "Resource %s does not have a class and type", rsc_id);
         return EINVAL;
     }
 
     {
         pcmk_node_t *node = pcmk_find_node(scheduler, host_uname);
 
         if (node == NULL) {
             out->err(out, "Node %s not found", host_uname);
             return pcmk_rc_node_unknown;
         }
 
         if (!(node->details->online)) {
             if (do_fail_resource) {
                 out->err(out, "Node %s is not online", host_uname);
                 return ENOTCONN;
             } else {
                 cib_only = true;
             }
         }
         if (!cib_only && pcmk__is_pacemaker_remote_node(node)) {
             node = pcmk__current_node(node->details->remote_rsc);
             if (node == NULL) {
                 out->err(out, "No cluster connection to Pacemaker Remote node %s detected",
                          host_uname);
                 return ENOTCONN;
             }
             router_node = node->details->uname;
         }
     }
 
     if (rsc->clone_name) {
         rsc_api_id = rsc->clone_name;
         rsc_long_id = rsc->id;
     } else {
         rsc_api_id = rsc->id;
     }
     if (do_fail_resource) {
         return pcmk_controld_api_fail(controld_api, host_uname, router_node,
                                       rsc_api_id, rsc_long_id,
                                       rsc_class, rsc_provider, rsc_type);
     } else {
         return pcmk_controld_api_refresh(controld_api, host_uname, router_node,
                                          rsc_api_id, rsc_long_id, rsc_class,
                                          rsc_provider, rsc_type, cib_only);
     }
 }
 
 /*!
  * \internal
  * \brief Get resource name as used in failure-related node attributes
  *
  * \param[in] rsc  Resource to check
  *
  * \return Newly allocated string containing resource's fail name
  * \note The caller is responsible for freeing the result.
  */
 static inline char *
 rsc_fail_name(const pcmk_resource_t *rsc)
 {
     const char *name = (rsc->clone_name? rsc->clone_name : rsc->id);
 
     if (pcmk_is_set(rsc->flags, pcmk_rsc_unique)) {
         return strdup(name);
     }
     return clone_strip(name);
 }
 
 // \return Standard Pacemaker return code
 static int
 clear_rsc_history(pcmk_ipc_api_t *controld_api, const char *host_uname,
                   const char *rsc_id, pcmk_scheduler_t *scheduler)
 {
     int rc = pcmk_rc_ok;
 
     /* Erase the resource's entire LRM history in the CIB, even if we're only
      * clearing a single operation's fail count. If we erased only entries for a
      * single operation, we might wind up with a wrong idea of the current
      * resource state, and we might not re-probe the resource.
      */
     rc = send_lrm_rsc_op(controld_api, false, host_uname, rsc_id, scheduler);
     if (rc != pcmk_rc_ok) {
         return rc;
     }
 
     crm_trace("Processing %d mainloop inputs",
               pcmk_controld_api_replies_expected(controld_api));
     while (g_main_context_iteration(NULL, FALSE)) {
         crm_trace("Processed mainloop input, %d still remaining",
                   pcmk_controld_api_replies_expected(controld_api));
     }
     return rc;
 }
 
 // \return Standard Pacemaker return code
 static int
 clear_rsc_failures(pcmk__output_t *out, pcmk_ipc_api_t *controld_api,
                    const char *node_name, const char *rsc_id, const char *operation,
                    const char *interval_spec, pcmk_scheduler_t *scheduler)
 {
     int rc = pcmk_rc_ok;
     const char *failed_value = NULL;
     const char *failed_id = NULL;
     char *interval_ms_s = NULL;
     GHashTable *rscs = NULL;
     GHashTableIter iter;
 
     /* Create a hash table to use as a set of resources to clean. This lets us
      * clean each resource only once (per node) regardless of how many failed
      * operations it has.
      */
     rscs = pcmk__strkey_table(NULL, NULL);
 
     // Normalize interval to milliseconds for comparison to history entry
     if (operation) {
         guint interval_ms = 0U;
 
         pcmk_parse_interval_spec(interval_spec, &interval_ms);
         interval_ms_s = crm_strdup_printf("%u", interval_ms);
     }
 
     for (xmlNode *xml_op = pcmk__xe_first_child(scheduler->failed, NULL, NULL,
                                                 NULL);
          xml_op != NULL; xml_op = pcmk__xe_next(xml_op)) {
 
         failed_id = crm_element_value(xml_op, PCMK__XA_RSC_ID);
         if (failed_id == NULL) {
             // Malformed history entry, should never happen
             continue;
         }
 
         // No resource specified means all resources match
         if (rsc_id) {
             pcmk_resource_t *fail_rsc = NULL;
 
             fail_rsc = pe_find_resource_with_flags(scheduler->resources,
                                                    failed_id,
                                                    pcmk_rsc_match_history
                                                    |pcmk_rsc_match_anon_basename);
             if (!fail_rsc || !pcmk__str_eq(rsc_id, fail_rsc->id, pcmk__str_casei)) {
                 continue;
             }
         }
 
         // Host name should always have been provided by this point
         failed_value = crm_element_value(xml_op, PCMK_XA_UNAME);
         if (!pcmk__str_eq(node_name, failed_value, pcmk__str_casei)) {
             continue;
         }
 
         // No operation specified means all operations match
         if (operation) {
             failed_value = crm_element_value(xml_op, PCMK_XA_OPERATION);
             if (!pcmk__str_eq(operation, failed_value, pcmk__str_casei)) {
                 continue;
             }
 
             // Interval (if operation was specified) defaults to 0 (not all)
             failed_value = crm_element_value(xml_op, PCMK_META_INTERVAL);
             if (!pcmk__str_eq(interval_ms_s, failed_value, pcmk__str_casei)) {
                 continue;
             }
         }
 
         g_hash_table_add(rscs, (gpointer) failed_id);
     }
 
     free(interval_ms_s);
 
     g_hash_table_iter_init(&iter, rscs);
     while (g_hash_table_iter_next(&iter, (gpointer *) &failed_id, NULL)) {
         crm_debug("Erasing failures of %s on %s", failed_id, node_name);
         rc = clear_rsc_history(controld_api, node_name, failed_id, scheduler);
         if (rc != pcmk_rc_ok) {
             return rc;
         }
     }
     g_hash_table_destroy(rscs);
     return rc;
 }
 
 // \return Standard Pacemaker return code
 static int
 clear_rsc_fail_attrs(const pcmk_resource_t *rsc, const char *operation,
                      const char *interval_spec, const pcmk_node_t *node)
 {
     int rc = pcmk_rc_ok;
     int attr_options = pcmk__node_attr_none;
     char *rsc_name = rsc_fail_name(rsc);
 
     if (pcmk__is_pacemaker_remote_node(node)) {
         attr_options |= pcmk__node_attr_remote;
     }
 
     rc = pcmk__attrd_api_clear_failures(NULL, node->details->uname, rsc_name,
                                         operation, interval_spec, NULL,
                                         attr_options);
     free(rsc_name);
     return rc;
 }
 
 // \return Standard Pacemaker return code
 int
 cli_resource_delete(pcmk_ipc_api_t *controld_api, const char *host_uname,
                     const pcmk_resource_t *rsc, const char *operation,
                     const char *interval_spec, bool just_failures,
                     pcmk_scheduler_t *scheduler, gboolean force)
 {
     pcmk__output_t *out = scheduler->priv;
     int rc = pcmk_rc_ok;
     pcmk_node_t *node = NULL;
 
     if (rsc == NULL) {
         return ENXIO;
 
     } else if (rsc->children) {
 
         for (const GList *lpc = rsc->children; lpc != NULL; lpc = lpc->next) {
             const pcmk_resource_t *child = (const pcmk_resource_t *) lpc->data;
 
             rc = cli_resource_delete(controld_api, host_uname, child, operation,
                                      interval_spec, just_failures, scheduler,
                                      force);
             if (rc != pcmk_rc_ok) {
                 return rc;
             }
         }
         return pcmk_rc_ok;
 
     } else if (host_uname == NULL) {
         GList *lpc = NULL;
         GList *nodes = g_hash_table_get_values(rsc->known_on);
 
         if(nodes == NULL && force) {
             nodes = pcmk__copy_node_list(scheduler->nodes, false);
 
         } else if(nodes == NULL && rsc->exclusive_discover) {
             GHashTableIter iter;
             pcmk_node_t *node = NULL;
 
             g_hash_table_iter_init(&iter, rsc->allowed_nodes);
             while (g_hash_table_iter_next(&iter, NULL, (void**)&node)) {
                 if(node->weight >= 0) {
                     nodes = g_list_prepend(nodes, node);
                 }
             }
 
         } else if(nodes == NULL) {
             nodes = g_hash_table_get_values(rsc->allowed_nodes);
         }
 
         for (lpc = nodes; lpc != NULL; lpc = lpc->next) {
             node = (pcmk_node_t *) lpc->data;
 
             if (node->details->online) {
                 rc = cli_resource_delete(controld_api, node->details->uname, rsc,
                                          operation, interval_spec, just_failures,
                                          scheduler, force);
             }
             if (rc != pcmk_rc_ok) {
                 g_list_free(nodes);
                 return rc;
             }
         }
 
         g_list_free(nodes);
         return pcmk_rc_ok;
     }
 
     node = pcmk_find_node(scheduler, host_uname);
 
     if (node == NULL) {
         out->err(out, "Unable to clean up %s because node %s not found",
                  rsc->id, host_uname);
         return ENODEV;
     }
 
     if (!node->details->rsc_discovery_enabled) {
         out->err(out, "Unable to clean up %s because resource discovery disabled on %s",
                  rsc->id, host_uname);
         return EOPNOTSUPP;
     }
 
     if (controld_api == NULL) {
         out->err(out, "Dry run: skipping clean-up of %s on %s due to CIB_file",
                  rsc->id, host_uname);
         return pcmk_rc_ok;
     }
 
     rc = clear_rsc_fail_attrs(rsc, operation, interval_spec, node);
     if (rc != pcmk_rc_ok) {
         out->err(out, "Unable to clean up %s failures on %s: %s",
                  rsc->id, host_uname, pcmk_rc_str(rc));
         return rc;
     }
 
     if (just_failures) {
         rc = clear_rsc_failures(out, controld_api, host_uname, rsc->id, operation,
                                 interval_spec, scheduler);
     } else {
         rc = clear_rsc_history(controld_api, host_uname, rsc->id, scheduler);
     }
     if (rc != pcmk_rc_ok) {
         out->err(out, "Cleaned %s failures on %s, but unable to clean history: %s",
                  rsc->id, host_uname, pcmk_rc_str(rc));
     } else {
         out->info(out, "Cleaned up %s on %s", rsc->id, host_uname);
     }
     return rc;
 }
 
 // \return Standard Pacemaker return code
 int
 cli_cleanup_all(pcmk_ipc_api_t *controld_api, const char *node_name,
                 const char *operation, const char *interval_spec,
                 pcmk_scheduler_t *scheduler)
 {
     pcmk__output_t *out = scheduler->priv;
     int rc = pcmk_rc_ok;
     int attr_options = pcmk__node_attr_none;
     const char *display_name = node_name? node_name : "all nodes";
 
     if (controld_api == NULL) {
         out->info(out, "Dry run: skipping clean-up of %s due to CIB_file",
                   display_name);
         return rc;
     }
 
     if (node_name) {
         pcmk_node_t *node = pcmk_find_node(scheduler, node_name);
 
         if (node == NULL) {
             out->err(out, "Unknown node: %s", node_name);
             return ENXIO;
         }
         if (pcmk__is_pacemaker_remote_node(node)) {
             attr_options |= pcmk__node_attr_remote;
         }
     }
 
     rc = pcmk__attrd_api_clear_failures(NULL, node_name, NULL, operation,
                                         interval_spec, NULL, attr_options);
     if (rc != pcmk_rc_ok) {
         out->err(out, "Unable to clean up all failures on %s: %s",
                  display_name, pcmk_rc_str(rc));
         return rc;
     }
 
     if (node_name) {
         rc = clear_rsc_failures(out, controld_api, node_name, NULL,
                                 operation, interval_spec, scheduler);
         if (rc != pcmk_rc_ok) {
             out->err(out, "Cleaned all resource failures on %s, but unable to clean history: %s",
                      node_name, pcmk_rc_str(rc));
             return rc;
         }
     } else {
         for (GList *iter = scheduler->nodes; iter; iter = iter->next) {
             pcmk_node_t *node = (pcmk_node_t *) iter->data;
 
             rc = clear_rsc_failures(out, controld_api, node->details->uname, NULL,
                                     operation, interval_spec, scheduler);
             if (rc != pcmk_rc_ok) {
                 out->err(out, "Cleaned all resource failures on all nodes, but unable to clean history: %s",
                          pcmk_rc_str(rc));
                 return rc;
             }
         }
     }
 
     out->info(out, "Cleaned up all resources on %s", display_name);
     return rc;
 }
 
 static void
 check_role(resource_checks_t *checks)
 {
     const char *role_s = g_hash_table_lookup(checks->rsc->meta,
                                              PCMK_META_TARGET_ROLE);
 
     if (role_s == NULL) {
         return;
     }
     switch (pcmk_parse_role(role_s)) {
         case pcmk_role_stopped:
             checks->flags |= rsc_remain_stopped;
             break;
 
         case pcmk_role_unpromoted:
             if (pcmk_is_set(pe__const_top_resource(checks->rsc, false)->flags,
                             pcmk_rsc_promotable)) {
                 checks->flags |= rsc_unpromotable;
             }
             break;
 
         default:
             break;
     }
 }
 
 static void
 check_managed(resource_checks_t *checks)
 {
     const char *managed_s = g_hash_table_lookup(checks->rsc->meta,
                                                 PCMK_META_IS_MANAGED);
 
     if ((managed_s != NULL) && !crm_is_true(managed_s)) {
         checks->flags |= rsc_unmanaged;
     }
 }
 
 static void
 check_locked(resource_checks_t *checks)
 {
     if (checks->rsc->lock_node != NULL) {
         checks->flags |= rsc_locked;
         checks->lock_node = checks->rsc->lock_node->details->uname;
     }
 }
 
 static bool
 node_is_unhealthy(pcmk_node_t *node)
 {
     switch (pe__health_strategy(node->details->data_set)) {
         case pcmk__health_strategy_none:
             break;
 
         case pcmk__health_strategy_no_red:
             if (pe__node_health(node) < 0) {
                 return true;
             }
             break;
 
         case pcmk__health_strategy_only_green:
             if (pe__node_health(node) <= 0) {
                 return true;
             }
             break;
 
         case pcmk__health_strategy_progressive:
         case pcmk__health_strategy_custom:
             /* @TODO These are finite scores, possibly with rules, and possibly
              * combining with other scores, so attributing these as a cause is
              * nontrivial.
              */
             break;
     }
     return false;
 }
 
 static void
 check_node_health(resource_checks_t *checks, pcmk_node_t *node)
 {
     if (node == NULL) {
         GHashTableIter iter;
         bool allowed = false;
         bool all_nodes_unhealthy = true;
 
         g_hash_table_iter_init(&iter, checks->rsc->allowed_nodes);
         while (g_hash_table_iter_next(&iter, NULL, (void **) &node)) {
             allowed = true;
             if (!node_is_unhealthy(node)) {
                 all_nodes_unhealthy = false;
                 break;
             }
         }
         if (allowed && all_nodes_unhealthy) {
             checks->flags |= rsc_node_health;
         }
 
     } else if (node_is_unhealthy(node)) {
         checks->flags |= rsc_node_health;
     }
 }
 
 int
 cli_resource_check(pcmk__output_t *out, pcmk_resource_t *rsc, pcmk_node_t *node)
 {
     resource_checks_t checks = { .rsc = rsc };
 
     check_role(&checks);
     check_managed(&checks);
     check_locked(&checks);
     check_node_health(&checks, node);
 
     return out->message(out, "resource-check-list", &checks);
 }
 
 // \return Standard Pacemaker return code
 int
 cli_resource_fail(pcmk_ipc_api_t *controld_api, const char *host_uname,
                   const char *rsc_id, pcmk_scheduler_t *scheduler)
 {
     crm_notice("Failing %s on %s", rsc_id, host_uname);
     return send_lrm_rsc_op(controld_api, true, host_uname, rsc_id, scheduler);
 }
 
 static GHashTable *
 generate_resource_params(pcmk_resource_t *rsc, pcmk_node_t *node,
                          pcmk_scheduler_t *scheduler)
 {
     GHashTable *params = NULL;
     GHashTable *meta = NULL;
     GHashTable *combined = NULL;
     GHashTableIter iter;
     char *key = NULL;
     char *value = NULL;
 
     combined = pcmk__strkey_table(free, free);
 
     params = pe_rsc_params(rsc, node, scheduler);
     if (params != NULL) {
         g_hash_table_iter_init(&iter, params);
         while (g_hash_table_iter_next(&iter, (gpointer *) & key, (gpointer *) & value)) {
             pcmk__insert_dup(combined, key, value);
         }
     }
 
     meta = pcmk__strkey_table(free, free);
     get_meta_attributes(meta, rsc, NULL, scheduler);
     if (meta != NULL) {
         g_hash_table_iter_init(&iter, meta);
         while (g_hash_table_iter_next(&iter, (gpointer *) & key, (gpointer *) & value)) {
             char *crm_name = crm_meta_name(key);
 
             g_hash_table_insert(combined, crm_name, strdup(value));
         }
         g_hash_table_destroy(meta);
     }
 
     return combined;
 }
 
 bool resource_is_running_on(pcmk_resource_t *rsc, const char *host)
 {
     bool found = true;
     GList *hIter = NULL;
     GList *hosts = NULL;
 
     if (rsc == NULL) {
         return false;
     }
 
     rsc->fns->location(rsc, &hosts, TRUE);
     for (hIter = hosts; host != NULL && hIter != NULL; hIter = hIter->next) {
         pcmk_node_t *node = (pcmk_node_t *) hIter->data;
 
         if (pcmk__strcase_any_of(host, node->details->uname, node->details->id, NULL)) {
             crm_trace("Resource %s is running on %s\n", rsc->id, host);
             goto done;
         }
     }
 
     if (host != NULL) {
         crm_trace("Resource %s is not running on: %s\n", rsc->id, host);
         found = false;
 
     } else if(host == NULL && hosts == NULL) {
         crm_trace("Resource %s is not running\n", rsc->id);
         found = false;
     }
 
   done:
     g_list_free(hosts);
     return found;
 }
 
 /*!
  * \internal
  * \brief Create a list of all resources active on host from a given list
  *
  * \param[in] host      Name of host to check whether resources are active
  * \param[in] rsc_list  List of resources to check
  *
  * \return New list of resources from list that are active on host
  */
 static GList *
 get_active_resources(const char *host, GList *rsc_list)
 {
     GList *rIter = NULL;
     GList *active = NULL;
 
     for (rIter = rsc_list; rIter != NULL; rIter = rIter->next) {
         pcmk_resource_t *rsc = (pcmk_resource_t *) rIter->data;
 
         /* Expand groups to their members, because if we're restarting a member
          * other than the first, we can't otherwise tell which resources are
          * stopping and starting.
          */
         if (pcmk__is_group(rsc)) {
             active = g_list_concat(active,
                                    get_active_resources(host, rsc->children));
         } else if (resource_is_running_on(rsc, host)) {
             active = g_list_append(active, strdup(rsc->id));
         }
     }
     return active;
 }
 
 static void dump_list(GList *items, const char *tag)
 {
     int lpc = 0;
     GList *item = NULL;
 
     for (item = items; item != NULL; item = item->next) {
         crm_trace("%s[%d]: %s", tag, lpc, (char*)item->data);
         lpc++;
     }
 }
 
 static void display_list(pcmk__output_t *out, GList *items, const char *tag)
 {
     GList *item = NULL;
 
     for (item = items; item != NULL; item = item->next) {
         out->info(out, "%s%s", tag, (const char *)item->data);
     }
 }
 
 /*!
  * \internal
  * \brief Upgrade XML to latest schema version and use it as scheduler input
  *
  * This also updates the scheduler timestamp to the current time.
  *
  * \param[in,out] scheduler  Scheduler data to update
  * \param[in,out] xml        XML to use as input
  *
  * \return Standard Pacemaker return code
  * \note On success, caller is responsible for freeing memory allocated for
  *       scheduler->now.
  */
 int
 update_scheduler_input(pcmk_scheduler_t *scheduler, xmlNode **xml)
 {
     int rc = pcmk__update_configured_schema(xml, false);
 
     if (rc == pcmk_rc_ok) {
         scheduler->input = *xml;
         scheduler->now = crm_time_new(NULL);
     }
     return pcmk_rc_ok;
 }
 
 /*!
  * \internal
  * \brief Update scheduler XML input based on a CIB query
  *
  * \param[in] scheduler  Scheduler data to initialize
  * \param[in] cib        Connection to the CIB manager
  *
  * \return Standard Pacemaker return code
  * \note On success, caller is responsible for freeing memory allocated for
  *       scheduler->input and scheduler->now.
  */
 static int
 update_scheduler_input_to_cib(pcmk__output_t *out, pcmk_scheduler_t *scheduler,
                               cib_t *cib)
 {
     xmlNode *cib_xml_copy = NULL;
     int rc = pcmk_rc_ok;
 
     rc = cib->cmds->query(cib, NULL, &cib_xml_copy, cib_scope_local | cib_sync_call);
     rc = pcmk_legacy2rc(rc);
 
     if (rc != pcmk_rc_ok) {
         out->err(out, "Could not obtain the current CIB: %s (%d)", pcmk_rc_str(rc), rc);
         return rc;
     }
     rc = update_scheduler_input(scheduler, &cib_xml_copy);
     if (rc != pcmk_rc_ok) {
         out->err(out, "Could not upgrade the current CIB XML");
         free_xml(cib_xml_copy);
         return rc;
     }
 
     return rc;
 }
 
 // \return Standard Pacemaker return code
 static int
 update_dataset(cib_t *cib, pcmk_scheduler_t *scheduler, bool simulate)
 {
     char *pid = NULL;
     char *shadow_file = NULL;
     cib_t *shadow_cib = NULL;
     int rc = pcmk_rc_ok;
 
     pcmk__output_t *out = scheduler->priv;
 
     pe_reset_working_set(scheduler);
     pcmk__set_scheduler_flags(scheduler,
                               pcmk_sched_no_counts|pcmk_sched_no_compat);
     rc = update_scheduler_input_to_cib(out, scheduler, cib);
     if (rc != pcmk_rc_ok) {
         return rc;
     }
 
     if(simulate) {
         bool prev_quiet = false;
 
         pid = pcmk__getpid_s();
         shadow_cib = cib_shadow_new(pid);
         shadow_file = get_shadow_file(pid);
 
         if (shadow_cib == NULL) {
             out->err(out, "Could not create shadow cib: '%s'", pid);
             rc = ENXIO;
             goto done;
         }
 
         rc = pcmk__xml_write_file(scheduler->input, shadow_file, false, NULL);
         if (rc != pcmk_rc_ok) {
             out->err(out, "Could not populate shadow cib: %s", pcmk_rc_str(rc));
             goto done;
         }
 
         rc = shadow_cib->cmds->signon(shadow_cib, crm_system_name, cib_command);
         rc = pcmk_legacy2rc(rc);
 
         if (rc != pcmk_rc_ok) {
             out->err(out, "Could not connect to shadow cib: %s",
                      pcmk_rc_str(rc));
             goto done;
         }
 
         pcmk__schedule_actions(scheduler->input,
                                pcmk_sched_no_counts|pcmk_sched_no_compat,
                                scheduler);
 
         prev_quiet = out->is_quiet(out);
         out->quiet = true;
         pcmk__simulate_transition(scheduler, shadow_cib, NULL);
         out->quiet = prev_quiet;
 
         rc = update_dataset(shadow_cib, scheduler, false);
 
     } else {
         cluster_status(scheduler);
     }
 
   done:
     // Do not free scheduler->input here, we need rsc->xml to be valid later on
     cib_delete(shadow_cib);
     free(pid);
 
     if(shadow_file) {
         unlink(shadow_file);
         free(shadow_file);
     }
 
     return rc;
 }
 
 /*!
  * \internal
  * \brief Find the maximum stop timeout of a resource and its children (if any)
  *
  * \param[in,out] rsc  Resource to get timeout for
  *
  * \return Maximum stop timeout for \p rsc (in milliseconds)
  */
 static guint
 max_rsc_stop_timeout(pcmk_resource_t *rsc)
 {
     long long result_ll;
     guint max_delay = 0;
     xmlNode *config = NULL;
     GHashTable *meta = NULL;
 
     if (rsc == NULL) {
         return 0;
     }
 
     // If resource is collective, use maximum of its children's stop timeouts
     if (rsc->children != NULL) {
         for (GList *iter = rsc->children; iter; iter = iter->next) {
             pcmk_resource_t *child = iter->data;
             guint delay = max_rsc_stop_timeout(child);
 
             if (delay > max_delay) {
                 pcmk__rsc_trace(rsc,
                                 "Maximum stop timeout for %s is now %s "
                                 "due to %s", rsc->id,
                                 pcmk__readable_interval(delay), child->id);
                 max_delay = delay;
             }
         }
         return max_delay;
     }
 
     // Get resource's stop action configuration from CIB
     config = pcmk__find_action_config(rsc, PCMK_ACTION_STOP, 0, true);
 
     /* Get configured timeout for stop action (fully evaluated for rules,
      * defaults, etc.).
      *
      * @TODO This currently ignores node (which might matter for rules)
      */
     meta = pcmk__unpack_action_meta(rsc, NULL, PCMK_ACTION_STOP, 0, config);
     if ((pcmk__scan_ll(g_hash_table_lookup(meta, PCMK_META_TIMEOUT),
                        &result_ll, -1LL) == pcmk_rc_ok) && (result_ll >= 0)) {
         max_delay = (guint) QB_MIN(result_ll, UINT_MAX);
     }
     g_hash_table_destroy(meta);
 
     return max_delay;
 }
 
 /*!
  * \internal
  * \brief Find a reasonable waiting time for stopping any one resource in a list
  *
  * \param[in,out] scheduler  Scheduler data
  * \param[in]     resources  List of names of resources that will be stopped
  *
  * \return Rough estimate of a reasonable time to wait (in seconds) to stop any
  *         one resource in \p resources
  * \note This estimate is very rough, simply the maximum stop timeout of all
  *       given resources and their children, plus a small fudge factor. It does
  *       not account for children that must be stopped in sequence, action
  *       throttling, or any demotions needed. It checks the stop timeout, even
  *       if the resources in question are actually being started.
  */
 static guint
 wait_time_estimate(pcmk_scheduler_t *scheduler, const GList *resources)
 {
     guint max_delay = 0U;
 
     // Find maximum stop timeout in milliseconds
     for (const GList *item = resources; item != NULL; item = item->next) {
         pcmk_resource_t *rsc = pe_find_resource(scheduler->resources,
                                                 (const char *) item->data);
         guint delay = max_rsc_stop_timeout(rsc);
 
         if (delay > max_delay) {
             pcmk__rsc_trace(rsc,
                             "Wait time is now %s due to %s",
                             pcmk__readable_interval(delay), rsc->id);
             max_delay = delay;
         }
     }
 
     return (max_delay / 1000U) + 5U;
 }
 
 #define waiting_for_starts(d, r, h) ((d != NULL) || \
                                     (!resource_is_running_on((r), (h))))
 
 /*!
  * \internal
  * \brief Restart a resource (on a particular host if requested).
  *
  * \param[in,out] out                 Output object
  * \param[in,out] rsc                 The resource to restart
  * \param[in]     node                Node to restart resource on (NULL for all)
  * \param[in]     move_lifetime       If not NULL, how long constraint should
  *                                    remain in effect (as ISO 8601 string)
  * \param[in]     timeout_ms          Consider failed if actions do not complete
  *                                    in this time (specified in milliseconds,
  *                                    but a two-second granularity is actually
  *                                    used; if 0, it will be calculated based on
  *                                    the resource timeout)
  * \param[in,out] cib                 Connection to the CIB manager
  * \param[in]     cib_options         Group of enum cib_call_options flags to
  *                                    use with CIB calls
  * \param[in]     promoted_role_only  If true, limit to promoted instances
  * \param[in]     force               If true, apply only to requested instance
  *                                    if part of a collective resource
  *
  * \return Standard Pacemaker return code (exits on certain failures)
  */
 int
 cli_resource_restart(pcmk__output_t *out, pcmk_resource_t *rsc,
                      const pcmk_node_t *node, const char *move_lifetime,
                      guint timeout_ms, cib_t *cib, int cib_options,
                      gboolean promoted_role_only, gboolean force)
 {
     int rc = pcmk_rc_ok;
     int lpc = 0;
     int before = 0;
     guint step_timeout_s = 0;
     guint sleep_interval = 2U;
     guint timeout = timeout_ms / 1000U;
 
     bool stop_via_ban = false;
     char *rsc_id = NULL;
     char *lookup_id = NULL;
     char *orig_target_role = NULL;
 
     GList *list_delta = NULL;
     GList *target_active = NULL;
     GList *current_active = NULL;
     GList *restart_target_active = NULL;
 
     pcmk_scheduler_t *scheduler = NULL;
     pcmk_resource_t *parent = uber_parent(rsc);
 
     bool running = false;
     const char *id = rsc->clone_name ? rsc->clone_name : rsc->id;
     const char *host = node ? node->details->uname : NULL;
 
     /* If the implicit resource or primitive resource of a bundle is given, operate on the
      * bundle itself instead.
      */
     if (pcmk__is_bundled(rsc)) {
         rsc = parent->parent;
     }
 
     running = resource_is_running_on(rsc, host);
 
     if (pcmk__is_clone(parent) && !running) {
         if (pcmk__is_unique_clone(parent)) {
             lookup_id = strdup(rsc->id);
         } else {
             lookup_id = clone_strip(rsc->id);
         }
 
         rsc = parent->fns->find_rsc(parent, lookup_id, node,
                                     pcmk_rsc_match_basename
                                     |pcmk_rsc_match_current_node);
         free(lookup_id);
         running = resource_is_running_on(rsc, host);
     }
 
     if (!running) {
         if (host) {
             out->err(out, "%s is not running on %s and so cannot be restarted", id, host);
         } else {
             out->err(out, "%s is not running anywhere and so cannot be restarted", id);
         }
         return ENXIO;
     }
 
     if (!pcmk_is_set(rsc->flags, pcmk_rsc_managed)) {
         out->err(out, "Unmanaged resources cannot be restarted.");
         return EAGAIN;
     }
 
     rsc_id = strdup(rsc->id);
 
     if (pcmk__is_unique_clone(parent)) {
         lookup_id = strdup(rsc->id);
     } else {
         lookup_id = clone_strip(rsc->id);
     }
 
     if (host) {
         if (pcmk__is_clone(rsc) || pe_bundle_replicas(rsc)) {
             stop_via_ban = true;
         } else if (pcmk__is_clone(parent)) {
             stop_via_ban = true;
             free(lookup_id);
             lookup_id = strdup(parent->id);
         }
     }
 
     /*
       grab full cib
       determine originally active resources
       disable or ban
       poll cib and watch for affected resources to get stopped
       without --timeout, calculate the stop timeout for each step and wait for that
       if we hit --timeout or the service timeout, re-enable or un-ban, report failure and indicate which resources we couldn't take down
       if everything stopped, re-enable or un-ban
       poll cib and watch for affected resources to get started
       without --timeout, calculate the start timeout for each step and wait for that
       if we hit --timeout or the service timeout, report (different) failure and indicate which resources we couldn't bring back up
       report success
 
       Optimizations:
       - use constraints to determine ordered list of affected resources
       - Allow a --no-deps option (aka. --force-restart)
     */
 
     scheduler = pe_new_working_set();
     if (scheduler == NULL) {
         rc = errno;
         out->err(out, "Could not allocate scheduler data: %s", pcmk_rc_str(rc));
         goto done;
     }
 
     scheduler->priv = out;
     rc = update_dataset(cib, scheduler, false);
 
     if(rc != pcmk_rc_ok) {
         out->err(out, "Could not get new resource list: %s (%d)", pcmk_rc_str(rc), rc);
         goto done;
     }
 
     restart_target_active = get_active_resources(host, scheduler->resources);
     current_active = get_active_resources(host, scheduler->resources);
 
     dump_list(current_active, "Origin");
 
     if (stop_via_ban) {
         /* Stop the clone or bundle instance by banning it from the host */
         out->quiet = true;
         rc = cli_resource_ban(out, lookup_id, host, move_lifetime, cib,
                               cib_options, promoted_role_only,
                               PCMK_ROLE_PROMOTED);
     } else {
         xmlNode *xml_search = NULL;
 
         /* Stop the resource by setting PCMK_META_TARGET_ROLE to Stopped.
          * Remember any existing PCMK_META_TARGET_ROLE so we can restore it
          * later (though it only makes any difference if it's Unpromoted).
          */
 
         rc = find_resource_attr(out, cib, PCMK_XA_VALUE, lookup_id, NULL, NULL, NULL,
                                 PCMK_META_TARGET_ROLE, &xml_search);
 
         if (rc == pcmk_rc_ok) {
             orig_target_role = crm_element_value_copy(xml_search, PCMK_XA_VALUE);
         }
 
         free_xml(xml_search);
 
         rc = cli_resource_update_attribute(rsc, rsc_id, NULL,
                                            PCMK_XE_META_ATTRIBUTES, NULL,
                                            PCMK_META_TARGET_ROLE,
                                            PCMK_ACTION_STOPPED, FALSE, cib,
                                            force);
     }
     if(rc != pcmk_rc_ok) {
         out->err(out, "Could not set " PCMK_META_TARGET_ROLE " for %s: %s (%d)",
                  rsc_id, pcmk_rc_str(rc), rc);
         if (current_active != NULL) {
             g_list_free_full(current_active, free);
             current_active = NULL;
         }
         if (restart_target_active != NULL) {
             g_list_free_full(restart_target_active, free);
             restart_target_active = NULL;
         }
         goto done;
     }
 
     rc = update_dataset(cib, scheduler, true);
     if(rc != pcmk_rc_ok) {
         out->err(out, "Could not determine which resources would be stopped");
         goto failure;
     }
 
     target_active = get_active_resources(host, scheduler->resources);
     dump_list(target_active, "Target");
 
     list_delta = pcmk__subtract_lists(current_active, target_active, (GCompareFunc) strcmp);
     out->info(out, "Waiting for %d resources to stop:", g_list_length(list_delta));
     display_list(out, list_delta, " * ");
 
     step_timeout_s = timeout / sleep_interval;
     while (list_delta != NULL) {
         before = g_list_length(list_delta);
         if(timeout_ms == 0) {
             step_timeout_s = wait_time_estimate(scheduler, list_delta)
                              / sleep_interval;
         }
 
         /* We probably don't need the entire step timeout */
         for(lpc = 0; (lpc < step_timeout_s) && (list_delta != NULL); lpc++) {
             sleep(sleep_interval);
             if(timeout) {
                 timeout -= sleep_interval;
                 crm_trace("%us remaining", timeout);
             }
             rc = update_dataset(cib, scheduler, FALSE);
             if(rc != pcmk_rc_ok) {
                 out->err(out, "Could not determine which resources were stopped");
                 goto failure;
             }
 
             if (current_active != NULL) {
                 g_list_free_full(current_active, free);
             }
             current_active = get_active_resources(host, scheduler->resources);
 
             g_list_free(list_delta);
             list_delta = pcmk__subtract_lists(current_active, target_active, (GCompareFunc) strcmp);
 
             dump_list(current_active, "Current");
             dump_list(list_delta, "Delta");
         }
 
         crm_trace("%d (was %d) resources remaining", g_list_length(list_delta), before);
         if(before == g_list_length(list_delta)) {
             /* aborted during stop phase, print the contents of list_delta */
             out->err(out, "Could not complete shutdown of %s, %d resources remaining", rsc_id, g_list_length(list_delta));
             display_list(out, list_delta, " * ");
             rc = ETIME;
             goto failure;
         }
 
     }
 
     if (stop_via_ban) {
         rc = cli_resource_clear(lookup_id, host, NULL, cib, cib_options, true, force);
 
     } else if (orig_target_role) {
         rc = cli_resource_update_attribute(rsc, rsc_id, NULL,
                                            PCMK_XE_META_ATTRIBUTES, NULL,
                                            PCMK_META_TARGET_ROLE,
                                            orig_target_role, FALSE, cib, force);
         free(orig_target_role);
         orig_target_role = NULL;
     } else {
         rc = cli_resource_delete_attribute(rsc, rsc_id, NULL,
                                            PCMK_XE_META_ATTRIBUTES, NULL,
                                            PCMK_META_TARGET_ROLE, cib,
                                            cib_options, force);
     }
 
     if(rc != pcmk_rc_ok) {
         out->err(out,
                  "Could not unset " PCMK_META_TARGET_ROLE " for %s: %s (%d)",
                  rsc_id, pcmk_rc_str(rc), rc);
         goto done;
     }
 
     if (target_active != NULL) {
         g_list_free_full(target_active, free);
     }
     target_active = restart_target_active;
 
     list_delta = pcmk__subtract_lists(target_active, current_active, (GCompareFunc) strcmp);
     out->info(out, "Waiting for %d resources to start again:", g_list_length(list_delta));
     display_list(out, list_delta, " * ");
 
     step_timeout_s = timeout / sleep_interval;
     while (waiting_for_starts(list_delta, rsc, host)) {
         before = g_list_length(list_delta);
         if(timeout_ms == 0) {
             step_timeout_s = wait_time_estimate(scheduler, list_delta)
                              / sleep_interval;
         }
 
         /* We probably don't need the entire step timeout */
         for (lpc = 0; (lpc < step_timeout_s) && waiting_for_starts(list_delta, rsc, host); lpc++) {
 
             sleep(sleep_interval);
             if(timeout) {
                 timeout -= sleep_interval;
                 crm_trace("%ds remaining", timeout);
             }
 
             rc = update_dataset(cib, scheduler, false);
             if(rc != pcmk_rc_ok) {
                 out->err(out, "Could not determine which resources were started");
                 goto failure;
             }
 
             /* It's OK if dependent resources moved to a different node,
              * so we check active resources on all nodes.
              */
             if (current_active != NULL) {
                 g_list_free_full(current_active, free);
             }
             current_active = get_active_resources(NULL, scheduler->resources);
 
             g_list_free(list_delta);
             list_delta = pcmk__subtract_lists(target_active, current_active, (GCompareFunc) strcmp);
             dump_list(current_active, "Current");
             dump_list(list_delta, "Delta");
         }
 
         if(before == g_list_length(list_delta)) {
             /* aborted during start phase, print the contents of list_delta */
             out->err(out, "Could not complete restart of %s, %d resources remaining", rsc_id, g_list_length(list_delta));
             display_list(out, list_delta, " * ");
             rc = ETIME;
             goto failure;
         }
 
     }
 
     rc = pcmk_rc_ok;
     goto done;
 
   failure:
     if (stop_via_ban) {
         cli_resource_clear(lookup_id, host, NULL, cib, cib_options, true, force);
     } else if (orig_target_role) {
         cli_resource_update_attribute(rsc, rsc_id, NULL,
                                       PCMK_XE_META_ATTRIBUTES, NULL,
                                       PCMK_META_TARGET_ROLE, orig_target_role,
                                       FALSE, cib, force);
         free(orig_target_role);
     } else {
         cli_resource_delete_attribute(rsc, rsc_id, NULL,
                                       PCMK_XE_META_ATTRIBUTES, NULL,
                                       PCMK_META_TARGET_ROLE, cib, cib_options,
                                       force);
     }
 
 done:
     if (list_delta != NULL) {
         g_list_free(list_delta);
     }
     if (current_active != NULL) {
         g_list_free_full(current_active, free);
     }
     if (target_active != NULL && (target_active != restart_target_active)) {
         g_list_free_full(target_active, free);
     }
     if (restart_target_active != NULL) {
         g_list_free_full(restart_target_active, free);
     }
     free(rsc_id);
     free(lookup_id);
     pe_free_working_set(scheduler);
     return rc;
 }
 
 static inline bool
 action_is_pending(const pcmk_action_t *action)
 {
     if (pcmk_any_flags_set(action->flags,
                            pcmk_action_optional|pcmk_action_pseudo)
         || !pcmk_is_set(action->flags, pcmk_action_runnable)
         || pcmk__str_eq(PCMK_ACTION_NOTIFY, action->task, pcmk__str_casei)) {
         return false;
     }
     return true;
 }
 
 /*!
  * \internal
  * \brief Check whether any actions in a list are pending
  *
  * \param[in] actions   List of actions to check
  *
  * \return true if any actions in the list are pending, otherwise false
  */
 static bool
 actions_are_pending(const GList *actions)
 {
     for (const GList *action = actions; action != NULL; action = action->next) {
         const pcmk_action_t *a = (const pcmk_action_t *) action->data;
 
         if (action_is_pending(a)) {
             crm_notice("Waiting for %s (flags=%#.8x)", a->uuid, a->flags);
             return true;
         }
     }
     return false;
 }
 
 static void
 print_pending_actions(pcmk__output_t *out, GList *actions)
 {
     GList *action;
 
     out->info(out, "Pending actions:");
     for (action = actions; action != NULL; action = action->next) {
         pcmk_action_t *a = (pcmk_action_t *) action->data;
 
         if (!action_is_pending(a)) {
             continue;
         }
 
         if (a->node) {
             out->info(out, "\tAction %d: %s\ton %s",
                       a->id, a->uuid, pcmk__node_name(a->node));
         } else {
             out->info(out, "\tAction %d: %s", a->id, a->uuid);
         }
     }
 }
 
 /* For --wait, timeout (in seconds) to use if caller doesn't specify one */
 #define WAIT_DEFAULT_TIMEOUT_S (60 * 60)
 
 /* For --wait, how long to sleep between cluster state checks */
 #define WAIT_SLEEP_S (2)
 
 /*!
  * \internal
  * \brief Wait until all pending cluster actions are complete
  *
  * This waits until either the CIB's transition graph is idle or a timeout is
  * reached.
  *
  * \param[in,out] out          Output object
  * \param[in]     timeout_ms   Consider failed if actions do not complete in
  *                             this time (specified in milliseconds, but
  *                             one-second granularity is actually used; if 0, a
  *                             default will be used)
  * \param[in,out] cib          Connection to the CIB manager
  *
  * \return Standard Pacemaker return code
  */
 int
 wait_till_stable(pcmk__output_t *out, guint timeout_ms, cib_t * cib)
 {
     pcmk_scheduler_t *scheduler = NULL;
     xmlXPathObjectPtr search;
     int rc = pcmk_rc_ok;
     bool pending_unknown_state_resources;
     time_t expire_time = time(NULL);
     time_t time_diff;
     bool printed_version_warning = out->is_quiet(out); // i.e. don't print if quiet
     char *xpath = NULL;
 
     if (timeout_ms == 0) {
         expire_time += WAIT_DEFAULT_TIMEOUT_S;
     } else {
         expire_time += (timeout_ms + 999) / 1000;
     }
 
     scheduler = pe_new_working_set();
     if (scheduler == NULL) {
         return ENOMEM;
     }
 
     xpath = crm_strdup_printf("/" PCMK_XE_CIB "/" PCMK_XE_STATUS
                               "/" PCMK__XE_NODE_STATE "/" PCMK__XE_LRM
                               "/" PCMK__XE_LRM_RESOURCES
                               "/" PCMK__XE_LRM_RESOURCE
                               "/" PCMK__XE_LRM_RSC_OP
                               "[@" PCMK__XA_RC_CODE "='%d']",
                               PCMK_OCF_UNKNOWN);
     do {
         /* Abort if timeout is reached */
         time_diff = expire_time - time(NULL);
         if (time_diff <= 0) {
             print_pending_actions(out, scheduler->actions);
             rc = ETIME;
             break;
         }
 
         crm_info("Waiting up to %lld seconds for cluster actions to complete",
                  (long long) time_diff);
 
         if (rc == pcmk_rc_ok) { /* this avoids sleep on first loop iteration */
             sleep(WAIT_SLEEP_S);
         }
 
         /* Get latest transition graph */
         pe_reset_working_set(scheduler);
         rc = update_scheduler_input_to_cib(out, scheduler, cib);
         if (rc != pcmk_rc_ok) {
             break;
         }
         pcmk__schedule_actions(scheduler->input,
                                pcmk_sched_no_counts|pcmk_sched_no_compat,
                                scheduler);
 
         if (!printed_version_warning) {
             /* If the DC has a different version than the local node, the two
              * could come to different conclusions about what actions need to be
              * done. Warn the user in this case.
              *
              * @TODO A possible long-term solution would be to reimplement the
              * wait as a new controller operation that would be forwarded to the
              * DC. However, that would have potential problems of its own.
              */
             const char *dc_version = g_hash_table_lookup(scheduler->config_hash,
                                                          PCMK_OPT_DC_VERSION);
 
             if (!pcmk__str_eq(dc_version, PACEMAKER_VERSION "-" BUILD_VERSION, pcmk__str_casei)) {
                 out->info(out, "warning: wait option may not work properly in "
                           "mixed-version cluster");
                 printed_version_warning = true;
             }
         }
 
         search = xpath_search(scheduler->input, xpath);
         pending_unknown_state_resources = (numXpathResults(search) > 0);
         freeXpathObject(search);
     } while (actions_are_pending(scheduler->actions) || pending_unknown_state_resources);
 
     pe_free_working_set(scheduler);
     free(xpath);
     return rc;
 }
 
 static const char *
 get_action(const char *rsc_action) {
     const char *action = NULL;
 
     if (pcmk__str_eq(rsc_action, "validate", pcmk__str_casei)) {
         action = PCMK_ACTION_VALIDATE_ALL;
 
     } else if (pcmk__str_eq(rsc_action, "force-check", pcmk__str_casei)) {
         action = PCMK_ACTION_MONITOR;
 
     } else if (pcmk__strcase_any_of(rsc_action, "force-start", "force-stop",
                                     "force-demote", "force-promote", NULL)) {
         action = rsc_action+6;
     } else {
         action = rsc_action;
     }
 
     return action;
 }
 
 /*!
  * \brief Set up environment variables as expected by resource agents
  *
  * When the cluster executes resource agents, it adds certain environment
  * variables (directly or via resource meta-attributes) expected by some
  * resource agents. Add the essential ones that many resource agents expect, so
  * the behavior is the same for command-line execution.
  *
  * \param[in,out] params       Resource parameters that will be passed to agent
  * \param[in]     timeout_ms   Action timeout (in milliseconds)
  * \param[in]     check_level  OCF check level
  * \param[in]     verbosity    Verbosity level
  */
 static void
 set_agent_environment(GHashTable *params, guint timeout_ms, int check_level,
                       int verbosity)
 {
     g_hash_table_insert(params, crm_meta_name(PCMK_META_TIMEOUT),
                         crm_strdup_printf("%u", timeout_ms));
 
     pcmk__insert_dup(params, PCMK_XA_CRM_FEATURE_SET, CRM_FEATURE_SET);
 
     if (check_level >= 0) {
         char *level = crm_strdup_printf("%d", check_level);
 
         setenv("OCF_CHECK_LEVEL", level, 1);
         free(level);
     }
 
     pcmk__set_env_option(PCMK__ENV_DEBUG, ((verbosity > 0)? "1" : "0"), true);
     if (verbosity > 1) {
         setenv("OCF_TRACE_RA", "1", 1);
     }
 
     /* A resource agent using the standard ocf-shellfuncs library will not print
      * messages to stderr if it doesn't have a controlling terminal (e.g. if
      * crm_resource is called via script or ssh). This forces it to do so.
      */
     setenv("OCF_TRACE_FILE", "/dev/stderr", 0);
 }
 
 /*!
  * \internal
  * \brief Apply command-line overrides to resource parameters
  *
  * \param[in,out] params     Parameters to be passed to agent
  * \param[in]     overrides  Parameters to override (or NULL if none)
  */
 static void
 apply_overrides(GHashTable *params, GHashTable *overrides)
 {
     if (overrides != NULL) {
         GHashTableIter iter;
         char *name = NULL;
         char *value = NULL;
 
         g_hash_table_iter_init(&iter, overrides);
         while (g_hash_table_iter_next(&iter, (gpointer *) &name,
                                       (gpointer *) &value)) {
             pcmk__insert_dup(params, name, value);
         }
     }
 }
 
 crm_exit_t
 cli_resource_execute_from_params(pcmk__output_t *out, const char *rsc_name,
                                  const char *rsc_class, const char *rsc_prov,
                                  const char *rsc_type, const char *rsc_action,
                                  GHashTable *params, GHashTable *override_hash,
                                  guint timeout_ms, int resource_verbose,
                                  gboolean force, int check_level)
 {
     const char *class = rsc_class;
     const char *action = get_action(rsc_action);
     crm_exit_t exit_code = CRM_EX_OK;
     svc_action_t *op = NULL;
 
     // If no timeout was provided, use the same default as the cluster
     if (timeout_ms == 0U) {
         timeout_ms = PCMK_DEFAULT_ACTION_TIMEOUT_MS;
     }
 
     set_agent_environment(params, timeout_ms, check_level, resource_verbose);
     apply_overrides(params, override_hash);
 
     op = services__create_resource_action(rsc_name? rsc_name : "test",
                                           rsc_class, rsc_prov, rsc_type, action,
                                           0, QB_MIN(timeout_ms, INT_MAX),
                                           params, 0);
     if (op == NULL) {
         out->err(out, "Could not execute %s using %s%s%s:%s: %s",
                  action, rsc_class, (rsc_prov? ":" : ""),
                  (rsc_prov? rsc_prov : ""), rsc_type, strerror(ENOMEM));
         g_hash_table_destroy(params);
         return CRM_EX_OSERR;
     }
 
+#if PCMK__ENABLE_SERVICE
     if (pcmk__str_eq(rsc_class, PCMK_RESOURCE_CLASS_SERVICE, pcmk__str_casei)) {
         class = resources_find_service_class(rsc_type);
     }
-    if (!pcmk__strcase_any_of(class, PCMK_RESOURCE_CLASS_OCF,
-                              PCMK_RESOURCE_CLASS_LSB, NULL)) {
+#endif
+
+    if (!pcmk_is_set(pcmk_get_ra_caps(class), pcmk_ra_cap_cli_exec)) {
         services__format_result(op, CRM_EX_UNIMPLEMENT_FEATURE, PCMK_EXEC_ERROR,
                                 "Manual execution of the %s standard is "
                                 "unsupported", pcmk__s(class, "unspecified"));
     }
 
     if (op->rc != PCMK_OCF_UNKNOWN) {
         exit_code = op->rc;
         goto done;
     }
 
     services_action_sync(op);
 
     // Map results to OCF codes for consistent reporting to user
     {
         enum ocf_exitcode ocf_code = services_result2ocf(class, action, op->rc);
 
         // Cast variable instead of function return to keep compilers happy
         exit_code = (crm_exit_t) ocf_code;
     }
 
 done:
     out->message(out, "resource-agent-action", resource_verbose, rsc_class,
                  rsc_prov, rsc_type, rsc_name, rsc_action, override_hash,
                  exit_code, op->status, services__exit_reason(op),
                  op->stdout_data, op->stderr_data);
     services_action_free(op);
     return exit_code;
 }
 
 /*!
  * \internal
  * \brief Get the timeout the cluster would use for an action
  *
  * \param[in] rsc     Resource that action is for
  * \param[in] action  Name of action
  */
 static guint
 get_action_timeout(pcmk_resource_t *rsc, const char *action)
 {
     long long timeout_ms = -1LL;
     xmlNode *op = pcmk__find_action_config(rsc, action, 0, true);
     GHashTable *meta = pcmk__unpack_action_meta(rsc, NULL, action, 0, op);
 
     if ((pcmk__scan_ll(g_hash_table_lookup(meta, PCMK_META_TIMEOUT),
                        &timeout_ms, -1LL) != pcmk_rc_ok)
         || (timeout_ms <= 0LL)) {
         timeout_ms = PCMK_DEFAULT_ACTION_TIMEOUT_MS;
     }
     g_hash_table_destroy(meta);
     return (guint) QB_MIN(timeout_ms, UINT_MAX);
 }
 
 crm_exit_t
 cli_resource_execute(pcmk_resource_t *rsc, const char *requested_name,
                      const char *rsc_action, GHashTable *override_hash,
                      guint timeout_ms, cib_t *cib, pcmk_scheduler_t *scheduler,
                      int resource_verbose, gboolean force, int check_level)
 {
     pcmk__output_t *out = scheduler->priv;
     crm_exit_t exit_code = CRM_EX_OK;
     const char *rid = NULL;
     const char *rtype = NULL;
     const char *rprov = NULL;
     const char *rclass = NULL;
     GHashTable *params = NULL;
 
     if (pcmk__strcase_any_of(rsc_action, "force-start", "force-demote",
                                     "force-promote", NULL)) {
         if (pcmk__is_clone(rsc)) {
             GList *nodes = cli_resource_search(rsc, requested_name, scheduler);
             if(nodes != NULL && force == FALSE) {
                 out->err(out, "It is not safe to %s %s here: the cluster claims it is already active",
                          rsc_action, rsc->id);
                 out->err(out,
                          "Try setting "
                          PCMK_META_TARGET_ROLE "=" PCMK_ROLE_STOPPED
                          " first or specifying the force option");
                 return CRM_EX_UNSAFE;
             }
 
             g_list_free_full(nodes, free);
         }
     }
 
     if (pcmk__is_clone(rsc)) {
         /* Grab the first child resource in the hope it's not a group */
         rsc = rsc->children->data;
     }
 
     if (pcmk__is_group(rsc)) {
         out->err(out, "Sorry, the %s option doesn't support group resources", rsc_action);
         return CRM_EX_UNIMPLEMENT_FEATURE;
     } else if (pcmk__is_bundled(rsc)) {
         out->err(out, "Sorry, the %s option doesn't support bundled resources", rsc_action);
         return CRM_EX_UNIMPLEMENT_FEATURE;
     }
 
     rclass = crm_element_value(rsc->xml, PCMK_XA_CLASS);
     rprov = crm_element_value(rsc->xml, PCMK_XA_PROVIDER);
     rtype = crm_element_value(rsc->xml, PCMK_XA_TYPE);
 
     params = generate_resource_params(rsc, NULL /* @TODO use local node */,
                                       scheduler);
 
     if (timeout_ms == 0U) {
         timeout_ms = get_action_timeout(rsc, get_action(rsc_action));
     }
 
     rid = pcmk__is_anonymous_clone(rsc->parent)? requested_name : rsc->id;
 
     exit_code = cli_resource_execute_from_params(out, rid, rclass, rprov, rtype, rsc_action,
                                                  params, override_hash, timeout_ms,
                                                  resource_verbose, force, check_level);
     return exit_code;
 }
 
 // \return Standard Pacemaker return code
 int
 cli_resource_move(const pcmk_resource_t *rsc, const char *rsc_id,
                   const char *host_name, const char *move_lifetime, cib_t *cib,
                   int cib_options, pcmk_scheduler_t *scheduler,
                   gboolean promoted_role_only, gboolean force)
 {
     pcmk__output_t *out = scheduler->priv;
     int rc = pcmk_rc_ok;
     unsigned int count = 0;
     pcmk_node_t *current = NULL;
     pcmk_node_t *dest = pcmk_find_node(scheduler, host_name);
     bool cur_is_dest = false;
 
     if (dest == NULL) {
         return pcmk_rc_node_unknown;
     }
 
     if (promoted_role_only
         && !pcmk_is_set(rsc->flags, pcmk_rsc_promotable)) {
 
         const pcmk_resource_t *p = pe__const_top_resource(rsc, false);
 
         if (pcmk_is_set(p->flags, pcmk_rsc_promotable)) {
             out->info(out, "Using parent '%s' for move instead of '%s'.", rsc->id, rsc_id);
             rsc_id = p->id;
             rsc = p;
 
         } else {
             out->info(out, "Ignoring --promoted option: %s is not promotable",
                       rsc_id);
             promoted_role_only = FALSE;
         }
     }
 
     current = pe__find_active_requires(rsc, &count);
 
     if (pcmk_is_set(rsc->flags, pcmk_rsc_promotable)) {
         unsigned int promoted_count = 0;
         pcmk_node_t *promoted_node = NULL;
 
         for (const GList *iter = rsc->children; iter; iter = iter->next) {
             const pcmk_resource_t *child = (const pcmk_resource_t *) iter->data;
             enum rsc_role_e child_role = child->fns->state(child, TRUE);
 
             if (child_role == pcmk_role_promoted) {
                 rsc = child;
                 promoted_node = pcmk__current_node(child);
                 promoted_count++;
             }
         }
         if (promoted_role_only || (promoted_count != 0)) {
             count = promoted_count;
             current = promoted_node;
         }
 
     }
 
     if (count > 1) {
         if (pcmk__is_clone(rsc)) {
             current = NULL;
         } else {
             return pcmk_rc_multiple;
         }
     }
 
     if (pcmk__same_node(current, dest)) {
         cur_is_dest = true;
         if (force) {
             crm_info("%s is already %s on %s, reinforcing placement with location constraint.",
                      rsc_id, promoted_role_only?"promoted":"active",
                      pcmk__node_name(dest));
         } else {
             return pcmk_rc_already;
         }
     }
 
     /* Clear any previous prefer constraints across all nodes. */
     cli_resource_clear(rsc_id, NULL, scheduler->nodes, cib, cib_options, false,
                        force);
 
     /* Clear any previous ban constraints on 'dest'. */
     cli_resource_clear(rsc_id, dest->details->uname, scheduler->nodes, cib,
                        cib_options, TRUE, force);
 
     /* Record an explicit preference for 'dest' */
     rc = cli_resource_prefer(out, rsc_id, dest->details->uname, move_lifetime,
                              cib, cib_options, promoted_role_only,
                              PCMK_ROLE_PROMOTED);
 
     crm_trace("%s%s now prefers %s%s",
               rsc->id, (promoted_role_only? " (promoted)" : ""),
               pcmk__node_name(dest), force?"(forced)":"");
 
     /* only ban the previous location if current location != destination location.
      * it is possible to use -M to enforce a location without regard of where the
      * resource is currently located */
     if (force && !cur_is_dest) {
         /* Ban the original location if possible */
         if(current) {
             (void)cli_resource_ban(out, rsc_id, current->details->uname, move_lifetime,
                                    cib, cib_options, promoted_role_only,
                                    PCMK_ROLE_PROMOTED);
         } else if(count > 1) {
             out->info(out, "Resource '%s' is currently %s in %d locations. "
                       "One may now move to %s",
                       rsc_id, (promoted_role_only? "promoted" : "active"),
                       count, pcmk__node_name(dest));
             out->info(out, "To prevent '%s' from being %s at a specific location, "
                       "specify a node.",
                       rsc_id, (promoted_role_only? "promoted" : "active"));
 
         } else {
             crm_trace("Not banning %s from its current location: not active", rsc_id);
         }
     }
 
     return rc;
 }