diff --git a/.gitignore b/.gitignore index 6523bf8b5d..a3eb175569 100644 --- a/.gitignore +++ b/.gitignore @@ -1,354 +1,354 @@ # # Copyright 2011-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. # # Common conventions for files that should be ignored *~ *.bz2 *.diff *.orig *.patch *.rej *.sed *.swp *.tar.gz *.tgz \#* .\#* logs # libtool artifacts *.la *.lo .libs libltdl libtool libtool.m4 ltdl.m4 /m4/argz.m4 /m4/ltargz.m4 /m4/ltoptions.m4 /m4/ltsugar.m4 /m4/ltversion.m4 /m4/lt~obsolete.m4 # autotools artifacts .deps .dirstamp Makefile Makefile.in aclocal.m4 autoconf autoheader autom4te.cache/ automake /confdefs.h config.log config.status configure /conftest* # gettext artifacts /ABOUT-NLS /m4/codeset.m4 /m4/fcntl-o.m4 /m4/gettext.m4 /m4/glibc2.m4 /m4/glibc21.m4 /m4/iconv.m4 /m4/intdiv0.m4 /m4/intl.m4 /m4/intldir.m4 /m4/intlmacosx.m4 /m4/intmax.m4 /m4/inttypes-pri.m4 /m4/inttypes_h.m4 /m4/lcmessage.m4 /m4/lib-ld.m4 /m4/lib-link.m4 /m4/lib-prefix.m4 /m4/lock.m4 /m4/longlong.m4 /m4/nls.m4 /m4/po.m4 /m4/printf-posix.m4 /m4/progtest.m4 /m4/size_max.m4 /m4/stdint_h.m4 /m4/threadlib.m4 /m4/uintmax_t.m4 /m4/visibility.m4 /m4/wchar_t.m4 /m4/wint_t.m4 /m4/xsize.m4 /po/*.gmo /po/*.header /po/*.pot /po/*.sin /po/Makefile.in.in /po/Makevars.template /po/POTFILES /po/Rules-quot /po/stamp-po # configure targets /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/ping /agents/ocf/remote /agents/stonith/fence_legacy /agents/stonith/fence_watchdog /cts/benchmark/clubench /cts/cluster_test /cts/cts /cts/cts-attrd /cts/cts-cli /cts/cts-exec /cts/cts-fencing /cts/cts-lab /cts/cts-regression /cts/cts-scheduler /cts/cts-schemas /cts/lab/CTS.py /cts/support/LSBDummy /cts/support/cts-support /cts/support/fence_dummy /cts/support/pacemaker-cts-dummyd /cts/support/pacemaker-cts-dummyd@.service /daemons/execd/pacemaker_remote /daemons/execd/pacemaker_remote.service /daemons/fenced/fence_legacy /daemons/fenced/fence_watchdog /daemons/pacemakerd/pacemaker.service /doc/Doxyfile /etc/init.d/pacemaker /etc/logrotate.d/pacemaker /etc/sysconfig/pacemaker /include/config.h /include/config.h.in /include/crm_config.h /maint/bumplibs /python/pacemaker/buildoptions.py /python/setup.py /tools/cluster-clean /tools/cluster-helper /tools/cluster-init -/tools/cibsecret /tools/crm_error /tools/crm_failcount /tools/crm_master /tools/crm_mon.service /tools/crm_report /tools/crm_rule /tools/crm_standby /tools/pcmk_simtimes /tools/report.collector /tools/report.common /xml/rng-helper # Compiled targets and intermediary files *.o *.pc *.pyc /daemons/attrd/pacemaker-attrd /daemons/based/pacemaker-based /daemons/controld/pacemaker-controld /daemons/execd/cts-exec-helper /daemons/execd/pacemaker-execd /daemons/execd/pacemaker-remoted /daemons/fenced/cts-fence-helper /daemons/fenced/pacemaker-fenced /daemons/pacemakerd/pacemakerd /daemons/schedulerd/pacemaker-schedulerd /devel/scratch /lib/gnu/stdalign.h /tools/attrd_updater /tools/cibadmin /tools/crmadmin /tools/crm_attribute /tools/crm_diff /tools/crm_mon /tools/crm_node /tools/crm_resource /tools/crm_shadow /tools/crm_simulate /tools/crm_ticket /tools/crm_verify /tools/iso8601 /tools/stonith_admin # Generated XML schema files /xml/crm_mon.rng /xml/pacemaker*.rng /xml/versions.rng /xml/api/api-result*.rng # Working directories for make dist and make export /pacemaker-[a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9] # Documentation build targets and intermediary files *.7 *.7.xml *.7.html *.8 *.8.xml *.8.html GPATH GRTAGS GTAGS TAGS /daemons/fenced/pacemaker-fenced.xml /daemons/schedulerd/pacemaker-schedulerd.xml /doc/HTML /doc/abi/ /doc/abi-check /doc/api/ /doc/crm_fencing.html /doc/sphinx/*/_build /doc/sphinx/*/conf.py /doc/sphinx/*/generated /doc/sphinx/build-[0-9]*.txt # Test artifacts (from unit tests, regression tests, static analysis, etc.) *.coverity *.gcda *.gcno coverity-* pacemaker_*.info /coverage /cppcheck.out /cts/scheduler/bug-rh-1097457.log /cts/scheduler/bug-rh-1097457.trs /cts/scheduler/shadow.* /cts/schemas/test-*/ref/*.up* /cts/schemas/test-*/ref.err/*.up.err* /cts/test-suite.log /lib/*/fuzzers/*/*_fuzzer /lib/*/tests/*/*.log /lib/*/tests/*/*_test /lib/*/tests/*/*.trs /lib/common/tests/schemas/schemas /test/_test_file.c # Packaging artifacts *.rpm /pacemaker.spec /rpm/[A-LN-Z]* /rpm/build.counter /rpm/mock # Project maintainer artifacts /maint/gnulib /maint/mocked/based /maint/testcc_helper.cc /maint/testcc_*_h # Formerly built files (helps when jumping back and forth in checkout) /.ABI-build /Doxyfile /HTML /abi_dumps /abi-check /agents/ocf/o2cb /build.counter /compat_reports /compile /cts/.regression.failed.diff /attrd /cib /config.guess /config.sub /coverage.sh /crmd /cts/CTS.py /cts/CTSlab.py /cts/CTSvars.py /cts/HBDummy /cts/LSBDummy /cts/OCFIPraTest.py /cts/cts-coverage /cts/cts-log-watcher /cts/cts-support /cts/fence_dummy /cts/lab/CTSlab.py /cts/lab/CTSvars.py /cts/lab/OCFIPraTest.py /cts/lab/cluster_test /cts/lab/cts /cts/lab/cts-log-watcher /cts/lxc_autogen.sh /cts/pacemaker-cts-dummyd /cts/pacemaker-cts-dummyd@.service /daemons/based/cibmon /daemons/fenced/fence_legacy /daemons/fenced/fence_watchdog /daemons/pacemakerd/pacemaker /daemons/pacemakerd/pacemaker.combined.upstart /daemons/pacemakerd/pacemaker.upstart /depcomp /doc/*.build /doc/*/en-US/Ap-*.xml /doc/*/en-US/Ch-*.xml /doc/*/publican.cfg /doc/*/publish /doc/*/tmp/** /doc/.ABI-build /doc/Clusters_from_Scratch.txt /doc/Pacemaker_Explained.txt /doc/abi_dumps /doc/acls.html /doc/compat_reports /doc/publican-catalog* /doc/shared/en-US/*.xml /doc/shared/en-US/images/pcmk-*.png /doc/shared/en-US/images/Policy-Engine-*.png /extra/*/* /fencing /include/stamp-* /install-sh /lib/common/md5.c /lib/common/tests/flags/pcmk__clear_flags_as /lib/common/tests/flags/pcmk__set_flags_as /lib/common/tests/flags/pcmk_all_flags_set /lib/common/tests/flags/pcmk_any_flags_set /lib/common/tests/operations/parse_op_key /lib/common/tests/strings/pcmk__btoa /lib/common/tests/strings/pcmk__parse_ll_range /lib/common/tests/strings/pcmk__scan_double /lib/common/tests/strings/pcmk__str_any_of /lib/common/tests/strings/pcmk__strcmp /lib/common/tests/strings/pcmk__char_in_any_str /lib/common/tests/utils/pcmk_str_is_infinity /lib/common/tests/utils/pcmk_str_is_minus_infinity /lib/gnu/libgnu.a /lib/pengine/tests/rules/ /lrmd /ltmain.sh /mcp /missing /mock /pacemaker-*.spec /pengine /py-compile /scratch +/tools/cibsecret /tools/cluster-init /tools/crm_mon.upstart /test-driver /xml/assets /xml/crm.dtd /xml/version-diff.sh ylwrap pacemaker.sysusers_* diff --git a/configure.ac b/configure.ac index eddcc1c7b7..87cb9bc005 100644 --- a/configure.ac +++ b/configure.ac @@ -1,2140 +1,2139 @@ dnl dnl autoconf for Pacemaker dnl dnl Copyright 2009-2025 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.28) AS_IF([test x"${PKG_CONFIG}" != x""], [], [AC_MSG_FAILURE([Could not find required build tool pkg-config (0.28 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 # 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 # 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 ============================================== 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 # 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 within Pacemaker dnl @COMPAT This should be --with-systemd 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([deprecated-libs], [AS_HELP_STRING([--enable-deprecated-libs], [Build and install deprecated C libraries @<:@yes@:>@])] ) yes_no_try "$enable_deprecated_libs" "yes" enable_deprecated_libs=$? AM_CONDITIONAL([BUILD_DEPRECATED_LIBS], [test $enable_deprecated_libs -ne $DISABLED]) # 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 values 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 @COMPAT This should be --enable-cibsecrets option AC_ARG_WITH([cibsecrets], [AS_HELP_STRING([--with-cibsecrets], [support separate file for CIB secrets @<:@no@:>@])] ) yes_no_try "$with_cibsecrets" "no" with_cibsecrets=$? 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], m4_normalize([ default value for concurrent-fencing cluster option (deprecated) @<:@true@:>@]))], ) AS_CASE([$with_concurrent_fencing_default], [""], [with_concurrent_fencing_default="true"], [true], [], [false], [PCMK_FEATURES="$PCMK_FEATURES concurrent-fencing-default-false"], [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" ] ) dnl --with-* options: directory locations INITDIR="" AC_ARG_WITH([initdir], [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([PCMK_OCF_ROOT], [resource-agents], [ocfrootdir], [], [PCMK_OCF_ROOT="/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@:>@]))], [ PCMK_OCF_ROOT="$withval" ] ) dnl Get default from resource-agents if possible PKG_CHECK_VAR([PCMK__OCF_RA_PATH], [resource-agents], [ocfrapath], [], [PCMK__OCF_RA_PATH="$PCMK_OCF_ROOT/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@:>@]))], [ PCMK__OCF_RA_PATH="$withval" ] ) OCF_RA_INSTALL_DIR="$PCMK_OCF_ROOT/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.6]) 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(PCMK__VALGRIND_EXEC, valgrind, /usr/bin/valgrind) AC_DEFINE_UNQUOTED(PCMK__VALGRIND_EXEC, "$PCMK__VALGRIND_EXEC", 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 # 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 # 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 # 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 x"$enable_nls" = x"yes"], [PCMK_FEATURES="$PCMK_FEATURES nls"]) AS_IF([test x"$with_concurrent_fencing_default" = x"true"], [PCMK__CONCURRENT_FENCING_DEFAULT_TRUE="1"], [PCMK__CONCURRENT_FENCING_DEFAULT_TRUE="0"]) AC_DEFINE_UNQUOTED([PCMK__CONCURRENT_FENCING_DEFAULT_TRUE], [$PCMK__CONCURRENT_FENCING_DEFAULT_TRUE], [Whether concurrent-fencing cluster option default is true]) 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 [] # 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])] ) } 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 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 PCMK_OCF_ROOT AC_SUBST(PCMK_OCF_ROOT) AC_DEFINE_UNQUOTED([PCMK_OCF_ROOT], ["$PCMK_OCF_ROOT"], [OCF root directory for resource agents and libraries]) expand_path_option PCMK__OCF_RA_PATH AC_SUBST(PCMK__OCF_RA_PATH) AC_DEFINE_UNQUOTED([PCMK__OCF_RA_PATH], ["$PCMK__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 PCMK_SCHEMA_DIR="${datadir}/pacemaker" AC_DEFINE_UNQUOTED([PCMK_SCHEMA_DIR], ["$PCMK_SCHEMA_DIR"], [Location for the Pacemaker Relax-NG Schema]) AC_SUBST(PCMK_SCHEMA_DIR) 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) PCMK__PERSISTENT_DATA_DIR="${localstatedir}/lib/pacemaker" AC_DEFINE_UNQUOTED([PCMK__PERSISTENT_DATA_DIR], ["$PCMK__PERSISTENT_DATA_DIR"], [Location to store directory produced by Pacemaker daemons]) AC_SUBST(PCMK__PERSISTENT_DATA_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) PCMK_SCHEDULER_INPUT_DIR="${localstatedir}/lib/pacemaker/pengine" AC_DEFINE_UNQUOTED([PCMK_SCHEDULER_INPUT_DIR], ["$PCMK_SCHEDULER_INPUT_DIR"], [Where to keep scheduler outputs]) AC_SUBST(PCMK_SCHEDULER_INPUT_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) PCMK__OCF_TMP_DIR="${runstatedir}/resource-agents" AC_DEFINE_UNQUOTED([PCMK__OCF_TMP_DIR], ["$PCMK__OCF_TMP_DIR"], [Where resource agents should keep state files]) AC_SUBST(PCMK__OCF_TMP_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) # Fedora >=42 makes /usr/sbin a symlink to /usr/bin. It updates the RPM macros # to set _sbindir to "${_exec_prefix}/bin", the same value as _bindir. # Previously it was set to "${_exec_prefix}/sbin". (Note that because of the # symlink, paths beginning with /usr/sbin remain valid.) # # This causes problems with bundle resources. Pacemaker automatically generates # a configuration for the bundle's container resource. If the bundle contains a # primitive and the container's run-command attribute is unset, the generated # container resource has its run_cmd attribute set to # SBIN_DIR "/" PCMK__SERVER_REMOTED, which is intended as a reasonable default. # If SBIN_DIR becomes "/usr/bin" instead of "/usr/sbin", at least two problems # can occur: # 1. The container resource's digest changes compared to the digest in the # resource history entry. Pacemaker interprets this as a configuration # change and restarts the container resource. # 2. If the container is running a different OS distro or an older version of # Fedora, then the new /usr/bin/pacemaker-remoted path may be invalid; the # executable was installed at /usr/sbin/pacemaker-remoted, which is NOT a # symlink to /usr/bin path. In this case, the container fails to start. # # We override the value only for the SBIN_DIR constant, which is used only for # the sbd path and the default pacemaker-remoted path. There is no need to # override sbindir, which would affect installation directories. # # There is no more specific way than the below, to detect whether the build # system has this /usr/sbin vs. /usr/bin change in effect. Thus corner cases are # possible when sbindir/bindir are manually specified or in distros with # atypical defaults. # # At time of writing, autoconf is unchanged. However, we perform the override # here instead of in the spec file, in case autoconf changes in the future. # # Note that other distros (for example, RHEL) are likely to incorporate these # changes in the future. # # References: # * https://fedoraproject.org/wiki/Changes/Unify_bin_and_sbin # * https://bodhi.fedoraproject.org/updates/FEDORA-2025-da0a082e66 # * https://discussion.fedoraproject.org/t/144562 AS_IF([test x"$sbindir" = x"$bindir" \ && test x"$sbindir" = x"${exec_prefix}/bin"], [SBIN_DIR="${exec_prefix}/sbin"], [SBIN_DIR="$sbindir"]) AC_DEFINE_UNQUOTED([SBIN_DIR], ["$SBIN_DIR"], [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 SBIN_DIR do dirname=`eval echo '${'${j}'}'` 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 ]]) ], [[#define _GNU_SOURCE #include ]]) ], [], [[#include ]]) ]) 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 ]]) ]) ]) 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_PROG([HELP2MAN], [help2man]) AC_PATH_PROG([SPHINX], [sphinx-build]) 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([BUILD_SPHINX_DOCS], [test x"${SPHINX}" != 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_SEARCH_LIBS([socket], [socket]) save_LIBS="$LIBS" DL_LIBS="" LIBS="" AC_SEARCH_LIBS([dlopen], [dl], [test "$ac_cv_search_dlopen" = "none required" || DL_LIBS="$LIBS"]) AC_SUBST(DL_LIBS) LIBS="$save_LIBS" save_LIBS="$LIBS" PAM_LIBS="" LIBS="" AC_SEARCH_LIBS([pam_start], [pam], [test "$ac_cv_search_pam_start" = "none required" || PAM_LIBS="$LIBS"]) AC_SUBST(PAM_LIBS) LIBS="$save_LIBS" PKG_CHECK_MODULES([UUID], [uuid], [CPPFLAGS="${CPPFLAGS} ${UUID_CFLAGS}" LIBS="${LIBS} ${UUID_LIBS}"]) # 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]) PKG_CHECK_MODULES(LIBXML2, [libxml-2.0 >= 2.9.2], [CPPFLAGS="${CPPFLAGS} ${LIBXML2_CFLAGS}" LIBS="${LIBS} ${LIBXML2_LIBS}"]) AC_PATH_PROGS(XMLLINT_PATH, xmllint, /usr/bin/xmllint) AC_DEFINE_UNQUOTED(XMLLINT_PATH, "$XMLLINT_PATH", xmllint command) REQUIRE_LIB([xslt], [xsltApplyStylesheet]) 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([sys/signalfd.h]) AC_CHECK_HEADERS([uuid/uuid.h]) AC_CHECK_HEADERS([security/pam_appl.h pam/pam_appl.h]) AS_IF([test x"$ac_cv_lib_pam_pam_start" = x"yes"], AS_IF([test x"$ac_cv_header_security_pam_appl_h" = x"yes" dnl || test x"$ac_cv_header_pam_pam_appl_h" = x"yes"], [PCMK_FEATURES="$PCMK_FEATURES pam"])) # 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 #include ]) 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 ]]) dnl ======================================================================== dnl Unit test declarations dnl ======================================================================== AC_CHECK_DECLS([assert_float_equal], [], [], [[ #include #include #include #include ]]) 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 ]], [[ 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 ]]) dnl ======================================================================== dnl Functions dnl ======================================================================== REQUIRE_FUNC([alphasort]) REQUIRE_FUNC([getopt]) REQUIRE_FUNC([scandir]) REQUIRE_FUNC([sched_getscheduler]) 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 #include ]], [[ 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 ]], [[ 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 ]], [[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]) save_LIBS="$LIBS" found_curses=0 CURSES_LIBS="" LIBS="" AC_SEARCH_LIBS([printw], [ncurses curses], [test "$ac_cv_search_printw" = "none required" || CURSES_LIBS="$LIBS" found_curses=1], [found_curses=0]) LIBS="$save_LIBS" dnl Check for printw() prototype compatibility AS_IF([test $found_curses -eq 1 && cc_supports_flag -Wcast-qual], [ ac_save_LIBS="$LIBS" LIBS="$CURSES_LIBS" # 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 #elif defined(HAVE_NCURSES_NCURSES_H) # include #elif defined(HAVE_CURSES_H) # include #elif defined(HAVE_CURSES_CURSES_H) # include #endif ], [printw((const char *)"Test");] )], [AC_MSG_RESULT([yes]) PCMK_FEATURES="$PCMK_FEATURES ncurses" ], [ found_curses=0 CURSES_LIBS="" 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/) ])) ] ) LIBS="$ac_save_LIBS" cc_restore_flags ]) AC_DEFINE_UNQUOTED([PCMK__ENABLE_CURSES], [$found_curses], [have ncurses library]) AC_SUBST(CURSES_LIBS) 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]) ] ) AM_CONDITIONAL([BUILD_PROFILING], [test "$with_profiling" = "$REQUIRED"]) dnl ======================================================================== dnl Cluster infrastructure - LibQB dnl ======================================================================== PKG_CHECK_MODULES([libqb], [libqb >= 1.0.1]) 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) dnl also defines QB_FEATURE_LOG_HIRES_TIMESTAMPS 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], [ save_LIBS="$LIBS" STONITH_LIBS="" LIBS="" AC_SEARCH_LIBS([PILLoadPlugin], [pils], [test "$ac_cv_search_PILLoadPlugin" = "none required" || STONITH_LIBS="$LIBS"]) LIBS="" AC_SEARCH_LIBS([G_main_add_IPC_Channel], [plumb], [test "$ac_cv_search_G_main_add_IPC_Channel" = "none required" || STONITH_LIBS="$STONITH_LIBS $LIBS"]) AC_SUBST(STONITH_LIBS) LIBS="$save_LIBS" PCMK_FEATURES="$PCMK_FEATURES lha" ]) AM_CONDITIONAL([BUILD_LHA_SUPPORT], [test x"$ac_cv_header_stonith_stonith_h" = x"yes"]) dnl =============================== dnl Detect DBus and systemd support dnl =============================== HAVE_dbus=0 PC_NAME_DBUS="" PKG_CHECK_MODULES([DBUS],[dbus-1 >= 1.5.12], [ HAVE_dbus=1 PC_NAME_DBUS="dbus-1" CPPFLAGS="${CPPFLAGS} ${DBUS_CFLAGS}" ],[]) AC_DEFINE_UNQUOTED(HAVE_DBUS, $HAVE_dbus, Support dbus) 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) 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]) 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" PCMK__CIB_SECRETS_DIR="${localstatedir}/lib/pacemaker/lrm/secrets" AC_DEFINE_UNQUOTED([PCMK__CIB_SECRETS_DIR], ["$PCMK__CIB_SECRETS_DIR"], [Location for CIB secrets]) AC_SUBST([PCMK__CIB_SECRETS_DIR]) ] ) AC_DEFINE_UNQUOTED([PCMK__ENABLE_CIBSECRETS], [$with_cibsecrets], [Support CIB secrets]) AM_CONDITIONAL([BUILD_CIBSECRETS], [test $with_cibsecrets -eq $REQUIRED]) dnl ======================================================================== dnl GnuTLS dnl ======================================================================== PKG_CHECK_MODULES(GNUTLS, [gnutls >= 3.4.6], [CPPFLAGS="${CPPFLAGS} ${GNUTLS_CFLAGS}" LIBS="${LIBS} ${GNUTLS_LIBS}"]) # --- 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/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-regression], [cts/cts-scheduler], [cts/cts-schemas], [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.service \ 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/digest/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/messages/Makefile \ lib/common/tests/nodes/Makefile \ lib/common/tests/nvpair/Makefile \ lib/common/tests/options/Makefile \ lib/common/tests/output/Makefile \ lib/common/tests/patchset/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/xml_comment/Makefile \ lib/common/tests/xml_element/Makefile \ lib/common/tests/xml_idref/Makefile \ lib/common/tests/xpath/Makefile \ lib/fencing/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/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 = ${PCMK_OCF_ROOT}]) 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/cli/regression.cibadmin.exp b/cts/cli/regression.cibadmin.exp index f953eb8e72..9842745c99 100644 --- a/cts/cli/regression.cibadmin.exp +++ b/cts/cli/regression.cibadmin.exp @@ -1,89 +1,89 @@ =#=#=#= Begin test: Validate CIB =#=#=#= =#=#=#= Current cib after: Validate CIB =#=#=#= =#=#=#= End test: Validate CIB - OK (0) =#=#=#= * Passed: cibadmin - Validate CIB =#=#=#= Begin test: Digest calculation =#=#=#= -01fdf92db1638e8a7e0d8a72ec114c9f +afd2a860bc4940c19b0190571980b35a =#=#=#= End test: Digest calculation - OK (0) =#=#=#= * Passed: cibadmin - Digest calculation =#=#=#= Begin test: Require --force for CIB erasure =#=#=#= cibadmin: The supplied command is considered dangerous. To prevent accidental destruction of the cluster, the --force flag is required in order to proceed. =#=#=#= Current cib after: Require --force for CIB erasure =#=#=#= =#=#=#= End test: Require --force for CIB erasure - Operation not safe (107) =#=#=#= * Passed: cibadmin - Require --force for CIB erasure =#=#=#= Begin test: Allow CIB erasure with --force =#=#=#= =#=#=#= End test: Allow CIB erasure with --force - OK (0) =#=#=#= * Passed: cibadmin - Allow CIB erasure with --force =#=#=#= Begin test: Query CIB =#=#=#= =#=#=#= Current cib after: Query CIB =#=#=#= =#=#=#= End test: Query CIB - OK (0) =#=#=#= * Passed: cibadmin - Query CIB diff --git a/include/crm/crm.h b/include/crm/crm.h index a819902cc9..053c3267d3 100644 --- a/include/crm/crm.h +++ b/include/crm/crm.h @@ -1,153 +1,153 @@ /* * Copyright 2004-2025 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_CRM__H # define PCMK__CRM_CRM__H # include # include # include # include # include # include #include #ifdef __cplusplus extern "C" { #endif /** * \file * \brief A dumping ground * \ingroup core */ #ifndef PCMK_ALLOW_DEPRECATED /*! * \brief Allow use of deprecated Pacemaker APIs * * By default, external code using Pacemaker headers is allowed to use * deprecated Pacemaker APIs. If PCMK_ALLOW_DEPRECATED is defined to 0 before * including any Pacemaker headers, deprecated APIs will be unusable. It is * strongly recommended to leave this unchanged for production and release * builds, to avoid breakage when users upgrade to new Pacemaker releases that * deprecate more APIs. This should be defined to 0 only for development and * testing builds when desiring to check for usage of currently deprecated APIs. */ #define PCMK_ALLOW_DEPRECATED 1 #endif /*! * The CRM feature set assists with compatibility in mixed-version clusters. * The major version number increases when nodes with different versions * would not work (rolling upgrades are not allowed). The minor version * number increases when mixed-version clusters are allowed only during * rolling upgrades (a node with the oldest feature set will be elected DC). The * minor-minor version number is ignored, but allows resource agents to detect * cluster support for various features. * * The feature set also affects the processing of old saved CIBs (such as for * many scheduler regression tests). * * Particular feature points currently tested by Pacemaker code: * * >=3.2.0: DC supports PCMK_EXEC_INVALID and PCMK_EXEC_NOT_CONNECTED * >=3.19.0: DC supports PCMK__CIB_REQUEST_COMMIT_TRANSACT */ -#define CRM_FEATURE_SET "3.20.2" +#define CRM_FEATURE_SET "3.20.3" /* Pacemaker's CPG protocols use fixed-width binary fields for the sender and * recipient of a CPG message. This imposes an arbitrary limit on cluster node * names. */ //! \brief Maximum length of a Corosync cluster node name (in bytes) #define MAX_NAME 256 # define CRM_META "CRM_meta" // NOTE: sbd (as of at least 1.5.2) uses this extern char *crm_system_name; /* Sub-systems */ # define CRM_SYSTEM_DC "dc" #define CRM_SYSTEM_DCIB "dcib" // Primary instance of CIB manager # define CRM_SYSTEM_CIB "cib" # define CRM_SYSTEM_CRMD "crmd" # define CRM_SYSTEM_LRMD "lrmd" # define CRM_SYSTEM_PENGINE "pengine" # define CRM_SYSTEM_TENGINE "tengine" # define CRM_SYSTEM_MCP "pacemakerd" // Names of internally generated node attributes // @TODO Replace these with PCMK_NODE_ATTR_* # define CRM_ATTR_UNAME "#uname" # define CRM_ATTR_ID "#id" # define CRM_ATTR_KIND "#kind" # define CRM_ATTR_ROLE "#role" # define CRM_ATTR_IS_DC "#is_dc" # define CRM_ATTR_CLUSTER_NAME "#cluster-name" # define CRM_ATTR_SITE_NAME "#site-name" # define CRM_ATTR_UNFENCED "#node-unfenced" # define CRM_ATTR_DIGESTS_ALL "#digests-all" # define CRM_ATTR_DIGESTS_SECURE "#digests-secure" # define CRM_ATTR_PROTOCOL "#attrd-protocol" # define CRM_ATTR_FEATURE_SET "#feature-set" /* Valid operations */ # define CRM_OP_NOOP "noop" # define CRM_OP_JOIN_ANNOUNCE "join_announce" # define CRM_OP_JOIN_OFFER "join_offer" # define CRM_OP_JOIN_REQUEST "join_request" # define CRM_OP_JOIN_ACKNAK "join_ack_nack" # define CRM_OP_JOIN_CONFIRM "join_confirm" # define CRM_OP_PING "ping" # define CRM_OP_NODE_INFO "node-info" # define CRM_OP_THROTTLE "throttle" # define CRM_OP_VOTE "vote" # define CRM_OP_NOVOTE "no-vote" # define CRM_OP_HELLO "hello" # define CRM_OP_PECALC "pe_calc" # define CRM_OP_QUIT "quit" # define CRM_OP_SHUTDOWN_REQ "req_shutdown" # define CRM_OP_SHUTDOWN PCMK_ACTION_DO_SHUTDOWN # define CRM_OP_REGISTER "register" # define CRM_OP_IPC_FWD "ipc_fwd" # define CRM_OP_INVOKE_LRM "lrm_invoke" # define CRM_OP_LRM_DELETE PCMK_ACTION_LRM_DELETE # define CRM_OP_LRM_FAIL "lrm_fail" # define CRM_OP_PROBED "probe_complete" # define CRM_OP_REPROBE "probe_again" # define CRM_OP_CLEAR_FAILCOUNT PCMK_ACTION_CLEAR_FAILCOUNT # define CRM_OP_REMOTE_STATE "remote_state" # define CRM_OP_RM_NODE_CACHE "rm_node_cache" # define CRM_OP_MAINTENANCE_NODES PCMK_ACTION_MAINTENANCE_NODES /* Possible cluster membership states */ # define CRMD_JOINSTATE_DOWN "down" # define CRMD_JOINSTATE_PENDING "pending" # define CRMD_JOINSTATE_MEMBER "member" # define CRMD_JOINSTATE_NACK "banned" # include # include # include # include #ifdef __cplusplus } #endif #if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1) #include #endif #endif diff --git a/lib/common/digest.c b/lib/common/digest.c index 418d098577..cdc4a61879 100644 --- a/lib/common/digest.c +++ b/lib/common/digest.c @@ -1,397 +1,402 @@ /* * Copyright 2015-2025 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 #include #include #include #include #include #include // GString, etc. #include // gnutls_hash_fast(), gnutls_hash_get_len() #include // gnutls_strerror() #include #include #include "crmcommon_private.h" #define BEST_EFFORT_STATUS 0 /* * Pacemaker uses digests (MD5 hashes) of stringified XML to detect changes in * the CIB as a whole, a particular resource's agent parameters, and the device * parameters last used to unfence a particular node. * * "v2" digests hash pcmk__xml_string() directly, while less efficient "v1" * digests do the same with a prefixed space, suffixed newline, and optional * pre-sorting. * * On-disk CIB digests use v1 without sorting. * * Operation digests use v1 with sorting, and are stored in a resource's * operation history in the CIB status section. They come in three flavors: * - a digest of (nearly) all resource parameters and options, used to detect * any resource configuration change; * - a digest of resource parameters marked as nonreloadable, used to decide * whether a reload or full restart is needed after a configuration change; * - and a digest of resource parameters not marked as private, used in * simulations where private parameters have been removed from the input. * * Unfencing digests are set as node attributes, and are used to require * that nodes be unfenced again after a device's configuration changes. */ /*! * \internal * \brief Dump XML in a format used with v1 digests * * \param[in] xml Root of XML to dump * * \return Newly allocated buffer containing dumped XML */ static GString * dump_xml_for_digest(const xmlNode *xml) { GString *buffer = g_string_sized_new(1024); /* for compatibility with the old result which is used for v1 digests */ g_string_append_c(buffer, ' '); pcmk__xml_string(xml, 0, buffer, 0); g_string_append_c(buffer, '\n'); return buffer; } /*! * \internal * \brief Calculate and return v1 digest of XML tree * * \param[in] input Root of XML to digest * * \return Newly allocated string containing digest * * \note Example return value: "c048eae664dba840e1d2060f00299e9d" */ static char * calculate_xml_digest_v1(const xmlNode *input) { GString *buffer = dump_xml_for_digest(input); char *digest = NULL; // buffer->len > 2 for initial space and trailing newline CRM_CHECK(buffer->len > 2, g_string_free(buffer, TRUE); return NULL); digest = crm_md5sum((const char *) buffer->str); crm_log_xml_trace(input, "digest:source"); g_string_free(buffer, TRUE); return digest; } /*! * \internal * \brief Calculate and return the digest of a CIB, suitable for storing on disk * * \param[in] input Root of XML to digest * * \return Newly allocated string containing digest */ char * pcmk__digest_on_disk_cib(const xmlNode *input) { /* Always use the v1 format for on-disk digests. * * Switching to v2 affects even full-restart upgrades, so it would be a * compatibility nightmare. * * We only use this once at startup. All other invocations are in a * separate child process. */ return calculate_xml_digest_v1(input); } /*! * \internal * \brief Calculate and return digest of a \c PCMK_XE_PARAMETERS element * * This is intended for parameters of a resource operation (also known as * resource action). A \c PCMK_XE_PARAMETERS element from a different source * (for example, resource agent metadata) may have child elements, which are not * allowed here. * * The digest is invariant to changes in the order of XML attributes. * * \param[in] input XML element to digest (must have no children) * * \return Newly allocated string containing digest */ char * pcmk__digest_op_params(const xmlNode *input) { /* Switching to v2 digests would likely cause restarts during rolling * upgrades. * * @TODO Confirm this. Switch to v2 if safe, or drop this TODO otherwise. */ char *digest = NULL; xmlNode *sorted = NULL; pcmk__assert(input->children == NULL); sorted = pcmk__xe_create(NULL, (const char *) input->name); pcmk__xe_copy_attrs(sorted, input, pcmk__xaf_none); pcmk__xe_sort_attrs(sorted); digest = calculate_xml_digest_v1(sorted); pcmk__xml_free(sorted); return digest; } /*! * \internal * \brief Calculate and return the digest of an XML tree * * \param[in] xml XML tree to digest * \param[in] filter Whether to filter certain XML attributes * * \return Newly allocated string containing digest */ char * pcmk__digest_xml(const xmlNode *xml, bool filter) { /* @TODO Filtering accounts for significant CPU usage. Consider removing if * possible. */ char *digest = NULL; GString *buf = g_string_sized_new(1024); pcmk__xml_string(xml, (filter? pcmk__xml_fmt_filtered : 0), buf, 0); digest = crm_md5sum(buf->str); if (digest == NULL) { goto done; } pcmk__if_tracing( { char *trace_file = crm_strdup_printf("%s/digest-%s", pcmk__get_tmpdir(), digest); crm_trace("Saving %s.%s.%s to %s", pcmk__xe_get(xml, PCMK_XA_ADMIN_EPOCH), pcmk__xe_get(xml, PCMK_XA_EPOCH), pcmk__xe_get(xml, PCMK_XA_NUM_UPDATES), trace_file); save_xml_to_file(xml, "digest input", trace_file); free(trace_file); }, {} ); done: g_string_free(buf, TRUE); return digest; } /*! * \internal * \brief Check whether calculated digest of given XML matches expected digest * * \param[in] input Root of XML tree to digest * \param[in] expected Expected digest in on-disk format * * \return true if digests match, false on mismatch or error */ bool pcmk__verify_digest(const xmlNode *input, const char *expected) { char *calculated = NULL; bool passed; if (input != NULL) { calculated = pcmk__digest_on_disk_cib(input); if (calculated == NULL) { crm_perror(LOG_ERR, "Could not calculate digest for comparison"); return false; } } passed = pcmk__str_eq(expected, calculated, pcmk__str_casei); if (passed) { crm_trace("Digest comparison passed: %s", calculated); } else { crm_err("Digest comparison failed: expected %s, calculated %s", expected, calculated); } free(calculated); return passed; } /*! * \internal * \brief Check whether an XML attribute should be excluded from CIB digests * * \param[in] name XML attribute name * * \return true if XML attribute should be excluded from CIB digest calculation */ bool pcmk__xa_filterable(const char *name) { static const char *filter[] = { PCMK_XA_CRM_DEBUG_ORIGIN, PCMK_XA_CIB_LAST_WRITTEN, PCMK_XA_UPDATE_ORIGIN, PCMK_XA_UPDATE_CLIENT, PCMK_XA_UPDATE_USER, }; for (int i = 0; i < PCMK__NELEM(filter); i++) { if (strcmp(name, filter[i]) == 0) { return true; } } return false; } char * crm_md5sum(const char *buffer) { char *digest = NULL; gchar *raw_digest = NULL; - // GLib throws an error and returns NULL if buffer is NULL or empty - if (pcmk__str_empty(buffer)) { + /* g_compute_checksum_for_string returns NULL if the input string is empty. + * There are instances where we may want to hash an empty, but non-NULL, + * string so here we just hardcode the result. + */ + if (buffer == NULL) { return NULL; + } else if (pcmk__str_empty(buffer)) { + return pcmk__str_copy("d41d8cd98f00b204e9800998ecf8427e"); } raw_digest = g_compute_checksum_for_string(G_CHECKSUM_MD5, buffer, -1); if (raw_digest == NULL) { crm_err("Failed to calculate hash"); return NULL; } digest = pcmk__str_copy(raw_digest); g_free(raw_digest); crm_trace("Digest %s.", digest); return digest; } // Return true if a is an attribute that should be filtered static bool should_filter_for_digest(xmlAttrPtr a, void *user_data) { if (strncmp((const char *) a->name, CRM_META "_", sizeof(CRM_META " ") - 1) == 0) { return true; } return pcmk__str_any_of((const char *) a->name, PCMK_XA_ID, PCMK_XA_CRM_FEATURE_SET, PCMK__XA_OP_DIGEST, PCMK__META_ON_NODE, PCMK__META_ON_NODE_UUID, "pcmk_external_ip", NULL); } /*! * \internal * \brief Remove XML attributes not needed for operation digest * * \param[in,out] param_set XML with operation parameters */ void pcmk__filter_op_for_digest(xmlNode *param_set) { char *key = NULL; char *timeout = NULL; guint interval_ms = 0; if (param_set == NULL) { return; } /* Timeout is useful for recurring operation digests, so grab it before * removing meta-attributes */ key = crm_meta_name(PCMK_META_INTERVAL); pcmk__xe_get_guint(param_set, key, &interval_ms); free(key); key = NULL; if (interval_ms != 0) { key = crm_meta_name(PCMK_META_TIMEOUT); timeout = pcmk__xe_get_copy(param_set, key); } // Remove all CRM_meta_* attributes and certain other attributes pcmk__xe_remove_matching_attrs(param_set, false, should_filter_for_digest, NULL); // Add timeout back for recurring operation digests if (timeout != NULL) { crm_xml_add(param_set, key, timeout); } free(timeout); free(key); } // Deprecated functions kept only for backward API compatibility // LCOV_EXCL_START #include #include char * calculate_on_disk_digest(xmlNode *input) { return calculate_xml_digest_v1(input); } char * calculate_operation_digest(xmlNode *input, const char *version) { xmlNode *sorted = sorted_xml(input, NULL, true); char *digest = calculate_xml_digest_v1(sorted); pcmk__xml_free(sorted); return digest; } char * calculate_xml_versioned_digest(xmlNode *input, gboolean sort, gboolean do_filter, const char *version) { if ((version == NULL) || (compare_version("3.0.5", version) > 0)) { xmlNode *sorted = NULL; char *digest = NULL; if (sort) { xmlNode *sorted = sorted_xml(input, NULL, true); input = sorted; } crm_trace("Using v1 digest algorithm for %s", pcmk__s(version, "unknown feature set")); digest = calculate_xml_digest_v1(input); pcmk__xml_free(sorted); return digest; } crm_trace("Using v2 digest algorithm for %s", version); return pcmk__digest_xml(input, do_filter); } // LCOV_EXCL_STOP // End deprecated API diff --git a/python/pacemaker/_cts/remote.py b/python/pacemaker/_cts/remote.py index d62adc9109..4745ca2777 100644 --- a/python/pacemaker/_cts/remote.py +++ b/python/pacemaker/_cts/remote.py @@ -1,281 +1,290 @@ """Remote command runner for Pacemaker's Cluster Test Suite (CTS).""" __all__ = ["RemoteExec", "RemoteFactory"] __copyright__ = "Copyright 2014-2025 the Pacemaker project contributors" __license__ = "GNU General Public License version 2 or later (GPLv2+) WITHOUT ANY WARRANTY" import re import os from subprocess import Popen, PIPE from threading import Thread from pacemaker._cts.logging import LogFactory def convert2string(lines): """ Convert byte strings to UTF-8 strings. Lists of byte strings are converted to a list of UTF-8 strings. All other text formats are passed through. """ if isinstance(lines, bytes): return lines.decode("utf-8") if isinstance(lines, list): lst = [] for line in lines: if isinstance(line, bytes): line = line.decode("utf-8") lst.append(line) return lst return lines class AsyncCmd(Thread): """A class for doing the hard work of running a command on another machine.""" def __init__(self, node, command, proc=None, delegate=None): """ Create a new AsyncCmd instance. Arguments: node -- The remote machine to run on command -- The ssh command string to use for remote execution proc -- If not None, a process object previously created with Popen. Instead of spawning a new process, we will then wait on this process to finish and handle its output. delegate -- When the command completes, call the async_complete method on this object """ self._command = command self._delegate = delegate self._logger = LogFactory() self._node = node self._proc = proc Thread.__init__(self) def run(self): """Run the previously instantiated AsyncCmd object.""" out = None err = None if not self._proc: # pylint: disable=consider-using-with self._proc = Popen(self._command, stdout=PIPE, stderr=PIPE, close_fds=True, shell=True) self._logger.debug(f"cmd: async: target={self._node}, pid={self._proc.pid}: {self._command}") self._proc.wait() if self._delegate: self._logger.debug(f"cmd: pid {self._proc.pid} returned {self._proc.returncode} to {self._delegate!r}") else: self._logger.debug(f"cmd: pid {self._proc.pid} returned {self._proc.returncode}") if self._proc.stderr: err = self._proc.stderr.readlines() self._proc.stderr.close() for line in err: self._logger.debug(f"cmd: stderr[{self._proc.pid}]: {line}") err = convert2string(err) if self._proc.stdout: out = self._proc.stdout.readlines() self._proc.stdout.close() out = convert2string(out) if self._delegate: self._delegate.async_complete(self._proc.pid, self._proc.returncode, out, err) class RemoteExec: """ An abstract class for remote execution. It runs a command on another machine using ssh and scp. """ def __init__(self, command, cp_command, silent=False): """ Create a new RemoteExec instance. Arguments: command -- The ssh command string to use for remote execution cp_command -- The scp command string to use for copying files silent -- Should we log command status? """ self._command = command self._cp_command = cp_command self._logger = LogFactory() self._silent = silent self._our_node = os.uname()[1].lower() def _fixcmd(self, cmd): """Perform shell escapes on certain characters in the input cmd string.""" return re.sub("\'", "'\\''", cmd) def _cmd(self, args): """Given a list of arguments, return the string that will be run on the remote system.""" sysname = args[0] command = args[1] if sysname is None or sysname.lower() in [self._our_node, "localhost"]: ret = command else: ret = f"{self._command} {sysname} '{self._fixcmd(command)}'" return ret def _log(self, args): """Log a message.""" if not self._silent: self._logger.log(args) def _debug(self, args): """Log a message at the debug level.""" if not self._silent: self._logger.debug(args) def call_async(self, node, command, delegate=None): """ Run the given command on the given remote system and do not wait for it to complete. Arguments: node -- The remote machine to run on command -- The command to run, as a string delegate -- When the command completes, call the async_complete method on this object Returns the running process object. """ aproc = AsyncCmd(node, self._cmd([node, command]), delegate=delegate) aproc.start() return aproc def __call__(self, node, command, synchronous=True, verbose=2): """ Run the given command on the given remote system. If you call this class like a function, this is what gets called. It's approximately the same as a system() call on the remote machine. Arguments: node -- The remote machine to run on command -- The command to run, as a string synchronous -- Should we wait for the command to complete? - verbose -- If 0, do not lo:g anything. If 1, log the command and its + verbose -- If 0, do not log anything. If 1, log the command and its return code but not its output. If 2, additionally log command output. Returns a tuple of (return code, command output). """ rc = 0 result = None # pylint: disable=consider-using-with proc = Popen(self._cmd([node, command]), stdout=PIPE, stderr=PIPE, close_fds=True, shell=True) if not synchronous and proc.pid > 0 and not self._silent: aproc = AsyncCmd(node, command, proc=proc) aproc.start() return (rc, result) if proc.stdout: result = proc.stdout.readlines() proc.stdout.close() else: self._log("No stdout stream") rc = proc.wait() if verbose > 0: self._debug(f"cmd: target={node}, rc={rc}: {command}") result = convert2string(result) if proc.stderr: errors = proc.stderr.readlines() proc.stderr.close() for err in errors: self._debug(f"cmd: stderr: {err}") if verbose == 2: for line in result: self._debug(f"cmd: stdout: {line}") return (rc, result) def copy(self, source, target, silent=False): """ Perform a copy of the source file to the remote target. This function uses the cp_command provided when the RemoteExec object was created. Returns the return code of the cp_command. """ # @TODO Use subprocess module with argument array instead # (self._cp_command should be an array too) cmd = f"{self._cp_command} '{source}' '{target}'" rc = os.system(cmd) if not silent: self._debug(f"cmd: rc={rc}: {cmd}") return rc def exists_on_all(self, filename, hosts): """Return True if specified file exists on all specified hosts.""" for host in hosts: - rc = self(host, f"test -r {filename}") + (rc, _) = self(host, f"test -r {filename}") if rc != 0: return False return True + def exists_on_none(self, filename, hosts): + """Return True if specified file does not exist on any specified host.""" + for host in hosts: + (rc, _) = self(host, f"test -r {filename}") + if rc == 0: + return False + + return True + class RemoteFactory: """A class for constructing a singleton instance of a RemoteExec object.""" # Class variables # -n: no stdin, -x: no X11, # -o ServerAliveInterval=5: disconnect after 3*5s if the server # stops responding command = ("ssh -l root -n -x -o ServerAliveInterval=5 " "-o ConnectTimeout=10 -o TCPKeepAlive=yes " "-o ServerAliveCountMax=3 ") # -B: batch mode, -q: no stats (quiet) cp_command = "scp -B -q" instance = None # pylint: disable=invalid-name def getInstance(self): """ Return the previously created instance of RemoteExec. If no instance exists, create one and then return that. """ if not RemoteFactory.instance: RemoteFactory.instance = RemoteExec(RemoteFactory.command, RemoteFactory.cp_command, False) return RemoteFactory.instance def enable_qarsh(self): """Enable the QA remote shell.""" # http://nstraz.wordpress.com/2008/12/03/introducing-qarsh/ print("Using QARSH for connections to cluster nodes") RemoteFactory.command = "qarsh -t 300 -l root" RemoteFactory.cp_command = "qacp -q" diff --git a/python/pacemaker/_cts/tests/__init__.py b/python/pacemaker/_cts/tests/__init__.py index 27bef12fdf..4108f37f47 100644 --- a/python/pacemaker/_cts/tests/__init__.py +++ b/python/pacemaker/_cts/tests/__init__.py @@ -1,87 +1,89 @@ """Test classes for the `pacemaker._cts` package.""" -__copyright__ = "Copyright 2023-2024 the Pacemaker project contributors" +__copyright__ = "Copyright 2023-2025 the Pacemaker project contributors" __license__ = "GNU Lesser General Public License version 2.1 or later (LGPLv2.1+)" +from pacemaker._cts.tests.cibsecret import CibsecretTest from pacemaker._cts.tests.componentfail import ComponentFail from pacemaker._cts.tests.ctstest import CTSTest from pacemaker._cts.tests.fliptest import FlipTest from pacemaker._cts.tests.maintenancemode import MaintenanceMode from pacemaker._cts.tests.nearquorumpointtest import NearQuorumPointTest from pacemaker._cts.tests.partialstart import PartialStart from pacemaker._cts.tests.reattach import Reattach from pacemaker._cts.tests.restartonebyone import RestartOnebyOne from pacemaker._cts.tests.resourcerecover import ResourceRecover from pacemaker._cts.tests.restarttest import RestartTest from pacemaker._cts.tests.resynccib import ResyncCIB from pacemaker._cts.tests.remotebasic import RemoteBasic from pacemaker._cts.tests.remotedriver import RemoteDriver from pacemaker._cts.tests.remotemigrate import RemoteMigrate from pacemaker._cts.tests.remoterscfailure import RemoteRscFailure from pacemaker._cts.tests.remotestonithd import RemoteStonithd from pacemaker._cts.tests.simulstart import SimulStart from pacemaker._cts.tests.simulstop import SimulStop from pacemaker._cts.tests.simulstartlite import SimulStartLite from pacemaker._cts.tests.simulstoplite import SimulStopLite from pacemaker._cts.tests.splitbraintest import SplitBrainTest from pacemaker._cts.tests.standbytest import StandbyTest from pacemaker._cts.tests.starttest import StartTest from pacemaker._cts.tests.startonebyone import StartOnebyOne from pacemaker._cts.tests.stonithdtest import StonithdTest from pacemaker._cts.tests.stoponebyone import StopOnebyOne from pacemaker._cts.tests.stoptest import StopTest def test_list(cm, audits): """ Return a list of runnable test class objects. These are objects that are enabled and whose is_applicable methods return True. """ # cm is a reasonable name here. # pylint: disable=invalid-name # A list of all enabled test classes, in the order that they should # be run (if we're doing --once). There are various other ways of # specifying which tests should be run, in which case the order here # will not matter. # # Note that just because a test is listed here doesn't mean it will # definitely be run - is_applicable is still taken into consideration. # Also note that there are other tests that are excluded from this # list for various reasons. enabled_test_classes = [ + CibsecretTest, FlipTest, RestartTest, StonithdTest, StartOnebyOne, SimulStart, SimulStop, StopOnebyOne, RestartOnebyOne, PartialStart, StandbyTest, MaintenanceMode, ResourceRecover, ComponentFail, SplitBrainTest, Reattach, ResyncCIB, NearQuorumPointTest, RemoteBasic, RemoteStonithd, RemoteMigrate, RemoteRscFailure, ] result = [] for testclass in enabled_test_classes: bound_test = testclass(cm) if bound_test.is_applicable(): bound_test.audits = audits result.append(bound_test) return result diff --git a/python/pacemaker/_cts/tests/cibsecret.py b/python/pacemaker/_cts/tests/cibsecret.py new file mode 100644 index 0000000000..679f8b0dfb --- /dev/null +++ b/python/pacemaker/_cts/tests/cibsecret.py @@ -0,0 +1,231 @@ +"""Test managing secrets with cibsecret.""" + +__all__ = ["CibsecretTest"] +__copyright__ = "Copyright 2025 the Pacemaker project contributors" +__license__ = "GNU General Public License version 2 or later (GPLv2+) WITHOUT ANY WARRANTY" + +from pacemaker._cts.tests.ctstest import CTSTest +from pacemaker._cts.tests.simulstartlite import SimulStartLite +from pacemaker._cts.timer import Timer + +# Disable various pylint warnings that occur in so many places throughout this +# file it's easiest to just take care of them globally. This does introduce the +# possibility that we'll miss some other cause of the same warning, but we'll +# just have to be careful. + +# pylint doesn't understand that self._env is subscriptable. +# pylint: disable=unsubscriptable-object +# pylint doesn't understand that self._rsh is callable. +# pylint: disable=not-callable + + +# This comes from include/config.h as private API, assuming pacemaker is built +# with cibsecrets support. I don't want to expose this value publically, at +# least not until we default to including cibsecrets, so it's just set here +# for now. +SECRETS_DIR = "/var/lib/pacemaker/lrm/secrets" + + +class CibsecretTest(CTSTest): + """Test managing secrets with cibsecret.""" + + def __init__(self, cm): + """ + Create a new CibsecretTest instance. + + Arguments: + cm -- A ClusterManager instance + """ + CTSTest.__init__(self, cm) + self.name = "Cibsecret" + + self._secret = "passwd" + self._secret_val = "SecreT_PASS" + + self._rid = "secretDummy" + self._startall = SimulStartLite(cm) + + def _insert_dummy(self, node): + """Create a dummy resource on the given node.""" + pats = [ + f"{node}.*" + (self.templates["Pat:RscOpOK"] % ("start", self._rid)) + ] + + watch = self.create_watch(pats, 60) + watch.set_watch() + + self._cm.add_dummy_rsc(node, self._rid) + + with Timer(self._logger, self.name, "addDummy"): + watch.look_for_all() + + if watch.unmatched: + self.debug("Failed to find patterns when adding dummy resource") + return repr(watch.unmatched) + + return "" + + def _check_cib_value(self, node, expected): + """Check that the secret has the expected value.""" + (rc, lines) = self._rsh(node, f"crm_resource -r {self._rid} -g {self._secret}", + verbose=1) + s = " ".join(lines).strip() + + if rc != 0 or s != expected: + return self.failure(f"Secret set to '{s}' in CIB, not '{expected}'") + + # This is self.success, except without incrementing the success counter + return True + + def _test_check(self, node): + """Test the 'cibsecret check' subcommand.""" + (rc, _) = self._rsh(node, f"cibsecret check {self._rid} {self._secret}", + verbose=1) + if rc != 0: + return self.failure("Failed to check secret") + + # This is self.success, except without incrementing the success counter + return True + + def _test_delete(self, node): + """Test the 'cibsecret delete' subcommand.""" + (rc, _) = self._rsh(node, f"cibsecret delete {self._rid} {self._secret}", + verbose=1) + if rc != 0: + return self.failure("Failed to delete secret") + + # This is self.success, except without incrementing the success counter + return True + + def _test_get(self, node, expected): + """Test the 'cibsecret get' subcommand.""" + (rc, lines) = self._rsh(node, f"cibsecret get {self._rid} {self._secret}", + verbose=1) + s = " ".join(lines).strip() + + if rc != 0 or s != expected: + return self.failure(f"Secret set to '{s}' in local file, not '{expected}'") + + # This is self.success, except without incrementing the success counter + return True + + def _test_set(self, node): + """Test the 'cibsecret set' subcommand.""" + (rc, _) = self._rsh(node, f"cibsecret set {self._rid} {self._secret} {self._secret_val}", + verbose=1) + if rc != 0: + return self.failure("Failed to set secret") + + # This is self.success, except without incrementing the success counter + return True + + def _test_stash(self, node): + """Test the 'cibsecret stash' subcommand.""" + (rc, _) = self._rsh(node, f"cibsecret stash {self._rid} {self._secret}", + verbose=1) + if rc != 0: + return self.failure(f"Failed to stash secret {self._secret}") + + # This is self.success, except without incrementing the success counter + return True + + def _test_sync(self, node): + """Test the 'cibsecret sync' subcommand.""" + (rc, _) = self._rsh(node, "cibsecret sync", verbose=1) + if rc != 0: + return self.failure("Failed to sync secrets") + + # This is self.success, except without incrementing the success counter + return True + + def _test_unstash(self, node): + """Test the 'cibsecret unstash' subcommand.""" + (rc, _) = self._rsh(node, f"cibsecret unstash {self._rid} {self._secret}", + verbose=1) + if rc != 0: + return self.failure(f"Failed to unstash secret {self._secret}") + + # This is self.success, except without incrementing the success counter + return True + + def _test_secrets_removed(self): + """Verify that the secret and its checksum file has been removed.""" + f = f"{SECRETS_DIR}/{self._rid}/{self._secret}" + if not self._rsh.exists_on_none(f, self._env["nodes"]): + return self.failure(f"{f} not deleted from all hosts") + + f = f"{SECRETS_DIR}/{self._rid}/{self._secret}.sign" + if not self._rsh.exists_on_none(f, self._env["nodes"]): + return self.failure(f"{f} not deleted from all hosts") + + return True + + # @TODO: Two improvements that could be made to this test: + # + # (1) Add a test for the 'cibsecret sync' command. This requires modifying + # the test so it brings down one node before creating secrets, then + # bringing the node back up, running 'cibsecret sync', and verifying the + # secrets are copied over. All of this is possible with ctslab, it's + # just kind of a lot of code. + # + # (2) Add some tests for failure cases like trying to stash a value that's + # already secret, etc. + def __call__(self, node): + """Perform this test.""" + self.incr("calls") + ret = self._startall(None) + if not ret: + return self.failure("Start all nodes failed") + + ret = self._insert_dummy(node) + if ret != "": + return self.failure(ret) + + # Test setting a new secret, verifying its value in both the CIB and + # the local store on each node. + if not self._test_set(node): + return False + if not self._check_cib_value(node, "lrm://"): + return False + + for n in self._env["nodes"]: + if not self._test_get(n, self._secret_val): + return False + + # Test checking the secret on each node. + for n in self._env["nodes"]: + if not self._test_check(n): + return False + + # Test moving the secret into the CIB, but now we can only verify that + # its value in the CIB is correct since it's no longer a secret. We + # can also verify that it's been removed from the local store everywhere. + if not self._test_unstash(node): + return False + if not self._check_cib_value(node, self._secret_val): + return False + + self._test_secrets_removed() + + # Test moving the secret back out of the CIB, again verifying its + # value in both places. + if not self._test_stash(node): + return False + if not self._check_cib_value(node, "lrm://"): + return False + + for n in self._env["nodes"]: + if not self._test_get(n, self._secret_val): + return False + + # Delete the secret + if not self._test_delete(node): + return False + + self._test_secrets_removed() + + return self.success() + + @property + def errors_to_ignore(self): + return [r"Reloading .* \(agent\)"] diff --git a/python/pylintrc b/python/pylintrc index baea3f713a..7aecb3a079 100644 --- a/python/pylintrc +++ b/python/pylintrc @@ -1,559 +1,560 @@ # NOTE: Any line with CHANGED: describes something that we changed from the # default pylintrc configuration. [MAIN] # Python code to execute, usually for sys.path manipulation such as # pygtk.require(). #init-hook= # Files or directories to be skipped. They should be base names, not # paths. ignore=CVS # Add files or directories matching the regex patterns to the ignore-list. The # regex matches against paths and can be in Posix or Windows format. ignore-paths= # Files or directories matching the regex patterns are skipped. The regex # matches against base names, not paths. ignore-patterns=^\.# # Pickle collected data for later comparisons. persistent=yes # List of plugins (as comma separated values of python modules names) to load, # usually to register additional checkers. load-plugins= # Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the # number of processors available to use. jobs=1 # When enabled, pylint would attempt to guess common misconfiguration and emit # user-friendly hints instead of false-positive error messages. suggestion-mode=yes # Allow loading of arbitrary C extensions. Extensions are imported into the # active Python interpreter and may run arbitrary code. unsafe-load-any-extension=no # A comma-separated list of package or module names from where C extensions may # be loaded. Extensions are loading into the active Python interpreter and may # run arbitrary code extension-pkg-allow-list= # Minimum supported python version # CHANGED py-version = 3.6 # Control the amount of potential inferred values when inferring a single # object. This can help the performance when dealing with large functions or # complex, nested conditions. limit-inference-results=100 # Specify a score threshold under which the program will exit with error. fail-under=10.0 # Return non-zero exit code if any of these messages/categories are detected, # even if score is above --fail-under value. Syntax same as enable. Messages # specified are enabled, while categories only check already-enabled messages. fail-on= # Clear in-memory caches upon conclusion of linting. Useful if running pylint in # a server-like mode. clear-cache-post-run=no [MESSAGES CONTROL] # Only show warnings with the listed confidence levels. Leave empty to show # all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED # confidence= # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option # multiple time (only on the command line, not in the configuration file where # it should appear only once). See also the "--disable" option for examples. enable= use-symbolic-message-instead, useless-suppression, # Disable the message, report, category or checker with the given id(s). You # can either give multiple identifiers separated by comma (,) or put this # option multiple times (only on the command line, not in the configuration # file where it should appear only once).You can also use "--disable=all" to # disable everything first and then re-enable specific checks. For example, if # you want to run only the similarities checker, you can use "--disable=all # --enable=similarities". If you want to run only the classes checker, but have # no Warning level messages displayed, use"--disable=all --enable=classes # --disable=W" # CHANGED disable=R0801, line-too-long, too-few-public-methods, too-many-arguments, too-many-branches, too-many-instance-attributes, too-many-positional-arguments, + too-many-return-statements, too-many-statements, unrecognized-option, useless-option-value [REPORTS] # Set the output format. Available formats are text, parseable, colorized, msvs # (visual studio) and html. You can also give a reporter class, eg # mypackage.mymodule.MyReporterClass. output-format=text # Tells whether to display a full report or only the messages reports=no # Python expression which should return a note less than 10 (10 is the highest # note). You have access to the variables 'fatal', 'error', 'warning', 'refactor', 'convention' # and 'info', which contain the number of messages in each category, as # well as 'statement', which is the total number of statements analyzed. This # score is used by the global evaluation report (RP0004). evaluation=max(0, 0 if fatal else 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)) # Template used to display messages. This is a python new-style format string # used to format the message information. See doc for all details #msg-template= # Activate the evaluation score. score=yes [LOGGING] # Logging modules to check that the string format arguments are in logging # function parameter format logging-modules=logging # The type of string formatting that logging methods do. `old` means using % # formatting, `new` is for `{}` formatting. logging-format-style=old [MISCELLANEOUS] # List of note tags to take in consideration, separated by a comma. # CHANGED: Don't do anything about FIXME, XXX, or TODO notes= # Regular expression of note tags to take in consideration. #notes-rgx= [SIMILARITIES] # Minimum lines number of a similarity. min-similarity-lines=6 # Ignore comments when computing similarities. ignore-comments=yes # Ignore docstrings when computing similarities. ignore-docstrings=yes # Ignore imports when computing similarities. ignore-imports=yes # Signatures are removed from the similarity computation ignore-signatures=yes [VARIABLES] # Tells whether we should check for unused import in __init__ files. init-import=no # List of additional names supposed to be defined in builtins. Remember that # you should avoid defining new builtins when possible. additional-builtins= # List of strings which can identify a callback function by name. A callback # name must start or end with one of those strings. callbacks=cb_,_cb # Tells whether unused global variables should be treated as a violation. allow-global-unused-variables=yes # List of names allowed to shadow builtins allowed-redefined-builtins= # List of qualified module names which can have objects that can redefine # builtins. redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io [FORMAT] # Maximum number of characters on a single line. max-line-length=100 # Regexp for a line that is allowed to be longer than the limit. ignore-long-lines=^\s*(# )??$ # Allow the body of an if to be on the same line as the test if there is no # else. single-line-if-stmt=no # Allow the body of a class to be on the same line as the declaration if body # contains single statement. single-line-class-stmt=no # Maximum number of lines in a module max-module-lines=2000 # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 # tab). indent-string=' ' # Number of spaces of indent required inside a hanging or continued line. indent-after-paren=4 # Expected format of line ending, e.g. empty (any line ending), LF or CRLF. expected-line-ending-format= [BASIC] # Good variable names which should always be accepted, separated by a comma # CHANGED: Single variable names are handled by variable-rgx below, leaving # _ here as the name for any variable that should be ignored. good-names=_ # Good variable names regexes, separated by a comma. If names match any regex, # they will always be accepted good-names-rgxs= # Bad variable names which should always be refused, separated by a comma bad-names=foo,bar,baz,toto,tutu,tata # Bad variable names regexes, separated by a comma. If names match any regex, # they will always be refused bad-names-rgxs= # Colon-delimited sets of names that determine each other's naming style when # the name regexes allow several styles. name-group= # Include a hint for the correct naming format with invalid-name include-naming-hint=no # Naming style matching correct function names. function-naming-style=snake_case # Regular expression matching correct function names function-rgx=[a-z_][a-z0-9_]{2,30}$ # Naming style matching correct variable names. variable-naming-style=snake_case # Regular expression matching correct variable names # CHANGED: One letter variables are fine variable-rgx=[a-z_][a-z0-9_]{,30}$ # Naming style matching correct constant names. const-naming-style=UPPER_CASE # Regular expression matching correct constant names const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ # Naming style matching correct attribute names. attr-naming-style=snake_case # Regular expression matching correct attribute names attr-rgx=[a-z_][a-z0-9_]{2,}$ # Naming style matching correct argument names. argument-naming-style=snake_case # Regular expression matching correct argument names argument-rgx=[a-z_][a-z0-9_]{2,30}$ # Naming style matching correct class attribute names. class-attribute-naming-style=any # Regular expression matching correct class attribute names class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ # Naming style matching correct class constant names. class-const-naming-style=UPPER_CASE # Regular expression matching correct class constant names. Overrides class- # const-naming-style. #class-const-rgx= # Naming style matching correct inline iteration names. inlinevar-naming-style=any # Regular expression matching correct inline iteration names inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ # Naming style matching correct class names. class-naming-style=PascalCase # Regular expression matching correct class names class-rgx=[A-Z_][a-zA-Z0-9]+$ # Naming style matching correct module names. module-naming-style=snake_case # Regular expression matching correct module names module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ # Naming style matching correct method names. method-naming-style=snake_case # Regular expression matching correct method names method-rgx=[a-z_][a-z0-9_]{2,}$ # Regular expression matching correct type variable names #typevar-rgx= # Regular expression which should only match function or class names that do # not require a docstring. Use ^(?!__init__$)_ to also check __init__. no-docstring-rgx=__.*__ # Minimum line length for functions/classes that require docstrings, shorter # ones are exempt. docstring-min-length=-1 # List of decorators that define properties, such as abc.abstractproperty. property-classes=abc.abstractproperty [TYPECHECK] # Regex pattern to define which classes are considered mixins if ignore-mixin- # members is set to 'yes' mixin-class-rgx=.*MixIn # List of module names for which member attributes should not be checked # (useful for modules/projects where namespaces are manipulated during runtime # and thus existing member attributes cannot be deduced by static analysis). It # supports qualified module names, as well as Unix pattern matching. ignored-modules= # List of class names for which member attributes should not be checked (useful # for classes with dynamically set attributes). This supports the use of # qualified names. ignored-classes=SQLObject, optparse.Values, thread._local, _thread._local # List of members which are set dynamically and missed by pylint inference # system, and so shouldn't trigger E1101 when accessed. Python regular # expressions are accepted. generated-members=REQUEST,acl_users,aq_parent,argparse.Namespace # List of decorators that create context managers from functions, such as # contextlib.contextmanager. contextmanager-decorators=contextlib.contextmanager # Tells whether to warn about missing members when the owner of the attribute # is inferred to be None. ignore-none=yes # This flag controls whether pylint should warn about no-member and similar # checks whenever an opaque object is returned when inferring. The inference # can return multiple potential results while evaluating a Python object, but # some branches might not be evaluated, which results in partial inference. In # that case, it might be useful to still emit no-member and other checks for # the rest of the inferred objects. ignore-on-opaque-inference=yes # Show a hint with possible names when a member name was not found. The aspect # of finding the hint is based on edit distance. missing-member-hint=yes # The minimum edit distance a name should have in order to be considered a # similar match for a missing member name. missing-member-hint-distance=1 # The total number of similar names that should be taken in consideration when # showing a hint for a missing member. missing-member-max-choices=1 [SPELLING] # Spelling dictionary name. Available dictionaries: none. To make it working # install python-enchant package. spelling-dict= # List of comma separated words that should not be checked. spelling-ignore-words= # List of comma separated words that should be considered directives if they # appear and the beginning of a comment and should not be checked. spelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy:,pragma:,# noinspection # A path to a file that contains private dictionary; one word per line. spelling-private-dict-file=.pyenchant_pylint_custom_dict.txt # Tells whether to store unknown words to indicated private dictionary in # --spelling-private-dict-file option instead of raising a message. spelling-store-unknown-words=no # Limits count of emitted suggestions for spelling mistakes. max-spelling-suggestions=2 [DESIGN] # Maximum number of arguments for function / method max-args=10 # Maximum number of locals for function / method body max-locals=25 # Maximum number of return / yield for function / method body max-returns=11 # Maximum number of branch for function / method body max-branches=27 # Maximum number of statements in function / method body max-statements=100 # Maximum number of parents for a class (see R0901). max-parents=7 # List of qualified class names to ignore when counting class parents (see R0901). ignored-parents= # Maximum number of attributes for a class (see R0902). max-attributes=11 # Minimum number of public methods for a class (see R0903). min-public-methods=2 # Maximum number of public methods for a class (see R0904). max-public-methods=25 # Maximum number of boolean expressions in an if statement (see R0916). max-bool-expr=5 # Maximum number of statements in a try-block max-try-statements = 14 # List of regular expressions of class ancestor names to # ignore when counting public methods (see R0903). exclude-too-few-public-methods= [CLASSES] # List of method names used to declare (i.e. assign) instance attributes. # CHANGED: Remove setUp and __post_init__, add reset defining-attr-methods=__init__,__new__,reset # List of valid names for the first argument in a class method. valid-classmethod-first-arg=cls # List of valid names for the first argument in a metaclass class method. valid-metaclass-classmethod-first-arg=mcs # List of member names, which should be excluded from the protected access # warning. exclude-protected=_asdict,_fields,_replace,_source,_make # Warn about protected attribute access inside special methods check-protected-access-in-special-methods=no [IMPORTS] # List of modules that can be imported at any level, not just the top level # one. allow-any-import-level= # Allow wildcard imports from modules that define __all__. allow-wildcard-with-all=no # Analyse import fallback blocks. This can be used to support both Python 2 and # 3 compatible code, which means that the block might have code that exists # only in one or another interpreter, leading to false positives when analysed. analyse-fallback-blocks=no # Deprecated modules which should not be used, separated by a comma deprecated-modules=regsub,TERMIOS,Bastion,rexec # Create a graph of every (i.e. internal and external) dependencies in the # given file (report RP0402 must not be disabled) import-graph= # Create a graph of external dependencies in the given file (report RP0402 must # not be disabled) ext-import-graph= # Create a graph of internal dependencies in the given file (report RP0402 must # not be disabled) int-import-graph= # Force import order to recognize a module as part of the standard # compatibility libraries. known-standard-library= # Force import order to recognize a module as part of a third party library. known-third-party=enchant # Couples of modules and preferred modules, separated by a comma. preferred-modules= [EXCEPTIONS] # Exceptions that will emit a warning when being caught. Defaults to # "Exception" overgeneral-exceptions=builtins.Exception [TYPING] # Set to ``no`` if the app / library does **NOT** need to support runtime # introspection of type annotations. If you use type annotations # **exclusively** for type checking of an application, you're probably fine. # For libraries, evaluate if some users what to access the type hints at # runtime first, e.g., through ``typing.get_type_hints``. Applies to Python # versions 3.7 - 3.9 runtime-typing = no [DEPRECATED_BUILTINS] # List of builtins function names that should not be used, separated by a comma bad-functions=map,input [REFACTORING] # Maximum number of nested blocks for function / method body max-nested-blocks=5 # Complete name of functions that never returns. When checking for # inconsistent-return-statements if a never returning function is called then # it will be considered as an explicit return statement and no message will be # printed. never-returning-functions=sys.exit,argparse.parse_error [STRING] # This flag controls whether inconsistent-quotes generates a warning when the # character used as a quote delimiter is used inconsistently within a module. check-quote-consistency=no # This flag controls whether the implicit-str-concat should generate a warning # on implicit string concatenation in sequences defined over several lines. check-str-concat-over-line-jumps=no [CODE_STYLE] # Max line length for which to sill emit suggestions. Used to prevent optional # suggestions which would get split by a code formatter (e.g., black). Will # default to the setting for ``max-line-length``. #max-line-length-suggestions= diff --git a/tools/Makefile.am b/tools/Makefile.am index a5350c2144..5fb1f8c75e 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -1,151 +1,160 @@ # -# Copyright 2004-2023 the Pacemaker project contributors +# Copyright 2004-2025 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 $(top_srcdir)/mk/common.mk include $(top_srcdir)/mk/man.mk if BUILD_SYSTEMD systemdsystemunit_DATA = crm_mon.service endif noinst_HEADERS = crm_mon.h \ crm_resource.h pcmkdir = $(datadir)/$(PACKAGE) pcmk_DATA = report.common \ report.collector sbin_SCRIPTS = crm_report \ crm_standby \ crm_master \ crm_failcount -if BUILD_CIBSECRETS -sbin_SCRIPTS += cibsecret -endif + noinst_SCRIPTS = cluster-clean \ cluster-helper \ pcmk_simtimes EXTRA_DIST = $(wildcard *.inc) \ fix-manpages sbin_PROGRAMS = attrd_updater \ cibadmin \ crmadmin \ crm_simulate \ crm_attribute \ crm_diff \ crm_error \ crm_mon \ crm_node \ crm_resource \ crm_rule \ crm_shadow \ crm_verify \ crm_ticket \ iso8601 \ stonith_admin +if BUILD_CIBSECRETS +sbin_PROGRAMS += cibsecret +endif + ## SOURCES # A few tools are just thin wrappers around crm_attribute. # This makes their help get updated when crm_attribute changes # (see mk/common.mk). MAN8DEPS = crm_attribute crmadmin_SOURCES = crmadmin.c crmadmin_LDADD = $(top_builddir)/lib/pacemaker/libpacemaker.la crmadmin_LDADD += $(top_builddir)/lib/pengine/libpe_status.la crmadmin_LDADD += $(top_builddir)/lib/cib/libcib.la crmadmin_LDADD += $(top_builddir)/lib/common/libcrmcommon.la crm_error_SOURCES = crm_error.c crm_error_LDADD = $(top_builddir)/lib/pacemaker/libpacemaker.la crm_error_LDADD += $(top_builddir)/lib/common/libcrmcommon.la cibadmin_SOURCES = cibadmin.c cibadmin_LDADD = $(top_builddir)/lib/pacemaker/libpacemaker.la cibadmin_LDADD += $(top_builddir)/lib/cib/libcib.la cibadmin_LDADD += $(top_builddir)/lib/common/libcrmcommon.la +if BUILD_CIBSECRETS +cibsecret_SOURCES = cibsecret.c +cibsecret_LDADD = $(top_builddir)/lib/pacemaker/libpacemaker.la +cibsecret_LDADD += $(top_builddir)/lib/cib/libcib.la +cibsecret_LDADD += $(top_builddir)/lib/common/libcrmcommon.la +endif + crm_shadow_SOURCES = crm_shadow.c crm_shadow_LDADD = $(top_builddir)/lib/cib/libcib.la crm_shadow_LDADD += $(top_builddir)/lib/common/libcrmcommon.la crm_node_SOURCES = crm_node.c crm_node_LDADD = $(top_builddir)/lib/pacemaker/libpacemaker.la crm_node_LDADD += $(top_builddir)/lib/cib/libcib.la crm_node_LDADD += $(top_builddir)/lib/common/libcrmcommon.la crm_simulate_SOURCES = crm_simulate.c crm_simulate_LDADD = $(top_builddir)/lib/pacemaker/libpacemaker.la crm_simulate_LDADD += $(top_builddir)/lib/pengine/libpe_status.la crm_simulate_LDADD += $(top_builddir)/lib/cib/libcib.la crm_simulate_LDADD += $(top_builddir)/lib/common/libcrmcommon.la crm_diff_SOURCES = crm_diff.c crm_diff_LDADD = $(top_builddir)/lib/common/libcrmcommon.la crm_mon_SOURCES = crm_mon.c crm_mon_curses.c crm_mon_LDADD = $(top_builddir)/lib/pacemaker/libpacemaker.la crm_mon_LDADD += $(top_builddir)/lib/pengine/libpe_status.la crm_mon_LDADD += $(top_builddir)/lib/fencing/libstonithd.la crm_mon_LDADD += $(top_builddir)/lib/cib/libcib.la crm_mon_LDADD += $(top_builddir)/lib/common/libcrmcommon.la crm_mon_LDADD += $(CURSES_LIBS) crm_verify_SOURCES = crm_verify.c crm_verify_LDADD = $(top_builddir)/lib/pacemaker/libpacemaker.la crm_verify_LDADD += $(top_builddir)/lib/pengine/libpe_status.la crm_verify_LDADD += $(top_builddir)/lib/cib/libcib.la crm_verify_LDADD += $(top_builddir)/lib/common/libcrmcommon.la crm_attribute_SOURCES = crm_attribute.c crm_attribute_LDADD = $(top_builddir)/lib/pacemaker/libpacemaker.la crm_attribute_LDADD += $(top_builddir)/lib/cib/libcib.la crm_attribute_LDADD += $(top_builddir)/lib/common/libcrmcommon.la crm_resource_SOURCES = crm_resource.c \ crm_resource_ban.c \ crm_resource_print.c \ crm_resource_runtime.c crm_resource_LDADD = $(top_builddir)/lib/pacemaker/libpacemaker.la crm_resource_LDADD += $(top_builddir)/lib/pengine/libpe_status.la crm_resource_LDADD += $(top_builddir)/lib/cib/libcib.la crm_resource_LDADD += $(top_builddir)/lib/lrmd/liblrmd.la crm_resource_LDADD += $(top_builddir)/lib/fencing/libstonithd.la crm_resource_LDADD += $(top_builddir)/lib/services/libcrmservice.la crm_resource_LDADD += $(top_builddir)/lib/common/libcrmcommon.la crm_rule_SOURCES = crm_rule.c crm_rule_LDADD = $(top_builddir)/lib/pacemaker/libpacemaker.la crm_rule_LDADD += $(top_builddir)/lib/pengine/libpe_status.la crm_rule_LDADD += $(top_builddir)/lib/cib/libcib.la crm_rule_LDADD += $(top_builddir)/lib/common/libcrmcommon.la iso8601_SOURCES = iso8601.c iso8601_LDADD = $(top_builddir)/lib/common/libcrmcommon.la attrd_updater_SOURCES = attrd_updater.c attrd_updater_LDADD = $(top_builddir)/lib/pacemaker/libpacemaker.la attrd_updater_LDADD += $(top_builddir)/lib/common/libcrmcommon.la crm_ticket_SOURCES = crm_ticket.c crm_ticket_LDADD = $(top_builddir)/lib/pacemaker/libpacemaker.la crm_ticket_LDADD += $(top_builddir)/lib/pengine/libpe_status.la crm_ticket_LDADD += $(top_builddir)/lib/cib/libcib.la crm_ticket_LDADD += $(top_builddir)/lib/common/libcrmcommon.la stonith_admin_SOURCES = stonith_admin.c stonith_admin_LDADD = $(top_builddir)/lib/pacemaker/libpacemaker.la stonith_admin_LDADD += $(top_builddir)/lib/pengine/libpe_status.la stonith_admin_LDADD += $(top_builddir)/lib/cib/libcib.la stonith_admin_LDADD += $(top_builddir)/lib/fencing/libstonithd.la stonith_admin_LDADD += $(top_builddir)/lib/common/libcrmcommon.la CLEANFILES = $(man8_MANS) diff --git a/tools/cibsecret.8.inc b/tools/cibsecret.8.inc new file mode 100644 index 0000000000..099d53f2ce --- /dev/null +++ b/tools/cibsecret.8.inc @@ -0,0 +1,11 @@ +[synopsis] +cibsecret [OPTION?] [options] + +/in Pacemaker CIB/ +.SH OPTIONS + +/for better performance./ +.SH SUBCOMMANDS + +/Make a sensitive resource parameter/ +.SH KNOWN LIMITATIONS diff --git a/tools/cibsecret.c b/tools/cibsecret.c new file mode 100644 index 0000000000..43479a8ab2 --- /dev/null +++ b/tools/cibsecret.c @@ -0,0 +1,1213 @@ +/* + * Copyright 2025 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 + +#include // EINVAL, ENODEV, ENOENT, ENOTCONN +#include +#include +#include // setenv, unsetenv +#include +#include // LOG_DEBUG +#include // umask, S_IRGRP, S_IROTH, ... +#include // WEXITSTATUS +#include // geteuid + +#include +#include // xmlNode, xmlNodeGetContent +#include // xmlFree +#include // xmlChar + +#include // cib__signon_query +#include +#include +#include // crm_strdup_printf +#include // crm_md5sum +#include // crm_element_value, PCMK_XA_* + +#include // pcmk__query_node_name + +#define SUMMARY "cibsecret - manage sensitive information in Pacemaker CIB" + +#define LRM_MAGIC "lrm://" +#define SSH_OPTS "-o StrictHostKeyChecking=no" + +static gchar **remainder = NULL; +static gboolean no_cib = FALSE; + +static GOptionEntry entries[] = { + { "no-cib", 'C', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &no_cib, + "Don't read or write the CIB", + NULL }, + + { G_OPTION_REMAINING, 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING_ARRAY, &remainder, + NULL, + NULL }, + + { NULL } +}; + +/*! + * \internal + * \brief A function for running a command on remote hosts + * + * \param[in,out] out Output object + * \param[in] nodes A list of remote hosts + * \param[in] cmdline The command line to run + */ +typedef int (*rsh_fn_t)(pcmk__output_t *out, gchar **nodes, const char *cmdline); + +/*! + * \internal + * \brief A function for copying a file to remote hosts + * + * \param[in,out] out Output object + * \param[in] nodes A list of remote hosts + * \param[in] to The destination path on the remote host + * \param[in] from The local file (or directory) to copy + * + * \note \p from can either be a single file or a directory. It cannot be + * be multiple files in a space-separated string. If multiple files need + * to be copied, either copy the entire directory at once or call this + * function multiple times. + */ +typedef int (*rcp_fn_t)(pcmk__output_t *out, gchar **nodes, const char *to, + const char *from); + +struct subcommand_entry { + const char *name; + int args; + const char *usage; + bool requires_cib; + /* The shell version of cibsecret exited with a wide variety of error codes + * for all sorts of situations. Our standard Pacemaker return codes don't + * really line up with what it was doing - either we don't have a code with + * the right name, or we have one that doesn't map to the right exit code, + * etc. + * + * For backwards compatibility, the subcommand handler functions will + * return a standard Pacemaker so other functions here know what to do, but + * it will also take exit_code as an out parameter for the subcommands to + * set and for us to exit with. + */ + int (*handler)(pcmk__output_t *out, rsh_fn_t rsh_fn, rcp_fn_t rcp_fn, + crm_exit_t *exit_code); +}; + +static int +run_cmdline(pcmk__output_t *out, const char *cmdline, char **standard_out) +{ + int rc = pcmk_rc_ok; + gboolean success = FALSE; + GError *error = NULL; + gchar *sout = NULL; + gchar *serr = NULL; + gint status; + + /* A failure here is a failure starting the program (for example, it doesn't + * exist on the $PATH), not that it ran but exited with an error code. + */ + success = g_spawn_command_line_sync(cmdline, &sout, &serr, &status, &error); + if (!success) { + out->err(out, "%s", error->message); + rc = pcmk_rc_error; + goto done; + } + + /* A failure here indicates that the program exited with a non-zero exit + * code or due to a fatal signal. + */ + /* @FIXME @COMPAT g_spawn_check_exit_status is deprecated as of glib 2.70 + * and is replaced with g_spawn_check_wait_status. + */ + success = g_spawn_check_exit_status(status, &error); + + if (!success) { + out->err(out, "%s", error->message); + out->subprocess_output(out, WEXITSTATUS(status), sout, serr); + rc = pcmk_rc_error; + } + +done: + pcmk__str_update(standard_out, sout); + + g_free(sout); + g_free(serr); + g_clear_error(&error); + + return rc; +} + +static int +pssh(pcmk__output_t *out, gchar **nodes, const char *cmdline) +{ + int rc = pcmk_rc_ok; + char *s = NULL; + gchar *hosts = g_strjoinv(" ", nodes); + + s = crm_strdup_printf("pssh -i -H \"%s\" -x \"" SSH_OPTS "\" -- \"%s\"", + hosts, cmdline); + rc = run_cmdline(out, s, NULL); + + free(s); + g_free(hosts); + return rc; +} + +static int +pdsh(pcmk__output_t *out, gchar **nodes, const char *cmdline) +{ + int rc = pcmk_rc_ok; + char *s = NULL; + gchar *hosts = g_strjoinv(",", nodes); + + s = crm_strdup_printf("pdsh -w \"%s\" -- \"%s\"", hosts, cmdline); + setenv("PDSH_SSH_ARGS_APPEND", SSH_OPTS, 1); + rc = run_cmdline(out, s, NULL); + unsetenv("PDSH_SSH_ARGS_APPEND"); + + free(s); + g_free(hosts); + return rc; +} + +static int +ssh(pcmk__output_t *out, gchar **nodes, const char *cmdline) +{ + int rc = pcmk_rc_ok; + + for (gchar **node = nodes; *node != NULL; node++) { + char *s = crm_strdup_printf("ssh " SSH_OPTS " \"%s\" -- \"%s\"", + *node, cmdline); + + rc = run_cmdline(out, s, NULL); + free(s); + + if (rc != pcmk_rc_ok) { + return rc; + } + + } + + return rc; +} + +static int +pscp(pcmk__output_t *out, gchar **nodes, const char *to, const char *from) +{ + int rc = pcmk_rc_ok; + char *s = NULL; + gchar *hosts = g_strjoinv(" ", nodes); + + s = crm_strdup_printf("pscp.pssh -H \"%s\" -x \"-pr\" -x \"" SSH_OPTS "\" -- \"%s\" \"%s\"", + hosts, from, to); + rc = run_cmdline(out, s, NULL); + + free(s); + g_free(hosts); + return rc; +} + +static int +pdcp(pcmk__output_t *out, gchar **nodes, const char *to, const char *from) +{ + int rc = pcmk_rc_ok; + char *s = NULL; + gchar *hosts = g_strjoinv(",", nodes); + + s = crm_strdup_printf("pdcp -pr -w \"%s\" -- \"%s\" \"%s\"", hosts, from, to); + setenv("PDSH_SSH_ARGS_APPEND", SSH_OPTS, 1); + rc = run_cmdline(out, s, NULL); + unsetenv("PDSH_SSH_ARGS_APPEND"); + + free(s); + g_free(hosts); + return rc; +} + +static int +scp(pcmk__output_t *out, gchar **nodes, const char *to, const char *from) +{ + int rc = pcmk_rc_ok; + + for (gchar **node = nodes; *node != NULL; node++) { + char *s = crm_strdup_printf("scp -pqr " SSH_OPTS " \"%s\" \"%s:%s\"", + from, *node, to); + + rc = run_cmdline(out, s, NULL); + free(s); + + if (rc != pcmk_rc_ok) { + return rc; + } + + } + + return rc; +} + +static gchar ** +reachable_hosts(pcmk__output_t *out, GList *all) +{ + GPtrArray *reachable = NULL; + gchar *path = NULL; + + path = g_find_program_in_path("fping"); + + reachable = g_ptr_array_new(); + + if ((path == NULL) || (geteuid() != 0)) { + for (GList *host = all; host != NULL; host = host->next) { + int rc = pcmk_rc_ok; + char *cmdline = crm_strdup_printf("ping -c 2 -q %s", (char *) host->data); + + rc = run_cmdline(out, cmdline, NULL); + + free(cmdline); + + if (rc == pcmk_rc_ok) { + g_ptr_array_add(reachable, g_strdup(host->data)); + } + } + + } else { + GString *all_str = g_string_sized_new(64); + gchar **parts = NULL; + char *standard_out = NULL; + char *cmdline = NULL; + + for (GList *host = all; host != NULL; host = host->next) { + pcmk__add_word(&all_str, 64, host->data); + } + + cmdline = crm_strdup_printf("fping -a -q %s", all_str->str); + run_cmdline(out, cmdline, &standard_out); + + parts = g_strsplit(standard_out, "\n", 0); + for (gchar **p = parts; *p != NULL; p++) { + if (pcmk__str_empty(*p)) { + continue; + } + + g_ptr_array_add(reachable, g_strdup(*p)); + } + + free(cmdline); + free(standard_out); + g_string_free(all_str, TRUE); + g_strfreev(parts); + } + + g_free(path); + g_ptr_array_add(reachable, NULL); + return (char **) g_ptr_array_free(reachable, FALSE); +} + +struct node_data { + pcmk__output_t *out; + char *local_node; + const char *field; + GList *all_nodes; +}; + +static void +node_iter_helper(xmlNode *result, void *user_data) +{ + struct node_data *data = user_data; + const char *uname = pcmk__xe_get(result, PCMK_XA_UNAME); + const char *id = pcmk__xe_get(result, data->field); + const char *name = pcmk__s(uname, id); + + /* Filter out the local node */ + if (pcmk__str_eq(name, data->local_node, pcmk__str_null_matches)) { + return; + } + + data->all_nodes = g_list_append(data->all_nodes, g_strdup(name)); +} + +static gchar ** +get_live_peers(pcmk__output_t *out) +{ + int rc = pcmk_rc_ok; + xmlNode *xml_node = NULL; + gchar **reachable = NULL; + + struct node_data nd = { + .out = out, + .all_nodes = NULL + }; + + /* Get the local node name. */ + rc = pcmk__query_node_name(out, 0, &(nd.local_node), 0); + if (rc != pcmk_rc_ok) { + out->err(out, "Could not get local node name"); + goto done; + } + + /* Get a list of all node names, filtering out the local node. */ + rc = cib__signon_query(out, NULL, &xml_node); + if (rc != pcmk_rc_ok) { + out->err(out, "Could not get list of cluster nodes"); + goto done; + } + + nd.field = PCMK_XA_ID; + pcmk__xpath_foreach_result(xml_node->doc, PCMK__XP_MEMBER_NODE_CONFIG, + node_iter_helper, &nd); + nd.field = PCMK_XA_VALUE; + pcmk__xpath_foreach_result(xml_node->doc, PCMK__XP_GUEST_NODE_CONFIG, + node_iter_helper, &nd); + nd.field = PCMK_XA_ID; + pcmk__xpath_foreach_result(xml_node->doc, PCMK__XP_REMOTE_NODE_CONFIG, + node_iter_helper, &nd); + + if (nd.all_nodes == NULL) { + goto done; + } + + /* Get a list of all nodes that respond to pings */ + reachable = reachable_hosts(out, nd.all_nodes); + + /* Warn the user about any that didn't respond to pings */ + for (const GList *iter = nd.all_nodes; iter != NULL; iter = iter->next) { + bool found = false; + + for (gchar **host = reachable; *host != NULL; host++) { + if (pcmk__str_eq(iter->data, *host, pcmk__str_none)) { + found = true; + break; + } + } + + if (!found) { + out->info(out, "Node %s is down - you'll need to update it " + "with `cibsecret sync` later", (char *) iter->data); + } + } + +done: + free(nd.local_node); + free(xml_node); + + if (nd.all_nodes != NULL) { + g_list_free_full(nd.all_nodes, g_free); + } + + return reachable; +} + +static int +sync_one_file(pcmk__output_t *out, rsh_fn_t rsh_fn, rcp_fn_t rcp_fn, + const char *path) +{ + int rc = pcmk_rc_ok; + gchar *dirname = NULL; + gchar **peers = get_live_peers(out); + gchar *peer_str = NULL; + char *cmdline = NULL; + + if (peers == NULL) { + return pcmk_rc_ok; + } + + peer_str = g_strjoinv(" ", peers); + + if (pcmk__str_eq(remainder[0], "delete", pcmk__str_none)) { + out->info(out, "Deleting %s from %s ...", path, peer_str); + } else { + out->info(out, "Syncing %s to %s ...", path, peer_str); + } + + dirname = g_path_get_dirname(path); + + cmdline = crm_strdup_printf("mkdir -p %s", dirname); + rc = rsh_fn(out, peers, cmdline); + if (rc != pcmk_rc_ok) { + goto done; + } + + if (g_file_test(path, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) { + char *sign_path = NULL; + + rc = rcp_fn(out, peers, dirname, path); + if (rc != pcmk_rc_ok) { + goto done; + } + + sign_path = crm_strdup_printf("%s.sign", path); + rc = rcp_fn(out, peers, dirname, sign_path); + free(sign_path); + + } else { + free(cmdline); + cmdline = crm_strdup_printf("rm -f %s %s.sign", path, path); + rc = rsh_fn(out, peers, cmdline); + } + +done: + free(cmdline); + g_free(dirname); + g_strfreev(peers); + g_free(peer_str); + return rc; +} + +static int +check_cib_rsc(pcmk__output_t *out, const char *rsc) +{ + int rc = pcmk_rc_ok; + char *cmdline = NULL; + + if (no_cib) { + return rc; + } + + cmdline = crm_strdup_printf("crm_resource -r %s -W", rsc); + rc = run_cmdline(out, cmdline, NULL); + + free(cmdline); + return rc; +} + +static bool +is_secret(const char *s) +{ + if (no_cib) { + /* Assume that the secret is in the CIB if we can't connect */ + return true; + } + + return pcmk__str_eq(s, LRM_MAGIC, pcmk__str_none); +} + +static char * +get_cib_param(pcmk__output_t *out, const char *rsc, const char *param) +{ + int rc = pcmk_rc_ok; + char *cmdline = NULL; + char *standard_out = NULL; + char *retval = NULL; + char *xpath = NULL; + xmlNode *xml = NULL; + xmlNode *node = NULL; + xmlChar *content = NULL; + + if (no_cib) { + return NULL; + } + + cmdline = crm_strdup_printf("crm_resource -r %s -g %s --output-as=xml", rsc, param); + rc = run_cmdline(out, cmdline, &standard_out); + + if (rc != pcmk_rc_ok) { + goto done; + } + + xml = pcmk__xml_parse(standard_out); + + if (xml == NULL) { + goto done; + } + + xpath = crm_strdup_printf("//" PCMK_XE_ITEM "[@" PCMK_XA_NAME "='%s']", param); + node = pcmk__xpath_find_one(xml->doc, xpath, LOG_DEBUG); + + if (node == NULL) { + goto done; + } + + content = xmlNodeGetContent(node); + + if (content != NULL) { + retval = pcmk__str_copy((char *) content); + xmlFree(content); + } + +done: + free(cmdline); + free(standard_out); + free(xpath); + pcmk__xml_free(xml); + return retval; +} + +static int +remove_cib_param(pcmk__output_t *out, const char *rsc, const char *param) +{ + int rc = pcmk_rc_ok; + char *cmdline = NULL; + + if (no_cib) { + return rc; + } + + cmdline = crm_strdup_printf("crm_resource -r %s -d %s", rsc, param); + rc = run_cmdline(out, cmdline, NULL); + free(cmdline); + return rc; +} + +static int +set_cib_param(pcmk__output_t *out, const char *rsc, const char *param, + const char *value) +{ + int rc = pcmk_rc_ok; + char *cmdline = NULL; + + if (no_cib) { + return rc; + } + + cmdline = crm_strdup_printf("crm_resource -r %s -p %s -v %s", rsc, param, + value); + rc = run_cmdline(out, cmdline, NULL); + free(cmdline); + return rc; +} + +static char * +local_files_get(const char *rsc, const char *param) +{ + char *retval = NULL; + char *lf_file = NULL; + gchar *contents = NULL; + + lf_file = crm_strdup_printf(PCMK__CIB_SECRETS_DIR "/%s/%s", rsc, param); + if (g_file_get_contents(lf_file, &contents, NULL, NULL)) { + contents = g_strchomp(contents); + retval = pcmk__str_copy(contents); + g_free(contents); + } + + free(lf_file); + return retval; +} + +static char * +local_files_getsum(const char *rsc, const char *param) +{ + char *retval = NULL; + char *lf_file = NULL; + gchar *contents = NULL; + + lf_file = crm_strdup_printf(PCMK__CIB_SECRETS_DIR "/%s/%s.sign", rsc, param); + if (g_file_get_contents(lf_file, &contents, NULL, NULL)) { + contents = g_strchomp(contents); + retval = pcmk__str_copy(contents); + g_free(contents); + } + + free(lf_file); + return retval; +} + +static int +local_files_remove(pcmk__output_t *out, rsh_fn_t rsh_fn, rcp_fn_t rcp_fn, + const char *rsc, const char *param) +{ + int rc = pcmk_rc_ok; + char *lf_file = NULL; + char *cmdline = NULL; + + lf_file = crm_strdup_printf(PCMK__CIB_SECRETS_DIR "/%s/%s", rsc, param); + + cmdline = crm_strdup_printf("rm -f %s %s.sign", lf_file, lf_file); + rc = run_cmdline(out, cmdline, NULL); + + if (rc != pcmk_rc_ok) { + goto done; + } + + rc = sync_one_file(out, rsh_fn, rcp_fn, lf_file); + +done: + free(lf_file); + free(cmdline); + return rc; +} + +static int +local_files_set(pcmk__output_t *out, rsh_fn_t rsh_fn, rcp_fn_t rcp_fn, + const char *rsc, const char *param, const char *value) +{ + char *contents = NULL; + char *lf_dir = NULL; + char *lf_file = NULL; + char *sign_file = NULL; + char *calc_sum = NULL; + int rc = pcmk_rc_ok; + + lf_dir = crm_strdup_printf(PCMK__CIB_SECRETS_DIR "/%s", rsc); + + if (g_mkdir_with_parents(lf_dir, 0700) != 0) { + rc = errno; + goto done; + } + + lf_file = crm_strdup_printf("%s/%s", lf_dir, param); + contents = crm_strdup_printf("%s\n", value); + if (!g_file_set_contents(lf_file, contents, -1, NULL)) { + rc = EIO; + goto done; + } + + free(contents); + + sign_file = crm_strdup_printf("%s/%s.sign", lf_dir, param); + calc_sum = crm_md5sum(value); + contents = crm_strdup_printf("%s\n", calc_sum); + + if (!g_file_set_contents(sign_file, contents, -1, NULL)) { + rc = EIO; + goto done; + } + + rc = sync_one_file(out, rsh_fn, rcp_fn, lf_file); + +done: + free(contents); + free(calc_sum); + free(sign_file); + free(lf_dir); + free(lf_file); + return rc; +} + +static int +subcommand_check(pcmk__output_t *out, rsh_fn_t rsh_fn, rcp_fn_t rcp_fn, + crm_exit_t *exit_code) +{ + int rc = pcmk_rc_ok; + const char *rsc = remainder[1]; + const char *param = remainder[2]; + char *value = NULL; + char *calc_sum = NULL; + char *local_sum = NULL; + char *local_value = NULL; + + if (check_cib_rsc(out, rsc) != pcmk_rc_ok) { + *exit_code = CRM_EX_NOSUCH; + rc = ENODEV; + goto done; + } + + value = get_cib_param(out, rsc, param); + if ((value == NULL) || !is_secret(value)) { + out->err(out, "Resource %s parameter %s not set as secret, nothing to check", + rsc, param); + *exit_code = CRM_EX_CONFIG; + rc = EINVAL; + goto done; + } + + local_sum = local_files_getsum(rsc, param); + if (local_sum == NULL) { + out->err(out, "No checksum for resource %s parameter %s", rsc, param); + *exit_code = CRM_EX_OSFILE; + rc = ENOENT; + goto done; + } + + local_value = local_files_get(rsc, param); + if (local_value != NULL) { + calc_sum = crm_md5sum(local_value); + } + + if ((local_value == NULL) || !pcmk__str_eq(calc_sum, local_sum, pcmk__str_none)) { + out->err(out, "Checksum mismatch for resource %s parameter %s", rsc, param); + *exit_code = CRM_EX_DIGEST; + rc = EINVAL; + } + +done: + free(local_sum); + free(local_value); + free(calc_sum); + free(value); + return rc; +} + +static int +subcommand_delete(pcmk__output_t *out, rsh_fn_t rsh_fn, rcp_fn_t rcp_fn, + crm_exit_t *exit_code) +{ + int rc = pcmk_rc_ok; + const char *rsc = remainder[1]; + const char *param = remainder[2]; + + if (check_cib_rsc(out, rsc) != pcmk_rc_ok) { + *exit_code = CRM_EX_NOSUCH; + rc = ENODEV; + goto done; + } + + rc = local_files_remove(out, rsh_fn, rcp_fn, rsc, param); + if (rc != pcmk_rc_ok) { + goto done; + } + + rc = remove_cib_param(out, rsc, param); + +done: + return rc; +} + +static int +subcommand_get(pcmk__output_t *out, rsh_fn_t rsh_fn, rcp_fn_t rcp_fn, + crm_exit_t *exit_code) +{ + int rc = subcommand_check(out, rsh_fn, rcp_fn, exit_code); + char *value = NULL; + const char *rsc = remainder[1]; + const char *param = remainder[2]; + + if (rc != pcmk_rc_ok) { + return rc; + } + + value = local_files_get(rsc, param); + pcmk__assert(value != NULL); + out->info(out, "%s", value); + + free(value); + return pcmk_rc_ok; +} + +/* The previous shell implementation of cibsecret allowed passing the value + * to set (what would be remainder[3] here) via stdin, which we do not support + * here at the moment. + */ +static int +subcommand_set(pcmk__output_t *out, rsh_fn_t rsh_fn, rcp_fn_t rcp_fn, + crm_exit_t *exit_code) +{ + int rc = pcmk_rc_ok; + const char *rsc = remainder[1]; + const char *param = remainder[2]; + const char *value = remainder[3]; + char *current = NULL; + + if (check_cib_rsc(out, rsc) != pcmk_rc_ok) { + *exit_code = CRM_EX_NOSUCH; + rc = ENODEV; + goto done; + } + + current = get_cib_param(out, rsc, param); + if ((current != NULL) && !pcmk__str_any_of(current, LRM_MAGIC, value, NULL)) { + out->err(out, "CIB value <%s> different for %s rsc parameter %s; please " + "delete it first", current, rsc, param); + *exit_code = CRM_EX_CONFIG; + rc = EINVAL; + goto done; + } + + rc = local_files_set(out, rsh_fn, rcp_fn, rsc, param, value); + if (rc != pcmk_rc_ok) { + goto done; + } + + rc = set_cib_param(out, rsc, param, LRM_MAGIC); + +done: + free(current); + return rc; +} + +static int +subcommand_stash(pcmk__output_t *out, rsh_fn_t rsh_fn, rcp_fn_t rcp_fn, + crm_exit_t *exit_code) +{ + int rc = pcmk_rc_ok; + const char *rsc = remainder[1]; + const char *param = remainder[2]; + char *value = NULL; + + if (check_cib_rsc(out, rsc) != pcmk_rc_ok) { + *exit_code = CRM_EX_NOSUCH; + rc = ENODEV; + goto done; + } + + value = get_cib_param(out, rsc, param); + if ((value == NULL) || is_secret(value)) { + if (value == NULL) { + out->err(out, "Nothing to stash for resource %s parameter %s", rsc, + param); + *exit_code = CRM_EX_NOSUCH; + } else { + out->err(out, "Resource %s parameter %s already set as secret", rsc, + param); + *exit_code = CRM_EX_EXISTS; + } + + rc = EINVAL; + goto done; + } + + remainder = g_realloc(remainder, sizeof(gchar *) * 5); + remainder[3] = g_strdup(value); + remainder[4] = NULL; + + rc = subcommand_set(out, rsh_fn, rcp_fn, exit_code); + +done: + free(value); + return rc; +} + +static int +subcommand_sync(pcmk__output_t *out, rsh_fn_t rsh_fn, rcp_fn_t rcp_fn, + crm_exit_t *exit_code) +{ + int rc = pcmk_rc_ok; + gchar *dirname = NULL; + char *cmdline = NULL; + gchar **peers = get_live_peers(out); + gchar *peer_str = NULL; + + if (peers == NULL) { + return pcmk_rc_ok; + } + + peer_str = g_strjoinv(" ", peers); + + out->info(out, "Syncing %s to %s ...", PCMK__CIB_SECRETS_DIR, peer_str); + g_free(peer_str); + + dirname = g_path_get_dirname(PCMK__CIB_SECRETS_DIR); + + rc = rsh_fn(out, peers, "rm -rf " PCMK__CIB_SECRETS_DIR); + if (rc != pcmk_rc_ok) { + *exit_code = CRM_EX_ERROR; + goto done; + } + + cmdline = crm_strdup_printf("mkdir -p %s", dirname); + rc = rsh_fn(out, peers, cmdline); + free(cmdline); + + if (rc != pcmk_rc_ok) { + *exit_code = CRM_EX_ERROR; + goto done; + } + + rc = rcp_fn(out, peers, dirname, PCMK__CIB_SECRETS_DIR); + + if (rc != pcmk_rc_ok) { + *exit_code = CRM_EX_ERROR; + } + +done: + g_strfreev(peers); + g_free(dirname); + return rc; +} + +static int +subcommand_unstash(pcmk__output_t *out, rsh_fn_t rsh_fn, rcp_fn_t rcp_fn, + crm_exit_t *exit_code) +{ + int rc = pcmk_rc_ok; + const char *rsc = remainder[1]; + const char *param = remainder[2]; + char *local_value = NULL; + char *cib_value = NULL; + + local_value = local_files_get(rsc, param); + if (local_value == NULL) { + out->err(out, "Nothing to unstash for resource %s parameter %s", + rsc, param); + *exit_code = CRM_EX_NOSUCH; + rc = EINVAL; + goto done; + } + + if (check_cib_rsc(out, rsc) != pcmk_rc_ok) { + *exit_code = CRM_EX_NOSUCH; + rc = ENODEV; + goto done; + } + + cib_value = get_cib_param(out, rsc, param); + if (!is_secret(cib_value)) { + out->info(out, "Resource %s parameter %s is not set as secret, but we " + "have a local value so proceeding anyway", rsc, param); + } + + rc = local_files_remove(out, rsh_fn, rcp_fn, rsc, param); + if (rc != pcmk_rc_ok) { + goto done; + } + + rc = set_cib_param(out, rsc, param, local_value); + +done: + free(cib_value); + free(local_value); + return rc; +} + +static struct subcommand_entry subcommand_table[] = { + { "check", 2, "check ", false, + subcommand_check }, + { "delete", 2, "delete ", false, + subcommand_delete }, + { "get", 2, "get ", false, + subcommand_get }, + { "set", 3, "set ", false, + subcommand_set }, + { "stash", 2, "stash ", true, + subcommand_stash }, + { "sync", 0, "sync", false, subcommand_sync }, + { "unstash", 2, "unstash ", true, + subcommand_unstash }, + { NULL }, +}; + +static bool +tools_installed(pcmk__output_t *out, rsh_fn_t *rsh_fn, rcp_fn_t *rcp_fn, + GError **error) +{ + gchar *path = NULL; + + path = g_find_program_in_path("pssh"); + if (path != NULL) { + g_free(path); + *rsh_fn = pssh; + *rcp_fn = pscp; + return true; + } + + path = g_find_program_in_path("pdsh"); + if (path != NULL) { + g_free(path); + *rsh_fn = pdsh; + *rcp_fn = pdcp; + return true; + } + + path = g_find_program_in_path("ssh"); + if (path != NULL) { + g_free(path); + *rsh_fn = ssh; + *rcp_fn = scp; + return true; + } + + out->err(out, "Please install one of pssh, pdsh, or ssh"); + return false; +} + +static pcmk__supported_format_t formats[] = { + PCMK__SUPPORTED_FORMAT_NONE, + PCMK__SUPPORTED_FORMAT_TEXT, + PCMK__SUPPORTED_FORMAT_XML, + { NULL, NULL, NULL } +}; + +static GOptionContext * +build_arg_context(pcmk__common_args_t *args, GOptionGroup **group) { + const char *desc = NULL; + GOptionContext *context = NULL; + + desc = "This command manages sensitive resource parameter values that should not be\n" + "stored directly in Pacemaker's Cluster Information Base (CIB). Such values\n" + "are handled by storing a special string directly in the CIB that tells\n" + "Pacemaker to look in a separate, protected file for the actual value.\n\n" + + "The secret files are not encrypted, but protected by file system permissions\n" + "such that only root can read or modify them.\n\n" + + "Since the secret files are stored locally, they must be synchronized across all\n" + "cluster nodes. This command handles the synchronization using (in order of\n" + "preference) pssh, pdsh, or ssh, so one of those must be installed. Before\n" + "synchronizing, this command will ping the cluster nodes to determine which are\n" + "alive, using fping if it is installed, otherwise the ping command. Installing\n" + "fping is strongly recommended for better performance.\n\n" + + "Commands and their parameters:\n\n" + "check \n" + "\tVerify that the locally stored value of a sensitive resource parameter\n" + "\tmatches its locally stored MD5 hash.\n\n" + "delete \n" + "\tRemove a sensitive resource parameter value.\n\n" + "get \n" + "\tDisplay the locally stored value of a sensitive resource parameter.\n\n" + "set \n" + "\tSet the value of a sensitive resource parameter.\n\n" + "stash \n" + "\tMake a non-sensitive resource parameter that is already in the CIB\n" + "\tsensitive (move its value to a locally stored and protected file).\n" + "\tThis may not be used with -C.\n\n" + "sync\n" + "\tCopy all locally stored secrets to all other nodes.\n\n" + "unstash \n" + "\tMake a sensitive resource parameter that is already in the CIB\n" + "\tnon-sensitive (move its value from the locally stored file to the CIB).\n" + "\tThis may not be used with -C.\n\n\n" + + "Known limitations:\n\n" + + "This command can only be run from full cluster nodes (not Pacemaker Remote\n" + "nodes).\n\n" + + "Changes are not atomic, so the cluster may use different values while a\n" + "change is in progress. To avoid problems, it is recommended to put the\n" + "cluster in maintenance mode when making changes with this command.\n\n" + + "Changes in secret values do not trigger an agent reload or restart of the\n" + "affected resource, since they do not change the CIB. If a response is\n" + "desired before the next cluster recheck interval, any CIB change (such as\n" + "setting a node attribute) will trigger it.\n\n" + + "If any node is down when changes to secrets are made, or a new node is\n" + "later added to the cluster, it may have different values when it joins the\n" + "cluster, before 'cibsecret sync' is run. To avoid this, it is recommended to\n" + "run the sync command (from another node) before starting Pacemaker on the\n" + "node.\n\n" + + "Examples:\n\n" + + "# cibsecret set ipmi_node1 passwd SecreT_PASS\n\n" + "# cibsecret get ipmi_node1 passwd\n\n" + "# cibsecret check ipmi_node1 passwd\n\n" + "# cibsecret stash ipmi_node2 passwd\n\n" + "# cibsecret sync\n"; + + context = pcmk__build_arg_context(args, "text (default), xml", group, + " [options]"); + g_option_context_set_description(context, desc); + pcmk__add_main_args(context, entries); + return context; +} + +int +main(int argc, char **argv) +{ + crm_exit_t exit_code = CRM_EX_OK; + int rc = pcmk_rc_ok; + + pcmk__output_t *out = NULL; + + GError *error = NULL; + + GOptionGroup *output_group = NULL; + pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY); + gchar **processed_args = pcmk__cmdline_preproc(argv, NULL); + GOptionContext *context = build_arg_context(args, &output_group); + + struct subcommand_entry cmd; + rsh_fn_t rsh_fn; + rcp_fn_t rcp_fn; + + pcmk__register_formats(output_group, formats); + if (!g_option_context_parse_strv(context, &processed_args, &error)) { + exit_code = CRM_EX_USAGE; + goto done; + } + + pcmk__cli_init_logging("cibsecret", args->verbosity); + + rc = pcmk__output_new(&out, args->output_ty, args->output_dest, argv); + if (rc != pcmk_rc_ok) { + exit_code = CRM_EX_ERROR; + g_set_error(&error, PCMK__EXITC_ERROR, exit_code, + "Error creating output format %s: %s", args->output_ty, + pcmk_rc_str(rc)); + goto done; + } + + if (args->version) { + out->version(out); + goto done; + } + + /* No subcommand was given */ + if ((remainder == NULL) || (g_strv_length(remainder) == 0)) { + gchar *help = g_option_context_get_help(context, TRUE, NULL); + + exit_code = CRM_EX_USAGE; + g_set_error(&error, PCMK__EXITC_ERROR, exit_code, + "Must specify a command option\n\n%s", help); + g_free(help); + goto done; + } + + /* Traverse the subcommand table looking for a match. */ + for (int i = 0; i < PCMK__NELEM(subcommand_table); i++) { + cmd = subcommand_table[i]; + + if (!pcmk__str_eq(remainder[0], cmd.name, pcmk__str_none)) { + continue; + } + + /* We found a match. Check that enough arguments were given and + * display a usage message if not. The "+ 1" is because the table + * entry lists how many arguments the subcommand takes, which does not + * include the subcommand itself. + */ + if (g_strv_length(remainder) != cmd.args + 1) { + exit_code = CRM_EX_USAGE; + g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Usage: %s", + cmd.usage); + goto done; + } + + /* We've found the subcommand handler and it's used correctly. */ + break; + } + + /* If we didn't find a match, a valid subcommand wasn't given. */ + if (cmd.name == NULL) { + exit_code = CRM_EX_USAGE; + g_set_error(&error, PCMK__EXITC_ERROR, exit_code, + "Invalid subcommand given; valid subcommands: " + "check, delete, get, set, stash, sync, unstash"); + goto done; + } + + /* Check that we have the tools necessary to manage secrets */ + if (!tools_installed(out, &rsh_fn, &rcp_fn, &error)) { + exit_code = CRM_EX_NOT_INSTALLED; + goto done; + } + + /* Set a default umask so files we create are only accessible by the + * cluster user. + */ + umask(S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IWOTH | S_IXOTH); + + /* Call the subcommand handler. If the handler fails, it will have already + * set exit_code to the reason why so there's no need to worry with + * additional error checking here at the moment. + */ + if (cmd.requires_cib && no_cib) { + out->err(out, "No access to Pacemaker, %s not supported", cmd.name); + exit_code = CRM_EX_USAGE; + goto done; + } + + cmd.handler(out, rsh_fn, rcp_fn, &exit_code); + + done: + g_strfreev(processed_args); + g_strfreev(remainder); + pcmk__free_arg_context(context); + + pcmk__output_and_clear_error(&error, out); + + if (out != NULL) { + out->finish(out, exit_code, true, NULL); + pcmk__output_free(out); + } + pcmk__unregister_formats(); + crm_exit(exit_code); +} diff --git a/tools/cibsecret.in b/tools/cibsecret.in deleted file mode 100644 index 264667fbc5..0000000000 --- a/tools/cibsecret.in +++ /dev/null @@ -1,440 +0,0 @@ -#!@BASH_PATH@ - -# Copyright 2011-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. -# - -# cibsecret -# -# Manage the secrets directory (by default, /var/lib/pacemaker/lrm/secrets). -# Secrets are ASCII files, holding one value per file: -# // - -# These constants must track crm_exit_t values -CRM_EX_OK=0 -CRM_EX_ERROR=1 -CRM_EX_NOT_INSTALLED=5 -CRM_EX_USAGE=64 -CRM_EX_UNAVAILABLE=69 -CRM_EX_OSFILE=72 -CRM_EX_CONFIG=78 -CRM_EX_DIGEST=104 -CRM_EX_NOSUCH=105 -CRM_EX_EXISTS=108 - -LRM_CIBSECRETS="@PCMK__CIB_SECRETS_DIR@" - -PROG="$(basename "$0")" -SSH_OPTS="-o StrictHostKeyChecking=no" -MAGIC="lrm://" - -usage() { - cat <] [] - -Options: - --help Show this message, then exit - --version Display version information, then exit - -C Don't read or write the CIB - -Commands and their parameters: - set - Set the value of a sensitive resource parameter. - - get - Display the locally stored value of a sensitive resource parameter. - - check - Verify that the locally stored value of a sensitive resource parameter - matches its locally stored MD5 hash. - - stash - Make a non-sensitive resource parameter that is already in the CIB - sensitive (move its value to a locally stored and protected file). - This may not be used with -C. - - unstash - Make a sensitive resource parameter that is already in the CIB - non-sensitive (move its value from the locally stored file to the CIB). - This may not be used with -C. - - delete - Remove a sensitive resource parameter value. - - sync - Copy all locally stored secrets to all other nodes. - -This command manages sensitive resource parameter values that should not be -stored directly in Pacemaker's Cluster Information Base (CIB). Such values -are handled by storing a special string directly in the CIB that tells -Pacemaker to look in a separate, protected file for the actual value. - -The secret files are not encrypted, but protected by file system permissions -such that only root can read or modify them. - -Since the secret files are stored locally, they must be synchronized across all -cluster nodes. This command handles the synchronization using (in order of -preference) pssh, pdsh, or ssh, so one of those must be installed. Before -synchronizing, this command will ping the cluster nodes to determine which are -alive, using fping if it is installed, otherwise the ping command. Installing -fping is strongly recommended for better performance. - -Known limitations: - - This command can only be run from full cluster nodes (not Pacemaker Remote - nodes). - - Changes are not atomic, so the cluster may use different values while a - change is in progress. To avoid problems, it is recommended to put the - cluster in maintenance mode when making changes with this command. - - Changes in secret values do not trigger an agent reload or restart of the - affected resource, since they do not change the CIB. If a response is - desired before the next cluster recheck interval, any CIB change (such as - setting a node attribute) will trigger it. - - If any node is down when changes to secrets are made, or a new node is - later added to the cluster, it may have different values when it joins the - cluster, before "$PROG sync" is run. To avoid this, it is recommended to - run the sync command (from another node) before starting Pacemaker on the - node. - -Examples: - - $PROG set ipmi_node1 passwd SecreT_PASS - - $PROG get ipmi_node1 passwd - - $PROG check ipmi_node1 passwd - - $PROG stash ipmi_node2 passwd - - $PROG sync -EOF - exit "$1" -} - -check_usage() { - case "$1" in - set) [ "$2" -ne 4 ] && [ "$2" -ne 3 ] && usage 1 ;; - get) [ "$2" -ne 3 ] && usage 1 ;; - check) [ "$2" -ne 3 ] && usage 1 ;; - stash) [ "$2" -ne 3 ] && usage 1 ;; - unstash) [ "$2" -ne 3 ] && usage 1 ;; - delete) [ "$2" -ne 3 ] && usage 1 ;; - sync) [ "$2" -ne 1 ] && usage 1 ;; - --help) usage $CRM_EX_OK ;; - --version) crm_attribute --version; exit $? ;; - *) usage $CRM_EX_USAGE ;; - esac -} - -fatal() { - rc=$1 - shift - echo "ERROR: $*" - exit $rc -} - -warn() { - echo "WARNING: $*" -} - -info() { - echo "INFO: $*" -} - -check_env() { - which md5sum >/dev/null 2>&1 || - fatal $CRM_EX_NOT_INSTALLED "please install md5sum to run $PROG" - if which pssh >/dev/null 2>&1; then - rsh=pssh_fun - rcp_to_from=pscp_fun - - # -q is a SUSE patch not present in upstream pssh - PSSH_QUIET_OPTION="" - pssh -q 2>&1|grep "no such option: -q" > /dev/null || - PSSH_QUIET_OPTION="-q" - elif which pdsh >/dev/null 2>&1; then - rsh=pdsh_fun - rcp_to_from=pdcp_fun - elif which ssh >/dev/null 2>&1; then - rsh=ssh_fun - rcp_to_from=scp_fun - else - fatal $CRM_EX_NOT_INSTALLED "please install pssh, pdsh, or ssh to run $PROG" - fi - ps axww | grep '[p]acemaker-controld' >/dev/null || - fatal $CRM_EX_UNAVAILABLE "pacemaker not running? $PROG needs pacemaker" -} - -# This must be called (and return success) before calling $rsh or $rcp_to_from -get_live_peers() { - # Get local node name - GLP_LOCAL_NODE="$(crm_node -n)" - [ $? -eq 0 ] || fatal $CRM_EX_UNAVAILABLE "couldn't get local node name" - - # Get a list of all other cluster nodes - GLP_ALL_PEERS="$(crmadmin -N -q)" - [ $? -eq 0 ] || fatal $CRM_EX_UNAVAILABLE "couldn't determine cluster nodes" - GLP_ALL_PEERS="$(echo "$GLP_ALL_PEERS" | grep -v "^${GLP_LOCAL_NODE}$")" - - # Make a list of those that respond to pings - if [ "$(id -u)" = "0" ] && which fping >/dev/null 2>&1; then - LIVE_NODES=$(fping -a $GLP_ALL_PEERS 2>/dev/null) - else - LIVE_NODES="" - for GLP_NODE in $GLP_ALL_PEERS; do \ - ping -c 2 -q "$GLP_NODE" >/dev/null 2>&1 && - LIVE_NODES="$LIVE_NODES $GLP_NODE" - done - fi - - # Warn the user about any that didn't respond to pings - GLP_DOWN="$( (for GLP_NODE in $LIVE_NODES $GLP_ALL_PEERS; do echo "$GLP_NODE"; done) | sort | uniq -u)" - if [ "$(echo "$GLP_DOWN" | wc -w)" = "1" ]; then - warn "node $GLP_DOWN is down" - warn "you'll need to update it using \"$PROG sync\" later" - elif [ -n "$GLP_DOWN" ]; then - warn "nodes $(echo "$GLP_DOWN" | tr '\n' ' ')are down" - warn "you'll need to update them using \"$PROG sync\" later" - fi - - if [ "$LIVE_NODES" = "" ]; then - info "no other nodes live" - return 1 - fi - return 0 -} - -pssh_fun() { - pssh $PSSH_QUIET_OPTION -i -H "$LIVE_NODES" -x "$SSH_OPTS" -- "$@" -} - -pscp_fun() { - PSCP_DEST="$1" - shift - pscp $PSSH_QUIET_OPTION -H "$LIVE_NODES" -x "-pr" -x "$SSH_OPTS" -- "$@" "$PSCP_DEST" -} - -pdsh_fun() { - PDSH_NODES=$(echo "$LIVE_NODES" | tr '[:space:]' ',') - export PDSH_SSH_ARGS_APPEND="$SSH_OPTS" - pdsh -w "$PDSH_NODES" -- "$@" -} - -pdcp_fun() { - PDCP_DEST="$1" - shift - PDCP_NODES=$(echo "$LIVE_NODES" | tr '[:space:]' ',') - export PDSH_SSH_ARGS_APPEND="$SSH_OPTS" - pdcp -pr -w "$PDCP_NODES" -- "$@" "$PDCP_DEST" -} - -ssh_fun() { - for SSH_NODE in $LIVE_NODES; do - ssh $SSH_OPTS "$SSH_NODE" -- "$@" || return - done -} - -scp_fun() { - SCP_DEST="$1" - shift - for SCP_NODE in $LIVE_NODES; do - scp -pqr $SSH_OPTS "$@" "$SCP_NODE:$SCP_DEST" || return - done -} - -# TODO: this procedure should be replaced with csync2 -# provided that csync2 has already been configured -sync_files() { - get_live_peers || return - if [ "$cmd" != "delete" ]; then - info "syncing $LRM_CIBSECRETS to $(echo "$LIVE_NODES" | tr '\n' ' ') ..." - else - info "deleting $LRM_CIBSECRETS from $(echo "$LIVE_NODES" | tr '\n' ' ') ..." - fi - $rsh rm -rf "$LRM_CIBSECRETS" && - $rsh mkdir -p "$(dirname "$LRM_CIBSECRETS")" && - $rcp_to_from "$(dirname "$LRM_CIBSECRETS")" "$LRM_CIBSECRETS" -} - -sync_one() { - SO_FILE="$1" - get_live_peers || return - if [ "$cmd" != "delete" ]; then - info "syncing $SO_FILE to $(echo "$LIVE_NODES" | tr '\n' ' ') ..." - else - info "deleting $SO_FILE from $(echo "$LIVE_NODES" | tr '\n' ' ') ..." - fi - $rsh mkdir -p "$(dirname "$SO_FILE")" && - if [ -f "$SO_FILE" ]; then - $rcp_to_from "$(dirname "$SO_FILE")" "$SO_FILE" "${SO_FILE}.sign" - else - $rsh rm -f "$SO_FILE" "${SO_FILE}.sign" - fi -} - -is_secret() { - # assume that the secret is in the CIB if we cannot talk to cib - [ "$NO_CRM" ] || test "$1" = "$MAGIC" -} - -check_cib_rsc() { - CCR_OUT="$($NO_CRM crm_resource -r "$1" -W 2>&1)" || fatal $CRM_EX_NOSUCH "$CCR_OUT" -} - -get_cib_param() { - GCP_RSC="$1" - GCP_PARAM="$2" - $NO_CRM crm_resource -r "$GCP_RSC" -g "$GCP_PARAM" 2>/dev/null -} - -set_cib_param() { - SET_RSC="$1" - SET_PARAM="$2" - SET_VAL="$3" - $NO_CRM crm_resource -r "$SET_RSC" -p "$SET_PARAM" -v "$SET_VAL" 2>/dev/null -} - -remove_cib_param() { - RM_RSC="$1" - RM_PARAM="$2" - $NO_CRM crm_resource -r "$RM_RSC" -d "$RM_PARAM" 2>/dev/null -} - -localfiles() { - LF_CMD="$1" - LF_RSC="$2" - LF_PARAM="$3" - LF_VALUE="$4" - LF_FILE="$LRM_CIBSECRETS/$LF_RSC/$LF_PARAM" - case "$LF_CMD" in - get) - cat "$LF_FILE" 2>/dev/null - true - ;; - - getsum) - cat "${LF_FILE}.sign" 2>/dev/null - true - ;; - - set) - LF_SUM="$(printf %s "$LF_VALUE" | md5sum)" || - fatal $CRM_EX_ERROR "md5sum failed to produce hash for resource $LF_RSC parameter $LF_PARAM" - LF_SUM="$(echo "$LF_SUM" | awk '{print $1}')" - mkdir -p "$(dirname "$LF_FILE")" && - echo "$LF_VALUE" > "$LF_FILE" && - echo "$LF_SUM" > "${LF_FILE}.sign" && - sync_one "$LF_FILE" - ;; - - remove) - rm -f "$LF_FILE" "${LF_FILE}.sign" - sync_one "$LF_FILE" - ;; - esac -} - -cibsecret_set() { - CS_VALUE="$1" - - if [ "$2" -ne 4 ]; then - read -p "Enter value: " CS_VALUE - fi - - check_cib_rsc "$rsc" - CIBSET_CURRENT="$(get_cib_param "$rsc" "$param")" - [ -z "$NO_CRM" ] && - [ ! -z "$CIBSET_CURRENT" ] && - [ "$CIBSET_CURRENT" != "$MAGIC" ] && - [ "$CIBSET_CURRENT" != "$CS_VALUE" ] && - fatal $CRM_EX_CONFIG "CIB value <$CIBSET_CURRENT> different for $rsc parameter $param; please delete it first" - localfiles set "$rsc" "$param" "$CS_VALUE" && - set_cib_param "$rsc" "$param" "$MAGIC" -} - -cibsecret_check() { - check_cib_rsc "$rsc" - is_secret "$(get_cib_param "$rsc" "$param")" || - fatal $CRM_EX_CONFIG "resource $rsc parameter $param not set as secret, nothing to check" - CSC_LOCAL_SUM="$(localfiles getsum "$rsc" "$param")" - [ "$CSC_LOCAL_SUM" ] || - fatal $CRM_EX_OSFILE "no MD5 hash for resource $rsc parameter $param" - CSC_LOCAL_VALUE="$(localfiles get "$rsc" "$param")" - CSC_CALC_SUM="$(printf "%s" "$CSC_LOCAL_VALUE" | md5sum | awk '{print $1}')" - [ "$CSC_CALC_SUM" = "$CSC_LOCAL_SUM" ] || - fatal $CRM_EX_DIGEST "MD5 hash mismatch for resource $rsc parameter $param" -} - -cibsecret_get() { - cibsecret_check - localfiles get "$rsc" "$param" -} - -cibsecret_delete() { - check_cib_rsc "$rsc" - localfiles remove "$rsc" "$param" && remove_cib_param "$rsc" "$param" -} - -cibsecret_stash() { - [ "$NO_CRM" ] && fatal $CRM_EX_USAGE "no access to Pacemaker, stash not supported" - check_cib_rsc "$rsc" - CIBSTASH_CURRENT="$(get_cib_param "$rsc" "$param")" - [ "$CIBSTASH_CURRENT" = "" ] && - fatal $CRM_EX_NOSUCH "nothing to stash for resource $rsc parameter $param" - is_secret "$CIBSTASH_CURRENT" && - fatal $CRM_EX_EXISTS "resource $rsc parameter $param already set as secret, nothing to stash" - cibsecret_set "$CIBSTASH_CURRENT" 4 -} - -cibsecret_unstash() { - [ "$NO_CRM" ] && fatal $CRM_EX_USAGE "no access to Pacemaker, unstash not supported" - UNSTASH_LOCAL_VALUE="$(localfiles get "$rsc" "$param")" - [ "$UNSTASH_LOCAL_VALUE" = "" ] && - fatal $CRM_EX_NOSUCH "nothing to unstash for resource $rsc parameter $param" - check_cib_rsc "$rsc" - is_secret "$(get_cib_param "$rsc" "$param")" || - warn "resource $rsc parameter $param not set as secret, but we have local value so proceeding anyway" - localfiles remove "$rsc" "$param" && - set_cib_param "$rsc" "$param" "$UNSTASH_LOCAL_VALUE" -} - -cibsecret_sync() { - sync_files -} - -# Grab arguments -if [ "$1" = "-C" ]; then - NO_CRM=':' - shift -fi -cmd="$1" -rsc="$2" -param="$3" -value="$4" - -# Ensure we have everything we need -check_usage "$cmd" $# -check_env -umask 0077 - -# for dirname() function (@TODO why are we replacing dirname?) -. "@PCMK_OCF_ROOT@/lib/heartbeat/ocf-shellfuncs" - -"cibsecret_$cmd" "$value" $# -rc=$? - -if [ $rc -ne 0 ]; then - fatal $CRM_EX_ERROR "$cmd(): failed with rc: $rc" -fi - -# vim: set filetype=sh: diff --git a/tools/fix-manpages b/tools/fix-manpages index f1f6f0d445..109a0f141d 100644 --- a/tools/fix-manpages +++ b/tools/fix-manpages @@ -1,33 +1,33 @@ # Because tools/*.8.inc include a synopsis, the following line removes # a redundant Usage: header from the man page and the couple lines after # it. /.SS "Usage:"/,+3d # The tools/*.8.inc files also include some additional section headers # on a per-tool basis. These section headers will get printed out as # .SH lines, but then the header from the --help-all output will also # get turned into groff. For instance, the following will be in the # man page for NOTES: # # .SH NOTES # .PP # Notes: # .PP # # The following block looks for any of those additional headers. The # 'n' command puts the next line in the pattern space, the two 'N' # commands append the next two lines, and then the 'd' command deletes # them. So basically, this just deletes # # .PP # Notes: # .PP # # This leaves the --help-all output looking good and removes redundant # stuff from the man page. Feel free to add additional headers here. # Not all tools will have all headers. -/.SH NOTES\|.SH INTERACTIVE USE\|.SH OPERATION SPECIFICATION\|.SH OUTPUT CONTROL\|.SH TIME SPECIFICATION/{ n +/.SH NOTES\|.SH INTERACTIVE USE\|.SH OPERATION SPECIFICATION\|.SH OUTPUT CONTROL\|.SH TIME SPECIFICATION\|.SH SUBCOMMANDS\|.SH KNOWN LIMITATIONS/{ n N N d } diff --git a/xml/Makefile.am b/xml/Makefile.am index 5b218a9148..37d4f487d4 100644 --- a/xml/Makefile.am +++ b/xml/Makefile.am @@ -1,288 +1,289 @@ # -# Copyright 2004-2024 the Pacemaker project contributors +# Copyright 2004-2025 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 $(top_srcdir)/mk/common.mk noarch_pkgconfig_DATA = $(builddir)/pacemaker-schemas.pc # Pacemaker has 3 schemas: the CIB schema, the API schema (for command-line # tool XML output), and a legacy schema for crm_mon --as-xml. # # See README.md for details on updating CIB schema files (API is similar) # The CIB and crm_mon schemas are installed directly in PCMK_SCHEMA_DIR # for historical reasons, while the API schema is installed in a subdirectory. APIdir = $(PCMK_SCHEMA_DIR)/api CIBdir = $(PCMK_SCHEMA_DIR) MONdir = $(PCMK_SCHEMA_DIR) basexsltdir = $(PCMK_SCHEMA_DIR)/base dist_basexslt_DATA = $(srcdir)/base/access-render-2.xsl # Extract a sorted list of available numeric schema versions # from filenames like NAME-MAJOR[.MINOR][.MINOR-MINOR].rng numeric_versions = $(shell ls -1 $(1) \ | sed -n -e 's/^.*-\([0-9][0-9.]*\).rng$$/\1/p' \ | sort -u -t. -k 1,1n -k 2,2n -k 3,3n) version_pairs = $(join \ $(1),$(addprefix \ -,$(wordlist \ 2,$(words $(1)),$(1) \ ) \ ) \ ) version_pairs_last = $(wordlist \ $(words \ $(wordlist \ 2,$(1),$(2) \ ) \ ),$(1),$(2) \ ) # NOTE: All files in API_request_base, CIB_cfg_base, API_base, and CIB_base # need to start with a unique prefix. These variables all get iterated over # and globbed, and two files starting with the same prefix will cause # problems. # Names of API schemas that form the choices for pacemaker-result content -API_request_base = command-output \ +API_request_base = cibsecret \ + command-output \ crm_attribute \ crm_diff \ crm_error \ crm_mon \ crm_node \ crm_resource \ crm_rule \ crm_shadow \ crm_simulate \ crm_ticket \ crmadmin \ digests \ iso8601 \ pacemakerd \ stonith_admin \ version # Names of CIB schemas that form the choices for cib/configuration content CIB_cfg_base = options \ nodes \ resources \ constraints \ fencing \ acls \ tags \ alerts # Names of all schemas (including top level and those included by others) API_base = $(API_request_base) \ any-element \ failure \ fence-event \ generic-list \ instruction \ item \ node-attrs \ node-history \ nodes \ ocf-ra \ options \ patchset \ resources \ status \ subprocess-output \ ticket CIB_base = cib \ $(CIB_cfg_base) \ status \ score \ rule \ nvset # Static schema files and transforms (only CIB has transforms) # # This is more complicated than it should be due to the need to support # VPATH builds and "make distcheck". We need the absolute paths for reliable # substitution back and forth, and relative paths for distributed files. API_abs_files = $(foreach base,$(API_base),$(wildcard $(abs_srcdir)/api/$(base)-*.rng)) CIB_abs_files = $(foreach base,$(CIB_base),$(wildcard $(abs_srcdir)/$(base).rng $(abs_srcdir)/$(base)-*.rng)) CIB_abs_xsl = $(abs_srcdir)/upgrade-1.3-0.xsl \ $(wildcard $(abs_srcdir)/upgrade-2.10-[0-2].xsl) \ $(wildcard $(abs_srcdir)/upgrade-3.10-*.xsl) MON_abs_files = $(abs_srcdir)/crm_mon.rng API_files = $(foreach base,$(API_base),$(wildcard $(srcdir)/api/$(base)-*.rng)) CIB_files = $(foreach base,$(CIB_base),$(wildcard $(srcdir)/$(base).rng $(srcdir)/$(base)-*.rng)) CIB_xsl = $(srcdir)/upgrade-1.3-0.xsl \ $(wildcard $(srcdir)/upgrade-2.10-[0-2].xsl) \ $(wildcard $(srcdir)/upgrade-3.10-*.xsl) MON_files = $(srcdir)/crm_mon.rng # Sorted lists of all schema versions API_versions = $(call numeric_versions,${API_files}) CIB_versions = $(call numeric_versions,${CIB_files}) MON_versions = $(call numeric_versions,$(wildcard $(srcdir)/api/crm_mon*.rng)) # The highest numeric schema version API_max ?= $(lastword $(API_versions)) CIB_max ?= $(lastword $(CIB_versions)) MON_max ?= $(lastword $(MON_versions)) # Build tree locations of static schema files and transforms (for VPATH builds) API_build_copies = $(foreach f,$(API_abs_files),$(subst $(abs_srcdir),$(abs_builddir),$(f))) CIB_build_copies = $(foreach f,$(CIB_abs_files) $(CIB_abs_xsl),$(subst $(abs_srcdir),$(abs_builddir),$(f))) MON_build_copies = $(foreach f,$(MON_abs_files),$(subst $(abs_srcdir),$(abs_builddir),$(f))) # Dynamically generated schema files API_generated = api/api-result.rng $(foreach base,$(API_versions),api/api-result-$(base).rng) CIB_generated = pacemaker.rng \ $(foreach base,$(CIB_versions),pacemaker-$(base).rng) \ versions.rng MON_generated = crm_mon.rng CIB_version_pairs = $(call version_pairs,${CIB_versions}) CIB_version_pairs_cnt = $(words ${CIB_version_pairs}) CIB_version_pairs_last = $(call version_pairs_last,${CIB_version_pairs_cnt},${CIB_version_pairs}) dist_API_DATA = $(API_files) dist_CIB_DATA = $(CIB_files) \ $(CIB_xsl) nodist_API_DATA = $(API_generated) nodist_CIB_DATA = $(CIB_generated) nodist_MON_DATA = $(MON_generated) EXTRA_DIST = README.md \ cibtr-2.rng \ context-of.xsl \ rng-helper \ ocf-meta2man.xsl \ upgrade-detail.xsl \ xslt_cibtr-2.rng .PHONY: cib-versions cib-versions: @echo "Max: $(CIB_max)" @echo "Available: $(CIB_versions)" .PHONY: api-versions api-versions: @echo "Max: $(API_max)" @echo "Available: $(API_versions)" # Dynamically generated top-level API schema api/api-result.rng: api/api-result-$(API_max).rng $(AM_V_at)$(MKDIR_P) api # might not exist in VPATH build $(AM_V_SCHEMA)cp $(top_builddir)/xml/$< $@ api/api-result-%.rng: $(API_build_copies) rng-helper Makefile.am $(AM_V_SCHEMA)$(builddir)/rng-helper build_api_rng "$@" "$*" \ $(API_request_base) crm_mon.rng: api/crm_mon-$(MON_max).rng $(AM_V_at)echo '' > $@ $(AM_V_at)echo '> $@ $(AM_V_at)echo ' datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_SCHEMA)echo '' >> $@ # Dynamically generated top-level CIB schema pacemaker.rng: pacemaker-$(CIB_max).rng $(AM_V_SCHEMA)cp $(top_builddir)/xml/$< $@ pacemaker-%.rng: $(CIB_build_copies) rng-helper Makefile.am $(AM_V_SCHEMA)$(builddir)/rng-helper build_cib_rng "$@" "$*" \ $(CIB_cfg_base) # Dynamically generated CIB schema listing all pacemaker versions # # @COMPAT "none" is deprecated since 2.1.8 versions.rng: pacemaker-$(CIB_max).rng Makefile.am $(AM_V_at)echo '' > $@ $(AM_V_at)echo '' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' none' >> $@ $(AM_V_at)for rng in $(CIB_versions); do echo " pacemaker-$$rng" >> $@; done $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_SCHEMA)echo '' >> $@ schemas: @if [ -z "$$SCHEMAS" ]; then \ ls *.rng; \ ls *.rng | sort -V | awk 'gsub("-[0-9.]+.rng", ""){if (last != $$0) {print; last=$$0} }'; \ printf "\nusage: make schemas SCHEMAS=\"\" [NEW_VERSION=\"\"]\n \ \nNot specifying NEW_VERSION will increase the last field of the newest version of the schema(s).\n"; \ else \ if [ -z "$$NEW_VERSION" ]; then \ OLD_VERSION=$$(ls *[0-9].rng | awk -F'-' 'gsub(".rng$$", "") {print $$NF}' | sort -Vu | tail -1); \ lf=$$(echo "$$OLD_VERSION" | awk -F"." '{print $$NF}'); \ ! echo "$$lf" | grep -q "^[[:digit:]]\+$$" && echo "Unable to detect version. Use NEW_VERSION= to specify version." && exit 1; \ lf=$$((lf+1)); \ NEW_VERSION=$$(echo "$$OLD_VERSION" | sed "s/[0-9]\+$$/$$lf/"); \ fi; \ for schema in $$SCHEMAS; do \ old_schema=$$(ls $$schema-[0-9]*.rng | sort -V | tail -1); \ new_schema=$$schema-$$NEW_VERSION.rng; \ echo "Copying $$old_schema to $$new_schema"; \ cp -n "$$old_schema" "$$new_schema"; \ done \ fi .PHONY: diff diff: rng-helper @echo "# Comparing changes in + since $(CIB_max)" @$(builddir)/rng-helper diff ${CIB_version_pairs_last} .PHONY: fulldiff fulldiff: rng-helper @echo "# Comparing all changes across all the subsequent increments" @$(builddir)/rng-helper diff ${CIB_version_pairs} CLEANFILES = $(API_generated) \ $(CIB_generated) \ $(MON_generated) # Remove pacemaker schema files generated by *any* source version. This allows # "make -C xml clean" to have the desired effect when checking out an earlier # revision in a source tree. .PHONY: clean-local clean-local: if [ "x$(srcdir)" != "x$(builddir)" ]; then \ rm -f $(API_build_copies) $(CIB_build_copies) $(MON_build_copies); \ fi rm -f $(builddir)/pacemaker-[0-9]*.[0-9]*.rng # Enable ability to use $@ in prerequisite .SECONDEXPANSION: # For VPATH builds, copy the static schema files into the build tree $(API_build_copies) $(CIB_build_copies) $(MON_build_copies): $$(subst $(abs_builddir),$(srcdir),$$(@)) $(AM_V_GEN)if [ "x$(srcdir)" != "x$(builddir)" ]; then \ $(MKDIR_P) "$(dir $(@))"; \ cp "$(<)" "$(@)"; \ fi diff --git a/xml/api/cibsecret-2.40.rng b/xml/api/cibsecret-2.40.rng new file mode 100644 index 0000000000..6f5918bf78 --- /dev/null +++ b/xml/api/cibsecret-2.40.rng @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + +