Page Menu
Home
ClusterLabs Projects
Search
Configure Global Search
Log In
Files
F3155677
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
126 KB
Referenced Files
None
Subscribers
None
View Options
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 34df335..8b01cf9 100644
--- a/src/attr.c
+++ b/src/attr.c
@@ -1,476 +1,476 @@
/*
* 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(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(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 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(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 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(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(tk, req_client->fd, msg);
if (rv)
goto reply_now;
return 1;
case ATTR_GET:
rv = attr_get(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(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 7d93296..3d11b5f 100644
--- a/src/main.c
+++ b/src/main.c
@@ -1,1698 +1,1705 @@
/*
* 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(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);
(void)send_header_plus(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(site, request, msg_size);
if (rv < 0)
goto out_close;
rv = tpt->recv_auth(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(site, &cl.msg, sendmsglen(&cl.msg));
if (rv < 0)
goto out_close;
read_more:
rv = tpt->recv_auth(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 c9147d8..fb5eb30 100644
--- a/src/ticket.c
+++ b/src/ticket.c
@@ -1,1440 +1,1440 @@
/*
* 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 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(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 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(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(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 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(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(tk, local, 1, reason);
} else {
rv = new_election(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 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(tk, OR_ADMIN);
if (rv) {
time_reset(&tk->delay_commit);
return rv;
} else {
return RLT_MORE;
}
}
static void start_revoke_ticket(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(tk, OP_REVOKE, OP_ACK, RLT_SUCCESS, OR_ADMIN);
}
/** Ticket revoke.
* Only to be started from the leader. */
static int do_revoke_ticket(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(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(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(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(tk);
} else {
rv = do_grant_ticket(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(req_client->fd, &omsg);
return rc;
}
int notify_client(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(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 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(&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 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(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(tk, notify_client);
break;
case 1:
if (tk->outcome != RLT_CIB_PENDING) {
tk->outcome = RLT_CIB_PENDING;
foreach_tkt_req(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(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(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 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(tk, OR_ADMIN);
if (rv != 0) { /* external program failed */
tk->outcome = rv;
foreach_tkt_req(tk, notify_client);
}
} else {
log_reacquire_reason(tk);
acquire_ticket(tk, OR_REACQUIRE);
}
break;
case ST_INIT:
no_resends(tk);
start_revoke_ticket(tk);
tk->outcome = RLT_SUCCESS;
foreach_tkt_req(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(tk, OR_ADMIN);
if (rv != 0) { /* external program failed */
tk->outcome = rv;
foreach_tkt_req(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(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(tk, OR_ADMIN);
if (rv != 0) { /* external program failed */
tk->outcome = rv;
foreach_tkt_req(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(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(tk);
}
} else if (!do_ext_prog(tk, 1)) {
/* this is ticket renewal, run local test */
ticket_broadcast(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(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(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_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(dest, &msg, sendmsglen(&msg));
}
int send_msg(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(dest, &msg, sendmsglen(&msg));
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Thu, Feb 27, 12:26 AM (14 h, 31 m ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1465943
Default Alt Text
(126 KB)
Attached To
Mode
rB Booth
Attached
Detach File
Event Timeline
Log In to Comment