diff --git a/configure.ac b/configure.ac
index 97abbbd..c15b134 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,517 +1,523 @@
 #                                               -*- Autoconf -*-
 # Process this file with autoconf to produce a configure script.
 
 # bootstrap / init
 AC_PREREQ([2.69])
 
 AC_INIT([booth],
     [m4_esyscmd([build-aux/git-version-gen --fallback 1.2 .tarball-version .gitarchivever])],
     [users@clusterlabs.org])
 
 AC_USE_SYSTEM_EXTENSIONS
 
 AM_INIT_AUTOMAKE([-Wno-portability subdir-objects])
 
 AC_CONFIG_MACRO_DIR([build-aux])
 AC_CONFIG_SRCDIR([src/main.c])
 AC_CONFIG_HEADERS([src/b_config.h src/booth_config.h])
 
 AC_CANONICAL_HOST
 
 AC_LANG([C])
 
 dnl Fix default variables - "prefix" variable if not specified
 if test "$prefix" = "NONE"; then
 	prefix="/usr"
 
 	dnl Fix "localstatedir" variable if not specified
 	if test "$localstatedir" = "\${prefix}/var"; then
 		localstatedir="/var"
 	fi
 	dnl Fix "sysconfdir" variable if not specified
 	if test "$sysconfdir" = "\${prefix}/etc"; then
 		sysconfdir="/etc"
 	fi
 	dnl Fix "libdir" variable if not specified
 	if test "$libdir" = "\${exec_prefix}/lib"; then
 		if test -e /usr/lib64; then
 			libdir="/usr/lib64"
 		else
 			libdir="/usr/lib"
 		fi
 	fi
 fi
 
 AC_MSG_NOTICE(Sanitizing exec_prefix: ${exec_prefix})
 case $exec_prefix in
   dnl For consistency with Corosync, map NONE->$prefix
   NONE)   exec_prefix=$prefix;;
   prefix) exec_prefix=$prefix;;
 esac
 
 # Checks for programs.
 
 # check stolen from gnulib/m4/gnu-make.m4
 if ! ${MAKE-make} --version /cannot/make/this >/dev/null 2>&1; then
 	AC_MSG_ERROR([you don't seem to have GNU make; it is required])
 fi
 
 AC_PROG_CC
 AC_PROG_INSTALL
 AC_PROG_LN_S
 AC_PROG_MAKE_SET
 AC_PROG_RANLIB
 PKG_PROG_PKG_CONFIG
 
 AC_PATH_PROGS(ASCIIDOC, asciidoc)
 AC_PATH_PROGS(ASCIIDOCTOR, asciidoctor)
 AC_PATH_PROGS(A2X, a2x)
 
 AM_CONDITIONAL(IS_ASCIIDOC, test x"${ASCIIDOC}" != x"")
 AM_CONDITIONAL(IS_A2X, test x"${A2X}" != x"")
 AM_CONDITIONAL(BUILD_ASCIIDOC, test x"${A2X}" != x"" || test x"${ASCIIDOCTOR}" != x"")
 
 AC_CHECK_LIB([xml2], xmlReadDoc)
 PKG_CHECK_MODULES(XML, [libxml-2.0])
 PKG_CHECK_MODULES(GLIB, [glib-2.0])
 PKG_CHECK_MODULES([PCMK], [pacemaker-service],,
 		  [PKG_CHECK_MODULES([PCMK], [pcmk-service])])
 
 # Python casing, prefer 3.3+ to 2.{6...}
 if test "x$PYTHON" = "x"; then
 	AM_PATH_PYTHON([3.3],, [AM_PATH_PYTHON([2.6])])
 else
 	# Just set Automake variables (mainly PYTHON_VERSION)
 	AM_PATH_PYTHON
 fi
 PYTHON_SHEBANG="$PYTHON ${PYTHON_OPTS--Es}"
 AC_SUBST([PYTHON_SHEBANG])
 AM_CONDITIONAL(PYTHON_IS_VERSION3, test "x${PYTHON_VERSION%%.*}" = "x3")
 
 # Checks for header files.
 AC_FUNC_ALLOCA
 AC_HEADER_DIRENT
 AC_HEADER_SYS_WAIT
 AC_CHECK_HEADERS([arpa/inet.h fcntl.h limits.h netdb.h netinet/in.h stdint.h \
 		  stdlib.h string.h sys/ioctl.h sys/param.h sys/socket.h \
 		  sys/time.h syslog.h unistd.h sys/types.h getopt.h malloc.h \
 		  sys/sockio.h utmpx.h])
 AC_CHECK_HEADERS(heartbeat/glue_config.h)
 AC_CHECK_HEADER([zlib.h],
 		[AC_SUBST(ZLIB_LIBS, ["-lz"])],
 		[AC_MSG_ERROR([zlib development files required])])
 saved_CPPFLAGS="${CPPFLAGS}"
 CPPFLAGS="${CPPFLAGS} ${PCMK_CFLAGS} ${GLIB_CFLAGS}"
 AC_CHECK_HEADER([crm/services.h], [],
 		[AC_MSG_ERROR([crm/services.h header required])])
 CPPFLAGS="${saved_CPPFLAGS}"
 
 # Checks for typedefs, structures, and compiler characteristics.
 AC_C_CONST
 AC_TYPE_UID_T
 AC_C_INLINE
 AC_TYPE_INT16_T
 AC_TYPE_INT32_T
 AC_TYPE_INT64_T
 AC_TYPE_INT8_T
 AC_TYPE_SIZE_T
 AC_TYPE_SSIZE_T
 AC_TYPE_UINT16_T
 AC_TYPE_UINT32_T
 AC_TYPE_UINT64_T
 AC_TYPE_UINT8_T
 AC_C_VOLATILE
 
 # Checks for library functions.
 AC_FUNC_CLOSEDIR_VOID
 AC_FUNC_ERROR_AT_LINE
 AC_REPLACE_FNMATCH
 AC_FUNC_FORK
 AC_PROG_GCC_TRADITIONAL
 AC_FUNC_MALLOC
 AC_FUNC_MEMCMP
 AC_FUNC_REALLOC
 AC_FUNC_SELECT_ARGTYPES
 AC_FUNC_VPRINTF
 AC_CHECK_FUNCS([alarm alphasort atexit bzero dup2 endgrent endpwent fcntl \
 		getcwd getpeerucred getpeereid gettimeofday memmove \
 		memset mkdir scandir select socket strcasecmp strchr strdup \
 		strerror strrchr strspn strstr \
 		sched_get_priority_max sched_setscheduler])
 
 AC_CONFIG_FILES([Makefile
 		 booth.pc
 		 src/Makefile
 		 docs/Makefile
 		 conf/Makefile])
 AC_CONFIG_FILES([conf/booth-arbitrator.service conf/booth@.service])
 AC_CONFIG_FILES([script/service-runnable], [chmod +x script/service-runnable])
 
 # ===============================================
 # Helpers
 # ===============================================
 
 ## PKG_CHECK_VAR wrapper that allows defining a default value
 ## when value from pkg-config is not detected
 
 AC_DEFUN([BOOTH_PKG_CHECK_VAR], [
 	  varname=$1
 	  default=$4
 	  AC_MSG_CHECKING([for pkg-conf $2 var $3])
 	  PKG_CHECK_VAR([$1], [$2], [$3])
 	  AS_VAR_IF([$1], [""],
 		    [AS_VAR_IF([default], [""],
 			       AC_MSG_ERROR([not found]),
 			       [AS_VAR_COPY([$varname], [default]) && AC_MSG_RESULT([not found, using default ${!varname}])])],
 		    [AC_MSG_RESULT([yes (detected: ${!varname})])])
 ])
 
 ## helper for CC stuff
 cc_supports_flag() {
          local CFLAGS="-Werror $@"
          AC_MSG_CHECKING(whether $CC supports "$@")
          AC_COMPILE_IFELSE([AC_LANG_SOURCE([[int main(){return 0;}]])],
            [RC=0; AC_MSG_RESULT(yes)],[RC=1; AC_MSG_RESULT(no)])
          return $RC
 }
 
 ## local defines
 PACKAGE_FEATURES=""
 
 # local options
 
 AC_ARG_ENABLE([fatal-warnings],
 	[  --enable-fatal-warnings         : enable fatal warnings. ],
 	[ default="no" ])
 
 AC_ARG_ENABLE([debug],
 	[  --enable-debug                  : enable debug build. ],
 	[ default="no" ])
 
 AC_ARG_ENABLE([user-flags],
 	[  --enable-user-flags             : rely on user environment. ],
 	[ default="no" ])
 
 AC_ARG_ENABLE([coverage],
 	[  --enable-coverage               : coverage analysis of the codebase. ],
 	[ default="no" ])
 
 AC_ARG_WITH([initddir],
 	[  --with-initddir=DIR             : path to init script directory. ],
 	[ INITDDIR="$withval" ],
 	[ INITDDIR="$sysconfdir/init.d" ])
 
 AC_ARG_WITH([html_man],
 	[  --with-html_man                 : Enable generating man pages in HTML.])
 
 AM_CONDITIONAL(BUILD_ASCIIDOC_HTML_MAN,
 	       (test "x${ASCIIDOC}" != "x" || test x"${ASCIIDOCTOR}" != x"") && test "x$with_html_man" = "xyes")
 
 AC_ARG_WITH([glue],
 	[  --without-glue                  : Avoid libraries from (cluster-)glue project.],
 	[],
 	[with_glue=yes])
 
 AC_ARG_WITH([run_build_tests],
 	[  --with-run-build-tests          : Enable running build tests when generating RPM],
 	[run_build_tests=yes],
 	[])
 AM_CONDITIONAL([RUN_BUILD_TESTS], [test "x$run_build_tests" = "xyes"])
 
 # figure out ocfdir automatically and allow manual override (mostly for CI)
 
 BOOTH_PKG_CHECK_VAR([OCFROOT], [resource-agents], [ocfrootdir], [/usr/lib/ocf])
 
 AC_ARG_WITH([ocfdir],
 	[  --with-ocfdir=DIR               : path to ocfdir (default: autodetected). ],
 	[ ocfdir="$withval" ],
 	[ ocfdir="$OCFROOT" ])
 
 AC_SUBST([ocfdir])
 
 AC_ARG_WITH([hmac_library],
 	[  --with-hmac-library=LIBRARY     : Select HMAC library to use (default: autodetect one of gnutls, gcrypt or mhash).])
 
 # GnuTLS, libgcrypt or mhash for hmac
 hmac_library_installed="no"
 if test "x$with_hmac_library" == "x" && test "x$hmac_library_installed" == "xno" || \
     test "x$with_hmac_library" == "xgnutls"; then
 	libgnutls_installed="yes"
 	PKG_CHECK_MODULES([LIBGNUTLS], [gnutls >= 2.10.0], [
 	    AC_DEFINE([HAVE_LIBGNUTLS], [1], [Have gnutls library])
 	    libgnutls_installed="yes"
 	    ], [libgnutls_installed="no"])
 	hmac_library_installed="${libgnutls_installed}"
 fi
 
 if test "x$with_hmac_library" == "x" && test "x$hmac_library_installed" == "xno" || \
     test "x$with_hmac_library" == "xgcrypt"; then
 	libgcrypt_installed="yes"
 	AC_CHECK_HEADERS(gcrypt.h, , [libgcrypt_installed="no"],)
 	AC_CHECK_LIB(gcrypt, gcry_md_open, , [libgcrypt_installed="no"])
 	hmac_library_installed="${libgcrypt_installed}"
 fi
 
 if test "x$with_hmac_library" == "x" && test "x$hmac_library_installed" == "xno" || \
     test "x$with_hmac_library" == "xmhash"; then
 	mhash_installed="yes"
 	AC_CHECK_HEADERS(mhash.h, , [mhash_installed="no"],)
 	AC_CHECK_LIB(mhash, mhash_init, , [mhash_installed="no"])
 	hmac_library_installed="${mhash_installed}"
 fi
 
 if test "x$with_hmac_library" != "x" && test "x$hmac_library_installed" == "xno";then
 	AC_MSG_ERROR([required HMAC library not detected])
 fi
 
 AM_CONDITIONAL(BUILD_AUTH_C, test "x${hmac_library_installed}" = "xyes")
 
 # figure out logging provider
 logging_provider=""
 if test "x$logging_provider" = "x" && test "x$with_glue" = "xyes"; then
 	AC_CHECK_LIB([plumb], [cl_log], [logging_provider="libplumb"])
 fi
 if test "x$logging_provider" = "x" && test "x$with_glue" = "xno"; then
 	PKG_CHECK_MODULES([LIBQB], [libqb])
 	AC_DEFINE([LOGGING_LIBQB], [], [use libqb as a logging provider])
 	PKG_CHECK_MODULES([LIBQB1], [libqb >= 1.0],
 			  [AC_DEFINE([LOGGING_LIBQB_MAJOR], [1],
 				     [libqb major version lower bound])],
 			  [AC_MSG_WARN([[syslog identifier will not get changed]])])
 	logging_provider=libqb
 fi
 case "$logging_provider" in
 libplumb)
 	LOGGER="ha_logger"
 	;;
 libqb)
 	LOGGER="logger -t booth-script"
 	;;
 *)
 	AC_MSG_ERROR([logging provider required (libplumb, or libqb when --without-glue)])
 	;;
 esac
 AM_CONDITIONAL([LOGGING_LIBQB], [test "x$logging_provider" = "xlibqb"])
 AC_SUBST([LOGGER])
 
 # figure out range2random provider
 range2random_provider=""
 if test "x$range2random_provider" = "x" && test "x$with_glue" = "xyes"; then
        AC_CHECK_LIB([plumb], [get_next_random], [range2random_provider="libplumb"])
        AC_CHECK_DECL([cl_rand_from_interval], [], [range2random_provider=""],
 		     [#include <clplumbing/cl_random.h>])
 fi
 if test "x$range2random_provider" = "x" && test "x$with_glue" = "xno"; then
        AC_CHECK_LIB([glib-2.0], [g_random_int_range], [range2random_provider="glib"])
 fi
 case "$range2random_provider" in
 libplumb)
 	;;
 glib)
 	PKG_CHECK_MODULES([GLIB], [glib-2.0])
 	AC_DEFINE([RANGE2RANDOM_GLIB], [], [use glib as a range2random provider])
 	;;
 *)
 	AC_MSG_ERROR([range2random provider required (libplumb, or glib when --without-glue)])
 	;;
 esac
 AM_CONDITIONAL([RANGE2RANDOM_GLIB], [test "x$range2random_provider" = "xglib"])
 
 # figure out nametag/distinguished-role provider
 nametag_provider=""
 if test "x$nametag_provider" = "x" && test "x$with_glue" != "xno"; then
 	AC_CHECK_LIB([plumbgpl], [set_proc_title], [nametag_provider="libplumbgpl"])
 fi
 if test "x$nametag_provider" = "x" && test "x$with_glue" = "xno"; then
 	AC_SEARCH_LIBS([sd_notify], [systemd systemd-daemon], [nametag_provider="libsystemd"])
 fi
 NOTIFY_ACCESS_SWITCH='# '
 case "$nametag_provider" in
 libplumbgpl)
 	;;
 libsystemd)
 	PKG_CHECK_MODULES([LIBSYSTEMD], [libsystemd],, [
 		PKG_CHECK_MODULES([LIBSYSTEMD], [libsystemd-daemon])
 	])
 	AC_DEFINE([NAMETAG_LIBSYSTEMD], [], [use libsystemd as a nametag provider])
 	NOTIFY_ACCESS_SWITCH=
 	;;
 *)
 	AC_MSG_ERROR([nametag provider required (libplumbgpl, or libsystemd when --without-glue)])
 	;;
 esac
 AM_CONDITIONAL([NAMETAG_LIBSYSTEMD], [test "x$nametag_provider" = "xlibsystemd"])
 AC_SUBST([NOTIFY_ACCESS_SWITCH])
 
 # figure out if "coredump nursing" supported and desired
 coredump_nursing="no"
 if test "x$with_glue" != "xno"; then
 	AC_CHECK_LIB([plumb], [cl_enable_coredumps], [coredump_nursing="libplumb"])
 fi
 if test "x$coredump_nursing" != "xno"; then
 	AC_DEFINE(COREDUMP_NURSING, [], [eligible for coredump nursing])
 fi
 AM_CONDITIONAL([COREDUMP_NURSING], [test "x$coredump_nursing" != "xno"])
 
 # define CRM daemon user & group
 BOOTH_PKG_CHECK_VAR([CRM_DAEMON_USER], [pacemaker], [daemon_user], [hacluster])
 AC_DEFINE_UNQUOTED(CRM_DAEMON_USER,"$CRM_DAEMON_USER", User to run Booth daemon as)
 
 BOOTH_PKG_CHECK_VAR([CRM_DAEMON_GROUP], [pacemaker], [daemon_group], [haclient])
 AC_DEFINE_UNQUOTED(CRM_DAEMON_GROUP,"$CRM_DAEMON_GROUP", Group to run Booth daemon as)
 
+# If libpacemaker is available, use it
+PKG_CHECK_MODULES([LIBPACEMAKER], [libpacemaker >= 2.1.7])
+if test "x$LIBPACEMAKER_CFLAGS" != "xno"; then
+    AC_DEFINE([LIBPACEMAKER], [1], [use libpacemaker for ticket manipulation])
+fi
+
 # *FLAGS handling goes here
 
 ENV_CFLAGS="$CFLAGS"
 ENV_CPPFLAGS="$CPPFLAGS"
 ENV_LDFLAGS="$LDFLAGS"
 
 # debug build stuff
 if test "x${enable_debug}" = xyes; then
 	AC_DEFINE_UNQUOTED([DEBUG], [1], [Compiling Debugging code])
 	OPT_CFLAGS="-O0 -U_FORTIFY_SOURCE"
 	PACKAGE_FEATURES="$PACKAGE_FEATURES debug"
 else
 	OPT_CFLAGS="-O3"
 fi
 
 # gdb flags
 if test "x${GCC}" = xyes; then
 	GDB_FLAGS="-ggdb3"
 else
 	GDB_FLAGS="-g"
 fi
 
 dnl Check for POSIX clock_gettime
 dnl
 AC_CACHE_CHECK([have clock_gettime],ac_cv_HAVE_CLOCK_GETTIME,[
 AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
 #include <time.h>
 ]],
 [[ struct timespec tv; clock_gettime(CLOCK_REALTIME, &tv); return 0;]])],
 [ac_cv_HAVE_CLOCK_GETTIME=yes], [ac_cv_HAVE_CLOCK_GETTIME=no])])
 AM_CONDITIONAL(BUILD_TIMER_C, test x"$ac_cv_HAVE_CLOCK_GETTIME" = x"yes")
 
 # extra warnings
 EXTRA_WARNINGS=""
 
 WARNLIST="
 	all
 	shadow
 	missing-prototypes
 	missing-declarations
 	strict-prototypes
 	declaration-after-statement
 	pointer-arith
 	write-strings
 	bad-function-cast
 	missing-format-attribute
 	format=2
 	format-security
 	format-nonliteral
 	no-long-long
 	unsigned-char
 	gnu89-inline
 	no-strict-aliasing
 	"
 
 for j in $WARNLIST; do
 	if cc_supports_flag -W$j; then
 		EXTRA_WARNINGS="$EXTRA_WARNINGS -W$j";
 	fi
 done
 
 if test "x${enable_coverage}" = xyes && \
 		cc_supports_flag -ftest-coverage && \
 		cc_supports_flag -fprofile-arcs ; then
 	AC_MSG_NOTICE([Enabling Coverage (enable -O0 by default)])
 	OPT_CFLAGS="-O0"
 	COVERAGE_CFLAGS="-ftest-coverage -fprofile-arcs"
 	COVERAGE_LDFLAGS="-ftest-coverage -fprofile-arcs"
 	PACKAGE_FEATURES="$PACKAGE_FEATURES coverage"
 else
 	COVERAGE_CFLAGS=""
 	COVERAGE_LDFLAGS=""
 fi
 
 if test "x${enable_fatal_warnings}" = xyes && \
 		cc_supports_flag -Werror ; then
 	AC_MSG_NOTICE([Enabling Fatal Warnings (-Werror)])
 	WERROR_CFLAGS="-Werror"
 	PACKAGE_FEATURES="$PACKAGE_FEATURES fatal-warnings"
 else
 	WERROR_CFLAGS=""
 fi
 
 # don't add addtional cflags
 if test "x${enable_user_flags}" = xyes; then
   OPT_CFLAGS=""
   GDB_FLAGS=""
   EXTRA_WARNINGS=""
 fi
 
 # final build of *FLAGS
 CFLAGS="$ENV_CFLAGS $OPT_CFLAGS $GDB_FLAGS $OS_CFLAGS \
 	$COVERAGE_CFLAGS $EXTRA_WARNINGS $WERROR_CFLAGS $LIBGNUTLS_CFLAGS"
 CPPFLAGS="$ENV_CPPFLAGS $OS_CPPFLAGS $GLIB_CFLAGS $RESMON_CFLAGS $XML_CFLAGS"
 LDFLAGS="$ENV_LDFLAGS $COVERAGE_LDFLAGS $OS_LDFLAGS"
-LIBS="$LIBS $XML_LIBS $LIBGNUTLS_LIBS"
+LIBS="$LIBS $XML_LIBS $LIBGNUTLS_LIBS $LIBPACEMAKER_LIBS"
 
 # substitute what we need:
 AC_SUBST([INITDDIR])
 
 BOOTH_LIB_DIR=${localstatedir}/lib/booth
 BOOTH_CORE_DIR=${localstatedir}/lib/booth/cores
 BOOTHSYSCONFDIR=${sysconfdir}/booth
 
 AC_SUBST([HAVE_LOG_CIB_DIFF])
 AC_SUBST([HAVE_XML_LOG_PATCHSET])
 AC_SUBST([BOOTH_LIB_DIR])
 AC_SUBST([BOOTH_CORE_DIR])
 AC_SUBST([BOOTHSYSCONFDIR])
 
 AC_DEFINE_UNQUOTED([BOOTH_LIB_DIR], "$(eval echo ${BOOTH_LIB_DIR})", [booth lib directory])
 AC_DEFINE_UNQUOTED([BOOTH_CORE_DIR], "$(eval echo ${BOOTH_CORE_DIR})", [booth working directory])
 AC_DEFINE_UNQUOTED([BOOTHSYSCONFDIR], "$(eval echo ${BOOTHSYSCONFDIR})", [booth config directory])
 
 AC_DEFINE_UNQUOTED([PACKAGE_FEATURES], "${PACKAGE_FEATURES}", [booth built-in features])
 
 AC_OUTPUT
 
 AC_MSG_RESULT([])
 AC_MSG_RESULT([$PACKAGE configuration:])
 AC_MSG_RESULT([  Version                  = ${VERSION}])
 AC_MSG_RESULT([  Prefix                   = ${prefix}])
 AC_MSG_RESULT([  Executables              = ${sbindir}])
 AC_MSG_RESULT([  Man pages                = ${mandir}])
 AC_MSG_RESULT([  Doc dir                  = ${docdir}])
 AC_MSG_RESULT([  Libraries                = ${libdir}])
 AC_MSG_RESULT([  Header files             = ${includedir}])
 AC_MSG_RESULT([  Arch-independent files   = ${datadir}])
 AC_MSG_RESULT([  State information        = ${localstatedir}])
 AC_MSG_RESULT([  System configuration     = ${sysconfdir}])
 AC_MSG_RESULT([  System init.d directory  = ${INITDDIR}])
 AC_MSG_RESULT([  booth config dir         = ${BOOTHSYSCONFDIR}])
 AC_MSG_RESULT([  ocf dir                  = ${ocfdir}])
 AC_MSG_RESULT([  Features                 = ${PACKAGE_FEATURES}])
 AC_MSG_RESULT([  Logging provider         = ${logging_provider}])
 AC_MSG_RESULT([  Range2random provider    = ${range2random_provider}])
 AC_MSG_RESULT([  Nametag provider         = ${nametag_provider}])
 AC_MSG_RESULT([  Coredump nursing         = ${coredump_nursing}])
 AC_MSG_RESULT([  Working directory        = ${BOOTH_CORE_DIR}])
 AC_MSG_RESULT([  HA group name            = ${CRM_DAEMON_GROUP}])
 AC_MSG_RESULT([  HA user name             = ${CRM_DAEMON_USER}])
 AC_MSG_RESULT([])
 AC_MSG_RESULT([$PACKAGE build info:])
 AC_MSG_RESULT([  Default optimization     = ${OPT_CFLAGS}])
 AC_MSG_RESULT([  Default debug options    = ${GDB_CFLAGS}])
 AC_MSG_RESULT([  Extra compiler warnings  = ${EXTRA_WARNING}])
 AC_MSG_RESULT([  Env. defined CFLAG       = ${ENV_CFLAGS}])
 AC_MSG_RESULT([  Env. defined CPPFLAGS    = ${ENV_CPPFLAGS}])
 AC_MSG_RESULT([  Env. defined LDFLAGS     = ${ENV_LDFLAGS}])
 AC_MSG_RESULT([  Coverage     CFLAGS      = ${COVERAGE_CFLAGS}])
 AC_MSG_RESULT([  Coverage     LDFLAGS     = ${COVERAGE_LDFLAGS}])
 AC_MSG_RESULT([  Fatal War.   CFLAGS      = ${WERROR_CFLAGS}])
 AC_MSG_RESULT([  Final        CFLAGS      = ${CFLAGS}])
 AC_MSG_RESULT([  Final        CPPFLAGS    = ${CPPFLAGS}])
 AC_MSG_RESULT([  Final        LDFLAGS     = ${LDFLAGS}])
diff --git a/src/Makefile.am b/src/Makefile.am
index b3c35bb..712dab5 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,55 +1,55 @@
 MAINTAINERCLEANFILES	= Makefile.in
 
 AM_CFLAGS		= -fPIC -Werror -funsigned-char -Wno-pointer-sign
 
 
 AM_CPPFLAGS		= -I$(top_builddir)/include
 
 sbin_PROGRAMS		= boothd
 
 boothd_SOURCES	 	= config.c main.c raft.c ticket.c transport.c \
-			  pacemaker.c handler.c request.c attr.c manual.c
+			  pcmk.c handler.c request.c attr.c manual.c
 
 noinst_HEADERS		= \
-			  attr.h booth.h handler.h log.h pacemaker.h request.h timer.h \
+			  attr.h booth.h handler.h log.h pcmk.h request.h timer.h \
 			  auth.h config.h inline-fn.h manual.h raft.h ticket.h transport.h
 
 if BUILD_TIMER_C
 boothd_SOURCES		+= timer.c
 endif
 
 if BUILD_AUTH_C
 boothd_SOURCES		+= auth.c
 endif
 
 boothd_LDFLAGS		= $(OS_DYFLAGS) -L./
 boothd_LDADD		= -lm $(GLIB_LIBS) $(ZLIB_LIBS)
 boothd_CFLAGS		= $(GLIB_CFLAGS) $(PCMK_CFLAGS)
 
 if !LOGGING_LIBQB
 boothd_LDADD		+= -lplumb
 else
 boothd_LDADD		+= $(LIBQB_LIBS)
 boothd_SOURCES		+= alt/logging_libqb.c
 noinst_HEADERS		+= alt/logging_libqb.h
 endif
 
 if !RANGE2RANDOM_GLIB
 boothd_LDADD		+= -lplumb
 else
 boothd_LDADD		+= $(GLIB_LIBS)
 boothd_SOURCES		+= alt/range2random_glib.c
 noinst_HEADERS		+= alt/range2random_glib.h
 endif
 
 if !NAMETAG_LIBSYSTEMD
 boothd_LDADD		+= -lplumbgpl
 else
 boothd_LDADD		+= $(LIBSYSTEMD_LIBS)
 boothd_SOURCES		+= alt/nametag_libsystemd.c
 noinst_HEADERS		+= alt/nametag_libsystemd.h
 endif
 
 if COREDUMP_NURSING
 boothd_LDADD		+= -lplumb
 endif
diff --git a/src/attr.c b/src/attr.c
index 0c68777..3bad7e0 100644
--- a/src/attr.c
+++ b/src/attr.c
@@ -1,493 +1,493 @@
 /*
  * Copyright (C) 2015 Dejan Muhamedagic <dejan@hello-penguin.com>
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public
  * License as published by the Free Software Foundation; either
  * version 2 of the License, or (at your option) any later version.
  *
  * This software is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  * General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License along
  * with this program; if not, write to the Free Software Foundation, Inc.,
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
 
 #include <stdio.h>
 #include <string.h>
 #include "attr.h"
 #include "booth.h"
 #include "ticket.h"
-#include "pacemaker.h"
+#include "pcmk.h"
 
 void print_geostore_usage(void)
 {
 	printf(
 	"Usage:\n"
 	"  geostore {list|set|get|delete} [-t ticket] [options] attr [value]\n"
 	"\n"
 	"  list:	     List all attributes\n"
 	"  set:          Set attribute to a value\n"
 	"  get:          Get attribute's value\n"
 	"  delete:       Delete attribute\n"
 	"\n"
 	"  -t <ticket>   Ticket where attribute resides\n"
 	"                (required, if more than one ticket is configured)\n"
 	"\n"
 	"Options:\n"
 	"  -c FILE       Specify config file [default " BOOTH_DEFAULT_CONF "]\n"
 	"                Can be a path or just a name without \".conf\" suffix\n"
 	"  -s <site>     Connect to a different site\n"
 	"  -h            Print this help\n"
 	"\n"
 	"Examples:\n"
 	"\n"
 	"  # geostore list -t ticket-A -s 10.121.8.183\n"
 	"  # geostore set -s 10.121.8.183 sr_status ACTIVE\n"
 	"  # geostore get -t ticket-A -s 10.121.8.183 sr_status\n"
 	"  # geostore delete -s 10.121.8.183 sr_status\n"
 	"\n"
 	"See the geostore(8) man page for more details.\n"
 	);
 }
 
 /*
  * the client side
  */
 
 /* cl has all the input parameters:
  * ticket, attr name, attr value
  */
 
 int test_attr_reply(cmd_result_t reply_code, cmd_request_t cmd)
 {
 	int rv = 0;
 	const char *op_str = NULL;
 
 	switch (cmd) {
 	case ATTR_SET:	op_str = "set";		break;
 	case ATTR_GET:	op_str = "get";		break;
 	case ATTR_LIST:	op_str = "list";	break;
 	case ATTR_DEL:	op_str = "delete";	break;
 	default:
 		log_error("internal error reading reply result!");
 		return -1;
 	}
 
 	switch (reply_code) {
 	case RLT_ASYNC:
 		log_info("%s command sent, result will be returned "
 			 "asynchronously.", op_str);
 		rv = 0;
 		break;
 
 	case RLT_SYNC_SUCC:
 	case RLT_SUCCESS:
 		if (cmd == ATTR_SET)
 			log_info("%s succeeded!", op_str);
 		rv = 0;
 		break;
 
 	case RLT_SYNC_FAIL:
 		log_info("%s failed!", op_str);
 		rv = -1;
 		break;
 
 	case RLT_INVALID_ARG:
 		log_error("ticket \"%s\" does not exist",
 				cl.attr_msg.attr.tkt_id);
 		rv = 1;
 		break;
 
 	case RLT_NO_SUCH_ATTR:
 		log_error("attribute \"%s\" not set",
 				cl.attr_msg.attr.name);
 		rv = 1;
 		break;
 
 	case RLT_AUTH:
 		log_error("authentication error");
 		rv = -1;
 		break;
 
 	default:
 		log_error("got an error code: %x", rv);
 		rv = -1;
 	}
 	return rv;
 }
 
 /* read the server's reply
  * need to first get the header which contains the length of the
  * reply
  * return codes:
  *   -2: header not received
  *   -1: header received, but message too short
  *   >=0: success
  */
 static int read_server_reply(
 		struct booth_transport const *tpt, struct booth_site *site,
 		char *msg)
 {
 	struct boothc_header *header;
 	int rv;
 	int len;
 
 	header = (struct boothc_header *)msg;
 	rv = tpt->recv(site, header, sizeof(*header));
 	if (rv < 0) {
 		return -2;
 	}
 	len = ntohl(header->length);
 	rv = tpt->recv(site, msg+sizeof(*header), len-sizeof(*header));
 	if (rv < 0) {
 		return -1;
 	}
 	return rv;
 }
 
 int do_attr_command(struct booth_config *conf, cmd_request_t cmd)
 {
 	struct booth_site *site = NULL;
 	struct boothc_header *header;
 	struct booth_transport const *tpt = NULL;
 	int len, rv = -1;
 	char *msg = NULL;
 
 	if (!*cl.site)
 		site = local;
 	else {
 		if (!find_site_by_name(conf, cl.site, &site, 1)) {
 			log_error("Site \"%s\" not configured.", cl.site);
 			goto out_close;
 		}
 	}
 
 	if (site->type == ARBITRATOR) {
 		if (site == local) {
 			log_error("We're just an arbitrator, no attributes here.");
 		} else {
 			log_error("%s is just an arbitrator, no attributes there.", cl.site);
 		}
 		goto out_close;
 	}
 
 	tpt = booth_transport + TCP;
 
 	init_header(&cl.attr_msg.header, cmd, 0, cl.options, 0, 0,
 		sizeof(cl.attr_msg));
 
 	rv = tpt->open(site);
 	if (rv < 0)
 		goto out_close;
 
 	rv = tpt->send(conf, site, &cl.attr_msg, sendmsglen(&cl.attr_msg));
 	if (rv < 0) {
 		goto out_close;
 	}
 
 	msg = malloc(MAX_MSG_LEN);
 	if (!msg) {
 		log_error("out of memory");
 		rv = -1;
 		goto out_close;
 	}
 
 	rv = read_server_reply(tpt, site, msg);
 	header = (struct boothc_header *)msg;
 	if (rv < 0) {
 		if (rv == -1)
 			(void)test_attr_reply(ntohl(header->result), cmd);
 		goto out_close;
 	}
 	len = ntohl(header->length);
 
 	if (check_boothc_header(header, len) < 0) {
 		log_error("message from %s receive error", site_string(site));
 		rv = -1;
 		goto out_close;
 	}
 
 	if (check_auth(conf, site, msg, len)) {
 		log_error("%s failed to authenticate", site_string(site));
 		rv = -1;
 		goto out_close;
 	}
 	rv = test_attr_reply(ntohl(header->result), cmd);
 
 out_close:
 	if (tpt && site)
 		tpt->close(site);
 	if (msg)
 		free(msg);
 	return rv;
 }
 
 /*
  * the server side
  */
 
 /* need to invert gboolean, our success is 0
  */
 #define gbool2rlt(i) (i ? RLT_SUCCESS : RLT_SYNC_FAIL)
 
 static void free_geo_attr(gpointer data)
 {
 	struct geo_attr *a = (struct geo_attr *)data;
 
 	if (!a)
 		return;
 	g_free(a->val);
 	g_free(a);
 }
 
 int store_geo_attr(struct ticket_config *tk, const char *name,
 		   const char *val, int notime)
 {
 	struct geo_attr *a;
 	GDestroyNotify free_geo_attr_notify = free_geo_attr;
 
 	if (!tk)
 		return -1;
 	/*
 	 * allocate new, if attr doesn't already exist
 	 * copy the attribute value
 	 * send status
 	 */
 	if (!tk->attr)
 		tk->attr = g_hash_table_new_full(g_str_hash, g_str_equal,
 			g_free, free_geo_attr_notify);
 	if (!tk->attr) {
 		log_error("out of memory");
 		return -1;
 	}
 
 	if (strnlen(name, BOOTH_NAME_LEN) == BOOTH_NAME_LEN)
 		tk_log_warn("name of the attribute too long (%d+ bytes), skipped",
 			 BOOTH_NAME_LEN);
 	else if (strnlen(val, BOOTH_ATTRVAL_LEN) == BOOTH_ATTRVAL_LEN)
 		tk_log_warn("value of the attribute too long (%d+ bytes), skipped",
 			 BOOTH_ATTRVAL_LEN);
 	else {
 		a = (struct geo_attr *)calloc(1, sizeof(struct geo_attr));
 		if (!a) {
 			log_error("out of memory");
 			return -1;
 		}
 
 		a->val = g_strdup(val);
 		if (!notime)
 			get_time(&a->update_ts);
 
 		g_hash_table_insert(tk->attr,
 			g_strdup(name), a);
 	}
 
 	return 0;
 }
 
 static cmd_result_t attr_set(struct ticket_config *tk, struct boothc_attr_msg *msg)
 {
 	int rc;
 
 	rc = store_geo_attr(tk, msg->attr.name, msg->attr.val, 0);
 	if (rc) {
 		return RLT_SYNC_FAIL;
 	}
 	(void)pcmk_handler.set_attr(tk, msg->attr.name, msg->attr.val);
 	return RLT_SUCCESS;
 }
 
 static cmd_result_t attr_del(struct ticket_config *tk, struct boothc_attr_msg *msg)
 {
 	gboolean rv;
 	gpointer orig_key, value;
 
 	/*
 	 * lookup attr
 	 * deallocate, if found
 	 * send status
 	 */
 	if (!tk->attr)
 		return RLT_NO_SUCH_ATTR;
 
 	rv = g_hash_table_lookup_extended(tk->attr, msg->attr.name,
 			&orig_key, &value);
 	if (!rv)
 		return RLT_NO_SUCH_ATTR;
 
 	rv = g_hash_table_remove(tk->attr, msg->attr.name);
 
 	(void)pcmk_handler.del_attr(tk, msg->attr.name);
 
 	return gbool2rlt(rv);
 }
 
 static void
 append_attr(gpointer key, gpointer value, gpointer user_data)
 {
 	char *attr_name = (char *)key;
 	struct geo_attr *a = (struct geo_attr *)value;
 	GString *data = (GString *)user_data;
 	char time_str[64];
 	time_t ts;
 
 	if (is_time_set(&a->update_ts)) {
 		ts = wall_ts(&a->update_ts);
 		strftime(time_str, sizeof(time_str), "%F %T",
 				localtime(&ts));
 	} else {
 		time_str[0] = '\0';
 	}
 	g_string_append_printf(data, "%s %s %s\n",
 		attr_name, a->val, time_str);
 }
 
 
 static cmd_result_t attr_get(struct booth_config *conf, struct ticket_config *tk,
 			     int fd, struct boothc_attr_msg *msg)
 {
 	cmd_result_t rv = RLT_SUCCESS;
 	struct boothc_hdr_msg hdr;
 	struct geo_attr *a;
 	GString *attr_val;
 
 	/*
 	 * lookup attr
 	 * send value
 	 */
 	if (!tk->attr) {
 		return RLT_NO_SUCH_ATTR;
 	}
 
 	a = (struct geo_attr *)g_hash_table_lookup(tk->attr, msg->attr.name);
 	if (!a) {
 		return RLT_NO_SUCH_ATTR;
 	}
 
 	attr_val = g_string_new(NULL);
 	if (!attr_val) {
 		log_error("out of memory");
 		return RLT_SYNC_FAIL;
 	}
 	g_string_printf(attr_val, "%s\n", a->val);
 	init_header(&hdr.header, ATTR_GET, 0, 0, RLT_SUCCESS, 0,
 		sizeof(hdr) + attr_val->len);
 
 	if (send_header_plus(conf, fd, &hdr, attr_val->str, attr_val->len)) {
 		rv = RLT_SYNC_FAIL;
 	}
 
 	if (attr_val) {
 		g_string_free(attr_val, TRUE);
 	}
 
 	return rv;
 }
 
 static cmd_result_t attr_list(struct booth_config *conf, struct ticket_config *tk,
 			      int fd, struct boothc_attr_msg *msg)
 {
 	GString *data;
 	cmd_result_t rv;
 	struct boothc_hdr_msg hdr;
 
 	/*
 	 * list all attributes for the ticket
 	 * send the list
 	 */
 	data = g_string_sized_new(512);
 	if (!data) {
 		log_error("out of memory");
 		return RLT_SYNC_FAIL;
 	}
 	if (tk->attr) {
 		g_hash_table_foreach(tk->attr, append_attr, data);
 	}
 
 	init_header(&hdr.header, ATTR_LIST, 0, 0, RLT_SUCCESS, 0,
 		sizeof(hdr) + data->len);
 	rv = send_header_plus(conf, fd, &hdr, data->str, data->len);
 
 	if (data) {
 		g_string_free(data, TRUE);
 	}
 
 	return rv;
 }
 
 int process_attr_request(struct booth_config *conf, struct client *req_client,
 			 void *buf)
 {
 	cmd_result_t rv = RLT_SYNC_FAIL;
 	struct ticket_config *tk;
 	int cmd;
 	struct boothc_attr_msg *msg;
 	struct boothc_hdr_msg hdr;
 
 	msg = (struct boothc_attr_msg *)buf;
 	cmd = ntohl(msg->header.cmd);
 	if (!check_ticket(conf, msg->attr.tkt_id, &tk)) {
 		log_warn("client referenced unknown ticket %s",
 				msg->attr.tkt_id);
 		rv = RLT_INVALID_ARG;
 		goto reply_now;
 	}
 
 	switch (cmd) {
 	case ATTR_LIST:
 		rv = attr_list(conf, tk, req_client->fd, msg);
 		if (rv) {
 			goto reply_now;
 		}
 
 		return 1;
 	case ATTR_GET:
 		rv = attr_get(conf, tk, req_client->fd, msg);
 		if (rv) {
 			goto reply_now;
 		}
 
 		return 1;
 	case ATTR_SET:
 		rv = attr_set(tk, msg);
 		break;
 	case ATTR_DEL:
 		rv = attr_del(tk, msg);
 		break;
 	}
 
 reply_now:
 	init_header(&hdr.header, CL_RESULT, 0, 0, rv, 0, sizeof(hdr));
 	send_header_plus(conf, req_client->fd, &hdr, NULL, 0);
 	return 1;
 }
 
 /* read attr message from another site */
 
 /* this is a NOOP and it should never be invoked
  * only clients retrieve/manage attributes and they connect
  * directly to the target site
  */
 int attr_recv(struct booth_config *conf, void *buf, struct booth_site *source)
 {
 	struct boothc_attr_msg *msg;
 	struct ticket_config *tk;
 
 	msg = (struct boothc_attr_msg *)buf;
 
 	log_warn("unexpected attribute message from %s",
 			site_string(source));
 
 	if (!check_ticket(conf, msg->attr.tkt_id, &tk)) {
 		log_warn("got invalid ticket name %s from %s",
 				msg->attr.tkt_id, site_string(source));
 		source->invalid_cnt++;
 		return -1;
 	}
 
 	return 0;
 }
diff --git a/src/handler.c b/src/handler.c
index 727b89c..13b04c8 100644
--- a/src/handler.c
+++ b/src/handler.c
@@ -1,283 +1,283 @@
 /* 
  * Copyright (C) 2014 Philipp Marek <philipp.marek@linbit.com>
  * 
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public
  * License as published by the Free Software Foundation; either
  * version 2 of the License, or (at your option) any later version.
  * 
  * This software is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  * General Public License for more details.
  * 
  * You should have received a copy of the GNU General Public License along
  * with this program; if not, write to the Free Software Foundation, Inc.,
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
 
 #include <stdlib.h>
 #include <string.h>
 #include <errno.h>
 #include <arpa/inet.h>
 #include <signal.h>
 #include <sys/wait.h>
 #include <inttypes.h>
 #include <dirent.h>
 #include <stdio.h>
 #include <assert.h>
 #include <time.h>
 #include "ticket.h"
 #include "config.h"
 #include "inline-fn.h"
 #include "log.h"
-#include "pacemaker.h"
+#include "pcmk.h"
 #include "booth.h"
 #include "handler.h"
 
 static int set_booth_env(struct ticket_config *tk)
 {
 	int rv;
 	char expires[16];
 
 	sprintf(expires, "%" PRId64, (int64_t)wall_ts(&tk->term_expires));
 	rv = setenv("BOOTH_TICKET", tk->name, 1) ||
 		setenv("BOOTH_LOCAL", local->addr_string, 1) ||
 		setenv("BOOTH_CONF_NAME", booth_conf->name, 1) ||
 		setenv("BOOTH_CONF_PATH", cl.configfile, 1) ||
 		setenv("BOOTH_TICKET_EXPIRES", expires, 1);
 
 	if (rv) {
 		log_error("Cannot set environment: %s", strerror(errno));
 	}
 	return rv;
 }
 
 static void
 closefiles(void)
 {
 	int fd;
 
 	/* close all descriptors except stdin/out/err */
 	for (fd = getdtablesize() - 1; fd > STDERR_FILENO; fd--) {
 		close(fd);
 	}
 }
 
 static void
 run_ext_prog(struct ticket_config *tk, char *prog)
 {
 	if (set_booth_env(tk)) {
 		_exit(1);
 	}
 	closefiles(); /* don't leak open files */
 	tk_log_debug("running handler %s", prog);
 	execv(prog, tk_test.argv);
 	tk_log_error("%s: execv failed (%s)", prog, strerror(errno));
 	_exit(1);
 }
 
 static int
 prog_filter(const struct dirent *dp)
 {
 	return (*dp->d_name != '.');
 }
 
 static pid_t curr_pid;
 static int ignore_status;
 
 static int
 test_exit_status(struct ticket_config *tk, char *prog, int status, int log_msg)
 {
 	int rv = -1;
 
 	if (WIFEXITED(status)) {
 		rv = WEXITSTATUS(status);
 	} else if (WIFSIGNALED(status)) {
 		rv = 128 + WTERMSIG(status);
 	}
 	if (rv) {
 		if (log_msg) {
 			tk_log_warn("handler \"%s\" failed: %s",
 				prog, interpret_rv(status));
 			tk_log_warn("we are not allowed to acquire ticket");
 		}
 	} else {
 		tk_log_debug("handler \"%s\" exited with success",
 			prog);
 	}
 	return rv;
 }
 
 static void
 reset_test_state(struct ticket_config *tk)
 {
 	tk_test.pid = 0;
 	set_progstate(tk, EXTPROG_IDLE);
 }
 
 int tk_test_exit_status(struct ticket_config *tk)
 {
 	int rv;
 
 	rv = test_exit_status(tk, tk_test.path, tk_test.status, !tk_test.is_dir);
 	reset_test_state(tk);
 	return rv;
 }
 
 void wait_child(int sig)
 {
 	int i, status;
 	struct ticket_config *tk;
 
 	/* use waitpid(2) and not wait(2) in order not to interfere
-	 * with popen(2)/pclose(2) and system(2) used in pacemaker.c
+	 * with popen(2)/pclose(2) and system(2) used in pcmk.c
 	 */
 	_FOREACH_TICKET(i, tk) {
 		if (tk_test.path && tk_test.pid > 0 &&
 				(tk_test.progstate == EXTPROG_RUNNING ||
 				tk_test.progstate == EXTPROG_IGNORE) &&
 				waitpid(tk_test.pid, &status, WNOHANG) == tk_test.pid) {
 			if (tk_test.progstate == EXTPROG_IGNORE) {
 				/* not interested in the outcome */
 				reset_test_state(tk);
 			} else {
 				tk_test.status = status;
 				set_progstate(tk, EXTPROG_EXITED);
 			}
 		}
 	}
 }
 
 /* the parent may want to have us stop processing scripts, say
  * when the ticket gets revoked
  */
 static void ignore_rest(int sig)
 {
 	signal(SIGTERM, SIG_IGN);
 	ignore_status = 1;
 	if (curr_pid > 0) {
 		(void)kill(curr_pid, SIGTERM);
 	}
 }
 
 void ext_prog_timeout(struct ticket_config *tk)
 {
 	tk_log_warn("handler timed out");
 }
 
 int is_ext_prog_running(struct ticket_config *tk)
 {
 	if (!tk_test.path)
 		return 0;
 	return (tk_test.pid > 0 && tk_test.progstate == EXTPROG_RUNNING);
 }
 
 void ignore_ext_test(struct ticket_config *tk)
 {
 	if (is_ext_prog_running(tk)) {
 		(void)kill(tk_test.pid, SIGTERM);
 		set_progstate(tk, EXTPROG_IGNORE);
 	} else if (tk_test.progstate == EXTPROG_EXITED) {
 		/* external prog exited, but the status not yet examined;
 		 * we're not interested in checking the status anymore */
 		reset_test_state(tk);
 	}
 }
 
 static void
 process_ext_dir(struct ticket_config *tk)
 {
 	char prog[FILENAME_MAX+1];
 	int rv, n_progs, i, status;
 	struct dirent **proglist, *dp;
 
 	signal(SIGTERM, (__sighandler_t)ignore_rest);
 	signal(SIGCHLD, SIG_DFL);
 	signal(SIGUSR1, SIG_DFL);
 	signal(SIGINT, SIG_DFL);
 	tk_log_debug("running programs in directory %s", tk_test.path);
 	n_progs = scandir(tk_test.path, &proglist, prog_filter, alphasort);
 	if (n_progs == -1) {
 		tk_log_error("%s: scandir failed (%s)", tk_test.path, strerror(errno));
 		_exit(1);
 	}
 	for (i = 0; i < n_progs; i++) {
 		if (ignore_status)
 			break;
 		dp = proglist[i];
 		if (strlen(dp->d_name) + strlen(tk_test.path) + 1 > FILENAME_MAX) {
 			tk_log_error("%s: name exceeds max length (%s)",
 				tk_test.path, dp->d_name);
 			_exit(1);
 		}
 		strcpy(prog, tk_test.path);
 		strcat(prog, "/");
 		strcat(prog, dp->d_name);
 		switch(curr_pid=fork()) {
 		case -1:
 			log_error("fork: %s", strerror(errno));
 			_exit(1);
 		case 0: /* child */
 			run_ext_prog(tk, prog);
 			break;  /* run_ext_prog effectively noreturn */
 		default: /* parent */
 			while (waitpid(curr_pid, &status, 0) != curr_pid)
 				;
 			curr_pid = 0;
 			if (!ignore_status) {
 				rv = test_exit_status(tk, prog, status, 1);
 				if (rv)
 					_exit(rv);
 			} else {
 				/*
 				 * To make ignore_rest function signal safe log_info
 				 * must be removed from signal function. Information
 				 * about signal delivery is important so put it here.
 				 */
 				log_info("external programs handler caught TERM, ignoring "
 				    "status of external test programs");
 			}
 		}
 	}
 	_exit(0);
 }
 
 /* run some external program
  * return codes:
  * RUNCMD_ERR: executing program failed (or some other failure)
  * RUNCMD_MORE: program forked, results later
  */
 int run_handler(struct ticket_config *tk)
 {
 	int rv = 0;
 	pid_t pid;
 	struct stat stbuf;
 
 	if (!tk_test.path)
 		return 0;
 
 	if (stat(tk_test.path, &stbuf)) {
 		tk_log_error("%s: stat failed (%s)", tk_test.path, strerror(errno));
 		return RUNCMD_ERR;
 	}
 	tk_test.is_dir = (stbuf.st_mode & S_IFDIR);
 
 	switch(pid=fork()) {
 	case -1:
 		log_error("fork: %s", strerror(errno));
 		return RUNCMD_ERR;
 	case 0: /* child */
 		if (tk_test.is_dir) {
 			process_ext_dir(tk);
 		} else {
 			run_ext_prog(tk, tk_test.path);
 		}
 	default: /* parent */
 		tk_test.pid = pid;
 		set_progstate(tk, EXTPROG_RUNNING);
 		rv = RUNCMD_MORE; /* program runs */
 	}
 
 	return rv;
 }
diff --git a/src/main.c b/src/main.c
index 0114cf5..b4e3492 100644
--- a/src/main.c
+++ b/src/main.c
@@ -1,1701 +1,1708 @@
 /* 
  * Copyright (C) 2011 Jiaju Zhang <jjzhang@suse.de>
  * Copyright (C) 2013-2014 Philipp Marek <philipp.marek@linbit.com>
  * 
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public
  * License as published by the Free Software Foundation; either
  * version 2 of the License, or (at your option) any later version.
  * 
  * This software is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  * General Public License for more details.
  * 
  * You should have received a copy of the GNU General Public License along
  * with this program; if not, write to the Free Software Foundation, Inc.,
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
 
 #include "b_config.h"
 
 #include <stdio.h>
 #include <unistd.h>
 #include <stdlib.h>
 #include <sched.h>
 #include <errno.h>
 #include <limits.h>
 #include <sys/file.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <sys/mman.h>
 #include <sys/time.h>
 #include <sys/resource.h>
 #include <sys/socket.h>
 #include <sys/un.h>
 #include <sys/poll.h>
 #include <sys/wait.h>
 #include <fcntl.h>
 #include <string.h>
 #include <ctype.h>
 #include <assert.h>
 #include <signal.h>
 #include <netdb.h>
 #include <arpa/inet.h>
 #include <sys/types.h>
 
 #include <crm/services.h>
 
 #if HAVE_LIBGNUTLS
 #include <gnutls/gnutls.h>
 #endif
 #if HAVE_LIBGCRYPT
 #include <gcrypt.h>
 #endif
 #ifndef NAMETAG_LIBSYSTEMD
 #include <clplumbing/setproctitle.h>
 #else
 #include "alt/nametag_libsystemd.h"
 #endif
 #ifdef COREDUMP_NURSING
 #include <sys/prctl.h>
 #include <clplumbing/coredumps.h>
 #endif
+#ifdef LIBPACEMAKER
+#include <pacemaker.h>
+#endif
 #include "log.h"
 #include "booth.h"
 #include "config.h"
 #include "transport.h"
 #include "inline-fn.h"
-#include "pacemaker.h"
+#include "pcmk.h"
 #include "ticket.h"
 #include "request.h"
 #include "attr.h"
 #include "handler.h"
 
 #define RELEASE_STR 	VERSION
 
 #define CLIENT_NALLOC		32
 
 static int daemonize = 1;
 int enable_stderr = 0;
 timetype start_time;
 
 
 /** Structure for "clients".
  * Filehandles with incoming data get registered here (and in pollfds),
  * along with their callbacks.
  * Because these can be reallocated with every new fd, addressing
  * happens _only_ by their numeric index. */
 struct client *clients = NULL;
 struct pollfd *pollfds = NULL;
 static int client_maxi;
 static int client_size = 0;
 
 
 static const struct booth_site _no_leader = {
 	.addr_string = "none",
 	.site_id = NO_ONE,
 	.index = -1,
 };
 struct booth_site *const no_leader = (struct booth_site*) &_no_leader;
 
 typedef enum
 {
 	BOOTHD_STARTED=0,
 	BOOTHD_STARTING
 } BOOTH_DAEMON_STATE;
 
 int poll_timeout;
 
 
 
 struct booth_config *booth_conf;
 struct command_line cl;
 
 /*
  * Global signal handlers variables
  */
 static int sig_exit_handler_called = 0;
 static int sig_exit_handler_sig = 0;
 static int sig_usr1_handler_called = 0;
 static int sig_chld_handler_called = 0;
 
 static const char *state_string(BOOTH_DAEMON_STATE st)
 {
 	if (st == BOOTHD_STARTED) {
 		return "started";
 	} else if (st == BOOTHD_STARTING) {
 		return "starting";
 	} else {
 		return "invalid";
 	}
 }
 
 static void client_alloc(void)
 {
 	int i;
 
 	if (!(clients = realloc(
 		clients, (client_size + CLIENT_NALLOC) * sizeof(*clients))
 	) || !(pollfds = realloc(
 		pollfds, (client_size + CLIENT_NALLOC) * sizeof(*pollfds))
 	)) {
 		log_error("can't alloc for client array");
 		exit(1);
 	}
 
 	for (i = client_size; i < client_size + CLIENT_NALLOC; i++) {
 		clients[i].workfn = NULL;
 		clients[i].deadfn = NULL;
 		clients[i].fd = -1;
 		pollfds[i].fd = -1;
 		pollfds[i].revents = 0;
 	}
 	client_size += CLIENT_NALLOC;
 }
 
 static void client_dead(int ci)
 {
 	struct client *c = clients + ci;
 
 	if (c->fd != -1) {
 		log_debug("removing client %d", c->fd);
 		close(c->fd);
 	}
 
 	c->fd = -1;
 	c->workfn = NULL;
 
 	if (c->msg) {
 		free(c->msg);
 		c->msg = NULL;
 		c->offset = 0;
 	}
 
 	pollfds[ci].fd = -1;
 }
 
 int client_add(int fd, const struct booth_transport *tpt,
 		workfn_t workfn, void (*deadfn)(int ci))
 {
 	int i;
 	struct client *c;
 
 
 	if (client_size - 1 <= client_maxi ) {
 		client_alloc();
 	}
 
 	for (i = 0; i < client_size; i++) {
 		c = clients + i;
 		if (c->fd != -1)
 			continue;
 
 		c->workfn = workfn;
 		if (deadfn)
 			c->deadfn = deadfn;
 		else
 			c->deadfn = client_dead;
 
 		c->transport = tpt;
 		c->fd = fd;
 		c->msg = NULL;
 		c->offset = 0;
 
 		pollfds[i].fd = fd;
 		pollfds[i].events = POLLIN;
 		if (i > client_maxi)
 			client_maxi = i;
 
 		return i;
 	}
 
 	assert(!"no client");
 }
 
 int find_client_by_fd(int fd)
 {
 	int i;
 
 	if (fd < 0)
 		return -1;
 
 	for (i = 0; i <= client_maxi; i++) {
 		if (clients[i].fd == fd)
 			return i;
 	}
 	return -1;
 }
 
 static int format_peers(char **pdata, unsigned int *len)
 {
 	struct booth_site *s;
 	char *data, *cp;
 	char time_str[64];
 	int i, alloc;
 
 	*pdata = NULL;
 	*len = 0;
 
 	alloc = booth_conf->site_count * (BOOTH_NAME_LEN + 256);
 	data = malloc(alloc);
 	if (!data)
 		return -ENOMEM;
 
 	cp = data;
 	_FOREACH_NODE(i, s) {
 		if (s == local)
 			continue;
 		strftime(time_str, sizeof(time_str), "%F %T",
 			localtime(&s->last_recv));
 		cp += snprintf(cp,
 				alloc - (cp - data),
 				"%-12s %s, last recv: %s\n",
 				type_to_string(s->type),
 				s->addr_string,
 				time_str);
 		cp += snprintf(cp,
 				alloc - (cp - data),
 				"\tSent pkts:%u error:%u resends:%u\n",
 				s->sent_cnt,
 				s->sent_err_cnt,
 				s->resend_cnt);
 		cp += snprintf(cp,
 				alloc - (cp - data),
 				"\tRecv pkts:%u error:%u authfail:%u invalid:%u\n\n",
 				s->recv_cnt,
 				s->recv_err_cnt,
 				s->sec_cnt,
 				s->invalid_cnt);
 		if (alloc - (cp - data) <= 0) {
 			free(data);
 			return -ENOMEM;
 		}
 	}
 
 	*pdata = data;
 	*len = cp - data;
 
 	return 0;
 }
 
 
 void list_peers(struct booth_config *conf, int fd)
 {
 	char *data;
 	unsigned int olen;
 	struct boothc_hdr_msg hdr;
 
 	if (format_peers(&data, &olen) < 0) {
 		goto out;
 	}
 
 	init_header(&hdr.header, CL_LIST, 0, 0, RLT_SUCCESS, 0, sizeof(hdr) + olen);
 	send_header_plus(conf, fd, &hdr, data, olen);
 
 out:
 	if (data) {
 		free(data);
 	}
 }
 
 /* trim trailing spaces if the key is ascii
  */
 static void trim_key()
 {
 	char *p;
 	int i;
 
 	for (i=0, p=booth_conf->authkey; i < booth_conf->authkey_len; i++, p++)
 		if (!isascii(*p))
 			return;
 
 	p = booth_conf->authkey;
 	while (booth_conf->authkey_len > 0 && isspace(*p)) {
 		p++;
 		booth_conf->authkey_len--;
 	}
 	memmove(booth_conf->authkey, p, booth_conf->authkey_len);
 
 	p = booth_conf->authkey + booth_conf->authkey_len - 1;
 	while (booth_conf->authkey_len > 0 && isspace(*p)) {
 		booth_conf->authkey_len--;
 		p--;
 	}
 }
 
 static int read_authkey()
 {
 	int fd;
 
 	booth_conf->authkey[0] = '\0';
 	fd = open(booth_conf->authfile, O_RDONLY);
 	if (fd < 0) {
 		log_error("cannot open %s: %s",
 			booth_conf->authfile, strerror(errno));
 		return -1;
 	}
 	if (fstat(fd, &booth_conf->authstat) < 0) {
 		log_error("cannot stat authentication file %s (%d): %s",
 			booth_conf->authfile, fd, strerror(errno));
 		close(fd);
 		return -1;
 	}
 	if (booth_conf->authstat.st_mode & (S_IRGRP | S_IROTH)) {
 		log_error("%s: file shall not be readable for anyone but the owner",
 			booth_conf->authfile);
 		close(fd);
 		return -1;
 	}
 	booth_conf->authkey_len = read(fd, booth_conf->authkey, BOOTH_MAX_KEY_LEN);
 	close(fd);
 	trim_key();
 	log_debug("read key of size %d in authfile %s",
 		booth_conf->authkey_len, booth_conf->authfile);
 	/* make sure that the key is of minimum length */
 	return (booth_conf->authkey_len >= BOOTH_MIN_KEY_LEN) ? 0 : -1;
 }
 
 int update_authkey()
 {
 	struct stat statbuf;
 
 	if (stat(booth_conf->authfile, &statbuf) < 0) {
 		log_error("cannot stat authentication file %s: %s",
 			booth_conf->authfile, strerror(errno));
 		return -1;
 	}
 	if (statbuf.st_mtime > booth_conf->authstat.st_mtime) {
 		return read_authkey();
 	}
 	return 0;
 }
 
 static int setup_config(struct booth_config **conf, int type)
 {
 	int rv;
 
 	assert(conf != NULL);
 
 	rv = read_config(conf, cl.configfile, type);
 	if (rv < 0) {
 		goto out;
 	}
 
 	if (booth_conf->authfile[0] != '\0') {
 		rv = read_authkey();
 		if (rv < 0)
 			goto out;
 #if HAVE_LIBGCRYPT
 		if (!gcry_check_version(NULL)) {
 			log_error("gcry_check_version");
 			rv = -ENOENT;
 			goto out;
 		}
 		gcry_control(GCRYCTL_DISABLE_SECMEM, 0);
 		gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0);
 #endif
 #if HAVE_LIBGNUTLS
 		if (gnutls_global_init() != 0) {
 			log_error("Cannot initialize GnuTLS");
 			rv = -EINVAL;
 			goto out;
 		};
 #endif
 	}
 
 	/* Set "local" pointer, ignoring errors. */
 	if (cl.type == DAEMON && cl.site[0]) {
 		if (!find_site_by_name(booth_conf, cl.site, &local, 1)) {
 			log_error("Cannot find \"%s\" in the configuration.",
 					cl.site);
 			return -EINVAL;
 		}
 		local->local = 1;
 	} else {
 		find_myself(booth_conf, NULL, type == CLIENT || type == GEOSTORE);
 	}
 
 	rv = check_config(booth_conf, type);
 	if (rv < 0)
 		goto out;
 
 
 	/* Per default the PID file name is derived from the
 	 * configuration name. */
 	if (!cl.lockfile[0]) {
 		snprintf(cl.lockfile, sizeof(cl.lockfile) - 1,
 		         "%s/%s.pid", BOOTH_RUN_DIR, (*conf)->name);
 	}
 
 out:
 	return rv;
 }
 
 static int setup_transport(void)
 {
 	int rv;
 
 	rv = transport()->init(message_recv);
 	if (rv < 0) {
 		log_error("failed to init booth_transport %s", transport()->name);
 		goto out;
 	}
 
 	rv = booth_transport[TCP].init(NULL);
 	if (rv < 0) {
 		log_error("failed to init booth_transport[TCP]");
 		goto out;
 	}
 
 out:
 	return rv;
 }
 
 
 static int write_daemon_state(int fd, int state)
 {
 	char *buffer;
 	int rv, size;
 
 	rv = asprintf(&buffer, "booth_pid=%d booth_state=%s booth_type=%s "
 			       "booth_cfg_name='%s' booth_id=%d "
 			       "booth_addr_string='%s' booth_port=%d\n",
 		      getpid(), state_string(state), type_to_string(local->type),
 		      booth_conf->name, get_local_id(), site_string(local),
 		      site_port(local));
 
 	if (rv < 0) {
 		log_error("Buffer write failed in write_daemon_state().");
 		return -1;
 	}
 
 	size = rv;
 
 	rv = ftruncate(fd, 0);
 	if (rv < 0) {
 		log_error("lockfile %s truncate error %d: %s",
 			  cl.lockfile, errno, strerror(errno));
 		free(buffer);
 		return rv;
 	}
 
 	rv = lseek(fd, 0, SEEK_SET);
 	if (rv < 0) {
 		log_error("lseek set fd(%d) offset to 0 error, return(%d), message(%s)",
 			  fd, rv, strerror(errno));
 		free(buffer);
 		return -1;
 	}
 
 	rv = write(fd, buffer, size);
 
 	if (rv != size) {
 		log_error("write to fd(%d, %d) returned %d, errno %d, message(%s)",
 			  fd, size, rv, errno, strerror(errno));
 		free(buffer);
 		return -1;
 	}
 
 	free(buffer);
 	return 0;
 }
 
 static int process_signals(void)
 {
 	if (sig_exit_handler_called) {
 		log_info("caught signal %d", sig_exit_handler_sig);
 		return 1;
 	}
 	if (sig_usr1_handler_called) {
 		sig_usr1_handler_called = 0;
 		tickets_log_info(booth_conf);
 	}
 	if (sig_chld_handler_called) {
 		sig_chld_handler_called = 0;
 		wait_child(SIGCHLD);
 	}
 
 	return 0;
 }
 
 static int loop(int fd)
 {
 	workfn_t workfn;
 	void (*deadfn) (int ci);
 	int rv, i;
 
 	rv = setup_transport();
 	if (rv < 0)
 		goto fail;
 
 	rv = setup_ticket(booth_conf);
 	if (rv < 0) {
 		goto fail;
 	}
 
 	rv = write_daemon_state(fd, BOOTHD_STARTED);
 	if (rv != 0) {
 		log_error("write daemon state %d to lockfile error %s: %s",
                       BOOTHD_STARTED, cl.lockfile, strerror(errno));
 		goto fail;
 	}
 
 	log_info("BOOTH %s daemon started, node id is 0x%08X (%d).",
 		type_to_string(local->type),
 			local->site_id, local->site_id);
 
 	while (1) {
 		rv = poll(pollfds, client_maxi + 1, poll_timeout);
 		if (rv == -1 && errno == EINTR)
 			continue;
 		if (rv < 0) {
 			log_error("poll failed: %s (%d)", strerror(errno), errno);
 			goto fail;
 		}
 
 		for (i = 0; i <= client_maxi; i++) {
 			if (clients[i].fd < 0)
 				continue;
 
 			if (pollfds[i].revents & POLLIN) {
 				workfn = clients[i].workfn;
 				if (workfn) {
 					workfn(booth_conf, i);
 				}
 			}
 			if (pollfds[i].revents &
 					(POLLERR | POLLHUP | POLLNVAL)) {
 				deadfn = clients[i].deadfn;
 				if (deadfn)
 					deadfn(i);
 			}
 		}
 
 		process_tickets(booth_conf);
 
 		if (process_signals() != 0) {
 			return 0;
 		}
 	}
 
 	return 0;
 
 fail:
 	return -1;
 }
 
 
 static int test_reply(cmd_result_t reply_code, cmd_request_t cmd)
 {
 	int rv = 0;
 	const char *op_str = NULL;
 
 	if (cmd == CMD_GRANT)
 		op_str = "grant";
 	else if (cmd == CMD_REVOKE)
 		op_str = "revoke";
 	else if (cmd == CMD_LIST)
 		op_str = "list";
 	else if (cmd == CMD_PEERS)
 		op_str = "peers";
 	else {
 		log_error("internal error reading reply result!");
 		return -1;
 	}
 
 	switch (reply_code) {
 	case RLT_OVERGRANT:
 		log_info("You're granting a granted ticket. "
 			 "If you wanted to migrate a ticket, "
 			 "use revoke first, then use grant.");
 		rv = -1;
 		break;
 
 	case RLT_TICKET_IDLE:
 		log_info("ticket is not owned");
 		rv = 0;
 		break;
 
 	case RLT_ASYNC:
 		log_info("%s command sent, result will be returned "
 			 "asynchronously. Please use \"booth list\" to "
 			 "see the outcome.", op_str);
 		rv = 0;
 		break;
 
 	case RLT_CIB_PENDING:
 		log_info("%s succeeded (CIB commit pending)", op_str);
 		/* wait for the CIB commit? */
 		rv = (cl.options & OPT_WAIT_COMMIT) ? 3 : 0;
 		break;
 
 	case RLT_MORE:
 		rv = 2;
 		break;
 
 	case RLT_SYNC_SUCC:
 	case RLT_SUCCESS:
 		if (cmd != CMD_LIST && cmd != CMD_PEERS)
 			log_info("%s succeeded!", op_str);
 		rv = 0;
 		break;
 
 	case RLT_SYNC_FAIL:
 		log_info("%s failed!", op_str);
 		rv = -1;
 		break;
 
 	case RLT_INVALID_ARG:
 		log_error("ticket \"%s\" does not exist",
 				cl.msg.ticket.id);
 		rv = -1;
 		break;
 
 	case RLT_AUTH:
 		log_error("authentication error");
 		rv = -1;
 		break;
 
 	case RLT_EXT_FAILED:
 		log_error("before-acquire-handler for ticket \"%s\" failed, grant denied",
 				cl.msg.ticket.id);
 		rv = -1;
 		break;
 
 	case RLT_ATTR_PREREQ:
 		log_error("attr-prereq for ticket \"%s\" failed, grant denied",
 				cl.msg.ticket.id);
 		rv = -1;
 		break;
 
 	case RLT_REDIRECT:
 		/* talk to another site */
 		rv = 1;
 		break;
 
 	default:
 		log_error("got an error code: %x", rv);
 		rv = -1;
 	}
 	return rv;
 }
 
 static int query_get_string_answer(cmd_request_t cmd)
 {
 	struct booth_site *site;
 	struct boothc_hdr_msg reply;
 	struct boothc_header *header;
 	char *data;
 	int data_len;
 	int rv;
 	struct booth_transport const *tpt;
 	int (*test_reply_f) (cmd_result_t reply_code, cmd_request_t cmd);
 	size_t msg_size;
 	void *request;
 
 	if (cl.type == GEOSTORE) {
 		test_reply_f = test_attr_reply;
 		msg_size = sizeof(cl.attr_msg);
 		request = &cl.attr_msg;
 	} else {
 		test_reply_f = test_reply;
 		msg_size = sizeof(cl.msg);
 		request = &cl.msg;
 	}
 	header = (struct boothc_header *)request;
 	data = NULL;
 
 	init_header(header, cmd, 0, cl.options, 0, 0, msg_size);
 
 	if (!*cl.site)
 		site = local;
 	else if (!find_site_by_name(booth_conf, cl.site, &site, 1)) {
 		log_error("cannot find site \"%s\"", cl.site);
 		rv = ENOENT;
 		goto out;
 	}
 
 	tpt = booth_transport + TCP;
 	rv = tpt->open(site);
 	if (rv < 0)
 		goto out_close;
 
 	rv = tpt->send(booth_conf, site, request, msg_size);
 	if (rv < 0)
 		goto out_close;
 
 	rv = tpt->recv_auth(booth_conf, site, &reply, sizeof(reply));
 	if (rv < 0)
 		goto out_close;
 
 	data_len = ntohl(reply.header.length) - rv;
 
 	/* no attribute, or no ticket found */
 	if (!data_len) {
 		goto out_test_reply;
 	}
 
 	data = malloc(data_len+1);
 	if (!data) {
 		rv = -ENOMEM;
 		goto out_close;
 	}
 	rv = tpt->recv(site, data, data_len);
 	if (rv < 0)
 		goto out_close;
 
 	*(data + data_len) = '\0';
 	(void)fputs(data, stdout);
 	fflush(stdout);
 
 out_test_reply:
 	rv = test_reply_f(ntohl(reply.header.result), cmd);
 out_close:
 	tpt->close(site);
 out:
 	if (data)
 		free(data);
 	return rv;
 }
 
 
 static int do_command(cmd_request_t cmd)
 {
 	struct booth_site *site;
 	struct boothc_ticket_msg reply;
 	struct booth_transport const *tpt;
 	uint32_t leader_id;
 	int rv;
 	int reply_cnt = 0, msg_logged = 0;
 	const char *op_str = "";
 
 	if (cmd == CMD_GRANT)
 		op_str = "grant";
 	else if (cmd == CMD_REVOKE)
 		op_str = "revoke";
 
 	rv = -1;
 	site = NULL;
 
 	/* Always use TCP for client - at least for now. */
 	tpt = booth_transport + TCP;
 
 	if (!*cl.site)
 		site = local;
 	else {
 		if (!find_site_by_name(booth_conf, cl.site, &site, 1)) {
 			log_error("Site \"%s\" not configured.", cl.site);
 			goto out_close;
 		}
 	}
 
 	if (site->type == ARBITRATOR) {
 		if (site == local) {
 			log_error("We're just an arbitrator, cannot grant/revoke tickets here.");
 		} else {
 			log_error("%s is just an arbitrator, cannot grant/revoke tickets there.", cl.site);
 		}
 		goto out_close;
 	}
 
 	assert(site->type == SITE);
 
 	/* We don't check for existence of ticket, so that asking can be
 	 * done without local configuration, too.
 	 * Although, that means that the UDP port has to be specified, too. */
 	if (!cl.msg.ticket.id[0]) {
 		/* If the loaded configuration has only a single ticket defined, use that. */
 		if (booth_conf->ticket_count == 1) {
 			strncpy(cl.msg.ticket.id, booth_conf->ticket[0].name,
 				sizeof(cl.msg.ticket.id));
 		} else {
 			log_error("No ticket given.");
 			goto out_close;
 		}
 	}
 
 redirect:
 	init_header(&cl.msg.header, cmd, 0, cl.options, 0, 0, sizeof(cl.msg));
 
 	rv = tpt->open(site);
 	if (rv < 0)
 		goto out_close;
 
 	rv = tpt->send(booth_conf, site, &cl.msg, sendmsglen(&cl.msg));
 	if (rv < 0) {
 		goto out_close;
 	}
 
 read_more:
 	rv = tpt->recv_auth(booth_conf, site, &reply, sizeof(reply));
 	if (rv < 0) {
 		/* print any errors depending on the code sent by the
 		 * server */
 		(void)test_reply(ntohl(reply.header.result), cmd);
 		goto out_close;
 	}
 
 	rv = test_reply(ntohl(reply.header.result), cmd);
 	if (rv == 1) {
 		tpt->close(site);
 		leader_id = ntohl(reply.ticket.leader);
 		if (!find_site_by_id(booth_conf, leader_id, &site)) {
 			log_error("Message with unknown redirect site %x received", leader_id);
 			rv = -1;
 			goto out_close;
 		}
 		goto redirect;
 	} else if (rv == 2 || rv == 3) {
 		/* the server has more to say */
 		/* don't wait too long */
 		if (reply_cnt > 1 && !(cl.options & OPT_WAIT)) {
 			rv = 0;
 			log_info("Giving up on waiting for the definite result. "
 				 "Please use \"booth list\" later to "
 				 "see the outcome.");
 			goto out_close;
 		}
 		if (reply_cnt == 0) {
 			log_info("%s request sent, "
 				"waiting for the result ...", op_str);
 			msg_logged++;
 		} else if (rv == 3 && msg_logged < 2) {
 			log_info("waiting for the CIB commit ...");
 			msg_logged++;
 		}
 		reply_cnt++;
 		goto read_more;
 	}
 
 out_close:
 	if (site)
 		tpt->close(site);
 	return rv;
 }
 
 
 
 static int _lockfile(int mode, int *fdp, pid_t *locked_by)
 {
 	struct flock lock;
 	int fd, rv;
 
 
 	/* After reboot the directory may not yet exist.
 	 * Try to create it, but ignore errors. */
 	if (strncmp(cl.lockfile, BOOTH_RUN_DIR,
 				strlen(BOOTH_RUN_DIR)) == 0)
 		(void)mkdir(BOOTH_RUN_DIR, 0775);
 
 
 	if (locked_by)
 		*locked_by = 0;
 
 	*fdp = -1;
 	fd = open(cl.lockfile, mode, 0664);
 	if (fd < 0)
 		return errno;
 
 	*fdp = fd;
 
 	lock.l_type = F_WRLCK;
 	lock.l_start = 0;
 	lock.l_whence = SEEK_SET;
 	lock.l_len = 0;
 	lock.l_pid = 0;
 
 
 	if (fcntl(fd, F_SETLK, &lock) == 0)
 		return 0;
 
 	rv = errno;
 
 	if (locked_by)
 		if (fcntl(fd, F_GETLK, &lock) == 0)
 			*locked_by = lock.l_pid;
 
 	return rv;
 }
 
 
 static inline int is_root(void)
 {
 	return geteuid() == 0;
 }
 
 
 static int create_lockfile(void)
 {
 	int rv, fd;
 
 	fd = -1;
 	rv = _lockfile(O_CREAT | O_WRONLY, &fd, NULL);
 
 	if (fd == -1) {
 		log_error("lockfile %s open error %d: %s",
 				cl.lockfile, rv, strerror(rv));
 		return -1;
 	}
 
 	if (rv < 0) {
 		log_error("lockfile %s setlk error %d: %s",
 				cl.lockfile, rv, strerror(rv));
 		goto fail;
 	}
 
 	rv = write_daemon_state(fd, BOOTHD_STARTING);
 	if (rv != 0) {
 		log_error("write daemon state %d to lockfile error %s: %s",
 				BOOTHD_STARTING, cl.lockfile, strerror(errno));
 		goto fail;
 	}
 
 	if (is_root()) {
 		if (fchown(fd, booth_conf->uid, booth_conf->gid) < 0)
 			log_error("fchown() on lockfile said %d: %s",
 					errno, strerror(errno));
 	}
 
 	return fd;
 
 fail:
 	close(fd);
 	return -1;
 }
 
 static void unlink_lockfile(int fd)
 {
 	unlink(cl.lockfile);
 	close(fd);
 }
 
 static void print_usage(void)
 {
 	printf(
 	"Usage:\n"
 	"  booth list [options]\n"
 	"  booth {grant|revoke} [options] <ticket>\n"
 	"  booth status [options]\n"
 	"\n"
 	"  list:	     List all tickets\n"
 	"  grant:        Grant ticket to site\n"
 	"  revoke:       Revoke ticket\n"
 	"\n"
 	"Options:\n"
 	"  -c FILE       Specify config file [default " BOOTH_DEFAULT_CONF "]\n"
 	"                Can be a path or just a name without \".conf\" suffix\n"
 	"  -s <site>     Connect/grant to a different site\n"
 	"  -F            Try to grant the ticket immediately\n"
 	"                even if not all sites are reachable\n"
 	"                For manual tickets:\n"
 	"                grant a manual ticket even if it has been already granted\n"
 	"  -w            Wait forever for the outcome of the request\n"
 	"  -C            Wait until the ticket is committed to the CIB (grant only)\n"
 	"  -h            Print this help\n"
 	"\n"
 	"Examples:\n"
 	"\n"
 	"  # booth list (list tickets)\n"
 	"  # booth grant ticket-A (grant ticket here)\n"
 	"  # booth grant -s 10.121.8.183 ticket-A (grant ticket to site 10.121.8.183)\n"
 	"  # booth revoke ticket-A (revoke ticket)\n"
 	"\n"
 	"See the booth(8) man page for more details.\n"
 	);
 }
 
 #define OPTION_STRING		"c:Dl:t:s:FhSwC"
 #define ATTR_OPTION_STRING		"c:Dt:s:h"
 
 void safe_copy(char *dest, char *value, size_t buflen, const char *description) {
 	int content_len = buflen - 1;
 
 	if (strlen(value) >= content_len) {
 		fprintf(stderr, "'%s' exceeds maximum %s length of %d\n",
 			value, description, content_len);
 		exit(EXIT_FAILURE);
 	}
 	strncpy(dest, value, content_len);
 	dest[content_len] = 0;
 }
 
 static int host_convert(char *hostname, char *ip_str, size_t ip_size)
 {
 	struct addrinfo *result = NULL, hints = {0};
 	int re = -1;
 
 	memset(&hints, 0, sizeof(hints));
 	hints.ai_family = AF_INET;
 	hints.ai_socktype = SOCK_DGRAM;
 
 	re = getaddrinfo(hostname, NULL, &hints, &result);
 
 	if (re == 0) {
 		struct in_addr addr = ((struct sockaddr_in *)result->ai_addr)->sin_addr;
 		const char *re_ntop = inet_ntop(AF_INET, &addr, ip_str, ip_size);
 		if (re_ntop == NULL) {
 			re = -1;
 		}
 	}
 
 	freeaddrinfo(result);
 	return re;
 }
 
 #define cparg(dest, descr) do { \
 	if (optind >= argc) \
 		goto missingarg; \
 	safe_copy(dest, argv[optind], sizeof(dest), descr); \
 	optind++; \
 } while(0)
 
 static int read_arguments(int argc, char **argv)
 {
 	int optchar;
 	char *arg1 = argv[1];
 	char *op = NULL;
 	char *cp;
 	const char *opt_string = OPTION_STRING;
 	char site_arg[INET_ADDRSTRLEN] = {0};
 	int left;
 
 	cl.type = 0;
 	if ((cp = strstr(argv[0], ATTR_PROG)) &&
 			!strcmp(cp, ATTR_PROG)) {
 		cl.type = GEOSTORE;
 		op = argv[1];
 		optind = 2;
 		opt_string = ATTR_OPTION_STRING;
 	} else if (argc > 1 && (strcmp(arg1, "arbitrator") == 0 ||
 			strcmp(arg1, "site") == 0 ||
 			strcmp(arg1, "start") == 0 ||
 			strcmp(arg1, "daemon") == 0)) {
 		cl.type = DAEMON;
 		optind = 2;
 	} else if (argc > 1 && (strcmp(arg1, "status") == 0)) {
 		cl.type = STATUS;
 		optind = 2;
 	} else if (argc > 1 && (strcmp(arg1, "client") == 0)) {
 		cl.type = CLIENT;
 		if (argc < 3) {
 			print_usage();
 			exit(EXIT_FAILURE);
 		}
 		op = argv[2];
 		optind = 3;
 	}
 	if (!cl.type) {
 		cl.type = CLIENT;
 		op = argv[1];
 		optind = 2;
     }
 
 	if (argc < 2 || !strcmp(arg1, "help") || !strcmp(arg1, "--help") ||
 			!strcmp(arg1, "-h")) {
 		if (cl.type == GEOSTORE)
 			print_geostore_usage();
 		else
 			print_usage();
 		exit(EXIT_SUCCESS);
 	}
 
 	if (!strcmp(arg1, "version") || !strcmp(arg1, "--version") ||
 			!strcmp(arg1, "-V")) {
 		printf("%s %s\n", argv[0], RELEASE_STR);
 		exit(EXIT_SUCCESS);
 	}
 
     if (cl.type == CLIENT) {
 		if (!strcmp(op, "list"))
 			cl.op = CMD_LIST;
 		else if (!strcmp(op, "grant"))
 			cl.op = CMD_GRANT;
 		else if (!strcmp(op, "revoke"))
 			cl.op = CMD_REVOKE;
 		else if (!strcmp(op, "peers"))
 			cl.op = CMD_PEERS;
 		else {
 			fprintf(stderr, "client operation \"%s\" is unknown\n",
 					op);
 			exit(EXIT_FAILURE);
 		}
 	} else if (cl.type == GEOSTORE) {
 		if (!strcmp(op, "list"))
 			cl.op = ATTR_LIST;
 		else if (!strcmp(op, "set"))
 			cl.op = ATTR_SET;
 		else if (!strcmp(op, "get"))
 			cl.op = ATTR_GET;
 		else if (!strcmp(op, "delete"))
 			cl.op = ATTR_DEL;
 		else {
 			fprintf(stderr, "attribute operation \"%s\" is unknown\n",
 					op);
 			exit(EXIT_FAILURE);
 		}
 	}
 
 	while (optind < argc) {
 		optchar = getopt(argc, argv, opt_string);
 
 		switch (optchar) {
 		case 'c':
 			if (strchr(optarg, '/')) {
 				safe_copy(cl.configfile, optarg,
 						sizeof(cl.configfile), "config file");
 			} else {
 				/* If no "/" in there, use with default directory. */
 				strcpy(cl.configfile, BOOTH_DEFAULT_CONF_DIR);
 				cp = cl.configfile + strlen(BOOTH_DEFAULT_CONF_DIR);
 				assert(cp > cl.configfile);
 				assert(*(cp-1) == '/');
 
 				/* Write at the \0, ie. after the "/" */
 				safe_copy(cp, optarg,
 						(sizeof(cl.configfile) -
 						 (cp -  cl.configfile) -
 						 strlen(BOOTH_DEFAULT_CONF_EXT)),
 						"config name");
 
 				/* If no extension, append ".conf".
 				 * Space is available, see -strlen() above. */
 				if (!strchr(cp, '.'))
 					strcat(cp, BOOTH_DEFAULT_CONF_EXT);
 			}
 			break;
 
 		case 'D':
 			debug_level++;
 			break;
 
 		case 'S':
 			daemonize = 0;
 			enable_stderr = 1;
 			break;
 
 		case 'l':
 			safe_copy(cl.lockfile, optarg, sizeof(cl.lockfile), "lock file");
 			break;
 		case 't':
 			if (cl.op == CMD_GRANT || cl.op == CMD_REVOKE) {
 				safe_copy(cl.msg.ticket.id, optarg,
 						sizeof(cl.msg.ticket.id), "ticket name");
 			} else if (cl.type == GEOSTORE) {
 				safe_copy(cl.attr_msg.attr.tkt_id, optarg,
 						sizeof(cl.attr_msg.attr.tkt_id), "ticket name");
 			} else {
 				print_usage();
 				exit(EXIT_FAILURE);
 			}
 			break;
 
 		case 's':
 			/* For testing and debugging: allow "-s site" also for
 			 * daemon start, so that the address that should be used
 			 * can be set manually.
 			 * This makes it easier to start multiple processes
 			 * on one machine. */
 			if (cl.type == CLIENT || cl.type == GEOSTORE ||
 					(cl.type == DAEMON && debug_level)) {
 				if (strcmp(optarg, OTHER_SITE) &&
 						host_convert(optarg, site_arg, INET_ADDRSTRLEN) == 0) {
 					safe_copy(cl.site, site_arg, sizeof(cl.site), "site name");
 				} else {
 					safe_copy(cl.site, optarg, sizeof(cl.site), "site name");
 				}
 			} else {
 				log_error("\"-s\" not allowed in daemon mode.");
 				exit(EXIT_FAILURE);
 			}
 			break;
 
 		case 'F':
 			if (cl.type != CLIENT || cl.op != CMD_GRANT) {
 				log_error("use \"-F\" only for client grant");
 				exit(EXIT_FAILURE);
 			}
 			cl.options |= OPT_IMMEDIATE;
 			break;
 
 		case 'w':
 			if (cl.type != CLIENT ||
 					(cl.op != CMD_GRANT && cl.op != CMD_REVOKE)) {
 				log_error("use \"-w\" only for grant and revoke");
 				exit(EXIT_FAILURE);
 			}
 			cl.options |= OPT_WAIT;
 			break;
 
 		case 'C':
 			if (cl.type != CLIENT || cl.op != CMD_GRANT) {
 				log_error("use \"-C\" only for grant");
 				exit(EXIT_FAILURE);
 			}
 			cl.options |= OPT_WAIT | OPT_WAIT_COMMIT;
 			break;
 
 		case 'h':
 			if (cl.type == GEOSTORE)
 				print_geostore_usage();
 			else
 				print_usage();
 			exit(EXIT_SUCCESS);
 			break;
 
 		case ':':
 		case '?':
 			fprintf(stderr, "Please use '-h' for usage.\n");
 			exit(EXIT_FAILURE);
 			break;
 
 		case -1:
 			/* No more parameters on cmdline, only arguments. */
 			goto extra_args;
 
 		default:
 			goto unknown;
 		};
 	}
 
 	return 0;
 
 extra_args:
 	if (cl.type == CLIENT && !cl.msg.ticket.id[0]) {
 		cparg(cl.msg.ticket.id, "ticket name");
 	} else if (cl.type == GEOSTORE) {
 		if (cl.op != ATTR_LIST) {
 			cparg(cl.attr_msg.attr.name, "attribute name");
 		}
 		if (cl.op == ATTR_SET) {
 			cparg(cl.attr_msg.attr.val, "attribute value");
 		}
 	}
 
 	if (optind == argc)
 		return 0;
 
 
 	left = argc - optind;
 	fprintf(stderr, "Superfluous argument%s: %s%s\n",
 			left == 1 ? "" : "s",
 			argv[optind],
 			left == 1 ? "" : "...");
 	exit(EXIT_FAILURE);
 
 unknown:
 	fprintf(stderr, "unknown option: %s\n", argv[optind]);
 	exit(EXIT_FAILURE);
 
 missingarg:
 	fprintf(stderr, "not enough arguments\n");
 	exit(EXIT_FAILURE);
 }
 
 
 static void set_scheduler(void)
 {
 	struct sched_param sched_param;
 	struct rlimit rlimit;
 	int rv;
 
 	rlimit.rlim_cur = RLIM_INFINITY;
 	rlimit.rlim_max = RLIM_INFINITY;
 	rv = setrlimit(RLIMIT_MEMLOCK, &rlimit);
 	if (rv < 0) {
 		log_error("setrlimit failed");
 	} else {
                 rv = mlockall(MCL_CURRENT | MCL_FUTURE);
                 if (rv < 0) {
                         log_error("mlockall failed");
                 }
         }
 
 	rv = sched_get_priority_max(SCHED_RR);
 	if (rv != -1) {
 		sched_param.sched_priority = rv;
 		rv = sched_setscheduler(0, SCHED_RR, &sched_param);
 		if (rv == -1)
 			log_error("could not set SCHED_RR priority %d: %s (%d)",
 					sched_param.sched_priority,
 					strerror(errno), errno);
 	} else {
 		log_error("could not get maximum scheduler priority err %d",
 				errno);
 	}
 }
 
 static int set_procfs_val(const char *path, const char *val)
 {
 	int rc = -1;
 	FILE *fp = fopen(path, "w");
 
 	if (fp) {
 		if (fprintf(fp, "%s", val) > 0)
 			rc = 0;
 		fclose(fp);
 	}
 	return rc;
 }
 
 static int do_status(struct booth_config **conf, int type)
 {
 	pid_t pid;
 	int rv, status_lock_fd, ret;
 	const char *reason = NULL;
 	char lockfile_data[1024], *cp;
 
 	assert(conf != NULL);
 
 	ret = PCMK_OCF_NOT_RUNNING;
 
 	rv = setup_config(conf, type);
 	if (rv) {
 		reason = "Error reading configuration.";
 		ret = PCMK_OCF_UNKNOWN_ERROR;
 		goto quit;
 	}
 
 
 	if (!local) {
 		reason = "No Service IP active here.";
 		goto quit;
 	}
 
 
 	rv = _lockfile(O_RDWR, &status_lock_fd, &pid);
 	if (status_lock_fd == -1) {
 		reason = "No PID file.";
 		goto quit;
 	}
 	if (rv == 0) {
 		close(status_lock_fd);
 		reason = "PID file not locked.";
 		goto quit;
 	}
 	if (pid) {
 		fprintf(stdout, "booth_lockpid=%d ", pid);
 		fflush(stdout);
 	}
 
 	rv = read(status_lock_fd, lockfile_data, sizeof(lockfile_data) - 1);
 	if (rv < 4) {
 		close(status_lock_fd);
 		reason = "Cannot read lockfile data.";
 		ret = PCMK_LSB_UNKNOWN_ERROR;
 		goto quit;
 	}
 	lockfile_data[rv] = 0;
 
 	close(status_lock_fd);
 
 
 	/* Make sure it's only a single line */
 	cp = strchr(lockfile_data, '\r');
 	if (cp)
 		*cp = 0;
 	cp = strchr(lockfile_data, '\n');
 	if (cp)
 		*cp = 0;
 
 
 
 	rv = setup_tcp_listener(1);
 	if (rv == 0) {
 		reason = "TCP port not in use.";
 		goto quit;
 	}
 
 
 	fprintf(stdout, "booth_lockfile='%s' %s\n",
 			cl.lockfile, lockfile_data);
 	if (!daemonize)
 		fprintf(stderr, "Booth at %s port %d seems to be running.\n",
 		        local->addr_string, site_port(local));
 	return 0;
 
 
 quit:
 	log_debug("not running: %s", reason);
 	/* Ie. "DEBUG" */
 	if (!daemonize)
 		fprintf(stderr, "not running: %s\n", reason);
 	return ret;
 }
 
 
 static int limit_this_process(void)
 {
 	int rv;
 	if (!is_root())
 		return 0;
 
 	if (setregid(booth_conf->gid, booth_conf->gid) < 0) {
 		rv = errno;
 		log_error("setregid() didn't work: %s", strerror(rv));
 		return rv;
 	}
 
 	if (setreuid(booth_conf->uid, booth_conf->uid) < 0) {
 		rv = errno;
 		log_error("setreuid() didn't work: %s", strerror(rv));
 		return rv;
 	}
 
 	return 0;
 }
 
 static int lock_fd = -1;
 
 static void server_exit(void)
 {
 	int rv;
 
 	if (lock_fd >= 0) {
 		/* We might not be able to delete it, but at least
 		 * make it empty. */
 		rv = ftruncate(lock_fd, 0);
 		(void)rv;
 		unlink_lockfile(lock_fd);
 	}
 	log_info("exiting");
 }
 
 static void sig_exit_handler(int sig)
 {
 	sig_exit_handler_sig = sig;
 	sig_exit_handler_called = 1;
 }
 
 static void sig_usr1_handler(int sig)
 {
 	sig_usr1_handler_called = 1;
 }
 
 static void sig_chld_handler(int sig)
 {
 	sig_chld_handler_called = 1;
 }
 
 static int do_server(struct booth_config **conf, int type)
 {
 	int rv = -1;
 	static char log_ent[128] = DAEMON_NAME "-";
 
 	assert(conf != NULL);
 
 	rv = setup_config(conf, type);
 	if (rv < 0) {
 		return rv;
 	}
 
 	if (!local) {
 		log_error("Cannot find myself in the configuration.");
 		exit(EXIT_FAILURE);
 	}
 
 	if (daemonize) {
 		if (daemon(0, 0) < 0) {
 			perror("daemon error");
 			exit(EXIT_FAILURE);
 		}
 	}
 
 	/*
 	 * Register signal and exit handler
 	 */
 	signal(SIGUSR1, (__sighandler_t)sig_usr1_handler);
 	signal(SIGTERM, (__sighandler_t)sig_exit_handler);
 	signal(SIGINT, (__sighandler_t)sig_exit_handler);
 	/* we'll handle errors there and then */
 	signal(SIGPIPE, SIG_IGN);
 
 	atexit(server_exit);
 
 	/* The lockfile must be written to _after_ the call to daemon(), so
 	 * that the lockfile contains the pid of the daemon, not the parent. */
 	lock_fd = create_lockfile();
 	if (lock_fd < 0)
 		return lock_fd;
 
 	strcat(log_ent, type_to_string(local->type));
 	cl_log_set_entity(log_ent);
 	cl_log_enable_stderr(enable_stderr ? TRUE : FALSE);
 	cl_log_set_facility(HA_LOG_FACILITY);
 	cl_inherit_logging_environment(0);
 
 	log_info("BOOTH %s %s daemon is starting",
 			type_to_string(local->type), RELEASE_STR);
 
 
 	set_scheduler();
 	/* we don't want to be killed by the OOM-killer */
 	if (set_procfs_val("/proc/self/oom_score_adj", "-999"))
 		(void)set_procfs_val("/proc/self/oom_adj", "-16");
 	set_proc_title("%s %s %s for [%s]:%d",
 	               DAEMON_NAME, cl.configfile, type_to_string(local->type),
 	               local->addr_string, site_port(local));
 
 	rv = limit_this_process();
 	if (rv)
 		return rv;
 
 #ifdef COREDUMP_NURSING
 	if (cl_enable_coredumps(TRUE) < 0){
 		log_error("enabling core dump failed");
 	}
 	cl_cdtocoredir();
 	prctl(PR_SET_DUMPABLE, (unsigned long)TRUE, 0UL, 0UL, 0UL);
 #else
 	if (chdir(BOOTH_CORE_DIR) < 0) {
 		log_error("cannot change working directory to %s", BOOTH_CORE_DIR);
 	}
 #endif
 
 	signal(SIGCHLD, (__sighandler_t)sig_chld_handler);
 	rv = loop(lock_fd);
 
 	return rv;
 }
 
 static int do_client(struct booth_config **conf)
 {
 	int rv;
 
 	rv = setup_config(conf, CLIENT);
 	if (rv < 0) {
 		log_error("cannot read config");
 		goto out;
 	}
 
 	switch (cl.op) {
 	case CMD_LIST:
 	case CMD_PEERS:
 		rv = query_get_string_answer(cl.op);
 		break;
 
 	case CMD_GRANT:
 	case CMD_REVOKE:
 		rv = do_command(cl.op);
 		break;
 	}
 
 out:
 	return rv;
 }
 
 static int do_attr(struct booth_config **conf)
 {
 	int rv = -1;
 
 	assert(conf != NULL);
 
 	rv = setup_config(conf, GEOSTORE);
 	if (rv < 0) {
 		log_error("cannot read config");
 		goto out;
 	}
 
 	/* We don't check for existence of ticket, so that asking can be
 	 * done without local configuration, too.
 	 * Although, that means that the UDP port has to be specified, too. */
 	if (!cl.attr_msg.attr.tkt_id[0]) {
 		/* If the loaded configuration has only a single ticket defined, use that. */
 		if ((*conf)->ticket_count == 1) {
 			strncpy(cl.attr_msg.attr.tkt_id,
 			        (*conf)->ticket[0].name,
 			        sizeof(cl.attr_msg.attr.tkt_id));
 		} else {
 			rv = 1;
 			log_error("No ticket given.");
 			goto out;
 		}
 	}
 
 	switch (cl.op) {
 	case ATTR_LIST:
 	case ATTR_GET:
 		rv = query_get_string_answer(cl.op);
 		break;
 
 	case ATTR_SET:
 	case ATTR_DEL:
 		rv = do_attr_command(booth_conf, cl.op);
 		break;
 	}
 
 out:
 	return rv;
 }
 
 int main(int argc, char *argv[], char *envp[])
 {
 	int rv;
 	const char *cp;
 #ifdef LOGGING_LIBQB
 	enum qb_log_target_slot i;
 #endif
 
+#ifdef LIBPACEMAKER
+	pcmk_api_init();
+#endif
+
 	init_set_proc_title(argc, argv, envp);
 	get_time(&start_time);
 
 	memset(&cl, 0, sizeof(cl));
 	strncpy(cl.configfile,
 			BOOTH_DEFAULT_CONF, BOOTH_PATH_LEN - 1);
 	cl.lockfile[0] = 0;
 	debug_level = 0;
 
 
 	cp = ((cp = strstr(argv[0], ATTR_PROG)) && !strcmp(cp, ATTR_PROG)
 		? ATTR_PROG
 		: "booth");
 #ifndef LOGGING_LIBQB
 	cl_log_set_entity(cp);
 #else
 	qb_log_init(cp, LOG_USER, LOG_DEBUG);  /* prio driven by debug_level */
 	for (i = QB_LOG_TARGET_START; i < QB_LOG_TARGET_MAX; i++) {
 		if (i == QB_LOG_SYSLOG || i == QB_LOG_BLACKBOX)
 			continue;
 		qb_log_format_set(i, "%t %H %N: [%P]: %p: %b");
 	}
 	(void) qb_log_filter_ctl(QB_LOG_STDERR, QB_LOG_FILTER_ADD,
 	                         QB_LOG_FILTER_FILE, "*", LOG_DEBUG);
 #endif
 	cl_log_enable_stderr(TRUE);
 	cl_log_set_facility(0);
 
 	rv = read_arguments(argc, argv);
 	if (rv < 0)
 		goto out;
 
 
 	switch (cl.type) {
 	case STATUS:
 		rv = do_status(&booth_conf, cl.type);
 		break;
 
 	case ARBITRATOR:
 	case DAEMON:
 	case SITE:
 		rv = do_server(&booth_conf, cl.type);
 		break;
 
 	case CLIENT:
 		rv = do_client(&booth_conf);
 		break;
 
 	case GEOSTORE:
 		rv = do_attr(&booth_conf);
 		break;
 	}
 
 out:
 #if HAVE_LIBGNUTLS
 	gnutls_global_deinit();
 #endif
 #ifdef LOGGING_LIBQB
 	qb_log_fini();
 #endif
 	/* Normalize values. 0x100 would be seen as "OK" by waitpid(). */
 	return (rv >= 0 && rv < 0x70) ? rv : 1;
 }
diff --git a/src/pacemaker.c b/src/pcmk.c
similarity index 55%
rename from src/pacemaker.c
rename to src/pcmk.c
index 9febb08..7f280a0 100644
--- a/src/pacemaker.c
+++ b/src/pcmk.c
@@ -1,435 +1,708 @@
 /* 
  * Copyright (C) 2011 Jiaju Zhang <jjzhang@suse.de>
  * Copyright (C) 2013-2014 Philipp Marek <philipp.marek@linbit.com>
  * 
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public
  * License as published by the Free Software Foundation; either
  * version 2 of the License, or (at your option) any later version.
  * 
  * This software is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  * General Public License for more details.
  * 
  * You should have received a copy of the GNU General Public License along
  * with this program; if not, write to the Free Software Foundation, Inc.,
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
 
+#include "b_config.h"
+
+#include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <errno.h>
 #include <inttypes.h>
 #include <unistd.h>
 #include <sys/types.h>
 #include <sys/wait.h>
 #include <libxml/parser.h>
 #include <libxml/tree.h>
 #include "ticket.h"
 #include "log.h"
 #include "attr.h"
-#include "pacemaker.h"
+#include "pcmk.h"
 #include "inline-fn.h"
 
+#ifdef LIBPACEMAKER
+#include <crm/common/results.h>
+#include <pacemaker.h>
+#endif
 
 #define COMMAND_MAX	2048
 
 const char * interpret_rv(int rv)
 {
 	static char text[64];
 
 	if (rv == 0)
 		return "0";
 
 	if (WIFSIGNALED(rv))
 		sprintf(text, "got signal %d", WTERMSIG(rv));
 	else
 		sprintf(text, "exit code %d", WEXITSTATUS(rv));
 
 	return text;
 }
 
 
-static int pcmk_write_ticket_atomic(struct ticket_config *tk, int grant)
+#ifdef LIBPACEMAKER
+static int pcmk_write_ticket_atomic(struct ticket_config *tk, bool grant)
+{
+    char *owner_s = NULL;
+    char *expires_s = NULL;
+    char *term_s = NULL;
+    char *grant_s = NULL;
+    char *booth_cfg_name = NULL;
+    GHashTable *attrs = NULL;
+    xmlNode *xml = NULL;
+    int rv;
+
+    attrs = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, free);
+    if (attrs == NULL) {
+        log_error("out of memory");
+        return -1;
+    }
+
+    if (grant) {
+        grant_s = strdup("true");
+    } else {
+        grant_s = strdup("false");
+    }
+
+    if (grant_s == NULL) {
+        log_error("out of memory");
+        return -1;
+    }
+
+    booth_cfg_name = strdup(booth_conf->name);
+
+    if (booth_cfg_name == NULL) {
+        log_error("out of memory");
+        free(grant_s);
+        return -1;
+    }
+
+    if (asprintf(&owner_s, "%" PRIi32, get_node_id(tk->leader)) == -1 ||
+        asprintf(&expires_s, "%" PRIi64, wall_ts(&tk->term_expires)) == -1 ||
+        asprintf(&term_s, "%" PRIi64, (int64_t) tk->current_term) == -1) {
+        log_error("out of memory");
+        free(owner_s);
+        free(expires_s);
+        free(term_s);
+        free(grant_s);
+        return -1;
+    }
+
+    g_hash_table_insert(attrs, (gpointer) "owner", owner_s);
+    g_hash_table_insert(attrs, (gpointer) "expires", expires_s);
+    g_hash_table_insert(attrs, (gpointer) "term", term_s);
+    g_hash_table_insert(attrs, (gpointer) "granted", grant_s);
+    g_hash_table_insert(attrs, (gpointer) "booth-cfg-name", booth_conf);
+
+    rv = pcmk_ticket_set_attr(&xml, tk->name, attrs, true);
+    g_hash_table_remove_all(attrs);
+    xmlFreeNode(xml);
+
+    if (rv != pcmk_rc_ok) {
+        log_error("pcmk_write_ticket_atomic: %s", pcmk_rc_str(rv));
+        return -1;
+    }
+
+    return 0;
+}
+#else
+static int pcmk_write_ticket_atomic(struct ticket_config *tk, bool grant)
 {
 	char cmd[COMMAND_MAX];
 	int rv;
 
 	/* The long format (--attr-value=) for attribute value is used instead of "-v",
 	* so that NO_ONE (which is -1) isn't seen as another option. */
 	rv = snprintf(cmd, COMMAND_MAX,
 			"crm_ticket -t '%s' "
 			"%s --force "
 			"-S owner --attr-value=%" PRIi32 " "
 			"-S expires --attr-value=%" PRIi64 " "
 			"-S term --attr-value=%" PRIi64 " "
 			"-S booth-cfg-name --attr-value=%s",
 			tk->name,
-			(grant > 0 ? "-g" :
-			 grant < 0 ? "-r" :
-			 ""),
+			grant ? "-g" : "-r",
 			(int32_t)get_node_id(tk->leader),
 			(int64_t)wall_ts(&tk->term_expires),
 			(int64_t)tk->current_term,
 			booth_conf->name);
 
 	if (rv < 0 || rv >= COMMAND_MAX) {
 		log_error("pcmk_write_ticket_atomic: cannot format crm_ticket cmdline (probably too long)");
 		return -1;
 	}
 
 	rv = system(cmd);
 	log_debug("command: '%s' was executed", cmd);
 	if (rv != 0)
 		log_error("\"%s\" failed, %s", cmd, interpret_rv(rv));
 
 	return rv;
 }
+#endif
 
 
 static int pcmk_grant_ticket(struct ticket_config *tk)
 {
 
-	return pcmk_write_ticket_atomic(tk, +1);
+	return pcmk_write_ticket_atomic(tk, true);
 }
 
 
 static int pcmk_revoke_ticket(struct ticket_config *tk)
 {
 
-	return pcmk_write_ticket_atomic(tk, -1);
+	return pcmk_write_ticket_atomic(tk, false);
 }
 
 
+#ifdef LIBPACEMAKER
+static int pcmk_set_attr(struct ticket_config *tk, const char *attr, const char *val)
+{
+    GHashTable *attrs = NULL;
+    xmlNode *xml = NULL;
+    int rv;
+
+    attrs = g_hash_table_new(g_str_hash, g_str_equal);
+    if (attrs == NULL) {
+        log_error("out of memory");
+        return -1;
+    }
+
+    g_hash_table_insert(attrs, (gpointer) attr, (gpointer) val);
+
+    rv = pcmk_ticket_set_attr(&xml, tk->name, attrs, false);
+    g_hash_table_destroy(attrs);
+    xmlFreeNode(xml);
+
+    if (rv != pcmk_rc_ok) {
+        log_error("pcmk_set_attr: %s", pcmk_rc_str(rv));
+        return -1;
+    }
+
+    return 0;
+}
+
+static int pcmk_del_attr(struct ticket_config *tk, const char *attr)
+{
+    GList *attrs = NULL;
+    xmlNode *xml = NULL;
+    int rv;
+
+    attrs = g_list_append(attrs, (gpointer) attr);
+    if (attrs == NULL) {
+        log_error("out of memory");
+        return -1;
+    }
+
+    rv = pcmk_ticket_remove_attr(&xml, tk->name, attrs, false);
+    g_list_free(attrs);
+    xmlFreeNode(xml);
+
+    if (rv != pcmk_rc_ok) {
+        log_error("pcmk_del_attr: %s", pcmk_rc_str(rv));
+        return -1;
+    }
+
+    return 0;
+}
+#else
 static int _run_crm_ticket(char *cmd)
 {
 	int i, rv;
 
 	/* If there are errors, there's not much we can do but retry ... */
 	for (i=0; i<3 &&
 			(rv = system(cmd));
 			i++) ;
 
 	log_debug("'%s' gave result %s", cmd, interpret_rv(rv));
 
 	return rv;
 }
 
 static int pcmk_set_attr(struct ticket_config *tk, const char *attr, const char *val)
 {
 	char cmd[COMMAND_MAX];
 	int rv;
 
 	rv = snprintf(cmd, COMMAND_MAX,
 		 "crm_ticket -t '%s' -S '%s' --attr-value='%s'",
 		 tk->name, attr, val);
 
 	if (rv < 0 || rv >= COMMAND_MAX) {
 		log_error("pcmk_set_attr: cannot format crm_ticket cmdline (probably too long)");
 		return -1;
 	}
 
 	return _run_crm_ticket(cmd);
 }
 
 static int pcmk_del_attr(struct ticket_config *tk, const char *attr)
 {
 	char cmd[COMMAND_MAX];
 	int rv;
 
 	rv = snprintf(cmd, COMMAND_MAX,
 		 "crm_ticket -t '%s' -D '%s'",
 		 tk->name, attr);
 
 	if (rv < 0 || rv >= COMMAND_MAX) {
 		log_error("pcmk_del_attr: cannot format crm_ticket cmdline (probably too long)");
 		return -1;
 	}
 
 	return _run_crm_ticket(cmd);
 }
+#endif
 
 
 typedef int (*attr_f)(struct booth_config *conf, struct ticket_config *tk,
 		      const char *name, const char *val);
 
 struct attr_tab
 {
 	const char *name;
 	attr_f handling_f;
 };
 
 static int save_expires(struct booth_config *conf, struct ticket_config *tk,
 			const char *name, const char *val)
 {
 	secs2tv(unwall_ts(atol(val)), &tk->term_expires);
 	return 0;
 }
 
 static int save_term(struct booth_config *conf, struct ticket_config *tk,
 		     const char *name, const char *val)
 {
 	tk->current_term = atol(val);
 	return 0;
 }
 
 static int parse_boolean(const char *val)
 {
 	long v;
 
 	if (!strncmp(val, "false", 5)) {
 		v = 0;
 	} else if (!strncmp(val, "true", 4)) {
 		v = 1;
 	} else {
 		v = atol(val);
 	}
 	return v;
 }
 
 static int save_granted(struct booth_config *conf, struct ticket_config *tk,
 			const char *name, const char *val)
 {
 	tk->is_granted = parse_boolean(val);
 	return 0;
 }
 
 static int save_owner(struct booth_config *conf, struct ticket_config *tk,
 		      const char *name, const char *val)
 {
 	/* No check, node could have been deconfigured. */
 	tk->leader = NULL;
 	return !find_site_by_id(conf, atol(val), &tk->leader);
 }
 
 static int ignore_attr(struct booth_config *conf, struct ticket_config *tk,
 		       const char *name, const char *val)
 {
 	return 0;
 }
 
 static int save_attr(struct ticket_config *tk, const char *name,
 		     const char *val)
 {
 	/* tell store_geo_attr not to store time, we don't have that
 	 * information available
 	 */
 	return store_geo_attr(tk, name, val, 1);
 }
 
 struct attr_tab attr_handlers[] = {
 	{ "expires", save_expires},
 	{ "term", save_term},
 	{ "granted", save_granted},
 	{ "owner", save_owner},
 	{ "id", ignore_attr},
 	{ "last-granted", ignore_attr},
 	{ "booth-cfg-name", ignore_attr},
 	{ NULL, 0},
 };
 
 
 /* get_attr is currently not used and has not been tested
  */
+#ifdef LIBPACEMAKER
+static int attr_from_xml(xmlNode *xml, const char *ticket_id, const char *attr,
+                         char **vp)
+{
+    xmlXPathObject *xpath_obj = NULL;
+    xmlXPathContextPtr xpath_ctx = NULL;
+    xmlNode *match = NULL;
+    xmlAttr *attr_xml = NULL;
+    char *expr = NULL;
+    int rv = 0;
+
+    rv = asprintf(&expr, "//" PCMK_XE_PACEMAKER_RESULT "/" PCMK_XE_TICKETS "/"
+                         PCMK_XE_TICKET "[@" PCMK_XA_ID "=\"%s\"]/" PCMK_XE_ATTRIBUTE
+                         "[@" PCMK_XA_NAME "=\"%s\"]", ticket_id, attr);
+    if (rv != 0) {
+        log_error("attr_from_xml: out of memory");
+        goto done;
+    }
+
+    xpath_ctx = xmlXPathNewContext(xml->doc);
+    if (xpath_ctx == NULL) {
+        log_error("attr_from_xml: could not create xpath context");
+        rv = -1;
+        goto done;
+    }
+
+    xpath_obj = xmlXPathEvalExpression((const xmlChar *) expr, xpath_ctx);
+    if (xpath_obj == NULL || xpath_obj->nodesetval == NULL || xpath_obj->nodesetval->nodeNr) {
+        log_error("attr_from_xml: could not evaluate xpath expression");
+        rv = -1;
+        goto done;
+    }
+
+    match = xpath_obj->nodesetval->nodeTab[0];
+    if (match->type != XML_ELEMENT_NODE) {
+        log_error("attr_from_xml: xpath result is not an XML_ELEMENT_NODE");
+        rv = -1;
+        goto done;
+    }
+
+    attr_xml = xmlHasProp(match, (const xmlChar *) attr);
+    if (attr_xml != NULL) {
+        *vp = g_strdup((const char *) attr_xml->children->content);
+    }
+
+done:
+    if (expr != NULL) {
+        free(expr);
+    }
+
+    if (xpath_obj != NULL) {
+        xmlXPathFreeObject(xpath_obj);
+    }
+
+    if (xpath_ctx != NULL) {
+        xmlXPathFreeContext(xpath_ctx);
+    }
+
+    if (attr_xml != NULL) {
+        xmlFreeProp(attr_xml);
+    }
+
+    return rv;
+}
+
+
+static int pcmk_get_attr(struct ticket_config *tk, const char *attr, const char **vp)
+{
+    xmlNode *xml = NULL;
+    int rv;
+
+    rv = pcmk_ticket_get_attr(&xml, tk->name, attr, NULL);
+
+    if (rv != pcmk_rc_ok) {
+        log_error("pcmk_get_attr: %s", pcmk_rc_str(rv));
+        xmlFreeNode(xml);
+        return -1;
+    }
+
+    rv = attr_from_xml(xml, tk->name, attr, (char **) vp);
+
+    xmlFreeNode(xml);
+    return rv;
+}
+#else
 static int pcmk_get_attr(struct ticket_config *tk, const char *attr, const char **vp)
 {
 	char cmd[COMMAND_MAX];
 	char line[BOOTH_ATTRVAL_LEN+1];
 	int rv = 0, pipe_rv;
 	int res;
 	FILE *p;
 
 
 	*vp = NULL;
 	res = snprintf(cmd, COMMAND_MAX,
 			"crm_ticket -t '%s' -G '%s' --quiet",
 			tk->name, attr);
 	if (res < 0 || res >= COMMAND_MAX) {
 		log_error("pcmk_get_attr: cannot format crm_ticket cmdline (probably too long)");
 		return -1;
 	}
 
 	p = popen(cmd, "r");
 	if (p == NULL) {
 		pipe_rv = errno;
 		log_error("popen error %d (%s) for \"%s\"",
 				pipe_rv, strerror(pipe_rv), cmd);
 		return (pipe_rv != 0 ? pipe_rv : EINVAL);
 	}
 	if (fgets(line, BOOTH_ATTRVAL_LEN, p) == NULL) {
 		rv = ENODATA;
 		goto out;
 	}
 
 	*vp = g_strdup(line);
 
 out:
 	pipe_rv = pclose(p);
 	if (!pipe_rv) {
 		log_debug("command \"%s\"", cmd);
 	} else if (WEXITSTATUS(pipe_rv) == 6) {
 		log_info("command \"%s\", ticket not found", cmd);
 	} else {
 		log_error("command \"%s\" %s", cmd, interpret_rv(pipe_rv));
 	}
 	return rv | pipe_rv;
 }
+#endif
 
 static int save_attributes(struct booth_config *conf, struct ticket_config *tk,
 			   xmlDocPtr doc)
 {
 	int rv = 0, rc;
 	xmlNodePtr n;
 	xmlAttrPtr attr;
 	xmlChar *v;
 	struct attr_tab *atp;
 
 	n = xmlDocGetRootElement(doc);
 	if (n == NULL) {
 		tk_log_error("crm_ticket xml output empty");
 		return -EINVAL;
 	}
-	if (xmlStrcmp(n->name, (const xmlChar *)"ticket_state")) {
+
+    if (xmlStrcmp(n->name, (const xmlChar *) PCMK_XE_PACEMAKER_RESULT) == 0) {
+        xmlNode *tickets_node = NULL;
+        xmlNode *ticket_node = NULL;
+
+        /* This is XML from a libpacemaker API call.  Move the node pointer to
+         * the ticket element containing the attributes we want to copy.
+         */
+
+        /* Look for a child node named <tickets>. */
+        for (xmlNode *child = n->children; child != NULL; child = child->next) {
+            if (xmlStrcmp(child->name, (const xmlChar *) PCMK_XE_TICKETS) == 0) {
+                tickets_node = child;
+                break;
+            }
+        }
+
+        if (tickets_node == NULL) {
+            tk_log_error("API result does not match expected");
+            return -EINVAL;
+        }
+
+        /* Under that should be a single <ticket> node containing the attributes
+         * we want to copy.  libpacemaker should only return one node because we
+         * asked for a specific ticket, but just to be safe...
+         */
+        for (xmlNode *child = tickets_node->children; child != NULL; child = child->next) {
+            if (xmlStrcmp(child->name, (const xmlChar *) PCMK_XE_TICKET) == 0) {
+                ticket_node = child;
+                break;
+            }
+        }
+
+        if (ticket_node == NULL) {
+            tk_log_error("API result does not match expected");
+            return -EINVAL;
+        }
+
+        n = ticket_node;
+    } else if (xmlStrcmp(n->name, (const xmlChar *) "ticket_state") != 0) {
+        /* This isn't any XML we expect */
 		tk_log_error("crm_ticket xml root element not ticket_state");
 		return -EINVAL;
-	}
+    }
+
 	for (attr = n->properties; attr; attr = attr->next) {
 		v = xmlGetProp(n, attr->name);
 		for (atp = attr_handlers; atp->name; atp++) {
 			if (!strcmp(atp->name, (const char *) attr->name)) {
 				rc = atp->handling_f(conf, tk,
 						     (const char *) attr->name,
 						     (const char *) v);
 				break;
 			}
 		}
 		if (!atp->name) {
 			rc = save_attr(tk, (const char *) attr->name,
 				       (const char *) v);
 		}
 		if (rc) {
 			tk_log_error("error storing attribute %s", attr->name);
 			rv |= rc;
 		}
 		xmlFree(v);
 	}
 	return rv;
 }
 
 
+#ifdef LIBPACEMAKER
+static int pcmk_load_ticket(struct ticket_config *tk)
+{
+    xmlNode *xml = NULL;
+    int rv;
+
+    rv = pcmk_ticket_state(&xml, tk->name);
+
+    if (rv == pcmk_rc_ok) {
+        rv = save_attributes(tk, xml->doc);
+    } else {
+        log_error("pcmk_load_ticket: %s", pcmk_rc_str(rv));
+        rv = -1;
+    }
+
+    xmlFreeNode(xml);
+    return rv;
+}
+#else
+
 #define CHUNK_SIZE 256
 
-static int parse_ticket_state(struct booth_config *conf, struct ticket_config *tk,
-			      FILE *p)
+static int read_ticket_state(struct booth_config *conf, struct ticket_config *tk,
+			     xmlDocPtr *doc, FILE *p)
 {
 	int rv = 0;
 	GString *input = NULL;
 	char line[CHUNK_SIZE];
-	xmlDocPtr doc = NULL;
 	int opts = XML_PARSE_COMPACT | XML_PARSE_NONET;
 
 	/* skip first two lines of output */
 	if (fgets(line, CHUNK_SIZE-1, p) == NULL || fgets(line, CHUNK_SIZE-1, p) == NULL) {
 		tk_log_error("crm_ticket xml output empty");
 		rv = ENODATA;
 		goto out;
 	}
 	input = g_string_sized_new(CHUNK_SIZE);
 	if (!input) {
 		log_error("out of memory");
 		rv = -1;
 		goto out;
 	}
 	while (fgets(line, CHUNK_SIZE-1, p) != NULL) {
 		if (!g_string_append(input, line)) {
 			log_error("out of memory");
 			rv = -1;
 			goto out;
 		}
 	}
 
-	doc = xmlReadDoc((const xmlChar *) input->str, NULL, NULL, opts);
-	if (doc == NULL) {
+	*doc = xmlReadDoc((const xmlChar *) input->str, NULL, NULL, opts);
+	if (*doc == NULL) {
 		const xmlError *errptr = xmlGetLastError();
 		if (errptr) {
 			tk_log_error("crm_ticket xml parse failed (domain=%d, level=%d, code=%d): %s",
 					errptr->domain, errptr->level,
 					errptr->code, errptr->message);
 		} else {
 			tk_log_error("crm_ticket xml parse failed");
 		}
 		rv = -EINVAL;
 		goto out;
 	}
-	rv = save_attributes(conf, tk, doc);
 
 out:
-	if (doc)
-		xmlFreeDoc(doc);
 	if (input)
 		g_string_free(input, TRUE);
 	return rv;
 }
 
 static int pcmk_load_ticket(struct booth_config *conf, struct ticket_config *tk)
 {
+	xmlDocPtr doc;
 	char cmd[COMMAND_MAX];
 	int rv = 0, pipe_rv;
 	int res;
 	FILE *p;
 
 	res = snprintf(cmd, COMMAND_MAX,
 			"crm_ticket -t '%s' -q",
 			tk->name);
 
 	if (res < 0 || res >= COMMAND_MAX) {
 		log_error("pcmk_load_ticket: cannot format crm_ticket cmdline (probably too long)");
 		return -1;
 	}
 
 	p = popen(cmd, "r");
 	if (p == NULL) {
 		pipe_rv = errno;
 		log_error("popen error %d (%s) for \"%s\"",
 				pipe_rv, strerror(pipe_rv), cmd);
 		return (pipe_rv != 0 ? pipe_rv : EINVAL);
 	}
 
-	rv = parse_ticket_state(conf, tk, p);
+	rv = read_ticket_state(conf, tk, &doc, p);
+	if (rv == 0) {
+		rv = save_attributes(conf, tk, doc);
+		xmlFreeDoc(doc);
+	}
 
 	if (!tk->leader) {
 		/* Hmm, no site found for the ticket we have in the
 		 * CIB!?
 		 * Assume that the ticket belonged to us if it was
 		 * granted here!
 		 */
 		log_warn("%s: no site matches; site got reconfigured?",
 			tk->name);
 		if (tk->is_granted) {
 			log_warn("%s: granted here, assume it belonged to us",
 				tk->name);
 			set_leader(tk, local);
 		}
 	}
 
 	pipe_rv = pclose(p);
 	if (!pipe_rv) {
 		log_debug("command \"%s\"", cmd);
 	} else if (WEXITSTATUS(pipe_rv) == 6) {
 		log_info("command \"%s\", ticket not found", cmd);
 	} else {
 		log_error("command \"%s\" %s", cmd, interpret_rv(pipe_rv));
 	}
 	return rv | pipe_rv;
 }
+#endif
 
 
 struct ticket_handler pcmk_handler = {
 	.grant_ticket   = pcmk_grant_ticket,
 	.revoke_ticket  = pcmk_revoke_ticket,
 	.load_ticket    = pcmk_load_ticket,
 	.set_attr    = pcmk_set_attr,
 	.get_attr    = pcmk_get_attr,
 	.del_attr    = pcmk_del_attr,
 };
diff --git a/src/pacemaker.h b/src/pcmk.h
similarity index 100%
rename from src/pacemaker.h
rename to src/pcmk.h
diff --git a/src/ticket.c b/src/ticket.c
index fc5f3a7..c7ae3a6 100644
--- a/src/ticket.c
+++ b/src/ticket.c
@@ -1,1446 +1,1446 @@
 /*
  * Copyright (C) 2011 Jiaju Zhang <jjzhang@suse.de>
  * Copyright (C) 2013-2014 Philipp Marek <philipp.marek@linbit.com>
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public
  * License as published by the Free Software Foundation; either
  * version 2 of the License, or (at your option) any later version.
  *
  * This software is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  * General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License along
  * with this program; if not, write to the Free Software Foundation, Inc.,
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
 
 #include "b_config.h"
 
 #include <stdlib.h>
 #include <string.h>
 #include <errno.h>
 #include <arpa/inet.h>
 #include <inttypes.h>
 #include <stdio.h>
 #include <assert.h>
 #include <time.h>
 #ifndef RANGE2RANDOM_GLIB
 #include <clplumbing/cl_random.h>
 #else
 #include "alt/range2random_glib.h"
 #endif
 #include "ticket.h"
 #include "config.h"
-#include "pacemaker.h"
+#include "pcmk.h"
 #include "inline-fn.h"
 #include "log.h"
 #include "booth.h"
 #include "raft.h"
 #include "handler.h"
 #include "request.h"
 #include "manual.h"
 
 extern int TIME_RES;
 
 /* Untrusted input, must fit (incl. \0) in a buffer of max chars. */
 int check_max_len_valid(const char *s, int max)
 {
 	for (int i = 0; i < max; i++) {
 		if (s[i] == 0) {
 			return 1;
 		}
 	}
 
 	return 0;
 }
 
 int find_ticket_by_name(struct booth_config *conf, const char *ticket,
 			struct ticket_config **found)
 {
 	struct ticket_config *tk;
 	int i;
 
 	if (found) {
 		*found = NULL;
 	}
 
 	FOREACH_TICKET(conf, i, tk) {
 		if (strncmp(tk->name, ticket, sizeof(tk->name))) {
 			continue;
 		}
 
 		if (found) {
 			*found = tk;
 		}
 
 		return 1;
 	}
 
 	return 0;
 }
 
 int check_ticket(struct booth_config *conf, char *ticket,
 		 struct ticket_config **found)
 {
 	if (found) {
 		*found = NULL;
 	}
 
 	if (conf == NULL) {
 		return 0;
 	}
 
 	if (!check_max_len_valid(ticket, sizeof(conf->ticket[0].name))) {
 		return 0;
 	}
 
 	return find_ticket_by_name(conf, ticket, found);
 }
 
 /* is it safe to commit the grant?
  * if we didn't hear from all sites on the initial grant, we may
  * need to delay the commit
  *
  * TODO: investigate possibility to devise from history whether a
  * missing site could be holding a ticket or not
  */
 static int ticket_dangerous(struct ticket_config *tk)
 {
 	int tdiff;
 	/* we may be invoked often, don't spam the log unnecessarily
 	 */
 	static int no_log_delay_msg;
 
 	if (!is_time_set(&tk->delay_commit)) {
 		return 0;
 	}
 
 	if (is_past(&tk->delay_commit) || all_sites_replied(tk)) {
 		if (tk->leader == local) {
 			tk_log_info("%s, committing to CIB",
 				    is_past(&tk->delay_commit) ?
 				    "ticket delay expired" : "all sites replied");
 		}
 
 		time_reset(&tk->delay_commit);
 		no_log_delay_msg = 0;
 		return 0;
 	}
 
 	tdiff = time_left(&tk->delay_commit);
 	tk_log_debug("delay ticket commit for another " intfmt(tdiff));
 
 	if (!no_log_delay_msg) {
 		tk_log_info("delaying ticket commit to CIB for " intfmt(tdiff));
 		tk_log_info("(or all sites are reached)");
 		no_log_delay_msg = 1;
 	}
 
 	return 1;
 }
 
 int ticket_write(struct ticket_config *tk)
 {
 	if (local->type != SITE) {
 		return -EINVAL;
 	}
 
 	if (ticket_dangerous(tk)) {
 		return 1;
 	}
 
 	if (tk->leader == local) {
 		if (tk->state != ST_LEADER) {
 			tk_log_info("ticket state not yet consistent, "
 				    "delaying ticket grant to CIB");
 			return 1;
 		}
 		pcmk_handler.grant_ticket(tk);
 	} else {
 		pcmk_handler.revoke_ticket(tk);
 	}
 
 	tk->update_cib = 0;
 	return 0;
 }
 
 void save_committed_tkt(struct ticket_config *tk)
 {
 	if (!tk->last_valid_tk) {
 		tk->last_valid_tk = malloc(sizeof(struct ticket_config));
 
 		if (!tk->last_valid_tk) {
 			log_error("out of memory");
 			return;
 		}
 	}
 
 	memcpy(tk->last_valid_tk, tk, sizeof(struct ticket_config));
 }
 
 static void ext_prog_failed(struct booth_config *conf, struct ticket_config *tk,
 			    int start_election)
 {
 	if (!is_manual(tk)) {
 		/* Give it to somebody else.
 	 	 * Just send a VOTE_FOR message, so the
 		 * others can start elections. */
 		if (!leader_and_valid(tk)) {
 			return;
 		}
 
 		save_committed_tkt(tk);
 		reset_ticket(tk);
 		ticket_write(tk);
 
 		if (start_election) {
 			ticket_broadcast(conf, tk, OP_VOTE_FOR, OP_REQ_VOTE,
 					 RLT_SUCCESS, OR_LOCAL_FAIL);
 		}
 	} else {
 		/* There is not much we can do now because
 		 * the manual ticket cannot be relocated.
 		 * Just warn the user. */
 		if (tk->leader != local) {
 			return;
 		}
 
 		save_committed_tkt(tk);
 		reset_ticket(tk);
 		ticket_write(tk);
 		log_error("external test failed on the specified machine, cannot acquire a manual ticket");
 	}
 }
 
 #define attr_found(geo_ap, ap) \
 	((geo_ap) && !strcmp((geo_ap)->val, (ap)->attr_val))
 
 int check_attr_prereq(struct ticket_config *tk, grant_type_e grant_type)
 {
 	GList *el;
 	struct attr_prereq *ap;
 	struct geo_attr *geo_ap;
 
 	for (el = g_list_first(tk->attr_prereqs); el; el = g_list_next(el))
 	{
 		ap = (struct attr_prereq *) el->data;
 		if (ap->grant_type != grant_type) {
 			continue;
 		}
 
 		geo_ap = (struct geo_attr *) g_hash_table_lookup(tk->attr, ap->attr_name);
 
 		switch(ap->op) {
 		case ATTR_OP_EQ:
 			if (!attr_found(geo_ap, ap)) {
 				goto fail;
 			}
 			break;
 
 		case ATTR_OP_NE:
 			if (attr_found(geo_ap, ap)) {
 				goto fail;
 			}
 			break;
 
 		default:
 			break;
 		}
 	}
 
 	return 0;
 
 fail:
 	tk_log_warn("'%s' attr-prereq failed", ap->attr_name);
 	return 1;
 }
 
 /* do we need to run the external program?
  * or we already done that and waiting for the outcome
  * or program exited and we can collect the status
  * return codes
  * 0: no program defined
  * RUNCMD_MORE: program forked, results later
  * != 0: executing program failed (or some other failure)
  */
 
 static int do_ext_prog(struct booth_config *conf, struct ticket_config *tk,
 		       int start_election)
 {
 	int rv = 0;
 
 	if (!tk_test.path) {
 		return 0;
 	}
 
 	switch(tk_test.progstate) {
 	case EXTPROG_IDLE:
 		rv = run_handler(tk);
 		if (rv == RUNCMD_ERR) {
 			tk_log_warn("couldn't run external test, not allowed to acquire ticket");
 			ext_prog_failed(conf, tk, start_election);
 		}
 		break;
 
 	case EXTPROG_RUNNING:
 		/* should never get here, but just in case */
 		rv = RUNCMD_MORE;
 		break;
 
 	case EXTPROG_EXITED:
 		rv = tk_test_exit_status(tk);
 		if (rv) {
 			ext_prog_failed(conf, tk, start_election);
 		}
 		break;
 
 	case EXTPROG_IGNORE:
 		/* nothing to do here */
 		break;
 	}
 
 	return rv;
 }
 
 /* Try to acquire a ticket
  * Could be manual grant or after start (if the ticket is granted
  * and still valid in the CIB)
  * If the external program needs to run, this is run twice, once
  * to start the program, and then to get the result and start
  * elections.
  */
 static int acquire_ticket(struct booth_config *conf, struct ticket_config *tk,
 			  cmd_reason_t reason)
 {
 	int rv;
 
 	if (reason == OR_ADMIN && check_attr_prereq(tk, GRANT_MANUAL)) {
 		return RLT_ATTR_PREREQ;
 	}
 
 	switch(do_ext_prog(conf, tk, 0)) {
 	case 0:
 		/* everything fine */
 		break;
 
 	case RUNCMD_MORE:
 		/* need to wait for the outcome before starting elections */
 		return 0;
 
 	default:
 		return RLT_EXT_FAILED;
 	}
 
 	if (is_manual(tk)) {
 		rv = manual_selection(conf, tk, local, 1, reason);
 	} else {
 		rv = new_election(conf, tk, local, 1, reason);
 	}
 
 	return rv ? RLT_SYNC_FAIL : 0;
 }
 
 /** Try to get the ticket for the local site.
  * */
 static int do_grant_ticket(struct booth_config *conf, struct ticket_config *tk,
 			   int options)
 {
 	int rv;
 
 	tk_log_info("granting ticket");
 
 	if (tk->leader == local) {
 		return RLT_SUCCESS;
 	}
 
 	if (is_owned(tk)) {
 		if (is_manual(tk) && (options & OPT_IMMEDIATE)) {
 			/* -F flag has been used while granting a manual ticket.
 			 * The ticket will be granted and may end up being granted
 			 * on multiple sites */
 			tk_log_warn("manual ticket forced to be granted! be aware that "
 				    "you may end up having two sites holding the same manual "
 				    "ticket! revoke the ticket from the unnecessary site!");
 		} else {
 			return RLT_OVERGRANT;
 		}
 	}
 
 	set_future_time(&tk->delay_commit, tk->term_duration + tk->acquire_after);
 
 	if (options & OPT_IMMEDIATE) {
 		tk_log_warn("granting ticket immediately! If there are "
 			    "unreachable sites, _hope_ you are sure that they don't "
 			    "have the ticket!");
 		time_reset(&tk->delay_commit);
 	}
 
 	rv = acquire_ticket(conf, tk, OR_ADMIN);
 
 	if (rv) {
 		time_reset(&tk->delay_commit);
 		return rv;
 	} else {
 		return RLT_MORE;
 	}
 }
 
 static void start_revoke_ticket(struct booth_config *conf, struct ticket_config *tk)
 {
 	tk_log_info("revoking ticket");
 
 	save_committed_tkt(tk);
 	reset_ticket_and_set_no_leader(tk);
 	ticket_write(tk);
 	ticket_broadcast(conf, tk, OP_REVOKE, OP_ACK, RLT_SUCCESS, OR_ADMIN);
 }
 
 /** Ticket revoke.
  * Only to be started from the leader. */
 static int do_revoke_ticket(struct booth_config *conf, struct ticket_config *tk)
 {
 	if (tk->acks_expected) {
 		tk_log_info("delay ticket revoke until the current operation finishes");
 		set_next_state(tk, ST_INIT);
 		return RLT_MORE;
 	} else {
 		start_revoke_ticket(conf, tk);
 		return RLT_SUCCESS;
 	}
 }
 
 static int number_sites_marked_as_granted(struct booth_config *conf,
 					  struct ticket_config *tk)
 {
 	int i, result = 0;
 	struct booth_site *ignored __attribute__((unused));
 
 	FOREACH_NODE(conf, i, ignored) {
 		result += tk->sites_where_granted[i];
 	}
 
 	return result;
 }
 
 static int list_ticket(struct booth_config *conf, char **pdata)
 {
 	GString *s = NULL;
 	struct ticket_config *tk;
 	struct booth_site *site;
 	char timeout_str[64];
 	char *pending_str = NULL;
 	int i, site_index;
 	time_t ts;
 
 	s = g_string_sized_new(BUFSIZ);
 
 	if (s == NULL) {
 		return -ENOMEM;
 	}
 
 	FOREACH_TICKET(conf, i, tk) {
 		if (!is_manual(tk) && is_time_set(&tk->term_expires)) {
 			/* Manual tickets doesn't have term_expires defined */
 			ts = wall_ts(&tk->term_expires);
 			strftime(timeout_str, sizeof(timeout_str), "%F %T",
 				 localtime(&ts));
 		} else {
 			strcpy(timeout_str, "INF");
 		}
 
 		if (tk->leader == local && is_time_set(&tk->delay_commit) &&
 		    !is_past(&tk->delay_commit)) {
 			char until_str[64];
 			int rc;
 
 			ts = wall_ts(&tk->delay_commit);
 			strftime(until_str, sizeof(until_str), "%F %T",
 				 localtime(&ts));
 			rc = asprintf(&pending_str, " (commit pending until %s)",
 				      until_str);
 
 			if (rc < 0) {
 				g_string_free(s, TRUE);
 				return -ENOMEM;
 			}
 		}
 
 		g_string_append_printf(s, "ticket: %s, leader: %s", tk->name,
 				       ticket_leader_string(tk));
 
 		if (is_owned(tk)) {
 			g_string_append_printf(s, ", expires: %s", timeout_str);
 
 			if (pending_str != NULL) {
 				g_string_append(s, pending_str);
 			}
 		}
 
 		if (is_manual(tk)) {
 			g_string_append(s, " [manual mode]");
 		}
 
 		g_string_append(s, "\n");
 
 		if (pending_str != NULL) {
 			free(pending_str);
 			pending_str = NULL;
 		}
 	}
 
 	FOREACH_TICKET(conf, i, tk) {
 		int multiple_grant_warning_length = number_sites_marked_as_granted(conf, tk);
 
 		if (multiple_grant_warning_length <= 1) {
 			continue;
 		}
 
 		g_string_append_printf(s, "\nWARNING: The ticket %s is granted to multiple sites: ",
 				       tk->name);
 
 		FOREACH_NODE(conf, site_index, site) {
 			if (tk->sites_where_granted[site_index] <= 0) {
 				continue;
 			}
 
 			g_string_append(s, site_string(site));
 
 			multiple_grant_warning_length--;
 			if (multiple_grant_warning_length > 0) {
 				g_string_append(s, ", ");
 			}
 		}
 
 		g_string_append(s, ". Revoke the ticket from the faulty sites.\n");
 	}
 
 	*pdata = strdup(s->str);
 	g_string_free(s, TRUE);
 
 	if (*pdata == NULL) {
 		return -ENOMEM;
 	}
 
 	return 0;
 }
 
 void disown_ticket(struct ticket_config *tk)
 {
 	set_leader(tk, NULL);
 	tk->is_granted = 0;
 	get_time(&tk->term_expires);
 }
 
 void reset_ticket(struct ticket_config *tk)
 {
 	ignore_ext_test(tk);
 	disown_ticket(tk);
 	no_resends(tk);
 	set_state(tk, ST_INIT);
 	set_next_state(tk, 0);
 	tk->voted_for = NULL;
 }
 
 void reset_ticket_and_set_no_leader(struct ticket_config *tk)
 {
 	mark_ticket_as_revoked_from_leader(tk);
 	reset_ticket(tk);
 
 	tk->leader = no_leader;
 	tk_log_debug("ticket leader set to no_leader");
 }
 
 static void log_reacquire_reason(struct ticket_config *tk)
 {
 	int valid;
 	const char *where_granted = NULL;
 	char buff[75];
 
 	valid = is_time_set(&tk->term_expires) && !is_past(&tk->term_expires);
 
 	if (tk->leader == local) {
 		where_granted = "granted here";
 	} else {
 		snprintf(buff, sizeof(buff), "granted to %s",
 			 site_string(tk->leader));
 		where_granted = buff;
 	}
 
 	if (!valid) {
 		tk_log_warn("%s, but not valid anymore (will try to reacquire)",
 			    where_granted);
 	}
 
 	if (tk->is_granted && tk->leader != local) {
 		if (tk->leader && tk->leader != no_leader) {
 			tk_log_error("granted here, but also %s, "
 				     "that's really too bad (will try to reacquire)",
 				     where_granted);
 		} else {
 			tk_log_warn("granted here, but we're "
 				    "not recorded as the grantee (will try to reacquire)");
 		}
 	}
 }
 
 void update_ticket_state(struct ticket_config *tk, struct booth_site *sender)
 {
 	if (tk->state == ST_CANDIDATE) {
 		tk_log_info("learned from %s about newer ticket, stopping elections",
 			    site_string(sender));
 		/* there could be rejects coming from others; don't log
 		 * warnings unnecessarily */
 		tk->expect_more_rejects = 1;
 	}
 
 	if (tk->leader == local || tk->is_granted) {
 		/* message from a live leader with valid ticket? */
 		if (sender == tk->leader && term_time_left(tk)) {
 			if (tk->is_granted) {
 				tk_log_warn("ticket was granted here, "
 					    "but it's live at %s (revoking here)",
 					    site_string(sender));
 			} else {
 				tk_log_info("ticket live at %s", site_string(sender));
 			}
 
 			disown_ticket(tk);
 			ticket_write(tk);
 			set_state(tk, ST_FOLLOWER);
 			set_next_state(tk, ST_FOLLOWER);
 		} else {
 			if (tk->state == ST_CANDIDATE) {
 				set_state(tk, ST_FOLLOWER);
 			}
 
 			set_next_state(tk, ST_LEADER);
 		}
 	} else {
 		if (!tk->leader || tk->leader == no_leader) {
 			if (sender) {
 				tk_log_info("ticket is not granted");
 			} else {
 				tk_log_info("ticket is not granted (from CIB)");
 			}
 
 			set_state(tk, ST_INIT);
 		} else {
 			if (sender) {
 				tk_log_info("ticket granted to %s (says %s)",
 					    site_string(tk->leader),
  					    tk->leader == sender ? "they" : site_string(sender));
 			} else {
 				tk_log_info("ticket granted to %s (from CIB)",
 					    site_string(tk->leader));
 			}
 
 			set_state(tk, ST_FOLLOWER);
 			/* just make sure that we check the ticket soon */
 			set_next_state(tk, ST_FOLLOWER);
 		}
 	}
 }
 
 int setup_ticket(struct booth_config *conf)
 {
 	struct ticket_config *tk;
 	int i;
 
 	FOREACH_TICKET(conf, i, tk) {
 		reset_ticket(tk);
 
 		if (local->type == SITE) {
 			if (!pcmk_handler.load_ticket(conf, tk)) {
 				update_ticket_state(tk, NULL);
 			}
 
 			tk->update_cib = 1;
 		}
 
 		tk_log_info("broadcasting state query");
 		/* wait until all send their status (or the first
 		 * timeout) */
 		tk->start_postpone = 1;
 		ticket_broadcast(conf, tk, OP_STATUS, OP_MY_INDEX, RLT_SUCCESS, 0);
 	}
 
 	return 0;
 }
 
 int ticket_answer_list(struct booth_config *conf, int fd)
 {
 	char *data = NULL;
 	int rv;
 	struct boothc_hdr_msg hdr;
 
 	rv = list_ticket(conf, &data);
 	if (rv < 0) {
 		goto out;
 	}
 
 	init_header(&hdr.header, CL_LIST, 0, 0, RLT_SUCCESS, 0, sizeof(hdr) + strlen(data));
 	rv = send_header_plus(conf, fd, &hdr, data, strlen(data));
 
 out:
 	if (data != NULL) {
 		free(data);
 	}
 	return rv;
 }
 
 int process_client_request(struct booth_config *conf, struct client *req_client,
 			   void *buf)
 {
 	int rv, rc = 1;
 	struct ticket_config *tk;
 	int cmd;
 	struct boothc_ticket_msg omsg;
 	struct boothc_ticket_msg *msg;
 
 	msg = (struct boothc_ticket_msg *)buf;
 	cmd = ntohl(msg->header.cmd);
 	if (!check_ticket(conf, msg->ticket.id, &tk)) {
 		log_warn("client referenced unknown ticket %s", msg->ticket.id);
 		rv = RLT_INVALID_ARG;
 		goto reply_now;
 	}
 
 	/* Perform the initial check before granting
 	 * an already granted non-manual ticket */
 	if (!is_manual(tk) && cmd == CMD_GRANT && is_owned(tk)) {
 		log_warn("client wants to grant an (already granted!) ticket %s",
 			 msg->ticket.id);
 
 		rv = RLT_OVERGRANT;
 		goto reply_now;
 	}
 
 	if (cmd == CMD_REVOKE && !is_owned(tk)) {
 		log_info("client wants to revoke a free ticket %s", msg->ticket.id);
 		rv = RLT_TICKET_IDLE;
 		goto reply_now;
 	}
 
 	if (cmd == CMD_REVOKE && tk->leader != local) {
 		tk_log_info("not granted here, redirect to %s",
 			    ticket_leader_string(tk));
 		rv = RLT_REDIRECT;
 		goto reply_now;
 	}
 
 	if (cmd == CMD_REVOKE) {
 		rv = do_revoke_ticket(conf, tk);
 	} else {
 		rv = do_grant_ticket(conf, tk, ntohl(msg->header.options));
 	}
 
 	if (rv == RLT_MORE) {
 		/* client may receive further notifications, save the
 		 * request for further processing */
 		add_req(tk, req_client, msg);
 		tk_log_debug("queue request %s for client %d",
 			     state_to_string(cmd), req_client->fd);
 		rc = 0; /* we're not yet done with the message */
 	}
 
 reply_now:
 	init_ticket_msg(&omsg, CL_RESULT, 0, rv, 0, tk);
 	send_client_msg(conf, req_client->fd, &omsg);
 	return rc;
 }
 
 int notify_client(struct booth_config *conf, struct ticket_config *tk,
                   int client_fd, struct boothc_ticket_msg *msg)
 {
 	struct boothc_ticket_msg omsg;
 	void (*deadfn) (int ci);
 	int rv, rc, ci;
 	int cmd, options;
 	struct client *req_client;
 
 	cmd = ntohl(msg->header.cmd);
 	options = ntohl(msg->header.options);
 	rv = tk->outcome;
 	ci = find_client_by_fd(client_fd);
 
 	if (ci < 0) {
 		tk_log_info("client %d (request %s) left before being notified",
 			    client_fd, state_to_string(cmd));
 		return 0;
 	}
 
 	tk_log_debug("notifying client %d (request %s)",
 		     client_fd, state_to_string(cmd));
 	init_ticket_msg(&omsg, CL_RESULT, 0, rv, 0, tk);
 	rc = send_client_msg(conf, client_fd, &omsg);
 
 	if (rc == 0 && (rv == RLT_MORE ||
 			(rv == RLT_CIB_PENDING && (options & OPT_WAIT_COMMIT)))) {
 		/* more to do here, keep the request */
 		return 1;
 	} else {
 		/* we sent a definite answer or there was a write error, drop
 		 * the client */
 		if (rc) {
 			tk_log_debug("failed to notify client %d (request %s)",
 				     client_fd, state_to_string(cmd));
 		} else {
 			tk_log_debug("client %d (request %s) got final notification",
 				     client_fd, state_to_string(cmd));
 		}
 
 		req_client = clients + ci;
 		deadfn = req_client->deadfn;
 
 		if (deadfn) {
 			deadfn(ci);
 		}
 
 		return 0; /* we're done with this request */
 	}
 }
 
 int ticket_broadcast(struct booth_config *conf, struct ticket_config *tk,
 		     cmd_request_t cmd, cmd_request_t expected_reply,
 		     cmd_result_t res, cmd_reason_t reason)
 {
 	struct boothc_ticket_msg msg;
 
 	init_ticket_msg(&msg, cmd, 0, res, reason, tk);
 	tk_log_debug("broadcasting '%s' (term=%d, valid=%d)",
 		     state_to_string(cmd),
 		     ntohl(msg.ticket.term),
 		     msg_term_time(&msg));
 
 	tk->last_request = cmd;
 
 	if (expected_reply) {
 		expect_replies(tk, expected_reply);
 	}
 
 	ticket_activate_timeout(tk);
 	return transport()->broadcast_auth(conf, &msg, sendmsglen(&msg));
 }
 
 /* update the ticket on the leader, write it to the CIB, and
    send out the update message to others with the new expiry
    time
 */
 int leader_update_ticket(struct booth_config *conf, struct ticket_config *tk)
 {
 	int rv = 0, rv2;
 	timetype now;
 
 	if (tk->ticket_updated >= 2) {
 		return 0;
 	}
 
 	/* for manual tickets, we don't set time expiration */
 	if (!is_manual(tk) && tk->ticket_updated < 1) {
 		tk->ticket_updated = 1;
 		get_time(&now);
 		copy_time(&now, &tk->last_renewal);
 		set_future_time(&tk->term_expires, tk->term_duration);
 		rv = ticket_broadcast(conf, tk, OP_UPDATE, OP_ACK, RLT_SUCCESS, 0);
 	}
 
 	if (tk->ticket_updated < 2) {
 		rv2 = ticket_write(tk);
 		switch(rv2) {
 		case 0:
 			tk->ticket_updated = 2;
 			tk->outcome = RLT_SUCCESS;
 			foreach_tkt_req(conf, tk, notify_client);
 			break;
 
 		case 1:
 			if (tk->outcome != RLT_CIB_PENDING) {
 				tk->outcome = RLT_CIB_PENDING;
 				foreach_tkt_req(conf, tk, notify_client);
 			}
 			break;
 
 		default:
 			break;
 		}
 	}
 
 	return rv;
 }
 
 static void log_lost_servers(struct booth_config *conf, struct ticket_config *tk)
 {
 	struct booth_site *n;
 	int i;
 
 	if (tk->retry_number > 1) {
 		/* log those that we couldn't reach, but do
 		 * that only on the first retry
 		 */
 		return;
 	}
 
 	FOREACH_NODE(conf, i, n) {
 		if (tk->acks_received & n->bitmask) {
 			continue;
 		}
 
 		tk_log_warn("%s %s didn't acknowledge our %s, "
 			    "will retry %d times",
 			    (n->type == ARBITRATOR ? "arbitrator" : "site"),
 			    site_string(n), state_to_string(tk->last_request),
 			    tk->retries);
 	}
 }
 
 static void resend_msg(struct booth_config *conf, struct ticket_config *tk)
 {
 	struct booth_site *n;
 	int i;
 
 	if (!(tk->acks_received ^ local->bitmask)) {
 		ticket_broadcast(conf, tk, tk->last_request, 0, RLT_SUCCESS, 0);
 	} else {
 		FOREACH_NODE(conf, i, n) {
 			if (tk->acks_received & n->bitmask) {
 				continue;
 			}
 
 			n->resend_cnt++;
 			tk_log_debug("resending %s to %s",
 				     state_to_string(tk->last_request),
 				     site_string(n));
 			send_msg(conf, tk->last_request, tk, n, NULL);
 		}
 
 		ticket_activate_timeout(tk);
 	}
 }
 
 static void handle_resends(struct booth_config *conf, struct ticket_config *tk)
 {
 	int ack_cnt;
 
 	if (++tk->retry_number > tk->retries) {
 		tk_log_info("giving up on sending retries");
 		no_resends(tk);
 		set_ticket_wakeup(tk);
 		return;
 	}
 
 	/* try to reach some sites again if we just stepped down */
 	if (tk->last_request == OP_VOTE_FOR) {
 		tk_log_warn("no answers to our VtFr request to step down (try #%d), "
 			    "we are alone",
 			    tk->retry_number);
 		goto just_resend;
 	}
 
 	if (!majority_of_bits(tk, tk->acks_received)) {
 		ack_cnt = count_bits(tk->acks_received) - 1;
 
 		if (!ack_cnt) {
 			tk_log_warn("no answers to our request (try #%d), "
 				    "we are alone",
 				    tk->retry_number);
 		} else {
 			tk_log_warn("not enough answers to our request (try #%d): "
 				    "only got %d answers",
 				    tk->retry_number, ack_cnt);
 		}
 	} else {
 		log_lost_servers(conf, tk);
 	}
 
 just_resend:
 	resend_msg(conf, tk);
 }
 
 static int postpone_ticket_processing(struct ticket_config *tk)
 {
 	extern timetype start_time;
 
 	return tk->start_postpone && (-time_left(&start_time) < tk->timeout);
 }
 
 #define has_extprog_exited(tk) ((tk)->clu_test.progstate == EXTPROG_EXITED)
 
 static void process_next_state(struct booth_config *conf, struct ticket_config *tk)
 {
 	int rv;
 
 	switch(tk->next_state) {
 	case ST_LEADER:
 		if (has_extprog_exited(tk)) {
 			if (tk->state == ST_LEADER) {
 				break;
 			}
 
 			rv = acquire_ticket(conf, tk, OR_ADMIN);
 			if (rv != 0) { /* external program failed */
 				tk->outcome = rv;
 				foreach_tkt_req(conf, tk, notify_client);
 			}
 		} else {
 			log_reacquire_reason(tk);
 			acquire_ticket(conf, tk, OR_REACQUIRE);
 		}
 		break;
 
 	case ST_INIT:
 		no_resends(tk);
 		start_revoke_ticket(conf, tk);
 		tk->outcome = RLT_SUCCESS;
 		foreach_tkt_req(conf, tk, notify_client);
 		break;
 
 	/* wanting to be follower is not much of an ambition; no
 	 * processing, just return; don't reset start_postpone until
 	 * we got some replies to status */
 	case ST_FOLLOWER:
 		return;
 
 	default:
 		break;
 	}
 
 	tk->start_postpone = 0;
 }
 
 static void ticket_lost(struct ticket_config *tk)
 {
 	int reason = OR_TKT_LOST;
 
 	if (tk->leader != local) {
 		tk_log_warn("lost at %s", site_string(tk->leader));
 	} else {
 		if (is_ext_prog_running(tk)) {
 			ext_prog_timeout(tk);
 			reason = OR_LOCAL_FAIL;
 		} else {
 			tk_log_warn("lost majority (revoking locally)");
 			reason = tk->election_reason ? tk->election_reason : OR_REACQUIRE;
 		}
 	}
 
 	tk->lost_leader = tk->leader;
 	save_committed_tkt(tk);
 	mark_ticket_as_revoked_from_leader(tk);
 	reset_ticket(tk);
 	set_state(tk, ST_FOLLOWER);
 
 	if (local->type == SITE) {
 		ticket_write(tk);
 		schedule_election(tk, reason);
 	}
 }
 
 static void next_action(struct booth_config *conf, struct ticket_config *tk)
 {
 	int rv;
 
 	switch(tk->state) {
 	case ST_INIT:
 		/* init state, handle resends for ticket revoke */
 		/* and rebroadcast if stepping down */
 		/* try to acquire ticket on grant */
 		if (has_extprog_exited(tk)) {
 			rv = acquire_ticket(conf, tk, OR_ADMIN);
 			if (rv != 0) { /* external program failed */
 				tk->outcome = rv;
 				foreach_tkt_req(conf, tk, notify_client);
 			}
 		} else {
 			if (tk->acks_expected) {
 				handle_resends(conf, tk);
 			}
 		}
 		break;
 
 	case ST_FOLLOWER:
 		if (!is_manual(tk)) {
 			/* leader/ticket lost? and we didn't vote yet */
 			tk_log_debug("leader: %s, voted_for: %s",
 				     site_string(tk->leader),
 				     site_string(tk->voted_for));
 
 			if (tk->leader) {
 				break;
 			}
 
 			if (!tk->voted_for || !tk->in_election) {
 				disown_ticket(tk);
 				if (!new_election(conf, tk, NULL, 1, OR_AGAIN)) {
 					ticket_activate_timeout(tk);
 				}
 			} else {
 				/* we should restart elections in case nothing
 				* happens in the meantime */
 				tk->in_election = 0;
 				ticket_activate_timeout(tk);
 			}
 		} else {
 			/* for manual tickets, also try to acquire ticket on grant
 			 * in the Follower state (because we may end up having
 			 * two Leaders) */
 			if (has_extprog_exited(tk)) {
 				rv = acquire_ticket(conf, tk, OR_ADMIN);
 				if (rv != 0) { /* external program failed */
 					tk->outcome = rv;
 					foreach_tkt_req(conf, tk, notify_client);
 				}
 			} else {
 				/* Otherwise, just send ACKs if needed */
 				if (tk->acks_expected) {
 					handle_resends(conf, tk);
 				}
 			}
 		}
 		break;
 
 	case ST_CANDIDATE:
 		/* elections timed out? */
 		elections_end(conf, tk);
 		break;
 
 	case ST_LEADER:
 		/* timeout or ticket renewal? */
 		if (tk->acks_expected) {
 			handle_resends(conf, tk);
 			if (majority_of_bits(tk, tk->acks_received)) {
 				leader_update_ticket(conf, tk);
 			}
 		} else if (!do_ext_prog(conf, tk, 1)) {
 			/* this is ticket renewal, run local test */
 			ticket_broadcast(conf, tk, OP_HEARTBEAT, OP_ACK, RLT_SUCCESS, 0);
 			tk->ticket_updated = 0;
 		}
 
 		break;
 
 	default:
 		break;
 	}
 }
 
 static void ticket_cron(struct booth_config *conf, struct ticket_config *tk)
 {
 	/* don't process the tickets too early after start */
 	if (postpone_ticket_processing(tk)) {
 		tk_log_debug("ticket processing postponed (start_postpone=%d)",
 			     tk->start_postpone);
 		/* but run again soon */
 		ticket_activate_timeout(tk);
 		return;
 	}
 
 	/* no need for status resends, we hope we got at least one
 	 * my_index back */
 	if (tk->acks_expected == OP_MY_INDEX) {
 		no_resends(tk);
 	}
 
 	/* after startup, we need to decide what to do based on the
 	 * current ticket state; tk->next_state has a hint
 	 * also used for revokes which had to be delayed
 	 */
 	if (tk->next_state) {
 		process_next_state(conf, tk);
 		goto out;
 	}
 
 	/* Has an owner, has an expiry date, and expiry date in the past?
 	 * For automatic tickets, losing the ticket must happen
 	 * in _every_ state.
 	 */
 	if (!is_manual(tk) && is_owned(tk) && is_time_set(&tk->term_expires) &&
 	    is_past(&tk->term_expires)) {
 		ticket_lost(tk);
 		goto out;
 	}
 
 	next_action(conf, tk);
 
 out:
 	tk->next_state = 0;
 	if (!tk->in_election && tk->update_cib) {
 		ticket_write(tk);
 	}
 }
 
 void process_tickets(struct booth_config *conf)
 {
 	struct ticket_config *tk;
 	int i;
 	timetype last_cron;
 
 	FOREACH_TICKET(conf, i, tk) {
 		if (!has_extprog_exited(tk) &&
 		    is_time_set(&tk->next_cron) && !is_past(&tk->next_cron)) {
 			continue;
 		}
 
 		tk_log_debug("ticket cron");
 
 		copy_time(&tk->next_cron, &last_cron);
 		ticket_cron(conf, tk);
 
 		if (time_cmp(&last_cron, &tk->next_cron, ==)) {
 			tk_log_debug("nobody set ticket wakeup");
 			set_ticket_wakeup(tk);
 		}
 	}
 }
 
 void tickets_log_info(struct booth_config *conf)
 {
 	struct ticket_config *tk;
 	int i;
 	time_t ts;
 
 	FOREACH_TICKET(conf, i, tk) {
 		ts = wall_ts(&tk->term_expires);
 		tk_log_info("state '%s' term %d leader %s expires %-24.24s",
 			    state_to_string(tk->state),
 			    tk->current_term,
 			    ticket_leader_string(tk),
 			    ctime(&ts));
 	}
 }
 
 static void update_acks(struct ticket_config *tk, struct booth_site *sender,
 			struct booth_site *leader, struct boothc_ticket_msg *msg)
 {
 	uint32_t cmd;
 	uint32_t req;
 
 	cmd = ntohl(msg->header.cmd);
 	req = ntohl(msg->header.request);
 	if (req != tk->last_request ||
 	    (tk->acks_expected != cmd && tk->acks_expected != OP_REJECTED)) {
 		return;
 	}
 
 	/* got an ack! */
 	tk->acks_received |= sender->bitmask;
 
 	if (all_replied(tk) ||
 	    /* we just stepped down, need only one site to start elections */
 	    (cmd == OP_REQ_VOTE && tk->last_request == OP_VOTE_FOR)) {
 		no_resends(tk);
 		tk->start_postpone = 0;
 		set_ticket_wakeup(tk);
 	}
 }
 
 /* read ticket message */
 int ticket_recv(struct booth_config *conf, void *buf, struct booth_site *source)
 {
 	struct boothc_ticket_msg *msg;
 	struct ticket_config *tk;
 	struct booth_site *leader;
 	uint32_t leader_u;
 
 	msg = (struct boothc_ticket_msg *)buf;
 
 	if (!check_ticket(conf, msg->ticket.id, &tk)) {
 		log_warn("got invalid ticket name %s from %s",
 			 msg->ticket.id, site_string(source));
 		source->invalid_cnt++;
 		return -EINVAL;
 	}
 
 
 	leader_u = ntohl(msg->ticket.leader);
 	if (!find_site_by_id(conf, leader_u, &leader)) {
 		tk_log_error("message with unknown leader %u received", leader_u);
 		source->invalid_cnt++;
 		return -EINVAL;
 	}
 
 	update_acks(tk, source, leader, msg);
 	return raft_answer(conf, tk, source, leader, msg);
 }
 
 static void log_next_wakeup(struct ticket_config *tk)
 {
 	int left;
 
 	left = time_left(&tk->next_cron);
 	tk_log_debug("set ticket wakeup in " intfmt(left));
 }
 
 /* New vote round; ยง5.2 */
 /* delay the next election start for some random time
  * (up to 1 second)
  */
 void add_random_delay(struct ticket_config *tk)
 {
 	timetype tv;
 
 	interval_add(&tk->next_cron, rand_time(min(1000, tk->timeout)), &tv);
 	ticket_next_cron_at(tk, &tv);
 
 	if (ANYDEBUG) {
 		log_next_wakeup(tk);
 	}
 }
 
 void set_ticket_wakeup(struct ticket_config *tk)
 {
 	timetype near_future, tv, next_vote;
 
 	set_future_time(&near_future, 10);
 
 	if (!is_manual(tk)) {
 		/* At least every hour, perhaps sooner (default) */
 		tk_log_debug("ticket will be woken up after up to one hour");
 		ticket_next_cron_in(tk, 3600*TIME_RES);
 
 		switch (tk->state) {
 		case ST_LEADER:
 			assert(tk->leader == local);
 
 			get_next_election_time(tk, &next_vote);
 
 			/* If timestamp is in the past, wakeup in
 			* near future */
 			if (!is_time_set(&next_vote)) {
 				tk_log_debug("next ts unset, wakeup soon");
 				ticket_next_cron_at(tk, &near_future);
 			} else if (is_past(&next_vote)) {
 				int tdiff = time_left(&next_vote);
 				tk_log_debug("next ts in the past " intfmt(tdiff));
 				ticket_next_cron_at(tk, &near_future);
 			} else {
 				ticket_next_cron_at(tk, &next_vote);
 			}
 			break;
 
 		case ST_CANDIDATE:
 			assert(is_time_set(&tk->election_end));
 			ticket_next_cron_at(tk, &tk->election_end);
 			break;
 
 		case ST_INIT:
 		case ST_FOLLOWER:
 			/* If there is (or should be) some owner, check on it later on.
 			* If no one is interested - don't care. */
 			if (is_owned(tk)) {
 				interval_add(&tk->term_expires, tk->acquire_after, &tv);
 				ticket_next_cron_at(tk, &tv);
 			}
 
 			break;
 
 		default:
 			tk_log_error("unknown ticket state: %d", tk->state);
 		}
 
 		if (tk->next_state) {
 			/* we need to do something soon here */
 			if (!tk->acks_expected) {
 				ticket_next_cron_at(tk, &near_future);
 			} else {
 				ticket_activate_timeout(tk);
 			}
 		}
 	} else {
 		/* At least six minutes, to make sure that multi-leader situations
 		 * will be solved promptly.
 		 */
 		tk_log_debug("manual ticket will be woken up after up to six minutes");
 		ticket_next_cron_in(tk, 60 * TIME_RES);
 
 		/* For manual tickets, no earlier timeout could be set in a similar
 		 * way as it is done in a switch above for automatic tickets.
 		 * The reason is that term's timeout is INF and no Raft-based elections
 		 * are performed.
 		 */
 	}
 
 	if (ANYDEBUG) {
 		log_next_wakeup(tk);
 	}
 }
 
 void schedule_election(struct ticket_config *tk, cmd_reason_t reason)
 {
 	if (local->type != SITE) {
 		return;
 	}
 
 	tk->election_reason = reason;
 	get_time(&tk->next_cron);
 	/* introduce a short delay before starting election */
 	add_random_delay(tk);
 }
 
 int is_manual(struct ticket_config *tk)
 {
 	return (tk->mode == TICKET_MODE_MANUAL) ? 1 : 0;
 }
 
 /* Given a state (in host byte order), return a human-readable (char*).
  * An array is used so that multiple states can be printed in a single printf(). */
 char *state_to_string(uint32_t state_ho)
 {
 	union mu { cmd_request_t s; char c[5]; };
 	static union mu cache[6] = { { 0 } }, *cur;
 	static int current = 0;
 
 	current ++;
 	if (current >= sizeof(cache)/sizeof(cache[0])) {
 		current = 0;
 	}
 
 	cur = cache + current;
 
 	cur->s = htonl(state_ho);
 	/* Shouldn't be necessary, union array is initialized with zeroes, and
 	 * these bytes never get written. */
 	cur->c[4] = 0;
 	return cur->c;
 }
 
 int send_reject(struct booth_config *conf, struct booth_site *dest,
 		struct ticket_config *tk, cmd_result_t code,
 		struct boothc_ticket_msg *in_msg)
 {
 	int req = ntohl(in_msg->header.cmd);
 	struct boothc_ticket_msg msg;
 
 	tk_log_debug("sending reject to %s", site_string(dest));
 	init_ticket_msg(&msg, OP_REJECTED, req, code, 0, tk);
 	return booth_udp_send_auth(conf, dest, &msg, sendmsglen(&msg));
 }
 
 int send_msg(struct booth_config *conf, int cmd, struct ticket_config *tk,
 	     struct booth_site *dest, struct boothc_ticket_msg *in_msg)
 {
 	int req = 0;
 	struct ticket_config *valid_tk = tk;
 	struct boothc_ticket_msg msg;
 
 	/* if we want to send the last valid ticket, then if we're in
 	 * the ST_CANDIDATE state, the last valid ticket is in
 	 * tk->last_valid_tk
 	 */
 	if (cmd == OP_MY_INDEX) {
 		if (tk->state == ST_CANDIDATE && tk->last_valid_tk) {
 			valid_tk = tk->last_valid_tk;
 		}
 
 		tk_log_info("sending status to %s", site_string(dest));
 	}
 
 	if (in_msg) {
 		req = ntohl(in_msg->header.cmd);
 	}
 
 	init_ticket_msg(&msg, cmd, req, RLT_SUCCESS, 0, valid_tk);
 	return booth_udp_send_auth(conf, dest, &msg, sendmsglen(&msg));
 }