Page MenuHomeClusterLabs Projects

No OneTemporary

diff --git a/INSTALL.md b/INSTALL.md
index 1845e42d20..e03c594918 100644
--- a/INSTALL.md
+++ b/INSTALL.md
@@ -1,83 +1,83 @@
# How to Install Pacemaker
## Build Dependencies
| Version | Fedora-based | Suse-based | Debian-based |
|:---------------:|:------------------:|:------------------:|:--------------:|
| 1.13 or later | automake | automake | automake |
| 2.64 or later | autoconf | autoconf | autoconf |
| | libtool | libtool | libtool |
| | libtool-ltdl-devel | | libltdl-dev |
| | libuuid-devel | libuuid-devel | uuid-dev |
| 0.27 or later | pkgconfig | pkgconfig | pkg-config |
| 2.42.0 or later | glib2-devel | glib2-devel | libglib2.0-dev |
-| | libxml2-devel | libxml2-devel | libxml2-dev |
+| 2.6.0 or later | libxml2-devel | libxml2-devel | libxml2-dev |
| | libxslt-devel | libxslt-devel | libxslt-dev |
| | bzip2-devel | libbz2-devel | libbz2-dev |
| 0.17.0 or later | libqb-devel | libqb-devel | libqb-dev |
| 3.4 or later | python3 | python3 | python3 |
| 0.18 or later | gettext-devel | gettext-tools | gettext |
| 0.18 or later | | | autopoint |
Also:
* make must be GNU (or compatible) (setting MAKE=gmake might also work but is
untested)
* GNU (or compatible) getopt must be somewhere on the PATH
### Cluster Stack Dependencies
*Only corosync is currently supported*
| Version | Fedora-based | Suse-based | Debian-based |
|:---------------:|:------------------:|:------------------:|:--------------:|
| 2.0.0 or later | corosynclib | libcorosync | corosync |
| 2.0.0 or later | corosynclib-devel | libcorosync-devel | |
| | | | libcfg-dev |
| | | | libcpg-dev |
| | | | libcmap-dev |
| | | | libquorum-dev |
### Optional Build Dependencies
| Feature Enabled | Version | Fedora-based | Suse-based | Debian-based |
|:-----------------------------------------------:|:--------------:|:-----------------------:|:-----------------------:|:-----------------------:|
| Pacemaker Remote and encrypted remote CIB admin | 2.12.0 or later| gnutls-devel | libgnutls-devel | libgnutls-dev |
| encrypted remote CIB admin | | pam-devel | pam-devel | libpam0g-dev |
| interactive crm_mon | | ncurses-devel | ncurses-devel | ncurses-dev |
| systemd support | | systemd-devel | systemd-devel | libsystemd-dev |
| systemd/upstart resource support | | dbus-devel | dbus-devel | libdbus-1-dev |
| Linux-HA style fencing agents | | cluster-glue-libs-devel | libglue-devel | cluster-glue-dev |
| documentation | | asciidoc or asciidoctor | asciidoc or asciidoctor | asciidoc or asciidoctor |
| documentation | | help2man | help2man | help2man |
| documentation | | inkscape | inkscape | inkscape |
| documentation | | docbook-style-xsl | docbook-xsl-stylesheets | docbook-xsl |
| documentation | | python3-sphinx | python3-sphinx | python3-sphinx |
| documentation (PDF) | | latexmk texlive texlive-capt-of texlive-collection-xetex texlive-fncychap texlive-framed texlive-multirow texlive-needspace texlive-tabulary texlive-titlesec texlive-threeparttable texlive-upquote texlive-wrapfig texlive-xetex | texlive texlive-latex | texlive texlive-latex-extra |
| annotated source code as HTML via "make global" | | global | global | global |
| RPM packages via "make rpm" | 4.11 or later | rpm | rpm | (n/a) |
| unit tests | 1.1.0 or later | libcmocka-devel | libcmocka-devel | libcmocka-dev |
## Optional testing dependencies
* procps and psmisc (if running cts-exec, cts-fencing, or CTS)
* valgrind (if running CTS valgrind tests)
* python3-systemd (if using CTS on cluster nodes running systemd)
* nmap (if not specifying an IP address base)
* oprofile (if running CTS profiling tests)
* dlm (to log DLM debugging info after CTS tests)
* xmllint (to validate tool output in cts-cli)
## Simple install
$ make && sudo make install
If GNU make is not your default make, use "gmake" instead.
## Detailed install
First, browse the build options that are available:
$ ./autogen.sh
$ ./configure --help
Re-run ./configure with any options you want, then proceed with the simple
method.
diff --git a/configure.ac b/configure.ac
index 4c8d3d7e70..6bff02e6a1 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,2235 +1,2235 @@
dnl
dnl autoconf for Pacemaker
dnl
dnl Copyright 2009-2023 the Pacemaker project contributors
dnl
dnl The version control history for this file may have further details.
dnl
dnl This source code is licensed under the GNU General Public License version 2
dnl or later (GPLv2+) WITHOUT ANY WARRANTY.
dnl ==============================================
dnl Bootstrap autotools
dnl ==============================================
# Require a minimum version of autoconf itself
AC_PREREQ(2.64)
dnl AC_CONFIG_MACRO_DIR is deprecated as of autoconf 2.70 (2020-12-08).
dnl Once we can require that version, we can simplify this, and no longer
dnl need ACLOCAL_AMFLAGS in Makefile.am.
m4_ifdef([AC_CONFIG_MACRO_DIRS],
[AC_CONFIG_MACRO_DIRS([m4])],
[AC_CONFIG_MACRO_DIR([m4])])
m4_include([m4/version.m4])
AC_INIT([pacemaker], VERSION_NUMBER, [users@clusterlabs.org], [pacemaker],
PCMK_URL)
LT_CONFIG_LTDL_DIR([libltdl])
AC_CONFIG_AUX_DIR([libltdl/config])
dnl Where #defines that autoconf makes (e.g. HAVE_whatever) go
dnl
dnl include/config.h
dnl - Internal API
dnl - Contains all defines
dnl - include/config.h.in is generated automatically by autoheader
dnl - Not to be included in any header files except crm_internal.h
dnl (which is also not to be included in any other header files)
dnl
dnl include/crm_config.h
dnl - External API
dnl - Contains a subset of defines
dnl - include/crm_config.h.in is manually edited to select the subset
dnl - Should not include HAVE_* defines
dnl - Safe to include anywhere
AC_CONFIG_HEADERS([include/config.h include/crm_config.h])
dnl 1.13: minimum automake version required
dnl foreign: don't require GNU-standard top-level files
dnl tar-ustar: use (older) POSIX variant of generated tar rather than v7
dnl subdir-objects: keep .o's with their .c's (no-op in 2.0+)
AM_INIT_AUTOMAKE([1.13 foreign tar-ustar subdir-objects])
dnl Require minimum version of pkg-config
PKG_PROG_PKG_CONFIG(0.27)
AS_IF([test x"${PKG_CONFIG}" != x""], [],
[AC_MSG_FAILURE([Could not find required build tool pkg-config (0.27 or later)])])
PKG_INSTALLDIR
PKG_NOARCH_INSTALLDIR
dnl ==============================================
dnl Compiler checks and helpers
dnl ==============================================
dnl A particular compiler can be forced by setting the CC environment variable
AC_PROG_CC
dnl C++ is needed only to run maintainer utilities, not to build
AC_PROG_CXX
dnl Use at least C99 if possible (automatic for autoconf >= 2.70)
m4_version_prereq([2.70], [:], [AC_PROG_CC_STDC])
# cc_supports_flag <compiler-flag>
# Return success if the C compiler supports the given flag
cc_supports_flag() {
local CFLAGS="-Werror $@"
AC_MSG_CHECKING([whether $CC supports $@])
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ ]], [[ ]])],
[RC=0; AC_MSG_RESULT([yes])],
[RC=1; AC_MSG_RESULT([no])])
return $RC
}
# cc_temp_flags <compiler-flags>
# Use the given flags for subsequent C compilation. These can be reverted to
# what was used previously with cc_restore_flags. This allows certain tests to
# use specific flags without affecting anything else.
cc_temp_flags() {
ac_save_CFLAGS="$CFLAGS"
CFLAGS="$*"
}
# cc_restore_flags
# Restore C compiler flags to what they were before the last cc_temp_flags
# call.
cc_restore_flags() {
CFLAGS=$ac_save_CFLAGS
}
# Check for fatal warning support
AS_IF([test $enable_fatal_warnings -ne $DISABLED dnl
&& test x"$GCC" = x"yes" && cc_supports_flag -Werror],
[WERROR="-Werror"],
[
WERROR=""
AS_CASE([$enable_fatal_warnings],
[$REQUIRED], [AC_MSG_ERROR([Compiler does not support fatal warnings])],
[$OPTIONAL], [enable_fatal_warnings=$DISABLED])
])
dnl We use md5.c from gnulib, which has its own m4 macros. Per its docs:
dnl "The macro gl_EARLY must be called as soon as possible after verifying that
dnl the C compiler is working. ... The core part of the gnulib checks are done
dnl by the macro gl_INIT." In addition, prevent gnulib from introducing OpenSSL
dnl as a dependency.
gl_EARLY
gl_SET_CRYPTO_CHECK_DEFAULT([no])
gl_INIT
AC_CHECK_SIZEOF(long)
dnl ==============================================
dnl Linker checks
dnl ==============================================
# Check whether linker supports --enable-new-dtags to use RUNPATH instead of
# RPATH. It is necessary to do this before libtool does linker detection.
# See also: https://github.com/kronosnet/kronosnet/issues/107
AX_CHECK_LINK_FLAG([-Wl,--enable-new-dtags],
[AM_LDFLAGS=-Wl,--enable-new-dtags],
[AC_MSG_ERROR(["Linker support for --enable-new-dtags is required"])])
AC_SUBST([AM_LDFLAGS])
saved_LDFLAGS="$LDFLAGS"
LDFLAGS="$AM_LDFLAGS $LDFLAGS"
LT_INIT([dlopen])
LDFLAGS="$saved_LDFLAGS"
LTDL_INIT([convenience])
dnl ==============================================
dnl Define configure options
dnl ==============================================
# yes_no_try <user-response> <default>
# Map a yes/no/try user selection to $REQUIRED for yes, $DISABLED for no, and
# $OPTIONAL for try.
DISABLED=0
REQUIRED=1
OPTIONAL=2
yes_no_try() {
local value
AS_IF([test x"$1" = x""], [value="$2"], [value="$1"])
AS_CASE(["`echo "$value" | tr '[A-Z]' '[a-z]'`"],
[0|no|false|disable], [return $DISABLED],
[1|yes|true|enable], [return $REQUIRED],
[try|check], [return $OPTIONAL]
)
AC_MSG_ERROR([Invalid option value "$value"])
}
#
# Fix the defaults of certain built-in variables so they can be used in the
# defaults for our custom arguments
#
AC_MSG_NOTICE([Sanitizing prefix: ${prefix}])
AS_IF([test x"$prefix" = x"NONE"],
[
prefix=/usr
dnl Fix default variables - "prefix" variable if not specified
AS_IF([test x"$localstatedir" = x"\${prefix}/var"],
[localstatedir="/var"])
AS_IF([test x"$sysconfdir" = x"\${prefix}/etc"],
[sysconfdir="/etc"])
])
AC_MSG_NOTICE([Sanitizing exec_prefix: ${exec_prefix}])
AS_CASE([$exec_prefix],
[prefix|NONE], [exec_prefix=$prefix])
AC_MSG_NOTICE([Sanitizing libdir: ${libdir}])
AS_CASE([$libdir],
[prefix|NONE], [
AC_MSG_CHECKING([which lib directory to use])
for aDir in lib64 lib
do
trydir="${exec_prefix}/${aDir}"
AS_IF([test -d ${trydir}],
[
libdir=${trydir}
break
])
done
AC_MSG_RESULT([$libdir])
])
# Start a list of optional features this build supports
PCMK_FEATURES=""
dnl This section should include only the definition of configure script
dnl options and determining their values. Processing should be done later when
dnl possible, other than what's needed to determine values and defaults.
dnl Per the autoconf docs, --enable-*/--disable-* options should control
dnl features inherent to Pacemaker, while --with-*/--without-* options should
dnl control the use of external software. However, --enable-*/--disable-* may
dnl implicitly require additional external dependencies, and
dnl --with-*/--without-* may implicitly enable or disable features, so the
dnl line is blurry.
dnl
dnl We also use --with-* options for custom file, directory, and path
dnl locations, since autoconf does not provide an option type for those.
dnl --enable-* options: build process
AC_ARG_ENABLE([quiet],
[AS_HELP_STRING([--enable-quiet],
[suppress make output unless there is an error @<:@no@:>@])]
)
yes_no_try "$enable_quiet" "no"
enable_quiet=$?
AC_ARG_ENABLE([fatal-warnings],
[AS_HELP_STRING([--enable-fatal-warnings],
[enable pedantic and fatal warnings for gcc @<:@try@:>@])],
)
yes_no_try "$enable_fatal_warnings" "try"
enable_fatal_warnings=$?
AC_ARG_ENABLE([hardening],
[AS_HELP_STRING([--enable-hardening],
[harden the resulting executables/libraries @<:@try@:>@])]
)
yes_no_try "$enable_hardening" "try"
enable_hardening=$?
dnl --enable-* options: features
AC_ARG_ENABLE([systemd],
[AS_HELP_STRING([--enable-systemd],
[enable support for managing resources via systemd @<:@try@:>@])]
)
yes_no_try "$enable_systemd" "try"
enable_systemd=$?
AC_ARG_ENABLE([upstart],
[AS_HELP_STRING([--enable-upstart],
[enable support for managing resources via Upstart (deprecated) @<:@try@:>@])]
)
yes_no_try "$enable_upstart" "try"
enable_upstart=$?
dnl --enable-* options: features inherent to Pacemaker
AC_ARG_ENABLE([compat-2.0],
[AS_HELP_STRING([--enable-compat-2.0], m4_normalize([
preserve certain output as it was in 2.0; this option will be
available only for the lifetime of the 2.1 series @<:@no@:>@]))]
)
yes_no_try "$enable_compat_2_0" "no"
enable_compat_2_0=$?
# Add an option to create symlinks at the pre-2.0.0 daemon name locations, so
# that users and tools can continue to invoke those names directly (e.g., for
# meta-data). This option will be removed in a future release.
AC_ARG_ENABLE([legacy-links],
[AS_HELP_STRING([--enable-legacy-links],
[add symlinks for old daemon names (deprecated) @<:@no@:>@])]
)
yes_no_try "$enable_legacy_links" "no"
enable_legacy_links=$?
# AM_GNU_GETTEXT calls AM_NLS which defines the nls option, but it defaults
# to enabled. We override the definition of AM_NLS to flip the default and mark
# it as experimental in the help text.
AC_DEFUN([AM_NLS],
[AC_MSG_CHECKING([whether NLS is requested])
AC_ARG_ENABLE([nls],
[AS_HELP_STRING([--enable-nls],
[use Native Language Support (experimental)])],
USE_NLS=$enableval, USE_NLS=no)
AC_MSG_RESULT([$USE_NLS])
AC_SUBST([USE_NLS])]
)
AM_GNU_GETTEXT([external])
AM_GNU_GETTEXT_VERSION([0.18])
dnl --with-* options: external software support, and custom locations
dnl This argument is defined via an M4 macro so default can be a variable
AC_DEFUN([VERSION_ARG],
[AC_ARG_WITH([version],
[AS_HELP_STRING([--with-version=VERSION],
[override package version @<:@$1@:>@])],
[ PACEMAKER_VERSION="$withval" ],
[ PACEMAKER_VERSION="$PACKAGE_VERSION" ])]
)
VERSION_ARG(VERSION_NUMBER)
CRM_DAEMON_USER=""
AC_ARG_WITH([daemon-user],
[AS_HELP_STRING([--with-daemon-user=USER],
[user to run unprivileged Pacemaker daemons as (advanced option: changing this may break other cluster components unless similarly configured) @<:@hacluster@:>@])],
[ CRM_DAEMON_USER="$withval" ]
)
AS_IF([test x"${CRM_DAEMON_USER}" = x""],
[CRM_DAEMON_USER="hacluster"])
CRM_DAEMON_GROUP=""
AC_ARG_WITH([daemon-group],
[AS_HELP_STRING([--with-daemon-group=GROUP],
[group to run unprivileged Pacemaker daemons as (advanced option: changing this may break other cluster components unless similarly configured) @<:@haclient@:>@])],
[ CRM_DAEMON_GROUP="$withval" ]
)
AS_IF([test x"${CRM_DAEMON_GROUP}" = x""],
[CRM_DAEMON_GROUP="haclient"])
BUG_URL=""
AC_ARG_WITH([bug-url],
[AS_HELP_STRING([--with-bug-url=DIR], m4_normalize([
address where users should submit bug reports
@<:@https://bugs.clusterlabs.org/enter_bug.cgi?product=Pacemaker@:>@]))],
[ BUG_URL="$withval" ]
)
AS_IF([test x"${BUG_URL}" = x""],
[BUG_URL="https://bugs.clusterlabs.org/enter_bug.cgi?product=Pacemaker"])
dnl --with-* options: features
AC_ARG_WITH([cibsecrets],
[AS_HELP_STRING([--with-cibsecrets],
[support separate file for CIB secrets @<:@no@:>@])]
)
yes_no_try "$with_cibsecrets" "no"
with_cibsecrets=$?
AC_ARG_WITH([gnutls],
[AS_HELP_STRING([--with-gnutls],
[support Pacemaker Remote and remote-tls-port using GnuTLS @<:@try@:>@])]
)
yes_no_try "$with_gnutls" "try"
with_gnutls=$?
PCMK_GNUTLS_PRIORITIES="NORMAL"
AC_ARG_WITH([gnutls-priorities],
[AS_HELP_STRING([--with-gnutls-priorities],
[default GnuTLS cipher priorities @<:@NORMAL@:>@])],
[ test x"$withval" = x"no" || PCMK_GNUTLS_PRIORITIES="$withval" ]
)
AC_ARG_WITH([concurrent-fencing-default],
[AS_HELP_STRING([--with-concurrent-fencing-default],
[default value for concurrent-fencing cluster option @<:@false@:>@])],
)
AS_CASE([$with_concurrent_fencing_default],
[""], [with_concurrent_fencing_default="false"],
[false], [],
[true], [PCMK_FEATURES="$PCMK_FEATURES default-concurrent-fencing"],
[AC_MSG_ERROR([Invalid value "$with_concurrent_fencing_default" for --with-concurrent-fencing-default])]
)
AC_ARG_WITH([sbd-sync-default],
[AS_HELP_STRING([--with-sbd-sync-default], m4_normalize([
default value used by sbd if SBD_SYNC_RESOURCE_STARTUP
environment variable is not set @<:@false@:>@]))],
)
AS_CASE([$with_sbd_sync_default],
[""], [with_sbd_sync_default=false],
[false], [],
[true], [PCMK_FEATURES="$PCMK_FEATURES default-sbd-sync"],
[AC_MSG_ERROR([Invalid value "$with_sbd_sync_default" for --with-sbd-sync-default])]
)
AC_ARG_WITH([resource-stickiness-default],
[AS_HELP_STRING([--with-resource-stickiness-default],
[If positive, value to add to new CIBs as explicit resource default for resource-stickiness @<:@0@:>@])],
)
errmsg="Invalid value \"$with_resource_stickiness_default\" for --with-resource-stickiness-default"
AS_CASE([$with_resource_stickiness_default],
[0|""], [with_resource_stickiness_default="0"],
[*[[!0-9]]*], [AC_MSG_ERROR([$errmsg])],
[PCMK_FEATURES="$PCMK_FEATURES default-resource-stickiness"]
)
AC_ARG_WITH([corosync],
[AS_HELP_STRING([--with-corosync],
[support the Corosync messaging and membership layer @<:@try@:>@])]
)
yes_no_try "$with_corosync" "try"
with_corosync=$?
dnl Get default from Corosync if possible
PKG_CHECK_VAR([PCMK__COROSYNC_CONF], [corosync], [corosysconfdir],
[PCMK__COROSYNC_CONF="$PCMK__COROSYNC_CONF/corosync.conf"],
[PCMK__COROSYNC_CONF="${sysconfdir}/corosync/corosync.conf"])
AC_ARG_WITH([corosync-conf],
[AS_HELP_STRING([--with-corosync-conf], m4_normalize([
location of Corosync configuration file
@<:@value from Corosync package if available otherwise
SYSCONFDIR/corosync/corosync.conf@:>@]))],
[ PCMK__COROSYNC_CONF="$withval" ]
)
AC_ARG_WITH([nagios],
[AS_HELP_STRING([--with-nagios], [support nagios resources (deprecated)])]
)
yes_no_try "$with_nagios" "try"
with_nagios=$?
dnl --with-* options: directory locations
AC_ARG_WITH([nagios-plugin-dir],
[AS_HELP_STRING([--with-nagios-plugin-dir=DIR],
[directory for nagios plugins (deprecated) @<:@LIBEXECDIR/nagios/plugins@:>@])],
[ NAGIOS_PLUGIN_DIR="$withval" ]
)
AC_ARG_WITH([nagios-metadata-dir],
[AS_HELP_STRING([--with-nagios-metadata-dir=DIR],
[directory for nagios plugins metadata (deprecated) @<:@DATADIR/nagios/plugins-metadata@:>@])],
[ NAGIOS_METADATA_DIR="$withval" ]
)
INITDIR=""
AC_ARG_WITH([initdir],
[AS_HELP_STRING([--with-initdir=DIR],
[directory for init (rc) scripts])],
[ INITDIR="$withval" ]
)
systemdsystemunitdir="${systemdsystemunitdir-}"
AC_ARG_WITH([systemdsystemunitdir],
[AS_HELP_STRING([--with-systemdsystemunitdir=DIR],
[directory for systemd unit files (advanced option: must match what systemd uses)])],
[ systemdsystemunitdir="$withval" ]
)
CONFIGDIR=""
AC_ARG_WITH([configdir],
[AS_HELP_STRING([--with-configdir=DIR],
[directory for Pacemaker configuration file @<:@SYSCONFDIR/sysconfig@:>@])],
[ CONFIGDIR="$withval" ]
)
dnl --runstatedir is available as of autoconf 2.70 (2020-12-08). When users
dnl have an older version, they can use our --with-runstatedir.
pcmk_runstatedir=""
AC_ARG_WITH([runstatedir],
[AS_HELP_STRING([--with-runstatedir=DIR],
[modifiable per-process data @<:@LOCALSTATEDIR/run@:>@ (ignored if --runstatedir is available)])],
[ pcmk_runstatedir="$withval" ]
)
CRM_LOG_DIR=""
AC_ARG_WITH([logdir],
[AS_HELP_STRING([--with-logdir=DIR],
[directory for Pacemaker log file @<:@LOCALSTATEDIR/log/pacemaker@:>@])],
[ CRM_LOG_DIR="$withval" ]
)
CRM_BUNDLE_DIR=""
AC_ARG_WITH([bundledir],
[AS_HELP_STRING([--with-bundledir=DIR],
[directory for Pacemaker bundle logs @<:@LOCALSTATEDIR/log/pacemaker/bundles@:>@])],
[ CRM_BUNDLE_DIR="$withval" ]
)
dnl Get default from resource-agents if possible. Otherwise, the default uses
dnl /usr/lib rather than libdir because it's determined by the OCF project and
dnl not Pacemaker. Even if a user wants to install Pacemaker to /usr/local or
dnl such, the OCF agents will be expected in their usual location. However, we
dnl do give the user the option to override it.
PKG_CHECK_VAR([OCF_ROOT_DIR], [resource-agents], [ocfrootdir], [],
[OCF_ROOT_DIR="/usr/lib/ocf"])
AC_ARG_WITH([ocfdir],
[AS_HELP_STRING([--with-ocfdir=DIR], m4_normalize([
OCF resource agent root directory (advanced option: changing this
may break other cluster components unless similarly configured)
@<:@value from resource-agents package if available otherwise
/usr/lib/ocf@:>@]))],
[ OCF_ROOT_DIR="$withval" ]
)
dnl Get default from resource-agents if possible
PKG_CHECK_VAR([OCF_RA_PATH], [resource-agents], [ocfrapath], [],
[OCF_RA_PATH="$OCF_ROOT_DIR/resource.d"])
AC_ARG_WITH([ocfrapath],
[AS_HELP_STRING([--with-ocfrapath=DIR], m4_normalize([
OCF resource agent directories (colon-separated) to search
@<:@value from resource-agents package if available otherwise
OCFDIR/resource.d@:>@]))],
[ OCF_RA_PATH="$withval" ]
)
OCF_RA_INSTALL_DIR="$OCF_ROOT_DIR/resource.d"
AC_ARG_WITH([ocfrainstalldir],
[AS_HELP_STRING([--with-ocfrainstalldir=DIR], m4_normalize([
OCF installation directory for Pacemakers resource agents
@<:@OCFDIR/resource.d@:>@]))],
[ OCF_RA_INSTALL_DIR="$withval" ]
)
dnl Get default from fence-agents if available
PKG_CHECK_VAR([FA_PREFIX], [fence-agents], [prefix],
[PCMK__FENCE_BINDIR="${FA_PREFIX}/sbin"],
[PCMK__FENCE_BINDIR="$sbindir"])
AC_ARG_WITH([fence-bindir],
[AS_HELP_STRING([--with-fence-bindir=DIR], m4_normalize([
directory for executable fence agents @<:@value from fence-agents
package if available otherwise SBINDIR@:>@]))],
[ PCMK__FENCE_BINDIR="$withval" ]
)
dnl --with-* options: non-production testing
AC_ARG_WITH([profiling],
[AS_HELP_STRING([--with-profiling],
[disable optimizations, for effective profiling @<:@no@:>@])]
)
yes_no_try "$with_profiling" "no"
with_profiling=$?
AC_ARG_WITH([coverage],
[AS_HELP_STRING([--with-coverage],
[disable optimizations, for effective profiling and coverage testing @<:@no@:>@])]
)
yes_no_try "$with_coverage" "no"
with_coverage=$?
AC_ARG_WITH([sanitizers],
[AS_HELP_STRING([--with-sanitizers=...,...],
[enable SANitizer build, do *NOT* use for production. Only ASAN/UBSAN/TSAN are currently supported])],
[ SANITIZERS="$withval" ],
[ SANITIZERS="" ])
dnl Environment variable options
AC_ARG_VAR([CFLAGS_HARDENED_LIB], [extra C compiler flags for hardened libraries])
AC_ARG_VAR([LDFLAGS_HARDENED_LIB], [extra linker flags for hardened libraries])
AC_ARG_VAR([CFLAGS_HARDENED_EXE], [extra C compiler flags for hardened executables])
AC_ARG_VAR([LDFLAGS_HARDENED_EXE], [extra linker flags for hardened executables])
dnl ==============================================
dnl Locate essential tools
dnl ==============================================
PATH="$PATH:/sbin:/usr/sbin:/usr/local/sbin:/usr/local/bin"
export PATH
dnl Pacemaker's executable python scripts will invoke the python specified by
dnl configure's PYTHON variable. If not specified, AM_PATH_PYTHON will check a
dnl built-in list with (unversioned) "python" having precedence. To configure
dnl Pacemaker to use a specific python interpreter version, define PYTHON
dnl when calling configure, for example: ./configure PYTHON=/usr/bin/python3.6
dnl If PYTHON was specified, ensure it is an absolute path
AS_IF([test x"${PYTHON}" != x""], [AC_PATH_PROG([PYTHON], [$PYTHON])])
dnl Require a minimum Python version
AM_PATH_PYTHON([3.4])
AC_PROG_LN_S
AC_PROG_MKDIR_P
AC_PATH_PROG([GIT], [git], [false])
dnl Bash is needed for building man pages and running regression tests.
dnl We set "BASH_PATH" because "BASH" is already an environment variable.
REQUIRE_PROG([BASH_PATH], [bash])
AC_PATH_PROGS(VALGRIND_BIN, valgrind, /usr/bin/valgrind)
AC_DEFINE_UNQUOTED(VALGRIND_BIN, "$VALGRIND_BIN", Valgrind command)
dnl ==============================================
dnl Package and schema versioning
dnl ==============================================
# Redefine PACKAGE_VERSION and VERSION according to PACEMAKER_VERSION in case
# the user used --with-version. Unfortunately, this can only affect the
# substitution variables and later uses in this file, not the config.h
# constants, so we have to be careful to use only PACEMAKER_VERSION in C code.
PACKAGE_VERSION=$PACEMAKER_VERSION
VERSION=$PACEMAKER_VERSION
AC_DEFINE_UNQUOTED(PACEMAKER_VERSION, "$VERSION",
[Version number of this Pacemaker build])
AC_MSG_CHECKING([build version])
AS_IF([test "$GIT" != "false" && test -d .git],
[
BUILD_VERSION=`"$GIT" log --pretty="format:%h" -n 1`
AC_MSG_RESULT([$BUILD_VERSION (git hash)])
],
[
# The current directory name make a reasonable default
# Most generated archives will include the hash or tag
BASE=`basename $PWD`
BUILD_VERSION=`echo $BASE | sed s:.*[[Pp]]acemaker-::`
AC_MSG_RESULT([$BUILD_VERSION (directory name)])
])
AC_DEFINE_UNQUOTED(BUILD_VERSION, "$BUILD_VERSION", Build version)
AC_SUBST(BUILD_VERSION)
# schema_files <schema-dir>
# List all manually edited RNG schemas (as opposed to auto-generated via make)
# in the given directory. Use git if available to list managed RNGs, in case
# there are leftover schema files from an earlier build of a different
# version. Otherwise, check all RNGs.
schema_files() {
local files="$("$GIT" ls-files "$1"/*.rng 2>/dev/null)"
AS_IF([test x"$files" = x""],
[
files="$(ls -1 "$1"/*.rng | grep -E -v \
'/(pacemaker|api-result|crm_mon|versions)[^/]*\.rng')"
])
echo "$files"
}
# latest_schema_version <schema-dir>
# Determine highest RNG version in the given schema directory.
latest_schema_version() {
schema_files "$1" | sed -n -e 's/^.*-\([[0-9]][[0-9.]]*\).rng$/\1/p' dnl
| sort -V | tail -1
}
# schemas_for_make <schema-dir>
# Like schema_files, but suitable for use in make variables.
schemas_for_make() {
local file
for file in $(schema_files "$1"); do
AS_ECHO_N(["\$(top_srcdir)/$file "])
done
}
# Detect highest API schema version
API_VERSION=$(latest_schema_version "xml/api")
AC_DEFINE_UNQUOTED([PCMK__API_VERSION], ["$API_VERSION"],
[Highest API schema version])
# Detect highest CIB schema version
CIB_VERSION=$(latest_schema_version "xml")
AC_SUBST(CIB_VERSION)
# Re-run configure at next make if schema files change, to re-detect versions
cib_schemas="$(schemas_for_make "xml")"
api_schemas="$(schemas_for_make "xml/api")"
CONFIG_STATUS_DEPENDENCIES="$cib_schemas $api_schemas"
AC_SUBST(CONFIG_STATUS_DEPENDENCIES)
dnl ==============================================
dnl Process simple options
dnl ==============================================
AS_IF([test $enable_compat_2_0 -ne $DISABLED],
[
AC_DEFINE_UNQUOTED([PCMK__COMPAT_2_0], [1],
[Keep certain output compatible with 2.0 release series])
PCMK_FEATURES="$PCMK_FEATURES compat-2.0"
]
)
AM_CONDITIONAL([BUILD_LEGACY_LINKS], [test $enable_legacy_links -ne $DISABLED])
AS_IF([test x"$enable_nls" = x"yes"], [PCMK_FEATURES="$PCMK_FEATURES nls"])
AC_DEFINE_UNQUOTED([PCMK__CONCURRENT_FENCING_DEFAULT],
["$with_concurrent_fencing_default"],
[Default value for concurrent-fencing cluster option])
AC_DEFINE_UNQUOTED([PCMK__SBD_SYNC_DEFAULT],
[$with_sbd_sync_default],
[Default value for SBD_SYNC_RESOURCE_STARTUP environment variable])
AC_DEFINE_UNQUOTED([PCMK__RESOURCE_STICKINESS_DEFAULT],
[$with_resource_stickiness_default],
[Default value for resource-stickiness resource meta-attribute])
AS_IF([test x"${PCMK_GNUTLS_PRIORITIES}" != x""], [],
[AC_MSG_ERROR([--with-gnutls-priorities value must not be empty])])
AC_DEFINE_UNQUOTED([PCMK_GNUTLS_PRIORITIES], ["$PCMK_GNUTLS_PRIORITIES"],
[GnuTLS cipher priorities])
AC_SUBST(PCMK_GNUTLS_PRIORITIES)
AC_SUBST(BUG_URL)
AC_DEFINE_UNQUOTED([PCMK__BUG_URL], ["$BUG_URL"],
[Where bugs should be reported])
AC_DEFINE_UNQUOTED([CRM_DAEMON_USER], ["$CRM_DAEMON_USER"],
[User to run Pacemaker daemons as])
AC_SUBST(CRM_DAEMON_USER)
AC_DEFINE_UNQUOTED([CRM_DAEMON_GROUP], ["$CRM_DAEMON_GROUP"],
[Group to run Pacemaker daemons as])
AC_SUBST(CRM_DAEMON_GROUP)
dnl ==============================================
dnl Process file paths
dnl ==============================================
# expand_path_option <path-variable-name> [<default>]
# Given the name of a file path variable, expand any variable references
# inside it, use the specified default if it is not specified, and ensure it
# is a full path.
expand_path_option() {
# The first argument is the variable *name* (not value)
ac_path_varname="$1"
# Get the original value of the variable
ac_path_value=$(eval echo "\${${ac_path_varname}}")
# Expand any literal variable expressions in the value so that we don't
# end up with something like '${prefix}' in #defines etc.
#
# Autoconf deliberately leaves values unexpanded to allow overriding
# the configure script choices in make commands (for example,
# "make exec_prefix=/foo install"). No longer being able to do this seems
# like no great loss.
eval ac_path_value=$(eval echo "${ac_path_value}")
# Use (expanded) default if necessary
AS_IF([test x"${ac_path_value}" = x""],
[eval ac_path_value=$(eval echo "$2")])
# Require a full path
AS_CASE(["$ac_path_value"],
[/*], [eval ${ac_path_varname}="$ac_path_value"],
[*], [AC_MSG_ERROR([$ac_path_varname value "$ac_path_value" is not a full path])]
)
}
AC_MSG_NOTICE([Sanitizing INITDIR: ${INITDIR}])
AS_CASE([$INITDIR],
[prefix], [INITDIR=$prefix],
[""], [
AC_MSG_CHECKING([which init (rc) directory to use])
for initdir in /etc/init.d /etc/rc.d/init.d /sbin/init.d \
/usr/local/etc/rc.d /etc/rc.d
do
AS_IF([test -d $initdir],
[
INITDIR=$initdir
break
])
done
AC_MSG_RESULT([$INITDIR])
])
AC_SUBST(INITDIR)
dnl Expand values of autoconf-provided directory options
expand_path_option prefix
expand_path_option exec_prefix
expand_path_option bindir
expand_path_option sbindir
expand_path_option libexecdir
expand_path_option datarootdir
expand_path_option datadir
expand_path_option sysconfdir
expand_path_option sharedstatedir
expand_path_option localstatedir
expand_path_option libdir
expand_path_option includedir
expand_path_option oldincludedir
expand_path_option infodir
expand_path_option mandir
AC_DEFUN([AC_DATAROOTDIR_CHECKED])
dnl Expand values of custom directory options
expand_path_option localedir "${datadir}/locale"
AC_DEFINE_UNQUOTED([PCMK__LOCALE_DIR],["$localedir"],
[Base directory for message catalogs])
AS_IF([test x"${runstatedir}" = x""], [runstatedir="${pcmk_runstatedir}"])
expand_path_option runstatedir "${localstatedir}/run"
AC_DEFINE_UNQUOTED([PCMK_RUN_DIR], ["$runstatedir"],
[Location for modifiable per-process data])
AC_SUBST(runstatedir)
expand_path_option INITDIR
AC_DEFINE_UNQUOTED([PCMK__LSB_INIT_DIR], ["$INITDIR"],
[Location for LSB init scripts])
expand_path_option docdir "${datadir}/doc/${PACKAGE}-${VERSION}"
AC_SUBST(docdir)
expand_path_option CONFIGDIR "${sysconfdir}/sysconfig"
AC_SUBST(CONFIGDIR)
expand_path_option PCMK__COROSYNC_CONF "${sysconfdir}/corosync/corosync.conf"
AC_SUBST(PCMK__COROSYNC_CONF)
expand_path_option CRM_LOG_DIR "${localstatedir}/log/pacemaker"
AC_DEFINE_UNQUOTED([CRM_LOG_DIR], ["$CRM_LOG_DIR"],
[Location for Pacemaker log file])
AC_SUBST(CRM_LOG_DIR)
expand_path_option CRM_BUNDLE_DIR "${localstatedir}/log/pacemaker/bundles"
AC_DEFINE_UNQUOTED([CRM_BUNDLE_DIR], ["$CRM_BUNDLE_DIR"],
[Location for Pacemaker bundle logs])
AC_SUBST(CRM_BUNDLE_DIR)
expand_path_option PCMK__FENCE_BINDIR
AC_SUBST(PCMK__FENCE_BINDIR)
AC_DEFINE_UNQUOTED([PCMK__FENCE_BINDIR], ["$PCMK__FENCE_BINDIR"],
[Location for executable fence agents])
expand_path_option OCF_ROOT_DIR
AC_SUBST(OCF_ROOT_DIR)
AC_DEFINE_UNQUOTED([OCF_ROOT_DIR], ["$OCF_ROOT_DIR"],
[OCF root directory for resource agents and libraries])
expand_path_option OCF_RA_PATH
AC_SUBST(OCF_RA_PATH)
AC_DEFINE_UNQUOTED([OCF_RA_PATH], ["$OCF_RA_PATH"],
[OCF directories to search for resource agents ])
expand_path_option OCF_RA_INSTALL_DIR
AC_SUBST(OCF_RA_INSTALL_DIR)
# Derived paths
CRM_SCHEMA_DIRECTORY="${datadir}/pacemaker"
AC_DEFINE_UNQUOTED([CRM_SCHEMA_DIRECTORY], ["$CRM_SCHEMA_DIRECTORY"],
[Location for the Pacemaker Relax-NG Schema])
AC_SUBST(CRM_SCHEMA_DIRECTORY)
CRM_CORE_DIR="${localstatedir}/lib/pacemaker/cores"
AC_DEFINE_UNQUOTED([CRM_CORE_DIR], ["$CRM_CORE_DIR"],
[Directory Pacemaker daemons should change to (without systemd, core files will go here)])
AC_SUBST(CRM_CORE_DIR)
CRM_PACEMAKER_DIR="${localstatedir}/lib/pacemaker"
AC_DEFINE_UNQUOTED([CRM_PACEMAKER_DIR], ["$CRM_PACEMAKER_DIR"],
[Location to store directory produced by Pacemaker daemons])
AC_SUBST(CRM_PACEMAKER_DIR)
CRM_BLACKBOX_DIR="${localstatedir}/lib/pacemaker/blackbox"
AC_DEFINE_UNQUOTED([CRM_BLACKBOX_DIR], ["$CRM_BLACKBOX_DIR"],
[Where to keep blackbox dumps])
AC_SUBST(CRM_BLACKBOX_DIR)
PE_STATE_DIR="${localstatedir}/lib/pacemaker/pengine"
AC_DEFINE_UNQUOTED([PE_STATE_DIR], ["$PE_STATE_DIR"],
[Where to keep scheduler outputs])
AC_SUBST(PE_STATE_DIR)
CRM_CONFIG_DIR="${localstatedir}/lib/pacemaker/cib"
AC_DEFINE_UNQUOTED([CRM_CONFIG_DIR], ["$CRM_CONFIG_DIR"],
[Where to keep configuration files])
AC_SUBST(CRM_CONFIG_DIR)
CRM_DAEMON_DIR="${libexecdir}/pacemaker"
AC_DEFINE_UNQUOTED([CRM_DAEMON_DIR], ["$CRM_DAEMON_DIR"],
[Location for Pacemaker daemons])
AC_SUBST(CRM_DAEMON_DIR)
CRM_STATE_DIR="${runstatedir}/crm"
AC_DEFINE_UNQUOTED([CRM_STATE_DIR], ["$CRM_STATE_DIR"],
[Where to keep state files and sockets])
AC_SUBST(CRM_STATE_DIR)
CRM_RSCTMP_DIR="${runstatedir}/resource-agents"
AC_DEFINE_UNQUOTED([CRM_RSCTMP_DIR], ["$CRM_RSCTMP_DIR"],
[Where resource agents should keep state files])
AC_SUBST(CRM_RSCTMP_DIR)
PACEMAKER_CONFIG_DIR="${sysconfdir}/pacemaker"
AC_DEFINE_UNQUOTED([PACEMAKER_CONFIG_DIR], ["$PACEMAKER_CONFIG_DIR"],
[Where to keep configuration files like authkey])
AC_SUBST(PACEMAKER_CONFIG_DIR)
AC_DEFINE_UNQUOTED([SBIN_DIR], ["$sbindir"], [Location for system binaries])
# Warn about any directories that don't exist (which may be OK)
for j in prefix exec_prefix bindir sbindir libexecdir datadir sysconfdir \
sharedstatedir localstatedir libdir includedir oldincludedir infodir \
mandir INITDIR docdir CONFIGDIR localedir
do
dirname=`eval echo '${'${j}'}'`
AS_IF([test ! -d "$dirname"],
[AC_MSG_WARN([$j directory ($dirname) does not exist (yet)])])
done
dnl ===============================================
dnl General Processing
dnl ===============================================
us_auth=
AC_CHECK_HEADER([sys/socket.h], [
AC_CHECK_DECL([SO_PEERCRED], [
# Linux
AC_CHECK_TYPE([struct ucred], [
us_auth=peercred_ucred;
AC_DEFINE([HAVE_UCRED], [1],
[Define if Unix socket auth method is
getsockopt(s, SO_PEERCRED, &ucred, ...)])
], [
# OpenBSD
AC_CHECK_TYPE([struct sockpeercred], [
us_auth=localpeercred_sockepeercred;
AC_DEFINE([HAVE_SOCKPEERCRED], [1],
[Define if Unix socket auth method is
getsockopt(s, SO_PEERCRED, &sockpeercred, ...)])
], [], [[#include <sys/socket.h>]])
], [[#define _GNU_SOURCE
#include <sys/socket.h>]])
], [], [[#include <sys/socket.h>]])
])
AS_IF([test -z "${us_auth}"], [
# FreeBSD
AC_CHECK_DECL([getpeereid], [
us_auth=getpeereid;
AC_DEFINE([HAVE_GETPEEREID], [1],
[Define if Unix socket auth method is
getpeereid(s, &uid, &gid)])
], [
# Solaris/OpenIndiana
AC_CHECK_DECL([getpeerucred], [
us_auth=getpeerucred;
AC_DEFINE([HAVE_GETPEERUCRED], [1],
[Define if Unix socket auth method is
getpeercred(s, &ucred)])
], [
AC_MSG_FAILURE([No way to authenticate a Unix socket peer])
], [[#include <ucred.h>]])
])
])
dnl OS-based decision-making is poor autotools practice; feature-based
dnl mechanisms are strongly preferred. Keep this section to a bare minimum;
dnl regard as a "necessary evil".
dnl Set host_os and host_cpu
AC_CANONICAL_HOST
INIT_EXT=""
PROCFS=0
dnl Solaris and some *BSD versions support procfs but not files we need
AS_CASE(["$host_os"],
[*bsd*], [INIT_EXT=".sh"],
[*linux*], [PROCFS=1],
[darwin*], [
LIBS="$LIBS -L${prefix}/lib"
CFLAGS="$CFLAGS -I${prefix}/include"
])
AC_SUBST(INIT_EXT)
AM_CONDITIONAL([SUPPORT_PROCFS], [test $PROCFS -eq 1])
AC_DEFINE_UNQUOTED([HAVE_LINUX_PROCFS], [$PROCFS],
[Define to 1 if procfs is supported])
AS_CASE(["$host_cpu"],
[ppc64|powerpc64], [
AS_CASE([$CFLAGS],
[*powerpc64*], [],
[*], [AS_IF([test x"$GCC" = x"yes"], [CFLAGS="$CFLAGS -m64"])
])
])
dnl ==============================================
dnl Documentation build dependencies and checks
dnl ==============================================
AC_PATH_PROGS([ASCIIDOC_CONV], [asciidoc asciidoctor])
AC_PATH_PROG([HELP2MAN], [help2man])
AC_PATH_PROG([SPHINX], [sphinx-build])
AC_PATH_PROG([INKSCAPE], [inkscape])
AC_PATH_PROG([XSLTPROC], [xsltproc])
AC_PATH_PROG([XMLCATALOG], [xmlcatalog])
AM_CONDITIONAL(BUILD_HELP, test x"${HELP2MAN}" != x"")
AS_IF([test x"${HELP2MAN}" != x""],
[PCMK_FEATURES="$PCMK_FEATURES generated-manpages"])
MANPAGE_XSLT=""
AS_IF([test x"${XSLTPROC}" != x""],
[
AC_MSG_CHECKING([for DocBook-to-manpage transform])
# first try to figure out correct template using xmlcatalog query,
# resort to extensive (semi-deterministic) file search if that fails
DOCBOOK_XSL_URI='http://docbook.sourceforge.net/release/xsl/current'
DOCBOOK_XSL_PATH='manpages/docbook.xsl'
MANPAGE_XSLT=$(${XMLCATALOG} "" ${DOCBOOK_XSL_URI}/${DOCBOOK_XSL_PATH} \
| sed -n 's|^file://||p;q')
AS_IF([test x"${MANPAGE_XSLT}" = x""],
[
DIRS=$(find "${datadir}" -name $(basename $(dirname ${DOCBOOK_XSL_PATH})) \
-type d 2>/dev/null | LC_ALL=C sort)
XSLT=$(basename ${DOCBOOK_XSL_PATH})
for d in ${DIRS}
do
AS_IF([test -f "${d}/${XSLT}"],
[
MANPAGE_XSLT="${d}/${XSLT}"
break
])
done
])
])
AC_MSG_RESULT([$MANPAGE_XSLT])
AC_SUBST(MANPAGE_XSLT)
AM_CONDITIONAL(BUILD_XML_HELP, test x"${MANPAGE_XSLT}" != x"")
AS_IF([test x"${MANPAGE_XSLT}" != x""],
[PCMK_FEATURES="$PCMK_FEATURES agent-manpages"])
AM_CONDITIONAL([IS_ASCIIDOC], [echo "${ASCIIDOC_CONV}" | grep -Eq 'asciidoc$'])
AM_CONDITIONAL([BUILD_ASCIIDOC], [test "x${ASCIIDOC_CONV}" != x])
AS_IF([test x"${ASCIIDOC_CONV}" != x""],
[PCMK_FEATURES="$PCMK_FEATURES ascii-docs"])
AM_CONDITIONAL([BUILD_SPHINX_DOCS],
[test x"${SPHINX}" != x"" && test x"${INKSCAPE}" != x""])
AM_COND_IF([BUILD_SPHINX_DOCS], [PCMK_FEATURES="$PCMK_FEATURES books"])
dnl Pacemaker's shell scripts (and thus man page builders) rely on GNU getopt
AC_MSG_CHECKING([for GNU-compatible getopt])
IFS_orig=$IFS
IFS=:
for PATH_DIR in $PATH
do
IFS=$IFS_orig
GETOPT_PATH="${PATH_DIR}/getopt"
AS_IF([test -f "$GETOPT_PATH" && test -x "$GETOPT_PATH"],
[
$GETOPT_PATH -T >/dev/null 2>/dev/null
AS_IF([test $? -eq 4], [break])
])
GETOPT_PATH=""
done
IFS=$IFS_orig
AS_IF([test -n "$GETOPT_PATH"], [AC_MSG_RESULT([$GETOPT_PATH])],
[
AC_MSG_RESULT([no])
AC_MSG_ERROR([Could not find required build tool GNU-compatible getopt])
])
AC_SUBST([GETOPT_PATH])
dnl ===============================================
dnl Libraries
dnl ===============================================
AC_CHECK_LIB(socket, socket) dnl -lsocket
AC_CHECK_LIB(c, dlopen) dnl if dlopen is in libc...
AC_CHECK_LIB(dl, dlopen) dnl -ldl (for Linux)
AC_CHECK_LIB(rt, sched_getscheduler) dnl -lrt (for Tru64)
AC_CHECK_LIB(gnugetopt, getopt_long) dnl -lgnugetopt ( if available )
AC_CHECK_LIB(pam, pam_start) dnl -lpam (if available)
PKG_CHECK_MODULES([UUID], [uuid],
[CPPFLAGS="${CPPFLAGS} ${UUID_CFLAGS}"
LIBS="${LIBS} ${UUID_LIBS}"])
AC_CHECK_FUNCS([sched_setscheduler])
AS_IF([test x"$ac_cv_func_sched_setscheduler" != x"yes"],
[PC_LIBS_RT=""],
[PC_LIBS_RT="-lrt"])
AC_SUBST(PC_LIBS_RT)
# Require minimum glib version
PKG_CHECK_MODULES([GLIB], [glib-2.0 >= 2.42.0],
[CPPFLAGS="${CPPFLAGS} ${GLIB_CFLAGS}"
LIBS="${LIBS} ${GLIB_LIBS}"])
# Check whether high-resolution sleep function is available
AC_CHECK_FUNCS([nanosleep usleep])
#
# Where is dlopen?
#
AS_IF([test x"$ac_cv_lib_c_dlopen" = x"yes"],
[LIBADD_DL=""],
[test x"$ac_cv_lib_dl_dlopen" = x"yes"],
[LIBADD_DL=-ldl],
[LIBADD_DL=${lt_cv_dlopen_libs}])
-PKG_CHECK_MODULES(LIBXML2, [libxml-2.0],
+PKG_CHECK_MODULES(LIBXML2, [libxml-2.0 >= 2.6.0],
[CPPFLAGS="${CPPFLAGS} ${LIBXML2_CFLAGS}"
LIBS="${LIBS} ${LIBXML2_LIBS}"])
REQUIRE_LIB([xslt], [xsltApplyStylesheet])
AC_MSG_CHECKING([whether __progname and __progname_full are available])
AC_LINK_IFELSE([AC_LANG_PROGRAM([[extern char *__progname, *__progname_full;]],
[[__progname = "foo";
__progname_full = "foo bar";]])],
[
have_progname="yes"
AC_DEFINE(HAVE_PROGNAME, 1,
[Define to 1 if processes can change their name])
],
[have_progname="no"])
AC_MSG_RESULT([$have_progname])
dnl ========================================================================
dnl Headers
dnl ========================================================================
# Some distributions insert #warnings into deprecated headers. If we will
# enable fatal warnings for the build, then enable them for the header checks
# as well, otherwise the build could fail even though the header check
# succeeds. (We should probably be doing this in more places.)
cc_temp_flags "$CFLAGS $WERROR"
# Optional headers (inclusion of these should be conditional in C code)
AC_CHECK_HEADERS([linux/swab.h])
AC_CHECK_HEADERS([stddef.h])
AC_CHECK_HEADERS([sys/signalfd.h])
AC_CHECK_HEADERS([uuid/uuid.h])
AC_CHECK_HEADERS([security/pam_appl.h pam/pam_appl.h])
# Required headers
REQUIRE_HEADER([arpa/inet.h])
REQUIRE_HEADER([ctype.h])
REQUIRE_HEADER([dirent.h])
REQUIRE_HEADER([dlfcn.h])
REQUIRE_HEADER([errno.h])
REQUIRE_HEADER([fcntl.h])
REQUIRE_HEADER([float.h])
REQUIRE_HEADER([glib.h])
REQUIRE_HEADER([grp.h])
REQUIRE_HEADER([inttypes.h])
REQUIRE_HEADER([libgen.h])
REQUIRE_HEADER([limits.h])
REQUIRE_HEADER([locale.h])
REQUIRE_HEADER([netdb.h])
REQUIRE_HEADER([netinet/in.h])
REQUIRE_HEADER([netinet/ip.h], [
#include <sys/types.h>
#include <netinet/in.h>
])
REQUIRE_HEADER([netinet/tcp.h])
REQUIRE_HEADER([pwd.h])
REQUIRE_HEADER([regex.h])
REQUIRE_HEADER([sched.h])
REQUIRE_HEADER([signal.h])
REQUIRE_HEADER([stdarg.h])
REQUIRE_HEADER([stdbool.h])
REQUIRE_HEADER([stdint.h])
REQUIRE_HEADER([stdio.h])
REQUIRE_HEADER([stdlib.h])
REQUIRE_HEADER([string.h])
REQUIRE_HEADER([strings.h])
REQUIRE_HEADER([sys/ioctl.h])
REQUIRE_HEADER([sys/param.h])
REQUIRE_HEADER([sys/reboot.h])
REQUIRE_HEADER([sys/resource.h])
REQUIRE_HEADER([sys/socket.h])
REQUIRE_HEADER([sys/stat.h])
REQUIRE_HEADER([sys/time.h])
REQUIRE_HEADER([sys/types.h])
REQUIRE_HEADER([sys/uio.h])
REQUIRE_HEADER([sys/utsname.h])
REQUIRE_HEADER([sys/wait.h])
REQUIRE_HEADER([termios.h])
REQUIRE_HEADER([time.h])
REQUIRE_HEADER([unistd.h])
REQUIRE_HEADER([libxml/xpath.h])
REQUIRE_HEADER([libxslt/xslt.h])
cc_restore_flags
dnl ========================================================================
dnl Generic declarations
dnl ========================================================================
AC_CHECK_DECLS([CLOCK_MONOTONIC], [PCMK_FEATURES="$PCMK_FEATURES monotonic"], [], [[
#include <time.h>
]])
dnl ========================================================================
dnl Unit test declarations
dnl ========================================================================
AC_CHECK_DECLS([assert_float_equal], [], [], [[
#include <stdarg.h>
#include <stddef.h>
#include <setjmp.h>
#include <cmocka.h>
]])
cc_temp_flags "$CFLAGS -Wl,--wrap=uname"
WRAPPABLE_UNAME="no"
AC_MSG_CHECKING([if uname() can be wrapped])
AC_RUN_IFELSE([AC_LANG_SOURCE([[
#include <sys/utsname.h>
int __wrap_uname(struct utsname *buf) {
return 100;
}
int main(int argc, char **argv) {
struct utsname x;
return uname(&x) == 100 ? 0 : 1;
}
]])],
[ WRAPPABLE_UNAME="yes" ], [ WRAPPABLE_UNAME="no"])
AC_MSG_RESULT([$WRAPPABLE_UNAME])
AM_CONDITIONAL([WRAPPABLE_UNAME], [test x"$WRAPPABLE_UNAME" = x"yes"])
cc_restore_flags
dnl ========================================================================
dnl Structures
dnl ========================================================================
AC_CHECK_MEMBERS([struct tm.tm_gmtoff],,,[[#include <time.h>]])
AC_CHECK_MEMBER([struct dirent.d_type],
AC_DEFINE(HAVE_STRUCT_DIRENT_D_TYPE,1,[Define this if struct dirent has d_type]),,
[#include <dirent.h>])
dnl ========================================================================
dnl Functions
dnl ========================================================================
REQUIRE_FUNC([alphasort])
REQUIRE_FUNC([getopt])
REQUIRE_FUNC([scandir])
REQUIRE_FUNC([setenv])
REQUIRE_FUNC([strndup])
REQUIRE_FUNC([strnlen])
REQUIRE_FUNC([unsetenv])
REQUIRE_FUNC([uuid_unparse])
REQUIRE_FUNC([vasprintf])
AC_CHECK_FUNCS([strchrnul])
AC_CHECK_FUNCS([fopen64])
AM_CONDITIONAL([WRAPPABLE_FOPEN64], [test x"$ac_cv_func_fopen64" = x"yes"])
AC_MSG_CHECKING([whether strerror always returns non-NULL])
AC_RUN_IFELSE([AC_LANG_PROGRAM([[
#include <stdio.h>
#include <string.h>
]], [[
return strerror(-1) == NULL;
]])],
[AC_MSG_RESULT([yes])],
[AC_MSG_ERROR([strerror() is not C99-compliant])],
[AC_MSG_ERROR([strerror() is not C99-compliant])])
AC_RUN_IFELSE([AC_LANG_PROGRAM([[#include <stdio.h>]], [[
const char *s = "some-command-line-arg";
char *name = NULL;
int n = sscanf(s, "%ms", &name);
return n != 1;
]])],
[have_sscanf_m="yes"],
[have_sscanf_m="no"],
[have_sscanf_m="no"])
AS_IF([test x"$have_sscanf_m" = x"yes"],
[AC_DEFINE([HAVE_SSCANF_M], [1],
[Define to 1 if sscanf %m modifier is available])])
dnl ========================================================================
dnl bzip2
dnl ========================================================================
REQUIRE_HEADER([bzlib.h])
REQUIRE_LIB([bz2], [BZ2_bzBuffToBuffCompress])
dnl ========================================================================
dnl sighandler_t is missing from Illumos, Solaris11 systems
dnl ========================================================================
AC_MSG_CHECKING([for sighandler_t])
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include <signal.h>]], [[sighandler_t *f;]])],
[
AC_MSG_RESULT([yes])
AC_DEFINE([HAVE_SIGHANDLER_T], [1],
[Define to 1 if sighandler_t is available])
],
[AC_MSG_RESULT([no])])
dnl ========================================================================
dnl ncurses
dnl ========================================================================
dnl
dnl A few OSes (e.g. Linux) deliver a default "ncurses" alongside "curses".
dnl Many non-Linux deliver "curses"; sites may add "ncurses".
dnl
dnl However, the source-code recommendation for both is to #include "curses.h"
dnl (i.e. "ncurses" still wants the include to be simple, no-'n', "curses.h").
dnl
dnl ncurses takes precedence.
dnl
AC_CHECK_HEADERS([curses.h curses/curses.h ncurses.h ncurses/ncurses.h])
dnl Although n-library is preferred, only look for it if the n-header was found.
CURSESLIBS=''
PC_NAME_CURSES=""
PC_LIBS_CURSES=""
AS_IF([test x"$ac_cv_header_ncurses_h" = x"yes"], [
AC_CHECK_LIB(ncurses, printw,
[AC_DEFINE(HAVE_LIBNCURSES,1, have ncurses library)])
CURSESLIBS=`$PKG_CONFIG --libs ncurses` || CURSESLIBS='-lncurses'
PC_NAME_CURSES="ncurses"
])
AS_IF([test x"$ac_cv_header_ncurses_ncurses_h" = x"yes"], [
AC_CHECK_LIB(ncurses, printw,
[AC_DEFINE(HAVE_LIBNCURSES,1, have ncurses library)])
CURSESLIBS=`$PKG_CONFIG --libs ncurses` || CURSESLIBS='-lncurses'
PC_NAME_CURSES="ncurses"
])
dnl Only look for non-n-library if there was no n-library.
AS_IF([test x"$CURSESLIBS" = x"" && test x"$ac_cv_header_curses_h" = x"yes"], [
AC_CHECK_LIB(curses, printw,
[CURSESLIBS='-lcurses'; AC_DEFINE(HAVE_LIBCURSES,1, have curses library)])
PC_LIBS_CURSES="$CURSESLIBS"
])
dnl Only look for non-n-library if there was no n-library.
AS_IF([test x"$CURSESLIBS" = x"" && test x"$ac_cv_header_curses_curses_h" = x"yes"], [
AC_CHECK_LIB(curses, printw,
[CURSESLIBS='-lcurses'; AC_DEFINE(HAVE_LIBCURSES,1, have curses library)])
PC_LIBS_CURSES="$CURSESLIBS"
])
AS_IF([test x"$CURSESLIBS" != x""],
[PCMK_FEATURES="$PCMK_FEATURES ncurses"])
dnl Check for printw() prototype compatibility
AS_IF([test x"$CURSESLIBS" != x"" && cc_supports_flag -Wcast-qual], [
ac_save_LIBS=$LIBS
LIBS="$CURSESLIBS"
# avoid broken test because of hardened build environment in Fedora 23+
# - https://fedoraproject.org/wiki/Changes/Harden_All_Packages
# - https://bugzilla.redhat.com/1297985
AS_IF([cc_supports_flag -fPIC],
[cc_temp_flags "-Wcast-qual $WERROR -fPIC"],
[cc_temp_flags "-Wcast-qual $WERROR"])
AC_MSG_CHECKING([whether curses library is compatible])
AC_LINK_IFELSE(
[AC_LANG_PROGRAM([
#if defined(HAVE_NCURSES_H)
# include <ncurses.h>
#elif defined(HAVE_NCURSES_NCURSES_H)
# include <ncurses/ncurses.h>
#elif defined(HAVE_CURSES_H)
# include <curses.h>
#endif
],
[printw((const char *)"Test");]
)],
[AC_MSG_RESULT([yes])],
[
AC_MSG_RESULT([no])
AC_MSG_WARN(m4_normalize([Disabling curses because the printw()
function of your (n)curses library is old.
If you wish to enable curses, update to a
newer version (ncurses 5.4 or later is
recommended, available from
https://invisible-island.net/ncurses/)
]))
AC_DEFINE([HAVE_INCOMPATIBLE_PRINTW], [1],
[Define to 1 if curses library has incompatible printw()])
]
)
LIBS=$ac_save_LIBS
cc_restore_flags
])
AC_SUBST(CURSESLIBS)
AC_SUBST(PC_NAME_CURSES)
AC_SUBST(PC_LIBS_CURSES)
dnl ========================================================================
dnl Profiling and GProf
dnl ========================================================================
CFLAGS_ORIG="$CFLAGS"
AS_IF([test $with_coverage -ne $DISABLED],
[
with_profiling=$REQUIRED
PCMK_FEATURES="$PCMK_FEATURES coverage"
CFLAGS="$CFLAGS -fprofile-arcs -ftest-coverage"
dnl During linking, make sure to specify -lgcov or -coverage
]
)
AS_IF([test $with_profiling -ne $DISABLED],
[
with_profiling=$REQUIRED
PCMK_FEATURES="$PCMK_FEATURES profile"
dnl Disable various compiler optimizations
CFLAGS="$CFLAGS -fno-omit-frame-pointer -fno-inline -fno-builtin"
dnl CFLAGS="$CFLAGS -fno-inline-functions"
dnl CFLAGS="$CFLAGS -fno-default-inline"
dnl CFLAGS="$CFLAGS -fno-inline-functions-called-once"
dnl CFLAGS="$CFLAGS -fno-optimize-sibling-calls"
dnl Turn off optimization so tools can get accurate line numbers
CFLAGS=`echo $CFLAGS | sed \
-e 's/-O.\ //g' \
-e 's/-Wp,-D_FORTIFY_SOURCE=.\ //g' \
-e 's/-D_FORTIFY_SOURCE=.\ //g'`
CFLAGS="$CFLAGS -O0 -g3 -gdwarf-2"
AC_MSG_NOTICE([CFLAGS before adding profiling options: $CFLAGS_ORIG])
AC_MSG_NOTICE([CFLAGS after: $CFLAGS])
]
)
AC_DEFINE_UNQUOTED([SUPPORT_PROFILING], [$with_profiling], [Support profiling])
AM_CONDITIONAL([BUILD_PROFILING], [test "$with_profiling" = "$REQUIRED"])
dnl ========================================================================
dnl Cluster infrastructure - LibQB
dnl ========================================================================
PKG_CHECK_MODULES(libqb, libqb >= 0.17)
CPPFLAGS="$libqb_CFLAGS $CPPFLAGS"
LIBS="$libqb_LIBS $LIBS"
dnl libqb 2.0.5+ (2022-03)
AC_CHECK_FUNCS([qb_ipcc_connect_async])
dnl libqb 2.0.2+ (2020-10)
AC_CHECK_FUNCS([qb_ipcc_auth_get])
dnl libqb 2.0.0+ (2020-05)
CHECK_ENUM_VALUE([qb/qblog.h],[qb_log_conf],[QB_LOG_CONF_MAX_LINE_LEN])
CHECK_ENUM_VALUE([qb/qblog.h],[qb_log_conf],[QB_LOG_CONF_ELLIPSIS])
dnl Support Linux-HA fence agents if available
AS_IF([test x"$cross_compiling" != x"yes"],
[CPPFLAGS="$CPPFLAGS -I${prefix}/include/heartbeat"])
AC_CHECK_HEADERS([stonith/stonith.h],
[
AC_CHECK_LIB([pils], [PILLoadPlugin])
AC_CHECK_LIB([plumb], [G_main_add_IPC_Channel])
PCMK_FEATURES="$PCMK_FEATURES lha"
])
AM_CONDITIONAL([BUILD_LHA_SUPPORT], [test x"$ac_cv_header_stonith_stonith_h" = x"yes"])
dnl ===============================================
dnl Detect DBus, systemd, and Upstart support
dnl ===============================================
HAVE_dbus=1
PKG_CHECK_MODULES([DBUS], [dbus-1],
[CPPFLAGS="${CPPFLAGS} ${DBUS_CFLAGS}"],
[HAVE_dbus=0])
AC_DEFINE_UNQUOTED(HAVE_DBUS, $HAVE_dbus, Support dbus)
AM_CONDITIONAL(BUILD_DBUS, test $HAVE_dbus = 1)
dnl libdbus 1.5.12+ (2012-03) / 1.6.0+ (2012-06)
AC_CHECK_TYPES([DBusBasicValue],,,[[#include <dbus/dbus.h>]])
AS_IF([test $HAVE_dbus = 0],
[PC_NAME_DBUS=""],
[PC_NAME_DBUS="dbus-1"])
AC_SUBST(PC_NAME_DBUS)
check_systemdsystemunitdir() {
AC_MSG_CHECKING([which system unit file directory to use])
PKG_CHECK_VAR([systemdsystemunitdir], [systemd], [systemdsystemunitdir])
AC_MSG_RESULT([${systemdsystemunitdir}])
test x"$systemdsystemunitdir" != x""
return $?
}
AS_CASE([$enable_systemd],
[$REQUIRED], [
AS_IF([test $HAVE_dbus = 0],
[AC_MSG_FAILURE([Cannot support systemd resources without DBus])])
AS_IF([test "$ac_cv_have_decl_CLOCK_MONOTONIC" = "no"],
[AC_MSG_FAILURE([Cannot support systemd resources without monotonic clock])])
AS_IF([check_systemdsystemunitdir], [],
[AC_MSG_FAILURE([Cannot support systemd resources without systemdsystemunitdir])])
],
[$OPTIONAL], [
AS_IF([test $HAVE_dbus = 0 \
|| test x"$ac_cv_have_decl_CLOCK_MONOTONIC" = x"no"],
[enable_systemd=$DISABLED],
[
AC_MSG_CHECKING([for systemd version (using dbus-send)])
ret=$({ dbus-send --system --print-reply \
--dest=org.freedesktop.systemd1 \
/org/freedesktop/systemd1 \
org.freedesktop.DBus.Properties.Get \
string:org.freedesktop.systemd1.Manager \
string:Version 2>/dev/null \
|| echo "version unavailable"; } | tail -n1)
# sanitize output a bit (interested just in value, not type),
# ret is intentionally unenquoted so as to normalize whitespace
ret=$(echo ${ret} | cut -d' ' -f2-)
AC_MSG_RESULT([${ret}])
AS_IF([test x"$ret" != x"unavailable" \
|| systemctl --version 2>/dev/null | grep -q systemd],
[
AS_IF([check_systemdsystemunitdir],
[enable_systemd=$REQUIRED],
[enable_systemd=$DISABLED])
],
[enable_systemd=$DISABLED]
)
])
],
)
AC_MSG_CHECKING([whether to enable support for managing resources via systemd])
AS_IF([test $enable_systemd -eq $DISABLED], [AC_MSG_RESULT([no])],
[
AC_MSG_RESULT([yes])
PCMK_FEATURES="$PCMK_FEATURES systemd"
]
)
AC_SUBST([systemdsystemunitdir])
AC_DEFINE_UNQUOTED([SUPPORT_SYSTEMD], [$enable_systemd],
[Support systemd resources])
AM_CONDITIONAL([BUILD_SYSTEMD], [test $enable_systemd = $REQUIRED])
AC_SUBST(SUPPORT_SYSTEMD)
AS_CASE([$enable_upstart],
[$REQUIRED], [
AS_IF([test $HAVE_dbus = 0],
[AC_MSG_FAILURE([Cannot support Upstart resources without DBus])])
],
[$OPTIONAL], [
AS_IF([test $HAVE_dbus = 0], [enable_upstart=$DISABLED],
[
AC_MSG_CHECKING([for Upstart version (using dbus-send)])
ret=$({ dbus-send --system --print-reply \
--dest=com.ubuntu.Upstart \
/com/ubuntu/Upstart org.freedesktop.DBus.Properties.Get \
string:com.ubuntu.Upstart0_6 string:version 2>/dev/null \
|| echo "version unavailable"; } | tail -n1)
# sanitize output a bit (interested just in value, not type),
# ret is intentionally unenquoted so as to normalize whitespace
ret=$(echo ${ret} | cut -d' ' -f2-)
AC_MSG_RESULT([${ret}])
AS_IF([test x"$ret" != x"unavailable" \
|| initctl --version 2>/dev/null | grep -q upstart],
[enable_upstart=$REQUIRED],
[enable_upstart=$DISABLED]
)
])
],
)
AC_MSG_CHECKING([whether to enable support for managing resources via Upstart])
AS_IF([test $enable_upstart -eq $DISABLED], [AC_MSG_RESULT([no])],
[
AC_MSG_RESULT([yes])
PCMK_FEATURES="$PCMK_FEATURES upstart"
]
)
AC_DEFINE_UNQUOTED([SUPPORT_UPSTART], [$enable_upstart],
[Support Upstart resources])
AM_CONDITIONAL([BUILD_UPSTART], [test $enable_upstart -eq $REQUIRED])
AC_SUBST(SUPPORT_UPSTART)
dnl ========================================================================
dnl Detect Nagios support
dnl ========================================================================
AS_CASE([$with_nagios],
[$REQUIRED], [
AS_IF([test x"$ac_cv_have_decl_CLOCK_MONOTONIC" = x"no"],
[AC_MSG_FAILURE([Cannot support nagios resources without monotonic clock])])
],
[$OPTIONAL], [
AS_IF([test x"$ac_cv_have_decl_CLOCK_MONOTONIC" = x"no"],
[with_nagios=$DISABLED], [with_nagios=$REQUIRED])
]
)
AS_IF([test $with_nagios -eq $REQUIRED], [PCMK_FEATURES="$PCMK_FEATURES nagios"])
AC_DEFINE_UNQUOTED([SUPPORT_NAGIOS], [$with_nagios], [Support nagios plugins])
AM_CONDITIONAL([BUILD_NAGIOS], [test $with_nagios -eq $REQUIRED])
AS_IF([test x"$NAGIOS_PLUGIN_DIR" = x""],
[NAGIOS_PLUGIN_DIR="${libexecdir}/nagios/plugins"])
AC_DEFINE_UNQUOTED(NAGIOS_PLUGIN_DIR, "$NAGIOS_PLUGIN_DIR", Directory for nagios plugins)
AC_SUBST(NAGIOS_PLUGIN_DIR)
AS_IF([test x"$NAGIOS_METADATA_DIR" = x""],
[NAGIOS_METADATA_DIR="${datadir}/nagios/plugins-metadata"])
AC_DEFINE_UNQUOTED(NAGIOS_METADATA_DIR, "$NAGIOS_METADATA_DIR", Directory for nagios plugins metadata)
AC_SUBST(NAGIOS_METADATA_DIR)
STACKS=""
CLUSTERLIBS=""
PC_NAME_CLUSTER=""
dnl ========================================================================
dnl Cluster stack - Corosync
dnl ========================================================================
COROSYNC_LIBS=""
AS_CASE([$with_corosync],
[$REQUIRED], [
# These will be fatal if unavailable
PKG_CHECK_MODULES([cpg], [libcpg])
PKG_CHECK_MODULES([cfg], [libcfg])
PKG_CHECK_MODULES([cmap], [libcmap])
PKG_CHECK_MODULES([quorum], [libquorum])
PKG_CHECK_MODULES([libcorosync_common], [libcorosync_common])
]
[$OPTIONAL], [
PKG_CHECK_MODULES([cpg], [libcpg], [], [with_corosync=$DISABLED])
PKG_CHECK_MODULES([cfg], [libcfg], [], [with_corosync=$DISABLED])
PKG_CHECK_MODULES([cmap], [libcmap], [], [with_corosync=$DISABLED])
PKG_CHECK_MODULES([quorum], [libquorum], [], [with_corosync=$DISABLED])
PKG_CHECK_MODULES([libcorosync_common], [libcorosync_common], [], [with_corosync=$DISABLED])
AS_IF([test $with_corosync -ne $DISABLED], [with_corosync=$REQUIRED])
]
)
AS_IF([test $with_corosync -ne $DISABLED],
[
AC_MSG_CHECKING([for Corosync 2 or later])
AC_MSG_RESULT([yes])
CFLAGS="$CFLAGS $libqb_CFLAGS $cpg_CFLAGS $cfg_CFLAGS $cmap_CFLAGS $quorum_CFLAGS $libcorosync_common_CFLAGS"
CPPFLAGS="$CPPFLAGS `$PKG_CONFIG --cflags-only-I corosync`"
COROSYNC_LIBS="$COROSYNC_LIBS $cpg_LIBS $cfg_LIBS $cmap_LIBS $quorum_LIBS $libcorosync_common_LIBS"
CLUSTERLIBS="$CLUSTERLIBS $COROSYNC_LIBS"
PC_NAME_CLUSTER="$PC_CLUSTER_NAME libcfg libcmap libcorosync_common libcpg libquorum"
STACKS="$STACKS corosync-ge-2"
dnl Shutdown tracking added (back) to corosync Jan 2021
saved_LIBS="$LIBS"
LIBS="$LIBS $COROSYNC_LIBS"
AC_CHECK_FUNCS([corosync_cfg_trackstart])
LIBS="$saved_LIBS"
]
)
AC_DEFINE_UNQUOTED([SUPPORT_COROSYNC], [$with_corosync],
[Support the Corosync messaging and membership layer])
AM_CONDITIONAL([BUILD_CS_SUPPORT], [test $with_corosync -eq $REQUIRED])
AC_SUBST([SUPPORT_COROSYNC])
dnl
dnl Cluster stack - Sanity
dnl
AS_IF([test x"$STACKS" != x""], [AC_MSG_NOTICE([Supported stacks:${STACKS}])],
[AC_MSG_FAILURE([At least one cluster stack must be supported])])
PCMK_FEATURES="${PCMK_FEATURES}${STACKS}"
AC_SUBST(CLUSTERLIBS)
AC_SUBST(PC_NAME_CLUSTER)
dnl ========================================================================
dnl CIB secrets
dnl ========================================================================
AS_IF([test $with_cibsecrets -ne $DISABLED],
[
with_cibsecrets=$REQUIRED
PCMK_FEATURES="$PCMK_FEATURES cibsecrets"
LRM_CIBSECRETS_DIR="${localstatedir}/lib/pacemaker/lrm/secrets"
AC_DEFINE_UNQUOTED([LRM_CIBSECRETS_DIR], ["$LRM_CIBSECRETS_DIR"],
[Location for CIB secrets])
AC_SUBST([LRM_CIBSECRETS_DIR])
]
)
AC_DEFINE_UNQUOTED([SUPPORT_CIBSECRETS], [$with_cibsecrets], [Support CIB secrets])
AM_CONDITIONAL([BUILD_CIBSECRETS], [test $with_cibsecrets -eq $REQUIRED])
dnl ========================================================================
dnl GnuTLS
dnl ========================================================================
dnl Require GnuTLS >=2.12.0 (2011-03) for Pacemaker Remote support
PC_NAME_GNUTLS=""
AS_CASE([$with_gnutls],
[$REQUIRED], [
REQUIRE_LIB([gnutls], [gnutls_sec_param_to_pk_bits])
REQUIRE_HEADER([gnutls/gnutls.h])
],
[$OPTIONAL], [
AC_CHECK_LIB([gnutls], [gnutls_sec_param_to_pk_bits],
[], [with_gnutls=$DISABLED])
AC_CHECK_HEADERS([gnutls/gnutls.h], [], [with_gnutls=$DISABLED])
]
)
AS_IF([test $with_gnutls -ne $DISABLED],
[
PC_NAME_GNUTLS="gnutls"
PCMK_FEATURES="$PCMK_FEATURES remote"
]
)
AC_SUBST([PC_NAME_GNUTLS])
AM_CONDITIONAL([BUILD_REMOTE], [test $with_gnutls -ne $DISABLED])
# --- ASAN/UBSAN/TSAN (see man gcc) ---
# when using SANitizers, we need to pass the -fsanitize..
# to both CFLAGS and LDFLAGS. The CFLAGS/LDFLAGS must be
# specified as first in the list or there will be runtime
# issues (for example user has to LD_PRELOAD asan for it to work
# properly).
AS_IF([test -n "${SANITIZERS}"], [
SANITIZERS=$(echo $SANITIZERS | sed -e 's/,/ /g')
for SANITIZER in $SANITIZERS
do
AS_CASE([$SANITIZER],
[asan|ASAN], [
SANITIZERS_CFLAGS="$SANITIZERS_CFLAGS -fsanitize=address"
SANITIZERS_LDFLAGS="$SANITIZERS_LDFLAGS -fsanitize=address -lasan"
PCMK_FEATURES="$PCMK_FEATURES asan"
REQUIRE_LIB([asan],[main])
],
[ubsan|UBSAN], [
SANITIZERS_CFLAGS="$SANITIZERS_CFLAGS -fsanitize=undefined"
SANITIZERS_LDFLAGS="$SANITIZERS_LDFLAGS -fsanitize=undefined -lubsan"
PCMK_FEATURES="$PCMK_FEATURES ubsan"
REQUIRE_LIB([ubsan],[main])
],
[tsan|TSAN], [
SANITIZERS_CFLAGS="$SANITIZERS_CFLAGS -fsanitize=thread"
SANITIZERS_LDFLAGS="$SANITIZERS_LDFLAGS -fsanitize=thread -ltsan"
PCMK_FEATURES="$PCMK_FEATURES tsan"
REQUIRE_LIB([tsan],[main])
])
done
])
dnl ========================================================================
dnl Compiler flags
dnl ========================================================================
dnl Make sure that CFLAGS is not exported. If the user did
dnl not have CFLAGS in their environment then this should have
dnl no effect. However if CFLAGS was exported from the user's
dnl environment, then the new CFLAGS will also be exported
dnl to sub processes.
AS_IF([export | fgrep " CFLAGS=" > /dev/null],
[
SAVED_CFLAGS="$CFLAGS"
unset CFLAGS
CFLAGS="$SAVED_CFLAGS"
unset SAVED_CFLAGS
])
CC_EXTRAS=""
AS_IF([test x"$GCC" != x"yes"], [CFLAGS="$CFLAGS -g"], [
CFLAGS="$CFLAGS -ggdb"
dnl When we don't have diagnostic push / pull, we can't explicitly disable
dnl checking for nonliteral formats in the places where they occur on purpose
dnl thus we disable nonliteral format checking globally as we are aborting
dnl on warnings.
dnl what makes the things really ugly is that nonliteral format checking is
dnl obviously available as an extra switch in very modern gcc but for older
dnl gcc this is part of -Wformat=2
dnl so if we have push/pull we can enable -Wformat=2 -Wformat-nonliteral
dnl if we don't have push/pull but -Wformat-nonliteral we can enable -Wformat=2
dnl otherwise none of both
gcc_diagnostic_push_pull=no
cc_temp_flags "$CFLAGS $WERROR"
AC_MSG_CHECKING([for gcc diagnostic push / pull])
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
#pragma GCC diagnostic push
#pragma GCC diagnostic pop
]])],
[
AC_MSG_RESULT([yes])
gcc_diagnostic_push_pull=yes
], AC_MSG_RESULT([no]))
cc_restore_flags
AS_IF([cc_supports_flag "-Wformat-nonliteral"],
[gcc_format_nonliteral=yes],
[gcc_format_nonliteral=no])
# We had to eliminate -Wnested-externs because of libtool changes
# Make sure to order options so that the former stand for prerequisites
# of the latter (e.g., -Wformat-nonliteral requires -Wformat).
EXTRA_FLAGS="-fgnu89-inline"
EXTRA_FLAGS="$EXTRA_FLAGS -Wall"
EXTRA_FLAGS="$EXTRA_FLAGS -Waggregate-return"
EXTRA_FLAGS="$EXTRA_FLAGS -Wbad-function-cast"
EXTRA_FLAGS="$EXTRA_FLAGS -Wcast-align"
EXTRA_FLAGS="$EXTRA_FLAGS -Wdeclaration-after-statement"
EXTRA_FLAGS="$EXTRA_FLAGS -Wendif-labels"
EXTRA_FLAGS="$EXTRA_FLAGS -Wfloat-equal"
EXTRA_FLAGS="$EXTRA_FLAGS -Wformat-security"
EXTRA_FLAGS="$EXTRA_FLAGS -Wimplicit-fallthrough"
EXTRA_FLAGS="$EXTRA_FLAGS -Wmissing-prototypes"
EXTRA_FLAGS="$EXTRA_FLAGS -Wmissing-declarations"
EXTRA_FLAGS="$EXTRA_FLAGS -Wnested-externs"
EXTRA_FLAGS="$EXTRA_FLAGS -Wno-long-long"
EXTRA_FLAGS="$EXTRA_FLAGS -Wno-strict-aliasing"
EXTRA_FLAGS="$EXTRA_FLAGS -Wpointer-arith"
EXTRA_FLAGS="$EXTRA_FLAGS -Wstrict-prototypes"
EXTRA_FLAGS="$EXTRA_FLAGS -Wwrite-strings"
EXTRA_FLAGS="$EXTRA_FLAGS -Wunused-but-set-variable"
EXTRA_FLAGS="$EXTRA_FLAGS -Wunsigned-char"
AS_IF([test x"$gcc_diagnostic_push_pull" = x"yes"],
[
AC_DEFINE([HAVE_FORMAT_NONLITERAL], [],
[gcc can complain about nonliterals in format])
EXTRA_FLAGS="$EXTRA_FLAGS -Wformat=2 -Wformat-nonliteral"
],
[test x"$gcc_format_nonliteral" = x"yes"],
[EXTRA_FLAGS="$EXTRA_FLAGS -Wformat=2"])
# Additional warnings it might be nice to enable one day
# -Wshadow
# -Wunreachable-code
for j in $EXTRA_FLAGS
do
AS_IF([cc_supports_flag $CC_EXTRAS $j], [CC_EXTRAS="$CC_EXTRAS $j"])
done
AC_MSG_NOTICE([Using additional gcc flags: ${CC_EXTRAS}])
])
dnl
dnl Hardening flags
dnl
dnl The prime control of whether to apply (targeted) hardening build flags and
dnl which ones is --{enable,disable}-hardening option passed to ./configure:
dnl
dnl --enable-hardening=try (default):
dnl depending on whether any of CFLAGS_HARDENED_EXE, LDFLAGS_HARDENED_EXE,
dnl CFLAGS_HARDENED_LIB or LDFLAGS_HARDENED_LIB environment variables
dnl (see below) is set and non-null, all these custom flags (even if not
dnl set) are used as are, otherwise the best effort is made to offer
dnl reasonably strong hardening in several categories (RELRO, PIE,
dnl "bind now", stack protector) according to what the selected toolchain
dnl can offer
dnl
dnl --enable-hardening:
dnl same effect as --enable-hardening=try when the environment variables
dnl in question are suppressed
dnl
dnl --disable-hardening:
dnl do not apply any targeted hardening measures at all
dnl
dnl The user-injected environment variables that regulate the hardening in
dnl default case are as follows:
dnl
dnl * CFLAGS_HARDENED_EXE, LDFLAGS_HARDENED_EXE
dnl compiler and linker flags (respectively) for daemon programs
dnl (pacemakerd, pacemaker-attrd, pacemaker-controld, pacemaker-execd,
dnl pacemaker-based, pacemaker-fenced, pacemaker-remoted,
dnl pacemaker-schedulerd)
dnl
dnl * CFLAGS_HARDENED_LIB, LDFLAGS_HARDENED_LIB
dnl compiler and linker flags (respectively) for libraries linked
dnl with the daemon programs
dnl
dnl Note that these are purposedly targeted variables (addressing particular
dnl targets all over the scattered Makefiles) and have no effect outside of
dnl the predestined scope (e.g., CLI utilities). For a global reach,
dnl use CFLAGS, LDFLAGS, etc. as usual.
dnl
dnl For guidance on the suitable flags consult, for instance:
dnl https://fedoraproject.org/wiki/Changes/Harden_All_Packages#Detailed_Harden_Flags_Description
dnl https://owasp.org/index.php/C-Based_Toolchain_Hardening#GCC.2FBinutils
dnl
AS_IF([test $enable_hardening -eq $OPTIONAL],
[
AS_IF([test "$(env | grep -Ec '^(C|LD)FLAGS_HARDENED_(EXE|LIB)=.')" = 0],
[enable_hardening=$REQUIRED],
[AC_MSG_NOTICE([Hardening: using custom flags from environment])]
)
],
[
unset CFLAGS_HARDENED_EXE
unset CFLAGS_HARDENED_LIB
unset LDFLAGS_HARDENED_EXE
unset LDFLAGS_HARDENED_LIB
]
)
AS_CASE([$enable_hardening],
[$DISABLED], [AC_MSG_NOTICE([Hardening: explicitly disabled])],
[$REQUIRED], [
CFLAGS_HARDENED_EXE=
CFLAGS_HARDENED_LIB=
LDFLAGS_HARDENED_EXE=
LDFLAGS_HARDENED_LIB=
relro=0
pie=0
bindnow=0
stackprot="none"
# daemons incl. libs: partial RELRO
flag="-Wl,-z,relro"
CC_CHECK_LDFLAGS(["${flag}"],
[
LDFLAGS_HARDENED_EXE="${LDFLAGS_HARDENED_EXE} ${flag}"
LDFLAGS_HARDENED_LIB="${LDFLAGS_HARDENED_LIB} ${flag}"
relro=1
])
# daemons: PIE for both CFLAGS and LDFLAGS
AS_IF([cc_supports_flag -fPIE],
[
flag="-pie"
CC_CHECK_LDFLAGS(["${flag}"],
[
CFLAGS_HARDENED_EXE="${CFLAGS_HARDENED_EXE} -fPIE"
LDFLAGS_HARDENED_EXE="${LDFLAGS_HARDENED_EXE} ${flag}"
pie=1
])
]
)
# daemons incl. libs: full RELRO if sensible + as-needed linking
# so as to possibly mitigate startup performance
# hit caused by excessive linking with unneeded
# libraries
AS_IF([test "${relro}" = 1 && test "${pie}" = 1],
[
flag="-Wl,-z,now"
CC_CHECK_LDFLAGS(["${flag}"],
[
LDFLAGS_HARDENED_EXE="${LDFLAGS_HARDENED_EXE} ${flag}"
LDFLAGS_HARDENED_LIB="${LDFLAGS_HARDENED_LIB} ${flag}"
bindnow=1
])
]
)
AS_IF([test "${bindnow}" = 1],
[
flag="-Wl,--as-needed"
CC_CHECK_LDFLAGS(["${flag}"],
[
LDFLAGS_HARDENED_EXE="${LDFLAGS_HARDENED_EXE} ${flag}"
LDFLAGS_HARDENED_LIB="${LDFLAGS_HARDENED_LIB} ${flag}"
])
])
# universal: prefer strong > all > default stack protector if possible
flag=
AS_IF([cc_supports_flag -fstack-protector-strong],
[
flag="-fstack-protector-strong"
stackprot="strong"
],
[cc_supports_flag -fstack-protector-all],
[
flag="-fstack-protector-all"
stackprot="all"
],
[cc_supports_flag -fstack-protector],
[
flag="-fstack-protector"
stackprot="default"
]
)
AS_IF([test -n "${flag}"], [CC_EXTRAS="${CC_EXTRAS} ${flag}"])
# universal: enable stack clash protection if possible
AS_IF([cc_supports_flag -fstack-clash-protection],
[
CC_EXTRAS="${CC_EXTRAS} -fstack-clash-protection"
AS_IF([test "${stackprot}" = "none"],
[stackprot="clash-only"],
[stackprot="${stackprot}+clash"]
)
]
)
# Log a summary
AS_IF([test "${relro}" = 1 || test "${pie}" = 1 || test x"${stackprot}" != x"none"],
[AC_MSG_NOTICE(m4_normalize([Hardening:
relro=${relro}
pie=${pie}
bindnow=${bindnow}
stackprot=${stackprot}]))
],
[AC_MSG_WARN([Hardening: no suitable features in the toolchain detected])]
)
],
)
CFLAGS="$SANITIZERS_CFLAGS $CFLAGS $CC_EXTRAS"
LDFLAGS="$SANITIZERS_LDFLAGS $LDFLAGS"
CFLAGS_HARDENED_EXE="$SANITIZERS_CFLAGS $CFLAGS_HARDENED_EXE"
LDFLAGS_HARDENED_EXE="$SANITIZERS_LDFLAGS $LDFLAGS_HARDENED_EXE"
NON_FATAL_CFLAGS="$CFLAGS"
AC_SUBST(NON_FATAL_CFLAGS)
dnl
dnl We reset CFLAGS to include our warnings *after* all function
dnl checking goes on, so that our warning flags don't keep the
dnl AC_*FUNCS() calls above from working. In particular, -Werror will
dnl *always* cause us troubles if we set it before here.
dnl
dnl
AS_IF([test $enable_fatal_warnings -ne $DISABLED], [
AC_MSG_NOTICE([Enabling fatal compiler warnings])
CFLAGS="$CFLAGS $WERROR"
])
AC_SUBST(CFLAGS)
dnl This is useful for use in Makefiles that need to remove one specific flag
CFLAGS_COPY="$CFLAGS"
AC_SUBST(CFLAGS_COPY)
AC_SUBST(LIBADD_DL) dnl extra flags for dynamic linking libraries
AC_SUBST(LOCALE)
dnl Options for cleaning up the compiler output
AS_IF([test $enable_quiet -ne $DISABLED],
[
AC_MSG_NOTICE([Suppressing make details])
QUIET_LIBTOOL_OPTS="--silent"
QUIET_MAKE_OPTS="-s" # POSIX compliant
],
[
QUIET_LIBTOOL_OPTS=""
QUIET_MAKE_OPTS=""
]
)
dnl Put the above variables to use
LIBTOOL="${LIBTOOL} --tag=CC \$(QUIET_LIBTOOL_OPTS)"
MAKEFLAGS="${MAKEFLAGS} ${QUIET_MAKE_OPTS}"
# Make features list available (sorted alphabetically, without leading space)
PCMK_FEATURES=`echo "$PCMK_FEATURES" | sed -e 's/^ //' -e 's/ /\n/g' | sort | xargs`
AC_DEFINE_UNQUOTED(CRM_FEATURES, "$PCMK_FEATURES", Set of enabled features)
AC_SUBST(PCMK_FEATURES)
AC_SUBST(CC)
AC_SUBST(MAKEFLAGS)
AC_SUBST(LIBTOOL)
AC_SUBST(QUIET_LIBTOOL_OPTS)
dnl Files we output that need to be executable
CONFIG_FILES_EXEC([agents/ocf/ClusterMon],
[agents/ocf/Dummy],
[agents/ocf/HealthCPU],
[agents/ocf/HealthIOWait],
[agents/ocf/HealthSMART],
[agents/ocf/Stateful],
[agents/ocf/SysInfo],
[agents/ocf/attribute],
[agents/ocf/controld],
[agents/ocf/ifspeed],
[agents/ocf/o2cb],
[agents/ocf/ping],
[agents/ocf/remote],
[agents/stonith/fence_legacy],
[agents/stonith/fence_watchdog],
[cts/cluster_test],
[cts/cts],
[cts/cts-attrd],
[cts/cts-cli],
[cts/cts-exec],
[cts/cts-fencing],
[cts/cts-lab],
[cts/cts-log-watcher],
[cts/cts-regression],
[cts/cts-scheduler],
[cts/benchmark/clubench],
[cts/support/LSBDummy],
[cts/support/cts-support],
[cts/support/fence_dummy],
[cts/support/pacemaker-cts-dummyd],
[doc/abi-check],
[maint/bumplibs],
[tools/cluster-clean],
[tools/cluster-helper],
[tools/crm_failcount],
[tools/crm_master],
[tools/crm_report],
[tools/crm_standby],
[tools/cibsecret],
[tools/pcmk_simtimes],
[xml/version-diff.sh])
dnl Other files we output
AC_CONFIG_FILES(Makefile \
agents/Makefile \
agents/alerts/Makefile \
agents/ocf/Makefile \
agents/stonith/Makefile \
cts/Makefile \
cts/benchmark/Makefile \
cts/scheduler/Makefile \
cts/scheduler/dot/Makefile \
cts/scheduler/exp/Makefile \
cts/scheduler/scores/Makefile \
cts/scheduler/stderr/Makefile \
cts/scheduler/summary/Makefile \
cts/scheduler/xml/Makefile \
cts/support/Makefile \
cts/support/pacemaker-cts-dummyd@.service \
daemons/Makefile \
daemons/attrd/Makefile \
daemons/based/Makefile \
daemons/controld/Makefile \
daemons/execd/Makefile \
daemons/execd/pacemaker_remote \
daemons/execd/pacemaker_remote.service \
daemons/fenced/Makefile \
daemons/pacemakerd/Makefile \
daemons/pacemakerd/pacemaker.combined.upstart \
daemons/pacemakerd/pacemaker.service \
daemons/pacemakerd/pacemaker.upstart \
daemons/schedulerd/Makefile \
devel/Makefile \
doc/Doxyfile \
doc/Makefile \
doc/sphinx/Makefile \
etc/Makefile \
etc/init.d/pacemaker \
etc/logrotate.d/pacemaker \
etc/sysconfig/pacemaker \
include/Makefile \
include/crm/Makefile \
include/crm/cib/Makefile \
include/crm/common/Makefile \
include/crm/cluster/Makefile \
include/crm/fencing/Makefile \
include/crm/pengine/Makefile \
include/pcmki/Makefile \
lib/Makefile \
lib/cib/Makefile \
lib/cluster/Makefile \
lib/common/Makefile \
lib/common/tests/Makefile \
lib/common/tests/acl/Makefile \
lib/common/tests/actions/Makefile \
lib/common/tests/agents/Makefile \
lib/common/tests/cmdline/Makefile \
lib/common/tests/flags/Makefile \
lib/common/tests/health/Makefile \
lib/common/tests/io/Makefile \
lib/common/tests/iso8601/Makefile \
lib/common/tests/lists/Makefile \
lib/common/tests/nvpair/Makefile \
lib/common/tests/options/Makefile \
lib/common/tests/output/Makefile \
lib/common/tests/procfs/Makefile \
lib/common/tests/results/Makefile \
lib/common/tests/scores/Makefile \
lib/common/tests/strings/Makefile \
lib/common/tests/utils/Makefile \
lib/common/tests/xml/Makefile \
lib/common/tests/xpath/Makefile \
lib/fencing/Makefile \
lib/gnu/Makefile \
lib/libpacemaker.pc \
lib/lrmd/Makefile \
lib/pacemaker/Makefile \
lib/pacemaker.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/rules/Makefile \
lib/pengine/tests/status/Makefile \
lib/pengine/tests/unpack/Makefile \
lib/pengine/tests/utils/Makefile \
lib/services/Makefile \
maint/Makefile \
po/Makefile.in \
python/Makefile \
python/setup.py \
python/pacemaker/Makefile \
python/pacemaker/_cts/Makefile \
python/pacemaker/_cts/tests/Makefile \
python/pacemaker/buildoptions.py \
python/tests/Makefile \
rpm/Makefile \
tests/Makefile \
tools/Makefile \
tools/crm_mon.service \
tools/crm_mon.upstart \
tools/report.collector \
tools/report.common \
xml/Makefile \
xml/pacemaker-schemas.pc \
)
dnl Now process the entire list of files added by previous
dnl calls to AC_CONFIG_FILES()
AC_OUTPUT()
dnl *****************
dnl Configure summary
dnl *****************
AC_MSG_NOTICE([])
AC_MSG_NOTICE([$PACKAGE configuration:])
AC_MSG_NOTICE([ Version = ${VERSION} (Build: $BUILD_VERSION)])
AC_MSG_NOTICE([ Features = ${PCMK_FEATURES}])
AC_MSG_NOTICE([])
AC_MSG_NOTICE([ Prefix = ${prefix}])
AC_MSG_NOTICE([ Executables = ${sbindir}])
AC_MSG_NOTICE([ Man pages = ${mandir}])
AC_MSG_NOTICE([ Libraries = ${libdir}])
AC_MSG_NOTICE([ Header files = ${includedir}])
AC_MSG_NOTICE([ Arch-independent files = ${datadir}])
AC_MSG_NOTICE([ State information = ${localstatedir}])
AC_MSG_NOTICE([ System configuration = ${sysconfdir}])
AC_MSG_NOTICE([ OCF agents = ${OCF_ROOT_DIR}])
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/lib/common/schemas.c b/lib/common/schemas.c
index dc5fef56de..3f8b76c93b 100644
--- a/lib/common/schemas.c
+++ b/lib/common/schemas.c
@@ -1,1237 +1,1232 @@
/*
* Copyright 2004-2023 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
#include <stdio.h>
#include <string.h>
#include <dirent.h>
#include <errno.h>
#include <sys/stat.h>
#include <stdarg.h>
#include <libxml/relaxng.h>
#include <libxslt/xslt.h>
#include <libxslt/transform.h>
#include <libxslt/security.h>
#include <libxslt/xsltutils.h>
#include <crm/msg_xml.h>
#include <crm/common/xml.h>
#include <crm/common/xml_internal.h> /* PCMK__XML_LOG_BASE */
typedef struct {
unsigned char v[2];
} schema_version_t;
#define SCHEMA_ZERO { .v = { 0, 0 } }
#define schema_scanf(s, prefix, version, suffix) \
sscanf((s), prefix "%hhu.%hhu" suffix, &((version).v[0]), &((version).v[1]))
#define schema_strdup_printf(prefix, version, suffix) \
crm_strdup_printf(prefix "%u.%u" suffix, (version).v[0], (version).v[1])
typedef struct {
xmlRelaxNGPtr rng;
xmlRelaxNGValidCtxtPtr valid;
xmlRelaxNGParserCtxtPtr parser;
} relaxng_ctx_cache_t;
enum schema_validator_e {
schema_validator_none,
schema_validator_rng
};
struct schema_s {
char *name;
char *transform;
void *cache;
enum schema_validator_e validator;
int after_transform;
schema_version_t version;
char *transform_enter;
bool transform_onleave;
};
static struct schema_s *known_schemas = NULL;
static int xml_schema_max = 0;
static bool silent_logging = FALSE;
static void
xml_log(int priority, const char *fmt, ...)
G_GNUC_PRINTF(2, 3);
static void
xml_log(int priority, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
if (silent_logging == FALSE) {
/* XXX should not this enable dechunking as well? */
PCMK__XML_LOG_BASE(priority, FALSE, 0, NULL, fmt, ap);
}
va_end(ap);
}
static int
xml_latest_schema_index(void)
{
// @COMPAT: pacemaker-next is deprecated since 2.1.5
return xml_schema_max - 3; // index from 0, ignore "pacemaker-next"/"none"
}
static int
xml_minimum_schema_index(void)
{
static int best = 0;
if (best == 0) {
int lpc = 0;
best = xml_latest_schema_index();
for (lpc = best; lpc > 0; lpc--) {
if (known_schemas[lpc].version.v[0]
< known_schemas[best].version.v[0]) {
return best;
} else {
best = lpc;
}
}
best = xml_latest_schema_index();
}
return best;
}
const char *
xml_latest_schema(void)
{
return get_schema_name(xml_latest_schema_index());
}
static inline bool
version_from_filename(const char *filename, schema_version_t *version)
{
int rc = schema_scanf(filename, "pacemaker-", *version, ".rng");
return (rc == 2);
}
static int
schema_filter(const struct dirent *a)
{
int rc = 0;
schema_version_t version = SCHEMA_ZERO;
if (strstr(a->d_name, "pacemaker-") != a->d_name) {
/* crm_trace("%s - wrong prefix", a->d_name); */
} else if (!pcmk__ends_with_ext(a->d_name, ".rng")) {
/* crm_trace("%s - wrong suffix", a->d_name); */
} else if (!version_from_filename(a->d_name, &version)) {
/* crm_trace("%s - wrong format", a->d_name); */
} else {
/* crm_debug("%s - candidate", a->d_name); */
rc = 1;
}
return rc;
}
static int
schema_sort(const struct dirent **a, const struct dirent **b)
{
schema_version_t a_version = SCHEMA_ZERO;
schema_version_t b_version = SCHEMA_ZERO;
if (!version_from_filename(a[0]->d_name, &a_version)
|| !version_from_filename(b[0]->d_name, &b_version)) {
// Shouldn't be possible, but makes static analysis happy
return 0;
}
for (int i = 0; i < 2; ++i) {
if (a_version.v[i] < b_version.v[i]) {
return -1;
} else if (a_version.v[i] > b_version.v[i]) {
return 1;
}
}
return 0;
}
/*!
* \internal
* \brief Add given schema + auxiliary data to internal bookkeeping.
*
* \note When providing \p version, should not be called directly but
* through \c add_schema_by_version.
*/
static void
add_schema(enum schema_validator_e validator, const schema_version_t *version,
const char *name, const char *transform,
const char *transform_enter, bool transform_onleave,
int after_transform)
{
int last = xml_schema_max;
bool have_version = FALSE;
xml_schema_max++;
known_schemas = pcmk__realloc(known_schemas,
xml_schema_max * sizeof(struct schema_s));
CRM_ASSERT(known_schemas != NULL);
memset(known_schemas+last, 0, sizeof(struct schema_s));
known_schemas[last].validator = validator;
known_schemas[last].after_transform = after_transform;
for (int i = 0; i < 2; ++i) {
known_schemas[last].version.v[i] = version->v[i];
if (version->v[i]) {
have_version = TRUE;
}
}
if (have_version) {
known_schemas[last].name = schema_strdup_printf("pacemaker-", *version, "");
} else {
CRM_ASSERT(name);
schema_scanf(name, "%*[^-]-", known_schemas[last].version, "");
known_schemas[last].name = strdup(name);
}
if (transform) {
known_schemas[last].transform = strdup(transform);
}
if (transform_enter) {
known_schemas[last].transform_enter = strdup(transform_enter);
}
known_schemas[last].transform_onleave = transform_onleave;
if (after_transform == 0) {
after_transform = xml_schema_max; /* upgrade is a one-way */
}
known_schemas[last].after_transform = after_transform;
if (known_schemas[last].after_transform < 0) {
crm_debug("Added supported schema %d: %s",
last, known_schemas[last].name);
} else if (known_schemas[last].transform) {
crm_debug("Added supported schema %d: %s (upgrades to %d with %s.xsl)",
last, known_schemas[last].name,
known_schemas[last].after_transform,
known_schemas[last].transform);
} else {
crm_debug("Added supported schema %d: %s (upgrades to %d)",
last, known_schemas[last].name,
known_schemas[last].after_transform);
}
}
/*!
* \internal
* \brief Add version-specified schema + auxiliary data to internal bookkeeping.
* \return Standard Pacemaker return value (the only possible values are
* \c ENOENT when no upgrade schema is associated, or \c pcmk_rc_ok otherwise.
*
* \note There's no reliance on the particular order of schemas entering here.
*
* \par A bit of theory
* We track 3 XSLT stylesheets that differ per usage:
* - "upgrade":
* . sparsely spread over the sequence of all available schemas,
* as they are only relevant when major version of the schema
* is getting bumped -- in that case, it MUST be set
* . name convention: upgrade-X.Y.xsl
* - "upgrade-enter":
* . may only accompany "upgrade" occurrence, but doesn't need to
* be present anytime such one is, i.e., it MAY not be set when
* "upgrade" is
* . name convention: upgrade-X.Y-enter.xsl,
* when not present: upgrade-enter.xsl
* - "upgrade-leave":
* . like "upgrade-enter", but SHOULD be present whenever
* "upgrade-enter" is (and vice versa, but that's only
* to prevent confusion based on observing the files,
* it would get ignored regardless)
* . name convention: (see "upgrade-enter")
*/
static int
add_schema_by_version(const schema_version_t *version, int next,
bool transform_expected)
{
bool transform_onleave = FALSE;
int rc = pcmk_rc_ok;
struct stat s;
char *xslt = NULL,
*transform_upgrade = NULL,
*transform_enter = NULL;
/* prologue for further transform_expected handling */
if (transform_expected) {
/* check if there's suitable "upgrade" stylesheet */
transform_upgrade = schema_strdup_printf("upgrade-", *version, );
xslt = pcmk__xml_artefact_path(pcmk__xml_artefact_ns_legacy_xslt,
transform_upgrade);
}
if (!transform_expected) {
/* jump directly to the end */
} else if (stat(xslt, &s) == 0) {
/* perhaps there's also a targeted "upgrade-enter" stylesheet */
transform_enter = schema_strdup_printf("upgrade-", *version, "-enter");
free(xslt);
xslt = pcmk__xml_artefact_path(pcmk__xml_artefact_ns_legacy_xslt,
transform_enter);
if (stat(xslt, &s) != 0) {
/* or initially, at least a generic one */
crm_debug("Upgrade-enter transform %s.xsl not found", xslt);
free(xslt);
free(transform_enter);
transform_enter = strdup("upgrade-enter");
xslt = pcmk__xml_artefact_path(pcmk__xml_artefact_ns_legacy_xslt,
transform_enter);
if (stat(xslt, &s) != 0) {
crm_debug("Upgrade-enter transform %s.xsl not found, either", xslt);
free(xslt);
xslt = NULL;
}
}
/* xslt contains full path to "upgrade-enter" stylesheet */
if (xslt != NULL) {
/* then there should be "upgrade-leave" counterpart (enter->leave) */
memcpy(strrchr(xslt, '-') + 1, "leave", sizeof("leave") - 1);
transform_onleave = (stat(xslt, &s) == 0);
free(xslt);
} else {
free(transform_enter);
transform_enter = NULL;
}
} else {
crm_err("Upgrade transform %s not found", xslt);
free(xslt);
free(transform_upgrade);
transform_upgrade = NULL;
next = -1;
rc = ENOENT;
}
add_schema(schema_validator_rng, version, NULL,
transform_upgrade, transform_enter, transform_onleave, next);
free(transform_upgrade);
free(transform_enter);
return rc;
}
static void
wrap_libxslt(bool finalize)
{
static xsltSecurityPrefsPtr secprefs;
int ret = 0;
/* security framework preferences */
if (!finalize) {
CRM_ASSERT(secprefs == NULL);
secprefs = xsltNewSecurityPrefs();
ret = xsltSetSecurityPrefs(secprefs, XSLT_SECPREF_WRITE_FILE,
xsltSecurityForbid)
| xsltSetSecurityPrefs(secprefs, XSLT_SECPREF_CREATE_DIRECTORY,
xsltSecurityForbid)
| xsltSetSecurityPrefs(secprefs, XSLT_SECPREF_READ_NETWORK,
xsltSecurityForbid)
| xsltSetSecurityPrefs(secprefs, XSLT_SECPREF_WRITE_NETWORK,
xsltSecurityForbid);
if (ret != 0) {
return;
}
} else {
xsltFreeSecurityPrefs(secprefs);
secprefs = NULL;
}
/* cleanup only */
if (finalize) {
xsltCleanupGlobals();
}
}
/*!
* \internal
* \brief Load pacemaker schemas into cache
*
* \note This currently also serves as an entry point for the
* generic initialization of the libxslt library.
*/
void
crm_schema_init(void)
{
int lpc, max;
char *base = pcmk__xml_artefact_root(pcmk__xml_artefact_ns_legacy_rng);
struct dirent **namelist = NULL;
const schema_version_t zero = SCHEMA_ZERO;
wrap_libxslt(false);
max = scandir(base, &namelist, schema_filter, schema_sort);
if (max < 0) {
crm_notice("scandir(%s) failed: %s (%d)", base, strerror(errno), errno);
free(base);
} else {
free(base);
for (lpc = 0; lpc < max; lpc++) {
bool transform_expected = FALSE;
int next = 0;
schema_version_t version = SCHEMA_ZERO;
if (!version_from_filename(namelist[lpc]->d_name, &version)) {
// Shouldn't be possible, but makes static analysis happy
crm_err("Skipping schema '%s': could not parse version",
namelist[lpc]->d_name);
continue;
}
if ((lpc + 1) < max) {
schema_version_t next_version = SCHEMA_ZERO;
if (version_from_filename(namelist[lpc+1]->d_name, &next_version)
&& (version.v[0] < next_version.v[0])) {
transform_expected = TRUE;
}
} else {
next = -1;
}
if (add_schema_by_version(&version, next, transform_expected)
== ENOENT) {
break;
}
}
for (lpc = 0; lpc < max; lpc++) {
free(namelist[lpc]);
}
free(namelist);
}
// @COMPAT: Deprecated since 2.1.5
add_schema(schema_validator_rng, &zero, "pacemaker-next",
NULL, NULL, FALSE, -1);
add_schema(schema_validator_none, &zero, PCMK__VALUE_NONE,
NULL, NULL, FALSE, -1);
}
static gboolean
validate_with_relaxng(xmlDocPtr doc, gboolean to_logs, const char *relaxng_file,
relaxng_ctx_cache_t **cached_ctx)
{
int rc = 0;
gboolean valid = TRUE;
relaxng_ctx_cache_t *ctx = NULL;
CRM_CHECK(doc != NULL, return FALSE);
CRM_CHECK(relaxng_file != NULL, return FALSE);
if (cached_ctx && *cached_ctx) {
ctx = *cached_ctx;
} else {
crm_debug("Creating RNG parser context");
ctx = calloc(1, sizeof(relaxng_ctx_cache_t));
- xmlLoadExtDtdDefaultValue = 1;
ctx->parser = xmlRelaxNGNewParserCtxt(relaxng_file);
CRM_CHECK(ctx->parser != NULL, goto cleanup);
if (to_logs) {
xmlRelaxNGSetParserErrors(ctx->parser,
(xmlRelaxNGValidityErrorFunc) xml_log,
(xmlRelaxNGValidityWarningFunc) xml_log,
GUINT_TO_POINTER(LOG_ERR));
} else {
xmlRelaxNGSetParserErrors(ctx->parser,
(xmlRelaxNGValidityErrorFunc) fprintf,
(xmlRelaxNGValidityWarningFunc) fprintf,
stderr);
}
ctx->rng = xmlRelaxNGParse(ctx->parser);
CRM_CHECK(ctx->rng != NULL,
crm_err("Could not find/parse %s", relaxng_file);
goto cleanup);
ctx->valid = xmlRelaxNGNewValidCtxt(ctx->rng);
CRM_CHECK(ctx->valid != NULL, goto cleanup);
if (to_logs) {
xmlRelaxNGSetValidErrors(ctx->valid,
(xmlRelaxNGValidityErrorFunc) xml_log,
(xmlRelaxNGValidityWarningFunc) xml_log,
GUINT_TO_POINTER(LOG_ERR));
} else {
xmlRelaxNGSetValidErrors(ctx->valid,
(xmlRelaxNGValidityErrorFunc) fprintf,
(xmlRelaxNGValidityWarningFunc) fprintf,
stderr);
}
}
- xmlLineNumbersDefault(1);
rc = xmlRelaxNGValidateDoc(ctx->valid, doc);
if (rc > 0) {
valid = FALSE;
} else if (rc < 0) {
crm_err("Internal libxml error during validation");
}
cleanup:
if (cached_ctx) {
*cached_ctx = ctx;
} else {
if (ctx->parser != NULL) {
xmlRelaxNGFreeParserCtxt(ctx->parser);
}
if (ctx->valid != NULL) {
xmlRelaxNGFreeValidCtxt(ctx->valid);
}
if (ctx->rng != NULL) {
xmlRelaxNGFree(ctx->rng);
}
free(ctx);
}
return valid;
}
/*!
* \internal
* \brief Clean up global memory associated with XML schemas
*/
void
crm_schema_cleanup(void)
{
int lpc;
relaxng_ctx_cache_t *ctx = NULL;
for (lpc = 0; lpc < xml_schema_max; lpc++) {
switch (known_schemas[lpc].validator) {
case schema_validator_none: // not cached
break;
case schema_validator_rng: // cached
ctx = (relaxng_ctx_cache_t *) known_schemas[lpc].cache;
if (ctx == NULL) {
break;
}
if (ctx->parser != NULL) {
xmlRelaxNGFreeParserCtxt(ctx->parser);
}
if (ctx->valid != NULL) {
xmlRelaxNGFreeValidCtxt(ctx->valid);
}
if (ctx->rng != NULL) {
xmlRelaxNGFree(ctx->rng);
}
free(ctx);
known_schemas[lpc].cache = NULL;
break;
}
free(known_schemas[lpc].name);
free(known_schemas[lpc].transform);
free(known_schemas[lpc].transform_enter);
}
free(known_schemas);
known_schemas = NULL;
wrap_libxslt(true);
}
static gboolean
validate_with(xmlNode *xml, int method, gboolean to_logs)
{
gboolean valid = FALSE;
char *file = NULL;
struct schema_s *schema = NULL;
relaxng_ctx_cache_t **cache = NULL;
if (method < 0) {
return FALSE;
}
schema = &(known_schemas[method]);
if (schema->validator == schema_validator_none) {
return TRUE;
}
if (pcmk__str_eq(schema->name, "pacemaker-next", pcmk__str_none)) {
crm_warn("The pacemaker-next schema is deprecated and will be removed "
"in a future release.");
}
file = pcmk__xml_artefact_path(pcmk__xml_artefact_ns_legacy_rng,
schema->name);
crm_trace("Validating with %s (type=%d)",
pcmk__s(file, "missing schema"), schema->validator);
switch (schema->validator) {
case schema_validator_rng:
cache = (relaxng_ctx_cache_t **) &(schema->cache);
valid = validate_with_relaxng(xml->doc, to_logs, file, cache);
break;
default:
crm_err("Unknown validator type: %d",
known_schemas[method].validator);
break;
}
free(file);
return valid;
}
static bool
validate_with_silent(xmlNode *xml, int method)
{
bool rc, sl_backup = silent_logging;
silent_logging = TRUE;
rc = validate_with(xml, method, TRUE);
silent_logging = sl_backup;
return rc;
}
static void
dump_file(const char *filename)
{
FILE *fp = NULL;
int ch, line = 0;
CRM_CHECK(filename != NULL, return);
fp = fopen(filename, "r");
if (fp == NULL) {
crm_perror(LOG_ERR, "Could not open %s for reading", filename);
return;
}
fprintf(stderr, "%4d ", ++line);
do {
ch = getc(fp);
if (ch == EOF) {
putc('\n', stderr);
break;
} else if (ch == '\n') {
fprintf(stderr, "\n%4d ", ++line);
} else {
putc(ch, stderr);
}
} while (1);
fclose(fp);
}
gboolean
validate_xml_verbose(const xmlNode *xml_blob)
{
int fd = 0;
xmlDoc *doc = NULL;
xmlNode *xml = NULL;
gboolean rc = FALSE;
char *filename = NULL;
filename = crm_strdup_printf("%s/cib-invalid.XXXXXX", pcmk__get_tmpdir());
umask(S_IWGRP | S_IWOTH | S_IROTH);
fd = mkstemp(filename);
write_xml_fd(xml_blob, filename, fd, FALSE);
dump_file(filename);
- doc = xmlParseFile(filename);
+ doc = xmlReadFile(filename, NULL, 0);
xml = xmlDocGetRootElement(doc);
rc = validate_xml(xml, NULL, FALSE);
free_xml(xml);
unlink(filename);
free(filename);
return rc;
}
gboolean
validate_xml(xmlNode *xml_blob, const char *validation, gboolean to_logs)
{
int version = 0;
CRM_CHECK((xml_blob != NULL) && (xml_blob->doc != NULL), return FALSE);
if (validation == NULL) {
validation = crm_element_value(xml_blob, XML_ATTR_VALIDATION);
}
if (validation == NULL) {
int lpc = 0;
bool valid = FALSE;
for (lpc = 0; lpc < xml_schema_max; lpc++) {
if (validate_with(xml_blob, lpc, FALSE)) {
valid = TRUE;
crm_xml_add(xml_blob, XML_ATTR_VALIDATION,
known_schemas[lpc].name);
crm_info("XML validated against %s", known_schemas[lpc].name);
if(known_schemas[lpc].after_transform == 0) {
break;
}
}
}
return valid;
}
version = get_schema_version(validation);
if (strcmp(validation, PCMK__VALUE_NONE) == 0) {
return TRUE;
} else if (version < xml_schema_max) {
return validate_with(xml_blob, version, to_logs);
}
crm_err("Unknown validator: %s", validation);
return FALSE;
}
static void
cib_upgrade_err(void *ctx, const char *fmt, ...)
G_GNUC_PRINTF(2, 3);
/* With this arrangement, an attempt to identify the message severity
as explicitly signalled directly from XSLT is performed in rather
a smart way (no reliance on formatting string + arguments being
always specified as ["%s", purposeful_string], as it can also be
["%s: %s", some_prefix, purposeful_string] etc. so every argument
pertaining %s specifier is investigated), and if such a mark found,
the respective level is determined and, when the messages are to go
to the native logs, the mark itself gets dropped
(by the means of string shift).
NOTE: whether the native logging is the right sink is decided per
the ctx parameter -- NULL denotes this case, otherwise it
carries a pointer to the numeric expression of the desired
target logging level (messages with higher level will be
suppressed)
NOTE: on some architectures, this string shift may not have any
effect, but that's an acceptable tradeoff
The logging level for not explicitly designated messages
(suspicious, likely internal errors or some runaways) is
LOG_WARNING.
*/
static void
cib_upgrade_err(void *ctx, const char *fmt, ...)
{
va_list ap, aq;
char *arg_cur;
bool found = FALSE;
const char *fmt_iter = fmt;
uint8_t msg_log_level = LOG_WARNING; /* default for runaway messages */
const unsigned * log_level = (const unsigned *) ctx;
enum {
escan_seennothing,
escan_seenpercent,
} scan_state = escan_seennothing;
va_start(ap, fmt);
va_copy(aq, ap);
while (!found && *fmt_iter != '\0') {
/* while casing schema borrowed from libqb:qb_vsnprintf_serialize */
switch (*fmt_iter++) {
case '%':
if (scan_state == escan_seennothing) {
scan_state = escan_seenpercent;
} else if (scan_state == escan_seenpercent) {
scan_state = escan_seennothing;
}
break;
case 's':
if (scan_state == escan_seenpercent) {
scan_state = escan_seennothing;
arg_cur = va_arg(aq, char *);
if (arg_cur != NULL) {
switch (arg_cur[0]) {
case 'W':
if (!strncmp(arg_cur, "WARNING: ",
sizeof("WARNING: ") - 1)) {
msg_log_level = LOG_WARNING;
}
if (ctx == NULL) {
memmove(arg_cur, arg_cur + sizeof("WARNING: ") - 1,
strlen(arg_cur + sizeof("WARNING: ") - 1) + 1);
}
found = TRUE;
break;
case 'I':
if (!strncmp(arg_cur, "INFO: ",
sizeof("INFO: ") - 1)) {
msg_log_level = LOG_INFO;
}
if (ctx == NULL) {
memmove(arg_cur, arg_cur + sizeof("INFO: ") - 1,
strlen(arg_cur + sizeof("INFO: ") - 1) + 1);
}
found = TRUE;
break;
case 'D':
if (!strncmp(arg_cur, "DEBUG: ",
sizeof("DEBUG: ") - 1)) {
msg_log_level = LOG_DEBUG;
}
if (ctx == NULL) {
memmove(arg_cur, arg_cur + sizeof("DEBUG: ") - 1,
strlen(arg_cur + sizeof("DEBUG: ") - 1) + 1);
}
found = TRUE;
break;
}
}
}
break;
case '#': case '-': case ' ': case '+': case '\'': case 'I': case '.':
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
case '*':
break;
case 'l':
case 'z':
case 't':
case 'j':
case 'd': case 'i':
case 'o':
case 'u':
case 'x': case 'X':
case 'e': case 'E':
case 'f': case 'F':
case 'g': case 'G':
case 'a': case 'A':
case 'c':
case 'p':
if (scan_state == escan_seenpercent) {
(void) va_arg(aq, void *); /* skip forward */
scan_state = escan_seennothing;
}
break;
default:
scan_state = escan_seennothing;
break;
}
}
if (log_level != NULL) {
/* intention of the following offset is:
cibadmin -V -> start showing INFO labelled messages */
if (*log_level + 4 >= msg_log_level) {
vfprintf(stderr, fmt, ap);
}
} else {
PCMK__XML_LOG_BASE(msg_log_level, TRUE, 0, "CIB upgrade: ", fmt, ap);
}
va_end(aq);
va_end(ap);
}
static xmlNode *
apply_transformation(xmlNode *xml, const char *transform, gboolean to_logs)
{
char *xform = NULL;
xmlNode *out = NULL;
xmlDocPtr res = NULL;
xsltStylesheet *xslt = NULL;
xform = pcmk__xml_artefact_path(pcmk__xml_artefact_ns_legacy_xslt,
transform);
- xmlLoadExtDtdDefaultValue = 1;
- xmlSubstituteEntitiesDefault(1);
-
/* for capturing, e.g., what's emitted via <xsl:message> */
if (to_logs) {
xsltSetGenericErrorFunc(NULL, cib_upgrade_err);
} else {
xsltSetGenericErrorFunc(&crm_log_level, cib_upgrade_err);
}
xslt = xsltParseStylesheetFile((pcmkXmlStr) xform);
CRM_CHECK(xslt != NULL, goto cleanup);
res = xsltApplyStylesheet(xslt, xml->doc, NULL);
CRM_CHECK(res != NULL, goto cleanup);
xsltSetGenericErrorFunc(NULL, NULL); /* restore default one */
out = xmlDocGetRootElement(res);
cleanup:
if (xslt) {
xsltFreeStylesheet(xslt);
}
free(xform);
return out;
}
/*!
* \internal
* \brief Possibly full enter->upgrade->leave trip per internal bookkeeping.
*
* \note Only emits warnings about enter/leave phases in case of issues.
*/
static xmlNode *
apply_upgrade(xmlNode *xml, const struct schema_s *schema, gboolean to_logs)
{
bool transform_onleave = schema->transform_onleave;
char *transform_leave;
xmlNode *upgrade = NULL,
*final = NULL;
if (schema->transform_enter) {
crm_debug("Upgrading %s-style configuration, pre-upgrade phase with %s.xsl",
schema->name, schema->transform_enter);
upgrade = apply_transformation(xml, schema->transform_enter, to_logs);
if (upgrade == NULL) {
crm_warn("Upgrade-enter transformation %s.xsl failed",
schema->transform_enter);
transform_onleave = FALSE;
}
}
if (upgrade == NULL) {
upgrade = xml;
}
crm_debug("Upgrading %s-style configuration, main phase with %s.xsl",
schema->name, schema->transform);
final = apply_transformation(upgrade, schema->transform, to_logs);
if (upgrade != xml) {
free_xml(upgrade);
upgrade = NULL;
}
if (final != NULL && transform_onleave) {
upgrade = final;
/* following condition ensured in add_schema_by_version */
CRM_ASSERT(schema->transform_enter != NULL);
transform_leave = strdup(schema->transform_enter);
/* enter -> leave */
memcpy(strrchr(transform_leave, '-') + 1, "leave", sizeof("leave") - 1);
crm_debug("Upgrading %s-style configuration, post-upgrade phase with %s.xsl",
schema->name, transform_leave);
final = apply_transformation(upgrade, transform_leave, to_logs);
if (final == NULL) {
crm_warn("Upgrade-leave transformation %s.xsl failed", transform_leave);
final = upgrade;
} else {
free_xml(upgrade);
}
free(transform_leave);
}
return final;
}
const char *
get_schema_name(int version)
{
if (version < 0 || version >= xml_schema_max) {
return "unknown";
}
return known_schemas[version].name;
}
int
get_schema_version(const char *name)
{
int lpc = 0;
if (name == NULL) {
name = PCMK__VALUE_NONE;
}
for (; lpc < xml_schema_max; lpc++) {
if (pcmk__str_eq(name, known_schemas[lpc].name, pcmk__str_casei)) {
return lpc;
}
}
return -1;
}
/* set which validation to use */
int
update_validation(xmlNode **xml_blob, int *best, int max, gboolean transform,
gboolean to_logs)
{
xmlNode *xml = NULL;
char *value = NULL;
int max_stable_schemas = xml_latest_schema_index();
int lpc = 0, match = -1, rc = pcmk_ok;
int next = -1; /* -1 denotes "inactive" value */
CRM_CHECK(best != NULL, return -EINVAL);
*best = 0;
CRM_CHECK((xml_blob != NULL) && (*xml_blob != NULL)
&& ((*xml_blob)->doc != NULL),
return -EINVAL);
xml = *xml_blob;
value = crm_element_value_copy(xml, XML_ATTR_VALIDATION);
if (value != NULL) {
match = get_schema_version(value);
lpc = match;
if (lpc >= 0 && transform == FALSE) {
*best = lpc++;
} else if (lpc < 0) {
crm_debug("Unknown validation schema");
lpc = 0;
}
}
if (match >= max_stable_schemas) {
/* nothing to do */
free(value);
*best = match;
return pcmk_ok;
}
while (lpc <= max_stable_schemas) {
crm_debug("Testing '%s' validation (%d of %d)",
known_schemas[lpc].name ? known_schemas[lpc].name : "<unset>",
lpc, max_stable_schemas);
if (validate_with(xml, lpc, to_logs) == FALSE) {
if (next != -1) {
crm_info("Configuration not valid for schema: %s",
known_schemas[lpc].name);
next = -1;
} else {
crm_trace("%s validation failed",
known_schemas[lpc].name ? known_schemas[lpc].name : "<unset>");
}
if (*best) {
/* we've satisfied the validation, no need to check further */
break;
}
rc = -pcmk_err_schema_validation;
} else {
if (next != -1) {
crm_debug("Configuration valid for schema: %s",
known_schemas[next].name);
next = -1;
}
rc = pcmk_ok;
}
if (rc == pcmk_ok) {
*best = lpc;
}
if (rc == pcmk_ok && transform) {
xmlNode *upgrade = NULL;
next = known_schemas[lpc].after_transform;
if (next <= lpc) {
/* There is no next version, or next would regress */
crm_trace("Stopping at %s", known_schemas[lpc].name);
break;
} else if (max > 0 && (lpc == max || next > max)) {
crm_trace("Upgrade limit reached at %s (lpc=%d, next=%d, max=%d)",
known_schemas[lpc].name, lpc, next, max);
break;
} else if (known_schemas[lpc].transform == NULL
/* possibly avoid transforming when readily valid
(in general more restricted when crossing the major
version boundary, as X.0 "transitional" version is
expected to be more strict than it's successors that
may re-allow constructs from previous major line) */
|| validate_with_silent(xml, next)) {
crm_debug("%s-style configuration is also valid for %s",
known_schemas[lpc].name, known_schemas[next].name);
lpc = next;
} else {
crm_debug("Upgrading %s-style configuration to %s with %s.xsl",
known_schemas[lpc].name, known_schemas[next].name,
known_schemas[lpc].transform);
upgrade = apply_upgrade(xml, &known_schemas[lpc], to_logs);
if (upgrade == NULL) {
crm_err("Transformation %s.xsl failed",
known_schemas[lpc].transform);
rc = -pcmk_err_transform_failed;
} else if (validate_with(upgrade, next, to_logs)) {
crm_info("Transformation %s.xsl successful",
known_schemas[lpc].transform);
lpc = next;
*best = next;
free_xml(xml);
xml = upgrade;
rc = pcmk_ok;
} else {
crm_err("Transformation %s.xsl did not produce a valid configuration",
known_schemas[lpc].transform);
crm_log_xml_info(upgrade, "transform:bad");
free_xml(upgrade);
rc = -pcmk_err_schema_validation;
}
next = -1;
}
}
if (transform == FALSE || rc != pcmk_ok) {
/* we need some progress! */
lpc++;
}
}
if (*best > match && *best) {
crm_info("%s the configuration from %s to %s",
transform?"Transformed":"Upgraded",
value ? value : "<none>", known_schemas[*best].name);
crm_xml_add(xml, XML_ATTR_VALIDATION, known_schemas[*best].name);
}
*xml_blob = xml;
free(value);
return rc;
}
gboolean
cli_config_update(xmlNode **xml, int *best_version, gboolean to_logs)
{
gboolean rc = TRUE;
const char *value = crm_element_value(*xml, XML_ATTR_VALIDATION);
char *const orig_value = strdup(value == NULL ? "(none)" : value);
int version = get_schema_version(value);
int orig_version = version;
int min_version = xml_minimum_schema_index();
if (version < min_version) {
// Current configuration schema is not acceptable, try to update
xmlNode *converted = NULL;
converted = copy_xml(*xml);
update_validation(&converted, &version, 0, TRUE, to_logs);
value = crm_element_value(converted, XML_ATTR_VALIDATION);
if (version < min_version) {
// Updated configuration schema is still not acceptable
if (version < orig_version || orig_version == -1) {
// We couldn't validate any schema at all
if (to_logs) {
pcmk__config_err("Cannot upgrade configuration (claiming "
"schema %s) to at least %s because it "
"does not validate with any schema from "
"%s to %s",
orig_value,
get_schema_name(min_version),
get_schema_name(orig_version),
xml_latest_schema());
} else {
fprintf(stderr, "Cannot upgrade configuration (claiming "
"schema %s) to at least %s because it "
"does not validate with any schema from "
"%s to %s\n",
orig_value,
get_schema_name(min_version),
get_schema_name(orig_version),
xml_latest_schema());
}
} else {
// We updated configuration successfully, but still too low
if (to_logs) {
pcmk__config_err("Cannot upgrade configuration (claiming "
"schema %s) to at least %s because it "
"would not upgrade past %s",
orig_value,
get_schema_name(min_version),
pcmk__s(value, "unspecified version"));
} else {
fprintf(stderr, "Cannot upgrade configuration (claiming "
"schema %s) to at least %s because it "
"would not upgrade past %s\n",
orig_value,
get_schema_name(min_version),
pcmk__s(value, "unspecified version"));
}
}
free_xml(converted);
converted = NULL;
rc = FALSE;
} else {
// Updated configuration schema is acceptable
free_xml(*xml);
*xml = converted;
if (version < xml_latest_schema_index()) {
if (to_logs) {
pcmk__config_warn("Configuration with schema %s was "
"internally upgraded to acceptable (but "
"not most recent) %s",
orig_value, get_schema_name(version));
}
} else {
if (to_logs) {
crm_info("Configuration with schema %s was internally "
"upgraded to latest version %s",
orig_value, get_schema_name(version));
}
}
}
} else if (version >= get_schema_version(PCMK__VALUE_NONE)) {
// Schema validation is disabled
if (to_logs) {
pcmk__config_warn("Schema validation of configuration is disabled "
"(enabling is encouraged and prevents common "
"misconfigurations)");
} else {
fprintf(stderr, "Schema validation of configuration is disabled "
"(enabling is encouraged and prevents common "
"misconfigurations)\n");
}
}
if (best_version) {
*best_version = version;
}
free(orig_value);
return rc;
}
diff --git a/lib/common/xml.c b/lib/common/xml.c
index 272d09241d..53ebff770f 100644
--- a/lib/common/xml.c
+++ b/lib/common/xml.c
@@ -1,2736 +1,2736 @@
/*
* Copyright 2004-2023 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <time.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <bzlib.h>
#include <libxml/parser.h>
#include <libxml/tree.h>
#include <libxml/xmlIO.h> /* xmlAllocOutputBuffer */
#include <crm/crm.h>
#include <crm/msg_xml.h>
#include <crm/common/xml.h>
#include <crm/common/xml_internal.h> // PCMK__XML_LOG_BASE, etc.
#include "crmcommon_private.h"
// Define this as 1 in development to get insanely verbose trace messages
#ifndef XML_PARSER_DEBUG
#define XML_PARSER_DEBUG 0
#endif
/* @TODO XML_PARSE_RECOVER allows some XML errors to be silently worked around
* by libxml2, which is potentially ambiguous and dangerous. We should drop it
* when we can break backward compatibility with configurations that might be
* relying on it (i.e. pacemaker 3.0.0).
*
* It might be a good idea to have a transitional period where we first try
* parsing without XML_PARSE_RECOVER, and if that fails, try parsing again with
* it, logging a warning if it succeeds.
*/
#define PCMK__XML_PARSE_OPTS_WITHOUT_RECOVER (XML_PARSE_NOBLANKS)
#define PCMK__XML_PARSE_OPTS_WITH_RECOVER (XML_PARSE_NOBLANKS | XML_PARSE_RECOVER)
bool
pcmk__tracking_xml_changes(xmlNode *xml, bool lazy)
{
if(xml == NULL || xml->doc == NULL || xml->doc->_private == NULL) {
return FALSE;
} else if (!pcmk_is_set(((xml_doc_private_t *)xml->doc->_private)->flags,
pcmk__xf_tracking)) {
return FALSE;
} else if (lazy && !pcmk_is_set(((xml_doc_private_t *)xml->doc->_private)->flags,
pcmk__xf_lazy)) {
return FALSE;
}
return TRUE;
}
static inline void
set_parent_flag(xmlNode *xml, long flag)
{
for(; xml; xml = xml->parent) {
xml_node_private_t *nodepriv = xml->_private;
if (nodepriv == NULL) {
/* During calls to xmlDocCopyNode(), _private will be unset for parent nodes */
} else {
pcmk__set_xml_flags(nodepriv, flag);
}
}
}
void
pcmk__set_xml_doc_flag(xmlNode *xml, enum xml_private_flags flag)
{
if(xml && xml->doc && xml->doc->_private){
/* During calls to xmlDocCopyNode(), xml->doc may be unset */
xml_doc_private_t *docpriv = xml->doc->_private;
pcmk__set_xml_flags(docpriv, flag);
}
}
// Mark document, element, and all element's parents as changed
void
pcmk__mark_xml_node_dirty(xmlNode *xml)
{
pcmk__set_xml_doc_flag(xml, pcmk__xf_dirty);
set_parent_flag(xml, pcmk__xf_dirty);
}
// Clear flags on XML node and its children
static void
reset_xml_node_flags(xmlNode *xml)
{
xmlNode *cIter = NULL;
xml_node_private_t *nodepriv = xml->_private;
if (nodepriv) {
nodepriv->flags = 0;
}
for (cIter = pcmk__xml_first_child(xml); cIter != NULL;
cIter = pcmk__xml_next(cIter)) {
reset_xml_node_flags(cIter);
}
}
// Set xpf_created flag on XML node and any children
void
pcmk__mark_xml_created(xmlNode *xml)
{
xmlNode *cIter = NULL;
xml_node_private_t *nodepriv = NULL;
CRM_ASSERT(xml != NULL);
nodepriv = xml->_private;
if (nodepriv && pcmk__tracking_xml_changes(xml, FALSE)) {
if (!pcmk_is_set(nodepriv->flags, pcmk__xf_created)) {
pcmk__set_xml_flags(nodepriv, pcmk__xf_created);
pcmk__mark_xml_node_dirty(xml);
}
for (cIter = pcmk__xml_first_child(xml); cIter != NULL;
cIter = pcmk__xml_next(cIter)) {
pcmk__mark_xml_created(cIter);
}
}
}
#define XML_DOC_PRIVATE_MAGIC 0x81726354UL
#define XML_NODE_PRIVATE_MAGIC 0x54637281UL
// Free an XML object previously marked as deleted
static void
free_deleted_object(void *data)
{
if(data) {
pcmk__deleted_xml_t *deleted_obj = data;
free(deleted_obj->path);
free(deleted_obj);
}
}
// Free and NULL user, ACLs, and deleted objects in an XML node's private data
static void
reset_xml_private_data(xml_doc_private_t *docpriv)
{
if (docpriv != NULL) {
CRM_ASSERT(docpriv->check == XML_DOC_PRIVATE_MAGIC);
free(docpriv->user);
docpriv->user = NULL;
if (docpriv->acls != NULL) {
pcmk__free_acls(docpriv->acls);
docpriv->acls = NULL;
}
if(docpriv->deleted_objs) {
g_list_free_full(docpriv->deleted_objs, free_deleted_object);
docpriv->deleted_objs = NULL;
}
}
}
// Free all private data associated with an XML node
static void
free_private_data(xmlNode *node)
{
/* Note:
This function frees private data assosciated with an XML node,
unless the function is being called as a result of internal
XSLT cleanup.
That could happen through, for example, the following chain of
function calls:
xsltApplyStylesheetInternal
-> xsltFreeTransformContext
-> xsltFreeRVTs
-> xmlFreeDoc
And in that case, the node would fulfill three conditions:
1. It would be a standalone document (i.e. it wouldn't be
part of a document)
2. It would have a space-prefixed name (for reference, please
see xsltInternals.h: XSLT_MARK_RES_TREE_FRAG)
3. It would carry its own payload in the _private field.
We do not free data in this circumstance to avoid a failed
assertion on the XML_*_PRIVATE_MAGIC later.
*/
if (node->name == NULL || node->name[0] != ' ') {
if (node->_private) {
if (node->type == XML_DOCUMENT_NODE) {
reset_xml_private_data(node->_private);
} else {
CRM_ASSERT(((xml_node_private_t *) node->_private)->check
== XML_NODE_PRIVATE_MAGIC);
/* nothing dynamically allocated nested */
}
free(node->_private);
node->_private = NULL;
}
}
}
// Allocate and initialize private data for an XML node
static void
new_private_data(xmlNode *node)
{
switch (node->type) {
case XML_DOCUMENT_NODE: {
xml_doc_private_t *docpriv = NULL;
docpriv = calloc(1, sizeof(xml_doc_private_t));
CRM_ASSERT(docpriv != NULL);
docpriv->check = XML_DOC_PRIVATE_MAGIC;
/* Flags will be reset if necessary when tracking is enabled */
pcmk__set_xml_flags(docpriv, pcmk__xf_dirty|pcmk__xf_created);
node->_private = docpriv;
break;
}
case XML_ELEMENT_NODE:
case XML_ATTRIBUTE_NODE:
case XML_COMMENT_NODE: {
xml_node_private_t *nodepriv = NULL;
nodepriv = calloc(1, sizeof(xml_node_private_t));
CRM_ASSERT(nodepriv != NULL);
nodepriv->check = XML_NODE_PRIVATE_MAGIC;
/* Flags will be reset if necessary when tracking is enabled */
pcmk__set_xml_flags(nodepriv, pcmk__xf_dirty|pcmk__xf_created);
node->_private = nodepriv;
if (pcmk__tracking_xml_changes(node, FALSE)) {
/* XML_ELEMENT_NODE doesn't get picked up here, node->doc is
* not hooked up at the point we are called
*/
pcmk__mark_xml_node_dirty(node);
}
break;
}
case XML_TEXT_NODE:
case XML_DTD_NODE:
case XML_CDATA_SECTION_NODE:
break;
default:
/* Ignore */
crm_trace("Ignoring %p %d", node, node->type);
CRM_LOG_ASSERT(node->type == XML_ELEMENT_NODE);
break;
}
}
void
xml_track_changes(xmlNode * xml, const char *user, xmlNode *acl_source, bool enforce_acls)
{
xml_accept_changes(xml);
crm_trace("Tracking changes%s to %p", enforce_acls?" with ACLs":"", xml);
pcmk__set_xml_doc_flag(xml, pcmk__xf_tracking);
if(enforce_acls) {
if(acl_source == NULL) {
acl_source = xml;
}
pcmk__set_xml_doc_flag(xml, pcmk__xf_acl_enabled);
pcmk__unpack_acl(acl_source, xml, user);
pcmk__apply_acl(xml);
}
}
bool xml_tracking_changes(xmlNode * xml)
{
return (xml != NULL) && (xml->doc != NULL) && (xml->doc->_private != NULL)
&& pcmk_is_set(((xml_doc_private_t *)(xml->doc->_private))->flags,
pcmk__xf_tracking);
}
bool xml_document_dirty(xmlNode *xml)
{
return (xml != NULL) && (xml->doc != NULL) && (xml->doc->_private != NULL)
&& pcmk_is_set(((xml_doc_private_t *)(xml->doc->_private))->flags,
pcmk__xf_dirty);
}
/*!
* \internal
* \brief Return ordinal position of an XML node among its siblings
*
* \param[in] xml XML node to check
* \param[in] ignore_if_set Don't count siblings with this flag set
*
* \return Ordinal position of \p xml (starting with 0)
*/
int
pcmk__xml_position(const xmlNode *xml, enum xml_private_flags ignore_if_set)
{
int position = 0;
for (const xmlNode *cIter = xml; cIter->prev; cIter = cIter->prev) {
xml_node_private_t *nodepriv = ((xmlNode*)cIter->prev)->_private;
if (!pcmk_is_set(nodepriv->flags, ignore_if_set)) {
position++;
}
}
return position;
}
// Remove all attributes marked as deleted from an XML node
static void
accept_attr_deletions(xmlNode *xml)
{
// Clear XML node's flags
((xml_node_private_t *) xml->_private)->flags = pcmk__xf_none;
// Remove this XML node's attributes that were marked as deleted
pcmk__xe_remove_matching_attrs(xml, pcmk__marked_as_deleted, NULL);
// Recursively do the same for this XML node's children
for (xmlNodePtr cIter = pcmk__xml_first_child(xml); cIter != NULL;
cIter = pcmk__xml_next(cIter)) {
accept_attr_deletions(cIter);
}
}
/*!
* \internal
* \brief Find first child XML node matching another given XML node
*
* \param[in] haystack XML whose children should be checked
* \param[in] needle XML to match (comment content or element name and ID)
* \param[in] exact If true and needle is a comment, position must match
*/
xmlNode *
pcmk__xml_match(const xmlNode *haystack, const xmlNode *needle, bool exact)
{
CRM_CHECK(needle != NULL, return NULL);
if (needle->type == XML_COMMENT_NODE) {
return pcmk__xc_match(haystack, needle, exact);
} else {
const char *id = ID(needle);
const char *attr = (id == NULL)? NULL : XML_ATTR_ID;
return pcmk__xe_match(haystack, (const char *) needle->name, attr, id);
}
}
void
xml_accept_changes(xmlNode * xml)
{
xmlNode *top = NULL;
xml_doc_private_t *docpriv = NULL;
if(xml == NULL) {
return;
}
crm_trace("Accepting changes to %p", xml);
docpriv = xml->doc->_private;
top = xmlDocGetRootElement(xml->doc);
reset_xml_private_data(xml->doc->_private);
if (!pcmk_is_set(docpriv->flags, pcmk__xf_dirty)) {
docpriv->flags = pcmk__xf_none;
return;
}
docpriv->flags = pcmk__xf_none;
accept_attr_deletions(top);
}
xmlNode *
find_xml_node(const xmlNode *root, const char *search_path, gboolean must_find)
{
xmlNode *a_child = NULL;
const char *name = (root == NULL)? "<NULL>" : (const char *) root->name;
if (search_path == NULL) {
crm_warn("Will never find <NULL>");
return NULL;
}
for (a_child = pcmk__xml_first_child(root); a_child != NULL;
a_child = pcmk__xml_next(a_child)) {
if (strcmp((const char *)a_child->name, search_path) == 0) {
return a_child;
}
}
if (must_find) {
crm_warn("Could not find %s in %s.", search_path, name);
} else if (root != NULL) {
crm_trace("Could not find %s in %s.", search_path, name);
} else {
crm_trace("Could not find %s in <NULL>.", search_path);
}
return NULL;
}
#define attr_matches(c, n, v) pcmk__str_eq(crm_element_value((c), (n)), \
(v), pcmk__str_none)
/*!
* \internal
* \brief Find first XML child element matching given criteria
*
* \param[in] parent XML element to search
* \param[in] node_name If not NULL, only match children of this type
* \param[in] attr_n If not NULL, only match children with an attribute
* of this name.
* \param[in] attr_v If \p attr_n and this are not NULL, only match children
* with an attribute named \p attr_n and this value
*
* \return Matching XML child element, or NULL if none found
*/
xmlNode *
pcmk__xe_match(const xmlNode *parent, const char *node_name,
const char *attr_n, const char *attr_v)
{
CRM_CHECK(parent != NULL, return NULL);
CRM_CHECK(attr_v == NULL || attr_n != NULL, return NULL);
for (xmlNode *child = pcmk__xml_first_child(parent); child != NULL;
child = pcmk__xml_next(child)) {
if (pcmk__str_eq(node_name, (const char *) (child->name),
pcmk__str_null_matches)
&& ((attr_n == NULL) ||
(attr_v == NULL && xmlHasProp(child, (pcmkXmlStr) attr_n)) ||
(attr_v != NULL && attr_matches(child, attr_n, attr_v)))) {
return child;
}
}
crm_trace("XML child node <%s%s%s%s%s> not found in %s",
(node_name? node_name : "(any)"),
(attr_n? " " : ""),
(attr_n? attr_n : ""),
(attr_n? "=" : ""),
(attr_n? attr_v : ""),
(const char *) parent->name);
return NULL;
}
void
copy_in_properties(xmlNode *target, const xmlNode *src)
{
if (src == NULL) {
crm_warn("No node to copy properties from");
} else if (target == NULL) {
crm_err("No node to copy properties into");
} else {
for (xmlAttrPtr a = pcmk__xe_first_attr(src); a != NULL; a = a->next) {
const char *p_name = (const char *) a->name;
const char *p_value = pcmk__xml_attr_value(a);
expand_plus_plus(target, p_name, p_value);
if (xml_acl_denied(target)) {
crm_trace("Cannot copy %s=%s to %s", p_name, p_value, target->name);
return;
}
}
}
return;
}
/*!
* \brief Parse integer assignment statements on this node and all its child
* nodes
*
* \param[in,out] target Root XML node to be processed
*
* \note This function is recursive
*/
void
fix_plus_plus_recursive(xmlNode *target)
{
/* TODO: Remove recursion and use xpath searches for value++ */
xmlNode *child = NULL;
for (xmlAttrPtr a = pcmk__xe_first_attr(target); a != NULL; a = a->next) {
const char *p_name = (const char *) a->name;
const char *p_value = pcmk__xml_attr_value(a);
expand_plus_plus(target, p_name, p_value);
}
for (child = pcmk__xml_first_child(target); child != NULL;
child = pcmk__xml_next(child)) {
fix_plus_plus_recursive(child);
}
}
/*!
* \brief Update current XML attribute value per parsed integer assignment
statement
*
* \param[in,out] target an XML node, containing a XML attribute that is
* initialized to some numeric value, to be processed
* \param[in] name name of the XML attribute, e.g. X, whose value
* should be updated
* \param[in] value assignment statement, e.g. "X++" or
* "X+=5", to be applied to the initialized value.
*
* \note The original XML attribute value is treated as 0 if non-numeric and
* truncated to be an integer if decimal-point-containing.
* \note The final XML attribute value is truncated to not exceed 1000000.
* \note Undefined behavior if unexpected input.
*/
void
expand_plus_plus(xmlNode * target, const char *name, const char *value)
{
int offset = 1;
int name_len = 0;
int int_value = 0;
int value_len = 0;
const char *old_value = NULL;
if (target == NULL || value == NULL || name == NULL) {
return;
}
old_value = crm_element_value(target, name);
if (old_value == NULL) {
/* if no previous value, set unexpanded */
goto set_unexpanded;
} else if (strstr(value, name) != value) {
goto set_unexpanded;
}
name_len = strlen(name);
value_len = strlen(value);
if (value_len < (name_len + 2)
|| value[name_len] != '+' || (value[name_len + 1] != '+' && value[name_len + 1] != '=')) {
goto set_unexpanded;
}
/* if we are expanding ourselves,
* then no previous value was set and leave int_value as 0
*/
if (old_value != value) {
int_value = char2score(old_value);
}
if (value[name_len + 1] != '+') {
const char *offset_s = value + (name_len + 2);
offset = char2score(offset_s);
}
int_value += offset;
if (int_value > INFINITY) {
int_value = (int)INFINITY;
}
crm_xml_add_int(target, name, int_value);
return;
set_unexpanded:
if (old_value == value) {
/* the old value is already set, nothing to do */
return;
}
crm_xml_add(target, name, value);
return;
}
/*!
* \internal
* \brief Remove an XML element's attributes that match some criteria
*
* \param[in,out] element XML element to modify
* \param[in] match If not NULL, only remove attributes for which
* this function returns true
* \param[in,out] user_data Data to pass to \p match
*/
void
pcmk__xe_remove_matching_attrs(xmlNode *element,
bool (*match)(xmlAttrPtr, void *),
void *user_data)
{
xmlAttrPtr next = NULL;
for (xmlAttrPtr a = pcmk__xe_first_attr(element); a != NULL; a = next) {
next = a->next; // Grab now because attribute might get removed
if ((match == NULL) || match(a, user_data)) {
if (!pcmk__check_acl(element, NULL, pcmk__xf_acl_write)) {
crm_trace("ACLs prevent removal of attributes (%s and "
"possibly others) from %s element",
(const char *) a->name, (const char *) element->name);
return; // ACLs apply to element, not particular attributes
}
if (pcmk__tracking_xml_changes(element, false)) {
// Leave (marked for removal) until after diff is calculated
set_parent_flag(element, pcmk__xf_dirty);
pcmk__set_xml_flags((xml_node_private_t *) a->_private,
pcmk__xf_deleted);
} else {
xmlRemoveProp(a);
}
}
}
}
xmlNode *
add_node_copy(xmlNode * parent, xmlNode * src_node)
{
xmlNode *child = NULL;
CRM_CHECK((parent != NULL) && (src_node != NULL), return NULL);
child = xmlDocCopyNode(src_node, parent->doc, 1);
if (child == NULL) {
return NULL;
}
xmlAddChild(parent, child);
pcmk__mark_xml_created(child);
return child;
}
xmlNode *
create_xml_node(xmlNode * parent, const char *name)
{
xmlDoc *doc = NULL;
xmlNode *node = NULL;
if (pcmk__str_empty(name)) {
CRM_CHECK(name != NULL && name[0] == 0, return NULL);
return NULL;
}
if (parent == NULL) {
doc = xmlNewDoc((pcmkXmlStr) "1.0");
if (doc == NULL) {
return NULL;
}
node = xmlNewDocRawNode(doc, NULL, (pcmkXmlStr) name, NULL);
if (node == NULL) {
xmlFreeDoc(doc);
return NULL;
}
xmlDocSetRootElement(doc, node);
} else {
node = xmlNewChild(parent, NULL, (pcmkXmlStr) name, NULL);
if (node == NULL) {
return NULL;
}
}
pcmk__mark_xml_created(node);
return node;
}
xmlNode *
pcmk_create_xml_text_node(xmlNode * parent, const char *name, const char *content)
{
xmlNode *node = create_xml_node(parent, name);
if (node != NULL) {
xmlNodeSetContent(node, (pcmkXmlStr) content);
}
return node;
}
xmlNode *
pcmk_create_html_node(xmlNode * parent, const char *element_name, const char *id,
const char *class_name, const char *text)
{
xmlNode *node = pcmk_create_xml_text_node(parent, element_name, text);
if (class_name != NULL) {
crm_xml_add(node, "class", class_name);
}
if (id != NULL) {
crm_xml_add(node, "id", id);
}
return node;
}
/*!
* Free an XML element and all of its children, removing it from its parent
*
* \param[in,out] xml XML element to free
*/
void
pcmk_free_xml_subtree(xmlNode *xml)
{
xmlUnlinkNode(xml); // Detaches from parent and siblings
xmlFreeNode(xml); // Frees
}
static void
free_xml_with_position(xmlNode * child, int position)
{
if (child != NULL) {
xmlNode *top = NULL;
xmlDoc *doc = child->doc;
xml_node_private_t *nodepriv = child->_private;
xml_doc_private_t *docpriv = NULL;
if (doc != NULL) {
top = xmlDocGetRootElement(doc);
}
if (doc != NULL && top == child) {
/* Free everything */
xmlFreeDoc(doc);
} else if (pcmk__check_acl(child, NULL, pcmk__xf_acl_write) == FALSE) {
GString *xpath = NULL;
pcmk__if_tracing({}, return);
xpath = pcmk__element_xpath(child);
qb_log_from_external_source(__func__, __FILE__,
"Cannot remove %s %x", LOG_TRACE,
__LINE__, 0, (const char *) xpath->str,
nodepriv->flags);
g_string_free(xpath, TRUE);
return;
} else {
if (doc && pcmk__tracking_xml_changes(child, FALSE)
&& !pcmk_is_set(nodepriv->flags, pcmk__xf_created)) {
GString *xpath = pcmk__element_xpath(child);
if (xpath != NULL) {
pcmk__deleted_xml_t *deleted_obj = NULL;
crm_trace("Deleting %s %p from %p",
(const char *) xpath->str, child, doc);
deleted_obj = calloc(1, sizeof(pcmk__deleted_xml_t));
deleted_obj->path = strdup((const char *) xpath->str);
CRM_ASSERT(deleted_obj->path != NULL);
g_string_free(xpath, TRUE);
deleted_obj->position = -1;
/* Record the "position" only for XML comments for now */
if (child->type == XML_COMMENT_NODE) {
if (position >= 0) {
deleted_obj->position = position;
} else {
deleted_obj->position = pcmk__xml_position(child,
pcmk__xf_skip);
}
}
docpriv = doc->_private;
docpriv->deleted_objs = g_list_append(docpriv->deleted_objs, deleted_obj);
pcmk__set_xml_doc_flag(child, pcmk__xf_dirty);
}
}
pcmk_free_xml_subtree(child);
}
}
}
void
free_xml(xmlNode * child)
{
free_xml_with_position(child, -1);
}
xmlNode *
copy_xml(xmlNode * src)
{
xmlDoc *doc = xmlNewDoc((pcmkXmlStr) "1.0");
xmlNode *copy = xmlDocCopyNode(src, doc, 1);
CRM_ASSERT(copy != NULL);
xmlDocSetRootElement(doc, copy);
return copy;
}
xmlNode *
string2xml(const char *input)
{
xmlNode *xml = NULL;
xmlDocPtr output = NULL;
xmlParserCtxtPtr ctxt = NULL;
- xmlErrorPtr last_error = NULL;
+ const xmlError *last_error = NULL;
if (input == NULL) {
crm_err("Can't parse NULL input");
return NULL;
}
/* create a parser context */
ctxt = xmlNewParserCtxt();
CRM_CHECK(ctxt != NULL, return NULL);
xmlCtxtResetLastError(ctxt);
xmlSetGenericErrorFunc(ctxt, pcmk__log_xmllib_err);
output = xmlCtxtReadDoc(ctxt, (pcmkXmlStr) input, NULL, NULL,
PCMK__XML_PARSE_OPTS_WITHOUT_RECOVER);
if (output == NULL) {
output = xmlCtxtReadDoc(ctxt, (pcmkXmlStr) input, NULL, NULL,
PCMK__XML_PARSE_OPTS_WITH_RECOVER);
if (output) {
crm_warn("Successfully recovered from XML errors "
"(note: a future release will treat this as a fatal failure)");
}
}
if (output) {
xml = xmlDocGetRootElement(output);
}
last_error = xmlCtxtGetLastError(ctxt);
if (last_error && last_error->code != XML_ERR_OK) {
/* crm_abort(__FILE__,__func__,__LINE__, "last_error->code != XML_ERR_OK", TRUE, TRUE); */
/*
* http://xmlsoft.org/html/libxml-xmlerror.html#xmlErrorLevel
* http://xmlsoft.org/html/libxml-xmlerror.html#xmlParserErrors
*/
crm_warn("Parsing failed (domain=%d, level=%d, code=%d): %s",
last_error->domain, last_error->level, last_error->code, last_error->message);
if (last_error->code == XML_ERR_DOCUMENT_EMPTY) {
CRM_LOG_ASSERT("Cannot parse an empty string");
} else if (last_error->code != XML_ERR_DOCUMENT_END) {
crm_err("Couldn't%s parse %d chars: %s", xml ? " fully" : "", (int)strlen(input),
input);
if (xml != NULL) {
crm_log_xml_err(xml, "Partial");
}
} else {
int len = strlen(input);
int lpc = 0;
while(lpc < len) {
crm_warn("Parse error[+%.3d]: %.80s", lpc, input+lpc);
lpc += 80;
}
CRM_LOG_ASSERT("String parsing error");
}
}
xmlFreeParserCtxt(ctxt);
return xml;
}
xmlNode *
stdin2xml(void)
{
size_t data_length = 0;
size_t read_chars = 0;
char *xml_buffer = NULL;
xmlNode *xml_obj = NULL;
do {
xml_buffer = pcmk__realloc(xml_buffer, data_length + PCMK__BUFFER_SIZE);
read_chars = fread(xml_buffer + data_length, 1, PCMK__BUFFER_SIZE,
stdin);
data_length += read_chars;
} while (read_chars == PCMK__BUFFER_SIZE);
if (data_length == 0) {
crm_warn("No XML supplied on stdin");
free(xml_buffer);
return NULL;
}
xml_buffer[data_length] = '\0';
xml_obj = string2xml(xml_buffer);
free(xml_buffer);
crm_log_xml_trace(xml_obj, "Created fragment");
return xml_obj;
}
static char *
decompress_file(const char *filename)
{
char *buffer = NULL;
int rc = 0;
size_t length = 0, read_len = 0;
BZFILE *bz_file = NULL;
FILE *input = fopen(filename, "r");
if (input == NULL) {
crm_perror(LOG_ERR, "Could not open %s for reading", filename);
return NULL;
}
bz_file = BZ2_bzReadOpen(&rc, input, 0, 0, NULL, 0);
rc = pcmk__bzlib2rc(rc);
if (rc != pcmk_rc_ok) {
crm_err("Could not prepare to read compressed %s: %s "
CRM_XS " rc=%d", filename, pcmk_rc_str(rc), rc);
BZ2_bzReadClose(&rc, bz_file);
fclose(input);
return NULL;
}
rc = BZ_OK;
// cppcheck seems not to understand the abort-logic in pcmk__realloc
// cppcheck-suppress memleak
while (rc == BZ_OK) {
buffer = pcmk__realloc(buffer, PCMK__BUFFER_SIZE + length + 1);
read_len = BZ2_bzRead(&rc, bz_file, buffer + length, PCMK__BUFFER_SIZE);
crm_trace("Read %ld bytes from file: %d", (long)read_len, rc);
if (rc == BZ_OK || rc == BZ_STREAM_END) {
length += read_len;
}
}
buffer[length] = '\0';
rc = pcmk__bzlib2rc(rc);
if (rc != pcmk_rc_ok) {
crm_err("Could not read compressed %s: %s " CRM_XS " rc=%d",
filename, pcmk_rc_str(rc), rc);
free(buffer);
buffer = NULL;
}
BZ2_bzReadClose(&rc, bz_file);
fclose(input);
return buffer;
}
/*!
* \internal
* \brief Remove XML text nodes from specified XML and all its children
*
* \param[in,out] xml XML to strip text from
*/
void
pcmk__strip_xml_text(xmlNode *xml)
{
xmlNode *iter = xml->children;
while (iter) {
xmlNode *next = iter->next;
switch (iter->type) {
case XML_TEXT_NODE:
/* Remove it */
pcmk_free_xml_subtree(iter);
break;
case XML_ELEMENT_NODE:
/* Search it */
pcmk__strip_xml_text(iter);
break;
default:
/* Leave it */
break;
}
iter = next;
}
}
xmlNode *
filename2xml(const char *filename)
{
xmlNode *xml = NULL;
xmlDocPtr output = NULL;
bool uncompressed = true;
xmlParserCtxtPtr ctxt = NULL;
- xmlErrorPtr last_error = NULL;
+ const xmlError *last_error = NULL;
/* create a parser context */
ctxt = xmlNewParserCtxt();
CRM_CHECK(ctxt != NULL, return NULL);
xmlCtxtResetLastError(ctxt);
xmlSetGenericErrorFunc(ctxt, pcmk__log_xmllib_err);
if (filename) {
uncompressed = !pcmk__ends_with_ext(filename, ".bz2");
}
if (pcmk__str_eq(filename, "-", pcmk__str_null_matches)) {
/* STDIN_FILENO == fileno(stdin) */
output = xmlCtxtReadFd(ctxt, STDIN_FILENO, "unknown.xml", NULL,
PCMK__XML_PARSE_OPTS_WITHOUT_RECOVER);
if (output == NULL) {
output = xmlCtxtReadFd(ctxt, STDIN_FILENO, "unknown.xml", NULL,
PCMK__XML_PARSE_OPTS_WITH_RECOVER);
if (output) {
crm_warn("Successfully recovered from XML errors "
"(note: a future release will treat this as a fatal failure)");
}
}
} else if (uncompressed) {
output = xmlCtxtReadFile(ctxt, filename, NULL,
PCMK__XML_PARSE_OPTS_WITHOUT_RECOVER);
if (output == NULL) {
output = xmlCtxtReadFile(ctxt, filename, NULL,
PCMK__XML_PARSE_OPTS_WITH_RECOVER);
if (output) {
crm_warn("Successfully recovered from XML errors "
"(note: a future release will treat this as a fatal failure)");
}
}
} else {
char *input = decompress_file(filename);
output = xmlCtxtReadDoc(ctxt, (pcmkXmlStr) input, NULL, NULL,
PCMK__XML_PARSE_OPTS_WITHOUT_RECOVER);
if (output == NULL) {
output = xmlCtxtReadDoc(ctxt, (pcmkXmlStr) input, NULL, NULL,
PCMK__XML_PARSE_OPTS_WITH_RECOVER);
if (output) {
crm_warn("Successfully recovered from XML errors "
"(note: a future release will treat this as a fatal failure)");
}
}
free(input);
}
if (output && (xml = xmlDocGetRootElement(output))) {
pcmk__strip_xml_text(xml);
}
last_error = xmlCtxtGetLastError(ctxt);
if (last_error && last_error->code != XML_ERR_OK) {
/* crm_abort(__FILE__,__func__,__LINE__, "last_error->code != XML_ERR_OK", TRUE, TRUE); */
/*
* http://xmlsoft.org/html/libxml-xmlerror.html#xmlErrorLevel
* http://xmlsoft.org/html/libxml-xmlerror.html#xmlParserErrors
*/
crm_err("Parsing failed (domain=%d, level=%d, code=%d): %s",
last_error->domain, last_error->level, last_error->code, last_error->message);
if (last_error && last_error->code != XML_ERR_OK) {
crm_err("Couldn't%s parse %s", xml ? " fully" : "", filename);
if (xml != NULL) {
crm_log_xml_err(xml, "Partial");
}
}
}
xmlFreeParserCtxt(ctxt);
return xml;
}
/*!
* \internal
* \brief Add a "last written" attribute to an XML element, set to current time
*
* \param[in,out] xe XML element to add attribute to
*
* \return Value that was set, or NULL on error
*/
const char *
pcmk__xe_add_last_written(xmlNode *xe)
{
char *now_s = pcmk__epoch2str(NULL, 0);
const char *result = NULL;
result = crm_xml_add(xe, XML_CIB_ATTR_WRITTEN,
pcmk__s(now_s, "Could not determine current time"));
free(now_s);
return result;
}
/*!
* \brief Sanitize a string so it is usable as an XML ID
*
* \param[in,out] id String to sanitize
*/
void
crm_xml_sanitize_id(char *id)
{
char *c;
for (c = id; *c; ++c) {
/* @TODO Sanitize more comprehensively */
switch (*c) {
case ':':
case '#':
*c = '.';
}
}
}
/*!
* \brief Set the ID of an XML element using a format
*
* \param[in,out] xml XML element
* \param[in] fmt printf-style format
* \param[in] ... any arguments required by format
*/
void
crm_xml_set_id(xmlNode *xml, const char *format, ...)
{
va_list ap;
int len = 0;
char *id = NULL;
/* equivalent to crm_strdup_printf() */
va_start(ap, format);
len = vasprintf(&id, format, ap);
va_end(ap);
CRM_ASSERT(len > 0);
crm_xml_sanitize_id(id);
crm_xml_add(xml, XML_ATTR_ID, id);
free(id);
}
/*!
* \internal
* \brief Write XML to a file stream
*
* \param[in] xml XML to write
* \param[in] filename Name of file being written (for logging only)
* \param[in,out] stream Open file stream corresponding to filename
* \param[in] compress Whether to compress XML before writing
* \param[out] nbytes Number of bytes written
*
* \return Standard Pacemaker return code
*/
static int
write_xml_stream(const xmlNode *xml, const char *filename, FILE *stream,
bool compress, unsigned int *nbytes)
{
int rc = pcmk_rc_ok;
char *buffer = NULL;
*nbytes = 0;
crm_log_xml_trace(xml, "writing");
buffer = dump_xml_formatted(xml);
CRM_CHECK(buffer && strlen(buffer),
crm_log_xml_warn(xml, "formatting failed");
rc = pcmk_rc_error;
goto bail);
if (compress) {
unsigned int in = 0;
BZFILE *bz_file = NULL;
rc = BZ_OK;
bz_file = BZ2_bzWriteOpen(&rc, stream, 5, 0, 30);
rc = pcmk__bzlib2rc(rc);
if (rc != pcmk_rc_ok) {
crm_warn("Not compressing %s: could not prepare file stream: %s "
CRM_XS " rc=%d", filename, pcmk_rc_str(rc), rc);
} else {
BZ2_bzWrite(&rc, bz_file, buffer, strlen(buffer));
rc = pcmk__bzlib2rc(rc);
if (rc != pcmk_rc_ok) {
crm_warn("Not compressing %s: could not compress data: %s "
CRM_XS " rc=%d errno=%d",
filename, pcmk_rc_str(rc), rc, errno);
}
}
if (rc == pcmk_rc_ok) {
BZ2_bzWriteClose(&rc, bz_file, 0, &in, nbytes);
rc = pcmk__bzlib2rc(rc);
if (rc != pcmk_rc_ok) {
crm_warn("Not compressing %s: could not write compressed data: %s "
CRM_XS " rc=%d errno=%d",
filename, pcmk_rc_str(rc), rc, errno);
*nbytes = 0; // retry without compression
} else {
crm_trace("Compressed XML for %s from %u bytes to %u",
filename, in, *nbytes);
}
}
rc = pcmk_rc_ok; // Either true, or we'll retry without compression
}
if (*nbytes == 0) {
rc = fprintf(stream, "%s", buffer);
if (rc < 0) {
rc = errno;
crm_perror(LOG_ERR, "writing %s", filename);
} else {
*nbytes = (unsigned int) rc;
rc = pcmk_rc_ok;
}
}
bail:
if (fflush(stream) != 0) {
rc = errno;
crm_perror(LOG_ERR, "flushing %s", filename);
}
/* Don't report error if the file does not support synchronization */
if (fsync(fileno(stream)) < 0 && errno != EROFS && errno != EINVAL) {
rc = errno;
crm_perror(LOG_ERR, "synchronizing %s", filename);
}
fclose(stream);
crm_trace("Saved %d bytes to %s as XML", *nbytes, filename);
free(buffer);
return rc;
}
/*!
* \brief Write XML to a file descriptor
*
* \param[in] xml XML to write
* \param[in] filename Name of file being written (for logging only)
* \param[in] fd Open file descriptor corresponding to filename
* \param[in] compress Whether to compress XML before writing
*
* \return Number of bytes written on success, -errno otherwise
*/
int
write_xml_fd(const xmlNode *xml, const char *filename, int fd,
gboolean compress)
{
FILE *stream = NULL;
unsigned int nbytes = 0;
int rc = pcmk_rc_ok;
CRM_CHECK((xml != NULL) && (fd > 0), return -EINVAL);
stream = fdopen(fd, "w");
if (stream == NULL) {
return -errno;
}
rc = write_xml_stream(xml, filename, stream, compress, &nbytes);
if (rc != pcmk_rc_ok) {
return pcmk_rc2legacy(rc);
}
return (int) nbytes;
}
/*!
* \brief Write XML to a file
*
* \param[in] xml XML to write
* \param[in] filename Name of file to write
* \param[in] compress Whether to compress XML before writing
*
* \return Number of bytes written on success, -errno otherwise
*/
int
write_xml_file(const xmlNode *xml, const char *filename, gboolean compress)
{
FILE *stream = NULL;
unsigned int nbytes = 0;
int rc = pcmk_rc_ok;
CRM_CHECK((xml != NULL) && (filename != NULL), return -EINVAL);
stream = fopen(filename, "w");
if (stream == NULL) {
return -errno;
}
rc = write_xml_stream(xml, filename, stream, compress, &nbytes);
if (rc != pcmk_rc_ok) {
return pcmk_rc2legacy(rc);
}
return (int) nbytes;
}
// Replace a portion of a dynamically allocated string (reallocating memory)
static char *
replace_text(char *text, int start, size_t *length, const char *replace)
{
size_t offset = strlen(replace) - 1; // We have space for 1 char already
*length += offset;
text = pcmk__realloc(text, *length);
for (size_t lpc = (*length) - 1; lpc > (start + offset); lpc--) {
text[lpc] = text[lpc - offset];
}
memcpy(text + start, replace, offset + 1);
return text;
}
/*!
* \brief Replace special characters with their XML escape sequences
*
* \param[in] text Text to escape
*
* \return Newly allocated string equivalent to \p text but with special
* characters replaced with XML escape sequences (or NULL if \p text
* is NULL)
*/
char *
crm_xml_escape(const char *text)
{
size_t length;
char *copy;
/*
* When xmlCtxtReadDoc() parses &lt; and friends in a
* value, it converts them to their human readable
* form.
*
* If one uses xmlNodeDump() to convert it back to a
* string, all is well, because special characters are
* converted back to their escape sequences.
*
* However xmlNodeDump() is randomly dog slow, even with the same
* input. So we need to replicate the escaping in our custom
* version so that the result can be re-parsed by xmlCtxtReadDoc()
* when necessary.
*/
if (text == NULL) {
return NULL;
}
length = 1 + strlen(text);
copy = strdup(text);
CRM_ASSERT(copy != NULL);
for (size_t index = 0; index < length; index++) {
if(copy[index] & 0x80 && copy[index+1] & 0x80){
index++;
break;
}
switch (copy[index]) {
case 0:
break;
case '<':
copy = replace_text(copy, index, &length, "&lt;");
break;
case '>':
copy = replace_text(copy, index, &length, "&gt;");
break;
case '"':
copy = replace_text(copy, index, &length, "&quot;");
break;
case '\'':
copy = replace_text(copy, index, &length, "&apos;");
break;
case '&':
copy = replace_text(copy, index, &length, "&amp;");
break;
case '\t':
/* Might as well just expand to a few spaces... */
copy = replace_text(copy, index, &length, " ");
break;
case '\n':
copy = replace_text(copy, index, &length, "\\n");
break;
case '\r':
copy = replace_text(copy, index, &length, "\\r");
break;
default:
/* Check for and replace non-printing characters with their octal equivalent */
if(copy[index] < ' ' || copy[index] > '~') {
char *replace = crm_strdup_printf("\\%.3o", copy[index]);
copy = replace_text(copy, index, &length, replace);
free(replace);
}
}
}
return copy;
}
/*!
* \internal
* \brief Append a string representation of an XML element to a buffer
*
* \param[in] data XML whose representation to append
* \param[in] options Group of \p pcmk__xml_fmt_options flags
* \param[in,out] buffer Where to append the content (must not be \p NULL)
* \param[in] depth Current indentation level
*/
static void
dump_xml_element(const xmlNode *data, uint32_t options, GString *buffer,
int depth)
{
bool pretty = pcmk_is_set(options, pcmk__xml_fmt_pretty);
bool filtered = pcmk_is_set(options, pcmk__xml_fmt_filtered);
int spaces = pretty? (2 * depth) : 0;
for (int lpc = 0; lpc < spaces; lpc++) {
g_string_append_c(buffer, ' ');
}
pcmk__g_strcat(buffer, "<", data->name, NULL);
for (const xmlAttr *attr = pcmk__xe_first_attr(data); attr != NULL;
attr = attr->next) {
if (!filtered || !pcmk__xa_filterable((const char *) (attr->name))) {
pcmk__dump_xml_attr(attr, buffer);
}
}
if (data->children == NULL) {
g_string_append(buffer, "/>");
} else {
g_string_append_c(buffer, '>');
}
if (pretty) {
g_string_append_c(buffer, '\n');
}
if (data->children) {
for (const xmlNode *child = data->children; child != NULL;
child = child->next) {
pcmk__xml2text(child, options, buffer, depth + 1);
}
for (int lpc = 0; lpc < spaces; lpc++) {
g_string_append_c(buffer, ' ');
}
pcmk__g_strcat(buffer, "</", data->name, ">", NULL);
if (pretty) {
g_string_append_c(buffer, '\n');
}
}
}
/*!
* \internal
* \brief Append XML text content to a buffer
*
* \param[in] data XML whose content to append
* \param[in] options Group of \p xml_log_options flags
* \param[in,out] buffer Where to append the content (must not be \p NULL)
* \param[in] depth Current indentation level
*/
static void
dump_xml_text(const xmlNode *data, uint32_t options, GString *buffer,
int depth)
{
/* @COMPAT: Remove when log_data_element() is removed. There are no internal
* code paths to this, except through the deprecated log_data_element().
*/
bool pretty = pcmk_is_set(options, pcmk__xml_fmt_pretty);
int spaces = pretty? (2 * depth) : 0;
for (int lpc = 0; lpc < spaces; lpc++) {
g_string_append_c(buffer, ' ');
}
g_string_append(buffer, (const gchar *) data->content);
if (pretty) {
g_string_append_c(buffer, '\n');
}
}
/*!
* \internal
* \brief Append XML CDATA content to a buffer
*
* \param[in] data XML whose content to append
* \param[in] options Group of \p pcmk__xml_fmt_options flags
* \param[in,out] buffer Where to append the content (must not be \p NULL)
* \param[in] depth Current indentation level
*/
static void
dump_xml_cdata(const xmlNode *data, uint32_t options, GString *buffer,
int depth)
{
bool pretty = pcmk_is_set(options, pcmk__xml_fmt_pretty);
int spaces = pretty? (2 * depth) : 0;
for (int lpc = 0; lpc < spaces; lpc++) {
g_string_append_c(buffer, ' ');
}
pcmk__g_strcat(buffer, "<![CDATA[", (const char *) data->content, "]]>",
NULL);
if (pretty) {
g_string_append_c(buffer, '\n');
}
}
/*!
* \internal
* \brief Append an XML comment to a buffer
*
* \param[in] data XML whose content to append
* \param[in] options Group of \p pcmk__xml_fmt_options flags
* \param[in,out] buffer Where to append the content (must not be \p NULL)
* \param[in] depth Current indentation level
*/
static void
dump_xml_comment(const xmlNode *data, uint32_t options, GString *buffer,
int depth)
{
bool pretty = pcmk_is_set(options, pcmk__xml_fmt_pretty);
int spaces = pretty? (2 * depth) : 0;
for (int lpc = 0; lpc < spaces; lpc++) {
g_string_append_c(buffer, ' ');
}
pcmk__g_strcat(buffer, "<!--", (const char *) data->content, "-->", NULL);
if (pretty) {
g_string_append_c(buffer, '\n');
}
}
/*!
* \internal
* \brief Get a string representation of an XML element type
*
* \param[in] type XML element type
*
* \return String representation of \p type
*/
static const char *
xml_element_type2str(xmlElementType type)
{
static const char *const element_type_names[] = {
[XML_ELEMENT_NODE] = "element",
[XML_ATTRIBUTE_NODE] = "attribute",
[XML_TEXT_NODE] = "text",
[XML_CDATA_SECTION_NODE] = "CDATA section",
[XML_ENTITY_REF_NODE] = "entity reference",
[XML_ENTITY_NODE] = "entity",
[XML_PI_NODE] = "PI",
[XML_COMMENT_NODE] = "comment",
[XML_DOCUMENT_NODE] = "document",
[XML_DOCUMENT_TYPE_NODE] = "document type",
[XML_DOCUMENT_FRAG_NODE] = "document fragment",
[XML_NOTATION_NODE] = "notation",
[XML_HTML_DOCUMENT_NODE] = "HTML document",
[XML_DTD_NODE] = "DTD",
[XML_ELEMENT_DECL] = "element declaration",
[XML_ATTRIBUTE_DECL] = "attribute declaration",
[XML_ENTITY_DECL] = "entity declaration",
[XML_NAMESPACE_DECL] = "namespace declaration",
[XML_XINCLUDE_START] = "XInclude start",
[XML_XINCLUDE_END] = "XInclude end",
};
if ((type < 0) || (type >= PCMK__NELEM(element_type_names))) {
return "unrecognized type";
}
return element_type_names[type];
}
/*!
* \internal
* \brief Create a text representation of an XML object
*
* \param[in] data XML to convert
* \param[in] options Group of \p pcmk__xml_fmt_options flags
* \param[in,out] buffer Where to store the text (must not be \p NULL)
* \param[in] depth Current indentation level
*/
void
pcmk__xml2text(const xmlNode *data, uint32_t options, GString *buffer,
int depth)
{
if (data == NULL) {
crm_trace("Nothing to dump");
return;
}
CRM_ASSERT(buffer != NULL);
CRM_CHECK(depth >= 0, depth = 0);
switch(data->type) {
case XML_ELEMENT_NODE:
/* Handle below */
dump_xml_element(data, options, buffer, depth);
break;
case XML_TEXT_NODE:
if (pcmk_is_set(options, pcmk__xml_fmt_text)) {
dump_xml_text(data, options, buffer, depth);
}
break;
case XML_COMMENT_NODE:
dump_xml_comment(data, options, buffer, depth);
break;
case XML_CDATA_SECTION_NODE:
dump_xml_cdata(data, options, buffer, depth);
break;
default:
crm_warn("Cannot convert XML %s node to text " CRM_XS " type=%d",
xml_element_type2str(data->type), data->type);
break;
}
}
char *
dump_xml_formatted_with_text(const xmlNode *xml)
{
/* libxml's xmlNodeDumpOutput() would work here since we're not specifically
* filtering out any nodes. However, use pcmk__xml2text() for consistency,
* to escape attribute values, and to allow a const argument.
*/
char *buffer = NULL;
GString *g_buffer = g_string_sized_new(1024);
pcmk__xml2text(xml, pcmk__xml_fmt_pretty|pcmk__xml_fmt_text, g_buffer, 0);
pcmk__str_update(&buffer, g_buffer->str);
g_string_free(g_buffer, TRUE);
return buffer;
}
char *
dump_xml_formatted(const xmlNode *xml)
{
char *buffer = NULL;
GString *g_buffer = g_string_sized_new(1024);
pcmk__xml2text(xml, pcmk__xml_fmt_pretty, g_buffer, 0);
pcmk__str_update(&buffer, g_buffer->str);
g_string_free(g_buffer, TRUE);
return buffer;
}
char *
dump_xml_unformatted(const xmlNode *xml)
{
char *buffer = NULL;
GString *g_buffer = g_string_sized_new(1024);
pcmk__xml2text(xml, 0, g_buffer, 0);
pcmk__str_update(&buffer, g_buffer->str);
g_string_free(g_buffer, TRUE);
return buffer;
}
int
pcmk__xml2fd(int fd, xmlNode *cur)
{
bool success;
xmlOutputBuffer *fd_out = xmlOutputBufferCreateFd(fd, NULL);
CRM_ASSERT(fd_out != NULL);
xmlNodeDumpOutput(fd_out, cur->doc, cur, 0, pcmk__xml_fmt_pretty, NULL);
success = xmlOutputBufferWrite(fd_out, sizeof("\n") - 1, "\n") != -1;
success = xmlOutputBufferClose(fd_out) != -1 && success;
if (!success) {
return EIO;
}
fsync(fd);
return pcmk_rc_ok;
}
void
xml_remove_prop(xmlNode * obj, const char *name)
{
if (crm_element_value(obj, name) == NULL) {
return;
}
if (pcmk__check_acl(obj, NULL, pcmk__xf_acl_write) == FALSE) {
crm_trace("Cannot remove %s from %s", name, obj->name);
} else if (pcmk__tracking_xml_changes(obj, FALSE)) {
/* Leave in place (marked for removal) until after the diff is calculated */
xmlAttr *attr = xmlHasProp(obj, (pcmkXmlStr) name);
xml_node_private_t *nodepriv = attr->_private;
set_parent_flag(obj, pcmk__xf_dirty);
pcmk__set_xml_flags(nodepriv, pcmk__xf_deleted);
} else {
xmlUnsetProp(obj, (pcmkXmlStr) name);
}
}
void
save_xml_to_file(const xmlNode *xml, const char *desc, const char *filename)
{
char *f = NULL;
if (filename == NULL) {
char *uuid = crm_generate_uuid();
f = crm_strdup_printf("%s/%s", pcmk__get_tmpdir(), uuid);
filename = f;
free(uuid);
}
crm_info("Saving %s to %s", desc, filename);
write_xml_file(xml, filename, FALSE);
free(f);
}
/*!
* \internal
* \brief Set a flag on all attributes of an XML element
*
* \param[in,out] xml XML node to set flags on
* \param[in] flag XML private flag to set
*/
static void
set_attrs_flag(xmlNode *xml, enum xml_private_flags flag)
{
for (xmlAttr *attr = pcmk__xe_first_attr(xml); attr; attr = attr->next) {
pcmk__set_xml_flags((xml_node_private_t *) (attr->_private), flag);
}
}
/*!
* \internal
* \brief Add an XML attribute to a node, marked as deleted
*
* When calculating XML changes, we need to know when an attribute has been
* deleted. Add the attribute back to the new XML, so that we can check the
* removal against ACLs, and mark it as deleted for later removal after
* differences have been calculated.
*
* \param[in,out] new_xml XML to modify
* \param[in] element Name of XML element that changed (for logging)
* \param[in] attr_name Name of attribute that was deleted
* \param[in] old_value Value of attribute that was deleted
*/
static void
mark_attr_deleted(xmlNode *new_xml, const char *element, const char *attr_name,
const char *old_value)
{
xml_doc_private_t *docpriv = new_xml->doc->_private;
xmlAttr *attr = NULL;
xml_node_private_t *nodepriv;
// Prevent the dirty flag being set recursively upwards
pcmk__clear_xml_flags(docpriv, pcmk__xf_tracking);
// Restore the old value (and the tracking flag)
attr = xmlSetProp(new_xml, (pcmkXmlStr) attr_name, (pcmkXmlStr) old_value);
pcmk__set_xml_flags(docpriv, pcmk__xf_tracking);
// Reset flags (so the attribute doesn't appear as newly created)
nodepriv = attr->_private;
nodepriv->flags = 0;
// Check ACLs and mark restored value for later removal
xml_remove_prop(new_xml, attr_name);
crm_trace("XML attribute %s=%s was removed from %s",
attr_name, old_value, element);
}
/*
* \internal
* \brief Check ACLs for a changed XML attribute
*/
static void
mark_attr_changed(xmlNode *new_xml, const char *element, const char *attr_name,
const char *old_value)
{
char *vcopy = crm_element_value_copy(new_xml, attr_name);
crm_trace("XML attribute %s was changed from '%s' to '%s' in %s",
attr_name, old_value, vcopy, element);
// Restore the original value
xmlSetProp(new_xml, (pcmkXmlStr) attr_name, (pcmkXmlStr) old_value);
// Change it back to the new value, to check ACLs
crm_xml_add(new_xml, attr_name, vcopy);
free(vcopy);
}
/*!
* \internal
* \brief Mark an XML attribute as having changed position
*
* \param[in,out] new_xml XML to modify
* \param[in] element Name of XML element that changed (for logging)
* \param[in,out] old_attr Attribute that moved, in original XML
* \param[in,out] new_attr Attribute that moved, in \p new_xml
* \param[in] p_old Ordinal position of \p old_attr in original XML
* \param[in] p_new Ordinal position of \p new_attr in \p new_xml
*/
static void
mark_attr_moved(xmlNode *new_xml, const char *element, xmlAttr *old_attr,
xmlAttr *new_attr, int p_old, int p_new)
{
xml_node_private_t *nodepriv = new_attr->_private;
crm_trace("XML attribute %s moved from position %d to %d in %s",
old_attr->name, p_old, p_new, element);
// Mark document, element, and all element's parents as changed
pcmk__mark_xml_node_dirty(new_xml);
// Mark attribute as changed
pcmk__set_xml_flags(nodepriv, pcmk__xf_dirty|pcmk__xf_moved);
nodepriv = (p_old > p_new)? old_attr->_private : new_attr->_private;
pcmk__set_xml_flags(nodepriv, pcmk__xf_skip);
}
/*!
* \internal
* \brief Calculate differences in all previously existing XML attributes
*
* \param[in,out] old_xml Original XML to compare
* \param[in,out] new_xml New XML to compare
*/
static void
xml_diff_old_attrs(xmlNode *old_xml, xmlNode *new_xml)
{
xmlAttr *attr_iter = pcmk__xe_first_attr(old_xml);
while (attr_iter != NULL) {
const char *name = (const char *) attr_iter->name;
xmlAttr *old_attr = attr_iter;
xmlAttr *new_attr = xmlHasProp(new_xml, attr_iter->name);
const char *old_value = pcmk__xml_attr_value(attr_iter);
attr_iter = attr_iter->next;
if (new_attr == NULL) {
mark_attr_deleted(new_xml, (const char *) old_xml->name, name,
old_value);
} else {
xml_node_private_t *nodepriv = new_attr->_private;
int new_pos = pcmk__xml_position((xmlNode*) new_attr,
pcmk__xf_skip);
int old_pos = pcmk__xml_position((xmlNode*) old_attr,
pcmk__xf_skip);
const char *new_value = crm_element_value(new_xml, name);
// This attribute isn't new
pcmk__clear_xml_flags(nodepriv, pcmk__xf_created);
if (strcmp(new_value, old_value) != 0) {
mark_attr_changed(new_xml, (const char *) old_xml->name, name,
old_value);
} else if ((old_pos != new_pos)
&& !pcmk__tracking_xml_changes(new_xml, TRUE)) {
mark_attr_moved(new_xml, (const char *) old_xml->name,
old_attr, new_attr, old_pos, new_pos);
}
}
}
}
/*!
* \internal
* \brief Check all attributes in new XML for creation
*
* For each of a given XML element's attributes marked as newly created, accept
* (and mark as dirty) or reject the creation according to ACLs.
*
* \param[in,out] new_xml XML to check
*/
static void
mark_created_attrs(xmlNode *new_xml)
{
xmlAttr *attr_iter = pcmk__xe_first_attr(new_xml);
while (attr_iter != NULL) {
xmlAttr *new_attr = attr_iter;
xml_node_private_t *nodepriv = attr_iter->_private;
attr_iter = attr_iter->next;
if (pcmk_is_set(nodepriv->flags, pcmk__xf_created)) {
const char *attr_name = (const char *) new_attr->name;
crm_trace("Created new attribute %s=%s in %s",
attr_name, pcmk__xml_attr_value(new_attr),
new_xml->name);
/* Check ACLs (we can't use the remove-then-create trick because it
* would modify the attribute position).
*/
if (pcmk__check_acl(new_xml, attr_name, pcmk__xf_acl_write)) {
pcmk__mark_xml_attr_dirty(new_attr);
} else {
// Creation was not allowed, so remove the attribute
xmlUnsetProp(new_xml, new_attr->name);
}
}
}
}
/*!
* \internal
* \brief Calculate differences in attributes between two XML nodes
*
* \param[in,out] old_xml Original XML to compare
* \param[in,out] new_xml New XML to compare
*/
static void
xml_diff_attrs(xmlNode *old_xml, xmlNode *new_xml)
{
set_attrs_flag(new_xml, pcmk__xf_created); // cleared later if not really new
xml_diff_old_attrs(old_xml, new_xml);
mark_created_attrs(new_xml);
}
/*!
* \internal
* \brief Add an XML child element to a node, marked as deleted
*
* When calculating XML changes, we need to know when a child element has been
* deleted. Add the child back to the new XML, so that we can check the removal
* against ACLs, and mark it as deleted for later removal after differences have
* been calculated.
*
* \param[in,out] old_child Child element from original XML
* \param[in,out] new_parent New XML to add marked copy to
*/
static void
mark_child_deleted(xmlNode *old_child, xmlNode *new_parent)
{
// Re-create the child element so we can check ACLs
xmlNode *candidate = add_node_copy(new_parent, old_child);
// Clear flags on new child and its children
reset_xml_node_flags(candidate);
// Check whether ACLs allow the deletion
pcmk__apply_acl(xmlDocGetRootElement(candidate->doc));
// Remove the child again (which will track it in document's deleted_objs)
free_xml_with_position(candidate,
pcmk__xml_position(old_child, pcmk__xf_skip));
if (pcmk__xml_match(new_parent, old_child, true) == NULL) {
pcmk__set_xml_flags((xml_node_private_t *) (old_child->_private),
pcmk__xf_skip);
}
}
static void
mark_child_moved(xmlNode *old_child, xmlNode *new_parent, xmlNode *new_child,
int p_old, int p_new)
{
xml_node_private_t *nodepriv = new_child->_private;
crm_trace("Child element %s with id='%s' moved from position %d to %d under %s",
new_child->name, (ID(new_child)? ID(new_child) : "<no id>"),
p_old, p_new, new_parent->name);
pcmk__mark_xml_node_dirty(new_parent);
pcmk__set_xml_flags(nodepriv, pcmk__xf_moved);
if (p_old > p_new) {
nodepriv = old_child->_private;
} else {
nodepriv = new_child->_private;
}
pcmk__set_xml_flags(nodepriv, pcmk__xf_skip);
}
// Given original and new XML, mark new XML portions that have changed
static void
mark_xml_changes(xmlNode *old_xml, xmlNode *new_xml, bool check_top)
{
xmlNode *cIter = NULL;
xml_node_private_t *nodepriv = NULL;
CRM_CHECK(new_xml != NULL, return);
if (old_xml == NULL) {
pcmk__mark_xml_created(new_xml);
pcmk__apply_creation_acl(new_xml, check_top);
return;
}
nodepriv = new_xml->_private;
CRM_CHECK(nodepriv != NULL, return);
if(nodepriv->flags & pcmk__xf_processed) {
/* Avoid re-comparing nodes */
return;
}
pcmk__set_xml_flags(nodepriv, pcmk__xf_processed);
xml_diff_attrs(old_xml, new_xml);
// Check for differences in the original children
for (cIter = pcmk__xml_first_child(old_xml); cIter != NULL; ) {
xmlNode *old_child = cIter;
xmlNode *new_child = pcmk__xml_match(new_xml, cIter, true);
cIter = pcmk__xml_next(cIter);
if(new_child) {
mark_xml_changes(old_child, new_child, TRUE);
} else {
mark_child_deleted(old_child, new_xml);
}
}
// Check for moved or created children
for (cIter = pcmk__xml_first_child(new_xml); cIter != NULL; ) {
xmlNode *new_child = cIter;
xmlNode *old_child = pcmk__xml_match(old_xml, cIter, true);
cIter = pcmk__xml_next(cIter);
if(old_child == NULL) {
// This is a newly created child
nodepriv = new_child->_private;
pcmk__set_xml_flags(nodepriv, pcmk__xf_skip);
mark_xml_changes(old_child, new_child, TRUE);
} else {
/* Check for movement, we already checked for differences */
int p_new = pcmk__xml_position(new_child, pcmk__xf_skip);
int p_old = pcmk__xml_position(old_child, pcmk__xf_skip);
if(p_old != p_new) {
mark_child_moved(old_child, new_xml, new_child, p_old, p_new);
}
}
}
}
void
xml_calculate_significant_changes(xmlNode *old_xml, xmlNode *new_xml)
{
pcmk__set_xml_doc_flag(new_xml, pcmk__xf_lazy);
xml_calculate_changes(old_xml, new_xml);
}
// Called functions may set the \p pcmk__xf_skip flag on parts of \p old_xml
void
xml_calculate_changes(xmlNode *old_xml, xmlNode *new_xml)
{
CRM_CHECK((old_xml != NULL) && (new_xml != NULL)
&& pcmk__xe_is(old_xml, (const char *) new_xml->name)
&& pcmk__str_eq(ID(old_xml), ID(new_xml), pcmk__str_none),
return);
if(xml_tracking_changes(new_xml) == FALSE) {
xml_track_changes(new_xml, NULL, NULL, FALSE);
}
mark_xml_changes(old_xml, new_xml, FALSE);
}
gboolean
can_prune_leaf(xmlNode * xml_node)
{
xmlNode *cIter = NULL;
gboolean can_prune = TRUE;
CRM_CHECK(xml_node != NULL, return FALSE);
if (pcmk__strcase_any_of((const char *) xml_node->name,
XML_TAG_RESOURCE_REF, XML_CIB_TAG_OBJ_REF,
XML_ACL_TAG_ROLE_REF, XML_ACL_TAG_ROLE_REFv1,
NULL)) {
return FALSE;
}
for (xmlAttrPtr a = pcmk__xe_first_attr(xml_node); a != NULL; a = a->next) {
const char *p_name = (const char *) a->name;
if (strcmp(p_name, XML_ATTR_ID) == 0) {
continue;
}
can_prune = FALSE;
}
cIter = pcmk__xml_first_child(xml_node);
while (cIter) {
xmlNode *child = cIter;
cIter = pcmk__xml_next(cIter);
if (can_prune_leaf(child)) {
free_xml(child);
} else {
can_prune = FALSE;
}
}
return can_prune;
}
/*!
* \internal
* \brief Find a comment with matching content in specified XML
*
* \param[in] root XML to search
* \param[in] search_comment Comment whose content should be searched for
* \param[in] exact If true, comment must also be at same position
*/
xmlNode *
pcmk__xc_match(const xmlNode *root, const xmlNode *search_comment, bool exact)
{
xmlNode *a_child = NULL;
int search_offset = pcmk__xml_position(search_comment, pcmk__xf_skip);
CRM_CHECK(search_comment->type == XML_COMMENT_NODE, return NULL);
for (a_child = pcmk__xml_first_child(root); a_child != NULL;
a_child = pcmk__xml_next(a_child)) {
if (exact) {
int offset = pcmk__xml_position(a_child, pcmk__xf_skip);
xml_node_private_t *nodepriv = a_child->_private;
if (offset < search_offset) {
continue;
} else if (offset > search_offset) {
return NULL;
}
if (pcmk_is_set(nodepriv->flags, pcmk__xf_skip)) {
continue;
}
}
if (a_child->type == XML_COMMENT_NODE
&& pcmk__str_eq((const char *)a_child->content, (const char *)search_comment->content, pcmk__str_casei)) {
return a_child;
} else if (exact) {
return NULL;
}
}
return NULL;
}
/*!
* \internal
* \brief Make one XML comment match another (in content)
*
* \param[in,out] parent If \p target is NULL and this is not, add or update
* comment child of this XML node that matches \p update
* \param[in,out] target If not NULL, update this XML comment node
* \param[in] update Make comment content match this (must not be NULL)
*
* \note At least one of \parent and \target must be non-NULL
*/
void
pcmk__xc_update(xmlNode *parent, xmlNode *target, xmlNode *update)
{
CRM_CHECK(update != NULL, return);
CRM_CHECK(update->type == XML_COMMENT_NODE, return);
if (target == NULL) {
target = pcmk__xc_match(parent, update, false);
}
if (target == NULL) {
add_node_copy(parent, update);
} else if (!pcmk__str_eq((const char *)target->content, (const char *)update->content, pcmk__str_casei)) {
xmlFree(target->content);
target->content = xmlStrdup(update->content);
}
}
/*!
* \internal
* \brief Make one XML tree match another (in children and attributes)
*
* \param[in,out] parent If \p target is NULL and this is not, add or update
* child of this XML node that matches \p update
* \param[in,out] target If not NULL, update this XML
* \param[in] update Make the desired XML match this (must not be NULL)
* \param[in] as_diff If false, expand "++" when making attributes match
*
* \note At least one of \p parent and \p target must be non-NULL
*/
void
pcmk__xml_update(xmlNode *parent, xmlNode *target, xmlNode *update,
bool as_diff)
{
xmlNode *a_child = NULL;
const char *object_name = NULL,
*object_href = NULL,
*object_href_val = NULL;
#if XML_PARSER_DEBUG
crm_log_xml_trace(update, "update:");
crm_log_xml_trace(target, "target:");
#endif
CRM_CHECK(update != NULL, return);
if (update->type == XML_COMMENT_NODE) {
pcmk__xc_update(parent, target, update);
return;
}
object_name = (const char *) update->name;
object_href_val = ID(update);
if (object_href_val != NULL) {
object_href = XML_ATTR_ID;
} else {
object_href_val = crm_element_value(update, XML_ATTR_IDREF);
object_href = (object_href_val == NULL) ? NULL : XML_ATTR_IDREF;
}
CRM_CHECK(object_name != NULL, return);
CRM_CHECK(target != NULL || parent != NULL, return);
if (target == NULL) {
target = pcmk__xe_match(parent, object_name,
object_href, object_href_val);
}
if (target == NULL) {
target = create_xml_node(parent, object_name);
CRM_CHECK(target != NULL, return);
#if XML_PARSER_DEBUG
crm_trace("Added <%s%s%s%s%s/>", pcmk__s(object_name, "<null>"),
object_href ? " " : "",
object_href ? object_href : "",
object_href ? "=" : "",
object_href ? object_href_val : "");
} else {
crm_trace("Found node <%s%s%s%s%s/> to update",
pcmk__s(object_name, "<null>"),
object_href ? " " : "",
object_href ? object_href : "",
object_href ? "=" : "",
object_href ? object_href_val : "");
#endif
}
CRM_CHECK(pcmk__xe_is(target, (const char *) update->name), return);
if (as_diff == FALSE) {
/* So that expand_plus_plus() gets called */
copy_in_properties(target, update);
} else {
/* No need for expand_plus_plus(), just raw speed */
for (xmlAttrPtr a = pcmk__xe_first_attr(update); a != NULL;
a = a->next) {
const char *p_value = pcmk__xml_attr_value(a);
/* Remove it first so the ordering of the update is preserved */
xmlUnsetProp(target, a->name);
xmlSetProp(target, a->name, (pcmkXmlStr) p_value);
}
}
for (a_child = pcmk__xml_first_child(update); a_child != NULL;
a_child = pcmk__xml_next(a_child)) {
#if XML_PARSER_DEBUG
crm_trace("Updating child <%s%s%s%s%s/>",
pcmk__s(object_name, "<null>"),
object_href ? " " : "",
object_href ? object_href : "",
object_href ? "=" : "",
object_href ? object_href_val : "");
#endif
pcmk__xml_update(target, NULL, a_child, as_diff);
}
#if XML_PARSER_DEBUG
crm_trace("Finished with <%s%s%s%s%s/>", pcmk__s(object_name, "<null>"),
object_href ? " " : "",
object_href ? object_href : "",
object_href ? "=" : "",
object_href ? object_href_val : "");
#endif
}
gboolean
update_xml_child(xmlNode * child, xmlNode * to_update)
{
gboolean can_update = TRUE;
xmlNode *child_of_child = NULL;
CRM_CHECK(child != NULL, return FALSE);
CRM_CHECK(to_update != NULL, return FALSE);
if (!pcmk__xe_is(to_update, (const char *) child->name)) {
can_update = FALSE;
} else if (!pcmk__str_eq(ID(to_update), ID(child), pcmk__str_none)) {
can_update = FALSE;
} else if (can_update) {
#if XML_PARSER_DEBUG
crm_log_xml_trace(child, "Update match found...");
#endif
pcmk__xml_update(NULL, child, to_update, false);
}
for (child_of_child = pcmk__xml_first_child(child); child_of_child != NULL;
child_of_child = pcmk__xml_next(child_of_child)) {
/* only update the first one */
if (can_update) {
break;
}
can_update = update_xml_child(child_of_child, to_update);
}
return can_update;
}
int
find_xml_children(xmlNode ** children, xmlNode * root,
const char *tag, const char *field, const char *value, gboolean search_matches)
{
int match_found = 0;
CRM_CHECK(root != NULL, return FALSE);
CRM_CHECK(children != NULL, return FALSE);
if ((tag != NULL) && !pcmk__xe_is(root, tag)) {
} else if (value != NULL && !pcmk__str_eq(value, crm_element_value(root, field), pcmk__str_casei)) {
} else {
if (*children == NULL) {
*children = create_xml_node(NULL, __func__);
}
add_node_copy(*children, root);
match_found = 1;
}
if (search_matches || match_found == 0) {
xmlNode *child = NULL;
for (child = pcmk__xml_first_child(root); child != NULL;
child = pcmk__xml_next(child)) {
match_found += find_xml_children(children, child, tag, field, value, search_matches);
}
}
return match_found;
}
gboolean
replace_xml_child(xmlNode * parent, xmlNode * child, xmlNode * update, gboolean delete_only)
{
gboolean can_delete = FALSE;
xmlNode *child_of_child = NULL;
const char *up_id = NULL;
const char *child_id = NULL;
const char *right_val = NULL;
CRM_CHECK(child != NULL, return FALSE);
CRM_CHECK(update != NULL, return FALSE);
up_id = ID(update);
child_id = ID(child);
if (up_id == NULL || (child_id && strcmp(child_id, up_id) == 0)) {
can_delete = TRUE;
}
if (!pcmk__xe_is(update, (const char *) child->name)) {
can_delete = FALSE;
}
if (can_delete && delete_only) {
for (xmlAttrPtr a = pcmk__xe_first_attr(update); a != NULL;
a = a->next) {
const char *p_name = (const char *) a->name;
const char *p_value = pcmk__xml_attr_value(a);
right_val = crm_element_value(child, p_name);
if (!pcmk__str_eq(p_value, right_val, pcmk__str_casei)) {
can_delete = FALSE;
}
}
}
if (can_delete && parent != NULL) {
crm_log_xml_trace(child, "Delete match found...");
if (delete_only || update == NULL) {
free_xml(child);
} else {
xmlNode *old = child;
xmlNode *new = xmlCopyNode(update, 1);
CRM_ASSERT(new != NULL);
// May be unnecessary but avoids slight changes to some test outputs
reset_xml_node_flags(new);
old = xmlReplaceNode(old, new);
if (xml_tracking_changes(new)) {
// Replaced sections may have included relevant ACLs
pcmk__apply_acl(new);
}
xml_calculate_changes(old, new);
xmlFreeNode(old);
}
return TRUE;
} else if (can_delete) {
crm_log_xml_debug(child, "Cannot delete the search root");
can_delete = FALSE;
}
child_of_child = pcmk__xml_first_child(child);
while (child_of_child) {
xmlNode *next = pcmk__xml_next(child_of_child);
can_delete = replace_xml_child(child, child_of_child, update, delete_only);
/* only delete the first one */
if (can_delete) {
child_of_child = NULL;
} else {
child_of_child = next;
}
}
return can_delete;
}
xmlNode *
sorted_xml(xmlNode *input, xmlNode *parent, gboolean recursive)
{
xmlNode *child = NULL;
GSList *nvpairs = NULL;
xmlNode *result = NULL;
CRM_CHECK(input != NULL, return NULL);
result = create_xml_node(parent, (const char *) input->name);
nvpairs = pcmk_xml_attrs2nvpairs(input);
nvpairs = pcmk_sort_nvpairs(nvpairs);
pcmk_nvpairs2xml_attrs(nvpairs, result);
pcmk_free_nvpairs(nvpairs);
for (child = pcmk__xml_first_child(input); child != NULL;
child = pcmk__xml_next(child)) {
if (recursive) {
sorted_xml(child, result, recursive);
} else {
add_node_copy(result, child);
}
}
return result;
}
xmlNode *
first_named_child(const xmlNode *parent, const char *name)
{
xmlNode *match = NULL;
for (match = pcmk__xe_first_child(parent); match != NULL;
match = pcmk__xe_next(match)) {
/*
* name == NULL gives first child regardless of name; this is
* semantically incorrect in this function, but may be necessary
* due to prior use of xml_child_iter_filter
*/
if (pcmk__str_eq(name, (const char *)match->name, pcmk__str_null_matches)) {
return match;
}
}
return NULL;
}
/*!
* \brief Get next instance of same XML tag
*
* \param[in] sibling XML tag to start from
*
* \return Next sibling XML tag with same name
*/
xmlNode *
crm_next_same_xml(const xmlNode *sibling)
{
xmlNode *match = pcmk__xe_next(sibling);
while (match != NULL) {
if (pcmk__xe_is(match, (const char *) sibling->name)) {
return match;
}
match = pcmk__xe_next(match);
}
return NULL;
}
void
crm_xml_init(void)
{
static bool init = true;
if(init) {
init = false;
/* The default allocator XML_BUFFER_ALLOC_EXACT does far too many
* pcmk__realloc()s and it can take upwards of 18 seconds (yes, seconds)
* to dump a 28kb tree which XML_BUFFER_ALLOC_DOUBLEIT can do in
* less than 1 second.
*/
xmlSetBufferAllocationScheme(XML_BUFFER_ALLOC_DOUBLEIT);
/* Populate and free the _private field when nodes are created and destroyed */
xmlDeregisterNodeDefault(free_private_data);
xmlRegisterNodeDefault(new_private_data);
crm_schema_init();
}
}
void
crm_xml_cleanup(void)
{
crm_schema_cleanup();
xmlCleanupParser();
}
#define XPATH_MAX 512
xmlNode *
expand_idref(xmlNode * input, xmlNode * top)
{
const char *ref = NULL;
xmlNode *result = input;
if (result == NULL) {
return NULL;
} else if (top == NULL) {
top = input;
}
ref = crm_element_value(result, XML_ATTR_IDREF);
if (ref != NULL) {
char *xpath_string = crm_strdup_printf("//%s[@" XML_ATTR_ID "='%s']",
result->name, ref);
result = get_xpath_object(xpath_string, top, LOG_ERR);
if (result == NULL) {
char *nodePath = (char *)xmlGetNodePath(top);
crm_err("No match for %s found in %s: Invalid configuration",
xpath_string, pcmk__s(nodePath, "unrecognizable path"));
free(nodePath);
}
free(xpath_string);
}
return result;
}
char *
pcmk__xml_artefact_root(enum pcmk__xml_artefact_ns ns)
{
static const char *base = NULL;
char *ret = NULL;
if (base == NULL) {
base = pcmk__env_option(PCMK__ENV_SCHEMA_DIRECTORY);
}
if (pcmk__str_empty(base)) {
base = CRM_SCHEMA_DIRECTORY;
}
switch (ns) {
case pcmk__xml_artefact_ns_legacy_rng:
case pcmk__xml_artefact_ns_legacy_xslt:
ret = strdup(base);
break;
case pcmk__xml_artefact_ns_base_rng:
case pcmk__xml_artefact_ns_base_xslt:
ret = crm_strdup_printf("%s/base", base);
break;
default:
crm_err("XML artefact family specified as %u not recognized", ns);
}
return ret;
}
char *
pcmk__xml_artefact_path(enum pcmk__xml_artefact_ns ns, const char *filespec)
{
char *base = pcmk__xml_artefact_root(ns), *ret = NULL;
switch (ns) {
case pcmk__xml_artefact_ns_legacy_rng:
case pcmk__xml_artefact_ns_base_rng:
ret = crm_strdup_printf("%s/%s.rng", base, filespec);
break;
case pcmk__xml_artefact_ns_legacy_xslt:
case pcmk__xml_artefact_ns_base_xslt:
ret = crm_strdup_printf("%s/%s.xsl", base, filespec);
break;
default:
crm_err("XML artefact family specified as %u not recognized", ns);
}
free(base);
return ret;
}
void
pcmk__xe_set_propv(xmlNodePtr node, va_list pairs)
{
while (true) {
const char *name, *value;
name = va_arg(pairs, const char *);
if (name == NULL) {
return;
}
value = va_arg(pairs, const char *);
if (value != NULL) {
crm_xml_add(node, name, value);
}
}
}
void
pcmk__xe_set_props(xmlNodePtr node, ...)
{
va_list pairs;
va_start(pairs, node);
pcmk__xe_set_propv(node, pairs);
va_end(pairs);
}
int
pcmk__xe_foreach_child(xmlNode *xml, const char *child_element_name,
int (*handler)(xmlNode *xml, void *userdata),
void *userdata)
{
xmlNode *children = (xml? xml->children : NULL);
CRM_ASSERT(handler != NULL);
for (xmlNode *node = children; node != NULL; node = node->next) {
if (node->type == XML_ELEMENT_NODE &&
pcmk__str_eq(child_element_name, (const char *) node->name, pcmk__str_null_matches)) {
int rc = handler(node, userdata);
if (rc != pcmk_rc_ok) {
return rc;
}
}
}
return pcmk_rc_ok;
}
// Deprecated functions kept only for backward API compatibility
// LCOV_EXCL_START
#include <crm/common/xml_compat.h>
xmlNode *
find_entity(xmlNode *parent, const char *node_name, const char *id)
{
return pcmk__xe_match(parent, node_name,
((id == NULL)? id : XML_ATTR_ID), id);
}
void
crm_destroy_xml(gpointer data)
{
free_xml(data);
}
xmlDoc *
getDocPtr(xmlNode *node)
{
xmlDoc *doc = NULL;
CRM_CHECK(node != NULL, return NULL);
doc = node->doc;
if (doc == NULL) {
doc = xmlNewDoc((pcmkXmlStr) "1.0");
xmlDocSetRootElement(doc, node);
}
return doc;
}
int
add_node_nocopy(xmlNode *parent, const char *name, xmlNode *child)
{
add_node_copy(parent, child);
free_xml(child);
return 1;
}
gboolean
xml_has_children(const xmlNode * xml_root)
{
if (xml_root != NULL && xml_root->children != NULL) {
return TRUE;
}
return FALSE;
}
// LCOV_EXCL_STOP
// End deprecated API

File Metadata

Mime Type
text/x-diff
Expires
Mon, Apr 21, 7:31 PM (4 h, 21 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1665574
Default Alt Text
(215 KB)

Event Timeline